468 lines
16 KiB
Nim
468 lines
16 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
|
|
when defined(RUMPK_KERNEL):
|
|
import net_glue
|
|
export net_glue
|
|
|
|
# memcpy removed to avoid C header conflict
|
|
|
|
# --- SHARED CONSTANTS & TYPES ---
|
|
|
|
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
|
|
SYS_SOCK_RESOLVE = 0x905
|
|
|
|
type
|
|
SockAddr* = object
|
|
sa_family*: uint16
|
|
sa_data*: array[14, char]
|
|
|
|
AddrInfo* = object
|
|
ai_flags*: cint
|
|
ai_family*: cint
|
|
ai_socktype*: cint
|
|
ai_protocol*: cint
|
|
ai_addrlen*: uint32
|
|
ai_addr*: ptr SockAddr
|
|
ai_canonname*: cstring
|
|
ai_next*: ptr AddrInfo
|
|
|
|
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
|
|
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
|
|
# =========================================================
|
|
# 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 rumpk_yield_internal() {.importc, cdecl.}
|
|
|
|
{.emit: """
|
|
extern int printf(const char *format, ...);
|
|
extern void trigger_http_test(void);
|
|
""".}
|
|
|
|
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
|
|
|
|
proc libc_impl_getaddrinfo*(node: cstring, service: cstring, hints: ptr AddrInfo, res: ptr ptr AddrInfo): int {.exportc: "libc_impl_getaddrinfo", cdecl.} =
|
|
# 1. Resolve Hostname
|
|
var ip: uint32
|
|
# {.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
|
|
if glue_resolve_check(addr ip_tmp) == 0:
|
|
ip = ip_tmp
|
|
resolved = true
|
|
elif status == 1:
|
|
# Pending
|
|
while true:
|
|
pump_membrane_stack()
|
|
if glue_resolve_check(addr ip) == 0:
|
|
resolved = true
|
|
break
|
|
if glue_resolve_check(addr ip) == -1:
|
|
break
|
|
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
|
|
ai.ai_addrlen = 16
|
|
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
|
|
{.emit: """
|
|
// Manual definition for NO_SYS/Freestanding
|
|
struct my_in_addr {
|
|
unsigned int s_addr;
|
|
};
|
|
struct my_sockaddr_in {
|
|
unsigned short sin_family;
|
|
unsigned short sin_port;
|
|
struct my_in_addr sin_addr;
|
|
char sin_zero[8];
|
|
};
|
|
|
|
struct my_sockaddr_in *sin = (struct my_sockaddr_in *)`sa`;
|
|
sin->sin_addr.s_addr = `ip`;
|
|
sin->sin_port = 0;
|
|
sin->sin_family = 2; // AF_INET
|
|
""".}
|
|
|
|
if res != nil:
|
|
res[] = ai
|
|
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)
|
|
dealloc(res)
|
|
|
|
# --- 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:
|
|
# =========================================================
|
|
# USERLAND SHIMS AND WRAPPERS
|
|
# =========================================================
|
|
|
|
# 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*() =
|
|
## 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))
|
|
|
|
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)))
|
|
|
|
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))
|
|
|
|
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)))
|
|
|
|
proc freeaddrinfo*(res: ptr AddrInfo) {.exportc, cdecl.} =
|
|
# No-op for now (Kernel allocated statically/leak for MVP)
|
|
# Or implement Syscall 0x906 if needed.
|
|
discard
|
|
|
|
# =========================================================
|
|
# 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: "syscall_get_time_ns", cdecl.} =
|
|
## Get monotonic time in nanoseconds from kernel
|
|
## Used by lwIP's sys_now() for timer management
|
|
return uint64(syscall(0x66))
|
|
|
|
|
|
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))
|
|
|
|
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
|