@scriptc/runtime 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/LICENSE +202 -0
  2. package/package.json +16 -6
  3. package/src/scr_array.c +488 -0
  4. package/src/scr_assert.c +1393 -0
  5. package/src/scr_async.c +2593 -0
  6. package/src/scr_async_dyn.c +607 -0
  7. package/src/scr_bytes.c +1425 -0
  8. package/src/scr_bytes_io.c +161 -0
  9. package/src/scr_child.c +3587 -0
  10. package/src/scr_closure.c +214 -0
  11. package/src/scr_console.c +143 -0
  12. package/src/scr_cycle.c +218 -0
  13. package/src/scr_dc.c +805 -0
  14. package/src/scr_dgram.c +1007 -0
  15. package/src/scr_dyn_handle.c +154 -0
  16. package/src/scr_dyn_invoke.c +644 -0
  17. package/src/scr_error.c +332 -0
  18. package/src/scr_events.c +714 -0
  19. package/src/scr_events_emitter.c +699 -0
  20. package/src/scr_exception.c +242 -0
  21. package/src/scr_fetch.c +1130 -0
  22. package/src/scr_fetch_curl.c +654 -0
  23. package/src/scr_http.c +3482 -0
  24. package/src/scr_http2.c +3030 -0
  25. package/src/scr_inspect.c +803 -0
  26. package/src/scr_inspect_island.c +51 -0
  27. package/src/scr_island.c +9363 -0
  28. package/src/scr_json.c +2151 -0
  29. package/src/scr_lib.c +3612 -0
  30. package/src/scr_loop_epoll.c +241 -0
  31. package/src/scr_loop_kqueue.c +121 -0
  32. package/src/scr_loop_wsapoll.c +208 -0
  33. package/src/scr_map.c +591 -0
  34. package/src/scr_net.c +3017 -0
  35. package/src/scr_net_island.c +458 -0
  36. package/src/scr_number.c +115 -0
  37. package/src/scr_object.c +25 -0
  38. package/src/scr_path.c +1266 -0
  39. package/src/scr_platform.h +119 -0
  40. package/src/scr_readline.c +287 -0
  41. package/src/scr_regex.c +1080 -0
  42. package/src/scr_runtime.h +5046 -0
  43. package/src/scr_stream.c +2877 -0
  44. package/src/scr_string.c +1264 -0
  45. package/src/scr_symbol.c +132 -0
  46. package/src/scr_test.c +662 -0
  47. package/src/scr_tls.c +1520 -0
  48. package/src/scr_union.c +105 -0
  49. package/src/scr_url.c +911 -0
  50. package/src/scr_url_internal.h +29 -0
  51. package/src/scr_url_params.c +561 -0
  52. package/src/scr_watch.c +568 -0
  53. package/src/scr_web.c +1778 -0
  54. package/src/scr_win.c +76 -0
  55. package/src/scr_zlib.c +197 -0
  56. package/src/scr_zlib_island.c +15 -0
  57. package/vendor/README.md +52 -0
  58. package/vendor/curl/COPYRIGHT +534 -0
  59. package/vendor/curl/include/curl/curl.h +3214 -0
  60. package/vendor/curl/include/curl/curlver.h +79 -0
  61. package/vendor/curl/include/curl/easy.h +125 -0
  62. package/vendor/curl/include/curl/header.h +74 -0
  63. package/vendor/curl/include/curl/mprintf.h +52 -0
  64. package/vendor/curl/include/curl/multi.h +460 -0
  65. package/vendor/curl/include/curl/options.h +70 -0
  66. package/vendor/curl/include/curl/stdcheaders.h +35 -0
  67. package/vendor/curl/include/curl/system.h +508 -0
  68. package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
  69. package/vendor/curl/include/curl/urlapi.h +149 -0
  70. package/vendor/curl/include/curl/websockets.h +84 -0
  71. package/vendor/mbedtls/LICENSE +553 -0
  72. package/vendor/mbedtls/include/CMakeLists.txt +22 -0
  73. package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
  74. package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
  75. package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
  76. package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
  77. package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
  78. package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
  79. package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
  80. package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
  81. package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
  82. package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
  83. package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
  84. package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
  85. package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
  86. package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
  87. package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
  88. package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
  89. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
  90. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
  91. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
  92. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
  93. package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
  94. package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
  95. package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
  96. package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
  97. package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
  98. package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
  99. package/vendor/mbedtls/include/mbedtls/des.h +385 -0
  100. package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
  101. package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
  102. package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
  103. package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
  104. package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
  105. package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
  106. package/vendor/mbedtls/include/mbedtls/error.h +201 -0
  107. package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
  108. package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
  109. package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
  110. package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
  111. package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
  112. package/vendor/mbedtls/include/mbedtls/md.h +526 -0
  113. package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
  114. package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
  115. package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
  116. package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
  117. package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
  118. package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
  119. package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
  120. package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
  121. package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
  122. package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
  123. package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
  124. package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
  125. package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
  126. package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
  127. package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
  128. package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
  129. package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
  130. package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
  131. package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
  132. package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
  133. package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
  134. package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
  135. package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
  136. package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
  137. package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
  138. package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
  139. package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
  140. package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
  141. package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
  142. package/vendor/mbedtls/include/mbedtls/version.h +78 -0
  143. package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
  144. package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
  145. package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
  146. package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
  147. package/vendor/mbedtls/include/psa/build_info.h +20 -0
  148. package/vendor/mbedtls/include/psa/crypto.h +4998 -0
  149. package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
  150. package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
  151. package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
  152. package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
  153. package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
  154. package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
  155. package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
  156. package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
  157. package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
  158. package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
  159. package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
  160. package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
  161. package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
  162. package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
  163. package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
  164. package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
  165. package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
  166. package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
  167. package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
  168. package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
  169. package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
  170. package/vendor/mbedtls/library/aes.c +2294 -0
  171. package/vendor/mbedtls/library/aesce.c +624 -0
  172. package/vendor/mbedtls/library/aesce.h +136 -0
  173. package/vendor/mbedtls/library/aesni.c +846 -0
  174. package/vendor/mbedtls/library/aesni.h +162 -0
  175. package/vendor/mbedtls/library/alignment.h +704 -0
  176. package/vendor/mbedtls/library/aria.c +969 -0
  177. package/vendor/mbedtls/library/asn1parse.c +468 -0
  178. package/vendor/mbedtls/library/asn1write.c +440 -0
  179. package/vendor/mbedtls/library/base64.c +322 -0
  180. package/vendor/mbedtls/library/base64_internal.h +45 -0
  181. package/vendor/mbedtls/library/bignum.c +2583 -0
  182. package/vendor/mbedtls/library/bignum_core.c +1240 -0
  183. package/vendor/mbedtls/library/bignum_core.h +872 -0
  184. package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
  185. package/vendor/mbedtls/library/bignum_internal.h +122 -0
  186. package/vendor/mbedtls/library/bignum_mod.c +394 -0
  187. package/vendor/mbedtls/library/bignum_mod.h +452 -0
  188. package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
  189. package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
  190. package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
  191. package/vendor/mbedtls/library/block_cipher.c +207 -0
  192. package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
  193. package/vendor/mbedtls/library/bn_mul.h +1094 -0
  194. package/vendor/mbedtls/library/camellia.c +1058 -0
  195. package/vendor/mbedtls/library/ccm.c +777 -0
  196. package/vendor/mbedtls/library/chacha20.c +563 -0
  197. package/vendor/mbedtls/library/chacha20_internal.h +23 -0
  198. package/vendor/mbedtls/library/chachapoly.c +487 -0
  199. package/vendor/mbedtls/library/check_crypto_config.h +136 -0
  200. package/vendor/mbedtls/library/cipher.c +1712 -0
  201. package/vendor/mbedtls/library/cipher_invasive.h +28 -0
  202. package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
  203. package/vendor/mbedtls/library/cipher_wrap.h +178 -0
  204. package/vendor/mbedtls/library/cmac.c +1067 -0
  205. package/vendor/mbedtls/library/common.h +487 -0
  206. package/vendor/mbedtls/library/constant_time.c +247 -0
  207. package/vendor/mbedtls/library/constant_time_impl.h +541 -0
  208. package/vendor/mbedtls/library/constant_time_internal.h +579 -0
  209. package/vendor/mbedtls/library/ctr.h +35 -0
  210. package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
  211. package/vendor/mbedtls/library/debug.c +475 -0
  212. package/vendor/mbedtls/library/debug_internal.h +185 -0
  213. package/vendor/mbedtls/library/des.c +1042 -0
  214. package/vendor/mbedtls/library/dhm.c +700 -0
  215. package/vendor/mbedtls/library/ecdh.c +696 -0
  216. package/vendor/mbedtls/library/ecdsa.c +858 -0
  217. package/vendor/mbedtls/library/ecjpake.c +1216 -0
  218. package/vendor/mbedtls/library/ecp.c +3674 -0
  219. package/vendor/mbedtls/library/ecp_curves.c +6125 -0
  220. package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
  221. package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
  222. package/vendor/mbedtls/library/ecp_invasive.h +305 -0
  223. package/vendor/mbedtls/library/entropy.c +680 -0
  224. package/vendor/mbedtls/library/entropy_poll.c +233 -0
  225. package/vendor/mbedtls/library/entropy_poll.h +64 -0
  226. package/vendor/mbedtls/library/error.c +878 -0
  227. package/vendor/mbedtls/library/gcm.c +1330 -0
  228. package/vendor/mbedtls/library/hkdf.c +161 -0
  229. package/vendor/mbedtls/library/hmac_drbg.c +633 -0
  230. package/vendor/mbedtls/library/lmots.c +789 -0
  231. package/vendor/mbedtls/library/lmots.h +288 -0
  232. package/vendor/mbedtls/library/lms.c +778 -0
  233. package/vendor/mbedtls/library/md.c +1108 -0
  234. package/vendor/mbedtls/library/md5.c +426 -0
  235. package/vendor/mbedtls/library/md_psa.h +26 -0
  236. package/vendor/mbedtls/library/md_wrap.h +46 -0
  237. package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
  238. package/vendor/mbedtls/library/mps_common.h +181 -0
  239. package/vendor/mbedtls/library/mps_error.h +89 -0
  240. package/vendor/mbedtls/library/mps_reader.c +538 -0
  241. package/vendor/mbedtls/library/mps_reader.h +366 -0
  242. package/vendor/mbedtls/library/mps_trace.c +112 -0
  243. package/vendor/mbedtls/library/mps_trace.h +154 -0
  244. package/vendor/mbedtls/library/net_sockets.c +694 -0
  245. package/vendor/mbedtls/library/nist_kw.c +729 -0
  246. package/vendor/mbedtls/library/oid.c +1166 -0
  247. package/vendor/mbedtls/library/padlock.c +157 -0
  248. package/vendor/mbedtls/library/padlock.h +111 -0
  249. package/vendor/mbedtls/library/pem.c +554 -0
  250. package/vendor/mbedtls/library/pk.c +1602 -0
  251. package/vendor/mbedtls/library/pk_ecc.c +261 -0
  252. package/vendor/mbedtls/library/pk_internal.h +241 -0
  253. package/vendor/mbedtls/library/pk_wrap.c +1618 -0
  254. package/vendor/mbedtls/library/pk_wrap.h +138 -0
  255. package/vendor/mbedtls/library/pkcs12.c +437 -0
  256. package/vendor/mbedtls/library/pkcs5.c +500 -0
  257. package/vendor/mbedtls/library/pkcs7.c +787 -0
  258. package/vendor/mbedtls/library/pkparse.c +1392 -0
  259. package/vendor/mbedtls/library/pkwrite.c +631 -0
  260. package/vendor/mbedtls/library/pkwrite.h +121 -0
  261. package/vendor/mbedtls/library/platform.c +402 -0
  262. package/vendor/mbedtls/library/platform_util.c +258 -0
  263. package/vendor/mbedtls/library/poly1305.c +492 -0
  264. package/vendor/mbedtls/library/psa_crypto.c +9532 -0
  265. package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
  266. package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
  267. package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
  268. package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
  269. package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
  270. package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
  271. package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
  272. package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
  273. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
  274. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
  275. package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
  276. package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
  277. package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
  278. package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
  279. package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
  280. package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
  281. package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
  282. package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
  283. package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
  284. package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
  285. package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
  286. package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
  287. package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
  288. package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
  289. package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
  290. package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
  291. package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
  292. package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
  293. package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
  294. package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
  295. package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
  296. package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
  297. package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
  298. package/vendor/mbedtls/library/psa_its_file.c +254 -0
  299. package/vendor/mbedtls/library/psa_util.c +614 -0
  300. package/vendor/mbedtls/library/psa_util_internal.h +100 -0
  301. package/vendor/mbedtls/library/ripemd160.c +490 -0
  302. package/vendor/mbedtls/library/rsa.c +3121 -0
  303. package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
  304. package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
  305. package/vendor/mbedtls/library/rsa_internal.h +188 -0
  306. package/vendor/mbedtls/library/sha1.c +480 -0
  307. package/vendor/mbedtls/library/sha256.c +983 -0
  308. package/vendor/mbedtls/library/sha3.c +721 -0
  309. package/vendor/mbedtls/library/sha512.c +1115 -0
  310. package/vendor/mbedtls/library/ssl_cache.c +410 -0
  311. package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
  312. package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
  313. package/vendor/mbedtls/library/ssl_client.c +1023 -0
  314. package/vendor/mbedtls/library/ssl_client.h +22 -0
  315. package/vendor/mbedtls/library/ssl_cookie.c +384 -0
  316. package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
  317. package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
  318. package/vendor/mbedtls/library/ssl_misc.h +3198 -0
  319. package/vendor/mbedtls/library/ssl_msg.c +6619 -0
  320. package/vendor/mbedtls/library/ssl_ticket.c +556 -0
  321. package/vendor/mbedtls/library/ssl_tls.c +10234 -0
  322. package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
  323. package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
  324. package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
  325. package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
  326. package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
  327. package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
  328. package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
  329. package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
  330. package/vendor/mbedtls/library/threading.c +193 -0
  331. package/vendor/mbedtls/library/threading_internal.h +28 -0
  332. package/vendor/mbedtls/library/timing.c +152 -0
  333. package/vendor/mbedtls/library/version.c +32 -0
  334. package/vendor/mbedtls/library/version_features.c +856 -0
  335. package/vendor/mbedtls/library/x509.c +1776 -0
  336. package/vendor/mbedtls/library/x509_create.c +570 -0
  337. package/vendor/mbedtls/library/x509_crl.c +708 -0
  338. package/vendor/mbedtls/library/x509_crt.c +3311 -0
  339. package/vendor/mbedtls/library/x509_csr.c +648 -0
  340. package/vendor/mbedtls/library/x509_internal.h +101 -0
  341. package/vendor/mbedtls/library/x509write.c +174 -0
  342. package/vendor/mbedtls/library/x509write_crt.c +688 -0
  343. package/vendor/mbedtls/library/x509write_csr.c +336 -0
  344. package/vendor/quickjs-ng/CMakeLists.txt +566 -0
  345. package/vendor/quickjs-ng/LICENSE +24 -0
  346. package/vendor/quickjs-ng/README.md +24 -0
  347. package/vendor/quickjs-ng/api-test.c +1195 -0
  348. package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  349. package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  350. package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  351. package/vendor/quickjs-ng/cutils.h +1998 -0
  352. package/vendor/quickjs-ng/dtoa.c +1619 -0
  353. package/vendor/quickjs-ng/dtoa.h +87 -0
  354. package/vendor/quickjs-ng/gen/function_source.c +81 -0
  355. package/vendor/quickjs-ng/gen/hello.c +53 -0
  356. package/vendor/quickjs-ng/gen/hello_module.c +106 -0
  357. package/vendor/quickjs-ng/gen/repl.c +3036 -0
  358. package/vendor/quickjs-ng/gen/standalone.c +323 -0
  359. package/vendor/quickjs-ng/gen/test_fib.c +81 -0
  360. package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
  361. package/vendor/quickjs-ng/libregexp.c +3474 -0
  362. package/vendor/quickjs-ng/libregexp.h +101 -0
  363. package/vendor/quickjs-ng/libunicode-table.h +5173 -0
  364. package/vendor/quickjs-ng/libunicode.c +2069 -0
  365. package/vendor/quickjs-ng/libunicode.h +172 -0
  366. package/vendor/quickjs-ng/list.h +107 -0
  367. package/vendor/quickjs-ng/lre-test.c +52 -0
  368. package/vendor/quickjs-ng/qjs.c +762 -0
  369. package/vendor/quickjs-ng/qjsc.c +673 -0
  370. package/vendor/quickjs-ng/quickjs-atom.h +280 -0
  371. package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  372. package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
  373. package/vendor/quickjs-ng/quickjs-libc.h +87 -0
  374. package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
  375. package/vendor/quickjs-ng/quickjs.c +64041 -0
  376. package/vendor/quickjs-ng/quickjs.h +1447 -0
  377. package/vendor/quickjs-ng/run-test262.c +2374 -0
  378. package/vendor/quickjs-ng/unicode_gen.c +3121 -0
  379. package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  380. package/vendor/ryu/LICENSE-Boost +23 -0
  381. package/vendor/ryu/common.h +114 -0
  382. package/vendor/ryu/d2s.c +509 -0
  383. package/vendor/ryu/d2s_full_table.h +367 -0
  384. package/vendor/ryu/d2s_intrinsics.h +357 -0
  385. package/vendor/ryu/d2s_small_table.h +186 -0
  386. package/vendor/ryu/digit_table.h +35 -0
  387. package/vendor/ryu/ryu.h +46 -0
  388. package/vendor/zlib/LICENSE +22 -0
  389. package/vendor/zlib/adler32.c +164 -0
  390. package/vendor/zlib/compress.c +75 -0
  391. package/vendor/zlib/crc32.c +1049 -0
  392. package/vendor/zlib/crc32.h +9446 -0
  393. package/vendor/zlib/deflate.c +2139 -0
  394. package/vendor/zlib/deflate.h +377 -0
  395. package/vendor/zlib/gzclose.c +23 -0
  396. package/vendor/zlib/gzguts.h +214 -0
  397. package/vendor/zlib/gzlib.c +582 -0
  398. package/vendor/zlib/gzread.c +602 -0
  399. package/vendor/zlib/gzwrite.c +631 -0
  400. package/vendor/zlib/infback.c +628 -0
  401. package/vendor/zlib/inffast.c +320 -0
  402. package/vendor/zlib/inffast.h +11 -0
  403. package/vendor/zlib/inffixed.h +94 -0
  404. package/vendor/zlib/inflate.c +1526 -0
  405. package/vendor/zlib/inflate.h +126 -0
  406. package/vendor/zlib/inftrees.c +299 -0
  407. package/vendor/zlib/inftrees.h +62 -0
  408. package/vendor/zlib/trees.c +1117 -0
  409. package/vendor/zlib/trees.h +128 -0
  410. package/vendor/zlib/uncompr.c +85 -0
  411. package/vendor/zlib/zconf.h +543 -0
  412. package/vendor/zlib/zlib.h +1938 -0
  413. package/vendor/zlib/zutil.c +299 -0
  414. package/vendor/zlib/zutil.h +254 -0
  415. package/README.md +0 -3
  416. package/index.js +0 -2
