rumpk/libs/libertaria/lwf_adapter.zig

513 lines
16 KiB
Zig

// 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]);
}