# 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: 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.. 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