diff --git a/aurp/aurp.go b/aurp/aurp.go index 3ecca05..1d6022e 100644 --- a/aurp/aurp.go +++ b/aurp/aurp.go @@ -4,6 +4,7 @@ package aurp import ( "encoding/binary" + "errors" "fmt" "io" ) @@ -82,17 +83,6 @@ const ( CmdCodeTickleAck CmdCode = 0x000f ) -// CmdSubcode is used to distinguish types of zone request/response. -type CmdSubcode uint16 - -// Various subcodes. -const ( - CmdSubcodeZoneInfo1 CmdSubcode = 0x0001 - CmdSubcodeZoneInfo2 CmdSubcode = 0x0002 // only for responses - CmdSubcodeGetZonesNet CmdSubcode = 0x0003 - CmdSubcodeGetDomainZoneList CmdSubcode = 0x0004 -) - // RoutingFlag is used in the flags field type RoutingFlag uint16 @@ -218,6 +208,65 @@ func ParsePacket(p []byte) (Packet, error) { rd.Header = h return rd, nil + case CmdCodeZoneReq: + sc, p, err := parseSubcode(p) + if err != nil { + return nil, err + } + switch sc { + case CmdSubcodeZoneInfoReq: + zir, err := parseZIReqPacket(p) + if err != nil { + return nil, err + } + zir.Header = h + return zir, nil + + case CmdSubcodeGetDomainZoneList: + // TODO + + case CmdSubcodeGetZonesNet: + // TODO + + default: + return nil, fmt.Errorf("unknown subcode %d", sc) + } + + case CmdCodeZoneRsp: + sc, p, err := parseSubcode(p) + if err != nil { + return nil, err + } + switch sc { + case CmdSubcodeZoneInfoNonExt, CmdSubcodeZoneInfoExt: + zir, err := parseZIRspPacket(p) + if err != nil { + return nil, err + } + zir.Header = h + zir.Subcode = sc + return zir, nil + + case CmdSubcodeGetDomainZoneList: + // TODO + + case CmdSubcodeGetZonesNet: + // TODO + + default: + return nil, fmt.Errorf("unknown subcode %d", sc) + } + + case CmdCodeTickle: + return &TicklePacket{ + Header: h, + }, nil + + case CmdCodeTickleAck: + return &TickleAckPacket{ + Header: h, + }, nil + default: return nil, fmt.Errorf("unknown routing packet command code %d", h.CommandCode) } @@ -225,4 +274,6 @@ 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/errors.go b/aurp/errors.go new file mode 100644 index 0000000..0abda7e --- /dev/null +++ b/aurp/errors.go @@ -0,0 +1,14 @@ +package aurp + +type ErrorCode int16 + +// Various error codes. +const ( + ErrCodeNormalClose ErrorCode = -1 + ErrCodeRoutingLoop ErrorCode = -2 + ErrCodeOutOfSync ErrorCode = -3 + ErrCodeOptionNegotiation ErrorCode = -4 + ErrCodeInvalidVersion ErrorCode = -5 + ErrCodeInsufficientResources ErrorCode = -6 + ErrCodeAuthentication ErrorCode = -7 +) diff --git a/aurp/open.go b/aurp/open.go index 20e0c3f..1e1b052 100644 --- a/aurp/open.go +++ b/aurp/open.go @@ -15,6 +15,9 @@ type OpenReqPacket struct { } func (p *OpenReqPacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeOpenReq + a := acc(w) a.writeTo(&p.Header) a.write16(p.Version) @@ -45,6 +48,9 @@ type OpenRspPacket struct { } func (p *OpenRspPacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeOpenRsp + a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.RateOrErrCode)) diff --git a/aurp/router_down.go b/aurp/router_down.go index 6cf5fd2..a98b82a 100644 --- a/aurp/router_down.go +++ b/aurp/router_down.go @@ -9,10 +9,12 @@ import ( type RDPacket struct { Header - ErrorCode int16 + ErrorCode ErrorCode } func (p *RDPacket) WriteTo(w io.Writer) (int64, error) { + p.CommandCode = CmdCodeRD + a := acc(w) a.writeTo(&p.Header) a.write16(uint16(p.ErrorCode)) @@ -24,6 +26,6 @@ func parseRD(p []byte) (*RDPacket, error) { return nil, fmt.Errorf("insufficient input length %d for router down packet", len(p)) } return &RDPacket{ - ErrorCode: int16(binary.BigEndian.Uint16(p[:2])), + ErrorCode: ErrorCode(binary.BigEndian.Uint16(p[:2])), }, nil } diff --git a/aurp/route_info.go b/aurp/routing_info.go similarity index 92% rename from aurp/route_info.go rename to aurp/routing_info.go index 0c9793e..5b71a64 100644 --- a/aurp/route_info.go +++ b/aurp/routing_info.go @@ -10,6 +10,12 @@ type RIReqPacket struct { Header } +func (p *RIReqPacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeRIReq + return p.Header.WriteTo(w) +} + type RIRspPacket struct { Header @@ -17,6 +23,8 @@ type RIRspPacket struct { } func (p *RIRspPacket) WriteTo(w io.Writer) (int64, error) { + p.CommandCode = CmdCodeRIRsp + a := acc(w) a.writeTo(&p.Header) a.writeTo(p.Networks) @@ -37,6 +45,11 @@ type RIAckPacket struct { Header } +func (p *RIAckPacket) WriteTo(w io.Writer) (int64, error) { + p.CommandCode = CmdCodeRIAck + return p.Header.WriteTo(w) +} + type RIUpdPacket struct { Header @@ -44,6 +57,8 @@ type RIUpdPacket struct { } func (p *RIUpdPacket) WriteTo(w io.Writer) (int64, error) { + p.CommandCode = CmdCodeRIUpd + a := acc(w) a.writeTo(&p.Header) a.writeTo(p.Events) diff --git a/aurp/tickle.go b/aurp/tickle.go new file mode 100644 index 0000000..ef3b88b --- /dev/null +++ b/aurp/tickle.go @@ -0,0 +1,25 @@ +package aurp + +import "io" + +type TicklePacket struct { + Header +} + +func (p *TicklePacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeTickle + p.Flags = 0 + return p.Header.WriteTo(w) +} + +type TickleAckPacket struct { + Header +} + +func (p *TickleAckPacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeTickleAck + p.Flags = 0 + return p.Header.WriteTo(w) +} diff --git a/aurp/zone_info.go b/aurp/zone_info.go new file mode 100644 index 0000000..231a6a9 --- /dev/null +++ b/aurp/zone_info.go @@ -0,0 +1,196 @@ +package aurp + +import ( + "encoding/binary" + "fmt" + "io" +) + +// CmdSubcode is used to distinguish types of zone request/response. +type CmdSubcode uint16 + +// Various subcodes. +const ( + CmdSubcodeZoneInfoReq CmdSubcode = 0x0001 + CmdSubcodeZoneInfoNonExt CmdSubcode = 0x0001 + CmdSubcodeZoneInfoExt CmdSubcode = 0x0002 + CmdSubcodeGetZonesNet CmdSubcode = 0x0003 + CmdSubcodeGetDomainZoneList CmdSubcode = 0x0004 +) + +func parseSubcode(p []byte) (CmdSubcode, []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 +} + +type ZIReqPacket struct { + Header + + Subcode CmdSubcode + Networks []uint16 +} + +func (p *ZIReqPacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeZoneReq + p.Flags = 0 + p.Subcode = CmdSubcodeZoneInfoReq + + 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: CmdSubcodeZoneInfoReq, + 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 CmdSubcode + Zones ZoneTuples +} + +func (p *ZIRspPacket) WriteTo(w io.Writer) (int64, error) { + p.Sequence = 0 + p.CommandCode = CmdCodeZoneRsp + p.Flags = 0 + // Subcode can vary for this packet type: it's either 1 or 2 + + 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 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 +}