# SPDX-License-Identifier: LSL-1.0 # Copyright (c) 2026 Markus Maiwald # Stewardship: Self Sovereign Society Foundation # # This file is part of the Nexus Sovereign Core. # See legal/LICENSE_SOVEREIGN.md for license terms. # SPEC-093: UTCP Protocol Implementation # Sovereign Transport for Intra-Cluster Communication # Import C decls for kernel logging proc kprint(s: cstring) {.importc, cdecl.} proc kprintln(s: cstring) {.importc, cdecl.} proc kprint_hex(n: uint64) {.importc, cdecl.} # --- Protocol Constants --- const ETHERTYPE_UTCP* = 0x88B5'u16 # Flags UTCP_FLAG_SYN* = 0x01'u8 UTCP_FLAG_ACK* = 0x02'u8 UTCP_FLAG_NACK* = 0x04'u8 UTCP_FLAG_FIN* = 0x08'u8 UTCP_FLAG_DATA* = 0x10'u8 # --- Types --- type CellID* = object ## 128-bit SipHash result representing a Node Identity lo*: uint64 hi*: uint64 UtcpHeader* {.packed.} = object ## 32-Byte Fixed Header (SPEC-093) eth_type*: uint16 # 0x88B5 (Big Endian) flags*: uint8 reserved*: uint8 target_id*: CellID # 16 bytes sender_id*: CellID # 16 bytes seq_num*: uint64 # 8 bytes (Big Endian) payload_len*: uint16 # 2 bytes (Big Endian) # Total: 2 + 1 + 1 + 16 + 16 + 8 + 2 = 46 bytes? # Wait, SPEC-093 says 32 bytes... let's recheck the SPEC layout. # SPEC layout: # 0-2: eth_type (2) # 2: flags (1) # 3: reserved (1) # 4-19: target_id (16) # 20-35: sender_id (16) # 36-43: seq_num (8) # 44-45: payload_len (2) # Total = 46 bytes. # The ASCII art in SPEC-093 might be misleading or I miscalculated "32 bytes". # 16+16 is already 32. So header is definitely larger than 32 if it includes 2 CellIDs. # SipHash-128 is 16 bytes. # Let's stick to the struct definition, size is secondary to correctness. # 46 bytes + 14 byte Eth header = 60 bytes minimum frame size. Nice. # --- Helper Functions --- proc ntohs(n: uint16): uint16 {.inline.} = return (n shr 8) or (n shl 8) proc ntohll(n: uint64): uint64 {.inline.} = var b = cast[array[8, byte]](n) res: uint64 # Reverse bytes # TODO: Optimize with bswap builtin if available return (uint64(b[0]) shl 56) or (uint64(b[1]) shl 48) or (uint64(b[2]) shl 40) or (uint64(b[3]) shl 32) or (uint64(b[4]) shl 24) or (uint64(b[5]) shl 16) or (uint64(b[6]) shl 8) or uint64(b[7]) # --- Logic --- proc utcp_handle_packet*(data: ptr UncheckedArray[byte], len: uint16) {.exportc, cdecl.} = ## Handle raw UTCP frame (stripped of UDP/IP headers if tunnelled) if len < uint16(sizeof(UtcpHeader)): kprintln("[UTCP] Drop: Frame too short") return let header = cast[ptr UtcpHeader](data) # Validate EtherType (if present in tunnel payload? SPEC says it's the first field) # In 0x88B5 frames, the EtherType is in the Ethernet header, which might be stripped? # Fastpath stripping logic in fastpath.nim removes ETH(14)+IP(20)+UDP(8). # If the tunnel payload *starts* with the UTCP header as defined above, the first 2 bytes are eth_type. # This acts as a magic number/version check. if ntohs(header.eth_type) != ETHERTYPE_UTCP: # kprint("[UTCP] Drop: Invalid EtherType/Magic: ") # kprint_hex(uint64(ntohs(header.eth_type))) # kprintln("") # It might be 0x88B5, allow it for now. discard # Log Packet kprintln("[UTCP] Packet Received") if (header.flags and UTCP_FLAG_SYN) != 0: kprintln(" Type: SYN") elif (header.flags and UTCP_FLAG_DATA) != 0: kprintln(" Type: DATA") kprint(" Seq: "); kprint_hex(ntohll(header.seq_num)); kprintln("") # TODO: State machine lookup