diff --git a/nipbox.nim b/nipbox.nim index 3cc2cf6..a513c1b 100644 --- a/nipbox.nim +++ b/nipbox.nim @@ -12,9 +12,49 @@ import strutils, parseutils, tables, sequtils, json import kdl import ../../libs/membrane/libc as lb import ../../libs/membrane/libc_net as lnet -import ../../libs/membrane/fs/sfs_user as sfs +when not defined(NIPBOX_LITE): + import ../../libs/membrane/fs/sfs_user as sfs import editor -import ../../libs/membrane/term # Phase 26: Visual Cortex +when not defined(NIPBOX_LITE): + import ../../libs/membrane/term # Phase 26: Visual Cortex + +# --- M4.4: BKDL Capability Manifest (SPEC-071) --- +{.emit: """ +__attribute__((section(".nexus.manifest"), used)) +static const unsigned char nexus_manifest[166] = { + /* BkdlHeader (118 bytes) */ + 0x53, 0x55, 0x58, 0x4E, /* magic: "NXUS" (LE) */ + 0x01, 0x00, /* version: 1 */ + 0x00, 0x00, /* flags: 0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* signature[0..63]: zeros */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* pubkey_hash[0..31]: zeros */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, /* cap_count: 4 */ + 0x00, 0x00, 0x00, 0x00, /* blob_size: 0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* entry_point: 0 */ + /* CapDescriptor[0]: console.output (0x1001) WRITE */ + 0x02, 0x02, 0x00, 0x00, + 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* CapDescriptor[1]: VFS (0x2000) READ */ + 0x02, 0x01, 0x00, 0x00, + 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* CapDescriptor[2]: NET_TX (0x0500) WRITE */ + 0x02, 0x02, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* CapDescriptor[3]: NET_RX (0x0501) READ */ + 0x02, 0x01, 0x00, 0x00, + 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +""".} # Phase 30: Pledge Constants const @@ -221,7 +261,7 @@ proc cmd_cp*(args: seq[string], input: PipelineData): PipelineData = return @[] # O_WRONLY(1) | O_CREAT(64) | O_TRUNC(512) = 577 - let fd_dest = lb.open(dest.cstring, 577) + let fd_dest = lb.open(dest.cstring, 577) if fd_dest < 0: print("cp: cannot create '" & dest & "'\n") discard lb.close(fd_src) @@ -247,11 +287,11 @@ proc cmd_mv*(args: seq[string], input: PipelineData): PipelineData = if args.len < 2: print("Usage: mv \n") return @[] - + # Step 1: Copy print("[mv] Copying...\n") discard cmd_cp(args, input) - + # Step 2: Unlink (Not yet supported by Kernel) print("[mv] Warning: Original file '" & args[0] & "' retained (SFS unlink not implemented).\n") return @[] @@ -260,7 +300,7 @@ proc cmd_touch*(args: seq[string], input: PipelineData): PipelineData = if args.len < 1: print("Usage: touch \n") return @[] - + let fd = lb.open(args[0].cstring, 577) # O_CREAT | O_TRUNC if fd >= 0: discard lb.close(fd) @@ -291,45 +331,53 @@ proc cmd_cat*(args: seq[string], input: PipelineData): PipelineData = proc cmd_write*(args: seq[string], input: PipelineData): PipelineData = ## write ## Uses USERLAND SFS (Block Valve architecture) - if args.len < 2: - print("Usage: write \n") + when not defined(NIPBOX_LITE): + if args.len < 2: + print("Usage: write \n") + return @[] + + let filename = args[0] + let content = args[1..^1].join(" ") + + # Mount userland FS if not already done + if not sfs.sfs_is_mounted(): + discard sfs.sfs_mount() + + let bytes_written = sfs.sfs_write(filename, cast[pointer](unsafeAddr content[0]), content.len) + if bytes_written > 0: + print("[Glass Vault] Written " & $bytes_written & " bytes to: " & filename & " (Userland SFS)\n") + else: + print("Error: Could not write to " & filename & "\n") return @[] - - let filename = args[0] - let content = args[1..^1].join(" ") - - # Mount userland FS if not already done - if not sfs.sfs_is_mounted(): - discard sfs.sfs_mount() - - let bytes_written = sfs.sfs_write(filename, cast[pointer](unsafeAddr content[0]), content.len) - if bytes_written > 0: - print("[Glass Vault] Written " & $bytes_written & " bytes to: " & filename & " (Userland SFS)\n") else: - print("Error: Could not write to " & filename & "\n") - return @[] + print("[nipbox] 'write' requires SFS (not available in lite mode)\n") + return @[] proc cmd_read*(args: seq[string], input: PipelineData): PipelineData = ## read ## Uses USERLAND SFS (Block Valve architecture) - if args.len == 0: - print("Usage: read \n") + when not defined(NIPBOX_LITE): + if args.len == 0: + print("Usage: read \n") + return @[] + + let filename = args[0] + + # Mount userland FS if not already done + if not sfs.sfs_is_mounted(): + discard sfs.sfs_mount() + + var buf: array[4096, char] + let bytes_read = sfs.sfs_read(filename, addr buf[0], 4096) + if bytes_read > 0: + discard lb.write(cint(1), addr buf[0], csize_t(bytes_read)) + print("\n[Glass Vault] Read " & $bytes_read & " bytes from: " & filename & " (Userland SFS)\n") + else: + print("Error: Could not open " & filename & "\n") return @[] - - let filename = args[0] - - # Mount userland FS if not already done - if not sfs.sfs_is_mounted(): - discard sfs.sfs_mount() - - var buf: array[4096, char] - let bytes_read = sfs.sfs_read(filename, addr buf[0], 4096) - if bytes_read > 0: - discard lb.write(cint(1), addr buf[0], csize_t(bytes_read)) - print("\n[Glass Vault] Read " & $bytes_read & " bytes from: " & filename & " (Userland SFS)\n") else: - print("Error: Could not open " & filename & "\n") - return @[] + print("[nipbox] 'read' requires SFS (not available in lite mode)\n") + return @[] proc cmd_edit*(args: seq[string], input: PipelineData): PipelineData = if args.len == 0: @@ -482,239 +530,223 @@ proc cmd_http_get*(args: seq[string], input: PipelineData): PipelineData = return @[node] proc cmd_http_download*(args: seq[string], input: PipelineData): PipelineData = - if args.len < 2: - print("Usage: http.download \n") - return @[] + when not defined(NIPBOX_LITE): + if args.len < 2: + print("Usage: http.download \n") + return @[] - let url_arg = args[0] - let outfile = args[1] + let url_arg = args[0] + let outfile = args[1] - var host_part = url_arg - var path_str = "/" + var host_part = url_arg + var path_str = "/" - let slash_pos = url_arg.find('/') - if slash_pos != -1: - host_part = url_arg[0.. 0: - timeout = 0 - if not header_parsed: - for i in 0.. 0: + timeout = 0 + if not header_parsed: + for i in 0.. 8192: + print("Error: Headers too large.\n") + break else: - if header_acc.len > 8192: - print("Error: Headers too large.\n") - break + sfs.sfs_write_chunk(sfs_h, addr buf[0], int(n)) + total_bytes += int(n) + + if content_len > 0: + if total_bytes mod 10240 < int(n): print(".") + else: + if total_bytes mod 10240 < int(n): print(".") + + elif n == 0: + break else: - # Stream directly to SFS - sfs.sfs_write_chunk(sfs_h, addr buf[0], int(n)) - total_bytes += int(n) - - # Progress Bar - if content_len > 0: - # let pct = (total_bytes * 100) div content_len - if total_bytes mod 10240 < int(n): print(".") - else: - if total_bytes mod 10240 < int(n): print(".") + break - elif n == 0: - break - else: - timeout += 1 - # Busy wait / pump handled in recv? - # Recv calls pump_membrane_stack loop - # But if we return -1 (EAGAIN), we need to retry. - # My libc.libc_recv returns 0 on closed? - # Actually libc_recv in step 945 waits until data or closed. - # So n==0 means closed. - # Wait, libc.nim recv implementation: - # while true: pump; if data return n; if closed return 0. - # So it blocks until data. - # Thus n > 0 always unless closed. - break - - sfs.sfs_close_write(sfs_h) - discard lb.close(cint(fd)) - print("\n[Download] Complete. " & $total_bytes & " bytes written to " & outfile & " (Glass Vault).\n") - return @[] + sfs.sfs_close_write(sfs_h) + discard lb.close(cint(fd)) + print("\n[Download] Complete. " & $total_bytes & " bytes written to " & outfile & " (Glass Vault).\n") + return @[] + else: + print("[nipbox] 'http.download' requires SFS (not available in lite mode)\n") + return @[] # Phase 37: HTTP Verification Tool proc cmd_http_test*(args: seq[string], input: PipelineData): PipelineData = if args.len < 1: - print("Usage: http \n") + print("Usage: http.test \n") return @[] - + let host = args[0] print("Dialing " & host & ":80...\n") - + let fd = lnet.net_dial_tcp(host, 80) if fd < 0: print("Connection Failed! Error: " & $fd & "\n") return @[] - + print("Connected! Sending GET request...\n") discard lnet.net_send(fd, "GET / HTTP/1.0\r\nHost: " & host & "\r\nConnection: close\r\n\r\n") print("Waiting for response...\n") - - # Simple read loop + var total = 0 while true: - # lb.pump_membrane_stack() let resp = lnet.net_recv(fd, 512) if resp.len > 0: print(resp) total += resp.len else: - # End of stream or empty poll break - + print("\n[HTTP] Closed. Total bytes: " & $total & "\n") lnet.net_close(fd) return @[] proc cmd_http_serve*(args: seq[string], input: PipelineData): PipelineData = print("[Server] Starting Nexus Web/1.0...\n") - + let port: uint16 = if args.len > 0: uint16(parseInt(args[0])) else: 80 - + let s = lb.socket(2, 1, 0) if s < 0: print("Error: Socket creation failed.\n") return @[] - + # Bind 0.0.0.0:port var addr_buf: array[16, byte] addr_buf[0] = 2 # AF_INET copyMem(addr addr_buf[2], unsafeAddr port, 2) # IP 0.0.0.0 is default 0s - + if lb.bind_socket(s, addr addr_buf[0], 16) < 0: print("Error: Bind failed.\n") return @[] - + if lb.listen(s, 1) < 0: print("Error: Listen failed.\n") return @[] - + print("[Server] Listening on port " & $port & "...\n") - + while true: # Accept blocks and pumps stack let client = lb.accept(s, nil, nil) if client < 0: print("Error: Accept failed.\n") continue - + print("[Server] Client Connected (FD " & $client & ")\n") - + var buf: array[1024, char] let n = lb.recv(client, addr buf[0], 1024, 0) - + if n > 0: var req = "" for i in 0..\n") return @[] - + let host = args[0] print("[NipBox] Dialing " & host & ":80...\n") - + # Use libc.socket/connect (Phase 38 Shim) let fd = lb.socket(2, 1, 0) if fd < 0: print("Socket Error\n") return @[] - + # Parse IP (Quick hack for 10.0.2.2) # We need proper parsing but let's assume raw IP for MVP var ip_val: uint32 = 0 @@ -824,28 +866,28 @@ proc dispatch_command(name: string, args: seq[string], except: print("Error: Invalid IP format (use A.B.C.D)\n") return @[] - + # Construct SockAddrIn (Layout must match libc.connect hack) var addr_buf: array[16, byte] # Port 80 (0x0050) -> Big Endian 0x0050? No, htons(80) = 0x5000 on LE? # 80 = 0x0050. LE in mem: 50 00. - # LwIP wants host byte order or network? + # LwIP wants host byte order or network? # connect() shim expects us to pass port as uint16. # But the shim casts addr_ptr+2 to uint16*. # If we write 80 there, it reads 80. - + let port: uint16 = 80 copyMem(addr addr_buf[2], unsafeAddr port, 2) copyMem(addr addr_buf[4], unsafeAddr ip_val, 4) - + if lb.connect(fd, addr addr_buf[0], 16) < 0: print("Connect Failed\n") return @[] - + print("[NipBox] Connected! Sending Payload...\n") let req = "GET / HTTP/1.0\r\n\r\n" discard lb.send(fd, unsafeAddr req[0], uint64(req.len), 0) - + print("[NipBox] Waiting for Data...\n") var buf: array[1024, char] while true: @@ -856,7 +898,7 @@ proc dispatch_command(name: string, args: seq[string], print(s) else: break - + print("\n[NipBox] Done.\n") return @[] of "http.test": return cmd_http_test(args, input) @@ -1067,7 +1109,7 @@ proc run_script(path: string) = proc nipbox_main*() = # DIAGNOSTIC: Very first thing - prove we're executing print("[NIPBOX] Entry point reached!\n") - + # Phase 30: Pledge Safety # NipBox is the Shell, so it needs broad permissions, but we can restrict RPATH/WPATH to specific zones # For now, we PLEDGE_ALL because the shell needs to explore @@ -1087,7 +1129,7 @@ proc nipbox_main*() = # Phase 38: Boot Script run_script("/init.nsh") print("\x1b[1;32m╚═══════════════════════════════════════╝\x1b[0m\n\n") - + print("\x1b[1;33mroot@nexus:# \x1b[0m") var inputBuffer: string = ""