666 lines
19 KiB
Nim
666 lines
19 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: 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 console_write(s: pointer, len: csize_t) {.importc: "console_write", cdecl.}
|
|
|
|
proc glue_print(s: string) =
|
|
console_write(unsafeAddr s[0], csize_t(s.len))
|
|
|
|
# =========================================================
|
|
# lwIP Syscall Bridge (kernel-native, no ecalls)
|
|
# syscall_get_time_ns and syscall_panic provided by clib.c
|
|
# =========================================================
|
|
proc rumpk_timer_now_ns(): uint64 {.importc, cdecl.}
|
|
|
|
proc syscall_get_random*(): uint32 {.exportc, cdecl.} =
|
|
let t = rumpk_timer_now_ns()
|
|
return uint32(t xor (t shr 32))
|
|
|
|
# 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 "lwip/raw.h"
|
|
#include "lwip/icmp.h"
|
|
#include "lwip/inet_chksum.h"
|
|
|
|
#include <string.h>
|
|
#include "lwip/dhcp.h"
|
|
#include "lwip/dns.h"
|
|
|
|
// Externs
|
|
extern int printf(const char *format, ...);
|
|
""".}
|
|
|
|
proc lwip_init*() {.importc: "lwip_init", cdecl.}
|
|
proc dns_init*() {.importc: "dns_init", cdecl.}
|
|
proc dns_tmr*() {.importc: "dns_tmr", 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.}
|
|
|
|
{.emit: """
|
|
// --- PING IMPLEMENTATION ---
|
|
static struct raw_pcb *ping_pcb;
|
|
static u16_t ping_seq_num;
|
|
|
|
const char* lwip_strerr(err_t err) { return "LwIP Error"; }
|
|
|
|
static u8_t ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr) {
|
|
LWIP_UNUSED_ARG(arg);
|
|
LWIP_UNUSED_ARG(pcb);
|
|
if (p->tot_len >= sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr)) {
|
|
printf("[Membrane] PING REPLY from %s: %d bytes\n", ipaddr_ntoa(addr), p->tot_len);
|
|
}
|
|
pbuf_free(p);
|
|
return 1; // Eat the packet
|
|
}
|
|
|
|
void ping_send(const ip_addr_t *addr) {
|
|
if (!ping_pcb) {
|
|
ping_pcb = raw_new(IP_PROTO_ICMP);
|
|
if (ping_pcb) {
|
|
raw_recv(ping_pcb, ping_recv, NULL);
|
|
raw_bind(ping_pcb, IP_ADDR_ANY);
|
|
}
|
|
}
|
|
if (!ping_pcb) return;
|
|
|
|
struct pbuf *p = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + 32, PBUF_RAM);
|
|
if (!p) return;
|
|
|
|
struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)p->payload;
|
|
ICMPH_TYPE_SET(iecho, ICMP_ECHO);
|
|
ICMPH_CODE_SET(iecho, 0);
|
|
iecho->chksum = 0;
|
|
iecho->id = 0xAFAF;
|
|
iecho->seqno = lwip_htons(++ping_seq_num);
|
|
|
|
// Fill payload
|
|
memset((char *)p->payload + sizeof(struct icmp_echo_hdr), 'A', 32);
|
|
|
|
iecho->chksum = inet_chksum(iecho, p->len);
|
|
|
|
raw_sendto(ping_pcb, p, addr);
|
|
pbuf_free(p);
|
|
}
|
|
""".}
|
|
|
|
# ... (Types and ION hooks) ...
|
|
|
|
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
|
|
|
|
|
|
# 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
|
|
# LwIP provides complete Ethernet frames (14-byte header + payload)
|
|
# VirtIO-net requires 12-byte header at start of buffer (Modern with MRG_RXBUF)
|
|
var offset = 12 # Start after VirtIO header space
|
|
{.emit: """
|
|
struct pbuf *curr = (struct pbuf *)`p`;
|
|
while (curr != NULL) {
|
|
if (`offset` + curr->len > 2000) break;
|
|
|
|
// Copy Ethernet frame directly (includes header)
|
|
memcpy((void*)((uintptr_t)`pkt`.data + `offset`), curr->payload, curr->len);
|
|
`offset` += curr->len;
|
|
curr = curr->next;
|
|
}
|
|
""".}
|
|
|
|
# Zero out VirtIO-net header (first 12 bytes - Modern with MRG_RXBUF)
|
|
{.emit: """
|
|
memset((void*)`pkt`.data, 0, 12);
|
|
""".}
|
|
|
|
pkt.len = uint16(offset) # Total: 12 (VirtIO) + Ethernet frame
|
|
|
|
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.} =
|
|
let mac = ion_get_mac()
|
|
glue_print("[Membrane] Configuring Interface with Hardware MAC\n")
|
|
{.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 from SysTable
|
|
ni->hwaddr[0] = `mac`[0];
|
|
ni->hwaddr[1] = `mac`[1];
|
|
ni->hwaddr[2] = `mac`[2];
|
|
ni->hwaddr[3] = `mac`[3];
|
|
ni->hwaddr[4] = `mac`[4];
|
|
ni->hwaddr[5] = `mac`[5];
|
|
""".}
|
|
return 0
|
|
|
|
# --- Membrane Globals ---
|
|
var g_netif: pointer
|
|
var last_tcp_tmr, last_arp_tmr, last_dhcp_fine, last_dhcp_coarse, last_dns_tmr: uint32
|
|
|
|
var membrane_started = false
|
|
|
|
proc membrane_init*() {.exportc, cdecl.} =
|
|
if membrane_started: return
|
|
membrane_started = true
|
|
let now = sys_now()
|
|
last_tcp_tmr = now
|
|
last_arp_tmr = now
|
|
last_dhcp_fine = now
|
|
last_dhcp_coarse = now
|
|
last_dns_tmr = now
|
|
|
|
glue_print("[Membrane] Initialization Starting...\n")
|
|
ion_user_init()
|
|
|
|
# 1. LwIP Stack Init
|
|
glue_print("[Membrane] Calling lwip_init()...\n")
|
|
lwip_init()
|
|
|
|
dns_init()
|
|
|
|
# Set Fallback DNS (10.0.2.3 - QEMU Default)
|
|
{.emit: """
|
|
static ip_addr_t dns_server;
|
|
IP4_ADDR(ip_2_ip4(&dns_server), 10, 0, 2, 3);
|
|
dns_setserver(0, &dns_server);
|
|
""".}
|
|
|
|
glue_print("[Membrane] DNS configured (10.0.2.3)\n")
|
|
|
|
# 2. Setup Netif with DHCP
|
|
{.emit: """
|
|
static struct netif ni_static;
|
|
ip4_addr_t ip, mask, gw;
|
|
|
|
// Start with zeros — DHCP will assign
|
|
IP4_ADDR(&ip, 0, 0, 0, 0);
|
|
IP4_ADDR(&mask, 0, 0, 0, 0);
|
|
IP4_ADDR(&gw, 0, 0, 0, 0);
|
|
|
|
struct netif *res = netif_add(&ni_static, &ip, &mask, &gw, NULL,
|
|
(netif_init_fn)ion_netif_init,
|
|
(netif_input_fn)ethernet_input);
|
|
|
|
if (res == NULL) {
|
|
printf("[Membrane] CRITICAL: netif_add FAILED!\n");
|
|
} else {
|
|
netif_set_default(&ni_static);
|
|
netif_set_up(&ni_static);
|
|
dhcp_start(&ni_static);
|
|
printf("[Membrane] DHCP started on io0\n");
|
|
}
|
|
|
|
`g_netif` = &ni_static;
|
|
""".}
|
|
|
|
glue_print("[Membrane] Network Stack Operational (DHCP...)\n")
|
|
|
|
proc glue_get_ip*(): uint32 {.exportc, cdecl.} =
|
|
## Returns current IP address in host byte order
|
|
{.emit: "return ip4_addr_get_u32(netif_ip4_addr((struct netif *)`g_netif`));".}
|
|
|
|
var last_notified_ip: uint32 = 0
|
|
var last_ping_time: uint32 = 0
|
|
var pump_iterations: uint64 = 0
|
|
|
|
proc glue_print_hex(v: uint64) =
|
|
const hex_chars = "0123456789ABCDEF"
|
|
var buf: array[20, char]
|
|
buf[0] = '0'; buf[1] = 'x'
|
|
var val = v
|
|
for i in countdown(15, 0):
|
|
buf[2+i] = hex_chars[int(val and 0xF)]
|
|
val = val shr 4
|
|
buf[18] = '\n'; buf[19] = '\0'
|
|
console_write(addr buf[0], 20)
|
|
|
|
proc pump_membrane_stack*() {.exportc, cdecl.} =
|
|
## The Pulse of the Membrane. Call frequently to handle timers and RX.
|
|
|
|
pump_iterations += 1
|
|
let now = sys_now()
|
|
|
|
# 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: ")
|
|
glue_print_hex(uint64(ip_addr))
|
|
glue_print("\n")
|
|
last_notified_ip = ip_addr
|
|
|
|
# Phase 40: Fast Trigger for Helios Probe
|
|
glue_print("[Membrane] IP Found. Triggering Helios Probe...\n")
|
|
{.emit: "trigger_http_test();" .}
|
|
|
|
# 1. LwIP Timers (Raw API needs manual polling)
|
|
{.emit: """
|
|
static int debug_tick = 0;
|
|
if (debug_tick++ % 1000 == 0) {
|
|
printf("[Membrane] sys_now: %u (iters=%llu)\n", `now`, `pump_iterations`);
|
|
}
|
|
""".}
|
|
|
|
# TCP Timer (250ms)
|
|
if (now - last_tcp_tmr >= 250) or (pump_iterations mod 25 == 0):
|
|
tcp_tmr()
|
|
last_tcp_tmr = now
|
|
|
|
# ARP Timer (5s)
|
|
if (now - last_arp_tmr >= 5000) or (pump_iterations mod 500 == 0):
|
|
etharp_tmr()
|
|
last_arp_tmr = now
|
|
|
|
# DHCP Timers
|
|
if (now - last_dhcp_fine >= 500) or (pump_iterations mod 50 == 0):
|
|
dhcp_fine_tmr()
|
|
last_dhcp_fine = now
|
|
|
|
if (now - last_dhcp_coarse >= 60000) or (pump_iterations mod 6000 == 0):
|
|
dhcp_coarse_tmr()
|
|
last_dhcp_coarse = now
|
|
|
|
# DNS Timer (1s)
|
|
if (now - last_dns_tmr >= 1000) or (pump_iterations mod 100 == 0):
|
|
dns_tmr()
|
|
last_dns_tmr = now
|
|
|
|
# Phase 37a: ICMP Ping Verification
|
|
if now - last_ping_time > 1000:
|
|
last_ping_time = now
|
|
|
|
if ip_addr != 0:
|
|
glue_print("[Membrane] TESTING EXTERNAL REACHABILITY: PING 142.250.185.78...\n")
|
|
{.emit: """
|
|
ip_addr_t target;
|
|
IP4_ADDR(&target, 142, 250, 185, 78);
|
|
ping_send(&target);
|
|
|
|
// Trigger the Helios TCP Probe
|
|
trigger_http_test();
|
|
""".}
|
|
|
|
# 2. RX Ingress
|
|
var pkt: IonPacket
|
|
# glue_print("[Membrane] Exit Pump\n")
|
|
while ion_net_rx(addr pkt):
|
|
# glue_print("[Membrane] Ingress Packet\n")
|
|
# DEBUG: Hex dump first 32 bytes (Disabled for Ping Test)
|
|
# {.emit: """
|
|
# printf("[Membrane] RX Hex Dump (first 32 bytes):\n");
|
|
# for (int i = 0; i < 32 && i < `pkt`.len; i++) {
|
|
# printf("%02x ", `pkt`.data[i]);
|
|
# if ((i + 1) % 16 == 0) printf("\n");
|
|
# }
|
|
# printf("\n");
|
|
# """.}
|
|
# Pass to LwIP
|
|
{.emit: """
|
|
struct pbuf *p = pbuf_alloc(PBUF_RAW, `pkt`.len, PBUF_POOL);
|
|
if (p != NULL) {
|
|
if (`pkt`.data == NULL) {
|
|
printf("[Membrane] ERROR: Ingress pkt.data is NULL!\n");
|
|
pbuf_free(p);
|
|
} else {
|
|
pbuf_take(p, (void*)((uintptr_t)`pkt`.data), `pkt`.len);
|
|
if (netif_default->input(p, netif_default) != ERR_OK) {
|
|
pbuf_free(p);
|
|
}
|
|
}
|
|
} else {
|
|
printf("[Membrane] CRITICAL: pbuf_alloc FAILED! (POOL OOM?)\n");
|
|
}
|
|
""".}
|
|
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
|
|
|
|
# --- DNS GLUE (C Implementation) ---
|
|
{.emit: """
|
|
static ip_addr_t g_dns_ip;
|
|
static int g_dns_status = 0; // 0=idle, 1=pending, 2=done, -1=error
|
|
|
|
static void my_dns_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
|
|
if (ipaddr != NULL) {
|
|
g_dns_ip = *ipaddr;
|
|
g_dns_status = 2; // Success
|
|
} else {
|
|
g_dns_status = -1; // Error
|
|
}
|
|
}
|
|
|
|
// Check if DNS is properly initialized
|
|
int glue_dns_check_init(void) {
|
|
// We can't directly access dns_pcbs[] as it's static in dns.c
|
|
// Instead, we'll try to get the DNS server, which will fail if DNS isn't init'd
|
|
const ip_addr_t *ns = dns_getserver(0);
|
|
if (ns == NULL) {
|
|
printf("[Membrane] DNS ERROR: dns_getserver returned NULL\\n");
|
|
return -1;
|
|
}
|
|
// If we got here, DNS subsystem is at least partially initialized
|
|
return 0;
|
|
}
|
|
|
|
int glue_resolve_start(char* hostname) {
|
|
// BYPASS: Mock DNS to unblock Userland
|
|
// printf("[Membrane] DNS MOCK: Resolving '%s' -> 10.0.2.2\n", hostname);
|
|
|
|
ip_addr_t ip;
|
|
IP4_ADDR(ip_2_ip4(&ip), 10, 0, 2, 2); // Gateway
|
|
g_dns_ip = ip;
|
|
g_dns_status = 2; // Done
|
|
return 0;
|
|
}
|
|
|
|
int glue_resolve_check(u32_t *ip_out) {
|
|
if (g_dns_status == 1) return 1;
|
|
if (g_dns_status == 2) {
|
|
*ip_out = ip4_addr_get_u32(&g_dns_ip);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// --- HELIOS PROBE (TCP REAChABILITY TEST) ---
|
|
static err_t tcp_recv_callback(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
|
|
if (p != NULL) {
|
|
printf("[Membrane] HELIOS: TCP RECEIVED DATA: %d bytes\n", p->tot_len);
|
|
// Print first 32 bytes of response
|
|
printf("[Membrane] HELIOS: Response Peek: ");
|
|
for(int i=0; i<32 && i<p->tot_len; i++) {
|
|
char c = ((char*)p->payload)[i];
|
|
if (c >= 32 && c <= 126) printf("%c", c);
|
|
else printf(".");
|
|
}
|
|
printf("\n");
|
|
tcp_recved(pcb, p->tot_len);
|
|
pbuf_free(p);
|
|
} else {
|
|
printf("[Membrane] HELIOS: TCP CONNECTION CLOSED by Remote.\n");
|
|
tcp_close(pcb);
|
|
}
|
|
return ERR_OK;
|
|
}
|
|
|
|
static err_t tcp_connected_callback(void *arg, struct tcp_pcb *pcb, err_t err) {
|
|
printf("[Membrane] HELIOS: TCP CONNECTED! Sending GET Request...\n");
|
|
const char *request = "GET / HTTP/1.0\r\nHost: google.com\r\nUser-Agent: NexusOS/1.0\r\n\r\n";
|
|
tcp_write(pcb, request, strlen(request), TCP_WRITE_FLAG_COPY);
|
|
tcp_output(pcb);
|
|
return ERR_OK;
|
|
}
|
|
|
|
void trigger_http_test(void) {
|
|
static int triggered = 0;
|
|
if (triggered) return;
|
|
triggered = 1;
|
|
|
|
ip_addr_t google_ip;
|
|
IP4_ADDR(ip_2_ip4(&google_ip), 142, 250, 185, 78);
|
|
|
|
struct tcp_pcb *pcb = tcp_new();
|
|
if (!pcb) {
|
|
printf("[Membrane] HELIOS Error: Failed to create TCP PCB\n");
|
|
return;
|
|
}
|
|
|
|
tcp_arg(pcb, NULL);
|
|
tcp_recv(pcb, tcp_recv_callback);
|
|
|
|
printf("[Membrane] HELIOS: INITIATING TCP CONNECTION to 142.250.185.78:80...\n");
|
|
tcp_connect(pcb, &google_ip, 80, tcp_connected_callback);
|
|
}
|
|
""".}
|