// SPDX-License-Identifier: LCL-1.0 // Copyright (c) 2026 Markus Maiwald // Stewardship: Self Sovereign Society Foundation // // This file is part of the Nexus Commonwealth. // See legal/LICENSE_COMMONWEALTH.md for license terms. //! Project LibWeb: LWF Adapter for Rumpk Kernel //! //! Freestanding LWF header parser for kernel-side routing decisions. //! Zero-copy — operates directly on ION slab buffers. //! Does NOT use std.mem.Allocator — all parsing is in-place. //! //! The full LWF codec (with allocation, encode, checksum) runs in //! the Membrane (userland) where std is available. The kernel only //! needs to parse the header to decide routing. //! //! Wire Format (after Ethernet header): //! [Eth 14B][LWF Header 88B][Payload ...][LWF Trailer 36B] //! //! Integration Points: //! - NetSwitch: EtherType 0x4C57 ("LW") → route to chan_lwf_rx //! - Membrane: Full LWF codec via upstream lwf.zig (uses std) // ========================================================= // LWF Constants (RFC-0000 v0.3.1) // ========================================================= pub const ETHERTYPE_LWF: u16 = 0x4C57; // "LW" in ASCII — Sovereign EtherType pub const LWF_MAGIC = [4]u8{ 'L', 'W', 'F', 0 }; pub const LWF_VERSION: u8 = 0x02; pub const HEADER_SIZE: usize = 88; pub const TRAILER_SIZE: usize = 36; pub const MIN_FRAME_SIZE: usize = HEADER_SIZE + TRAILER_SIZE; // 124 bytes // ========================================================= // Frame Classes (RFC-0000 Section 4.2) // ========================================================= pub const FrameClass = enum(u8) { micro = 0x00, // 128 bytes total mini = 0x01, // 512 bytes total standard = 0x02, // 1350 bytes total big = 0x03, // 4096 bytes total (exceeds ION slab!) jumbo = 0x04, // 9000 bytes total (exceeds ION slab!) _, pub fn maxTotal(self: FrameClass) u16 { return switch (self) { .micro => 128, .mini => 512, .standard => 1350, .big => 4096, .jumbo => 9000, _ => 0, }; } /// Check if frame class fits in an ION slab (2048 bytes) pub fn fitsInSlab(self: FrameClass) bool { return switch (self) { .micro, .mini, .standard => true, .big, .jumbo => false, _ => false, }; } }; // ========================================================= // Service Types (RFC-0121) // ========================================================= pub const ServiceType = struct { pub const DATA_TRANSPORT: u16 = 0x0001; pub const SLASH_PROTOCOL: u16 = 0x0002; pub const IDENTITY_SIGNAL: u16 = 0x0003; pub const ECONOMIC_SETTLEMENT: u16 = 0x0004; pub const RELAY_FORWARD: u16 = 0x0005; pub const STREAM_AUDIO: u16 = 0x0800; pub const STREAM_VIDEO: u16 = 0x0801; pub const STREAM_DATA: u16 = 0x0802; pub const SWARM_MANIFEST: u16 = 0x0B00; pub const SWARM_HAVE: u16 = 0x0B01; pub const SWARM_REQUEST: u16 = 0x0B02; pub const SWARM_BLOCK: u16 = 0x0B03; }; // ========================================================= // LWF Flags (RFC-0000 Section 4.3) // ========================================================= pub const Flags = struct { pub const ENCRYPTED: u8 = 0x01; pub const SIGNED: u8 = 0x02; pub const RELAYABLE: u8 = 0x04; pub const HAS_ENTROPY: u8 = 0x08; pub const FRAGMENTED: u8 = 0x10; pub const PRIORITY: u8 = 0x20; }; // ========================================================= // LWF Header View (Zero-Copy over ION slab) // ========================================================= /// Parsed header fields from a raw buffer. No allocation. /// All multi-byte integers are stored big-endian on the wire. pub const HeaderView = struct { /// Pointer to the start of the LWF header in the ION slab raw: [*]const u8, // Pre-parsed routing fields (hot path) service_type: u16, payload_len: u16, frame_class: FrameClass, version: u8, flags: u8, sequence: u32, timestamp: u64, /// Parse header from raw bytes. Returns null if invalid. /// Does NOT copy — references the original buffer. pub fn parse(data: [*]const u8, data_len: u16) ?HeaderView { if (data_len < HEADER_SIZE) return null; // Fast reject: Magic check (4 bytes at offset 0) if (data[0] != 'L' or data[1] != 'W' or data[2] != 'F' or data[3] != 0) return null; // Version check (offset 77) const ver = data[77]; if (ver != LWF_VERSION) return null; // Parse routing-critical fields const service = readU16Big(data[72..74]); const plen = readU16Big(data[74..76]); const fclass = data[76]; const flg = data[78]; const seq = readU32Big(data[68..72]); const ts = readU64Big(data[80..88]); // Sanity: payload_len must fit in remaining buffer const total_needed = HEADER_SIZE + @as(usize, plen) + TRAILER_SIZE; if (total_needed > data_len) return null; return HeaderView{ .raw = data, .service_type = service, .payload_len = plen, .frame_class = @enumFromInt(fclass), .version = ver, .flags = flg, .sequence = seq, .timestamp = ts, }; } /// Get destination hint (24 bytes at offset 4) pub fn destHint(self: *const HeaderView) *const [24]u8 { return @ptrCast(self.raw[4..28]); } /// Get source hint (24 bytes at offset 28) pub fn sourceHint(self: *const HeaderView) *const [24]u8 { return @ptrCast(self.raw[28..52]); } /// Get session ID (16 bytes at offset 52) pub fn sessionId(self: *const HeaderView) *const [16]u8 { return @ptrCast(self.raw[52..68]); } /// Get pointer to payload data (starts at offset 88) pub fn payloadPtr(self: *const HeaderView) [*]const u8 { return self.raw + HEADER_SIZE; } /// Check if frame has PRIORITY flag pub fn isPriority(self: *const HeaderView) bool { return (self.flags & Flags.PRIORITY) != 0; } /// Check if frame is encrypted pub fn isEncrypted(self: *const HeaderView) bool { return (self.flags & Flags.ENCRYPTED) != 0; } /// Total frame size (header + payload + trailer) pub fn totalSize(self: *const HeaderView) usize { return HEADER_SIZE + @as(usize, self.payload_len) + TRAILER_SIZE; } }; // ========================================================= // Fast Path: Validation for NetSwitch // ========================================================= /// Quick magic-byte check. Use before full parse for early rejection. /// Expects data to point past the Ethernet header (14 bytes). pub fn isLwfMagic(data: [*]const u8, len: u16) bool { if (len < 4) return false; return data[0] == 'L' and data[1] == 'W' and data[2] == 'F' and data[3] == 0; } /// Validate and parse an LWF frame from an ION slab. /// Returns the parsed header view, or null if invalid. /// The ION slab data should start at the LWF header (after Ethernet strip). pub fn validateFrame(data: [*]const u8, len: u16) ?HeaderView { return HeaderView.parse(data, len); } // ========================================================= // C ABI Exports (for Nim FFI) // ========================================================= /// Check if a raw buffer contains a valid LWF frame. /// Called from netswitch.nim to decide routing. /// Returns 1 if valid LWF, 0 otherwise. export fn lwf_validate(data: [*]const u8, len: u16) u8 { if (HeaderView.parse(data, len)) |_| { return 1; } return 0; } /// Get the service type from a validated LWF frame. /// Returns 0 on invalid input. export fn lwf_get_service_type(data: [*]const u8, len: u16) u16 { if (HeaderView.parse(data, len)) |hdr| { return hdr.service_type; } return 0; } /// Get the payload length from a validated LWF frame. export fn lwf_get_payload_len(data: [*]const u8, len: u16) u16 { if (HeaderView.parse(data, len)) |hdr| { return hdr.payload_len; } return 0; } /// Check if frame has PRIORITY flag set. export fn lwf_is_priority(data: [*]const u8, len: u16) u8 { if (HeaderView.parse(data, len)) |hdr| { return if (hdr.isPriority()) 1 else 0; } return 0; } // ========================================================= // Freestanding Integer Helpers (no std) // ========================================================= inline fn readU16Big(bytes: *const [2]u8) u16 { return (@as(u16, bytes[0]) << 8) | @as(u16, bytes[1]); } inline fn readU32Big(bytes: *const [4]u8) u32 { return (@as(u32, bytes[0]) << 24) | (@as(u32, bytes[1]) << 16) | (@as(u32, bytes[2]) << 8) | @as(u32, bytes[3]); } inline fn readU64Big(bytes: *const [8]u8) u64 { return (@as(u64, bytes[0]) << 56) | (@as(u64, bytes[1]) << 48) | (@as(u64, bytes[2]) << 40) | (@as(u64, bytes[3]) << 32) | (@as(u64, bytes[4]) << 24) | (@as(u64, bytes[5]) << 16) | (@as(u64, bytes[6]) << 8) | @as(u64, bytes[7]); } inline fn writeU16Big(buf: *[2]u8, val: u16) void { buf[0] = @truncate(val >> 8); buf[1] = @truncate(val); } inline fn writeU32Big(buf: *[4]u8, val: u32) void { buf[0] = @truncate(val >> 24); buf[1] = @truncate(val >> 16); buf[2] = @truncate(val >> 8); buf[3] = @truncate(val); } inline fn writeU64Big(buf: *[8]u8, val: u64) void { buf[0] = @truncate(val >> 56); buf[1] = @truncate(val >> 48); buf[2] = @truncate(val >> 40); buf[3] = @truncate(val >> 32); buf[4] = @truncate(val >> 24); buf[5] = @truncate(val >> 16); buf[6] = @truncate(val >> 8); buf[7] = @truncate(val); } // ========================================================= // Test Frame Builder (for unit tests only) // ========================================================= /// Build a minimal valid LWF frame in a buffer for testing. /// Returns the total frame size written. fn buildTestFrame(buf: []u8, payload: []const u8, service: u16, flags: u8) usize { const total = HEADER_SIZE + payload.len + TRAILER_SIZE; if (buf.len < total) return 0; // Zero the buffer for (buf[0..total]) |*b| b.* = 0; // Magic buf[0] = 'L'; buf[1] = 'W'; buf[2] = 'F'; buf[3] = 0; // dest_hint (4..28) — leave zeros // source_hint (28..52) — leave zeros // session_id (52..68) — leave zeros // Sequence (68..72) writeU32Big(buf[68..72], 1); // Service type (72..74) writeU16Big(buf[72..74], service); // Payload len (74..76) writeU16Big(buf[74..76], @truncate(payload.len)); // Frame class (76) buf[76] = @intFromEnum(FrameClass.standard); // Version (77) buf[77] = LWF_VERSION; // Flags (78) buf[78] = flags; // entropy_difficulty (79) — 0 // Timestamp (80..88) writeU64Big(buf[80..88], 0xDEADBEEF); // Payload for (payload, 0..) |byte, i| { buf[HEADER_SIZE + i] = byte; } // Trailer (zeros = no signature, no checksum) return total; } // ========================================================= // Tests // ========================================================= const testing = @import("std").testing; test "valid LWF frame parses correctly" { var buf: [512]u8 = undefined; const payload = "Hello LWF"; const sz = buildTestFrame(&buf, payload, ServiceType.DATA_TRANSPORT, 0); try testing.expect(sz > 0); const hdr = HeaderView.parse(&buf, @truncate(sz)); try testing.expect(hdr != null); const h = hdr.?; try testing.expectEqual(ServiceType.DATA_TRANSPORT, h.service_type); try testing.expectEqual(@as(u16, 9), h.payload_len); try testing.expectEqual(LWF_VERSION, h.version); try testing.expectEqual(@as(u8, 0), h.flags); try testing.expectEqual(@as(u32, 1), h.sequence); try testing.expectEqual(@as(u64, 0xDEADBEEF), h.timestamp); try testing.expectEqual(FrameClass.standard, h.frame_class); } test "invalid magic rejected" { var buf: [512]u8 = undefined; _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0); // Corrupt magic buf[0] = 'X'; const hdr = HeaderView.parse(&buf, 160); try testing.expect(hdr == null); } test "wrong version rejected" { var buf: [512]u8 = undefined; _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0); // Set wrong version buf[77] = 0x01; const hdr = HeaderView.parse(&buf, 160); try testing.expect(hdr == null); } test "buffer too small rejected" { var buf: [512]u8 = undefined; _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0); // Pass length smaller than header const hdr = HeaderView.parse(&buf, 80); try testing.expect(hdr == null); } test "payload overflow rejected" { var buf: [512]u8 = undefined; _ = buildTestFrame(&buf, "test", ServiceType.DATA_TRANSPORT, 0); // Claim huge payload that doesn't fit writeU16Big(buf[74..76], 5000); const hdr = HeaderView.parse(&buf, 160); try testing.expect(hdr == null); } test "priority flag detection" { var buf: [512]u8 = undefined; const sz = buildTestFrame(&buf, "urgent", ServiceType.SLASH_PROTOCOL, Flags.PRIORITY); const hdr = HeaderView.parse(&buf, @truncate(sz)).?; try testing.expect(hdr.isPriority()); try testing.expect(!hdr.isEncrypted()); } test "encrypted flag detection" { var buf: [512]u8 = undefined; const sz = buildTestFrame(&buf, "secret", ServiceType.IDENTITY_SIGNAL, Flags.ENCRYPTED | Flags.SIGNED); const hdr = HeaderView.parse(&buf, @truncate(sz)).?; try testing.expect(hdr.isEncrypted()); try testing.expect(!hdr.isPriority()); } test "isLwfMagic fast path" { var buf: [8]u8 = .{ 'L', 'W', 'F', 0, 0, 0, 0, 0 }; try testing.expect(isLwfMagic(&buf, 8)); buf[2] = 'X'; try testing.expect(!isLwfMagic(&buf, 8)); // Too short try testing.expect(!isLwfMagic(&buf, 3)); } test "C ABI lwf_validate matches HeaderView.parse" { var buf: [512]u8 = undefined; const sz = buildTestFrame(&buf, "abi_test", ServiceType.DATA_TRANSPORT, 0); try testing.expectEqual(@as(u8, 1), lwf_validate(&buf, @truncate(sz))); // Corrupt magic buf[0] = 0; try testing.expectEqual(@as(u8, 0), lwf_validate(&buf, @truncate(sz))); } test "C ABI lwf_get_service_type" { var buf: [512]u8 = undefined; const sz = buildTestFrame(&buf, "svc", ServiceType.ECONOMIC_SETTLEMENT, 0); try testing.expectEqual(ServiceType.ECONOMIC_SETTLEMENT, lwf_get_service_type(&buf, @truncate(sz))); } test "frame class slab fit check" { try testing.expect(FrameClass.micro.fitsInSlab()); try testing.expect(FrameClass.mini.fitsInSlab()); try testing.expect(FrameClass.standard.fitsInSlab()); try testing.expect(!FrameClass.big.fitsInSlab()); try testing.expect(!FrameClass.jumbo.fitsInSlab()); } test "totalSize calculation" { var buf: [512]u8 = undefined; const payload = "12345"; const sz = buildTestFrame(&buf, payload, ServiceType.DATA_TRANSPORT, 0); const hdr = HeaderView.parse(&buf, @truncate(sz)).?; try testing.expectEqual(@as(usize, 88 + 5 + 36), hdr.totalSize()); } test "dest and source hint accessors" { var buf: [512]u8 = undefined; _ = buildTestFrame(&buf, "hint", ServiceType.DATA_TRANSPORT, 0); // Write a known dest hint at offset 4 buf[4] = 0xAA; buf[27] = 0xBB; // Write a known source hint at offset 28 buf[28] = 0xCC; buf[51] = 0xDD; const hdr = HeaderView.parse(&buf, 160).?; try testing.expectEqual(@as(u8, 0xAA), hdr.destHint()[0]); try testing.expectEqual(@as(u8, 0xBB), hdr.destHint()[23]); try testing.expectEqual(@as(u8, 0xCC), hdr.sourceHint()[0]); try testing.expectEqual(@as(u8, 0xDD), hdr.sourceHint()[23]); } test "session ID accessor" { var buf: [512]u8 = undefined; _ = buildTestFrame(&buf, "sess", ServiceType.DATA_TRANSPORT, 0); // Write session ID at offset 52 buf[52] = 0x42; buf[67] = 0x99; const hdr = HeaderView.parse(&buf, 160).?; try testing.expectEqual(@as(u8, 0x42), hdr.sessionId()[0]); try testing.expectEqual(@as(u8, 0x99), hdr.sessionId()[15]); }