feat(membrane): dual-arch membrane, freestanding stubs, Libertaria LWF integration
This commit is contained in:
parent
8d64fe2180
commit
49c58fbd94
|
|
@ -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]);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ---
|
||||
|
||||
|
|
|
|||
|
|
@ -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] =
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@
|
|||
# (C) 2026 Markus Maiwald
|
||||
|
||||
import ion_client
|
||||
when defined(RUMPK_KERNEL):
|
||||
import net_glue
|
||||
export net_glue
|
||||
|
||||
# memcpy removed to avoid C header conflict
|
||||
|
||||
|
|
@ -50,6 +52,16 @@ proc syscall*(nr: int, a0: uint64 = 0, a1: uint64 = 0, a2: uint64 = 0): int =
|
|||
let v0 = a0
|
||||
let v1 = a1
|
||||
let v2 = a2
|
||||
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`;
|
||||
|
|
@ -64,25 +76,13 @@ 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.} =
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
if (res == NULL) {
|
||||
printf("[Membrane] CRITICAL: netif_add FAILED!\n");
|
||||
} else {
|
||||
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
|
||||
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);
|
||||
}
|
||||
""".}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue