From 31b7c345350d378de51f14cecde5e408373674dc Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 14 Apr 2024 15:28:07 +1000 Subject: [PATCH] More ZIP, another note I found on NBP --- atalk/zip/query_reply.go | 128 ++++++++++++++++++++++++++++++++++++--- atalk/zip/zip.go | 9 +-- main.go | 6 ++ 3 files changed, 128 insertions(+), 15 deletions(-) diff --git a/atalk/zip/query_reply.go b/atalk/zip/query_reply.go index fea06c5..3d98e7a 100644 --- a/atalk/zip/query_reply.go +++ b/atalk/zip/query_reply.go @@ -16,21 +16,131 @@ package zip -import "github.com/sfiera/multitalk/pkg/ddp" +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/sfiera/multitalk/pkg/ddp" +) type QueryPacket struct { - Function Function // 1 + // Function = 1 // NetworkCount uint8 Networks []ddp.Network } -type ReplyPacket struct { - Function Function // 2 or 8 - // NetworkCount uint8 - Tuples []ZoneTuple +func (p *QueryPacket) Marshal() ([]byte, error) { + if len(p.Networks) > 255 { + return nil, fmt.Errorf("too many networks [%d > 255]", len(p.Networks)) + } + 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 { - Network ddp.Network - ZoneName string +func UnmarshalQueryPacket(data []byte) (*QueryPacket, error) { + if len(data) < 2 { + 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 } diff --git a/atalk/zip/zip.go b/atalk/zip/zip.go index 9e4373d..4f2d5f1 100644 --- a/atalk/zip/zip.go +++ b/atalk/zip/zip.go @@ -39,13 +39,10 @@ func UnmarshalPacket(data []byte) (any, error) { } switch data[0] { case FunctionQuery: - return nil, fmt.Errorf("ZIP Query unmarshaling unimplemented") + return UnmarshalQueryPacket(data) - case FunctionReply: - return nil, fmt.Errorf("ZIP Reply unmarshaling unimplemented") - - case FunctionExtendedReply: - return nil, fmt.Errorf("ZIP Extended Reply unmarshaling unimplemented") + case FunctionReply, FunctionExtendedReply: + return UnmarshalReplyPacket(data) case FunctionGetNetInfo: return UnmarshalGetNetInfoPacket(data) diff --git a/main.go b/main.go index f3c5c97..b050760 100644 --- a/main.go +++ b/main.go @@ -631,6 +631,11 @@ func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp 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.Data = nbpRaw @@ -638,6 +643,7 @@ func handleNBPInAURP(pcapHandle *pcap.Handle, myHWAddr ethernet.Addr, ddpkt *ddp if err != nil { return err } + // TODO: outFrame.Dst = zone-specific multicast address outFrameRaw, err := ethertalk.Marshal(*outFrame) if err != nil { return err