@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
package/src/scr_net.c ADDED
@@ -0,0 +1,3017 @@
1
+ /* node:net — the inbound-networking slice: TCP servers and sockets over
2
+ * the event loop's readiness poller, the first LISTENING capability of
3
+ * the runtime. The poller is the scr_platform.h contract — kqueue on
4
+ * macOS/BSD (scr_loop_kqueue.c, a stateless spelling of the syscall
5
+ * sequences this file inlined historically), epoll+timerfd on Linux
6
+ * (scr_loop_epoll.c). Two Linux-only obligations shape this file's use
7
+ * of the seam: a watched fd is scrp_forget-ed BEFORE close(2) (epoll
8
+ * registrations can outlive a close; the kqueue backend's forget is a
9
+ * no-op so macOS behavior is untouched), and socket sends go through
10
+ * scr_net_send (MSG_NOSIGNAL where it exists) because Linux has no
11
+ * SO_NOSIGPIPE — a write to a closed peer must surface as an 'error'
12
+ * event, never a process-killing SIGPIPE.
13
+ *
14
+ * ── Design note (the server core) ────────────────────────────────────
15
+ *
16
+ * Object model. Two refcounted handle kinds, both LEAN allocations (no
17
+ * cycle header — the child precedent): ScrNetServer (a listener: fd,
18
+ * bound port, connection/error/close listener lists) and ScrNetSocket (a
19
+ * connection: fd, a state machine over the read/write halves, a growable
20
+ * write buffer, data/end/close/error/connect listener lists, an optional
21
+ * pipe destination, and — for accepted sockets — a +1 backref to the
22
+ * owning server so its connection count can gate the server's 'close').
23
+ * Listeners MOVE in (+1) and are released when the handle settles (a
24
+ * socket's 'close' fired, a server's 'close' fired, or the listen/connect
25
+ * failure delivered) — so a listener capturing its own handle cannot
26
+ * cycle past settlement, exactly the ScrChild ownership story. Handles a
27
+ * program abandons at exit are released by the unit's atexit cleanup, so
28
+ * the RC audit never mistakes a process-lifetime server for a leak.
29
+ *
30
+ * Event dispatch. One poller owned by this unit (lazily created), fds all
31
+ * non-blocking. The loop (scr_async.c) calls scr_net_dispatch() at every
32
+ * turn top — the scr_events.c hook shape — which alternates two phases
33
+ * until neither has work: (1) a SWEEP delivering deferred emits
34
+ * ('listening' callbacks, listen/connect failures, socket 'close', then
35
+ * server 'close' — socket-before-server order is what lets a drained
36
+ * server observe its last connection die in the same pass), and (2) a
37
+ * zero-timeout poller drain (accepts, reads, connect completions, write
38
+ * flushes) whose callbacks fire directly, macrotask-style on the main
39
+ * stack. The alternation stops early when a callback enqueued microtasks
40
+ * (scr_loop_has_ready) so promise jobs interleave between event batches,
41
+ * or when a callback threw (the loop surfaces the uncaught exception).
42
+ * Between turns the loop's idle poll(2) watches this unit's poller FD —
43
+ * kqueue and epoll fds both poll readable while events are pending — so socket
44
+ * readiness ends the sleep immediately; while curl's io poll owns the
45
+ * sleep instead (a --dynamic fetch in flight), net latency is capped at
46
+ * the signal-poll granularity, like signals.
47
+ *
48
+ * Read model. Sockets are CONSUMER-DRIVEN like the stdin slice: the read
49
+ * filter is armed only while a consumer exists (a 'data' listener or a
50
+ * pipe destination), one read(2) per arrived chunk, delivered borrowed to
51
+ * the listener snapshot (Node's emit-over-a-copy semantics; `once`
52
+ * entries leave the live list before running) and appended to the pipe
53
+ * destination. EOF fires 'end', then — allowHalfOpen is always false, the
54
+ * Node default — ends the write half; when both halves are done the fd
55
+ * closes and 'close' fires on the next sweep.
56
+ *
57
+ * Write model. write() attempts the syscall immediately when nothing is
58
+ * buffered (the common case: small responses go out in one call) and
59
+ * buffers the remainder with the write filter armed; end() marks the
60
+ * write half ENDING so the FIN (shutdown) goes out when the buffer
61
+ * drains. Backpressure is not modeled — Node's write() returns false and
62
+ * keeps buffering; this write() is void and buffers without bound.
63
+ *
64
+ * Loop liveness. A handle keeps the process alive from listen()/connect()
65
+ * until its 'close' delivers (scr_net_pending — the loop's exhaustion
66
+ * test): a listening server is a live interval, server.close() releases
67
+ * once every accepted connection drains, and an open socket holds the
68
+ * loop like Node's active TCP handle. A server that never listened and a
69
+ * socket already closed hold nothing.
70
+ *
71
+ * Close semantics, Node-matched: server.close() stops accepting NOW
72
+ * (the listening fd closes) and fires close callbacks + 'close' when the
73
+ * connection count reaches zero; socket 'end' precedes 'close';
74
+ * connect/listen failures fire 'error' then 'close' ('error' with no
75
+ * listener prints the error and exits 1 — the unhandled-'error'
76
+ * EventEmitter behavior, the child precedent); listeners registered
77
+ * after settlement release immediately and never fire; 'listening' (the
78
+ * listen callback) is deferred to the next dispatch pass, Node's
79
+ * next-tick emit.
80
+ *
81
+ * The differential harness pattern (tests/harness/server.test.ts): a
82
+ * LISTENING program cannot be compared by stdout alone — something must
83
+ * talk to it. Each fixture binds port 0, reports the real port over
84
+ * stderr (`PORT <n>\n` — stderr is never compared), and a per-case Node
85
+ * driver script issues the same requests against both lanes; the SERVER
86
+ * program's stdout/exit code and the DRIVER's stdout must each match
87
+ * byte-for-byte between the Node lane and the compiled lane. Ephemeral
88
+ * ports never appear in compared output. This is the template TLS/http2
89
+ * lanes inherit.
90
+ */
91
+ #include "scr_platform.h"
92
+ #include "scr_runtime.h"
93
+
94
+ #include <errno.h>
95
+ #include <fcntl.h>
96
+ #include <stdio.h>
97
+ #include <stdlib.h>
98
+ #include <string.h>
99
+ #include <unistd.h>
100
+ #ifdef _WIN32
101
+ #include <winsock2.h>
102
+ #include <ws2tcpip.h>
103
+ #else
104
+ #include <arpa/inet.h>
105
+ #include <netdb.h> /* getaddrinfo — the client bridges' blocking lookup */
106
+ #include <netinet/in.h>
107
+ #include <netinet/tcp.h>
108
+ #include <sys/socket.h>
109
+ #endif
110
+
111
+ static void scr_net_oom(void) {
112
+ fputs("scriptc: out of memory\n", stderr);
113
+ abort();
114
+ }
115
+
116
+ #ifdef _WIN32
117
+ /* ── the winsock arm ───────────────────────────────────────────────────
118
+ * The BSD socket calls this file speaks, respelled once so every call
119
+ * site below stays byte-identical: SOCKETs narrow through the int fd
120
+ * contract (kernel handles fit — scr_child.c's stdio plumbing relies on
121
+ * the same), failures land their WSA code in errno TRANSLATED to the
122
+ * POSIX constant the call sites and the error-name table test (libuv
123
+ * performs the same translation for Node, so the spellings agree with
124
+ * the Windows oracle), socket reads are recv (ReadFile/_read do not
125
+ * apply to winsock handles), and close is closesocket. WSAStartup lives
126
+ * in scrp_poller_new (scr_loop_wsapoll.c) — every socket() site in this
127
+ * file is preceded by scr_net_poller_init(). Two call-shape notes: a
128
+ * pending nonblocking connect answers WSAEWOULDBLOCK where the sites
129
+ * test EINPROGRESS (the connect wrapper translates), and SO_ERROR
130
+ * answers a WSA code the getsockopt wrapper maps before the table sees
131
+ * it. No SIGPIPE exists on Windows — a closed-peer send just fails. */
132
+ static int scr_w32_map_err(int e) {
133
+ switch (e) {
134
+ case WSAEWOULDBLOCK: return EAGAIN;
135
+ case WSAEINTR: return EINTR;
136
+ case WSAECONNREFUSED: return ECONNREFUSED;
137
+ case WSAECONNRESET: return ECONNRESET;
138
+ case WSAECONNABORTED: return ECONNABORTED;
139
+ case WSAESHUTDOWN: return EPIPE; /* libuv's map: send-after-shutdown is EPIPE */
140
+ case WSAEADDRINUSE: return EADDRINUSE;
141
+ case WSAEACCES: return EACCES;
142
+ case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL;
143
+ case WSAETIMEDOUT: return ETIMEDOUT;
144
+ case WSAEHOSTUNREACH: return EHOSTUNREACH;
145
+ case WSAENETUNREACH: return ENETUNREACH;
146
+ case WSAEINVAL: return EINVAL;
147
+ default: return e;
148
+ }
149
+ }
150
+
151
+ static void scr_w32_seterr(void) { errno = scr_w32_map_err((int)WSAGetLastError()); }
152
+
153
+ static int scr_w32_socket(int af, int type, int proto) {
154
+ SOCKET s = socket(af, type, proto);
155
+ if (s == INVALID_SOCKET) {
156
+ scr_w32_seterr();
157
+ return -1;
158
+ }
159
+ return (int)s;
160
+ }
161
+
162
+ static int scr_w32_bind(int fd, const struct sockaddr *sa, socklen_t len) {
163
+ if (bind((SOCKET)fd, sa, (int)len) != 0) {
164
+ scr_w32_seterr();
165
+ return -1;
166
+ }
167
+ return 0;
168
+ }
169
+
170
+ static int scr_w32_connect(int fd, const struct sockaddr *sa, socklen_t len) {
171
+ if (connect((SOCKET)fd, sa, (int)len) != 0) {
172
+ int e = (int)WSAGetLastError();
173
+ /* pending nonblocking connect: WSAEWOULDBLOCK here is the callers'
174
+ * EINPROGRESS, not recv's EAGAIN */
175
+ errno = e == WSAEWOULDBLOCK ? EINPROGRESS : scr_w32_map_err(e);
176
+ return -1;
177
+ }
178
+ return 0;
179
+ }
180
+
181
+ static int scr_w32_listen(int fd, int backlog) {
182
+ if (listen((SOCKET)fd, backlog) != 0) {
183
+ scr_w32_seterr();
184
+ return -1;
185
+ }
186
+ return 0;
187
+ }
188
+
189
+ static int scr_w32_accept(int fd, struct sockaddr *sa, socklen_t *len) {
190
+ int ilen = len != NULL ? (int)*len : 0;
191
+ SOCKET s = accept((SOCKET)fd, sa, len != NULL ? &ilen : NULL);
192
+ if (len != NULL) *len = (socklen_t)ilen;
193
+ if (s == INVALID_SOCKET) {
194
+ scr_w32_seterr();
195
+ return -1;
196
+ }
197
+ return (int)s;
198
+ }
199
+
200
+ static int scr_w32_getsockname(int fd, struct sockaddr *sa, socklen_t *len) {
201
+ int ilen = (int)*len;
202
+ int rc = getsockname((SOCKET)fd, sa, &ilen);
203
+ *len = (socklen_t)ilen;
204
+ if (rc != 0) {
205
+ scr_w32_seterr();
206
+ return -1;
207
+ }
208
+ return 0;
209
+ }
210
+
211
+ static int scr_w32_setsockopt(int fd, int level, int name, const void *val, socklen_t len) {
212
+ if (setsockopt((SOCKET)fd, level, name, (const char *)val, (int)len) != 0) {
213
+ scr_w32_seterr();
214
+ return -1;
215
+ }
216
+ return 0;
217
+ }
218
+
219
+ static int scr_w32_getsockopt(int fd, int level, int name, void *val, socklen_t *len) {
220
+ int ilen = (int)*len;
221
+ int rc = getsockopt((SOCKET)fd, level, name, (char *)val, &ilen);
222
+ *len = (socklen_t)ilen;
223
+ if (rc != 0) {
224
+ scr_w32_seterr();
225
+ return -1;
226
+ }
227
+ if (level == SOL_SOCKET && name == SO_ERROR && ilen == (int)sizeof(int)) {
228
+ *(int *)val = scr_w32_map_err(*(int *)val); /* the table speaks POSIX */
229
+ }
230
+ return 0;
231
+ }
232
+
233
+ static ssize_t scr_w32_read(int fd, void *buf, size_t len) {
234
+ int n = recv((SOCKET)fd, (char *)buf, len > 0x40000000 ? 0x40000000 : (int)len, 0);
235
+ if (n < 0) scr_w32_seterr();
236
+ return n;
237
+ }
238
+
239
+ static int scr_w32_close(int fd) {
240
+ if (closesocket((SOCKET)fd) != 0) {
241
+ scr_w32_seterr();
242
+ return -1;
243
+ }
244
+ return 0;
245
+ }
246
+
247
+ #define socket(af, type, proto) scr_w32_socket((af), (type), (proto))
248
+ #define bind(fd, sa, len) scr_w32_bind((fd), (sa), (len))
249
+ #define connect(fd, sa, len) scr_w32_connect((fd), (sa), (len))
250
+ #define listen(fd, backlog) scr_w32_listen((fd), (backlog))
251
+ #define accept(fd, sa, len) scr_w32_accept((fd), (sa), (len))
252
+ #define getsockname(fd, sa, len) scr_w32_getsockname((fd), (sa), (len))
253
+ #define setsockopt(fd, l, n, v, len) scr_w32_setsockopt((fd), (l), (n), (v), (len))
254
+ #define getsockopt(fd, l, n, v, len) scr_w32_getsockopt((fd), (l), (n), (v), (len))
255
+ #define read(fd, buf, len) scr_w32_read((fd), (buf), (len))
256
+ #define close(fd) scr_w32_close(fd)
257
+ #define SHUT_WR SD_SEND
258
+ #endif /* _WIN32 */
259
+
260
+ /* Node's error-code names for the socket errors this slice can meet. */
261
+ static const char *scr_net_errname(int err) {
262
+ switch (err) {
263
+ case ECONNREFUSED: return "ECONNREFUSED";
264
+ case ECONNRESET: return "ECONNRESET";
265
+ case ECONNABORTED: return "ECONNABORTED";
266
+ case EPIPE: return "EPIPE";
267
+ case EADDRINUSE: return "EADDRINUSE";
268
+ case EACCES: return "EACCES";
269
+ case EADDRNOTAVAIL: return "EADDRNOTAVAIL";
270
+ case ETIMEDOUT: return "ETIMEDOUT";
271
+ case EHOSTUNREACH: return "EHOSTUNREACH";
272
+ case ENETUNREACH: return "ENETUNREACH";
273
+ #ifdef EHOSTDOWN
274
+ case EHOSTDOWN: return "EHOSTDOWN"; /* absent from the win32 CRT */
275
+ #endif
276
+ case EINVAL: return "EINVAL";
277
+ default: return "EUNKNOWN";
278
+ }
279
+ }
280
+
281
+ /* ── listener lists (the events/child snapshot discipline) ─────────────
282
+ * The types live in scr_runtime.h: scr_http.c reuses the whole family
283
+ * for its request-body listener lists. */
284
+
285
+ void scr_net_ls_add(ScrNetLs *l, ScrClosure *cb, void *fn, bool once) {
286
+ if (l->n == l->cap) {
287
+ l->cap = l->cap ? l->cap * 2 : 2;
288
+ l->ls = realloc(l->ls, l->cap * sizeof *l->ls);
289
+ if (!l->ls) scr_net_oom();
290
+ }
291
+ l->ls[l->n].cb = cb;
292
+ l->ls[l->n].fn = fn;
293
+ l->ls[l->n].once = once;
294
+ l->n++;
295
+ }
296
+
297
+ void scr_net_ls_drop(ScrNetLs *l) {
298
+ for (size_t i = 0; i < l->n; i++) scr_closure_release(l->ls[i].cb);
299
+ free(l->ls);
300
+ l->ls = NULL;
301
+ l->n = l->cap = 0;
302
+ }
303
+
304
+ /* Snapshot for a firing pass: entries retained; `once` entries leave the
305
+ * LIVE list before their callback runs (Node's once semantics — a
306
+ * re-delivery during the callback re-registers cleanly). Caller invokes
307
+ * each snapshot entry, releases it, and frees the vector. */
308
+ size_t scr_net_ls_snapshot(ScrNetLs *l, ScrNetL **out) {
309
+ size_t n = l->n;
310
+ if (n == 0) {
311
+ *out = NULL;
312
+ return 0;
313
+ }
314
+ ScrNetL *snap = malloc(n * sizeof *snap);
315
+ if (!snap) scr_net_oom();
316
+ for (size_t i = 0; i < n; i++) {
317
+ snap[i] = l->ls[i];
318
+ scr_closure_retain(snap[i].cb);
319
+ }
320
+ /* remove the once entries from the live list */
321
+ size_t w = 0;
322
+ for (size_t i = 0; i < l->n; i++) {
323
+ if (l->ls[i].once) {
324
+ scr_closure_release(l->ls[i].cb);
325
+ } else {
326
+ l->ls[w++] = l->ls[i];
327
+ }
328
+ }
329
+ l->n = w;
330
+ *out = snap;
331
+ return n;
332
+ }
333
+
334
+ /* Fire a zero-payload event list (end/close/connect/listening) with the
335
+ * emitting handle bound as the ambient receiver for each callback (Node
336
+ * calls listeners with `this` === the emitter — scr_runtime.h's design
337
+ * note). `self` is BORROWED (the caller keeps the handle alive across
338
+ * the pass); NULL binds the undefined receiver. */
339
+ void scr_net_fire0_this(ScrNetLs *l, void *self, ScrDynHandleTag tag) {
340
+ ScrNetL *snap;
341
+ size_t n = scr_net_ls_snapshot(l, &snap);
342
+ scr_dyn_this_push(self, tag);
343
+ for (size_t i = 0; i < n; i++) {
344
+ if (!scr_exc_pending()) ((void (*)(ScrClosure *))snap[i].cb->fn)(snap[i].cb);
345
+ scr_closure_release(snap[i].cb);
346
+ }
347
+ scr_dyn_this_pop();
348
+ free(snap);
349
+ }
350
+
351
+ void scr_net_fire0(ScrNetLs *l) { scr_net_fire0_this(l, NULL, 0); }
352
+
353
+ /* Fire an error list through the ScrChildErrFn adapters (msg borrowed),
354
+ * the emitting handle bound as the ambient receiver. An 'error' with NO
355
+ * listener is fatal, like Node's unhandled 'error' EventEmitter throw:
356
+ * print and die 1 (_Exit skips atexit on purpose — the loop dies
357
+ * mid-turn with registries live, like process.exit). Exported:
358
+ * scr_http.c's req/res/client fire their 'error' the same way. */
359
+ void scr_net_fire_err_this(ScrNetLs *l, ScrStr *msg, void *self, ScrDynHandleTag tag) {
360
+ if (l->n == 0) {
361
+ fflush(stdout);
362
+ fprintf(stderr, "Unhandled 'error' event: Error: %s\n", msg->data);
363
+ _Exit(1);
364
+ }
365
+ ScrNetL *snap;
366
+ size_t n = scr_net_ls_snapshot(l, &snap);
367
+ scr_dyn_this_push(self, tag);
368
+ for (size_t i = 0; i < n; i++) {
369
+ if (!scr_exc_pending()) ((ScrChildErrFn)snap[i].fn)(snap[i].cb, msg);
370
+ scr_closure_release(snap[i].cb);
371
+ }
372
+ scr_dyn_this_pop();
373
+ free(snap);
374
+ }
375
+
376
+ void scr_net_fire_err(ScrNetLs *l, ScrStr *msg) { scr_net_fire_err_this(l, msg, NULL, 0); }
377
+
378
+ /* ── the handles ─────────────────────────────────────────────────────── */
379
+
380
+ enum { SCR_NET_K_SERVER = 1, SCR_NET_K_SOCKET = 2 };
381
+
382
+ struct ScrNetServer {
383
+ int kind; /* FIRST: poller udata routes on it */
384
+ size_t rc;
385
+ int fd; /* -1 while not listening */
386
+ int port;
387
+ bool listening;
388
+ bool closing; /* close() called; 'close' fires when conns drain */
389
+ bool close_emitted; /* settled: off the registry, listeners dropped */
390
+ bool emit_listening;
391
+ bool defer_conn; /* TLS: 'connection' fires post-handshake, not at accept */
392
+ bool bound_v6; /* the bound family (address()'s 'IPv6'/'IPv4' split) */
393
+ ScrStr *bound_host; /* the explicit bind host (NULL = the host-less any:
394
+ * '::' when bound_v6, else '0.0.0.0' — Node's own
395
+ * address().address answers) */
396
+ ScrStr *pending_err; /* deferred listen failure */
397
+ size_t nconns;
398
+ ScrNetLs conn_ls, err_ls, close_ls, listening_cbs;
399
+ /* protocol layer (scr_http.c): a C-level connection consumer + its
400
+ * context, freed with the handle */
401
+ ScrNetNativeConnFn native_conn;
402
+ void *native_ctx;
403
+ void (*native_ctx_free)(void *);
404
+ /* the HTTP-parser ctx ALIAS (borrowed — owned through the native-conn
405
+ * chain): late 'request' installs reach it even after a TLS wrap
406
+ * replaced native_ctx (the https/http2 servers). NULL = no parser. */
407
+ void *http_ctx;
408
+ /* The protocol layer's settle hook (scr_http.c): drops the ctx's OWN
409
+ * listener lists when the server settles — the settle-releases-
410
+ * listeners story extended across the layer seam. A request handler
411
+ * capturing its own server through a dyn binding box would otherwise
412
+ * cycle through edges the collector cannot trace (box → dyn → handle →
413
+ * ctx → closure → box). Takes http_ctx; NULL = no hook. */
414
+ void (*proto_settle)(void *http_ctx);
415
+ /* wrapper.close = fn — the portless close-proxy idiom: when set,
416
+ * server.close() invokes THIS closure (a compiler-emitted zero-arg
417
+ * wrapper around the user override) instead of closing; the override
418
+ * body reaches the real close through its bound `origClose` value
419
+ * (scr_net_server_close_direct — never re-consults the override). */
420
+ ScrClosure *close_override;
421
+ /* close-REQUEST order (a global stamp set by close_direct): drained
422
+ * servers settle in ascending order, matching Node's emission order
423
+ * when several close in one turn (the wrapper-closes-inner idiom). */
424
+ size_t close_seq;
425
+ bool in_registry;
426
+ struct ScrNetServer *next;
427
+ };
428
+
429
+ struct ScrNetSocket {
430
+ int kind; /* FIRST: poller udata routes on it */
431
+ size_t rc;
432
+ int fd; /* -1 once closed */
433
+ bool connecting;
434
+ bool rd_eof; /* peer FIN seen (or read half abandoned) */
435
+ bool enc_utf8; /* setEncoding('utf8'): 'data' delivers strings */
436
+ bool wr_ending; /* end() called: FIN when the buffer drains */
437
+ bool wr_done; /* FIN sent (or write half dead) */
438
+ bool had_error;
439
+ bool emit_close; /* fd closed; 'close' fires at the next sweep */
440
+ bool close_emitted; /* settled */
441
+ bool read_armed, write_armed;
442
+ /* write buffer: [whead, wlen) of wbuf is unsent */
443
+ char *wbuf;
444
+ size_t whead, wlen, wcap;
445
+ /* receive buffer: [rhead, rlen) of rbuf is arrived-but-unconsumed —
446
+ * fed by paused-mode reads ('readable' consumers) and unshift; drained
447
+ * FIRST by every consumer (data listeners, the parser attach, the TLS
448
+ * bio) so a peeked byte re-enters the stream ahead of the kernel's */
449
+ char *rbuf;
450
+ size_t rhead, rlen, rcap;
451
+ ScrNetSocket *pipe_dst; /* +1; src EOF end()s the dst, Node's pipe default */
452
+ /* peer identity for Node-shaped connect error messages */
453
+ char peer_ip[64];
454
+ int peer_port;
455
+ /* remoteAddress, cached at first read like Node's _getpeername — a
456
+ * value read while connected survives destroy; never-read sockets
457
+ * answer undefined after close */
458
+ ScrStr *remote_cache;
459
+ ScrStr *pending_err; /* deferred connect failure */
460
+ /* the caller-lookup dial (net.connect with a lookup option): true while
461
+ * the lookup decides; dial_ips holds its answered addresses (+1 each),
462
+ * dialed in order — a connect failure closes the fd and tries
463
+ * dial_ips[dial_i]; the LAST failure's message surfaces (Node's
464
+ * autoSelectFamily aggregate is the documented divergence) */
465
+ bool lookup_wait;
466
+ ScrStr **dial_ips;
467
+ size_t dial_n, dial_i;
468
+ /* the in-flight dial chain's LAST failure — surfaced (moved into
469
+ * pending_err) only when the list exhausts; a later success drops it */
470
+ ScrStr *dial_err;
471
+ ScrNetLs data_ls, end_ls, close_ls, err_ls, conn_ls, timeout_ls, readable_ls;
472
+ /* a server whose 'connection' waits for this socket's handshake — the
473
+ * TLS defer target (+1; the socket's own `server` backref stays the
474
+ * ACCEPTING server, which may be the demux wrapper) */
475
+ ScrNetServer *conn_pending;
476
+ /* the idle timer (setTimeout): a one-shot poller timer keyed by this
477
+ * socket's pointer, re-armed on every read/write/connect activity —
478
+ * 'timeout' fires once per idle period and never destroys the socket,
479
+ * Node's semantics */
480
+ double timeout_ms;
481
+ bool timeout_armed;
482
+ ScrNetServer *server; /* +1 backref (accepted sockets), NULL for clients */
483
+ /* protocol layer (scr_http.c): a C-level reader — counts as a consumer
484
+ * for read-arming; its context BORROWS this socket and is freed with it */
485
+ ScrNetNativeDataFn native_data;
486
+ ScrNetNativeEventFn native_eof;
487
+ ScrNetNativeEventFn native_closed;
488
+ ScrNetNativeEventFn native_timeout;
489
+ ScrNetNativeEventFn native_established; /* client connect completed (the
490
+ * h2 session's 'connect' moment;
491
+ * fires before conn_ls) */
492
+ ScrNetNativeErrFn native_err; /* true = consumed (the protocol layer owns
493
+ * the error story); false falls through to
494
+ * this socket's own err_ls */
495
+ void *native_ctx;
496
+ void (*native_ctx_free)(void *);
497
+ /* transport layer (scr_tls.c): reads/writes redirect through the ops
498
+ * once set; writes buffer and readiness drives the handshake until
499
+ * t_est. Sits BELOW the protocol layer — an https socket carries both
500
+ * (the parser in native_*, the TLS engine here). */
501
+ const ScrNetTransportOps *tops;
502
+ void *tctx;
503
+ bool t_est;
504
+ bool t_want_write; /* a handshake send hit EAGAIN: arm the write filter */
505
+ bool in_registry;
506
+ struct ScrNetSocket *next;
507
+ };
508
+
509
+ #ifdef SCR_RC_AUDIT
510
+ static long scr_net_live = 0;
511
+ long scr_net_live_count(void) { return scr_net_live; }
512
+ #endif
513
+
514
+ static ScrNetServer *scr_net_servers = NULL; /* registry: +1 each, tail-appended */
515
+ static ScrNetSocket *scr_net_socks = NULL;
516
+ static ScrPoller *scr_net_poller = NULL;
517
+
518
+ /* ── RC ──────────────────────────────────────────────────────────────── */
519
+
520
+ static void scr_net_close_fd_raw(int fd); /* forget-then-close, defined below */
521
+
522
+ ScrNetServer *scr_net_server_retain(ScrNetServer *s) {
523
+ if (s->rc != SIZE_MAX) s->rc++;
524
+ return s;
525
+ }
526
+
527
+ void scr_net_server_release(ScrNetServer *s) {
528
+ if (!s || s->rc == SIZE_MAX) return;
529
+ if (--s->rc == 0) {
530
+ if (s->native_ctx_free) s->native_ctx_free(s->native_ctx);
531
+ scr_net_ls_drop(&s->conn_ls);
532
+ scr_net_ls_drop(&s->err_ls);
533
+ scr_net_ls_drop(&s->close_ls);
534
+ scr_net_ls_drop(&s->listening_cbs);
535
+ scr_closure_release(s->close_override);
536
+ scr_str_release(s->bound_host);
537
+ scr_str_release(s->pending_err);
538
+ if (s->fd >= 0) scr_net_close_fd_raw(s->fd);
539
+ #ifdef SCR_RC_AUDIT
540
+ scr_net_live--;
541
+ #endif
542
+ free(s);
543
+ }
544
+ }
545
+
546
+ void *scr_net_server_retain_v(void *p) { return scr_net_server_retain((ScrNetServer *)p); }
547
+ void scr_net_server_release_v(void *p) { scr_net_server_release((ScrNetServer *)p); }
548
+
549
+ ScrNetSocket *scr_net_sock_retain(ScrNetSocket *s) {
550
+ if (s->rc != SIZE_MAX) s->rc++;
551
+ return s;
552
+ }
553
+
554
+ void scr_net_sock_release(ScrNetSocket *s) {
555
+ if (!s || s->rc == SIZE_MAX) return;
556
+ if (--s->rc == 0) {
557
+ if (s->native_ctx_free) s->native_ctx_free(s->native_ctx);
558
+ if (s->tops && s->tops->free) s->tops->free(s->tctx);
559
+ scr_net_ls_drop(&s->data_ls);
560
+ scr_net_ls_drop(&s->end_ls);
561
+ scr_net_ls_drop(&s->close_ls);
562
+ scr_net_ls_drop(&s->err_ls);
563
+ scr_net_ls_drop(&s->conn_ls);
564
+ scr_net_ls_drop(&s->timeout_ls);
565
+ scr_net_ls_drop(&s->readable_ls);
566
+ scr_str_release(s->pending_err);
567
+ scr_str_release(s->remote_cache);
568
+ for (size_t i = 0; i < s->dial_n; i++) scr_str_release(s->dial_ips[i]);
569
+ free(s->dial_ips);
570
+ scr_str_release(s->dial_err);
571
+ free(s->wbuf);
572
+ free(s->rbuf);
573
+ if (s->pipe_dst) scr_net_sock_release(s->pipe_dst);
574
+ if (s->conn_pending) scr_net_server_release(s->conn_pending);
575
+ if (s->server) scr_net_server_release(s->server);
576
+ if (s->fd >= 0) scr_net_close_fd_raw(s->fd);
577
+ #ifdef SCR_RC_AUDIT
578
+ scr_net_live--;
579
+ #endif
580
+ free(s);
581
+ }
582
+ }
583
+
584
+ void *scr_net_sock_retain_v(void *p) { return scr_net_sock_retain((ScrNetSocket *)p); }
585
+ void scr_net_sock_release_v(void *p) { scr_net_sock_release((ScrNetSocket *)p); }
586
+
587
+ /* ── poller plumbing (the scr_platform.h seam) ───────────────────────── */
588
+
589
+ static bool scr_net_poller_init(void) {
590
+ if (scr_net_poller != NULL) return true;
591
+ scr_net_poller = scrp_poller_new();
592
+ return scr_net_poller != NULL;
593
+ }
594
+
595
+ static void scr_net_nonblock(int fd) {
596
+ #ifdef _WIN32
597
+ u_long one = 1;
598
+ ioctlsocket((SOCKET)fd, FIONBIO, &one);
599
+ /* FD_CLOEXEC's spelling: spawned children must not inherit the socket */
600
+ SetHandleInformation((HANDLE)(SOCKET)fd, HANDLE_FLAG_INHERIT, 0);
601
+ #else
602
+ fcntl(fd, F_SETFL, O_NONBLOCK);
603
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
604
+ #ifdef SO_NOSIGPIPE
605
+ int one = 1;
606
+ setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one);
607
+ #endif
608
+ #endif
609
+ }
610
+
611
+ /* SO_REUSEADDR on listeners, POSIX only — libuv's exact split. On Unix
612
+ * the flag lets a restarted server rebind its TIME_WAIT port (Node's
613
+ * behavior, so the historical call stands). On Windows the SAME flag
614
+ * means port hijacking: a second bind of an actively-listened port
615
+ * SUCCEEDS and so does its listen(), so EADDRINUSE never surfaces — the
616
+ * net-port-check/net-error-codes fixtures hang waiting for an 'error'
617
+ * that never comes. Winsock's default already permits the TIME_WAIT
618
+ * rebind, so omitting the flag there loses nothing and restores Node's
619
+ * (libuv's) EADDRINUSE. */
620
+ static void scr_net_listen_reuseaddr(int fd) {
621
+ #ifdef _WIN32
622
+ (void)fd;
623
+ #else
624
+ int one = 1;
625
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
626
+ #endif
627
+ }
628
+
629
+ /* write(2) to a socket with SIGPIPE suppressed. macOS sets SO_NOSIGPIPE
630
+ * per fd above (send() path unchanged: write, exactly the historical
631
+ * call); Linux has no SO_NOSIGPIPE — MSG_NOSIGNAL on each send is the
632
+ * per-socket equivalent, turning a closed-peer write into EPIPE for the
633
+ * deferred 'error' sweep instead of process death. */
634
+ static ssize_t scr_net_send(int fd, const void *buf, size_t len) {
635
+ #if defined(_WIN32)
636
+ /* No SIGPIPE exists on Windows: a closed-peer send fails outright
637
+ * (WSAECONNRESET/WSAESHUTDOWN, mapped) for the deferred 'error' sweep. */
638
+ int n = send((SOCKET)fd, (const char *)buf, len > 0x40000000 ? 0x40000000 : (int)len, 0);
639
+ if (n < 0) scr_w32_seterr();
640
+ return n;
641
+ #elif defined(MSG_NOSIGNAL)
642
+ return send(fd, buf, len, MSG_NOSIGNAL);
643
+ #else
644
+ return write(fd, buf, len);
645
+ #endif
646
+ }
647
+
648
+ /* The idle timer rides the same poller: one-shot, keyed by the socket
649
+ * pointer (kqueue: EVFILT_TIMER ident; epoll: a per-key timerfd), in
650
+ * milliseconds. Re-arming an armed key replaces the deadline. */
651
+ static void scr_net_sock_timer_arm(ScrNetSocket *s) {
652
+ if (scr_net_poller == NULL || s->fd < 0 || s->timeout_ms <= 0) return;
653
+ (void)scrp_timer_arm(scr_net_poller, s, s->timeout_ms, s);
654
+ s->timeout_armed = true;
655
+ }
656
+
657
+ static void scr_net_sock_timer_cancel(ScrNetSocket *s) {
658
+ if (!s->timeout_armed) return;
659
+ s->timeout_armed = false;
660
+ if (scr_net_poller == NULL) return;
661
+ scrp_timer_cancel(scr_net_poller, s); /* may already have fired */
662
+ }
663
+
664
+ /* Activity: every read/write/connect resets the idle clock (re-arming an
665
+ * existing timer replaces its deadline). */
666
+ static void scr_net_sock_touch(ScrNetSocket *s) {
667
+ if (s->timeout_ms > 0 && s->fd >= 0) scr_net_sock_timer_arm(s);
668
+ }
669
+
670
+ /* Drop a watched fd's registrations and close it — forget MUST precede
671
+ * close(2) under epoll (see scr_platform.h; the kqueue forget is a
672
+ * no-op). Every close of a possibly-watched fd in this unit goes through
673
+ * here. */
674
+ static void scr_net_close_fd_raw(int fd) {
675
+ if (fd < 0) return;
676
+ if (scr_net_poller != NULL) scrp_forget(scr_net_poller, fd);
677
+ close(fd);
678
+ }
679
+
680
+ /* NULL-poller-safe watch wrappers (the historical scr_net_filter guards);
681
+ * registration failures are ignored exactly as kevent's always were. */
682
+ static void scr_net_watch_read(int fd, void *udata, bool on) {
683
+ if (scr_net_poller == NULL) return;
684
+ (void)scrp_watch_read(scr_net_poller, fd, udata, on);
685
+ }
686
+
687
+ static void scr_net_watch_write(int fd, void *udata, bool on) {
688
+ if (scr_net_poller == NULL) return;
689
+ (void)scrp_watch_write(scr_net_poller, fd, udata, on);
690
+ }
691
+
692
+ /* ── registries ──────────────────────────────────────────────────────── */
693
+
694
+ static void scr_net_server_register(ScrNetServer *s) {
695
+ if (s->in_registry) return;
696
+ s->in_registry = true;
697
+ s->next = NULL;
698
+ ScrNetServer **link = &scr_net_servers;
699
+ while (*link) link = &(*link)->next;
700
+ *link = scr_net_server_retain(s);
701
+ }
702
+
703
+ static void scr_net_server_unregister(ScrNetServer *s) {
704
+ if (!s->in_registry) return;
705
+ ScrNetServer **link = &scr_net_servers;
706
+ while (*link && *link != s) link = &(*link)->next;
707
+ if (*link) {
708
+ *link = s->next;
709
+ s->next = NULL;
710
+ s->in_registry = false;
711
+ scr_net_server_release(s);
712
+ }
713
+ }
714
+
715
+ static void scr_net_sock_register(ScrNetSocket *s) {
716
+ if (s->in_registry) return;
717
+ s->in_registry = true;
718
+ s->next = NULL;
719
+ ScrNetSocket **link = &scr_net_socks;
720
+ while (*link) link = &(*link)->next;
721
+ *link = scr_net_sock_retain(s);
722
+ }
723
+
724
+ static void scr_net_sock_unregister(ScrNetSocket *s) {
725
+ if (!s->in_registry) return;
726
+ ScrNetSocket **link = &scr_net_socks;
727
+ while (*link && *link != s) link = &(*link)->next;
728
+ if (*link) {
729
+ *link = s->next;
730
+ s->next = NULL;
731
+ s->in_registry = false;
732
+ scr_net_sock_release(s);
733
+ }
734
+ }
735
+
736
+ /* ── socket state machine ────────────────────────────────────────────── */
737
+
738
+ static ScrNetSocket *scr_net_sock_new(void) {
739
+ ScrNetSocket *s = calloc(1, sizeof *s);
740
+ if (!s) scr_net_oom();
741
+ s->kind = SCR_NET_K_SOCKET;
742
+ s->rc = 1;
743
+ s->fd = -1;
744
+ #ifdef SCR_RC_AUDIT
745
+ scr_net_live++;
746
+ #endif
747
+ return s;
748
+ }
749
+
750
+ /* Consumer-driven read arming (the stdin discipline: never read a byte
751
+ * nobody consumes — the socket buffer is the backpressure). */
752
+ static void scr_net_sock_update_read(ScrNetSocket *s) {
753
+ bool want = s->fd >= 0 && !s->connecting && !s->rd_eof &&
754
+ (s->data_ls.n > 0 || s->pipe_dst != NULL || s->native_data != NULL ||
755
+ s->readable_ls.n > 0 /* paused-mode consumer: bytes buffer */ ||
756
+ s->tops != NULL /* a transport is ALWAYS a consumer: the TLS
757
+ * layer must process protocol frames (the
758
+ * handshake, close_notify, 1.3 tickets)
759
+ * even when no app consumer exists, and a
760
+ * peer's FIN must reach the teardown —
761
+ * decrypted app data buffers like the
762
+ * paused-mode path's */ ||
763
+ s->wr_done /* FIN sent: drain to EOF so the peer's close is
764
+ * seen even with no consumer — Node resumes a
765
+ * finished socket the same way */);
766
+ if (want && !s->read_armed) {
767
+ scr_net_watch_read(s->fd, s, true);
768
+ s->read_armed = true;
769
+ } else if (!want && s->read_armed) {
770
+ scr_net_watch_read(s->fd, s, false);
771
+ s->read_armed = false;
772
+ }
773
+ }
774
+
775
+ static void scr_net_sock_update_write(ScrNetSocket *s) {
776
+ bool want = s->fd >= 0 && (s->connecting || s->wlen > s->whead || s->t_want_write);
777
+ if (want && !s->write_armed) {
778
+ scr_net_watch_write(s->fd, s, true);
779
+ s->write_armed = true;
780
+ } else if (!want && s->write_armed) {
781
+ scr_net_watch_write(s->fd, s, false);
782
+ s->write_armed = false;
783
+ }
784
+ }
785
+
786
+ /* Close the fd (registrations dropped first — the epoll obligation) and
787
+ * flag the 'close' sweep. */
788
+ static void scr_net_sock_close_fd(ScrNetSocket *s) {
789
+ scr_net_sock_timer_cancel(s);
790
+ if (s->fd >= 0) {
791
+ scr_net_close_fd_raw(s->fd);
792
+ s->fd = -1;
793
+ s->read_armed = s->write_armed = false;
794
+ }
795
+ if (!s->close_emitted) s->emit_close = true;
796
+ }
797
+
798
+ /* The FIN half: called when the write half is ENDING and the buffer just
799
+ * drained (or was empty). Full close once the read half is done too. */
800
+ static void scr_net_sock_maybe_finish_write(ScrNetSocket *s) {
801
+ if (s->fd < 0 || !s->wr_ending || s->wr_done || s->wlen > s->whead) return;
802
+ /* a transport says goodbye first (TLS close_notify), best-effort */
803
+ if (s->tops && s->t_est) s->tops->shutdown_write(s->tctx);
804
+ shutdown(s->fd, SHUT_WR);
805
+ s->wr_done = true;
806
+ if (s->rd_eof) scr_net_sock_close_fd(s);
807
+ else scr_net_sock_update_read(s); /* the drain-to-EOF mode arms here */
808
+ }
809
+
810
+ /* Append bytes to the write buffer (post-immediate-write remainder, or
811
+ * everything while connecting/buffered). */
812
+ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) {
813
+ if (len == 0) return;
814
+ /* compact the consumed head first */
815
+ if (s->whead > 0) {
816
+ memmove(s->wbuf, s->wbuf + s->whead, s->wlen - s->whead);
817
+ s->wlen -= s->whead;
818
+ s->whead = 0;
819
+ }
820
+ if (s->wlen + len > s->wcap) {
821
+ size_t cap = s->wcap ? s->wcap : 4096;
822
+ while (cap < s->wlen + len) cap *= 2;
823
+ s->wbuf = realloc(s->wbuf, cap);
824
+ if (!s->wbuf) scr_net_oom();
825
+ s->wcap = cap;
826
+ }
827
+ memcpy(s->wbuf + s->wlen, data, len);
828
+ s->wlen += len;
829
+ }
830
+
831
+ /* The write path shared by write()/end(data)/pipe delivery. Ignored on a
832
+ * dead or ended write half (Node fires 'error' there; this slice drops —
833
+ * SEMANTICS.md documents the bound). */
834
+ static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len) {
835
+ if (s->fd < 0 || s->wr_ending || s->close_emitted || len == 0) return;
836
+ if (s->connecting || s->wlen > s->whead || (s->tops && !s->t_est)) {
837
+ /* mid-connect and mid-handshake bytes buffer; the flush after
838
+ * 'connect'/establishment sends them */
839
+ scr_net_sock_buffer(s, data, len);
840
+ scr_net_sock_update_write(s);
841
+ return;
842
+ }
843
+ ssize_t sent = s->tops ? s->tops->xwrite(s->tctx, data, len) : scr_net_send(s->fd, data, len);
844
+ if (sent > 0) scr_net_sock_touch(s);
845
+ if (sent < 0) {
846
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) sent = 0;
847
+ else {
848
+ /* Delivery failure (EPIPE/ECONNRESET): defer to the error sweep. */
849
+ char msg[96];
850
+ snprintf(msg, sizeof msg, "write %s", scr_net_errname(errno));
851
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
852
+ s->had_error = true;
853
+ scr_net_sock_close_fd(s);
854
+ return;
855
+ }
856
+ }
857
+ if ((size_t)sent < len) {
858
+ scr_net_sock_buffer(s, data + sent, len - (size_t)sent);
859
+ scr_net_sock_update_write(s);
860
+ }
861
+ }
862
+
863
+ /* Flush on write-readiness; drives the FIN when ENDING. */
864
+ static void scr_net_sock_flush(ScrNetSocket *s) {
865
+ if (s->tops && !s->t_est) return; /* nothing moves until the handshake ends */
866
+ while (s->fd >= 0 && s->wlen > s->whead) {
867
+ ssize_t sent = s->tops ? s->tops->xwrite(s->tctx, s->wbuf + s->whead, s->wlen - s->whead)
868
+ : scr_net_send(s->fd, s->wbuf + s->whead, s->wlen - s->whead);
869
+ if (sent < 0) {
870
+ if (errno == EINTR) continue;
871
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return;
872
+ char msg[96];
873
+ snprintf(msg, sizeof msg, "write %s", scr_net_errname(errno));
874
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
875
+ s->had_error = true;
876
+ scr_net_sock_close_fd(s);
877
+ return;
878
+ }
879
+ if (sent > 0) scr_net_sock_touch(s);
880
+ s->whead += (size_t)sent;
881
+ }
882
+ if (s->wlen == s->whead) {
883
+ s->whead = s->wlen = 0;
884
+ scr_net_sock_update_write(s);
885
+ scr_net_sock_maybe_finish_write(s);
886
+ }
887
+ }
888
+
889
+ /* EOF: 'end' fires now (directly — Node's poll-phase emit), then the
890
+ * write half ends (allowHalfOpen false), and the fd closes once the FIN
891
+ * is out. 'close' waits for the sweep. */
892
+ static void scr_net_sock_eof(ScrNetSocket *s) {
893
+ s->rd_eof = true;
894
+ scr_net_sock_update_read(s);
895
+ if (s->native_eof) {
896
+ s->native_eof(s->native_ctx);
897
+ if (scr_exc_pending()) return;
898
+ }
899
+ if (s->pipe_dst) {
900
+ /* pipe's end-the-destination default */
901
+ ScrNetSocket *dst = s->pipe_dst;
902
+ if (!dst->wr_ending && dst->fd >= 0) {
903
+ dst->wr_ending = true;
904
+ scr_net_sock_maybe_finish_write(dst);
905
+ }
906
+ }
907
+ scr_net_fire0_this(&s->end_ls, s, SCR_DYNH_NET_SOCKET);
908
+ if (scr_exc_pending()) return;
909
+ if (!s->wr_ending) {
910
+ s->wr_ending = true;
911
+ scr_net_sock_maybe_finish_write(s);
912
+ }
913
+ if (s->wr_done) scr_net_sock_close_fd(s);
914
+ }
915
+
916
+ /* Deliver one arrived chunk to the socket's consumers (the protocol
917
+ * layer, the pipe destination, the 'data' listeners). Returns false when
918
+ * a callback threw (the caller bails; the loop surfaces it). */
919
+ static bool scr_net_sock_deliver(ScrNetSocket *s, const char *buf, size_t n) {
920
+ if (s->native_data) {
921
+ s->native_data(s->native_ctx, buf, n);
922
+ if (scr_exc_pending()) return false;
923
+ /* the protocol layer OWNS this chunk exclusively — an upgrade
924
+ * handover inside the call (native reader cleared, 'data' listeners
925
+ * registered) must not re-deliver bytes the parser consumed; the
926
+ * next chunk takes the listener path */
927
+ return true;
928
+ }
929
+ if (s->pipe_dst) scr_net_sock_write_raw(s->pipe_dst, buf, n);
930
+ if (s->data_ls.n > 0) {
931
+ ScrBytes *chunk = scr_bytes_new(SCR_BYTES_U8, (double)n);
932
+ memcpy(chunk->data, buf, n);
933
+ ScrNetL *snap;
934
+ size_t nl = scr_net_ls_snapshot(&s->data_ls, &snap);
935
+ scr_dyn_this_push(s, SCR_DYNH_NET_SOCKET);
936
+ scr_dyn_chunk_enc(s->enc_utf8);
937
+ for (size_t i = 0; i < nl; i++) {
938
+ if (!scr_exc_pending()) ((ScrNetDataFn)snap[i].fn)(snap[i].cb, chunk);
939
+ scr_closure_release(snap[i].cb);
940
+ }
941
+ scr_dyn_chunk_enc(false);
942
+ scr_dyn_this_pop();
943
+ free(snap);
944
+ scr_bytes_release(chunk);
945
+ if (scr_exc_pending()) return false;
946
+ }
947
+ return true;
948
+ }
949
+
950
+ /* One readable wake: read until EAGAIN/EOF, one 'data' emit per read(2)
951
+ * chunk (Node's chunking is arrival-driven too — only the reassembled
952
+ * bytes are contractual, the stdin stance). Peeked/unshifted bytes in
953
+ * the receive buffer deliver first — they precede the kernel's in the
954
+ * stream. A TLS socket drains those through the engine's bio instead. */
955
+ static void scr_net_sock_append_rbuf(ScrNetSocket *s, const char *data, size_t len);
956
+ static void scr_net_sock_transport_pump(ScrNetSocket *s);
957
+
958
+ static void scr_net_sock_read(ScrNetSocket *s) {
959
+ if (s->tops && !s->t_est) return; /* readiness feeds the handshake pump instead */
960
+ for (;;) {
961
+ if (s->fd < 0) return;
962
+ bool flowing = s->data_ls.n > 0 || s->pipe_dst || s->native_data;
963
+ /* buffered (peeked/unshifted) bytes first — they precede the
964
+ * kernel's in the stream; a TLS engine drains them through its bio */
965
+ if (!s->tops && flowing && s->rlen > s->rhead) {
966
+ char buf[65536];
967
+ size_t take = scr_net_sock_take_buffered(s, buf, sizeof buf);
968
+ if (!scr_net_sock_deliver(s, buf, take)) return;
969
+ scr_net_sock_update_read(s);
970
+ continue;
971
+ }
972
+ if (s->rd_eof || (!flowing && s->readable_ls.n == 0 && !s->wr_done &&
973
+ !(s->tops != NULL && s->t_est))) {
974
+ /* No consumer: stay paused — except through an ESTABLISHED
975
+ * transport, which must keep processing protocol frames (close
976
+ * alerts, 1.3 tickets) so a peer's close reaches the teardown
977
+ * (the handler-less TLS server whose client hangs up). */
978
+ return;
979
+ }
980
+ char buf[65536];
981
+ ssize_t n = s->tops ? s->tops->xread(s->tctx, buf, sizeof buf) : read(s->fd, buf, sizeof buf);
982
+ if (n < 0) {
983
+ if (errno == EINTR) continue;
984
+ if (errno == EAGAIN || errno == EWOULDBLOCK) return;
985
+ char msg[96];
986
+ snprintf(msg, sizeof msg, "read %s", scr_net_errname(errno));
987
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
988
+ s->had_error = true;
989
+ scr_net_sock_close_fd(s);
990
+ return;
991
+ }
992
+ if (n == 0) {
993
+ /* EOF announces 'readable' first (Node: a read() there answers
994
+ * null — the demux's dead-peer arm), then 'end' and the teardown */
995
+ scr_net_fire0_this(&s->readable_ls, s, SCR_DYNH_NET_SOCKET);
996
+ if (scr_exc_pending()) return;
997
+ scr_net_sock_eof(s);
998
+ return;
999
+ }
1000
+ scr_net_sock_touch(s);
1001
+ if (flowing) {
1002
+ if (!scr_net_sock_deliver(s, buf, (size_t)n)) return;
1003
+ } else if (s->readable_ls.n == 0) {
1004
+ /* the drain-to-EOF modes (wr_done, or an established transport
1005
+ * with no consumer): bytes arriving with nobody listening drop —
1006
+ * Node's resumed-but-unconsumed stream for the FIN-sent arm; for
1007
+ * the transport arm the peer would have to send app data at a
1008
+ * handler-less server for the difference to observe */
1009
+ } else {
1010
+ /* paused mode ('readable' consumers only): buffer and announce —
1011
+ * the listener consumes via read()/unshift */
1012
+ scr_net_sock_append_rbuf(s, buf, (size_t)n);
1013
+ scr_net_fire0_this(&s->readable_ls, s, SCR_DYNH_NET_SOCKET);
1014
+ if (scr_exc_pending()) return;
1015
+ if (s->tops && !s->t_est) {
1016
+ /* the listener routed this socket into a TLS server: the peeked
1017
+ * bytes now belong to the engine, and the poller may never signal
1018
+ * again if the whole flight is already buffered — pump NOW */
1019
+ scr_net_sock_transport_pump(s);
1020
+ return;
1021
+ }
1022
+ }
1023
+ scr_net_sock_update_read(s); /* a once-listener may have been the last consumer */
1024
+ }
1025
+ }
1026
+
1027
+ static void scr_net_sock_transport_pump(ScrNetSocket *s);
1028
+
1029
+ static void scr_net_sock_dial_next(ScrNetSocket *s);
1030
+
1031
+ /* Connect completion (write-readiness while connecting). */
1032
+ static void scr_net_sock_connect_done(ScrNetSocket *s) {
1033
+ int err = 0;
1034
+ socklen_t len = sizeof err;
1035
+ if (getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &err, &len) != 0) err = errno;
1036
+ s->connecting = false;
1037
+ if (err != 0 && s->dial_i < s->dial_n) {
1038
+ /* a caller-lookup dial with addresses left: record the failure
1039
+ * (last-wins, surfaced only on exhaustion) and retry QUIETLY — no
1040
+ * 'error'/'close' until the list runs out */
1041
+ char msg[128];
1042
+ snprintf(msg, sizeof msg, "connect %s %s:%d", scr_net_errname(err), s->peer_ip,
1043
+ s->peer_port);
1044
+ scr_str_release(s->dial_err);
1045
+ s->dial_err = scr_str_new(msg, strlen(msg));
1046
+ scr_net_close_fd_raw(s->fd); /* registrations dropped, then the fd */
1047
+ s->fd = -1;
1048
+ s->read_armed = s->write_armed = false;
1049
+ scr_net_sock_dial_next(s);
1050
+ return;
1051
+ }
1052
+ if (err != 0) {
1053
+ char msg[128];
1054
+ snprintf(msg, sizeof msg, "connect %s %s:%d", scr_net_errname(err), s->peer_ip,
1055
+ s->peer_port);
1056
+ if (s->dial_n > 0) {
1057
+ /* the dial chain's LAST address failed asynchronously */
1058
+ scr_str_release(s->dial_err);
1059
+ s->dial_err = NULL;
1060
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
1061
+ } else if (!s->pending_err) {
1062
+ s->pending_err = scr_str_new(msg, strlen(msg));
1063
+ }
1064
+ s->had_error = true;
1065
+ scr_net_sock_close_fd(s);
1066
+ return;
1067
+ }
1068
+ if (s->dial_err) {
1069
+ /* an earlier address failed but THIS one connected: the recorded
1070
+ * failure never surfaces */
1071
+ scr_str_release(s->dial_err);
1072
+ s->dial_err = NULL;
1073
+ }
1074
+ scr_net_sock_update_write(s); /* keeps the filter iff bytes are buffered */
1075
+ scr_net_sock_update_read(s);
1076
+ scr_net_sock_touch(s);
1077
+ if (s->tops && !s->t_est) {
1078
+ /* TCP is up; the TLS handshake starts now. 'connect' listeners wait
1079
+ * for establishment (they registered as secureConnect) and buffered
1080
+ * bytes flush there too. */
1081
+ scr_net_sock_transport_pump(s);
1082
+ return;
1083
+ }
1084
+ if (s->native_established) {
1085
+ s->native_established(s->native_ctx);
1086
+ if (scr_exc_pending()) return;
1087
+ }
1088
+ scr_net_fire0_this(&s->conn_ls, s, SCR_DYNH_NET_SOCKET);
1089
+ if (scr_exc_pending()) return;
1090
+ scr_net_sock_flush(s);
1091
+ }
1092
+
1093
+ /* Drive the transport handshake off readiness until it settles. On
1094
+ * establishment: the protocol layer attaches (https' parser), a
1095
+ * deferred server 'connection' fires ('secureConnection' timing), the
1096
+ * socket's own 'connect' listeners fire (the client's secureConnect),
1097
+ * buffered writes flush, and any plaintext that rode in with the final
1098
+ * handshake flight delivers. Failure already tore the socket down via
1099
+ * scr_net_sock_transport_error. */
1100
+ static void scr_net_sock_transport_pump(ScrNetSocket *s) {
1101
+ if (!s->tops || s->t_est || s->fd < 0 || s->connecting) return;
1102
+ int r = s->tops->handshake(s->tctx);
1103
+ if (r == 0) {
1104
+ scr_net_sock_update_read(s);
1105
+ scr_net_sock_update_write(s);
1106
+ return;
1107
+ }
1108
+ if (r < 0) return; /* the transport reported the failure */
1109
+ s->t_est = true;
1110
+ scr_net_sock_touch(s);
1111
+ if (s->tops->on_established) {
1112
+ s->tops->on_established(s->tctx);
1113
+ if (scr_exc_pending()) return;
1114
+ }
1115
+ if (s->conn_pending) {
1116
+ /* the deferred 'connection' ('secureConnection' timing): the target
1117
+ * is the server this socket was routed INTO — the accepting server
1118
+ * for a plain TLS accept, the emit('connection') target after a
1119
+ * demux */
1120
+ ScrNetServer *target = s->conn_pending;
1121
+ s->conn_pending = NULL;
1122
+ if (!target->close_emitted) {
1123
+ ScrNetL *snap;
1124
+ size_t n = scr_net_ls_snapshot(&target->conn_ls, &snap);
1125
+ scr_dyn_this_push(target, SCR_DYNH_NET_SERVER);
1126
+ for (size_t i = 0; i < n; i++) {
1127
+ if (!scr_exc_pending()) ((ScrNetConnFn)snap[i].fn)(snap[i].cb, scr_net_sock_retain(s));
1128
+ scr_closure_release(snap[i].cb);
1129
+ }
1130
+ scr_dyn_this_pop();
1131
+ free(snap);
1132
+ }
1133
+ scr_net_server_release(target);
1134
+ if (scr_exc_pending()) return;
1135
+ }
1136
+ if (s->native_established) {
1137
+ s->native_established(s->native_ctx);
1138
+ if (scr_exc_pending()) return;
1139
+ }
1140
+ scr_net_fire0_this(&s->conn_ls, s, SCR_DYNH_NET_SOCKET);
1141
+ if (scr_exc_pending()) return;
1142
+ scr_net_sock_flush(s);
1143
+ scr_net_sock_update_read(s);
1144
+ scr_net_sock_update_write(s);
1145
+ scr_net_sock_read(s); /* plaintext buffered inside the engine, if any */
1146
+ }
1147
+
1148
+ /* ── the server ──────────────────────────────────────────────────────── */
1149
+
1150
+ static ScrNetServer *scr_net_server_new(void) {
1151
+ ScrNetServer *s = calloc(1, sizeof *s);
1152
+ if (!s) scr_net_oom();
1153
+ s->kind = SCR_NET_K_SERVER;
1154
+ s->rc = 1;
1155
+ s->fd = -1;
1156
+ #ifdef SCR_RC_AUDIT
1157
+ scr_net_live++;
1158
+ #endif
1159
+ return s;
1160
+ }
1161
+
1162
+ ScrNetServer *scr_net_create_server(ScrClosure *handler /*moves, nullable*/, ScrNetConnFn fn) {
1163
+ ScrNetServer *s = scr_net_server_new();
1164
+ if (handler) scr_net_ls_add(&s->conn_ls, handler, (void *)fn, false);
1165
+ return s;
1166
+ }
1167
+
1168
+ /* listen(port): bind + listen NOW (Node fails synchronously into an async
1169
+ * 'error'); success defers the listen callback ('listening') to the next
1170
+ * dispatch pass, Node's next-tick emit. Binds like Node's host-less
1171
+ * listen: IPv6 any with dual-stack when the kernel allows, IPv4 fallback. */
1172
+ void scr_net_listen(ScrNetServer *s, double port, ScrClosure *cb /*moves, nullable*/) {
1173
+ if (cb) scr_net_ls_add(&s->listening_cbs, cb, NULL, true);
1174
+ if (s->listening || s->close_emitted) return;
1175
+ if (!scr_net_poller_init()) {
1176
+ fputs("scriptc: event poller init failed\n", stderr);
1177
+ abort();
1178
+ }
1179
+ int p = (int)port;
1180
+ int fd = socket(AF_INET6, SOCK_STREAM, 0);
1181
+ bool v6 = fd >= 0;
1182
+ if (v6) {
1183
+ int off = 0;
1184
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof off);
1185
+ } else {
1186
+ fd = socket(AF_INET, SOCK_STREAM, 0);
1187
+ }
1188
+ if (fd < 0) {
1189
+ fputs("scriptc: socket() failed\n", stderr);
1190
+ abort();
1191
+ }
1192
+ scr_net_listen_reuseaddr(fd);
1193
+ scr_net_nonblock(fd);
1194
+ int rc;
1195
+ if (v6) {
1196
+ struct sockaddr_in6 addr;
1197
+ memset(&addr, 0, sizeof addr);
1198
+ addr.sin6_family = AF_INET6;
1199
+ addr.sin6_addr = in6addr_any;
1200
+ addr.sin6_port = htons((uint16_t)p);
1201
+ rc = bind(fd, (struct sockaddr *)&addr, sizeof addr);
1202
+ } else {
1203
+ struct sockaddr_in addr;
1204
+ memset(&addr, 0, sizeof addr);
1205
+ addr.sin_family = AF_INET;
1206
+ addr.sin_addr.s_addr = INADDR_ANY;
1207
+ addr.sin_port = htons((uint16_t)p);
1208
+ rc = bind(fd, (struct sockaddr *)&addr, sizeof addr);
1209
+ }
1210
+ if (rc == 0) rc = listen(fd, 511); /* Node's default backlog */
1211
+ if (rc != 0) {
1212
+ int err = errno;
1213
+ close(fd);
1214
+ char msg[128];
1215
+ /* Node: "listen EADDRINUSE: address already in use :::4000" */
1216
+ snprintf(msg, sizeof msg, "listen %s: %s %s:%d", scr_net_errname(err),
1217
+ err == EADDRINUSE ? "address already in use" : "permission denied",
1218
+ v6 ? "::" : "0.0.0.0", p);
1219
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
1220
+ scr_net_server_register(s); /* the sweep delivers the failure */
1221
+ return;
1222
+ }
1223
+ /* the real port (ephemeral listen(0) discovers it here) */
1224
+ struct sockaddr_storage bound;
1225
+ socklen_t blen = sizeof bound;
1226
+ if (getsockname(fd, (struct sockaddr *)&bound, &blen) == 0) {
1227
+ s->port = bound.ss_family == AF_INET6
1228
+ ? ntohs(((struct sockaddr_in6 *)&bound)->sin6_port)
1229
+ : ntohs(((struct sockaddr_in *)&bound)->sin_port);
1230
+ }
1231
+ s->fd = fd;
1232
+ s->listening = true;
1233
+ s->emit_listening = true;
1234
+ s->bound_v6 = v6;
1235
+ scr_net_watch_read(fd, s, true);
1236
+ scr_net_server_register(s);
1237
+ }
1238
+
1239
+ /* listen({ port, host, ipv6Only }) — the explicit-interface bind
1240
+ * (portless's listenOnProxyInterface): host is an IP literal ("" = the
1241
+ * host-less dual-stack default above; "localhost" pins to 127.0.0.1, the
1242
+ * connect-side divergence); ipv6Only sets IPV6_V6ONLY before the bind
1243
+ * (meaningful on v6 addresses — "::" with ipv6Only leaves v4 to its own
1244
+ * listener, exactly the portless pair). Failures are the async 'error'
1245
+ * with Node's listen message naming the requested host; a non-IP host is
1246
+ * the async getaddrinfo ENOTFOUND (no resolver in this slice). */
1247
+ void scr_net_listen_opts(ScrNetServer *s, double port, ScrStr *host /*borrowed*/,
1248
+ bool ipv6_only, ScrClosure *cb /*moves, nullable*/) {
1249
+ if (host == NULL || host->len == 0) {
1250
+ scr_net_listen(s, port, cb);
1251
+ return;
1252
+ }
1253
+ if (cb) scr_net_ls_add(&s->listening_cbs, cb, NULL, true);
1254
+ if (s->listening || s->close_emitted) return;
1255
+ if (!scr_net_poller_init()) {
1256
+ fputs("scriptc: event poller init failed\n", stderr);
1257
+ abort();
1258
+ }
1259
+ int p = (int)port;
1260
+ char h[64];
1261
+ snprintf(h, sizeof h, "%.*s", (int)(host->len < 63 ? host->len : 63), host->data);
1262
+ if (strcmp(h, "localhost") == 0) snprintf(h, sizeof h, "127.0.0.1");
1263
+ struct sockaddr_in a4;
1264
+ struct sockaddr_in6 a6;
1265
+ struct sockaddr *sa = NULL;
1266
+ socklen_t salen = 0;
1267
+ bool v6 = false;
1268
+ memset(&a4, 0, sizeof a4);
1269
+ memset(&a6, 0, sizeof a6);
1270
+ if (inet_pton(AF_INET, h, &a4.sin_addr) == 1) {
1271
+ a4.sin_family = AF_INET;
1272
+ a4.sin_port = htons((uint16_t)p);
1273
+ sa = (struct sockaddr *)&a4;
1274
+ salen = sizeof a4;
1275
+ } else if (inet_pton(AF_INET6, h, &a6.sin6_addr) == 1) {
1276
+ a6.sin6_family = AF_INET6;
1277
+ a6.sin6_port = htons((uint16_t)p);
1278
+ sa = (struct sockaddr *)&a6;
1279
+ salen = sizeof a6;
1280
+ v6 = true;
1281
+ }
1282
+ if (!sa) {
1283
+ char msg[160];
1284
+ snprintf(msg, sizeof msg, "getaddrinfo ENOTFOUND %s", h);
1285
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
1286
+ scr_net_server_register(s);
1287
+ return;
1288
+ }
1289
+ int fd = socket(sa->sa_family, SOCK_STREAM, 0);
1290
+ if (fd < 0) {
1291
+ fputs("scriptc: socket() failed\n", stderr);
1292
+ abort();
1293
+ }
1294
+ if (v6) {
1295
+ int only = ipv6_only ? 1 : 0;
1296
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &only, sizeof only);
1297
+ }
1298
+ scr_net_listen_reuseaddr(fd);
1299
+ scr_net_nonblock(fd);
1300
+ int rc = bind(fd, sa, salen);
1301
+ if (rc == 0) rc = listen(fd, 511); /* Node's default backlog */
1302
+ if (rc != 0) {
1303
+ int err = errno;
1304
+ close(fd);
1305
+ char msg[160];
1306
+ snprintf(msg, sizeof msg, "listen %s: %s %s:%d", scr_net_errname(err),
1307
+ err == EADDRINUSE ? "address already in use"
1308
+ : err == EADDRNOTAVAIL ? "address not available"
1309
+ : "permission denied",
1310
+ h, p);
1311
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
1312
+ scr_net_server_register(s);
1313
+ return;
1314
+ }
1315
+ struct sockaddr_storage bound;
1316
+ socklen_t blen = sizeof bound;
1317
+ if (getsockname(fd, (struct sockaddr *)&bound, &blen) == 0) {
1318
+ s->port = bound.ss_family == AF_INET6
1319
+ ? ntohs(((struct sockaddr_in6 *)&bound)->sin6_port)
1320
+ : ntohs(((struct sockaddr_in *)&bound)->sin_port);
1321
+ }
1322
+ s->fd = fd;
1323
+ s->listening = true;
1324
+ s->emit_listening = true;
1325
+ s->bound_v6 = v6;
1326
+ scr_str_release(s->bound_host);
1327
+ s->bound_host = scr_str_new(h, strlen(h));
1328
+ scr_net_watch_read(fd, s, true);
1329
+ scr_net_server_register(s);
1330
+ }
1331
+
1332
+ double scr_net_server_port(ScrNetServer *s) { return (double)s->port; }
1333
+
1334
+ /* address()'s other two fields — the bound host ('::'/'0.0.0.0' for the
1335
+ * host-less any, the normalized explicit host otherwise) and the family
1336
+ * string. Answer the any-form defaults before listen (Node answers null
1337
+ * there — the serverPort stance, port 0). */
1338
+ ScrStr *scr_net_server_addr_ip(ScrNetServer *s) {
1339
+ if (s->bound_host) return scr_str_retain(s->bound_host);
1340
+ return s->bound_v6 ? scr_str_new("::", 2) : scr_str_new("0.0.0.0", 7);
1341
+ }
1342
+
1343
+ ScrStr *scr_net_server_addr_family(ScrNetServer *s) {
1344
+ return s->bound_v6 ? scr_str_new("IPv6", 4) : scr_str_new("IPv4", 4);
1345
+ }
1346
+
1347
+ /* The REAL close — what the bound `origClose` value reaches (never
1348
+ * consults the override, so the portless proxy-through idiom cannot
1349
+ * recurse). */
1350
+ void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/) {
1351
+ if (s->close_emitted) {
1352
+ scr_closure_release(cb);
1353
+ return;
1354
+ }
1355
+ if (cb) scr_net_ls_add(&s->close_ls, cb, NULL, true);
1356
+ if (s->listening) {
1357
+ scr_net_close_fd_raw(s->fd); /* stops accepting NOW (registrations dropped first) */
1358
+ s->fd = -1;
1359
+ s->listening = false;
1360
+ }
1361
+ /* A close BEFORE the deferred 'listening' delivered cancels it — Node's
1362
+ * emitListeningNT checks the handle close() nulled, so the listen
1363
+ * callback never runs (test-net-listen-close-server's mustNotCall). */
1364
+ if (s->emit_listening) {
1365
+ s->emit_listening = false;
1366
+ scr_net_ls_drop(&s->listening_cbs);
1367
+ }
1368
+ if (!s->closing) {
1369
+ static size_t scr_net_close_seq = 0;
1370
+ s->close_seq = ++scr_net_close_seq;
1371
+ }
1372
+ s->closing = true; /* the sweep fires 'close' once nconns drains */
1373
+ }
1374
+
1375
+ void scr_net_server_close(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/) {
1376
+ if (s->close_override != NULL && !s->close_emitted) {
1377
+ /* The override runs INSTEAD of the close; a close callback still
1378
+ * fires when the override's origClose completes the close — Node's
1379
+ * behavior with the callback passed through. */
1380
+ if (cb) scr_net_ls_add(&s->close_ls, cb, NULL, true);
1381
+ ScrClosure *ov = scr_closure_retain(s->close_override);
1382
+ ((void (*)(ScrClosure *))ov->fn)(ov);
1383
+ scr_closure_release(ov);
1384
+ return;
1385
+ }
1386
+ scr_net_server_close_direct(s, cb);
1387
+ }
1388
+
1389
+ /* wrapper.close = fn: the override MOVES in (a compiler-emitted zero-arg
1390
+ * wrapper — see the struct comment); reassignment releases the old one,
1391
+ * matching JS's last-write-wins. */
1392
+ void scr_net_server_set_close_override(ScrNetServer *s, ScrClosure *ov /*moves*/) {
1393
+ scr_closure_release(s->close_override);
1394
+ s->close_override = ov;
1395
+ }
1396
+
1397
+ void scr_net_server_on_error(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn,
1398
+ bool once) {
1399
+ if (s->close_emitted) {
1400
+ scr_closure_release(cb);
1401
+ return;
1402
+ }
1403
+ scr_net_ls_add(&s->err_ls, cb, (void *)fn, once);
1404
+ }
1405
+
1406
+ void scr_net_server_on_close(ScrNetServer *s, ScrClosure *cb /*moves*/, bool once) {
1407
+ if (s->close_emitted) {
1408
+ scr_closure_release(cb);
1409
+ return;
1410
+ }
1411
+ scr_net_ls_add(&s->close_ls, cb, NULL, once);
1412
+ }
1413
+
1414
+ /* server.on('listening', cb): joins the deferred 'listening' list the
1415
+ * listen(port, cb) callback rides (the sweep fires it once after a
1416
+ * successful bind — Node's next-tick emit). A registration AFTER the
1417
+ * emit sits inert forever, exactly Node's once-per-listen event; a
1418
+ * settled (closed) server releases the closure unregistered. */
1419
+ void scr_net_server_on_listening(ScrNetServer *s, ScrClosure *cb /*moves*/, bool once) {
1420
+ if (s->close_emitted) {
1421
+ scr_closure_release(cb);
1422
+ return;
1423
+ }
1424
+ scr_net_ls_add(&s->listening_cbs, cb, NULL, once);
1425
+ }
1426
+
1427
+ /* 'secureConnection': a TLS server's 'connection' list already fires at
1428
+ * handshake completion (defer_conn) — Node's secureConnection timing; on
1429
+ * a plain server the event never fires in Node, so the registration is
1430
+ * released unread. */
1431
+ void scr_net_server_on_secure_connection(ScrNetServer *s, ScrClosure *cb /*moves*/,
1432
+ ScrNetConnFn fn, bool once) {
1433
+ if (!s->defer_conn) {
1434
+ scr_closure_release(cb);
1435
+ return;
1436
+ }
1437
+ scr_net_server_on_connection(s, cb, fn, once);
1438
+ }
1439
+
1440
+ void scr_net_server_on_connection(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrNetConnFn fn,
1441
+ bool once) {
1442
+ if (s->close_emitted) {
1443
+ scr_closure_release(cb);
1444
+ return;
1445
+ }
1446
+ scr_net_ls_add(&s->conn_ls, cb, (void *)fn, once);
1447
+ }
1448
+
1449
+ /* Accept until EAGAIN; each connection fires the 'connection' listeners
1450
+ * directly (the accepted socket passes +1 per listener via the adapter). */
1451
+ static void scr_net_server_accept(ScrNetServer *srv) {
1452
+ for (;;) {
1453
+ if (!srv->listening || srv->fd < 0) return;
1454
+ int fd = accept(srv->fd, NULL, NULL);
1455
+ if (fd < 0) {
1456
+ if (errno == EINTR) continue;
1457
+ return; /* EAGAIN or a transient failure: the next wake retries */
1458
+ }
1459
+ scr_net_nonblock(fd);
1460
+ int one = 1;
1461
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
1462
+ ScrNetSocket *sock = scr_net_sock_new();
1463
+ sock->fd = fd;
1464
+ sock->server = scr_net_server_retain(srv);
1465
+ srv->nconns++;
1466
+ scr_net_sock_register(sock);
1467
+ if (srv->native_conn) {
1468
+ /* the protocol layer (scr_http.c / scr_tls.c) claims the connection */
1469
+ srv->native_conn(srv->native_ctx, sock);
1470
+ if (scr_exc_pending()) return;
1471
+ }
1472
+ if (srv->defer_conn) {
1473
+ /* 'connection' waits for the handshake (secureConnection timing) */
1474
+ sock->conn_pending = scr_net_server_retain(srv);
1475
+ }
1476
+ if (!srv->defer_conn) {
1477
+ ScrNetL *snap;
1478
+ size_t n = scr_net_ls_snapshot(&srv->conn_ls, &snap);
1479
+ scr_dyn_this_push(srv, SCR_DYNH_NET_SERVER);
1480
+ for (size_t i = 0; i < n; i++) {
1481
+ if (!scr_exc_pending()) ((ScrNetConnFn)snap[i].fn)(snap[i].cb, scr_net_sock_retain(sock));
1482
+ scr_closure_release(snap[i].cb);
1483
+ }
1484
+ scr_dyn_this_pop();
1485
+ free(snap);
1486
+ }
1487
+ scr_net_sock_release(sock); /* registry + server-count refs remain */
1488
+ if (scr_exc_pending()) return;
1489
+ }
1490
+ }
1491
+
1492
+ /* ── the client ──────────────────────────────────────────────────────── */
1493
+
1494
+ /* connect(port, host): numeric IPv4/IPv6 hosts and "localhost" (pinned to
1495
+ * 127.0.0.1 — the documented divergence from Node's family autoselect).
1496
+ * The connect callback registers as once('connect'). Never throws: every
1497
+ * failure is the async 'error' event, like Node. */
1498
+ /* Blocking first-answer hostname resolution for the CLIENT bridges (the
1499
+ * native fetch, the island's http/https client) — the dns.lookup
1500
+ * precedent (scr_dgram.c): getaddrinfo runs AT CALL TIME, first answer
1501
+ * wins. Returns +1: the resolved IP string, or a RETAIN of `host` when it
1502
+ * is already an IP literal or localhost (scr_net_connect's own arms) or
1503
+ * unresolvable — the dial's deferred "getaddrinfo ENOTFOUND host" error
1504
+ * is then Node's exact cause shape. node:net's own connect surface stays
1505
+ * resolver-less on purpose (Node's async lookup semantics are not this). */
1506
+ ScrStr *scr_net_blocking_lookup(ScrStr *host /*borrowed*/) {
1507
+ if (host->len == 9 && memcmp(host->data, "localhost", 9) == 0) return scr_str_retain(host);
1508
+ struct in_addr a4;
1509
+ struct in6_addr a6;
1510
+ if (inet_pton(AF_INET, host->data, &a4) == 1 || inet_pton(AF_INET6, host->data, &a6) == 1) {
1511
+ return scr_str_retain(host);
1512
+ }
1513
+ if (!scr_net_poller_init()) return scr_str_retain(host); /* WSAStartup on win32 */
1514
+ struct addrinfo hints;
1515
+ memset(&hints, 0, sizeof hints);
1516
+ hints.ai_socktype = SOCK_STREAM;
1517
+ struct addrinfo *res = NULL;
1518
+ if (getaddrinfo(host->data, NULL, &hints, &res) != 0 || res == NULL) {
1519
+ if (res) freeaddrinfo(res);
1520
+ return scr_str_retain(host);
1521
+ }
1522
+ char ip[INET6_ADDRSTRLEN] = "";
1523
+ const char *out = NULL;
1524
+ if (res->ai_family == AF_INET) {
1525
+ out = inet_ntop(AF_INET, &((struct sockaddr_in *)res->ai_addr)->sin_addr, ip, sizeof ip);
1526
+ } else if (res->ai_family == AF_INET6) {
1527
+ out = inet_ntop(AF_INET6, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, ip, sizeof ip);
1528
+ }
1529
+ freeaddrinfo(res);
1530
+ if (!out) return scr_str_retain(host);
1531
+ return scr_str_new(ip, strlen(ip));
1532
+ }
1533
+
1534
+ ScrNetSocket *scr_net_connect(double port, ScrStr *host /*borrowed, nullable*/,
1535
+ ScrClosure *cb /*moves, nullable*/) {
1536
+ ScrNetSocket *s = scr_net_sock_new();
1537
+ if (cb) scr_net_ls_add(&s->conn_ls, cb, NULL, true);
1538
+ s->peer_port = (int)port;
1539
+ const char *h = host && host->len > 0 ? host->data : "localhost";
1540
+ if (strcmp(h, "localhost") == 0) h = "127.0.0.1";
1541
+ snprintf(s->peer_ip, sizeof s->peer_ip, "%s", h);
1542
+ if (!scr_net_poller_init()) {
1543
+ fputs("scriptc: event poller init failed\n", stderr);
1544
+ abort();
1545
+ }
1546
+ struct sockaddr_in a4;
1547
+ struct sockaddr_in6 a6;
1548
+ struct sockaddr *sa = NULL;
1549
+ socklen_t salen = 0;
1550
+ memset(&a4, 0, sizeof a4);
1551
+ memset(&a6, 0, sizeof a6);
1552
+ if (inet_pton(AF_INET, h, &a4.sin_addr) == 1) {
1553
+ a4.sin_family = AF_INET;
1554
+ a4.sin_port = htons((uint16_t)s->peer_port);
1555
+ sa = (struct sockaddr *)&a4;
1556
+ salen = sizeof a4;
1557
+ } else if (inet_pton(AF_INET6, h, &a6.sin6_addr) == 1) {
1558
+ a6.sin6_family = AF_INET6;
1559
+ a6.sin6_port = htons((uint16_t)s->peer_port);
1560
+ sa = (struct sockaddr *)&a6;
1561
+ salen = sizeof a6;
1562
+ }
1563
+ if (!sa) {
1564
+ /* Non-numeric host: no resolver in this slice. Node's shape for a
1565
+ * failed lookup is ENOTFOUND; deliver it deferred. */
1566
+ char msg[160];
1567
+ snprintf(msg, sizeof msg, "getaddrinfo ENOTFOUND %s", h);
1568
+ s->pending_err = scr_str_new(msg, strlen(msg));
1569
+ s->had_error = true;
1570
+ s->emit_close = true;
1571
+ scr_net_sock_register(s);
1572
+ return s;
1573
+ }
1574
+ int fd = socket(sa->sa_family, SOCK_STREAM, 0);
1575
+ if (fd < 0) {
1576
+ fputs("scriptc: socket() failed\n", stderr);
1577
+ abort();
1578
+ }
1579
+ scr_net_nonblock(fd);
1580
+ int one = 1;
1581
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
1582
+ s->fd = fd;
1583
+ int rc = connect(fd, sa, salen);
1584
+ if (rc == 0) {
1585
+ /* Immediate success (loopback can): still async — the write filter
1586
+ * fires at once and connect_done delivers 'connect' from dispatch. */
1587
+ s->connecting = true;
1588
+ scr_net_sock_update_write(s);
1589
+ } else if (errno == EINPROGRESS) {
1590
+ s->connecting = true;
1591
+ scr_net_sock_update_write(s);
1592
+ } else {
1593
+ char msg[128];
1594
+ snprintf(msg, sizeof msg, "connect %s %s:%d", scr_net_errname(errno), s->peer_ip,
1595
+ s->peer_port);
1596
+ s->pending_err = scr_str_new(msg, strlen(msg));
1597
+ s->had_error = true;
1598
+ scr_net_sock_close_fd(s);
1599
+ }
1600
+ scr_net_sock_register(s);
1601
+ return s;
1602
+ }
1603
+
1604
+ /* ── the caller-lookup dial (net.connect with a lookup option) ─────────
1605
+ *
1606
+ * portless's createLoopbackConnection: connect({ host, port,
1607
+ * autoSelectFamily: true, lookup }) — the runtime invokes the caller's
1608
+ * resolver exactly as Node does (lookup(hostname, options, callback))
1609
+ * and dials its answered addresses IN ORDER: each connect failure closes
1610
+ * the fd and tries the next; the last failure's message is the socket's
1611
+ * 'error' (Node's AggregateError under autoSelectFamily is a documented
1612
+ * divergence). The answer callback is a runtime-minted closure over the
1613
+ * boxed socket whose fn is an emitter-synthesized per-shape thunk (the
1614
+ * SNI-answer pattern); late, repeated, or dead-socket answers are
1615
+ * ignored. */
1616
+
1617
+ /* Dials one IP now. Success (established or in progress) answers true;
1618
+ * failure records the message (LAST-wins — the retry chain's contract),
1619
+ * closes any fd, and answers false without emitting. */
1620
+ static bool scr_net_sock_dial_ip(ScrNetSocket *s, const ScrStr *ip) {
1621
+ char h[64];
1622
+ snprintf(h, sizeof h, "%.*s", (int)(ip->len < 63 ? ip->len : 63), ip->data);
1623
+ snprintf(s->peer_ip, sizeof s->peer_ip, "%s", h);
1624
+ struct sockaddr_in a4;
1625
+ struct sockaddr_in6 a6;
1626
+ struct sockaddr *sa = NULL;
1627
+ socklen_t salen = 0;
1628
+ memset(&a4, 0, sizeof a4);
1629
+ memset(&a6, 0, sizeof a6);
1630
+ if (inet_pton(AF_INET, h, &a4.sin_addr) == 1) {
1631
+ a4.sin_family = AF_INET;
1632
+ a4.sin_port = htons((uint16_t)s->peer_port);
1633
+ sa = (struct sockaddr *)&a4;
1634
+ salen = sizeof a4;
1635
+ } else if (inet_pton(AF_INET6, h, &a6.sin6_addr) == 1) {
1636
+ a6.sin6_family = AF_INET6;
1637
+ a6.sin6_port = htons((uint16_t)s->peer_port);
1638
+ sa = (struct sockaddr *)&a6;
1639
+ salen = sizeof a6;
1640
+ }
1641
+ char msg[160];
1642
+ if (!sa) {
1643
+ /* a non-IP answer: the lookup was supposed to resolve — Node's
1644
+ * lookup-failure shape */
1645
+ snprintf(msg, sizeof msg, "getaddrinfo ENOTFOUND %s", h);
1646
+ scr_str_release(s->dial_err);
1647
+ s->dial_err = scr_str_new(msg, strlen(msg));
1648
+ return false;
1649
+ }
1650
+ int fd = socket(sa->sa_family, SOCK_STREAM, 0);
1651
+ if (fd < 0) {
1652
+ fputs("scriptc: socket() failed\n", stderr);
1653
+ abort();
1654
+ }
1655
+ scr_net_nonblock(fd);
1656
+ int one = 1;
1657
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
1658
+ int rc = connect(fd, sa, salen);
1659
+ if (rc == 0 || errno == EINPROGRESS) {
1660
+ s->fd = fd;
1661
+ s->connecting = true;
1662
+ scr_net_sock_update_write(s);
1663
+ return true;
1664
+ }
1665
+ snprintf(msg, sizeof msg, "connect %s %s:%d", scr_net_errname(errno), s->peer_ip, s->peer_port);
1666
+ scr_str_release(s->dial_err);
1667
+ s->dial_err = scr_str_new(msg, strlen(msg));
1668
+ close(fd);
1669
+ return false;
1670
+ }
1671
+
1672
+ /* Dials the next answered addresses until one starts; exhaustion flags
1673
+ * the deferred 'error' + 'close' (the last dial's message stands). */
1674
+ static void scr_net_sock_dial_next(ScrNetSocket *s) {
1675
+ while (s->dial_i < s->dial_n) {
1676
+ const ScrStr *ip = s->dial_ips[s->dial_i++];
1677
+ if (scr_net_sock_dial_ip(s, ip)) return;
1678
+ }
1679
+ /* exhausted: the last failure surfaces now */
1680
+ if (s->dial_err != NULL && s->pending_err == NULL) {
1681
+ s->pending_err = s->dial_err;
1682
+ s->dial_err = NULL;
1683
+ }
1684
+ s->connecting = false;
1685
+ s->had_error = true;
1686
+ s->emit_close = true;
1687
+ }
1688
+
1689
+ ScrNetSocket *scr_net_connect_lookup(double port, ScrStr *host /*borrowed*/,
1690
+ ScrClosure *lookup /*moves*/, void *answer_fn) {
1691
+ ScrNetSocket *s = scr_net_sock_new();
1692
+ s->peer_port = (int)port;
1693
+ snprintf(s->peer_ip, sizeof s->peer_ip, "%.*s",
1694
+ (int)(host->len < 63 ? host->len : 63), host->data);
1695
+ if (!scr_net_poller_init()) {
1696
+ fputs("scriptc: event poller init failed\n", stderr);
1697
+ abort();
1698
+ }
1699
+ s->connecting = true; /* logically connecting while the lookup decides */
1700
+ s->lookup_wait = true;
1701
+ scr_net_sock_register(s);
1702
+ ScrClosure *ans = scr_closure_new(answer_fn, 1);
1703
+ ScrBox *box = scr_box_new_obj(&scr_net_sock_retain_v, &scr_net_sock_release_v, NULL);
1704
+ scr_box_set_ref(box, scr_net_sock_retain(s));
1705
+ ans->caps[0] = box;
1706
+ /* the lookup owns its +1 params per the universal convention; options
1707
+ * crosses as the dyn undefined (Node passes its option bag — the
1708
+ * lowered resolver shape types it `unknown` and portless ignores it) */
1709
+ ((void (*)(ScrClosure *, ScrStr *, ScrDyn *, ScrClosure *))lookup->fn)(
1710
+ lookup, scr_str_retain(host), scr_dyn_undefined(), ans);
1711
+ scr_closure_release(lookup);
1712
+ /* a synchronous throw inside the lookup left the exception pending —
1713
+ * the emitter's may-throw check unwinds past this return */
1714
+ return s;
1715
+ }
1716
+
1717
+ /* The answer closure's runtime half (its fn is the emitted per-shape
1718
+ * thunk; caps[0] boxes the socket). msg/ips are borrowed (nullable). */
1719
+ void scr_net_lookup_answer(ScrClosure *self, bool has_err, ScrStr *msg /*borrowed, nullable*/,
1720
+ ScrArr *ips /*borrowed, nullable*/) {
1721
+ ScrNetSocket *s = (ScrNetSocket *)scr_box_get_ref(self->caps[0]);
1722
+ if (s == NULL) return;
1723
+ if (!s->lookup_wait || s->close_emitted || s->fd >= 0) {
1724
+ scr_net_sock_release(s);
1725
+ return; /* late/repeated/dead-socket answers: the first decided */
1726
+ }
1727
+ s->lookup_wait = false;
1728
+ size_t n = ips != NULL ? (size_t)scr_arr_len(ips) : 0;
1729
+ if (has_err || n == 0) {
1730
+ /* lookup failure: the deferred 'error' (Node defers these too); an
1731
+ * EMPTY answer wears getaddrinfo's shape */
1732
+ if (msg != NULL && msg->len > 0) {
1733
+ scr_str_release(s->pending_err);
1734
+ s->pending_err = scr_str_retain(msg);
1735
+ } else if (s->pending_err == NULL) {
1736
+ char fb[160];
1737
+ snprintf(fb, sizeof fb, "getaddrinfo ENOTFOUND %s", s->peer_ip);
1738
+ s->pending_err = scr_str_new(fb, strlen(fb));
1739
+ }
1740
+ s->connecting = false;
1741
+ s->had_error = true;
1742
+ s->emit_close = true;
1743
+ scr_net_sock_release(s);
1744
+ return;
1745
+ }
1746
+ s->dial_ips = malloc(n * sizeof *s->dial_ips);
1747
+ if (!s->dial_ips) scr_net_oom();
1748
+ for (size_t i = 0; i < n; i++) s->dial_ips[i] = (ScrStr *)scr_arr_get_ref(ips, (double)i);
1749
+ s->dial_n = n;
1750
+ s->dial_i = 0;
1751
+ scr_net_sock_dial_next(s);
1752
+ scr_net_sock_release(s);
1753
+ }
1754
+
1755
+ /* ── the socket surface ──────────────────────────────────────────────── */
1756
+
1757
+ void scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/) {
1758
+ scr_net_sock_write_raw(s, data->data, data->len);
1759
+ }
1760
+
1761
+ void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) {
1762
+ scr_net_sock_write_raw(s, (const char *)data->data, data->len);
1763
+ }
1764
+
1765
+ void scr_net_sock_end(ScrNetSocket *s) {
1766
+ if (s->fd < 0 || s->wr_ending) return;
1767
+ s->wr_ending = true;
1768
+ scr_net_sock_maybe_finish_write(s);
1769
+ if (s->rd_eof && s->wr_done) scr_net_sock_close_fd(s);
1770
+ }
1771
+
1772
+ void scr_net_sock_end_str(ScrNetSocket *s, ScrStr *data /*borrowed*/) {
1773
+ scr_net_sock_write_str(s, data);
1774
+ scr_net_sock_end(s);
1775
+ }
1776
+
1777
+ void scr_net_sock_end_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) {
1778
+ scr_net_sock_write_bytes(s, data);
1779
+ scr_net_sock_end(s);
1780
+ }
1781
+
1782
+ /* socket.setTimeout(ms): ms > 0 arms the idle timer NOW (Node starts the
1783
+ * clock at the call — connecting time counts), ms <= 0 disables. */
1784
+ void scr_net_sock_set_timeout(ScrNetSocket *s, double ms) {
1785
+ if (ms > 0 && s->fd >= 0) {
1786
+ s->timeout_ms = ms;
1787
+ scr_net_sock_timer_arm(s);
1788
+ } else {
1789
+ s->timeout_ms = 0;
1790
+ scr_net_sock_timer_cancel(s);
1791
+ }
1792
+ }
1793
+
1794
+ void scr_net_sock_on_timeout(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
1795
+ if (s->close_emitted) {
1796
+ scr_closure_release(cb);
1797
+ return;
1798
+ }
1799
+ scr_net_ls_add(&s->timeout_ls, cb, NULL, once);
1800
+ }
1801
+
1802
+ /* socket.remoteAddress: the peer's address (+1), cached at first read
1803
+ * (Node's _getpeername cache: a value read while connected survives
1804
+ * destroy), or NULL — the undefined arm — for a never-read socket after
1805
+ * close. A dual-stack accept of an IPv4 peer reads "::ffff:a.b.c.d",
1806
+ * like Node. */
1807
+ /* socket.encrypted: true iff the socket carries a TLS transport (Node
1808
+ * types `encrypted: true` on TLSSocket from construction; plain sockets
1809
+ * answer the undefined arm — the emitter maps false to it). */
1810
+ bool scr_net_sock_encrypted(ScrNetSocket *s) { return s->tops != NULL; }
1811
+
1812
+ ScrStr *scr_net_sock_remote_address(ScrNetSocket *s) {
1813
+ if (s->remote_cache) return scr_str_retain(s->remote_cache);
1814
+ if (s->fd < 0) return NULL;
1815
+ struct sockaddr_storage ss;
1816
+ socklen_t len = sizeof ss;
1817
+ if (getpeername(s->fd, (struct sockaddr *)&ss, &len) != 0) return NULL;
1818
+ char buf[INET6_ADDRSTRLEN];
1819
+ const char *out = NULL;
1820
+ if (ss.ss_family == AF_INET) {
1821
+ out = inet_ntop(AF_INET, &((struct sockaddr_in *)&ss)->sin_addr, buf, sizeof buf);
1822
+ } else if (ss.ss_family == AF_INET6) {
1823
+ out = inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&ss)->sin6_addr, buf, sizeof buf);
1824
+ }
1825
+ if (!out) return NULL;
1826
+ s->remote_cache = scr_str_new(out, strlen(out));
1827
+ return scr_str_retain(s->remote_cache);
1828
+ }
1829
+
1830
+ void scr_net_sock_destroy(ScrNetSocket *s) {
1831
+ if (s->fd < 0 && !s->in_registry) return;
1832
+ s->rd_eof = true;
1833
+ s->wr_done = true;
1834
+ scr_net_sock_close_fd(s); /* 'close' fires at the next sweep */
1835
+ }
1836
+
1837
+ /* ── the paused-mode surface (the demux path: once('readable') +
1838
+ * read(1) + unshift) ─────────────────────────────────────────────────── */
1839
+
1840
+ /* Append arrived bytes to the receive buffer (paused-mode reads). */
1841
+ static void scr_net_sock_append_rbuf(ScrNetSocket *s, const char *data, size_t len) {
1842
+ if (len == 0) return;
1843
+ if (s->rhead > 0) {
1844
+ memmove(s->rbuf, s->rbuf + s->rhead, s->rlen - s->rhead);
1845
+ s->rlen -= s->rhead;
1846
+ s->rhead = 0;
1847
+ }
1848
+ if (s->rlen + len > s->rcap) {
1849
+ size_t cap = s->rcap ? s->rcap : 4096;
1850
+ while (cap < s->rlen + len) cap *= 2;
1851
+ s->rbuf = realloc(s->rbuf, cap);
1852
+ if (!s->rbuf) scr_net_oom();
1853
+ s->rcap = cap;
1854
+ }
1855
+ memcpy(s->rbuf + s->rlen, data, len);
1856
+ s->rlen += len;
1857
+ }
1858
+
1859
+ void scr_net_sock_on_readable(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
1860
+ if (s->close_emitted || s->rd_eof) {
1861
+ scr_closure_release(cb);
1862
+ return;
1863
+ }
1864
+ scr_net_ls_add(&s->readable_ls, cb, NULL, once);
1865
+ scr_net_sock_update_read(s);
1866
+ }
1867
+
1868
+ /* socket.read(n): n > 0 answers exactly n buffered bytes or NULL (Node's
1869
+ * less-than-n-buffered answer); n <= 0 (the read() form) drains the
1870
+ * whole buffer, NULL when empty. Always +1. */
1871
+ ScrBytes *scr_net_sock_read_bytes(ScrNetSocket *s, double n) {
1872
+ size_t avail = s->rlen - s->rhead;
1873
+ size_t want = n > 0 ? (size_t)n : avail;
1874
+ if (avail == 0 || want == 0 || want > avail) return NULL;
1875
+ ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)want);
1876
+ size_t took = scr_net_sock_take_buffered(s, (char *)out->data, want);
1877
+ (void)took;
1878
+ return out;
1879
+ }
1880
+
1881
+ /* socket.unshift(buf): the bytes re-enter the FRONT of the receive
1882
+ * buffer — every consumer (data listeners, an http parser, a TLS bio)
1883
+ * sees them before anything still in the kernel. */
1884
+ void scr_net_sock_unshift_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) {
1885
+ if (data->len == 0) return;
1886
+ size_t avail = s->rlen - s->rhead;
1887
+ if (s->rhead >= data->len) {
1888
+ /* room at the front (the read(1)-then-unshift shape: the byte slots
1889
+ * straight back where it came from) */
1890
+ s->rhead -= data->len;
1891
+ memcpy(s->rbuf + s->rhead, data->data, data->len);
1892
+ return;
1893
+ }
1894
+ size_t total = data->len + avail;
1895
+ char *fresh = malloc(total > 0 ? total : 1);
1896
+ if (!fresh) scr_net_oom();
1897
+ memcpy(fresh, data->data, data->len);
1898
+ if (avail > 0) memcpy(fresh + data->len, s->rbuf + s->rhead, avail);
1899
+ free(s->rbuf);
1900
+ s->rbuf = fresh;
1901
+ s->rhead = 0;
1902
+ s->rlen = total;
1903
+ s->rcap = total > 0 ? total : 1;
1904
+ }
1905
+
1906
+ /* server.emit('connection', socket): route an accepted socket into
1907
+ * ANOTHER server — the demux pattern (portless's first-byte TLS peek).
1908
+ * The target's protocol layer claims the socket (the http parser, or
1909
+ * the TLS transport whose deferred 'connection' fires post-handshake);
1910
+ * the socket's owning server stays the accepting one, exactly Node's
1911
+ * connection accounting. */
1912
+ void scr_net_server_emit_connection(ScrNetServer *srv, ScrNetSocket *sock) {
1913
+ if (srv->close_emitted) return;
1914
+ if (srv->native_conn) {
1915
+ srv->native_conn(srv->native_ctx, sock);
1916
+ if (scr_exc_pending()) return;
1917
+ }
1918
+ if (srv->defer_conn) {
1919
+ if (sock->conn_pending) scr_net_server_release(sock->conn_pending);
1920
+ sock->conn_pending = scr_net_server_retain(srv);
1921
+ return;
1922
+ }
1923
+ ScrNetL *snap;
1924
+ size_t n = scr_net_ls_snapshot(&srv->conn_ls, &snap);
1925
+ scr_dyn_this_push(srv, SCR_DYNH_NET_SERVER);
1926
+ for (size_t i = 0; i < n; i++) {
1927
+ if (!scr_exc_pending()) ((ScrNetConnFn)snap[i].fn)(snap[i].cb, scr_net_sock_retain(sock));
1928
+ scr_closure_release(snap[i].cb);
1929
+ }
1930
+ scr_dyn_this_pop();
1931
+ free(snap);
1932
+ }
1933
+
1934
+ /* The accepting server of a server-side socket (BORROWED; NULL on client
1935
+ * sockets) — the protocol layer binds it as the ambient receiver when
1936
+ * firing server-level events ('request': Node's this === server). */
1937
+ ScrNetServer *scr_net_sock_server(ScrNetSocket *s) { return s->server; }
1938
+
1939
+ /* socket.setEncoding(enc) — the req twin (scr_http_req_set_encoding's
1940
+ * contract: utf8 flips 'data' to strings, real-but-unsupported encodings
1941
+ * fence loudly, unknown names throw ERR_UNKNOWN_ENCODING). */
1942
+ void scr_net_sock_set_encoding(ScrNetSocket *s, ScrStr *enc /*borrowed*/) {
1943
+ if ((enc->len == 4 && memcmp(enc->data, "utf8", 4) == 0) ||
1944
+ (enc->len == 5 && memcmp(enc->data, "utf-8", 5) == 0)) {
1945
+ s->enc_utf8 = true;
1946
+ return;
1947
+ }
1948
+ static const char *const known[] = { "ascii", "latin1", "binary", "base64",
1949
+ "base64url", "hex", "ucs2", "ucs-2", "utf16le", "utf-16le", NULL };
1950
+ for (size_t i = 0; known[i]; i++) {
1951
+ if (enc->len == strlen(known[i]) && memcmp(enc->data, known[i], enc->len) == 0) {
1952
+ char msg[128];
1953
+ int n = snprintf(msg, sizeof msg, "setEncoding('%s') is not supported yet (only 'utf8' here)",
1954
+ known[i]);
1955
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
1956
+ return;
1957
+ }
1958
+ }
1959
+ char msg[128];
1960
+ int n = snprintf(msg, sizeof msg, "Unknown encoding: %.*s",
1961
+ (int)(enc->len < 64 ? enc->len : 64), enc->data);
1962
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_UNKNOWN_ENCODING");
1963
+ }
1964
+
1965
+ /* pipe(dst): every arrived chunk forwards to dst; src EOF end()s dst
1966
+ * (Node's default). Also arms the read side — a pipe is a consumer. */
1967
+ void scr_net_sock_pipe(ScrNetSocket *src, ScrNetSocket *dst) {
1968
+ if (src->pipe_dst) scr_net_sock_release(src->pipe_dst);
1969
+ src->pipe_dst = scr_net_sock_retain(dst);
1970
+ scr_net_sock_update_read(src);
1971
+ }
1972
+
1973
+ void scr_net_sock_on_data(ScrNetSocket *s, ScrClosure *cb /*moves*/, ScrNetDataFn fn,
1974
+ bool once) {
1975
+ if (s->close_emitted || s->rd_eof) {
1976
+ scr_closure_release(cb);
1977
+ return;
1978
+ }
1979
+ scr_net_ls_add(&s->data_ls, cb, (void *)fn, once);
1980
+ scr_net_sock_update_read(s);
1981
+ }
1982
+
1983
+ void scr_net_sock_on_end(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
1984
+ if (s->close_emitted || s->rd_eof) {
1985
+ scr_closure_release(cb);
1986
+ return;
1987
+ }
1988
+ scr_net_ls_add(&s->end_ls, cb, NULL, once);
1989
+ }
1990
+
1991
+ void scr_net_sock_on_close(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
1992
+ if (s->close_emitted) {
1993
+ scr_closure_release(cb);
1994
+ return;
1995
+ }
1996
+ scr_net_ls_add(&s->close_ls, cb, NULL, once);
1997
+ }
1998
+
1999
+ void scr_net_sock_on_error(ScrNetSocket *s, ScrClosure *cb /*moves*/, ScrChildErrFn fn,
2000
+ bool once) {
2001
+ if (s->close_emitted) {
2002
+ scr_closure_release(cb);
2003
+ return;
2004
+ }
2005
+ scr_net_ls_add(&s->err_ls, cb, (void *)fn, once);
2006
+ }
2007
+
2008
+ void scr_net_sock_on_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
2009
+ if (s->close_emitted || (!s->connecting && s->fd < 0)) {
2010
+ scr_closure_release(cb);
2011
+ return;
2012
+ }
2013
+ if (!s->connecting) {
2014
+ /* already connected: Node fires a late once('connect')… never — the
2015
+ * event is past. Release, like listeners after 'exit'. */
2016
+ scr_closure_release(cb);
2017
+ return;
2018
+ }
2019
+ scr_net_ls_add(&s->conn_ls, cb, NULL, once);
2020
+ }
2021
+
2022
+ /* The runtime-provided data adapters (the child/stdin thunk pattern). */
2023
+ void scr_net_data_thunk0(ScrClosure *cb, ScrBytes *chunk) {
2024
+ (void)chunk;
2025
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2026
+ }
2027
+
2028
+ void scr_net_data_thunk_bytes(ScrClosure *cb, ScrBytes *chunk) {
2029
+ /* the listener owns its +1 param per the universal convention */
2030
+ ((void (*)(ScrClosure *, ScrBytes *))cb->fn)(cb, scr_bytes_retain(chunk));
2031
+ }
2032
+
2033
+ /* The checked-dynamic data listener (a dynCheck-adapted (dyn) => void
2034
+ * closure): the chunk boxes BUFFER-flavored — Node hands Buffers to
2035
+ * 'data', so toString()/string coercion decode utf8 (the stream lane's
2036
+ * box-by-tag stance). The adapter owns its +1 dyn param. */
2037
+ void scr_net_data_thunk_dyn(ScrClosure *cb, ScrBytes *chunk) {
2038
+ ScrDyn *d = scr_dyn_new_chunk(chunk);
2039
+ ((void (*)(ScrClosure *, ScrDyn *))cb->fn)(cb, d);
2040
+ }
2041
+
2042
+ /* The connection-handler adapters: the socket arrives +1 from the firing
2043
+ * site; the zero-param shape releases it. */
2044
+ void scr_net_conn_thunk0(ScrClosure *cb, ScrNetSocket *sock) {
2045
+ scr_net_sock_release(sock);
2046
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2047
+ }
2048
+
2049
+ void scr_net_conn_thunk_sock(ScrClosure *cb, ScrNetSocket *sock) {
2050
+ ((void (*)(ScrClosure *, ScrNetSocket *))cb->fn)(cb, sock);
2051
+ }
2052
+
2053
+ /* ── the protocol-layer hooks (scr_http.c) ───────────────────────────── */
2054
+
2055
+ void scr_net_server_set_native_conn(ScrNetServer *s, ScrNetNativeConnFn fn, void *ctx,
2056
+ void (*ctx_free)(void *)) {
2057
+ s->native_conn = fn;
2058
+ s->native_ctx = ctx;
2059
+ s->native_ctx_free = ctx_free;
2060
+ }
2061
+
2062
+ void scr_net_server_set_proto_settle(ScrNetServer *s, void (*fn)(void *)) {
2063
+ s->proto_settle = fn;
2064
+ }
2065
+
2066
+ bool scr_net_server_settled(ScrNetServer *s) { return s->close_emitted; }
2067
+
2068
+ void scr_net_server_set_http_ctx(ScrNetServer *s, void *ctx) { s->http_ctx = ctx; }
2069
+ void *scr_net_server_get_http_ctx(ScrNetServer *s) { return s->http_ctx; }
2070
+
2071
+ void scr_net_sock_set_native_reader(ScrNetSocket *s, ScrNetNativeDataFn data,
2072
+ ScrNetNativeEventFn eof, ScrNetNativeEventFn closed,
2073
+ void *ctx, void (*ctx_free)(void *)) {
2074
+ s->native_data = data;
2075
+ s->native_eof = eof;
2076
+ s->native_closed = closed;
2077
+ s->native_ctx = ctx;
2078
+ s->native_ctx_free = ctx_free;
2079
+ scr_net_sock_update_read(s);
2080
+ }
2081
+
2082
+ /* The upgrade handover: clear the native reader's FN POINTERS but keep
2083
+ * the ctx (still freed at socket death as registered) — the protocol
2084
+ * parser steps aside mid-call and 'data'/pipe consumers take the raw
2085
+ * stream from the next byte on. */
2086
+ void scr_net_sock_clear_native_reader(ScrNetSocket *s) {
2087
+ s->native_data = NULL;
2088
+ s->native_eof = NULL;
2089
+ s->native_closed = NULL;
2090
+ scr_net_sock_update_read(s);
2091
+ }
2092
+
2093
+ /* socket.destroyed — true once the fd is gone (destroy() or full close),
2094
+ * Node's flag for "this stream is done". */
2095
+ bool scr_net_sock_destroyed(ScrNetSocket *s) { return s->fd < 0; }
2096
+
2097
+ /* socket.writable — Node's stream flag: the write half is open (no end()
2098
+ * yet, no FIN sent, fd alive). Connecting sockets answer true — writes
2099
+ * queue, exactly Node. */
2100
+ bool scr_net_sock_writable(ScrNetSocket *s) {
2101
+ return s->fd >= 0 && !s->wr_ending && !s->wr_done;
2102
+ }
2103
+
2104
+ /* The protocol layer's timeout/error hooks (scr_http.c's client and the
2105
+ * server parser's error swallowing). */
2106
+ void scr_net_sock_set_native_events(ScrNetSocket *s, ScrNetNativeEventFn timeout,
2107
+ ScrNetNativeErrFn err) {
2108
+ s->native_timeout = timeout;
2109
+ s->native_err = err;
2110
+ }
2111
+
2112
+ /* Client connect completion for the protocol layer (the h2 session's
2113
+ * 'connect' moment) — fires before the socket's own conn_ls. */
2114
+ void scr_net_sock_set_native_established(ScrNetSocket *s, ScrNetNativeEventFn fn) {
2115
+ s->native_established = fn;
2116
+ }
2117
+
2118
+ /* Raw bytes from the protocol layer (head/framing text). */
2119
+ void scr_net_sock_write_native(ScrNetSocket *s, const char *buf, size_t n) {
2120
+ scr_net_sock_write_raw(s, buf, n);
2121
+ }
2122
+
2123
+ /* ── the transport hooks (scr_tls.c) ─────────────────────────────────── */
2124
+
2125
+ void scr_net_sock_set_transport(ScrNetSocket *s, const ScrNetTransportOps *ops, void *tctx) {
2126
+ s->tops = ops;
2127
+ s->tctx = tctx;
2128
+ s->t_est = false;
2129
+ scr_net_sock_update_read(s); /* the handshake is a consumer */
2130
+ scr_net_sock_update_write(s);
2131
+ }
2132
+
2133
+ int scr_net_sock_fd(ScrNetSocket *s) { return s->fd; }
2134
+
2135
+ /* Drain up to n bytes of the receive buffer (peeked/unshifted bytes) —
2136
+ * consumers call this BEFORE the fd so re-queued bytes keep their
2137
+ * position in the stream. */
2138
+ size_t scr_net_sock_take_buffered(ScrNetSocket *s, char *buf, size_t n) {
2139
+ size_t avail = s->rlen - s->rhead;
2140
+ if (avail == 0) return 0;
2141
+ size_t take = n < avail ? n : avail;
2142
+ memcpy(buf, s->rbuf + s->rhead, take);
2143
+ s->rhead += take;
2144
+ if (s->rhead == s->rlen) s->rhead = s->rlen = 0;
2145
+ return take;
2146
+ }
2147
+
2148
+ void scr_net_sock_transport_want_write(ScrNetSocket *s) {
2149
+ s->t_want_write = true;
2150
+ scr_net_sock_update_write(s);
2151
+ }
2152
+
2153
+ /* The transport context, guarded by ops identity — the SNI answer path
2154
+ * re-finds its engine from the boxed socket, which may have died (or
2155
+ * been re-routed) between the callback and the answer. */
2156
+ void *scr_net_sock_transport_ctx_for(ScrNetSocket *s, const ScrNetTransportOps *ops) {
2157
+ return s->tops == ops ? s->tctx : NULL;
2158
+ }
2159
+
2160
+ /* Re-drive a parked handshake (the asynchronous SNI answer arrived; no
2161
+ * fd readiness is coming — the client is waiting for the ServerHello). */
2162
+ void scr_net_sock_transport_resume(ScrNetSocket *s) { scr_net_sock_transport_pump(s); }
2163
+
2164
+ /* Replay raw bytes at the FRONT of the receive buffer (the SNI
2165
+ * pre-parse handing the consumed ClientHello back to the TLS bio) —
2166
+ * unshift's shape over a raw span. */
2167
+ void scr_net_sock_replay(ScrNetSocket *s, const char *data, size_t len) {
2168
+ if (len == 0) return;
2169
+ size_t avail = s->rlen - s->rhead;
2170
+ if (s->rhead >= len) {
2171
+ s->rhead -= len;
2172
+ memcpy(s->rbuf + s->rhead, data, len);
2173
+ return;
2174
+ }
2175
+ size_t total = len + avail;
2176
+ char *fresh = malloc(total);
2177
+ if (!fresh) scr_net_oom();
2178
+ memcpy(fresh, data, len);
2179
+ if (avail > 0) memcpy(fresh + len, s->rbuf + s->rhead, avail);
2180
+ free(s->rbuf);
2181
+ s->rbuf = fresh;
2182
+ s->rhead = 0;
2183
+ s->rlen = total;
2184
+ s->rcap = total;
2185
+ }
2186
+
2187
+ void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg) {
2188
+ if (msg != NULL) {
2189
+ if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
2190
+ s->had_error = true;
2191
+ }
2192
+ scr_net_sock_close_fd(s); /* the sweep delivers 'error' (if any) then 'close' */
2193
+ }
2194
+
2195
+ void scr_net_server_defer_connections(ScrNetServer *s) { s->defer_conn = true; }
2196
+
2197
+ void scr_net_server_get_native_conn(ScrNetServer *s, ScrNetNativeConnFn *fn, void **ctx,
2198
+ void (**ctx_free)(void *)) {
2199
+ *fn = s->native_conn;
2200
+ *ctx = s->native_ctx;
2201
+ *ctx_free = s->native_ctx_free;
2202
+ }
2203
+
2204
+ /* ── the sweep: deferred emits ───────────────────────────────────────── */
2205
+
2206
+ /* The protocol layer's deferred-emit hook (scr_http.c: response/request
2207
+ * 'close' events and the client teardown queue fire from the sweep, one
2208
+ * pass after the work that flagged them — Node's later-than-the-handler
2209
+ * emits). Registered once by scr_http install-time code. */
2210
+ enum { SCR_NET_PROTO_SLOTS = 4 };
2211
+ static bool (*scr_net_proto_pendings[SCR_NET_PROTO_SLOTS])(void);
2212
+ static void (*scr_net_proto_sweeps[SCR_NET_PROTO_SLOTS])(void);
2213
+ static size_t scr_net_proto_n = 0;
2214
+
2215
+ /* ADDITIVE registration (each protocol unit — scr_http.c, scr_http2.c —
2216
+ * claims a slot at its install; both can co-link in one binary). */
2217
+ void scr_net_set_proto_sweep(bool (*pending)(void), void (*sweep)(void)) {
2218
+ if (scr_net_proto_n == SCR_NET_PROTO_SLOTS) abort();
2219
+ scr_net_proto_pendings[scr_net_proto_n] = pending;
2220
+ scr_net_proto_sweeps[scr_net_proto_n] = sweep;
2221
+ scr_net_proto_n++;
2222
+ }
2223
+
2224
+ static bool scr_net_proto_pending(void) {
2225
+ for (size_t i = 0; i < scr_net_proto_n; i++) {
2226
+ if (scr_net_proto_pendings[i]()) return true;
2227
+ }
2228
+ return false;
2229
+ }
2230
+
2231
+ static void scr_net_proto_sweep(void) {
2232
+ for (size_t i = 0; i < scr_net_proto_n; i++) {
2233
+ scr_net_proto_sweeps[i]();
2234
+ if (scr_exc_pending()) return;
2235
+ }
2236
+ }
2237
+
2238
+ static bool scr_net_flags_pending(void) {
2239
+ if (scr_net_proto_pending()) return true;
2240
+ for (ScrNetSocket *s = scr_net_socks; s; s = s->next) {
2241
+ if (s->pending_err || (s->emit_close && !s->close_emitted)) return true;
2242
+ /* bytes already out of the kernel with a consumer waiting — decrypted
2243
+ * plaintext inside a transport, or unshifted bytes in the receive
2244
+ * buffer: the poller can't re-signal them, the sweep must deliver */
2245
+ bool consumers = s->data_ls.n > 0 || s->pipe_dst || s->native_data;
2246
+ if (consumers && s->fd >= 0) {
2247
+ if (s->tops && s->t_est && s->tops->pending && s->tops->pending(s->tctx)) return true;
2248
+ if (!s->tops && s->rlen > s->rhead) return true;
2249
+ }
2250
+ }
2251
+ for (ScrNetServer *s = scr_net_servers; s; s = s->next) {
2252
+ if (s->pending_err || s->emit_listening) return true;
2253
+ if (s->closing && s->nconns == 0 && !s->close_emitted) return true;
2254
+ }
2255
+ return false;
2256
+ }
2257
+
2258
+ /* Settles a server whose 'close' is due: fires the close callbacks and
2259
+ * listeners, drops every listener list (the cycle story), and leaves the
2260
+ * registry. Shared by the server sweep and the socket settle below —
2261
+ * Node emits a DRAINED server's 'close' before the dying socket's own
2262
+ * 'close', so the socket settle calls this mid-flight. */
2263
+ static void scr_net_server_settle(ScrNetServer *srv) {
2264
+ srv->close_emitted = true;
2265
+ scr_net_fire0_this(&srv->close_ls, srv, SCR_DYNH_NET_SERVER);
2266
+ scr_net_ls_drop(&srv->conn_ls);
2267
+ scr_net_ls_drop(&srv->err_ls);
2268
+ scr_net_ls_drop(&srv->close_ls);
2269
+ scr_net_ls_drop(&srv->listening_cbs);
2270
+ if (srv->proto_settle && srv->http_ctx) srv->proto_settle(srv->http_ctx);
2271
+ scr_net_server_unregister(srv);
2272
+ }
2273
+
2274
+ /* Sockets first (a dying connection may drain its server), servers after.
2275
+ * Every fire checks for a pending exception and bails — the loop surfaces
2276
+ * it as an uncaught throw. */
2277
+ static void scr_net_sweep(void) {
2278
+ scr_net_proto_sweep();
2279
+ if (scr_exc_pending()) return;
2280
+ ScrNetSocket *sock = scr_net_socks;
2281
+ while (sock) {
2282
+ ScrNetSocket *next = sock->next; /* may unregister below */
2283
+ scr_net_sock_retain(sock); /* callbacks may drop every other ref */
2284
+ if (sock->fd >= 0 && (sock->data_ls.n > 0 || sock->pipe_dst || sock->native_data) &&
2285
+ ((sock->tops && sock->t_est && sock->tops->pending && sock->tops->pending(sock->tctx)) ||
2286
+ (!sock->tops && sock->rlen > sock->rhead))) {
2287
+ /* deliver bytes the kernel can't re-signal (see flags_pending) */
2288
+ scr_net_sock_read(sock);
2289
+ if (scr_exc_pending()) {
2290
+ scr_net_sock_release(sock);
2291
+ return;
2292
+ }
2293
+ }
2294
+ if (sock->pending_err) {
2295
+ ScrStr *msg = sock->pending_err;
2296
+ sock->pending_err = NULL;
2297
+ /* the protocol layer gets first claim (the http conn/client owns
2298
+ * the error story: 'error' on the req, or Node's silent server-side
2299
+ * teardown); unconsumed errors fall through to the socket's own
2300
+ * listeners — and the unhandled-'error' exit */
2301
+ bool consumed = false;
2302
+ if (sock->native_err) consumed = sock->native_err(sock->native_ctx, msg);
2303
+ if (!consumed && !scr_exc_pending()) scr_net_fire_err_this(&sock->err_ls, msg, sock, SCR_DYNH_NET_SOCKET);
2304
+ scr_str_release(msg);
2305
+ if (scr_exc_pending()) {
2306
+ scr_net_sock_release(sock);
2307
+ return;
2308
+ }
2309
+ }
2310
+ if (sock->emit_close && !sock->close_emitted && sock->fd < 0) {
2311
+ sock->close_emitted = true;
2312
+ sock->emit_close = false;
2313
+ /* The owning server's count drains FIRST, and a now-drained closing
2314
+ * server settles before this socket's own 'close' fires — Node's
2315
+ * observed order (differential: the net-echo fixture). */
2316
+ if (sock->server) {
2317
+ ScrNetServer *srv = sock->server;
2318
+ sock->server = NULL;
2319
+ srv->nconns--;
2320
+ if (srv->closing && srv->nconns == 0 && !srv->close_emitted) {
2321
+ scr_net_server_settle(srv);
2322
+ }
2323
+ scr_net_server_release(srv);
2324
+ if (scr_exc_pending()) {
2325
+ scr_net_sock_release(sock);
2326
+ return;
2327
+ }
2328
+ }
2329
+ if (sock->native_closed) {
2330
+ sock->native_closed(sock->native_ctx);
2331
+ if (scr_exc_pending()) {
2332
+ scr_net_sock_release(sock);
2333
+ return;
2334
+ }
2335
+ }
2336
+ scr_net_fire0_this(&sock->close_ls, sock, SCR_DYNH_NET_SOCKET);
2337
+ /* settle: listeners drop (cycle story) */
2338
+ scr_net_ls_drop(&sock->data_ls);
2339
+ scr_net_ls_drop(&sock->end_ls);
2340
+ scr_net_ls_drop(&sock->close_ls);
2341
+ scr_net_ls_drop(&sock->err_ls);
2342
+ scr_net_ls_drop(&sock->conn_ls);
2343
+ scr_net_ls_drop(&sock->timeout_ls);
2344
+ scr_net_ls_drop(&sock->readable_ls);
2345
+ if (sock->pipe_dst) {
2346
+ scr_net_sock_release(sock->pipe_dst);
2347
+ sock->pipe_dst = NULL;
2348
+ }
2349
+ if (sock->conn_pending) {
2350
+ scr_net_server_release(sock->conn_pending);
2351
+ sock->conn_pending = NULL;
2352
+ }
2353
+ scr_net_sock_unregister(sock);
2354
+ if (scr_exc_pending()) {
2355
+ scr_net_sock_release(sock);
2356
+ return;
2357
+ }
2358
+ }
2359
+ scr_net_sock_release(sock);
2360
+ sock = next;
2361
+ }
2362
+ ScrNetServer *srv = scr_net_servers;
2363
+ while (srv) {
2364
+ ScrNetServer *next = srv->next;
2365
+ scr_net_server_retain(srv);
2366
+ if (srv->emit_listening) {
2367
+ srv->emit_listening = false;
2368
+ scr_net_fire0_this(&srv->listening_cbs, srv, SCR_DYNH_NET_SERVER);
2369
+ if (scr_exc_pending()) {
2370
+ scr_net_server_release(srv);
2371
+ return;
2372
+ }
2373
+ }
2374
+ if (srv->pending_err) {
2375
+ ScrStr *msg = srv->pending_err;
2376
+ srv->pending_err = NULL;
2377
+ scr_net_fire_err_this(&srv->err_ls, msg, srv, SCR_DYNH_NET_SERVER);
2378
+ scr_str_release(msg);
2379
+ /* A failed listen SETTLES the handle: listeners drop (an error
2380
+ * listener capturing its own server would otherwise cycle forever —
2381
+ * the RC audit caught exactly that) and it leaves the registry. A
2382
+ * later listen() starts fresh; pre-failure listeners are gone,
2383
+ * where Node's would survive (SEMANTICS.md divergence 48). */
2384
+ if (!srv->listening && !srv->closing) {
2385
+ scr_net_ls_drop(&srv->conn_ls);
2386
+ scr_net_ls_drop(&srv->err_ls);
2387
+ scr_net_ls_drop(&srv->close_ls);
2388
+ scr_net_ls_drop(&srv->listening_cbs);
2389
+ scr_net_server_unregister(srv);
2390
+ }
2391
+ if (scr_exc_pending()) {
2392
+ scr_net_server_release(srv);
2393
+ return;
2394
+ }
2395
+ }
2396
+ scr_net_server_release(srv);
2397
+ srv = next;
2398
+ }
2399
+ /* Drained closing servers settle in close-REQUEST order (close_seq),
2400
+ * not registry order — Node emits 'close' in the order close() was
2401
+ * called when several drain in one turn. */
2402
+ for (;;) {
2403
+ ScrNetServer *due = NULL;
2404
+ for (ScrNetServer *it = scr_net_servers; it; it = it->next) {
2405
+ if (it->closing && it->nconns == 0 && !it->close_emitted &&
2406
+ (due == NULL || it->close_seq < due->close_seq)) {
2407
+ due = it;
2408
+ }
2409
+ }
2410
+ if (due == NULL) return;
2411
+ scr_net_server_retain(due);
2412
+ scr_net_server_settle(due);
2413
+ scr_net_server_release(due);
2414
+ if (scr_exc_pending()) return;
2415
+ }
2416
+ }
2417
+
2418
+ /* ── the loop hooks (scr_async.c) ────────────────────────────────────── */
2419
+
2420
+ static bool scr_net_pending(void) {
2421
+ if (scr_net_servers != NULL || scr_net_socks != NULL) return true;
2422
+ return scr_net_proto_pending();
2423
+ }
2424
+
2425
+ static int scr_net_pollfd(void) {
2426
+ return scr_net_poller != NULL ? scrp_poller_fd(scr_net_poller) : -1;
2427
+ }
2428
+
2429
+ static void scr_net_dispatch(void) {
2430
+ if (!scr_net_servers && !scr_net_socks &&
2431
+ !scr_net_proto_pending()) {
2432
+ return;
2433
+ }
2434
+ for (;;) {
2435
+ scr_net_sweep();
2436
+ if (scr_exc_pending()) return;
2437
+ if (scr_net_poller == NULL) return;
2438
+ ScrPollerEvent evs[64];
2439
+ int n = scrp_drain(scr_net_poller, evs, 64);
2440
+ for (int i = 0; i < n; i++) {
2441
+ void *udata = evs[i].udata;
2442
+ if (!udata) continue;
2443
+ int kind = *(int *)udata;
2444
+ if (kind == SCR_NET_K_SERVER) {
2445
+ scr_net_server_accept((ScrNetServer *)udata);
2446
+ } else if (kind == SCR_NET_K_SOCKET) {
2447
+ ScrNetSocket *s = (ScrNetSocket *)udata;
2448
+ /* A callback earlier in this batch may have closed this fd
2449
+ * (destroy inside a data listener) — its remaining events are
2450
+ * stale; the handle itself stays alive until the sweep. */
2451
+ if (s->fd < 0) continue;
2452
+ if (evs[i].events & SCRP_TIMER) {
2453
+ /* the idle period elapsed: 'timeout' fires once (one-shot — the
2454
+ * next activity re-arms); the socket stays open, like Node */
2455
+ s->timeout_armed = false;
2456
+ if (s->native_timeout) {
2457
+ s->native_timeout(s->native_ctx);
2458
+ if (scr_exc_pending()) return;
2459
+ }
2460
+ scr_net_fire0_this(&s->timeout_ls, s, SCR_DYNH_NET_SOCKET);
2461
+ }
2462
+ /* kqueue delivers one direction per event (today's exact order);
2463
+ * epoll may coalesce both into one — writes first (connect
2464
+ * completion, flushes), then reads, re-checking the fd a write
2465
+ * failure may have closed. */
2466
+ if (evs[i].events & SCRP_WRITABLE) {
2467
+ if (s->connecting) scr_net_sock_connect_done(s);
2468
+ else if (s->tops && !s->t_est) {
2469
+ s->t_want_write = false;
2470
+ scr_net_sock_transport_pump(s);
2471
+ if (!scr_exc_pending() && s->fd >= 0) scr_net_sock_update_write(s);
2472
+ } else {
2473
+ s->t_want_write = false;
2474
+ scr_net_sock_flush(s);
2475
+ }
2476
+ }
2477
+ if ((evs[i].events & SCRP_READABLE) && !scr_exc_pending() && s->fd >= 0) {
2478
+ if (s->tops && !s->t_est) scr_net_sock_transport_pump(s);
2479
+ else scr_net_sock_read(s);
2480
+ }
2481
+ }
2482
+ if (scr_exc_pending()) return;
2483
+ }
2484
+ if (!scr_net_flags_pending()) return;
2485
+ if (scr_loop_has_ready()) return; /* microtasks interleave first */
2486
+ }
2487
+ }
2488
+
2489
+ /* Exit-time registry cleanup (the events-unit precedent): handles a
2490
+ * program legitimately leaves live at exit — a server running until
2491
+ * SIGTERM, sockets the peer never closed — release their listeners and
2492
+ * registry references so the RC audit sees a clean heap. */
2493
+ static void scr_net_cleanup_atexit(void) {
2494
+ while (scr_net_socks) {
2495
+ ScrNetSocket *s = scr_net_socks;
2496
+ scr_net_ls_drop(&s->data_ls);
2497
+ scr_net_ls_drop(&s->end_ls);
2498
+ scr_net_ls_drop(&s->close_ls);
2499
+ scr_net_ls_drop(&s->err_ls);
2500
+ scr_net_ls_drop(&s->conn_ls);
2501
+ scr_net_ls_drop(&s->timeout_ls);
2502
+ scr_net_ls_drop(&s->readable_ls);
2503
+ /* Tear down the protocol layer's parser NOW: an in-flight http
2504
+ * exchange (a handler that threw before res.end()) holds req/res,
2505
+ * and the res holds this socket back — a cycle the lean untraced
2506
+ * handles cannot collect, visible only to the RC audit. No reads
2507
+ * follow at exit, so the parser hooks unhook first and the ctx
2508
+ * frees (scr_http_conn_free drops the in-flight pair; its close
2509
+ * emits are discarded at exit — the http unit's exiting flag). */
2510
+ if (s->native_ctx_free) {
2511
+ void (*ctx_free)(void *) = s->native_ctx_free;
2512
+ void *ctx = s->native_ctx;
2513
+ s->native_ctx_free = NULL;
2514
+ s->native_ctx = NULL;
2515
+ s->native_data = NULL;
2516
+ s->native_eof = NULL;
2517
+ s->native_closed = NULL;
2518
+ s->native_timeout = NULL;
2519
+ s->native_established = NULL;
2520
+ s->native_err = NULL;
2521
+ ctx_free(ctx);
2522
+ }
2523
+ if (s->pipe_dst) {
2524
+ scr_net_sock_release(s->pipe_dst);
2525
+ s->pipe_dst = NULL;
2526
+ }
2527
+ if (s->conn_pending) {
2528
+ scr_net_server_release(s->conn_pending);
2529
+ s->conn_pending = NULL;
2530
+ }
2531
+ if (s->server) {
2532
+ s->server->nconns--;
2533
+ scr_net_server_release(s->server);
2534
+ s->server = NULL;
2535
+ }
2536
+ scr_net_sock_unregister(s);
2537
+ }
2538
+ while (scr_net_servers) {
2539
+ ScrNetServer *s = scr_net_servers;
2540
+ scr_net_ls_drop(&s->conn_ls);
2541
+ scr_net_ls_drop(&s->err_ls);
2542
+ scr_net_ls_drop(&s->close_ls);
2543
+ scr_net_ls_drop(&s->listening_cbs);
2544
+ scr_closure_release(s->close_override);
2545
+ s->close_override = NULL;
2546
+ scr_net_server_unregister(s);
2547
+ }
2548
+ }
2549
+
2550
+ void scr_net_install(void) {
2551
+ static bool installed = false;
2552
+ if (installed) return;
2553
+ installed = true;
2554
+ atexit(scr_net_cleanup_atexit);
2555
+ scr_loop_set_net(&scr_net_pending, &scr_net_dispatch, &scr_net_pollfd);
2556
+ }
2557
+
2558
+ /* ── checked-dynamic handle dispatch (SCR_DYNH_NET_SOCKET ops) ─────────
2559
+ * The socket half of the HANDLE crossing (scr_http.c hosts the req/res
2560
+ * half and the shared design comment): members dispatch onto the same
2561
+ * entry points the static lowerings use; real-but-unmodeled members meet
2562
+ * the loud "not supported yet" ladder; unknown names answer Node's own
2563
+ * shapes ("<what> is not a function" / the undefined singleton). */
2564
+
2565
+ static void scr_net_dynh_unsupported(const char *member, const char *why) {
2566
+ char msg[160];
2567
+ int n = snprintf(msg, sizeof msg, "'Socket.prototype.%s' on a dynamic value is not supported yet%s%s",
2568
+ member, why ? " — " : "", why ? why : "");
2569
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
2570
+ }
2571
+
2572
+ /* The TLS-member hooks (scr_tls.c registers them when linked): TLSSocket
2573
+ * property reads, event names, and method names layer over the plain
2574
+ * socket dispatch without this unit naming that one's symbols. */
2575
+ static const ScrNetDynhTlsHooks *scr_net_dynh_tls = NULL;
2576
+ void scr_net_set_dynh_tls(const ScrNetDynhTlsHooks *hooks) { scr_net_dynh_tls = hooks; }
2577
+
2578
+ static bool scr_net_dynh_name_is(const ScrDyn *name, const char *lit) {
2579
+ size_t n = strlen(lit);
2580
+ return name->kind == SCR_DYN_STR && name->v.str->len == n &&
2581
+ memcmp(name->v.str->data, lit, n) == 0;
2582
+ }
2583
+
2584
+ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *method,
2585
+ ScrDyn *const *args, size_t argc, const char *what) {
2586
+ ScrNetSocket *s = (ScrNetSocket *)h;
2587
+ bool reg = false, once = false;
2588
+ if (strcmp(method, "on") == 0 || strcmp(method, "addListener") == 0) reg = true;
2589
+ else if (strcmp(method, "once") == 0) { reg = true; once = true; }
2590
+ if (reg) {
2591
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
2592
+ const ScrDyn *cb = argc > 1 ? args[1] : scr_dyn_undefined();
2593
+ scr_dyn_check_listener(cb, "listener");
2594
+ if (scr_exc_pending()) return NULL;
2595
+ if (scr_net_dynh_name_is(name, "data")) {
2596
+ scr_net_sock_on_data(s, scr_dyn_listener_closure_data(cb), (ScrNetDataFn)&scr_dyn_listener_fire_data, once);
2597
+ } else if (scr_net_dynh_name_is(name, "end")) {
2598
+ scr_net_sock_on_end(s, scr_dyn_listener_closure0(cb), once);
2599
+ } else if (scr_net_dynh_name_is(name, "close")) {
2600
+ scr_net_sock_on_close(s, scr_dyn_listener_closure0(cb), once);
2601
+ } else if (scr_net_dynh_name_is(name, "error")) {
2602
+ scr_net_sock_on_error(s, scr_dyn_listener_closure_err(cb), (ScrChildErrFn)&scr_dyn_listener_fire_err, once);
2603
+ } else if (scr_net_dynh_name_is(name, "connect")) {
2604
+ scr_net_sock_on_connect(s, scr_dyn_listener_closure0(cb), once);
2605
+ } else if (scr_net_dynh_name_is(name, "timeout")) {
2606
+ scr_net_sock_on_timeout(s, scr_dyn_listener_closure0(cb), once);
2607
+ } else if (scr_net_dynh_name_is(name, "readable")) {
2608
+ scr_net_sock_on_readable(s, scr_dyn_listener_closure0(cb), once);
2609
+ } else if (scr_net_dynh_name_is(name, "drain")) {
2610
+ /* This surface never backpressures (write answers true) — an
2611
+ * accepted, never-fired registration is the consistent answer. */
2612
+ } else if (scr_net_dynh_tls != NULL && name->kind == SCR_DYN_STR &&
2613
+ scr_net_dynh_tls->on(s, name->v.str->data, cb, once)) {
2614
+ /* a TLS event name ('secureConnect'), handled by the TLS unit */
2615
+ } else {
2616
+ ScrJsonBuf b;
2617
+ scr_jb_init(&b);
2618
+ scr_jb_puts(&b, "listening for '");
2619
+ if (name->kind == SCR_DYN_STR) {
2620
+ for (size_t i = 0; i < name->v.str->len; i++) scr_jb_putc(&b, name->v.str->data[i]);
2621
+ }
2622
+ scr_jb_puts(&b, "' on a dynamic Socket is not supported yet");
2623
+ scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
2624
+ return NULL;
2625
+ }
2626
+ return scr_dyn_retain(self);
2627
+ }
2628
+ if (strcmp(method, "write") == 0) {
2629
+ const ScrDyn *chunk = argc > 0 ? args[0] : scr_dyn_undefined();
2630
+ if (chunk->kind != SCR_DYN_STR && chunk->kind != SCR_DYN_BYTES) {
2631
+ scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", chunk);
2632
+ return NULL;
2633
+ }
2634
+ if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) {
2635
+ scr_net_dynh_unsupported("write", "write(chunk, callback) is not modeled");
2636
+ return NULL;
2637
+ }
2638
+ if (chunk->kind == SCR_DYN_STR) scr_net_sock_write_str(s, chunk->v.str);
2639
+ else scr_net_sock_write_bytes(s, chunk->v.bytes);
2640
+ return scr_dyn_new_bool(true); /* backpressure is not modeled (SEMANTICS.md) */
2641
+ }
2642
+ if (strcmp(method, "end") == 0) {
2643
+ const ScrDyn *chunk = argc > 0 ? args[0] : scr_dyn_undefined();
2644
+ if (chunk->kind == SCR_DYN_STR) scr_net_sock_end_str(s, chunk->v.str);
2645
+ else if (chunk->kind == SCR_DYN_BYTES) scr_net_sock_end_bytes(s, chunk->v.bytes);
2646
+ else if (chunk->kind == SCR_DYN_UNDEF || argc == 0) scr_net_sock_end(s);
2647
+ else if (chunk->kind == SCR_DYN_FUNC) {
2648
+ scr_net_dynh_unsupported("end", "end(callback) is not modeled");
2649
+ return NULL;
2650
+ } else {
2651
+ scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", chunk);
2652
+ return NULL;
2653
+ }
2654
+ return scr_dyn_retain(self);
2655
+ }
2656
+ if (strcmp(method, "destroy") == 0) {
2657
+ if (argc > 0 && args[0]->kind != SCR_DYN_UNDEF) {
2658
+ scr_net_dynh_unsupported("destroy", "destroy(error) carries a payload this surface does not model");
2659
+ return NULL;
2660
+ }
2661
+ scr_net_sock_destroy(s);
2662
+ return scr_dyn_retain(self);
2663
+ }
2664
+ if (strcmp(method, "setTimeout") == 0) {
2665
+ const ScrDyn *ms = argc > 0 ? args[0] : scr_dyn_undefined();
2666
+ if (ms->kind != SCR_DYN_NUM) {
2667
+ scr_dyn_arg_type_fail("msecs", "of type number", ms);
2668
+ return NULL;
2669
+ }
2670
+ scr_net_sock_set_timeout(s, ms->v.num);
2671
+ if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) {
2672
+ scr_net_sock_on_timeout(s, scr_dyn_listener_closure0(args[1]), true);
2673
+ } else if (argc > 1 && args[1]->kind != SCR_DYN_UNDEF) {
2674
+ scr_dyn_check_listener(args[1], "callback");
2675
+ return NULL;
2676
+ }
2677
+ return scr_dyn_retain(self);
2678
+ }
2679
+ if (strcmp(method, "setEncoding") == 0) {
2680
+ const ScrDyn *enc = argc > 0 ? args[0] : scr_dyn_undefined();
2681
+ if (enc->kind != SCR_DYN_STR) {
2682
+ scr_dyn_arg_type_fail("encoding", "of type string", enc);
2683
+ return NULL;
2684
+ }
2685
+ scr_net_sock_set_encoding(s, enc->v.str);
2686
+ if (scr_exc_pending()) return NULL;
2687
+ return scr_dyn_retain(self);
2688
+ }
2689
+ if (strcmp(method, "pipe") == 0) {
2690
+ const ScrDyn *dst = argc > 0 ? args[0] : scr_dyn_undefined();
2691
+ if (dst->kind == SCR_DYN_HANDLE && dst->v.handle.tag == SCR_DYNH_NET_SOCKET) {
2692
+ scr_net_sock_pipe(s, (ScrNetSocket *)dst->v.handle.ptr);
2693
+ return scr_dyn_retain((ScrDyn *)dst);
2694
+ }
2695
+ if (dst->kind == SCR_DYN_HANDLE) {
2696
+ /* Cross-unit destinations (a ServerResponse) accept the source
2697
+ * through their pipe_from hook — this unit cannot name theirs. */
2698
+ const ScrDynHandleOps *dops = scr_dyn_handle_ops_of(dst);
2699
+ if (dops->pipe_from && dops->pipe_from(dst->v.handle.ptr, self)) {
2700
+ return scr_dyn_retain((ScrDyn *)dst);
2701
+ }
2702
+ }
2703
+ scr_net_dynh_unsupported("pipe", "only Socket and ServerResponse destinations are modeled");
2704
+ return NULL;
2705
+ }
2706
+ if (strcmp(method, "toString") == 0) {
2707
+ ScrStr *str = scr_str_new("[object Object]", 15);
2708
+ ScrDyn *d = scr_dyn_new_str(str);
2709
+ scr_str_release(str);
2710
+ return d;
2711
+ }
2712
+ if (scr_net_dynh_tls != NULL) {
2713
+ ScrDyn *out = NULL;
2714
+ if (scr_net_dynh_tls->invoke(s, method, args, argc, &out)) return out;
2715
+ }
2716
+ {
2717
+ static const char *const known[] = { "connect", "setNoDelay", "setKeepAlive",
2718
+ "address", "pause", "resume", "ref", "unref", "cork", "uncork", "read", "unpipe",
2719
+ "off", "removeListener", "removeAllListeners", "emit", "prependListener",
2720
+ "prependOnceListener", "listenerCount", "listeners", "resetAndDestroy", "destroySoon", NULL };
2721
+ for (size_t i = 0; known[i]; i++) {
2722
+ if (strcmp(method, known[i]) == 0) {
2723
+ scr_net_dynh_unsupported(method, NULL);
2724
+ return NULL;
2725
+ }
2726
+ }
2727
+ }
2728
+ {
2729
+ char msg[160];
2730
+ int n = snprintf(msg, sizeof msg, "%s is not a function", what);
2731
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)n);
2732
+ }
2733
+ return NULL;
2734
+ }
2735
+
2736
+ static ScrDyn *scr_net_dynh_sock_get(void *h, const char *key, size_t key_len) {
2737
+ ScrNetSocket *s = (ScrNetSocket *)h;
2738
+ (void)key_len;
2739
+ if (strcmp(key, "destroyed") == 0) return scr_dyn_new_bool(scr_net_sock_destroyed(s));
2740
+ if (strcmp(key, "writable") == 0) return scr_dyn_new_bool(scr_net_sock_writable(s));
2741
+ if (strcmp(key, "remoteAddress") == 0) {
2742
+ ScrStr *a = scr_net_sock_remote_address(s);
2743
+ if (!a) return NULL; /* the undefined arm, the static lane's split */
2744
+ ScrDyn *d = scr_dyn_new_str(a);
2745
+ scr_str_release(a);
2746
+ return d;
2747
+ }
2748
+ if (strcmp(key, "encrypted") == 0) {
2749
+ /* Node types `encrypted: true` on TLSSocket only; plain sockets read
2750
+ * undefined — the static lane's false→undefined mapping. */
2751
+ return scr_net_sock_encrypted(s) ? scr_dyn_new_bool(true) : NULL;
2752
+ }
2753
+ if (scr_net_dynh_tls != NULL) {
2754
+ ScrDyn *out = NULL;
2755
+ if (scr_net_dynh_tls->get(s, key, &out)) return out;
2756
+ }
2757
+ {
2758
+ static const char *const known[] = { "remotePort", "remoteFamily", "localAddress",
2759
+ "localPort", "localFamily", "bytesRead", "bytesWritten", "connecting", "pending",
2760
+ "readyState", "bufferSize", "timeout", "readable", "closed", "errored", NULL };
2761
+ for (size_t i = 0; known[i]; i++) {
2762
+ if (strcmp(key, known[i]) == 0) {
2763
+ scr_net_dynh_unsupported(key, NULL);
2764
+ return NULL;
2765
+ }
2766
+ }
2767
+ }
2768
+ return NULL;
2769
+ }
2770
+
2771
+ static bool scr_net_dynh_sock_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
2772
+ (void)h; (void)key; (void)key_len; (void)value;
2773
+ return false;
2774
+ }
2775
+
2776
+ static const ScrDynHandleOps scr_net_dynh_sock_ops = {
2777
+ "Socket",
2778
+ &scr_net_sock_retain_v,
2779
+ &scr_net_sock_release_v,
2780
+ &scr_net_dynh_sock_invoke,
2781
+ &scr_net_dynh_sock_get,
2782
+ &scr_net_dynh_sock_set,
2783
+ NULL,
2784
+ };
2785
+
2786
+ /* ── checked-dynamic handle dispatch (SCR_DYNH_NET_SERVER ops) ─────────
2787
+ * The server half: `let server; server = createServer(...)` puts the
2788
+ * handle into a dyn binding, and every listener body's `this` answers
2789
+ * the emitting server (the ambient receiver) — both dispatch here.
2790
+ * Members ride the same entry points the static lowerings use. HTTP
2791
+ * server events ('request') route through the hook scr_http.c registers
2792
+ * at its dyn install — this unit cannot name http entry points without
2793
+ * breaking the link gate. */
2794
+
2795
+ static bool (*scr_net_dynh_http_on)(ScrNetServer *s, const char *event, const ScrDyn *cb,
2796
+ bool once);
2797
+ void scr_net_set_dynh_http_on(bool (*fn)(ScrNetServer *, const char *, const ScrDyn *, bool)) {
2798
+ scr_net_dynh_http_on = fn;
2799
+ }
2800
+
2801
+ static void scr_net_dynh_srv_unsupported(const char *member, const char *why) {
2802
+ char msg[160];
2803
+ int n = snprintf(msg, sizeof msg, "'Server.prototype.%s' on a dynamic value is not supported yet%s%s",
2804
+ member, why ? " — " : "", why ? why : "");
2805
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)n);
2806
+ }
2807
+
2808
+ /* The 'connection' fire for a dyn listener: box the socket by reference
2809
+ * and call through the checked-dynamic machinery (the ScrNetConnFn ABI —
2810
+ * sock arrives +1 and the adapter owns it, like the static adapters). */
2811
+ static void scr_net_dynh_fire_conn(ScrClosure *cb, ScrNetSocket *sock /* +1 */) {
2812
+ ScrDyn *fn = scr_dyn_listener_fn(cb);
2813
+ ScrDyn *dsock = scr_dyn_new_handle(sock, SCR_DYNH_NET_SOCKET);
2814
+ ScrDyn *args[1] = { dsock };
2815
+ ScrDyn *r = scr_dyn_call(fn, args, 1, "listener");
2816
+ scr_dyn_release(r);
2817
+ scr_dyn_release(dsock);
2818
+ scr_dyn_release(fn);
2819
+ scr_net_sock_release(sock);
2820
+ }
2821
+
2822
+ /* server.address() — Node's { address, family, port } object, null while
2823
+ * not listening (never bound, or already closed). */
2824
+ static ScrDyn *scr_net_dynh_srv_address(ScrNetServer *s) {
2825
+ if (!s->listening || s->fd < 0) return scr_dyn_new_null();
2826
+ ScrDyn *o = scr_dyn_new_obj();
2827
+ {
2828
+ ScrStr *a = s->bound_host ? scr_str_retain(s->bound_host)
2829
+ : scr_str_new(s->bound_v6 ? "::" : "0.0.0.0", s->bound_v6 ? 2 : 7);
2830
+ scr_dyn_obj_set(o, "address", 7, scr_dyn_new_str(a));
2831
+ scr_str_release(a);
2832
+ }
2833
+ {
2834
+ const char *fam = s->bound_v6 ? "IPv6" : "IPv4";
2835
+ ScrStr *f = scr_str_new(fam, 4);
2836
+ scr_dyn_obj_set(o, "family", 6, scr_dyn_new_str(f));
2837
+ scr_str_release(f);
2838
+ }
2839
+ scr_dyn_obj_set(o, "port", 4, scr_dyn_new_num((double)s->port));
2840
+ return o;
2841
+ }
2842
+
2843
+ static ScrDyn *scr_net_dynh_srv_invoke(void *h, ScrDyn *self, const char *method,
2844
+ ScrDyn *const *args, size_t argc, const char *what) {
2845
+ ScrNetServer *s = (ScrNetServer *)h;
2846
+ bool reg = false, once = false;
2847
+ if (strcmp(method, "on") == 0 || strcmp(method, "addListener") == 0) reg = true;
2848
+ else if (strcmp(method, "once") == 0) { reg = true; once = true; }
2849
+ if (reg) {
2850
+ const ScrDyn *name = argc > 0 ? args[0] : scr_dyn_undefined();
2851
+ const ScrDyn *cb = argc > 1 ? args[1] : scr_dyn_undefined();
2852
+ scr_dyn_check_listener(cb, "listener");
2853
+ if (scr_exc_pending()) return NULL;
2854
+ if (scr_net_dynh_name_is(name, "listening")) {
2855
+ scr_net_server_on_listening(s, scr_dyn_listener_closure0(cb), once);
2856
+ } else if (scr_net_dynh_name_is(name, "close")) {
2857
+ scr_net_server_on_close(s, scr_dyn_listener_closure0(cb), once);
2858
+ } else if (scr_net_dynh_name_is(name, "error")) {
2859
+ scr_net_server_on_error(s, scr_dyn_listener_closure_err(cb),
2860
+ (ScrChildErrFn)&scr_dyn_listener_fire_err, once);
2861
+ } else if (scr_net_dynh_name_is(name, "connection")) {
2862
+ scr_net_server_on_connection(s, scr_dyn_listener_closure_fn(cb, (void *)&scr_net_dynh_fire_conn),
2863
+ (ScrNetConnFn)&scr_net_dynh_fire_conn, once);
2864
+ } else if (scr_net_dynh_name_is(name, "secureConnection") && s->defer_conn) {
2865
+ /* a TLS server's 'connection' list IS post-handshake (deferred) —
2866
+ * exactly Node's secureConnection timing; on a plain server the
2867
+ * name falls through to the fence below */
2868
+ scr_net_server_on_connection(s, scr_dyn_listener_closure_fn(cb, (void *)&scr_net_dynh_fire_conn),
2869
+ (ScrNetConnFn)&scr_net_dynh_fire_conn, once);
2870
+ } else if (name->kind == SCR_DYN_STR && scr_net_dynh_http_on &&
2871
+ scr_net_dynh_http_on(s, name->v.str->data, cb, once)) {
2872
+ /* the http layer accepted the event ('request') */
2873
+ } else {
2874
+ ScrJsonBuf b;
2875
+ scr_jb_init(&b);
2876
+ scr_jb_puts(&b, "listening for '");
2877
+ if (name->kind == SCR_DYN_STR) {
2878
+ for (size_t i = 0; i < name->v.str->len; i++) scr_jb_putc(&b, name->v.str->data[i]);
2879
+ }
2880
+ scr_jb_puts(&b, "' on a dynamic Server is not supported yet");
2881
+ scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
2882
+ return NULL;
2883
+ }
2884
+ return scr_dyn_retain(self);
2885
+ }
2886
+ if (strcmp(method, "listen") == 0) {
2887
+ /* listen([port][, host][, cb]) — the static lowering's shapes over
2888
+ * dyn arguments; the options-object form fences loudly. */
2889
+ double port = 0;
2890
+ ScrStr *host = NULL;
2891
+ const ScrDyn *cb = NULL;
2892
+ size_t i = 0;
2893
+ if (i < argc && args[i]->kind == SCR_DYN_NUM) { port = args[i]->v.num; i++; }
2894
+ else if (i < argc && args[i]->kind == SCR_DYN_STR && args[i]->v.str->len > 0 &&
2895
+ args[i]->v.str->data[0] >= '0' && args[i]->v.str->data[0] <= '9') {
2896
+ /* Node coerces a numeric port string */
2897
+ port = strtod(args[i]->v.str->data, NULL);
2898
+ i++;
2899
+ } else if (i < argc && args[i]->kind == SCR_DYN_OBJ) {
2900
+ scr_net_dynh_srv_unsupported("listen", "the options-object form on a dynamic value is not modeled (use listen(port[, host][, cb]))");
2901
+ return NULL;
2902
+ }
2903
+ if (i < argc && args[i]->kind == SCR_DYN_STR) { host = args[i]->v.str; i++; }
2904
+ if (i < argc && args[i]->kind == SCR_DYN_FUNC) { cb = args[i]; i++; }
2905
+ if (i < argc && args[i]->kind != SCR_DYN_UNDEF && args[i]->kind != SCR_DYN_NULL) {
2906
+ scr_net_dynh_srv_unsupported("listen", "unrecognized arguments (the modeled shape is listen(port[, host][, cb]))");
2907
+ return NULL;
2908
+ }
2909
+ ScrClosure *clo = cb ? scr_dyn_listener_closure0(cb) : NULL;
2910
+ if (host) scr_net_listen_opts(s, port, host, false, clo);
2911
+ else scr_net_listen(s, port, clo);
2912
+ return scr_dyn_retain(self); /* Node's `return this` chaining */
2913
+ }
2914
+ if (strcmp(method, "close") == 0) {
2915
+ const ScrDyn *cb = argc > 0 ? args[0] : scr_dyn_undefined();
2916
+ if (cb->kind == SCR_DYN_FUNC) {
2917
+ scr_net_server_close(s, scr_dyn_listener_closure0(cb));
2918
+ } else if (cb->kind == SCR_DYN_UNDEF || argc == 0) {
2919
+ scr_net_server_close(s, NULL);
2920
+ } else {
2921
+ scr_dyn_check_listener(cb, "callback");
2922
+ return NULL;
2923
+ }
2924
+ return scr_dyn_retain(self);
2925
+ }
2926
+ if (strcmp(method, "address") == 0) {
2927
+ return scr_net_dynh_srv_address(s);
2928
+ }
2929
+ if (strcmp(method, "emit") == 0 && argc > 0 && scr_net_dynh_name_is(args[0], "connection")) {
2930
+ const ScrDyn *sockd = argc > 1 ? args[1] : scr_dyn_undefined();
2931
+ if (sockd->kind == SCR_DYN_HANDLE && sockd->v.handle.tag == SCR_DYNH_NET_SOCKET) {
2932
+ scr_net_server_emit_connection(s, (ScrNetSocket *)sockd->v.handle.ptr);
2933
+ return scr_dyn_new_bool(true);
2934
+ }
2935
+ scr_dyn_arg_type_fail("socket", "an instance of Socket", sockd);
2936
+ return NULL;
2937
+ }
2938
+ if (strcmp(method, "toString") == 0) {
2939
+ ScrStr *str = scr_str_new("[object Object]", 15);
2940
+ ScrDyn *d = scr_dyn_new_str(str);
2941
+ scr_str_release(str);
2942
+ return d;
2943
+ }
2944
+ {
2945
+ static const char *const known[] = { "ref", "unref", "setTimeout", "getConnections",
2946
+ "off", "removeListener", "removeAllListeners", "emit", "prependListener",
2947
+ "prependOnceListener", "listenerCount", "listeners", "closeAllConnections",
2948
+ "closeIdleConnections", NULL };
2949
+ for (size_t i = 0; known[i]; i++) {
2950
+ if (strcmp(method, known[i]) == 0) {
2951
+ scr_net_dynh_srv_unsupported(method, NULL);
2952
+ return NULL;
2953
+ }
2954
+ }
2955
+ }
2956
+ {
2957
+ char msg[160];
2958
+ int n = snprintf(msg, sizeof msg, "%s is not a function", what);
2959
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)n);
2960
+ }
2961
+ return NULL;
2962
+ }
2963
+
2964
+ static ScrDyn *scr_net_dynh_srv_get(void *h, const char *key, size_t key_len) {
2965
+ ScrNetServer *s = (ScrNetServer *)h;
2966
+ (void)key_len;
2967
+ if (strcmp(key, "listening") == 0) return scr_dyn_new_bool(s->listening);
2968
+ {
2969
+ static const char *const known[] = { "maxConnections", "connections", "maxHeadersCount",
2970
+ "timeout", "keepAliveTimeout", "headersTimeout", "requestTimeout", NULL };
2971
+ for (size_t i = 0; known[i]; i++) {
2972
+ if (strcmp(key, known[i]) == 0) {
2973
+ scr_net_dynh_srv_unsupported(key, NULL);
2974
+ return NULL;
2975
+ }
2976
+ }
2977
+ }
2978
+ return NULL;
2979
+ }
2980
+
2981
+ static bool scr_net_dynh_srv_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
2982
+ (void)h; (void)key; (void)key_len; (void)value;
2983
+ return false;
2984
+ }
2985
+
2986
+ static const ScrDynHandleOps scr_net_dynh_srv_ops = {
2987
+ "Server",
2988
+ &scr_net_server_retain_v,
2989
+ &scr_net_server_release_v,
2990
+ &scr_net_dynh_srv_invoke,
2991
+ &scr_net_dynh_srv_get,
2992
+ &scr_net_dynh_srv_set,
2993
+ NULL,
2994
+ };
2995
+
2996
+ void scr_net_dyn_install(void) {
2997
+ scr_dyn_handle_install(SCR_DYNH_NET_SOCKET, &scr_net_dynh_sock_ops);
2998
+ scr_dyn_handle_install(SCR_DYNH_NET_SERVER, &scr_net_dynh_srv_ops);
2999
+ }
3000
+
3001
+ /* Checked-dynamic chunks into a TYPED socket (an untyped JS payload
3002
+ * flowing into socket.write/end — the static lowering's dyn-data arm):
3003
+ * STR/BYTES dispatch onto the typed entries; undefined/null end()s plain
3004
+ * (Node accepts them); anything else throws Node's ERR_INVALID_ARG_TYPE
3005
+ * chunk TypeError. Both borrowed. */
3006
+ void scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d) {
3007
+ if (d->kind == SCR_DYN_STR) scr_net_sock_write_str(s, d->v.str);
3008
+ else if (d->kind == SCR_DYN_BYTES) scr_net_sock_write_bytes(s, d->v.bytes);
3009
+ else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d);
3010
+ }
3011
+
3012
+ void scr_net_sock_end_dynv(ScrNetSocket *s, const ScrDyn *d) {
3013
+ if (d->kind == SCR_DYN_STR) scr_net_sock_end_str(s, d->v.str);
3014
+ else if (d->kind == SCR_DYN_BYTES) scr_net_sock_end_bytes(s, d->v.bytes);
3015
+ else if (d->kind == SCR_DYN_UNDEF || d->kind == SCR_DYN_NULL) scr_net_sock_end(s);
3016
+ else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d);
3017
+ }