code wiki / (root) / nx_media_fetch.nx

nx_media_fetch.nx source

↩ module page · 189 lines · 7399 B

1// nx_media_fetch.nx -- bring source bytes into a MediaPool. 2// 3// Bottom-most ingestion primitive: takes a source descriptor 4// (path / stdin / fd) and emits *u8 + len bytes ready for the 5// sniffer and per-medium parsers. 6// 7// Three transports today (substrate-native; no external deps): 8// - filesystem path : sys_open / sys_read / sys_close 9// - stdin : sys_read(0, ...) 10// - prefetched bytes : caller already has the buffer; we just 11// register provenance + push into pool 12// 13// URL transport is OWNED BY nishi-browser (Phase 0a DNS / 0b TLS / 14// 0c HTTPS already shipping per the active investigation memory). 15// nx_media_fetch.nx accepts the bytes nishi-browser hands back. 16// 17// See: docs/NISHI_MEDIA_INGESTION_ROADMAP.md 18// 19// nx_safety_envelope: 20// intended_use: "Fetch bytes from a source descriptor and 21// register them in a MediaPool with provenance 22// + sniff verdict." 23// sil_target: SIL2 24// asil_target: QM 25// dal_target: DAL C 26// iec_62304_class: NONE 27// evidence: [no_floating_point, 28// bounded_read_loop_with_progress_check, 29// provenance_origin_recorded, 30// sniff_verdict_attached_to_pool_entry] 31// hazard_register: [bug-tape-fetch-truncated-silently, 32// bug-tape-fetch-empty-treated-as-success, 33// bug-tape-symlink-traversal-attack] 34// residual_risk: "Filesystem fetcher follows symlinks by 35// default; caller validates path before 36// passing for trust-sensitive ingestions." 37// verdict: NOT_YET_EVALUATED 38 39import "nx_syscalls.nx" 40import "nx_media_pool.nx" 41import "nx_media_sniff.nx" 42 43// ===== fetch verdict ============================================== 44 45const NX_FETCH_OK: i64 = 0 46const NX_FETCH_OPEN_FAILED: i64 = -1 47const NX_FETCH_READ_FAILED: i64 = -2 48const NX_FETCH_EMPTY: i64 = -3 49const NX_FETCH_OVERSIZE: i64 = -4 50const NX_FETCH_POOL_FULL: i64 = -5 51 52// ===== default size caps ========================================== 53// 54// Hard ceiling for a single fetch. Prevents runaway reads from 55// pathological sources (e.g. /dev/zero). Caller can override. 56 57const NX_FETCH_DEFAULT_MAX: i64 = 134217728 // 128 MiB 58const NX_FETCH_INITIAL_BUF: i64 = 65536 // 64 KiB grow-from 59 60// ===== read-until-eof helper ====================================== 61// 62// Read from `fd` into a freshly-mmap'd buffer, growing exponentially. 63// Returns 0 on success and writes buffer ptr + length to outparams. 64// Returns NX_FETCH_READ_FAILED / NX_FETCH_OVERSIZE / NX_FETCH_EMPTY 65// on failure. 66 67func nx_fetch_read_all(fd: i64, max_bytes: i64, 68 out_buf: **u8, out_len: *i64) -> i64 { 69 var cap: i64 = NX_FETCH_INITIAL_BUF 70 var buf: *u8 = sys_mmap(cap) 71 var n: i64 = 0 72 let MAX: i64 = max_bytes 73 let LOOP_BUDGET: i64 = 4096 74 var iter: i64 = 0 75 var done: i64 = 0 76 while done == 0 { 77 if iter >= LOOP_BUDGET { *out_buf = 0 as *u8; *out_len = 0; return NX_FETCH_READ_FAILED } 78 if n >= cap { 79 let new_cap: i64 = cap * 2 80 if new_cap > MAX { 81 *out_buf = 0 as *u8; *out_len = 0 82 return NX_FETCH_OVERSIZE 83 } 84 let new_buf: *u8 = sys_mmap(new_cap) 85 // Copy old into new. 86 var c: i64 = 0 87 while c < n { 88 let src_p: *u8 = ((buf as i64) + c) as *u8 89 let dst_p: *u8 = ((new_buf as i64) + c) as *u8 90 *dst_p = *src_p 91 c = c + 1 92 } 93 buf = new_buf 94 cap = new_cap 95 } 96 let dst: *u8 = ((buf as i64) + n) as *u8 97 let want: i64 = cap - n 98 let got: i64 = sys_read(fd, dst, want) 99 if got < 0 { *out_buf = 0 as *u8; *out_len = 0; return NX_FETCH_READ_FAILED } 100 if got == 0 { done = 1 } 101 n = n + got 102 if n > MAX { *out_buf = 0 as *u8; *out_len = 0; return NX_FETCH_OVERSIZE } 103 iter = iter + 1 104 } 105 if n == 0 { *out_buf = 0 as *u8; *out_len = 0; return NX_FETCH_EMPTY } 106 *out_buf = buf 107 *out_len = n 108 return NX_FETCH_OK 109} 110 111// ===== fetch from filesystem ====================================== 112// 113// Opens `path` read-only, slurps the file, registers in pool with 114// src_kind=USER_FILE. Returns media_id on success or a negative 115// NX_FETCH_* code on failure. 116 117func nx_fetch_path_into_pool(pool: *MediaPool, path: *u8, plen: i64, 118 max_bytes: i64, fetched_at: i64) -> i64 { 119 var max: i64 = max_bytes 120 if max <= 0 { max = NX_FETCH_DEFAULT_MAX } 121 122 let fd: i64 = sys_open(path, 0, 0) // O_RDONLY 123 if fd < 0 { return NX_FETCH_OPEN_FAILED } 124 125 var buf: *u8 = 0 as *u8 126 var n: i64 = 0 127 let rc: i64 = nx_fetch_read_all(fd, max, &buf, &n) 128 sys_close(fd) 129 if rc != NX_FETCH_OK { return rc } 130 131 let mtype: i64 = nx_sniff(buf, n, 0 as *u8, 0, path, plen) 132 let mid: i64 = nx_media_pool_add(pool, 133 mtype, 134 NX_MEDIA_SRC_USER_FILE, 135 buf, n, 136 0 as *u8, 0, 137 path, plen, 138 fetched_at) 139 if mid < 0 { return NX_FETCH_POOL_FULL } 140 return mid 141} 142 143// ===== fetch from stdin =========================================== 144 145func nx_fetch_stdin_into_pool(pool: *MediaPool, mime: *u8, mlen: i64, 146 max_bytes: i64, fetched_at: i64) -> i64 { 147 var max: i64 = max_bytes 148 if max <= 0 { max = NX_FETCH_DEFAULT_MAX } 149 var buf: *u8 = 0 as *u8 150 var n: i64 = 0 151 let rc: i64 = nx_fetch_read_all(0, max, &buf, &n) 152 if rc != NX_FETCH_OK { return rc } 153 154 let origin: *u8 = "stdin" as *u8 155 let mtype: i64 = nx_sniff(buf, n, mime, mlen, 0 as *u8, 0) 156 let mid: i64 = nx_media_pool_add(pool, 157 mtype, 158 NX_MEDIA_SRC_USER_PASTE, 159 buf, n, 160 mime, mlen, 161 origin, 5, 162 fetched_at) 163 if mid < 0 { return NX_FETCH_POOL_FULL } 164 return mid 165} 166 167// ===== register prefetched bytes (browser / voice / batch) ======== 168// 169// Caller already has the bytes in hand (nishi-browser HTTP response, 170// nx_mic capture buffer, batch-walker pre-read). Just sniff + 171// register provenance. 172 173func nx_fetch_register_bytes(pool: *MediaPool, src_kind: i64, 174 buf: *u8, n: i64, 175 mime: *u8, mlen: i64, 176 origin: *u8, olen: i64, 177 fetched_at: i64) -> i64 { 178 if n <= 0 { return NX_FETCH_EMPTY } 179 let mtype: i64 = nx_sniff(buf, n, mime, mlen, origin, olen) 180 let mid: i64 = nx_media_pool_add(pool, 181 mtype, 182 src_kind, 183 buf, n, 184 mime, mlen, 185 origin, olen, 186 fetched_at) 187 if mid < 0 { return NX_FETCH_POOL_FULL } 188 return mid 189}