jrouter/aurp/domain.go
2024-03-22 16:14:55 +11:00

137 lines
4 KiB
Go

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 // Should always be 0x0000
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 DomainHeader{}, b, err
}
sdi, b, err := parseDomainIdentifier(b)
if err != nil {
return DomainHeader{}, b, err
}
if len(b) < 6 { // sizeof(version + reserved + packettype)
return DomainHeader{}, 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])
}
}