@scriptc/runtime 0.0.0 → 0.0.1
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.
- package/LICENSE +202 -0
- package/package.json +16 -6
- package/src/scr_array.c +488 -0
- package/src/scr_assert.c +1393 -0
- package/src/scr_async.c +2593 -0
- package/src/scr_async_dyn.c +607 -0
- package/src/scr_bytes.c +1425 -0
- package/src/scr_bytes_io.c +161 -0
- package/src/scr_child.c +3587 -0
- package/src/scr_closure.c +214 -0
- package/src/scr_console.c +143 -0
- package/src/scr_cycle.c +218 -0
- package/src/scr_dc.c +805 -0
- package/src/scr_dgram.c +1007 -0
- package/src/scr_dyn_handle.c +154 -0
- package/src/scr_dyn_invoke.c +644 -0
- package/src/scr_error.c +332 -0
- package/src/scr_events.c +714 -0
- package/src/scr_events_emitter.c +699 -0
- package/src/scr_exception.c +242 -0
- package/src/scr_fetch.c +1130 -0
- package/src/scr_fetch_curl.c +654 -0
- package/src/scr_http.c +3482 -0
- package/src/scr_http2.c +3030 -0
- package/src/scr_inspect.c +803 -0
- package/src/scr_inspect_island.c +51 -0
- package/src/scr_island.c +9363 -0
- package/src/scr_json.c +2151 -0
- package/src/scr_lib.c +3612 -0
- package/src/scr_loop_epoll.c +241 -0
- package/src/scr_loop_kqueue.c +121 -0
- package/src/scr_loop_wsapoll.c +208 -0
- package/src/scr_map.c +591 -0
- package/src/scr_net.c +3017 -0
- package/src/scr_net_island.c +458 -0
- package/src/scr_number.c +115 -0
- package/src/scr_object.c +25 -0
- package/src/scr_path.c +1266 -0
- package/src/scr_platform.h +119 -0
- package/src/scr_readline.c +287 -0
- package/src/scr_regex.c +1080 -0
- package/src/scr_runtime.h +5036 -0
- package/src/scr_stream.c +2877 -0
- package/src/scr_string.c +1176 -0
- package/src/scr_symbol.c +132 -0
- package/src/scr_test.c +662 -0
- package/src/scr_tls.c +1520 -0
- package/src/scr_union.c +105 -0
- package/src/scr_url.c +911 -0
- package/src/scr_url_internal.h +29 -0
- package/src/scr_url_params.c +561 -0
- package/src/scr_watch.c +568 -0
- package/src/scr_web.c +1778 -0
- package/src/scr_win.c +76 -0
- package/src/scr_zlib.c +197 -0
- package/src/scr_zlib_island.c +15 -0
- package/vendor/README.md +52 -0
- package/vendor/curl/COPYRIGHT +534 -0
- package/vendor/curl/include/curl/curl.h +3214 -0
- package/vendor/curl/include/curl/curlver.h +79 -0
- package/vendor/curl/include/curl/easy.h +125 -0
- package/vendor/curl/include/curl/header.h +74 -0
- package/vendor/curl/include/curl/mprintf.h +52 -0
- package/vendor/curl/include/curl/multi.h +460 -0
- package/vendor/curl/include/curl/options.h +70 -0
- package/vendor/curl/include/curl/stdcheaders.h +35 -0
- package/vendor/curl/include/curl/system.h +508 -0
- package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
- package/vendor/curl/include/curl/urlapi.h +149 -0
- package/vendor/curl/include/curl/websockets.h +84 -0
- package/vendor/mbedtls/LICENSE +553 -0
- package/vendor/mbedtls/include/CMakeLists.txt +22 -0
- package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
- package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
- package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
- package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
- package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
- package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
- package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
- package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
- package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
- package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
- package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
- package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
- package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
- package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
- package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
- package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
- package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
- package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
- package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
- package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
- package/vendor/mbedtls/include/mbedtls/des.h +385 -0
- package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
- package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
- package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
- package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
- package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
- package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
- package/vendor/mbedtls/include/mbedtls/error.h +201 -0
- package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
- package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
- package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
- package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
- package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
- package/vendor/mbedtls/include/mbedtls/md.h +526 -0
- package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
- package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
- package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
- package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
- package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
- package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
- package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
- package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
- package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
- package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
- package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
- package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
- package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
- package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
- package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
- package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
- package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
- package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
- package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
- package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
- package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
- package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
- package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
- package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
- package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
- package/vendor/mbedtls/include/mbedtls/version.h +78 -0
- package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
- package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
- package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
- package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
- package/vendor/mbedtls/include/psa/build_info.h +20 -0
- package/vendor/mbedtls/include/psa/crypto.h +4998 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
- package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
- package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
- package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
- package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
- package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
- package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
- package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
- package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
- package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
- package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
- package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
- package/vendor/mbedtls/library/aes.c +2294 -0
- package/vendor/mbedtls/library/aesce.c +624 -0
- package/vendor/mbedtls/library/aesce.h +136 -0
- package/vendor/mbedtls/library/aesni.c +846 -0
- package/vendor/mbedtls/library/aesni.h +162 -0
- package/vendor/mbedtls/library/alignment.h +704 -0
- package/vendor/mbedtls/library/aria.c +969 -0
- package/vendor/mbedtls/library/asn1parse.c +468 -0
- package/vendor/mbedtls/library/asn1write.c +440 -0
- package/vendor/mbedtls/library/base64.c +322 -0
- package/vendor/mbedtls/library/base64_internal.h +45 -0
- package/vendor/mbedtls/library/bignum.c +2583 -0
- package/vendor/mbedtls/library/bignum_core.c +1240 -0
- package/vendor/mbedtls/library/bignum_core.h +872 -0
- package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
- package/vendor/mbedtls/library/bignum_internal.h +122 -0
- package/vendor/mbedtls/library/bignum_mod.c +394 -0
- package/vendor/mbedtls/library/bignum_mod.h +452 -0
- package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
- package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
- package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
- package/vendor/mbedtls/library/block_cipher.c +207 -0
- package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
- package/vendor/mbedtls/library/bn_mul.h +1094 -0
- package/vendor/mbedtls/library/camellia.c +1058 -0
- package/vendor/mbedtls/library/ccm.c +777 -0
- package/vendor/mbedtls/library/chacha20.c +563 -0
- package/vendor/mbedtls/library/chacha20_internal.h +23 -0
- package/vendor/mbedtls/library/chachapoly.c +487 -0
- package/vendor/mbedtls/library/check_crypto_config.h +136 -0
- package/vendor/mbedtls/library/cipher.c +1712 -0
- package/vendor/mbedtls/library/cipher_invasive.h +28 -0
- package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
- package/vendor/mbedtls/library/cipher_wrap.h +178 -0
- package/vendor/mbedtls/library/cmac.c +1067 -0
- package/vendor/mbedtls/library/common.h +487 -0
- package/vendor/mbedtls/library/constant_time.c +247 -0
- package/vendor/mbedtls/library/constant_time_impl.h +541 -0
- package/vendor/mbedtls/library/constant_time_internal.h +579 -0
- package/vendor/mbedtls/library/ctr.h +35 -0
- package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
- package/vendor/mbedtls/library/debug.c +475 -0
- package/vendor/mbedtls/library/debug_internal.h +185 -0
- package/vendor/mbedtls/library/des.c +1042 -0
- package/vendor/mbedtls/library/dhm.c +700 -0
- package/vendor/mbedtls/library/ecdh.c +696 -0
- package/vendor/mbedtls/library/ecdsa.c +858 -0
- package/vendor/mbedtls/library/ecjpake.c +1216 -0
- package/vendor/mbedtls/library/ecp.c +3674 -0
- package/vendor/mbedtls/library/ecp_curves.c +6125 -0
- package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
- package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
- package/vendor/mbedtls/library/ecp_invasive.h +305 -0
- package/vendor/mbedtls/library/entropy.c +680 -0
- package/vendor/mbedtls/library/entropy_poll.c +233 -0
- package/vendor/mbedtls/library/entropy_poll.h +64 -0
- package/vendor/mbedtls/library/error.c +878 -0
- package/vendor/mbedtls/library/gcm.c +1330 -0
- package/vendor/mbedtls/library/hkdf.c +161 -0
- package/vendor/mbedtls/library/hmac_drbg.c +633 -0
- package/vendor/mbedtls/library/lmots.c +789 -0
- package/vendor/mbedtls/library/lmots.h +288 -0
- package/vendor/mbedtls/library/lms.c +778 -0
- package/vendor/mbedtls/library/md.c +1108 -0
- package/vendor/mbedtls/library/md5.c +426 -0
- package/vendor/mbedtls/library/md_psa.h +26 -0
- package/vendor/mbedtls/library/md_wrap.h +46 -0
- package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
- package/vendor/mbedtls/library/mps_common.h +181 -0
- package/vendor/mbedtls/library/mps_error.h +89 -0
- package/vendor/mbedtls/library/mps_reader.c +538 -0
- package/vendor/mbedtls/library/mps_reader.h +366 -0
- package/vendor/mbedtls/library/mps_trace.c +112 -0
- package/vendor/mbedtls/library/mps_trace.h +154 -0
- package/vendor/mbedtls/library/net_sockets.c +694 -0
- package/vendor/mbedtls/library/nist_kw.c +729 -0
- package/vendor/mbedtls/library/oid.c +1166 -0
- package/vendor/mbedtls/library/padlock.c +157 -0
- package/vendor/mbedtls/library/padlock.h +111 -0
- package/vendor/mbedtls/library/pem.c +554 -0
- package/vendor/mbedtls/library/pk.c +1602 -0
- package/vendor/mbedtls/library/pk_ecc.c +261 -0
- package/vendor/mbedtls/library/pk_internal.h +241 -0
- package/vendor/mbedtls/library/pk_wrap.c +1618 -0
- package/vendor/mbedtls/library/pk_wrap.h +138 -0
- package/vendor/mbedtls/library/pkcs12.c +437 -0
- package/vendor/mbedtls/library/pkcs5.c +500 -0
- package/vendor/mbedtls/library/pkcs7.c +787 -0
- package/vendor/mbedtls/library/pkparse.c +1392 -0
- package/vendor/mbedtls/library/pkwrite.c +631 -0
- package/vendor/mbedtls/library/pkwrite.h +121 -0
- package/vendor/mbedtls/library/platform.c +402 -0
- package/vendor/mbedtls/library/platform_util.c +258 -0
- package/vendor/mbedtls/library/poly1305.c +492 -0
- package/vendor/mbedtls/library/psa_crypto.c +9532 -0
- package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
- package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
- package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
- package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
- package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
- package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
- package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
- package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
- package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
- package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
- package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
- package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
- package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
- package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
- package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
- package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
- package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
- package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
- package/vendor/mbedtls/library/psa_its_file.c +254 -0
- package/vendor/mbedtls/library/psa_util.c +614 -0
- package/vendor/mbedtls/library/psa_util_internal.h +100 -0
- package/vendor/mbedtls/library/ripemd160.c +490 -0
- package/vendor/mbedtls/library/rsa.c +3121 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
- package/vendor/mbedtls/library/rsa_internal.h +188 -0
- package/vendor/mbedtls/library/sha1.c +480 -0
- package/vendor/mbedtls/library/sha256.c +983 -0
- package/vendor/mbedtls/library/sha3.c +721 -0
- package/vendor/mbedtls/library/sha512.c +1115 -0
- package/vendor/mbedtls/library/ssl_cache.c +410 -0
- package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
- package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
- package/vendor/mbedtls/library/ssl_client.c +1023 -0
- package/vendor/mbedtls/library/ssl_client.h +22 -0
- package/vendor/mbedtls/library/ssl_cookie.c +384 -0
- package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
- package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
- package/vendor/mbedtls/library/ssl_misc.h +3198 -0
- package/vendor/mbedtls/library/ssl_msg.c +6619 -0
- package/vendor/mbedtls/library/ssl_ticket.c +556 -0
- package/vendor/mbedtls/library/ssl_tls.c +10234 -0
- package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
- package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
- package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
- package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
- package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
- package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
- package/vendor/mbedtls/library/threading.c +193 -0
- package/vendor/mbedtls/library/threading_internal.h +28 -0
- package/vendor/mbedtls/library/timing.c +152 -0
- package/vendor/mbedtls/library/version.c +32 -0
- package/vendor/mbedtls/library/version_features.c +856 -0
- package/vendor/mbedtls/library/x509.c +1776 -0
- package/vendor/mbedtls/library/x509_create.c +570 -0
- package/vendor/mbedtls/library/x509_crl.c +708 -0
- package/vendor/mbedtls/library/x509_crt.c +3311 -0
- package/vendor/mbedtls/library/x509_csr.c +648 -0
- package/vendor/mbedtls/library/x509_internal.h +101 -0
- package/vendor/mbedtls/library/x509write.c +174 -0
- package/vendor/mbedtls/library/x509write_crt.c +688 -0
- package/vendor/mbedtls/library/x509write_csr.c +336 -0
- package/vendor/quickjs-ng/CMakeLists.txt +566 -0
- package/vendor/quickjs-ng/LICENSE +24 -0
- package/vendor/quickjs-ng/README.md +24 -0
- package/vendor/quickjs-ng/api-test.c +1195 -0
- package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
- package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
- package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
- package/vendor/quickjs-ng/cutils.h +1998 -0
- package/vendor/quickjs-ng/dtoa.c +1619 -0
- package/vendor/quickjs-ng/dtoa.h +87 -0
- package/vendor/quickjs-ng/gen/function_source.c +81 -0
- package/vendor/quickjs-ng/gen/hello.c +53 -0
- package/vendor/quickjs-ng/gen/hello_module.c +106 -0
- package/vendor/quickjs-ng/gen/repl.c +3036 -0
- package/vendor/quickjs-ng/gen/standalone.c +323 -0
- package/vendor/quickjs-ng/gen/test_fib.c +81 -0
- package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
- package/vendor/quickjs-ng/libregexp.c +3474 -0
- package/vendor/quickjs-ng/libregexp.h +101 -0
- package/vendor/quickjs-ng/libunicode-table.h +5173 -0
- package/vendor/quickjs-ng/libunicode.c +2069 -0
- package/vendor/quickjs-ng/libunicode.h +172 -0
- package/vendor/quickjs-ng/list.h +107 -0
- package/vendor/quickjs-ng/lre-test.c +52 -0
- package/vendor/quickjs-ng/qjs.c +762 -0
- package/vendor/quickjs-ng/qjsc.c +673 -0
- package/vendor/quickjs-ng/quickjs-atom.h +280 -0
- package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
- package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
- package/vendor/quickjs-ng/quickjs-libc.h +87 -0
- package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
- package/vendor/quickjs-ng/quickjs.c +64041 -0
- package/vendor/quickjs-ng/quickjs.h +1447 -0
- package/vendor/quickjs-ng/run-test262.c +2374 -0
- package/vendor/quickjs-ng/unicode_gen.c +3121 -0
- package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
- package/vendor/ryu/LICENSE-Boost +23 -0
- package/vendor/ryu/common.h +114 -0
- package/vendor/ryu/d2s.c +509 -0
- package/vendor/ryu/d2s_full_table.h +367 -0
- package/vendor/ryu/d2s_intrinsics.h +357 -0
- package/vendor/ryu/d2s_small_table.h +186 -0
- package/vendor/ryu/digit_table.h +35 -0
- package/vendor/ryu/ryu.h +46 -0
- package/vendor/zlib/LICENSE +22 -0
- package/vendor/zlib/adler32.c +164 -0
- package/vendor/zlib/compress.c +75 -0
- package/vendor/zlib/crc32.c +1049 -0
- package/vendor/zlib/crc32.h +9446 -0
- package/vendor/zlib/deflate.c +2139 -0
- package/vendor/zlib/deflate.h +377 -0
- package/vendor/zlib/gzclose.c +23 -0
- package/vendor/zlib/gzguts.h +214 -0
- package/vendor/zlib/gzlib.c +582 -0
- package/vendor/zlib/gzread.c +602 -0
- package/vendor/zlib/gzwrite.c +631 -0
- package/vendor/zlib/infback.c +628 -0
- package/vendor/zlib/inffast.c +320 -0
- package/vendor/zlib/inffast.h +11 -0
- package/vendor/zlib/inffixed.h +94 -0
- package/vendor/zlib/inflate.c +1526 -0
- package/vendor/zlib/inflate.h +126 -0
- package/vendor/zlib/inftrees.c +299 -0
- package/vendor/zlib/inftrees.h +62 -0
- package/vendor/zlib/trees.c +1117 -0
- package/vendor/zlib/trees.h +128 -0
- package/vendor/zlib/uncompr.c +85 -0
- package/vendor/zlib/zconf.h +543 -0
- package/vendor/zlib/zlib.h +1938 -0
- package/vendor/zlib/zutil.c +299 -0
- package/vendor/zlib/zutil.h +254 -0
- package/README.md +0 -3
- 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
|
+
}
|