rumpk/libs/membrane/libc.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