More ZIP, another note I found on NBP

This commit is contained in:
Josh Deprez 2024-04-14 15:28:07 +10:00
parent 05215af5f7
commit 31b7c34535
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
3 changed files with 128 additions and 15 deletions

View file

@ -16,21 +16,131 @@
package zip package zip
import "github.com/sfiera/multitalk/pkg/ddp" import (
"bytes"
"encoding/binary"
"fmt"
"github.com/sfiera/multitalk/pkg/ddp"
)
type QueryPacket struct { type QueryPacket struct {
Function Function // 1 // Function = 1
// NetworkCount uint8 // NetworkCount uint8
Networks []ddp.Network Networks []ddp.Network
} }
type ReplyPacket struct { func (p *QueryPacket) Marshal() ([]byte, error) {
Function Function // 2 or 8 if len(p.Networks) > 255 {
// NetworkCount uint8 return nil, fmt.Errorf("too many networks [%d > 255]", len(p.Networks))
Tuples []ZoneTuple }
b := bytes.NewBuffer(nil)
b.WriteByte(FunctionQuery)
b.WriteByte(byte(len(p.Networks)))
for _, n := range p.Networks {
write16(b, n)
}
return b.Bytes(), nil
} }
type ZoneTuple struct { func UnmarshalQueryPacket(data []byte) (*QueryPacket, error) {
Network ddp.Network if len(data) < 2 {
ZoneName string return nil, fmt.Errorf("insufficient input length %d for ZIP Query packet", len(data))
}
if data[0] != FunctionQuery {
return nil, fmt.Errorf("not a ZIP Query packet (funciton = %d)", data[0])
}
p := &QueryPacket{
Networks: make([]ddp.Network, 0, data[1]),
}
for range data[1] {
data = data[2:]
if len(data) < 2 {
return nil, fmt.Errorf("insufficient remaining input length %d for network number", len(data))
}
p.Networks = append(p.Networks, ddp.Network(binary.BigEndian.Uint16(data[:2])))
}
return p, nil
}
type ReplyPacket struct {
// Function = 2 or 8
Extended bool
// NetworkCount uint8
// "Replies contain the number of zones lists indicated in the Reply header"
// and
// "Extended Replies can contain only one zones list. ...
// (the network numbers in each pair will all be the same in an Extended
// Reply). The network count in the header indicates, not the number of zone
// names in the packet, but the number of zone names in the entire zones
// list for the requested network, which may span more than one packet."
// and
// "Note: Extended ZIP Replies may also be used for responding to ZIP
// queries with zones lists that all fit in one Reply packet. In this case,
// the network count will be equal to the number of zone names in the
// packet"
Networks map[ddp.Network][]string
}
func (p *ReplyPacket) Marshal() ([]byte, error) {
if len(p.Networks) > 255 {
return nil, fmt.Errorf("too many networks [%d > 255]", len(p.Networks))
}
if len(p.Networks) > 1 && p.Extended {
return nil, fmt.Errorf("extended reply can only contain 1 network")
}
b := bytes.NewBuffer(nil)
if p.Extended {
b.WriteByte(FunctionExtendedReply)
} else {
b.WriteByte(FunctionReply)
b.WriteByte(byte(len(p.Networks)))
}
for n, zs := range p.Networks {
if p.Extended {
if len(zs) > 255 {
return nil, fmt.Errorf("too many zone names [%d > 255]", len(zs))
}
// TODO: handle spreading extended replies across multiple packets
b.WriteByte(byte(len(zs)))
}
for _, z := range zs {
if len(z) > 32 {
return nil, fmt.Errorf("len(%q) > 32", z)
}
write16(b, n)
b.WriteByte(byte(len(z)))
b.WriteString(z)
}
}
return b.Bytes(), nil
}
func UnmarshalReplyPacket(data []byte) (*ReplyPacket, error) {
if len(data) < 2 {
return nil, fmt.Errorf("insufficient input length %d for ZIP Reply packet", len(data))
}
if data[0] != FunctionReply && data[0] != FunctionExtendedReply {
return nil, fmt.Errorf("not a Reply or an Extended Reply (function = %d)", data[0])
}
p := &ReplyPacket{
Extended: data[0] == FunctionExtendedReply,
Networks: make(map[ddp.Network][]string),
}
// "network count" is kinda irrelevant for unmarshalling?
data = data[2:]
for len(data) > 0 {
if len(data) < 3 {
return nil, fmt.Errorf("insufficinet remaining input length %d for zone tuple", len(data))
}
network := ddp.Network(binary.BigEndian.Uint16(data[:2]))
slen := data[2]
data = data[3:]
if len(data) < int(slen) {
return nil, fmt.Errorf("insufficient remaining input length %d for length-%d prefixed string", len(data), slen)
}
p.Networks[network] = append(p.Networks[network], string(data[:slen]))
data = data[slen:]
}
return p, nil
} }

View file

@ -39,13 +39,10 @@ func UnmarshalPacket(data []byte) (any, error) {
} }
switch data[0] { switch data[0] {
case FunctionQuery: case FunctionQuery:
return nil, fmt.Errorf("ZIP Query unmarshaling unimplemented") return UnmarshalQueryPacket(data)
case FunctionReply: case FunctionReply, FunctionExtendedReply:
return nil, fmt.Errorf("ZIP Reply unmarshaling unimplemented") return UnmarshalReplyPacket(data)
case FunctionExtendedReply:
return nil, fmt.Errorf("ZIP Extended Reply unmarshaling unimplemented")
case FunctionGetNetInfo: case FunctionGetNetInfo:
return UnmarshalGetNetInfoPacket(data) return UnmarshalGetNetInfoPacket(data)

View file

@ -631,6 +631,11 @@ func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp
return fmt.Errorf("couldn't marshal LkUp: %v", err) return fmt.Errorf("couldn't marshal LkUp: %v", err)
} }
// "If the destination network is extended, however, the router must also
// change the destination network number to $0000, so that the packet is
// received by all nodes on the network (within the correct zone multicast
// address)."
ddpkt.DstNet = 0x0000
ddpkt.DstNode = 0xFF // Broadcast node address within the dest network ddpkt.DstNode = 0xFF // Broadcast node address within the dest network
ddpkt.Data = nbpRaw ddpkt.Data = nbpRaw
@ -638,6 +643,7 @@ func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp
if err != nil { if err != nil {
return err return err
} }
// TODO: outFrame.Dst = zone-specific multicast address
outFrameRaw, err := ethertalk.Marshal(*outFrame) outFrameRaw, err := ethertalk.Marshal(*outFrame)
if err != nil { if err != nil {
return err return err