nx_cam_chaturbate_probe.nx source
↩ module page · 283 lines · 13796 B
1// nx_cam_chaturbate_probe.nx -- the NETWORK DRIVER for nx_cam_chaturbate (/compare/mediaingest R9).
2//
3// SPLIT ON PURPOSE, following the R1 precedent in this same lane (nx_source_health.nx pure lib +
4// nx_source_health_probe.nx driver): the decision core stays import-light so its gate can prove every
5// state with no network and no TLS closure, and only THIS organ carries the fetch stack. The lane has
6// already paid for ignoring that split once -- nx_media_grab still cannot take its pacing import
7// because the added closure tipped it past the nx_cc module limit.
8//
9// VERBS
10// nx_cam_chaturbate_probe bulk [user]
11// GET /affiliates/api/onlinerooms/?format=json -- ONE request that carries EVERY online room.
12// With no user it prints the record count; with a user it prints that room's state.
13// THIS IS THE RATE-LIMIT-SAFE PATH and the default way to watch a fleet: N performers cost one
14// call, not N. Paying one ajax per performer is what flags an exit IP into a site-wide 403.
15// nx_cam_chaturbate_probe resolve <user>
16// POST /get_edge_hls_url_ajax/ -- the per-room URL resolve. Costs Cloudflare exposure, so it is
17// for the moment a recording actually starts, never for polling.
18//
19// *** THE RESOLVED URL IS NEVER FETCHED BY THIS ORGAN. ***
20// The edge token it carries is single-use / connection-bound: any fetch of the tokenised playlist --
21// including a "just checking it works" GET -- consumes it and the real consumer then gets 403. This
22// organ prints the URL and stops. cb_capture_contract() states that rule in a return value so a gate
23// can assert it from outside, rather than in a comment no caller can see.
24//
25// Both verbs go through the R0 pacer, so this cannot itself become the thing that gets us throttled.
26// Egress is sovereign-owned only: no residential proxy pool, ever (standing bound, R12).
27// license_tier: ORIGINAL
28
29import "nx_syscalls.nx"
30import "nx_cam_chaturbate.nx"
31import "nx_source_health.nx" // R1 sh_preflight -- see p_preflight_skip / p_record below
32import "nx_x509_trust_store.nx"
33import "nx_tls13_client_validate_certificate.nx"
34import "nx_tls13_client_session_run.nx"
35import "nx_tls13_chrome_session.nx"
36import "nx_https_url_for_fetch.nx"
37import "nx_https_url_connect.nx"
38import "nx_https_post_complete.nx"
39import "nx_https_fetch_follow.nx"
40import "nx_http_response_parse.nx"
41import "nx_csprng.nx"
42import "nx_paced_fetch.nx"
43
44const P_OUT_CAP: i64 = 8388608 // 8 MiB: the online-rooms feed is large and must not be truncated
45const P_URL_CAP: i64 = 4096
46const P_BODY_CAP: i64 = 1024
47const P_RAND_BYTES: i64 = 32
48const P_VC_BYTES: i64 = 128
49const P_TARGET_BYTES: i64 = 64
50const P_FDP_BYTES: i64 = 16
51const P_SOCK_TMO_S: i64 = 20
52const P_MAX_HOPS: i64 = 4
53const P_TRUST_CERTS: i64 = 300
54const P_TRUST_CAP: i64 = 4194304
55const P_OFF_BYTES: i64 = 16
56const P_RESP_FIELDS_BYTES: i64 = 128 // nx_http_response_parse writes its parsed fields into this array
57const P_HOST: *u8 = "chaturbate.com"
58
59const P_CERTDATA: *u8 = "data/mozilla_certdata.txt"
60const P_AJAX_URL: *u8 = "https://chaturbate.com/get_edge_hls_url_ajax/"
61const P_AJAX_PATH: *u8 = "/get_edge_hls_url_ajax/"
62const P_BULK_URL: *u8 = "https://chaturbate.com/affiliates/api/onlinerooms/?format=json"
63const P_CT_FORM: *u8 = "application/x-www-form-urlencoded"
64const P_XHDR: *u8 = "X-Requested-With: XMLHttpRequest\r\n"
65
66const P_EXIT_USAGE: i64 = 2
67const P_EXIT_TRUST: i64 = 3
68const P_EXIT_CONNECT: i64 = 4
69const P_EXIT_TLS: i64 = 5
70const P_EXIT_HTTP: i64 = 6
71
72func p_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
73func p_w(s: *u8) -> i64 { sys_write(1, s, p_slen(s)); return 0 }
74func p_wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 }
75func p_wn(v: i64) -> i64 { cb_wn(v); return 0 }
76
77// header/body split: the first CRLFCRLF. Returns the body offset, or -1.
78func p_body_off(buf: *u8, n: i64) -> i64 {
79 var i: i64 = 0
80 while i + 3 < n {
81 if buf[i] == (13 as u8) {
82 if buf[i+1] == (10 as u8) {
83 if buf[i+2] == (13 as u8) { if buf[i+3] == (10 as u8) { return i + 4 } }
84 }
85 }
86 i = i + 1
87 }
88 return 0 - 1
89}
90
91// ---- POST the ajax and return the raw response length; sets status_out[0]. ----
92// The ONLY network call this verb makes. It does NOT touch the URL it comes back with.
93func p_post_ajax(user: *u8, out: *u8, out_cap: i64, status_out: *i64) -> i64 {
94 status_out[0] = 0
95 let lr: i64 = nx_trust_store_load_from_certdata(P_CERTDATA, P_TRUST_CERTS, P_TRUST_CAP)
96 if lr <= 0 { return 0 - P_EXIT_TRUST }
97 let store: *TrustStore = lr as *TrustStore
98
99 let urlbuf: *u8 = sys_mmap(P_URL_CAP)
100 var ui: i64 = 0
101 while P_AJAX_URL[ui] != (0 as u8) { urlbuf[ui] = P_AJAX_URL[ui]; ui = ui + 1 }
102 urlbuf[ui] = 0 as u8
103
104 let target_raw: *u8 = sys_mmap(P_TARGET_BYTES)
105 let target: *NxHttpsTarget = target_raw as *NxHttpsTarget
106 target.url = nx_url_new()
107 target.port = 0
108 if nx_https_url_for_fetch(urlbuf, target) != NX_HTTPS_URL_OK { return 0 - P_EXIT_CONNECT }
109
110 let fd_p: *i64 = (sys_mmap(P_FDP_BYTES)) as *i64
111 if nx_https_url_connect(target, urlbuf, sys_now_realtime_sec(), fd_p) != NX_HTTPS_CONNECT_OK { return 0 - P_EXIT_CONNECT }
112 let fd: i64 = fd_p[0]
113 sys_set_socket_timeout(fd, P_SOCK_TMO_S)
114
115 let cr: *u8 = sys_mmap(P_RAND_BYTES); nx_csprng_fill(cr, P_RAND_BYTES)
116 let priv: *u8 = sys_mmap(P_RAND_BYTES); nx_csprng_fill(priv, P_RAND_BYTES)
117 let vc_raw: *u8 = sys_mmap(P_VC_BYTES)
118 let vc: *TlsValidationContext = vc_raw as *TlsValidationContext
119 vc.store = store
120 vc.sni_host = urlbuf + target.url.host_off
121 vc.sni_host_len = target.url.host_len
122 vc.now_epoch = sys_now_realtime_sec()
123 vc.cached_cert = 0 as *u8
124 vc.cached_cert_len = 0
125 vc.cert_out = 0 as *u8
126 vc.cert_out_cap = 0
127 vc.cert_out_len = 0
128
129 // Chrome-JA3 hello: CB's edge fingerprints its clients, so the minimal hello draws a challenge.
130 let sr: i64 = nx_tls13_client_session_run_chrome(fd, urlbuf + target.url.host_off, target.url.host_len, cr, priv, vc)
131 if sr <= 0 { sys_close(fd); return 0 - P_EXIT_TLS }
132 let s: *Tls13ClientSession = sr as *Tls13ClientSession
133
134 let body: *u8 = sys_mmap(P_BODY_CAP)
135 let bn: i64 = cb_build_body(user, body, P_BODY_CAP)
136 if bn <= 0 { sys_close(fd); return 0 - P_EXIT_USAGE }
137
138 let n: i64 = nx_https_post_complete_xhdr(
139 s, fd,
140 P_AJAX_PATH, p_slen(P_AJAX_PATH),
141 urlbuf + target.url.host_off, target.url.host_len,
142 P_CT_FORM, p_slen(P_CT_FORM),
143 body, bn,
144 "" as *u8, 0,
145 P_XHDR, p_slen(P_XHDR),
146 out, out_cap
147 )
148 sys_close(fd)
149 if n <= 0 { return 0 - P_EXIT_HTTP }
150
151 let r: *i64 = (sys_mmap(P_RESP_FIELDS_BYTES)) as *i64
152 if nx_http_response_parse(out, n, r) != 0 { return 0 - P_EXIT_HTTP }
153 status_out[0] = r[1]
154 return n
155}
156
157// ---- R1 SOURCE HEALTH IN THE SHIPPING PATH -----------------------------------------------------
158// nx_compare_rank measured R1 as FINISH state=LIB-GATE-ONLY importers=2 -- sh_preflight was imported
159// ONLY by its own gate and its own probe, i.e. by validation organs, never by a program that ships
160// work. A contract symbol that exists and that nothing in the working path calls is the adoption
161// inversion the estate keeps paying for: the rung reads done on the board and buys nothing. These two
162// helpers are the smallest honest fix -- the chaturbate lane ASKS before it spends a request and
163// RECORDS what happened after, so the persisted verdict a flapping CDN writes is actually consulted.
164func p_preflight_skip(now_ms: i64) -> i64 {
165 let v: i64 = sh_lookup(P_HOST, p_slen(P_HOST), now_ms)
166 if sh_should_skip(v) == 1 {
167 p_w("SKIP host=" as *u8); p_w(P_HOST)
168 p_w(" source-health=" as *u8); p_w(sh_verdict_str(v))
169 p_w(" -- a persisted DEAD or WALLED verdict inside its TTL; not spending a request on it\n" as *u8)
170 return 1
171 }
172 return 0
173}
174
175// connect_ok is derived from what we actually observed, never assumed: a status line means the peer
176// answered. wall_code stays SH_ABT_CLEAN here because per-response bot-wall classification is R13's
177// job in the capture path, not this pre-flight's -- claiming a wall we did not classify would be the
178// over-blocking false positive R1 already had to correct once.
179func p_record(connect_ok: i64, status: i64, elapsed_ms: i64, now_ms: i64) -> i64 {
180 return sh_preflight(P_HOST, p_slen(P_HOST), connect_ok, status, elapsed_ms, SH_ABT_CLEAN, now_ms)
181}
182
183func main(argc: i64, argv: *i64) -> i64 {
184 if argc < 2 {
185 p_w("usage: nx_cam_chaturbate_probe bulk [user] | resolve <user>\n" as *u8)
186 p_w(" bulk -- ONE request covering every online room (the rate-limit-safe fleet path)\n" as *u8)
187 p_w(" resolve -- per-room POST; costs Cloudflare exposure, use only at record start\n" as *u8)
188 p_w(" NOTE: a resolved URL carries a SINGLE-USE edge token. This organ never fetches it.\n" as *u8)
189 sys_exit(P_EXIT_USAGE); return P_EXIT_USAGE
190 }
191 let verb: *u8 = argv[1] as *u8
192
193 // ---- bulk ----
194 if cb_ieq(verb, 0, p_slen(verb), "bulk" as *u8) == 1 {
195 let store_r: i64 = nx_trust_store_load_from_certdata(P_CERTDATA, P_TRUST_CERTS, P_TRUST_CAP)
196 if store_r <= 0 { p_w("BULK trust-load FAIL\n" as *u8); sys_exit(P_EXIT_TRUST); return P_EXIT_TRUST }
197 let store: *TrustStore = store_r as *TrustStore
198 let out: *u8 = sys_mmap(P_OUT_CAP)
199 let st: *i64 = (sys_mmap(P_OFF_BYTES)) as *i64
200 let t0: i64 = sys_now_realtime_ms()
201 if p_preflight_skip(t0) == 1 { return 0 }
202 pf_before_burst(P_BULK_URL)
203 let n: i64 = nx_https_fetch_follow_chrome(P_BULK_URL, store, out, P_OUT_CAP, P_MAX_HOPS, st)
204 let t1: i64 = sys_now_realtime_ms()
205 pf_after(P_BULK_URL, st[0], 0)
206 var ok0: i64 = 0
207 if st[0] > 0 { ok0 = 1 }
208 p_record(ok0, st[0], t1 - t0, t1)
209 p_w("BULK http=" as *u8); p_wn(st[0]); p_w(" bytes=" as *u8); p_wn(n); p_w("\n" as *u8)
210 if n <= 0 { p_w("BULK fetch FAILED (rc above is -verdict)\n" as *u8); sys_exit(P_EXIT_HTTP); return P_EXIT_HTTP }
211 if argc >= 3 {
212 let user: *u8 = argv[2] as *u8
213 let off: *i64 = (sys_mmap(P_OFF_BYTES)) as *i64
214 let stt: i64 = cb_bulk_lookup(out, n, user, off)
215 p_w("BULK user=" as *u8); p_w(user); p_w(" state=" as *u8); p_w(cb_state_name(stt))
216 p_w(" recordable=" as *u8); p_wn(cb_is_recordable(stt)); p_w("\n" as *u8)
217 } else {
218 p_w("BULK records=" as *u8); p_wn(cb_bulk_count(out, n)); p_w("\n" as *u8)
219 }
220 return 0
221 }
222
223 // ---- resolve ----
224 if cb_ieq(verb, 0, p_slen(verb), "resolve" as *u8) == 1 {
225 if argc < 3 { p_w("usage: nx_cam_chaturbate_probe resolve <user>\n" as *u8); sys_exit(P_EXIT_USAGE); return P_EXIT_USAGE }
226 let user: *u8 = argv[2] as *u8
227 let out: *u8 = sys_mmap(P_OUT_CAP)
228 let st: *i64 = (sys_mmap(P_OFF_BYTES)) as *i64
229 let t0: i64 = sys_now_realtime_ms()
230 if p_preflight_skip(t0) == 1 { return 0 }
231 pf_before_burst(P_AJAX_URL)
232 let n: i64 = p_post_ajax(user, out, P_OUT_CAP, st)
233 let t1: i64 = sys_now_realtime_ms()
234 pf_after(P_AJAX_URL, st[0], 0)
235 var okr: i64 = 0
236 if st[0] > 0 { okr = 1 }
237 p_record(okr, st[0], t1 - t0, t1)
238 if n <= 0 { p_w("RESOLVE transport FAILED rc=" as *u8); p_wn(n); p_w("\n" as *u8); sys_exit(P_EXIT_HTTP); return P_EXIT_HTTP }
239 let bo: i64 = p_body_off(out, n)
240 var blen: i64 = 0
241 if bo >= 0 { blen = n - bo }
242 let off: *i64 = (sys_mmap(P_OFF_BYTES)) as *i64
243 var rs_off: i64 = 0
244 var rs_len: i64 = 0
245 var url_off: i64 = 0
246 var url_len: i64 = 0
247 if bo >= 0 {
248 rs_len = cb_field(((out as i64) + bo) as *u8, blen, "room_status" as *u8, off)
249 if rs_len > 0 { rs_off = off[0] }
250 url_len = cb_field(((out as i64) + bo) as *u8, blen, "url" as *u8, off)
251 if url_len > 0 { url_off = off[0] } else { if url_len == 0 { url_off = off[0] } }
252 }
253 if rs_len < 0 { rs_len = 0 }
254 if url_len < 0 { url_len = 0 }
255 let state: i64 = cb_decide(st[0], ((out as i64) + bo) as *u8, rs_off, rs_len, url_len)
256 p_w("RESOLVE user=" as *u8); p_w(user)
257 p_w(" http=" as *u8); p_wn(st[0])
258 p_w(" state=" as *u8); p_w(cb_state_name(state))
259 p_w(" recordable=" as *u8); p_wn(cb_is_recordable(state))
260 p_w(" backoff=" as *u8); p_wn(cb_should_backoff(state))
261 p_w("\n" as *u8)
262 if cb_is_recordable(state) == 1 {
263 let cmaf: i64 = cb_flag(((out as i64) + bo) as *u8, blen, "cmaf_edge" as *u8)
264 let rawurl: *u8 = ((out as i64) + bo + url_off) as *u8
265 p_w("RESOLVE cmaf_edge=" as *u8); p_wn(cmaf); p_w("\n" as *u8)
266 if cmaf == 1 {
267 let rw: *u8 = sys_mmap(P_URL_CAP)
268 let rn: i64 = cb_cmaf_rewrite(rawurl, url_len, rw, P_URL_CAP)
269 if rn > 0 { p_w("RESOLVE url=" as *u8); p_wb(rw, rn); p_w("\n" as *u8) }
270 else { p_w("RESOLVE url=" as *u8); p_wb(rawurl, url_len); p_w("\n" as *u8) }
271 } else {
272 p_w("RESOLVE url=" as *u8); p_wb(rawurl, url_len); p_w("\n" as *u8)
273 }
274 // The contract, printed so the next reader cannot miss it.
275 p_w("RESOLVE handoff=SINGLE-CONSUMER the edge token is single-use; hand this URL to ONE consumer and do not re-resolve or pre-probe it\n" as *u8)
276 }
277 return 0
278 }
279
280 p_w("unknown verb\n" as *u8)
281 sys_exit(P_EXIT_USAGE)
282 return P_EXIT_USAGE
283}