code wiki / (root) / nx_moonraker_io.nx

nx_moonraker_io.nx source

↩ module page · 211 lines · 8834 B

1// nx_moonraker_io.nx -- network roundtrip layer for nx_moonraker_client. 2// 3// v2 of the Moonraker client arc: composes the v1 request builders 4// (URL / JSON / POST/GET byte assembly) with real socket I/O so we 5// can actually talk to a Klipper printer over the LAN. 6// 7// Loopback-only for this primitive (composes tcp_connect_loopback 8// from nx_tls13_io for testability via fork-based smoke). An 9// operator-facing variant accepting arbitrary sockaddr_in is queued 10// as v3 -- trivial extension once loopback is proven. 11// 12// Composition map: 13// nx_moonraker_client -- request builders (v1) 14// nx_tls13_io -- tcp_connect_loopback (loopback helper) 15// nx_syscalls -- sys_write / sys_read / sys_close 16// 17// Per cardinal feedback-engineering-sciences-bits-up-3d-print-first: 18// this primitive demonstrates that the substrate already has every 19// piece needed to talk to networked engineering tools (printers, 20// CNCs, robotics controllers) -- the operator-side workflow is just 21// composition of shipped primitives. 22// 23// Per cardinal feedback-real-playtest-loop-not-just-logs: the smoke 24// uses fork() with a real fake Moonraker server in the parent and 25// our real client in the child. Bytes flow over a real loopback 26// socket through the real Linux kernel TCP stack in qemu. 27// 28// license_tier: ORIGINAL 29 30import "nx_syscalls.nx" 31import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 32import "nx_tls13_io.nx" 33import "nx_moonraker_client.nx" 34 35// ===== verdicts =================================================== 36 37const NX_MR_IO_OK: i64 = 0 38const NX_MR_IO_ERR_BUILD_FAILED: i64 = 1 39const NX_MR_IO_ERR_CONNECT_FAILED: i64 = 2 40const NX_MR_IO_ERR_WRITE_FAILED: i64 = 3 41const NX_MR_IO_ERR_READ_FAILED: i64 = 4 42 43func nx_mr_io_verdict_name(v: i64) -> *u8 { 44 if v == NX_MR_IO_OK { return "OK" } 45 if v == NX_MR_IO_ERR_BUILD_FAILED { return "BUILD_FAILED" } 46 if v == NX_MR_IO_ERR_CONNECT_FAILED { return "CONNECT_FAILED" } 47 if v == NX_MR_IO_ERR_WRITE_FAILED { return "WRITE_FAILED" } 48 if v == NX_MR_IO_ERR_READ_FAILED { return "READ_FAILED" } 49 return "UNKNOWN" 50} 51 52// ===== generic IPv4 TCP connect ==================================== 53// 54// Builds sockaddr_in inline for an arbitrary IPv4 destination. 55// Caller passes 4 octet bytes (e.g. 192, 168, 1, 42) + port. 56// Returns connected socket fd on success, negative on failure. 57// 58// AF_INET sockaddr_in layout (16 bytes): 59// [0..1] sa_family = AF_INET (2) 60// [2..3] sin_port (network byte order, big-endian) 61// [4..7] sin_addr (IPv4 octets in network byte order) 62// [8..15] zero pad 63 64// IP packed: low 32 bits hold (a << 24) | (b << 16) | (c << 8) | d. 65// Caller uses nx_mr_pack_ipv4(a, b, c, d) to construct. 66func nx_mr_pack_ipv4(a: i64, b: i64, c: i64, d: i64) -> i64 { 67 return ((a & 0xff) << 24) | ((b & 0xff) << 16) | ((c & 0xff) << 8) | (d & 0xff) 68} 69 70func nx_mr_tcp_connect_ipv4(ipv4: i64, port: i64) -> i64 { 71 let fd: i64 = sys_socket(AF_INET, 1, 0) 72 if fd < 0 { return fd } 73 let addr: *u8 = sys_mmap(16) 74 addr[0] = 2; addr[1] = 0 75 addr[2] = (port >> 8) & 0xff 76 addr[3] = port & 0xff 77 addr[4] = (ipv4 >> 24) & 0xff 78 addr[5] = (ipv4 >> 16) & 0xff 79 addr[6] = (ipv4 >> 8) & 0xff 80 addr[7] = ipv4 & 0xff 81 addr[8] = 0; addr[9] = 0; addr[10] = 0; addr[11] = 0 82 addr[12] = 0; addr[13] = 0; addr[14] = 0; addr[15] = 0 83 let rc: i64 = nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) 84 if rc < 0 { return rc } 85 return fd 86} 87 88// ===== POST roundtrip over loopback ================================ 89// 90// Builds the POST request, connects to 127.0.0.1:port, sends the 91// request bytes, reads response into out_buf (up to out_cap), and 92// returns the number of response bytes read (or negative verdict). 93// 94// scratch_buf is caller-provided storage for the constructed request. 95// Size it generously (4KB+ should cover any reasonable Moonraker POST). 96 97func nx_mr_post_loopback(port: i64, 98 host: *u8, host_len: i64, 99 path: *u8, path_len: i64, 100 body: *u8, body_len: i64, 101 api_key: *u8, api_key_len: i64, 102 scratch_buf: *u8, scratch_cap: i64, 103 out_buf: *u8, out_cap: i64) -> i64 { 104 let req_len: i64 = nx_mr_build_post_request(host, host_len, 105 path, path_len, 106 body, body_len, 107 api_key, api_key_len, 108 scratch_buf, scratch_cap) 109 if req_len <= 0 { return 0 - NX_MR_IO_ERR_BUILD_FAILED } 110 111 let fd: i64 = tcp_connect_loopback(port) 112 if fd < 0 { return 0 - NX_MR_IO_ERR_CONNECT_FAILED } 113 114 let n_w: i64 = sys_write(fd, scratch_buf, req_len) 115 if n_w != req_len { 116 sys_close(fd) 117 return 0 - NX_MR_IO_ERR_WRITE_FAILED 118 } 119 120 let n_r: i64 = sys_read(fd, out_buf, out_cap) 121 sys_close(fd) 122 if n_r < 0 { return 0 - NX_MR_IO_ERR_READ_FAILED } 123 return n_r 124} 125 126// ===== POST/GET to arbitrary IPv4 (operator-facing) =============== 127// 128// Same as the loopback variants but takes 4 IPv4 octets + port for 129// the actual destination. Operator calls these with their real 130// printer LAN IP (e.g., 192, 168, 1, 42, 7125) when running against 131// hardware. The loopback wrappers above are special cases of these 132// for testability. 133 134func nx_mr_post_ipv4(ipv4: i64, port: i64, 135 host: *u8, host_len: i64, 136 path: *u8, path_len: i64, 137 body: *u8, body_len: i64, 138 api_key: *u8, api_key_len: i64, 139 scratch_buf: *u8, scratch_cap: i64, 140 out_buf: *u8, out_cap: i64) -> i64 { 141 let req_len: i64 = nx_mr_build_post_request(host, host_len, 142 path, path_len, 143 body, body_len, 144 api_key, api_key_len, 145 scratch_buf, scratch_cap) 146 if req_len <= 0 { return 0 - NX_MR_IO_ERR_BUILD_FAILED } 147 let fd: i64 = nx_mr_tcp_connect_ipv4(ipv4, port) 148 if fd < 0 { return 0 - NX_MR_IO_ERR_CONNECT_FAILED } 149 let n_w: i64 = sys_write(fd, scratch_buf, req_len) 150 if n_w != req_len { 151 sys_close(fd) 152 return 0 - NX_MR_IO_ERR_WRITE_FAILED 153 } 154 let n_r: i64 = sys_read(fd, out_buf, out_cap) 155 sys_close(fd) 156 if n_r < 0 { return 0 - NX_MR_IO_ERR_READ_FAILED } 157 return n_r 158} 159 160func nx_mr_get_ipv4(ipv4: i64, port: i64, 161 host: *u8, host_len: i64, 162 path: *u8, path_len: i64, 163 api_key: *u8, api_key_len: i64, 164 scratch_buf: *u8, scratch_cap: i64, 165 out_buf: *u8, out_cap: i64) -> i64 { 166 let req_len: i64 = nx_mr_build_get_request(host, host_len, 167 path, path_len, 168 api_key, api_key_len, 169 scratch_buf, scratch_cap) 170 if req_len <= 0 { return 0 - NX_MR_IO_ERR_BUILD_FAILED } 171 let fd: i64 = nx_mr_tcp_connect_ipv4(ipv4, port) 172 if fd < 0 { return 0 - NX_MR_IO_ERR_CONNECT_FAILED } 173 let n_w: i64 = sys_write(fd, scratch_buf, req_len) 174 if n_w != req_len { 175 sys_close(fd) 176 return 0 - NX_MR_IO_ERR_WRITE_FAILED 177 } 178 let n_r: i64 = sys_read(fd, out_buf, out_cap) 179 sys_close(fd) 180 if n_r < 0 { return 0 - NX_MR_IO_ERR_READ_FAILED } 181 return n_r 182} 183 184// ===== GET roundtrip over loopback ================================= 185 186func nx_mr_get_loopback(port: i64, 187 host: *u8, host_len: i64, 188 path: *u8, path_len: i64, 189 api_key: *u8, api_key_len: i64, 190 scratch_buf: *u8, scratch_cap: i64, 191 out_buf: *u8, out_cap: i64) -> i64 { 192 let req_len: i64 = nx_mr_build_get_request(host, host_len, 193 path, path_len, 194 api_key, api_key_len, 195 scratch_buf, scratch_cap) 196 if req_len <= 0 { return 0 - NX_MR_IO_ERR_BUILD_FAILED } 197 198 let fd: i64 = tcp_connect_loopback(port) 199 if fd < 0 { return 0 - NX_MR_IO_ERR_CONNECT_FAILED } 200 201 let n_w: i64 = sys_write(fd, scratch_buf, req_len) 202 if n_w != req_len { 203 sys_close(fd) 204 return 0 - NX_MR_IO_ERR_WRITE_FAILED 205 } 206 207 let n_r: i64 = sys_read(fd, out_buf, out_cap) 208 sys_close(fd) 209 if n_r < 0 { return 0 - NX_MR_IO_ERR_READ_FAILED } 210 return n_r 211}