# 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: Network Transport Glue # MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI) # Rumpk Phase 36: Membrane Networking (Userland High-Speed IO) import ion_client # NOTE: Do NOT import ../../core/ion - it pulls in the KERNEL-ONLY 2MB memory pool! proc debug_print(s: pointer, len: uint) {.importc: "debug_print", cdecl.} proc glue_print(s: string) = debug_print(unsafeAddr s[0], uint(s.len)) # LwIP Imports {.passC: "-Icore/rumpk/vendor/lwip/src/include".} {.passC: "-Icore/rumpk/libs/membrane/include".} # --- LwIP C Definitions --- # Since we are building with 'any' OS and freestanding, we use Emit for C structural access # but provide Nim wrappers for logic. {.emit: """ #include "lwip/init.h" #include "lwip/netif.h" #include "lwip/pbuf.h" #include "lwip/etharp.h" #include "lwip/tcp.h" #include "lwip/timeouts.h" #include "netif/ethernet.h" #include #include "lwip/dhcp.h" // If string.h is missing, we need the prototype for our clib.c implementation void* memcpy(void* dest, const void* src, size_t n); extern err_t etharp_output(struct netif *netif, struct pbuf *p, const ip4_addr_t *ipaddr); """.} proc lwip_init*() {.importc: "lwip_init", cdecl.} proc etharp_tmr*() {.importc: "etharp_tmr", cdecl.} proc tcp_tmr*() {.importc: "tcp_tmr", cdecl.} proc dhcp_fine_tmr() {.importc: "dhcp_fine_tmr", cdecl.} proc dhcp_coarse_tmr() {.importc: "dhcp_coarse_tmr", cdecl.} proc sys_now*(): uint32 {.importc: "sys_now", cdecl.} 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 # Forward declarations for LwIP callbacks proc ion_linkoutput(netif: pointer, p: pointer): int32 {.exportc, cdecl.} = ## Callback: LwIP -> Netif -> ION Ring # glue_print("[Membrane] Egress Packet\n") var pkt: IonPacket if not ion_user_alloc(addr pkt): return -1 # ERR_MEM # Copy pbuf chain into a single ION slab var offset = 0 {.emit: """ struct pbuf *curr = (struct pbuf *)`p`; while (curr != NULL) { if (`offset` + curr->len > 2000) break; memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len); `offset` += curr->len; curr = curr->next; } """.} pkt.len = uint16(offset) 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.} = {.emit: """ struct netif *ni = (struct netif *)`netif`; ni->name[0] = 'i'; ni->name[1] = 'o'; ni->output = etharp_output; ni->linkoutput = (netif_linkoutput_fn)ion_linkoutput; ni->mtu = 1500; ni->hwaddr_len = 6; ni->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_LINK_UP; // Set MAC: 00:DE:AD:BE:EF:01 (matching QEMU -netdev tap) ni->hwaddr[0] = 0x00; ni->hwaddr[1] = 0xDE; ni->hwaddr[2] = 0xAD; ni->hwaddr[3] = 0xBE; ni->hwaddr[4] = 0xEF; ni->hwaddr[5] = 0x01; """.} return 0 # --- Membrane Globals --- var g_netif: pointer var last_tcp_tmr, last_arp_tmr, last_dhcp_fine, last_dhcp_coarse: uint32 var membrane_started = false proc membrane_init*() {.exportc, cdecl.} = if membrane_started: return membrane_started = true glue_print("[Membrane] Initialization...\n") ion_user_init() # 1. LwIP Stack Init glue_print("[Membrane] Calling lwip_init()...\n") lwip_init() glue_print("[Membrane] lwip_init() returned.\n") # 2. Setup Netif {.emit: """ static struct netif ni_static; ip4_addr_t ip, mask, gw; // Phase 38: DHCP Enabled IP4_ADDR(&ip, 0, 0, 0, 0); IP4_ADDR(&mask, 0, 0, 0, 0); IP4_ADDR(&gw, 0, 0, 0, 0); netif_add(&ni_static, &ip, &mask, &gw, NULL, (netif_init_fn)ion_netif_init, (netif_input_fn)ethernet_input); netif_set_default(&ni_static); netif_set_up(&ni_static); dhcp_start(&ni_static); `g_netif` = &ni_static; """.} glue_print("[Membrane] Network Stack Operational (Waiting for DHCP IP...)\n") var last_notified_ip: uint32 = 0 proc pump_membrane_stack*() {.exportc, cdecl.} = ## The Pulse of the Membrane. Call frequently to handle timers and RX. if not ion_net_available(): return let now = sys_now() # proc kprint_hex_ext(v: uint64) {.importc: "kprint_hex", cdecl.} # kprint_hex_ext(uint64(now)) # Debug: Print time (LOUD!) # 3. Check for IP (Avoid continuous Nim string allocation/leak) var ip_addr: uint32 {.emit: "`ip_addr` = ip4_addr_get_u32(netif_ip4_addr((struct netif *)`g_netif`));".} if ip_addr != 0 and ip_addr != last_notified_ip: glue_print("[Membrane] IP STATUS CHANGE: ") # Call Zig kprint_hex directly proc kprint_hex_ext(v: uint64) {.importc: "kprint_hex", cdecl.} kprint_hex_ext(uint64(ip_addr)) glue_print("\n") last_notified_ip = ip_addr # 1. LwIP Timers (Raw API needs manual polling) if now - last_tcp_tmr >= 250: tcp_tmr() last_tcp_tmr = now if now - last_arp_tmr >= 5000: etharp_tmr() last_arp_tmr = now # DHCP Timers if now - last_dhcp_fine >= 500: # glue_print("[Membrane] DHCP Fine Timer\n") dhcp_fine_tmr() last_dhcp_fine = now if now - last_dhcp_coarse >= 60000: dhcp_coarse_tmr() last_dhcp_coarse = now # 2. RX Ingress var pkt: IonPacket while ion_net_rx(addr pkt): # Pass to LwIP {.emit: """ struct pbuf *p = pbuf_alloc(PBUF_RAW, `pkt`.len, PBUF_POOL); if (p != NULL) { pbuf_take(p, `pkt`.data, `pkt`.len); if (netif_default->input(p, netif_default) != ERR_OK) { pbuf_free(p); } } """.} ion_user_free(pkt) # --- Glue Stubs (Phase 37) --- # --- Glue Implementation (Phase 38) --- # --- Glue Implementation (Phase 40) --- # Global C definition for NexusSock to ensure visibility {.emit: """ typedef struct { NI fd; void* pcb; 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; } NexusSock_C; """.} proc ion_tcp_connected(arg: pointer, pcb: pointer, err: int8): int8 {.exportc, cdecl.} = glue_print("[Membrane] TCP Connected!\n") {.emit: """ NexusSock_C *s = (NexusSock_C *)`arg`; s->state = 3; // ESTABLISHED """.} return 0 proc ion_tcp_sent(arg: pointer, pcb: pointer, len: uint16): int8 {.exportc, cdecl.} = return 0 proc ion_tcp_recv(arg: pointer, pcb: pointer, p: pointer, err: int8): int8 {.exportc, cdecl.} = if p == nil: glue_print("[Membrane] TCP Closed by remote\n") {.emit: """ NexusSock_C *s = (NexusSock_C *)`arg`; 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) { if (s->rx_len + q->len < 4096) { memcpy(&s->rx_buf[s->rx_len], q->payload, q->len); s->rx_len += q->len; } } pbuf_free(curr); // We must cast pcb to struct tcp_pcb* for the macro/function tcp_recved((struct tcp_pcb *)`pcb`, curr->tot_len); } """.} return 0 proc ion_tcp_accept(arg: pointer, new_pcb: pointer, err: int8): int8 {.exportc, cdecl.} = # Callback when a listening socket receives a new connection {.emit: """ NexusSock_C *listener = (NexusSock_C *)`arg`; // Store the new PCB in the backlog slot // MVP: Only 1 pending connection allowed 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. // The new pcb is passed as a parameter." // We'll set callbacks later when libc performs the accept() syscall. } else { // Backlog full, reject? tcp_abort((struct tcp_pcb *)`new_pcb`); return -1; // ERR_ABRT } """.} return 0 proc glue_setup_socket*(sock: ptr NexusSock, pcb_ptr: pointer) {.exportc, cdecl.} = # Wire LwIP callbacks to a NexusSock {.emit: """ NexusSock_C *ns = (NexusSock_C *)`sock`; struct tcp_pcb *pcb = (struct tcp_pcb *)`pcb_ptr`; ns->pcb = pcb; 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, ...); """.} proc glue_connect*(sock: ptr NexusSock, ip: uint32, port: uint16): int {.exportc, cdecl.} = glue_print("[Membrane] glue_connect called\n") sock.state = CONNECTING sock.rx_len = 0 {.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`; tcp_connect(pcb, &remote_ip, `port`, (tcp_connected_fn)ion_tcp_connected); """.} return 0 proc glue_bind*(sock: ptr NexusSock, port: uint16): int {.exportc, cdecl.} = sock.state = LISTEN # Pre-state {.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? // Actually we need tcp_arg for accept callback. NexusSock_C *ns = (NexusSock_C *)`sock`; ns->pcb = pcb; tcp_arg(pcb, ns); """.} return 0 proc glue_listen*(sock: ptr NexusSock): int {.exportc, cdecl.} = {.emit: """ 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); """.} return 0 proc glue_accept_peek*(sock: ptr NexusSock): pointer {.exportc, cdecl.} = # Returns new PCB if pending, else nil var p: pointer = nil {.emit: """ NexusSock_C *ns = (NexusSock_C *)`sock`; if (ns->accepted_pending) { `p` = ns->accepted_pcb; ns->accepted_pending = 0; ns->accepted_pcb = NULL; } """.} return p 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); """.} return len 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 proc glue_close*(sock: ptr NexusSock): int {.exportc, cdecl.} = {.emit: """ struct tcp_pcb *pcb = (struct tcp_pcb *)`sock`->pcb; if (pcb != NULL) { tcp_close(pcb); `sock`->pcb = NULL; } """.} return 0