feat(membrane): dual-arch membrane, freestanding stubs, Libertaria LWF integration

This commit is contained in:
Markus Maiwald 2026-02-15 19:59:20 +01:00
parent 8d64fe2180
commit 49c58fbd94
12 changed files with 1192 additions and 200 deletions

View File

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

View File

@ -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);
}

View File

@ -1,7 +1,20 @@
// SPDX-License-Identifier: LSL-1.0
// Copyright (c) 2026 Markus Maiwald
// Stewardship: Self Sovereign Society Foundation
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdarg.h> #include <stdarg.h>
// 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 // Types needed for stubs
typedef int32_t pid_t; typedef int32_t pid_t;
typedef int32_t uid_t; typedef int32_t uid_t;
@ -15,13 +28,46 @@ struct stat {
int errno = 0; 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); 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) // LwIP Panic Handler (for Membrane stack)
extern void console_write(const void* p, size_t len); 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; } int atoi(const char* nptr) { return 0; }
double strtod(const char* nptr, char** endptr) { double strtod(const char* nptr, char** endptr) {
if (endptr) *endptr = (char*)nptr; const char *p = nptr;
return 0.0; 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; } double pow(double x, double y) { return 0.0; }
@ -55,8 +134,42 @@ double log10(double x) { return 0.0; }
#ifdef RUMPK_KERNEL #ifdef RUMPK_KERNEL
extern long k_handle_syscall(long nr, long a0, long a1, long a2); 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 #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) { long syscall(long nr, long a0, long a1, long a2) {
#ifdef RUMPK_KERNEL #ifdef RUMPK_KERNEL
return k_handle_syscall(nr, a0, a1, a2); return k_handle_syscall(nr, a0, a1, a2);
@ -222,11 +335,7 @@ int printf(const char *format, ...) {
return res; return res;
} }
int fwrite(const void *ptr, size_t size, size_t nmemb, void *stream) { // REDUNDANT REMOVED
return write(1, ptr, size * nmemb);
}
int fflush(void *stream) { return 0; }
// System stubs // System stubs
extern void nexus_yield(void); extern void nexus_yield(void);
@ -270,6 +379,16 @@ int memcmp(const void *s1, const void *s2, size_t n) {
return 0; 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) { void *memmove(void *dest, const void *src, size_t n) {
char *d = (char *)dest; char *d = (char *)dest;
const char *s = (const char *)src; 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 #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) { void console_write(const void* p, size_t len) {
hal_console_write(p, len); hal_console_write(p, len);
} }
#else #else
// User Mode: Syscall
void console_write(const void* p, size_t len) { void console_write(const void* p, size_t len) {
write(1, p, len); write(1, p, len);
} }

View File

@ -12,9 +12,10 @@
import ../../core/ion 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 GAP = 10 # Pixels between windows
FOCUS_COLOR = 0xFF00FFFF'u32 # Cyan border for focused window FOCUS_COLOR = 0xFF00FFFF'u32 # Cyan border for focused window
BG_COLOR = 0xFF101020'u32 # Dark Blue background 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 ix >= 0 and ix < fb_w:
if y >= 0 and y < fb_h: fb[y * fb_w + ix] = color 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 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: for iy in y ..< y + h:
if iy >= 0 and iy < fb_h: if iy >= 0 and iy < fb_h:
if x >= 0 and x < fb_w: fb[iy * fb_w + x] = color 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 let screen_x = s.x - c.view_x
if screen_x + s.width < 0 or screen_x >= int(sys.fb_width): if screen_x + s.width < 0 or screen_x >= int(sys.fb_width):
continue continue
let screen_y = (int(sys.fb_height) - s.height) div 2 let screen_y = (int(sys.fb_height) - s.height) div 2
blit_surface(s, screen_x, screen_y) blit_surface(s, screen_x, screen_y)
if s.focused: if s.focused:
draw_border(screen_x, screen_y, s.width, s.height, FOCUS_COLOR) draw_border(screen_x, screen_y, s.width, s.height, FOCUS_COLOR)
proc create_surface*(w, h: int): int32 = proc create_surface*(w, h: int): int32 =
let id = hal_surface_alloc(uint32(w), uint32(h)) let id = hal_surface_alloc(uint32(w), uint32(h))
if id < 0: return -1 if id < 0: return -1
let p = hal_surface_get_ptr(id) let p = hal_surface_get_ptr(id)
if p == nil: return -1 if p == nil: return -1
@ -127,11 +128,11 @@ proc create_surface*(w, h: int): int32 =
s.width = w s.width = w
s.height = h s.height = h
s.dirty = true s.dirty = true
c.surfaces.add(s) c.surfaces.add(s)
if c.surfaces.len == 1: if c.surfaces.len == 1:
c.focused_idx = 0 c.focused_idx = 0
return id return id
proc compositor_step*() = proc compositor_step*() =

View File

@ -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

View File

@ -0,0 +1,13 @@
#ifndef STDIO_H
#define STDIO_H
#include <stddef.h>
#include <stdarg.h>
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

View File

@ -0,0 +1,10 @@
#ifndef STDLIB_H
#define STDLIB_H
#include <stddef.h>
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

View File

@ -3,4 +3,16 @@
#include "lfs_config.h" #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 #endif

View File

