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 <stdint.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
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);
}

View File

@ -12,7 +12,8 @@
import ../../core/ion
const SYS_TABLE_ADDR = 0x83000000'u64
const SYS_TABLE_ADDR = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
const
GAP = 10 # Pixels between windows

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"
#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

View File

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

View File

@ -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
@ -97,8 +100,12 @@ type
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]
@ -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] =

View File

@ -12,7 +12,9 @@
# (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
@ -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
@ -369,8 +358,10 @@ else:
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.} =

View File

@ -18,6 +18,16 @@ proc console_write(s: pointer, len: csize_t) {.importc: "console_write", cdecl.}
proc glue_print(s: string) =
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
{.passC: "-Icore/rumpk/vendor/lwip/src/include".}
{.passC: "-Icore/rumpk/libs/membrane/include".}
@ -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
@ -192,31 +204,14 @@ 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() # Initialize DNS resolver
dns_init()
# Set Fallback DNS (10.0.2.3 - QEMU Default)
{.emit: """
@ -225,34 +220,35 @@ proc membrane_init*() {.exportc, cdecl.} =
dns_setserver(0, &dns_server);
""".}
glue_print("[Membrane] DNS resolver configured with fallback 10.0.2.3\n")
glue_print("[Membrane] DNS configured (10.0.2.3)\n")
glue_print("[Membrane] lwip_init() returned. DNS Initialized.\n")
# 2. Setup Netif
# 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);
// 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);
printf("[Membrane] netif_add returned: 0x%x\n", (unsigned int)res);
struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL,
(netif_init_fn)ion_netif_init,
(netif_input_fn)ethernet_input);
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
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
@ -667,4 +663,3 @@ void trigger_http_test(void) {
tcp_connect(pcb, &google_ip, 80, tcp_connected_callback);
}
""".}