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}