code wiki / (root) / nx_escl.nx

nx_escl.nx source

↩ module page · 108 lines · 5418 B

1// nx_escl.nx -- sovereign eSCL (AirScan / Mopria Scan) protocol layer -- R-SCAN R0, the "scan from ANY 2// scanner" rung. eSCL is to scanners what IPP is to printers: a vendor-neutral HTTP + XML standard that Apple 3// AirScan and Mopria Scan use, and that modern Brother/HP/Canon/Epson MFPs answer. This file is the pure 4// protocol layer: 5// - EMIT a ScanSettings XML document (the POST body for /eSCL/ScanJobs): resolution, color mode, region, 6// input source, document format. 7// - PARSE the key fields from a ScannerCapabilities XML response (GET /eSCL/ScannerCapabilities): MaxWidth, 8// MaxHeight, supported color modes + document formats, via targeted tag extraction (no full XML parser). 9// 10// Live transport (GET caps -> POST ScanJobs -> GET NextDocument over HTTP) + the JPEG decode are the next 11// rungs (R-SCAN R1/R2). never-brick #26: pure byte emit/parse, the only syscall is sys_mmap for an int-format 12// scratch. no_silent_failure: an absent tag yields -1, a missing format yields 0 -- never a fabricated value. 13// 14// genealogy_id: project-printer-management-ipp-sclass-2026-06-20 ; license_tier: ORIGINAL 15// CITED: eSCL/Mopria scan uses the PWG SM + HP eSCL schemas; XML uses single-quoted attrs here to stay 16// literal-clean. Namespace prefixes scan:/pwg: are the common emission; prefix-robust parsing is a follow-on. 17 18import "nx_syscalls.nx" 19 20func ex_puts(buf: *u8, off: i64, s: *u8) -> i64 { 21 var o: i64 = off 22 var i: i64 = 0 23 while s[i] != (0 as u8) { buf[o] = s[i]; o = o + 1; i = i + 1 } 24 return o 25} 26 27func ex_puti(buf: *u8, off: i64, v: i64) -> i64 { 28 if v == 0 { buf[off] = 48 as u8; return off + 1 } 29 var o: i64 = off 30 var m: i64 = v 31 let t: *u8 = sys_mmap(24) 32 var k: i64 = 0 33 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 34 var i: i64 = k - 1 35 while i >= 0 { buf[o] = t[i]; o = o + 1; i = i - 1 } 36 return o 37} 38 39// Emit an eSCL ScanSettings document. color/fmt/source are NUL-terminated eSCL tokens 40// (e.g. color="Grayscale8"/"RGB24", fmt="image/jpeg"/"application/pdf", source="Platen"/"Feeder"). 41// Returns the byte length written. 42func nx_escl_scan_settings(buf: *u8, res: i64, w: i64, h: i64, color: *u8, fmt: *u8, source: *u8) -> i64 { 43 var o: i64 = 0 44 o = ex_puts(buf, o, "<?xml version='1.0' encoding='UTF-8'?>\n" as *u8) 45 o = ex_puts(buf, o, "<scan:ScanSettings xmlns:pwg='http://www.pwg.org/schemas/2010/12/sm' xmlns:scan='http://schemas.hp.com/imaging/escl/2011/05/03'>\n" as *u8) 46 o = ex_puts(buf, o, "<pwg:Version>2.6</pwg:Version>\n" as *u8) 47 o = ex_puts(buf, o, "<scan:Intent>Document</scan:Intent>\n" as *u8) 48 o = ex_puts(buf, o, "<pwg:ScanRegions><pwg:ScanRegion>" as *u8) 49 o = ex_puts(buf, o, "<pwg:Height>" as *u8); o = ex_puti(buf, o, h); o = ex_puts(buf, o, "</pwg:Height>" as *u8) 50 o = ex_puts(buf, o, "<pwg:Width>" as *u8); o = ex_puti(buf, o, w); o = ex_puts(buf, o, "</pwg:Width>" as *u8) 51 o = ex_puts(buf, o, "<pwg:XOffset>0</pwg:XOffset><pwg:YOffset>0</pwg:YOffset>" as *u8) 52 o = ex_puts(buf, o, "</pwg:ScanRegion></pwg:ScanRegions>\n" as *u8) 53 o = ex_puts(buf, o, "<scan:InputSource>" as *u8); o = ex_puts(buf, o, source); o = ex_puts(buf, o, "</scan:InputSource>\n" as *u8) 54 o = ex_puts(buf, o, "<scan:ColorMode>" as *u8); o = ex_puts(buf, o, color); o = ex_puts(buf, o, "</scan:ColorMode>\n" as *u8) 55 o = ex_puts(buf, o, "<scan:XResolution>" as *u8); o = ex_puti(buf, o, res); o = ex_puts(buf, o, "</scan:XResolution>\n" as *u8) 56 o = ex_puts(buf, o, "<scan:YResolution>" as *u8); o = ex_puti(buf, o, res); o = ex_puts(buf, o, "</scan:YResolution>\n" as *u8) 57 o = ex_puts(buf, o, "<pwg:DocumentFormat>" as *u8); o = ex_puts(buf, o, fmt); o = ex_puts(buf, o, "</pwg:DocumentFormat>\n" as *u8) 58 o = ex_puts(buf, o, "</scan:ScanSettings>\n" as *u8) 59 return o 60} 61 62// First index of needle (length nlen) in buf[0..n), or -1. Bounded. 63func nx_escl_find(buf: *u8, n: i64, needle: *u8, nlen: i64) -> i64 { 64 if nlen <= 0 { return 0 - 1 } 65 if nlen > n { return 0 - 1 } 66 let last: i64 = n - nlen 67 var i: i64 = 0 68 var keep: i64 = 1 69 var found: i64 = 0 - 1 70 while keep == 1 { 71 if i > last { keep = 0 } 72 else { 73 var j: i64 = 0 74 var ok: i64 = 1 75 while j < nlen { if (buf[i + j] as i64) != (needle[j] as i64) { ok = 0; j = nlen } else { j = j + 1 } } 76 if ok == 1 { found = i; keep = 0 } 77 else { i = i + 1 } 78 } 79 } 80 return found 81} 82 83// 1 if buf contains needle, else 0. 84func nx_escl_has(buf: *u8, n: i64, needle: *u8, nlen: i64) -> i64 { 85 if nx_escl_find(buf, n, needle, nlen) >= 0 { return 1 } 86 return 0 87} 88 89// Integer value immediately following an opening tag like "<scan:MaxWidth>" (tagopen incl. the '>'). 90// Returns the parsed integer, or -1 if the tag is absent / not followed by digits. 91func nx_escl_caps_int(buf: *u8, n: i64, tagopen: *u8, tlen: i64) -> i64 { 92 let idx: i64 = nx_escl_find(buf, n, tagopen, tlen) 93 if idx < 0 { return 0 - 1 } 94 var o: i64 = idx + tlen 95 var v: i64 = 0 96 var any: i64 = 0 97 var keep: i64 = 1 98 while keep == 1 { 99 if o >= n { keep = 0 } 100 else { 101 let ch: i64 = buf[o] as i64 102 if ch >= 48 { if ch <= 57 { v = v * 10 + (ch - 48); any = 1; o = o + 1 } else { keep = 0 } } 103 else { keep = 0 } 104 } 105 } 106 if any == 0 { return 0 - 1 } 107 return v 108}