@@ -0,0 +1,3030 @@
1
+ /* node:http2 — REAL HTTP/2 over the net loop: the h2c (cleartext,
2
+ * prior-knowledge) server and client sessions the Node suite's http2
3
+ * family exercises, with an RFC 7540 frame codec and an RFC 7541 HPACK
4
+ * codec written here (nothing vendored).
5
+ *
6
+ * ── Design note ──────────────────────────────────────────────────────
7
+ *
8
+ * Layering. One ScrH2Session per TCP connection, attached as the
9
+ * socket's native reader (the scr_http.c parser-seam precedent): every
10
+ * read(2) chunk feeds the frame reassembly buffer, complete frames
11
+ * dispatch to the per-stream state machines, and outbound frames go
12
+ * through scr_net_sock_write_native. The server side hangs a refcounted
13
+ * ScrH2SrvCtx off the net server (native-conn + http_ctx alias, exactly
14
+ * the ScrHttpSrvCtx shape — the ctx's leading `proto` tag keeps the two
15
+ * parser families apart on the shared alias slot); the client side dials
16
+ * scr_net_connect and installs the same reader.
17
+ *
18
+ * HPACK. The decoder is COMPLETE (static + dynamic table, all literal
19
+ * forms, size updates, Huffman — peers index and Huffman-code at will;
20
+ * Node's encoder does both). The encoder is deliberately minimal and
21
+ * RFC-valid: indexed for exact static-table matches, literal WITHOUT
22
+ * indexing otherwise (indexed name when the static table has it), no
23
+ * Huffman, no dynamic insertions — the peer's decoder state machine
24
+ * never depends on choices we don't make.
25
+ *
26
+ * Flow control. Both windows are tracked on send (DATA splits to
27
+ * min(stream window, connection window, peer max frame); the remainder
28
+ * buffers until WINDOW_UPDATE/SETTINGS reopen it) and replenished
29
+ * eagerly on receive (a WINDOW_UPDATE pair — stream + connection — goes
30
+ * out per DATA frame, so the peer never stalls).
31
+ *
32
+ * Events, Node-shaped. 'stream'/'response'/'data' fire inline at frame
33
+ * dispatch (macrotask position, like every net callback); 'end' fires
34
+ * when END_STREAM has arrived, the recv queue is drained, and the
35
+ * stream is FLOWING (a 'data' listener or resume() — Node's paused-mode
36
+ * holdback); stream 'close' and session lifecycle emits are DEFERRED to
37
+ * the unit's sweep (scr_net_set_proto_sweep — the scr_http.c emit-queue
38
+ * pattern), one pass after the work that flagged them. A stream closing
39
+ * with a nonzero code fires 'error' (ERR_HTTP2_STREAM_ERROR, Node's
40
+ * message) before its 'close'; rstCode carries the code either way.
41
+ *
42
+ * Ownership. The socket owns its session (+1 through native_ctx); the
43
+ * session holds the socket (+1, dropped when the socket reports closed
44
+ * — that break is what unwinds the pair) and every OPEN stream (+1,
45
+ * dropped at stream settlement); a stream holds its session (+1 for its
46
+ * whole life — safe: the session's stream ref drops first). Listener
47
+ * lists drop at settlement (the cycle story shared with scr_net.c), and
48
+ * an atexit cleanup drains the emit queue so the RC audit stays clean.
49
+ *
50
+ * The differential harness pattern is scr_net.c's: fixtures bind port 0,
51
+ * report `PORT <n>` on stderr, and a Node driver (or the program's own
52
+ * in-process h2 client — the Node-suite shape) does the talking. */
53
+ #include "scr_platform.h"
54
+ #include "scr_runtime.h"
55
+
56
+ #include <inttypes.h>
57
+ #include <stdio.h>
58
+ #include <stdlib.h>
59
+ #include <string.h>
60
+ #include <time.h>
61
+
62
+ static void scr_h2_oom(void) {
63
+ fputs("scriptc: out of memory\n", stderr);
64
+ abort();
65
+ }
66
+
67
+ /* ── HPACK tables (RFC 7541 Appendices A and B, generated) ───────────── */
68
+
69
+ typedef struct {
70
+ uint32_t code;
71
+ uint8_t len;
72
+ } ScrH2Huff;
73
+
74
+ typedef struct {
75
+ const char *name;
76
+ const char *value;
77
+ } ScrH2StaticEntry;
78
+
79
+ static const ScrH2Huff SCR_H2_HUFF[257] = {
80
+ {0x1ff8u, 13}, {0x7fffd8u, 23}, {0xfffffe2u, 28}, {0xfffffe3u, 28},
81
+ {0xfffffe4u, 28}, {0xfffffe5u, 28}, {0xfffffe6u, 28}, {0xfffffe7u, 28},
82
+ {0xfffffe8u, 28}, {0xffffeau, 24}, {0x3ffffffcu, 30}, {0xfffffe9u, 28},
83
+ {0xfffffeau, 28}, {0x3ffffffdu, 30}, {0xfffffebu, 28}, {0xfffffecu, 28},
84
+ {0xfffffedu, 28}, {0xfffffeeu, 28}, {0xfffffefu, 28}, {0xffffff0u, 28},
85
+ {0xffffff1u, 28}, {0xffffff2u, 28}, {0x3ffffffeu, 30}, {0xffffff3u, 28},
86
+ {0xffffff4u, 28}, {0xffffff5u, 28}, {0xffffff6u, 28}, {0xffffff7u, 28},
87
+ {0xffffff8u, 28}, {0xffffff9u, 28}, {0xffffffau, 28}, {0xffffffbu, 28},
88
+ {0x14u, 6}, {0x3f8u, 10}, {0x3f9u, 10}, {0xffau, 12},
89
+ {0x1ff9u, 13}, {0x15u, 6}, {0xf8u, 8}, {0x7fau, 11},
90
+ {0x3fau, 10}, {0x3fbu, 10}, {0xf9u, 8}, {0x7fbu, 11},
91
+ {0xfau, 8}, {0x16u, 6}, {0x17u, 6}, {0x18u, 6},
92
+ {0x0u, 5}, {0x1u, 5}, {0x2u, 5}, {0x19u, 6},
93
+ {0x1au, 6}, {0x1bu, 6}, {0x1cu, 6}, {0x1du, 6},
94
+ {0x1eu, 6}, {0x1fu, 6}, {0x5cu, 7}, {0xfbu, 8},
95
+ {0x7ffcu, 15}, {0x20u, 6}, {0xffbu, 12}, {0x3fcu, 10},
96
+ {0x1ffau, 13}, {0x21u, 6}, {0x5du, 7}, {0x5eu, 7},
97
+ {0x5fu, 7}, {0x60u, 7}, {0x61u, 7}, {0x62u, 7},
98
+ {0x63u, 7}, {0x64u, 7}, {0x65u, 7}, {0x66u, 7},
99
+ {0x67u, 7}, {0x68u, 7}, {0x69u, 7}, {0x6au, 7},
100
+ {0x6bu, 7}, {0x6cu, 7}, {0x6du, 7}, {0x6eu, 7},
101
+ {0x6fu, 7}, {0x70u, 7}, {0x71u, 7}, {0x72u, 7},
102
+ {0xfcu, 8}, {0x73u, 7}, {0xfdu, 8}, {0x1ffbu, 13},
103
+ {0x7fff0u, 19}, {0x1ffcu, 13}, {0x3ffcu, 14}, {0x22u, 6},
104
+ {0x7ffdu, 15}, {0x3u, 5}, {0x23u, 6}, {0x4u, 5},
105
+ {0x24u, 6}, {0x5u, 5}, {0x25u, 6}, {0x26u, 6},
106
+ {0x27u, 6}, {0x6u, 5}, {0x74u, 7}, {0x75u, 7},
107
+ {0x28u, 6}, {0x29u, 6}, {0x2au, 6}, {0x7u, 5},
108
+ {0x2bu, 6}, {0x76u, 7}, {0x2cu, 6}, {0x8u, 5},
109
+ {0x9u, 5}, {0x2du, 6}, {0x77u, 7}, {0x78u, 7},
110
+ {0x79u, 7}, {0x7au, 7}, {0x7bu, 7}, {0x7ffeu, 15},
111
+ {0x7fcu, 11}, {0x3ffdu, 14}, {0x1ffdu, 13}, {0xffffffcu, 28},
112
+ {0xfffe6u, 20}, {0x3fffd2u, 22}, {0xfffe7u, 20}, {0xfffe8u, 20},
113
+ {0x3fffd3u, 22}, {0x3fffd4u, 22}, {0x3fffd5u, 22}, {0x7fffd9u, 23},
114
+ {0x3fffd6u, 22}, {0x7fffdau, 23}, {0x7fffdbu, 23}, {0x7fffdcu, 23},
115
+ {0x7fffddu, 23}, {0x7fffdeu, 23}, {0xffffebu, 24}, {0x7fffdfu, 23},
116
+ {0xffffecu, 24}, {0xffffedu, 24}, {0x3fffd7u, 22}, {0x7fffe0u, 23},
117
+ {0xffffeeu, 24}, {0x7fffe1u, 23}, {0x7fffe2u, 23}, {0x7fffe3u, 23},
118
+ {0x7fffe4u, 23}, {0x1fffdcu, 21}, {0x3fffd8u, 22}, {0x7fffe5u, 23},
119
+ {0x3fffd9u, 22}, {0x7fffe6u, 23}, {0x7fffe7u, 23}, {0xffffefu, 24},
120
+ {0x3fffdau, 22}, {0x1fffddu, 21}, {0xfffe9u, 20}, {0x3fffdbu, 22},
121
+ {0x3fffdcu, 22}, {0x7fffe8u, 23}, {0x7fffe9u, 23}, {0x1fffdeu, 21},
122
+ {0x7fffeau, 23}, {0x3fffddu, 22}, {0x3fffdeu, 22}, {0xfffff0u, 24},
123
+ {0x1fffdfu, 21}, {0x3fffdfu, 22}, {0x7fffebu, 23}, {0x7fffecu, 23},
124
+ {0x1fffe0u, 21}, {0x1fffe1u, 21}, {0x3fffe0u, 22}, {0x1fffe2u, 21},
125
+ {0x7fffedu, 23}, {0x3fffe1u, 22}, {0x7fffeeu, 23}, {0x7fffefu, 23},
126
+ {0xfffeau, 20}, {0x3fffe2u, 22}, {0x3fffe3u, 22}, {0x3fffe4u, 22},
127
+ {0x7ffff0u, 23}, {0x3fffe5u, 22}, {0x3fffe6u, 22}, {0x7ffff1u, 23},
128
+ {0x3ffffe0u, 26}, {0x3ffffe1u, 26}, {0xfffebu, 20}, {0x7fff1u, 19},
129
+ {0x3fffe7u, 22}, {0x7ffff2u, 23}, {0x3fffe8u, 22}, {0x1ffffecu, 25},
130
+ {0x3ffffe2u, 26}, {0x3ffffe3u, 26}, {0x3ffffe4u, 26}, {0x7ffffdeu, 27},
131
+ {0x7ffffdfu, 27}, {0x3ffffe5u, 26}, {0xfffff1u, 24}, {0x1ffffedu, 25},
132
+ {0x7fff2u, 19}, {0x1fffe3u, 21}, {0x3ffffe6u, 26}, {0x7ffffe0u, 27},
133
+ {0x7ffffe1u, 27}, {0x3ffffe7u, 26}, {0x7ffffe2u, 27}, {0xfffff2u, 24},
134
+ {0x1fffe4u, 21}, {0x1fffe5u, 21}, {0x3ffffe8u, 26}, {0x3ffffe9u, 26},
135
+ {0xffffffdu, 28}, {0x7ffffe3u, 27}, {0x7ffffe4u, 27}, {0x7ffffe5u, 27},
136
+ {0xfffecu, 20}, {0xfffff3u, 24}, {0xfffedu, 20}, {0x1fffe6u, 21},
137
+ {0x3fffe9u, 22}, {0x1fffe7u, 21}, {0x1fffe8u, 21}, {0x7ffff3u, 23},
138
+ {0x3fffeau, 22}, {0x3fffebu, 22}, {0x1ffffeeu, 25}, {0x1ffffefu, 25},
139
+ {0xfffff4u, 24}, {0xfffff5u, 24}, {0x3ffffeau, 26}, {0x7ffff4u, 23},
140
+ {0x3ffffebu, 26}, {0x7ffffe6u, 27}, {0x3ffffecu, 26}, {0x3ffffedu, 26},
141
+ {0x7ffffe7u, 27}, {0x7ffffe8u, 27}, {0x7ffffe9u, 27}, {0x7ffffeau, 27},
142
+ {0x7ffffebu, 27}, {0xffffffeu, 28}, {0x7ffffecu, 27}, {0x7ffffedu, 27},
143
+ {0x7ffffeeu, 27}, {0x7ffffefu, 27}, {0x7fffff0u, 27}, {0x3ffffeeu, 26},
144
+ {0x3fffffffu, 30},
145
+ };
146
+
147
+ static const ScrH2StaticEntry SCR_H2_STATIC[61] = {
148
+ {":authority", ""},
149
+ {":method", "GET"},
150
+ {":method", "POST"},
151
+ {":path", "/"},
152
+ {":path", "/index.html"},
153
+ {":scheme", "http"},
154
+ {":scheme", "https"},
155
+ {":status", "200"},
156
+ {":status", "204"},
157
+ {":status", "206"},
158
+ {":status", "304"},
159
+ {":status", "400"},
160
+ {":status", "404"},
161
+ {":status", "500"},
162
+ {"accept-charset", ""},
163
+ {"accept-encoding", "gzip, deflate"},
164
+ {"accept-language", ""},
165
+ {"accept-ranges", ""},
166
+ {"accept", ""},
167
+ {"access-control-allow-origin", ""},
168
+ {"age", ""},
169
+ {"allow", ""},
170
+ {"authorization", ""},
171
+ {"cache-control", ""},
172
+ {"content-disposition", ""},
173
+ {"content-encoding", ""},
174
+ {"content-language", ""},
175
+ {"content-length", ""},
176
+ {"content-location", ""},
177
+ {"content-range", ""},
178
+ {"content-type", ""},
179
+ {"cookie", ""},
180
+ {"date", ""},
181
+ {"etag", ""},
182
+ {"expect", ""},
183
+ {"expires", ""},
184
+ {"from", ""},
185
+ {"host", ""},
186
+ {"if-match", ""},
187
+ {"if-modified-since", ""},
188
+ {"if-none-match", ""},
189
+ {"if-range", ""},
190
+ {"if-unmodified-since", ""},
191
+ {"last-modified", ""},
192
+ {"link", ""},
193
+ {"location", ""},
194
+ {"max-forwards", ""},
195
+ {"proxy-authenticate", ""},
196
+ {"proxy-authorization", ""},
197
+ {"range", ""},
198
+ {"referer", ""},
199
+ {"refresh", ""},
200
+ {"retry-after", ""},
201
+ {"server", ""},
202
+ {"set-cookie", ""},
203
+ {"strict-transport-security", ""},
204
+ {"transfer-encoding", ""},
205
+ {"user-agent", ""},
206
+ {"vary", ""},
207
+ {"via", ""},
208
+ {"www-authenticate", ""},
209
+ };
210
+
211
+ /* ── Huffman decoding (bit-walk over a tree built once) ──────────────── */
212
+
213
+ typedef struct ScrH2HuffNode {
214
+ struct ScrH2HuffNode *child[2];
215
+ int sym; /* -1 = interior */
216
+ } ScrH2HuffNode;
217
+
218
+ static ScrH2HuffNode *scr_h2_huff_root = NULL;
219
+
220
+ static ScrH2HuffNode *scr_h2_huff_node(void) {
221
+ ScrH2HuffNode *n = calloc(1, sizeof *n);
222
+ if (!n) scr_h2_oom();
223
+ n->sym = -1;
224
+ return n;
225
+ }
226
+
227
+ static void scr_h2_huff_init(void) {
228
+ if (scr_h2_huff_root) return;
229
+ scr_h2_huff_root = scr_h2_huff_node();
230
+ for (int s = 0; s < 257; s++) {
231
+ ScrH2HuffNode *n = scr_h2_huff_root;
232
+ for (int b = SCR_H2_HUFF[s].len - 1; b >= 0; b--) {
233
+ int bit = (SCR_H2_HUFF[s].code >> b) & 1;
234
+ if (!n->child[bit]) n->child[bit] = scr_h2_huff_node();
235
+ n = n->child[bit];
236
+ }
237
+ n->sym = s;
238
+ }
239
+ }
240
+
241
+ /* Decode `n` Huffman bytes into a malloc'd buffer (*out, *out_len).
242
+ * False on EOS-in-string or a truncated non-padding tail. */
243
+ static bool scr_h2_huff_decode(const uint8_t *p, size_t n, char **out, size_t *out_len) {
244
+ scr_h2_huff_init();
245
+ size_t cap = n * 2 + 8;
246
+ char *buf = malloc(cap);
247
+ if (!buf) scr_h2_oom();
248
+ size_t len = 0;
249
+ ScrH2HuffNode *node = scr_h2_huff_root;
250
+ int depth = 0; /* bits since the last emitted symbol (padding check) */
251
+ for (size_t i = 0; i < n; i++) {
252
+ for (int b = 7; b >= 0; b--) {
253
+ int bit = (p[i] >> b) & 1;
254
+ node = node->child[bit];
255
+ depth++;
256
+ if (!node) { free(buf); return false; }
257
+ if (node->sym >= 0) {
258
+ if (node->sym == 256) { free(buf); return false; } /* EOS in string */
259
+ if (len == cap) {
260
+ cap *= 2;
261
+ buf = realloc(buf, cap);
262
+ if (!buf) scr_h2_oom();
263
+ }
264
+ buf[len++] = (char)node->sym;
265
+ node = scr_h2_huff_root;
266
+ depth = 0;
267
+ }
268
+ }
269
+ }
270
+ /* Padding must be a prefix of EOS (all-ones), at most 7 bits: the
271
+ * walk from the root must not have consumed 8+ bits, and every
272
+ * consumed bit must have been 1 — the all-ones path from the root. */
273
+ if (depth > 7) { free(buf); return false; }
274
+ ScrH2HuffNode *ones = scr_h2_huff_root;
275
+ for (int i = 0; i < depth; i++) ones = ones->child[1];
276
+ if (node != ones) { free(buf); return false; }
277
+ *out = buf;
278
+ *out_len = len;
279
+ return true;
280
+ }
281
+
282
+ /* ── HPACK decoder (RFC 7541) ────────────────────────────────────────── */
283
+
284
+ typedef struct {
285
+ char *name;
286
+ size_t name_len;
287
+ char *value;
288
+ size_t value_len;
289
+ } ScrH2TableEntry;
290
+
291
+ /* The dynamic table: a ring of entries, newest at (head-1). Size
292
+ * accounting per RFC 7541 §4.1 (32 bytes overhead per entry). */
293
+ typedef struct {
294
+ ScrH2TableEntry *e;
295
+ size_t cap; /* ring capacity (entries) */
296
+ size_t head; /* next insert slot */
297
+ size_t n; /* live entries */
298
+ size_t size; /* octets per §4.1 */
299
+ size_t max; /* current max (SETTINGS value, lowered by updates) */
300
+ size_t settings_max; /* the SETTINGS_HEADER_TABLE_SIZE ceiling */
301
+ } ScrH2Dyntab;
302
+
303
+ static void scr_h2_dyntab_init(ScrH2Dyntab *t) {
304
+ memset(t, 0, sizeof *t);
305
+ t->max = 4096;
306
+ t->settings_max = 4096;
307
+ }
308
+
309
+ static void scr_h2_dyntab_evict(ScrH2Dyntab *t, size_t need) {
310
+ while (t->n > 0 && t->size + need > t->max) {
311
+ size_t oldest = (t->head + t->cap - t->n) % t->cap;
312
+ ScrH2TableEntry *e = &t->e[oldest];
313
+ t->size -= e->name_len + e->value_len + 32;
314
+ free(e->name);
315
+ free(e->value);
316
+ t->n--;
317
+ }
318
+ }
319
+
320
+ static void scr_h2_dyntab_add(ScrH2Dyntab *t, const char *name, size_t name_len,
321
+ const char *value, size_t value_len) {
322
+ size_t need = name_len + value_len + 32;
323
+ scr_h2_dyntab_evict(t, need);
324
+ if (need > t->max) return; /* larger than the table: evicts all, adds nothing */
325
+ if (t->n == t->cap) {
326
+ size_t ncap = t->cap ? t->cap * 2 : 16;
327
+ ScrH2TableEntry *ne = malloc(ncap * sizeof *ne);
328
+ if (!ne) scr_h2_oom();
329
+ /* re-lay the ring oldest-first */
330
+ for (size_t i = 0; i < t->n; i++) {
331
+ size_t idx = (t->head + t->cap - t->n + i) % t->cap;
332
+ ne[i] = t->e[idx];
333
+ }
334
+ free(t->e);
335
+ t->e = ne;
336
+ t->cap = ncap;
337
+ t->head = t->n;
338
+ }
339
+ ScrH2TableEntry *e = &t->e[t->head];
340
+ e->name = malloc(name_len + 1);
341
+ e->value = malloc(value_len + 1);
342
+ if (!e->name || !e->value) scr_h2_oom();
343
+ memcpy(e->name, name, name_len);
344
+ e->name[name_len] = 0;
345
+ memcpy(e->value, value, value_len);
346
+ e->value[value_len] = 0;
347
+ e->name_len = name_len;
348
+ e->value_len = value_len;
349
+ t->head = (t->head + 1) % t->cap;
350
+ t->n++;
351
+ t->size += need;
352
+ }
353
+
354
+ static void scr_h2_dyntab_free(ScrH2Dyntab *t) {
355
+ for (size_t i = 0; i < t->n; i++) {
356
+ size_t idx = (t->head + t->cap - t->n + i) % t->cap;
357
+ free(t->e[idx].name);
358
+ free(t->e[idx].value);
359
+ }
360
+ free(t->e);
361
+ memset(t, 0, sizeof *t);
362
+ }
363
+
364
+ /* index 1..61 static, 62.. dynamic (newest first). False = bad index. */
365
+ static bool scr_h2_table_lookup(const ScrH2Dyntab *t, uint64_t idx,
366
+ const char **name, size_t *name_len,
367
+ const char **value, size_t *value_len) {
368
+ if (idx >= 1 && idx <= 61) {
369
+ const ScrH2StaticEntry *e = &SCR_H2_STATIC[idx - 1];
370
+ *name = e->name;
371
+ *name_len = strlen(e->name);
372
+ *value = e->value;
373
+ *value_len = strlen(e->value);
374
+ return true;
375
+ }
376
+ if (idx >= 62 && idx - 62 < t->n) {
377
+ size_t back = (size_t)(idx - 62); /* 0 = newest */
378
+ size_t slot = (t->head + t->cap - 1 - back) % t->cap;
379
+ const ScrH2TableEntry *e = &t->e[slot];
380
+ *name = e->name;
381
+ *name_len = e->name_len;
382
+ *value = e->value;
383
+ *value_len = e->value_len;
384
+ return true;
385
+ }
386
+ return false;
387
+ }
388
+
389
+ /* §5.1 integer decode. False on truncation/overflow. */
390
+ static bool scr_h2_int_decode(const uint8_t **pp, const uint8_t *end, int prefix_bits,
391
+ uint64_t *out) {
392
+ if (*pp >= end) return false;
393
+ uint64_t max_prefix = (1u << prefix_bits) - 1;
394
+ uint64_t v = **pp & max_prefix;
395
+ (*pp)++;
396
+ if (v < max_prefix) {
397
+ *out = v;
398
+ return true;
399
+ }
400
+ int shift = 0;
401
+ for (;;) {
402
+ if (*pp >= end || shift > 56) return false;
403
+ uint8_t b = **pp;
404
+ (*pp)++;
405
+ v += (uint64_t)(b & 0x7f) << shift;
406
+ shift += 7;
407
+ if ((b & 0x80) == 0) break;
408
+ }
409
+ *out = v;
410
+ return true;
411
+ }
412
+
413
+ /* §5.2 string literal: length-prefixed, optionally Huffman. The result
414
+ * is always a fresh malloc'd buffer the caller frees. */
415
+ static bool scr_h2_str_decode(const uint8_t **pp, const uint8_t *end,
416
+ char **out, size_t *out_len) {
417
+ if (*pp >= end) return false;
418
+ bool huff = (**pp & 0x80) != 0;
419
+ uint64_t len;
420
+ if (!scr_h2_int_decode(pp, end, 7, &len)) return false;
421
+ if ((uint64_t)(end - *pp) < len) return false;
422
+ if (huff) {
423
+ if (!scr_h2_huff_decode(*pp, (size_t)len, out, out_len)) return false;
424
+ } else {
425
+ char *buf = malloc((size_t)len + 1);
426
+ if (!buf) scr_h2_oom();
427
+ memcpy(buf, *pp, (size_t)len);
428
+ buf[len] = 0;
429
+ *out = buf;
430
+ *out_len = (size_t)len;
431
+ }
432
+ *pp += len;
433
+ return true;
434
+ }
435
+
436
+ /* One decoded header list: parallel ScrStr arrays (the ScrHttpReq header
437
+ * storage shape — names arrive lowercase on a valid h2 wire). */
438
+ typedef struct {
439
+ ScrStr **names; /* each +1 */
440
+ ScrStr **values; /* each +1 */
441
+ size_t n, cap;
442
+ } ScrH2Headers;
443
+
444
+ static void scr_h2_headers_push(ScrH2Headers *h, ScrStr *name /*moves*/, ScrStr *value /*moves*/) {
445
+ if (h->n == h->cap) {
446
+ h->cap = h->cap ? h->cap * 2 : 8;
447
+ h->names = realloc(h->names, h->cap * sizeof *h->names);
448
+ h->values = realloc(h->values, h->cap * sizeof *h->values);
449
+ if (!h->names || !h->values) scr_h2_oom();
450
+ }
451
+ h->names[h->n] = name;
452
+ h->values[h->n] = value;
453
+ h->n++;
454
+ }
455
+
456
+ static void scr_h2_headers_free(ScrH2Headers *h) {
457
+ for (size_t i = 0; i < h->n; i++) {
458
+ scr_str_release(h->names[i]);
459
+ scr_str_release(h->values[i]);
460
+ }
461
+ free(h->names);
462
+ free(h->values);
463
+ memset(h, 0, sizeof *h);
464
+ }
465
+
466
+ /* Decode a complete header block into `out`. False = COMPRESSION_ERROR. */
467
+ static bool scr_h2_hpack_decode(ScrH2Dyntab *t, const uint8_t *p, size_t n, ScrH2Headers *out) {
468
+ const uint8_t *end = p + n;
469
+ while (p < end) {
470
+ uint8_t b = *p;
471
+ if (b & 0x80) {
472
+ /* §6.1 indexed */
473
+ uint64_t idx;
474
+ if (!scr_h2_int_decode(&p, end, 7, &idx)) return false;
475
+ const char *nm, *vl;
476
+ size_t nl, vll;
477
+ if (idx == 0 || !scr_h2_table_lookup(t, idx, &nm, &nl, &vl, &vll)) return false;
478
+ scr_h2_headers_push(out, scr_str_new(nm, nl), scr_str_new(vl, vll));
479
+ continue;
480
+ }
481
+ if ((b & 0xe0) == 0x20) {
482
+ /* §6.3 dynamic table size update */
483
+ uint64_t sz;
484
+ if (!scr_h2_int_decode(&p, end, 5, &sz)) return false;
485
+ if (sz > t->settings_max) return false;
486
+ t->max = (size_t)sz;
487
+ scr_h2_dyntab_evict(t, 0);
488
+ continue;
489
+ }
490
+ /* §6.2 literals: incremental (01xxxxxx, 6-bit name index), without
491
+ * indexing (0000xxxx), never indexed (0001xxxx) — 4-bit name index. */
492
+ bool incremental = (b & 0xc0) == 0x40;
493
+ int prefix = incremental ? 6 : 4;
494
+ uint64_t idx;
495
+ if (!scr_h2_int_decode(&p, end, prefix, &idx)) return false;
496
+ char *name;
497
+ size_t name_len;
498
+ bool name_owned = false;
499
+ if (idx == 0) {
500
+ if (!scr_h2_str_decode(&p, end, &name, &name_len)) return false;
501
+ name_owned = true;
502
+ } else {
503
+ const char *nm, *vl;
504
+ size_t nl, vll;
505
+ if (!scr_h2_table_lookup(t, idx, &nm, &nl, &vl, &vll)) return false;
506
+ name = (char *)nm;
507
+ name_len = nl;
508
+ }
509
+ char *value;
510
+ size_t value_len;
511
+ if (!scr_h2_str_decode(&p, end, &value, &value_len)) {
512
+ if (name_owned) free(name);
513
+ return false;
514
+ }
515
+ if (incremental) scr_h2_dyntab_add(t, name, name_len, value, value_len);
516
+ scr_h2_headers_push(out, scr_str_new(name, name_len), scr_str_new(value, value_len));
517
+ if (name_owned) free(name);
518
+ free(value);
519
+ }
520
+ return true;
521
+ }
522
+
523
+ /* ── HPACK encoder (static-match + literal-without-indexing) ─────────── */
524
+
525
+ typedef struct {
526
+ char *data;
527
+ size_t len, cap;
528
+ } ScrH2Buf;
529
+
530
+ static void scr_h2_buf_reserve(ScrH2Buf *b, size_t extra) {
531
+ if (b->len + extra <= b->cap) return;
532
+ size_t ncap = b->cap ? b->cap : 256;
533
+ while (ncap < b->len + extra) ncap *= 2;
534
+ b->data = realloc(b->data, ncap);
535
+ if (!b->data) scr_h2_oom();
536
+ b->cap = ncap;
537
+ }
538
+
539
+ static void scr_h2_buf_put(ScrH2Buf *b, const void *p, size_t n) {
540
+ scr_h2_buf_reserve(b, n);
541
+ memcpy(b->data + b->len, p, n);
542
+ b->len += n;
543
+ }
544
+
545
+ static void scr_h2_buf_u8(ScrH2Buf *b, uint8_t v) { scr_h2_buf_put(b, &v, 1); }
546
+
547
+ static void scr_h2_int_encode(ScrH2Buf *b, uint8_t first_bits, int prefix_bits, uint64_t v) {
548
+ uint64_t max_prefix = (1u << prefix_bits) - 1;
549
+ if (v < max_prefix) {
550
+ scr_h2_buf_u8(b, (uint8_t)(first_bits | v));
551
+ return;
552
+ }
553
+ scr_h2_buf_u8(b, (uint8_t)(first_bits | max_prefix));
554
+ v -= max_prefix;
555
+ while (v >= 128) {
556
+ scr_h2_buf_u8(b, (uint8_t)(v % 128 + 128));
557
+ v /= 128;
558
+ }
559
+ scr_h2_buf_u8(b, (uint8_t)v);
560
+ }
561
+
562
+ static void scr_h2_str_encode(ScrH2Buf *b, const char *s, size_t n) {
563
+ scr_h2_int_encode(b, 0x00, 7, n); /* no Huffman bit */
564
+ scr_h2_buf_put(b, s, n);
565
+ }
566
+
567
+ /* Encode one header. Exact static match → indexed; static name match →
568
+ * literal-without-indexing with indexed name; else both literal. */
569
+ static void scr_h2_hpack_encode(ScrH2Buf *b, const char *name, size_t name_len,
570
+ const char *value, size_t value_len) {
571
+ int name_idx = 0;
572
+ for (int i = 0; i < 61; i++) {
573
+ const ScrH2StaticEntry *e = &SCR_H2_STATIC[i];
574
+ if (strlen(e->name) != name_len || memcmp(e->name, name, name_len) != 0) continue;
575
+ if (name_idx == 0) name_idx = i + 1;
576
+ if (strlen(e->value) == value_len && memcmp(e->value, value, value_len) == 0) {
577
+ scr_h2_int_encode(b, 0x80, 7, (uint64_t)(i + 1));
578
+ return;
579
+ }
580
+ }
581
+ scr_h2_int_encode(b, 0x00, 4, (uint64_t)name_idx);
582
+ if (name_idx == 0) scr_h2_str_encode(b, name, name_len);
583
+ scr_h2_str_encode(b, value, value_len);
584
+ }
585
+
586
+ /* ── frames (RFC 7540 §4, §6) ────────────────────────────────────────── */
587
+
588
+ enum {
589
+ SCR_H2_F_DATA = 0x0,
590
+ SCR_H2_F_HEADERS = 0x1,
591
+ SCR_H2_F_PRIORITY = 0x2,
592
+ SCR_H2_F_RST_STREAM = 0x3,
593
+ SCR_H2_F_SETTINGS = 0x4,
594
+ SCR_H2_F_PUSH_PROMISE = 0x5,
595
+ SCR_H2_F_PING = 0x6,
596
+ SCR_H2_F_GOAWAY = 0x7,
597
+ SCR_H2_F_WINDOW_UPDATE = 0x8,
598
+ SCR_H2_F_CONTINUATION = 0x9,
599
+ };
600
+
601
+ enum {
602
+ SCR_H2_FLAG_END_STREAM = 0x1, /* DATA, HEADERS */
603
+ SCR_H2_FLAG_ACK = 0x1, /* SETTINGS, PING */
604
+ SCR_H2_FLAG_END_HEADERS = 0x4, /* HEADERS, PUSH_PROMISE, CONTINUATION */
605
+ SCR_H2_FLAG_PADDED = 0x8, /* DATA, HEADERS, PUSH_PROMISE */
606
+ SCR_H2_FLAG_PRIORITY = 0x20, /* HEADERS */
607
+ };
608
+
609
+ /* NGHTTP2 error-code names (RST_STREAM / GOAWAY codes 0..13) — the
610
+ * ERR_HTTP2_STREAM_ERROR message vocabulary. */
611
+ static const char *const SCR_H2_CODE_NAMES[] = {
612
+ "NGHTTP2_NO_ERROR", "NGHTTP2_PROTOCOL_ERROR", "NGHTTP2_INTERNAL_ERROR",
613
+ "NGHTTP2_FLOW_CONTROL_ERROR", "NGHTTP2_SETTINGS_TIMEOUT", "NGHTTP2_STREAM_CLOSED",
614
+ "NGHTTP2_FRAME_SIZE_ERROR", "NGHTTP2_REFUSED_STREAM", "NGHTTP2_CANCEL",
615
+ "NGHTTP2_COMPRESSION_ERROR", "NGHTTP2_CONNECT_ERROR", "NGHTTP2_ENHANCE_YOUR_CALM",
616
+ "NGHTTP2_INADEQUATE_SECURITY", "NGHTTP2_HTTP_1_1_REQUIRED",
617
+ };
618
+
619
+ static const char *scr_h2_code_name(uint32_t code) {
620
+ if (code < sizeof SCR_H2_CODE_NAMES / sizeof *SCR_H2_CODE_NAMES) {
621
+ return SCR_H2_CODE_NAMES[code];
622
+ }
623
+ return NULL;
624
+ }
625
+
626
+ static const char SCR_H2_PREFACE[24] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
627
+
628
+ enum {
629
+ SCR_H2_DEFAULT_WINDOW = 65535,
630
+ SCR_H2_DEFAULT_MAX_FRAME = 16384,
631
+ };
632
+
633
+ /* ── the handles ─────────────────────────────────────────────────────── */
634
+
635
+ /* Stream state: the RFC machine collapsed to what the API observes. */
636
+ enum {
637
+ SCR_H2_S_OPEN = 0,
638
+ SCR_H2_S_CLOSED_LOCAL = 1, /* END_STREAM sent */
639
+ SCR_H2_S_CLOSED_REMOTE = 2, /* END_STREAM received */
640
+ SCR_H2_S_CLOSED = 3,
641
+ };
642
+
643
+ typedef struct ScrH2Chunk {
644
+ struct ScrH2Chunk *next;
645
+ size_t len;
646
+ char data[];
647
+ } ScrH2Chunk;
648
+
649
+ struct ScrH2Stream {
650
+ size_t rc;
651
+ int32_t id;
652
+ int state;
653
+ bool destroyed;
654
+ bool aborted; /* remote quit before END_STREAM */
655
+ uint32_t rst_code; /* stream.rstCode — set by either side's RST */
656
+ bool rst_sent, rst_recv;
657
+ bool respond_sent; /* server: respond() ran */
658
+ bool want_end; /* end() called; END_STREAM rides the last flush */
659
+ bool end_recv; /* END_STREAM arrived */
660
+ bool end_emitted;
661
+ bool close_queued, close_emitted;
662
+ bool err_code_queued; /* a nonzero-code 'error' rides the close sweep */
663
+ bool flowing; /* a 'data' listener or resume(); pause() clears */
664
+ bool had_data; /* a nonempty DATA frame arrived (the empty-body
665
+ * 'end' rule: nothing to consume — Node emits
666
+ * 'end' without a reader) */
667
+ bool enc_utf8; /* setEncoding('utf8') */
668
+ struct ScrH2Session *sess; /* +1 for the stream's whole life */
669
+ /* the compat pair (Http2ServerRequest/Response over this stream), +1
670
+ * each; the settle releases both — that break unwinds the stream↔res
671
+ * ownership cycle (res holds the stream through the ops vtable) */
672
+ ScrHttpReq *compat_req;
673
+ ScrHttpRes *compat_res;
674
+ /* recv queue (paused-mode buffering), one node per DATA frame */
675
+ ScrH2Chunk *rq_head, *rq_tail;
676
+ /* send buffer for window-blocked bytes */
677
+ ScrH2Buf wbuf;
678
+ int64_t send_window;
679
+ ScrNetLs data_ls, end_ls, close_ls, err_ls, resp_ls, aborted_ls, ready_ls,
680
+ trailers_ls, wanttrailers_ls, push_ls, frameerr_ls;
681
+ };
682
+
683
+ /* The server-side ctx (the ScrHttpSrvCtx shape; `proto` FIRST — the
684
+ * http_ctx alias slot is shared with scr_http.c's parser family). */
685
+ typedef struct ScrH2SrvCtx {
686
+ int proto; /* SCR_NET_PROTO_H2 */
687
+ size_t rc;
688
+ ScrNetLs stream_ls;
689
+ ScrNetLs session_ls;
690
+ ScrNetLs request_ls; /* the compat (req, res) listeners — createServer's
691
+ * eager handler and server.on("request", ...) */
692
+ } ScrH2SrvCtx;
693
+
694
+ /* The SETTINGS the API observes (session.localSettings/remoteSettings —
695
+ * Node's record shape; maxHeaderSize aliases maxHeaderListSize). */
696
+ typedef struct {
697
+ uint32_t header_table_size;
698
+ bool enable_push;
699
+ uint32_t initial_window_size;
700
+ uint32_t max_frame_size;
701
+ uint32_t max_concurrent_streams;
702
+ uint32_t max_header_list_size;
703
+ bool enable_connect_protocol;
704
+ } ScrH2Settings;
705
+
706
+ static void scr_h2_settings_defaults(ScrH2Settings *s) {
707
+ s->header_table_size = 4096;
708
+ s->enable_push = true;
709
+ s->initial_window_size = 65535;
710
+ s->max_frame_size = 16384;
711
+ s->max_concurrent_streams = 0xffffffffu;
712
+ s->max_header_list_size = 65535;
713
+ s->enable_connect_protocol = false;
714
+ }
715
+
716
+ struct ScrH2Session {
717
+ size_t rc;
718
+ bool client;
719
+ bool destroyed;
720
+ bool closed; /* close() called (graceful GOAWAY) */
721
+ bool close_queued, close_emitted;
722
+ bool connect_queued; /* client 'connect' emit pending */
723
+ bool preface_done; /* server: magic received; client: always true */
724
+ bool goaway_sent, goaway_recv;
725
+ uint32_t goaway_code;
726
+ ScrNetSocket *sock; /* +1; dropped (NULLed) when the socket closes */
727
+ ScrH2SrvCtx *srv; /* +1; NULL on client sessions */
728
+ /* frame reassembly */
729
+ char *rbuf;
730
+ size_t rlen, rcap;
731
+ /* an in-flight header block (HEADERS + CONTINUATIONs) */
732
+ ScrH2Buf hblock;
733
+ int32_t hblock_stream;
734
+ uint8_t hblock_flags;
735
+ bool hblock_active;
736
+ /* HPACK */
737
+ ScrH2Dyntab dec_table;
738
+ /* client: the :authority default (host[:port] from the connect URL) */
739
+ char *authority;
740
+ size_t authority_len;
741
+ /* streams (live vector, each +1) */
742
+ ScrH2Stream **streams;
743
+ size_t nstreams, streams_cap;
744
+ int32_t next_stream_id; /* client request ids: 1, 3, 5, ... */
745
+ int32_t last_recv_stream_id; /* the GOAWAY last-stream answer */
746
+ /* flow control + peer settings */
747
+ int64_t send_window;
748
+ uint32_t peer_max_frame;
749
+ uint32_t peer_init_window;
750
+ /* the API-visible settings records + the in-flight local ack */
751
+ ScrH2Settings local_settings, remote_settings;
752
+ bool pending_settings_ack;
753
+ ScrNetLs stream_ls, close_ls, err_ls, connect_ls, goaway_ls,
754
+ remote_settings_ls, local_settings_ls;
755
+ };
756
+
757
+ /* ── refcounts + the RC audit ────────────────────────────────────────── */
758
+
759
+ #ifdef SCR_RC_AUDIT
760
+ static long scr_h2_sessions_live = 0;
761
+ static long scr_h2_streams_live = 0;
762
+ long scr_http2_live_count(void) { return scr_h2_sessions_live + scr_h2_streams_live; }
763
+ #endif
764
+
765
+ ScrH2Session *scr_http2_session_retain(ScrH2Session *s) {
766
+ if (s && s->rc != SIZE_MAX) s->rc++;
767
+ return s;
768
+ }
769
+
770
+ static void scr_h2_session_free(ScrH2Session *s);
771
+
772
+ void scr_http2_session_release(ScrH2Session *s) {
773
+ if (!s || s->rc == SIZE_MAX) return;
774
+ if (--s->rc == 0) scr_h2_session_free(s);
775
+ }
776
+
777
+ void *scr_http2_session_retain_v(void *p) { return scr_http2_session_retain(p); }
778
+ void scr_http2_session_release_v(void *p) { scr_http2_session_release(p); }
779
+
780
+ ScrH2Stream *scr_http2_stream_retain(ScrH2Stream *st) {
781
+ if (st && st->rc != SIZE_MAX) st->rc++;
782
+ return st;
783
+ }
784
+
785
+ static void scr_h2_stream_free(ScrH2Stream *st);
786
+
787
+ void scr_http2_stream_release(ScrH2Stream *st) {
788
+ if (!st || st->rc == SIZE_MAX) return;
789
+ if (--st->rc == 0) scr_h2_stream_free(st);
790
+ }
791
+
792
+ void *scr_http2_stream_retain_v(void *p) { return scr_http2_stream_retain(p); }
793
+ void scr_http2_stream_release_v(void *p) { scr_http2_stream_release(p); }
794
+
795
+ static ScrH2SrvCtx *scr_h2_srv_ctx_retain(ScrH2SrvCtx *ctx) {
796
+ ctx->rc++;
797
+ return ctx;
798
+ }
799
+
800
+ static void scr_h2_srv_ctx_release(ScrH2SrvCtx *ctx) {
801
+ if (--ctx->rc > 0) return;
802
+ scr_net_ls_drop(&ctx->stream_ls);
803
+ scr_net_ls_drop(&ctx->session_ls);
804
+ scr_net_ls_drop(&ctx->request_ls);
805
+ free(ctx);
806
+ }
807
+
808
+ static void scr_h2_stream_drop_lists(ScrH2Stream *st) {
809
+ scr_net_ls_drop(&st->data_ls);
810
+ scr_net_ls_drop(&st->end_ls);
811
+ scr_net_ls_drop(&st->close_ls);
812
+ scr_net_ls_drop(&st->err_ls);
813
+ scr_net_ls_drop(&st->resp_ls);
814
+ scr_net_ls_drop(&st->aborted_ls);
815
+ scr_net_ls_drop(&st->ready_ls);
816
+ scr_net_ls_drop(&st->trailers_ls);
817
+ scr_net_ls_drop(&st->wanttrailers_ls);
818
+ scr_net_ls_drop(&st->push_ls);
819
+ scr_net_ls_drop(&st->frameerr_ls);
820
+ }
821
+
822
+ static void scr_h2_stream_free(ScrH2Stream *st) {
823
+ scr_h2_stream_drop_lists(st);
824
+ scr_http_req_release(st->compat_req);
825
+ scr_http_res_release(st->compat_res);
826
+ ScrH2Chunk *c = st->rq_head;
827
+ while (c) {
828
+ ScrH2Chunk *next = c->next;
829
+ free(c);
830
+ c = next;
831
+ }
832
+ free(st->wbuf.data);
833
+ scr_http2_session_release(st->sess);
834
+ free(st);
835
+ #ifdef SCR_RC_AUDIT
836
+ scr_h2_streams_live--;
837
+ #endif
838
+ }
839
+
840
+ static void scr_h2_session_drop_lists(ScrH2Session *s) {
841
+ scr_net_ls_drop(&s->stream_ls);
842
+ scr_net_ls_drop(&s->close_ls);
843
+ scr_net_ls_drop(&s->err_ls);
844
+ scr_net_ls_drop(&s->connect_ls);
845
+ scr_net_ls_drop(&s->goaway_ls);
846
+ scr_net_ls_drop(&s->remote_settings_ls);
847
+ scr_net_ls_drop(&s->local_settings_ls);
848
+ }
849
+
850
+ static void scr_h2_session_free(ScrH2Session *s) {
851
+ scr_h2_session_drop_lists(s);
852
+ for (size_t i = 0; i < s->nstreams; i++) scr_http2_stream_release(s->streams[i]);
853
+ free(s->streams);
854
+ free(s->rbuf);
855
+ free(s->hblock.data);
856
+ free(s->authority);
857
+ scr_h2_dyntab_free(&s->dec_table);
858
+ if (s->srv) scr_h2_srv_ctx_release(s->srv);
859
+ if (s->sock) scr_net_sock_release(s->sock);
860
+ free(s);
861
+ #ifdef SCR_RC_AUDIT
862
+ scr_h2_sessions_live--;
863
+ #endif
864
+ }
865
+
866
+ /* ── the deferred-emit queue (the scr_http.c pattern) ────────────────── */
867
+
868
+ enum {
869
+ SCR_H2_EMIT_STREAM_CLOSE, /* 'error' (nonzero code) then 'close' */
870
+ SCR_H2_EMIT_SESSION_CLOSE, /* session 'close' */
871
+ SCR_H2_EMIT_SESSION_CONNECT, /* client 'connect' */
872
+ SCR_H2_EMIT_SERVER_SESSION, /* server 'session' (h is the session) */
873
+ SCR_H2_EMIT_STREAM_DRAIN, /* deliver buffered data (+ maybe 'end') */
874
+ };
875
+
876
+ typedef struct {
877
+ int kind;
878
+ void *h; /* +1, typed by kind */
879
+ } ScrH2Emit;
880
+
881
+ static ScrH2Emit *scr_h2_emits = NULL;
882
+ static size_t scr_h2_emits_head = 0, scr_h2_emits_len = 0, scr_h2_emits_cap = 0;
883
+ static bool scr_h2_exiting = false;
884
+
885
+ static void scr_h2_emit_release(ScrH2Emit *e) {
886
+ switch (e->kind) {
887
+ case SCR_H2_EMIT_STREAM_CLOSE:
888
+ case SCR_H2_EMIT_STREAM_DRAIN:
889
+ scr_http2_stream_release((ScrH2Stream *)e->h);
890
+ break;
891
+ default:
892
+ scr_http2_session_release((ScrH2Session *)e->h);
893
+ break;
894
+ }
895
+ }
896
+
897
+ static void scr_h2_emit_push(int kind, void *h /*moves +1*/) {
898
+ if (scr_h2_exiting) {
899
+ ScrH2Emit e = { kind, h };
900
+ scr_h2_emit_release(&e);
901
+ return;
902
+ }
903
+ if (scr_h2_emits_len == scr_h2_emits_cap) {
904
+ if (scr_h2_emits_head > 0) {
905
+ memmove(scr_h2_emits, scr_h2_emits + scr_h2_emits_head,
906
+ (scr_h2_emits_len - scr_h2_emits_head) * sizeof *scr_h2_emits);
907
+ scr_h2_emits_len -= scr_h2_emits_head;
908
+ scr_h2_emits_head = 0;
909
+ }
910
+ if (scr_h2_emits_len == scr_h2_emits_cap) {
911
+ scr_h2_emits_cap = scr_h2_emits_cap ? scr_h2_emits_cap * 2 : 8;
912
+ scr_h2_emits = realloc(scr_h2_emits, scr_h2_emits_cap * sizeof *scr_h2_emits);
913
+ if (!scr_h2_emits) scr_h2_oom();
914
+ }
915
+ }
916
+ scr_h2_emits[scr_h2_emits_len].kind = kind;
917
+ scr_h2_emits[scr_h2_emits_len].h = h;
918
+ scr_h2_emits_len++;
919
+ }
920
+
921
+ /* ── frame send ──────────────────────────────────────────────────────── */
922
+
923
+ static bool scr_h2_sock_alive(ScrH2Session *s) {
924
+ return s->sock != NULL && !scr_net_sock_destroyed(s->sock);
925
+ }
926
+
927
+ static void scr_h2_send_frame(ScrH2Session *s, uint8_t type, uint8_t flags,
928
+ int32_t stream_id, const void *payload, size_t n) {
929
+ if (!scr_h2_sock_alive(s)) return;
930
+ uint8_t head[9];
931
+ head[0] = (uint8_t)(n >> 16);
932
+ head[1] = (uint8_t)(n >> 8);
933
+ head[2] = (uint8_t)n;
934
+ head[3] = type;
935
+ head[4] = flags;
936
+ head[5] = (uint8_t)((uint32_t)stream_id >> 24) & 0x7f;
937
+ head[6] = (uint8_t)((uint32_t)stream_id >> 16);
938
+ head[7] = (uint8_t)((uint32_t)stream_id >> 8);
939
+ head[8] = (uint8_t)stream_id;
940
+ scr_net_sock_write_native(s->sock, (const char *)head, 9);
941
+ if (n > 0) scr_net_sock_write_native(s->sock, payload, n);
942
+ }
943
+
944
+ static void scr_h2_send_settings(ScrH2Session *s) {
945
+ /* Defaults on the wire — except the CLIENT's enablePush, which Node
946
+ * pins to 0 (clients never accept pushes; localSettings reads false). */
947
+ if (s->client) {
948
+ static const uint8_t no_push[6] = { 0x0, 0x2, 0, 0, 0, 0 };
949
+ scr_h2_send_frame(s, SCR_H2_F_SETTINGS, 0, 0, no_push, 6);
950
+ return;
951
+ }
952
+ scr_h2_send_frame(s, SCR_H2_F_SETTINGS, 0, 0, NULL, 0);
953
+ }
954
+
955
+ static void scr_h2_send_settings_ack(ScrH2Session *s) {
956
+ scr_h2_send_frame(s, SCR_H2_F_SETTINGS, SCR_H2_FLAG_ACK, 0, NULL, 0);
957
+ }
958
+
959
+ static void scr_h2_send_rst(ScrH2Session *s, int32_t stream_id, uint32_t code) {
960
+ uint8_t p[4] = { (uint8_t)(code >> 24), (uint8_t)(code >> 16), (uint8_t)(code >> 8), (uint8_t)code };
961
+ scr_h2_send_frame(s, SCR_H2_F_RST_STREAM, 0, stream_id, p, 4);
962
+ }
963
+
964
+ static void scr_h2_send_window_update(ScrH2Session *s, int32_t stream_id, uint32_t inc) {
965
+ uint8_t p[4] = { (uint8_t)(inc >> 24) & 0x7f, (uint8_t)(inc >> 16), (uint8_t)(inc >> 8), (uint8_t)inc };
966
+ scr_h2_send_frame(s, SCR_H2_F_WINDOW_UPDATE, 0, stream_id, p, 4);
967
+ }
968
+
969
+ static void scr_h2_send_goaway(ScrH2Session *s, uint32_t code) {
970
+ if (s->goaway_sent) return;
971
+ s->goaway_sent = true;
972
+ uint32_t last = (uint32_t)s->last_recv_stream_id;
973
+ uint8_t p[8] = {
974
+ (uint8_t)(last >> 24) & 0x7f, (uint8_t)(last >> 16), (uint8_t)(last >> 8), (uint8_t)last,
975
+ (uint8_t)(code >> 24), (uint8_t)(code >> 16), (uint8_t)(code >> 8), (uint8_t)code,
976
+ };
977
+ scr_h2_send_frame(s, SCR_H2_F_GOAWAY, 0, 0, p, 8);
978
+ }
979
+
980
+ /* A header block as HEADERS (+ CONTINUATIONs past the peer's max frame). */
981
+ static void scr_h2_send_headers(ScrH2Session *s, int32_t stream_id, const ScrH2Buf *block,
982
+ bool end_stream) {
983
+ size_t max = s->peer_max_frame ? s->peer_max_frame : SCR_H2_DEFAULT_MAX_FRAME;
984
+ size_t off = 0;
985
+ bool first = true;
986
+ do {
987
+ size_t n = block->len - off;
988
+ if (n > max) n = max;
989
+ uint8_t type = first ? SCR_H2_F_HEADERS : SCR_H2_F_CONTINUATION;
990
+ uint8_t flags = 0;
991
+ if (first && end_stream) flags |= SCR_H2_FLAG_END_STREAM;
992
+ if (off + n == block->len) flags |= SCR_H2_FLAG_END_HEADERS;
993
+ scr_h2_send_frame(s, type, flags, stream_id, block->data + off, n);
994
+ off += n;
995
+ first = false;
996
+ } while (off < block->len);
997
+ }
998
+
999
+ /* ── stream bookkeeping ──────────────────────────────────────────────── */
1000
+
1001
+ static ScrH2Stream *scr_h2_find_stream(ScrH2Session *s, int32_t id) {
1002
+ for (size_t i = 0; i < s->nstreams; i++) {
1003
+ if (s->streams[i]->id == id) return s->streams[i];
1004
+ }
1005
+ return NULL;
1006
+ }
1007
+
1008
+ static ScrH2Stream *scr_h2_stream_new(ScrH2Session *sess, int32_t id) {
1009
+ ScrH2Stream *st = calloc(1, sizeof *st);
1010
+ if (!st) scr_h2_oom();
1011
+ st->rc = 1;
1012
+ st->id = id;
1013
+ st->sess = scr_http2_session_retain(sess);
1014
+ st->send_window = sess->peer_init_window ? (int64_t)sess->peer_init_window : SCR_H2_DEFAULT_WINDOW;
1015
+ #ifdef SCR_RC_AUDIT
1016
+ scr_h2_streams_live++;
1017
+ #endif
1018
+ if (sess->nstreams == sess->streams_cap) {
1019
+ sess->streams_cap = sess->streams_cap ? sess->streams_cap * 2 : 8;
1020
+ sess->streams = realloc(sess->streams, sess->streams_cap * sizeof *sess->streams);
1021
+ if (!sess->streams) scr_h2_oom();
1022
+ }
1023
+ sess->streams[sess->nstreams++] = scr_http2_stream_retain(st);
1024
+ return st;
1025
+ }
1026
+
1027
+ /* Drop the session's ref on a settled stream (its 'close' is queued or
1028
+ * emitted — the vector entry is what kept it discoverable by id). */
1029
+ static void scr_h2_session_forget_stream(ScrH2Session *s, ScrH2Stream *st) {
1030
+ for (size_t i = 0; i < s->nstreams; i++) {
1031
+ if (s->streams[i] == st) {
1032
+ memmove(s->streams + i, s->streams + i + 1, (s->nstreams - i - 1) * sizeof *s->streams);
1033
+ s->nstreams--;
1034
+ scr_http2_stream_release(st);
1035
+ return;
1036
+ }
1037
+ }
1038
+ }
1039
+
1040
+ static void scr_h2_session_maybe_finish_close(ScrH2Session *s);
1041
+
1042
+ /* Queue the stream's 'close' (idempotent) and forget it session-side. */
1043
+ static void scr_h2_stream_settle(ScrH2Stream *st) {
1044
+ if (st->close_queued || st->close_emitted) return;
1045
+ st->close_queued = true;
1046
+ st->state = SCR_H2_S_CLOSED;
1047
+ /* the compat pair settles with the stream: req 'aborted' fires NOW for
1048
+ * abnormal teardowns, res/req 'close' ride scr_http.c's emit queue
1049
+ * (res before req — the http/1 drop order), and BOTH refs release —
1050
+ * the break that unwinds the stream↔res ownership cycle */
1051
+ if (st->compat_req != NULL || st->compat_res != NULL) {
1052
+ ScrHttpReq *req = st->compat_req;
1053
+ ScrHttpRes *res = st->compat_res;
1054
+ st->compat_req = NULL;
1055
+ st->compat_res = NULL;
1056
+ if (req != NULL && st->aborted) scr_http_h2_req_aborted(req);
1057
+ if (res != NULL) scr_http_h2_res_close(res);
1058
+ if (req != NULL) scr_http_h2_req_close(req);
1059
+ scr_http_res_release(res);
1060
+ scr_http_req_release(req);
1061
+ }
1062
+ scr_h2_emit_push(SCR_H2_EMIT_STREAM_CLOSE, scr_http2_stream_retain(st));
1063
+ ScrH2Session *sess = st->sess;
1064
+ scr_h2_session_forget_stream(sess, st);
1065
+ scr_h2_session_maybe_finish_close(sess);
1066
+ }
1067
+
1068
+ /* ── data receive: paused-mode queue + flowing delivery ──────────────── */
1069
+
1070
+ static void scr_h2_stream_rq_push(ScrH2Stream *st, const char *data, size_t n) {
1071
+ ScrH2Chunk *c = malloc(sizeof *c + n);
1072
+ if (!c) scr_h2_oom();
1073
+ c->next = NULL;
1074
+ c->len = n;
1075
+ memcpy(c->data, data, n);
1076
+ if (st->rq_tail) st->rq_tail->next = c;
1077
+ else st->rq_head = c;
1078
+ st->rq_tail = c;
1079
+ }
1080
+
1081
+ static void scr_h2_stream_fire_data(ScrH2Stream *st, const char *data, size_t n) {
1082
+ if (st->data_ls.n == 0) return;
1083
+ ScrBytes *chunk = scr_bytes_new(SCR_BYTES_U8, (double)n);
1084
+ memcpy(chunk->data, data, n);
1085
+ ScrNetL *snap;
1086
+ size_t nl = scr_net_ls_snapshot(&st->data_ls, &snap);
1087
+ scr_dyn_this_push(st, SCR_DYNH_H2_STREAM);
1088
+ scr_dyn_chunk_enc(st->enc_utf8);
1089
+ for (size_t i = 0; i < nl; i++) {
1090
+ if (!scr_exc_pending()) ((ScrNetDataFn)snap[i].fn)(snap[i].cb, chunk);
1091
+ scr_closure_release(snap[i].cb);
1092
+ }
1093
+ scr_dyn_chunk_enc(false);
1094
+ scr_dyn_this_pop();
1095
+ free(snap);
1096
+ scr_bytes_release(chunk);
1097
+ }
1098
+
1099
+ static void scr_h2_stream_maybe_emit_end(ScrH2Stream *st) {
1100
+ if (st->end_emitted || !st->end_recv || st->rq_head != NULL) return;
1101
+ /* FLOWING is Node's consumption gate — except an EMPTY body, which has
1102
+ * nothing to consume: Node emits 'end' there without a reader. */
1103
+ if (!st->flowing && st->had_data) return;
1104
+ if (st->destroyed) return;
1105
+ st->end_emitted = true;
1106
+ scr_net_fire0_this(&st->end_ls, st, SCR_DYNH_H2_STREAM);
1107
+ if (scr_exc_pending()) return;
1108
+ /* fully done? both halves closed → settle */
1109
+ if (st->state == SCR_H2_S_CLOSED_REMOTE || st->state == SCR_H2_S_CLOSED) {
1110
+ /* our half must be done too before the close */
1111
+ if (st->want_end && st->wbuf.len == 0) scr_h2_stream_settle(st);
1112
+ else if (st->state == SCR_H2_S_CLOSED) scr_h2_stream_settle(st);
1113
+ }
1114
+ }
1115
+
1116
+ /* Drain the paused-mode queue into 'data' listeners (flowing only). */
1117
+ static void scr_h2_stream_drain(ScrH2Stream *st) {
1118
+ while (st->flowing && st->rq_head) {
1119
+ ScrH2Chunk *c = st->rq_head;
1120
+ st->rq_head = c->next;
1121
+ if (!st->rq_head) st->rq_tail = NULL;
1122
+ scr_h2_stream_fire_data(st, c->data, c->len);
1123
+ free(c);
1124
+ if (scr_exc_pending()) return;
1125
+ }
1126
+ scr_h2_stream_maybe_emit_end(st);
1127
+ }
1128
+
1129
+ static void scr_h2_stream_queue_drain(ScrH2Stream *st) {
1130
+ scr_h2_emit_push(SCR_H2_EMIT_STREAM_DRAIN, scr_http2_stream_retain(st));
1131
+ }
1132
+
1133
+ /* ── the sweep (deferred emits) ──────────────────────────────────────── */
1134
+
1135
+ static bool scr_h2_proto_pending(void) { return scr_h2_emits_head < scr_h2_emits_len; }
1136
+
1137
+ static void scr_h2_proto_sweep(void) {
1138
+ while (scr_h2_emits_head < scr_h2_emits_len) {
1139
+ ScrH2Emit e = scr_h2_emits[scr_h2_emits_head++];
1140
+ switch (e.kind) {
1141
+ case SCR_H2_EMIT_STREAM_CLOSE: {
1142
+ ScrH2Stream *st = (ScrH2Stream *)e.h;
1143
+ if (!st->close_emitted) {
1144
+ st->close_emitted = true;
1145
+ /* Nonzero codes error first — except NO_ERROR and CANCEL (Node's
1146
+ * rule on both the closing and the receiving side). */
1147
+ uint32_t code = st->rst_code;
1148
+ if (code != 0 && code != 8 && !scr_exc_pending()) {
1149
+ const char *name = scr_h2_code_name(code);
1150
+ char msg[80];
1151
+ int n = name != NULL
1152
+ ? snprintf(msg, sizeof msg, "Stream closed with error code %s", name)
1153
+ : snprintf(msg, sizeof msg, "Stream closed with error code %" PRIu32, code);
1154
+ ScrStr *m = scr_str_new(msg, (size_t)n);
1155
+ scr_net_fire_err_this(&st->err_ls, m, st, SCR_DYNH_H2_STREAM);
1156
+ scr_str_release(m);
1157
+ }
1158
+ if (!scr_exc_pending()) {
1159
+ scr_net_fire0_this(&st->close_ls, st, SCR_DYNH_H2_STREAM);
1160
+ }
1161
+ scr_h2_stream_drop_lists(st);
1162
+ }
1163
+ break;
1164
+ }
1165
+ case SCR_H2_EMIT_STREAM_DRAIN:
1166
+ scr_h2_stream_drain((ScrH2Stream *)e.h);
1167
+ break;
1168
+ case SCR_H2_EMIT_SESSION_CLOSE: {
1169
+ ScrH2Session *s = (ScrH2Session *)e.h;
1170
+ if (!s->close_emitted) {
1171
+ s->close_emitted = true;
1172
+ scr_net_fire0_this(&s->close_ls, s, SCR_DYNH_H2_SESSION);
1173
+ scr_h2_session_drop_lists(s);
1174
+ }
1175
+ break;
1176
+ }
1177
+ case SCR_H2_EMIT_SESSION_CONNECT: {
1178
+ ScrH2Session *s = (ScrH2Session *)e.h;
1179
+ if (!s->destroyed) {
1180
+ /* (session, socket) through the registered adapters — fire0's
1181
+ * zero-arg shape would starve the (session, socket) listeners. */
1182
+ ScrNetL *snap;
1183
+ size_t n = scr_net_ls_snapshot(&s->connect_ls, &snap);
1184
+ scr_dyn_this_push(s, SCR_DYNH_H2_SESSION);
1185
+ for (size_t i = 0; i < n; i++) {
1186
+ if (!scr_exc_pending()) {
1187
+ ScrNetSocket *sock = s->sock ? scr_net_sock_retain(s->sock) : NULL;
1188
+ ((ScrH2ConnectFn)snap[i].fn)(snap[i].cb, scr_http2_session_retain(s), sock);
1189
+ }
1190
+ scr_closure_release(snap[i].cb);
1191
+ }
1192
+ scr_dyn_this_pop();
1193
+ free(snap);
1194
+ scr_net_ls_drop(&s->connect_ls); /* fires once */
1195
+ }
1196
+ break;
1197
+ }
1198
+ case SCR_H2_EMIT_SERVER_SESSION: {
1199
+ ScrH2Session *s = (ScrH2Session *)e.h;
1200
+ if (!s->destroyed && s->srv) {
1201
+ ScrNetL *snap;
1202
+ size_t n = scr_net_ls_snapshot(&s->srv->session_ls, &snap);
1203
+ ScrNetServer *server = s->sock ? scr_net_sock_server(s->sock) : NULL;
1204
+ scr_dyn_this_push(server, SCR_DYNH_NET_SERVER);
1205
+ for (size_t i = 0; i < n; i++) {
1206
+ if (!scr_exc_pending()) {
1207
+ ((void (*)(ScrClosure *, ScrH2Session *))snap[i].fn)(snap[i].cb, scr_http2_session_retain(s));
1208
+ }
1209
+ scr_closure_release(snap[i].cb);
1210
+ }
1211
+ scr_dyn_this_pop();
1212
+ free(snap);
1213
+ }
1214
+ break;
1215
+ }
1216
+ }
1217
+ scr_h2_emit_release(&e);
1218
+ if (scr_exc_pending()) return;
1219
+ }
1220
+ scr_h2_emits_head = scr_h2_emits_len = 0;
1221
+ }
1222
+
1223
+ static void scr_h2_cleanup_atexit(void) {
1224
+ scr_h2_exiting = true;
1225
+ while (scr_h2_emits_head < scr_h2_emits_len) {
1226
+ ScrH2Emit e = scr_h2_emits[scr_h2_emits_head++];
1227
+ scr_h2_emit_release(&e);
1228
+ }
1229
+ free(scr_h2_emits);
1230
+ scr_h2_emits = NULL;
1231
+ scr_h2_emits_head = scr_h2_emits_len = scr_h2_emits_cap = 0;
1232
+ }
1233
+
1234
+ static const ScrHttpH2Ops scr_h2_compat_ops;
1235
+ static void scr_h2_request_hook(void *ctx, ScrClosure *cb, void *fn, bool once);
1236
+
1237
+ static void scr_http2_install(void) {
1238
+ static bool installed = false;
1239
+ if (installed) return;
1240
+ installed = true;
1241
+ atexit(scr_h2_cleanup_atexit);
1242
+ scr_net_set_proto_sweep(&scr_h2_proto_pending, &scr_h2_proto_sweep);
1243
+ /* the compat seam: scr_http.c routes h2-backed responses through the
1244
+ * ops vtable and h2-tagged 'request' registrations through the hook */
1245
+ scr_http_set_h2_ops(&scr_h2_compat_ops);
1246
+ scr_http_set_h2_request_hook(&scr_h2_request_hook);
1247
+ }
1248
+
1249
+ /* ── session lifecycle ───────────────────────────────────────────────── */
1250
+
1251
+ static void scr_h2_session_queue_close(ScrH2Session *s) {
1252
+ if (s->close_queued || s->close_emitted) return;
1253
+ s->close_queued = true;
1254
+ scr_h2_emit_push(SCR_H2_EMIT_SESSION_CLOSE, scr_http2_session_retain(s));
1255
+ }
1256
+
1257
+ /* A graceful close whose last stream just drained: end the write half —
1258
+ * the peer answers FIN, the socket closes, the session settles. */
1259
+ static void scr_h2_session_maybe_finish_close(ScrH2Session *s) {
1260
+ if (!s->closed || s->destroyed || s->nstreams > 0) return;
1261
+ if (scr_h2_sock_alive(s)) scr_net_sock_end(s->sock);
1262
+ }
1263
+
1264
+ static bool scr_h2_stream_local_closed(ScrH2Stream *st);
1265
+
1266
+ /* Settle every live stream at session teardown: a stream whose WRITABLE
1267
+ * side is still open aborted (Node's 'aborted' rule — an abnormal end
1268
+ * with the local half unfinished; a request/response that already sent
1269
+ * END_STREAM settles quietly). */
1270
+ static void scr_h2_session_abort_streams(ScrH2Session *s) {
1271
+ while (s->nstreams > 0) {
1272
+ ScrH2Stream *st = scr_http2_stream_retain(s->streams[0]);
1273
+ if (!scr_h2_stream_local_closed(st) && !st->want_end &&
1274
+ !st->rst_recv && !st->rst_sent && !st->destroyed) {
1275
+ st->aborted = true;
1276
+ scr_net_fire0_this(&st->aborted_ls, st, SCR_DYNH_H2_STREAM);
1277
+ }
1278
+ scr_h2_stream_settle(st); /* forgets it from the vector */
1279
+ scr_http2_stream_release(st);
1280
+ if (scr_exc_pending()) return;
1281
+ }
1282
+ }
1283
+
1284
+ void scr_http2_session_destroy(ScrH2Session *s) {
1285
+ if (s->destroyed) return;
1286
+ s->destroyed = true;
1287
+ scr_h2_session_abort_streams(s);
1288
+ scr_h2_session_queue_close(s);
1289
+ if (scr_h2_sock_alive(s)) scr_net_sock_destroy(s->sock);
1290
+ }
1291
+
1292
+ void scr_http2_session_close(ScrH2Session *s, ScrClosure *cb /*moves, nullable*/) {
1293
+ if (cb) scr_net_ls_add(&s->close_ls, cb, NULL, true);
1294
+ if (s->closed || s->destroyed) return;
1295
+ s->closed = true;
1296
+ scr_h2_send_goaway(s, 0 /* NGHTTP2_NO_ERROR */);
1297
+ scr_h2_session_maybe_finish_close(s);
1298
+ }
1299
+
1300
+ /* ── the socket-facing callbacks (native reader) ─────────────────────── */
1301
+
1302
+ static void scr_h2_dispatch_headers(ScrH2Session *s, int32_t stream_id, uint8_t flags,
1303
+ ScrH2Headers *h);
1304
+ static void scr_h2_stream_flush(ScrH2Stream *st);
1305
+ static void scr_h2_compat_dispatch(ScrH2Session *s, ScrH2Stream *st, const ScrH2Headers *h,
1306
+ bool end_stream);
1307
+
1308
+ /* The settings record the API observes (Node's key order; maxHeaderSize
1309
+ * aliases maxHeaderListSize; customSettings not modeled — empty). +1. */
1310
+ static ScrDyn *scr_h2_settings_dom(const ScrH2Settings *st) {
1311
+ ScrDyn *obj = scr_dyn_new_obj();
1312
+ scr_dyn_obj_set(obj, "headerTableSize", 15, scr_dyn_new_num((double)st->header_table_size));
1313
+ scr_dyn_obj_set(obj, "enablePush", 10, scr_dyn_new_bool(st->enable_push));
1314
+ scr_dyn_obj_set(obj, "initialWindowSize", 17, scr_dyn_new_num((double)st->initial_window_size));
1315
+ scr_dyn_obj_set(obj, "maxFrameSize", 12, scr_dyn_new_num((double)st->max_frame_size));
1316
+ scr_dyn_obj_set(obj, "maxConcurrentStreams", 20, scr_dyn_new_num((double)st->max_concurrent_streams));
1317
+ scr_dyn_obj_set(obj, "maxHeaderListSize", 17, scr_dyn_new_num((double)st->max_header_list_size));
1318
+ scr_dyn_obj_set(obj, "maxHeaderSize", 13, scr_dyn_new_num((double)st->max_header_list_size));
1319
+ scr_dyn_obj_set(obj, "enableConnectProtocol", 21, scr_dyn_new_bool(st->enable_connect_protocol));
1320
+ scr_dyn_obj_set(obj, "customSettings", 14, scr_dyn_new_obj());
1321
+ return obj;
1322
+ }
1323
+
1324
+ /* Fire a settings-payload list: each entry's fn is (cb, settings +1). */
1325
+ static void scr_h2_fire_settings(ScrNetLs *ls, ScrH2Session *s, const ScrH2Settings *st) {
1326
+ if (ls->n == 0) return;
1327
+ ScrNetL *snap;
1328
+ size_t n = scr_net_ls_snapshot(ls, &snap);
1329
+ scr_dyn_this_push(s, SCR_DYNH_H2_SESSION);
1330
+ for (size_t i = 0; i < n; i++) {
1331
+ if (!scr_exc_pending()) {
1332
+ ((ScrH2SettingsFn)snap[i].fn)(snap[i].cb, scr_h2_settings_dom(st));
1333
+ }
1334
+ scr_closure_release(snap[i].cb);
1335
+ }
1336
+ scr_dyn_this_pop();
1337
+ free(snap);
1338
+ }
1339
+
1340
+ /* One complete frame. Returns false when the session tore down. */
1341
+ static bool scr_h2_on_frame(ScrH2Session *s, uint8_t type, uint8_t flags, int32_t stream_id,
1342
+ const uint8_t *p, size_t n) {
1343
+ /* An open header block admits only its CONTINUATIONs. */
1344
+ if (s->hblock_active && type != SCR_H2_F_CONTINUATION) {
1345
+ scr_http2_session_destroy(s);
1346
+ return false;
1347
+ }
1348
+ switch (type) {
1349
+ case SCR_H2_F_HEADERS: {
1350
+ if (flags & SCR_H2_FLAG_PADDED) {
1351
+ if (n < 1) goto proto_err;
1352
+ uint8_t pad = p[0];
1353
+ if ((size_t)pad + 1 > n) goto proto_err;
1354
+ p += 1;
1355
+ n -= 1 + pad;
1356
+ }
1357
+ if (flags & SCR_H2_FLAG_PRIORITY) {
1358
+ if (n < 5) goto proto_err;
1359
+ p += 5;
1360
+ n -= 5;
1361
+ }
1362
+ s->hblock.len = 0;
1363
+ scr_h2_buf_put(&s->hblock, p, n);
1364
+ s->hblock_stream = stream_id;
1365
+ s->hblock_flags = flags;
1366
+ if (flags & SCR_H2_FLAG_END_HEADERS) {
1367
+ ScrH2Headers h = {0};
1368
+ bool ok = scr_h2_hpack_decode(&s->dec_table, (const uint8_t *)s->hblock.data, s->hblock.len, &h);
1369
+ s->hblock.len = 0;
1370
+ if (!ok) {
1371
+ scr_h2_headers_free(&h);
1372
+ goto proto_err;
1373
+ }
1374
+ scr_h2_dispatch_headers(s, stream_id, s->hblock_flags, &h);
1375
+ scr_h2_headers_free(&h);
1376
+ } else {
1377
+ s->hblock_active = true;
1378
+ }
1379
+ break;
1380
+ }
1381
+ case SCR_H2_F_CONTINUATION: {
1382
+ if (!s->hblock_active || stream_id != s->hblock_stream) goto proto_err;
1383
+ scr_h2_buf_put(&s->hblock, p, n);
1384
+ if (flags & SCR_H2_FLAG_END_HEADERS) {
1385
+ s->hblock_active = false;
1386
+ ScrH2Headers h = {0};
1387
+ bool ok = scr_h2_hpack_decode(&s->dec_table, (const uint8_t *)s->hblock.data, s->hblock.len, &h);
1388
+ s->hblock.len = 0;
1389
+ if (!ok) {
1390
+ scr_h2_headers_free(&h);
1391
+ goto proto_err;
1392
+ }
1393
+ scr_h2_dispatch_headers(s, stream_id, s->hblock_flags, &h);
1394
+ scr_h2_headers_free(&h);
1395
+ }
1396
+ break;
1397
+ }
1398
+ case SCR_H2_F_DATA: {
1399
+ if (flags & SCR_H2_FLAG_PADDED) {
1400
+ if (n < 1) goto proto_err;
1401
+ uint8_t pad = p[0];
1402
+ if ((size_t)pad + 1 > n) goto proto_err;
1403
+ p += 1;
1404
+ n -= 1 + pad;
1405
+ }
1406
+ /* eager replenish: the peer's view of both windows stays full */
1407
+ if (n > 0) {
1408
+ scr_h2_send_window_update(s, 0, (uint32_t)n);
1409
+ scr_h2_send_window_update(s, stream_id, (uint32_t)n);
1410
+ }
1411
+ ScrH2Stream *st = scr_h2_find_stream(s, stream_id);
1412
+ if (!st || st->destroyed) break; /* late DATA on a gone stream: drop */
1413
+ if (st->compat_req != NULL) {
1414
+ /* the compat req owns the READ side: DATA feeds req 'data',
1415
+ * END_STREAM fires req 'end' — the stream's own queue stays empty */
1416
+ if (n > 0) {
1417
+ scr_http_h2_req_data(st->compat_req, (const char *)p, n);
1418
+ if (scr_exc_pending()) return false;
1419
+ }
1420
+ if (flags & SCR_H2_FLAG_END_STREAM) {
1421
+ st->end_recv = true;
1422
+ st->end_emitted = true;
1423
+ st->state = st->state == SCR_H2_S_CLOSED_LOCAL ? SCR_H2_S_CLOSED : SCR_H2_S_CLOSED_REMOTE;
1424
+ scr_http_h2_req_end(st->compat_req);
1425
+ if (scr_exc_pending()) return false;
1426
+ if (st->state == SCR_H2_S_CLOSED) scr_h2_stream_settle(st);
1427
+ }
1428
+ break;
1429
+ }
1430
+ if (n > 0) {
1431
+ st->had_data = true;
1432
+ if (st->flowing && st->rq_head == NULL) {
1433
+ scr_h2_stream_fire_data(st, (const char *)p, n);
1434
+ if (scr_exc_pending()) return false;
1435
+ } else {
1436
+ scr_h2_stream_rq_push(st, (const char *)p, n);
1437
+ if (st->flowing) scr_h2_stream_queue_drain(st);
1438
+ }
1439
+ }
1440
+ if (flags & SCR_H2_FLAG_END_STREAM) {
1441
+ st->end_recv = true;
1442
+ st->state = st->state == SCR_H2_S_CLOSED_LOCAL ? SCR_H2_S_CLOSED : SCR_H2_S_CLOSED_REMOTE;
1443
+ scr_h2_stream_maybe_emit_end(st);
1444
+ if (scr_exc_pending()) return false;
1445
+ if (st->state == SCR_H2_S_CLOSED && st->end_emitted) scr_h2_stream_settle(st);
1446
+ }
1447
+ break;
1448
+ }
1449
+ case SCR_H2_F_SETTINGS: {
1450
+ if (flags & SCR_H2_FLAG_ACK) {
1451
+ s->pending_settings_ack = false;
1452
+ scr_h2_fire_settings(&s->local_settings_ls, s, &s->local_settings);
1453
+ if (scr_exc_pending()) return false;
1454
+ break;
1455
+ }
1456
+ for (size_t off = 0; off + 6 <= n; off += 6) {
1457
+ uint16_t id = (uint16_t)((p[off] << 8) | p[off + 1]);
1458
+ uint32_t v = ((uint32_t)p[off + 2] << 24) | ((uint32_t)p[off + 3] << 16) |
1459
+ ((uint32_t)p[off + 4] << 8) | p[off + 5];
1460
+ if (id == 0x4) {
1461
+ /* INITIAL_WINDOW_SIZE: adjust every open stream by the delta */
1462
+ int64_t delta = (int64_t)v - (int64_t)(s->peer_init_window ? s->peer_init_window : SCR_H2_DEFAULT_WINDOW);
1463
+ s->peer_init_window = v;
1464
+ s->remote_settings.initial_window_size = v;
1465
+ for (size_t i = 0; i < s->nstreams; i++) {
1466
+ s->streams[i]->send_window += delta;
1467
+ if (delta > 0) scr_h2_stream_flush(s->streams[i]);
1468
+ }
1469
+ } else if (id == 0x5) {
1470
+ s->peer_max_frame = v;
1471
+ s->remote_settings.max_frame_size = v;
1472
+ } else if (id == 0x1) {
1473
+ s->remote_settings.header_table_size = v;
1474
+ } else if (id == 0x2) {
1475
+ s->remote_settings.enable_push = v != 0;
1476
+ } else if (id == 0x3) {
1477
+ s->remote_settings.max_concurrent_streams = v;
1478
+ } else if (id == 0x6) {
1479
+ s->remote_settings.max_header_list_size = v;
1480
+ } else if (id == 0x8) {
1481
+ s->remote_settings.enable_connect_protocol = v != 0;
1482
+ }
1483
+ /* HEADER_TABLE_SIZE (0x1) governs OUR encoder's dynamic table — it
1484
+ * never indexes, so any ceiling is honored; the rest are recorded
1485
+ * for the remoteSettings read (MAX_CONCURRENT_STREAMS backpressure
1486
+ * is not modeled). */
1487
+ }
1488
+ scr_h2_send_settings_ack(s);
1489
+ scr_h2_fire_settings(&s->remote_settings_ls, s, &s->remote_settings);
1490
+ if (scr_exc_pending()) return false;
1491
+ break;
1492
+ }
1493
+ case SCR_H2_F_PING: {
1494
+ if (!(flags & SCR_H2_FLAG_ACK) && n == 8) {
1495
+ scr_h2_send_frame(s, SCR_H2_F_PING, SCR_H2_FLAG_ACK, 0, p, 8);
1496
+ }
1497
+ break;
1498
+ }
1499
+ case SCR_H2_F_WINDOW_UPDATE: {
1500
+ if (n != 4) goto proto_err;
1501
+ uint32_t inc = (((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]) & 0x7fffffff;
1502
+ if (stream_id == 0) {
1503
+ s->send_window += inc;
1504
+ for (size_t i = 0; i < s->nstreams && !scr_exc_pending(); i++) {
1505
+ scr_h2_stream_flush(s->streams[i]);
1506
+ }
1507
+ } else {
1508
+ ScrH2Stream *st = scr_h2_find_stream(s, stream_id);
1509
+ if (st) {
1510
+ st->send_window += inc;
1511
+ scr_h2_stream_flush(st);
1512
+ }
1513
+ }
1514
+ break;
1515
+ }
1516
+ case SCR_H2_F_RST_STREAM: {
1517
+ if (n != 4) goto proto_err;
1518
+ uint32_t code = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
1519
+ ScrH2Stream *st = scr_h2_find_stream(s, stream_id);
1520
+ if (st && !st->destroyed) {
1521
+ st->rst_recv = true;
1522
+ st->rst_code = code;
1523
+ st->destroyed = true;
1524
+ scr_h2_stream_settle(st);
1525
+ }
1526
+ break;
1527
+ }
1528
+ case SCR_H2_F_GOAWAY: {
1529
+ if (n < 8) goto proto_err;
1530
+ uint32_t code = ((uint32_t)p[4] << 24) | ((uint32_t)p[5] << 16) | ((uint32_t)p[6] << 8) | p[7];
1531
+ s->goaway_recv = true;
1532
+ s->goaway_code = code;
1533
+ {
1534
+ ScrNetL *snap;
1535
+ size_t nl = scr_net_ls_snapshot(&s->goaway_ls, &snap);
1536
+ int32_t last = (int32_t)((((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]) & 0x7fffffff);
1537
+ scr_dyn_this_push(s, SCR_DYNH_H2_SESSION);
1538
+ for (size_t i = 0; i < nl; i++) {
1539
+ if (!scr_exc_pending()) {
1540
+ ((void (*)(ScrClosure *, double, double))snap[i].fn)(snap[i].cb, (double)code, (double)last);
1541
+ }
1542
+ scr_closure_release(snap[i].cb);
1543
+ }
1544
+ scr_dyn_this_pop();
1545
+ free(snap);
1546
+ if (scr_exc_pending()) return false;
1547
+ }
1548
+ if (code != 0) {
1549
+ /* an error GOAWAY tears the session down NOW */
1550
+ scr_http2_session_destroy(s);
1551
+ return false;
1552
+ }
1553
+ /* NO_ERROR: no new streams; the session winds down as streams drain */
1554
+ s->closed = true;
1555
+ scr_h2_session_maybe_finish_close(s);
1556
+ break;
1557
+ }
1558
+ case SCR_H2_F_PUSH_PROMISE: {
1559
+ /* The promised header block still updates HPACK state (decode it),
1560
+ * then the promise is refused — push is not modeled yet. */
1561
+ if (flags & SCR_H2_FLAG_PADDED) {
1562
+ if (n < 1) goto proto_err;
1563
+ uint8_t pad = p[0];
1564
+ if ((size_t)pad + 1 > n) goto proto_err;
1565
+ p += 1;
1566
+ n -= 1 + pad;
1567
+ }
1568
+ if (n < 4) goto proto_err;
1569
+ int32_t promised = (int32_t)((((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]) & 0x7fffffff);
1570
+ /* M1 restriction: a promise split across CONTINUATION frames is not
1571
+ * reassembled — Node's promises fit one frame in practice. */
1572
+ if (!(flags & SCR_H2_FLAG_END_HEADERS)) goto proto_err;
1573
+ ScrH2Headers h = {0};
1574
+ bool ok = scr_h2_hpack_decode(&s->dec_table, p + 4, n - 4, &h);
1575
+ scr_h2_headers_free(&h);
1576
+ if (!ok) goto proto_err;
1577
+ scr_h2_send_rst(s, promised, 7 /* NGHTTP2_REFUSED_STREAM */);
1578
+ break;
1579
+ }
1580
+ default:
1581
+ break; /* PRIORITY, ALTSVC, ORIGIN, unknown: ignored */
1582
+ }
1583
+ return true;
1584
+ proto_err:
1585
+ scr_h2_send_goaway(s, 1 /* NGHTTP2_PROTOCOL_ERROR */);
1586
+ scr_http2_session_destroy(s);
1587
+ return false;
1588
+ }
1589
+
1590
+ static void scr_h2_on_data(void *ctx, const char *buf, size_t n) {
1591
+ ScrH2Session *s = ctx;
1592
+ if (s->destroyed) return;
1593
+ /* append to the reassembly buffer */
1594
+ if (s->rlen + n > s->rcap) {
1595
+ size_t ncap = s->rcap ? s->rcap : 4096;
1596
+ while (ncap < s->rlen + n) ncap *= 2;
1597
+ s->rbuf = realloc(s->rbuf, ncap);
1598
+ if (!s->rbuf) scr_h2_oom();
1599
+ s->rcap = ncap;
1600
+ }
1601
+ memcpy(s->rbuf + s->rlen, buf, n);
1602
+ s->rlen += n;
1603
+ size_t off = 0;
1604
+ scr_http2_session_retain(s); /* callbacks may drop every other ref */
1605
+ if (!s->client && !s->preface_done) {
1606
+ if (s->rlen - off < 24) goto done;
1607
+ if (memcmp(s->rbuf + off, SCR_H2_PREFACE, 24) != 0) {
1608
+ scr_http2_session_destroy(s);
1609
+ goto done;
1610
+ }
1611
+ s->preface_done = true;
1612
+ off += 24;
1613
+ }
1614
+ while (!s->destroyed && s->rlen - off >= 9) {
1615
+ const uint8_t *h = (const uint8_t *)s->rbuf + off;
1616
+ size_t len = ((size_t)h[0] << 16) | ((size_t)h[1] << 8) | h[2];
1617
+ if (s->rlen - off < 9 + len) break;
1618
+ uint8_t type = h[3];
1619
+ uint8_t flags = h[4];
1620
+ int32_t stream_id = (int32_t)((((uint32_t)h[5] << 24) | ((uint32_t)h[6] << 16) |
1621
+ ((uint32_t)h[7] << 8) | h[8]) & 0x7fffffff);
1622
+ if (stream_id > s->last_recv_stream_id &&
1623
+ (s->client ? (stream_id % 2 == 0) : (stream_id % 2 == 1))) {
1624
+ s->last_recv_stream_id = stream_id;
1625
+ }
1626
+ off += 9;
1627
+ if (!scr_h2_on_frame(s, type, flags, stream_id, (const uint8_t *)s->rbuf + off, len)) {
1628
+ off += len;
1629
+ break;
1630
+ }
1631
+ off += len;
1632
+ if (scr_exc_pending()) break;
1633
+ }
1634
+ done:
1635
+ if (off > 0 && off < s->rlen) memmove(s->rbuf, s->rbuf + off, s->rlen - off);
1636
+ s->rlen -= off < s->rlen ? off : s->rlen;
1637
+ scr_http2_session_release(s);
1638
+ }
1639
+
1640
+ static void scr_h2_on_eof(void *ctx) {
1641
+ ScrH2Session *s = ctx;
1642
+ if (s->destroyed) return;
1643
+ /* The peer is done sending. After a graceful GOAWAY exchange this is
1644
+ * the normal wind-down; streams still open aborted. */
1645
+ scr_h2_session_abort_streams(s);
1646
+ }
1647
+
1648
+ static void scr_h2_on_closed(void *ctx) {
1649
+ ScrH2Session *s = ctx;
1650
+ s->destroyed = true;
1651
+ scr_h2_session_abort_streams(s);
1652
+ scr_h2_session_queue_close(s);
1653
+ if (s->sock) {
1654
+ /* break the socket↔session cycle (the socket still owns the ctx) */
1655
+ scr_net_sock_release(s->sock);
1656
+ s->sock = NULL;
1657
+ }
1658
+ }
1659
+
1660
+ static bool scr_h2_on_err(void *ctx, ScrStr *msg) {
1661
+ ScrH2Session *s = ctx;
1662
+ if (s->err_ls.n == 0) return false; /* the socket's error story applies */
1663
+ scr_net_fire_err_this(&s->err_ls, msg, s, SCR_DYNH_H2_SESSION);
1664
+ return true;
1665
+ }
1666
+
1667
+ static void scr_h2_session_ctx_free(void *ctx) { scr_http2_session_release(ctx); }
1668
+
1669
+ static ScrH2Session *scr_h2_session_new(bool client) {
1670
+ scr_http2_install();
1671
+ ScrH2Session *s = calloc(1, sizeof *s);
1672
+ if (!s) scr_h2_oom();
1673
+ s->rc = 1;
1674
+ s->client = client;
1675
+ s->preface_done = client; /* clients never RECEIVE the magic */
1676
+ s->next_stream_id = 1;
1677
+ s->send_window = SCR_H2_DEFAULT_WINDOW;
1678
+ s->peer_max_frame = SCR_H2_DEFAULT_MAX_FRAME;
1679
+ s->peer_init_window = SCR_H2_DEFAULT_WINDOW;
1680
+ scr_h2_settings_defaults(&s->local_settings);
1681
+ scr_h2_settings_defaults(&s->remote_settings);
1682
+ if (client) s->local_settings.enable_push = false; /* Node pins client push off */
1683
+ scr_h2_dyntab_init(&s->dec_table);
1684
+ #ifdef SCR_RC_AUDIT
1685
+ scr_h2_sessions_live++;
1686
+ #endif
1687
+ return s;
1688
+ }
1689
+
1690
+ /* ── stream send path ────────────────────────────────────────────────── */
1691
+
1692
+ static bool scr_h2_stream_local_closed(ScrH2Stream *st) {
1693
+ return st->state == SCR_H2_S_CLOSED_LOCAL || st->state == SCR_H2_S_CLOSED;
1694
+ }
1695
+
1696
+ static void scr_h2_stream_mark_local_closed(ScrH2Stream *st) {
1697
+ st->state = st->state == SCR_H2_S_CLOSED_REMOTE || st->state == SCR_H2_S_CLOSED
1698
+ ? SCR_H2_S_CLOSED
1699
+ : SCR_H2_S_CLOSED_LOCAL;
1700
+ }
1701
+
1702
+ /* Flush window-permitting bytes; the END_STREAM rides the flush that
1703
+ * empties the buffer (an empty DATA frame when end() found it empty). */
1704
+ static void scr_h2_stream_flush(ScrH2Stream *st) {
1705
+ ScrH2Session *s = st->sess;
1706
+ if (st->destroyed || scr_h2_stream_local_closed(st) || !scr_h2_sock_alive(s)) return;
1707
+ size_t max_frame = s->peer_max_frame ? s->peer_max_frame : SCR_H2_DEFAULT_MAX_FRAME;
1708
+ size_t off = 0;
1709
+ while (off < st->wbuf.len) {
1710
+ int64_t window = st->send_window < s->send_window ? st->send_window : s->send_window;
1711
+ if (window <= 0) break;
1712
+ size_t n = st->wbuf.len - off;
1713
+ if (n > (size_t)window) n = (size_t)window;
1714
+ if (n > max_frame) n = max_frame;
1715
+ bool last = off + n == st->wbuf.len;
1716
+ uint8_t flags = last && st->want_end ? SCR_H2_FLAG_END_STREAM : 0;
1717
+ scr_h2_send_frame(s, SCR_H2_F_DATA, flags, st->id, st->wbuf.data + off, n);
1718
+ st->send_window -= (int64_t)n;
1719
+ s->send_window -= (int64_t)n;
1720
+ off += n;
1721
+ if (flags & SCR_H2_FLAG_END_STREAM) scr_h2_stream_mark_local_closed(st);
1722
+ }
1723
+ if (off > 0) {
1724
+ memmove(st->wbuf.data, st->wbuf.data + off, st->wbuf.len - off);
1725
+ st->wbuf.len -= off;
1726
+ }
1727
+ if (st->wbuf.len == 0 && st->want_end && !scr_h2_stream_local_closed(st)) {
1728
+ scr_h2_send_frame(s, SCR_H2_F_DATA, SCR_H2_FLAG_END_STREAM, st->id, NULL, 0);
1729
+ scr_h2_stream_mark_local_closed(st);
1730
+ }
1731
+ /* both halves done (and the read side fully delivered) → settle */
1732
+ if (st->state == SCR_H2_S_CLOSED && (st->end_emitted || !st->end_recv)) {
1733
+ scr_h2_stream_settle(st);
1734
+ }
1735
+ }
1736
+
1737
+ static void scr_h2_stream_send(ScrH2Stream *st, const char *data, size_t n) {
1738
+ if (st->destroyed || scr_h2_stream_local_closed(st)) return;
1739
+ scr_h2_buf_put(&st->wbuf, data, n);
1740
+ scr_h2_stream_flush(st);
1741
+ }
1742
+
1743
+ /* ── the outgoing header-list builder ────────────────────────────────── */
1744
+
1745
+ /* Encode one `name: value` with the NAME lowercased on the wire (h2
1746
+ * requires lowercase header names). */
1747
+ static void scr_h2_encode_header_lower(ScrH2Buf *b, const char *name, size_t name_len,
1748
+ const char *value, size_t value_len) {
1749
+ bool lower_ok = true;
1750
+ for (size_t k = 0; k < name_len && lower_ok; k++) {
1751
+ lower_ok = !(name[k] >= 'A' && name[k] <= 'Z');
1752
+ }
1753
+ if (lower_ok) {
1754
+ scr_h2_hpack_encode(b, name, name_len, value, value_len);
1755
+ return;
1756
+ }
1757
+ char *low = malloc(name_len);
1758
+ if (!low) scr_h2_oom();
1759
+ for (size_t k = 0; k < name_len; k++) {
1760
+ char c = name[k];
1761
+ low[k] = c >= 'A' && c <= 'Z' ? (char)(c + 32) : c;
1762
+ }
1763
+ scr_h2_hpack_encode(b, low, name_len, value, value_len);
1764
+ free(low);
1765
+ }
1766
+
1767
+ /* Append `name: value` unless the name is a pseudo-header (those are
1768
+ * placed by the caller in canonical positions). `pairs` is the lowered
1769
+ * flat [name, value, ...] ScrArr (borrowed, nullable). */
1770
+ static void scr_h2_encode_regulars(ScrH2Buf *b, ScrArr *pairs) {
1771
+ if (!pairs) return;
1772
+ size_t n = (size_t)scr_arr_len(pairs);
1773
+ for (size_t i = 0; i + 1 < n; i += 2) {
1774
+ ScrStr *name = scr_arr_get_ref(pairs, (double)i);
1775
+ ScrStr *value = scr_arr_get_ref(pairs, (double)(i + 1));
1776
+ if (name->len > 0 && name->data[0] != ':') {
1777
+ scr_h2_encode_header_lower(b, name->data, name->len, value->data, value->len);
1778
+ }
1779
+ scr_str_release(name);
1780
+ scr_str_release(value);
1781
+ }
1782
+ }
1783
+
1784
+ /* The implicit date header (Node's auto date on respond). */
1785
+ static void scr_h2_encode_date(ScrH2Buf *b) {
1786
+ char date[40];
1787
+ time_t now = time(NULL);
1788
+ struct tm g;
1789
+ #ifdef _WIN32
1790
+ gmtime_s(&g, &now);
1791
+ #else
1792
+ gmtime_r(&now, &g);
1793
+ #endif
1794
+ size_t n = strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S GMT", &g);
1795
+ scr_h2_hpack_encode(b, "date", 4, date, n);
1796
+ }
1797
+
1798
+ /* The pseudo-header value in `pairs`, +1 (NULL when absent). */
1799
+ static ScrStr *scr_h2_pairs_pseudo(ScrArr *pairs, const char *name) {
1800
+ if (!pairs) return NULL;
1801
+ size_t want = strlen(name);
1802
+ size_t n = (size_t)scr_arr_len(pairs);
1803
+ for (size_t i = 0; i + 1 < n; i += 2) {
1804
+ ScrStr *nm = scr_arr_get_ref(pairs, (double)i);
1805
+ bool hit = nm->len == want && memcmp(nm->data, name, want) == 0;
1806
+ scr_str_release(nm);
1807
+ if (hit) return scr_arr_get_ref(pairs, (double)(i + 1));
1808
+ }
1809
+ return NULL;
1810
+ }
1811
+
1812
+ /* ── stream API (respond / write / end / close / destroy / events) ───── */
1813
+
1814
+ void scr_http2_stream_respond(ScrH2Stream *st, ScrArr *pairs /*borrowed, nullable*/,
1815
+ bool end_stream) {
1816
+ ScrH2Session *s = st->sess;
1817
+ if (st->destroyed || st->respond_sent || !scr_h2_sock_alive(s)) return;
1818
+ st->respond_sent = true;
1819
+ ScrH2Buf block = {0};
1820
+ ScrStr *status = scr_h2_pairs_pseudo(pairs, ":status");
1821
+ scr_h2_hpack_encode(&block, ":status", 7, status ? status->data : "200", status ? status->len : 3);
1822
+ if (status) scr_str_release(status);
1823
+ scr_h2_encode_regulars(&block, pairs);
1824
+ /* Node's auto date header (last unless the caller provided one) */
1825
+ ScrStr *own_date = scr_h2_pairs_pseudo(pairs, "date");
1826
+ if (own_date) {
1827
+ scr_str_release(own_date);
1828
+ } else {
1829
+ scr_h2_encode_date(&block);
1830
+ }
1831
+ scr_h2_send_headers(s, st->id, &block, end_stream);
1832
+ free(block.data);
1833
+ if (end_stream) {
1834
+ scr_h2_stream_mark_local_closed(st);
1835
+ if (st->state == SCR_H2_S_CLOSED && (st->end_emitted || !st->end_recv)) {
1836
+ scr_h2_stream_settle(st);
1837
+ }
1838
+ }
1839
+ }
1840
+
1841
+ void scr_http2_stream_write_str(ScrH2Stream *st, ScrStr *data /*borrowed*/) {
1842
+ scr_h2_stream_send(st, data->data, data->len);
1843
+ }
1844
+
1845
+ void scr_http2_stream_write_bytes(ScrH2Stream *st, ScrBytes *data /*borrowed*/) {
1846
+ scr_h2_stream_send(st, (const char *)data->data, (size_t)data->len);
1847
+ }
1848
+
1849
+ void scr_http2_stream_end(ScrH2Stream *st) {
1850
+ if (st->destroyed || st->want_end || scr_h2_stream_local_closed(st)) return;
1851
+ st->want_end = true;
1852
+ scr_h2_stream_flush(st);
1853
+ }
1854
+
1855
+ void scr_http2_stream_end_str(ScrH2Stream *st, ScrStr *data /*borrowed*/) {
1856
+ if (st->destroyed || st->want_end || scr_h2_stream_local_closed(st)) return;
1857
+ scr_h2_buf_put(&st->wbuf, data->data, data->len);
1858
+ st->want_end = true;
1859
+ scr_h2_stream_flush(st);
1860
+ }
1861
+
1862
+ void scr_http2_stream_end_bytes(ScrH2Stream *st, ScrBytes *data /*borrowed*/) {
1863
+ if (st->destroyed || st->want_end || scr_h2_stream_local_closed(st)) return;
1864
+ scr_h2_buf_put(&st->wbuf, (const char *)data->data, (size_t)data->len);
1865
+ st->want_end = true;
1866
+ scr_h2_stream_flush(st);
1867
+ }
1868
+
1869
+ void scr_http2_stream_close(ScrH2Stream *st, double code, ScrClosure *cb /*moves, nullable*/) {
1870
+ if (cb) scr_net_ls_add(&st->close_ls, cb, NULL, true);
1871
+ if (st->destroyed || st->close_queued || st->close_emitted) return;
1872
+ uint32_t c = code >= 0 && code <= 0xffffffff ? (uint32_t)code : 0;
1873
+ st->rst_code = c;
1874
+ st->rst_sent = true;
1875
+ st->destroyed = true;
1876
+ if (st->state != SCR_H2_S_CLOSED && scr_h2_sock_alive(st->sess)) {
1877
+ scr_h2_send_rst(st->sess, st->id, c);
1878
+ }
1879
+ scr_h2_stream_settle(st);
1880
+ }
1881
+
1882
+ void scr_http2_stream_destroy(ScrH2Stream *st) {
1883
+ if (st->destroyed || st->close_emitted) return;
1884
+ st->rst_sent = true;
1885
+ st->destroyed = true;
1886
+ if (st->state != SCR_H2_S_CLOSED && scr_h2_sock_alive(st->sess)) {
1887
+ scr_h2_send_rst(st->sess, st->id, 0 /* NGHTTP2_NO_ERROR */);
1888
+ }
1889
+ scr_h2_stream_settle(st);
1890
+ }
1891
+
1892
+ void scr_http2_stream_set_encoding(ScrH2Stream *st, ScrStr *enc /*borrowed*/) {
1893
+ /* ascii/latin1 deliver through the same string window as utf8 (the
1894
+ * suite's bodies are ASCII; SEMANTICS.md notes the residue) */
1895
+ if ((enc->len == 4 && memcmp(enc->data, "utf8", 4) == 0) ||
1896
+ (enc->len == 5 && memcmp(enc->data, "utf-8", 5) == 0) ||
1897
+ (enc->len == 5 && memcmp(enc->data, "ascii", 5) == 0) ||
1898
+ (enc->len == 6 && memcmp(enc->data, "latin1", 6) == 0)) {
1899
+ st->enc_utf8 = true;
1900
+ return;
1901
+ }
1902
+ char msg[96];
1903
+ int n = snprintf(msg, sizeof msg, "Unknown encoding: %.*s", (int)(enc->len > 40 ? 40 : enc->len), enc->data);
1904
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_UNKNOWN_ENCODING");
1905
+ }
1906
+
1907
+ /* The chaining spelling (`client.request(h).setEncoding(enc)`): same
1908
+ * write, answers the receiver +1. */
1909
+ ScrH2Stream *scr_http2_stream_set_encoding_ret(ScrH2Stream *st, ScrStr *enc /*borrowed*/) {
1910
+ scr_http2_stream_set_encoding(st, enc);
1911
+ return scr_http2_stream_retain(st);
1912
+ }
1913
+
1914
+ void scr_http2_stream_resume(ScrH2Stream *st) {
1915
+ if (st->flowing) return;
1916
+ st->flowing = true;
1917
+ if (st->rq_head || (st->end_recv && !st->end_emitted)) scr_h2_stream_queue_drain(st);
1918
+ }
1919
+
1920
+ void scr_http2_stream_pause(ScrH2Stream *st) { st->flowing = false; }
1921
+
1922
+ void scr_http2_stream_on_data(ScrH2Stream *st, ScrClosure *cb /*moves*/, ScrNetDataFn fn, bool once) {
1923
+ if (st->close_emitted) {
1924
+ scr_closure_release(cb);
1925
+ return;
1926
+ }
1927
+ scr_net_ls_add(&st->data_ls, cb, (void *)fn, once);
1928
+ if (!st->flowing) {
1929
+ st->flowing = true;
1930
+ if (st->rq_head || (st->end_recv && !st->end_emitted)) scr_h2_stream_queue_drain(st);
1931
+ }
1932
+ }
1933
+
1934
+ /* Zero-payload stream events; 'end' arms the drain when it already
1935
+ * happened (a late listener still sees Node's ordering guarantees). */
1936
+ void scr_http2_stream_on_end(ScrH2Stream *st, ScrClosure *cb /*moves*/, bool once) {
1937
+ if (st->end_emitted || st->close_emitted) {
1938
+ scr_closure_release(cb);
1939
+ return;
1940
+ }
1941
+ scr_net_ls_add(&st->end_ls, cb, NULL, once);
1942
+ }
1943
+
1944
+ void scr_http2_stream_on_close(ScrH2Stream *st, ScrClosure *cb /*moves*/, bool once) {
1945
+ if (st->close_emitted) {
1946
+ scr_closure_release(cb);
1947
+ return;
1948
+ }
1949
+ scr_net_ls_add(&st->close_ls, cb, NULL, once);
1950
+ }
1951
+
1952
+ void scr_http2_stream_on_aborted(ScrH2Stream *st, ScrClosure *cb /*moves*/, bool once) {
1953
+ if (st->close_emitted) {
1954
+ scr_closure_release(cb);
1955
+ return;
1956
+ }
1957
+ scr_net_ls_add(&st->aborted_ls, cb, NULL, once);
1958
+ }
1959
+
1960
+ void scr_http2_stream_on_error(ScrH2Stream *st, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once) {
1961
+ if (st->close_emitted) {
1962
+ scr_closure_release(cb);
1963
+ return;
1964
+ }
1965
+ scr_net_ls_add(&st->err_ls, cb, (void *)fn, once);
1966
+ }
1967
+
1968
+ /* 'response' (client): the emitted adapter receives the raw payload
1969
+ * (pairs, status, flags) and builds the headers record program-side. */
1970
+ void scr_http2_stream_on_response(ScrH2Stream *st, ScrClosure *cb /*moves*/, ScrH2RespFn fn, bool once) {
1971
+ if (st->close_emitted) {
1972
+ scr_closure_release(cb);
1973
+ return;
1974
+ }
1975
+ scr_net_ls_add(&st->resp_ls, cb, (void *)fn, once);
1976
+ }
1977
+
1978
+ double scr_http2_stream_id(ScrH2Stream *st) { return (double)st->id; }
1979
+ double scr_http2_stream_rst_code(ScrH2Stream *st) { return (double)st->rst_code; }
1980
+ bool scr_http2_stream_destroyed(ScrH2Stream *st) { return st->destroyed; }
1981
+ bool scr_http2_stream_closed(ScrH2Stream *st) { return st->close_queued || st->close_emitted; }
1982
+ bool scr_http2_stream_aborted(ScrH2Stream *st) { return st->aborted; }
1983
+ bool scr_http2_stream_pending(ScrH2Stream *st) { return false; /* streams send at request() */ }
1984
+
1985
+ ScrH2Session *scr_http2_stream_session(ScrH2Stream *st) {
1986
+ return scr_http2_session_retain(st->sess);
1987
+ }
1988
+
1989
+ /* ── headers dispatch (both roles) ───────────────────────────────────── */
1990
+
1991
+ static ScrArr *scr_h2_headers_pairs(const ScrH2Headers *h, bool skip_status, double *status_out) {
1992
+ ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, h->n * 2);
1993
+ for (size_t i = 0; i < h->n; i++) {
1994
+ ScrStr *name = h->names[i];
1995
+ if (skip_status && name->len == 7 && memcmp(name->data, ":status", 7) == 0) {
1996
+ if (status_out) {
1997
+ double v = 0;
1998
+ for (size_t k = 0; k < h->values[i]->len; k++) {
1999
+ char c = h->values[i]->data[k];
2000
+ if (c < '0' || c > '9') break;
2001
+ v = v * 10 + (c - '0');
2002
+ }
2003
+ *status_out = v;
2004
+ }
2005
+ continue;
2006
+ }
2007
+ scr_arr_push_ref(pairs, scr_str_retain(name));
2008
+ scr_arr_push_ref(pairs, scr_str_retain(h->values[i]));
2009
+ }
2010
+ return pairs;
2011
+ }
2012
+
2013
+ /* Fire a 'stream'-shaped list: (stream +1, pairs +1, flags) per entry. */
2014
+ static void scr_h2_fire_stream_list(ScrNetLs *ls, ScrH2Stream *st, ScrArr *pairs /*borrowed*/,
2015
+ double flags, void *self, ScrDynHandleTag tag) {
2016
+ ScrNetL *snap;
2017
+ size_t n = scr_net_ls_snapshot(ls, &snap);
2018
+ scr_dyn_this_push(self, tag);
2019
+ for (size_t i = 0; i < n; i++) {
2020
+ if (!scr_exc_pending()) {
2021
+ ((ScrH2StreamFn)snap[i].fn)(snap[i].cb, scr_http2_stream_retain(st), scr_arr_retain(pairs), flags);
2022
+ }
2023
+ scr_closure_release(snap[i].cb);
2024
+ }
2025
+ scr_dyn_this_pop();
2026
+ free(snap);
2027
+ }
2028
+
2029
+ static void scr_h2_dispatch_headers(ScrH2Session *s, int32_t stream_id, uint8_t flags,
2030
+ ScrH2Headers *h) {
2031
+ bool end_stream = (flags & SCR_H2_FLAG_END_STREAM) != 0;
2032
+ double fl = (double)(flags & (SCR_H2_FLAG_END_STREAM | SCR_H2_FLAG_END_HEADERS));
2033
+ if (!s->client) {
2034
+ if (stream_id % 2 == 0) return; /* server streams are client-initiated (odd) */
2035
+ ScrH2Stream *st = scr_h2_find_stream(s, stream_id);
2036
+ if (st != NULL) return; /* trailers: not modeled yet */
2037
+ if (s->closed || s->goaway_sent) {
2038
+ scr_h2_send_rst(s, stream_id, 7 /* NGHTTP2_REFUSED_STREAM */);
2039
+ return;
2040
+ }
2041
+ st = scr_h2_stream_new(s, stream_id);
2042
+ if (end_stream) {
2043
+ st->end_recv = true;
2044
+ st->state = SCR_H2_S_CLOSED_REMOTE;
2045
+ }
2046
+ ScrArr *pairs = scr_h2_headers_pairs(h, false, NULL);
2047
+ ScrNetServer *server = s->sock ? scr_net_sock_server(s->sock) : NULL;
2048
+ if (s->srv) scr_h2_fire_stream_list(&s->srv->stream_ls, st, pairs, fl, server, SCR_DYNH_NET_SERVER);
2049
+ if (!scr_exc_pending()) {
2050
+ scr_h2_fire_stream_list(&s->stream_ls, st, pairs, fl, s, SCR_DYNH_H2_SESSION);
2051
+ }
2052
+ scr_arr_release(pairs);
2053
+ /* the compat layer: 'request' listeners get the (req, res) pair */
2054
+ if (!scr_exc_pending() && s->srv != NULL && s->srv->request_ls.n > 0 && !st->destroyed) {
2055
+ scr_h2_compat_dispatch(s, st, h, end_stream);
2056
+ }
2057
+ scr_http2_stream_release(st);
2058
+ return;
2059
+ }
2060
+ /* client: response headers on a stream this session opened */
2061
+ ScrH2Stream *st = scr_h2_find_stream(s, stream_id);
2062
+ if (!st || st->destroyed) return;
2063
+ if (end_stream) {
2064
+ st->end_recv = true;
2065
+ st->state = scr_h2_stream_local_closed(st) ? SCR_H2_S_CLOSED : SCR_H2_S_CLOSED_REMOTE;
2066
+ }
2067
+ double status = 0;
2068
+ ScrArr *pairs = scr_h2_headers_pairs(h, true, &status);
2069
+ ScrNetL *snap;
2070
+ size_t n = scr_net_ls_snapshot(&st->resp_ls, &snap);
2071
+ scr_dyn_this_push(st, SCR_DYNH_H2_STREAM);
2072
+ for (size_t i = 0; i < n; i++) {
2073
+ if (!scr_exc_pending()) {
2074
+ ((ScrH2RespFn)snap[i].fn)(snap[i].cb, scr_arr_retain(pairs), status, fl);
2075
+ }
2076
+ scr_closure_release(snap[i].cb);
2077
+ }
2078
+ scr_dyn_this_pop();
2079
+ free(snap);
2080
+ scr_arr_release(pairs);
2081
+ if (scr_exc_pending()) return;
2082
+ if (end_stream) {
2083
+ scr_h2_stream_maybe_emit_end(st);
2084
+ if (scr_exc_pending()) return;
2085
+ if (st->state == SCR_H2_S_CLOSED && st->end_emitted) scr_h2_stream_settle(st);
2086
+ }
2087
+ }
2088
+
2089
+ /* ── the compat layer (Http2ServerRequest/Http2ServerResponse) ─────────
2090
+ *
2091
+ * Node's http1-style API over h2 streams: the (req, res) pair ARE
2092
+ * scr_http.c's handles — the http/1 surface is the API template — and
2093
+ * this unit is the transport (HEADERS/DATA frames through the ops
2094
+ * vtable scr_http.c routes h2-backed responses through). A stream with
2095
+ * a compat req bound hands its READ side to the req (DATA frames feed
2096
+ * req 'data', END_STREAM fires req 'end'); settle releases the pair. */
2097
+
2098
+ static void scr_h2_compat_respond(void *p, double status, ScrStr *const *names,
2099
+ ScrStr *const *values, size_t nheaders, bool add_date) {
2100
+ ScrH2Stream *st = (ScrH2Stream *)p;
2101
+ ScrH2Session *s = st->sess;
2102
+ if (st->destroyed || st->respond_sent || !scr_h2_sock_alive(s)) return;
2103
+ st->respond_sent = true;
2104
+ ScrH2Buf block = {0};
2105
+ char stbuf[16];
2106
+ int stn = snprintf(stbuf, sizeof stbuf, "%d", (int)status);
2107
+ scr_h2_hpack_encode(&block, ":status", 7, stbuf, (size_t)stn);
2108
+ bool have_date = false;
2109
+ for (size_t i = 0; i < nheaders; i++) {
2110
+ const ScrStr *name = names[i];
2111
+ /* connection-specific headers never go on an h2 wire (Node's compat
2112
+ * drops them with a warning; the drop is the load-bearing half) */
2113
+ static const char *const conn_specific[] = {
2114
+ "connection", "keep-alive", "proxy-connection", "transfer-encoding",
2115
+ "upgrade", "http2-settings", NULL,
2116
+ };
2117
+ bool skip = false;
2118
+ for (size_t k = 0; conn_specific[k] != NULL && !skip; k++) {
2119
+ size_t cl = strlen(conn_specific[k]);
2120
+ if (name->len == cl) {
2121
+ bool eq = true;
2122
+ for (size_t j = 0; j < cl && eq; j++) {
2123
+ char c = name->data[j];
2124
+ if (c >= 'A' && c <= 'Z') c = (char)(c + 32);
2125
+ eq = c == conn_specific[k][j];
2126
+ }
2127
+ skip = eq;
2128
+ }
2129
+ }
2130
+ if (skip) continue;
2131
+ if (name->len == 4) {
2132
+ bool is_date = true;
2133
+ for (size_t j = 0; j < 4 && is_date; j++) {
2134
+ char c = name->data[j];
2135
+ if (c >= 'A' && c <= 'Z') c = (char)(c + 32);
2136
+ is_date = c == "date"[j];
2137
+ }
2138
+ if (is_date) have_date = true;
2139
+ }
2140
+ scr_h2_encode_header_lower(&block, name->data, name->len, values[i]->data, values[i]->len);
2141
+ }
2142
+ if (add_date && !have_date) scr_h2_encode_date(&block);
2143
+ scr_h2_send_headers(s, st->id, &block, false);
2144
+ free(block.data);
2145
+ }
2146
+
2147
+ static void scr_h2_compat_write(void *p, const char *data, size_t n) {
2148
+ scr_h2_stream_send((ScrH2Stream *)p, data, n);
2149
+ }
2150
+
2151
+ static void scr_h2_compat_end(void *p, const char *data, size_t n) {
2152
+ ScrH2Stream *st = (ScrH2Stream *)p;
2153
+ if (st->destroyed || st->want_end || scr_h2_stream_local_closed(st)) return;
2154
+ if (n > 0) scr_h2_buf_put(&st->wbuf, data, n);
2155
+ st->want_end = true;
2156
+ scr_h2_stream_flush(st);
2157
+ }
2158
+
2159
+ static void scr_h2_compat_destroy(void *p) { scr_http2_stream_destroy((ScrH2Stream *)p); }
2160
+
2161
+ static const ScrHttpH2Ops scr_h2_compat_ops = {
2162
+ &scr_http2_stream_retain_v,
2163
+ &scr_http2_stream_release_v,
2164
+ &scr_h2_compat_respond,
2165
+ &scr_h2_compat_write,
2166
+ &scr_h2_compat_end,
2167
+ &scr_h2_compat_destroy,
2168
+ };
2169
+
2170
+ /* server.on("request", ...) routed here by scr_http.c when the server's
2171
+ * ctx carries the h2 proto tag (the compat registration seam). */
2172
+ static void scr_h2_request_hook(void *ctx, ScrClosure *cb /*moves*/, void *fn, bool once) {
2173
+ ScrH2SrvCtx *srv = (ScrH2SrvCtx *)ctx;
2174
+ scr_net_ls_add(&srv->request_ls, cb, fn, once);
2175
+ }
2176
+
2177
+ /* Build the (req, res) pair over a fresh server stream and fire the
2178
+ * 'request' listeners (this = the net server, the http/1 shape). */
2179
+ static void scr_h2_compat_dispatch(ScrH2Session *s, ScrH2Stream *st, const ScrH2Headers *h,
2180
+ bool end_stream) {
2181
+ ScrHttpReq *req = scr_http_h2_req_new(s->sock, st);
2182
+ for (size_t i = 0; i < h->n; i++) {
2183
+ ScrStr *name = h->names[i];
2184
+ ScrStr *value = h->values[i];
2185
+ if (name->len == 7 && memcmp(name->data, ":method", 7) == 0) {
2186
+ scr_http_h2_req_line(req, value, NULL);
2187
+ } else if (name->len == 5 && memcmp(name->data, ":path", 5) == 0) {
2188
+ scr_http_h2_req_line(req, NULL, value);
2189
+ }
2190
+ /* EVERY pair — pseudo-headers included — lands in req.headers
2191
+ * (Node's compat shape: req.headers[':path'] answers). */
2192
+ scr_http_h2_req_header(req, name, value);
2193
+ }
2194
+ ScrHttpRes *res = scr_http_h2_res_new(s->sock, st);
2195
+ st->compat_req = scr_http_req_retain(req);
2196
+ st->compat_res = scr_http_res_retain(res);
2197
+ /* the req owns the READ side: the stream's own 'end' bookkeeping is
2198
+ * already satisfied (settle conditions see a delivered read side) */
2199
+ if (end_stream) st->end_emitted = true;
2200
+ ScrNetL *snap;
2201
+ size_t n = scr_net_ls_snapshot(&s->srv->request_ls, &snap);
2202
+ ScrNetServer *server = s->sock ? scr_net_sock_server(s->sock) : NULL;
2203
+ scr_dyn_this_push(server, SCR_DYNH_NET_SERVER);
2204
+ for (size_t i = 0; i < n; i++) {
2205
+ if (!scr_exc_pending()) {
2206
+ ((ScrHttpReqFn)snap[i].fn)(snap[i].cb, scr_http_req_retain(req), scr_http_res_retain(res));
2207
+ }
2208
+ scr_closure_release(snap[i].cb);
2209
+ }
2210
+ scr_dyn_this_pop();
2211
+ free(snap);
2212
+ /* a body-less request ends NOW — the handler's synchronous listeners
2213
+ * see 'end' right after it returns, the http/1 parser's order */
2214
+ if (!scr_exc_pending() && end_stream) scr_http_h2_req_end(req);
2215
+ scr_http_req_release(req);
2216
+ scr_http_res_release(res);
2217
+ }
2218
+
2219
+ /* ── the h2c server ──────────────────────────────────────────────────── */
2220
+
2221
+ static void scr_h2_srv_ctx_free_v(void *p) { scr_h2_srv_ctx_release((ScrH2SrvCtx *)p); }
2222
+
2223
+ static void scr_h2_srv_ctx_settle(void *p) {
2224
+ ScrH2SrvCtx *ctx = (ScrH2SrvCtx *)p;
2225
+ scr_net_ls_drop(&ctx->stream_ls);
2226
+ scr_net_ls_drop(&ctx->session_ls);
2227
+ scr_net_ls_drop(&ctx->request_ls);
2228
+ }
2229
+
2230
+ static void scr_h2_on_connection(void *ctx, ScrNetSocket *sock) {
2231
+ ScrH2SrvCtx *srv = (ScrH2SrvCtx *)ctx;
2232
+ ScrH2Session *sess = scr_h2_session_new(false);
2233
+ sess->srv = scr_h2_srv_ctx_retain(srv);
2234
+ sess->sock = scr_net_sock_retain(sock);
2235
+ sess->next_stream_id = 2; /* server-initiated ids are even (push, unused) */
2236
+ scr_net_sock_set_native_reader(sock, &scr_h2_on_data, &scr_h2_on_eof, &scr_h2_on_closed,
2237
+ sess, &scr_h2_session_ctx_free);
2238
+ scr_net_sock_set_native_events(sock, NULL, &scr_h2_on_err);
2239
+ scr_h2_send_settings(sess);
2240
+ scr_h2_emit_push(SCR_H2_EMIT_SERVER_SESSION, scr_http2_session_retain(sess));
2241
+ }
2242
+
2243
+ ScrNetServer *scr_http2_create_server(void) {
2244
+ scr_http2_install();
2245
+ ScrNetServer *s = scr_net_create_server(NULL, NULL);
2246
+ ScrH2SrvCtx *ctx = calloc(1, sizeof *ctx);
2247
+ if (!ctx) scr_h2_oom();
2248
+ ctx->proto = SCR_NET_PROTO_H2;
2249
+ ctx->rc = 1;
2250
+ scr_net_server_set_native_conn(s, &scr_h2_on_connection, ctx, &scr_h2_srv_ctx_free_v);
2251
+ scr_net_server_set_http_ctx(s, ctx);
2252
+ scr_net_server_set_proto_settle(s, &scr_h2_srv_ctx_settle);
2253
+ return s;
2254
+ }
2255
+
2256
+ /* http2.createServer(handler): the eager (req, res) handler is the first
2257
+ * compat 'request' listener — Node's exact route (server.on("request")
2258
+ * appends through the scr_http.c hook). */
2259
+ ScrNetServer *scr_http2_create_server_req(ScrClosure *handler /*moves*/, ScrHttpReqFn fn) {
2260
+ ScrNetServer *s = scr_http2_create_server();
2261
+ ScrH2SrvCtx *ctx = (ScrH2SrvCtx *)scr_net_server_get_http_ctx(s);
2262
+ if (ctx != NULL && ctx->proto == SCR_NET_PROTO_H2) {
2263
+ scr_net_ls_add(&ctx->request_ls, handler, (void *)fn, false);
2264
+ } else {
2265
+ scr_closure_release(handler);
2266
+ }
2267
+ return s;
2268
+ }
2269
+
2270
+ /* http2.createSecureServer({ cert, key }) — the REAL h2-over-TLS server:
2271
+ * the same h2c session machinery behind an mbedTLS handshake whose ALPN
2272
+ * advertises h2 alone. scr_tls.c owns the transport (the engine installs
2273
+ * per accepted socket) and calls back into scr_h2_on_connection at
2274
+ * establishment, so everything after the handshake is protocol-identical
2275
+ * to the h2c server — 'stream'/'session' listeners, multiplexing, flow
2276
+ * control, RST semantics. An http/1.1-only client fails the handshake
2277
+ * with no_application_protocol (Node's h2-only split); a no-ALPN client
2278
+ * tears down at establishment (Node parks it as 'unknownProtocol' and
2279
+ * destroys at the timeout — this surface destroys now). */
2280
+ ScrNetServer *scr_http2_create_secure_server(const char *cert, size_t cert_len,
2281
+ const char *key, size_t key_len) {
2282
+ scr_http2_install();
2283
+ ScrNetServer *s = scr_net_create_server(NULL, NULL);
2284
+ ScrH2SrvCtx *ctx = calloc(1, sizeof *ctx);
2285
+ if (!ctx) scr_h2_oom();
2286
+ ctx->proto = SCR_NET_PROTO_H2;
2287
+ ctx->rc = 1;
2288
+ scr_net_server_set_http_ctx(s, ctx); /* borrowed alias: 'stream'/'session' registration */
2289
+ scr_net_server_set_proto_settle(s, &scr_h2_srv_ctx_settle);
2290
+ /* the TLS wrap owns the ctx reference (+1 moved in) and dispatches
2291
+ * established h2 connections back into scr_h2_on_connection */
2292
+ scr_tls_server_wrap_h2(s, cert, cert_len, key, key_len, &scr_h2_on_connection, ctx,
2293
+ &scr_h2_srv_ctx_free_v);
2294
+ return s;
2295
+ }
2296
+
2297
+ /* The h2 ctx behind a net server handle; NULL when the server is not an
2298
+ * h2c server (the proto tag keeps scr_http.c's parser family apart). */
2299
+ static ScrH2SrvCtx *scr_h2_server_ctx(ScrNetServer *s) {
2300
+ ScrH2SrvCtx *ctx = (ScrH2SrvCtx *)scr_net_server_get_http_ctx(s);
2301
+ if (ctx == NULL || ctx->proto != SCR_NET_PROTO_H2) return NULL;
2302
+ return ctx;
2303
+ }
2304
+
2305
+ void scr_http2_server_on_stream(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH2StreamFn fn,
2306
+ bool once) {
2307
+ ScrH2SrvCtx *ctx = scr_h2_server_ctx(s);
2308
+ if (ctx == NULL || scr_net_server_settled(s)) {
2309
+ scr_closure_release(cb);
2310
+ return;
2311
+ }
2312
+ scr_net_ls_add(&ctx->stream_ls, cb, (void *)fn, once);
2313
+ }
2314
+
2315
+ void scr_http2_server_on_session(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH2SessionFn fn,
2316
+ bool once) {
2317
+ ScrH2SrvCtx *ctx = scr_h2_server_ctx(s);
2318
+ if (ctx == NULL || scr_net_server_settled(s)) {
2319
+ scr_closure_release(cb);
2320
+ return;
2321
+ }
2322
+ scr_net_ls_add(&ctx->session_ls, cb, (void *)fn, once);
2323
+ }
2324
+
2325
+ /* ── the h2c client (http2.connect) ──────────────────────────────────── */
2326
+
2327
+ static void scr_h2_on_established(void *ctx) {
2328
+ ScrH2Session *s = ctx;
2329
+ if (!s->destroyed && !s->connect_queued) {
2330
+ s->connect_queued = true;
2331
+ scr_h2_emit_push(SCR_H2_EMIT_SESSION_CONNECT, scr_http2_session_retain(s));
2332
+ }
2333
+ }
2334
+
2335
+ /* http2.connect(authority[, listener]): an http authority dials h2c
2336
+ * prior knowledge — the preface and SETTINGS go out immediately (the
2337
+ * socket buffers until the dial lands). An httpS authority dials the
2338
+ * TLS client transport with ALPN ["h2"] (scr_tls.c — linked whenever
2339
+ * this unit is): the same preface/SETTINGS buffer through the handshake
2340
+ * and everything after establishment is protocol-identical. `ca`
2341
+ * replaces the client trust anchors (len 0 = none) and
2342
+ * reject_unauthorized false disables the verify gate — the local-CA
2343
+ * self-connect shapes. Throws catchably on unparsable input. */
2344
+ ScrH2Session *scr_http2_connect(ScrStr *url /*borrowed*/, bool reject_unauthorized,
2345
+ const char *ca /*borrowed, len 0 = none*/, size_t ca_len,
2346
+ ScrClosure *cb /*moves, nullable*/, ScrH2ConnectFn fn) {
2347
+ const char *p = url->data;
2348
+ const char *sep = strstr(p, "://");
2349
+ if (sep == NULL || url->len == 0) {
2350
+ if (cb) scr_closure_release(cb);
2351
+ char msg[128];
2352
+ int n = snprintf(msg, sizeof msg, "Invalid URL: %.*s", (int)(url->len > 80 ? 80 : url->len), p);
2353
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_URL");
2354
+ return NULL;
2355
+ }
2356
+ size_t scheme_len = (size_t)(sep - p);
2357
+ bool secure = scheme_len == 5 && memcmp(p, "https", 5) == 0;
2358
+ if (!secure && !(scheme_len == 4 && memcmp(p, "http", 4) == 0)) {
2359
+ if (cb) scr_closure_release(cb);
2360
+ char msg[128];
2361
+ int n = snprintf(msg, sizeof msg, "Protocol \"%.*s:\" not supported. Expected \"http:\"",
2362
+ (int)(scheme_len > 40 ? 40 : scheme_len), p);
2363
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_PROTOCOL");
2364
+ return NULL;
2365
+ }
2366
+ const char *host = sep + 3;
2367
+ const char *end = p + url->len;
2368
+ const char *host_end = host;
2369
+ while (host_end < end && *host_end != ':' && *host_end != '/') host_end++;
2370
+ double default_port = secure ? 443 : 80;
2371
+ double port = default_port;
2372
+ if (host_end < end && *host_end == ':') {
2373
+ port = 0;
2374
+ for (const char *q = host_end + 1; q < end && *q >= '0' && *q <= '9'; q++) {
2375
+ port = port * 10 + (*q - '0');
2376
+ }
2377
+ }
2378
+ ScrH2Session *s = scr_h2_session_new(true);
2379
+ /* :authority — host[:port], the port elided when it is the default */
2380
+ size_t hlen = (size_t)(host_end - host);
2381
+ ScrH2Buf auth = {0};
2382
+ scr_h2_buf_put(&auth, host, hlen);
2383
+ if (port != default_port) {
2384
+ char pbuf[16];
2385
+ int n = snprintf(pbuf, sizeof pbuf, ":%d", (int)port);
2386
+ scr_h2_buf_put(&auth, pbuf, (size_t)n);
2387
+ }
2388
+ scr_h2_buf_u8(&auth, 0); /* NUL-terminate; len tracks the text */
2389
+ auth.len--;
2390
+ /* stash the authority in the session's hblock buf slot? No — its own
2391
+ * field: reuse rbuf? Neither: a dedicated malloc. */
2392
+ ScrStr *host_str = scr_str_new(host, hlen);
2393
+ ScrNetSocket *sock = scr_net_connect(port, host_str, NULL);
2394
+ if (secure) scr_tls_h2_client_wrap(sock, host_str, reject_unauthorized, ca, ca_len);
2395
+ scr_str_release(host_str);
2396
+ s->sock = sock; /* scr_net_connect returned +1 — the session takes it */
2397
+ s->authority = auth.data;
2398
+ s->authority_len = auth.len;
2399
+ if (cb) scr_net_ls_add(&s->connect_ls, cb, (void *)fn, true);
2400
+ scr_net_sock_set_native_reader(sock, &scr_h2_on_data, &scr_h2_on_eof, &scr_h2_on_closed,
2401
+ s, &scr_h2_session_ctx_free);
2402
+ scr_net_sock_set_native_events(sock, NULL, &scr_h2_on_err);
2403
+ scr_net_sock_set_native_established(sock, &scr_h2_on_established);
2404
+ scr_net_sock_write_native(sock, SCR_H2_PREFACE, 24);
2405
+ scr_h2_send_settings(s);
2406
+ /* TWO owners now: the socket's native ctx and the returned handle. */
2407
+ return scr_http2_session_retain(s);
2408
+ }
2409
+
2410
+ /* client.request(headers?, { endStream? }): sends HEADERS now (requests
2411
+ * before the dial lands ride the socket's write buffer). end_stream:
2412
+ * -1 = default (END_STREAM iff the method carries no payload), 0/1
2413
+ * explicit. Returns the stream +1. */
2414
+ ScrH2Stream *scr_http2_session_request(ScrH2Session *s, ScrArr *pairs /*borrowed, nullable*/,
2415
+ double end_stream) {
2416
+ if (s->destroyed || s->closed || !s->client) {
2417
+ scr_throw_error_msg_code(SCR_ERR_ERROR, "New streams cannot be created after the session has been destroyed",
2418
+ 63, "ERR_HTTP2_INVALID_SESSION");
2419
+ return NULL;
2420
+ }
2421
+ ScrH2Stream *st = scr_h2_stream_new(s, s->next_stream_id);
2422
+ s->next_stream_id += 2;
2423
+ ScrStr *method = scr_h2_pairs_pseudo(pairs, ":method");
2424
+ ScrStr *path = scr_h2_pairs_pseudo(pairs, ":path");
2425
+ ScrStr *scheme = scr_h2_pairs_pseudo(pairs, ":scheme");
2426
+ ScrStr *authority = scr_h2_pairs_pseudo(pairs, ":authority");
2427
+ ScrH2Buf block = {0};
2428
+ scr_h2_hpack_encode(&block, ":method", 7, method ? method->data : "GET", method ? method->len : 3);
2429
+ if (authority) {
2430
+ scr_h2_hpack_encode(&block, ":authority", 10, authority->data, authority->len);
2431
+ } else {
2432
+ scr_h2_hpack_encode(&block, ":authority", 10, s->authority ? s->authority : "", s->authority_len);
2433
+ }
2434
+ /* the default :scheme follows the transport — https over TLS sessions
2435
+ * (Node derives it from the connect authority the same way) */
2436
+ const char *dscheme = s->sock != NULL && scr_net_sock_encrypted(s->sock) ? "https" : "http";
2437
+ scr_h2_hpack_encode(&block, ":scheme", 7, scheme ? scheme->data : dscheme,
2438
+ scheme ? scheme->len : strlen(dscheme));
2439
+ scr_h2_hpack_encode(&block, ":path", 5, path ? path->data : "/", path ? path->len : 1);
2440
+ scr_h2_encode_regulars(&block, pairs);
2441
+ bool end;
2442
+ if (end_stream < 0) {
2443
+ /* Node's payload-meaningless default: GET/HEAD/DELETE end at HEADERS */
2444
+ end = method == NULL ||
2445
+ (method->len == 3 && memcmp(method->data, "GET", 3) == 0) ||
2446
+ (method->len == 4 && memcmp(method->data, "HEAD", 4) == 0) ||
2447
+ (method->len == 6 && memcmp(method->data, "DELETE", 6) == 0);
2448
+ } else {
2449
+ end = end_stream > 0;
2450
+ }
2451
+ if (method) scr_str_release(method);
2452
+ if (path) scr_str_release(path);
2453
+ if (scheme) scr_str_release(scheme);
2454
+ if (authority) scr_str_release(authority);
2455
+ scr_h2_send_headers(s, st->id, &block, end);
2456
+ free(block.data);
2457
+ if (end) scr_h2_stream_mark_local_closed(st);
2458
+ return st; /* the create ref moves out; the session's vector holds its own */
2459
+ }
2460
+
2461
+ /* ── session events + props ──────────────────────────────────────────── */
2462
+
2463
+ void scr_http2_session_on_close(ScrH2Session *s, ScrClosure *cb /*moves*/, bool once) {
2464
+ if (s->close_emitted) {
2465
+ scr_closure_release(cb);
2466
+ return;
2467
+ }
2468
+ scr_net_ls_add(&s->close_ls, cb, NULL, once);
2469
+ }
2470
+
2471
+ void scr_http2_session_on_error(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once) {
2472
+ if (s->close_emitted) {
2473
+ scr_closure_release(cb);
2474
+ return;
2475
+ }
2476
+ scr_net_ls_add(&s->err_ls, cb, (void *)fn, once);
2477
+ }
2478
+
2479
+ void scr_http2_session_on_connect(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2ConnectFn fn, bool once) {
2480
+ if (s->close_emitted) {
2481
+ scr_closure_release(cb);
2482
+ return;
2483
+ }
2484
+ scr_net_ls_add(&s->connect_ls, cb, (void *)fn, once);
2485
+ }
2486
+
2487
+ void scr_http2_session_on_stream(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2StreamFn fn, bool once) {
2488
+ if (s->close_emitted) {
2489
+ scr_closure_release(cb);
2490
+ return;
2491
+ }
2492
+ scr_net_ls_add(&s->stream_ls, cb, (void *)fn, once);
2493
+ }
2494
+
2495
+ void scr_http2_session_on_goaway(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2GoawayFn fn, bool once) {
2496
+ if (s->close_emitted) {
2497
+ scr_closure_release(cb);
2498
+ return;
2499
+ }
2500
+ scr_net_ls_add(&s->goaway_ls, cb, (void *)fn, once);
2501
+ }
2502
+
2503
+ /* ── the settings surface ────────────────────────────────────────────── */
2504
+
2505
+ /* session.settings(settings[, cb]): the recognized keys update the local
2506
+ * record and go on a SETTINGS frame NOW; the callback (if any) fires on
2507
+ * the peer's ACK alongside the 'localSettings' listeners. Values are
2508
+ * applied as given (Node's ERR_HTTP2_INVALID_SETTING_VALUE validation is
2509
+ * not modeled — SEMANTICS.md). */
2510
+ void scr_http2_session_settings(ScrH2Session *s, const ScrDyn *settings /*borrowed, nullable*/,
2511
+ ScrClosure *cb /*moves, nullable*/, ScrH2SettingsFn cbfn) {
2512
+ if (cb != NULL) scr_net_ls_add(&s->local_settings_ls, cb, (void *)cbfn, true);
2513
+ uint8_t payload[7 * 6];
2514
+ size_t n = 0;
2515
+ if (settings != NULL && settings->kind == SCR_DYN_OBJ) {
2516
+ static const struct { const char *key; uint16_t id; bool boolean; } keys[] = {
2517
+ { "headerTableSize", 0x1, false },
2518
+ { "enablePush", 0x2, true },
2519
+ { "maxConcurrentStreams", 0x3, false },
2520
+ { "initialWindowSize", 0x4, false },
2521
+ { "maxFrameSize", 0x5, false },
2522
+ { "maxHeaderListSize", 0x6, false },
2523
+ { "enableConnectProtocol", 0x8, true },
2524
+ };
2525
+ for (size_t k = 0; k < sizeof keys / sizeof *keys; k++) {
2526
+ const ScrDyn *v = scr_dyn_obj_get(settings, keys[k].key, strlen(keys[k].key));
2527
+ if (v == NULL) continue;
2528
+ uint32_t value;
2529
+ if (keys[k].boolean) {
2530
+ value = scr_dyn_truthy(v) ? 1 : 0;
2531
+ } else if (v->kind == SCR_DYN_NUM) {
2532
+ value = v->v.num < 0 ? 0 : v->v.num > 4294967295.0 ? 0xffffffffu : (uint32_t)v->v.num;
2533
+ } else {
2534
+ continue;
2535
+ }
2536
+ switch (keys[k].id) {
2537
+ case 0x1: s->local_settings.header_table_size = value; break;
2538
+ case 0x2: s->local_settings.enable_push = value != 0; break;
2539
+ case 0x3: s->local_settings.max_concurrent_streams = value; break;
2540
+ case 0x4: s->local_settings.initial_window_size = value; break;
2541
+ case 0x5: s->local_settings.max_frame_size = value; break;
2542
+ case 0x6: s->local_settings.max_header_list_size = value; break;
2543
+ case 0x8: s->local_settings.enable_connect_protocol = value != 0; break;
2544
+ }
2545
+ payload[n] = (uint8_t)(keys[k].id >> 8);
2546
+ payload[n + 1] = (uint8_t)keys[k].id;
2547
+ payload[n + 2] = (uint8_t)(value >> 24);
2548
+ payload[n + 3] = (uint8_t)(value >> 16);
2549
+ payload[n + 4] = (uint8_t)(value >> 8);
2550
+ payload[n + 5] = (uint8_t)value;
2551
+ n += 6;
2552
+ }
2553
+ }
2554
+ s->pending_settings_ack = true;
2555
+ scr_h2_send_frame(s, SCR_H2_F_SETTINGS, 0, 0, payload, n);
2556
+ }
2557
+
2558
+ void scr_http2_session_on_settings(ScrH2Session *s, ScrClosure *cb /*moves*/, ScrH2SettingsFn fn,
2559
+ bool once, bool local) {
2560
+ if (s->close_emitted) {
2561
+ scr_closure_release(cb);
2562
+ return;
2563
+ }
2564
+ scr_net_ls_add(local ? &s->local_settings_ls : &s->remote_settings_ls, cb, (void *)fn, once);
2565
+ }
2566
+
2567
+ ScrDyn *scr_http2_session_settings_get(ScrH2Session *s, bool local) {
2568
+ return scr_h2_settings_dom(local ? &s->local_settings : &s->remote_settings);
2569
+ }
2570
+
2571
+ bool scr_http2_session_pending_settings_ack(ScrH2Session *s) { return s->pending_settings_ack; }
2572
+
2573
+ /* http2.getDefaultSettings() — the constant defaults record. +1. */
2574
+ ScrDyn *scr_http2_get_default_settings(void) {
2575
+ ScrH2Settings d;
2576
+ scr_h2_settings_defaults(&d);
2577
+ return scr_h2_settings_dom(&d);
2578
+ }
2579
+
2580
+ /* The fixed passthrough for emitted settings adapters, and the zero-arg
2581
+ * flavor session.settings(_, cb) callbacks ride. */
2582
+ void scr_http2_settings_thunk(ScrClosure *cb, ScrDyn *settings) {
2583
+ ((void (*)(ScrClosure *, ScrDyn *))cb->fn)(cb, settings);
2584
+ }
2585
+
2586
+ void scr_http2_settings_thunk0(ScrClosure *cb, ScrDyn *settings) {
2587
+ scr_dyn_release(settings);
2588
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2589
+ }
2590
+
2591
+ /* The typed lane's DYN spellings (session receivers with mustCall-style
2592
+ * callbacks): the raw DOM function crosses the lib boundary and fires
2593
+ * through the same runtime-built listener closures the dyn handle ops
2594
+ * use — Node's exact payload shapes. */
2595
+ static void scr_h2_dyn_fire_settings(ScrClosure *cb, ScrDyn *settings /*+1*/);
2596
+ static void scr_h2_dyn_fire_settings_cb(ScrClosure *cb, ScrDyn *settings /*+1*/);
2597
+
2598
+ void scr_http2_session_settings_dyncb(ScrH2Session *s, const ScrDyn *settings /*borrowed*/,
2599
+ const ScrDyn *cb /*borrowed*/) {
2600
+ scr_dyn_check_listener(cb, "callback");
2601
+ if (scr_exc_pending()) return;
2602
+ scr_http2_session_settings(s, settings,
2603
+ scr_dyn_listener_closure_fn(cb, (void *)&scr_h2_dyn_fire_settings_cb),
2604
+ (ScrH2SettingsFn)&scr_h2_dyn_fire_settings_cb);
2605
+ }
2606
+
2607
+ void scr_http2_session_on_settings_dyn(ScrH2Session *s, const ScrDyn *cb /*borrowed*/,
2608
+ bool once, bool local) {
2609
+ scr_dyn_check_listener(cb, "listener");
2610
+ if (scr_exc_pending()) return;
2611
+ scr_http2_session_on_settings(s, scr_dyn_listener_closure_fn(cb, (void *)&scr_h2_dyn_fire_settings),
2612
+ (ScrH2SettingsFn)&scr_h2_dyn_fire_settings, once, local);
2613
+ }
2614
+
2615
+ bool scr_http2_session_closed(ScrH2Session *s) { return s->closed; }
2616
+ bool scr_http2_session_destroyed(ScrH2Session *s) { return s->destroyed; }
2617
+ bool scr_http2_session_encrypted(ScrH2Session *s) {
2618
+ return s->sock != NULL && scr_net_sock_encrypted(s->sock); /* true on the TLS arm */
2619
+ }
2620
+ double scr_http2_session_type(ScrH2Session *s) { return s->client ? 1 : 0; }
2621
+
2622
+ ScrStr *scr_http2_session_alpn(ScrH2Session *s) {
2623
+ /* TLS sessions negotiated h2; cleartext sessions answer Node's h2c */
2624
+ return scr_http2_session_encrypted(s) ? scr_str_new("h2", 2) : scr_str_new("h2c", 3);
2625
+ }
2626
+
2627
+ ScrNetSocket *scr_http2_session_socket(ScrH2Session *s) {
2628
+ return s->sock ? scr_net_sock_retain(s->sock) : NULL;
2629
+ }
2630
+
2631
+ /* ── the emitted-adapter thunks ──────────────────────────────────────── */
2632
+
2633
+ /* 'stream'/'session'-shaped payloads pass through whole; the EMITTED
2634
+ * adapter closure owns arity (it releases what it drops) and builds the
2635
+ * headers record from the pairs. */
2636
+ void scr_http2_stream_thunk(ScrClosure *cb, ScrH2Stream *st, ScrArr *pairs, double flags) {
2637
+ ((void (*)(ScrClosure *, ScrH2Stream *, ScrArr *, double))cb->fn)(cb, st, pairs, flags);
2638
+ }
2639
+
2640
+ void scr_http2_resp_thunk(ScrClosure *cb, ScrArr *pairs, double status, double flags) {
2641
+ ((void (*)(ScrClosure *, ScrArr *, double, double))cb->fn)(cb, pairs, status, flags);
2642
+ }
2643
+
2644
+ void scr_http2_session_thunk(ScrClosure *cb, ScrH2Session *sess) {
2645
+ ((void (*)(ScrClosure *, ScrH2Session *))cb->fn)(cb, sess);
2646
+ }
2647
+
2648
+ void scr_http2_session_thunk0(ScrClosure *cb, ScrH2Session *sess) {
2649
+ scr_http2_session_release(sess);
2650
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2651
+ }
2652
+
2653
+ /* The 'connect' adapters: (session, socket), and every shorter prefix.
2654
+ * Both arrive +1 (socket may be NULL after teardown); unused ones
2655
+ * release here. */
2656
+ void scr_http2_connect_thunk2(ScrClosure *cb, ScrH2Session *sess, ScrNetSocket *sock) {
2657
+ ((void (*)(ScrClosure *, ScrH2Session *, ScrNetSocket *))cb->fn)(cb, sess, sock);
2658
+ }
2659
+
2660
+ void scr_http2_connect_thunk1(ScrClosure *cb, ScrH2Session *sess, ScrNetSocket *sock) {
2661
+ if (sock) scr_net_sock_release(sock);
2662
+ ((void (*)(ScrClosure *, ScrH2Session *))cb->fn)(cb, sess);
2663
+ }
2664
+
2665
+ void scr_http2_connect_thunk0(ScrClosure *cb, ScrH2Session *sess, ScrNetSocket *sock) {
2666
+ if (sock) scr_net_sock_release(sock);
2667
+ scr_http2_session_release(sess);
2668
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2669
+ }
2670
+
2671
+ void scr_http2_goaway_thunk2(ScrClosure *cb, double code, double last) {
2672
+ ((void (*)(ScrClosure *, double, double))cb->fn)(cb, code, last);
2673
+ }
2674
+
2675
+ void scr_http2_goaway_thunk1(ScrClosure *cb, double code, double last) {
2676
+ (void)last;
2677
+ ((void (*)(ScrClosure *, double))cb->fn)(cb, code);
2678
+ }
2679
+
2680
+ void scr_http2_goaway_thunk0(ScrClosure *cb, double code, double last) {
2681
+ (void)code;
2682
+ (void)last;
2683
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2684
+ }
2685
+
2686
+ /* ── the checked-dynamic handle surface (SCR_DYNH_H2_SESSION/STREAM) ───
2687
+ *
2688
+ * Boxed h2 handles reaching dynamic code (a mustCall-wrapped listener's
2689
+ * parameter, or a handle stored in an untyped binding) dispatch their
2690
+ * members here, the scr_http.c req/res ops precedent: `.on(...)` wires a
2691
+ * runtime-built listener closure over the dyn callback, header events
2692
+ * build a DOM headers object from the pairs, method calls decode dyn
2693
+ * args onto the static entry points, and property reads box the scalar
2694
+ * answers. Real-but-unmodeled members throw the loud ladder; names the
2695
+ * surface never had throw Node's "<x> is not a function". */
2696
+
2697
+ static ScrDyn *scr_h2_pairs_to_dom(ScrArr *pairs /*borrowed*/, double status) {
2698
+ ScrDyn *obj = scr_dyn_new_obj();
2699
+ size_t n = (size_t)scr_arr_len(pairs);
2700
+ for (size_t i = 0; i + 1 < n; i += 2) {
2701
+ ScrStr *name = (ScrStr *)scr_arr_get_ref(pairs, (double)i);
2702
+ ScrStr *value = (ScrStr *)scr_arr_get_ref(pairs, (double)(i + 1));
2703
+ if (!scr_dyn_obj_get(obj, name->data, name->len)) {
2704
+ scr_dyn_obj_set(obj, name->data, name->len, scr_dyn_new_str(value));
2705
+ }
2706
+ scr_str_release(name);
2707
+ scr_str_release(value);
2708
+ }
2709
+ if (status > 0) {
2710
+ scr_dyn_obj_set(obj, ":status", 7, scr_dyn_new_num(status));
2711
+ }
2712
+ return obj;
2713
+ }
2714
+
2715
+ /* Header event fire thunks (the tuple this unit alone can box). */
2716
+ static void scr_h2_dyn_fire_stream(ScrClosure *cb, ScrH2Stream *st, ScrArr *pairs, double flags) {
2717
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
2718
+ ScrDyn *hstream = scr_dyn_new_handle(st, SCR_DYNH_H2_STREAM);
2719
+ ScrDyn *hdrs = scr_h2_pairs_to_dom(pairs, 0);
2720
+ ScrDyn *fl = scr_dyn_new_num(flags);
2721
+ ScrDyn *args[3] = { hstream, hdrs, fl };
2722
+ ScrDyn *r = scr_dyn_call(fn, args, 3, "listener");
2723
+ scr_dyn_release(r);
2724
+ scr_dyn_release(fl);
2725
+ scr_dyn_release(hdrs);
2726
+ scr_dyn_release(hstream);
2727
+ scr_dyn_release(fn);
2728
+ scr_http2_stream_release(st);
2729
+ scr_arr_release(pairs);
2730
+ }
2731
+
2732
+ static void scr_h2_dyn_fire_resp(ScrClosure *cb, ScrArr *pairs, double status, double flags) {
2733
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
2734
+ ScrDyn *hdrs = scr_h2_pairs_to_dom(pairs, status);
2735
+ ScrDyn *fl = scr_dyn_new_num(flags);
2736
+ ScrDyn *args[2] = { hdrs, fl };
2737
+ ScrDyn *r = scr_dyn_call(fn, args, 2, "listener");
2738
+ scr_dyn_release(r);
2739
+ scr_dyn_release(fl);
2740
+ scr_dyn_release(hdrs);
2741
+ scr_dyn_release(fn);
2742
+ scr_arr_release(pairs);
2743
+ }
2744
+
2745
+ static void scr_h2_dyn_fire_connect(ScrClosure *cb, ScrH2Session *s, ScrNetSocket *sock) {
2746
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
2747
+ ScrDyn *hs = scr_dyn_new_handle(s, SCR_DYNH_H2_SESSION);
2748
+ ScrDyn *hsock = sock ? scr_dyn_new_handle(sock, SCR_DYNH_NET_SOCKET) : scr_dyn_retain(scr_dyn_undefined());
2749
+ ScrDyn *args[2] = { hs, hsock };
2750
+ ScrDyn *r = scr_dyn_call(fn, args, 2, "listener");
2751
+ scr_dyn_release(r);
2752
+ scr_dyn_release(hsock);
2753
+ scr_dyn_release(hs);
2754
+ scr_dyn_release(fn);
2755
+ scr_http2_session_release(s);
2756
+ if (sock) scr_net_sock_release(sock);
2757
+ }
2758
+
2759
+ static void scr_h2_dyn_fire_settings(ScrClosure *cb, ScrDyn *settings /*+1*/) {
2760
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
2761
+ ScrDyn *args[1] = { settings };
2762
+ ScrDyn *r = scr_dyn_call(fn, args, 1, "listener");
2763
+ scr_dyn_release(r);
2764
+ scr_dyn_release(fn);
2765
+ scr_dyn_release(settings);
2766
+ }
2767
+
2768
+ /* The settings(obj, cb) CALLBACK shape: (err = null, settings, duration). */
2769
+ static void scr_h2_dyn_fire_settings_cb(ScrClosure *cb, ScrDyn *settings /*+1*/) {
2770
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
2771
+ ScrDyn *err = scr_dyn_new_null();
2772
+ ScrDyn *dur = scr_dyn_new_num(0);
2773
+ ScrDyn *args[3] = { err, settings, dur };
2774
+ ScrDyn *r = scr_dyn_call(fn, args, 3, "listener");
2775
+ scr_dyn_release(r);
2776
+ scr_dyn_release(dur);
2777
+ scr_dyn_release(err);
2778
+ scr_dyn_release(fn);
2779
+ scr_dyn_release(settings);
2780
+ }
2781
+
2782
+ static void scr_h2_dyn_fire_goaway(ScrClosure *cb, double code, double last) {
2783
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
2784
+ ScrDyn *a0 = scr_dyn_new_num(code);
2785
+ ScrDyn *a1 = scr_dyn_new_num(last);
2786
+ ScrDyn *args[2] = { a0, a1 };
2787
+ ScrDyn *r = scr_dyn_call(fn, args, 2, "listener");
2788
+ scr_dyn_release(r);
2789
+ scr_dyn_release(a1);
2790
+ scr_dyn_release(a0);
2791
+ scr_dyn_release(fn);
2792
+ }
2793
+
2794
+ /* Flatten a DOM headers object into the flat pairs array (numbers via
2795
+ * String(n); the :status/:method/... pseudo-headers pass through). */
2796
+ static ScrArr *scr_h2_dom_to_pairs(const ScrDyn *headers) {
2797
+ ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
2798
+ for (size_t i = 0; i < headers->v.obj.len; i++) {
2799
+ const ScrDynEntry *e = &headers->v.obj.entries[i];
2800
+ const ScrDyn *v = e->value;
2801
+ ScrStr *vs;
2802
+ if (v->kind == SCR_DYN_STR) vs = scr_str_retain(v->v.str);
2803
+ else if (v->kind == SCR_DYN_NUM) vs = scr_f64_to_scrstr(v->v.num);
2804
+ else continue; /* undefined/null header: dropped, Node's stance */
2805
+ scr_arr_push_ref(pairs, scr_str_new(e->key, e->key_len));
2806
+ scr_arr_push_ref(pairs, vs);
2807
+ }
2808
+ return pairs;
2809
+ }
2810
+
2811
+ /* endStream from a dyn options object ({ endStream: true }); default d. */
2812
+ static bool scr_h2_dyn_end_opt(const ScrDyn *opts, bool dflt) {
2813
+ if (opts == NULL || opts->kind != SCR_DYN_OBJ) return dflt;
2814
+ const ScrDyn *e = scr_dyn_obj_get(opts, "endStream", 9);
2815
+ if (e == NULL) return dflt;
2816
+ return e->kind == SCR_DYN_BOOL ? e->v.b : dflt;
2817
+ }
2818
+
2819
+ static void scr_h2_dyn_not_a_fn(const char *what) {
2820
+ char msg[160];
2821
+ int n = snprintf(msg, sizeof msg, "%s is not a function", what);
2822
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)n);
2823
+ }
2824
+
2825
+ /* ── stream ops ──────────────────────────────────────────────────────── */
2826
+
2827
+ static ScrDyn *scr_h2_dynh_stream_invoke(void *h, ScrDyn *self, const char *method,
2828
+ ScrDyn *const *args, size_t argc, const char *what) {
2829
+ ScrH2Stream *st = (ScrH2Stream *)h;
2830
+ if (strcmp(method, "respond") == 0) {
2831
+ ScrArr *pairs = argc > 0 && args[0]->kind == SCR_DYN_OBJ ? scr_h2_dom_to_pairs(args[0]) : NULL;
2832
+ bool end = scr_h2_dyn_end_opt(argc > 1 ? args[1] : NULL, false);
2833
+ scr_http2_stream_respond(st, pairs, end);
2834
+ if (pairs) scr_arr_release(pairs);
2835
+ return scr_dyn_undefined();
2836
+ }
2837
+ if (strcmp(method, "write") == 0 || strcmp(method, "end") == 0) {
2838
+ bool end = method[0] == 'e';
2839
+ const ScrDyn *data = argc > 0 ? args[0] : NULL;
2840
+ if (data && data->kind == SCR_DYN_STR) {
2841
+ if (end) scr_http2_stream_end_str(st, data->v.str);
2842
+ else scr_http2_stream_write_str(st, data->v.str);
2843
+ } else if (data && data->kind == SCR_DYN_BYTES) {
2844
+ if (end) scr_http2_stream_end_bytes(st, data->v.bytes);
2845
+ else scr_http2_stream_write_bytes(st, data->v.bytes);
2846
+ } else if (end) {
2847
+ scr_http2_stream_end(st);
2848
+ }
2849
+ return scr_dyn_undefined();
2850
+ }
2851
+ if (strcmp(method, "close") == 0) {
2852
+ double code = argc > 0 && args[0]->kind == SCR_DYN_NUM ? args[0]->v.num : 0;
2853
+ ScrClosure *cb = argc > 1 && args[1]->kind == SCR_DYN_FUNC ? scr_dyn_listener_closure0(args[1]) : NULL;
2854
+ scr_http2_stream_close(st, code, cb);
2855
+ return scr_dyn_undefined();
2856
+ }
2857
+ if (strcmp(method, "destroy") == 0) { scr_http2_stream_destroy(st); return scr_dyn_undefined(); }
2858
+ if (strcmp(method, "setEncoding") == 0) {
2859
+ if (argc > 0 && args[0]->kind == SCR_DYN_STR) scr_http2_stream_set_encoding(st, args[0]->v.str);
2860
+ return scr_dyn_retain(self);
2861
+ }
2862
+ if (strcmp(method, "resume") == 0) { scr_http2_stream_resume(st); return scr_dyn_retain(self); }
2863
+ if (strcmp(method, "pause") == 0) { scr_http2_stream_pause(st); return scr_dyn_retain(self); }
2864
+ if (strcmp(method, "on") == 0 || strcmp(method, "once") == 0 || strcmp(method, "addListener") == 0) {
2865
+ bool once = method[0] == 'o' && method[1] == 'n' && method[2] == 'c';
2866
+ if (argc < 2 || args[0]->kind != SCR_DYN_STR) { scr_dyn_check_listener(argc > 1 ? args[1] : scr_dyn_undefined(), "listener"); return NULL; }
2867
+ scr_dyn_check_listener(args[1], "listener");
2868
+ const char *ev = args[0]->v.str->data;
2869
+ if (strcmp(ev, "data") == 0) {
2870
+ scr_http2_stream_on_data(st, scr_dyn_listener_closure_data(args[1]), (ScrNetDataFn)&scr_dyn_listener_fire_data, once);
2871
+ } else if (strcmp(ev, "end") == 0) {
2872
+ scr_http2_stream_on_end(st, scr_dyn_listener_closure0(args[1]), once);
2873
+ } else if (strcmp(ev, "close") == 0) {
2874
+ scr_http2_stream_on_close(st, scr_dyn_listener_closure0(args[1]), once);
2875
+ } else if (strcmp(ev, "aborted") == 0) {
2876
+ scr_http2_stream_on_aborted(st, scr_dyn_listener_closure0(args[1]), once);
2877
+ } else if (strcmp(ev, "error") == 0) {
2878
+ scr_http2_stream_on_error(st, scr_dyn_listener_closure_err(args[1]), (ScrChildErrFn)&scr_dyn_listener_fire_err, once);
2879
+ } else if (strcmp(ev, "response") == 0) {
2880
+ scr_http2_stream_on_response(st, scr_dyn_listener_closure_fn(args[1], (void *)&scr_h2_dyn_fire_resp), (ScrH2RespFn)&scr_h2_dyn_fire_resp, once);
2881
+ } else if (strcmp(ev, "push") == 0 || strcmp(ev, "headers") == 0 || strcmp(ev, "trailers") == 0 ||
2882
+ strcmp(ev, "frameError") == 0 || strcmp(ev, "ready") == 0 || strcmp(ev, "timeout") == 0 ||
2883
+ strcmp(ev, "wantTrailers") == 0 || strcmp(ev, "drain") == 0 || strcmp(ev, "finish") == 0) {
2884
+ /* modeled elsewhere or inert here: accept the registration, never fire */
2885
+ } else {
2886
+ char msg[128];
2887
+ int n = snprintf(msg, sizeof msg, "listening for '%s' on an Http2Stream is not supported yet", ev);
2888
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
2889
+ return NULL;
2890
+ }
2891
+ return scr_dyn_retain(self);
2892
+ }
2893
+ scr_h2_dyn_not_a_fn(what);
2894
+ return NULL;
2895
+ }
2896
+
2897
+ static ScrDyn *scr_h2_dynh_stream_get(void *h, const char *key, size_t key_len) {
2898
+ ScrH2Stream *st = (ScrH2Stream *)h;
2899
+ (void)key_len;
2900
+ if (strcmp(key, "id") == 0) return scr_dyn_new_num(scr_http2_stream_id(st));
2901
+ if (strcmp(key, "rstCode") == 0) return scr_dyn_new_num(scr_http2_stream_rst_code(st));
2902
+ if (strcmp(key, "destroyed") == 0) return scr_dyn_new_bool(scr_http2_stream_destroyed(st));
2903
+ if (strcmp(key, "closed") == 0) return scr_dyn_new_bool(scr_http2_stream_closed(st));
2904
+ if (strcmp(key, "aborted") == 0) return scr_dyn_new_bool(scr_http2_stream_aborted(st));
2905
+ if (strcmp(key, "pending") == 0) return scr_dyn_new_bool(scr_http2_stream_pending(st));
2906
+ if (strcmp(key, "session") == 0) return scr_dyn_new_handle(st->sess, SCR_DYNH_H2_SESSION);
2907
+ return NULL;
2908
+ }
2909
+
2910
+ static bool scr_h2_dynh_stream_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
2911
+ (void)h; (void)key; (void)key_len; (void)value;
2912
+ return false;
2913
+ }
2914
+
2915
+ /* ── session ops ─────────────────────────────────────────────────────── */
2916
+
2917
+ static ScrDyn *scr_h2_dynh_session_invoke(void *h, ScrDyn *self, const char *method,
2918
+ ScrDyn *const *args, size_t argc, const char *what) {
2919
+ ScrH2Session *s = (ScrH2Session *)h;
2920
+ if (strcmp(method, "request") == 0) {
2921
+ ScrArr *pairs = argc > 0 && args[0]->kind == SCR_DYN_OBJ ? scr_h2_dom_to_pairs(args[0]) : NULL;
2922
+ double end = -1;
2923
+ if (argc > 1 && args[1]->kind == SCR_DYN_OBJ) {
2924
+ const ScrDyn *e = scr_dyn_obj_get(args[1], "endStream", 9);
2925
+ if (e && e->kind == SCR_DYN_BOOL) end = e->v.b ? 1 : 0;
2926
+ }
2927
+ ScrH2Stream *st = scr_http2_session_request(s, pairs, end);
2928
+ if (pairs) scr_arr_release(pairs);
2929
+ if (st == NULL) return NULL; /* pending exception */
2930
+ ScrDyn *d = scr_dyn_new_handle(st, SCR_DYNH_H2_STREAM);
2931
+ scr_http2_stream_release(st); /* the box holds its own +1 */
2932
+ return d;
2933
+ }
2934
+ if (strcmp(method, "close") == 0) {
2935
+ ScrClosure *cb = argc > 0 && args[0]->kind == SCR_DYN_FUNC ? scr_dyn_listener_closure0(args[0]) : NULL;
2936
+ scr_http2_session_close(s, cb);
2937
+ return scr_dyn_undefined();
2938
+ }
2939
+ if (strcmp(method, "destroy") == 0) { scr_http2_session_destroy(s); return scr_dyn_undefined(); }
2940
+ if (strcmp(method, "on") == 0 || strcmp(method, "once") == 0 || strcmp(method, "addListener") == 0) {
2941
+ bool once = method[0] == 'o' && method[1] == 'n' && method[2] == 'c';
2942
+ if (argc < 2 || args[0]->kind != SCR_DYN_STR) { scr_dyn_check_listener(argc > 1 ? args[1] : scr_dyn_undefined(), "listener"); return NULL; }
2943
+ scr_dyn_check_listener(args[1], "listener");
2944
+ const char *ev = args[0]->v.str->data;
2945
+ if (strcmp(ev, "close") == 0) {
2946
+ scr_http2_session_on_close(s, scr_dyn_listener_closure0(args[1]), once);
2947
+ } else if (strcmp(ev, "error") == 0) {
2948
+ scr_http2_session_on_error(s, scr_dyn_listener_closure_err(args[1]), (ScrChildErrFn)&scr_dyn_listener_fire_err, once);
2949
+ } else if (strcmp(ev, "connect") == 0) {
2950
+ scr_http2_session_on_connect(s, scr_dyn_listener_closure_fn(args[1], (void *)&scr_h2_dyn_fire_connect), (ScrH2ConnectFn)&scr_h2_dyn_fire_connect, once);
2951
+ } else if (strcmp(ev, "stream") == 0) {
2952
+ scr_http2_session_on_stream(s, scr_dyn_listener_closure_fn(args[1], (void *)&scr_h2_dyn_fire_stream), (ScrH2StreamFn)&scr_h2_dyn_fire_stream, once);
2953
+ } else if (strcmp(ev, "goaway") == 0) {
2954
+ scr_http2_session_on_goaway(s, scr_dyn_listener_closure_fn(args[1], (void *)&scr_h2_dyn_fire_goaway), (ScrH2GoawayFn)&scr_h2_dyn_fire_goaway, once);
2955
+ } else if (strcmp(ev, "localSettings") == 0 || strcmp(ev, "remoteSettings") == 0) {
2956
+ scr_http2_session_on_settings(s, scr_dyn_listener_closure_fn(args[1], (void *)&scr_h2_dyn_fire_settings), (ScrH2SettingsFn)&scr_h2_dyn_fire_settings, once, ev[0] == 'l');
2957
+ } else if (strcmp(ev, "ping") == 0 || strcmp(ev, "frameError") == 0 || strcmp(ev, "timeout") == 0) {
2958
+ /* inert here: accept, never fire */
2959
+ } else {
2960
+ char msg[128];
2961
+ int n = snprintf(msg, sizeof msg, "listening for '%s' on an Http2Session is not supported yet", ev);
2962
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
2963
+ return NULL;
2964
+ }
2965
+ return scr_dyn_retain(self);
2966
+ }
2967
+ if (strcmp(method, "settings") == 0) {
2968
+ const ScrDyn *settings = argc > 0 && args[0]->kind == SCR_DYN_OBJ ? args[0] : NULL;
2969
+ ScrClosure *cb = argc > 1 && args[1]->kind == SCR_DYN_FUNC
2970
+ ? scr_dyn_listener_closure_fn(args[1], (void *)&scr_h2_dyn_fire_settings_cb)
2971
+ : NULL;
2972
+ scr_http2_session_settings(s, settings, cb, (ScrH2SettingsFn)&scr_h2_dyn_fire_settings_cb);
2973
+ return scr_dyn_retain(self);
2974
+ }
2975
+ if (strcmp(method, "setTimeout") == 0 || strcmp(method, "ref") == 0 || strcmp(method, "unref") == 0 ||
2976
+ strcmp(method, "goaway") == 0 || strcmp(method, "ping") == 0) {
2977
+ /* accepted no-ops / not-yet-modeled tuning — return this */
2978
+ return scr_dyn_retain(self);
2979
+ }
2980
+ scr_h2_dyn_not_a_fn(what);
2981
+ return NULL;
2982
+ }
2983
+
2984
+ static ScrDyn *scr_h2_dynh_session_get(void *h, const char *key, size_t key_len) {
2985
+ ScrH2Session *s = (ScrH2Session *)h;
2986
+ (void)key_len;
2987
+ if (strcmp(key, "closed") == 0) return scr_dyn_new_bool(scr_http2_session_closed(s));
2988
+ if (strcmp(key, "destroyed") == 0) return scr_dyn_new_bool(scr_http2_session_destroyed(s));
2989
+ if (strcmp(key, "encrypted") == 0) return scr_dyn_new_bool(scr_http2_session_encrypted(s));
2990
+ if (strcmp(key, "type") == 0) return scr_dyn_new_num(scr_http2_session_type(s));
2991
+ if (strcmp(key, "alpnProtocol") == 0) {
2992
+ ScrStr *a = scr_http2_session_alpn(s);
2993
+ ScrDyn *d = scr_dyn_new_str(a);
2994
+ scr_str_release(a);
2995
+ return d;
2996
+ }
2997
+ if (strcmp(key, "pendingSettingsAck") == 0) {
2998
+ return scr_dyn_new_bool(scr_http2_session_pending_settings_ack(s));
2999
+ }
3000
+ if (strcmp(key, "localSettings") == 0) return scr_http2_session_settings_get(s, true);
3001
+ if (strcmp(key, "remoteSettings") == 0) return scr_http2_session_settings_get(s, false);
3002
+ if (strcmp(key, "socket") == 0) {
3003
+ ScrNetSocket *sock = scr_http2_session_socket(s);
3004
+ if (sock == NULL) return NULL;
3005
+ ScrDyn *d = scr_dyn_new_handle(sock, SCR_DYNH_NET_SOCKET);
3006
+ scr_net_sock_release(sock);
3007
+ return d;
3008
+ }
3009
+ return NULL;
3010
+ }
3011
+
3012
+ static bool scr_h2_dynh_session_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
3013
+ (void)h; (void)key; (void)key_len; (void)value;
3014
+ return false;
3015
+ }
3016
+
3017
+ static const ScrDynHandleOps scr_h2_dynh_stream_ops = {
3018
+ "Http2Stream", &scr_http2_stream_retain_v, &scr_http2_stream_release_v,
3019
+ &scr_h2_dynh_stream_invoke, &scr_h2_dynh_stream_get, &scr_h2_dynh_stream_set, NULL,
3020
+ };
3021
+
3022
+ static const ScrDynHandleOps scr_h2_dynh_session_ops = {
3023
+ "Http2Session", &scr_http2_session_retain_v, &scr_http2_session_release_v,
3024
+ &scr_h2_dynh_session_invoke, &scr_h2_dynh_session_get, &scr_h2_dynh_session_set, NULL,
3025
+ };
3026
+
3027
+ void scr_http2_dyn_install(void) {
3028
+ scr_dyn_handle_install(SCR_DYNH_H2_STREAM, &scr_h2_dynh_stream_ops);
3029
+ scr_dyn_handle_install(SCR_DYNH_H2_SESSION, &scr_h2_dynh_session_ops);
3030
+ }