From 49c58fbd941438fbb3b426d0c7d2948508db9ac1 Mon Sep 17 00:00:00 2001 From: Markus Maiwald Date: Sun, 15 Feb 2026 19:59:20 +0100 Subject: [PATCH] feat(membrane): dual-arch membrane, freestanding stubs, Libertaria LWF integration --- libs/libertaria/lwf_adapter.zig | 512 +++++++++++++++++++++++++++++++ libs/libertaria/lwf_membrane.zig | 300 ++++++++++++++++++ libs/membrane/clib.c | 146 ++++++++- libs/membrane/compositor.nim | 17 +- libs/membrane/include/math.h | 16 + libs/membrane/include/stdio.h | 13 + libs/membrane/include/stdlib.h | 10 + libs/membrane/include/string.h | 12 + libs/membrane/ion.zig | 27 +- libs/membrane/ion_client.nim | 27 +- libs/membrane/libc.nim | 133 ++++---- libs/membrane/net_glue.nim | 179 ++++++----- 12 files changed, 1192 insertions(+), 200 deletions(-) create mode 100644 libs/libertaria/lwf_adapter.zig create mode 100644 libs/libertaria/lwf_membrane.zig create mode 100644 libs/membrane/include/math.h create mode 100644 libs/membrane/include/stdio.h create mode 100644 libs/membrane/include/stdlib.h diff --git a/libs/libertaria/lwf_adapter.zig b/libs/libertaria/lwf_adapter.zig new file mode 100644 index 0000000..146e56b --- /dev/null +++ b/libs/libertaria/lwf_adapter.zig @@ -0,0 +1,512 @@ +// 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]); +} diff --git a/libs/libertaria/lwf_membrane.zig b/libs/libertaria/lwf_membrane.zig new file mode 100644 index 0000000..d6ce49b --- /dev/null +++ b/libs/libertaria/lwf_membrane.zig @@ -0,0 +1,300 @@ +// 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 Membrane Client +//! +//! Userland-side LWF frame handler. Runs in the Membrane where std is +//! available. Consumes validated LWF frames from the dedicated ION +//! channel (s_lwf_rx) and produces encrypted outbound frames (s_lwf_tx). +//! +//! This module bridges: +//! - ION ring (SysTable s_lwf_rx/s_lwf_tx) for zero-copy kernel IPC +//! - Upstream LWF codec (libertaria-stack lwf.zig) for full encode/decode +//! - Noise Protocol for transport encryption/decryption +//! +//! Architecture: +//! VirtIO-net → NetSwitch (validates header) → chan_lwf_rx → [this module] +//! [this module] → chan_lwf_tx → NetSwitch → VirtIO-net +//! +//! NOTE: This file is NOT compiled freestanding. It targets the Membrane +//! (userland) and has access to std.mem.Allocator. + +const std = @import("std"); + +// ========================================================= +// ION Slab Constants (must match ion/memory.nim) +// ========================================================= + +const SLAB_SIZE: usize = 2048; +const SYSTABLE_ADDR: usize = if (builtin.cpu.arch == .aarch64) 0x50000000 else 0x83000000; +const ETH_HEADER_SIZE: usize = 14; + +// ========================================================= +// LWF Header Constants (duplicated from lwf_adapter.zig +// for use with std — adapter is freestanding, this is not) +// ========================================================= + +pub const HEADER_SIZE: usize = 88; +pub const TRAILER_SIZE: usize = 36; +pub const MIN_FRAME_SIZE: usize = HEADER_SIZE + TRAILER_SIZE; +pub const LWF_MAGIC = [4]u8{ 'L', 'W', 'F', 0 }; +pub const LWF_VERSION: u8 = 0x02; + +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; +}; + +// ========================================================= +// Frame Processing Result +// ========================================================= + +pub const FrameError = error{ + TooSmall, + InvalidMagic, + InvalidVersion, + PayloadOverflow, + DecryptionFailed, + NoSession, + SlabTooSmall, +}; + +pub const ProcessedFrame = struct { + service_type: u16, + payload: []const u8, // Points into slab — valid until ion_free + session_id: [16]u8, + dest_hint: [24]u8, + source_hint: [24]u8, + sequence: u32, + flags: u8, + encrypted: bool, +}; + +// ========================================================= +// LWF Membrane Client +// ========================================================= + +pub const LwfClient = struct { + /// Callback type for incoming LWF frames + pub const FrameHandler = *const fn (frame: ProcessedFrame) void; + + on_frame: ?FrameHandler, + + pub fn init() LwfClient { + return .{ + .on_frame = null, + }; + } + + /// Register a callback for incoming LWF frames + pub fn setHandler(self: *LwfClient, handler: FrameHandler) void { + self.on_frame = handler; + } + + /// Parse an LWF frame from a raw ION slab buffer. + /// The buffer starts AFTER the Ethernet header (NetSwitch strips it + /// to EtherType, but the ION packet still contains the full Ethernet + /// frame — so caller must offset by 14 bytes). + pub fn parseFrame(data: [*]const u8, len: u16) FrameError!ProcessedFrame { + if (len < HEADER_SIZE) return error.TooSmall; + + // Magic check + if (data[0] != 'L' or data[1] != 'W' or data[2] != 'F' or data[3] != 0) + return error.InvalidMagic; + + // Version check (offset 77) + if (data[77] != LWF_VERSION) return error.InvalidVersion; + + // Parse fields + const payload_len = readU16Big(data[74..76]); + const total_needed = HEADER_SIZE + @as(usize, payload_len) + TRAILER_SIZE; + if (total_needed > len) return error.PayloadOverflow; + + var frame: ProcessedFrame = undefined; + frame.service_type = readU16Big(data[72..74]); + frame.sequence = readU32Big(data[68..72]); + frame.flags = data[78]; + frame.encrypted = (frame.flags & Flags.ENCRYPTED) != 0; + frame.payload = data[HEADER_SIZE .. HEADER_SIZE + payload_len]; + + @memcpy(&frame.dest_hint, data[4..28]); + @memcpy(&frame.source_hint, data[28..52]); + @memcpy(&frame.session_id, data[52..68]); + + return frame; + } + + /// Build an outbound LWF frame into a slab buffer. + /// Returns the total frame size written. + pub fn buildFrame( + buf: []u8, + service_type: u16, + payload: []const u8, + session_id: [16]u8, + dest_hint: [24]u8, + source_hint: [24]u8, + sequence: u32, + flags: u8, + ) FrameError!usize { + const total = HEADER_SIZE + payload.len + TRAILER_SIZE; + if (buf.len < total) return error.SlabTooSmall; + + // Zero header + trailer regions + @memset(buf[0..HEADER_SIZE], 0); + @memset(buf[HEADER_SIZE + payload.len ..][0..TRAILER_SIZE], 0); + + // Magic + buf[0] = 'L'; + buf[1] = 'W'; + buf[2] = 'F'; + buf[3] = 0; + + // Dest/Source hints + @memcpy(buf[4..28], &dest_hint); + @memcpy(buf[28..52], &source_hint); + + // Session ID + @memcpy(buf[52..68], &session_id); + + // Sequence + writeU32Big(buf[68..72], sequence); + + // Service type + payload len + writeU16Big(buf[72..74], service_type); + writeU16Big(buf[74..76], @truncate(payload.len)); + + // Frame class (auto-select based on total size) + buf[76] = if (total <= 128) 0x00 // micro + else if (total <= 512) 0x01 // mini + else if (total <= 1350) 0x02 // standard + else if (total <= 4096) 0x03 // big + else 0x04; // jumbo + + // Version + buf[77] = LWF_VERSION; + + // Flags + buf[78] = flags; + + // Payload + @memcpy(buf[HEADER_SIZE..][0..payload.len], payload); + + return total; + } +}; + +// ========================================================= +// Integer Helpers +// ========================================================= + +fn readU16Big(bytes: *const [2]u8) u16 { + return (@as(u16, bytes[0]) << 8) | @as(u16, bytes[1]); +} + +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]); +} + +fn writeU16Big(buf: *[2]u8, val: u16) void { + buf[0] = @truncate(val >> 8); + buf[1] = @truncate(val); +} + +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); +} + +// ========================================================= +// Tests +// ========================================================= + +test "parseFrame valid" { + var buf: [512]u8 = undefined; + const payload = "Hello Noise"; + const sz = try LwfClient.buildFrame( + &buf, + 0x0001, // DATA_TRANSPORT + payload, + [_]u8{0xAA} ** 16, // session + [_]u8{0xBB} ** 24, // dest + [_]u8{0xCC} ** 24, // src + 42, + 0, + ); + + const frame = try LwfClient.parseFrame(&buf, @truncate(sz)); + try std.testing.expectEqual(@as(u16, 0x0001), frame.service_type); + try std.testing.expectEqual(@as(u32, 42), frame.sequence); + try std.testing.expectEqualSlices(u8, payload, frame.payload); + try std.testing.expectEqual(@as(u8, 0xAA), frame.session_id[0]); + try std.testing.expect(!frame.encrypted); +} + +test "parseFrame encrypted flag" { + var buf: [512]u8 = undefined; + const sz = try LwfClient.buildFrame( + &buf, + 0x0003, // IDENTITY_SIGNAL + "encrypted_payload", + [_]u8{0} ** 16, + [_]u8{0} ** 24, + [_]u8{0} ** 24, + 1, + Flags.ENCRYPTED | Flags.SIGNED, + ); + + const frame = try LwfClient.parseFrame(&buf, @truncate(sz)); + try std.testing.expect(frame.encrypted); + try std.testing.expectEqual(@as(u16, 0x0003), frame.service_type); +} + +test "buildFrame auto frame class" { + var buf: [2048]u8 = undefined; + + // Micro (total <= 128) + _ = try LwfClient.buildFrame(&buf, 0, "", [_]u8{0} ** 16, [_]u8{0} ** 24, [_]u8{0} ** 24, 0, 0); + try std.testing.expectEqual(@as(u8, 0x00), buf[76]); // micro + + // Standard (total > 512) + var payload: [500]u8 = undefined; + @memset(&payload, 0x42); + _ = try LwfClient.buildFrame(&buf, 0, &payload, [_]u8{0} ** 16, [_]u8{0} ** 24, [_]u8{0} ** 24, 0, 0); + try std.testing.expectEqual(@as(u8, 0x02), buf[76]); // standard +} + +test "parseFrame rejects bad magic" { + var buf: [512]u8 = undefined; + _ = try LwfClient.buildFrame(&buf, 0, "x", [_]u8{0} ** 16, [_]u8{0} ** 24, [_]u8{0} ** 24, 0, 0); + buf[0] = 'X'; + try std.testing.expectError(error.InvalidMagic, LwfClient.parseFrame(&buf, 160)); +} + +test "buildFrame roundtrip preserves hints" { + var buf: [512]u8 = undefined; + const dest = [_]u8{0xDE} ** 24; + const src = [_]u8{0x5A} ** 24; + const sess = [_]u8{0xF0} ** 16; + + _ = try LwfClient.buildFrame(&buf, 0x0800, "audio", sess, dest, src, 99, Flags.PRIORITY); + + const frame = try LwfClient.parseFrame(&buf, 200); + try std.testing.expectEqual(@as(u16, 0x0800), frame.service_type); + try std.testing.expectEqual(@as(u32, 99), frame.sequence); + try std.testing.expectEqualSlices(u8, &dest, &frame.dest_hint); + try std.testing.expectEqualSlices(u8, &src, &frame.source_hint); + try std.testing.expectEqualSlices(u8, &sess, &frame.session_id); +} diff --git a/libs/membrane/clib.c b/libs/membrane/clib.c index ae02f95..1fb8473 100644 --- a/libs/membrane/clib.c +++ b/libs/membrane/clib.c @@ -1,7 +1,20 @@ +// SPDX-License-Identifier: LSL-1.0 +// Copyright (c) 2026 Markus Maiwald +// Stewardship: Self Sovereign Society Foundation #include #include #include +// Freestanding FILE stubs (Nim's system.nim references stderr) +struct _FILE { int dummy; }; +typedef struct _FILE FILE; +static FILE _stdin_placeholder; +static FILE _stdout_placeholder; +static FILE _stderr_placeholder; +FILE *stdin = &_stdin_placeholder; +FILE *stdout = &_stdout_placeholder; +FILE *stderr = &_stderr_placeholder; + // Types needed for stubs typedef int32_t pid_t; typedef int32_t uid_t; @@ -15,13 +28,46 @@ struct stat { int errno = 0; -// Basic memory stubs -extern void* malloc(size_t size); -extern void free(void* ptr); - -// Forward declare memset (defined below) void* memset(void* s, int c, size_t n); +// Basic memory stubs (Bump Allocator) - USERLAND ONLY +// Kernel gets malloc/free/calloc from stubs.zig +#ifdef RUMPK_USER +static uint8_t heap[4 * 1024 * 1024]; // 4MB Heap +static size_t heap_idx = 0; + +void* malloc(size_t size) { + if (size == 0) return NULL; + size = (size + 7) & ~7; // Align to 8 bytes + if (heap_idx + size > sizeof(heap)) return NULL; + void* ptr = &heap[heap_idx]; + heap_idx += size; + return ptr; +} + +void free(void* ptr) { + (void)ptr; +} + +void* calloc(size_t nmemb, size_t size) { + size_t total = nmemb * size; + void* ptr = malloc(total); + if (ptr) memset(ptr, 0, total); + return ptr; +} +#endif // RUMPK_USER + +// Forward declarations +int write(int fd, const void *buf, size_t count); +int read(int fd, void *buf, size_t count); +int open(const char *pathname, int flags, ...); +int close(int fd); + +void* memset(void* s, int c, size_t n); + +// Basic memory stubs +// Basic memory stubs (Bump Allocator) + // LwIP Panic Handler (for Membrane stack) extern void console_write(const void* p, size_t len); @@ -44,8 +90,41 @@ int strncmp(const char *s1, const char *s2, size_t n) { int atoi(const char* nptr) { return 0; } double strtod(const char* nptr, char** endptr) { - if (endptr) *endptr = (char*)nptr; - return 0.0; + const char *p = nptr; + double result = 0.0; + double sign = 1.0; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + if (*p == '-') { sign = -1.0; p++; } + else if (*p == '+') { p++; } + while (*p >= '0' && *p <= '9') { + result = result * 10.0 + (*p - '0'); + p++; + } + if (*p == '.') { + double frac = 0.1; + p++; + while (*p >= '0' && *p <= '9') { + result += (*p - '0') * frac; + frac *= 0.1; + p++; + } + } + if (*p == 'e' || *p == 'E') { + p++; + int exp_sign = 1, exp_val = 0; + if (*p == '-') { exp_sign = -1; p++; } + else if (*p == '+') { p++; } + while (*p >= '0' && *p <= '9') { + exp_val = exp_val * 10 + (*p - '0'); + p++; + } + double m = 1.0; + for (int i = 0; i < exp_val; i++) + m = exp_sign > 0 ? m * 10.0 : m * 0.1; + result *= m; + } + if (endptr) *endptr = (char*)p; + return sign * result; } double pow(double x, double y) { return 0.0; } @@ -55,8 +134,42 @@ double log10(double x) { return 0.0; } #ifdef RUMPK_KERNEL extern long k_handle_syscall(long nr, long a0, long a1, long a2); +extern uint64_t hal_get_time_ns(void); +extern void hal_console_write(const void* p, size_t len); +extern void hal_panic(const char* msg); +uint64_t syscall_get_time_ns(void) { return hal_get_time_ns(); } +void syscall_panic(void) { while(1); } #endif +// Support for Nim system.nim +#ifndef OMIT_EXIT +void exit(int status) { +#ifdef RUMPK_KERNEL + while(1); +#else + syscall(1, (long)status, 0, 0); // SYS_EXIT + while(1); +#endif +} + +void abort(void) { + exit(1); +} +#endif + +size_t fwrite(const void* ptr, size_t size, size_t nmemb, void* stream) { +#ifdef RUMPK_KERNEL + hal_console_write(ptr, size * nmemb); +#else + write(1, ptr, size * nmemb); // Forward to stdout +#endif + return nmemb; +} + +int fflush(void* stream) { + return 0; +} + long syscall(long nr, long a0, long a1, long a2) { #ifdef RUMPK_KERNEL return k_handle_syscall(nr, a0, a1, a2); @@ -222,11 +335,7 @@ int printf(const char *format, ...) { return res; } -int fwrite(const void *ptr, size_t size, size_t nmemb, void *stream) { - return write(1, ptr, size * nmemb); -} - -int fflush(void *stream) { return 0; } +// REDUNDANT REMOVED // System stubs extern void nexus_yield(void); @@ -270,6 +379,16 @@ int memcmp(const void *s1, const void *s2, size_t n) { return 0; } +#ifdef RUMPK_USER +void *memchr(const void *s, int c, size_t n) { + const unsigned char *p = (const unsigned char *)s; + for (size_t i = 0; i < n; i++) { + if (p[i] == (unsigned char)c) return (void *)(p + i); + } + return (void *)0; +} +#endif + void *memmove(void *dest, const void *src, size_t n) { char *d = (char *)dest; const char *s = (const char *)src; @@ -343,13 +462,10 @@ uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { #ifdef RUMPK_KERNEL -// Kernel Mode: Direct UART -extern void hal_console_write(const char* ptr, size_t len); void console_write(const void* p, size_t len) { hal_console_write(p, len); } #else -// User Mode: Syscall void console_write(const void* p, size_t len) { write(1, p, len); } diff --git a/libs/membrane/compositor.nim b/libs/membrane/compositor.nim index 55bb9c8..cbfbc06 100644 --- a/libs/membrane/compositor.nim +++ b/libs/membrane/compositor.nim @@ -12,9 +12,10 @@ import ../../core/ion -const SYS_TABLE_ADDR = 0x83000000'u64 +const SYS_TABLE_ADDR = when defined(arm64): 0x50000000'u64 + else: 0x83000000'u64 -const +const GAP = 10 # Pixels between windows FOCUS_COLOR = 0xFF00FFFF'u32 # Cyan border for focused window BG_COLOR = 0xFF101020'u32 # Dark Blue background @@ -73,7 +74,7 @@ proc draw_border(x, y, w, h: int, color: uint32) = if ix >= 0 and ix < fb_w: if y >= 0 and y < fb_h: fb[y * fb_w + ix] = color if y + h - 1 >= 0 and y + h - 1 < fb_h: fb[(y + h - 1) * fb_w + ix] = color - + for iy in y ..< y + h: if iy >= 0 and iy < fb_h: if x >= 0 and x < fb_w: fb[iy * fb_w + x] = color @@ -107,17 +108,17 @@ proc render_frame*(c: var Compositor) = let screen_x = s.x - c.view_x if screen_x + s.width < 0 or screen_x >= int(sys.fb_width): continue - + let screen_y = (int(sys.fb_height) - s.height) div 2 blit_surface(s, screen_x, screen_y) - + if s.focused: draw_border(screen_x, screen_y, s.width, s.height, FOCUS_COLOR) proc create_surface*(w, h: int): int32 = let id = hal_surface_alloc(uint32(w), uint32(h)) if id < 0: return -1 - + let p = hal_surface_get_ptr(id) if p == nil: return -1 @@ -127,11 +128,11 @@ proc create_surface*(w, h: int): int32 = s.width = w s.height = h s.dirty = true - + c.surfaces.add(s) if c.surfaces.len == 1: c.focused_idx = 0 - + return id proc compositor_step*() = diff --git a/libs/membrane/include/math.h b/libs/membrane/include/math.h new file mode 100644 index 0000000..71d739c --- /dev/null +++ b/libs/membrane/include/math.h @@ -0,0 +1,16 @@ +/* Minimal math.h stub for freestanding Nim builds */ +#ifndef _MATH_H_STUB +#define _MATH_H_STUB + +static inline double fabs(double x) { return x < 0 ? -x : x; } +static inline float fabsf(float x) { return x < 0 ? -x : x; } +static inline double fmod(double x, double y) { return x - (long long)(x / y) * y; } +static inline double floor(double x) { return (double)(long long)x - (x < (double)(long long)x); } +static inline double ceil(double x) { return (double)(long long)x + (x > (double)(long long)x); } +static inline double round(double x) { return floor(x + 0.5); } + +#define HUGE_VAL __builtin_huge_val() +#define NAN __builtin_nan("") +#define INFINITY __builtin_inf() + +#endif diff --git a/libs/membrane/include/stdio.h b/libs/membrane/include/stdio.h new file mode 100644 index 0000000..b4e3c57 --- /dev/null +++ b/libs/membrane/include/stdio.h @@ -0,0 +1,13 @@ +#ifndef STDIO_H +#define STDIO_H +#include +#include +typedef void FILE; +#define stderr ((FILE*)0) +#define stdout ((FILE*)1) +int printf(const char* format, ...); +int sprintf(char* str, const char* format, ...); +int snprintf(char* str, size_t size, const char* format, ...); +size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream); +int fflush(FILE* stream); +#endif diff --git a/libs/membrane/include/stdlib.h b/libs/membrane/include/stdlib.h new file mode 100644 index 0000000..4c4e00e --- /dev/null +++ b/libs/membrane/include/stdlib.h @@ -0,0 +1,10 @@ +#ifndef STDLIB_H +#define STDLIB_H +#include +void* malloc(size_t size); +void free(void* ptr); +void* realloc(void* ptr, size_t size); +void abort(void); +void exit(int status); +int atoi(const char* str); +#endif diff --git a/libs/membrane/include/string.h b/libs/membrane/include/string.h index a1c2c34..2b86b65 100644 --- a/libs/membrane/include/string.h +++ b/libs/membrane/include/string.h @@ -3,4 +3,16 @@ #include "lfs_config.h" +#ifndef _SIZE_T_DEFINED +#define _SIZE_T_DEFINED +typedef unsigned long size_t; +#endif + +void *memchr(const void *s, int c, size_t n); +void *memcpy(void *dest, const void *src, size_t n); +void *memset(void *s, int c, size_t n); +int memcmp(const void *s1, const void *s2, size_t n); +void *memmove(void *dest, const void *src, size_t n); +double strtod(const char *nptr, char **endptr); + #endif diff --git a/libs/membrane/ion.zig b/libs/membrane/ion.zig index 116196b..d718108 100644 --- a/libs/membrane/ion.zig +++ b/libs/membrane/ion.zig @@ -14,6 +14,7 @@ //! All memory accesses to the SysTable are through volatile pointers. const std = @import("std"); +const builtin = @import("builtin"); // --- Protocol Definitions (Match core/ion.nim) --- @@ -94,7 +95,11 @@ pub fn RingBuffer(comptime T: type) type { self.data[head & mask] = item; // Commit - asm volatile ("fence" ::: .{ .memory = true }); + switch (builtin.cpu.arch) { + .riscv64 => asm volatile ("fence" ::: .{ .memory = true }), + .aarch64 => asm volatile ("dmb ish" ::: .{ .memory = true }), + else => @compileError("unsupported arch"), + } self.head = (head + 1) & mask; return true; } @@ -115,6 +120,8 @@ pub const SysTable = extern struct { fn_vfs_list: u64, fn_vfs_write: u64, fn_vfs_close: u64, + fn_vfs_dup: u64, + fn_vfs_dup2: u64, fn_log: u64, fn_pledge: u64, // Framebuffer @@ -127,6 +134,7 @@ pub const SysTable = extern struct { // Crypto fn_siphash: u64, fn_ed25519_verify: u64, + fn_blake3: u64, // Network s_net_rx: *RingBuffer(IonPacket), s_net_tx: *RingBuffer(IonPacket), @@ -134,16 +142,27 @@ pub const SysTable = extern struct { // Phase 36.3: Shared ION (16 bytes) fn_ion_alloc: u64, fn_ion_free: u64, + + // Phase 36.4: Wait Multi + fn_wait_multi: u64, + + // Phase 36.5: HW Info + net_mac: [6]u8, + reserved_mac: [2]u8, + + // Project LibWeb: LWF Sovereign Channel + s_lwf_rx: *RingBuffer(IonPacket), // Kernel -> User (LWF frames) + s_lwf_tx: *RingBuffer(IonPacket), // User -> Kernel (LWF frames) }; comptime { if (@sizeOf(IonPacket) != 24) @compileError("IonPacket size mismatch!"); - if (@sizeOf(SysTable) != 184) { - @compileError("SysTable size mismatch! Expected 184"); + if (@sizeOf(SysTable) != 240) { + @compileError("SysTable size mismatch! Expected 240 (LibWeb LWF channels added)"); } } -const SYSTABLE_ADDR: usize = 0x83000000; +const SYSTABLE_ADDR: usize = if (builtin.cpu.arch == .aarch64) 0x50000000 else 0x83000000; // --- API --- diff --git a/libs/membrane/ion_client.nim b/libs/membrane/ion_client.nim index ae68104..e1ebdc3 100644 --- a/libs/membrane/ion_client.nim +++ b/libs/membrane/ion_client.nim @@ -17,7 +17,8 @@ # Instead, locally declare only the types we need for userspace import ../../core/ring -const SYS_TABLE_ADDR* = 0x83000000'u64 +const SYS_TABLE_ADDR* = when defined(arm64): 0x50000000'u64 + else: 0x83000000'u64 # Local type declarations (must match core/ion.nim definitions) type @@ -70,6 +71,8 @@ type fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.} fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.} fn_vfs_close*: proc(fd: int32): int32 {.cdecl.} + fn_vfs_dup*: proc(fd: int32, min_fd: int32): int32 {.cdecl.} + fn_vfs_dup2*: proc(old_fd, new_fd: int32): int32 {.cdecl.} fn_log*: pointer fn_pledge*: proc(promises: uint64): int32 {.cdecl.} fb_addr*: uint64 @@ -89,16 +92,20 @@ type # Phase 36.3: Shared ION (16 bytes) fn_ion_alloc*: proc(out_id: ptr uint16): uint64 {.cdecl.} fn_ion_free*: proc(id: uint16) {.cdecl.} - + # Phase 36.4: I/O Multiplexing (8 bytes) fn_wait_multi*: proc(mask: uint64): int32 {.cdecl.} - + # Phase 36.5: Network Hardware Info (8 bytes) net_mac*: array[6, byte] reserved_mac*: array[2, byte] + # Project LibWeb: LWF Sovereign Channel (16 bytes) + s_lwf_rx*: pointer # Kernel -> User (LWF frames) + s_lwf_tx*: pointer # User -> Kernel (LWF frames) + static: - doAssert sizeof(SysTable) == 208 + doAssert sizeof(SysTable) == 240 var membrane_rx_ring_ptr*: ptr RingBuffer[IonPacket, 256] var membrane_tx_ring_ptr*: ptr RingBuffer[IonPacket, 256] @@ -125,7 +132,7 @@ proc ion_user_init*() {.exportc.} = var err = "[ION-Client] ERROR: Invalid SysTable Magic!\n" console_write(addr err[0], uint(err.len)) return - + membrane_rx_ring_ptr = cast[ptr RingBuffer[IonPacket, 256]](sys.s_rx) membrane_tx_ring_ptr = cast[ptr RingBuffer[IonPacket, 256]](sys.s_tx) membrane_cmd_ring_ptr = cast[ptr RingBuffer[CmdPacket, 256]](sys.s_cmd) @@ -133,7 +140,7 @@ proc ion_user_init*() {.exportc.} = # Phase 36.2: Network rings membrane_net_rx_ptr = cast[ptr HAL_Ring_Input](sys.s_net_rx) membrane_net_tx_ptr = cast[ptr HAL_Ring_Input](sys.s_net_tx) - + var ok = "[ION-Client] Rings Mapped.\n" console_write(addr ok[0], uint(ok.len)) else: @@ -144,10 +151,10 @@ proc ion_user_init*() {.exportc.} = # Pure shared-memory slab allocator - NO kernel function calls! const - USER_SLAB_BASE = 0x83010000'u64 # Start of user packet slab in SysTable region + USER_SLAB_BASE = SYS_TABLE_ADDR + 0x10000'u64 # Start of user packet slab in SysTable region USER_SLAB_COUNT = 512 # Number of packet slots USER_PKT_SIZE = 2048 # Size of each packet buffer - USER_BITMAP_ADDR = 0x83000100'u64 # Bitmap stored in SysTable region (after SysTable struct) + USER_BITMAP_ADDR = SYS_TABLE_ADDR + 0x100'u64 # Bitmap stored in SysTable region (after SysTable struct) # Get pointer to shared bitmap (512 bits = 64 bytes for 512 slots) proc get_user_bitmap(): ptr array[64, byte] = @@ -156,7 +163,7 @@ proc get_user_bitmap(): ptr array[64, byte] = proc ion_user_alloc*(out_pkt: ptr IonPacket): bool {.exportc.} = ## Allocate packet from shared slab - pure userland, no kernel call let bitmap = get_user_bitmap() - + # Find first free slot for byteIdx in 0 ..< 64: if bitmap[byteIdx] != 0xFF: # At least one bit free @@ -168,7 +175,7 @@ proc ion_user_alloc*(out_pkt: ptr IonPacket): bool {.exportc.} = if (bitmap[byteIdx] and mask) == 0: # Found free slot - mark as used bitmap[byteIdx] = bitmap[byteIdx] or mask - + let addr_val = USER_SLAB_BASE + uint64(slotIdx) * USER_PKT_SIZE out_pkt.id = uint16(slotIdx) or 0x8000 out_pkt.phys = addr_val diff --git a/libs/membrane/libc.nim b/libs/membrane/libc.nim index b7288ab..f0d87f2 100644 --- a/libs/membrane/libc.nim +++ b/libs/membrane/libc.nim @@ -12,13 +12,15 @@ # (C) 2026 Markus Maiwald import ion_client -import net_glue +when defined(RUMPK_KERNEL): + import net_glue + export net_glue # memcpy removed to avoid C header conflict # --- SHARED CONSTANTS & TYPES --- -const +const MAX_SOCKS = 32 FD_OFFSET = 3 # Syscalls @@ -50,39 +52,37 @@ proc syscall*(nr: int, a0: uint64 = 0, a1: uint64 = 0, a2: uint64 = 0): int = let v0 = a0 let v1 = a1 let v2 = a2 - {.emit: """ - register unsigned long a7 __asm__("a7") = `n`; - register unsigned long a0_ __asm__("a0") = `v0`; - register unsigned long a1_ __asm__("a1") = `v1`; - register unsigned long a2_ __asm__("a2") = `v2`; - __asm__ volatile("ecall" : "+r"(a0_) : "r"(a7), "r"(a1_), "r"(a2_) : "memory"); - `res` = (int)a0_; - """.} + when defined(arm64): + {.emit: """ + register unsigned long x8_ __asm__("x8") = `n`; + register unsigned long x0_ __asm__("x0") = `v0`; + register unsigned long x1_ __asm__("x1") = `v1`; + register unsigned long x2_ __asm__("x2") = `v2`; + __asm__ volatile("svc #0" : "+r"(x0_) : "r"(x8_), "r"(x1_), "r"(x2_) : "memory"); + `res` = (long)x0_; + """.} + else: + {.emit: """ + register unsigned long a7 __asm__("a7") = `n`; + register unsigned long a0_ __asm__("a0") = `v0`; + register unsigned long a1_ __asm__("a1") = `v1`; + register unsigned long a2_ __asm__("a2") = `v2`; + __asm__ volatile("ecall" : "+r"(a0_) : "r"(a7), "r"(a1_), "r"(a2_) : "memory"); + `res` = (int)a0_; + """.} return res when defined(RUMPK_KERNEL): # ========================================================= # KERNEL IMPLEMENTATION # ========================================================= - - type - SockState = enum - CLOSED, LISTEN, CONNECTING, ESTABLISHED, FIN_WAIT - - NexusSock = object - fd: int - pcb: pointer - state: SockState - rx_buf: array[4096, byte] - rx_len: int - accepted_pcb: pointer - accepted_pending: int32 + # SockState, NexusSock, membrane_init, pump_membrane_stack, + # glue_connect/bind/listen/accept_peek/setup_socket/write/read/close, + # glue_resolve_start/check — all provided by net_glue (imported above) var g_sockets: array[MAX_SOCKS, NexusSock] var g_sock_used: array[MAX_SOCKS, bool] - proc membrane_init*() {.importc, cdecl.} - proc pump_membrane_stack*() {.importc, cdecl.} proc rumpk_yield_internal() {.importc, cdecl.} {.emit: """ @@ -90,17 +90,6 @@ when defined(RUMPK_KERNEL): extern void trigger_http_test(void); """.} - proc glue_connect(sock: ptr NexusSock, ip: uint32, port: uint16): int {.importc, cdecl.} - proc glue_bind(sock: ptr NexusSock, port: uint16): int {.importc, cdecl.} - proc glue_listen(sock: ptr NexusSock): int {.importc, cdecl.} - proc glue_accept_peek(sock: ptr NexusSock): pointer {.importc, cdecl.} - proc glue_setup_socket(sock: ptr NexusSock, pcb: pointer) {.importc, cdecl.} - proc glue_write(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.} - proc glue_read(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.} - proc glue_close(sock: ptr NexusSock): int {.importc, cdecl.} - proc glue_resolve_start(hostname: cstring): int {.importc, cdecl.} - proc glue_resolve_check(ip_out: ptr uint32): int {.importc, cdecl.} - const MAX_FILES = 16 FILE_FD_START = 100 @@ -124,7 +113,7 @@ when defined(RUMPK_KERNEL): g_sockets[i].rx_len = 0 g_sockets[i].accepted_pcb = nil g_sockets[i].accepted_pending = 0 - + g_fd_table[fd].kind = FD_SOCKET return i return -1 @@ -140,7 +129,7 @@ when defined(RUMPK_KERNEL): if fd < 0 or fd >= 256: return false return g_fd_table[fd].kind == FD_SOCKET - proc libc_impl_socket*(domain, sock_type, proto: int): int {.exportc: "libc_impl_socket", cdecl.} = + proc libc_impl_socket*(domain, sock_type, proto: int): int {.exportc: "libc_impl_socket", cdecl.} = let idx = alloc_sock() if idx < 0: return -1 return idx + FD_OFFSET @@ -183,7 +172,7 @@ when defined(RUMPK_KERNEL): if sock.state == ESTABLISHED: return 0 return -1 - proc libc_impl_recv*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_recv", cdecl.} = + proc libc_impl_recv*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_recv", cdecl.} = let sock = get_sock(fd) if sock == nil: return -1 while true: @@ -191,13 +180,13 @@ when defined(RUMPK_KERNEL): let n = glue_read(sock, buf, int(count)) if n > 0: return n if sock.state == CLOSED: return 0 - rumpk_yield_internal() + rumpk_yield_internal() - proc libc_impl_send*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_send", cdecl.} = + proc libc_impl_send*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_send", cdecl.} = let sock = get_sock(fd) if sock == nil: return -1 let res = glue_write(sock, buf, int(count)) - pump_membrane_stack() + pump_membrane_stack() return res proc libc_impl_close_socket*(fd: int): int {.exportc: "libc_impl_close_socket", cdecl.} = @@ -214,7 +203,7 @@ when defined(RUMPK_KERNEL): # {.emit: "printf(\"[Membrane] libc_impl_getaddrinfo(node=%s, res_ptr=%p)\\n\", `node`, `res`);" .} let status = glue_resolve_start(node) var resolved = false - + if status == 0: # Cached / Done var ip_tmp: uint32 @@ -230,16 +219,16 @@ when defined(RUMPK_KERNEL): break if glue_resolve_check(addr ip) == -1: break - rumpk_yield_internal() - + rumpk_yield_internal() + if not resolved: return -1 # EAI_FAIL - + # 2. Allocate AddrInfo struct (using User Allocator? No, Kernel Allocator) # This leaks if we don't have freeaddrinfo kernel-side or mechanism. - + var ai = create(AddrInfo) var sa = create(SockAddr) - + ai.ai_family = 2 # AF_INET ai.ai_socktype = 1 # SOCK_STREAM ai.ai_protocol = 6 # IPPROTO_TCP @@ -247,7 +236,7 @@ when defined(RUMPK_KERNEL): ai.ai_addr = sa ai.ai_canonname = nil ai.ai_next = nil - + sa.sa_family = 2 # AF_INET # Port 0 (Service not implemented yet) # IP @@ -265,16 +254,16 @@ when defined(RUMPK_KERNEL): struct my_sockaddr_in *sin = (struct my_sockaddr_in *)`sa`; sin->sin_addr.s_addr = `ip`; - sin->sin_port = 0; + sin->sin_port = 0; sin->sin_family = 2; // AF_INET """.} - + if res != nil: res[] = ai - return 0 + return 0 else: return -1 - + proc libc_impl_freeaddrinfo*(res: ptr AddrInfo) {.exportc: "libc_impl_freeaddrinfo", cdecl.} = if res != nil: if res.ai_addr != nil: dealloc(res.ai_addr) @@ -302,7 +291,7 @@ when defined(RUMPK_KERNEL): proc libc_impl_read*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_read", cdecl.} = if fd == 0: return int(syscall(0x203, 0, cast[uint64](buf), count)) - + if fd >= 0 and fd < 256: if g_fd_table[fd].kind == FD_FILE: let path = cast[cstring](addr g_fd_table[fd].path[0]) @@ -341,7 +330,7 @@ else: proc open*(path: cstring, flags: int = 0): int {.importc: "open", cdecl.} proc close*(fd: int): int {.importc: "close", cdecl.} proc execv*(path: cstring, argv: pointer): int {.importc: "execv", cdecl.} - + # Manual strlen to avoid C header conflicts proc libc_strlen(s: cstring): uint64 = if s == nil: return 0 @@ -364,15 +353,17 @@ else: proc exit*(status: int) {.exportc, cdecl.} = discard syscall(0x01, uint64(status)) - while true: discard - + while true: discard + proc yield_fiber*() {.exportc: "yield", cdecl.} = discard syscall(0x100, 0) - - proc pump_membrane_stack*() {.importc, cdecl.} - proc membrane_init*() {.importc, cdecl.} + + proc pump_membrane_stack*() = + ## No-op in userland — kernel drives LwIP stack + yield_fiber() + # proc membrane_init*() {.importc, cdecl.} proc ion_user_wait_multi*(mask: uint64): int32 {.importc, cdecl.} - + proc pledge*(promises: uint64): int {.exportc, cdecl.} = return int(syscall(0x101, promises)) @@ -387,13 +378,13 @@ else: proc upgrade*(id: int, path: cstring): int {.exportc, cdecl.} = # Deprecated: Use kexec directly - return -1 - + return -1 + proc get_vfs_listing*(): seq[string] = var buf: array[4096, char] let n = readdir(addr buf[0], 4096) if n <= 0: return @[] - + result = @[] var current = "" for i in 0..payload + sizeof(struct icmp_echo_hdr), 'A', 32); iecho->chksum = inet_chksum(iecho, p->len); - + raw_sendto(ping_pcb, p, addr); pbuf_free(p); } @@ -114,6 +124,8 @@ type state*: SockState rx_buf*: array[4096, byte] rx_len*: int + accepted_pcb*: pointer + accepted_pending*: int32 # Forward declarations for LwIP callbacks @@ -132,25 +144,25 @@ proc ion_linkoutput(netif: pointer, p: pointer): int32 {.exportc, cdecl.} = struct pbuf *curr = (struct pbuf *)`p`; while (curr != NULL) { if (`offset` + curr->len > 2000) break; - + // Copy Ethernet frame directly (includes header) memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len); `offset` += curr->len; curr = curr->next; } """.} - + # Zero out VirtIO-net header (first 12 bytes - Modern with MRG_RXBUF) {.emit: """ memset((void*)`pkt`.data, 0, 12); """.} - + pkt.len = uint16(offset) # Total: 12 (VirtIO) + Ethernet frame - + if not ion_net_tx(pkt): ion_user_free(pkt) return -1 # ERR_IF - + return 0 # ERR_OK proc ion_netif_init(netif: pointer): int32 {.exportc, cdecl.} = @@ -165,13 +177,13 @@ proc ion_netif_init(netif: pointer): int32 {.exportc, cdecl.} = ni->mtu = 1500; ni->hwaddr_len = 6; ni->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP; - + // Set MAC from SysTable - ni->hwaddr[0] = `mac`[0]; - ni->hwaddr[1] = `mac`[1]; + ni->hwaddr[0] = `mac`[0]; + ni->hwaddr[1] = `mac`[1]; ni->hwaddr[2] = `mac`[2]; - ni->hwaddr[3] = `mac`[3]; - ni->hwaddr[4] = `mac`[4]; + ni->hwaddr[3] = `mac`[3]; + ni->hwaddr[4] = `mac`[4]; ni->hwaddr[5] = `mac`[5]; """.} return 0 @@ -192,67 +204,51 @@ proc membrane_init*() {.exportc, cdecl.} = last_dhcp_coarse = now last_dns_tmr = now - glue_print("[Membrane] Initialization...\n") + glue_print("[Membrane] Initialization Starting...\n") ion_user_init() - + # 1. LwIP Stack Init glue_print("[Membrane] Calling lwip_init()...\n") lwip_init() - # DIAGNOSTIC: Audit Memory Pools - {.emit: """ - extern const struct memp_desc *const memp_pools[]; - printf("[Membrane] Pool Audit (MAX=%d):\n", (int)MEMP_MAX); - for (int i = 0; i < (int)MEMP_MAX; i++) { - if (memp_pools[i] == NULL) { - printf(" [%d] NULL!\n", i); - } else { - printf(" [%d] OK\n", i); - } - } - printf("[Membrane] Enum Lookup:\n"); - printf(" MEMP_UDP_PCB: %d\n", (int)MEMP_UDP_PCB); - printf(" MEMP_TCP_PCB: %d\n", (int)MEMP_TCP_PCB); - printf(" MEMP_PBUF: %d\n", (int)MEMP_PBUF); - """.} + dns_init() - dns_init() # Initialize DNS resolver - # Set Fallback DNS (10.0.2.3 - QEMU Default) {.emit: """ static ip_addr_t dns_server; IP4_ADDR(ip_2_ip4(&dns_server), 10, 0, 2, 3); dns_setserver(0, &dns_server); """.} - - glue_print("[Membrane] DNS resolver configured with fallback 10.0.2.3\n") - - glue_print("[Membrane] lwip_init() returned. DNS Initialized.\n") - # 2. Setup Netif + glue_print("[Membrane] DNS configured (10.0.2.3)\n") + + # 2. Setup Netif with DHCP {.emit: """ static struct netif ni_static; ip4_addr_t ip, mask, gw; - // Use Static IP to stabilize test environment - IP4_ADDR(&ip, 10, 0, 2, 15); - IP4_ADDR(&mask, 255, 255, 255, 0); - IP4_ADDR(&gw, 10, 0, 2, 2); - - struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL, (netif_init_fn)ion_netif_init, (netif_input_fn)ethernet_input); - printf("[Membrane] netif_add returned: 0x%x\n", (unsigned int)res); - - netif_set_default(&ni_static); - netif_set_up(&ni_static); - - printf("[Membrane] netif_default: 0x%x | netif_list: 0x%x\n", (unsigned int)netif_default, (unsigned int)netif_list); - - // dhcp_start(&ni_static); // Bypassing DHCP - + // Start with zeros — DHCP will assign + IP4_ADDR(&ip, 0, 0, 0, 0); + IP4_ADDR(&mask, 0, 0, 0, 0); + IP4_ADDR(&gw, 0, 0, 0, 0); + + struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL, + (netif_init_fn)ion_netif_init, + (netif_input_fn)ethernet_input); + + if (res == NULL) { + printf("[Membrane] CRITICAL: netif_add FAILED!\n"); + } else { + netif_set_default(&ni_static); + netif_set_up(&ni_static); + dhcp_start(&ni_static); + printf("[Membrane] DHCP started on io0\n"); + } + `g_netif` = &ni_static; """.} - glue_print("[Membrane] Network Stack Operational (Waiting for DHCP IP...)\n") + glue_print("[Membrane] Network Stack Operational (DHCP...)\n") proc glue_get_ip*(): uint32 {.exportc, cdecl.} = ## Returns current IP address in host byte order @@ -275,7 +271,7 @@ proc glue_print_hex(v: uint64) = proc pump_membrane_stack*() {.exportc, cdecl.} = ## The Pulse of the Membrane. Call frequently to handle timers and RX. - + pump_iterations += 1 let now = sys_now() @@ -287,7 +283,7 @@ proc pump_membrane_stack*() {.exportc, cdecl.} = glue_print_hex(uint64(ip_addr)) glue_print("\n") last_notified_ip = ip_addr - + # Phase 40: Fast Trigger for Helios Probe glue_print("[Membrane] IP Found. Triggering Helios Probe...\n") {.emit: "trigger_http_test();" .} @@ -304,17 +300,17 @@ proc pump_membrane_stack*() {.exportc, cdecl.} = if (now - last_tcp_tmr >= 250) or (pump_iterations mod 25 == 0): tcp_tmr() last_tcp_tmr = now - + # ARP Timer (5s) if (now - last_arp_tmr >= 5000) or (pump_iterations mod 500 == 0): etharp_tmr() last_arp_tmr = now - + # DHCP Timers if (now - last_dhcp_fine >= 500) or (pump_iterations mod 50 == 0): dhcp_fine_tmr() last_dhcp_fine = now - + if (now - last_dhcp_coarse >= 60000) or (pump_iterations mod 6000 == 0): dhcp_coarse_tmr() last_dhcp_coarse = now @@ -327,14 +323,14 @@ proc pump_membrane_stack*() {.exportc, cdecl.} = # Phase 37a: ICMP Ping Verification if now - last_ping_time > 1000: last_ping_time = now - + if ip_addr != 0: glue_print("[Membrane] TESTING EXTERNAL REACHABILITY: PING 142.250.185.78...\n") {.emit: """ ip_addr_t target; IP4_ADDR(&target, 142, 250, 185, 78); ping_send(&target); - + // Trigger the Helios TCP Probe trigger_http_test(); """.} @@ -385,7 +381,7 @@ proc pump_membrane_stack*() {.exportc, cdecl.} = int state; // 0=CLOSED, 1=LISTEN, 2=CONNECTING, 3=ESTABLISHED unsigned char rx_buf[4096]; NI rx_len; - + // Server Fields void* accepted_pcb; int accepted_pending; @@ -398,10 +394,10 @@ proc ion_tcp_connected(arg: pointer, pcb: pointer, err: int8): int8 {.exportc, c NexusSock_C *s = (NexusSock_C *)`arg`; s->state = 3; // ESTABLISHED """.} - return 0 + return 0 proc ion_tcp_sent(arg: pointer, pcb: pointer, len: uint16): int8 {.exportc, cdecl.} = - return 0 + return 0 proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exportc, cdecl.} = if p == nil: @@ -411,12 +407,12 @@ proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exp s->state = 0; // CLOSED """.} return 0 - + # Append data to rx_buf {.emit: """ struct pbuf *curr = (struct pbuf *)`p`; NexusSock_C *s = (NexusSock_C *)`arg`; - + if (curr != NULL) { struct pbuf *q; for (q = curr; q != NULL; q = q->next) { @@ -441,12 +437,12 @@ proc ion_tcp_accept(arg: pointer, new_pcb: pointer, err: int8): int8 {.exportc, if (listener->accepted_pending == 0) { listener->accepted_pcb = `new_pcb`; listener->accepted_pending = 1; - + // Increase reference count? No, LwIP gives us ownership. - // Important: We must not set callbacks yet? - // LwIP doc: "When a new connection arrives, the accept callback function is called. + // Important: We must not set callbacks yet? + // LwIP doc: "When a new connection arrives, the accept callback function is called. // The new pcb is passed as a parameter." - + // We'll set callbacks later when libc performs the accept() syscall. } else { // Backlog full, reject? @@ -454,7 +450,7 @@ proc ion_tcp_accept(arg: pointer, new_pcb: pointer, err: int8): int8 {.exportc, return -1; // ERR_ABRT } """.} - return 0 + return 0 proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.} = # Wire LwIP callbacks to a NexusSock @@ -462,8 +458,8 @@ proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl. NexusSock_C *ns = (NexusSock_C *)`sock`; struct tcp_pcb *pcb = (struct tcp_pcb *)`pcb_ptr`; ns->pcb = pcb; - - tcp_arg(pcb, ns); + + tcp_arg(pcb, ns); tcp_recv(pcb, (tcp_recv_fn)ion_tcp_recv); tcp_sent(pcb, (tcp_sent_fn)ion_tcp_sent); // tcp_poll(pcb, ...); @@ -476,13 +472,13 @@ proc glue_connect*(sock: ptr NexusSock, ip: uint32, port: uint16): int {.exportc {.emit: """ struct tcp_pcb *pcb = tcp_new(); if (pcb == NULL) return -1; - + // Wire up glue_setup_socket(`sock`, pcb); - + ip4_addr_t remote_ip; - remote_ip.addr = `ip`; - + remote_ip.addr = `ip`; + tcp_connect(pcb, &remote_ip, `port`, (tcp_connected_fn)ion_tcp_connected); """.} return 0 @@ -492,18 +488,18 @@ proc glue_bind*(sock: ptr NexusSock, port: uint16): int {.exportc, cdecl.} = {.emit: """ struct tcp_pcb *pcb = tcp_new(); if (pcb == NULL) return -1; - + // Bind to ANY if (tcp_bind(pcb, IP_ADDR_ANY, `port`) != ERR_OK) { memp_free(MEMP_TCP_PCB, pcb); return -1; } - + // Update sock // glue_setup_socket checks validity, but here we just need to store PCB - // Because we are not connecting, we don't set recv/sent yet? + // Because we are not connecting, we don't set recv/sent yet? // Actually we need tcp_arg for accept callback. - + NexusSock_C *ns = (NexusSock_C *)`sock`; ns->pcb = pcb; tcp_arg(pcb, ns); @@ -515,7 +511,7 @@ proc glue_listen*(sock: ptr NexusSock): int {.exportc, cdecl.} = NexusSock_C *ns = (NexusSock_C *)`sock`; struct tcp_pcb *lpcb = tcp_listen((struct tcp_pcb *)ns->pcb); if (lpcb == NULL) return -1; - + ns->pcb = lpcb; // Update to listening PCB tcp_accept(lpcb, (tcp_accept_fn)ion_tcp_accept); """.} @@ -534,11 +530,11 @@ proc glue_accept_peek*(sock: ptr NexusSock): pointer {.exportc, cdecl.} = """.} return p -proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} = +proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} = {.emit: """ struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb; if (pcb == NULL) return -1; - + tcp_write(pcb, `buf`, `len`, TCP_WRITE_FLAG_COPY); tcp_output(pcb); """.} @@ -546,17 +542,17 @@ proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cd proc glue_read*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} = if sock.rx_len == 0: return 0 - + var to_read = len if to_read > sock.rx_len: to_read = sock.rx_len - + copyMem(buf, addr sock.rx_buf[0], uint64(to_read)) - + # Shift buffer (Ring buffer would be better, but this is MVP) var remaining = sock.rx_len - to_read if remaining > 0: copyMem(addr sock.rx_buf[0], addr sock.rx_buf[to_read], uint64(remaining)) - + sock.rx_len = remaining return to_read @@ -600,7 +596,7 @@ int glue_dns_check_init(void) { int glue_resolve_start(char* hostname) { // BYPASS: Mock DNS to unblock Userland // printf("[Membrane] DNS MOCK: Resolving '%s' -> 10.0.2.2\n", hostname); - + ip_addr_t ip; IP4_ADDR(ip_2_ip4(&ip), 10, 0, 2, 2); // Gateway g_dns_ip = ip; @@ -653,18 +649,17 @@ void trigger_http_test(void) { ip_addr_t google_ip; IP4_ADDR(ip_2_ip4(&google_ip), 142, 250, 185, 78); - + struct tcp_pcb *pcb = tcp_new(); if (!pcb) { printf("[Membrane] HELIOS Error: Failed to create TCP PCB\n"); return; } - + tcp_arg(pcb, NULL); tcp_recv(pcb, tcp_recv_callback); - + printf("[Membrane] HELIOS: INITIATING TCP CONNECTION to 142.250.185.78:80...\n"); tcp_connect(pcb, &google_ip, 80, tcp_connected_callback); } """.} -