package internet import ( "errors" "math" "net" ) // StackNode is an abstraction of a packet exchanging protocol controller. This is the building block for all protocols, // from Ethernet to IP to TCP, practically any protocol can be expressed as a StackNode and function completely. type StackNode interface { // Encapsulate writes the stack node's frame into carrierData[frameOffset:] // along with any other frame or payload the stack node encapsulates. // The returned integer is amount of bytes written such that carrierData[frameOffset:frameOffset+n] // contains written data. Data inside carrierData[:frameOffset] usually contains data necessary for // a StackNode to correctly emit valid frame data: such is the case for TCP packets which require IP // frame data for checksum calculation. Thus StackNodes must provide fields in their own frame // required by sub-stacknodes for correct encapsulation; in the case of IPv4/6 this means including fields // used in pseudo-header checksum like local IP (see [ipv4.CRCWriteUDPPseudo]). // // When [net.ErrClosed] is returned the StackNode should be discarded and any written data passed up normally. // Errors returned by Encapsulate are "extraordinary" and should not be returned unless the StackNode is receiving invalid carrierData/frameOffset. Encapsulate(carrierData []byte, frameOffset int) (int, error) // Demux reads from the argument buffer where frameOffset is the offset of this StackNode's frame first byte. // The stack node then dispatches(demuxes) the encapsulated frames to its corresponding sub-stack-node(s). Demux(carrierData []byte, frameOffset int) error LocalPort() uint16 Protocol() uint64 // Connect ConnectionID() *uint64 // SetFlagPending(flagPending func(numPendingEncapsulations int)) } // node is a concrete StackNode as stored in Stacks. Methods are devirtualized for performance benefits, especially on TinyGo. type node struct { currConnID uint64 connID *uint64 demux func([]byte, int) error encapsulate func([]byte, int) (int, error) proto uint16 port uint16 } var ( errZeroMaxNodes = errors.New("zero max ports") errZeroPort = errors.New("port must be greater than zero") errInvalidProto = errors.New("invalid protocol") errProtoRegistered = errors.New("protocol already registered") errNodesFull = errors.New("no more room for new nodes") _ = net.ErrClosed ) func registerNode(nodesPtr *[]node, h node) error { if cap(*nodesPtr)-len(*nodesPtr) <= 0 { *nodesPtr = nodesCompact(*nodesPtr) } if cap(*nodesPtr)-len(*nodesPtr) <= 0 { return errNodesFull } *nodesPtr = append(*nodesPtr, h) return nil } func handleNodeError(nodesPtr *[]node, nodeIdx int, err error) (discarded bool) { if err != nil { if nodeIdx >= len(*nodesPtr) { panic("unreachable") } nodes := *nodesPtr if checkNodeErr(&nodes[nodeIdx], err) { // *nodesPtr = slices.Delete(nodes, nodeIdx, nodeIdx+1) (*nodesPtr)[nodeIdx] = node{} // 'Delete' node without modifying slice length. discarded = true } } return discarded } func (node *node) IsInvalid() bool { return node.demux == nil || node.encapsulate == nil || (node.connID != nil && node.currConnID != *node.connID) } func checkNodeErr(node *node, err error) (discard bool) { return node.IsInvalid() || (err != nil && err == net.ErrClosed) } func nodeFromStackNode(s StackNode, port uint16, protocol uint64) node { if protocol > math.MaxUint16 { panic(">16bit protocol number unsupported") } var currConnID uint64 connIDPtr := s.ConnectionID() if connIDPtr != nil { currConnID = *connIDPtr } return node{ currConnID: currConnID, connID: connIDPtr, demux: s.Demux, encapsulate: s.Encapsulate, proto: uint16(protocol), port: port, } } func getNode(nodes []node, port uint16, protocol uint16) (node *node) { for i := range nodes { node := &nodes[i] if node.port == port && node.proto == protocol { return node } } return nil } func getEncapsulateNode(nodes *[]node, carrierData []byte, frameOffset int) (nodeIdx int, written int, err error) { destroyed := false for i := range *nodes { node := &(*nodes)[i] if node.IsInvalid() { destroyed = true node.destroy() continue } written, err = node.encapsulate(carrierData, frameOffset) if written > 0 { return i, written, err } else if err != nil { } } if destroyed { *nodes = nodesCompact(*nodes) } return -1, 0, nil } // destroy removes all references to underlying StackNode. Allows garbage collection of node if possible. func (n *node) destroy() { *n = node{} } func getNodeByProto(nodes []node, protocol uint16) int { for i := range nodes { node := &nodes[i] if node.proto == protocol { return i } } return -1 } func nodesCompact(nodes []node) []node { nilOff := 0 for i := 0; i < len(nodes); i++ { if !nodes[i].IsInvalid() { nodes[nilOff] = nodes[i] nilOff++ } } return nodes[:nilOff] }