390 lines
9.5 KiB
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
|
|
}
|