diff --git a/aurp/aurp.go b/aurp/aurp.go index 91aedf1..7947eae 100644 --- a/aurp/aurp.go +++ b/aurp/aurp.go @@ -6,138 +6,8 @@ import ( "encoding/binary" "fmt" "io" - "net" ) -// DomainHeader represents the header used to encapsulate both AppleTalk data -// packets and AURP packets within UDP. -type DomainHeader struct { - DestinationDI DomainIdentifier - SourceDI DomainIdentifier - Version uint16 // Should always be 0x0001 - Reserved uint16 - PacketType PacketType // 2 = AppleTalk data packet, 3 = AURP packet -} - -// PacketType is used to distinguish domain-header encapsulated packets. -type PacketType uint16 - -// Various packet types. -const ( - PacketTypeAppleTalk PacketType = 0x0002 - PacketTypeRouting PacketType = 0x0003 -) - -// WriteTo writes the encoded form of the domain header to w. -func (dh *DomainHeader) WriteTo(w io.Writer) (int64, error) { - a := acc(w) - a.writeTo(dh.DestinationDI) - a.writeTo(dh.SourceDI) - a.write16(dh.Version) - a.write16(dh.Reserved) - a.write16(uint16(dh.PacketType)) - return a.ret() -} - -// parseDomainHeader parses a domain header, returning the DH and the remainder -// of the input slice. It does not validate the version or packet type fields. -func parseDomainHeader(b []byte) (*DomainHeader, []byte, error) { - ddi, b, err := parseDomainIdentifier(b) - if err != nil { - return nil, b, err - } - sdi, b, err := parseDomainIdentifier(b) - if err != nil { - return nil, b, err - } - if len(b) < 6 { // sizeof(version + reserved + packettype) - return nil, b, fmt.Errorf("insufficient remaining input length %d < 6", len(b)) - } - return &DomainHeader{ - DestinationDI: ddi, - SourceDI: sdi, - Version: binary.BigEndian.Uint16(b[:2]), - Reserved: binary.BigEndian.Uint16(b[2:4]), - PacketType: PacketType(binary.BigEndian.Uint16(b[4:6])), - }, b[6:], nil -} - -// DomainIdentifier is the byte representation of a domain identifier. -type DomainIdentifier interface { - io.WriterTo -} - -// NullDomainIdentifier represents a null domain identifier. -type NullDomainIdentifier struct{} - -// WriteTo writes the encoded form of the domain identifier to w. -func (NullDomainIdentifier) WriteTo(w io.Writer) (int64, error) { - n, err := w.Write([]byte{0x01, 0x00}) - return int64(n), err -} - -// IPDomainIdentifier represents an IP address in a domain identifier. -type IPDomainIdentifier net.IP - -// WriteTo writes the encoded form of the domain identifier to w. -func (i IPDomainIdentifier) WriteTo(w io.Writer) (int64, error) { - v4 := net.IP(i).To4() - if v4 == nil { - return 0, fmt.Errorf("need v4 IP address, got %v", i) - } - - a := acc(w) - a.write([]byte{ - 0x07, // byte 1: length of the DI, in bytes - 0x01, // byte 2: authority: 1 = IP address - 0x00, 0x00, // bytes 3, 4: distinguisher: reserved) - }) - a.write(v4) // bytes 5-8: IP address - return a.ret() -} - -// Authority represents the different possible authorities ("types") for domain -// identifiers. -type Authority byte - -// Various authorities. -const ( - // AuthorityNull is for null domain identifiers, suitable only when there is - // no need to distinguish the domains connected to a tunnel. - AuthorityNull Authority = iota - - // AuthorityIP is for - AuthorityIP -) - -// parseDomainIdentifier parses a DI from the front of b, and returns the DI and -// the remainder of the input slice or an error. -func parseDomainIdentifier(b []byte) (DomainIdentifier, []byte, error) { - if len(b) < 2 { - return nil, b, fmt.Errorf("insufficient input length %d for domain identifier", len(b)) - } - // Now we know there is a length byte and authority byte, see if there is - // that much more data - lf := int(b[0]) - if len(b) < 1+lf { - return nil, b, fmt.Errorf("input length %d < 1+specified length %d in domain identifier", len(b), lf) - } - switch Authority(b[1]) { - case AuthorityNull: - // That's it, that's the whole DI. - return NullDomainIdentifier{}, b[2:], nil - - case AuthorityIP: - if lf != 7 { - return nil, b, fmt.Errorf("incorrect length %d for IP domain identifier", lf) - } - return IPDomainIdentifier(b[5:8]), b[8:], nil - - default: - return nil, b, fmt.Errorf("unknown domain identifier authority %d", b[1]) - } -} - // TrHeader represent an AURP-Tr packet header. It includes the domain header. type TrHeader struct { *DomainHeader @@ -245,84 +115,6 @@ const ( RoutingFlagSendZoneInfo RoutingFlag = 0x4000 ) -// OptionTuple is used to pass option information in Open-Req and Open-Rsp -// packets. -type OptionTuple struct { - // Length uint8 = 1(for Type) + len(Data) - Type OptionType - Data []byte -} - -func (ot *OptionTuple) WriteTo(w io.Writer) (int64, error) { - if len(ot.Data) > 254 { - return 0, fmt.Errorf("option tuple data too long [%d > 254]", len(ot.Data)) - } - - a := acc(w) - a.write([]byte{ - byte(len(ot.Data) + 1), - byte(ot.Type), - }) - a.write(ot.Data) - return a.ret() -} - -func parseOptionTuple(p []byte) (OptionTuple, []byte, error) { - if len(p) < 2 { - return OptionTuple{}, p, fmt.Errorf("insufficient input length %d for option tuple", len(p)) - } - olen := int(p[0]) + 1 - if len(p) < olen { - return OptionTuple{}, p, fmt.Errorf("insufficient input for option tuple data length %d", olen) - } - return OptionTuple{ - Type: OptionType(p[1]), - Data: p[2:olen], - }, p[olen:], nil -} - -// OptionType is used to distinguish different options. -type OptionType uint8 - -// Various option types -const ( - OptionTypeAuthentication OptionType = 0x01 - // All other types reserved -) - -type Options []OptionTuple - -func (o Options) WriteTo(w io.Writer) (int64, error) { - if len(o) > 255 { - return 0, fmt.Errorf("too many options [%d > 255]", len(o)) - } - - a := acc(w) - a.write8(uint8(len(o))) - for _, ot := range o { - a.writeTo(&ot) - } - return a.ret() -} - -func parseOptions(p []byte) (Options, error) { - if len(p) < 1 { - return nil, fmt.Errorf("insufficint input length %d for options", len(p)) - } - optc := p[0] - opts := make([]OptionTuple, optc) - for i := range optc { - ot, np, err := parseOptionTuple(p) - if err != nil { - return nil, fmt.Errorf("parsing option %d: %w", i, err) - } - opts[i] = ot - p = np - } - // TODO: warn about trailing data? - return opts, nil -} - // Packet represents a full AURP packet, not including UDP or lower layers, but // including the domain header and higher layers. type Packet interface { @@ -343,174 +135,6 @@ func (p *AppleTalkPacket) WriteTo(w io.Writer) (int64, error) { return a.ret() } -// OpenReq is used to open a one-way connection between AIRs. -type OpenReqPacket struct { - *Header - - Version uint16 // currently always 1 - Options Options -} - -func (p *OpenReqPacket) WriteTo(w io.Writer) (int64, error) { - a := acc(w) - a.writeTo(p.Header) - a.write16(p.Version) - a.writeTo(p.Options) - return a.ret() -} - -func parseOpenReq(p []byte) (*OpenReqPacket, error) { - if len(p) < 3 { - return nil, fmt.Errorf("insufficient input length %d for Open-Req packet", len(p)) - } - opts, err := parseOptions(p[2:]) - if err != nil { - return nil, err - } - return &OpenReqPacket{ - Version: binary.BigEndian.Uint16(p[:2]), - Options: opts, - }, nil -} - -// OpenRsp is used to respond to Open-Req. -type OpenRspPacket struct { - *Header - - RateOrErrCode int16 - Options Options -} - -func (p *OpenRspPacket) WriteTo(w io.Writer) (int64, error) { - a := acc(w) - a.writeTo(p.Header) - a.write16(uint16(p.RateOrErrCode)) - a.writeTo(p.Options) - return a.ret() -} - -func parseOpenRsp(p []byte) (*OpenRspPacket, error) { - if len(p) < 3 { - return nil, fmt.Errorf("insufficient input length %d for Open-Rsp packet", len(p)) - } - opts, err := parseOptions(p[2:]) - if err != nil { - return nil, err - } - return &OpenRspPacket{ - RateOrErrCode: int16(binary.BigEndian.Uint16(p[:2])), - Options: opts, - }, nil -} - -type RIReqPacket struct { - *Header -} - -type RIRspPacket struct { - *Header - - RTMPData []byte -} - -func (p *RIRspPacket) WriteTo(w io.Writer) (int64, error) { - a := acc(w) - a.writeTo(p.Header) - a.write(p.RTMPData) - return a.ret() -} - -type RIAckPacket struct { - *Header -} - -type RIUpdPacket struct { - *Header - - Events Events -} - -func (p *RIUpdPacket) WriteTo(w io.Writer) (int64, error) { - a := acc(w) - a.writeTo(p.Header) - a.writeTo(p.Events) - return a.ret() -} - -func parseRIUpd(p []byte) (*RIUpdPacket, error) { - var e Events - for len(p) > 0 { - et, nextp, err := parseEventTuple(p) - if err != nil { - return nil, fmt.Errorf("parsing event tuple %d: %w", len(e), err) - } - e = append(e, et) - p = nextp - } - return &RIUpdPacket{ - Events: e, - }, nil -} - -type Events []EventTuple - -func (e Events) WriteTo(w io.Writer) (int64, error) { - a := acc(w) - for _, et := range e { - a.writeTo(&et) - } - return a.ret() -} - -type EventTuple struct { - EventCode EventCode - RangeStart uint16 // or simply the network number - Distance uint8 - RangeEnd uint16 -} - -func (et *EventTuple) WriteTo(w io.Writer) (int64, error) { - a := acc(w) - a.write8(uint8(et.EventCode)) - a.write16(et.RangeStart) - a.write8(et.Distance) - if et.Distance&0x80 != 0 { // extended tuple - a.write16(et.RangeEnd) - } - return a.ret() -} - -func parseEventTuple(p []byte) (EventTuple, []byte, error) { - if len(p) < 4 { - return EventTuple{}, p, fmt.Errorf("insufficient input length %d for network event tuple", len(p)) - } - - var et EventTuple - et.EventCode = EventCode(p[0]) - et.RangeStart = binary.BigEndian.Uint16(p[1:3]) - et.Distance = p[3] - if et.Distance&0x80 == 0 { - return et, p[4:], nil - } - - if len(p) < 6 { - return EventTuple{}, p, fmt.Errorf("insufficient input length %d for extended network event tuple", len(p)) - } - et.RangeEnd = binary.BigEndian.Uint16(p[4:6]) - return et, p[6:], nil -} - -type EventCode uint8 - -const ( - EventCodeNull EventCode = 0 - EventCodeNA EventCode = 1 - EventCodeND EventCode = 2 - EventCodeNRC EventCode = 3 - EventCodeNDC EventCode = 4 - EventCodeZC EventCode = 5 -) - // ParsePacket parses the body of a UDP packet for a domain header, and then // based on the packet type, an AURP-Tr header, an AURP routing header, and // then a particular packet type. diff --git a/aurp/domain.go b/aurp/domain.go new file mode 100644 index 0000000..ad5458e --- /dev/null +++ b/aurp/domain.go @@ -0,0 +1,137 @@ +package aurp + +import ( + "encoding/binary" + "fmt" + "io" + "net" +) + +// DomainHeader represents the header used to encapsulate both AppleTalk data +// packets and AURP packets within UDP. +type DomainHeader struct { + DestinationDI DomainIdentifier + SourceDI DomainIdentifier + Version uint16 // Should always be 0x0001 + Reserved uint16 + PacketType PacketType // 2 = AppleTalk data packet, 3 = AURP packet +} + +// PacketType is used to distinguish domain-header encapsulated packets. +type PacketType uint16 + +// Various packet types. +const ( + PacketTypeAppleTalk PacketType = 0x0002 + PacketTypeRouting PacketType = 0x0003 +) + +// WriteTo writes the encoded form of the domain header to w. +func (dh *DomainHeader) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(dh.DestinationDI) + a.writeTo(dh.SourceDI) + a.write16(dh.Version) + a.write16(dh.Reserved) + a.write16(uint16(dh.PacketType)) + return a.ret() +} + +// parseDomainHeader parses a domain header, returning the DH and the remainder +// of the input slice. It does not validate the version or packet type fields. +func parseDomainHeader(b []byte) (*DomainHeader, []byte, error) { + ddi, b, err := parseDomainIdentifier(b) + if err != nil { + return nil, b, err + } + sdi, b, err := parseDomainIdentifier(b) + if err != nil { + return nil, b, err + } + if len(b) < 6 { // sizeof(version + reserved + packettype) + return nil, b, fmt.Errorf("insufficient remaining input length %d < 6", len(b)) + } + return &DomainHeader{ + DestinationDI: ddi, + SourceDI: sdi, + Version: binary.BigEndian.Uint16(b[:2]), + Reserved: binary.BigEndian.Uint16(b[2:4]), + PacketType: PacketType(binary.BigEndian.Uint16(b[4:6])), + }, b[6:], nil +} + +// DomainIdentifier is the byte representation of a domain identifier. +type DomainIdentifier interface { + io.WriterTo +} + +// NullDomainIdentifier represents a null domain identifier. +type NullDomainIdentifier struct{} + +// WriteTo writes the encoded form of the domain identifier to w. +func (NullDomainIdentifier) WriteTo(w io.Writer) (int64, error) { + n, err := w.Write([]byte{0x01, 0x00}) + return int64(n), err +} + +// IPDomainIdentifier represents an IP address in a domain identifier. +type IPDomainIdentifier net.IP + +// WriteTo writes the encoded form of the domain identifier to w. +func (i IPDomainIdentifier) WriteTo(w io.Writer) (int64, error) { + v4 := net.IP(i).To4() + if v4 == nil { + return 0, fmt.Errorf("need v4 IP address, got %v", i) + } + + a := acc(w) + a.write([]byte{ + 0x07, // byte 1: length of the DI, in bytes + 0x01, // byte 2: authority: 1 = IP address + 0x00, 0x00, // bytes 3, 4: distinguisher: reserved) + }) + a.write(v4) // bytes 5-8: IP address + return a.ret() +} + +// Authority represents the different possible authorities ("types") for domain +// identifiers. +type Authority byte + +// Various authorities. +const ( + // AuthorityNull is for null domain identifiers, suitable only when there is + // no need to distinguish the domains connected to a tunnel. + AuthorityNull Authority = iota + + // AuthorityIP is for + AuthorityIP +) + +// parseDomainIdentifier parses a DI from the front of b, and returns the DI and +// the remainder of the input slice or an error. +func parseDomainIdentifier(b []byte) (DomainIdentifier, []byte, error) { + if len(b) < 2 { + return nil, b, fmt.Errorf("insufficient input length %d for domain identifier", len(b)) + } + // Now we know there is a length byte and authority byte, see if there is + // that much more data + lf := int(b[0]) + if len(b) < 1+lf { + return nil, b, fmt.Errorf("input length %d < 1+specified length %d in domain identifier", len(b), lf) + } + switch Authority(b[1]) { + case AuthorityNull: + // That's it, that's the whole DI. + return NullDomainIdentifier{}, b[2:], nil + + case AuthorityIP: + if lf != 7 { + return nil, b, fmt.Errorf("incorrect length %d for IP domain identifier", lf) + } + return IPDomainIdentifier(b[5:8]), b[8:], nil + + default: + return nil, b, fmt.Errorf("unknown domain identifier authority %d", b[1]) + } +} diff --git a/aurp/open.go b/aurp/open.go new file mode 100644 index 0000000..b09f0a3 --- /dev/null +++ b/aurp/open.go @@ -0,0 +1,145 @@ +package aurp + +import ( + "encoding/binary" + "fmt" + "io" +) + +// OpenReq is used to open a one-way connection between AIRs. +type OpenReqPacket struct { + *Header + + Version uint16 // currently always 1 + Options Options +} + +func (p *OpenReqPacket) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(p.Header) + a.write16(p.Version) + a.writeTo(p.Options) + return a.ret() +} + +func parseOpenReq(p []byte) (*OpenReqPacket, error) { + if len(p) < 3 { + return nil, fmt.Errorf("insufficient input length %d for Open-Req packet", len(p)) + } + opts, err := parseOptions(p[2:]) + if err != nil { + return nil, err + } + return &OpenReqPacket{ + Version: binary.BigEndian.Uint16(p[:2]), + Options: opts, + }, nil +} + +// OpenRsp is used to respond to Open-Req. +type OpenRspPacket struct { + *Header + + RateOrErrCode int16 + Options Options +} + +func (p *OpenRspPacket) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(p.Header) + a.write16(uint16(p.RateOrErrCode)) + a.writeTo(p.Options) + return a.ret() +} + +func parseOpenRsp(p []byte) (*OpenRspPacket, error) { + if len(p) < 3 { + return nil, fmt.Errorf("insufficient input length %d for Open-Rsp packet", len(p)) + } + opts, err := parseOptions(p[2:]) + if err != nil { + return nil, err + } + return &OpenRspPacket{ + RateOrErrCode: int16(binary.BigEndian.Uint16(p[:2])), + Options: opts, + }, nil +} + +// OptionTuple is used to pass option information in Open-Req and Open-Rsp +// packets. +type OptionTuple struct { + // Length uint8 = 1(for Type) + len(Data) + Type OptionType + Data []byte +} + +func (ot *OptionTuple) WriteTo(w io.Writer) (int64, error) { + if len(ot.Data) > 254 { + return 0, fmt.Errorf("option tuple data too long [%d > 254]", len(ot.Data)) + } + + a := acc(w) + a.write([]byte{ + byte(len(ot.Data) + 1), + byte(ot.Type), + }) + a.write(ot.Data) + return a.ret() +} + +func parseOptionTuple(p []byte) (OptionTuple, []byte, error) { + if len(p) < 2 { + return OptionTuple{}, p, fmt.Errorf("insufficient input length %d for option tuple", len(p)) + } + olen := int(p[0]) + 1 + if len(p) < olen { + return OptionTuple{}, p, fmt.Errorf("insufficient input for option tuple data length %d", olen) + } + return OptionTuple{ + Type: OptionType(p[1]), + Data: p[2:olen], + }, p[olen:], nil +} + +// OptionType is used to distinguish different options. +type OptionType uint8 + +// Various option types +const ( + OptionTypeAuthentication OptionType = 0x01 + // All other types reserved +) + +type Options []OptionTuple + +func (o Options) WriteTo(w io.Writer) (int64, error) { + if len(o) > 255 { + return 0, fmt.Errorf("too many options [%d > 255]", len(o)) + } + + a := acc(w) + a.write8(uint8(len(o))) + for _, ot := range o { + a.writeTo(&ot) + } + return a.ret() +} + +func parseOptions(p []byte) (Options, error) { + if len(p) < 1 { + return nil, fmt.Errorf("insufficint input length %d for options", len(p)) + } + optc := p[0] + opts := make([]OptionTuple, optc) + for i := range optc { + ot, np, err := parseOptionTuple(p) + if err != nil { + return nil, fmt.Errorf("parsing option %d: %w", i, err) + } + opts[i] = ot + p = np + } + // TODO: warn about trailing data? + return opts, nil +} diff --git a/aurp/ri.go b/aurp/ri.go new file mode 100644 index 0000000..9ca655d --- /dev/null +++ b/aurp/ri.go @@ -0,0 +1,115 @@ +package aurp + +import ( + "encoding/binary" + "fmt" + "io" +) + +type RIReqPacket struct { + *Header +} + +type RIRspPacket struct { + *Header + + RTMPData []byte +} + +func (p *RIRspPacket) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(p.Header) + a.write(p.RTMPData) + return a.ret() +} + +type RIAckPacket struct { + *Header +} + +type RIUpdPacket struct { + *Header + + Events Events +} + +func (p *RIUpdPacket) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(p.Header) + a.writeTo(p.Events) + return a.ret() +} + +func parseRIUpd(p []byte) (*RIUpdPacket, error) { + var e Events + for len(p) > 0 { + et, nextp, err := parseEventTuple(p) + if err != nil { + return nil, fmt.Errorf("parsing event tuple %d: %w", len(e), err) + } + e = append(e, et) + p = nextp + } + return &RIUpdPacket{ + Events: e, + }, nil +} + +type Events []EventTuple + +func (e Events) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + for _, et := range e { + a.writeTo(&et) + } + return a.ret() +} + +type EventTuple struct { + EventCode EventCode + RangeStart uint16 // or simply the network number + Distance uint8 + RangeEnd uint16 +} + +func (et *EventTuple) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.write8(uint8(et.EventCode)) + a.write16(et.RangeStart) + a.write8(et.Distance) + if et.Distance&0x80 != 0 { // extended tuple + a.write16(et.RangeEnd) + } + return a.ret() +} + +func parseEventTuple(p []byte) (EventTuple, []byte, error) { + if len(p) < 4 { + return EventTuple{}, p, fmt.Errorf("insufficient input length %d for network event tuple", len(p)) + } + + var et EventTuple + et.EventCode = EventCode(p[0]) + et.RangeStart = binary.BigEndian.Uint16(p[1:3]) + et.Distance = p[3] + if et.Distance&0x80 == 0 { + return et, p[4:], nil + } + + if len(p) < 6 { + return EventTuple{}, p, fmt.Errorf("insufficient input length %d for extended network event tuple", len(p)) + } + et.RangeEnd = binary.BigEndian.Uint16(p[4:6]) + return et, p[6:], nil +} + +type EventCode uint8 + +const ( + EventCodeNull EventCode = 0 + EventCodeNA EventCode = 1 + EventCodeND EventCode = 2 + EventCodeNRC EventCode = 3 + EventCodeNDC EventCode = 4 + EventCodeZC EventCode = 5 +)