jrouter/aurp/zone_info.go

390 lines
9.5 KiB
Go

/*
Copyright 2024 Josh Deprez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aurp
import (
"encoding/binary"
"fmt"
"io"
"strings"
"github.com/sfiera/multitalk/pkg/ddp"
)
// Subcode is used to distinguish types of zone request/response.
type Subcode uint16
// Various subcodes.
const (
SubcodeZoneInfoReq Subcode = 0x0001
SubcodeZoneInfoNonExt Subcode = 0x0001
SubcodeZoneInfoExt Subcode = 0x0002
SubcodeGetZonesNet Subcode = 0x0003
SubcodeGetDomainZoneList Subcode = 0x0004
)
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 Subcode(binary.BigEndian.Uint16(p[:2])), p[2:], nil
}
type ZIReqPacket struct {
Header
Subcode
Networks []ddp.Network
}
func (p *ZIReqPacket) WriteTo(w io.Writer) (int64, error) {
a := acc(w)
a.writeTo(&p.Header)
a.write16(uint16(p.Subcode))
for _, n := range p.Networks {
a.write16(uint16(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([]ddp.Network, c)
for i := range c {
ns[i] = ddp.Network(binary.BigEndian.Uint16(p[2*i:][:2]))
}
return &ZIReqPacket{
Subcode: SubcodeZoneInfoReq,
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
Zones ZoneTuples
}
func (p *ZIRspPacket) WriteTo(w io.Writer) (int64, error) {
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 GDZLReqPacket struct {
Header
Subcode
StartIndex uint16
}
func (p *GDZLReqPacket) WriteTo(w io.Writer) (int64, error) {
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)
}
}
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)
}
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
func (zs ZoneTuples) String() string {
var sb strings.Builder
for i, zt := range zs {
if i > 0 {
sb.WriteString(", ")
}
fmt.Fprintf(&sb, "%d %q", zt.Network, zt.Name)
}
return sb.String()
}
type ZoneTuple struct {
Network ddp.Network
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(uint16(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 = ddp.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[offset+1:]) < 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
}