From 56d69309ea9f67bf59d255b5b9b1de347b0f6553 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 17 Mar 2024 21:08:18 +1100 Subject: [PATCH] Packet formats complete --- aurp/aurp.go | 45 +++++++--- aurp/zone_info.go | 219 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 235 insertions(+), 29 deletions(-) diff --git a/aurp/aurp.go b/aurp/aurp.go index 1d6022e..c6b5078 100644 --- a/aurp/aurp.go +++ b/aurp/aurp.go @@ -4,7 +4,6 @@ package aurp import ( "encoding/binary" - "errors" "fmt" "io" ) @@ -214,7 +213,7 @@ func ParsePacket(p []byte) (Packet, error) { return nil, err } switch sc { - case CmdSubcodeZoneInfoReq: + case SubcodeZoneInfoReq: zir, err := parseZIReqPacket(p) if err != nil { return nil, err @@ -222,11 +221,21 @@ func ParsePacket(p []byte) (Packet, error) { zir.Header = h return zir, nil - case CmdSubcodeGetDomainZoneList: - // TODO + case SubcodeGetDomainZoneList: + gdzl, err := parseGDZLReqPacket(p) + if err != nil { + return nil, err + } + gdzl.Header = h + return gdzl, nil - case CmdSubcodeGetZonesNet: - // TODO + case SubcodeGetZonesNet: + gzn, err := parseGZNReqPacket(p) + if err != nil { + return nil, err + } + gzn.Header = h + return gzn, nil default: return nil, fmt.Errorf("unknown subcode %d", sc) @@ -238,20 +247,30 @@ func ParsePacket(p []byte) (Packet, error) { return nil, err } switch sc { - case CmdSubcodeZoneInfoNonExt, CmdSubcodeZoneInfoExt: + case SubcodeZoneInfoNonExt, SubcodeZoneInfoExt: zir, err := parseZIRspPacket(p) if err != nil { return nil, err } zir.Header = h - zir.Subcode = sc + zir.Subcode = sc // 1 or 2, only known at this layer return zir, nil - case CmdSubcodeGetDomainZoneList: - // TODO + case SubcodeGetDomainZoneList: + gdzl, err := parseGDZLRspPacket(p) + if err != nil { + return nil, err + } + gdzl.Header = h + return gdzl, nil - case CmdSubcodeGetZonesNet: - // TODO + case SubcodeGetZonesNet: + gzn, err := parseGZNRspPacket(p) + if err != nil { + return nil, err + } + gzn.Header = h + return gzn, nil default: return nil, fmt.Errorf("unknown subcode %d", sc) @@ -274,6 +293,4 @@ func ParsePacket(p []byte) (Packet, error) { default: return nil, fmt.Errorf("unsupported domain header packet type %d", dh.PacketType) } - - return nil, errors.New("unimplemented packet handling") } diff --git a/aurp/zone_info.go b/aurp/zone_info.go index 231a6a9..ea98a8e 100644 --- a/aurp/zone_info.go +++ b/aurp/zone_info.go @@ -6,29 +6,28 @@ import ( "io" ) -// CmdSubcode is used to distinguish types of zone request/response. -type CmdSubcode uint16 +// Subcode is used to distinguish types of zone request/response. +type Subcode uint16 // Various subcodes. const ( - CmdSubcodeZoneInfoReq CmdSubcode = 0x0001 - CmdSubcodeZoneInfoNonExt CmdSubcode = 0x0001 - CmdSubcodeZoneInfoExt CmdSubcode = 0x0002 - CmdSubcodeGetZonesNet CmdSubcode = 0x0003 - CmdSubcodeGetDomainZoneList CmdSubcode = 0x0004 + SubcodeZoneInfoReq Subcode = 0x0001 + SubcodeZoneInfoNonExt Subcode = 0x0001 + SubcodeZoneInfoExt Subcode = 0x0002 + SubcodeGetZonesNet Subcode = 0x0003 + SubcodeGetDomainZoneList Subcode = 0x0004 ) -func parseSubcode(p []byte) (CmdSubcode, []byte, error) { +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 CmdSubcode(binary.BigEndian.Uint16(p[:2])), p[2:], nil + return Subcode(binary.BigEndian.Uint16(p[:2])), p[2:], nil } type ZIReqPacket struct { Header - - Subcode CmdSubcode + Subcode Networks []uint16 } @@ -36,7 +35,7 @@ func (p *ZIReqPacket) WriteTo(w io.Writer) (int64, error) { p.Sequence = 0 p.CommandCode = CmdCodeZoneReq p.Flags = 0 - p.Subcode = CmdSubcodeZoneInfoReq + p.Subcode = SubcodeZoneInfoReq a := acc(w) a.writeTo(&p.Header) @@ -57,7 +56,7 @@ func parseZIReqPacket(p []byte) (*ZIReqPacket, error) { ns[i] = binary.BigEndian.Uint16(p[i*2:][:2]) } return &ZIReqPacket{ - Subcode: CmdSubcodeZoneInfoReq, + Subcode: SubcodeZoneInfoReq, Networks: ns, }, nil } @@ -75,8 +74,8 @@ func parseZIReqPacket(p []byte) (*ZIReqPacket, error) { // "Duplicate zone names never exist in extended ZI-Rsp packets" type ZIRspPacket struct { Header - Subcode CmdSubcode - Zones ZoneTuples + Subcode + Zones ZoneTuples } func (p *ZIRspPacket) WriteTo(w io.Writer) (int64, error) { @@ -103,6 +102,196 @@ func parseZIRspPacket(p []byte) (*ZIRspPacket, error) { }, nil } +type GDZLReqPacket struct { + Header + Subcode + StartIndex uint16 +} + +func (p *GDZLReqPacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeZoneReq + p.Flags = 0 + p.Subcode = SubcodeGetDomainZoneList + + 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) + } + } + + p.Sequence = 0 + p.CommandCode = CmdCodeZoneRsp + // Flags is used to distinguish the final response packet, so leave alone + p.Subcode = SubcodeGetDomainZoneList + + 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) + } + + p.Sequence = 0 + p.CommandCode = CmdCodeZoneReq + p.Flags = 0 + p.Subcode = SubcodeGetZonesNet + + 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 {