@ -14,6 +14,7 @@
//! All memory accesses to the SysTable are through volatile pointers. //! All memory accesses to the SysTable are through volatile pointers.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
// --- Protocol Definitions (Match core/ion.nim) --- // --- Protocol Definitions (Match core/ion.nim) ---
@ -94,7 +95,11 @@ pub fn RingBuffer(comptime T: type) type {
self.data[head & mask] = item; self.data[head & mask] = item;
// Commit // 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; self.head = (head + 1) & mask;
return true; return true;
} }
@ -115,6 +120,8 @@ pub const SysTable = extern struct {
fn_vfs_list: u64, fn_vfs_list: u64,
fn_vfs_write: u64, fn_vfs_write: u64,
fn_vfs_close: u64, fn_vfs_close: u64,
fn_vfs_dup: u64,
fn_vfs_dup2: u64,
fn_log: u64, fn_log: u64,
fn_pledge: u64, fn_pledge: u64,
// Framebuffer // Framebuffer
@ -127,6 +134,7 @@ pub const SysTable = extern struct {
// Crypto // Crypto
fn_siphash: u64, fn_siphash: u64,
fn_ed25519_verify: u64, fn_ed25519_verify: u64,
fn_blake3: u64,
// Network // Network
s_net_rx: *RingBuffer(IonPacket), s_net_rx: *RingBuffer(IonPacket),
s_net_tx: *RingBuffer(IonPacket), s_net_tx: *RingBuffer(IonPacket),
@ -134,16 +142,27 @@ pub const SysTable = extern struct {
// Phase 36.3: Shared ION (16 bytes) // Phase 36.3: Shared ION (16 bytes)
fn_ion_alloc: u64, fn_ion_alloc: u64,
fn_ion_free: 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 { comptime {
if (@sizeOf(IonPacket) != 24) @compileError("IonPacket size mismatch!"); if (@sizeOf(IonPacket) != 24) @compileError("IonPacket size mismatch!");
if (@sizeOf(SysTable) != 184) { if (@sizeOf(SysTable) != 240) {
@compileError("SysTable size mismatch! Expected 184"); @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 --- // --- API ---

View File

@ -17,7 +17,8 @@
# Instead, locally declare only the types we need for userspace # Instead, locally declare only the types we need for userspace
import ../../core/ring 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) # Local type declarations (must match core/ion.nim definitions)
type type
@ -70,6 +71,8 @@ type
fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.} 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_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.}
fn_vfs_close*: proc(fd: int32): int32 {.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_log*: pointer
fn_pledge*: proc(promises: uint64): int32 {.cdecl.} fn_pledge*: proc(promises: uint64): int32 {.cdecl.}
fb_addr*: uint64 fb_addr*: uint64
@ -89,16 +92,20 @@ type
# Phase 36.3: Shared ION (16 bytes) # Phase 36.3: Shared ION (16 bytes)
fn_ion_alloc*: proc(out_id: ptr uint16): uint64 {.cdecl.} fn_ion_alloc*: proc(out_id: ptr uint16): uint64 {.cdecl.}
fn_ion_free*: proc(id: uint16) {.cdecl.} fn_ion_free*: proc(id: uint16) {.cdecl.}
# Phase 36.4: I/O Multiplexing (8 bytes) # Phase 36.4: I/O Multiplexing (8 bytes)
fn_wait_multi*: proc(mask: uint64): int32 {.cdecl.} fn_wait_multi*: proc(mask: uint64): int32 {.cdecl.}
# Phase 36.5: Network Hardware Info (8 bytes) # Phase 36.5: Network Hardware Info (8 bytes)
net_mac*: array[6, byte] net_mac*: array[6, byte]
reserved_mac*: array[2, 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: static:
doAssert sizeof(SysTable) == 208 doAssert sizeof(SysTable) == 240
var membrane_rx_ring_ptr*: ptr RingBuffer[IonPacket, 256] var membrane_rx_ring_ptr*: ptr RingBuffer[IonPacket, 256]
var membrane_tx_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" var err = "[ION-Client] ERROR: Invalid SysTable Magic!\n"
console_write(addr err[0], uint(err.len)) console_write(addr err[0], uint(err.len))
return return
membrane_rx_ring_ptr = cast[ptr RingBuffer[IonPacket, 256]](sys.s_rx) 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_tx_ring_ptr = cast[ptr RingBuffer[IonPacket, 256]](sys.s_tx)
membrane_cmd_ring_ptr = cast[ptr RingBuffer[CmdPacket, 256]](sys.s_cmd) 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 # Phase 36.2: Network rings
membrane_net_rx_ptr = cast[ptr HAL_Ring_Input](sys.s_net_rx) 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) membrane_net_tx_ptr = cast[ptr HAL_Ring_Input](sys.s_net_tx)
var ok = "[ION-Client] Rings Mapped.\n" var ok = "[ION-Client] Rings Mapped.\n"
console_write(addr ok[0], uint(ok.len)) console_write(addr ok[0], uint(ok.len))
else: else:
@ -144,10 +151,10 @@ proc ion_user_init*() {.exportc.} =
# Pure shared-memory slab allocator - NO kernel function calls! # Pure shared-memory slab allocator - NO kernel function calls!
const 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_SLAB_COUNT = 512 # Number of packet slots
USER_PKT_SIZE = 2048 # Size of each packet buffer 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) # Get pointer to shared bitmap (512 bits = 64 bytes for 512 slots)
proc get_user_bitmap(): ptr array[64, byte] = 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.} = proc ion_user_alloc*(out_pkt: ptr IonPacket): bool {.exportc.} =
## Allocate packet from shared slab - pure userland, no kernel call ## Allocate packet from shared slab - pure userland, no kernel call
let bitmap = get_user_bitmap() let bitmap = get_user_bitmap()
# Find first free slot # Find first free slot
for byteIdx in 0 ..< 64: for byteIdx in 0 ..< 64:
if bitmap[byteIdx] != 0xFF: # At least one bit free 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: if (bitmap[byteIdx] and mask) == 0:
# Found free slot - mark as used # Found free slot - mark as used
bitmap[byteIdx] = bitmap[byteIdx] or mask bitmap[byteIdx] = bitmap[byteIdx] or mask
let addr_val = USER_SLAB_BASE + uint64(slotIdx) * USER_PKT_SIZE let addr_val = USER_SLAB_BASE + uint64(slotIdx) * USER_PKT_SIZE
out_pkt.id = uint16(slotIdx) or 0x8000 out_pkt.id = uint16(slotIdx) or 0x8000
out_pkt.phys = addr_val out_pkt.phys = addr_val

View File

@ -12,13 +12,15 @@
# (C) 2026 Markus Maiwald # (C) 2026 Markus Maiwald
import ion_client import ion_client
import net_glue when defined(RUMPK_KERNEL):
import net_glue
export net_glue
# memcpy removed to avoid C header conflict # memcpy removed to avoid C header conflict
# --- SHARED CONSTANTS & TYPES --- # --- SHARED CONSTANTS & TYPES ---
const const
MAX_SOCKS = 32 MAX_SOCKS = 32
FD_OFFSET = 3 FD_OFFSET = 3
# Syscalls # Syscalls
@ -50,39 +52,37 @@ proc syscall*(nr: int, a0: uint64 = 0, a1: uint64 = 0, a2: uint64 = 0): int =
let v0 = a0 let v0 = a0
let v1 = a1 let v1 = a1
let v2 = a2 let v2 = a2
{.emit: """ when defined(arm64):
register unsigned long a7 __asm__("a7") = `n`; {.emit: """
register unsigned long a0_ __asm__("a0") = `v0`; register unsigned long x8_ __asm__("x8") = `n`;
register unsigned long a1_ __asm__("a1") = `v1`; register unsigned long x0_ __asm__("x0") = `v0`;
register unsigned long a2_ __asm__("a2") = `v2`; register unsigned long x1_ __asm__("x1") = `v1`;
__asm__ volatile("ecall" : "+r"(a0_) : "r"(a7), "r"(a1_), "r"(a2_) : "memory"); register unsigned long x2_ __asm__("x2") = `v2`;
`res` = (int)a0_; __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 return res
when defined(RUMPK_KERNEL): when defined(RUMPK_KERNEL):
# ========================================================= # =========================================================
# KERNEL IMPLEMENTATION # KERNEL IMPLEMENTATION
# ========================================================= # =========================================================
# SockState, NexusSock, membrane_init, pump_membrane_stack,
type # glue_connect/bind/listen/accept_peek/setup_socket/write/read/close,
SockState = enum # glue_resolve_start/check — all provided by net_glue (imported above)
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
var g_sockets: array[MAX_SOCKS, NexusSock] var g_sockets: array[MAX_SOCKS, NexusSock]
var g_sock_used: array[MAX_SOCKS, bool] 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.} proc rumpk_yield_internal() {.importc, cdecl.}
{.emit: """ {.emit: """
@ -90,17 +90,6 @@ when defined(RUMPK_KERNEL):
extern void trigger_http_test(void); 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 const
MAX_FILES = 16 MAX_FILES = 16
FILE_FD_START = 100 FILE_FD_START = 100
@ -124,7 +113,7 @@ when defined(RUMPK_KERNEL):
g_sockets[i].rx_len = 0 g_sockets[i].rx_len = 0
g_sockets[i].accepted_pcb = nil g_sockets[i].accepted_pcb = nil
g_sockets[i].accepted_pending = 0 g_sockets[i].accepted_pending = 0
g_fd_table[fd].kind = FD_SOCKET g_fd_table[fd].kind = FD_SOCKET
return i return i
return -1 return -1
@ -140,7 +129,7 @@ when defined(RUMPK_KERNEL):
if fd < 0 or fd >= 256: return false if fd < 0 or fd >= 256: return false
return g_fd_table[fd].kind == FD_SOCKET 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() let idx = alloc_sock()
if idx < 0: return -1 if idx < 0: return -1
return idx + FD_OFFSET return idx + FD_OFFSET
@ -183,7 +172,7 @@ when defined(RUMPK_KERNEL):
if sock.state == ESTABLISHED: return 0 if sock.state == ESTABLISHED: return 0
return -1 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) let sock = get_sock(fd)
if sock == nil: return -1 if sock == nil: return -1
while true: while true:
@ -191,13 +180,13 @@ when defined(RUMPK_KERNEL):
let n = glue_read(sock, buf, int(count)) let n = glue_read(sock, buf, int(count))
if n > 0: return n if n > 0: return n
if sock.state == CLOSED: return 0 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) let sock = get_sock(fd)
if sock == nil: return -1 if sock == nil: return -1
let res = glue_write(sock, buf, int(count)) let res = glue_write(sock, buf, int(count))
pump_membrane_stack() pump_membrane_stack()
return res return res
proc libc_impl_close_socket*(fd: int): int {.exportc: "libc_impl_close_socket", cdecl.} = 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`);" .} # {.emit: "printf(\"[Membrane] libc_impl_getaddrinfo(node=%s, res_ptr=%p)\\n\", `node`, `res`);" .}
let status = glue_resolve_start(node) let status = glue_resolve_start(node)
var resolved = false var resolved = false
if status == 0: if status == 0:
# Cached / Done # Cached / Done
var ip_tmp: uint32 var ip_tmp: uint32
@ -230,16 +219,16 @@ when defined(RUMPK_KERNEL):
break break
if glue_resolve_check(addr ip) == -1: if glue_resolve_check(addr ip) == -1:
break break
rumpk_yield_internal() rumpk_yield_internal()
if not resolved: return -1 # EAI_FAIL if not resolved: return -1 # EAI_FAIL
# 2. Allocate AddrInfo struct (using User Allocator? No, Kernel Allocator) # 2. Allocate AddrInfo struct (using User Allocator? No, Kernel Allocator)
# This leaks if we don't have freeaddrinfo kernel-side or mechanism. # This leaks if we don't have freeaddrinfo kernel-side or mechanism.
var ai = create(AddrInfo) var ai = create(AddrInfo)
var sa = create(SockAddr) var sa = create(SockAddr)
ai.ai_family = 2 # AF_INET ai.ai_family = 2 # AF_INET
ai.ai_socktype = 1 # SOCK_STREAM ai.ai_socktype = 1 # SOCK_STREAM
ai.ai_protocol = 6 # IPPROTO_TCP ai.ai_protocol = 6 # IPPROTO_TCP
@ -247,7 +236,7 @@ when defined(RUMPK_KERNEL):
ai.ai_addr = sa ai.ai_addr = sa
ai.ai_canonname = nil ai.ai_canonname = nil
ai.ai_next = nil ai.ai_next = nil
sa.sa_family = 2 # AF_INET sa.sa_family = 2 # AF_INET
# Port 0 (Service not implemented yet) # Port 0 (Service not implemented yet)
# IP # IP
@ -265,16 +254,16 @@ when defined(RUMPK_KERNEL):
struct my_sockaddr_in *sin = (struct my_sockaddr_in *)`sa`; struct my_sockaddr_in *sin = (struct my_sockaddr_in *)`sa`;
sin->sin_addr.s_addr = `ip`; sin->sin_addr.s_addr = `ip`;
sin->sin_port = 0; sin->sin_port = 0;
sin->sin_family = 2; // AF_INET sin->sin_family = 2; // AF_INET
""".} """.}
if res != nil: if res != nil:
res[] = ai res[] = ai
return 0 return 0
else: else:
return -1 return -1
proc libc_impl_freeaddrinfo*(res: ptr AddrInfo) {.exportc: "libc_impl_freeaddrinfo", cdecl.} = proc libc_impl_freeaddrinfo*(res: ptr AddrInfo) {.exportc: "libc_impl_freeaddrinfo", cdecl.} =
if res != nil: if res != nil:
if res.ai_addr != nil: dealloc(res.ai_addr) 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.} = 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: return int(syscall(0x203, 0, cast[uint64](buf), count))
if fd >= 0 and fd < 256: if fd >= 0 and fd < 256:
if g_fd_table[fd].kind == FD_FILE: if g_fd_table[fd].kind == FD_FILE:
let path = cast[cstring](addr g_fd_table[fd].path[0]) 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 open*(path: cstring, flags: int = 0): int {.importc: "open", cdecl.}
proc close*(fd: int): int {.importc: "close", cdecl.} proc close*(fd: int): int {.importc: "close", cdecl.}
proc execv*(path: cstring, argv: pointer): int {.importc: "execv", cdecl.} proc execv*(path: cstring, argv: pointer): int {.importc: "execv", cdecl.}
# Manual strlen to avoid C header conflicts # Manual strlen to avoid C header conflicts
proc libc_strlen(s: cstring): uint64 = proc libc_strlen(s: cstring): uint64 =
if s == nil: return 0 if s == nil: return 0
@ -364,15 +353,17 @@ else:
proc exit*(status: int) {.exportc, cdecl.} = proc exit*(status: int) {.exportc, cdecl.} =
discard syscall(0x01, uint64(status)) discard syscall(0x01, uint64(status))
while true: discard while true: discard
proc yield_fiber*() {.exportc: "yield", cdecl.} = proc yield_fiber*() {.exportc: "yield", cdecl.} =
discard syscall(0x100, 0) discard syscall(0x100, 0)
proc pump_membrane_stack*() {.importc, cdecl.} proc pump_membrane_stack*() =
proc membrane_init*() {.importc, cdecl.} ## 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 ion_user_wait_multi*(mask: uint64): int32 {.importc, cdecl.}
proc pledge*(promises: uint64): int {.exportc, cdecl.} = proc pledge*(promises: uint64): int {.exportc, cdecl.} =
return int(syscall(0x101, promises)) return int(syscall(0x101, promises))
@ -387,13 +378,13 @@ else:
proc upgrade*(id: int, path: cstring): int {.exportc, cdecl.} = proc upgrade*(id: int, path: cstring): int {.exportc, cdecl.} =
# Deprecated: Use kexec directly # Deprecated: Use kexec directly
return -1 return -1
proc get_vfs_listing*(): seq[string] = proc get_vfs_listing*(): seq[string] =
var buf: array[4096, char] var buf: array[4096, char]
let n = readdir(addr buf[0], 4096) let n = readdir(addr buf[0], 4096)
if n <= 0: return @[] if n <= 0: return @[]
result = @[] result = @[]
var current = "" var current = ""
for i in 0..<n: for i in 0..<n:
@ -415,7 +406,7 @@ else:
proc sys_surface_get_ptr*(surf_id: int): pointer {.exportc, cdecl.} = proc sys_surface_get_ptr*(surf_id: int): pointer {.exportc, cdecl.} =
return cast[pointer](syscall(0x302, uint64(surf_id))) return cast[pointer](syscall(0x302, uint64(surf_id)))
proc socket*(domain, sock_type, protocol: int): int {.exportc, cdecl.} = proc socket*(domain, sock_type, protocol: int): int {.exportc, cdecl.} =
return int(syscall(SYS_SOCK_SOCKET, uint64(domain), uint64(sock_type), uint64(protocol))) return int(syscall(SYS_SOCK_SOCKET, uint64(domain), uint64(sock_type), uint64(protocol)))
proc bind_socket*(fd: int, addr_ptr: pointer, len: int): int {.exportc: "bind", cdecl.} = proc bind_socket*(fd: int, addr_ptr: pointer, len: int): int {.exportc: "bind", cdecl.} =
@ -430,16 +421,16 @@ else:
proc accept*(fd: int, addr_ptr: pointer, len_ptr: pointer): int {.exportc, cdecl.} = proc accept*(fd: int, addr_ptr: pointer, len_ptr: pointer): int {.exportc, cdecl.} =
return int(syscall(SYS_SOCK_ACCEPT, uint64(fd), cast[uint64](addr_ptr), cast[uint64](len_ptr))) return int(syscall(SYS_SOCK_ACCEPT, uint64(fd), cast[uint64](addr_ptr), cast[uint64](len_ptr)))
proc send*(fd: int, buf: pointer, count: uint64, flags: int): int {.exportc, cdecl.} = proc send*(fd: int, buf: pointer, count: uint64, flags: int): int {.exportc, cdecl.} =
return int(syscall(0x204, uint64(fd), cast[uint64](buf), count)) return int(syscall(0x204, uint64(fd), cast[uint64](buf), count))
proc recv*(fd: int, buf: pointer, count: uint64, flags: int): int {.exportc, cdecl.} = proc recv*(fd: int, buf: pointer, count: uint64, flags: int): int {.exportc, cdecl.} =
return int(syscall(0x203, uint64(fd), cast[uint64](buf), count)) return int(syscall(0x203, uint64(fd), cast[uint64](buf), count))
proc getaddrinfo*(node: cstring, service: cstring, hints: ptr AddrInfo, res: ptr ptr AddrInfo): int {.exportc, cdecl.} = proc getaddrinfo*(node: cstring, service: cstring, hints: ptr AddrInfo, res: ptr ptr AddrInfo): int {.exportc, cdecl.} =
# Syscall 0x905 # Syscall 0x905
return int(syscall(SYS_SOCK_RESOLVE, cast[uint64](node), cast[uint64](service), cast[uint64](res))) return int(syscall(SYS_SOCK_RESOLVE, cast[uint64](node), cast[uint64](service), cast[uint64](res)))
proc freeaddrinfo*(res: ptr AddrInfo) {.exportc, cdecl.} = proc freeaddrinfo*(res: ptr AddrInfo) {.exportc, cdecl.} =
# No-op for now (Kernel allocated statically/leak for MVP) # No-op for now (Kernel allocated statically/leak for MVP)
# Or implement Syscall 0x906 if needed. # Or implement Syscall 0x906 if needed.
@ -462,10 +453,10 @@ proc syscall_get_random*(): uint32 {.exportc, cdecl.} =
## Generate cryptographically strong random number for TCP ISN ## Generate cryptographically strong random number for TCP ISN
## Implementation: SipHash-2-4(MonolithKey, Time || CycleCount) ## Implementation: SipHash-2-4(MonolithKey, Time || CycleCount)
## Per SPEC-805: Hash Strategy ## Per SPEC-805: Hash Strategy
# TODO: Optimize to avoid overhead if called frequently # TODO: Optimize to avoid overhead if called frequently
let time_ns = syscall_get_time_ns() let time_ns = syscall_get_time_ns()
# Temporary simple mix # Temporary simple mix
return uint32(time_ns xor (time_ns shr 32)) return uint32(time_ns xor (time_ns shr 32))

View File

@ -14,10 +14,20 @@ import ion_client
# NOTE: Do NOT import ../../core/ion - it pulls in the KERNEL-ONLY 2MB memory pool! # NOTE: Do NOT import ../../core/ion - it pulls in the KERNEL-ONLY 2MB memory pool!
proc console_write(s: pointer, len: csize_t) {.importc: "console_write", cdecl.} proc console_write(s: pointer, len: csize_t) {.importc: "console_write", cdecl.}
proc glue_print(s: string) = proc glue_print(s: string) =
console_write(unsafeAddr s[0], csize_t(s.len)) console_write(unsafeAddr s[0], csize_t(s.len))
# =========================================================
# lwIP Syscall Bridge (kernel-native, no ecalls)
# syscall_get_time_ns and syscall_panic provided by clib.c
# =========================================================
proc rumpk_timer_now_ns(): uint64 {.importc, cdecl.}
proc syscall_get_random*(): uint32 {.exportc, cdecl.} =
let t = rumpk_timer_now_ns()
return uint32(t xor (t shr 32))
# LwIP Imports # LwIP Imports
{.passC: "-Icore/rumpk/vendor/lwip/src/include".} {.passC: "-Icore/rumpk/vendor/lwip/src/include".}
{.passC: "-Icore/rumpk/libs/membrane/include".} {.passC: "-Icore/rumpk/libs/membrane/include".}
@ -96,7 +106,7 @@ void ping_send(const ip_addr_t *addr) {
memset((char *)p->payload + sizeof(struct icmp_echo_hdr), 'A', 32); memset((char *)p->payload + sizeof(struct icmp_echo_hdr), 'A', 32);
iecho->chksum = inet_chksum(iecho, p->len); iecho->chksum = inet_chksum(iecho, p->len);
raw_sendto(ping_pcb, p, addr); raw_sendto(ping_pcb, p, addr);
pbuf_free(p); pbuf_free(p);
} }
@ -114,6 +124,8 @@ type
state*: SockState state*: SockState
rx_buf*: array[4096, byte] rx_buf*: array[4096, byte]
rx_len*: int rx_len*: int
accepted_pcb*: pointer
accepted_pending*: int32
# Forward declarations for LwIP callbacks # 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`; struct pbuf *curr = (struct pbuf *)`p`;
while (curr != NULL) { while (curr != NULL) {
if (`offset` + curr->len > 2000) break; if (`offset` + curr->len > 2000) break;
// Copy Ethernet frame directly (includes header) // Copy Ethernet frame directly (includes header)
memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len); memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len);
`offset` += curr->len; `offset` += curr->len;
curr = curr->next; curr = curr->next;
} }
""".} """.}
# Zero out VirtIO-net header (first 12 bytes - Modern with MRG_RXBUF) # Zero out VirtIO-net header (first 12 bytes - Modern with MRG_RXBUF)
{.emit: """ {.emit: """
memset((void*)`pkt`.data, 0, 12); memset((void*)`pkt`.data, 0, 12);
""".} """.}
pkt.len = uint16(offset) # Total: 12 (VirtIO) + Ethernet frame pkt.len = uint16(offset) # Total: 12 (VirtIO) + Ethernet frame
if not ion_net_tx(pkt): if not ion_net_tx(pkt):
ion_user_free(pkt) ion_user_free(pkt)
return -1 # ERR_IF return -1 # ERR_IF
return 0 # ERR_OK return 0 # ERR_OK
proc ion_netif_init(netif: pointer): int32 {.exportc, cdecl.} = 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->mtu = 1500;
ni->hwaddr_len = 6; ni->hwaddr_len = 6;
ni->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP; ni->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP;
// Set MAC from SysTable // Set MAC from SysTable
ni->hwaddr[0] = `mac`[0]; ni->hwaddr[0] = `mac`[0];
ni->hwaddr[1] = `mac`[1]; ni->hwaddr[1] = `mac`[1];
ni->hwaddr[2] = `mac`[2]; ni->hwaddr[2] = `mac`[2];
ni->hwaddr[3] = `mac`[3]; ni->hwaddr[3] = `mac`[3];
ni->hwaddr[4] = `mac`[4]; ni->hwaddr[4] = `mac`[4];
ni->hwaddr[5] = `mac`[5]; ni->hwaddr[5] = `mac`[5];
""".} """.}
return 0 return 0
@ -192,67 +204,51 @@ proc membrane_init*() {.exportc, cdecl.} =
last_dhcp_coarse = now last_dhcp_coarse = now
last_dns_tmr = now last_dns_tmr = now
glue_print("[Membrane] Initialization...\n") glue_print("[Membrane] Initialization Starting...\n")
ion_user_init() ion_user_init()
# 1. LwIP Stack Init # 1. LwIP Stack Init
glue_print("[Membrane] Calling lwip_init()...\n") glue_print("[Membrane] Calling lwip_init()...\n")
lwip_init() lwip_init()
# DIAGNOSTIC: Audit Memory Pools dns_init()
{.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() # Initialize DNS resolver
# Set Fallback DNS (10.0.2.3 - QEMU Default) # Set Fallback DNS (10.0.2.3 - QEMU Default)
{.emit: """ {.emit: """
static ip_addr_t dns_server; static ip_addr_t dns_server;
IP4_ADDR(ip_2_ip4(&dns_server), 10, 0, 2, 3); IP4_ADDR(ip_2_ip4(&dns_server), 10, 0, 2, 3);
dns_setserver(0, &dns_server); 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: """ {.emit: """
static struct netif ni_static; static struct netif ni_static;
ip4_addr_t ip, mask, gw; ip4_addr_t ip, mask, gw;
// Use Static IP to stabilize test environment // Start with zeros — DHCP will assign
IP4_ADDR(&ip, 10, 0, 2, 15); IP4_ADDR(&ip, 0, 0, 0, 0);
IP4_ADDR(&mask, 255, 255, 255, 0); IP4_ADDR(&mask, 0, 0, 0, 0);
IP4_ADDR(&gw, 10, 0, 2, 2); 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); struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL,
printf("[Membrane] netif_add returned: 0x%x\n", (unsigned int)res); (netif_init_fn)ion_netif_init,
(netif_input_fn)ethernet_input);
netif_set_default(&ni_static);
netif_set_up(&ni_static); if (res == NULL) {
printf("[Membrane] CRITICAL: netif_add FAILED!\n");
printf("[Membrane] netif_default: 0x%x | netif_list: 0x%x\n", (unsigned int)netif_default, (unsigned int)netif_list); } else {
netif_set_default(&ni_static);
// dhcp_start(&ni_static); // Bypassing DHCP netif_set_up(&ni_static);
dhcp_start(&ni_static);
printf("[Membrane] DHCP started on io0\n");
}
`g_netif` = &ni_static; `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.} = proc glue_get_ip*(): uint32 {.exportc, cdecl.} =
## Returns current IP address in host byte order ## Returns current IP address in host byte order
@ -275,7 +271,7 @@ proc glue_print_hex(v: uint64) =
proc pump_membrane_stack*() {.exportc, cdecl.} = proc pump_membrane_stack*() {.exportc, cdecl.} =
## The Pulse of the Membrane. Call frequently to handle timers and RX. ## The Pulse of the Membrane. Call frequently to handle timers and RX.
pump_iterations += 1 pump_iterations += 1
let now = sys_now() let now = sys_now()
@ -287,7 +283,7 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
glue_print_hex(uint64(ip_addr)) glue_print_hex(uint64(ip_addr))
glue_print("\n") glue_print("\n")
last_notified_ip = ip_addr last_notified_ip = ip_addr
# Phase 40: Fast Trigger for Helios Probe # Phase 40: Fast Trigger for Helios Probe
glue_print("[Membrane] IP Found. Triggering Helios Probe...\n") glue_print("[Membrane] IP Found. Triggering Helios Probe...\n")
{.emit: "trigger_http_test();" .} {.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): if (now - last_tcp_tmr >= 250) or (pump_iterations mod 25 == 0):
tcp_tmr() tcp_tmr()
last_tcp_tmr = now last_tcp_tmr = now
# ARP Timer (5s) # ARP Timer (5s)
if (now - last_arp_tmr >= 5000) or (pump_iterations mod 500 == 0): if (now - last_arp_tmr >= 5000) or (pump_iterations mod 500 == 0):
etharp_tmr() etharp_tmr()
last_arp_tmr = now last_arp_tmr = now
# DHCP Timers # DHCP Timers
if (now - last_dhcp_fine >= 500) or (pump_iterations mod 50 == 0): if (now - last_dhcp_fine >= 500) or (pump_iterations mod 50 == 0):
dhcp_fine_tmr() dhcp_fine_tmr()
last_dhcp_fine = now last_dhcp_fine = now
if (now - last_dhcp_coarse >= 60000) or (pump_iterations mod 6000 == 0): if (now - last_dhcp_coarse >= 60000) or (pump_iterations mod 6000 == 0):
dhcp_coarse_tmr() dhcp_coarse_tmr()
last_dhcp_coarse = now last_dhcp_coarse = now
@ -327,14 +323,14 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
# Phase 37a: ICMP Ping Verification # Phase 37a: ICMP Ping Verification
if now - last_ping_time > 1000: if now - last_ping_time > 1000:
last_ping_time = now last_ping_time = now
if ip_addr != 0: if ip_addr != 0:
glue_print("[Membrane] TESTING EXTERNAL REACHABILITY: PING 142.250.185.78...\n") glue_print("[Membrane] TESTING EXTERNAL REACHABILITY: PING 142.250.185.78...\n")
{.emit: """ {.emit: """
ip_addr_t target; ip_addr_t target;
IP4_ADDR(&target, 142, 250, 185, 78); IP4_ADDR(&target, 142, 250, 185, 78);
ping_send(&target); ping_send(&target);
// Trigger the Helios TCP Probe // Trigger the Helios TCP Probe
trigger_http_test(); trigger_http_test();
""".} """.}
@ -385,7 +381,7 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
int state; // 0=CLOSED, 1=LISTEN, 2=CONNECTING, 3=ESTABLISHED int state; // 0=CLOSED, 1=LISTEN, 2=CONNECTING, 3=ESTABLISHED
unsigned char rx_buf[4096]; unsigned char rx_buf[4096];
NI rx_len; NI rx_len;
// Server Fields // Server Fields
void* accepted_pcb; void* accepted_pcb;
int accepted_pending; 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`; NexusSock_C *s = (NexusSock_C *)`arg`;
s->state = 3; // ESTABLISHED s->state = 3; // ESTABLISHED
""".} """.}
return 0 return 0
proc ion_tcp_sent(arg: pointer, pcb: pointer, len: uint16): int8 {.exportc, cdecl.} = 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.} = proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exportc, cdecl.} =
if p == nil: 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 s->state = 0; // CLOSED
""".} """.}
return 0 return 0
# Append data to rx_buf # Append data to rx_buf
{.emit: """ {.emit: """
struct pbuf *curr = (struct pbuf *)`p`; struct pbuf *curr = (struct pbuf *)`p`;
NexusSock_C *s = (NexusSock_C *)`arg`; NexusSock_C *s = (NexusSock_C *)`arg`;
if (curr != NULL) { if (curr != NULL) {
struct pbuf *q; struct pbuf *q;
for (q = curr; q != NULL; q = q->next) { 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) { if (listener->accepted_pending == 0) {
listener->accepted_pcb = `new_pcb`; listener->accepted_pcb = `new_pcb`;
listener->accepted_pending = 1; listener->accepted_pending = 1;
// Increase reference count? No, LwIP gives us ownership. // Increase reference count? No, LwIP gives us ownership.
// Important: We must not set callbacks yet? // Important: We must not set callbacks yet?
// LwIP doc: "When a new connection arrives, the accept callback function is called. // LwIP doc: "When a new connection arrives, the accept callback function is called.
// The new pcb is passed as a parameter." // The new pcb is passed as a parameter."
// We'll set callbacks later when libc performs the accept() syscall. // We'll set callbacks later when libc performs the accept() syscall.
} else { } else {
// Backlog full, reject? // 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 -1; // ERR_ABRT
} }
""".} """.}
return 0 return 0
proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.} = proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.} =
# Wire LwIP callbacks to a NexusSock # 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`; NexusSock_C *ns = (NexusSock_C *)`sock`;
struct tcp_pcb *pcb = (struct tcp_pcb *)`pcb_ptr`; struct tcp_pcb *pcb = (struct tcp_pcb *)`pcb_ptr`;
ns->pcb = pcb; ns->pcb = pcb;
tcp_arg(pcb, ns); tcp_arg(pcb, ns);
tcp_recv(pcb, (tcp_recv_fn)ion_tcp_recv); tcp_recv(pcb, (tcp_recv_fn)ion_tcp_recv);
tcp_sent(pcb, (tcp_sent_fn)ion_tcp_sent); tcp_sent(pcb, (tcp_sent_fn)ion_tcp_sent);
// tcp_poll(pcb, ...); // tcp_poll(pcb, ...);
@ -476,13 +472,13 @@ proc glue_connect*(sock: ptr NexusSock, ip: uint32, port: uint16): int {.exportc
{.emit: """ {.emit: """
struct tcp_pcb *pcb = tcp_new(); struct tcp_pcb *pcb = tcp_new();
if (pcb == NULL) return -1; if (pcb == NULL) return -1;
// Wire up // Wire up
glue_setup_socket(`sock`, pcb); glue_setup_socket(`sock`, pcb);
ip4_addr_t remote_ip; 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); tcp_connect(pcb, &remote_ip, `port`, (tcp_connected_fn)ion_tcp_connected);
""".} """.}
return 0 return 0
@ -492,18 +488,18 @@ proc glue_bind*(sock: ptr NexusSock, port: uint16): int {.exportc, cdecl.} =
{.emit: """ {.emit: """
struct tcp_pcb *pcb = tcp_new(); struct tcp_pcb *pcb = tcp_new();
if (pcb == NULL) return -1; if (pcb == NULL) return -1;
// Bind to ANY // Bind to ANY
if (tcp_bind(pcb, IP_ADDR_ANY, `port`) != ERR_OK) { if (tcp_bind(pcb, IP_ADDR_ANY, `port`) != ERR_OK) {
memp_free(MEMP_TCP_PCB, pcb); memp_free(MEMP_TCP_PCB, pcb);
return -1; return -1;
} }
// Update sock // Update sock
// glue_setup_socket checks validity, but here we just need to store PCB // 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. // Actually we need tcp_arg for accept callback.
NexusSock_C *ns = (NexusSock_C *)`sock`; NexusSock_C *ns = (NexusSock_C *)`sock`;
ns->pcb = pcb; ns->pcb = pcb;
tcp_arg(pcb, ns); tcp_arg(pcb, ns);
@ -515,7 +511,7 @@ proc glue_listen*(sock: ptr NexusSock): int {.exportc, cdecl.} =
NexusSock_C *ns = (NexusSock_C *)`sock`; NexusSock_C *ns = (NexusSock_C *)`sock`;
struct tcp_pcb *lpcb = tcp_listen((struct tcp_pcb *)ns->pcb); struct tcp_pcb *lpcb = tcp_listen((struct tcp_pcb *)ns->pcb);
if (lpcb == NULL) return -1; if (lpcb == NULL) return -1;
ns->pcb = lpcb; // Update to listening PCB ns->pcb = lpcb; // Update to listening PCB
tcp_accept(lpcb, (tcp_accept_fn)ion_tcp_accept); 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 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: """ {.emit: """
struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb; struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb;
if (pcb == NULL) return -1; if (pcb == NULL) return -1;
tcp_write(pcb, `buf`, `len`, TCP_WRITE_FLAG_COPY); tcp_write(pcb, `buf`, `len`, TCP_WRITE_FLAG_COPY);
tcp_output(pcb); 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.} = proc glue_read*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} =
if sock.rx_len == 0: return 0 if sock.rx_len == 0: return 0
var to_read = len var to_read = len
if to_read > sock.rx_len: to_read = sock.rx_len if to_read > sock.rx_len: to_read = sock.rx_len
copyMem(buf, addr sock.rx_buf[0], uint64(to_read)) copyMem(buf, addr sock.rx_buf[0], uint64(to_read))
# Shift buffer (Ring buffer would be better, but this is MVP) # Shift buffer (Ring buffer would be better, but this is MVP)
var remaining = sock.rx_len - to_read var remaining = sock.rx_len - to_read
if remaining > 0: if remaining > 0:
copyMem(addr sock.rx_buf[0], addr sock.rx_buf[to_read], uint64(remaining)) copyMem(addr sock.rx_buf[0], addr sock.rx_buf[to_read], uint64(remaining))
sock.rx_len = remaining sock.rx_len = remaining
return to_read return to_read
@ -600,7 +596,7 @@ int glue_dns_check_init(void) {
int glue_resolve_start(char* hostname) { int glue_resolve_start(char* hostname) {
// BYPASS: Mock DNS to unblock Userland // BYPASS: Mock DNS to unblock Userland
// printf("[Membrane] DNS MOCK: Resolving '%s' -> 10.0.2.2\n", hostname); // printf("[Membrane] DNS MOCK: Resolving '%s' -> 10.0.2.2\n", hostname);
ip_addr_t ip; ip_addr_t ip;
IP4_ADDR(ip_2_ip4(&ip), 10, 0, 2, 2); // Gateway IP4_ADDR(ip_2_ip4(&ip), 10, 0, 2, 2); // Gateway
g_dns_ip = ip; g_dns_ip = ip;
@ -653,18 +649,17 @@ void trigger_http_test(void) {
ip_addr_t google_ip; ip_addr_t google_ip;
IP4_ADDR(ip_2_ip4(&google_ip), 142, 250, 185, 78); IP4_ADDR(ip_2_ip4(&google_ip), 142, 250, 185, 78);
struct tcp_pcb *pcb = tcp_new(); struct tcp_pcb *pcb = tcp_new();
if (!pcb) { if (!pcb) {
printf("[Membrane] HELIOS Error: Failed to create TCP PCB\n"); printf("[Membrane] HELIOS Error: Failed to create TCP PCB\n");
return; return;
} }
tcp_arg(pcb, NULL); tcp_arg(pcb, NULL);
tcp_recv(pcb, tcp_recv_callback); tcp_recv(pcb, tcp_recv_callback);
printf("[Membrane] HELIOS: INITIATING TCP CONNECTION to 142.250.185.78:80...\n"); printf("[Membrane] HELIOS: INITIATING TCP CONNECTION to 142.250.185.78:80...\n");
tcp_connect(pcb, &google_ip, 80, tcp_connected_callback); tcp_connect(pcb, &google_ip, 80, tcp_connected_callback);
} }
""".} """.}