418 lines
14 KiB
Nim
418 lines
14 KiB
Nim
# SPDX-License-Identifier: LSL-1.0
|
|
# Copyright (c) 2026 Markus Maiwald
|
|
# Stewardship: Self Sovereign Society Foundation
|
|
#
|
|
# This file is part of the Nexus Sovereign Core.
|
|
# See legal/LICENSE_SOVEREIGN.md for license terms.
|
|
|
|
## Nexus Membrane: POSIX Core Shim
|
|
|
|
# Markus Maiwald (Architect) | Voxis Forge (AI)
|
|
# libc.nim - Sovereign Libc for Nexus
|
|
# (C) 2026 Markus Maiwald
|
|
|
|
import ion_client
|
|
import net_glue
|
|
|
|
|
|
# memcpy removed to avoid C header conflict
|
|
|
|
|
|
|
|
proc syscall*(nr: int, a0: uint64 = 0, a1: uint64 = 0, a2: uint64 = 0): int =
|
|
var res: int
|
|
let n = cast[uint64](nr)
|
|
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_;
|
|
""".}
|
|
return res
|
|
|
|
# --- LIBC IO SHIMS ---
|
|
|
|
when not defined(RUMPK_KERNEL):
|
|
# write and execv are defined in clib.c/libnexus.a
|
|
proc write*(fd: int, buf: pointer, count: uint64): int {.importc: "write", cdecl.}
|
|
proc read*(fd: int, buf: pointer, count: uint64): int {.importc: "read", cdecl.}
|
|
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
|
|
var i: int = 0
|
|
let p = cast[ptr UncheckedArray[char]](s)
|
|
# Safe manual loop avoids external dependencies
|
|
while p[i] != '\0':
|
|
i.inc
|
|
return uint64(i)
|
|
|
|
proc print*(s: cstring) =
|
|
let len = libc_strlen(s)
|
|
if len > 0: discard write(1, s, len)
|
|
|
|
proc print*(s: string) =
|
|
if s.len > 0: discard write(1, unsafeAddr s[0], uint64(s.len))
|
|
|
|
proc readdir*(buf: pointer, max_len: uint64): int {.exportc, cdecl.} =
|
|
return int(syscall(0x202, cast[uint64](buf), max_len))
|
|
|
|
proc exit*(status: int) {.exportc, cdecl.} =
|
|
discard syscall(0x01, uint64(status))
|
|
while true: discard
|
|
|
|
proc yield_fiber*() {.exportc: "yield", cdecl.} =
|
|
discard syscall(0x100, 0)
|
|
|
|
proc pump_membrane_stack*() {.importc, cdecl.}
|
|
|
|
proc pledge*(promises: uint64): int {.exportc, cdecl.} =
|
|
return int(syscall(0x101, promises))
|
|
|
|
proc spawn*(entry: pointer, arg: uint64): int {.exportc, cdecl.} =
|
|
return int(syscall(0x500, cast[uint64](entry), arg))
|
|
|
|
proc join*(fid: int): int {.exportc, cdecl.} =
|
|
return int(syscall(0x501, uint64(fid)))
|
|
|
|
proc kexec*(entry: pointer): int {.exportc, cdecl.} =
|
|
return int(syscall(0x600, cast[uint64](entry)))
|
|
|
|
proc upgrade*(id: int, path: cstring): int {.exportc, cdecl.} =
|
|
# Deprecated: Use kexec directly
|
|
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:
|
|
if buf[i] == '\n':
|
|
if current.len > 0:
|
|
result.add(current)
|
|
current = ""
|
|
else:
|
|
current.add(buf[i])
|
|
if current.len > 0: result.add(current)
|
|
|
|
# Surface API (Glyph)
|
|
proc sys_surface_create*(width, height: int): int {.exportc, cdecl.} =
|
|
return int(syscall(0x300, uint64(width), uint64(height)))
|
|
|
|
proc sys_surface_flip*(surf_id: int = 0) {.exportc, cdecl.} =
|
|
discard syscall(0x301, uint64(surf_id))
|
|
|
|
proc sys_surface_get_ptr*(surf_id: int): pointer {.exportc, cdecl.} =
|
|
return cast[pointer](syscall(0x302, uint64(surf_id)))
|
|
|
|
# --- NETWORK SHIMS (Membrane) ---
|
|
|
|
const
|
|
MAX_SOCKS = 32
|
|
FD_OFFSET = 3
|
|
# Syscalls
|
|
SYS_SOCK_SOCKET = 0x900
|
|
SYS_SOCK_BIND = 0x901
|
|
SYS_SOCK_CONNECT= 0x902
|
|
SYS_SOCK_LISTEN = 0x903
|
|
SYS_SOCK_ACCEPT = 0x904
|
|
|
|
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
|
|
|
|
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.}
|
|
|
|
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.}
|
|
|
|
const
|
|
MAX_FILES = 16
|
|
FILE_FD_START = 100
|
|
|
|
type
|
|
FDType = enum FD_NONE, FD_SOCKET, FD_FILE
|
|
FD_Entry = object
|
|
kind: FDType
|
|
path: array[64, char] # For files
|
|
|
|
var g_fd_table: array[256, FD_Entry]
|
|
|
|
proc alloc_sock(): int =
|
|
for i in 0..<MAX_SOCKS:
|
|
if not g_sock_used[i]:
|
|
let fd = i + FD_OFFSET
|
|
g_sock_used[i] = true
|
|
g_sockets[i].fd = fd
|
|
g_sockets[i].pcb = nil
|
|
g_sockets[i].state = CLOSED
|
|
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
|
|
|
|
proc get_sock(fd: int): ptr NexusSock =
|
|
let idx = fd - FD_OFFSET
|
|
if idx < 0 or idx >= MAX_SOCKS: return nil
|
|
if not g_sock_used[idx]: return nil
|
|
if g_fd_table[fd].kind != FD_SOCKET: return nil
|
|
return addr g_sockets[idx]
|
|
|
|
proc libc_is_socket_fd*(fd: int): bool {.exportc: "libc_is_socket_fd", cdecl.} =
|
|
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.} =
|
|
let idx = alloc_sock()
|
|
if idx < 0: return -1
|
|
return idx + FD_OFFSET
|
|
|
|
proc libc_impl_bind*(fd: int, addr_ptr: pointer, len: int): int {.exportc: "libc_impl_bind", cdecl.} =
|
|
let sock = get_sock(fd)
|
|
if sock == nil: return -1
|
|
let port_ptr = cast[ptr uint16](cast[uint64](addr_ptr) + 2)
|
|
return glue_bind(sock, port_ptr[])
|
|
|
|
proc libc_impl_listen*(fd: int, backlog: int): int {.exportc: "libc_impl_listen", cdecl.} =
|
|
let sock = get_sock(fd)
|
|
if sock == nil: return -1
|
|
return glue_listen(sock)
|
|
|
|
proc libc_impl_accept*(fd: int, addr_ptr: pointer, len_ptr: pointer): int {.exportc: "libc_impl_accept", cdecl.} =
|
|
let listener = get_sock(fd)
|
|
if listener == nil: return -1
|
|
while true:
|
|
pump_membrane_stack()
|
|
let client_pcb = glue_accept_peek(listener)
|
|
if client_pcb != nil:
|
|
let client_idx = alloc_sock()
|
|
if client_idx < 0: return -1
|
|
let client = addr g_sockets[client_idx]
|
|
glue_setup_socket(client, client_pcb)
|
|
client.state = ESTABLISHED
|
|
return client.fd
|
|
rumpk_yield_internal()
|
|
|
|
proc libc_impl_connect*(fd: int, addr_ptr: pointer, len: int): int {.exportc: "libc_impl_connect", cdecl.} =
|
|
let sock = get_sock(fd)
|
|
if sock == nil: return -1
|
|
let ip_ptr = cast[ptr uint32](cast[uint64](addr_ptr) + 4)
|
|
let port_ptr = cast[ptr uint16](cast[uint64](addr_ptr) + 2)
|
|
let res = glue_connect(sock, ip_ptr[], port_ptr[])
|
|
while sock.state == CONNECTING:
|
|
pump_membrane_stack()
|
|
rumpk_yield_internal()
|
|
if sock.state == ESTABLISHED: return 0
|
|
return -1
|
|
|
|
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:
|
|
pump_membrane_stack()
|
|
let n = glue_read(sock, buf, int(count))
|
|
if n > 0: return n
|
|
if sock.state == CLOSED: return 0
|
|
rumpk_yield_internal()
|
|
|
|
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()
|
|
return res
|
|
|
|
proc libc_impl_close_socket*(fd: int): int {.exportc: "libc_impl_close_socket", cdecl.} =
|
|
let sock = get_sock(fd)
|
|
if sock == nil: return -1
|
|
discard glue_close(sock)
|
|
let idx = fd - FD_OFFSET
|
|
g_sock_used[idx] = false
|
|
return 0
|
|
|
|
# --- VFS SHIMS ---
|
|
# These route POSIX file calls to our Sovereign File System (SFS)
|
|
proc sfs_open_file*(path: cstring, flags: int): int32 {.importc, cdecl.}
|
|
proc sfs_read_file*(path: cstring, dest: pointer, max_len: int): int {.importc, cdecl.}
|
|
proc sfs_write_file*(name: cstring, data: pointer, data_len: int) {.importc, cdecl.}
|
|
|
|
proc libc_impl_open*(path: cstring, flags: int): int {.exportc: "libc_impl_open", cdecl.} =
|
|
# Find free File FD
|
|
for i in FILE_FD_START..<255:
|
|
if g_fd_table[i].kind == FD_NONE:
|
|
g_fd_table[i].kind = FD_FILE
|
|
let p = cast[ptr UncheckedArray[char]](path)
|
|
var j = 0
|
|
while p[j] != '\0' and j < 63:
|
|
g_fd_table[i].path[j] = p[j]
|
|
j += 1
|
|
g_fd_table[i].path[j] = '\0'
|
|
return i
|
|
return -1
|
|
|
|
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])
|
|
return sfs_read_file(path, buf, int(count))
|
|
elif g_fd_table[fd].kind == FD_SOCKET:
|
|
return libc_impl_recv(fd, buf, count)
|
|
return -1
|
|
|
|
proc libc_impl_write*(fd: int, buf: pointer, count: uint64): int {.exportc: "libc_impl_write", cdecl.} =
|
|
if fd == 1 or fd == 2: return int(syscall(0x204, uint64(fd), 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])
|
|
sfs_write_file(path, buf, int(count))
|
|
return int(count)
|
|
elif g_fd_table[fd].kind == FD_SOCKET:
|
|
return libc_impl_send(fd, buf, count)
|
|
return -1
|
|
|
|
proc libc_impl_close*(fd: int): int {.exportc: "libc_impl_close", cdecl.} =
|
|
if fd < 0 or fd >= 256: return -1
|
|
if g_fd_table[fd].kind == FD_SOCKET:
|
|
discard libc_impl_close_socket(fd)
|
|
g_fd_table[fd].kind = FD_NONE
|
|
return 0
|
|
|
|
else:
|
|
# USER WRAPPERS
|
|
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.} =
|
|
return int(syscall(SYS_SOCK_BIND, uint64(fd), cast[uint64](addr_ptr), uint64(len)))
|
|
|
|
proc connect*(fd: int, addr_ptr: pointer, len: int): int {.exportc, cdecl.} =
|
|
return int(syscall(SYS_SOCK_CONNECT, uint64(fd), cast[uint64](addr_ptr), uint64(len)))
|
|
|
|
proc listen*(fd: int, backlog: int): int {.exportc, cdecl.} =
|
|
return int(syscall(SYS_SOCK_LISTEN, uint64(fd), uint64(backlog)))
|
|
|
|
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.} =
|
|
return int(syscall(0x204, uint64(fd), cast[uint64](buf), count))
|
|
|
|
proc recv*(fd: int, buf: pointer, count: uint64, flags: int): int {.exportc, cdecl.} =
|
|
return int(syscall(0x203, uint64(fd), cast[uint64](buf), count))
|
|
|
|
# =========================================================
|
|
# lwIP Syscall Bridge (SPEC-701, SPEC-805)
|
|
# =========================================================
|
|
|
|
# The Graft: These C-compatible exports provide the kernel interface
|
|
# required by sys_arch.c without pulling in kernel-only code.
|
|
|
|
proc syscall_get_time_ns*(): uint64 {.exportc, cdecl.} =
|
|
## Get monotonic time in nanoseconds from kernel
|
|
## Used by lwIP's sys_now() for timer management
|
|
# TODO: Add dedicated syscall 0x700 for TIME
|
|
# For now, use rdtime directly (architecture-specific)
|
|
var ticks: uint64
|
|
{.emit: """
|
|
#if defined(__riscv)
|
|
__asm__ volatile ("rdtime %0" : "=r"(`ticks`));
|
|
// RISC-V QEMU virt: 10MHz timer -> 100ns per tick
|
|
`ticks` = `ticks` * 100;
|
|
#elif defined(__aarch64__)
|
|
__asm__ volatile ("mrs %0, cntvct_el0" : "=r"(`ticks`));
|
|
// ARM64: Assume 1GHz for now (should read cntfrq_el0)
|
|
// `ticks` = `ticks`;
|
|
#else
|
|
`ticks` = 0;
|
|
#endif
|
|
""".}
|
|
return ticks
|
|
|
|
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
|
|
|
|
let sys = get_sys_table()
|
|
|
|
# Get high-resolution time
|
|
let time_ns = syscall_get_time_ns()
|
|
|
|
# Mix time with itself (upper/lower bits)
|
|
var mix_data: array[16, byte]
|
|
copyMem(addr mix_data[0], unsafeAddr time_ns, 8)
|
|
|
|
# Add cycle counter for additional entropy
|
|
var cycles: uint64
|
|
{.emit: """
|
|
#if defined(__riscv)
|
|
__asm__ volatile ("rdcycle %0" : "=r"(`cycles`));
|
|
#else
|
|
`cycles` = 0;
|
|
#endif
|
|
""".}
|
|
copyMem(addr mix_data[8], unsafeAddr cycles, 8)
|
|
|
|
# Use SipHash with system key (SPEC-805)
|
|
# TODO: Use actual Monolith key when available
|
|
var key: array[16, byte]
|
|
for i in 0..<16:
|
|
key[i] = byte(i xor 0xAA) # Temporary key (Phase 39: Use Monolith)
|
|
|
|
var hash_out: array[16, byte]
|
|
if sys.fn_siphash != nil:
|
|
sys.fn_siphash(addr key, addr mix_data[0], 16, addr hash_out)
|
|
# Return first 32 bits
|
|
var rnd: uint32
|
|
copyMem(addr rnd, addr hash_out[0], 4)
|
|
return rnd
|
|
else:
|
|
# Fallback: XOR mixing if SipHash unavailable
|
|
return uint32(time_ns xor (time_ns shr 32) xor cycles)
|
|
|
|
proc syscall_panic*() {.exportc, cdecl, noreturn.} =
|
|
## Trigger kernel panic from lwIP assertion failure
|
|
## Routes to kernel's EXIT syscall
|
|
discard syscall(0x01, 255) # EXIT with error code 255
|
|
while true: discard # noreturn
|
|
|