nx_etag.nx source
↩ module page · 161 lines · 5777 B
1// etag.nx -- generate + compare HTTP ETag headers (RFC 7232).
2//
3// The ETag header identifies a specific version of a resource so
4// conditional requests (If-None-Match / If-Match) can avoid
5// re-sending bytes that haven't changed. Every static-file server
6// needs this, every CDN lives or dies on it.
7//
8// Format (RFC 7232 §2.3):
9// ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" <- strong
10// ETag: W/"3ad4a9c3" <- weak
11//
12// Strong ETags mean byte-exact match; weak (W/-prefixed) means
13// semantically equivalent but possibly byte-different (e.g. same
14// content reflowed whitespace).
15//
16// We generate ETags by:
17// 1. sha256 (existing) of the file contents -> 32 bytes
18// 2. hex-encode (existing) -> 64 ASCII chars
19// 3. truncate to etag_size bytes (caller choice; 8-32 typical)
20// 4. wrap in double quotes
21//
22// Comparison follows RFC 7232 §2.3.2: strong match requires both
23// to be strong and byte-identical; weak match matches regardless
24// of W/- prefix.
25//
26// Invariants:
27// ET1 Output always ends with a trailing DQUOTE.
28// ET2 Weak prefix is exactly "W/" (2 ASCII bytes, case-sensitive).
29// ET3 Caller chooses truncation length; 8 hex chars (4 bytes
30// of entropy) is the minimum practical, 32 (128 bits) is
31// plenty.
32
33// nx_safety_envelope:
34// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
35// sil_target: SIL1
36// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
37// verdict: NOT_YET_EVALUATED
38
39import "nx_syscalls.nx"
40import "nx_sha256.nx"
41import "nx_hex_codec.nx"
42
43const ETAG_ERR_SHORT: i64 = -1
44
45// Generate a strong ETag from data[0..n] into out buffer.
46// `trunc` is how many hex characters to include (without quotes).
47// Writes: "<trunc hex chars>" (trunc + 2 bytes total, DQUOTE-wrapped).
48// Returns bytes written or ETAG_ERR_SHORT.
49func etag_gen_strong(data: *u8, n: i64,
50 out: *u8, cap: i64, trunc: i64) -> i64 {
51 if trunc < 2 { return ETAG_ERR_SHORT }
52 if trunc > 64 { return ETAG_ERR_SHORT }
53 let total: i64 = trunc + 2
54 if cap < total { return ETAG_ERR_SHORT }
55
56 let digest: *u8 = sys_mmap(64)
57 sha256_digest(data, n, digest)
58 let hex_out: *u8 = sys_mmap(96)
59 hex_encode(digest, 32, hex_out)
60
61 out[0] = 0x22 // '\"'
62 var i: i64 = 0
63 while i < trunc {
64 out[1 + i] = hex_out[i]
65 i = i + 1
66 }
67 out[1 + trunc] = 0x22
68 return total
69}
70
71// Generate a weak ETag: W/"<trunc hex>".
72func etag_gen_weak(data: *u8, n: i64,
73 out: *u8, cap: i64, trunc: i64) -> i64 {
74 if cap < trunc + 4 { return ETAG_ERR_SHORT }
75 out[0] = 0x57 // 'W'
76 out[1] = 0x2F // '/'
77 let inner: i64 = etag_gen_strong(data, n, out + 2, cap - 2, trunc)
78 if inner < 0 { return inner }
79 return inner + 2
80}
81
82// Test whether an ETag value begins with the weak "W/" prefix.
83func etag_is_weak(tag: *u8, n: i64) -> i64 {
84 if n < 3 { return 0 }
85 if tag[0] != 0x57 { return 0 }
86 if tag[1] != 0x2F { return 0 }
87 return 1
88}
89
90// Strip outer quotes. Writes (start_off, end_off) into the
91// caller's slot pair. Returns 1 if the tag was well-formed,
92// 0 otherwise.
93func etag_strip_quotes(tag: *u8, n: i64,
94 start_out: *i64, end_out: *i64) -> i64 {
95 var start: i64 = 0
96 if etag_is_weak(tag, n) == 1 { start = 2 }
97 if n < start + 2 { return 0 }
98 if tag[start] != 0x22 { return 0 } // opening quote
99 if tag[n - 1] != 0x22 { return 0 } // closing quote
100 *start_out = start + 1
101 *end_out = n - 1
102 return 1
103}
104
105// Weak match: ignore W/- prefix, compare inner opaque bytes.
106// Returns 1 if they match, 0 otherwise.
107func etag_weak_match(a: *u8, a_len: i64,
108 b: *u8, b_len: i64) -> i64 {
109 let a_start: *i64 = (sys_mmap(32)) as *i64
110 let a_end: *i64 = (sys_mmap(32)) as *i64
111 let b_start: *i64 = (sys_mmap(32)) as *i64
112 let b_end: *i64 = (sys_mmap(32)) as *i64
113 if etag_strip_quotes(a, a_len, a_start, a_end) != 1 { return 0 }
114 if etag_strip_quotes(b, b_len, b_start, b_end) != 1 { return 0 }
115 let la: i64 = *a_end - *a_start
116 let lb: i64 = *b_end - *b_start
117 if la != lb { return 0 }
118 var i: i64 = 0
119 while i < la {
120 if a[*a_start + i] != b[*b_start + i] { return 0 }
121 i = i + 1
122 }
123 return 1
124}
125
126// Strong match: both must be strong AND byte-identical inner.
127func etag_strong_match(a: *u8, a_len: i64,
128 b: *u8, b_len: i64) -> i64 {
129 if etag_is_weak(a, a_len) == 1 { return 0 }
130 if etag_is_weak(b, b_len) == 1 { return 0 }
131 return etag_weak_match(a, a_len, b, b_len)
132}
133
134// Compile-only smoke.
135func main() -> i64 {
136 let tag1: *u8 = sys_mmap(64)
137 let tag2: *u8 = sys_mmap(64)
138 let tag3: *u8 = sys_mmap(64)
139
140 let data: *u8 = "hello world"
141 let l1: i64 = etag_gen_strong(data, 11, tag1, 64, 16)
142 if l1 != 18 { return 1 } // 16 hex + 2 quotes
143 if tag1[0] != 0x22 { return 2 }
144 if tag1[17] != 0x22 { return 3 }
145
146 let l2: i64 = etag_gen_weak(data, 11, tag2, 64, 16)
147 if l2 != 20 { return 4 } // W/ + 18 = 20
148 if tag2[0] != 0x57 { return 5 }
149 if tag2[1] != 0x2F { return 6 }
150
151 // Same underlying data -> weak-match equal.
152 if etag_weak_match(tag1, l1, tag2, l2) != 1 { return 7 }
153 // Strong match fails because tag2 is weak.
154 if etag_strong_match(tag1, l1, tag2, l2) != 0 { return 8 }
155
156 // Different data -> no match.
157 let l3: i64 = etag_gen_strong("hello worlds", 12, tag3, 64, 16)
158 if etag_weak_match(tag1, l1, tag3, l3) != 0 { return 9 }
159 if etag_strong_match(tag1, l1, tag3, l3) != 0 { return 10 }
160 return 0
161}