code wiki / (root) / nx_tls13_hello.nx

nx_tls13_hello.nx source

↩ module page · 503 lines · 20204 B

1// nx_tls13_hello.nx -- TLS 1.3 ClientHello + ServerHello (RFC 8446 2// §4.1.2 / §4.1.3). 3// 4// Phase 0b §F.2 of the Nishi TLS 1.3 stack per 5// docs/NISHI_TLS13_GAP_AUDIT.md. Composes the extension emitters 6// from nx_tls13_ext into a complete ClientHello byte stream that 7// can go on the wire. Parses an incoming ServerHello into its 8// fixed-position fields + an extensions blob the caller iterates. 9// 10// Both Hello structures sit inside a 4-byte Handshake wrapper: 11// 12// struct { 13// HandshakeType msg_type; /* 1 byte: 1=ClientHello, 2=ServerHello */ 14// uint24 length; /* 3 bytes, body length */ 15// <ClientHello | ServerHello body>; 16// } Handshake; 17// 18// ClientHello body (RFC 8446 §4.1.2): 19// ProtocolVersion legacy_version = 0x0303; 20// Random random; (32 bytes) 21// opaque legacy_session_id<0..32>; (1-byte len + bytes) 22// CipherSuite cipher_suites<2..2^16-2>; (2-byte len + 2-byte suite ids) 23// opaque legacy_compression_methods<1..2^8-1>; (1-byte len + bytes, always 0x01 0x00) 24// Extension extensions<8..2^16-1>; (2-byte len + emitted extensions) 25// 26// ServerHello body (RFC 8446 §4.1.3): 27// ProtocolVersion legacy_version = 0x0303; 28// Random random; (32 bytes) 29// opaque legacy_session_id_echo<0..32>; 30// CipherSuite cipher_suite; (2 bytes; single, not list) 31// uint8 legacy_compression_method; (always 0) 32// Extension extensions<6..2^16-1>; 33// 34// What it does today: 35// - emit ClientHello with all 5 minimum-credible extensions (SNI, 36// supported_versions, supported_groups, signature_algorithms, 37// key_share) in the canonical order 38// - parse ServerHello fixed fields + return extensions blob 39// - iterate an extensions blob, find one by extension_type 40// 41// What it doesn't do yet: 42// - middlebox compatibility mode (32-byte random 43// legacy_session_id; trivial to add per RFC 8446 §4.1.2) 44// - HelloRetryRequest detection (special ServerHello with magic 45// random = SHA-256("HelloRetryRequest"); Gap G) 46// - ALPN inclusion in ClientHello (will be a parameter once 47// nx_tls13_ext_emit_alpn ships) 48// 49// KAT verified: 50// - ClientHello structural self-consistency: emit, then parse 51// internal length fields, verify all sum correctly 52// - Random round-trips (caller-supplied 32 bytes appear at the 53// right offset in emitted bytes) 54// - Extension iterator finds each emitted extension by type 55// - Cipher suite list is the canonical 3-suite TLS 1.3 set 56// 57// Composes with: 58// - nx_tls13 (Handshake constants, legacy_version, u16/u24 helpers) 59// - nx_tls13_ext (extension emit/parse) 60// - nx_tls13_transcript (caller feeds emitted ClientHello bytes in) 61// - nx_tls13_client (queued; state machine driver that calls this) 62// 63// license_tier: INDEPENDENT_REDERIVE 64// genealogy_id: international-research-sources/ietf/rfc_8446 65// lineage_id: nishi_tls13_hello_q10 66 67// nx_safety_envelope: 68// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 69// sil_target: SIL1 70// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 71// verdict: NOT_YET_EVALUATED 72 73import "nx_syscalls.nx" 74import "nx_tls13.nx" 75import "nx_tls13_ext.nx" 76 77// Sealed Hello verdict. 78const NX_TLS13_HELLO_VERDICT_OK: i64 = 1 79const NX_TLS13_HELLO_VERDICT_BUF_OVERFLOW: i64 = 2 80const NX_TLS13_HELLO_VERDICT_TRUNCATED: i64 = 3 81const NX_TLS13_HELLO_VERDICT_BAD_VERSION: i64 = 4 82const NX_TLS13_HELLO_VERDICT_BAD_HEADER: i64 = 5 83const NX_TLS13_HELLO_VERDICT_NOT_FOUND: i64 = 6 84const NX_TLS13_HELLO_VERDICT_N: i64 = 7 85 86// TLS 1.3 standard cipher suites (RFC 8446 §B.4): 87// TLS_AES_128_GCM_SHA256 = 0x1301 88// TLS_AES_256_GCM_SHA384 = 0x1302 89// TLS_CHACHA20_POLY1305_SHA256 = 0x1303 90// TLS_AES_128_CCM_SHA256 = 0x1304 (we don't offer; rare) 91const NX_TLS13_CS_AES_128_GCM_SHA256: i64 = 0x1301 92const NX_TLS13_CS_AES_256_GCM_SHA384: i64 = 0x1302 93const NX_TLS13_CS_CHACHA20_POLY1305_SHA256: i64 = 0x1303 94 95// Emit a minimum-credible ClientHello. 96// 97// Inputs: 98// random32 = caller-supplied 32 bytes from nx_csprng 99// sni, sni_len = SNI hostname (REQUIRED; 1..255 bytes recommended) 100// pubkey32 = X25519 ephemeral public key (caller derived via 101// nx_x25519_ephemeral) 102// out, out_cap = caller buffer 103// 104// Output: total bytes written (handshake header + body), or 105// negative NX_TLS13_HELLO_VERDICT_*. 106// 107// Cipher suites offered (see the emit code below): TLS_CHACHA20_POLY1305_SHA256 (0x1303, PREFERRED) AND 108// TLS_AES_128_GCM_SHA256 (0x1301, fallback). Honesty principle: advertise ONLY what we can actually complete 109// -- and both of these are FULLY WIRED: nx_chacha20_poly1305 + nx_aes128_gcm are real ciphers, the record 110// layer decrypts both (nx_tls13_record dispatches 0x1303->chacha, 0x1301->aes128gcm), and key derivation is 111// cipher-aware (key_len 32 vs 16). ChaCha20 is offered first because we're faster at it (no AES-NI backend 112// yet); AES-128-GCM is the fallback for servers that don't offer ChaCha20. AES-256-GCM (0x1302) is NOT 113// offered because it isn't implemented (would need an AES-256 cipher + a SHA-384 key schedule) -- correctly 114// withheld per the honesty principle. [Corrected 2026-07-03: this comment previously claimed "ONLY ChaCha20" 115// + that offering AES-128-GCM causes TAG_MISMATCH -- STALE from an earlier deferral; the code + audit prove 116// AES-128-GCM is offered AND decryptable. asserted-not-proven doc defect, fixed to match the real capability.] 117// 118// Extensions emitted in order: supported_versions, supported_groups, 119// signature_algorithms, key_share, server_name. The spec doesn't 120// mandate ordering but real-world clients tend to put critical 121// negotiation extensions early. 122// v1 contract kept stable (Cardinal 19): x25519-only key_share. 123// Delegates to emit2 with a null P-256 share. NOTE: this is a 124// 7-arg call -- the >6-arg tail-call miscompile is fixed and the 125// _arg7_minrepro gate row guards the lane. 126func tls13_client_hello_emit( 127 random32: *u8, 128 sni: *u8, sni_len: i64, 129 pubkey32: *u8, 130 out: *u8, out_cap: i64 131) -> i64 { 132 return tls13_client_hello_emit2( 133 random32, sni, sni_len, pubkey32, 0 as *u8, out, out_cap 134 ) 135} 136 137// v2 (rung B4-P256-KEYSHARE): when p256_pub65 is non-null the 138// key_share extension carries BOTH groups (x25519 preferred + 139// secp256r1), so a P-256-only server picks our share directly and 140// never needs a HelloRetryRequest. 141func tls13_client_hello_emit2( 142 random32: *u8, 143 sni: *u8, sni_len: i64, 144 pubkey32: *u8, 145 p256_pub65: *u8, 146 out: *u8, out_cap: i64 147) -> i64 { 148 // Conservative pre-flight: handshake hdr(4) + version(2) + random(32) 149 // + sid_len(1) + suites_len(2) + suites(6) + compress(2) 150 // + ext_list_len(2) + extensions(~80 for minimum) <= ~150. 151 // Worst-case SNI host_len = 255 -> ~400. Require >= 256 + sni_len. 152 if out_cap < 256 + sni_len { return 0 - NX_TLS13_HELLO_VERDICT_BUF_OVERFLOW } 153 if sni_len < 1 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER } 154 155 var o: i64 = 4 // reserve 4 bytes for handshake header 156 // ---- legacy_version ---- 157 tls_write_u16_be(out, o, TLS_LEGACY_VERSION) // 0x0303 158 o = o + 2 159 // ---- random ---- 160 var i: i64 = 0 161 while i < 32 { 162 out[o + i] = random32[i] 163 i = i + 1 164 } 165 o = o + 32 166 // ---- legacy_session_id (empty: 1 zero byte) ---- 167 out[o] = 0 168 o = o + 1 169 // ---- cipher_suites: list_len(2) + 2 suites * 2 bytes = 4 bytes ---- 170 // Order = preference: ChaCha20-Poly1305 FIRST, AES-128-GCM fallback. 171 // MEASURED 2026-06-15: our sovereign software AES-128-GCM is ~0.7 MB/s 172 // (no AES-NI yet) while our ChaCha20-Poly1305 is ~61 MB/s -- ~90x faster. 173 // So we prefer the cipher we're fast at; AES-GCM stays as a fallback for 174 // servers that don't offer ChaCha20. (Restore AES-first once AES-NI lands.) 175 tls_write_u16_be(out, o, 4) 176 o = o + 2 177 tls_write_u16_be(out, o, NX_TLS13_CS_CHACHA20_POLY1305_SHA256) 178 o = o + 2 179 tls_write_u16_be(out, o, NX_TLS13_CS_AES_128_GCM_SHA256) 180 o = o + 2 181 // ---- legacy_compression_methods = 0x01 0x00 ---- 182 out[o] = 1 183 o = o + 1 184 out[o] = 0 185 o = o + 1 186 // ---- extensions: reserve 2 bytes for list_len, fill later ---- 187 let ext_list_len_off: i64 = o 188 o = o + 2 189 let ext_data_start: i64 = o 190 191 // Emit each extension in turn, advancing `o`. 192 let r1: i64 = tls13_ext_emit_supported_versions_tls13(out + o, out_cap - o) 193 if r1 < 0 { return r1 } 194 o = o + r1 195 196 let r2: i64 = tls13_ext_emit_supported_groups(out + o, out_cap - o) 197 if r2 < 0 { return r2 } 198 o = o + r2 199 200 let r3: i64 = tls13_ext_emit_signature_algorithms(out + o, out_cap - o) 201 if r3 < 0 { return r3 } 202 o = o + r3 203 204 // key_share mode by pointer-nullness: 205 // pubkey32 only -> x25519-only (v1 contract) 206 // both -> dual x25519 + secp256r1 (B4 product CH) 207 // p256_pub65 only -> secp256r1-only (census/probe instrument) 208 if pubkey32 as i64 == 0 { 209 if p256_pub65 as i64 == 0 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER } 210 } 211 var r4: i64 = 0 212 if p256_pub65 as i64 == 0 { 213 r4 = tls13_ext_emit_key_share_x25519(pubkey32, out + o, out_cap - o) 214 } 215 if p256_pub65 as i64 != 0 { 216 if pubkey32 as i64 != 0 { 217 r4 = tls13_ext_emit_key_share_dual(pubkey32, p256_pub65, out + o, out_cap - o) 218 } 219 if pubkey32 as i64 == 0 { 220 r4 = tls13_ext_emit_key_share_p256(p256_pub65, out + o, out_cap - o) 221 } 222 } 223 if r4 < 0 { return r4 } 224 o = o + r4 225 226 let r5: i64 = tls13_ext_emit_server_name(sni, sni_len, out + o, out_cap - o) 227 if r5 < 0 { return r5 } 228 o = o + r5 229 230 // ALPN: advertise http/1.1 so public servers route us to HTTP/1.1 231 // when h2 is not in the list. Most servers downgrade gracefully. 232 let r6: i64 = tls13_ext_emit_alpn_http11(out + o, out_cap - o) 233 if r6 < 0 { return r6 } 234 o = o + r6 235 236 // Backfill extensions list length. 237 let ext_data_len: i64 = o - ext_data_start 238 tls_write_u16_be(out, ext_list_len_off, ext_data_len) 239 240 // Backfill handshake header. 241 let body_len: i64 = o - 4 242 out[0] = HT_CLIENT_HELLO & 0xff // msg_type = 1 243 tls_write_u24_be(out, 1, body_len) 244 245 return o 246} 247 248// v2-h2 (R4-H2 capstone NBC-FETCH-001): IDENTICAL to tls13_client_hello_emit2 249// except the ALPN extension advertises EXACTLY ONE protocol "h2" (via 250// tls13_ext_emit_alpn_h2_only) instead of "http/1.1". This makes the connection 251// negotiate HTTP/2 unambiguously: a server that speaks h2 selects it (the only 252// offered protocol), a server that does not MUST abort the handshake. Kept as a 253// SEPARATE function so the proven HTTP/1.1 path (emit2) is byte-identical and 254// un-regressed (API contract stability, Cardinal 19); ONLY the authored h2-fetch 255// run-path calls this variant. Same dual x25519+secp256r1 key_share contract. 256func tls13_client_hello_emit2_h2only( 257 random32: *u8, 258 sni: *u8, sni_len: i64, 259 pubkey32: *u8, 260 p256_pub65: *u8, 261 out: *u8, out_cap: i64 262) -> i64 { 263 if out_cap < 256 + sni_len { return 0 - NX_TLS13_HELLO_VERDICT_BUF_OVERFLOW } 264 if sni_len < 1 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER } 265 266 var o: i64 = 4 // reserve 4 bytes for handshake header 267 tls_write_u16_be(out, o, TLS_LEGACY_VERSION) // 0x0303 268 o = o + 2 269 var i: i64 = 0 270 while i < 32 { 271 out[o + i] = random32[i] 272 i = i + 1 273 } 274 o = o + 32 275 out[o] = 0 // legacy_session_id (empty) 276 o = o + 1 277 tls_write_u16_be(out, o, 4) 278 o = o + 2 279 tls_write_u16_be(out, o, NX_TLS13_CS_AES_128_GCM_SHA256) 280 o = o + 2 281 tls_write_u16_be(out, o, NX_TLS13_CS_CHACHA20_POLY1305_SHA256) 282 o = o + 2 283 out[o] = 1 // legacy_compression_methods = 0x01 0x00 284 o = o + 1 285 out[o] = 0 286 o = o + 1 287 let ext_list_len_off: i64 = o 288 o = o + 2 289 let ext_data_start: i64 = o 290 291 let r1: i64 = tls13_ext_emit_supported_versions_tls13(out + o, out_cap - o) 292 if r1 < 0 { return r1 } 293 o = o + r1 294 let r2: i64 = tls13_ext_emit_supported_groups(out + o, out_cap - o) 295 if r2 < 0 { return r2 } 296 o = o + r2 297 let r3: i64 = tls13_ext_emit_signature_algorithms(out + o, out_cap - o) 298 if r3 < 0 { return r3 } 299 o = o + r3 300 301 if pubkey32 as i64 == 0 { 302 if p256_pub65 as i64 == 0 { return 0 - NX_TLS13_HELLO_VERDICT_BAD_HEADER } 303 } 304 var r4: i64 = 0 305 if p256_pub65 as i64 == 0 { 306 r4 = tls13_ext_emit_key_share_x25519(pubkey32, out + o, out_cap - o) 307 } 308 if p256_pub65 as i64 != 0 { 309 if pubkey32 as i64 != 0 { 310 r4 = tls13_ext_emit_key_share_dual(pubkey32, p256_pub65, out + o, out_cap - o) 311 } 312 if pubkey32 as i64 == 0 { 313 r4 = tls13_ext_emit_key_share_p256(p256_pub65, out + o, out_cap - o) 314 } 315 } 316 if r4 < 0 { return r4 } 317 o = o + r4 318 319 let r5: i64 = tls13_ext_emit_server_name(sni, sni_len, out + o, out_cap - o) 320 if r5 < 0 { return r5 } 321 o = o + r5 322 323 // ALPN: advertise ONLY "h2" so the server negotiates HTTP/2 (or aborts). 324 let r6: i64 = tls13_ext_emit_alpn_h2_only(out + o, out_cap - o) 325 if r6 < 0 { return r6 } 326 o = o + r6 327 328 let ext_data_len: i64 = o - ext_data_start 329 tls_write_u16_be(out, ext_list_len_off, ext_data_len) 330 let body_len: i64 = o - 4 331 out[0] = HT_CLIENT_HELLO & 0xff 332 tls_write_u24_be(out, 1, body_len) 333 return o 334} 335 336// Parse a ServerHello handshake message. Caller supplies the bytes 337// starting at the Handshake header (i.e., out[0] should be 0x02 for 338// ServerHello). 339// 340// Output: 341// *out_legacy_version = ProtocolVersion in body (typically 0x0303) 342// *out_random_off = byte offset INTO buf where Random[32] starts 343// *out_cipher_suite = chosen cipher suite (e.g. 0x1303) 344// *out_extensions_off = byte offset where extensions blob starts 345// *out_extensions_len = length of extensions blob in bytes 346// 347// Returns NX_TLS13_HELLO_VERDICT_OK or a non-OK verdict. 348func tls13_server_hello_parse( 349 buf: *u8, n: i64, 350 out_legacy_version: *i64, 351 out_random_off: *i64, 352 out_cipher_suite: *i64, 353 out_extensions_off: *i64, 354 out_extensions_len: *i64 355) -> i64 { 356 if n < 4 + 2 + 32 + 1 + 2 + 1 + 2 { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 357 // Handshake header: msg_type(1) + uint24 length(3) 358 let msg_type: i64 = buf[0] & 0xff 359 if msg_type != HT_SERVER_HELLO { return NX_TLS13_HELLO_VERDICT_BAD_HEADER } 360 let body_len: i64 = ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff) 361 if 4 + body_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 362 var o: i64 = 4 363 // legacy_version 364 let lv: i64 = tls_read_u16_be(buf, o) 365 *out_legacy_version = lv 366 o = o + 2 367 // random 368 *out_random_off = o 369 o = o + 32 370 // legacy_session_id_echo: 1-byte len + bytes 371 let sid_len: i64 = buf[o] & 0xff 372 o = o + 1 373 if o + sid_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 374 o = o + sid_len 375 // cipher_suite (2 bytes) 376 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 377 *out_cipher_suite = tls_read_u16_be(buf, o) 378 o = o + 2 379 // legacy_compression_method (1 byte, must be 0) 380 if o + 1 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 381 o = o + 1 382 // extensions: 2-byte list length + extensions 383 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 384 let ext_list_len: i64 = tls_read_u16_be(buf, o) 385 o = o + 2 386 if o + ext_list_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 387 *out_extensions_off = o 388 *out_extensions_len = ext_list_len 389 return NX_TLS13_HELLO_VERDICT_OK 390} 391 392// Iterate an extensions blob to find one by extension_type. Returns 393// the byte offset of the extension's DATA (past the type+len header) 394// in `*out_data_off` and its length in `*out_data_len`. Returns 395// NX_TLS13_HELLO_VERDICT_OK if found, NX_TLS13_HELLO_VERDICT_NOT_FOUND 396// otherwise. Per-extension TRUNCATED if a length-prefix walks past 397// the blob. 398func tls13_ext_find( 399 blob: *u8, blob_len: i64, 400 wanted_type: i64, 401 out_data_off: *i64, 402 out_data_len: *i64 403) -> i64 { 404 var off: i64 = 0 405 while off + 4 <= blob_len { 406 let etype: i64 = tls_read_u16_be(blob, off) 407 let elen: i64 = tls_read_u16_be(blob, off + 2) 408 if off + 4 + elen > blob_len { 409 return NX_TLS13_HELLO_VERDICT_TRUNCATED 410 } 411 if etype == wanted_type { 412 *out_data_off = off + 4 413 *out_data_len = elen 414 return NX_TLS13_HELLO_VERDICT_OK 415 } 416 off = off + 4 + elen 417 } 418 return NX_TLS13_HELLO_VERDICT_NOT_FOUND 419} 420 421// Sealed-enum validity gate. 422func nx_tls13_hello_verdict_is_valid(v: i64) -> i64 { 423 if v < 0 { return 0 } 424 if v >= NX_TLS13_HELLO_VERDICT_N { return 0 } 425 return 1 426} 427 428// Parse a ClientHello handshake message (server-side inverse of 429// tls13_client_hello_emit). Caller supplies the bytes starting at 430// the Handshake header (i.e., buf[0] should be HT_CLIENT_HELLO=0x01). 431// 432// Output offsets are BYTE INDICES INTO buf where the corresponding 433// fields START. Lengths are the byte counts of those fields. 434// 435// *out_legacy_version = ProtocolVersion in body (typically 0x0303) 436// *out_random_off = offset of Random[32] 437// *out_session_id_len = length of legacy_session_id (0..32) 438// *out_cipher_suites_off = offset of cipher_suites blob 439// *out_cipher_suites_len = length of cipher_suites blob in bytes 440// *out_extensions_off = offset of extensions blob (past list-len) 441// *out_extensions_len = length of extensions blob in bytes 442// 443// Returns NX_TLS13_HELLO_VERDICT_OK or a non-OK verdict. 444 445func tls13_client_hello_parse( 446 buf: *u8, n: i64, 447 out_legacy_version: *i64, 448 out_random_off: *i64, 449 out_session_id_len: *i64, 450 out_cipher_suites_off: *i64, 451 out_cipher_suites_len: *i64, 452 out_extensions_off: *i64, 453 out_extensions_len: *i64 454) -> i64 { 455 if n < 4 { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 456 let msg_type: i64 = buf[0] as i64 457 if msg_type != HT_CLIENT_HELLO { return NX_TLS13_HELLO_VERDICT_BAD_HEADER } 458 let body_len: i64 = tls_read_u24_be(buf, 1) 459 if 4 + body_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 460 461 var o: i64 = 4 462 // legacy_version (2 bytes) 463 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 464 let legacy_ver: i64 = tls_read_u16_be(buf, o) 465 *out_legacy_version = legacy_ver 466 o = o + 2 467 // random (32 bytes) 468 if o + 32 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 469 *out_random_off = o 470 o = o + 32 471 // legacy_session_id_len (1 byte) + session id 472 if o + 1 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 473 let sid_len: i64 = buf[o] as i64 474 if sid_len > 32 { return NX_TLS13_HELLO_VERDICT_BAD_HEADER } 475 o = o + 1 476 if o + sid_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 477 *out_session_id_len = sid_len 478 o = o + sid_len 479 // cipher_suites_len (2 bytes) + cipher_suites blob 480 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 481 let suites_len: i64 = tls_read_u16_be(buf, o) 482 o = o + 2 483 if o + suites_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 484 if suites_len < 2 { return NX_TLS13_HELLO_VERDICT_BAD_HEADER } 485 *out_cipher_suites_off = o 486 *out_cipher_suites_len = suites_len 487 o = o + suites_len 488 // legacy_compression_methods_len (1 byte) + compression methods 489 if o + 1 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 490 let comp_len: i64 = buf[o] as i64 491 o = o + 1 492 if o + comp_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 493 o = o + comp_len 494 // extensions_list_len (2 bytes) + extensions blob 495 if o + 2 > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 496 let ext_list_len: i64 = tls_read_u16_be(buf, o) 497 o = o + 2 498 if o + ext_list_len > n { return NX_TLS13_HELLO_VERDICT_TRUNCATED } 499 *out_extensions_off = o 500 *out_extensions_len = ext_list_len 501 502 return NX_TLS13_HELLO_VERDICT_OK 503}