jrouter/aurp/transport.go

312 lines
8.6 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"
"github.com/sfiera/multitalk/pkg/ddp"
)
// TrHeader represent an AURP-Tr packet header. It includes the domain header.
type TrHeader struct {
DomainHeader
ConnectionID uint16
Sequence uint16
}
// WriteTo writes the encoded form of the header to w, including the domain
// header.
func (h *TrHeader) WriteTo(w io.Writer) (int64, error) {
a := acc(w)
a.writeTo(&h.DomainHeader)
a.write16(h.ConnectionID)
a.write16(h.Sequence)
return a.ret()
}
func parseTrHeader(p []byte) (TrHeader, []byte, error) {
if len(p) < 4 {
return TrHeader{}, p, fmt.Errorf("insufficient input length %d for tr header", len(p))
}
return TrHeader{
ConnectionID: binary.BigEndian.Uint16(p[:2]),
Sequence: binary.BigEndian.Uint16(p[2:4]),
}, p[4:], nil
}
// Transport tracks local and remote domain identifiers, connection IDs, and
// sequence numbers for use in a pair of one-way connections.
type Transport struct {
// LocalDI and RemoteDI are used for producing packets.
// When sending a packet, we use LocalDI as SourceDI and RemoteDI as
// DestinationDI.
// (When receiving a packet, we expect to see LocalDI as DestinationDI
// - but it might not be - and we expect to see RemoteDI as SourceDI.)
LocalDI, RemoteDI DomainIdentifier
// LocalConnID is used for packets sent in the role of data receiver.
// RemoteConnID is used for packets sent in the role of data sender.
LocalConnID, RemoteConnID uint16
// LocalSeq is used for packets sent (as data sender)
// RemoteSeq is used to check packets received (remote is the data sender).
LocalSeq, RemoteSeq uint16
}
// domainHeader returns a new domain header suitable for sending a packet.
func (tr *Transport) domainHeader(pt PacketType) DomainHeader {
return DomainHeader{
DestinationDI: tr.RemoteDI,
SourceDI: tr.LocalDI,
Version: 1,
Reserved: 0,
PacketType: pt,
}
}
// transaction returns a new TrHeader, usable for transaction requests or
// responses. Both data senders and data receivers can send transactions.
// It should be given one of tr.LocalConnID (as data receiver) or
// tr.RemoteConnID (as data sender).
func (tr *Transport) transaction(connID uint16) TrHeader {
return TrHeader{
DomainHeader: tr.domainHeader(PacketTypeRouting),
ConnectionID: connID,
Sequence: 0, // Transaction packets all use sequence number 0.
}
}
// sequenced returns a new TrHeader usable for sending a sequenced data packet.
// Only data senders send sequenced data.
func (tr *Transport) sequenced(connID, seq uint16) TrHeader {
return TrHeader{
DomainHeader: tr.domainHeader(PacketTypeRouting),
ConnectionID: connID,
Sequence: seq,
}
}
// NewAppleTalkPacket returns a new AppleTalkPacket.
func (tr *Transport) NewAppleTalkPacket(data []byte) *AppleTalkPacket {
return &AppleTalkPacket{
DomainHeader: tr.domainHeader(PacketTypeAppleTalk),
Data: data,
}
}
// NewOpenReqPacket returns a new Open-Req packet structure. By default it sets
// all SUI flags and uses version 1.
func (tr *Transport) NewOpenReqPacket(opts Options) *OpenReqPacket {
return &OpenReqPacket{
Header: Header{
TrHeader: tr.transaction(tr.LocalConnID),
CommandCode: CmdCodeOpenReq,
Flags: RoutingFlagAllSUI,
},
Version: 1,
Options: opts,
}
}
// NewOpenRspPacket returns a new Open-Rsp packet structure.
func (tr *Transport) NewOpenRspPacket(envFlags RoutingFlag, rateOrErr int16, opts Options) *OpenRspPacket {
return &OpenRspPacket{
Header: Header{
TrHeader: tr.transaction(tr.RemoteConnID),
CommandCode: CmdCodeOpenRsp,
Flags: envFlags,
},
RateOrErrCode: rateOrErr,
Options: opts,
}
}
// NewRIReqPacket returns a new RI-Req packet structure. By default it sets all
// SUI flags.
func (tr *Transport) NewRIReqPacket() *RIReqPacket {
return &RIReqPacket{
Header: Header{
TrHeader: tr.transaction(tr.LocalConnID),
CommandCode: CmdCodeRIReq,
Flags: RoutingFlagAllSUI,
},
}
}
// NewRIRspPacket returs a new RI-Rsp packet structure.
func (tr *Transport) NewRIRspPacket(last RoutingFlag, nets NetworkTuples) *RIRspPacket {
return &RIRspPacket{
Header: Header{
TrHeader: tr.sequenced(tr.RemoteConnID, tr.LocalSeq),
CommandCode: CmdCodeRIRsp,
Flags: last,
},
Networks: nets,
}
}
// NewRIAckPacket returns a new RI-Ack packet structure.
func (tr *Transport) NewRIAckPacket(connID, seq uint16, szi RoutingFlag) *RIAckPacket {
return &RIAckPacket{
Header: Header{
TrHeader: tr.sequenced(connID, seq),
CommandCode: CmdCodeRIAck,
Flags: szi,
},
}
}
// NewRIUpdPacket returns a new RI-Upd packet structure.
func (tr *Transport) NewRIUpdPacket(events EventTuples) *RIUpdPacket {
return &RIUpdPacket{
Header: Header{
TrHeader: tr.sequenced(tr.RemoteConnID, tr.LocalSeq),
CommandCode: CmdCodeRIUpd,
Flags: 0,
},
Events: events,
}
}
// NewZIRspPacket returns a new ZI-Rsp packet structure containing the given
// zone information. It automatically chooses between subcodes 1 or 2 depending
// on whether there is one network ID or more than one network ID.
func (tr *Transport) NewZIRspPacket(zoneLists map[ddp.Network][]string) *ZIRspPacket {
// Only one zone: use non-extended
subcode := SubcodeZoneInfoNonExt
if len(zoneLists) == 1 {
// Only one network: use extended format
subcode = SubcodeZoneInfoExt
}
// Translate from network->zones map into zone tuples
var zones ZoneTuples
for nn, zl := range zoneLists {
for _, z := range zl {
zones = append(zones, ZoneTuple{
Network: nn,
Name: z,
})
}
}
return &ZIRspPacket{
Header: Header{
TrHeader: tr.transaction(tr.RemoteConnID),
CommandCode: CmdCodeZoneRsp,
Flags: 0,
},
Subcode: subcode,
Zones: zones,
}
}
// NewGDZLReqPacket returns a new GDZL-Req packet structure.
func (tr *Transport) NewGDZLReqPacket(startIdx uint16) *GDZLReqPacket {
return &GDZLReqPacket{
Header: Header{
TrHeader: tr.transaction(tr.LocalConnID),
CommandCode: CmdCodeZoneReq,
Flags: 0,
},
Subcode: SubcodeGetDomainZoneList,
StartIndex: startIdx,
}
}
// NewGZNRspPacket returns a new GDZL-Rsp packet structure. If GDZL function is
// not supported, startIdx should be set to -1.
func (tr *Transport) NewGDZLRspPacket(startIdx int16, zoneNames []string) *GDZLRspPacket {
return &GDZLRspPacket{
Header: Header{
TrHeader: tr.transaction(tr.RemoteConnID),
CommandCode: CmdCodeZoneReq,
Flags: 0,
},
Subcode: SubcodeGetDomainZoneList,
StartIndex: startIdx,
ZoneNames: zoneNames,
}
}
// NewGZNReqPacket returns a new GZN-Req packet structure requesting nets for a
// given zone name.
func (tr *Transport) NewGZNReqPacket(zoneName string) *GZNReqPacket {
return &GZNReqPacket{
Header: Header{
TrHeader: tr.transaction(tr.LocalConnID),
CommandCode: CmdCodeZoneReq,
Flags: 0,
},
Subcode: SubcodeGetZonesNet,
ZoneName: zoneName,
}
}
// NewGZNRspPacket returns a new GZN-Rsp packet structure.
func (tr *Transport) NewGZNRspPacket(zoneName string, notSupported bool, nets NetworkTuples) *GZNRspPacket {
return &GZNRspPacket{
Header: Header{
TrHeader: tr.transaction(tr.RemoteConnID),
CommandCode: CmdCodeZoneReq,
Flags: 0,
},
Subcode: SubcodeGetZonesNet,
ZoneName: zoneName,
NotSupported: notSupported,
Networks: nets,
}
}
// NewRDPacket returns a new RD packet structure.
func (tr *Transport) NewRDPacket(errCode ErrorCode) *RDPacket {
return &RDPacket{
Header: Header{
TrHeader: tr.transaction(tr.LocalConnID),
CommandCode: CmdCodeRD,
Flags: 0,
},
ErrorCode: errCode,
}
}
// NewTicklePacket returns a new Tickle packet structure.
func (tr *Transport) NewTicklePacket() *TicklePacket {
return &TicklePacket{
Header: Header{
TrHeader: tr.transaction(tr.LocalConnID),
CommandCode: CmdCodeTickle,
Flags: 0,
},
}
}
// NewTickleAckPacket returns a new Tickle-Ack packet.
func (tr *Transport) NewTickleAckPacket() *TickleAckPacket {
return &TickleAckPacket{
Header: Header{
TrHeader: tr.transaction(tr.RemoteConnID),
CommandCode: CmdCodeTickleAck,
Flags: 0,
},
}
}