package aurp import ( "encoding/binary" "fmt" "io" ) // Subcode is used to distinguish types of zone request/response. type Subcode uint16 // Various subcodes. const ( SubcodeZoneInfoReq Subcode = 0x0001 SubcodeZoneInfoNonExt Subcode = 0x0001 SubcodeZoneInfoExt Subcode = 0x0002 SubcodeGetZonesNet Subcode = 0x0003 SubcodeGetDomainZoneList Subcode = 0x0004 ) func parseSubcode(p []byte) (Subcode, []byte, error) { if len(p) < 2 { return 0, p, fmt.Errorf("insufficient input length %d for subcode", len(p)) } return Subcode(binary.BigEndian.Uint16(p[:2])), p[2:], nil } type ZIReqPacket struct { Header Subcode Networks []uint16 } func (p *ZIReqPacket) WriteTo(w io.Writer) (int64, error) { a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.Subcode)) for _, n := range p.Networks { a.write16(n) } return a.ret() } func parseZIReqPacket(p []byte) (*ZIReqPacket, error) { if len(p)%2 != 0 { return nil, fmt.Errorf("odd number of bytes %d for networks", len(p)) } c := len(p) / 2 ns := make([]uint16, 0, c) for i := range c { ns[i] = binary.BigEndian.Uint16(p[i*2:][:2]) } return &ZIReqPacket{ Subcode: SubcodeZoneInfoReq, Networks: ns, }, nil } // ZIRspPacket represents a ZI-Rsp: a response packet to a ZI-Req. // // "When the data sender receives a ZI-Req and the zone list for the network or // networks for which that ZI-Req requested zone information fits in one ZI-Rsp // packet, it sends a nonextended ZI-Rsp." // "When the data sender receives a ZI-Req and the zone list for a network about // which that ZI-Req requested zone information does not fit in a single ZI-Rsp // packet, it sends a sequence of extended ZI-Rsp packets." // "All tuples in a single extended ZI-Rsp packet must contain the same network // number" // "Duplicate zone names never exist in extended ZI-Rsp packets" type ZIRspPacket struct { Header Subcode Zones ZoneTuples } func (p *ZIRspPacket) WriteTo(w io.Writer) (int64, error) { a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.Subcode)) a.writeTo(p.Zones) return a.ret() } func parseZIRspPacket(p []byte) (*ZIRspPacket, error) { zs, err := parseZoneTuples(p) if err != nil { return nil, err } return &ZIRspPacket{ // Subcode needs to be provided by layer above Zones: zs, }, nil } type GDZLReqPacket struct { Header Subcode StartIndex uint16 } func (p *GDZLReqPacket) WriteTo(w io.Writer) (int64, error) { a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.Subcode)) a.write16(p.StartIndex) return a.ret() } func parseGDZLReqPacket(p []byte) (*GDZLReqPacket, error) { if len(p) < 2 { return nil, fmt.Errorf("insufficient input length %d for GDZL-Req packet", len(p)) } return &GDZLReqPacket{ Subcode: SubcodeGetDomainZoneList, StartIndex: binary.BigEndian.Uint16(p[:2]), }, nil } type GDZLRspPacket struct { Header Subcode StartIndex int16 ZoneNames []string } func (p *GDZLRspPacket) WriteTo(w io.Writer) (int64, error) { for _, zn := range p.ZoneNames { if len(zn) > 127 { return 0, fmt.Errorf("zone name %q too long", zn) } } a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.Subcode)) a.write16(uint16(p.StartIndex)) if p.StartIndex == -1 { return a.ret() } for _, zn := range p.ZoneNames { // The spec is not clear what format these take, and Apple's example // implementation always returns -1 (not supported), and I have no // packet captures of this subcode. // I'm guessing they're Pascal-style (length-prefixed) since that's used // in the ZI-Rsp long tuples as well as throughout all the ancient Mac // code. a.write8(uint8(len(zn))) a.write([]byte(zn)) } return a.ret() } func parseGDZLRspPacket(p []byte) (*GDZLRspPacket, error) { if len(p) < 2 { return nil, fmt.Errorf("insufficient input length %d for GDZL-Rsp packet", len(p)) } gdzl := &GDZLRspPacket{ Subcode: SubcodeGetDomainZoneList, StartIndex: int16(binary.BigEndian.Uint16(p[:2])), } if gdzl.StartIndex == -1 { return gdzl, nil } // See comment in GDZLRspPacket.WriteTo about the assumption here. p = p[2:] for len(p) > 0 { strLen := p[0] p = p[1:] if len(p) < int(strLen) { return nil, fmt.Errorf("insufficient remaining input length %d for zone name with length prefix %d", len(p), strLen) } gdzl.ZoneNames = append(gdzl.ZoneNames, string(p[:strLen])) p = p[strLen:] } return gdzl, nil } type GZNReqPacket struct { Header Subcode ZoneName string } func (p *GZNReqPacket) WriteTo(w io.Writer) (int64, error) { if len(p.ZoneName) > 127 { return 0, fmt.Errorf("zone name %q too long", p.ZoneName) } a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.Subcode)) a.write8(uint8(len(p.ZoneName))) a.write([]byte(p.ZoneName)) return a.ret() } func parseGZNReqPacket(p []byte) (*GZNReqPacket, error) { if len(p) < 1 { return nil, fmt.Errorf("insufficient input length %d for GZN-Req packet", len(p)) } strLen := p[0] p = p[1:] if len(p) < int(strLen) { return nil, fmt.Errorf("insufficient remaining input length %d for zone name with length prefix %d", len(p), strLen) } return &GZNReqPacket{ Subcode: SubcodeGetZonesNet, ZoneName: string(p[:strLen]), }, nil } type GZNRspPacket struct { Header Subcode ZoneName string NotSupported bool Networks NetworkTuples } func (p *GZNRspPacket) WriteTo(w io.Writer) (int64, error) { if len(p.ZoneName) > 127 { return 0, fmt.Errorf("zone name %q too long", p.ZoneName) } a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.Subcode)) a.write8(uint8(len(p.ZoneName))) if p.NotSupported { a.write16(0xffff) // -1 return a.ret() } a.writeTo(p.Networks) return a.ret() } func parseGZNRspPacket(p []byte) (*GZNRspPacket, error) { if len(p) < 1 { return nil, fmt.Errorf("insufficient input length %d for GZN-Rsp packet", len(p)) } gzn := &GZNRspPacket{ Subcode: SubcodeGetZonesNet, } strLen := p[0] p = p[1:] if len(p) < int(strLen) { return nil, fmt.Errorf("insufficient remaining input length %d for zone name with length prefix %d", len(p), strLen) } gzn.ZoneName = string(p[:strLen]) p = p[strLen:] if len(p) < 2 { return nil, fmt.Errorf("insufficient remaining input length %d for GZN-Rsp packet", len(p)) } gzn.NotSupported = p[0] == 0xff && p[1] == 0xff if gzn.NotSupported { return gzn, nil } ns, err := parseNetworkTuples(p) if err != nil { return nil, err } gzn.Networks = ns return gzn, nil } type ZoneTuples []ZoneTuple type ZoneTuple struct { Network uint16 Name string } func (zs ZoneTuples) WriteTo(w io.Writer) (int64, error) { if len(zs) > 65535 { return 0, fmt.Errorf("too many zone tuples [%d > 65535]", len(zs)) } for _, zt := range zs { if len(zt.Name) > 127 { return 0, fmt.Errorf("zone name %q too long", zt.Name) } } a := acc(w) a.write16(uint16(len(zs))) offsets := make(map[string]uint16) for _, zt := range zs { a.write16(zt.Network) if offset, wrote := offsets[zt.Name]; wrote { // Optimised tuple a.write16(0x8000 | offset) continue } // Long tuple offsets[zt.Name] = uint16(a.n - 4) // 4 = sizeof(zone count) + sizeof(first network number) a.write8(uint8(len(zt.Name))) a.write([]byte(zt.Name)) } return a.ret() } func parseZoneTuples(p []byte) (ZoneTuples, error) { if len(p) < 2 { return nil, fmt.Errorf("insufficient input length %d for zone tuples", len(p)) } count := binary.BigEndian.Uint16(p[:2]) p = p[2:] if len(p) < int(3*count) { return nil, fmt.Errorf("insufficient remaining input length %d for %d zone tuples", len(p), count) } zs := make(ZoneTuples, 0, count) var fromFirst []byte for range count { if len(p) < 3 { return nil, fmt.Errorf("insufficient remaining input length %d for another zone tuple", len(p)) } var zt ZoneTuple zt.Network = binary.BigEndian.Uint16(p[:2]) p = p[2:] if nameLen := p[0]; nameLen&0x80 == 0 { // Long tuple if fromFirst == nil { fromFirst = p } p = p[1:] if len(p) < int(nameLen) { return nil, fmt.Errorf("insufficient remaining input length %d for zone name of length %d", len(p), nameLen) } zt.Name = string(p[:nameLen]) p = p[nameLen:] } else { // Optimised tuple if len(p) < 2 { return nil, fmt.Errorf("insufficient remaining input length %d for offset", len(p)) } offset := binary.BigEndian.Uint16(p[:2]) offset &^= 0x8000 p = p[2:] if int(offset) >= len(fromFirst) { return nil, fmt.Errorf("optimized zone tuple offset %d out of range", offset) } nameLen := fromFirst[offset] if len(fromFirst) < int(nameLen) { return nil, fmt.Errorf("insufficient remaining input length %d for zone name of length %d", len(p), nameLen) } zt.Name = string(fromFirst[offset+1:][:nameLen]) } zs = append(zs, zt) } return zs, nil }