ZI-Req, ZI-Rsp

This commit is contained in:
Josh Deprez 2024-03-17 18:19:36 +11:00
parent e18614bfec
commit 3d66d67713
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
7 changed files with 322 additions and 13 deletions

View file

@ -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")
}

14
aurp/errors.go Normal file
View file

@ -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
)

View file

@ -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))

View file

@ -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
}

View file

@ -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)

25
aurp/tickle.go Normal file
View file

@ -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)
}

196
aurp/zone_info.go Normal file
View file

@ -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
}