nx_research_ka.nx source
↩ module page · 211 lines · 12732 B
1// nx_research_ka.nx -- R4 FULL: per-host connection POOL + keep-alive fetch, with a PROVEN-path fallback.
2// Operator 2026-07-01 chose R4. nx_https_get_complete has 30 callers (untouchable) so this is a SELF-CONTAINED module:
3// * caches ONE (host, fd, session) across calls (module static globals) -> a researcher hitting ONE host repeatedly
4// pays the ~300ms cert-verify (certloop) ONCE, then reuses the TLS session for every subsequent GET.
5// * SAFE by construction: only the clean case (fresh/reused -> HTTP 200, Content-Length body) uses the fast path;
6// ANYTHING else (redirect/non-200/stale/parse-miss) closes the pooled conn and FALLS BACK to the proven
7// nx_https_fetch_follow (fresh connect, full redirect+chunk handling). Worst case == today's behavior.
8// Proven mechanism: nx_https_ka_probe (2 GETs / 1 handshake, GREEN). license_tier: ORIGINAL expect_exit: 0
9import "nx_syscalls.nx"
10import "nx_tls13.nx"
11import "nx_tls13_record.nx"
12import "nx_tls13_read_record_from_fd.nx"
13import "nx_tls13_client_session.nx"
14import "nx_x509_trust_store.nx"
15import "nx_trust_store_load_from_certdata.nx"
16import "nx_tls13_client_validate_certificate.nx"
17import "nx_tls13_client_session_run.nx"
18import "nx_url.nx"
19import "nx_https_url_for_fetch.nx"
20import "nx_https_url_connect.nx"
21import "nx_http_response_parse.nx"
22import "nx_https_fetch_follow.nx"
23const K_MAGIC_4096: i64 = 4096
24const K_MAGIC_16645: i64 = 16645
25const K_MAGIC_2048: i64 = 2048
26const K_MAGIC_4194304: i64 = 4194304
27
28// ---- single-entry per-host connection pool (module state) ----
29static G_KA_HOSTBUF: i64 // ptr to a persistent 512B host buffer (0 = uninit)
30static G_KA_HLEN: i64
31static G_KA_FD: i64 // 0 = no pooled connection
32static G_KA_SESS: i64 // *Tls13ClientSession
33static G_KA_HANDSHAKES:i64 // count of fresh handshakes (test/telemetry)
34static G_KA_REUSES: i64 // count of reused fetches
35
36func rka_handshakes() -> i64 { return G_KA_HANDSHAKES }
37func rka_reuses() -> i64 { return G_KA_REUSES }
38
39func rp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
40func rn(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
41func rka_write_n(fd: i64, buf: *u8, n: i64) -> i64 { var off: i64=0; while off<n { let w: i64=sys_write(fd,(buf as i64+off) as *u8,n-off); if w<=0 {return 0-1} off=off+w } return 0 }
42func rka_cat(out: *u8, o: i64, s: *u8) -> i64 { var j: i64=0; while s[j]!=(0 as u8) { out[o]=s[j]; o=o+1; j=j+1 } return o }
43
44func rka_build_req(path: *u8, path_len: i64, host: *u8, host_len: i64, out: *u8) -> i64 {
45 var o: i64 = 0
46 o = rka_cat(out, o, "GET " as *u8)
47 var pi: i64=0; while pi<path_len { out[o]=path[pi]; o=o+1; pi=pi+1 }
48 o = rka_cat(out, o, " HTTP/1.1\r\nHost: " as *u8)
49 var hi: i64=0; while hi<host_len { out[o]=host[hi]; o=o+1; hi=hi+1 }
50 o = rka_cat(out, o, "\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.5\r\nAccept-Encoding: identity\r\nConnection: keep-alive\r\n\r\n" as *u8)
51 return o
52}
53func rka_hdr_end(buf: *u8, n: i64) -> i64 { var i: i64=0; while i+4<=n { if buf[i]==(13 as u8){if buf[i+1]==(10 as u8){if buf[i+2]==(13 as u8){if buf[i+3]==(10 as u8){return i}}}} i=i+1 } return 0-1 }
54func rka_lc(c: i64) -> i64 { if c>=65 { if c<=90 { return c+32 } } return c }
55func rka_clen(buf: *u8, hdr_end: i64) -> i64 {
56 let pat: *u8 = "content-length:" as *u8
57 var i: i64=0
58 while i<hdr_end {
59 var atline: i64=0
60 if i==0 { atline=1 } else { if buf[i-1]==(10 as u8) { atline=1 } }
61 if atline==1 {
62 var mm: i64=1; var j: i64=0
63 while j<15 { if rka_lc(buf[i+j] as i64)!=rka_lc(pat[j] as i64) { mm=0; j=15 } else { j=j+1 } }
64 if mm==1 {
65 var p: i64=i+15
66 var d: i64=0
67 while d==0 { if p>=hdr_end { d=1 } else { if buf[p]==(32 as u8) { p=p+1 } else { d=1 } } }
68 var v: i64=0; var any: i64=0; var go: i64=1
69 while go==1 { if p>=hdr_end { go=0 } else { let c: i64=buf[p] as i64; if c>=48 { if c<=57 { v=v*10+(c-48); any=1; p=p+1 } else { go=0 } } else { go=0 } } }
70 if any==1 { return v }
71 return 0-1
72 }
73 }
74 i=i+1
75 }
76 return 0-1
77}
78// send ONE keep-alive GET over an OPEN session+fd, drain Content-Length body, LEAVE fd open. (mirrors get_complete's cl-stop)
79func rka_get(s: *Tls13ClientSession, fd: i64, path: *u8, path_len: i64, host: *u8, host_len: i64, out_buf: *u8, out_cap: i64) -> i64 {
80 if s.state != NX_TLS13_CSESSION_STATE_CONNECTED { return 0-1 }
81 let req: *u8 = sys_mmap(K_MAGIC_4096)
82 let req_len: i64 = rka_build_req(path, path_len, host, host_len, req)
83 let rec_buf: *u8 = sys_mmap(req_len + 64)
84 let ct_out: *u8 = rec_buf + NX_TLS13_RECORD_HEADER_LEN
85 let tag_out: *u8 = rec_buf + NX_TLS13_RECORD_HEADER_LEN + req_len + 1
86 let enc_v: i64 = nx_tls13_record_encrypt_v2(s.cipher_suite, s.client_app_traffic_key, s.client_app_iv, s.client_app_seq, req, req_len, NX_TLS13_CT_APPLICATION_DATA, 0, rec_buf, ct_out, tag_out)
87 s.client_app_seq = s.client_app_seq + 1
88 if enc_v != NX_TLS13_REC_VERDICT_OK { return 0-1 }
89 let total_rec_len: i64 = NX_TLS13_RECORD_HEADER_LEN + req_len + 1 + NX_TLS13_RECORD_TAG_LEN
90 if rka_write_n(fd, rec_buf, total_rec_len) < 0 { return 0-1 }
91 var accumulated: i64 = 0
92 var body_target: i64 = 0
93 var hdr_parsed: i64 = 0
94 let rec_in: *u8 = sys_mmap(K_MAGIC_16645)
95 let plaintext: *u8 = sys_mmap(K_MAGIC_16645)
96 let pct: *i64 = sys_mmap(16) as *i64
97 let plen: *i64 = sys_mmap(16) as *i64
98 while accumulated < out_cap {
99 let rt: i64 = nx_tls13_read_record_from_fd(fd, rec_in, K_MAGIC_16645)
100 if rt < 0 { return accumulated }
101 let rec_ct: *u8 = rec_in + NX_TLS13_RECORD_HEADER_LEN
102 let rec_ct_len: i64 = rt - NX_TLS13_RECORD_HEADER_LEN - NX_TLS13_RECORD_TAG_LEN
103 let rec_tag: *u8 = rec_in + rt - NX_TLS13_RECORD_TAG_LEN
104 let dv: i64 = nx_tls13_record_decrypt_v2(s.cipher_suite, s.server_app_traffic_key, s.server_app_iv, s.server_app_seq, rec_in, rec_ct, rec_ct_len, rec_tag, plaintext, pct, plen)
105 s.server_app_seq = s.server_app_seq + 1
106 if dv != NX_TLS13_REC_VERDICT_OK { return 0-1 }
107 if *pct == NX_TLS13_CT_ALERT { return accumulated }
108 if *pct == NX_TLS13_CT_APPLICATION_DATA {
109 let to_copy: i64 = *plen
110 if to_copy > out_cap - accumulated { return accumulated }
111 var i: i64=0; while i<to_copy { out_buf[accumulated+i]=plaintext[i]; i=i+1 }
112 accumulated = accumulated + to_copy
113 if hdr_parsed==0 { let he: i64=rka_hdr_end(out_buf, accumulated); if he>=0 { hdr_parsed=1; let cl: i64=rka_clen(out_buf, he); if cl>=0 { body_target=he+4+cl } } }
114 if body_target>0 { if accumulated>=body_target { return accumulated } }
115 }
116 }
117 return accumulated
118}
119
120func rka_path(urlbuf: *u8, target: *NxHttpsTarget, path_out: *u8) -> i64 {
121 var p: i64 = target.url.host_off + target.url.host_len
122 if urlbuf[p] == 0x3A as u8 {
123 p = p + 1
124 while urlbuf[p] >= 0x30 as u8 { if urlbuf[p] <= 0x39 as u8 { p = p + 1 } else { break } }
125 }
126 if urlbuf[p] != 0x2F as u8 { path_out[0] = 0x2F as u8; path_out[1] = 0 as u8; return 1 }
127 var k: i64 = 0
128 while urlbuf[p] != 0 as u8 { path_out[k] = urlbuf[p]; p = p + 1; k = k + 1 }
129 path_out[k] = 0 as u8
130 return k
131}
132func rka_host_match(host: *u8, hlen: i64) -> i64 {
133 if G_KA_FD == 0 { return 0 }
134 if G_KA_HOSTBUF == 0 { return 0 }
135 if G_KA_HLEN != hlen { return 0 }
136 let hb: *u8 = G_KA_HOSTBUF as *u8
137 var i: i64 = 0
138 while i < hlen { if hb[i] != host[i] { return 0 } i = i + 1 }
139 return 1
140}
141func rka_host_cache(host: *u8, hlen: i64) -> i64 {
142 if G_KA_HOSTBUF == 0 { G_KA_HOSTBUF = sys_mmap(512) as i64 }
143 let hb: *u8 = G_KA_HOSTBUF as *u8
144 var i: i64 = 0
145 while i < hlen { hb[i] = host[i]; i = i + 1 }
146 G_KA_HLEN = hlen
147 return 0
148}
149
150// THE pooled keep-alive fetch. Drop-in for nx_https_fetch_follow (same signature); falls back to it on any non-clean case.
151func rf_fetch_ka(url0: *u8, store: *TrustStore, out: *u8, out_cap: i64, out_status: *i64) -> i64 {
152 out_status[0] = 0
153 let urlbuf: *u8 = sys_mmap(K_MAGIC_4096)
154 var ui: i64=0; while url0[ui]!=(0 as u8) { urlbuf[ui]=url0[ui]; ui=ui+1 } urlbuf[ui]=0 as u8
155 let target_raw: *u8 = sys_mmap(64); let target: *NxHttpsTarget = target_raw as *NxHttpsTarget
156 target.url = nx_url_new(); target.port = 0
157 if nx_https_url_for_fetch(urlbuf, target) != NX_HTTPS_URL_OK { return nx_https_fetch_follow(url0, store, out, out_cap, 6, out_status) }
158 let host: *u8 = urlbuf + target.url.host_off
159 let hlen: i64 = target.url.host_len
160 let path: *u8 = sys_mmap(K_MAGIC_2048)
161 let plen: i64 = rka_path(urlbuf, target, path)
162 let rr: *i64 = sys_mmap(128) as *i64
163 // ---- POOL REUSE (no handshake) ----
164 if rka_host_match(host, hlen) == 1 {
165 let session: *Tls13ClientSession = G_KA_SESS as *Tls13ClientSession
166 let n: i64 = rka_get(session, G_KA_FD, path, plen, host, hlen, out, out_cap)
167 if n > 0 { if nx_http_response_parse(out, n, rr) == 0 { if rr[1] == 200 { G_KA_REUSES = G_KA_REUSES + 1; out_status[0]=200; return n } } }
168 sys_close(G_KA_FD); G_KA_FD = 0 // stale/non-200 -> drop pooled conn, fall through to fresh
169 }
170 // ---- FRESH connect + handshake (pays certloop ONCE, then cache) ----
171 let fd_p: *i64 = sys_mmap(16) as *i64
172 if nx_https_url_connect(target, urlbuf, sys_now_realtime_sec(), fd_p) != NX_HTTPS_CONNECT_OK { return nx_https_fetch_follow(url0, store, out, out_cap, 6, out_status) }
173 let fd: i64 = fd_p[0]
174 let cr: *u8 = sys_mmap(32); let pv: *u8 = sys_mmap(32)
175 var i: i64=0; while i<32 { cr[i]=(0xC0+i) as u8; pv[i]=(0xA0+i) as u8; i=i+1 }
176 let vc_raw: *u8 = sys_mmap(64); let vc: *TlsValidationContext = vc_raw as *TlsValidationContext
177 vc.store = store; vc.sni_host = host; vc.sni_host_len = hlen; vc.now_epoch = sys_now_realtime_sec()
178 G_KA_HANDSHAKES = G_KA_HANDSHAKES + 1
179 let sr: i64 = nx_tls13_client_session_run(fd, host, hlen, cr, pv, vc)
180 if sr <= 0 { sys_close(fd); return nx_https_fetch_follow(url0, store, out, out_cap, 6, out_status) }
181 let session2: *Tls13ClientSession = sr as *Tls13ClientSession
182 let n2: i64 = rka_get(session2, fd, path, plen, host, hlen, out, out_cap)
183 if n2 > 0 { if nx_http_response_parse(out, n2, rr) == 0 { if rr[1] == 200 {
184 rka_host_cache(host, hlen); G_KA_FD = fd; G_KA_SESS = sr // CACHE for reuse
185 out_status[0]=200; return n2
186 } } }
187 sys_close(fd) // non-200 (redirect/chunked/etc) -> proven fallback (fresh, full handling)
188 return nx_https_fetch_follow(url0, store, out, out_cap, 6, out_status)
189}
190
191// self-test: fetch the SAME host 3x -> expect 1 handshake + 2 reuses, all 200.
192func main() -> i64 {
193 let sres: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, K_MAGIC_4194304)
194 if sres <= 0 { rp("trust load FAIL\n" as *u8); return 1 }
195 let store: *TrustStore = sres as *TrustStore
196 let cap: i64 = K_MAGIC_4194304
197 let out: *u8 = sys_mmap(cap)
198 let status: *i64 = sys_mmap(16) as *i64
199 rp("=== nx_research_ka -- R4 per-host connection POOL (3 GETs to 1 host -> 1 handshake) ===\n" as *u8)
200 let a: i64 = rf_fetch_ka("https://en.wikipedia.org/wiki/WebGL" as *u8, store, out, cap, status)
201 rp(" GET1 bytes="); rn(a); rp(" status="); rn(status[0]); rp("\n" as *u8)
202 let b: i64 = rf_fetch_ka("https://en.wikipedia.org/wiki/WebGL" as *u8, store, out, cap, status)
203 rp(" GET2 bytes="); rn(b); rp(" status="); rn(status[0]); rp("\n" as *u8)
204 let c: i64 = rf_fetch_ka("https://en.wikipedia.org/wiki/WebGL" as *u8, store, out, cap, status)
205 rp(" GET3 bytes="); rn(c); rp(" status="); rn(status[0]); rp("\n" as *u8)
206 rp(" handshakes="); rn(rka_handshakes()); rp(" reuses="); rn(rka_reuses()); rp("\n" as *u8)
207 var green: i64=0; if a>0 { if b>0 { if c>0 { if rka_handshakes()==1 { if rka_reuses()==2 { green=1 } } } } }
208 if green==1 { rp("verdict=GREEN -- R4 FULL: 3 fetches to 1 host used 1 handshake + 2 connection reuses (cert tax amortized).\n" as *u8); return 0 }
209 rp("verdict=RED (handshakes="); rn(rka_handshakes()); rp(" reuses="); rn(rka_reuses()); rp(")\n" as *u8)
210 return 1
211}