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,9 +12,10 @@
|
|||
|
||||
import ../../core/ion
|
||||
|
||||
const SYS_TABLE_ADDR = 0x83000000'u64
|
||||
const SYS_TABLE_ADDR = when defined(arm64): 0x50000000'u64
|
||||
else: 0x83000000'u64
|
||||
|
||||
const
|
||||
const
|
||||
GAP = 10 # Pixels between windows
|
||||
FOCUS_COLOR = 0xFF00FFFF'u32 # Cyan border for focused window
|
||||
BG_COLOR = 0xFF101020'u32 # Dark Blue background
|
||||
|
|
@ -73,7 +74,7 @@ proc draw_border(x, y, w, h: int, color: uint32) =
|
|||
if ix >= 0 and ix < fb_w:
|
||||
if y >= 0 and y < fb_h: fb[y * fb_w + ix] = color
|
||||
if y + h - 1 >= 0 and y + h - 1 < fb_h: fb[(y + h - 1) * fb_w + ix] = color
|
||||
|
||||
|
||||
for iy in y ..< y + h:
|
||||
if iy >= 0 and iy < fb_h:
|
||||
if x >= 0 and x < fb_w: fb[iy * fb_w + x] = color
|
||||
|
|
@ -107,17 +108,17 @@ proc render_frame*(c: var Compositor) =
|
|||
let screen_x = s.x - c.view_x
|
||||
if screen_x + s.width < 0 or screen_x >= int(sys.fb_width):
|
||||
continue
|
||||
|
||||
|
||||
let screen_y = (int(sys.fb_height) - s.height) div 2
|
||||
blit_surface(s, screen_x, screen_y)
|
||||
|
||||
|
||||
if s.focused:
|
||||
draw_border(screen_x, screen_y, s.width, s.height, FOCUS_COLOR)
|
||||
|
||||
proc create_surface*(w, h: int): int32 =
|
||||
let id = hal_surface_alloc(uint32(w), uint32(h))
|
||||
if id < 0: return -1
|
||||
|
||||
|
||||
let p = hal_surface_get_ptr(id)
|
||||
if p == nil: return -1
|
||||
|
||||
|
|
@ -127,11 +128,11 @@ proc create_surface*(w, h: int): int32 =
|
|||
s.width = w
|
||||
s.height = h
|
||||
s.dirty = true
|
||||
|
||||
|
||||
c.surfaces.add(s)
|
||||
if c.surfaces.len == 1:
|
||||
c.focused_idx = 0
|
||||
|
||||
|
||||
return id
|
||||
|
||||
proc compositor_step*() =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -89,16 +92,20 @@ type
|
|||
# Phase 36.3: Shared ION (16 bytes)
|
||||
fn_ion_alloc*: proc(out_id: ptr uint16): uint64 {.cdecl.}
|
||||
fn_ion_free*: proc(id: uint16) {.cdecl.}
|
||||
|
||||
|
||||
# Phase 36.4: I/O Multiplexing (8 bytes)
|
||||
fn_wait_multi*: proc(mask: uint64): int32 {.cdecl.}
|
||||
|
||||
|
||||
# Phase 36.5: Network Hardware Info (8 bytes)
|
||||
net_mac*: array[6, byte]
|
||||
reserved_mac*: array[2, byte]
|
||||
|
||||
# Project LibWeb: LWF Sovereign Channel (16 bytes)
|
||||
s_lwf_rx*: pointer # Kernel -> User (LWF frames)
|
||||
s_lwf_tx*: pointer # User -> Kernel (LWF frames)
|
||||
|
||||
static:
|
||||
doAssert sizeof(SysTable) == 208
|
||||
doAssert sizeof(SysTable) == 240
|
||||
|
||||
var membrane_rx_ring_ptr*: ptr RingBuffer[IonPacket, 256]
|
||||
var membrane_tx_ring_ptr*: ptr RingBuffer[IonPacket, 256]
|
||||
|
|
@ -125,7 +132,7 @@ proc ion_user_init*() {.exportc.} =
|
|||
var err = "[ION-Client] ERROR: Invalid SysTable Magic!\n"
|
||||
console_write(addr err[0], uint(err.len))
|
||||
return
|
||||
|
||||
|
||||
membrane_rx_ring_ptr = cast[ptr RingBuffer[IonPacket, 256]](sys.s_rx)
|
||||
membrane_tx_ring_ptr = cast[ptr RingBuffer[IonPacket, 256]](sys.s_tx)
|
||||
membrane_cmd_ring_ptr = cast[ptr RingBuffer[CmdPacket, 256]](sys.s_cmd)
|
||||
|
|
@ -133,7 +140,7 @@ proc ion_user_init*() {.exportc.} =
|
|||
# Phase 36.2: Network rings
|
||||
membrane_net_rx_ptr = cast[ptr HAL_Ring_Input](sys.s_net_rx)
|
||||
membrane_net_tx_ptr = cast[ptr HAL_Ring_Input](sys.s_net_tx)
|
||||
|
||||
|
||||
var ok = "[ION-Client] Rings Mapped.\n"
|
||||
console_write(addr ok[0], uint(ok.len))
|
||||
else:
|
||||
|
|
@ -144,10 +151,10 @@ proc ion_user_init*() {.exportc.} =
|
|||
# Pure shared-memory slab allocator - NO kernel function calls!
|
||||
|
||||
const
|
||||
USER_SLAB_BASE = 0x83010000'u64 # Start of user packet slab in SysTable region
|
||||
USER_SLAB_BASE = SYS_TABLE_ADDR + 0x10000'u64 # Start of user packet slab in SysTable region
|
||||
USER_SLAB_COUNT = 512 # Number of packet slots
|
||||
USER_PKT_SIZE = 2048 # Size of each packet buffer
|
||||
USER_BITMAP_ADDR = 0x83000100'u64 # Bitmap stored in SysTable region (after SysTable struct)
|
||||
USER_BITMAP_ADDR = SYS_TABLE_ADDR + 0x100'u64 # Bitmap stored in SysTable region (after SysTable struct)
|
||||
|
||||
# Get pointer to shared bitmap (512 bits = 64 bytes for 512 slots)
|
||||
proc get_user_bitmap(): ptr array[64, byte] =
|
||||
|
|
@ -156,7 +163,7 @@ proc get_user_bitmap(): ptr array[64, byte] =
|
|||
proc ion_user_alloc*(out_pkt: ptr IonPacket): bool {.exportc.} =
|
||||
## Allocate packet from shared slab - pure userland, no kernel call
|
||||
let bitmap = get_user_bitmap()
|
||||
|
||||
|
||||
# Find first free slot
|
||||
for byteIdx in 0 ..< 64:
|
||||
if bitmap[byteIdx] != 0xFF: # At least one bit free
|
||||
|
|
@ -168,7 +175,7 @@ proc ion_user_alloc*(out_pkt: ptr IonPacket): bool {.exportc.} =
|
|||
if (bitmap[byteIdx] and mask) == 0:
|
||||
# Found free slot - mark as used
|
||||
bitmap[byteIdx] = bitmap[byteIdx] or mask
|
||||
|
||||
|
||||
let addr_val = USER_SLAB_BASE + uint64(slotIdx) * USER_PKT_SIZE
|
||||
out_pkt.id = uint16(slotIdx) or 0x8000
|
||||
out_pkt.phys = addr_val
|
||||
|
|
|
|||
|
|
@ -12,13 +12,15 @@
|
|||
# (C) 2026 Markus Maiwald
|
||||
|
||||
import ion_client
|
||||
import net_glue
|
||||
when defined(RUMPK_KERNEL):
|
||||
import net_glue
|
||||
export net_glue
|
||||
|
||||
# memcpy removed to avoid C header conflict
|
||||
|
||||
# --- SHARED CONSTANTS & TYPES ---
|
||||
|
||||
const
|
||||
const
|
||||
MAX_SOCKS = 32
|
||||
FD_OFFSET = 3
|
||||
# Syscalls
|
||||
|
|
@ -50,39 +52,37 @@ proc syscall*(nr: int, a0: uint64 = 0, a1: uint64 = 0, a2: uint64 = 0): int =
|
|||
let v0 = a0
|
||||
let v1 = a1
|
||||
let v2 = a2
|
||||
{.emit: """
|
||||
register unsigned long a7 __asm__("a7") = `n`;
|
||||
register unsigned long a0_ __asm__("a0") = `v0`;
|
||||
register unsigned long a1_ __asm__("a1") = `v1`;
|
||||
register unsigned long a2_ __asm__("a2") = `v2`;
|
||||
__asm__ volatile("ecall" : "+r"(a0_) : "r"(a7), "r"(a1_), "r"(a2_) : "memory");
|
||||
`res` = (int)a0_;
|
||||
""".}
|
||||
when defined(arm64):
|
||||
{.emit: """
|
||||
register unsigned long x8_ __asm__("x8") = `n`;
|
||||
register unsigned long x0_ __asm__("x0") = `v0`;
|
||||
register unsigned long x1_ __asm__("x1") = `v1`;
|
||||
register unsigned long x2_ __asm__("x2") = `v2`;
|
||||
__asm__ volatile("svc #0" : "+r"(x0_) : "r"(x8_), "r"(x1_), "r"(x2_) : "memory");
|
||||
`res` = (long)x0_;
|
||||
""".}
|
||||
else:
|
||||
{.emit: """
|
||||
register unsigned long a7 __asm__("a7") = `n`;
|
||||
register unsigned long a0_ __asm__("a0") = `v0`;
|
||||
register unsigned long a1_ __asm__("a1") = `v1`;
|
||||
register unsigned long a2_ __asm__("a2") = `v2`;
|
||||
__asm__ volatile("ecall" : "+r"(a0_) : "r"(a7), "r"(a1_), "r"(a2_) : "memory");
|
||||
`res` = (int)a0_;
|
||||
""".}
|
||||
return res
|
||||
|
||||
when defined(RUMPK_KERNEL):
|
||||
# =========================================================
|
||||
# KERNEL IMPLEMENTATION
|
||||
# =========================================================
|
||||
|
||||
type
|
||||
SockState = enum
|
||||
CLOSED, LISTEN, CONNECTING, ESTABLISHED, FIN_WAIT
|
||||
|
||||
NexusSock = object
|
||||
fd: int
|
||||
pcb: pointer
|
||||
state: SockState
|
||||
rx_buf: array[4096, byte]
|
||||
rx_len: int
|
||||
accepted_pcb: pointer
|
||||
accepted_pending: int32
|
||||
# SockState, NexusSock, membrane_init, pump_membrane_stack,
|
||||
# glue_connect/bind/listen/accept_peek/setup_socket/write/read/close,
|
||||
# glue_resolve_start/check — all provided by net_glue (imported above)
|
||||
|
||||
var g_sockets: array[MAX_SOCKS, NexusSock]
|
||||
var g_sock_used: array[MAX_SOCKS, bool]
|
||||
|
||||
proc membrane_init*() {.importc, cdecl.}
|
||||
proc pump_membrane_stack*() {.importc, cdecl.}
|
||||
proc rumpk_yield_internal() {.importc, cdecl.}
|
||||
|
||||
{.emit: """
|
||||
|
|
@ -90,17 +90,6 @@ when defined(RUMPK_KERNEL):
|
|||
extern void trigger_http_test(void);
|
||||
""".}
|
||||
|
||||
proc glue_connect(sock: ptr NexusSock, ip: uint32, port: uint16): int {.importc, cdecl.}
|
||||
proc glue_bind(sock: ptr NexusSock, port: uint16): int {.importc, cdecl.}
|
||||
proc glue_listen(sock: ptr NexusSock): int {.importc, cdecl.}
|
||||
proc glue_accept_peek(sock: ptr NexusSock): pointer {.importc, cdecl.}
|
||||
proc glue_setup_socket(sock: ptr NexusSock, pcb: pointer) {.importc, cdecl.}
|
||||
proc glue_write(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.}
|
||||
proc glue_read(sock: ptr NexusSock, buf: pointer, len: int): int {.importc, cdecl.}
|
||||
proc glue_close(sock: ptr NexusSock): int {.importc, cdecl.}
|
||||
proc glue_resolve_start(hostname: cstring): int {.importc, cdecl.}
|
||||
proc glue_resolve_check(ip_out: ptr uint32): int {.importc, cdecl.}
|
||||
|
||||
const
|
||||
MAX_FILES = 16
|
||||
FILE_FD_START = 100
|
||||
|
|
@ -124,7 +113,7 @@ when defined(RUMPK_KERNEL):
|
|||
g_sockets[i].rx_len = 0
|
||||
g_sockets[i].accepted_pcb = nil
|
||||
g_sockets[i].accepted_pending = 0
|
||||
|
||||
|
||||
g_fd_table[fd].kind = FD_SOCKET
|
||||
return i
|
||||
return -1
|
||||
|
|
@ -140,7 +129,7 @@ when defined(RUMPK_KERNEL):
|
|||
if fd < 0 or fd >= 256: return false
|
||||
return g_fd_table[fd].kind == FD_SOCKET
|
||||
|
||||
proc libc_impl_socket*(domain, sock_type, proto: int): int {.exportc: "libc_impl_socket", cdecl.} =
|
||||
proc libc_impl_socket*(domain, sock_type, proto: int): int {.exportc: "libc_impl_socket", cdecl.} =
|
||||
let idx = alloc_sock()
|
||||
if idx < 0: return -1
|
||||
return idx + FD_OFFSET
|
||||
|
|
@ -183,7 +172,7 @@ when defined(RUMPK_KERNEL):
|
|||
if sock.state == ESTABLISHED: return 0
|
||||
return -1
|
||||
|
||||
proc libc_impl_recv*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_recv", cdecl.} =
|
||||
proc libc_impl_recv*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_recv", cdecl.} =
|
||||
let sock = get_sock(fd)
|
||||
if sock == nil: return -1
|
||||
while true:
|
||||
|
|
@ -191,13 +180,13 @@ when defined(RUMPK_KERNEL):
|
|||
let n = glue_read(sock, buf, int(count))
|
||||
if n > 0: return n
|
||||
if sock.state == CLOSED: return 0
|
||||
rumpk_yield_internal()
|
||||
rumpk_yield_internal()
|
||||
|
||||
proc libc_impl_send*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_send", cdecl.} =
|
||||
proc libc_impl_send*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_send", cdecl.} =
|
||||
let sock = get_sock(fd)
|
||||
if sock == nil: return -1
|
||||
let res = glue_write(sock, buf, int(count))
|
||||
pump_membrane_stack()
|
||||
pump_membrane_stack()
|
||||
return res
|
||||
|
||||
proc libc_impl_close_socket*(fd: int): int {.exportc: "libc_impl_close_socket", cdecl.} =
|
||||
|
|
@ -214,7 +203,7 @@ when defined(RUMPK_KERNEL):
|
|||
# {.emit: "printf(\"[Membrane] libc_impl_getaddrinfo(node=%s, res_ptr=%p)\\n\", `node`, `res`);" .}
|
||||
let status = glue_resolve_start(node)
|
||||
var resolved = false
|
||||
|
||||
|
||||
if status == 0:
|
||||
# Cached / Done
|
||||
var ip_tmp: uint32
|
||||
|
|
@ -230,16 +219,16 @@ when defined(RUMPK_KERNEL):
|
|||
break
|
||||
if glue_resolve_check(addr ip) == -1:
|
||||
break
|
||||
rumpk_yield_internal()
|
||||
|
||||
rumpk_yield_internal()
|
||||
|
||||
if not resolved: return -1 # EAI_FAIL
|
||||
|
||||
|
||||
# 2. Allocate AddrInfo struct (using User Allocator? No, Kernel Allocator)
|
||||
# This leaks if we don't have freeaddrinfo kernel-side or mechanism.
|
||||
|
||||
|
||||
var ai = create(AddrInfo)
|
||||
var sa = create(SockAddr)
|
||||
|
||||
|
||||
ai.ai_family = 2 # AF_INET
|
||||
ai.ai_socktype = 1 # SOCK_STREAM
|
||||
ai.ai_protocol = 6 # IPPROTO_TCP
|
||||
|
|
@ -247,7 +236,7 @@ when defined(RUMPK_KERNEL):
|
|||
ai.ai_addr = sa
|
||||
ai.ai_canonname = nil
|
||||
ai.ai_next = nil
|
||||
|
||||
|
||||
sa.sa_family = 2 # AF_INET
|
||||
# Port 0 (Service not implemented yet)
|
||||
# IP
|
||||
|
|
@ -265,16 +254,16 @@ when defined(RUMPK_KERNEL):
|
|||
|
||||
struct my_sockaddr_in *sin = (struct my_sockaddr_in *)`sa`;
|
||||
sin->sin_addr.s_addr = `ip`;
|
||||
sin->sin_port = 0;
|
||||
sin->sin_port = 0;
|
||||
sin->sin_family = 2; // AF_INET
|
||||
""".}
|
||||
|
||||
|
||||
if res != nil:
|
||||
res[] = ai
|
||||
return 0
|
||||
return 0
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
proc libc_impl_freeaddrinfo*(res: ptr AddrInfo) {.exportc: "libc_impl_freeaddrinfo", cdecl.} =
|
||||
if res != nil:
|
||||
if res.ai_addr != nil: dealloc(res.ai_addr)
|
||||
|
|
@ -302,7 +291,7 @@ when defined(RUMPK_KERNEL):
|
|||
|
||||
proc libc_impl_read*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_read", cdecl.} =
|
||||
if fd == 0: return int(syscall(0x203, 0, cast[uint64](buf), count))
|
||||
|
||||
|
||||
if fd >= 0 and fd < 256:
|
||||
if g_fd_table[fd].kind == FD_FILE:
|
||||
let path = cast[cstring](addr g_fd_table[fd].path[0])
|
||||
|
|
@ -341,7 +330,7 @@ else:
|
|||
proc open*(path: cstring, flags: int = 0): int {.importc: "open", cdecl.}
|
||||
proc close*(fd: int): int {.importc: "close", cdecl.}
|
||||
proc execv*(path: cstring, argv: pointer): int {.importc: "execv", cdecl.}
|
||||
|
||||
|
||||
# Manual strlen to avoid C header conflicts
|
||||
proc libc_strlen(s: cstring): uint64 =
|
||||
if s == nil: return 0
|
||||
|
|
@ -364,15 +353,17 @@ else:
|
|||
|
||||
proc exit*(status: int) {.exportc, cdecl.} =
|
||||
discard syscall(0x01, uint64(status))
|
||||
while true: discard
|
||||
|
||||
while true: discard
|
||||
|
||||
proc yield_fiber*() {.exportc: "yield", cdecl.} =
|
||||
discard syscall(0x100, 0)
|
||||
|
||||
proc pump_membrane_stack*() {.importc, cdecl.}
|
||||
proc membrane_init*() {.importc, cdecl.}
|
||||
|
||||
proc pump_membrane_stack*() =
|
||||
## No-op in userland — kernel drives LwIP stack
|
||||
yield_fiber()
|
||||
# proc membrane_init*() {.importc, cdecl.}
|
||||
proc ion_user_wait_multi*(mask: uint64): int32 {.importc, cdecl.}
|
||||
|
||||
|
||||
proc pledge*(promises: uint64): int {.exportc, cdecl.} =
|
||||
return int(syscall(0x101, promises))
|
||||
|
||||
|
|
@ -387,13 +378,13 @@ else:
|
|||
|
||||
proc upgrade*(id: int, path: cstring): int {.exportc, cdecl.} =
|
||||
# Deprecated: Use kexec directly
|
||||
return -1
|
||||
|
||||
return -1
|
||||
|
||||
proc get_vfs_listing*(): seq[string] =
|
||||
var buf: array[4096, char]
|
||||
let n = readdir(addr buf[0], 4096)
|
||||
if n <= 0: return @[]
|
||||
|
||||
|
||||
result = @[]
|
||||
var current = ""
|
||||
for i in 0..<n:
|
||||
|
|
@ -415,7 +406,7 @@ else:
|
|||
proc sys_surface_get_ptr*(surf_id: int): pointer {.exportc, cdecl.} =
|
||||
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)))
|
||||
|
||||
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.} =
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
proc getaddrinfo*(node: cstring, service: cstring, hints: ptr AddrInfo, res: ptr ptr AddrInfo): int {.exportc, cdecl.} =
|
||||
# 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.} =
|
||||
# No-op for now (Kernel allocated statically/leak for MVP)
|
||||
# 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
|
||||
## Implementation: SipHash-2-4(MonolithKey, Time || CycleCount)
|
||||
## Per SPEC-805: Hash Strategy
|
||||
|
||||
|
||||
# TODO: Optimize to avoid overhead if called frequently
|
||||
let time_ns = syscall_get_time_ns()
|
||||
|
||||
|
||||
# Temporary simple mix
|
||||
return uint32(time_ns xor (time_ns shr 32))
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,20 @@ import ion_client
|
|||
# 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 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".}
|
||||
|
|
@ -96,7 +106,7 @@ void ping_send(const ip_addr_t *addr) {
|
|||
memset((char *)p->payload + sizeof(struct icmp_echo_hdr), 'A', 32);
|
||||
|
||||
iecho->chksum = inet_chksum(iecho, p->len);
|
||||
|
||||
|
||||
raw_sendto(ping_pcb, p, addr);
|
||||
pbuf_free(p);
|
||||
}
|
||||
|
|
@ -114,6 +124,8 @@ type
|
|||
state*: SockState
|
||||
rx_buf*: array[4096, byte]
|
||||
rx_len*: int
|
||||
accepted_pcb*: pointer
|
||||
accepted_pending*: int32
|
||||
|
||||
|
||||
# Forward declarations for LwIP callbacks
|
||||
|
|
@ -132,25 +144,25 @@ proc ion_linkoutput(netif: pointer, p: pointer): int32 {.exportc, cdecl.} =
|
|||
struct pbuf *curr = (struct pbuf *)`p`;
|
||||
while (curr != NULL) {
|
||||
if (`offset` + curr->len > 2000) break;
|
||||
|
||||
|
||||
// Copy Ethernet frame directly (includes header)
|
||||
memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len);
|
||||
`offset` += curr->len;
|
||||
curr = curr->next;
|
||||
}
|
||||
""".}
|
||||
|
||||
|
||||
# Zero out VirtIO-net header (first 12 bytes - Modern with MRG_RXBUF)
|
||||
{.emit: """
|
||||
memset((void*)`pkt`.data, 0, 12);
|
||||
""".}
|
||||
|
||||
|
||||
pkt.len = uint16(offset) # Total: 12 (VirtIO) + Ethernet frame
|
||||
|
||||
|
||||
if not ion_net_tx(pkt):
|
||||
ion_user_free(pkt)
|
||||
return -1 # ERR_IF
|
||||
|
||||
|
||||
return 0 # ERR_OK
|
||||
|
||||
proc ion_netif_init(netif: pointer): int32 {.exportc, cdecl.} =
|
||||
|
|
@ -165,13 +177,13 @@ proc ion_netif_init(netif: pointer): int32 {.exportc, cdecl.} =
|
|||
ni->mtu = 1500;
|
||||
ni->hwaddr_len = 6;
|
||||
ni->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP;
|
||||
|
||||
|
||||
// Set MAC from SysTable
|
||||
ni->hwaddr[0] = `mac`[0];
|
||||
ni->hwaddr[1] = `mac`[1];
|
||||
ni->hwaddr[0] = `mac`[0];
|
||||
ni->hwaddr[1] = `mac`[1];
|
||||
ni->hwaddr[2] = `mac`[2];
|
||||
ni->hwaddr[3] = `mac`[3];
|
||||
ni->hwaddr[4] = `mac`[4];
|
||||
ni->hwaddr[3] = `mac`[3];
|
||||
ni->hwaddr[4] = `mac`[4];
|
||||
ni->hwaddr[5] = `mac`[5];
|
||||
""".}
|
||||
return 0
|
||||
|
|
@ -192,67 +204,51 @@ proc membrane_init*() {.exportc, cdecl.} =
|
|||
last_dhcp_coarse = now
|
||||
last_dns_tmr = now
|
||||
|
||||
glue_print("[Membrane] Initialization...\n")
|
||||
glue_print("[Membrane] Initialization Starting...\n")
|
||||
ion_user_init()
|
||||
|
||||
|
||||
# 1. LwIP Stack Init
|
||||
glue_print("[Membrane] Calling lwip_init()...\n")
|
||||
lwip_init()
|
||||
|
||||
# DIAGNOSTIC: Audit Memory Pools
|
||||
{.emit: """
|
||||
extern const struct memp_desc *const memp_pools[];
|
||||
printf("[Membrane] Pool Audit (MAX=%d):\n", (int)MEMP_MAX);
|
||||
for (int i = 0; i < (int)MEMP_MAX; i++) {
|
||||
if (memp_pools[i] == NULL) {
|
||||
printf(" [%d] NULL!\n", i);
|
||||
} else {
|
||||
printf(" [%d] OK\n", i);
|
||||
}
|
||||
}
|
||||
printf("[Membrane] Enum Lookup:\n");
|
||||
printf(" MEMP_UDP_PCB: %d\n", (int)MEMP_UDP_PCB);
|
||||
printf(" MEMP_TCP_PCB: %d\n", (int)MEMP_TCP_PCB);
|
||||
printf(" MEMP_PBUF: %d\n", (int)MEMP_PBUF);
|
||||
""".}
|
||||
dns_init()
|
||||
|
||||
dns_init() # Initialize DNS resolver
|
||||
|
||||
# Set Fallback DNS (10.0.2.3 - QEMU Default)
|
||||
{.emit: """
|
||||
static ip_addr_t dns_server;
|
||||
IP4_ADDR(ip_2_ip4(&dns_server), 10, 0, 2, 3);
|
||||
dns_setserver(0, &dns_server);
|
||||
""".}
|
||||
|
||||
glue_print("[Membrane] DNS resolver configured with fallback 10.0.2.3\n")
|
||||
|
||||
glue_print("[Membrane] lwip_init() returned. DNS Initialized.\n")
|
||||
|
||||
# 2. Setup Netif
|
||||
glue_print("[Membrane] DNS configured (10.0.2.3)\n")
|
||||
|
||||
# 2. Setup Netif with DHCP
|
||||
{.emit: """
|
||||
static struct netif ni_static;
|
||||
ip4_addr_t ip, mask, gw;
|
||||
|
||||
// Use Static IP to stabilize test environment
|
||||
IP4_ADDR(&ip, 10, 0, 2, 15);
|
||||
IP4_ADDR(&mask, 255, 255, 255, 0);
|
||||
IP4_ADDR(&gw, 10, 0, 2, 2);
|
||||
|
||||
struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL, (netif_init_fn)ion_netif_init, (netif_input_fn)ethernet_input);
|
||||
printf("[Membrane] netif_add returned: 0x%x\n", (unsigned int)res);
|
||||
|
||||
netif_set_default(&ni_static);
|
||||
netif_set_up(&ni_static);
|
||||
|
||||
printf("[Membrane] netif_default: 0x%x | netif_list: 0x%x\n", (unsigned int)netif_default, (unsigned int)netif_list);
|
||||
|
||||
// dhcp_start(&ni_static); // Bypassing DHCP
|
||||
|
||||
// Start with zeros — DHCP will assign
|
||||
IP4_ADDR(&ip, 0, 0, 0, 0);
|
||||
IP4_ADDR(&mask, 0, 0, 0, 0);
|
||||
IP4_ADDR(&gw, 0, 0, 0, 0);
|
||||
|
||||
struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL,
|
||||
(netif_init_fn)ion_netif_init,
|
||||
(netif_input_fn)ethernet_input);
|
||||
|
||||
if (res == NULL) {
|
||||
printf("[Membrane] CRITICAL: netif_add FAILED!\n");
|
||||
} else {
|
||||
netif_set_default(&ni_static);
|
||||
netif_set_up(&ni_static);
|
||||
dhcp_start(&ni_static);
|
||||
printf("[Membrane] DHCP started on io0\n");
|
||||
}
|
||||
|
||||
`g_netif` = &ni_static;
|
||||
""".}
|
||||
|
||||
glue_print("[Membrane] Network Stack Operational (Waiting for DHCP IP...)\n")
|
||||
glue_print("[Membrane] Network Stack Operational (DHCP...)\n")
|
||||
|
||||
proc glue_get_ip*(): uint32 {.exportc, cdecl.} =
|
||||
## Returns current IP address in host byte order
|
||||
|
|
@ -275,7 +271,7 @@ proc glue_print_hex(v: uint64) =
|
|||
|
||||
proc pump_membrane_stack*() {.exportc, cdecl.} =
|
||||
## The Pulse of the Membrane. Call frequently to handle timers and RX.
|
||||
|
||||
|
||||
pump_iterations += 1
|
||||
let now = sys_now()
|
||||
|
||||
|
|
@ -287,7 +283,7 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
|
|||
glue_print_hex(uint64(ip_addr))
|
||||
glue_print("\n")
|
||||
last_notified_ip = ip_addr
|
||||
|
||||
|
||||
# Phase 40: Fast Trigger for Helios Probe
|
||||
glue_print("[Membrane] IP Found. Triggering Helios Probe...\n")
|
||||
{.emit: "trigger_http_test();" .}
|
||||
|
|
@ -304,17 +300,17 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
|
|||
if (now - last_tcp_tmr >= 250) or (pump_iterations mod 25 == 0):
|
||||
tcp_tmr()
|
||||
last_tcp_tmr = now
|
||||
|
||||
|
||||
# ARP Timer (5s)
|
||||
if (now - last_arp_tmr >= 5000) or (pump_iterations mod 500 == 0):
|
||||
etharp_tmr()
|
||||
last_arp_tmr = now
|
||||
|
||||
|
||||
# DHCP Timers
|
||||
if (now - last_dhcp_fine >= 500) or (pump_iterations mod 50 == 0):
|
||||
dhcp_fine_tmr()
|
||||
last_dhcp_fine = now
|
||||
|
||||
|
||||
if (now - last_dhcp_coarse >= 60000) or (pump_iterations mod 6000 == 0):
|
||||
dhcp_coarse_tmr()
|
||||
last_dhcp_coarse = now
|
||||
|
|
@ -327,14 +323,14 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
|
|||
# Phase 37a: ICMP Ping Verification
|
||||
if now - last_ping_time > 1000:
|
||||
last_ping_time = now
|
||||
|
||||
|
||||
if ip_addr != 0:
|
||||
glue_print("[Membrane] TESTING EXTERNAL REACHABILITY: PING 142.250.185.78...\n")
|
||||
{.emit: """
|
||||
ip_addr_t target;
|
||||
IP4_ADDR(&target, 142, 250, 185, 78);
|
||||
ping_send(&target);
|
||||
|
||||
|
||||
// Trigger the Helios TCP Probe
|
||||
trigger_http_test();
|
||||
""".}
|
||||
|
|
@ -385,7 +381,7 @@ proc pump_membrane_stack*() {.exportc, cdecl.} =
|
|||
int state; // 0=CLOSED, 1=LISTEN, 2=CONNECTING, 3=ESTABLISHED
|
||||
unsigned char rx_buf[4096];
|
||||
NI rx_len;
|
||||
|
||||
|
||||
// Server Fields
|
||||
void* accepted_pcb;
|
||||
int accepted_pending;
|
||||
|
|
@ -398,10 +394,10 @@ proc ion_tcp_connected(arg: pointer, pcb: pointer, err: int8): int8 {.exportc, c
|
|||
NexusSock_C *s = (NexusSock_C *)`arg`;
|
||||
s->state = 3; // ESTABLISHED
|
||||
""".}
|
||||
return 0
|
||||
return 0
|
||||
|
||||
proc ion_tcp_sent(arg: pointer, pcb: pointer, len: uint16): int8 {.exportc, cdecl.} =
|
||||
return 0
|
||||
return 0
|
||||
|
||||
proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exportc, cdecl.} =
|
||||
if p == nil:
|
||||
|
|
@ -411,12 +407,12 @@ proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exp
|
|||
s->state = 0; // CLOSED
|
||||
""".}
|
||||
return 0
|
||||
|
||||
|
||||
# Append data to rx_buf
|
||||
{.emit: """
|
||||
struct pbuf *curr = (struct pbuf *)`p`;
|
||||
NexusSock_C *s = (NexusSock_C *)`arg`;
|
||||
|
||||
|
||||
if (curr != NULL) {
|
||||
struct pbuf *q;
|
||||
for (q = curr; q != NULL; q = q->next) {
|
||||
|
|
@ -441,12 +437,12 @@ proc ion_tcp_accept(arg: pointer, new_pcb: pointer, err: int8): int8 {.exportc,
|
|||
if (listener->accepted_pending == 0) {
|
||||
listener->accepted_pcb = `new_pcb`;
|
||||
listener->accepted_pending = 1;
|
||||
|
||||
|
||||
// Increase reference count? No, LwIP gives us ownership.
|
||||
// Important: We must not set callbacks yet?
|
||||
// LwIP doc: "When a new connection arrives, the accept callback function is called.
|
||||
// Important: We must not set callbacks yet?
|
||||
// LwIP doc: "When a new connection arrives, the accept callback function is called.
|
||||
// The new pcb is passed as a parameter."
|
||||
|
||||
|
||||
// We'll set callbacks later when libc performs the accept() syscall.
|
||||
} else {
|
||||
// Backlog full, reject?
|
||||
|
|
@ -454,7 +450,7 @@ proc ion_tcp_accept(arg: pointer, new_pcb: pointer, err: int8): int8 {.exportc,
|
|||
return -1; // ERR_ABRT
|
||||
}
|
||||
""".}
|
||||
return 0
|
||||
return 0
|
||||
|
||||
proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.} =
|
||||
# Wire LwIP callbacks to a NexusSock
|
||||
|
|
@ -462,8 +458,8 @@ proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.
|
|||
NexusSock_C *ns = (NexusSock_C *)`sock`;
|
||||
struct tcp_pcb *pcb = (struct tcp_pcb *)`pcb_ptr`;
|
||||
ns->pcb = pcb;
|
||||
|
||||
tcp_arg(pcb, ns);
|
||||
|
||||
tcp_arg(pcb, ns);
|
||||
tcp_recv(pcb, (tcp_recv_fn)ion_tcp_recv);
|
||||
tcp_sent(pcb, (tcp_sent_fn)ion_tcp_sent);
|
||||
// tcp_poll(pcb, ...);
|
||||
|
|
@ -476,13 +472,13 @@ proc glue_connect*(sock: ptr NexusSock, ip: uint32, port: uint16): int {.exportc
|
|||
{.emit: """
|
||||
struct tcp_pcb *pcb = tcp_new();
|
||||
if (pcb == NULL) return -1;
|
||||
|
||||
|
||||
// Wire up
|
||||
glue_setup_socket(`sock`, pcb);
|
||||
|
||||
|
||||
ip4_addr_t remote_ip;
|
||||
remote_ip.addr = `ip`;
|
||||
|
||||
remote_ip.addr = `ip`;
|
||||
|
||||
tcp_connect(pcb, &remote_ip, `port`, (tcp_connected_fn)ion_tcp_connected);
|
||||
""".}
|
||||
return 0
|
||||
|
|
@ -492,18 +488,18 @@ proc glue_bind*(sock: ptr NexusSock, port: uint16): int {.exportc, cdecl.} =
|
|||
{.emit: """
|
||||
struct tcp_pcb *pcb = tcp_new();
|
||||
if (pcb == NULL) return -1;
|
||||
|
||||
|
||||
// Bind to ANY
|
||||
if (tcp_bind(pcb, IP_ADDR_ANY, `port`) != ERR_OK) {
|
||||
memp_free(MEMP_TCP_PCB, pcb);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Update sock
|
||||
// glue_setup_socket checks validity, but here we just need to store PCB
|
||||
// Because we are not connecting, we don't set recv/sent yet?
|
||||
// Because we are not connecting, we don't set recv/sent yet?
|
||||
// Actually we need tcp_arg for accept callback.
|
||||
|
||||
|
||||
NexusSock_C *ns = (NexusSock_C *)`sock`;
|
||||
ns->pcb = pcb;
|
||||
tcp_arg(pcb, ns);
|
||||
|
|
@ -515,7 +511,7 @@ proc glue_listen*(sock: ptr NexusSock): int {.exportc, cdecl.} =
|
|||
NexusSock_C *ns = (NexusSock_C *)`sock`;
|
||||
struct tcp_pcb *lpcb = tcp_listen((struct tcp_pcb *)ns->pcb);
|
||||
if (lpcb == NULL) return -1;
|
||||
|
||||
|
||||
ns->pcb = lpcb; // Update to listening PCB
|
||||
tcp_accept(lpcb, (tcp_accept_fn)ion_tcp_accept);
|
||||
""".}
|
||||
|
|
@ -534,11 +530,11 @@ proc glue_accept_peek*(sock: ptr NexusSock): pointer {.exportc, cdecl.} =
|
|||
""".}
|
||||
return p
|
||||
|
||||
proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} =
|
||||
proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} =
|
||||
{.emit: """
|
||||
struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb;
|
||||
if (pcb == NULL) return -1;
|
||||
|
||||
|
||||
tcp_write(pcb, `buf`, `len`, TCP_WRITE_FLAG_COPY);
|
||||
tcp_output(pcb);
|
||||
""".}
|
||||
|
|
@ -546,17 +542,17 @@ proc glue_write*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cd
|
|||
|
||||
proc glue_read*(sock: ptr NexusSock, buf: pointer, len: int): int {.exportc, cdecl.} =
|
||||
if sock.rx_len == 0: return 0
|
||||
|
||||
|
||||
var to_read = len
|
||||
if to_read > sock.rx_len: to_read = sock.rx_len
|
||||
|
||||
|
||||
copyMem(buf, addr sock.rx_buf[0], uint64(to_read))
|
||||
|
||||
|
||||
# Shift buffer (Ring buffer would be better, but this is MVP)
|
||||
var remaining = sock.rx_len - to_read
|
||||
if remaining > 0:
|
||||
copyMem(addr sock.rx_buf[0], addr sock.rx_buf[to_read], uint64(remaining))
|
||||
|
||||
|
||||
sock.rx_len = remaining
|
||||
return to_read
|
||||
|
||||
|
|
@ -600,7 +596,7 @@ int glue_dns_check_init(void) {
|
|||
int glue_resolve_start(char* hostname) {
|
||||
// BYPASS: Mock DNS to unblock Userland
|
||||
// printf("[Membrane] DNS MOCK: Resolving '%s' -> 10.0.2.2\n", hostname);
|
||||
|
||||
|
||||
ip_addr_t ip;
|
||||
IP4_ADDR(ip_2_ip4(&ip), 10, 0, 2, 2); // Gateway
|
||||
g_dns_ip = ip;
|
||||
|
|
@ -653,18 +649,17 @@ void trigger_http_test(void) {
|
|||
|
||||
ip_addr_t google_ip;
|
||||
IP4_ADDR(ip_2_ip4(&google_ip), 142, 250, 185, 78);
|
||||
|
||||
|
||||
struct tcp_pcb *pcb = tcp_new();
|
||||
if (!pcb) {
|
||||
printf("[Membrane] HELIOS Error: Failed to create TCP PCB\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
tcp_arg(pcb, NULL);
|
||||
tcp_recv(pcb, tcp_recv_callback);
|
||||
|
||||
|
||||
printf("[Membrane] HELIOS: INITIATING TCP CONNECTION to 142.250.185.78:80...\n");
|
||||
tcp_connect(pcb, &google_ip, 80, tcp_connected_callback);
|
||||
}
|
||||
""".}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue