nx_model_get.nx source
↩ module page · 395 lines · 19427 B
1// nx_model_get.nx -- G9 mg_fetch: hash-verified model acquisition into the gen model store (/compare/gen watch G9).
2// usage: nx_model_get <url> <expected-sha256> <dest-name>
3// <dest-name> is relative to the model-store root (e.g. diffusion/z_image_turbo-Q8_0.gguf, loras/x.safetensors).
4//
5// COMPOSES INCUMBENTS, DUPLICATES NONE:
6// transport = nx_https_fetch_range (own TLS-1.3, Range windows -- one bounded window in memory at a time, so a
7// multi-GB body STREAMS and can never be silently truncated by a buffer cap)
8// hashing = the streaming sha256_init/update/final already inside this compilation unit (nx_sha256 via the
9// TLS stack / nx_weights_pull) -- the digest covers the FULL body across all windows
10// verify law = nx_weights_pull (wp_hexenc + fail-closed compare): a mismatched body is DELETED, never stored
11// decisions = nx_model_get_lib (dest admission, hex normalize, Content-Range/Length parse, conf parse),
12// offline-proven by nx_model_get_gate
13// Redirects (HF resolve -> CDN) are followed by a bytes=0-0 probe that reads the Location header, because the
14// range fetcher itself deliberately does not follow. Resumable: an interrupted pull leaves <dest>.part; a re-run
15// re-hashes the part from disk and continues from its byte count. Idempotent: an existing verified dest returns
16// OK-ALREADY-VERIFIED with no network touched. An existing dest that does NOT match is REFUSED and kept
17// (additive-only: this organ never deletes a model already in the store; only its own .part on hash mismatch).
18// exit: 0 VERIFIED / OK-ALREADY-VERIFIED | 1 REFUSED (hash mismatch / existing mismatch) | 2 usage/bad-args |
19// 3 UNPROVEN (transport, trust store, no-length -- partial kept for resume where one exists)
20// license_tier: ORIGINAL
21import "nx_syscalls.nx"
22import "nx_https_fetch_follow.nx"
23import "nx_trust_store_load_from_certdata.nx"
24import "nx_weights_pull.nx"
25import "nx_model_get_lib.nx"
26
27const MG_URLCAP: i64 = 4096 // same bound the fetch stack uses for a url buffer
28const MG_PROBE_CAP: i64 = 262144 // bytes=0-0 probe response buffer (headers + stub body)
29const MG_HOPS_MAX: i64 = 6 // redirect budget, mirrors the fetch stack's default
30const MG_HASH_WIN: i64 = 1048576 // on-disk re-hash window for idempotency/resume
31const MG_WINDOW_DEFAULT: i64 = 8388608 // range window when the conf carries none (8 MiB)
32const MG_WIN_RETRIES: i64 = 3 // window failures tolerated before declaring TRANSPORT-STALL
33const MG_PROGRESS_EVERY: i64 = 32 // announce progress every N windows
34const MG_CERT_ROOTS: i64 = 512
35const MG_CERT_CAP: i64 = 4194304
36const MG_CONF_PATH: *u8 = "knowledge/gen_model_store.conf"
37const MG_ROOT_DEFAULT: *u8 = "/volume1/ai/models"
38const MG_RECEIPT_PATH: *u8 = "knowledge/status/modelget.log"
39
40func mgo_put(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
41func mgo_num(v: i64) -> i64 {
42 var m: i64=v
43 if m<0 { mgo_put("-" as *u8); m=0-m }
44 let t: *u8=sys_mmap(28)
45 var k: i64=0
46 if m==0 { t[0]=48 as u8; k=1 }
47 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
48 let b: *u8=sys_mmap(28)
49 var j: i64=0
50 while j<k { b[j]=t[k-1-j]; j=j+1 }
51 sys_write(1,b,k)
52 return 0
53}
54
55// stream-hash an on-disk file into ctx (sha256_update over MG_HASH_WIN windows). Returns bytes hashed, -1 unreadable.
56func mg_hash_file_into(ctx: *Sha256, path: *u8) -> i64 {
57 let fd: i64 = sys_openat_rd(path)
58 if fd<0 { return 0-1 }
59 let buf: *u8 = sys_mmap(MG_HASH_WIN)
60 var total: i64=0
61 var go: i64=1
62 var bad: i64=0
63 while go==1 {
64 let r: i64 = sys_read(fd, buf, MG_HASH_WIN)
65 if r<0 { bad=1; go=0 } else { if r==0 { go=0 } else { sha256_update(ctx, buf, r); total=total+r } }
66 }
67 sys_close(fd)
68 if bad==1 { return 0-1 }
69 return total
70}
71
72// full-file sha256 hex of an existing artifact (for the idempotency check). Returns bytes, -1 unreadable.
73func mg_hash_file_hex(path: *u8, out_hex: *u8) -> i64 {
74 let ctx_raw: *u8 = sys_mmap(256)
75 let ctx: *Sha256 = ctx_raw as *Sha256
76 sha256_init(ctx)
77 let n: i64 = mg_hash_file_into(ctx, path)
78 if n<0 { return 0-1 }
79 let dig: *u8 = sys_mmap(32)
80 sha256_final(ctx, dig)
81 wp_hexenc(dig, 32, out_hex)
82 return n
83}
84
85// ONE bytes=0-0 range request with HEADER visibility (status, Location, Content-Range total, Content-Length).
86// The shipped nx_https_fetch_range returns only the body, so redirect-following a range pull needs this probe.
87// Returns: 3 REDIRECT (locout filled) | 2 RANGE-OK (totbox = total from Content-Range) |
88// 1 NO-RANGE (200; totbox = Content-Length or -1) | negative = transport/parse failure.
89func mg_probe(urlbuf: *u8, store: *TrustStore, use_chrome: i64, st: *i64, locout: *u8, totbox: *i64) -> i64 {
90 st[0]=0
91 totbox[0]=0-1
92 let target_raw: *u8 = sys_mmap(64)
93 let target: *NxHttpsTarget = target_raw as *NxHttpsTarget
94 target.url = nx_url_new()
95 target.port = 0
96 if nx_https_url_for_fetch(urlbuf, target) != NX_HTTPS_URL_OK { return 0-1 }
97 let fd_p: *i64 = (sys_mmap(16)) as *i64
98 if nx_https_url_connect(target, urlbuf, sys_now_realtime_sec(), fd_p) != NX_HTTPS_CONNECT_OK { return 0-2 }
99 let fd: i64 = fd_p[0]
100 sys_set_socket_timeout(fd, 30)
101 let cr: *u8 = sys_mmap(32)
102 let priv: *u8 = sys_mmap(32)
103 nx_csprng_fill(cr, 32)
104 nx_csprng_fill(priv, 32)
105 let vc_raw: *u8 = sys_mmap(64)
106 let vc: *TlsValidationContext = vc_raw as *TlsValidationContext
107 vc.store = store
108 vc.sni_host = urlbuf + target.url.host_off
109 vc.sni_host_len = target.url.host_len
110 vc.now_epoch = sys_now_realtime_sec()
111 var sr: i64 = 0
112 if use_chrome==1 { sr = nx_tls13_client_session_run_chrome(fd, urlbuf + target.url.host_off, target.url.host_len, cr, priv, vc) }
113 else { sr = nx_tls13_client_session_run(fd, urlbuf + target.url.host_off, target.url.host_len, cr, priv, vc) }
114 if sr<=0 { sys_close(fd); return 0-3 }
115 let session: *Tls13ClientSession = sr as *Tls13ClientSession
116 let path: *u8 = sys_mmap(2048)
117 let plen: i64 = ff_path(urlbuf, target, path)
118 let req: *u8 = sys_mmap(4096)
119 let req_len: i64 = nx_http_build_range_get(path, plen, urlbuf + target.url.host_off, target.url.host_len, 0, 0, req)
120 let buf: *u8 = sys_mmap(MG_PROBE_CAP)
121 let gc: i64 = nx_https_req_complete(session, fd, req, req_len, buf, MG_PROBE_CAP)
122 sys_close(fd)
123 if gc<0 { return 0-4 }
124 let r: *i64 = (sys_mmap(128)) as *i64
125 if nx_http_response_parse(buf, gc, r) != 0 { return 0-5 }
126 st[0] = r[1]
127 let body_off: i64 = r[6]
128 if nx_redir_is_redirect(r[1])==1 {
129 let loc: *u8 = sys_mmap(MG_URLCAP)
130 let ln: i64 = nx_redir_location(buf, gc, loc, MG_URLCAP)
131 if ln<=0 { return 0-6 }
132 let resolved: *u8 = sys_mmap(MG_URLCAP)
133 ff_resolve_location(loc, urlbuf, target, resolved)
134 var k: i64=0
135 while resolved[k]!=(0 as u8) { locout[k]=resolved[k]; k=k+1 }
136 locout[k]=0 as u8
137 return 3
138 }
139 if r[1]==206 { totbox[0] = mg_content_range_total(buf, body_off); return 2 }
140 if r[1]==200 { totbox[0] = mg_content_length(buf, body_off); return 1 }
141 return 0-7
142}
143
144// follow redirects (bounded) until a range-answering endpoint; urlbuf ends holding the FINAL url.
145// chromebox carries/returns the TLS-hello flavor that worked. Returns probe mode (1/2) or negative.
146func mg_resolve(urlbuf: *u8, store: *TrustStore, chromebox: *i64, totbox: *i64) -> i64 {
147 let st: *i64 = (sys_mmap(16)) as *i64
148 let loc: *u8 = sys_mmap(MG_URLCAP)
149 var hop: i64=0
150 var lastrc: i64=0-9
151 while hop<=MG_HOPS_MAX {
152 var rc: i64 = mg_probe(urlbuf, store, chromebox[0], st, loc, totbox)
153 if rc<0 { if chromebox[0]==0 {
154 chromebox[0]=1
155 rc = mg_probe(urlbuf, store, 1, st, loc, totbox)
156 } }
157 if rc==3 {
158 mgo_put("hop=" as *u8); mgo_num(hop+1); mgo_put(" status=" as *u8); mgo_num(st[0]); mgo_put(" -> " as *u8); mgo_put(loc); mgo_put("\n" as *u8)
159 var k: i64=0
160 while loc[k]!=(0 as u8) { urlbuf[k]=loc[k]; k=k+1 }
161 urlbuf[k]=0 as u8
162 hop=hop+1
163 } else {
164 if rc==2 { return 2 }
165 if rc==1 { return 1 }
166 mgo_put("probe rc=" as *u8); mgo_num(rc); mgo_put(" status=" as *u8); mgo_num(st[0]); mgo_put("\n" as *u8)
167 return rc
168 }
169 lastrc=rc
170 }
171 return 0-8
172}
173
174func mg_receipt(verdict: *u8, dest: *u8, bytes: i64, hex: *u8, url: *u8) -> i64 {
175 let fd: i64 = sys_openat_append(MG_RECEIPT_PATH, MODE_0644)
176 if fd<0 { mgo_put("receipt=UNWRITTEN (knowledge/status/modelget.log not writable from this cwd)\n" as *u8); return 0-1 }
177 let line: *u8 = sys_mmap(8192)
178 var o: i64=0
179 o = mg_cat(line, o, "ts=" as *u8)
180 let t: *u8 = sys_mmap(28)
181 var m: i64 = sys_now_realtime_sec()
182 var k: i64=0
183 if m==0 { t[0]=48 as u8; k=1 }
184 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
185 var j: i64=k-1
186 while j>=0 { line[o]=t[j]; o=o+1; j=j-1 }
187 o = mg_cat(line, o, " verdict=" as *u8)
188 o = mg_cat(line, o, verdict)
189 o = mg_cat(line, o, " dest=" as *u8)
190 o = mg_cat(line, o, dest)
191 o = mg_cat(line, o, " bytes=" as *u8)
192 var b: i64 = bytes
193 if b<0 { line[o]=45 as u8; o=o+1; b=0-b }
194 k=0
195 if b==0 { t[0]=48 as u8; k=1 }
196 while b>0 { t[k]=(48+(b%10)) as u8; b=b/10; k=k+1 }
197 j=k-1
198 while j>=0 { line[o]=t[j]; o=o+1; j=j-1 }
199 o = mg_cat(line, o, " sha256=" as *u8)
200 o = mg_cat(line, o, hex)
201 o = mg_cat(line, o, " url=" as *u8)
202 o = mg_cat(line, o, url)
203 line[o]=10 as u8
204 o=o+1
205 sys_write(fd, line, o)
206 sys_fsync(fd)
207 sys_close(fd)
208 mgo_put("receipt=knowledge/status/modelget.log\n" as *u8)
209 return 0
210}
211
212func main(argc: i64, argv: *i64) -> i64 {
213 if argc<4 {
214 mgo_put("usage: nx_model_get <url> <expected-sha256> <dest-name>\n dest-name is relative to the model-store root (diffusion/... or loras/...)\n" as *u8)
215 return 2
216 }
217 let url: *u8 = argv[1] as *u8
218 let hexarg: *u8 = argv[2] as *u8
219 let name: *u8 = argv[3] as *u8
220
221 let exphex: *u8 = sys_mmap(80)
222 if mg_hex_norm(hexarg, exphex)!=1 {
223 mgo_put("REFUSED-BAD-HASH rule=expected-sha256-must-be-64-hex -- got " as *u8); mgo_put(hexarg); mgo_put("\n" as *u8)
224 return 2
225 }
226 if mg_dest_ok(name)!=1 {
227 mgo_put("REFUSED-DEST-NAME rule=relative-name-only (no leading slash, no .., no backslash) -- got " as *u8); mgo_put(name); mgo_put("\n" as *u8)
228 return 2
229 }
230
231 // ---- model-store root + window from the conf (env > conf > code default: rule 17; provenance announced) ----
232 let root: *u8 = sys_mmap(2048)
233 var window: i64 = MG_WINDOW_DEFAULT
234 var rootsrc: *u8 = "DEFAULT" as *u8
235 let clen: *i64 = (sys_mmap(16)) as *i64
236 let conf: *u8 = sys_read_file(MG_CONF_PATH, clen)
237 if (conf as i64)!=0 {
238 let rl: i64 = mg_conf_val(conf, clen[0], "root" as *u8, root, 2048)
239 if rl>0 { rootsrc = "CONF" as *u8 }
240 let wv: *u8 = sys_mmap(64)
241 let wl: i64 = mg_conf_val(conf, clen[0], "window_bytes" as *u8, wv, 64)
242 if wl>0 { let w: i64 = mg_parse_uint(wv, wl, 0); if w>0 { window = w } }
243 }
244 if root[0]==(0 as u8) { mg_cat(root, 0, MG_ROOT_DEFAULT); root[mg_slen(MG_ROOT_DEFAULT)]=0 as u8 }
245 mgo_put("store_root=" as *u8); mgo_put(root); mgo_put(" (" as *u8); mgo_put(rootsrc); mgo_put(") window_bytes=" as *u8); mgo_num(window); mgo_put("\n" as *u8)
246
247 let dest: *u8 = sys_mmap(4096)
248 mg_join(root, name, dest)
249 let part: *u8 = sys_mmap(4096)
250 var po: i64 = mg_cat(part, 0, dest)
251 po = mg_cat(part, po, ".part" as *u8)
252 part[po]=0 as u8
253
254 // ---- idempotency: an existing verified dest is a no-op (no network) ----
255 let efd: i64 = sys_openat_rd(dest)
256 if efd>=0 {
257 sys_close(efd)
258 let ehex: *u8 = sys_mmap(80)
259 let en: i64 = mg_hash_file_hex(dest, ehex)
260 if en>=0 { if wp_streq(ehex, exphex)==1 {
261 mgo_put("OK-ALREADY-VERIFIED dest=" as *u8); mgo_put(dest); mgo_put(" bytes=" as *u8); mgo_num(en); mgo_put(" sha256=" as *u8); mgo_put(ehex); mgo_put("\n" as *u8)
262 return 0
263 } }
264 mgo_put("REFUSED-EXISTING-MISMATCH dest=" as *u8); mgo_put(dest); mgo_put(" bytes=" as *u8); mgo_num(en); mgo_put(" sha256=" as *u8); mgo_put(ehex); mgo_put(" expected=" as *u8); mgo_put(exphex)
265 mgo_put(" rule=additive-only -- an existing store artifact is never overwritten or deleted; remove it deliberately or pick a new dest-name\n" as *u8)
266 return 1
267 }
268
269 // ---- trust store (same Mozilla bundle every sovereign fetcher loads) ----
270 var tr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, MG_CERT_ROOTS, MG_CERT_CAP)
271 if tr<=0 { tr = nx_trust_store_load_from_certdata("/tmp/mozilla_certdata.txt" as *u8, MG_CERT_ROOTS, MG_CERT_CAP) }
272 if tr<=0 { mgo_put("UNPROVEN trust-store-load-failed (need data/mozilla_certdata.txt on cwd) -- nothing fetched\n" as *u8); return 3 }
273 let store: *TrustStore = tr as *TrustStore
274
275 return mg_fetch(url, exphex, dest, part, store, window)
276}
277
278// G9 mg_fetch -- THE CONTRACT SYMBOL (/compare/gen watch row MODEL: hash-verified acquisition). resolve redirects
279// -> range-window stream (hash as it lands) -> fail-closed verify over the FULL body -> rename into the store.
280// exit-code shaped return: 0 VERIFIED | 1 REFUSED-HASH-MISMATCH | 3 UNPROVEN (transport; partial kept for resume)
281func mg_fetch(url: *u8, exphex: *u8, dest: *u8, part: *u8, store: *TrustStore, window: i64) -> i64 {
282 // ---- resolve redirects to the final range-answering url ----
283 let urlbuf: *u8 = sys_mmap(MG_URLCAP)
284 var ui: i64=0
285 while url[ui]!=(0 as u8) { urlbuf[ui]=url[ui]; ui=ui+1 }
286 urlbuf[ui]=0 as u8
287 let chromebox: *i64 = (sys_mmap(16)) as *i64
288 chromebox[0]=0
289 let totbox: *i64 = (sys_mmap(16)) as *i64
290 var mode: i64 = mg_resolve(urlbuf, store, chromebox, totbox)
291 if mode<0 { mgo_put("UNPROVEN transport-resolve-failed rc=" as *u8); mgo_num(mode); mgo_put(" -- nothing stored\n" as *u8); return 3 }
292 var total: i64 = totbox[0]
293 if total<=0 { mgo_put("UNPROVEN no-declared-length (no Content-Range total, no Content-Length) -- refusing: completeness would be unjudgeable\n" as *u8); return 3 }
294 mgo_put("resolved mode=" as *u8); mgo_num(mode); mgo_put(" total_bytes=" as *u8); mgo_num(total); mgo_put(" chrome_hello=" as *u8); mgo_num(chromebox[0]); mgo_put("\n" as *u8)
295 if mode==1 { if total>window {
296 mgo_put("REFUSED-NO-RANGE-SUPPORT rule=a-body-larger-than-the-window-must-stream -- server ignored Range and the body (" as *u8); mgo_num(total)
297 mgo_put(" B) exceeds window_bytes (" as *u8); mgo_num(window); mgo_put(" B); refusing rather than truncate\n" as *u8)
298 return 3
299 } }
300
301 // ---- streaming hash context; resume from .part if one exists ----
302 let ctx_raw: *u8 = sys_mmap(256)
303 let ctx: *Sha256 = ctx_raw as *Sha256
304 sha256_init(ctx)
305 var base: i64=0
306 let pfd: i64 = sys_openat_rd(part)
307 if pfd>=0 {
308 sys_close(pfd)
309 let pn: i64 = mg_hash_file_into(ctx, part)
310 if pn>0 { if pn<total {
311 base=pn
312 mgo_put("RESUME part=" as *u8); mgo_put(part); mgo_put(" bytes_already=" as *u8); mgo_num(pn); mgo_put("\n" as *u8)
313 } }
314 if base==0 {
315 sys_unlinkat(part)
316 sha256_init(ctx)
317 mgo_put("part-discarded (empty or not smaller than total) -- restarting from 0\n" as *u8)
318 }
319 }
320
321 let afd: i64 = sys_openat_append(part, MODE_0644)
322 if afd<0 { mgo_put("UNPROVEN cannot-open-part-for-append dest=" as *u8); mgo_put(part); mgo_put("\n" as *u8); return 3 }
323
324 // ---- the window loop: fetch range -> hash -> append; never more than one window in memory ----
325 let wbuf: *u8 = sys_mmap(window)
326 let st: *i64 = (sys_mmap(16)) as *i64
327 var off: i64=base
328 var winfails: i64=0
329 var wincount: i64=0
330 var stalled: i64=0
331 while off<total {
332 var want: i64=window
333 if off+want>total { want=total-off }
334 let n: i64 = nx_https_fetch_range(urlbuf, store, wbuf, window, off, off+want-1, st, chromebox[0])
335 var okwin: i64=0
336 if n==want {
337 if st[0]==206 { okwin=1 }
338 if st[0]==200 { if off==0 { if want==total { okwin=1 } } }
339 }
340 if okwin==0 {
341 winfails=winfails+1
342 mgo_put("window-retry off=" as *u8); mgo_num(off); mgo_put(" got=" as *u8); mgo_num(n); mgo_put(" want=" as *u8); mgo_num(want); mgo_put(" status=" as *u8); mgo_num(st[0]); mgo_put(" fail=" as *u8); mgo_num(winfails); mgo_put("\n" as *u8)
343 if winfails>=MG_WIN_RETRIES {
344 sys_close(afd)
345 mgo_put("UNPROVEN transport-stall at off=" as *u8); mgo_num(off); mgo_put(" of " as *u8); mgo_num(total)
346 mgo_put(" -- PARTIAL KEPT for resume at " as *u8); mgo_put(part); mgo_put(" (re-run the same command)\n" as *u8)
347 off=total
348 stalled=1
349 } else {
350 // a signed CDN url may have expired mid-pull: re-resolve from the ORIGINAL url and retry this window
351 var ri: i64=0
352 while url[ri]!=(0 as u8) { urlbuf[ri]=url[ri]; ri=ri+1 }
353 urlbuf[ri]=0 as u8
354 let m2: i64 = mg_resolve(urlbuf, store, chromebox, totbox)
355 if m2<0 { mgo_put("re-resolve failed rc=" as *u8); mgo_num(m2); mgo_put("\n" as *u8) }
356 }
357 } else {
358 winfails=0
359 sha256_update(ctx, wbuf, n)
360 var sent: i64=0
361 var wbad: i64=0
362 while sent<n { let w: i64 = sys_write(afd, (wbuf as i64 + sent) as *u8, n-sent); if w<=0 { wbad=1; sent=n } else { sent=sent+w } }
363 if wbad==1 {
364 sys_close(afd)
365 mgo_put("UNPROVEN disk-write-failed at off=" as *u8); mgo_num(off); mgo_put(" -- PARTIAL KEPT at " as *u8); mgo_put(part); mgo_put("\n" as *u8)
366 off=total
367 stalled=1
368 } else {
369 off=off+n
370 wincount=wincount+1
371 if wincount%MG_PROGRESS_EVERY==0 { mgo_put("progress bytes=" as *u8); mgo_num(off); mgo_put(" of " as *u8); mgo_num(total); mgo_put("\n" as *u8) }
372 }
373 }
374 }
375 if stalled==1 { return 3 }
376 sys_fsync(afd)
377 sys_close(afd)
378
379 // ---- fail-closed verify over the FULL body (the nx_weights_pull law: never store a corrupt artifact) ----
380 let dig: *u8 = sys_mmap(32)
381 sha256_final(ctx, dig)
382 let got: *u8 = sys_mmap(80)
383 wp_hexenc(dig, 32, got)
384 if wp_streq(got, exphex)==1 {
385 sys_renameat(part, dest)
386 mgo_put("VERIFIED dest=" as *u8); mgo_put(dest); mgo_put(" bytes=" as *u8); mgo_num(total); mgo_put(" sha256=" as *u8); mgo_put(got); mgo_put("\n" as *u8)
387 mg_receipt("VERIFIED" as *u8, dest, total, got, url)
388 return 0
389 }
390 sys_unlinkat(part)
391 mgo_put("REFUSED-HASH-MISMATCH expected=" as *u8); mgo_put(exphex); mgo_put(" got=" as *u8); mgo_put(got); mgo_put(" bytes=" as *u8); mgo_num(total)
392 mgo_put(" rule=fail-closed-verify -- the partial was DELETED; a corrupt, tampered or truncated body is never stored (nx_weights_pull law)\n" as *u8)
393 mg_receipt("REFUSED-HASH-MISMATCH" as *u8, dest, total, got, url)
394 return 1
395}