Repository: awslabs/llrt Branch: main Commit: 36bd4a837d3f Files: 657 Total size: 4.5 MB Directory structure: gitextract_jm62atds/ ├── .cargo/ │ └── config.toml ├── .fleet/ │ └── settings.json ├── .github/ │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── build-modules.yml │ ├── build.yml │ ├── ci.yml │ ├── publish.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .vscode/ │ ├── launch.json │ └── settings.json ├── .yarnrc.yml ├── API.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── GOVERNANCE.md ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── NOTICE ├── README.md ├── THIRD_PARTY_LICENSES ├── benchmarks/ │ └── v8-v7/ │ ├── README.txt │ ├── base.js │ ├── crypto.js │ ├── deltablue.js │ ├── earley-boyer.js │ ├── index.js │ ├── navier-stokes.js │ ├── raytrace.js │ ├── regexp.js │ ├── richards.js │ └── splay.js ├── build.mjs ├── example/ │ ├── clear-ddb-table.mjs │ ├── functions/ │ │ ├── build.mjs │ │ ├── package.json │ │ ├── src/ │ │ │ ├── api.ts │ │ │ ├── hello.mjs │ │ │ ├── non-included-sdk.mjs │ │ │ ├── react/ │ │ │ │ ├── App.css │ │ │ │ ├── App.tsx │ │ │ │ ├── CreateTodo.tsx │ │ │ │ ├── TodoItem.tsx │ │ │ │ ├── TodoList.css │ │ │ │ ├── TodoList.tsx │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ ├── ssr.ts │ │ │ ├── types.d.ts │ │ │ ├── v2.js │ │ │ ├── v3-lib.mjs │ │ │ ├── v3-mono.mjs │ │ │ ├── v3-s3.mjs │ │ │ └── v3.mjs │ │ └── tsconfig.json │ ├── infrastructure/ │ │ ├── cdk.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── llrt-sam/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── hello-world/ │ │ │ ├── app.ts │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ ├── samconfig.toml │ │ └── template.yaml │ ├── llrt-sam-container-image/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── events/ │ │ │ └── event.json │ │ ├── hello-world/ │ │ │ ├── Dockerfile │ │ │ └── app.mjs │ │ ├── samconfig.toml │ │ └── template.yaml │ └── register-hooks/ │ ├── hooks/ │ │ ├── calc.js │ │ ├── fs.js │ │ ├── http.js │ │ └── v8.js │ ├── simple-server.js │ ├── simple-server.sh │ ├── src/ │ │ └── http.js │ ├── test.js │ └── test.sh ├── fixtures/ │ ├── a.js │ ├── b.js │ ├── c.cjs │ ├── cjs-handler.cjs │ ├── d.cjs │ ├── define-property-export.cjs │ ├── empty.js │ ├── empty.lrt │ ├── export-function.cjs │ ├── fs/ │ │ └── readdir/ │ │ ├── readdir.js │ │ └── recursive/ │ │ └── readdir.js │ ├── handler.mjs │ ├── hello.js │ ├── hello.txt │ ├── import.cjs │ ├── import.js │ ├── local.mjs │ ├── node_modules/ │ │ ├── elem-aws-lambda-powertools/ │ │ │ ├── commons/ │ │ │ │ ├── lib/ │ │ │ │ │ ├── cjs/ │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ └── typeUtils.js │ │ │ │ │ └── esm/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── typeUtils.js │ │ │ │ └── package.json │ │ │ └── jmespath/ │ │ │ ├── lib/ │ │ │ │ ├── cjs/ │ │ │ │ │ └── index.js │ │ │ │ └── esm/ │ │ │ │ └── index.js │ │ │ └── package.json │ │ ├── elem-debug/ │ │ │ ├── package.json │ │ │ └── src/ │ │ │ └── browser.js │ │ ├── elem-hono/ │ │ │ ├── dist/ │ │ │ │ ├── cjs/ │ │ │ │ │ └── utils/ │ │ │ │ │ └── url.js │ │ │ │ └── index.js │ │ │ └── package.json │ │ ├── elem-lodash.merge/ │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── elem-react-dom/ │ │ │ ├── cjs/ │ │ │ │ ├── react-dom-server.edge.development.js │ │ │ │ └── react-dom.development.js │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── server.edge.js │ │ └── elem-uuid/ │ │ ├── dist/ │ │ │ └── commonjs-browser/ │ │ │ └── index.js │ │ └── package.json │ ├── package.json │ ├── primitive-handler.mjs │ ├── prop-export.cjs │ ├── referenced-exports.cjs │ ├── require.mjs │ ├── sdk-handler.mjs │ ├── sdk-runtime-init.mjs │ ├── test1245/ │ │ ├── index.js │ │ ├── main/ │ │ │ └── foo.js │ │ └── package.json │ ├── test903/ │ │ ├── bar.mjs │ │ └── foo.mjs │ ├── test_modules/ │ │ ├── test-aws-lambda-powertools-jmespath.js │ │ ├── test-debug.js │ │ ├── test-elem-hono.js │ │ ├── test-lodash.merge.js │ │ ├── test-react-dom.js │ │ └── test-uuid.js │ ├── throw.js │ ├── throwing-handler.mjs │ ├── throwing-init-handler.mjs │ └── tla-webcall-handler.mjs ├── index.mjs ├── lambda-server.js ├── libs/ │ ├── llrt_build/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_compression/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_context/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_dns_cache/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_encoding/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src/ │ │ └── lib.rs │ ├── llrt_hooking/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_json/ │ │ ├── Cargo.toml │ │ ├── benches/ │ │ │ └── json.rs │ │ ├── build.rs │ │ └── src/ │ │ ├── escape.rs │ │ ├── lib.rs │ │ ├── parse.rs │ │ └── stringify.rs │ ├── llrt_logging/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_numbers/ │ │ ├── Cargo.toml │ │ ├── benches/ │ │ │ └── numbers.rs │ │ └── src/ │ │ └── lib.rs │ ├── llrt_test/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_test_tls/ │ │ ├── Cargo.toml │ │ ├── data/ │ │ │ ├── generate.sh │ │ │ ├── root.pem │ │ │ ├── server.key │ │ │ └── server.pem │ │ └── src/ │ │ ├── api.rs │ │ ├── config.rs │ │ ├── lib.rs │ │ └── server.rs │ └── llrt_utils/ │ ├── Cargo.toml │ └── src/ │ ├── any_of.rs │ ├── bytearray_buffer.rs │ ├── bytes.rs │ ├── class.rs │ ├── clone.rs │ ├── ctx.rs │ ├── error.rs │ ├── error_messages.rs │ ├── fs.rs │ ├── hash.rs │ ├── io.rs │ ├── latch.rs │ ├── lib.rs │ ├── macros.rs │ ├── mc_oneshot.rs │ ├── module.rs │ ├── object.rs │ ├── option.rs │ ├── primordials.rs │ ├── provider.rs │ ├── result.rs │ ├── reuse_list.rs │ ├── signals.rs │ ├── string.rs │ ├── sysinfo.rs │ └── time.rs ├── linker/ │ ├── ar │ ├── cc │ ├── cc-aarch64-linux-gnu │ ├── cc-aarch64-linux-musl │ ├── cc-x86_64-linux-gnu │ ├── cc-x86_64-linux-musl │ ├── cxx │ ├── cxx-aarch64-linux-gnu │ ├── cxx-aarch64-linux-musl │ ├── cxx-x86_64-linux-gnu │ ├── cxx-x86_64-linux-musl │ └── ranlib ├── llrt/ │ ├── Cargo.toml │ └── src/ │ ├── base.rs │ ├── main.c │ ├── main.rs │ ├── minimal_tracer.rs │ └── repl.rs ├── llrt_core/ │ ├── Cargo.toml │ ├── build.rs │ └── src/ │ ├── builtins_inspect.rs │ ├── bytecode.rs │ ├── compiler.rs │ ├── compiler_common.rs │ ├── environment.rs │ ├── http.rs │ ├── lib.rs │ ├── libs.rs │ ├── modules/ │ │ ├── console.rs │ │ ├── embedded/ │ │ │ ├── loader.rs │ │ │ ├── mod.rs │ │ │ └── resolver.rs │ │ ├── js/ │ │ │ ├── @llrt/ │ │ │ │ ├── expect/ │ │ │ │ │ ├── jest-asymmetric-matchers.ts │ │ │ │ │ ├── jest-expect.ts │ │ │ │ │ ├── jest-utils.ts │ │ │ │ │ └── stringify.ts │ │ │ │ └── test/ │ │ │ │ ├── CircularBuffer.ts │ │ │ │ ├── Color.ts │ │ │ │ ├── SocketClient.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared.ts │ │ │ │ └── worker.ts │ │ │ ├── llrt.d.ts │ │ │ ├── stream/ │ │ │ │ └── promises.ts │ │ │ └── stream.ts │ │ ├── llrt/ │ │ │ ├── hex.rs │ │ │ ├── mod.rs │ │ │ ├── qjs.rs │ │ │ ├── util.rs │ │ │ └── xml.rs │ │ └── mod.rs │ ├── runtime_client.rs │ ├── security.rs │ └── vm.rs ├── llrt_modules/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── lib.rs │ ├── module/ │ │ ├── loader.rs │ │ ├── mod.rs │ │ ├── require.rs │ │ └── resolver.rs │ ├── module_builder.rs │ └── package/ │ ├── loader.rs │ ├── mod.rs │ └── resolver.rs ├── modules/ │ ├── llrt_abort/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── abort_controller.rs │ │ ├── abort_signal.rs │ │ └── lib.rs │ ├── llrt_assert/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_async_hooks/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── finalization_registry.rs │ │ └── lib.rs │ ├── llrt_buffer/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── array_buffer_view.rs │ │ ├── blob.rs │ │ ├── buffer.rs │ │ ├── file.rs │ │ └── lib.rs │ ├── llrt_child_process/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_console/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_crypto/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── crc32.rs │ │ ├── hash.rs │ │ ├── lib.rs │ │ ├── provider/ │ │ │ ├── graviola.rs │ │ │ ├── mod.rs │ │ │ ├── openssl.rs │ │ │ ├── ring.rs │ │ │ └── rust/ │ │ │ ├── aes_variants.rs │ │ │ └── mod.rs │ │ └── subtle/ │ │ ├── crypto_key.rs │ │ ├── derive.rs │ │ ├── derive_algorithm.rs │ │ ├── digest.rs │ │ ├── encryption.rs │ │ ├── encryption_algorithm.rs │ │ ├── export_key.rs │ │ ├── generate_key.rs │ │ ├── import_key.rs │ │ ├── key_algorithm.rs │ │ ├── mod.rs │ │ ├── sign.rs │ │ ├── sign_algorithm.rs │ │ ├── stubs.rs │ │ ├── verify.rs │ │ └── wrapping.rs │ ├── llrt_dgram/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── lib.rs │ │ └── socket.rs │ ├── llrt_dns/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── lookup.rs │ ├── llrt_events/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── custom_event.rs │ │ ├── event.rs │ │ ├── event_target.rs │ │ └── lib.rs │ ├── llrt_exceptions/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_fetch/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── body.rs │ │ ├── fetch.rs │ │ ├── form_data.rs │ │ ├── headers.rs │ │ ├── incoming.rs │ │ ├── lib.rs │ │ ├── request.rs │ │ ├── response.rs │ │ └── security.rs │ ├── llrt_fs/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── access.rs │ │ ├── chmod.rs │ │ ├── file_handle.rs │ │ ├── lib.rs │ │ ├── mkdir.rs │ │ ├── open.rs │ │ ├── read_dir.rs │ │ ├── read_file.rs │ │ ├── rename.rs │ │ ├── rm.rs │ │ ├── stats.rs │ │ ├── symlink.rs │ │ └── write_file.rs │ ├── llrt_http/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── agent.rs │ │ ├── client.rs │ │ ├── config.rs │ │ └── lib.rs │ ├── llrt_intl/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── cldr_data.rs │ │ ├── date_time_format.rs │ │ ├── lib.rs │ │ └── pattern_formatter.rs │ ├── llrt_navigator/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_net/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── security.rs │ │ ├── server.rs │ │ └── socket.rs │ ├── llrt_os/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── network.rs │ │ ├── statistics.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── llrt_path/ │ │ ├── Cargo.toml │ │ ├── benches/ │ │ │ └── slash_replacement.rs │ │ └── src/ │ │ └── lib.rs │ ├── llrt_perf_hooks/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── performance.rs │ ├── llrt_process/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_stream/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── readable.rs │ │ └── writable.rs │ ├── llrt_stream_web/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── queuing_strategy/ │ │ │ ├── byte_length.rs │ │ │ ├── count.rs │ │ │ └── mod.rs │ │ ├── readable/ │ │ │ ├── byob_reader.rs │ │ │ ├── byte_controller.rs │ │ │ ├── controller.rs │ │ │ ├── default_controller.rs │ │ │ ├── default_reader.rs │ │ │ ├── iterator.rs │ │ │ ├── mod.rs │ │ │ ├── objects.rs │ │ │ ├── reader.rs │ │ │ └── stream/ │ │ │ ├── algorithms.rs │ │ │ ├── mod.rs │ │ │ ├── pipe.rs │ │ │ ├── source.rs │ │ │ └── tee.rs │ │ ├── readable_writable_pair.rs │ │ ├── utils/ │ │ │ ├── mod.rs │ │ │ ├── promise.rs │ │ │ └── queue.rs │ │ └── writable/ │ │ ├── default_controller.rs │ │ ├── default_writer.rs │ │ ├── mod.rs │ │ ├── objects.rs │ │ ├── stream/ │ │ │ ├── mod.rs │ │ │ └── sink.rs │ │ └── writer.rs │ ├── llrt_string_decoder/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── string_decoder.rs │ ├── llrt_temporal/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── duration.rs │ │ ├── instant.rs │ │ ├── lib.rs │ │ ├── now.rs │ │ ├── plain_date.rs │ │ ├── plain_date_time.rs │ │ ├── plain_time.rs │ │ ├── utils/ │ │ │ ├── date.rs │ │ │ ├── date_time.rs │ │ │ ├── mod.rs │ │ │ ├── round/ │ │ │ │ ├── date_time.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── span.rs │ │ │ │ ├── time.rs │ │ │ │ ├── timestamp.rs │ │ │ │ └── zoned.rs │ │ │ ├── span.rs │ │ │ ├── time.rs │ │ │ ├── total/ │ │ │ │ ├── mod.rs │ │ │ │ └── span.rs │ │ │ └── zoned.rs │ │ └── zoned_date_time.rs │ ├── llrt_timers/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_tls/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── no_verification.rs │ │ ├── openssl_config.rs │ │ └── rustls_config.rs │ ├── llrt_tty/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── llrt_url/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── url_class.rs │ │ └── url_search_params.rs │ ├── llrt_util/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── text_decoder.rs │ │ └── text_encoder.rs │ └── llrt_zlib/ │ ├── Cargo.toml │ └── src/ │ ├── brotli.rs │ ├── lib.rs │ ├── zlib.rs │ └── zstd.rs ├── pack ├── package.json ├── rustfmt.toml ├── sdk.cfg ├── shims/ │ ├── @aws-crypto/ │ │ ├── crc32.js │ │ ├── crc32c.js │ │ ├── index.js │ │ ├── sha1-browser.js │ │ └── sha256-browser.js │ ├── @smithy/ │ │ ├── abort-controller.js │ │ ├── split-stream.js │ │ ├── util-base64.js │ │ ├── util-hex-encoding.js │ │ └── util-utf8.js │ ├── collect-stream-body.js │ ├── create-read-stream.js │ ├── is-streaming.js │ ├── mnemonist/ │ │ └── lru-cache.js │ ├── sdk-stream-mixin.js │ ├── stream-collector.js │ └── string-hasher.js ├── tests/ │ ├── e2e/ │ │ ├── IntegTestResourcesStack.template.yml │ │ ├── README.md │ │ ├── cognito_identity.e2e.test.ts │ │ ├── dynamodb.e2e.test.ts │ │ └── s3.e2e.test.ts │ ├── unit/ │ │ ├── assert.test.ts │ │ ├── async_hooks.test.ts │ │ ├── asynchronous-processing-discipline.test.ts │ │ ├── buffer.test.ts │ │ ├── child_process.test.ts │ │ ├── clone.test.ts │ │ ├── compile.test.ts │ │ ├── console.test.ts │ │ ├── crypto.subtle.test.ts │ │ ├── crypto.test.ts │ │ ├── date.test.ts │ │ ├── dgram.test.ts │ │ ├── dns.test.ts │ │ ├── encoding.test.ts │ │ ├── events.test.ts │ │ ├── exceptions.test.ts │ │ ├── executable.test.ts │ │ ├── fetch.formdata.test.ts │ │ ├── fetch.headers.test.ts │ │ ├── fetch.request.test.ts │ │ ├── fetch.response.test.ts │ │ ├── fetch.test.ts │ │ ├── fs.test.ts │ │ ├── import.test.ts │ │ ├── intl.test.ts │ │ ├── jest-expect.test.ts │ │ ├── json.test.ts │ │ ├── llrt.qjs.test.ts │ │ ├── llrt.xml.test.ts │ │ ├── module.test.ts │ │ ├── navigator.test.ts │ │ ├── net.test.ts │ │ ├── numbers.test.ts │ │ ├── os.test.ts │ │ ├── path.test.ts │ │ ├── perf_hooks.test.ts │ │ ├── performance.test.ts │ │ ├── process.test.ts │ │ ├── require.test.ts │ │ ├── socket.test.ts │ │ ├── stream.test.ts │ │ ├── string_decoder.test.ts │ │ ├── symbol-to-string-tag.test.ts │ │ ├── temporal.duration.test.ts │ │ ├── temporal.instant.test.ts │ │ ├── temporal.now.test.ts │ │ ├── temporal.plaindate.test.ts │ │ ├── temporal.plaindatetime.test.ts │ │ ├── temporal.plaintime.test.ts │ │ ├── temporal.zoneddatetime.test.ts │ │ ├── test-utils.ts │ │ ├── timers.test.ts │ │ ├── tty.test.ts │ │ ├── url.test.ts │ │ ├── util.test.ts │ │ └── zlib.test.ts │ └── wpt/ │ ├── FileAPI/ │ │ └── support/ │ │ ├── Blob.js │ │ └── send-file-formdata-helper.js │ ├── FileAPI.blob.test.ts │ ├── FileAPI.file.test.ts │ ├── FileAPI.harness.js │ ├── README.md │ ├── WebCryptoAPI.derive_bits_keys.test.ts │ ├── WebCryptoAPI.digest.test.ts │ ├── WebCryptoAPI.harness.js │ ├── WebCryptoAPI.test.ts │ ├── common/ │ │ ├── gc.js │ │ ├── get-host-info.sub.js │ │ └── subset-tests.js │ ├── console.harness.js │ ├── console.test.ts │ ├── encoding/ │ │ └── resources/ │ │ ├── decoding-helpers.js │ │ └── encodings.js │ ├── encoding.harness.js │ ├── encoding.test.ts │ ├── fetch/ │ │ └── api/ │ │ ├── cors/ │ │ │ └── resources/ │ │ │ └── not-cors-safelisted.json │ │ ├── request/ │ │ │ ├── request-cache.js │ │ │ └── request-error.js │ │ └── resources/ │ │ ├── data.json │ │ ├── keepalive-helper.js │ │ ├── keepalive-worker.js │ │ ├── sw-intercept-abort.js │ │ ├── sw-intercept.js │ │ └── utils.js │ ├── fetch.api.abort.test.ts │ ├── fetch.api.basic.test.ts │ ├── fetch.api.body.test.ts │ ├── fetch.api.headers.test.ts │ ├── fetch.api.request.test.ts │ ├── fetch.api.response.test.ts │ ├── fetch.harness.js │ ├── hr-time.harness.js │ ├── hr-time.test.ts │ ├── resources/ │ │ ├── idlharness.js │ │ └── testharness.js │ ├── streams/ │ │ └── resources/ │ │ ├── recording-streams.js │ │ ├── rs-test-templates.js │ │ ├── rs-utils.js │ │ └── test-utils.js │ ├── streams.harness.js │ ├── streams.piping.test.ts │ ├── streams.readable-byte-streams.test.ts │ ├── streams.readable-streams.test.ts │ ├── streams.writable-streams.test.ts │ ├── url/ │ │ └── resources/ │ │ ├── IdnaTestV2-removed.json │ │ ├── IdnaTestV2.json │ │ ├── percent-encoding.json │ │ ├── setters_tests.json │ │ ├── toascii.json │ │ ├── urltestdata-javascript-only.json │ │ └── urltestdata.json │ ├── url.harness.js │ ├── url.test.ts │ ├── webidl-harness.js │ └── webidl.ecmascript-binding.es-exceptions.test.ts ├── tsconfig.json ├── types/ │ ├── .eslintrc.cjs │ ├── abort.d.ts │ ├── assert.d.ts │ ├── async_hooks.d.ts │ ├── buffer.d.ts │ ├── child_process.d.ts │ ├── console.d.ts │ ├── crypto.d.ts │ ├── dgram.d.ts │ ├── dns.d.ts │ ├── dom-events.d.ts │ ├── events.d.ts │ ├── exceptions.d.ts │ ├── fetch.d.ts │ ├── fs/ │ │ └── promises.d.ts │ ├── fs.d.ts │ ├── globals.d.ts │ ├── https.d.ts │ ├── index.d.ts │ ├── module.d.ts │ ├── navigator.d.ts │ ├── net.d.ts │ ├── os.d.ts │ ├── package.json │ ├── path.d.ts │ ├── perf_hooks.d.ts │ ├── process.d.ts │ ├── stream/ │ │ └── web.d.ts │ ├── stream.d.ts │ ├── string_decoder.d.ts │ ├── timers.d.ts │ ├── timezone.d.ts │ ├── tsconfig.json │ ├── tty.d.ts │ ├── url.d.ts │ ├── util.d.ts │ └── zlib.d.ts ├── vitest.config.mjs └── wpt_errors.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [target.aarch64-unknown-linux-musl] rustflags = [ "-Ctarget-feature=+lse,+crt-static", "-Ctarget-cpu=neoverse-n1", "-Zunstable-options", "-Cpanic=immediate-abort", "-Zpanic_abort_tests", "-Zthreads=8", ] linker = "./linker/cc-aarch64-linux-musl" ar = "./linker/ar" [target.x86_64-unknown-linux-musl] rustflags = [ "-Ctarget-feature=+crt-static", "-Ctarget-cpu=haswell", "-Zunstable-options", "-Cpanic=immediate-abort", "-Zpanic_abort_tests", "-Zthreads=8", ] linker = "./linker/cc-x86_64-linux-musl" ar = "./linker/ar" [env] CC_aarch64_apple_darwin = "/usr/bin/clang" CC_x86_64_apple_darwin = "/usr/bin/clang" CXX_aarch64_apple_darwin = "/usr/bin/clang" CXX_x86_64_apple_darwin = "/usr/bin/clang" [unstable] build-std = ["core", "compiler_builtins", "alloc", "std", "panic_abort"] ================================================ FILE: .fleet/settings.json ================================================ { "backend.maxHeapSizeMb": 2048 } ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly # Maintain dependencies for JS - package-ecosystem: "npm" directory: "/" # Location of package manifests schedule: interval: monthly groups: "@types": patterns: - "@types*" update-types: - "minor" - "patch" "@aws-sdk": patterns: - "@aws-sdk*" update-types: - "minor" - "patch" aws-cdk: patterns: - "aws-cdk" - "aws-cdk-lib" update-types: - "minor" - "patch" # Maintain dependencies for Rust - package-ecosystem: cargo directory: "/" schedule: interval: daily groups: rustcrypto: patterns: - "aes*" - "cbc" - "const-oid" - "ctr" - "der" - "ecdsa" - "elliptic-curve" - "md-5" - "hmac" - "p256" - "p384" - "p521" - "pkcs8" - "rsa" - "spki" - "x25519-dalek" ================================================ FILE: .github/pull_request_template.md ================================================ ### Issue # (if available) ### Description of changes ### Checklist - [ ] Created unit tests in `tests/unit` and/or in Rust for my feature if needed - [ ] Ran `make fix` to format JS and apply Clippy auto fixes - [ ] Made sure my code didn't add any additional warnings: `make check` - [ ] Added relevant type info in `types/` directory - [ ] Updated documentation if needed ([API.md](API.md)/[README.md](README.md)/Other) _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ ================================================ FILE: .github/workflows/build-modules.yml ================================================ name: Setup, Build & Test modules on: workflow_call: inputs: os: required: true type: string platform: required: true type: string arch: required: true type: string toolchain: required: true type: string crypto: required: false type: string default: "ring" permissions: contents: read jobs: build: name: ${{ inputs.arch }}-${{ inputs.platform }}-${{ inputs.crypto }} runs-on: ${{ inputs.os }} steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} - name: Install Windows OpenSSL if: inputs.platform == 'windows' && inputs.crypto == 'openssl' uses: msys2/setup-msys2@v2 with: msystem: MINGW64 update: true path-type: inherit install: >- mingw-w64-x86_64-gcc mingw-w64-x86_64-openssl mingw-w64-x86_64-pkgconf - name: Set OpenSSL env for Windows if: inputs.platform == 'windows' && inputs.crypto == 'openssl' shell: bash run: | echo "OPENSSL_DIR=D:/a/_temp/msys64/mingw64" >> $GITHUB_ENV echo "OPENSSL_LIB_DIR=D:/a/_temp/msys64/mingw64/lib" >> $GITHUB_ENV echo "OPENSSL_INCLUDE_DIR=D:/a/_temp/msys64/mingw64/include" >> $GITHUB_ENV - name: Map crypto feature to TLS and crypto features id: features shell: bash run: | case "${{ inputs.crypto }}" in graviola) echo "tls_feature=tls-graviola" >> $GITHUB_OUTPUT echo "crypto_feature=crypto-graviola" >> $GITHUB_OUTPUT ;; ring) echo "tls_feature=tls-ring" >> $GITHUB_OUTPUT echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT ;; openssl) echo "tls_feature=tls-openssl" >> $GITHUB_OUTPUT echo "crypto_feature=crypto-openssl" >> $GITHUB_OUTPUT if [ "${{ inputs.platform }}" != "windows" ]; then echo "extra_features=,openssl-vendored" >> $GITHUB_OUTPUT fi ;; aws-lc) echo "tls_feature=tls-aws-lc" >> $GITHUB_OUTPUT echo "crypto_feature=crypto-ring" >> $GITHUB_OUTPUT ;; *) echo "Unknown crypto feature: ${{ inputs.crypto }}" exit 1 ;; esac - name: Run build crates shell: bash env: RUSTFLAGS: "" TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} run: | crates=$(cargo metadata --no-deps --format-version 1 --quiet | jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name') for crate in $crates; do echo "Compiling crate: $crate" if [ "$crate" = "llrt_fetch" ]; then cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,compression-rust,$TLS_FEATURE" elif [ "$crate" = "llrt_http" ]; then cargo build -p "$crate" --no-default-features --features "http1,http2,webpki-roots,$TLS_FEATURE" elif [ "$crate" = "llrt_crypto" ]; then cargo build -p "$crate" --no-default-features --features "$CRYPTO_FEATURE" else cargo build -p "$crate" fi done - name: Run build all shell: bash env: TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | cargo build -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE$EXTRA_FEATURES" - name: Run tests all shell: bash env: TLS_FEATURE: ${{ steps.features.outputs.tls_feature }} CRYPTO_FEATURE: ${{ steps.features.outputs.crypto_feature }} EXTRA_FEATURES: ${{ steps.features.outputs.extra_features }} run: | cargo test -p llrt_modules --no-default-features --features "base,$TLS_FEATURE,$CRYPTO_FEATURE$EXTRA_FEATURES" ================================================ FILE: .github/workflows/build.yml ================================================ name: Setup, Build & Test on: workflow_call: inputs: os: required: true type: string platform: required: true type: string arch: required: true type: string release: required: false type: string toolchain: required: false type: string default: "nightly" crypto: required: false type: string default: "" jobs: build: runs-on: ${{ inputs.os }} name: build ${{ inputs.arch }}-${{ inputs.platform }}${{ inputs.crypto && format('-{0}', inputs.crypto) || '' }} steps: - name: Checkout uses: actions/checkout@v6 - name: Checkout submodules run: git submodule update --init --checkout - name: Setup Node.js uses: actions/setup-node@v6 with: cache: yarn node-version: lts/* - name: Install Linux dependencies if: inputs.platform == 'linux' run: | sudo apt-get -y update sudo apt-get -y install make nodejs sudo snap install zig --classic --beta - name: Install MacOS dependencies if: inputs.platform == 'darwin' env: HOMEBREW_NO_AUTO_UPDATE: 1 run: | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew install zig make - name: Install zig on windows if: inputs.platform == 'windows' shell: pwsh run: | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser iex "& {$(irm get.scoop.sh)} -RunAsAdmin" scoop install zig - name: Install Windows dependencies if: inputs.platform == 'windows' uses: msys2/setup-msys2@v2 with: msystem: MINGW64 update: true path-type: inherit install: >- mingw-w64-x86_64-make mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc mingw-w64-x86_64-clang mingw-w64-x86_64-perl zip zstd patch unzip - name: Install JavaScript dependencies run: | corepack enable yarn - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} - name: Map crypto to features id: features if: inputs.crypto != '' shell: bash run: | case "${{ inputs.crypto }}" in graviola) echo "cargo_features=--no-default-features --features macro,crypto-graviola-rust,tls-graviola" >> $GITHUB_OUTPUT ;; ring) echo "cargo_features=--no-default-features --features macro,crypto-ring-rust,tls-ring" >> $GITHUB_OUTPUT ;; openssl) if [ "${{ inputs.platform }}" = "windows" ]; then echo "cargo_features=--no-default-features --features macro,crypto-openssl,tls-openssl" >> $GITHUB_OUTPUT else echo "cargo_features=--no-default-features --features macro,crypto-openssl,tls-openssl,openssl-vendored" >> $GITHUB_OUTPUT fi ;; aws-lc) echo "cargo_features=--no-default-features --features macro,crypto-rust,tls-aws-lc" >> $GITHUB_OUTPUT ;; *) echo "Unknown crypto: ${{ inputs.crypto }}" exit 1 ;; esac - name: Run tests if: inputs.platform != 'windows' env: CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | make test-ci 2>&1 - name: Run tests on windows if: inputs.platform == 'windows' shell: msys2 {0} env: CARGO_FEATURES: ${{ steps.features.outputs.cargo_features }} run: | set -e make test-ci 2>&1 - name: Build Linux binaries if: inputs.release && inputs.platform == 'linux' run: | make libs-${{ inputs.release }} make release-aws-${{ inputs.release }} make release-aws-${{ inputs.release }}-no-sdk make release-aws-${{ inputs.release }}-full-sdk - name: Build Darwin binaries if: inputs.release && inputs.platform == 'darwin' run: | make llrt-darwin-${{ inputs.release }}.zip make llrt-darwin-${{ inputs.release }}-no-sdk.zip make llrt-darwin-${{ inputs.release }}-full-sdk.zip - name: Build Windows binaries if: inputs.release && inputs.platform == 'windows' shell: msys2 {0} run: | # HACK: Add scoop/shims to PATH export PATH="$PATH:/c/Users/$USER/scoop/shims" make stdlib rm -rf target/ || true make llrt-windows-${{ inputs.release }}.zip make llrt-windows-${{ inputs.release }}-no-sdk.zip make llrt-windows-${{ inputs.release }}-full-sdk.zip - name: Upload artifacts if: inputs.release uses: actions/upload-artifact@v7 with: name: artifacts-${{ inputs.platform }}-${{ inputs.arch }} path: | *.zip llrt-container-arm64* llrt-container-x64* - name: Upload changelog if: inputs.release && inputs.platform == 'linux' && inputs.arch == 'x86_64' uses: actions/upload-artifact@v7 with: name: changelog path: CHANGELOG.md ================================================ FILE: .github/workflows/ci.yml ================================================ name: LLRT CI on: push: branches: - "main" pull_request: # Only run on the latest ref concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: check: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable components: clippy, rustfmt - name: Format run: cargo fmt --all -- --check - name: Clippy run: | #create mock js files mkdir -p bundle/js for i in {1..5}; do echo "console.log(123);" > "bundle/js/test$i.js" done cargo clippy --all-targets -- -D warnings build: needs: - check strategy: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: # Ubuntu x64 - os: ubuntu-latest platform: linux arch: x86_64 toolchain: nightly crypto: ring - os: ubuntu-latest platform: linux arch: x86_64 toolchain: nightly crypto: aws-lc - os: ubuntu-latest platform: linux arch: x86_64 toolchain: nightly crypto: graviola - os: ubuntu-latest platform: linux arch: x86_64 toolchain: nightly crypto: openssl # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: nightly crypto: ring - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: nightly crypto: aws-lc - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: nightly crypto: graviola - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: nightly crypto: openssl # macOS arm64 - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly crypto: ring - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly crypto: aws-lc - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly crypto: graviola - os: macos-latest platform: darwin arch: aarch64 toolchain: nightly crypto: openssl # Windows x64 (ring, openssl, graviola - no aws-lc) - os: windows-latest platform: windows arch: x86_64 toolchain: nightly-x86_64-pc-windows-gnu crypto: ring - os: windows-latest platform: windows arch: x86_64 toolchain: nightly-x86_64-pc-windows-gnu crypto: openssl - os: windows-latest platform: windows arch: x86_64 toolchain: nightly-x86_64-pc-windows-gnu crypto: graviola uses: ./.github/workflows/build.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} crypto: ${{ matrix.crypto }} modules: needs: - check strategy: fail-fast: false matrix: include: # Ubuntu x64 - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable crypto: ring - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable crypto: aws-lc - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable crypto: graviola - os: ubuntu-latest platform: linux arch: x86_64 toolchain: stable crypto: openssl # Ubuntu arm64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: stable crypto: ring - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: stable crypto: aws-lc - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: stable crypto: graviola - os: ubuntu-24.04-arm platform: linux arch: aarch64 toolchain: stable crypto: openssl # macOS arm64 - os: macos-latest platform: darwin arch: aarch64 toolchain: stable crypto: ring - os: macos-latest platform: darwin arch: aarch64 toolchain: stable crypto: aws-lc - os: macos-latest platform: darwin arch: aarch64 toolchain: stable crypto: graviola - os: macos-latest platform: darwin arch: aarch64 toolchain: stable crypto: openssl # Windows x64 (ring, openssl, graviola - no aws-lc) - os: windows-latest platform: windows arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu crypto: ring - os: windows-latest platform: windows arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu crypto: openssl - os: windows-latest platform: windows arch: x86_64 toolchain: stable-x86_64-pc-windows-gnu crypto: graviola uses: ./.github/workflows/build-modules.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} toolchain: ${{ matrix.toolchain }} crypto: ${{ matrix.crypto }} ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish crates on: workflow_call: inputs: ref: required: true type: string secrets: CRATES_IO_TOKEN: required: true jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - name: Publish utils working-directory: ./llrt_utils run: | cargo publish sleep 10 env: CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - name: Publish modules working-directory: ./llrt_modules run: | cargo publish env: CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} ================================================ FILE: .github/workflows/release.yml ================================================ name: LLRT Release on: push: tags: - "v*.*.*" jobs: build: strategy: fail-fast: ${{ startsWith(github.ref, 'refs/tags/') }} matrix: include: - os: windows-latest platform: windows arch: x86_64 release: x64 toolchain: nightly-x86_64-pc-windows-gnu - os: ubuntu-latest platform: linux arch: x86_64 release: x64 - os: ubuntu-24.04-arm platform: linux arch: aarch64 release: arm64 - os: macos-latest platform: darwin arch: x86_64 release: x64 - os: macos-latest platform: darwin arch: aarch64 release: arm64 uses: ./.github/workflows/build.yml with: os: ${{ matrix.os }} platform: ${{ matrix.platform }} arch: ${{ matrix.arch }} release: ${{ matrix.release }} toolchain: ${{ matrix.toolchain }} release: permissions: contents: write discussions: write runs-on: ubuntu-latest needs: - build steps: - name: Download artifacts uses: actions/download-artifact@v8 with: merge-multiple: true - name: Fix permissions run: | chmod 755 ./llrt-container-x64* chmod 755 ./llrt-container-arm64* - name: Release uses: softprops/action-gh-release@v2 with: body_path: ./CHANGELOG.md preserve_order: true prerelease: contains(github.ref, 'beta') || contains(github.ref, 'alpha') || contains(github.ref, 'rc') files: | ./llrt-lambda-x64.zip ./llrt-lambda-x64-no-sdk.zip ./llrt-lambda-x64-full-sdk.zip ./llrt-lambda-arm64.zip ./llrt-lambda-arm64-no-sdk.zip ./llrt-lambda-arm64-full-sdk.zip ./llrt-container-x64 ./llrt-container-x64-no-sdk ./llrt-container-x64-full-sdk ./llrt-container-arm64 ./llrt-container-arm64-no-sdk ./llrt-container-arm64-full-sdk ./llrt-linux-x64.zip ./llrt-linux-x64-no-sdk.zip ./llrt-linux-x64-full-sdk.zip ./llrt-linux-arm64.zip ./llrt-linux-arm64-no-sdk.zip ./llrt-linux-arm64-full-sdk.zip ./llrt-darwin-x64.zip ./llrt-darwin-x64-no-sdk.zip ./llrt-darwin-x64-full-sdk.zip ./llrt-darwin-arm64.zip ./llrt-darwin-arm64-no-sdk.zip ./llrt-darwin-arm64-full-sdk.zip ./llrt-windows-x64.zip ./llrt-windows-x64-no-sdk.zip ./llrt-windows-x64-full-sdk.zip publish: needs: - build uses: ./.github/workflows/publish.yml with: ref: ${{ github.ref }} secrets: CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} ================================================ FILE: .gitignore ================================================ .DS_Store .idea .yarn .pnp* .tmp-llrt-aws-sdk /lib /target /llrt_core/target /llrt_core/src/bytecode_cache.rs /example/functions/build /slask /VERSION /bundle *.zip cdk.out flamegraph.svg out.stacks yarn-error.log llrt-container-* bootstrap node_modules !/fixtures/node_modules tests/wpt/revision /wpt wpt_errors.tmp ================================================ FILE: .gitmodules ================================================ [submodule "zstd"] path = zstd url = https://github.com/facebook/zstd.git update = none # Workaround for https://github.com/rust-lang/cargo/issues/4247 [submodule "wpt"] path = wpt url = https://github.com/web-platform-tests/wpt branch = master update = none # Workaround for https://github.com/rust-lang/cargo/issues/4247 ================================================ FILE: .prettierignore ================================================ # Ignore web-platform-tests to avoid collisions! /wpt **/node_modules ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "lldb", "request": "launch", "name": "Debug executable 'llrt'", "cargo": { "args": ["build"], "filter": { "name": "llrt", "kind": "bin" } }, "env": { "AWS_LAMBDA_FUNCTION_NAME": "n/a", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1", "AWS_LAMBDA_FUNCTION_VERSION": "1", //"AWS_LAMBDA_RUNTIME_API": "localhost:3000", "_EXIT_LOOP": "1", "RUST_LOG": "llrt=trace,hyper::http=trace", //"_HANDLER": "index.handler", "ISENGARD_PRODUCTION_ACCOUNT": "false", "AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "" }, "args": ["index.mjs"], "cwd": "${workspaceFolder}" }, { "type": "lldb", "request": "launch", "name": "Debug tests 'llrt'", "cargo": { "args": ["build"], "filter": { "name": "llrt", "kind": "bin" } }, "env": { "AWS_LAMBDA_FUNCTION_NAME": "n/a", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1", "AWS_LAMBDA_FUNCTION_VERSION": "1", "_AWS_LAMBDA_RUNTIME_API": "http://localhost:3000", "_EXIT_LOOP": "1", "RUST_LOG": "llrt=trace,hyper::http=trace", //"_HANDLER": "index.handler", "ISENGARD_PRODUCTION_ACCOUNT": "false", "AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_SESSION_TOKEN": "" }, "args": ["test", "-d", "bundle"], "cwd": "${workspaceFolder}" } ] } ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": ["Abortable", "rquickjs", "LLRT", "llrt"], "cmake.configureOnOpen": false, "rust-analyzer.showUnlinkedFileNotification": false, "editor.formatOnPaste": true, "editor.formatOnSave": true, "yaml.format.singleQuote": false } ================================================ FILE: .yarnrc.yml ================================================ nodeLinker: node-modules ================================================ FILE: API.md ================================================ # API documentation > [!NOTE] > The long term goal for LLRT is to become [WinterTC compliant](https://min-common-api.proposal.wintertc.org/). Not every API from Node.js will be supported. # Node.js API ## assert [ok](https://nodejs.org/api/assert.html#assertokvalue-message) ## async_hooks ### Static methods [createHook](https://nodejs.org/api/async_hooks.html#async_hookscreatehookcallbacks) [currentId](https://nodejs.org/api/async_hooks.html#async_hooksexecutionasyncid) [executionAsyncId](https://nodejs.org/api/async_hooks.html#async_hooksexecutionasyncid) [triggerAsyncId](https://nodejs.org/api/async_hooks.html#async_hookstriggerasyncid) ### Class: AsyncHook [enable](https://nodejs.org/api/async_hooks.html#asynchookenable) [disable](https://nodejs.org/api/async_hooks.html#asynchookdisable) #### Hook callbacks [init](https://nodejs.org/api/async_hooks.html#initasyncid-type-triggerasyncid-resource) [before](https://nodejs.org/api/async_hooks.html#beforeasyncid) [after](https://nodejs.org/api/async_hooks.html#afterasyncid) [destroy](https://nodejs.org/api/async_hooks.html#destroyasyncid) [promiseResolve](https://nodejs.org/api/async_hooks.html#promiseresolveasyncid) ## buffer ### Static methods [alloc](https://nodejs.org/api/buffer.html#static-method-bufferallocsize-fill-encoding) [allocUnsafe](https://nodejs.org/api/buffer.html#static-method-bufferallocunsafesize) [allocUnsafeSlow](https://nodejs.org/api/buffer.html#static-method-bufferallocunsafeslowsize) [byteLength](https://nodejs.org/api/buffer.html#static-method-bufferbytelengthstring-encoding) [concat](https://nodejs.org/api/buffer.html#static-method-bufferconcatlist-totallength) [from](https://nodejs.org/api/buffer.html#static-method-bufferfromarray) [isBuffer](https://nodejs.org/api/buffer.html#static-method-bufferisbufferobj) [isEncoding](https://nodejs.org/api/buffer.html#static-method-bufferisencodingencoding) ### Prototype methods [copy](https://nodejs.org/api/buffer.html#bufcopytarget-targetstart-sourcestart-sourceend) [readBigInt64BE](https://nodejs.org/api/buffer.html#bufreadbigint64beoffset) [readBigInt64LE](https://nodejs.org/api/buffer.html#bufreadbigint64leoffset) [readDoubleBE](https://nodejs.org/api/buffer.html#bufreaddoublebeoffset) [readDoubleLE](https://nodejs.org/api/buffer.html#bufreaddoubleleoffset) [readFloatBE](https://nodejs.org/api/buffer.html#bufreadfloatbeoffset) [readFloatLE](https://nodejs.org/api/buffer.html#bufreadfloatleoffset) [readInt8](https://nodejs.org/api/buffer.html#bufreadint8offset) [readInt16BE](https://nodejs.org/api/buffer.html#bufreadint16beoffset) [readInt16LE](https://nodejs.org/api/buffer.html#bufreadint16leoffset) [readInt32BE](https://nodejs.org/api/buffer.html#bufreadint32beoffset) [readInt32LE](https://nodejs.org/api/buffer.html#bufreadint32leoffset) [readUInt8](https://nodejs.org/api/buffer.html#bufreaduint8offset) [readUInt16BE](https://nodejs.org/api/buffer.html#bufreaduint16beoffset) [readUInt16LE](https://nodejs.org/api/buffer.html#bufreaduint16leoffset) [readUInt32BE](https://nodejs.org/api/buffer.html#bufreaduint32beoffset) [readUInt32LE](https://nodejs.org/api/buffer.html#bufreaduint32leoffset) [subarray](https://nodejs.org/api/buffer.html#bufsubarraystart-end) [toString](https://nodejs.org/api/buffer.html#buftostringencoding-start-end) [write](https://nodejs.org/api/buffer.html#bufwritestring-offset-length-encoding) [writeBigInt64BE](https://nodejs.org/api/buffer.html#bufwritebigint64bevalue-offset) [writeBigInt64LE](https://nodejs.org/api/buffer.html#bufwritebigint64levalue-offset) [writeDoubleBE](https://nodejs.org/api/buffer.html#bufwritedoublebevalue-offset) [writeDoubleLE](https://nodejs.org/api/buffer.html#bufwritedoublelevalue-offset) [writeFloatBE](https://nodejs.org/api/buffer.html#bufwritefloatbevalue-offset) [writeFloatLE](https://nodejs.org/api/buffer.html#bufwritefloatlevalue-offset) [writeInt8](https://nodejs.org/api/buffer.html#bufwriteint8value-offset) [writeInt16BE](https://nodejs.org/api/buffer.html#bufwriteint16bevalue-offset) [writeInt16LE](https://nodejs.org/api/buffer.html#bufwriteint16levalue-offset) [writeInt32BE](https://nodejs.org/api/buffer.html#bufwriteint32bevalue-offset) [writeInt32LE](https://nodejs.org/api/buffer.html#bufwriteint32levalue-offset) [writeUInt8](https://nodejs.org/api/buffer.html#bufwriteuint8value-offset) [writeUInt16BE](https://nodejs.org/api/buffer.html#bufwriteuint16bevalue-offset) [writeUInt16LE](https://nodejs.org/api/buffer.html#bufwriteuint16levalue-offset) [writeUInt32BE](https://nodejs.org/api/buffer.html#bufwriteuint32bevalue-offset) [writeUInt32LE](https://nodejs.org/api/buffer.html#bufwriteuint32levalue-offset) ### Constants [constants.MAX_LENGTH](https://nodejs.org/api/buffer.html#bufferconstantsmax_length) [constants.MAX_STRING_LENGTH](https://nodejs.org/api/buffer.html#bufferconstantsmax_string_length) Everything else inherited from [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) ## child_process > [!WARNING] > `spawn` uses native streams that is not 100% compatible with the Node.js Streams API. [spawn](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options) ## console [Console](https://nodejs.org/api/console.html#class-console) ## crypto [createHash](https://nodejs.org/api/crypto.html#cryptocreatehashalgorithm-options) [createHmac](https://nodejs.org/api/crypto.html#cryptocreatehmacalgorithm-key-options) [getRandomValues](https://nodejs.org/api/crypto.html#cryptogetrandomvaluestypedarray) [randomBytes](https://nodejs.org/api/crypto.html#cryptorandombytessize-callback) [randomFill](https://nodejs.org/api/crypto.html#cryptorandomfillbuffer-offset-size-callback) [randomFillSync](https://nodejs.org/api/crypto.html#cryptorandomfillsyncbuffer-offset-size) [randomInt](https://nodejs.org/api/crypto.html#cryptorandomintmin-max-callback) [randomUUID](https://nodejs.org/api/crypto.html#cryptorandomuuidoptions) [webcrypto](https://nodejs.org/api/crypto.html#cryptowebcrypto) ### LLRT specific hash classes Lightweight and fast hash classes for LLRT. - `Md5` - `Sha1` - `Sha256` - `Sha384` - `Sha512` - `Crc32` - `Crc32c` ## crypto.subtle [subtle.decrypt](https://nodejs.org/api/webcrypto.html#subtledecryptalgorithm-key-data) [subtle.deriveBits](https://nodejs.org/api/webcrypto.html#subtlederivebitsalgorithm-basekey-length) [subtle.digest](https://nodejs.org/api/webcrypto.html#subtledigestalgorithm-data) [subtle.encrypt](https://nodejs.org/api/webcrypto.html#subtleencryptalgorithm-key-data) [subtle.exportKey](https://nodejs.org/api/webcrypto.html#subtleexportkeyformat-key) [subtle.generateKey](https://nodejs.org/api/webcrypto.html#subtlegeneratekeyalgorithm-extractable-keyusages) [subtle.importKey](https://nodejs.org/api/webcrypto.html#subtleimportkeyformat-keydata-algorithm-extractable-keyusages) [subtle.sign](https://nodejs.org/api/webcrypto.html#subtlesignalgorithm-key-data) [subtle.verify](https://nodejs.org/api/webcrypto.html#subtleverifyalgorithm-key-signature-datah) ## dgram [createSocket](https://nodejs.org/api/dgram.html#dgramcreatesocketoptions-callback) ### Class: dgram.Socket [address](https://nodejs.org/api/dgram.html#socketaddress) [bind](https://nodejs.org/api/dgram.html#socketbindport-address-callback) [close](https://nodejs.org/api/dgram.html#socketclosecallback) [ref](https://nodejs.org/api/dgram.html#socketref) [send](https://nodejs.org/api/dgram.html#socketsendmsg-offset-length-port-address-callback) [unref](hhttps://nodejs.org/api/dgram.html#socketunref) ## dns [lookup](https://nodejs.org/api/dns.html#dnslookuphostname-options-callback) ## events [EventEmitter](https://nodejs.org/api/events.html#class-eventemitter) ## fs [accessSync](https://nodejs.org/api/fs.html#fsaccesssyncpath-mode) [constants](https://nodejs.org/api/fs.html#file-access-constants) [lstatSync](https://nodejs.org/api/fs.html#fslstatsyncpath-options) [mkdirSync](https://nodejs.org/api/fs.html#fsmkdirsyncpath-options) [mkdtempSync](https://nodejs.org/api/fs.html#fsmkdtempsyncprefix-options) [readdirSync](https://nodejs.org/api/fs.html#fsreaddirsyncpath-options) [readFileSync](https://nodejs.org/api/fs.html#fsreadfilesyncpath-options) [rmdirSync](https://nodejs.org/api/fs.html#fsrmdirsyncpath-options) [rmSync](https://nodejs.org/api/fs.html#fsrmsyncpath-options) [statSync](https://nodejs.org/api/fs.html#fsstatsyncpath-options) [writeFileSync](https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options) [chmodSync](https://nodejs.org/api/fs.html#fschmodsyncpath-mode) [renameSync](https://nodejs.org/api/fs.html#fsrenamesyncoldpath-newpath) [symlinkSync](https://nodejs.org/api/fs.html#fssymlinksynctarget-path-type) ## fs/promises [access](https://nodejs.org/api/fs.html#fsstatpath-options-callback) [constants](https://nodejs.org/api/fs.html#file-access-constants) [lstat](https://nodejs.org/api/fs.html#fspromiseslstatpath-options) [mkdir](https://nodejs.org/api/fs.html#fsmkdirpath-options-callback) [mkdtemp](https://nodejs.org/api/fs.html#fsmkdtempprefix-options-callback) [open](https://nodejs.org/api/fs.html#fspromisesopenpath-flags-mode) [readdir](https://nodejs.org/api/fs.html#fspromisesreaddirpath-options) [readFile](https://nodejs.org/api/fs.html#filehandlereadfileoptions) [rm](https://nodejs.org/api/fs.html#fsrmpath-options-callback) [rmdir](https://nodejs.org/api/fs.html#fsrmdirpath-options-callback) [stat](https://nodejs.org/api/fs.html#fsstatpath-options-callback) [writeFile](https://nodejs.org/api/fs.html#fspromiseswritefilefile-data-options) [chmod](https://nodejs.org/api/fs.html#fspromiseschmodpath-mode) [rename](https://nodejs.org/api/fs.html#fspromisesrenameoldpath-newpath) [symlink](https://nodejs.org/api/fs.html#fspromisessymlinktarget-path-type) ## https [Agent](https://nodejs.org/api/https.html#class-httpsagent) ## module [builtinModules](https://nodejs.org/api/module.html#modulebuiltinmodules) [createRequire](https://nodejs.org/api/module.html#modulecreaterequirefilename) > [!NOTE] > `require` is available from esm modules natively. This function is just for compatibility [isBuiltin](https://nodejs.org/api/module.html#moduleisbuiltinmodulename) [registerHooks](https://nodejs.org/api/module.html#moduleregisterhooksoptions) ## net > [!WARNING] > These APIs uses native streams that is not 100% compatible with the Node.js Streams API. Server APIs like `createSever` provides limited functionality useful for testing purposes. Serverless applications typically don't expose servers. Some server options are not supported: > `highWaterMark`, `pauseOnConnect`, `keepAlive`, `noDelay`, `keepAliveInitialDelay` [connect](https://nodejs.org/api/net.html#netconnect) [createConnection](https://nodejs.org/api/net.html#netcreateconnection) [createServer](https://nodejs.org/api/net.html#netcreateserveroptions-connectionlistener) ## os [arch](https://nodejs.org/api/os.html#osarch) [availableParallelism](https://nodejs.org/api/os.html#osavailableparallelism) [cpus](https://nodejs.org/api/os.html#oscpus) [devNull](https://nodejs.org/api/os.html#osdevnull) [endianness](https://nodejs.org/api/os.html#osendianness) [EOL](https://nodejs.org/api/os.html#oseol) [freemem](https://nodejs.org/api/os.html#osfreemem) [getPriority](https://nodejs.org/api/os.html#osgetprioritypid) [homedir](https://nodejs.org/api/os.html#oshomedir) [hostname](https://nodejs.org/api/os.html#oshostname) [loadavg](https://nodejs.org/api/os.html#osloadavg) [machine](https://nodejs.org/api/os.html#osmachine) [networkInterfaces](https://nodejs.org/api/os.html#osnetworkinterfaces) [platform](https://nodejs.org/api/os.html#osplatform) [release](https://nodejs.org/api/os.html#osrelease) [setPriority](https://nodejs.org/api/os.html#ossetprioritypid-priority) [tmpdir](https://nodejs.org/api/os.html#osplatform) [totalmem](https://nodejs.org/api/os.html#ostotalmem) [type](https://nodejs.org/api/os.html#ostype) [uptime](https://nodejs.org/api/os.html#osuptime) [userInfo](https://nodejs.org/api/os.html#osuserinfooptions) [version](https://nodejs.org/api/os.html#osversion) ## path [basename](https://nodejs.org/api/path.html#pathbasenamepath-suffix) [delimiter](https://nodejs.org/api/path.html#pathdelimiter) [dirname](https://nodejs.org/api/path.html#pathdirnamepath) [extname](https://nodejs.org/api/path.html#pathextnamepath) [format](https://nodejs.org/api/path.html#pathformatpathobject) [isAbsolute](https://nodejs.org/api/path.html#pathisabsolutepath) [join](https://nodejs.org/api/path.html#pathjoinpaths) [normalize](https://nodejs.org/api/path.html#pathnormalizepath) [parse](https://nodejs.org/api/path.html#pathparsepath) [relative](https://nodejs.org/api/path.html#pathrelativefrom-to) [resolve](https://nodejs.org/api/path.html#pathresolvepaths) ## perf_hooks _performance is available globally_ [performance.now](https://nodejs.org/api/perf_hooks.html#performancenow) ## process _process is available globally_ [arch](https://nodejs.org/api/process.html#processarch) [argv](https://nodejs.org/api/process.html#processargv) [argv0](https://nodejs.org/api/process.html#processargv0) [cwd](https://nodejs.org/api/process.html#processcwd) [env](https://nodejs.org/api/process.html#processenv) [exit](https://nodejs.org/api/process.html#processexitcode) [exitCode](https://nodejs.org/api/process.html#processexitcode-1) [getegid](https://nodejs.org/api/process.html#processgetegid) [geteuid](https://nodejs.org/api/process.html#processgeteuid) [getgid](https://nodejs.org/api/process.html#processgetgid) [getuid](https://nodejs.org/api/process.html#processgetuid) [hrtime](https://nodejs.org/api/process.html#processhrtime) [id](https://nodejs.org/api/process.html#processpid) [kill](https://nodejs.org/api/process.html#processkillpid-signal) [platform](https://nodejs.org/api/process.html#processplatform) [release](https://nodejs.org/api/process.html#processrelease) [setegid](https://nodejs.org/api/process.html#processsetegidgid) [seteuid](https://nodejs.org/api/process.html#processseteuiduid) [setgid](https://nodejs.org/api/process.html#processsetgidgid) [setuid](https://nodejs.org/api/process.html#processsetuiduid) [version](https://nodejs.org/api/process.html#processversion) [versions](https://nodejs.org/api/process.html#processversions) ## stream [Duplex](https://nodejs.org/api/stream.html#class-streamduplex) [PassThrough](https://nodejs.org/api/stream.html#class-streampassthrough) [Readable](https://nodejs.org/api/stream.html#class-streamreadable) [Stream](https://nodejs.org/api/stream.html#class-stream) [Transform](https://nodejs.org/api/stream.html#class-streamtransform) [Writable](https://nodejs.org/api/stream.html#class-streamwritable) [finished](https://nodejs.org/api/stream.html#streamfinishedstream-options-callback) [pipeline](https://nodejs.org/api/stream.html#streampipelinestreams-callback) ## stream/promises [finished](https://nodejs.org/api/stream.html#streamfinishedstream-options-callback) [pipeline](https://nodejs.org/api/stream.html#streampipelinestreams-callback) ## string_decoder [StringDecoder](https://nodejs.org/api/string_decoder.html#class-stringdecoder) ## timers _Also available globally_ [clearImmediate](https://nodejs.org/api/timers.html#clearimmediateimmediate) [clearInterval](https://nodejs.org/api/timers.html#clearintervaltimeout) [clearTimeout](https://nodejs.org/api/timers.html#cleartimeouttimeout) [setImmediate](https://nodejs.org/api/timers.html#setimmediatecallback-args) [setInterval](https://nodejs.org/api/timers.html#setintervalcallback-delay-args) [setTimeout](https://nodejs.org/api/timers.html#settimeoutcallback-delay-args) ## tty [isatty](https://nodejs.org/api/tty.html#ttyisattyfd) ## url ### Class [URL](https://nodejs.org/api/url.html#class-url) [URLSearchParams](https://nodejs.org/api/url.html#class-urlsearchparams) ### Prototype methods [domainToASCII](https://nodejs.org/api/url.html#urldomaintoasciidomain) [domainToUnicode](https://nodejs.org/api/url.html#urldomaintounicodedomain) [fileURLToPath](https://nodejs.org/api/url.html#urlfileurltopathurl-options) [format](https://nodejs.org/api/url.html#urlformaturl-options) [pathToFileURL](https://nodejs.org/api/url.html#urlpathtofileurlpath-options) [urlToHttpOptions](https://nodejs.org/api/url.html#urlurltohttpoptionsurl) ## util > [!IMPORTANT] > Supported encodings: hex, base64, utf-8, utf-16le, windows-1252 and their aliases. [format](https://nodejs.org/api/util.html#utilformatformat-args) [inherits](https://nodejs.org/api/util.html#utilinheritsconstructor-superconstructor) [TextDecoder](https://nodejs.org/api/util.html#class-utiltextdecoder) [TextEncoder](https://nodejs.org/api/util.html#class-utiltextdecoder) ## zlib ### Convenience methods [deflate](https://nodejs.org/api/zlib.html#zlibdeflatebuffer-options-callback) [deflateSync](https://nodejs.org/api/zlib.html#zlibdeflatesyncbuffer-options) [deflateRaw](https://nodejs.org/api/zlib.html#zlibdeflaterawbuffer-options-callback) [deflateRawSync](https://nodejs.org/api/zlib.html#zlibdeflaterawsyncbuffer-options) [gzip](https://nodejs.org/api/zlib.html#zlibgzipbuffer-options-callback) [gzipSync](https://nodejs.org/api/zlib.html#zlibgzipsyncbuffer-options) [inflate](https://nodejs.org/api/zlib.html#zlibinflatebuffer-options-callback) [inflateSync](https://nodejs.org/api/zlib.html#zlibinflatesyncbuffer-options) [inflateRaw](https://nodejs.org/api/zlib.html#zlibinflaterawbuffer-options-callback) [inflateRawSync](https://nodejs.org/api/zlib.html#zlibinflaterawsyncbuffer-options) [gunzip](https://nodejs.org/api/zlib.html#zlibgunzipbuffer-options-callback) [gunzipSync](https://nodejs.org/api/zlib.html#zlibgunzipsyncbuffer-options) [brotliCompress](https://nodejs.org/api/zlib.html#zlibbrotlicompressbuffer-options-callback) [brotliCompressSync](https://nodejs.org/api/zlib.html#zlibbrotlicompresssyncbuffer-options) [brotliDecompress](https://nodejs.org/api/zlib.html#zlibbrotlidecompressbuffer-options-callback) [brotliDecompressSync](https://nodejs.org/api/zlib.html#zlibbrotlidecompresssyncbuffer-options) [zstdCompress](https://nodejs.org/api/zlib.html#zlibzstdcompressbuffer-options-callback) [zstdCompressSync](https://nodejs.org/api/zlib.html#zlibzstdcompresssyncbuffer-options) [zstdDecompress](https://nodejs.org/api/zlib.html#zlibzstddecompressbuffer-options-callback) [zstdDecompressSync](https://nodejs.org/api/zlib.html#zlibzstddecompresssyncbuffer-options) # LLRT API ## llrt:hex ```typescript export function encode( value: string | Array | ArrayBuffer | Uint8Array ): string; export function decode(value: string): Uint8Array; ``` ## llrt:timezone Lightweight timezone support for LLRT. Provides timezone offset calculations and a minimal `Intl.DateTimeFormat` implementation for dayjs and similar library compatibility. ```typescript interface Timezone { /** * Get the UTC offset in minutes for a timezone at a given time. * Returns a positive value for timezones ahead of UTC (e.g., 540 for Asia/Tokyo) * and a negative value for timezones behind UTC (e.g., -420 for America/Denver). * Automatically handles DST transitions. */ getOffset(timezone: string, epochMs: number): number; /** * List all available IANA timezone names. */ list(): string[]; } declare var Timezone: Timezone; export { Timezone }; ``` ### Example ```javascript import { Timezone } from "llrt:timezone"; // Get current offset for Denver (handles DST automatically) const offset = Timezone.getOffset("America/Denver", Date.now()); // Returns -420 (UTC-7) in winter, -360 (UTC-6) in summer // Check DST transition const beforeDst = new Date("2024-03-09T12:00:00Z").getTime(); const afterDst = new Date("2024-03-11T12:00:00Z").getTime(); console.log(Timezone.getOffset("America/Denver", beforeDst)); // -420 console.log(Timezone.getOffset("America/Denver", afterDst)); // -360 // List all available timezones const zones = Timezone.list(); ``` ### Intl.DateTimeFormat LLRT provides a minimal `Intl.DateTimeFormat` implementation focused on timezone support. This enables libraries like dayjs to work with timezone conversions transparently. ```javascript // Create a formatter for a specific timezone const formatter = new Intl.DateTimeFormat("en-US", { timeZone: "America/Denver", hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", }); // Format a date const date = new Date("2022-03-02T15:45:34Z"); console.log(formatter.format(date)); // "03/02/2022, 08:45:34" // Get formatted parts const parts = formatter.formatToParts(date); // [{ type: "month", value: "03" }, { type: "literal", value: "/" }, ...] // Get resolved options const options = formatter.resolvedOptions(); console.log(options.timeZone); // "America/Denver" ``` ### Date.prototype.toLocaleString with timezone `Date.prototype.toLocaleString` is enhanced to support the `timeZone` option: ```javascript const date = new Date("2022-03-02T15:45:34Z"); // Convert to Denver time console.log(date.toLocaleString("en-US", { timeZone: "America/Denver" })); // "03/02/2022, 8:45:34 AM" // Convert to Tokyo time console.log(date.toLocaleString("en-US", { timeZone: "Asia/Tokyo" })); // "03/03/2022, 12:45:34 AM" ``` ### Using with dayjs The timezone module enables dayjs timezone support without polyfills: ```javascript const dayjs = require("dayjs"); const utc = require("dayjs/plugin/utc"); const timezone = require("dayjs/plugin/timezone"); dayjs.extend(utc); dayjs.extend(timezone); // Convert between timezones const date = dayjs("2022-03-02T15:45:34Z"); console.log(date.tz("America/Denver").format()); // "2022-03-02T08:45:34-07:00" console.log(date.tz("Asia/Tokyo").format()); // "2022-03-03T00:45:34+09:00" // Get start of day in a specific timezone const denver = date.tz("America/Denver"); console.log(denver.startOf("day").format()); // "2022-03-02T00:00:00-07:00" ``` ## llrt:qjs ```typescript interface MemoryInfo { malloc_size: number; malloc_limit: number; memory_used_size: number; malloc_count: number; memory_used_count: number; atom_count: number; atom_size: number; str_count: number; str_size: number; obj_count: number; obj_size: number; prop_count: number; prop_size: number; shape_count: number; shape_size: number; js_func_count: number; js_func_size: number; js_func_code_size: number; js_func_pc2line_count: number; js_func_pc2line_size: number; c_func_count: number; array_count: number; fast_array_count: number; fast_array_elements: number; binary_object_count: number; binary_object_size: number; } export function ComputeMemoryUsage(): MemoryInfo; ``` ## llrt:xml A lightweight and fast XML parser and builder ```typescript export class XmlText { constructor(text: string); toString(): string; } export class XmlNode { constructor(name: string); withName(name: string): this; addAttribute(name: string, value: string): this; addChildNode(node: XmlNode | XmlText): this; removeAttribute(name: string): this; toString(): string; } type XmlParserOptions = { ignoreAttributes?: boolean; attributeNamePrefix?: string; textNodeName?: string; attributeValueProcessor?: (attrName: string, attrValue: string, jpath: string) => unknown; tagValueProcessor?: (attrName: string, attrValue: string, jpath: string, hasAttributes: boolean) => unknown; } export class XMLParser(options?: XmlParserOptions){ parse(xml:string):object } ``` ## llrt:util ```typescript export function dimensions(): [number, number]; export function load(path: string): any; export function print(value: any): void; ``` # Web Platform API ## CONSOLE [Console](https://developer.mozilla.org/en-US/docs/Web/API/console) ## DOM [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) ## ECMASCRIPT [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) ## ENCODING [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) [TextEncoder](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) ## FETCH [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Headers) > [!IMPORTANT] > There are some differences with the [WHATWG standard](https://fetch.spec.whatwg.org). Mainly browser specific behavior is removed: > > - `keepalive` is always true > - `request.body` can only be `string`, `Array`, `ArrayBuffer` or `Uint8Array` > - `response.body` returns `null`. Use `response.text()`, `response.json()` etc > - `mode`, `credentials`, `referrerPolicy`, `priority`, `cache` is not available/applicable ## FILEAPI [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) [File](https://developer.mozilla.org/en-US/docs/Web/API/File) ## HR-TIME [performance.now](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) [performance.timeOrigin](https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin) ## HTML [atob](https://developer.mozilla.org/en-US/docs/Web/API/atob) [btoa](https://developer.mozilla.org/en-US/docs/Web/API/btoa) [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval) [clearTimeout](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout) [navigator](https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator) [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/Window/queueMicrotask) [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval) [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) [userAgent](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent) ## STREAMS [ByteLengthQueuingStrategy](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) [CountQueuingStrategy](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) [ReadableByteStreamController](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController) [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) [ReadableStreamBYOBReader](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader) [ReadableStreamBYOBRequest](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBRequest) [ReadableStreamDefaultController](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController) [ReadableStreamDefaultReader](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) [WritableStream](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) [WritableStreamDefaultController](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController) [WritableStreamDefaultWriter](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter) ## URL [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) ## WEBCRYPTO [Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) [CryptoKey](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) ## WEBIDL [DOMException](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) ## XHR [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) ================================================ FILE: CHANGELOG.md ================================================ ### Features - Added dgram (UDP socket) module support (@chessbyte, @richarddavison) - Added timezone support with minimal Intl.DateTimeFormat (@chessbyte, @richarddavison) - Added Symbol.toStringTag to Web API classes (@chessbyte) - Added Symbol.toStringTag to Crypto and SubtleCrypto (@chessbyte) - Implemented `symlink` and `symlinkSync` in fs module (@kyubisation, @richarddavison) - Exposed FormData in fetch (@nabetti1720) - Exposed `module.registerHooks()` (@nabetti1720, @richarddavison) - Exposed `llrt:qjs` module (@nabetti1720) - Added modular crypto with multiple backend options (@richarddavison) - Eliminated `ring` dependency from pure-rust crypto backend (@nabetti1720) - Exported Md5, Sha1, Sha256, Sha384, Sha512 hash classes (@richarddavison) - Improved performance interface compatibility in perf_hooks (@nabetti1720) - Simplified child_process signal handling and detachment (@richarddavison) - Added custom inspect functions for Map, Set, DataView, and ArrayBuffer (@richarddavison) - Added basic Agent support (@Sytten) - Transitioned from `chrono/chrono-tz` to `jiff` (@nabetti1720) ### Fixes - Fix Proxy object handling JSON and console modules - Fix S3 endpoint resolution issue in new SDK version (@richarddavison) - Fix suite hook handling in TestAgent (@richarddavison) - Handle secret param in Hash constructors for SDK signing (@richarddavison) - Fix Headers keys and values methods to return iterable values (@richarddavison) - Improve encoding handling in Hash and Hmac implementations (@richarddavison) - Fix Buffer from utf16le (@Sytten) - Buffer improvements (@Sytten) - Use Latin-1 encoding for atob() per WHATWG spec (@chessbyte, @richarddavison) - Fix buffer offsets when repeating subarray (@nabetti1720, @richarddavison) - Fix stream/web primordials used before initialization (@nabetti1720) - Correctly handle 'require' with parent directory specification (@nabetti1720, @richarddavison) - Register and run test hooks in correct order (@kyubisation, @richarddavison) - Fix & simplify json escape (@richarddavison) - Require primordials to be initialized from module once to avoid data race (@richarddavison) - Ignore init for global clients not in us-east-1 (@perpil) - Improve support for dns lookup (@Sytten) - Remove zstd WriteBuf dependency for byte slice conversion (@chessbyte) - Disable `mlkem` in rustls-graviola (@nabetti1720) - Optional webpki (@Sytten) ### Maintenance - Upgrade rquickjs to 0.11 (@nabetti1720) - Upgrade rquickjs to 0.10.0 (@mohebifar, @richarddavison) - Eliminate use of `jwalk` for directory recursion (@nabetti1720) - Update SDK dependencies and remove UUID module (@richarddavison) - Dependency upgrades Thanks for all the reports and contributors Full list of changes: https://github.com/awslabs/llrt/compare/v0.7.0-beta...v0.8.1-beta ================================================ FILE: CODE_OF_CONDUCT.md ================================================ ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guidelines Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional documentation, we greatly value feedback and contributions from our community. Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. ## Reporting Bugs/Feature Requests We welcome you to use the GitHub issue tracker to report bugs or suggest features. When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: - A reproducible test case or series of steps - The version of our code being used - Any modifications you've made relevant to the bug - Anything unusual about your environment or deployment ## Contributing via Pull Requests Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 1. You are working against the latest source on the _main_ branch. 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. To send us a pull request, please: 1. Fork the repository. 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 3. Ensure local tests pass. 4. Commit to your fork using clear commit messages. 5. Send us a pull request, answering any default questions in the pull request interface. 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). ## Finding contributions to work on Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact with any additional questions or comments. ## Security issue notifications If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. ## Licensing See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "libs/llrt_build", "libs/llrt_compression", "libs/llrt_context", "libs/llrt_encoding", "libs/llrt_json", "libs/llrt_numbers", "libs/llrt_test", "libs/llrt_test_tls", "libs/llrt_utils", "modules/llrt_abort", "modules/llrt_async_hooks", "modules/llrt_buffer", "modules/llrt_child_process", "modules/llrt_console", "modules/llrt_crypto", "modules/llrt_dgram", "modules/llrt_events", "modules/llrt_exceptions", "modules/llrt_fetch", "modules/llrt_fs", "modules/llrt_http", "modules/llrt_intl", "modules/llrt_navigator", "modules/llrt_net", "modules/llrt_os", "modules/llrt_path", "modules/llrt_perf_hooks", "modules/llrt_process", "modules/llrt_stream", "modules/llrt_stream_web", "modules/llrt_string_decoder", "modules/llrt_temporal", "modules/llrt_timers", "modules/llrt_tls", "modules/llrt_tty", "modules/llrt_url", "modules/llrt_util", "modules/llrt_zlib", "llrt_modules", "llrt_core", "llrt", ] [profile.flame] inherits = "release" strip = false debug = true [profile.release] strip = true lto = true codegen-units = 1 opt-level = 3 panic = "abort" [profile.test] opt-level = 3 ================================================ FILE: GOVERNANCE.md ================================================ # Project Governance (Very Minimal Governance model) This open source project is managed by a Steering Committee composed of the maintainers of this project. Maintainers are defined as individuals with full commit access to the project repositories. ## Steering Committee The Steering Committee will be responsible for oversight of all technical, project, approval, and policy matters for the project. This notably includes brand and trademark management. The Steering Committee members are listed in the MAINTAINERS.md file in the repository. New maintainers (and accordingly, Steering Committee members) may be added or removed by no less than 3/4 affirmative vote of the Steering Committee. The Steering Committee will appoint a Chair responsible for organizing Steering Committee activity. If the Steering Committee Chair is removed from the Committee (or the Chair steps down from that role), it is the responsibility of the Steering Committe to appoint a new Chair. The Steering Committee may, at its discretion, add or remove members who are not maintainers. ## Voting The Steering Committee will strive for all decisions to be made by consensus. While explicit agreement of the entire Steering Committee is preferred, it is not required for consensus. Rather, the Steering Committee will determine consensus based on their good faith consideration of a number of factors, including the dominant view of the Steering Committee and nature of support and objections. The Steering Committee will document evidence of consensus in accordance with these requirements. If consensus cannot be reached, the Steering Committee will make the decision by a vote. The Steering Committee Chair will call a vote with reasonable notice to the Steering Committee, setting out a discussion period and a separate voting period. Any discussion may be conducted in person or electronically by text, voice, or video. The discussion will be open to the public, with the notable exception of discussions involving embargoed security issues or the addition or removal of maintainers, which will be private. In any vote, each voting representative will have one vote. Except as specifically noted elsewhere in this document, decisions by vote require a simple majority vote of all voting members. ## Termination of Membership A maintainer’s access (and accordingly, their position on the Steering Committee) will be removed if any of the following occur: - Resignation: Written notice of resignation to the Steering Committee - Steering Committee Vote: 3/4 affirmative vote of the Steering Committee to remove a member - Unreachable Member: If a member is unresponsive for more than six months, the remaining active members of the Steering Committee may vote to remove the member ## License of this document This document is a modified work of the GitHub Minimal Viable Governance model, located here:  [https://github.com/github/MVG/](https://github.com/github/MVG/) This document may be used, modified, and/or distributed under the terms of the  [Creative Commons Attribution 4.0 International (CC-BY) license](https://creativecommons.org/licenses/by/4.0/legalcode). ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MAINTAINERS.md ================================================ ## Table of contents - [Overview](#overview) - [Current Maintainers](#current-maintainers) - [Labels](#labels) - [Maintainer Responsibilities](#maintainer-responsibilities) - [Uphold Code of Conduct](#uphold-code-of-conduct) - [Prioritize Security](#prioritize-security) - [Review Pull Requests](#review-pull-requests) - [Triage New Issues](#triage-new-issues) ## Overview > **Please treat this content as a living document.** This is document explains who the maintainers are (see below), what they do in this repo, and how they should be doing it. If you're interested in contributing, see [CONTRIBUTING](CONTRIBUTING.md). ## Current Maintainers | Maintainer | GitHub ID | Affiliation | | --------------- | --------------------------------------------------- | ----------- | | Richard Davison | [richarddavison](https://github.com/richarddavison) | Amazon | | Nik Pinski | [nikp](https://github.com/nikp) | Amazon | | Harold Sun | [bnusunny](https://github.com/bnusunny) | Amazon | | Iain Maitland | [imaitland](https://github.com/imaitland) | Amazon | ## Labels > WORK IN PROGRESS ## Maintainer Responsibilities Maintainers are active and visible members of the community, and have [maintain-level permissions on a repository](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization). Use those privileges to serve the community and evolve code as follows. Be aware of recurring ambiguous situations and [document them](#common-scenarios) to help your fellow maintainers. ### Uphold Code of Conduct Model the behavior set forward by the [Code of Conduct](CODE_OF_CONDUCT.md) and raise any violations to other maintainers and admins. There could be unusual circumstances where inappropriate behavior does not immediately fall within the [Code of Conduct](CODE_OF_CONDUCT.md). These might be nuanced and should be handled with extra care - when in doubt, do not engage and reach out to other maintainers and admins. ### Prioritize Security Security is your number one priority. Maintainer's Github keys must be password protected securely and any reported security vulnerabilities are addressed before features or bugs. Note that this repository is monitored and supported 24/7 by Amazon Security, see [Reporting a Vulnerability](CONTRIBUTING.md#security-issue-notifications) for details. ### Review Pull Requests Review pull requests regularly, comment, suggest, reject, merge and close. Accept only high quality pull-requests. Provide code reviews and guidance on incoming pull requests. PRs are [labeled](#labels) based on file changes and semantic title. Pay attention to whether labels reflect the current state of the PR and correct accordingly. Use and enforce [semantic versioning](https://semver.org/) pull request titles, as these will be used for [CHANGELOG](CHANGELOG.md) and [Release notes](https://github.com/awslabs/llrt/releases) - make sure they communicate their intent at the human level. See [Common scenarios](#common-scenarios) section for additional guidance. ### Triage New Issues Manage [labels](#labels), review issues regularly, and create new labels as needed by the project. Remove `triage` label when you're able to confirm the validity of a request, a bug can be reproduced, etc. Give priority to the original author for implementation, unless it is a sensitive task that is best handled by maintainers. Make sure issues are assigned to our [board of activities](https://github.com/orgs/awslabs/projects/145/) Use our [labels](#labels) to signal good first issues to new community members, and to set expectation that this might need additional feedback from the author, other customers, experienced community members and/or maintainers. > WORK IN PROGRESS ================================================ FILE: Makefile ================================================ TARGET_linux_x86_64 = x86_64-unknown-linux-musl TARGET_linux_arm64 = aarch64-unknown-linux-musl TARGET_darwin_x86_64 = x86_64-apple-darwin TARGET_darwin_arm64 = aarch64-apple-darwin TARGET_windows_x86_64 = x86_64-pc-windows-gnu TARGET_windows_arm64 = aarch64-is-not-yet-supported RUST_VERSION = nightly TOOLCHAIN = +$(RUST_VERSION) BUILD_ARG = $(TOOLCHAIN) build -r BUILD_DIR = ./target/release BUNDLE_DIR = bundle TS_SOURCES = $(wildcard llrt_core/src/modules/js/*.ts) $(wildcard llrt_core/src/modules/js/@llrt/test/*.ts) $(wildcard llrt_core/src/modules/js/@llrt/*.ts) $(wildcard tests/unit/*.ts) STD_JS_FILE = $(BUNDLE_DIR)/js/@llrt/std.js RELEASE_ARCH_NAME_x64 = x86_64 RELEASE_ARCH_NAME_arm64 = arm64 LAMBDA_PREFIX = llrt-lambda RELEASE_TARGETS = arm64 x64 RELEASE_ZIPS = $(addprefix $(LAMBDA_PREFIX)-,$(RELEASE_TARGETS)) ifeq ($(OS),Windows_NT) DETECTED_OS := windows ARCH = x86_64 else DETECTED_OS := $(shell uname | tr A-Z a-z) ARCH = $(shell uname -m) endif ifeq ($(ARCH),aarch64) ARCH = arm64 endif ZSTD_LIB_CC_ARGS = -s -O3 -flto ZSTD_LIB_ARGS = -j lib-nomt UNAME=Linux ZSTD_LIB_COMPRESSION=0 ZSTD_LIB_DICTBUILDER=0 AR="zig ar" ifeq ($(DETECTED_OS),windows) ZSTD_LIB_CC_x64 = CC="zig cc -target x86_64-windows-gnu $(ZSTD_LIB_CC_ARGS)" else ZSTD_LIB_CC_arm64 = CC="zig cc -target aarch64-linux-musl $(ZSTD_LIB_CC_ARGS)" ZSTD_LIB_CC_x64 = CC="zig cc -target x86_64-linux-musl $(ZSTD_LIB_CC_ARGS)" endif CURRENT_TARGET ?= $(TARGET_$(DETECTED_OS)_$(ARCH)) export CC_aarch64_unknown_linux_musl = $(CURDIR)/linker/cc-aarch64-linux-musl export CXX_aarch64_unknown_linux_musl = $(CURDIR)/linker/cxx-aarch64-linux-musl export AR_aarch64_unknown_linux_musl = $(CURDIR)/linker/ar export CC_x86_64_unknown_linux_musl = $(CURDIR)/linker/cc-x86_64-linux-musl export CXX_x86_64_unknown_linux_musl = $(CURDIR)/linker/cxx-x86_64-linux-musl export AR_x86_64_unknown_linux_musl = $(CURDIR)/linker/ar define alias_template release${1}: llrt-$(DETECTED_OS)-$(ARCH)${1}.zip llrt-linux-x86_64${1}.zip: llrt-linux-x64${1}.zip llrt-windows-x86_64${1}.zip: llrt-windows-x64${1}.zip llrt-darwin-x86_64${1}.zip: llrt-darwin-x64${1}.zip endef $(eval $(call alias_template,-full-sdk)) $(eval $(call alias_template,)) $(eval $(call alias_template,-no-sdk)) define release_template release-aws-${1}${2}: | llrt-lambda-${1}${2}.zip llrt-container-${1}${2} llrt-linux-${1}${2}.zip llrt-lambda-${1}${2}.zip: export SDK_BUNDLE_MODE = ${3} llrt-lambda-${1}${2}.zip: | clean-js js cargo $$(BUILD_ARG) --target $$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1})) --features lambda ./pack target/$$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1}))/release/llrt target/$$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1}))/release/bootstrap @rm -rf $$@ zip -j $$@ target/$$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1}))/release/bootstrap llrt-container-${1}${2}: export SDK_BUNDLE_MODE = ${3} llrt-container-${1}${2}: | clean-js js cargo $$(BUILD_ARG) --target $$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1})) --features lambda,uncompressed mv target/$$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1}))/release/llrt $$@ llrt-linux-${1}${2}.zip: export SDK_BUNDLE_MODE = ${3} llrt-linux-${1}${2}.zip: | clean-js js cargo $$(BUILD_ARG) --target $$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1})) @rm -rf $$@ zip -j $$@ target/$$(TARGET_linux_$$(RELEASE_ARCH_NAME_${1}))/release/llrt llrt-darwin-${1}${2}.zip: export SDK_BUNDLE_MODE = ${3} llrt-darwin-${1}${2}.zip: | clean-js js cargo $$(BUILD_ARG) --target $$(TARGET_darwin_$$(RELEASE_ARCH_NAME_${1})) @rm -rf $$@ zip -j $$@ target/$$(TARGET_darwin_$$(RELEASE_ARCH_NAME_${1}))/release/llrt # llrt-windows-arm64* is automatically generated, but not currently supported. llrt-windows-${1}${2}.zip: export SDK_BUNDLE_MODE = ${3} llrt-windows-${1}${2}.zip: | clean-js js cargo $$(BUILD_ARG) --target $$(TARGET_windows_$$(RELEASE_ARCH_NAME_${1})) zip -j $$@ target/$$(TARGET_windows_$$(RELEASE_ARCH_NAME_${1}))/release/llrt.exe endef $(foreach target,$(RELEASE_TARGETS),$(eval $(call release_template,$(target),-full-sdk,FULL))) $(foreach target,$(RELEASE_TARGETS),$(eval $(call release_template,$(target),,STD))) $(foreach target,$(RELEASE_TARGETS),$(eval $(call release_template,$(target),-no-sdk,NONE))) build: js cargo $(BUILD_ARG) --target $(CURRENT_TARGET) ifeq ($(DETECTED_OS),windows) stdlib: rustup toolchain install $(RUST_VERSION) --target $(TARGET_windows_x86_64) rustup target add $(TARGET_windows_x86_64) rustup component add rust-src --toolchain $(RUST_VERSION) --target $(TARGET_windows_x86_64) else stdlib-x64: rustup toolchain install $(RUST_VERSION) --target $(TARGET_linux_x86_64) rustup target add $(TARGET_linux_x86_64) rustup component add rust-src --toolchain $(RUST_VERSION) --target $(TARGET_linux_x86_64) stdlib-arm64: rustup toolchain install $(RUST_VERSION) --target $(TARGET_linux_arm64) rustup target add $(TARGET_linux_arm64) rustup component add rust-src --toolchain $(RUST_VERSION) --target $(TARGET_linux_arm64) stdlib: | stdlib-x64 stdlib-arm64 endif toolchain: rustup toolchain install $(RUST_VERSION) --target $(CURRENT_TARGET) rustup target add $(CURRENT_TARGET) rustup component add rust-src --toolchain $(RUST_VERSION) --target $(CURRENT_TARGET) clean-js: rm -rf ./bundle clean: clean-js rm -rf ./target rm -rf ./lib js: $(STD_JS_FILE) bundle/js/%.js: $(TS_SOURCES) node build.mjs fix: npx pretty-quick cargo fix --allow-dirty cargo clippy --fix --allow-dirty cargo fmt bloat: js cargo build --profile=flame --target $(CURRENT_TARGET) cargo bloat --profile=flame --crates run: export AWS_LAMBDA_FUNCTION_NAME = n/a run: export AWS_LAMBDA_FUNCTION_MEMORY_SIZE = 1 run: export AWS_LAMBDA_FUNCTION_VERSION = 1 run: export AWS_LAMBDA_RUNTIME_API = localhost:3000 run: export _EXIT_ITERATIONS = 1 run: export JS_MINIFY = 0 run: export RUST_LOG = llrt=trace run: export _HANDLER = index.handler run: cargo run -vv run-ssr: export AWS_LAMBDA_RUNTIME_API = localhost:3000 run-ssr: export TABLE_NAME=quickjs-table run-ssr: export AWS_REGION = us-east-1 run-ssr: export _HANDLER = index.handler run-ssr: js cargo build cd example/functions && yarn build && cd build && ../../../target/debug/llrt flame: cargo flamegraph --profile flame -- index.mjs run-cli: export RUST_LOG = llrt=trace run-cli: js cargo run test: export JS_MINIFY = 0 test: export TEST_SUB_DIR = unit test: export LLRT_ASYNC_HOOKS = 1 test: js cargo run -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) init-wpt: cd wpt && \ git sparse-checkout init --no-cone && \ git sparse-checkout set \ /README.md \ /console \ /docs \ /encoding \ /FileAPI \ /fetch \ /hr-time \ /streams \ /tools \ /url \ /WebCryptoAPI \ /webidl \ /wpt \ /xhr update-wpt: ( cd wpt && git fetch origin master && git reset --hard FETCH_HEAD && git log -1 --oneline > ../tests/wpt/revision ) test-wpt: export JS_MINIFY = 0 test-wpt: export TEST_SUB_DIR = wpt test-wpt: js npx pretty-quick --pattern "tests/wpt/**/*.{js,ts,json}" cargo run -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) 2> wpt_errors.tmp tidyup-wpt: sed -E 's/\x1b\[[0-9;]*m//g' wpt_errors.tmp \ | sed '1,/^$$/d' \ | sed -E '/^ ?[^ ]/s|^.*__tests__/|🧪/|' \ > wpt_errors.txt test-e2e: export JS_MINIFY = 0 test-e2e: export TEST_TIMEOUT = 60000 test-e2e: export SDK_BUNDLE_MODE = STD test-e2e: export TEST_SUB_DIR = e2e test-e2e: js cargo run -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) test-ci: export JS_MINIFY = 0 test-ci: export RUST_BACKTRACE = 1 test-ci: export TEST_SUB_DIR = unit test-ci: export LLRT_ASYNC_HOOKS = 1 test-ci: clean-js | toolchain js ifdef CARGO_FEATURES cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) $(CARGO_FEATURES) -- --nocapture --show-output cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) $(CARGO_FEATURES) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) else cargo $(TOOLCHAIN) -Z build-std -Z build-std-features test --target $(CURRENT_TARGET) --features lambda -- --nocapture --show-output cargo $(TOOLCHAIN) run -r --target $(CURRENT_TARGET) -- test -d bundle/js/__tests__/$(TEST_SUB_DIR) endif libs-arm64: lib/arm64/libzstd.a lib/zstd.h lib/zstd_errors.h libs-x64: lib/x64/libzstd.a lib/zstd.h lib/zstd_errors.h libs: | libs-arm64 libs-x64 lib/zstd.h: cp zstd/lib/zstd.h $@ lib/zstd_errors.h: cp zstd/lib/zstd_errors.h $@ lib/arm64/libzstd.a: mkdir -p $(dir $@) rm -f zstd/lib/-.o cd zstd/lib && make clean && make $(ZSTD_LIB_ARGS) $(ZSTD_LIB_CC_arm64) cp zstd/lib/libzstd.a $@ lib/x64/libzstd.a: mkdir -p $(dir $@) rm -f zstd/lib/-.o cd zstd/lib && make clean && make $(ZSTD_LIB_ARGS) $(ZSTD_LIB_CC_x64) cp zstd/lib/libzstd.a $@ bench: cargo build -r hyperfine -N --warmup=100 "node fixtures/hello.js" "deno run fixtures/hello.js" "bun fixtures/hello.js" "$(BUILD_DIR)/llrt fixtures/hello.js" "qjs fixtures/hello.js" deploy: cd example/infrastructure && yarn deploy --require-approval never check: cargo clippy --all-targets --no-default-features --features "lambda,macro,no-sdk,uncompressed,crypto-rust,tls-ring" -- -D warnings test-rs: cargo test --all-targets --no-default-features --features "lambda,macro,no-sdk,uncompressed,crypto-rust,tls-ring" check-crates: cargo metadata --no-deps --format-version 1 --quiet | \ jq -r '.packages[] | select(.manifest_path | contains("modules/")) | .name' | \ while read crate; do \ echo "Checking crate: $$crate"; \ cargo check -p "$$crate"; \ done .PHONY: libs check check-all check-crates libs-arm64 libs-x64 toolchain clean-js release-linux release-darwin release-windows lambda stdlib stdlib-x64 stdlib-arm64 test test-ci run js run-release build release clean flame deploy ================================================ FILE: NOTICE ================================================ LLRT Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ================================================ FILE: README.md ================================================ [![LLRT CI](https://github.com/awslabs/llrt/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/awslabs/llrt/actions/workflows/ci.yml) [![LLRT Release](https://github.com/awslabs/llrt/actions/workflows/release.yml/badge.svg)](https://github.com/awslabs/llrt/actions/workflows/release.yml) LLRT (**L**ow **L**atency **R**un**t**ime) is a lightweight JavaScript runtime designed to address the growing demand for fast and efficient Serverless applications. LLRT offers up to over **10x** faster startup and up to **2x** overall lower cost compared to other JavaScript runtimes running on **AWS Lambda** It's built in Rust, utilizing QuickJS as JavaScript engine, ensuring efficient memory usage and swift startup. > [!WARNING] > LLRT is an **experimental** package. It is subject to change and intended only for evaluation purposes. LLRT - [DynamoDB Put, ARM, 128MB](example/functions/src/v3-lib.mjs): ![DynamoDB Put LLRT](./benchmarks/llrt-ddb-put.png "LLRT DynamoDB Put") Node.js 20 - [DynamoDB Put, ARM, 128MB](example/functions/src/v3-lib.mjs): ![DynamoDB Put Node20](./benchmarks/node20-ddb-put.png "Node20 DynamoDB Put") HTTP benchmarks measured in **round trip time** for a cold start ([why?](#benchmark-methodology)) ## Configure Lambda functions to use LLRT Download the last LLRT release from ### Option 1: Custom runtime (recommended) Choose `Custom Runtime on Amazon Linux 2023` and package the LLRT `bootstrap` binary together with your JS code. ### Option 2: Use a layer Choose `Custom Runtime on Amazon Linux 2023`, upload `llrt-lambda-arm64.zip` or `llrt-lambda-x64.zip` as a layer and add to your function ### Option 3: Package LLRT in a container image See our [AWS SAM example](./example/llrt-sam-container-image) or: ```dockerfile FROM --platform=arm64 busybox WORKDIR /var/task/ COPY app.mjs ./ ADD https://github.com/awslabs/llrt/releases/latest/download/llrt-container-arm64 /usr/bin/llrt RUN chmod +x /usr/bin/llrt ENV LAMBDA_HANDLER "app.handler" CMD [ "llrt" ] ``` ### Option 4: AWS SAM The following [example project](example/llrt-sam/) sets up a lambda instrumented with a layer containing the llrt runtime. ### Option 5: AWS CDK You can use [`cdk-lambda-llrt` construct library](https://github.com/tmokmss/cdk-lambda-llrt) to deploy LLRT Lambda functions with AWS CDK. ```ts import { LlrtFunction } from "cdk-lambda-llrt"; const handler = new LlrtFunction(this, "Handler", { entry: "lambda/index.ts", }); ``` See [Construct Hub](https://constructs.dev/packages/cdk-lambda-llrt/) and [its examples](https://github.com/tmokmss/cdk-lambda-llrt/tree/main/example) for more details. That's it 🎉 > [!IMPORTANT] > Even though LLRT supports [ES2023](https://262.ecma-international.org/14.0/) it's **NOT** a drop in replacement for Node.js. Consult [Compatibility matrix](#compatibility-matrix) and [API](API.md) for more details. > All dependencies should be bundled for a `browser` platform and mark included `@aws-sdk` packages as external. ## Testing & ensuring compatibility The best way to ensure your code is compatible with LLRT is to write tests and execute them using the built-in test runner. The test runner currently supports Jest/Chai assertions. There are three main types of tests you can create: Unit Tests - Useful for validating specific modules and functions in isolation - Allow focused testing of individual components End-to-End (E2E) Tests - Validate overall compatibility with AWS SDK and WinterTC compliance - Test the integration between all components - Confirm expected behavior from end-user perspective - For more information about the E2E Tests and how to run them, see [here](tests/e2e/README.md). Web Platform Tests (WPT) - Useful for validating LLRT’s behavior against standardized browser APIs and runtime expectations - Ensure compatibility with web standards and cross-runtime environments - Help verify alignment with WinterTC and broader JavaScript ecosystem - For setup instructions and how to run WPT in LLRT, see [here](tests/wpt/README.md). ### Test runner Test runner uses a lightweight Jest-like API and supports Jest/Chai assertions. For examples on how to implement tests for LLRT see the `/tests` folder of this repository. To run tests, execute the `llrt test` command. LLRT scans the current directory and sub-directories for files that ends with `*.test.js` or `*.test.mjs`. You can also provide a specific test directory to scan by using the `llrt test -d ` option. The test runner also has support for filters. Using filters is as simple as adding additional command line arguments, i.e: `llrt test crypto` will only run tests that match the filename containing `crypto`. ## Compatibility matrix > [!NOTE] > LLRT only support a fraction of the Node.js APIs. It is **NOT** a drop in replacement for Node.js, nor will it ever be. Below is a high level overview of partially supported APIs and modules. For more details consult the [API](API.md) documentation | [Node.js API](https://nodejs.org/api/index.html) | Node.js | LLRT | | ------------------------------------------------ | ------- | ----- | | node:assert | ✔︎ | ✔︎️⚠️ | | node:async_hooks | ✔︎ | ✔︎️⚠️ | | node:buffer | ✔︎ | ✔︎️⚠️ | | node:child_process | ✔︎ | ✔︎⚠️ | | node:cluster | ✔︎ | ✘ | | node:console | ✔︎ | ✔︎⚠️ | | node:crypto | ✔︎ | ✔︎⚠️ | | node:dgram | ✔︎ | ✘ | | node:diagnostics_channel | ✔︎ | ✘ | | node:dns | ✔︎ | ✔︎⚠️ | | node:events | ✔︎ | ✔︎⚠️ | | node:fs | ✔︎ | ✔︎⚠️ | | node:fs/promises | ✔︎ | ✔︎⚠️ | | node:http | ✔︎ | ✘⏱ | | node:http2 | ✔︎ | ✘ | | node:https | ✔︎ | ✘⏱ | | node:inspector | ✔︎ | ✘ | | node:inspector/promises | ✔︎ | ✘ | | node:module | ✔︎ | ✔︎⚠️ | | node:net | ✔︎ | ✔︎⚠️ | | node:os | ✔︎ | ✔︎⚠️ | | node:path | ✔︎ | ✔︎⚠️ | | node:perf_hooks | ✔︎ | ✔︎⚠️ | | node:process | ✔︎ | ✔︎⚠️ | | node:querystring | ✔︎ | ✘ | | node:readline | ✔︎ | ✘ | | node:readline/promises | ✔︎ | ✘ | | node:repl | ✔︎ | ✘ | | node:sqlite | ✔︎ | ✘ | | node:stream | ✔︎ | ✔︎\* | | node:stream/promises | ✔︎ | ✔︎\* | | node:stream/web | ✔︎ | ✔︎⚠️ | | node:string_decoder | ✔︎ | ✔︎ | | node:test | ✔︎ | ✘ | | node:timers | ✔︎ | ✔︎⚠️ | | node:tls | ✔︎ | ✘⏱ | | node:tty | ✔︎ | ✔︎⚠️ | | node:url | ✔︎ | ✔︎⚠️ | | node:util | ✔︎ | ✔︎⚠️ | | node:v8 | ✔︎ | ✘\*\* | | node:vm | ✔︎ | ✘ | | node:wasi | ✔︎ | ✘ | | node:worker_threads | ✔︎ | ✘ | | node:zlib | ✔︎ | ✔︎⚠️ | | [LLRT API](https://github.com/awslabs/llrt/blob/main/API.md) | Node.js | LLRT | | ------------------------------------------------------------ | ------- | ---- | | llrt:hex | ✘ | ✔︎ | | llrt:qjs | ✘ | ✔︎ | | llrt:util | ✘ | ✔︎ | | llrt:xml | ✘ | ✔︎ | | [Web Platform API](https://min-common-api.proposal.wintertc.org/) | LLRT | | ----------------------------------------------------------------- | ---- | | COMPRESSION | ✘⏱ | | CONSOLE | ✔︎⚠️ | | DOM | ✔︎⚠️ | | ECMASCRIPT | ✔︎⚠️ | | ENCODING | ✔︎⚠️ | | FETCH | ✔︎⚠️ | | FILEAPI | ✔︎⚠️ | | HR-TIME | ✔︎ | | HTML | ✔︎⚠️ | | STREAMS | ✔︎⚠️ | | URL | ✔︎ | | URLPATTERN | ✘⏱ | | WASM-JS-API-2 | ✘ | | WASM-WEB-API-2 | ✘ | | WEBCRYPTO | ✔︎⚠️ | | WEBIDL | ✔︎⚠️ | | XHR | ✔︎⚠️ | | Other features | LLRT | | -------------- | ---- | | async/await | ✔︎ | | esm | ✔︎ | | cjs | ✔︎ | | Intl | ✔︎⚠️ | | Temporal | ✔︎⚠️ | _⚠️ = partially supported in LLRT_
_⏱ = planned partial support_
_\* = Not native_
_\*\* = The `module.registerHooks()` API allows you to emulate some functionality. See also `example/register-hooks`._
## Using node_modules (dependencies) with LLRT Since LLRT is meant for performance critical application it's not recommended to deploy `node_modules` without bundling, minification and tree-shaking. LLRT can work with any bundler of your choice. Below are some configurations for popular bundlers: > [!WARNING] > LLRT implements native modules that are largely compatible with the following external packages. > By implementing the following conversions in the bundler's alias function, your application may be faster, but we recommend that you test thoroughly as they are not fully compatible. | Node.js | LLRT | | --------------- | -------- | | fast-xml-parser | llrt:xml | ### ESBuild ```shell esbuild index.js --platform=browser --target=es2023 --format=esm --bundle --minify --external:@aws-sdk --external:@smithy ``` ### Rollup ```javascript import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; import terser from "@rollup/plugin-terser"; export default { input: "index.js", output: { file: "dist/bundle.js", format: "esm", sourcemap: true, target: "es2023", }, plugins: [resolve(), commonjs(), terser()], external: ["@aws-sdk", "@smithy"], }; ``` ### Webpack ```javascript import TerserPlugin from "terser-webpack-plugin"; import nodeExternals from "webpack-node-externals"; export default { entry: "./index.js", output: { path: "dist", filename: "bundle.js", libraryTarget: "module", }, target: "web", mode: "production", resolve: { extensions: [".js"], }, externals: [nodeExternals(), "@aws-sdk", "@smithy"], optimization: { minimize: true, minimizer: [ new TerserPlugin({ terserOptions: { ecma: 2023, }, }), ], }, }; ``` ## Using AWS SDK (v3) with LLRT LLRT includes many AWS SDK clients and utils as part of the runtime, built into the executable. These SDK Clients have been specifically fine-tuned to offer best performance while not compromising on compatibility. LLRT replaces some JavaScript dependencies used by the AWS SDK by native ones such as Hash calculations and XML parsing. V3 SDK packages not included in the list below have to be bundled with your source code. For an example on how to use a non-included SDK, see [this example build script (buildExternalSdkFunction)](example/functions/build.mjs) LLRT supports the following three bundles by default. Bundle types and suffixes are as follows. | Bundle Type | Suffix | Purpose of Use | | ----------- | ----------- | --------------------------------------------------------- | | no-sdk | \*-no-sdk | Suitable for workloads that do not use `@aws-sdk`. | | std-sdk | (none) | Suitable for workloads that utilize the major `@aws-sdk`. | | full-sdk | \*-full-sdk | Suitable for workloads that utilize any `@aws-sdk`. | The relationship between the supported packages for each bundle type is as follows. | Analytics | no-sdk | std-sdk | full-sdk | | ------------------------------------ | ------ | ------- | -------- | | @aws-sdk/client-athena | | | ✔︎ | | @aws-sdk/client-firehose | | | ✔︎ | | @aws-sdk/client-glue | | | ✔︎ | | @aws-sdk/client-kinesis | | | ✔︎ | | @aws-sdk/client-opensearch | | | ✔︎ | | @aws-sdk/client-opensearchserverless | | | ✔︎ | | Application integration | no-sdk | std-sdk | full-sdk | | --------------------------- | ------ | ------- | -------- | | @aws-sdk/client-eventbridge | | ✔︎ | ✔︎ | | @aws-sdk/client-scheduler | | | ✔︎ | | @aws-sdk/client-sfn | | ✔︎ | ✔︎ | | @aws-sdk/client-sns | | ✔︎ | ✔︎ | | @aws-sdk/client-sqs | | ✔︎ | ✔︎ | | Business applications | no-sdk | std-sdk | full-sdk | | --------------------- | ------ | ------- | -------- | | @aws-sdk/client-ses | | ✔︎ | ✔︎ | | @aws-sdk/client-sesv2 | | | ✔︎ | | Compute services | no-sdk | std-sdk | full-sdk | | ---------------------------- | ------ | ------- | -------- | | @aws-sdk/client-auto-scaling | | | ✔︎ | | @aws-sdk/client-batch | | | ✔︎ | | @aws-sdk/client-ec2 | | | ✔︎ | | @aws-sdk/client-lambda | | | ✔︎ | | Containers | no-sdk | std-sdk | full-sdk | | -------------------------------- | ------ | ------- | -------- | | @aws-sdk/client-ecr | | | ✔︎ | | @aws-sdk/client-ecs | | | ✔︎ | | @aws-sdk/client-eks | | | ✔︎ | | @aws-sdk/client-servicediscovery | | | ✔︎ | | Databases | no-sdk | std-sdk | full-sdk | | -------------------------------- | ------ | ------- | -------- | | @aws-sdk/client-dynamodb | | ✔︎ | ✔︎ | | @aws-sdk/client-dynamodb-streams | | | ✔︎ | | @aws-sdk/client-elasticache | | | ✔︎ | | @aws-sdk/client-rds | | | ✔︎ | | @aws-sdk/client-rds-data | | | ✔︎ | | Developer tools | no-sdk | std-sdk | full-sdk | | -------------------- | ------ | ------- | -------- | | @aws-sdk/client-xray | | ✔︎ | ✔︎ | | Front-end web and mobile services | no-sdk | std-sdk | full-sdk | | --------------------------------- | ------ | ------- | -------- | | @aws-sdk/client-amplify | | | ✔︎ | | @aws-sdk/client-appsync | | | ✔︎ | | @aws-sdk/client-location | | | ✔︎ | | Machine Learning (ML) and Artificial Intelligence (AI) | no-sdk | std-sdk | full-sdk | | ------------------------------------------------------ | ------ | ------- | -------- | | @aws-sdk/client-bedrock | | | ✔︎ | | @aws-sdk/client-bedrock-runtime | | | ✔︎ | | @aws-sdk/client-bedrock-agent | | | ✔︎ | | @aws-sdk/client-bedrock-agent-runtime | | | ✔︎ | | @aws-sdk/client-polly | | | ✔︎ | | @aws-sdk/client-rekognition | | | ✔︎ | | @aws-sdk/client-textract | | | ✔︎ | | @aws-sdk/client-translate | | | ✔︎ | | Management and governance | no-sdk | std-sdk | full-sdk | | --------------------------------- | ------ | ------- | -------- | | @aws-sdk/client-appconfig | | | ✔︎ | | @aws-sdk/client-appconfigdata | | | ✔︎ | | @aws-sdk/client-cloudformation | | | ✔︎ | | @aws-sdk/client-cloudwatch | | | ✔︎ | | @aws-sdk/client-cloudwatch-events | | ✔︎ | ✔︎ | | @aws-sdk/client-cloudwatch-logs | | ✔︎ | ✔︎ | | @aws-sdk/client-service-catalog | | | ✔︎ | | @aws-sdk/client-ssm | | ✔︎ | ✔︎ | | Media | no-sdk | std-sdk | full-sdk | | ---------------------------- | ------ | ------- | -------- | | @aws-sdk/client-mediaconvert | | | ✔︎ | | Networking and content delivery | no-sdk | std-sdk | full-sdk | | ----------------------------------------- | ------ | ------- | -------- | | @aws-sdk/client-api-gateway | | | ✔︎ | | @aws-sdk/client-apigatewayv2 | | | ✔︎ | | @aws-sdk/client-elastic-load-balancing-v2 | | | ✔︎ | | Security, identity, and compliance | no-sdk | std-sdk | full-sdk | | ----------------------------------------- | ------ | ------- | -------- | | @aws-sdk/client-acm | | | ✔︎ | | @aws-sdk/client-cognito-identity | | ✔︎ | ✔︎ | | @aws-sdk/client-cognito-identity-provider | | ✔︎ | ✔︎ | | @aws-sdk/client-iam | | | ✔︎ | | @aws-sdk/client-kms | | ✔︎ | ✔︎ | | @aws-sdk/client-secrets-manager | | ✔︎ | ✔︎ | | @aws-sdk/client-sso | | | ✔︎ | | @aws-sdk/client-sso-admin | | | ✔︎ | | @aws-sdk/client-sso-oidc | | | ✔︎ | | @aws-sdk/client-sts | | ✔︎ | ✔︎ | | @aws-sdk/client-verifiedpermissions | | | ✔︎ | | Storage | no-sdk | std-sdk | full-sdk | | ------------------- | ------ | ------- | -------- | | @aws-sdk/client-efs | | | ✔︎ | | @aws-sdk/client-s3 | | ✔︎ | ✔︎ | | Other bundled packages | no-sdk | std-sdk | full-sdk | | -------------------------------- | ------ | ------- | -------- | | @aws-crypto | | ✔︎ | ✔︎ | | @aws-sdk/credential-providers | | ✔︎ | ✔︎ | | @aws-sdk/lib-dynamodb | | ✔︎ | ✔︎ | | @aws-sdk/lib-storage | | ✔︎ | ✔︎ | | @aws-sdk/s3-presigned-post | | ✔︎ | ✔︎ | | @aws-sdk/s3-request-presigner | | ✔︎ | ✔︎ | | @aws-sdk/util-dynamodb | | ✔︎ | ✔︎ | | @aws-sdk/util-user-agent-browser | | ✔︎ | ✔︎ | | @smithy | | ✔︎ | ✔︎ | > [!IMPORTANT] > LLRT currently does not support returning streams from SDK responses. Use `response.Body.transformToString();` or `response.Body.transformToByteArray();` as shown below. > > ```javascript > const response = await client.send(command); > // or 'transformToByteArray()' > const str = await response.Body.transformToString(); > ``` ## Running TypeScript with LLRT Same principle as dependencies applies when using TypeScript. TypeScript must be bundled and transpiled into ES2023 JavaScript. > [!NOTE] > LLRT will not support running TypeScript without transpilation. This is by design for performance reasons. Transpiling requires CPU and memory that adds latency and cost during execution. This can be avoided if done ahead of time during deployment. ## Rationale What justifies the introduction of another JavaScript runtime in light of existing options such as [Node.js](https://nodejs.org/en), [Bun](https://bun.sh) & [Deno](https://deno.com/)? Node.js, Bun, and Deno represent highly proficient JavaScript runtimes. However, they are designed with general-purpose applications in mind. These runtimes were not specifically tailored for the demands of a Serverless environment, characterized by short-lived runtime instances. They each depend on a ([Just-In-Time compiler (JIT)](https://en.wikipedia.org/wiki/Just-in-time_compilation) for dynamic code compilation and optimization during execution. While JIT compilation offers substantial long-term performance advantages, it carries a computational and memory overhead. In contrast, LLRT distinguishes itself by not incorporating a JIT compiler, a strategic decision that yields two significant advantages: A) JIT compilation is a notably sophisticated technological component, introducing increased system complexity and contributing substantially to the runtime's overall size. B) Without the JIT overhead, LLRT conserves both CPU and memory resources that can be more efficiently allocated to code execution tasks, thereby reducing application startup times. ## Limitations There are many cases where LLRT shows notable performance drawbacks compared with JIT-powered runtimes, such as large data processing, Monte Carlo simulations or performing tasks with hundreds of thousands or millions of iterations. LLRT is most effective when applied to smaller Serverless functions dedicated to tasks such as data transformation, real time processing, AWS service integrations, authorization, validation etc. It is designed to complement existing components rather than serve as a comprehensive replacement for everything. Notably, given its supported APIs are based on Node.js specification, transitioning back to alternative solutions requires minimal code adjustments. ## Building from source 1. Clone code and cd to directory ``` git clone git@github.com:awslabs/llrt.git cd llrt ``` 2. Install git submodules ``` git submodule update --init --checkout ``` 3. Install rust ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y source "$HOME/.cargo/env" ``` 4. Install dependencies ``` # MacOS brew install zig make cmake zstd node corepack # Ubuntu sudo apt -y install make zstd sudo snap install zig --classic --beta # Windows WSL2 (requires systemd to be enabled*) sudo apt -y install cmake g++ gcc make zip zstd sudo snap install zig --classic --beta # Windows WSL2 (If Node.js is not yet installed) sudo curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash nvm install --lts ``` _\* See [Microsoft Devblogs](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/#how-can-you-get-systemd-on-your-machine)_ 5. Install Node.js packages ``` corepack enable yarn ``` 6. Install generate libs and setup rust targets & toolchains ``` make stdlib && make libs ``` > [!NOTE] > If these commands exit with an error that says `can't cd to zstd/lib`, > you've not cloned this repository recursively. Run `git submodule update --init` to download the submodules and run the commands above again. 7. Build binaries for Lambda (Per bundle type and architecture desired) ``` # for arm64, use make llrt-lambda-arm64.zip make llrt-lambda-arm64-no-sdk.zip make llrt-lambda-arm64-full-sdk.zip # or for x86-64, use make llrt-lambda-x64.zip make llrt-lambda-x64-no-sdk.zip make llrt-lambda-x64-full-sdk.zip ``` 8. Build binaries for Container (Per bundle type and architecture desired) ``` # for arm64, use make llrt-container-arm64 make llrt-container-arm64-no-sdk make llrt-container-arm64-full-sdk # or for x86-64, use make llrt-container-x64 make llrt-container-x64-no-sdk make llrt-container-x64-full-sdk ``` 9. Optionally build for your local machine (Mac or Linux) ``` make release make release-no-sdk make release-full-sdk ``` You should now have a `llrt-lambda-arm64*.zip` or `llrt-lambda-x64*.zip`. You can manually upload this as a Lambda layer or use it via your Infrastructure-as-code pipeline ## Crypto and TLS Backend Options LLRT supports multiple cryptographic backends for both the crypto module and TLS connections. These can be configured via Cargo features. ### Crypto Provider Features | Feature | Description | | ----------------------- | ----------------------------------------------------------------- | | `crypto-rust` (default) | Pure Rust crypto using RustCrypto crates | | `crypto-ring` | Ring-only crypto (limited algorithm support) | | `crypto-ring-rust` | Ring for hashing/HMAC, RustCrypto for everything else | | `crypto-graviola` | Graviola-only crypto (limited algorithm support) | | `crypto-graviola-rust` | Graviola for hashing/HMAC/AES-GCM, RustCrypto for everything else | | `crypto-openssl` | OpenSSL-based crypto | ### TLS Backend Features | Feature | Description | | -------------------- | --------------------------------------------- | | `tls-ring` (default) | rustls with ring crypto | | `tls-aws-lc` | rustls with AWS-LC crypto (optimized for AWS) | | `tls-graviola` | rustls with graviola crypto | | `tls-openssl` | OpenSSL for TLS | ### Building with Different Backends ```bash # Default (crypto-rust + tls-ring) cargo build --release # Using AWS-LC for TLS cargo build --release --no-default-features --features "macro,tls-aws-lc" # Using OpenSSL for both crypto and TLS cargo build --release --no-default-features --features "macro,crypto-openssl,tls-openssl" # Using Graviola for both crypto and TLS cargo build --release --no-default-features --features "macro,crypto-graviola-rust,tls-graviola" ``` ## Running Lambda emulator Please note that in order to run the example you will need: - Valid AWS credentials via a `~/.aws/credentials` or via environment variables. ```bash export AWS_ACCESS_KEY_ID=XXX export AWS_SECRET_ACCESS_KEY=YYY export AWS_REGION=us-east-1 ``` - A DynamoDB table (with `id` as the partition key) on `us-east-1` - The `dynamodb:PutItem` IAM permission on this table. You can use this policy (don't forget to modify ): ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "putItem", "Effect": "Allow", "Action": "dynamodb:PutItem", "Resource": "arn:aws:dynamodb:us-east-1::table/quickjs-table" } ] } ``` Start the `lambda-server.js` in a separate terminal node lambda-server.js Then run llrt: make run ## Environment Variables ### `LLRT_ASYNC_HOOKS=value` When using asynchronous hooks, the hooking function inside QuickJS is activated. This is disabled by default as there is concern that it may have a significant impact on performance. By setting this environment variable to `1`, the asynchronous hook function can be enabled, allowing you to track asynchronous processing using the `async_hooks` module. ### `LLRT_EXTRA_CA_CERTS=file` Load extra certificate authorities from a PEM encoded file ### `LLRT_GC_THRESHOLD_MB=value` Set a memory threshold in MB for garbage collection. Default threshold is 20MB ### `LLRT_HTTP_VERSION=value` Extends the HTTP request version. By default, only HTTP/1.1 is enabled. Specifying '2' will enable HTTP/1.1 and HTTP/2. ### `LLRT_LOG=[target][=][level][,...]` Filter the log output by target module, level, or both (using `=`). Log levels are case-insensitive and will also enable any higher priority logs. Log levels in descending priority order: - `Error` - `Warn | Warning` - `Info` - `Debug` - `Trace` Example filters: - `warn` will enable all warning and error logs - `llrt_core::vm=trace` will enable all logs in the `llrt_core::vm` module - `warn,llrt_core::vm=trace` will enable all logs in the `llrt_core::vm` module and all warning and error logs in other modules ### `LLRT_NET_ALLOW="host[ ...]"` Space-delimited list of hosts or socket paths which should be allowed for network connections. Network connections will be denied for any host or socket path missing from this list. Set an empty list to deny all connections ### `LLRT_NET_DENY="host[ ...]"` Space-delimited list of hosts or socket paths which should be denied for network connections ### `LLRT_NET_POOL_IDLE_TIMEOUT=value` Set a timeout in seconds for idle sockets being kept-alive. Default timeout is 15 seconds ### `LLRT_PLATFORM=value` Used to explicitly specify a preferred platform for the Node.js package resolver. The default is `browser`. If `node` is specified, "node" takes precedence in the search path. If a value other than `browser` or `node` is specified, it will behave as if "browser" was specified. ### `LLRT_REGISTER_HOOKS=file` If you want to enable a hooking mechanism that is mostly compatible with Node.js's `module.registerHooks()`, specify the js file name in this environment variable. We provide a concrete example in `example/register-hooks`. > [!NOTE] > This environment variable is only effective when running on AWS Lambda. > When using the LLRT CLI, hook files must be specified using the --import option instead of this environment variable. ### `LLRT_SDK_CONNECTION_WARMUP=1` Initializes TLS connections in parallel during function init which significantly reduces cold starts due. Enabled by default, can be disabled with value `0` or `false` ### `LLRT_TLS_VERSION=value` Set the TLS version to be used for network connections. By default only TLS 1.2 is enabled. TLS 1.3 can also be enabled by setting this variable to `1.3` ## Benchmark Methodology Although Init Duration [reported by Lambda](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html) is commonly used to understand cold start impact on overall request latency, this metric does not include the time needed to copy code into the Lambda sandbox. The technical definition of Init Duration ([source](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-logging.html#node-logging-output)): > For the first request served, the amount of time it took the runtime to load the function and run code outside of the handler method. Measuring round-trip request duration provides a more complete picture of user facing cold-start latency. Lambda invocation results (λ-labeled row) report the sum total of Init Duration + Function Duration. ## Security See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. ## License This library is licensed under the Apache-2.0 License. See the [LICENSE](LICENSE) file. ================================================ FILE: THIRD_PARTY_LICENSES ================================================ ** zstd; version zstd -- http://facebook.github.io/zstd/ Copyright (c) 2016-present, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------ ** quickjs; version 2021-03-27 -- https://bellard.org/quickjs/ Copyright 2020 Fabrice Bellard and Charlie Gordon. ** rquickjs; version v0.4.0 -- https://github.com/DelSkayn/rquickjs Copyright (c) 2020 Mees Delzenne MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------ ** esbuild; version 0.19.12 -- github.com/evanw/esbuild Copyright (c) 2020 Evan Wallace Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: benchmarks/v8-v7/README.txt ================================================ V8 Benchmark Suite ================== This is the V8 benchmark suite: A collection of pure JavaScript benchmarks that we have used to tune V8. The licenses for the individual benchmarks are included in the JavaScript files. In addition to the benchmarks, the suite consists of the benchmark framework (base.js), which must be loaded before any of the individual benchmark files, and two benchmark runners: An HTML version (run.html) and a standalone JavaScript version (run.js). Changes From Version 1 To Version 2 =================================== For version 2 the crypto benchmark was fixed. Previously, the decryption stage was given plaintext as input, which resulted in an error. Now, the decryption stage is given the output of the encryption stage as input. The result is checked against the original plaintext. For this to give the correct results the crypto objects are reset for each iteration of the benchmark. In addition, the size of the plain text has been increased a little and the use of Math.random() and new Date() to build an RNG pool has been removed. Other benchmarks were fixed to do elementary verification of the results of their calculations. This is to avoid accidentally obtaining scores that are the result of an incorrect JavaScript engine optimization. Changes From Version 2 To Version 3 =================================== Version 3 adds a new benchmark, RegExp. The RegExp benchmark is generated by loading 50 of the most popular pages on the web and logging all regexp operations performed. Each operation is given a weight that is calculated from an estimate of the popularity of the pages where it occurs and the number of times it is executed while loading each page. Finally the literal letters in the data are encoded using ROT13 in a way that does not affect how the regexps match their input. Changes from Version 3 to Version 4 =================================== The Splay benchmark is a newcomer in version 4. It manipulates a splay tree by adding and removing data nodes, thus exercising the memory management subsystem of the JavaScript engine. Furthermore, all the unused parts of the Prototype library were removed from the RayTrace benchmark. This does not affect the running of the benchmark. Changes from Version 4 to Version 5 =================================== Removed duplicate line in random seed code, and changed the name of the Object.prototype.inherits function in the DeltaBlue benchmark to inheritsFrom to avoid name clashes when running in Chromium with extensions enabled. Changes from Version 5 to Version 6 =================================== Removed dead code from the RayTrace benchmark and fixed a couple of typos in the DeltaBlue implementation. Changed the Splay benchmark to avoid converting the same numeric key to a string over and over again and to avoid inserting and removing the same element repeatedly thus increasing pressure on the memory subsystem. Changed the RegExp benchmark to exercise the regular expression engine on different input strings. Furthermore, the benchmark runner was changed to run the benchmarks for at least a few times to stabilize the reported numbers on slower machines. Changes from Version 6 to Version 7 =================================== Added the Navier-Stokes benchmark, a 2D differential equation solver that stresses arithmetic computations on double arrays. ================================================ FILE: benchmarks/v8-v7/base.js ================================================ // Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Simple framework for running the benchmark suites and // computing a score based on the timing measurements. // A benchmark has a name (string) and a function that will be run to // do the performance measurement. The optional setup and tearDown // arguments are functions that will be invoked before and after // running the benchmark, but the running time of these functions will // not be accounted for in the benchmark score. function Benchmark(name, run, setup, tearDown) { this.name = name; this.run = run; this.Setup = setup ? setup : function () {}; this.TearDown = tearDown ? tearDown : function () {}; } // Benchmark results hold the benchmark and the measured time used to // run the benchmark. The benchmark score is computed later once a // full benchmark suite has run to completion. function BenchmarkResult(benchmark, time) { this.benchmark = benchmark; this.time = time; } // Automatically convert results to numbers. Used by the geometric // mean computation. BenchmarkResult.prototype.valueOf = function () { return this.time; }; // Suites of benchmarks consist of a name and the set of benchmarks in // addition to the reference timing that the final score will be based // on. This way, all scores are relative to a reference run and higher // scores implies better performance. function BenchmarkSuite(name, reference, benchmarks) { this.name = name; this.reference = reference; this.benchmarks = benchmarks; BenchmarkSuite.suites.push(this); } // Keep track of all declared benchmark suites. BenchmarkSuite.suites = []; // Scores are not comparable across versions. Bump the version if // you're making changes that will affect that scores, e.g. if you add // a new benchmark or change an existing one. BenchmarkSuite.version = "7"; // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function () { var seed = 49734321; return function () { // Robert Jenkins' 32 bit integer hash function. seed = (seed + 0x7ed55d16 + (seed << 12)) & 0xffffffff; seed = (seed ^ 0xc761c23c ^ (seed >>> 19)) & 0xffffffff; seed = (seed + 0x165667b1 + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = (seed + 0xfd7046c5 + (seed << 3)) & 0xffffffff; seed = (seed ^ 0xb55a4f09 ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); // Runs all registered benchmark suites and optionally yields between // each individual benchmark to avoid running for too long in the // context of browsers. Once done, the final score is reported to the // runner. BenchmarkSuite.RunSuites = function (runner) { var continuation = null; var suites = BenchmarkSuite.suites; var length = suites.length; BenchmarkSuite.scores = []; var index = 0; function RunStep() { while (continuation || index < length) { if (continuation) { continuation = continuation(); } else { var suite = suites[index++]; if (runner.NotifyStart) runner.NotifyStart(suite.name); continuation = suite.RunStep(runner); } if (continuation && typeof window != "undefined" && window.setTimeout) { window.setTimeout(RunStep, 25); return; } } if (runner.NotifyScore) { var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); var formatted = BenchmarkSuite.FormatScore(100 * score); runner.NotifyScore(formatted); } } RunStep(); }; // Counts the total number of registered benchmarks. Useful for // showing progress as a percentage. BenchmarkSuite.CountBenchmarks = function () { var result = 0; var suites = BenchmarkSuite.suites; for (var i = 0; i < suites.length; i++) { result += suites[i].benchmarks.length; } return result; }; // Computes the geometric mean of a set of numbers. BenchmarkSuite.GeometricMean = function (numbers) { var log = 0; for (var i = 0; i < numbers.length; i++) { log += Math.log(numbers[i]); } return Math.pow(Math.E, log / numbers.length); }; // Converts a score value to a string with at least three significant // digits. BenchmarkSuite.FormatScore = function (value) { if (value > 100) { return value.toFixed(0); } else { return value.toPrecision(3); } }; // Notifies the runner that we're done running a single benchmark in // the benchmark suite. This can be useful to report progress. BenchmarkSuite.prototype.NotifyStep = function (result) { this.results.push(result); if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); }; // Notifies the runner that we're done with running a suite and that // we have a result which can be reported to the user if needed. BenchmarkSuite.prototype.NotifyResult = function () { var mean = BenchmarkSuite.GeometricMean(this.results); var score = this.reference / mean; BenchmarkSuite.scores.push(score); if (this.runner.NotifyResult) { var formatted = BenchmarkSuite.FormatScore(100 * score); this.runner.NotifyResult(this.name, formatted); } }; // Notifies the runner that running a benchmark resulted in an error. BenchmarkSuite.prototype.NotifyError = function (error) { if (this.runner.NotifyError) { this.runner.NotifyError(this.name, error); } if (this.runner.NotifyStep) { this.runner.NotifyStep(this.name); } }; // Runs a single benchmark for at least a second and computes the // average time it takes to run a single iteration. BenchmarkSuite.prototype.RunSingleBenchmark = function (benchmark, data) { function Measure(data) { var elapsed = 0; var start = new Date(); for (var n = 0; elapsed < 1000; n++) { benchmark.run(); elapsed = new Date() - start; } if (data != null) { data.runs += n; data.elapsed += elapsed; } } if (data == null) { // Measure the benchmark once for warm up and throw the result // away. Return a fresh data object. Measure(null); return { runs: 0, elapsed: 0 }; } else { Measure(data); // If we've run too few iterations, we continue for another second. if (data.runs < 32) return data; var usec = (data.elapsed * 1000) / data.runs; this.NotifyStep(new BenchmarkResult(benchmark, usec)); return null; } }; // This function starts running a suite, but stops between each // individual benchmark in the suite and returns a continuation // function which can be invoked to run the next benchmark. Once the // last benchmark has been executed, null is returned. BenchmarkSuite.prototype.RunStep = function (runner) { this.results = []; this.runner = runner; var length = this.benchmarks.length; var index = 0; var suite = this; var data; // Run the setup, the actual benchmark, and the tear down in three // separate steps to allow the framework to yield between any of the // steps. function RunNextSetup() { if (index < length) { try { suite.benchmarks[index].Setup(); } catch (e) { suite.NotifyError(e); return null; } return RunNextBenchmark; } suite.NotifyResult(); return null; } function RunNextBenchmark() { try { data = suite.RunSingleBenchmark(suite.benchmarks[index], data); } catch (e) { suite.NotifyError(e); return null; } // If data is null, we're done with this benchmark. return data == null ? RunNextTearDown : RunNextBenchmark(); } function RunNextTearDown() { try { suite.benchmarks[index++].TearDown(); } catch (e) { suite.NotifyError(e); return null; } return RunNextSetup; } // Start out running the setup. return RunNextSetup(); }; ================================================ FILE: benchmarks/v8-v7/crypto.js ================================================ /* * Copyright (c) 2003-2005 Tom Wu * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. * * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * In addition, the following condition applies: * * All redistributions must retain an intact copy of this copyright notice * and disclaimer. */ // The code has been adapted for use as a benchmark by Google. var Crypto = new BenchmarkSuite("Crypto", 266181, [ new Benchmark("Encrypt", encrypt), new Benchmark("Decrypt", decrypt), ]); // Basic JavaScript BN library - subset useful for RSA encryption. // Bits per digit var dbits; var BI_DB; var BI_DM; var BI_DV; var BI_FP; var BI_FV; var BI_F1; var BI_F2; // JavaScript engine analysis var canary = 0xdeadbeefcafe; var j_lm = (canary & 0xffffff) == 0xefcafe; // (public) Constructor function BigInteger(a, b, c) { this.array = new Array(); if (a != null) if ("number" == typeof a) this.fromNumber(a, b, c); else if (b == null && "string" != typeof a) this.fromString(a, 256); else this.fromString(a, b); } // return new, unset BigInteger function nbi() { return new BigInteger(null); } // am: Compute w_j += (x*this_i), propagate carries, // c is initial carry, returns final carry. // c < 3*dvalue, x < 2*dvalue, this_i < dvalue // We need to select the fastest one that works in this environment. // am1: use a single mult and divide to get the high bits, // max digit bits should be 26 because // max internal value = 2*dvalue^2-2*dvalue (< 2^53) function am1(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; while (--n >= 0) { var v = x * this_array[i++] + w_array[j] + c; c = Math.floor(v / 0x4000000); w_array[j++] = v & 0x3ffffff; } return c; } // am2 avoids a big mult-and-extract completely. // Max digit bits should be <= 30 because we do bitwise ops // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) function am2(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; var xl = x & 0x7fff, xh = x >> 15; while (--n >= 0) { var l = this_array[i] & 0x7fff; var h = this_array[i++] >> 15; var m = xh * l + h * xl; l = xl * l + ((m & 0x7fff) << 15) + w_array[j] + (c & 0x3fffffff); c = (l >>> 30) + (m >>> 15) + xh * h + (c >>> 30); w_array[j++] = l & 0x3fffffff; } return c; } // Alternately, set max digit bits to 28 since some // browsers slow down when dealing with 32-bit numbers. function am3(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; var xl = x & 0x3fff, xh = x >> 14; while (--n >= 0) { var l = this_array[i] & 0x3fff; var h = this_array[i++] >> 14; var m = xh * l + h * xl; l = xl * l + ((m & 0x3fff) << 14) + w_array[j] + c; c = (l >> 28) + (m >> 14) + xh * h; w_array[j++] = l & 0xfffffff; } return c; } // This is tailored to VMs with 2-bit tagging. It makes sure // that all the computations stay within the 29 bits available. function am4(i, x, w, j, c, n) { var this_array = this.array; var w_array = w.array; var xl = x & 0x1fff, xh = x >> 13; while (--n >= 0) { var l = this_array[i] & 0x1fff; var h = this_array[i++] >> 13; var m = xh * l + h * xl; l = xl * l + ((m & 0x1fff) << 13) + w_array[j] + c; c = (l >> 26) + (m >> 13) + xh * h; w_array[j++] = l & 0x3ffffff; } return c; } // am3/28 is best for SM, Rhino, but am4/26 is best for v8. // Kestrel (Opera 9.5) gets its best result with am4/26. // IE7 does 9% better with am3/28 than with am4/26. // Firefox (SM) gets 10% faster with am3/28 than with am4/26. setupEngine = function (fn, bits) { BigInteger.prototype.am = fn; dbits = bits; BI_DB = dbits; BI_DM = (1 << dbits) - 1; BI_DV = 1 << dbits; BI_FP = 52; BI_FV = Math.pow(2, BI_FP); BI_F1 = BI_FP - dbits; BI_F2 = 2 * dbits - BI_FP; }; // Digit conversions var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; var BI_RC = new Array(); var rr, vv; rr = "0".charCodeAt(0); for (vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; rr = "a".charCodeAt(0); for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; rr = "A".charCodeAt(0); for (vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; function int2char(n) { return BI_RM.charAt(n); } function intAt(s, i) { var c = BI_RC[s.charCodeAt(i)]; return c == null ? -1 : c; } // (protected) copy this to r function bnpCopyTo(r) { var this_array = this.array; var r_array = r.array; for (var i = this.t - 1; i >= 0; --i) r_array[i] = this_array[i]; r.t = this.t; r.s = this.s; } // (protected) set from integer value x, -DV <= x < DV function bnpFromInt(x) { var this_array = this.array; this.t = 1; this.s = x < 0 ? -1 : 0; if (x > 0) this_array[0] = x; else if (x < -1) this_array[0] = x + DV; else this.t = 0; } // return bigint initialized to value function nbv(i) { var r = nbi(); r.fromInt(i); return r; } // (protected) set from string and radix function bnpFromString(s, b) { var this_array = this.array; var k; if (b == 16) k = 4; else if (b == 8) k = 3; else if (b == 256) k = 8; // byte array else if (b == 2) k = 1; else if (b == 32) k = 5; else if (b == 4) k = 2; else { this.fromRadix(s, b); return; } this.t = 0; this.s = 0; var i = s.length, mi = false, sh = 0; while (--i >= 0) { var x = k == 8 ? s[i] & 0xff : intAt(s, i); if (x < 0) { if (s.charAt(i) == "-") mi = true; continue; } mi = false; if (sh == 0) this_array[this.t++] = x; else if (sh + k > BI_DB) { this_array[this.t - 1] |= (x & ((1 << (BI_DB - sh)) - 1)) << sh; this_array[this.t++] = x >> (BI_DB - sh); } else this_array[this.t - 1] |= x << sh; sh += k; if (sh >= BI_DB) sh -= BI_DB; } if (k == 8 && (s[0] & 0x80) != 0) { this.s = -1; if (sh > 0) this_array[this.t - 1] |= ((1 << (BI_DB - sh)) - 1) << sh; } this.clamp(); if (mi) BigInteger.ZERO.subTo(this, this); } // (protected) clamp off excess high words function bnpClamp() { var this_array = this.array; var c = this.s & BI_DM; while (this.t > 0 && this_array[this.t - 1] == c) --this.t; } // (public) return string representation in given radix function bnToString(b) { var this_array = this.array; if (this.s < 0) return "-" + this.negate().toString(b); var k; if (b == 16) k = 4; else if (b == 8) k = 3; else if (b == 2) k = 1; else if (b == 32) k = 5; else if (b == 4) k = 2; else return this.toRadix(b); var km = (1 << k) - 1, d, m = false, r = "", i = this.t; var p = BI_DB - ((i * BI_DB) % k); if (i-- > 0) { if (p < BI_DB && (d = this_array[i] >> p) > 0) { m = true; r = int2char(d); } while (i >= 0) { if (p < k) { d = (this_array[i] & ((1 << p) - 1)) << (k - p); d |= this_array[--i] >> (p += BI_DB - k); } else { d = (this_array[i] >> (p -= k)) & km; if (p <= 0) { p += BI_DB; --i; } } if (d > 0) m = true; if (m) r += int2char(d); } } return m ? r : "0"; } // (public) -this function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this, r); return r; } // (public) |this| function bnAbs() { return this.s < 0 ? this.negate() : this; } // (public) return + if this > a, - if this < a, 0 if equal function bnCompareTo(a) { var this_array = this.array; var a_array = a.array; var r = this.s - a.s; if (r != 0) return r; var i = this.t; r = i - a.t; if (r != 0) return r; while (--i >= 0) if ((r = this_array[i] - a_array[i]) != 0) return r; return 0; } // returns bit length of the integer x function nbits(x) { var r = 1, t; if ((t = x >>> 16) != 0) { x = t; r += 16; } if ((t = x >> 8) != 0) { x = t; r += 8; } if ((t = x >> 4) != 0) { x = t; r += 4; } if ((t = x >> 2) != 0) { x = t; r += 2; } if ((t = x >> 1) != 0) { x = t; r += 1; } return r; } // (public) return the number of bits in "this" function bnBitLength() { var this_array = this.array; if (this.t <= 0) return 0; return ( BI_DB * (this.t - 1) + nbits(this_array[this.t - 1] ^ (this.s & BI_DM)) ); } // (protected) r = this << n*DB function bnpDLShiftTo(n, r) { var this_array = this.array; var r_array = r.array; var i; for (i = this.t - 1; i >= 0; --i) r_array[i + n] = this_array[i]; for (i = n - 1; i >= 0; --i) r_array[i] = 0; r.t = this.t + n; r.s = this.s; } // (protected) r = this >> n*DB function bnpDRShiftTo(n, r) { var this_array = this.array; var r_array = r.array; for (var i = n; i < this.t; ++i) r_array[i - n] = this_array[i]; r.t = Math.max(this.t - n, 0); r.s = this.s; } // (protected) r = this << n function bnpLShiftTo(n, r) { var this_array = this.array; var r_array = r.array; var bs = n % BI_DB; var cbs = BI_DB - bs; var bm = (1 << cbs) - 1; var ds = Math.floor(n / BI_DB), c = (this.s << bs) & BI_DM, i; for (i = this.t - 1; i >= 0; --i) { r_array[i + ds + 1] = (this_array[i] >> cbs) | c; c = (this_array[i] & bm) << bs; } for (i = ds - 1; i >= 0; --i) r_array[i] = 0; r_array[ds] = c; r.t = this.t + ds + 1; r.s = this.s; r.clamp(); } // (protected) r = this >> n function bnpRShiftTo(n, r) { var this_array = this.array; var r_array = r.array; r.s = this.s; var ds = Math.floor(n / BI_DB); if (ds >= this.t) { r.t = 0; return; } var bs = n % BI_DB; var cbs = BI_DB - bs; var bm = (1 << bs) - 1; r_array[0] = this_array[ds] >> bs; for (var i = ds + 1; i < this.t; ++i) { r_array[i - ds - 1] |= (this_array[i] & bm) << cbs; r_array[i - ds] = this_array[i] >> bs; } if (bs > 0) r_array[this.t - ds - 1] |= (this.s & bm) << cbs; r.t = this.t - ds; r.clamp(); } // (protected) r = this - a function bnpSubTo(a, r) { var this_array = this.array; var r_array = r.array; var a_array = a.array; var i = 0, c = 0, m = Math.min(a.t, this.t); while (i < m) { c += this_array[i] - a_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } if (a.t < this.t) { c -= a.s; while (i < this.t) { c += this_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c += this.s; } else { c += this.s; while (i < a.t) { c -= a_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c -= a.s; } r.s = c < 0 ? -1 : 0; if (c < -1) r_array[i++] = BI_DV + c; else if (c > 0) r_array[i++] = c; r.t = i; r.clamp(); } // (protected) r = this * a, r != this,a (HAC 14.12) // "this" should be the larger one if appropriate. function bnpMultiplyTo(a, r) { var this_array = this.array; var r_array = r.array; var x = this.abs(), y = a.abs(); var y_array = y.array; var i = x.t; r.t = i + y.t; while (--i >= 0) r_array[i] = 0; for (i = 0; i < y.t; ++i) r_array[i + x.t] = x.am(0, y_array[i], r, i, 0, x.t); r.s = 0; r.clamp(); if (this.s != a.s) BigInteger.ZERO.subTo(r, r); } // (protected) r = this^2, r != this (HAC 14.16) function bnpSquareTo(r) { var x = this.abs(); var x_array = x.array; var r_array = r.array; var i = (r.t = 2 * x.t); while (--i >= 0) r_array[i] = 0; for (i = 0; i < x.t - 1; ++i) { var c = x.am(i, x_array[i], r, 2 * i, 0, 1); if ( (r_array[i + x.t] += x.am( i + 1, 2 * x_array[i], r, 2 * i + 1, c, x.t - i - 1 )) >= BI_DV ) { r_array[i + x.t] -= BI_DV; r_array[i + x.t + 1] = 1; } } if (r.t > 0) r_array[r.t - 1] += x.am(i, x_array[i], r, 2 * i, 0, 1); r.s = 0; r.clamp(); } // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. function bnpDivRemTo(m, q, r) { var pm = m.abs(); if (pm.t <= 0) return; var pt = this.abs(); if (pt.t < pm.t) { if (q != null) q.fromInt(0); if (r != null) this.copyTo(r); return; } if (r == null) r = nbi(); var y = nbi(), ts = this.s, ms = m.s; var pm_array = pm.array; var nsh = BI_DB - nbits(pm_array[pm.t - 1]); // normalize modulus if (nsh > 0) { pm.lShiftTo(nsh, y); pt.lShiftTo(nsh, r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; var y_array = y.array; var y0 = y_array[ys - 1]; if (y0 == 0) return; var yt = y0 * (1 << BI_F1) + (ys > 1 ? y_array[ys - 2] >> BI_F2 : 0); var d1 = BI_FV / yt, d2 = (1 << BI_F1) / yt, e = 1 << BI_F2; var i = r.t, j = i - ys, t = q == null ? nbi() : q; y.dlShiftTo(j, t); var r_array = r.array; if (r.compareTo(t) >= 0) { r_array[r.t++] = 1; r.subTo(t, r); } BigInteger.ONE.dlShiftTo(ys, t); t.subTo(y, y); // "negative" y so we can replace sub with am later while (y.t < ys) y_array[y.t++] = 0; while (--j >= 0) { // Estimate quotient digit var qd = r_array[--i] == y0 ? BI_DM : Math.floor(r_array[i] * d1 + (r_array[i - 1] + e) * d2); if ((r_array[i] += y.am(0, qd, r, j, 0, ys)) < qd) { // Try it out y.dlShiftTo(j, t); r.subTo(t, r); while (r_array[i] < --qd) r.subTo(t, r); } } if (q != null) { r.drShiftTo(ys, q); if (ts != ms) BigInteger.ZERO.subTo(q, q); } r.t = ys; r.clamp(); if (nsh > 0) r.rShiftTo(nsh, r); // Denormalize remainder if (ts < 0) BigInteger.ZERO.subTo(r, r); } // (public) this mod a function bnMod(a) { var r = nbi(); this.abs().divRemTo(a, null, r); if (this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r, r); return r; } // Modular reduction using "classic" algorithm function Classic(m) { this.m = m; } function cConvert(x) { if (x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); else return x; } function cRevert(x) { return x; } function cReduce(x) { x.divRemTo(this.m, null, x); } function cMulTo(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } function cSqrTo(x, r) { x.squareTo(r); this.reduce(r); } Classic.prototype.convert = cConvert; Classic.prototype.revert = cRevert; Classic.prototype.reduce = cReduce; Classic.prototype.mulTo = cMulTo; Classic.prototype.sqrTo = cSqrTo; // (protected) return "-1/this % 2^DB"; useful for Mont. reduction // justification: // xy == 1 (mod m) // xy = 1+km // xy(2-xy) = (1+km)(1-km) // x[y(2-xy)] = 1-k^2m^2 // x[y(2-xy)] == 1 (mod m^2) // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. // JS multiply "overflows" differently from C/C++, so care is needed here. function bnpInvDigit() { var this_array = this.array; if (this.t < 1) return 0; var x = this_array[0]; if ((x & 1) == 0) return 0; var y = x & 3; // y == 1/x mod 2^2 y = (y * (2 - (x & 0xf) * y)) & 0xf; // y == 1/x mod 2^4 y = (y * (2 - (x & 0xff) * y)) & 0xff; // y == 1/x mod 2^8 y = (y * (2 - (((x & 0xffff) * y) & 0xffff))) & 0xffff; // y == 1/x mod 2^16 // last step - calculate inverse mod DV directly; // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints y = (y * (2 - ((x * y) % BI_DV))) % BI_DV; // y == 1/x mod 2^dbits // we really want the negative inverse, and -DV < y < DV return y > 0 ? BI_DV - y : -y; } // Montgomery reduction function Montgomery(m) { this.m = m; this.mp = m.invDigit(); this.mpl = this.mp & 0x7fff; this.mph = this.mp >> 15; this.um = (1 << (BI_DB - 15)) - 1; this.mt2 = 2 * m.t; } // xR mod m function montConvert(x) { var r = nbi(); x.abs().dlShiftTo(this.m.t, r); r.divRemTo(this.m, null, r); if (x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r, r); return r; } // x/R mod m function montRevert(x) { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } // x = x/R mod m (HAC 14.32) function montReduce(x) { var x_array = x.array; while (x.t <= this.mt2) // pad x so am has enough room later x_array[x.t++] = 0; for (var i = 0; i < this.m.t; ++i) { // faster way of calculating u0 = x[i]*mp mod DV var j = x_array[i] & 0x7fff; var u0 = (j * this.mpl + (((j * this.mph + (x_array[i] >> 15) * this.mpl) & this.um) << 15)) & BI_DM; // use am to combine the multiply-shift-add into one call j = i + this.m.t; x_array[j] += this.m.am(0, u0, x, i, 0, this.m.t); // propagate carry while (x_array[j] >= BI_DV) { x_array[j] -= BI_DV; x_array[++j]++; } } x.clamp(); x.drShiftTo(this.m.t, x); if (x.compareTo(this.m) >= 0) x.subTo(this.m, x); } // r = "x^2/R mod m"; x != r function montSqrTo(x, r) { x.squareTo(r); this.reduce(r); } // r = "xy/R mod m"; x,y != r function montMulTo(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } Montgomery.prototype.convert = montConvert; Montgomery.prototype.revert = montRevert; Montgomery.prototype.reduce = montReduce; Montgomery.prototype.mulTo = montMulTo; Montgomery.prototype.sqrTo = montSqrTo; // (protected) true iff this is even function bnpIsEven() { var this_array = this.array; return (this.t > 0 ? this_array[0] & 1 : this.s) == 0; } // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) function bnpExp(e, z) { if (e > 0xffffffff || e < 1) return BigInteger.ONE; var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e) - 1; g.copyTo(r); while (--i >= 0) { z.sqrTo(r, r2); if ((e & (1 << i)) > 0) z.mulTo(r2, g, r); else { var t = r; r = r2; r2 = t; } } return z.revert(r); } // (public) this^e % m, 0 <= e < 2^32 function bnModPowInt(e, m) { var z; if (e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); return this.exp(e, z); } // protected BigInteger.prototype.copyTo = bnpCopyTo; BigInteger.prototype.fromInt = bnpFromInt; BigInteger.prototype.fromString = bnpFromString; BigInteger.prototype.clamp = bnpClamp; BigInteger.prototype.dlShiftTo = bnpDLShiftTo; BigInteger.prototype.drShiftTo = bnpDRShiftTo; BigInteger.prototype.lShiftTo = bnpLShiftTo; BigInteger.prototype.rShiftTo = bnpRShiftTo; BigInteger.prototype.subTo = bnpSubTo; BigInteger.prototype.multiplyTo = bnpMultiplyTo; BigInteger.prototype.squareTo = bnpSquareTo; BigInteger.prototype.divRemTo = bnpDivRemTo; BigInteger.prototype.invDigit = bnpInvDigit; BigInteger.prototype.isEven = bnpIsEven; BigInteger.prototype.exp = bnpExp; // public BigInteger.prototype.toString = bnToString; BigInteger.prototype.negate = bnNegate; BigInteger.prototype.abs = bnAbs; BigInteger.prototype.compareTo = bnCompareTo; BigInteger.prototype.bitLength = bnBitLength; BigInteger.prototype.mod = bnMod; BigInteger.prototype.modPowInt = bnModPowInt; // "constants" BigInteger.ZERO = nbv(0); BigInteger.ONE = nbv(1); // Copyright (c) 2005 Tom Wu // All Rights Reserved. // See "LICENSE" for details. // Extended JavaScript BN functions, required for RSA private ops. // (public) function bnClone() { var r = nbi(); this.copyTo(r); return r; } // (public) return value as integer function bnIntValue() { var this_array = this.array; if (this.s < 0) { if (this.t == 1) return this_array[0] - BI_DV; else if (this.t == 0) return -1; } else if (this.t == 1) return this_array[0]; else if (this.t == 0) return 0; // assumes 16 < DB < 32 return ((this_array[1] & ((1 << (32 - BI_DB)) - 1)) << BI_DB) | this_array[0]; } // (public) return value as byte function bnByteValue() { var this_array = this.array; return this.t == 0 ? this.s : (this_array[0] << 24) >> 24; } // (public) return value as short (assumes DB>=16) function bnShortValue() { var this_array = this.array; return this.t == 0 ? this.s : (this_array[0] << 16) >> 16; } // (protected) return x s.t. r^x < DV function bnpChunkSize(r) { return Math.floor((Math.LN2 * BI_DB) / Math.log(r)); } // (public) 0 if this == 0, 1 if this > 0 function bnSigNum() { var this_array = this.array; if (this.s < 0) return -1; else if (this.t <= 0 || (this.t == 1 && this_array[0] <= 0)) return 0; else return 1; } // (protected) convert to radix string function bnpToRadix(b) { if (b == null) b = 10; if (this.signum() == 0 || b < 2 || b > 36) return "0"; var cs = this.chunkSize(b); var a = Math.pow(b, cs); var d = nbv(a), y = nbi(), z = nbi(), r = ""; this.divRemTo(d, y, z); while (y.signum() > 0) { r = (a + z.intValue()).toString(b).substr(1) + r; y.divRemTo(d, y, z); } return z.intValue().toString(b) + r; } // (protected) convert from radix string function bnpFromRadix(s, b) { this.fromInt(0); if (b == null) b = 10; var cs = this.chunkSize(b); var d = Math.pow(b, cs), mi = false, j = 0, w = 0; for (var i = 0; i < s.length; ++i) { var x = intAt(s, i); if (x < 0) { if (s.charAt(i) == "-" && this.signum() == 0) mi = true; continue; } w = b * w + x; if (++j >= cs) { this.dMultiply(d); this.dAddOffset(w, 0); j = 0; w = 0; } } if (j > 0) { this.dMultiply(Math.pow(b, j)); this.dAddOffset(w, 0); } if (mi) BigInteger.ZERO.subTo(this, this); } // (protected) alternate constructor function bnpFromNumber(a, b, c) { if ("number" == typeof b) { // new BigInteger(int,int,RNG) if (a < 2) this.fromInt(1); else { this.fromNumber(a, c); if (!this.testBit(a - 1)) // force MSB set this.bitwiseTo(BigInteger.ONE.shiftLeft(a - 1), op_or, this); if (this.isEven()) this.dAddOffset(1, 0); // force odd while (!this.isProbablePrime(b)) { this.dAddOffset(2, 0); if (this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a - 1), this); } } } else { // new BigInteger(int,RNG) var x = new Array(), t = a & 7; x.length = (a >> 3) + 1; b.nextBytes(x); if (t > 0) x[0] &= (1 << t) - 1; else x[0] = 0; this.fromString(x, 256); } } // (public) convert to bigendian byte array function bnToByteArray() { var this_array = this.array; var i = this.t, r = new Array(); r[0] = this.s; var p = BI_DB - ((i * BI_DB) % 8), d, k = 0; if (i-- > 0) { if (p < BI_DB && (d = this_array[i] >> p) != (this.s & BI_DM) >> p) r[k++] = d | (this.s << (BI_DB - p)); while (i >= 0) { if (p < 8) { d = (this_array[i] & ((1 << p) - 1)) << (8 - p); d |= this_array[--i] >> (p += BI_DB - 8); } else { d = (this_array[i] >> (p -= 8)) & 0xff; if (p <= 0) { p += BI_DB; --i; } } if ((d & 0x80) != 0) d |= -256; if (k == 0 && (this.s & 0x80) != (d & 0x80)) ++k; if (k > 0 || d != this.s) r[k++] = d; } } return r; } function bnEquals(a) { return this.compareTo(a) == 0; } function bnMin(a) { return this.compareTo(a) < 0 ? this : a; } function bnMax(a) { return this.compareTo(a) > 0 ? this : a; } // (protected) r = this op a (bitwise) function bnpBitwiseTo(a, op, r) { var this_array = this.array; var a_array = a.array; var r_array = r.array; var i, f, m = Math.min(a.t, this.t); for (i = 0; i < m; ++i) r_array[i] = op(this_array[i], a_array[i]); if (a.t < this.t) { f = a.s & BI_DM; for (i = m; i < this.t; ++i) r_array[i] = op(this_array[i], f); r.t = this.t; } else { f = this.s & BI_DM; for (i = m; i < a.t; ++i) r_array[i] = op(f, a_array[i]); r.t = a.t; } r.s = op(this.s, a.s); r.clamp(); } // (public) this & a function op_and(x, y) { return x & y; } function bnAnd(a) { var r = nbi(); this.bitwiseTo(a, op_and, r); return r; } // (public) this | a function op_or(x, y) { return x | y; } function bnOr(a) { var r = nbi(); this.bitwiseTo(a, op_or, r); return r; } // (public) this ^ a function op_xor(x, y) { return x ^ y; } function bnXor(a) { var r = nbi(); this.bitwiseTo(a, op_xor, r); return r; } // (public) this & ~a function op_andnot(x, y) { return x & ~y; } function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a, op_andnot, r); return r; } // (public) ~this function bnNot() { var this_array = this.array; var r = nbi(); var r_array = r.array; for (var i = 0; i < this.t; ++i) r_array[i] = BI_DM & ~this_array[i]; r.t = this.t; r.s = ~this.s; return r; } // (public) this << n function bnShiftLeft(n) { var r = nbi(); if (n < 0) this.rShiftTo(-n, r); else this.lShiftTo(n, r); return r; } // (public) this >> n function bnShiftRight(n) { var r = nbi(); if (n < 0) this.lShiftTo(-n, r); else this.rShiftTo(n, r); return r; } // return index of lowest 1-bit in x, x < 2^31 function lbit(x) { if (x == 0) return -1; var r = 0; if ((x & 0xffff) == 0) { x >>= 16; r += 16; } if ((x & 0xff) == 0) { x >>= 8; r += 8; } if ((x & 0xf) == 0) { x >>= 4; r += 4; } if ((x & 3) == 0) { x >>= 2; r += 2; } if ((x & 1) == 0) ++r; return r; } // (public) returns index of lowest 1-bit (or -1 if none) function bnGetLowestSetBit() { var this_array = this.array; for (var i = 0; i < this.t; ++i) if (this_array[i] != 0) return i * BI_DB + lbit(this_array[i]); if (this.s < 0) return this.t * BI_DB; return -1; } // return number of 1 bits in x function cbit(x) { var r = 0; while (x != 0) { x &= x - 1; ++r; } return r; } // (public) return number of set bits function bnBitCount() { var r = 0, x = this.s & BI_DM; for (var i = 0; i < this.t; ++i) r += cbit(this_array[i] ^ x); return r; } // (public) true iff nth bit is set function bnTestBit(n) { var this_array = this.array; var j = Math.floor(n / BI_DB); if (j >= this.t) return this.s != 0; return (this_array[j] & (1 << n % BI_DB)) != 0; } // (protected) this op (1<>= BI_DB; } if (a.t < this.t) { c += a.s; while (i < this.t) { c += this_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c += this.s; } else { c += this.s; while (i < a.t) { c += a_array[i]; r_array[i++] = c & BI_DM; c >>= BI_DB; } c += a.s; } r.s = c < 0 ? -1 : 0; if (c > 0) r_array[i++] = c; else if (c < -1) r_array[i++] = BI_DV + c; r.t = i; r.clamp(); } // (public) this + a function bnAdd(a) { var r = nbi(); this.addTo(a, r); return r; } // (public) this - a function bnSubtract(a) { var r = nbi(); this.subTo(a, r); return r; } // (public) this * a function bnMultiply(a) { var r = nbi(); this.multiplyTo(a, r); return r; } // (public) this / a function bnDivide(a) { var r = nbi(); this.divRemTo(a, r, null); return r; } // (public) this % a function bnRemainder(a) { var r = nbi(); this.divRemTo(a, null, r); return r; } // (public) [this/a,this%a] function bnDivideAndRemainder(a) { var q = nbi(), r = nbi(); this.divRemTo(a, q, r); return new Array(q, r); } // (protected) this *= n, this >= 0, 1 < n < DV function bnpDMultiply(n) { var this_array = this.array; this_array[this.t] = this.am(0, n - 1, this, 0, 0, this.t); ++this.t; this.clamp(); } // (protected) this += n << w words, this >= 0 function bnpDAddOffset(n, w) { var this_array = this.array; while (this.t <= w) this_array[this.t++] = 0; this_array[w] += n; while (this_array[w] >= BI_DV) { this_array[w] -= BI_DV; if (++w >= this.t) this_array[this.t++] = 0; ++this_array[w]; } } // A "null" reducer function NullExp() {} function nNop(x) { return x; } function nMulTo(x, y, r) { x.multiplyTo(y, r); } function nSqrTo(x, r) { x.squareTo(r); } NullExp.prototype.convert = nNop; NullExp.prototype.revert = nNop; NullExp.prototype.mulTo = nMulTo; NullExp.prototype.sqrTo = nSqrTo; // (public) this^e function bnPow(e) { return this.exp(e, new NullExp()); } // (protected) r = lower n words of "this * a", a.t <= n // "this" should be the larger one if appropriate. function bnpMultiplyLowerTo(a, n, r) { var r_array = r.array; var a_array = a.array; var i = Math.min(this.t + a.t, n); r.s = 0; // assumes a,this >= 0 r.t = i; while (i > 0) r_array[--i] = 0; var j; for (j = r.t - this.t; i < j; ++i) r_array[i + this.t] = this.am(0, a_array[i], r, i, 0, this.t); for (j = Math.min(a.t, n); i < j; ++i) this.am(0, a_array[i], r, i, 0, n - i); r.clamp(); } // (protected) r = "this * a" without lower n words, n > 0 // "this" should be the larger one if appropriate. function bnpMultiplyUpperTo(a, n, r) { var r_array = r.array; var a_array = a.array; --n; var i = (r.t = this.t + a.t - n); r.s = 0; // assumes a,this >= 0 while (--i >= 0) r_array[i] = 0; for (i = Math.max(n - this.t, 0); i < a.t; ++i) r_array[this.t + i - n] = this.am( n - i, a_array[i], r, 0, 0, this.t + i - n ); r.clamp(); r.drShiftTo(1, r); } // Barrett modular reduction function Barrett(m) { // setup Barrett this.r2 = nbi(); this.q3 = nbi(); BigInteger.ONE.dlShiftTo(2 * m.t, this.r2); this.mu = this.r2.divide(m); this.m = m; } function barrettConvert(x) { if (x.s < 0 || x.t > 2 * this.m.t) return x.mod(this.m); else if (x.compareTo(this.m) < 0) return x; else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } } function barrettRevert(x) { return x; } // x = x mod m (HAC 14.42) function barrettReduce(x) { x.drShiftTo(this.m.t - 1, this.r2); if (x.t > this.m.t + 1) { x.t = this.m.t + 1; x.clamp(); } this.mu.multiplyUpperTo(this.r2, this.m.t + 1, this.q3); this.m.multiplyLowerTo(this.q3, this.m.t + 1, this.r2); while (x.compareTo(this.r2) < 0) x.dAddOffset(1, this.m.t + 1); x.subTo(this.r2, x); while (x.compareTo(this.m) >= 0) x.subTo(this.m, x); } // r = x^2 mod m; x != r function barrettSqrTo(x, r) { x.squareTo(r); this.reduce(r); } // r = x*y mod m; x,y != r function barrettMulTo(x, y, r) { x.multiplyTo(y, r); this.reduce(r); } Barrett.prototype.convert = barrettConvert; Barrett.prototype.revert = barrettRevert; Barrett.prototype.reduce = barrettReduce; Barrett.prototype.mulTo = barrettMulTo; Barrett.prototype.sqrTo = barrettSqrTo; // (public) this^e % m (HAC 14.85) function bnModPow(e, m) { var e_array = e.array; var i = e.bitLength(), k, r = nbv(1), z; if (i <= 0) return r; else if (i < 18) k = 1; else if (i < 48) k = 3; else if (i < 144) k = 4; else if (i < 768) k = 5; else k = 6; if (i < 8) z = new Classic(m); else if (m.isEven()) z = new Barrett(m); else z = new Montgomery(m); // precomputation var g = new Array(), n = 3, k1 = k - 1, km = (1 << k) - 1; g[1] = z.convert(this); if (k > 1) { var g2 = nbi(); z.sqrTo(g[1], g2); while (n <= km) { g[n] = nbi(); z.mulTo(g2, g[n - 2], g[n]); n += 2; } } var j = e.t - 1, w, is1 = true, r2 = nbi(), t; i = nbits(e_array[j]) - 1; while (j >= 0) { if (i >= k1) w = (e_array[j] >> (i - k1)) & km; else { w = (e_array[j] & ((1 << (i + 1)) - 1)) << (k1 - i); if (j > 0) w |= e_array[j - 1] >> (BI_DB + i - k1); } n = k; while ((w & 1) == 0) { w >>= 1; --n; } if ((i -= n) < 0) { i += BI_DB; --j; } if (is1) { // ret == 1, don't bother squaring or multiplying it g[w].copyTo(r); is1 = false; } else { while (n > 1) { z.sqrTo(r, r2); z.sqrTo(r2, r); n -= 2; } if (n > 0) z.sqrTo(r, r2); else { t = r; r = r2; r2 = t; } z.mulTo(r2, g[w], r); } while (j >= 0 && (e_array[j] & (1 << i)) == 0) { z.sqrTo(r, r2); t = r; r = r2; r2 = t; if (--i < 0) { i = BI_DB - 1; --j; } } } return z.revert(r); } // (public) gcd(this,a) (HAC 14.54) function bnGCD(a) { var x = this.s < 0 ? this.negate() : this.clone(); var y = a.s < 0 ? a.negate() : a.clone(); if (x.compareTo(y) < 0) { var t = x; x = y; y = t; } var i = x.getLowestSetBit(), g = y.getLowestSetBit(); if (g < 0) return x; if (i < g) g = i; if (g > 0) { x.rShiftTo(g, x); y.rShiftTo(g, y); } while (x.signum() > 0) { if ((i = x.getLowestSetBit()) > 0) x.rShiftTo(i, x); if ((i = y.getLowestSetBit()) > 0) y.rShiftTo(i, y); if (x.compareTo(y) >= 0) { x.subTo(y, x); x.rShiftTo(1, x); } else { y.subTo(x, y); y.rShiftTo(1, y); } } if (g > 0) y.lShiftTo(g, y); return y; } // (protected) this % n, n < 2^26 function bnpModInt(n) { var this_array = this.array; if (n <= 0) return 0; var d = BI_DV % n, r = this.s < 0 ? n - 1 : 0; if (this.t > 0) if (d == 0) r = this_array[0] % n; else for (var i = this.t - 1; i >= 0; --i) r = (d * r + this_array[i]) % n; return r; } // (public) 1/this % m (HAC 14.61) function bnModInverse(m) { var ac = m.isEven(); if ((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; var u = m.clone(), v = this.clone(); var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); while (u.signum() != 0) { while (u.isEven()) { u.rShiftTo(1, u); if (ac) { if (!a.isEven() || !b.isEven()) { a.addTo(this, a); b.subTo(m, b); } a.rShiftTo(1, a); } else if (!b.isEven()) b.subTo(m, b); b.rShiftTo(1, b); } while (v.isEven()) { v.rShiftTo(1, v); if (ac) { if (!c.isEven() || !d.isEven()) { c.addTo(this, c); d.subTo(m, d); } c.rShiftTo(1, c); } else if (!d.isEven()) d.subTo(m, d); d.rShiftTo(1, d); } if (u.compareTo(v) >= 0) { u.subTo(v, u); if (ac) a.subTo(c, a); b.subTo(d, b); } else { v.subTo(u, v); if (ac) c.subTo(a, c); d.subTo(b, d); } } if (v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; if (d.compareTo(m) >= 0) return d.subtract(m); if (d.signum() < 0) d.addTo(m, d); else return d; if (d.signum() < 0) return d.add(m); else return d; } var lowprimes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, ]; var lplim = (1 << 26) / lowprimes[lowprimes.length - 1]; // (public) test primality with certainty >= 1-.5^t function bnIsProbablePrime(t) { var i, x = this.abs(); var x_array = x.array; if (x.t == 1 && x_array[0] <= lowprimes[lowprimes.length - 1]) { for (i = 0; i < lowprimes.length; ++i) if (x_array[0] == lowprimes[i]) return true; return false; } if (x.isEven()) return false; i = 1; while (i < lowprimes.length) { var m = lowprimes[i], j = i + 1; while (j < lowprimes.length && m < lplim) m *= lowprimes[j++]; m = x.modInt(m); while (i < j) if (m % lowprimes[i++] == 0) return false; } return x.millerRabin(t); } // (protected) true if probably prime (HAC 4.24, Miller-Rabin) function bnpMillerRabin(t) { var n1 = this.subtract(BigInteger.ONE); var k = n1.getLowestSetBit(); if (k <= 0) return false; var r = n1.shiftRight(k); t = (t + 1) >> 1; if (t > lowprimes.length) t = lowprimes.length; var a = nbi(); for (var i = 0; i < t; ++i) { a.fromInt(lowprimes[i]); var y = a.modPow(r, this); if (y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { var j = 1; while (j++ < k && y.compareTo(n1) != 0) { y = y.modPowInt(2, this); if (y.compareTo(BigInteger.ONE) == 0) return false; } if (y.compareTo(n1) != 0) return false; } } return true; } // protected BigInteger.prototype.chunkSize = bnpChunkSize; BigInteger.prototype.toRadix = bnpToRadix; BigInteger.prototype.fromRadix = bnpFromRadix; BigInteger.prototype.fromNumber = bnpFromNumber; BigInteger.prototype.bitwiseTo = bnpBitwiseTo; BigInteger.prototype.changeBit = bnpChangeBit; BigInteger.prototype.addTo = bnpAddTo; BigInteger.prototype.dMultiply = bnpDMultiply; BigInteger.prototype.dAddOffset = bnpDAddOffset; BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; BigInteger.prototype.modInt = bnpModInt; BigInteger.prototype.millerRabin = bnpMillerRabin; // public BigInteger.prototype.clone = bnClone; BigInteger.prototype.intValue = bnIntValue; BigInteger.prototype.byteValue = bnByteValue; BigInteger.prototype.shortValue = bnShortValue; BigInteger.prototype.signum = bnSigNum; BigInteger.prototype.toByteArray = bnToByteArray; BigInteger.prototype.equals = bnEquals; BigInteger.prototype.min = bnMin; BigInteger.prototype.max = bnMax; BigInteger.prototype.and = bnAnd; BigInteger.prototype.or = bnOr; BigInteger.prototype.xor = bnXor; BigInteger.prototype.andNot = bnAndNot; BigInteger.prototype.not = bnNot; BigInteger.prototype.shiftLeft = bnShiftLeft; BigInteger.prototype.shiftRight = bnShiftRight; BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; BigInteger.prototype.bitCount = bnBitCount; BigInteger.prototype.testBit = bnTestBit; BigInteger.prototype.setBit = bnSetBit; BigInteger.prototype.clearBit = bnClearBit; BigInteger.prototype.flipBit = bnFlipBit; BigInteger.prototype.add = bnAdd; BigInteger.prototype.subtract = bnSubtract; BigInteger.prototype.multiply = bnMultiply; BigInteger.prototype.divide = bnDivide; BigInteger.prototype.remainder = bnRemainder; BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; BigInteger.prototype.modPow = bnModPow; BigInteger.prototype.modInverse = bnModInverse; BigInteger.prototype.pow = bnPow; BigInteger.prototype.gcd = bnGCD; BigInteger.prototype.isProbablePrime = bnIsProbablePrime; // BigInteger interfaces not implemented in jsbn: // BigInteger(int signum, byte[] magnitude) // double doubleValue() // float floatValue() // int hashCode() // long longValue() // static BigInteger valueOf(long val) // prng4.js - uses Arcfour as a PRNG function Arcfour() { this.i = 0; this.j = 0; this.S = new Array(); } // Initialize arcfour context from key, an array of ints, each from [0..255] function ARC4init(key) { var i, j, t; for (i = 0; i < 256; ++i) this.S[i] = i; j = 0; for (i = 0; i < 256; ++i) { j = (j + this.S[i] + key[i % key.length]) & 255; t = this.S[i]; this.S[i] = this.S[j]; this.S[j] = t; } this.i = 0; this.j = 0; } function ARC4next() { var t; this.i = (this.i + 1) & 255; this.j = (this.j + this.S[this.i]) & 255; t = this.S[this.i]; this.S[this.i] = this.S[this.j]; this.S[this.j] = t; return this.S[(t + this.S[this.i]) & 255]; } Arcfour.prototype.init = ARC4init; Arcfour.prototype.next = ARC4next; // Plug in your RNG constructor here function prng_newstate() { return new Arcfour(); } // Pool size must be a multiple of 4 and greater than 32. // An array of bytes the size of the pool will be passed to init() var rng_psize = 256; // Random number generator - requires a PRNG backend, e.g. prng4.js // For best results, put code like // // in your main HTML document. var rng_state; var rng_pool; var rng_pptr; // Mix in a 32-bit integer into the pool function rng_seed_int(x) { rng_pool[rng_pptr++] ^= x & 255; rng_pool[rng_pptr++] ^= (x >> 8) & 255; rng_pool[rng_pptr++] ^= (x >> 16) & 255; rng_pool[rng_pptr++] ^= (x >> 24) & 255; if (rng_pptr >= rng_psize) rng_pptr -= rng_psize; } // Mix in the current time (w/milliseconds) into the pool function rng_seed_time() { // Use pre-computed date to avoid making the benchmark // results dependent on the current date. rng_seed_int(1122926989487); } // Initialize the pool with junk if needed. if (rng_pool == null) { rng_pool = new Array(); rng_pptr = 0; var t; while (rng_pptr < rng_psize) { // extract some randomness from Math.random() t = Math.floor(65536 * Math.random()); rng_pool[rng_pptr++] = t >>> 8; rng_pool[rng_pptr++] = t & 255; } rng_pptr = 0; rng_seed_time(); //rng_seed_int(window.screenX); //rng_seed_int(window.screenY); } function rng_get_byte() { if (rng_state == null) { rng_seed_time(); rng_state = prng_newstate(); rng_state.init(rng_pool); for (rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) rng_pool[rng_pptr] = 0; rng_pptr = 0; //rng_pool = null; } // TODO: allow reseeding after first request return rng_state.next(); } function rng_get_bytes(ba) { var i; for (i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); } function SecureRandom() {} SecureRandom.prototype.nextBytes = rng_get_bytes; // Depends on jsbn.js and rng.js // convert a (hex) string to a bignum object function parseBigInt(str, r) { return new BigInteger(str, r); } function linebrk(s, n) { var ret = ""; var i = 0; while (i + n < s.length) { ret += s.substring(i, i + n) + "\n"; i += n; } return ret + s.substring(i, s.length); } function byte2Hex(b) { if (b < 0x10) return "0" + b.toString(16); else return b.toString(16); } // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint function pkcs1pad2(s, n) { if (n < s.length + 11) { alert("Message too long for RSA"); return null; } var ba = new Array(); var i = s.length - 1; while (i >= 0 && n > 0) ba[--n] = s.charCodeAt(i--); ba[--n] = 0; var rng = new SecureRandom(); var x = new Array(); while (n > 2) { // random non-zero pad x[0] = 0; while (x[0] == 0) rng.nextBytes(x); ba[--n] = x[0]; } ba[--n] = 2; ba[--n] = 0; return new BigInteger(ba); } // "empty" RSA key constructor function RSAKey() { this.n = null; this.e = 0; this.d = null; this.p = null; this.q = null; this.dmp1 = null; this.dmq1 = null; this.coeff = null; } // Set the public key fields N and e from hex strings function RSASetPublic(N, E) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); } else alert("Invalid RSA public key"); } // Perform raw public operation on "x": return x^e (mod n) function RSADoPublic(x) { return x.modPowInt(this.e, this.n); } // Return the PKCS#1 RSA encryption of "text" as an even-length hex string function RSAEncrypt(text) { var m = pkcs1pad2(text, (this.n.bitLength() + 7) >> 3); if (m == null) return null; var c = this.doPublic(m); if (c == null) return null; var h = c.toString(16); if ((h.length & 1) == 0) return h; else return "0" + h; } // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string //function RSAEncryptB64(text) { // var h = this.encrypt(text); // if(h) return hex2b64(h); else return null; //} // protected RSAKey.prototype.doPublic = RSADoPublic; // public RSAKey.prototype.setPublic = RSASetPublic; RSAKey.prototype.encrypt = RSAEncrypt; //RSAKey.prototype.encrypt_b64 = RSAEncryptB64; // Depends on rsa.js and jsbn2.js // Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext function pkcs1unpad2(d, n) { var b = d.toByteArray(); var i = 0; while (i < b.length && b[i] == 0) ++i; if (b.length - i != n - 1 || b[i] != 2) return null; ++i; while (b[i] != 0) if (++i >= b.length) return null; var ret = ""; while (++i < b.length) ret += String.fromCharCode(b[i]); return ret; } // Set the private key fields N, e, and d from hex strings function RSASetPrivate(N, E, D) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = parseBigInt(D, 16); } else alert("Invalid RSA private key"); } // Set the private key fields N, e, d and CRT params from hex strings function RSASetPrivateEx(N, E, D, P, Q, DP, DQ, C) { if (N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N, 16); this.e = parseInt(E, 16); this.d = parseBigInt(D, 16); this.p = parseBigInt(P, 16); this.q = parseBigInt(Q, 16); this.dmp1 = parseBigInt(DP, 16); this.dmq1 = parseBigInt(DQ, 16); this.coeff = parseBigInt(C, 16); } else alert("Invalid RSA private key"); } // Generate a new random private key B bits long, using public expt E function RSAGenerate(B, E) { var rng = new SecureRandom(); var qs = B >> 1; this.e = parseInt(E, 16); var ee = new BigInteger(E, 16); for (;;) { for (;;) { this.p = new BigInteger(B - qs, 1, rng); if ( this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10) ) break; } for (;;) { this.q = new BigInteger(qs, 1, rng); if ( this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10) ) break; } if (this.p.compareTo(this.q) <= 0) { var t = this.p; this.p = this.q; this.q = t; } var p1 = this.p.subtract(BigInteger.ONE); var q1 = this.q.subtract(BigInteger.ONE); var phi = p1.multiply(q1); if (phi.gcd(ee).compareTo(BigInteger.ONE) == 0) { this.n = this.p.multiply(this.q); this.d = ee.modInverse(phi); this.dmp1 = this.d.mod(p1); this.dmq1 = this.d.mod(q1); this.coeff = this.q.modInverse(this.p); break; } } } // Perform raw private operation on "x": return x^d (mod n) function RSADoPrivate(x) { if (this.p == null || this.q == null) return x.modPow(this.d, this.n); // TODO: re-calculate any missing CRT params var xp = x.mod(this.p).modPow(this.dmp1, this.p); var xq = x.mod(this.q).modPow(this.dmq1, this.q); while (xp.compareTo(xq) < 0) xp = xp.add(this.p); return xp .subtract(xq) .multiply(this.coeff) .mod(this.p) .multiply(this.q) .add(xq); } // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is an even-length hex string and the output is a plain string. function RSADecrypt(ctext) { var c = parseBigInt(ctext, 16); var m = this.doPrivate(c); if (m == null) return null; return pkcs1unpad2(m, (this.n.bitLength() + 7) >> 3); } // Return the PKCS#1 RSA decryption of "ctext". // "ctext" is a Base64-encoded string and the output is a plain string. //function RSAB64Decrypt(ctext) { // var h = b64tohex(ctext); // if(h) return this.decrypt(h); else return null; //} // protected RSAKey.prototype.doPrivate = RSADoPrivate; // public RSAKey.prototype.setPrivate = RSASetPrivate; RSAKey.prototype.setPrivateEx = RSASetPrivateEx; RSAKey.prototype.generate = RSAGenerate; RSAKey.prototype.decrypt = RSADecrypt; //RSAKey.prototype.b64_decrypt = RSAB64Decrypt; nValue = "a5261939975948bb7a58dffe5ff54e65f0498f9175f5a09288810b8975871e99af3b5dd94057b0fc07535f5f97444504fa35169d461d0d30cf0192e307727c065168c788771c561a9400fb49175e9e6aa4e23fe11af69e9412dd23b0cb6684c4c2429bce139e848ab26d0829073351f4acd36074eafd036a5eb83359d2a698d3"; eValue = "10001"; dValue = "8e9912f6d3645894e8d38cb58c0db81ff516cf4c7e5a14c7f1eddb1459d2cded4d8d293fc97aee6aefb861859c8b6a3d1dfe710463e1f9ddc72048c09751971c4a580aa51eb523357a3cc48d31cfad1d4a165066ed92d4748fb6571211da5cb14bc11b6e2df7c1a559e6d5ac1cd5c94703a22891464fba23d0d965086277a161"; pValue = "d090ce58a92c75233a6486cb0a9209bf3583b64f540c76f5294bb97d285eed33aec220bde14b2417951178ac152ceab6da7090905b478195498b352048f15e7d"; qValue = "cab575dc652bb66df15a0359609d51d1db184750c00c6698b90ef3465c99655103edbf0d54c56aec0ce3c4d22592338092a126a0cc49f65a4a30d222b411e58f"; dmp1Value = "1a24bca8e273df2f0e47c199bbf678604e7df7215480c77c8db39f49b000ce2cf7500038acfff5433b7d582a01f1826e6f4d42e1c57f5e1fef7b12aabc59fd25"; dmq1Value = "3d06982efbbe47339e1f6d36b1216b8a741d410b0c662f54f7118b27b9a4ec9d914337eb39841d8666f3034408cf94f5b62f11c402fc994fe15a05493150d9fd"; coeffValue = "3a3e731acd8960b7ff9eb81a7ff93bd1cfa74cbd56987db58b4594fb09c09084db1734c8143f98b602b981aaa9243ca28deb69b5b280ee8dcee0fd2625e53250"; setupEngine(am3, 28); var TEXT = "The quick brown fox jumped over the extremely lazy frog! " + "Now is the time for all good men to come to the party."; var encrypted; function encrypt() { var RSA = new RSAKey(); RSA.setPublic(nValue, eValue); RSA.setPrivateEx( nValue, eValue, dValue, pValue, qValue, dmp1Value, dmq1Value, coeffValue ); encrypted = RSA.encrypt(TEXT); } function decrypt() { var RSA = new RSAKey(); RSA.setPublic(nValue, eValue); RSA.setPrivateEx( nValue, eValue, dValue, pValue, qValue, dmp1Value, dmq1Value, coeffValue ); var decrypted = RSA.decrypt(encrypted); if (decrypted != TEXT) { throw new Error("Crypto operation failed"); } } ================================================ FILE: benchmarks/v8-v7/deltablue.js ================================================ // Copyright 2008 the V8 project authors. All rights reserved. // Copyright 1996 John Maloney and Mario Wolczko. // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // This implementation of the DeltaBlue benchmark is derived // from the Smalltalk implementation by John Maloney and Mario // Wolczko. Some parts have been translated directly, whereas // others have been modified more aggresively to make it feel // more like a JavaScript program. var DeltaBlue = new BenchmarkSuite("DeltaBlue", 66118, [ new Benchmark("DeltaBlue", deltaBlue), ]); /** * A JavaScript implementation of the DeltaBlue constraint-solving * algorithm, as described in: * * "The DeltaBlue Algorithm: An Incremental Constraint Hierarchy Solver" * Bjorn N. Freeman-Benson and John Maloney * January 1990 Communications of the ACM, * also available as University of Washington TR 89-08-06. * * Beware: this benchmark is written in a grotesque style where * the constraint model is built by side-effects from constructors. * I've kept it this way to avoid deviating too much from the original * implementation. */ /* --- O b j e c t M o d e l --- */ Object.prototype.inheritsFrom = function (shuper) { function Inheriter() {} Inheriter.prototype = shuper.prototype; this.prototype = new Inheriter(); this.superConstructor = shuper; }; function OrderedCollection() { this.elms = new Array(); } OrderedCollection.prototype.add = function (elm) { this.elms.push(elm); }; OrderedCollection.prototype.at = function (index) { return this.elms[index]; }; OrderedCollection.prototype.size = function () { return this.elms.length; }; OrderedCollection.prototype.removeFirst = function () { return this.elms.pop(); }; OrderedCollection.prototype.remove = function (elm) { var index = 0, skipped = 0; for (var i = 0; i < this.elms.length; i++) { var value = this.elms[i]; if (value != elm) { this.elms[index] = value; index++; } else { skipped++; } } for (var i = 0; i < skipped; i++) this.elms.pop(); }; /* --- * * S t r e n g t h * --- */ /** * Strengths are used to measure the relative importance of constraints. * New strengths may be inserted in the strength hierarchy without * disrupting current constraints. Strengths cannot be created outside * this class, so pointer comparison can be used for value comparison. */ function Strength(strengthValue, name) { this.strengthValue = strengthValue; this.name = name; } Strength.stronger = function (s1, s2) { return s1.strengthValue < s2.strengthValue; }; Strength.weaker = function (s1, s2) { return s1.strengthValue > s2.strengthValue; }; Strength.weakestOf = function (s1, s2) { return this.weaker(s1, s2) ? s1 : s2; }; Strength.strongest = function (s1, s2) { return this.stronger(s1, s2) ? s1 : s2; }; Strength.prototype.nextWeaker = function () { switch (this.strengthValue) { case 0: return Strength.WEAKEST; case 1: return Strength.WEAK_DEFAULT; case 2: return Strength.NORMAL; case 3: return Strength.STRONG_DEFAULT; case 4: return Strength.PREFERRED; case 5: return Strength.REQUIRED; } }; // Strength constants. Strength.REQUIRED = new Strength(0, "required"); Strength.STONG_PREFERRED = new Strength(1, "strongPreferred"); Strength.PREFERRED = new Strength(2, "preferred"); Strength.STRONG_DEFAULT = new Strength(3, "strongDefault"); Strength.NORMAL = new Strength(4, "normal"); Strength.WEAK_DEFAULT = new Strength(5, "weakDefault"); Strength.WEAKEST = new Strength(6, "weakest"); /* --- * * C o n s t r a i n t * --- */ /** * An abstract class representing a system-maintainable relationship * (or "constraint") between a set of variables. A constraint supplies * a strength instance variable; concrete subclasses provide a means * of storing the constrained variables and other information required * to represent a constraint. */ function Constraint(strength) { this.strength = strength; } /** * Activate this constraint and attempt to satisfy it. */ Constraint.prototype.addConstraint = function () { this.addToGraph(); planner.incrementalAdd(this); }; /** * Attempt to find a way to enforce this constraint. If successful, * record the solution, perhaps modifying the current dataflow * graph. Answer the constraint that this constraint overrides, if * there is one, or nil, if there isn't. * Assume: I am not already satisfied. */ Constraint.prototype.satisfy = function (mark) { this.chooseMethod(mark); if (!this.isSatisfied()) { if (this.strength == Strength.REQUIRED) alert("Could not satisfy a required constraint!"); return null; } this.markInputs(mark); var out = this.output(); var overridden = out.determinedBy; if (overridden != null) overridden.markUnsatisfied(); out.determinedBy = this; if (!planner.addPropagate(this, mark)) alert("Cycle encountered"); out.mark = mark; return overridden; }; Constraint.prototype.destroyConstraint = function () { if (this.isSatisfied()) planner.incrementalRemove(this); else this.removeFromGraph(); }; /** * Normal constraints are not input constraints. An input constraint * is one that depends on external state, such as the mouse, the * keybord, a clock, or some arbitraty piece of imperative code. */ Constraint.prototype.isInput = function () { return false; }; /* --- * * U n a r y C o n s t r a i n t * --- */ /** * Abstract superclass for constraints having a single possible output * variable. */ function UnaryConstraint(v, strength) { UnaryConstraint.superConstructor.call(this, strength); this.myOutput = v; this.satisfied = false; this.addConstraint(); } UnaryConstraint.inheritsFrom(Constraint); /** * Adds this constraint to the constraint graph */ UnaryConstraint.prototype.addToGraph = function () { this.myOutput.addConstraint(this); this.satisfied = false; }; /** * Decides if this constraint can be satisfied and records that * decision. */ UnaryConstraint.prototype.chooseMethod = function (mark) { this.satisfied = this.myOutput.mark != mark && Strength.stronger(this.strength, this.myOutput.walkStrength); }; /** * Returns true if this constraint is satisfied in the current solution. */ UnaryConstraint.prototype.isSatisfied = function () { return this.satisfied; }; UnaryConstraint.prototype.markInputs = function (mark) { // has no inputs }; /** * Returns the current output variable. */ UnaryConstraint.prototype.output = function () { return this.myOutput; }; /** * Calculate the walkabout strength, the stay flag, and, if it is * 'stay', the value for the current output of this constraint. Assume * this constraint is satisfied. */ UnaryConstraint.prototype.recalculate = function () { this.myOutput.walkStrength = this.strength; this.myOutput.stay = !this.isInput(); if (this.myOutput.stay) this.execute(); // Stay optimization }; /** * Records that this constraint is unsatisfied */ UnaryConstraint.prototype.markUnsatisfied = function () { this.satisfied = false; }; UnaryConstraint.prototype.inputsKnown = function () { return true; }; UnaryConstraint.prototype.removeFromGraph = function () { if (this.myOutput != null) this.myOutput.removeConstraint(this); this.satisfied = false; }; /* --- * * S t a y C o n s t r a i n t * --- */ /** * Variables that should, with some level of preference, stay the same. * Planners may exploit the fact that instances, if satisfied, will not * change their output during plan execution. This is called "stay * optimization". */ function StayConstraint(v, str) { StayConstraint.superConstructor.call(this, v, str); } StayConstraint.inheritsFrom(UnaryConstraint); StayConstraint.prototype.execute = function () { // Stay constraints do nothing }; /* --- * * E d i t C o n s t r a i n t * --- */ /** * A unary input constraint used to mark a variable that the client * wishes to change. */ function EditConstraint(v, str) { EditConstraint.superConstructor.call(this, v, str); } EditConstraint.inheritsFrom(UnaryConstraint); /** * Edits indicate that a variable is to be changed by imperative code. */ EditConstraint.prototype.isInput = function () { return true; }; EditConstraint.prototype.execute = function () { // Edit constraints do nothing }; /* --- * * B i n a r y C o n s t r a i n t * --- */ var Direction = new Object(); Direction.NONE = 0; Direction.FORWARD = 1; Direction.BACKWARD = -1; /** * Abstract superclass for constraints having two possible output * variables. */ function BinaryConstraint(var1, var2, strength) { BinaryConstraint.superConstructor.call(this, strength); this.v1 = var1; this.v2 = var2; this.direction = Direction.NONE; this.addConstraint(); } BinaryConstraint.inheritsFrom(Constraint); /** * Decides if this constraint can be satisfied and which way it * should flow based on the relative strength of the variables related, * and record that decision. */ BinaryConstraint.prototype.chooseMethod = function (mark) { if (this.v1.mark == mark) { this.direction = this.v2.mark != mark && Strength.stronger(this.strength, this.v2.walkStrength) ? Direction.FORWARD : Direction.NONE; } if (this.v2.mark == mark) { this.direction = this.v1.mark != mark && Strength.stronger(this.strength, this.v1.walkStrength) ? Direction.BACKWARD : Direction.NONE; } if (Strength.weaker(this.v1.walkStrength, this.v2.walkStrength)) { this.direction = Strength.stronger(this.strength, this.v1.walkStrength) ? Direction.BACKWARD : Direction.NONE; } else { this.direction = Strength.stronger(this.strength, this.v2.walkStrength) ? Direction.FORWARD : Direction.BACKWARD; } }; /** * Add this constraint to the constraint graph */ BinaryConstraint.prototype.addToGraph = function () { this.v1.addConstraint(this); this.v2.addConstraint(this); this.direction = Direction.NONE; }; /** * Answer true if this constraint is satisfied in the current solution. */ BinaryConstraint.prototype.isSatisfied = function () { return this.direction != Direction.NONE; }; /** * Mark the input variable with the given mark. */ BinaryConstraint.prototype.markInputs = function (mark) { this.input().mark = mark; }; /** * Returns the current input variable */ BinaryConstraint.prototype.input = function () { return this.direction == Direction.FORWARD ? this.v1 : this.v2; }; /** * Returns the current output variable */ BinaryConstraint.prototype.output = function () { return this.direction == Direction.FORWARD ? this.v2 : this.v1; }; /** * Calculate the walkabout strength, the stay flag, and, if it is * 'stay', the value for the current output of this * constraint. Assume this constraint is satisfied. */ BinaryConstraint.prototype.recalculate = function () { var ihn = this.input(), out = this.output(); out.walkStrength = Strength.weakestOf(this.strength, ihn.walkStrength); out.stay = ihn.stay; if (out.stay) this.execute(); }; /** * Record the fact that this constraint is unsatisfied. */ BinaryConstraint.prototype.markUnsatisfied = function () { this.direction = Direction.NONE; }; BinaryConstraint.prototype.inputsKnown = function (mark) { var i = this.input(); return i.mark == mark || i.stay || i.determinedBy == null; }; BinaryConstraint.prototype.removeFromGraph = function () { if (this.v1 != null) this.v1.removeConstraint(this); if (this.v2 != null) this.v2.removeConstraint(this); this.direction = Direction.NONE; }; /* --- * * S c a l e C o n s t r a i n t * --- */ /** * Relates two variables by the linear scaling relationship: "v2 = * (v1 * scale) + offset". Either v1 or v2 may be changed to maintain * this relationship but the scale factor and offset are considered * read-only. */ function ScaleConstraint(src, scale, offset, dest, strength) { this.direction = Direction.NONE; this.scale = scale; this.offset = offset; ScaleConstraint.superConstructor.call(this, src, dest, strength); } ScaleConstraint.inheritsFrom(BinaryConstraint); /** * Adds this constraint to the constraint graph. */ ScaleConstraint.prototype.addToGraph = function () { ScaleConstraint.superConstructor.prototype.addToGraph.call(this); this.scale.addConstraint(this); this.offset.addConstraint(this); }; ScaleConstraint.prototype.removeFromGraph = function () { ScaleConstraint.superConstructor.prototype.removeFromGraph.call(this); if (this.scale != null) this.scale.removeConstraint(this); if (this.offset != null) this.offset.removeConstraint(this); }; ScaleConstraint.prototype.markInputs = function (mark) { ScaleConstraint.superConstructor.prototype.markInputs.call(this, mark); this.scale.mark = this.offset.mark = mark; }; /** * Enforce this constraint. Assume that it is satisfied. */ ScaleConstraint.prototype.execute = function () { if (this.direction == Direction.FORWARD) { this.v2.value = this.v1.value * this.scale.value + this.offset.value; } else { this.v1.value = (this.v2.value - this.offset.value) / this.scale.value; } }; /** * Calculate the walkabout strength, the stay flag, and, if it is * 'stay', the value for the current output of this constraint. Assume * this constraint is satisfied. */ ScaleConstraint.prototype.recalculate = function () { var ihn = this.input(), out = this.output(); out.walkStrength = Strength.weakestOf(this.strength, ihn.walkStrength); out.stay = ihn.stay && this.scale.stay && this.offset.stay; if (out.stay) this.execute(); }; /* --- * * E q u a l i t y C o n s t r a i n t * --- */ /** * Constrains two variables to have the same value. */ function EqualityConstraint(var1, var2, strength) { EqualityConstraint.superConstructor.call(this, var1, var2, strength); } EqualityConstraint.inheritsFrom(BinaryConstraint); /** * Enforce this constraint. Assume that it is satisfied. */ EqualityConstraint.prototype.execute = function () { this.output().value = this.input().value; }; /* --- * * V a r i a b l e * --- */ /** * A constrained variable. In addition to its value, it maintain the * structure of the constraint graph, the current dataflow graph, and * various parameters of interest to the DeltaBlue incremental * constraint solver. **/ function Variable(name, initialValue) { this.value = initialValue || 0; this.constraints = new OrderedCollection(); this.determinedBy = null; this.mark = 0; this.walkStrength = Strength.WEAKEST; this.stay = true; this.name = name; } /** * Add the given constraint to the set of all constraints that refer * this variable. */ Variable.prototype.addConstraint = function (c) { this.constraints.add(c); }; /** * Removes all traces of c from this variable. */ Variable.prototype.removeConstraint = function (c) { this.constraints.remove(c); if (this.determinedBy == c) this.determinedBy = null; }; /* --- * * P l a n n e r * --- */ /** * The DeltaBlue planner */ function Planner() { this.currentMark = 0; } /** * Attempt to satisfy the given constraint and, if successful, * incrementally update the dataflow graph. Details: If satifying * the constraint is successful, it may override a weaker constraint * on its output. The algorithm attempts to resatisfy that * constraint using some other method. This process is repeated * until either a) it reaches a variable that was not previously * determined by any constraint or b) it reaches a constraint that * is too weak to be satisfied using any of its methods. The * variables of constraints that have been processed are marked with * a unique mark value so that we know where we've been. This allows * the algorithm to avoid getting into an infinite loop even if the * constraint graph has an inadvertent cycle. */ Planner.prototype.incrementalAdd = function (c) { var mark = this.newMark(); var overridden = c.satisfy(mark); while (overridden != null) overridden = overridden.satisfy(mark); }; /** * Entry point for retracting a constraint. Remove the given * constraint and incrementally update the dataflow graph. * Details: Retracting the given constraint may allow some currently * unsatisfiable downstream constraint to be satisfied. We therefore collect * a list of unsatisfied downstream constraints and attempt to * satisfy each one in turn. This list is traversed by constraint * strength, strongest first, as a heuristic for avoiding * unnecessarily adding and then overriding weak constraints. * Assume: c is satisfied. */ Planner.prototype.incrementalRemove = function (c) { var out = c.output(); c.markUnsatisfied(); c.removeFromGraph(); var unsatisfied = this.removePropagateFrom(out); var strength = Strength.REQUIRED; do { for (var i = 0; i < unsatisfied.size(); i++) { var u = unsatisfied.at(i); if (u.strength == strength) this.incrementalAdd(u); } strength = strength.nextWeaker(); } while (strength != Strength.WEAKEST); }; /** * Select a previously unused mark value. */ Planner.prototype.newMark = function () { return ++this.currentMark; }; /** * Extract a plan for resatisfaction starting from the given source * constraints, usually a set of input constraints. This method * assumes that stay optimization is desired; the plan will contain * only constraints whose output variables are not stay. Constraints * that do no computation, such as stay and edit constraints, are * not included in the plan. * Details: The outputs of a constraint are marked when it is added * to the plan under construction. A constraint may be appended to * the plan when all its input variables are known. A variable is * known if either a) the variable is marked (indicating that has * been computed by a constraint appearing earlier in the plan), b) * the variable is 'stay' (i.e. it is a constant at plan execution * time), or c) the variable is not determined by any * constraint. The last provision is for past states of history * variables, which are not stay but which are also not computed by * any constraint. * Assume: sources are all satisfied. */ Planner.prototype.makePlan = function (sources) { var mark = this.newMark(); var plan = new Plan(); var todo = sources; while (todo.size() > 0) { var c = todo.removeFirst(); if (c.output().mark != mark && c.inputsKnown(mark)) { plan.addConstraint(c); c.output().mark = mark; this.addConstraintsConsumingTo(c.output(), todo); } } return plan; }; /** * Extract a plan for resatisfying starting from the output of the * given constraints, usually a set of input constraints. */ Planner.prototype.extractPlanFromConstraints = function (constraints) { var sources = new OrderedCollection(); for (var i = 0; i < constraints.size(); i++) { var c = constraints.at(i); if (c.isInput() && c.isSatisfied()) // not in plan already and eligible for inclusion sources.add(c); } return this.makePlan(sources); }; /** * Recompute the walkabout strengths and stay flags of all variables * downstream of the given constraint and recompute the actual * values of all variables whose stay flag is true. If a cycle is * detected, remove the given constraint and answer * false. Otherwise, answer true. * Details: Cycles are detected when a marked variable is * encountered downstream of the given constraint. The sender is * assumed to have marked the inputs of the given constraint with * the given mark. Thus, encountering a marked node downstream of * the output constraint means that there is a path from the * constraint's output to one of its inputs. */ Planner.prototype.addPropagate = function (c, mark) { var todo = new OrderedCollection(); todo.add(c); while (todo.size() > 0) { var d = todo.removeFirst(); if (d.output().mark == mark) { this.incrementalRemove(c); return false; } d.recalculate(); this.addConstraintsConsumingTo(d.output(), todo); } return true; }; /** * Update the walkabout strengths and stay flags of all variables * downstream of the given constraint. Answer a collection of * unsatisfied constraints sorted in order of decreasing strength. */ Planner.prototype.removePropagateFrom = function (out) { out.determinedBy = null; out.walkStrength = Strength.WEAKEST; out.stay = true; var unsatisfied = new OrderedCollection(); var todo = new OrderedCollection(); todo.add(out); while (todo.size() > 0) { var v = todo.removeFirst(); for (var i = 0; i < v.constraints.size(); i++) { var c = v.constraints.at(i); if (!c.isSatisfied()) unsatisfied.add(c); } var determining = v.determinedBy; for (var i = 0; i < v.constraints.size(); i++) { var next = v.constraints.at(i); if (next != determining && next.isSatisfied()) { next.recalculate(); todo.add(next.output()); } } } return unsatisfied; }; Planner.prototype.addConstraintsConsumingTo = function (v, coll) { var determining = v.determinedBy; var cc = v.constraints; for (var i = 0; i < cc.size(); i++) { var c = cc.at(i); if (c != determining && c.isSatisfied()) coll.add(c); } }; /* --- * * P l a n * --- */ /** * A Plan is an ordered list of constraints to be executed in sequence * to resatisfy all currently satisfiable constraints in the face of * one or more changing inputs. */ function Plan() { this.v = new OrderedCollection(); } Plan.prototype.addConstraint = function (c) { this.v.add(c); }; Plan.prototype.size = function () { return this.v.size(); }; Plan.prototype.constraintAt = function (index) { return this.v.at(index); }; Plan.prototype.execute = function () { for (var i = 0; i < this.size(); i++) { var c = this.constraintAt(i); c.execute(); } }; /* --- * * M a i n * --- */ /** * This is the standard DeltaBlue benchmark. A long chain of equality * constraints is constructed with a stay constraint on one end. An * edit constraint is then added to the opposite end and the time is * measured for adding and removing this constraint, and extracting * and executing a constraint satisfaction plan. There are two cases. * In case 1, the added constraint is stronger than the stay * constraint and values must propagate down the entire length of the * chain. In case 2, the added constraint is weaker than the stay * constraint so it cannot be accomodated. The cost in this case is, * of course, very low. Typical situations lie somewhere between these * two extremes. */ function chainTest(n) { planner = new Planner(); var prev = null, first = null, last = null; // Build chain of n equality constraints for (var i = 0; i <= n; i++) { var name = "v" + i; var v = new Variable(name); if (prev != null) new EqualityConstraint(prev, v, Strength.REQUIRED); if (i == 0) first = v; if (i == n) last = v; prev = v; } new StayConstraint(last, Strength.STRONG_DEFAULT); var edit = new EditConstraint(first, Strength.PREFERRED); var edits = new OrderedCollection(); edits.add(edit); var plan = planner.extractPlanFromConstraints(edits); for (var i = 0; i < 100; i++) { first.value = i; plan.execute(); if (last.value != i) alert("Chain test failed."); } } /** * This test constructs a two sets of variables related to each * other by a simple linear transformation (scale and offset). The * time is measured to change a variable on either side of the * mapping and to change the scale and offset factors. */ function projectionTest(n) { planner = new Planner(); var scale = new Variable("scale", 10); var offset = new Variable("offset", 1000); var src = null, dst = null; var dests = new OrderedCollection(); for (var i = 0; i < n; i++) { src = new Variable("src" + i, i); dst = new Variable("dst" + i, i); dests.add(dst); new StayConstraint(src, Strength.NORMAL); new ScaleConstraint(src, scale, offset, dst, Strength.REQUIRED); } change(src, 17); if (dst.value != 1170) alert("Projection 1 failed"); change(dst, 1050); if (src.value != 5) alert("Projection 2 failed"); change(scale, 5); for (var i = 0; i < n - 1; i++) { if (dests.at(i).value != i * 5 + 1000) alert("Projection 3 failed"); } change(offset, 2000); for (var i = 0; i < n - 1; i++) { if (dests.at(i).value != i * 5 + 2000) alert("Projection 4 failed"); } } function change(v, newValue) { var edit = new EditConstraint(v, Strength.PREFERRED); var edits = new OrderedCollection(); edits.add(edit); var plan = planner.extractPlanFromConstraints(edits); for (var i = 0; i < 10; i++) { v.value = newValue; plan.execute(); } edit.destroyConstraint(); } // Global variable holding the current planner. var planner = null; function deltaBlue() { chainTest(100); projectionTest(100); } ================================================ FILE: benchmarks/v8-v7/earley-boyer.js ================================================ // This file is automatically generated by scheme2js, except for the // benchmark harness code at the beginning and end of the file. var EarleyBoyer = new BenchmarkSuite("EarleyBoyer", 666463, [ new Benchmark("Earley", function () { BgL_earleyzd2benchmarkzd2(); }), new Benchmark("Boyer", function () { BgL_nboyerzd2benchmarkzd2(); }), ]); /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /************* GENERATED FILE - DO NOT EDIT *************/ /* * To use write/prints/... the default-output port has to be set first. * Simply setting SC_DEFAULT_OUT and SC_ERROR_OUT to the desired values * should do the trick. * In the following example the std-out and error-port are redirected to * a DIV. function initRuntime() { function escapeHTML(s) { var tmp = s; tmp = tmp.replace(/&/g, "&"); tmp = tmp.replace(//g, ">"); tmp = tmp.replace(/ /g, " "); tmp = tmp.replace(/\n/g, "
"); tmp = tmp.replace(/\t/g, "    "); return tmp; } document.write("
"); SC_DEFAULT_OUT = new sc_GenericOutputPort( function(s) { var stdout = document.getElementById('stdout'); stdout.innerHTML = stdout.innerHTML + escapeHTML(s); }); SC_ERROR_OUT = SC_DEFAULT_OUT; } */ function sc_print_debug() { sc_print.apply(null, arguments); } /*** META ((export *js*)) */ var sc_JS_GLOBALS = this; var __sc_LINE = -1; var __sc_FILE = ""; /*** META ((export #t)) */ function sc_alert() { var len = arguments.length; var s = ""; var i; for (i = 0; i < len; i++) { s += sc_toDisplayString(arguments[i]); } return alert(s); } /*** META ((export #t)) */ function sc_typeof(x) { return typeof x; } /*** META ((export #t)) */ function sc_error() { var a = [sc_jsstring2symbol("*error*")]; for (var i = 0; i < arguments.length; i++) { a[i + 1] = arguments[i]; } throw a; } /*** META ((export #t) (peephole (prefix "throw "))) */ function sc_raise(obj) { throw obj; } /*** META ((export with-handler-lambda)) */ function sc_withHandlerLambda(handler, body) { try { return body(); } catch (e) { if (!e._internalException) return handler(e); else throw e; } } var sc_properties = new Object(); /*** META ((export #t)) */ function sc_putpropBang(sym, key, val) { var ht = sc_properties[sym]; if (!ht) { ht = new Object(); sc_properties[sym] = ht; } ht[key] = val; } /*** META ((export #t)) */ function sc_getprop(sym, key) { var ht = sc_properties[sym]; if (ht) { if (key in ht) return ht[key]; else return false; } else return false; } /*** META ((export #t)) */ function sc_rempropBang(sym, key) { var ht = sc_properties[sym]; if (ht) delete ht[key]; } /*** META ((export #t)) */ function sc_any2String(o) { return jsstring2string(sc_toDisplayString(o)); } /*** META ((export #t) (peephole (infix 2 2 "===")) (type bool)) */ function sc_isEqv(o1, o2) { return o1 === o2; } /*** META ((export #t) (peephole (infix 2 2 "===")) (type bool)) */ function sc_isEq(o1, o2) { return o1 === o2; } /*** META ((export #t) (type bool)) */ function sc_isNumber(n) { return typeof n === "number"; } /*** META ((export #t) (type bool)) */ function sc_isComplex(n) { return sc_isNumber(n); } /*** META ((export #t) (type bool)) */ function sc_isReal(n) { return sc_isNumber(n); } /*** META ((export #t) (type bool)) */ function sc_isRational(n) { return sc_isReal(n); } /*** META ((export #t) (type bool)) */ function sc_isInteger(n) { return parseInt(n) === n; } /*** META ((export #t) (type bool) (peephole (postfix ", false"))) */ // we don't have exact numbers... function sc_isExact(n) { return false; } /*** META ((export #t) (peephole (postfix ", true")) (type bool)) */ function sc_isInexact(n) { return true; } /*** META ((export = =fx =fl) (type bool) (peephole (infix 2 2 "==="))) */ function sc_equal(x) { for (var i = 1; i < arguments.length; i++) if (x !== arguments[i]) return false; return true; } /*** META ((export < = arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export > >fx >fl) (type bool) (peephole (infix 2 2 ">"))) */ function sc_greater(x, y) { for (var i = 1; i < arguments.length; i++) { if (x <= arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export <= <=fx <=fl) (type bool) (peephole (infix 2 2 "<="))) */ function sc_lessEqual(x, y) { for (var i = 1; i < arguments.length; i++) { if (x > arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export >= >=fl >=fx) (type bool) (peephole (infix 2 2 ">="))) */ function sc_greaterEqual(x, y) { for (var i = 1; i < arguments.length; i++) { if (x < arguments[i]) return false; x = arguments[i]; } return true; } /*** META ((export #t) (type bool) (peephole (postfix "=== 0"))) */ function sc_isZero(x) { return x === 0; } /*** META ((export #t) (type bool) (peephole (postfix "> 0"))) */ function sc_isPositive(x) { return x > 0; } /*** META ((export #t) (type bool) (peephole (postfix "< 0"))) */ function sc_isNegative(x) { return x < 0; } /*** META ((export #t) (type bool) (peephole (postfix "%2===1"))) */ function sc_isOdd(x) { return x % 2 === 1; } /*** META ((export #t) (type bool) (peephole (postfix "%2===0"))) */ function sc_isEven(x) { return x % 2 === 0; } /*** META ((export #t)) */ var sc_max = Math.max; /*** META ((export #t)) */ var sc_min = Math.min; /*** META ((export + +fx +fl) (peephole (infix 0 #f "+" "0"))) */ function sc_plus() { var sum = 0; for (var i = 0; i < arguments.length; i++) sum += arguments[i]; return sum; } /*** META ((export * *fx *fl) (peephole (infix 0 #f "*" "1"))) */ function sc_multi() { var product = 1; for (var i = 0; i < arguments.length; i++) product *= arguments[i]; return product; } /*** META ((export - -fx -fl) (peephole (minus))) */ function sc_minus(x) { if (arguments.length === 1) return -x; else { var res = x; for (var i = 1; i < arguments.length; i++) res -= arguments[i]; return res; } } /*** META ((export / /fl) (peephole (div))) */ function sc_div(x) { if (arguments.length === 1) return 1 / x; else { var res = x; for (var i = 1; i < arguments.length; i++) res /= arguments[i]; return res; } } /*** META ((export #t)) */ var sc_abs = Math.abs; /*** META ((export quotient /fx) (peephole (hole 2 "parseInt(" x "/" y ")"))) */ function sc_quotient(x, y) { return parseInt(x / y); } /*** META ((export #t) (peephole (infix 2 2 "%"))) */ function sc_remainder(x, y) { return x % y; } /*** META ((export #t) (peephole (modulo))) */ function sc_modulo(x, y) { var remainder = x % y; // if they don't have the same sign if (remainder * y < 0) return remainder + y; else return remainder; } function sc_euclid_gcd(a, b) { var temp; if (a === 0) return b; if (b === 0) return a; if (a < 0) { a = -a; } if (b < 0) { b = -b; } if (b > a) { temp = a; a = b; b = temp; } while (true) { a %= b; if (a === 0) { return b; } b %= a; if (b === 0) { return a; } } return b; } /*** META ((export #t)) */ function sc_gcd() { var gcd = 0; for (var i = 0; i < arguments.length; i++) gcd = sc_euclid_gcd(gcd, arguments[i]); return gcd; } /*** META ((export #t)) */ function sc_lcm() { var lcm = 1; for (var i = 0; i < arguments.length; i++) { var f = Math.round(arguments[i] / sc_euclid_gcd(arguments[i], lcm)); lcm *= Math.abs(f); } return lcm; } // LIMITATION: numerator and denominator don't make sense in floating point world. //var SC_MAX_DECIMALS = 1000000 // // function sc_numerator(x) { // var rounded = Math.round(x * SC_MAX_DECIMALS); // return Math.round(rounded / sc_euclid_gcd(rounded, SC_MAX_DECIMALS)); // } // function sc_denominator(x) { // var rounded = Math.round(x * SC_MAX_DECIMALS); // return Math.round(SC_MAX_DECIMALS / sc_euclid_gcd(rounded, SC_MAX_DECIMALS)); // } /*** META ((export #t)) */ var sc_floor = Math.floor; /*** META ((export #t)) */ var sc_ceiling = Math.ceil; /*** META ((export #t)) */ var sc_truncate = parseInt; /*** META ((export #t)) */ var sc_round = Math.round; // LIMITATION: sc_rationalize doesn't make sense in a floating point world. /*** META ((export #t)) */ var sc_exp = Math.exp; /*** META ((export #t)) */ var sc_log = Math.log; /*** META ((export #t)) */ var sc_sin = Math.sin; /*** META ((export #t)) */ var sc_cos = Math.cos; /*** META ((export #t)) */ var sc_tan = Math.tan; /*** META ((export #t)) */ var sc_asin = Math.asin; /*** META ((export #t)) */ var sc_acos = Math.acos; /*** META ((export #t)) */ var sc_atan = Math.atan; /*** META ((export #t)) */ var sc_sqrt = Math.sqrt; /*** META ((export #t)) */ var sc_expt = Math.pow; // LIMITATION: we don't have complex numbers. // LIMITATION: the following functions are hence not implemented. // LIMITATION: make-rectangular, make-polar, real-part, imag-part, magnitude, angle // LIMITATION: 2 argument atan /*** META ((export #t) (peephole (id))) */ function sc_exact2inexact(x) { return x; } /*** META ((export #t) (peephole (id))) */ function sc_inexact2exact(x) { return x; } function sc_number2jsstring(x, radix) { if (radix) return x.toString(radix); else return x.toString(); } function sc_jsstring2number(s, radix) { if (s === "") return false; if (radix) { var t = parseInt(s, radix); if (!t && t !== 0) return false; // verify that each char is in range. (parseInt ignores leading // white and trailing chars) var allowedChars = "01234567890abcdefghijklmnopqrstuvwxyz".substring( 0, radix + 1 ); if (new RegExp("^[" + allowedChars + "]*$", "i").test(s)) return t; else return false; } else { var t = +s; // does not ignore trailing chars. if (!t && t !== 0) return false; // simply verify that first char is not whitespace. var c = s.charAt(0); // if +c is 0, but the char is not "0", then we have a whitespace. if (+c === 0 && c !== "0") return false; return t; } } /*** META ((export #t) (type bool) (peephole (not))) */ function sc_not(b) { return b === false; } /*** META ((export #t) (type bool)) */ function sc_isBoolean(b) { return b === true || b === false; } function sc_Pair(car, cdr) { this.car = car; this.cdr = cdr; } sc_Pair.prototype.toString = function () { return sc_toDisplayString(this); }; sc_Pair.prototype.sc_toWriteOrDisplayString = function (writeOrDisplay) { var current = this; var res = "("; while (true) { res += writeOrDisplay(current.car); if (sc_isPair(current.cdr)) { res += " "; current = current.cdr; } else if (current.cdr !== null) { res += " . " + writeOrDisplay(current.cdr); break; } // current.cdr == null else break; } res += ")"; return res; }; sc_Pair.prototype.sc_toDisplayString = function () { return this.sc_toWriteOrDisplayString(sc_toDisplayString); }; sc_Pair.prototype.sc_toWriteString = function () { return this.sc_toWriteOrDisplayString(sc_toWriteString); }; // sc_Pair.prototype.sc_toWriteCircleString in IO.js /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_Pair"))) */ function sc_isPair(p) { return p instanceof sc_Pair; } function sc_isPairEqual(p1, p2, comp) { return comp(p1.car, p2.car) && comp(p1.cdr, p2.cdr); } /*** META ((export #t) (peephole (hole 2 "new sc_Pair(" car ", " cdr ")"))) */ function sc_cons(car, cdr) { return new sc_Pair(car, cdr); } /*** META ((export cons*)) */ function sc_consStar() { var res = arguments[arguments.length - 1]; for (var i = arguments.length - 2; i >= 0; i--) res = new sc_Pair(arguments[i], res); return res; } /*** META ((export #t) (peephole (postfix ".car"))) */ function sc_car(p) { return p.car; } /*** META ((export #t) (peephole (postfix ".cdr"))) */ function sc_cdr(p) { return p.cdr; } /*** META ((export #t) (peephole (hole 2 p ".car = " val))) */ function sc_setCarBang(p, val) { p.car = val; } /*** META ((export #t) (peephole (hole 2 p ".cdr = " val))) */ function sc_setCdrBang(p, val) { p.cdr = val; } /*** META ((export #t) (peephole (postfix ".car.car"))) */ function sc_caar(p) { return p.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.car"))) */ function sc_cadr(p) { return p.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.cdr"))) */ function sc_cdar(p) { return p.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr"))) */ function sc_cddr(p) { return p.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".car.car.car"))) */ function sc_caaar(p) { return p.car.car.car; } /*** META ((export #t) (peephole (postfix ".car.cdr.car"))) */ function sc_cadar(p) { return p.car.cdr.car; } /*** META ((export #t) (peephole (postfix ".cdr.car.car"))) */ function sc_caadr(p) { return p.cdr.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.car"))) */ function sc_caddr(p) { return p.cdr.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.car.cdr"))) */ function sc_cdaar(p) { return p.car.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.car.cdr"))) */ function sc_cdadr(p) { return p.cdr.car.cdr; } /*** META ((export #t) (peephole (postfix ".car.cdr.cdr"))) */ function sc_cddar(p) { return p.car.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.cdr"))) */ function sc_cdddr(p) { return p.cdr.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".car.car.car.car"))) */ function sc_caaaar(p) { return p.car.car.car.car; } /*** META ((export #t) (peephole (postfix ".car.cdr.car.car"))) */ function sc_caadar(p) { return p.car.cdr.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.car.car.car"))) */ function sc_caaadr(p) { return p.cdr.car.car.car; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.car.car"))) */ function sc_caaddr(p) { return p.cdr.cdr.car.car; } /*** META ((export #t) (peephole (postfix ".car.car.car.cdr"))) */ function sc_cdaaar(p) { return p.car.car.car.cdr; } /*** META ((export #t) (peephole (postfix ".car.cdr.car.cdr"))) */ function sc_cdadar(p) { return p.car.cdr.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.car.car.cdr"))) */ function sc_cdaadr(p) { return p.cdr.car.car.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.car.cdr"))) */ function sc_cdaddr(p) { return p.cdr.cdr.car.cdr; } /*** META ((export #t) (peephole (postfix ".car.car.cdr.car"))) */ function sc_cadaar(p) { return p.car.car.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.cdr.cdr.car"))) */ function sc_caddar(p) { return p.car.cdr.cdr.car; } /*** META ((export #t) (peephole (postfix ".cdr.car.cdr.car"))) */ function sc_cadadr(p) { return p.cdr.car.cdr.car; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.cdr.car"))) */ function sc_cadddr(p) { return p.cdr.cdr.cdr.car; } /*** META ((export #t) (peephole (postfix ".car.car.cdr.cdr"))) */ function sc_cddaar(p) { return p.car.car.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".car.cdr.cdr.cdr"))) */ function sc_cdddar(p) { return p.car.cdr.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.car.cdr.cdr"))) */ function sc_cddadr(p) { return p.cdr.car.cdr.cdr; } /*** META ((export #t) (peephole (postfix ".cdr.cdr.cdr.cdr"))) */ function sc_cddddr(p) { return p.cdr.cdr.cdr.cdr; } /*** META ((export #t)) */ function sc_lastPair(l) { if (!sc_isPair(l)) sc_error("sc_lastPair: pair expected"); var res = l; var cdr = l.cdr; while (sc_isPair(cdr)) { res = cdr; cdr = res.cdr; } return res; } /*** META ((export #t) (type bool) (peephole (postfix " === null"))) */ function sc_isNull(o) { return o === null; } /*** META ((export #t) (type bool)) */ function sc_isList(o) { var rabbit; var turtle; var rabbit = o; var turtle = o; while (true) { if (rabbit === null || (rabbit instanceof sc_Pair && rabbit.cdr === null)) return true; // end of list else if (rabbit instanceof sc_Pair && rabbit.cdr instanceof sc_Pair) { rabbit = rabbit.cdr.cdr; turtle = turtle.cdr; if (rabbit === turtle) return false; // cycle } else return false; // not pair } } /*** META ((export #t)) */ function sc_list() { var res = null; var a = arguments; for (var i = a.length - 1; i >= 0; i--) res = new sc_Pair(a[i], res); return res; } /*** META ((export #t)) */ function sc_iota(num, init) { var res = null; if (!init) init = 0; for (var i = num - 1; i >= 0; i--) res = new sc_Pair(i + init, res); return res; } /*** META ((export #t)) */ function sc_makeList(nbEls, fill) { var res = null; for (var i = 0; i < nbEls; i++) res = new sc_Pair(fill, res); return res; } /*** META ((export #t)) */ function sc_length(l) { var res = 0; while (l !== null) { res++; l = l.cdr; } return res; } /*** META ((export #t)) */ function sc_remq(o, l) { var dummy = { cdr: null }; var tail = dummy; while (l !== null) { if (l.car !== o) { tail.cdr = sc_cons(l.car, null); tail = tail.cdr; } l = l.cdr; } return dummy.cdr; } /*** META ((export #t)) */ function sc_remqBang(o, l) { var dummy = { cdr: null }; var tail = dummy; var needsAssig = true; while (l !== null) { if (l.car === o) { needsAssig = true; } else { if (needsAssig) { tail.cdr = l; needsAssig = false; } tail = l; } l = l.cdr; } tail.cdr = null; return dummy.cdr; } /*** META ((export #t)) */ function sc_delete(o, l) { var dummy = { cdr: null }; var tail = dummy; while (l !== null) { if (!sc_isEqual(l.car, o)) { tail.cdr = sc_cons(l.car, null); tail = tail.cdr; } l = l.cdr; } return dummy.cdr; } /*** META ((export #t)) */ function sc_deleteBang(o, l) { var dummy = { cdr: null }; var tail = dummy; var needsAssig = true; while (l !== null) { if (sc_isEqual(l.car, o)) { needsAssig = true; } else { if (needsAssig) { tail.cdr = l; needsAssig = false; } tail = l; } l = l.cdr; } tail.cdr = null; return dummy.cdr; } function sc_reverseAppendBang(l1, l2) { var res = l2; while (l1 !== null) { var tmp = res; res = l1; l1 = l1.cdr; res.cdr = tmp; } return res; } function sc_dualAppend(l1, l2) { if (l1 === null) return l2; if (l2 === null) return l1; var rev = sc_reverse(l1); return sc_reverseAppendBang(rev, l2); } /*** META ((export #t)) */ function sc_append() { if (arguments.length === 0) return null; var res = arguments[arguments.length - 1]; for (var i = arguments.length - 2; i >= 0; i--) res = sc_dualAppend(arguments[i], res); return res; } function sc_dualAppendBang(l1, l2) { if (l1 === null) return l2; if (l2 === null) return l1; var tmp = l1; while (tmp.cdr !== null) tmp = tmp.cdr; tmp.cdr = l2; return l1; } /*** META ((export #t)) */ function sc_appendBang() { var res = null; for (var i = 0; i < arguments.length; i++) res = sc_dualAppendBang(res, arguments[i]); return res; } /*** META ((export #t)) */ function sc_reverse(l1) { var res = null; while (l1 !== null) { res = sc_cons(l1.car, res); l1 = l1.cdr; } return res; } /*** META ((export #t)) */ function sc_reverseBang(l) { return sc_reverseAppendBang(l, null); } /*** META ((export #t)) */ function sc_listTail(l, k) { var res = l; for (var i = 0; i < k; i++) { res = res.cdr; } return res; } /*** META ((export #t)) */ function sc_listRef(l, k) { return sc_listTail(l, k).car; } /* // unoptimized generic versions function sc_memX(o, l, comp) { while (l != null) { if (comp(l.car, o)) return l; l = l.cdr; } return false; } function sc_memq(o, l) { return sc_memX(o, l, sc_isEq); } function sc_memv(o, l) { return sc_memX(o, l, sc_isEqv); } function sc_member(o, l) { return sc_memX(o, l, sc_isEqual); } */ /* optimized versions */ /*** META ((export #t)) */ function sc_memq(o, l) { while (l !== null) { if (l.car === o) return l; l = l.cdr; } return false; } /*** META ((export #t)) */ function sc_memv(o, l) { while (l !== null) { if (l.car === o) return l; l = l.cdr; } return false; } /*** META ((export #t)) */ function sc_member(o, l) { while (l !== null) { if (sc_isEqual(l.car, o)) return l; l = l.cdr; } return false; } /* // generic unoptimized versions function sc_assX(o, al, comp) { while (al != null) { if (comp(al.car.car, o)) return al.car; al = al.cdr; } return false; } function sc_assq(o, al) { return sc_assX(o, al, sc_isEq); } function sc_assv(o, al) { return sc_assX(o, al, sc_isEqv); } function sc_assoc(o, al) { return sc_assX(o, al, sc_isEqual); } */ // optimized versions /*** META ((export #t)) */ function sc_assq(o, al) { while (al !== null) { if (al.car.car === o) return al.car; al = al.cdr; } return false; } /*** META ((export #t)) */ function sc_assv(o, al) { while (al !== null) { if (al.car.car === o) return al.car; al = al.cdr; } return false; } /*** META ((export #t)) */ function sc_assoc(o, al) { while (al !== null) { if (sc_isEqual(al.car.car, o)) return al.car; al = al.cdr; } return false; } /* can be used for mutable strings and characters */ function sc_isCharStringEqual(cs1, cs2) { return cs1.val === cs2.val; } function sc_isCharStringLess(cs1, cs2) { return cs1.val < cs2.val; } function sc_isCharStringGreater(cs1, cs2) { return cs1.val > cs2.val; } function sc_isCharStringLessEqual(cs1, cs2) { return cs1.val <= cs2.val; } function sc_isCharStringGreaterEqual(cs1, cs2) { return cs1.val >= cs2.val; } function sc_isCharStringCIEqual(cs1, cs2) { return cs1.val.toLowerCase() === cs2.val.toLowerCase(); } function sc_isCharStringCILess(cs1, cs2) { return cs1.val.toLowerCase() < cs2.val.toLowerCase(); } function sc_isCharStringCIGreater(cs1, cs2) { return cs1.val.toLowerCase() > cs2.val.toLowerCase(); } function sc_isCharStringCILessEqual(cs1, cs2) { return cs1.val.toLowerCase() <= cs2.val.toLowerCase(); } function sc_isCharStringCIGreaterEqual(cs1, cs2) { return cs1.val.toLowerCase() >= cs2.val.toLowerCase(); } function sc_Char(c) { var cached = sc_Char.lazy[c]; if (cached) return cached; this.val = c; sc_Char.lazy[c] = this; // add return, so FF does not complain. return undefined; } sc_Char.lazy = new Object(); // thanks to Eric sc_Char.char2readable = { "\000": "#\\null", "\007": "#\\bell", "\010": "#\\backspace", "\011": "#\\tab", "\012": "#\\newline", "\014": "#\\page", "\015": "#\\return", "\033": "#\\escape", "\040": "#\\space", "\177": "#\\delete", /* poeticless names */ "\001": "#\\soh", "\002": "#\\stx", "\003": "#\\etx", "\004": "#\\eot", "\005": "#\\enq", "\006": "#\\ack", "\013": "#\\vt", "\016": "#\\so", "\017": "#\\si", "\020": "#\\dle", "\021": "#\\dc1", "\022": "#\\dc2", "\023": "#\\dc3", "\024": "#\\dc4", "\025": "#\\nak", "\026": "#\\syn", "\027": "#\\etb", "\030": "#\\can", "\031": "#\\em", "\032": "#\\sub", "\033": "#\\esc", "\034": "#\\fs", "\035": "#\\gs", "\036": "#\\rs", "\037": "#\\us", }; sc_Char.readable2char = { null: "\000", bell: "\007", backspace: "\010", tab: "\011", newline: "\012", page: "\014", return: "\015", escape: "\033", space: "\040", delete: "\000", soh: "\001", stx: "\002", etx: "\003", eot: "\004", enq: "\005", ack: "\006", bel: "\007", bs: "\010", ht: "\011", nl: "\012", vt: "\013", np: "\014", cr: "\015", so: "\016", si: "\017", dle: "\020", dc1: "\021", dc2: "\022", dc3: "\023", dc4: "\024", nak: "\025", syn: "\026", etb: "\027", can: "\030", em: "\031", sub: "\032", esc: "\033", fs: "\034", gs: "\035", rs: "\036", us: "\037", sp: "\040", del: "\177", }; sc_Char.prototype.toString = function () { return this.val; }; // sc_toDisplayString == toString sc_Char.prototype.sc_toWriteString = function () { var entry = sc_Char.char2readable[this.val]; if (entry) return entry; else return "#\\" + this.val; }; /*** META ((export #t) (type bool) (peephole (postfix "instanceof sc_Char"))) */ function sc_isChar(c) { return c instanceof sc_Char; } /*** META ((export char=?) (type bool) (peephole (hole 2 c1 ".val === " c2 ".val"))) */ var sc_isCharEqual = sc_isCharStringEqual; /*** META ((export char?) (type bool) (peephole (hole 2 c1 ".val > " c2 ".val"))) */ var sc_isCharGreater = sc_isCharStringGreater; /*** META ((export char<=?) (type bool) (peephole (hole 2 c1 ".val <= " c2 ".val"))) */ var sc_isCharLessEqual = sc_isCharStringLessEqual; /*** META ((export char>=?) (type bool) (peephole (hole 2 c1 ".val >= " c2 ".val"))) */ var sc_isCharGreaterEqual = sc_isCharStringGreaterEqual; /*** META ((export char-ci=?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() === " c2 ".val.toLowerCase()"))) */ var sc_isCharCIEqual = sc_isCharStringCIEqual; /*** META ((export char-ci?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() > " c2 ".val.toLowerCase()"))) */ var sc_isCharCIGreater = sc_isCharStringCIGreater; /*** META ((export char-ci<=?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() <= " c2 ".val.toLowerCase()"))) */ var sc_isCharCILessEqual = sc_isCharStringCILessEqual; /*** META ((export char-ci>=?) (type bool) (peephole (hole 2 c1 ".val.toLowerCase() >= " c2 ".val.toLowerCase()"))) */ var sc_isCharCIGreaterEqual = sc_isCharStringCIGreaterEqual; var SC_NUMBER_CLASS = "0123456789"; var SC_WHITESPACE_CLASS = " \r\n\t\f"; var SC_LOWER_CLASS = "abcdefghijklmnopqrstuvwxyz"; var SC_UPPER_CLASS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; function sc_isCharOfClass(c, cl) { return cl.indexOf(c) != -1; } /*** META ((export #t) (type bool)) */ function sc_isCharAlphabetic(c) { return ( sc_isCharOfClass(c.val, SC_LOWER_CLASS) || sc_isCharOfClass(c.val, SC_UPPER_CLASS) ); } /*** META ((export #t) (type bool) (peephole (hole 1 "SC_NUMBER_CLASS.indexOf(" c ".val) != -1"))) */ function sc_isCharNumeric(c) { return sc_isCharOfClass(c.val, SC_NUMBER_CLASS); } /*** META ((export #t) (type bool)) */ function sc_isCharWhitespace(c) { var tmp = c.val; return ( tmp === " " || tmp === "\r" || tmp === "\n" || tmp === "\t" || tmp === "\f" ); } /*** META ((export #t) (type bool) (peephole (hole 1 "SC_UPPER_CLASS.indexOf(" c ".val) != -1"))) */ function sc_isCharUpperCase(c) { return sc_isCharOfClass(c.val, SC_UPPER_CLASS); } /*** META ((export #t) (type bool) (peephole (hole 1 "SC_LOWER_CLASS.indexOf(" c ".val) != -1"))) */ function sc_isCharLowerCase(c) { return sc_isCharOfClass(c.val, SC_LOWER_CLASS); } /*** META ((export #t) (peephole (postfix ".val.charCodeAt(0)"))) */ function sc_char2integer(c) { return c.val.charCodeAt(0); } /*** META ((export #t) (peephole (hole 1 "new sc_Char(String.fromCharCode(" n "))"))) */ function sc_integer2char(n) { return new sc_Char(String.fromCharCode(n)); } /*** META ((export #t) (peephole (hole 1 "new sc_Char(" c ".val.toUpperCase())"))) */ function sc_charUpcase(c) { return new sc_Char(c.val.toUpperCase()); } /*** META ((export #t) (peephole (hole 1 "new sc_Char(" c ".val.toLowerCase())"))) */ function sc_charDowncase(c) { return new sc_Char(c.val.toLowerCase()); } function sc_makeJSStringOfLength(k, c) { var fill; if (c === undefined) fill = " "; else fill = c; var res = ""; var len = 1; // every round doubles the size of fill. while (k >= len) { if (k & len) res = res.concat(fill); fill = fill.concat(fill); len *= 2; } return res; } function sc_makejsString(k, c) { var fill; if (c) fill = c.val; else fill = " "; return sc_makeJSStringOfLength(k, fill); } function sc_jsstring2list(s) { var res = null; for (var i = s.length - 1; i >= 0; i--) res = sc_cons(new sc_Char(s.charAt(i)), res); return res; } function sc_list2jsstring(l) { var a = new Array(); while (l !== null) { a.push(l.car.val); l = l.cdr; } return "".concat.apply("", a); } var sc_Vector = Array; sc_Vector.prototype.sc_toWriteOrDisplayString = function (writeOrDisplay) { if (this.length === 0) return "#()"; var res = "#(" + writeOrDisplay(this[0]); for (var i = 1; i < this.length; i++) res += " " + writeOrDisplay(this[i]); res += ")"; return res; }; sc_Vector.prototype.sc_toDisplayString = function () { return this.sc_toWriteOrDisplayString(sc_toDisplayString); }; sc_Vector.prototype.sc_toWriteString = function () { return this.sc_toWriteOrDisplayString(sc_toWriteString); }; /*** META ((export vector? array?) (type bool) (peephole (postfix " instanceof sc_Vector"))) */ function sc_isVector(v) { return v instanceof sc_Vector; } // only applies to vectors function sc_isVectorEqual(v1, v2, comp) { if (v1.length !== v2.length) return false; for (var i = 0; i < v1.length; i++) if (!comp(v1[i], v2[i])) return false; return true; } /*** META ((export make-vector make-array)) */ function sc_makeVector(size, fill) { var a = new sc_Vector(size); if (fill !== undefined) sc_vectorFillBang(a, fill); return a; } /*** META ((export vector array) (peephole (vector))) */ function sc_vector() { var a = new sc_Vector(); for (var i = 0; i < arguments.length; i++) a.push(arguments[i]); return a; } /*** META ((export vector-length array-length) (peephole (postfix ".length"))) */ function sc_vectorLength(v) { return v.length; } /*** META ((export vector-ref array-ref) (peephole (hole 2 v "[" pos "]"))) */ function sc_vectorRef(v, pos) { return v[pos]; } /*** META ((export vector-set! array-set!) (peephole (hole 3 v "[" pos "] = " val))) */ function sc_vectorSetBang(v, pos, val) { v[pos] = val; } /*** META ((export vector->list array->list)) */ function sc_vector2list(a) { var res = null; for (var i = a.length - 1; i >= 0; i--) res = sc_cons(a[i], res); return res; } /*** META ((export list->vector list->array)) */ function sc_list2vector(l) { var a = new sc_Vector(); while (l !== null) { a.push(l.car); l = l.cdr; } return a; } /*** META ((export vector-fill! array-fill!)) */ function sc_vectorFillBang(a, fill) { for (var i = 0; i < a.length; i++) a[i] = fill; } /*** META ((export #t)) */ function sc_copyVector(a, len) { if (len <= a.length) return a.slice(0, len); else { var tmp = a.concat(); tmp.length = len; return tmp; } } /*** META ((export #t) (peephole (hole 3 a ".slice(" start "," end ")"))) */ function sc_vectorCopy(a, start, end) { return a.slice(start, end); } /*** META ((export #t)) */ function sc_vectorCopyBang(target, tstart, source, sstart, send) { if (!sstart) sstart = 0; if (!send) send = source.length; // if target == source we don't want to overwrite not yet copied elements. if (tstart <= sstart) { for (var i = tstart, j = sstart; j < send; i++, j++) { target[i] = source[j]; } } else { var diff = send - sstart; for (var i = tstart + diff - 1, j = send - 1; j >= sstart; i--, j--) { target[i] = source[j]; } } return target; } /*** META ((export #t) (type bool) (peephole (hole 1 "typeof " o " === 'function'"))) */ function sc_isProcedure(o) { return typeof o === "function"; } /*** META ((export #t)) */ function sc_apply(proc) { var args = new Array(); // first part of arguments are not in list-form. for (var i = 1; i < arguments.length - 1; i++) args.push(arguments[i]); var l = arguments[arguments.length - 1]; while (l !== null) { args.push(l.car); l = l.cdr; } return proc.apply(null, args); } /*** META ((export #t)) */ function sc_map(proc, l1) { if (l1 === undefined) return null; // else var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); var revres = null; while (l1 !== null) { for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } revres = sc_cons(proc.apply(null, applyArgs), revres); } return sc_reverseAppendBang(revres, null); } /*** META ((export #t)) */ function sc_mapBang(proc, l1) { if (l1 === undefined) return null; // else var l1_orig = l1; var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); while (l1 !== null) { var tmp = l1; for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } tmp.car = proc.apply(null, applyArgs); } return l1_orig; } /*** META ((export #t)) */ function sc_forEach(proc, l1) { if (l1 === undefined) return undefined; // else var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); while (l1 !== null) { for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } proc.apply(null, applyArgs); } // add return so FF does not complain. return undefined; } /*** META ((export #t)) */ function sc_filter(proc, l1) { var dummy = { cdr: null }; var tail = dummy; while (l1 !== null) { if (proc(l1.car) !== false) { tail.cdr = sc_cons(l1.car, null); tail = tail.cdr; } l1 = l1.cdr; } return dummy.cdr; } /*** META ((export #t)) */ function sc_filterBang(proc, l1) { var head = sc_cons("dummy", l1); var it = head; var next = l1; while (next !== null) { if (proc(next.car) !== false) { it.cdr = next; it = next; } next = next.cdr; } it.cdr = null; return head.cdr; } function sc_filterMap1(proc, l1) { var revres = null; while (l1 !== null) { var tmp = proc(l1.car); if (tmp !== false) revres = sc_cons(tmp, revres); l1 = l1.cdr; } return sc_reverseAppendBang(revres, null); } function sc_filterMap2(proc, l1, l2) { var revres = null; while (l1 !== null) { var tmp = proc(l1.car, l2.car); if (tmp !== false) revres = sc_cons(tmp, revres); l1 = l1.cdr; l2 = l2.cdr; } return sc_reverseAppendBang(revres, null); } /*** META ((export #t)) */ function sc_filterMap(proc, l1, l2, l3) { if (l2 === undefined) return sc_filterMap1(proc, l1); else if (l3 === undefined) return sc_filterMap2(proc, l1, l2); // else var nbApplyArgs = arguments.length - 1; var applyArgs = new Array(nbApplyArgs); var revres = null; while (l1 !== null) { for (var i = 0; i < nbApplyArgs; i++) { applyArgs[i] = arguments[i + 1].car; arguments[i + 1] = arguments[i + 1].cdr; } var tmp = proc.apply(null, applyArgs); if (tmp !== false) revres = sc_cons(tmp, revres); } return sc_reverseAppendBang(revres, null); } /*** META ((export #t)) */ function sc_any(proc, l) { var revres = null; while (l !== null) { var tmp = proc(l.car); if (tmp !== false) return tmp; l = l.cdr; } return false; } /*** META ((export any?) (peephole (hole 2 "sc_any(" proc "," l ") !== false"))) */ function sc_anyPred(proc, l) { return sc_any(proc, l) !== false; } /*** META ((export #t)) */ function sc_every(proc, l) { var revres = null; var tmp = true; while (l !== null) { tmp = proc(l.car); if (tmp === false) return false; l = l.cdr; } return tmp; } /*** META ((export every?) (peephole (hole 2 "sc_every(" proc "," l ") !== false"))) */ function sc_everyPred(proc, l) { var tmp = sc_every(proc, l); if (tmp !== false) return true; return false; } /*** META ((export #t) (peephole (postfix "()"))) */ function sc_force(o) { return o(); } /*** META ((export #t)) */ function sc_makePromise(proc) { var isResultReady = false; var result = undefined; return function () { if (!isResultReady) { var tmp = proc(); if (!isResultReady) { isResultReady = true; result = tmp; } } return result; }; } function sc_Values(values) { this.values = values; } /*** META ((export #t) (peephole (values))) */ function sc_values() { if (arguments.length === 1) return arguments[0]; else return new sc_Values(arguments); } /*** META ((export #t)) */ function sc_callWithValues(producer, consumer) { var produced = producer(); if (produced instanceof sc_Values) return consumer.apply(null, produced.values); else return consumer(produced); } /*** META ((export #t)) */ function sc_dynamicWind(before, thunk, after) { before(); try { var res = thunk(); return res; } finally { after(); } } // TODO: eval/scheme-report-environment/null-environment/interaction-environment // LIMITATION: 'load' doesn't exist without files. // LIMITATION: transcript-on/transcript-off doesn't exist without files. function sc_Struct(name) { this.name = name; } sc_Struct.prototype.sc_toDisplayString = function () { return "#"; }; sc_Struct.prototype.sc_toWriteString = sc_Struct.prototype.sc_toDisplayString; /*** META ((export #t) (peephole (hole 1 "new sc_Struct(" name ")"))) */ function sc_makeStruct(name) { return new sc_Struct(name); } /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_Struct"))) */ function sc_isStruct(o) { return o instanceof sc_Struct; } /*** META ((export #t) (type bool) (peephole (hole 2 "(" 1 " instanceof sc_Struct) && ( " 1 ".name === " 0 ")"))) */ function sc_isStructNamed(name, s) { return s instanceof sc_Struct && s.name === name; } /*** META ((export struct-field) (peephole (hole 3 0 "[" 2 "]"))) */ function sc_getStructField(s, name, field) { return s[field]; } /*** META ((export struct-field-set!) (peephole (hole 4 0 "[" 2 "] = " 3))) */ function sc_setStructFieldBang(s, name, field, val) { s[field] = val; } /*** META ((export #t) (peephole (prefix "~"))) */ function sc_bitNot(x) { return ~x; } /*** META ((export #t) (peephole (infix 2 2 "&"))) */ function sc_bitAnd(x, y) { return x & y; } /*** META ((export #t) (peephole (infix 2 2 "|"))) */ function sc_bitOr(x, y) { return x | y; } /*** META ((export #t) (peephole (infix 2 2 "^"))) */ function sc_bitXor(x, y) { return x ^ y; } /*** META ((export #t) (peephole (infix 2 2 "<<"))) */ function sc_bitLsh(x, y) { return x << y; } /*** META ((export #t) (peephole (infix 2 2 ">>"))) */ function sc_bitRsh(x, y) { return x >> y; } /*** META ((export #t) (peephole (infix 2 2 ">>>"))) */ function sc_bitUrsh(x, y) { return x >>> y; } /*** META ((export js-field js-property) (peephole (hole 2 o "[" field "]"))) */ function sc_jsField(o, field) { return o[field]; } /*** META ((export js-field-set! js-property-set!) (peephole (hole 3 o "[" field "] = " val))) */ function sc_setJsFieldBang(o, field, val) { return (o[field] = val); } /*** META ((export js-field-delete! js-property-delete!) (peephole (hole 2 "delete" o "[" field "]"))) */ function sc_deleteJsFieldBang(o, field) { delete o[field]; } /*** META ((export #t) (peephole (jsCall))) */ function sc_jsCall(o, fun) { var args = new Array(); for (var i = 2; i < arguments.length; i++) args[i - 2] = arguments[i]; return fun.apply(o, args); } /*** META ((export #t) (peephole (jsMethodCall))) */ function sc_jsMethodCall(o, field) { var args = new Array(); for (var i = 2; i < arguments.length; i++) args[i - 2] = arguments[i]; return o[field].apply(o, args); } /*** META ((export new js-new) (peephole (jsNew))) */ function sc_jsNew(c) { var evalStr = "new c("; evalStr += arguments.length > 1 ? "arguments[1]" : ""; for (var i = 2; i < arguments.length; i++) evalStr += ", arguments[" + i + "]"; evalStr += ")"; return eval(evalStr); } // ======================== RegExp ==================== /*** META ((export #t)) */ function sc_pregexp(re) { return new RegExp(sc_string2jsstring(re)); } /*** META ((export #t)) */ function sc_pregexpMatch(re, s) { var reg = re instanceof RegExp ? re : sc_pregexp(re); var tmp = reg.exec(sc_string2jsstring(s)); if (tmp == null) return false; var res = null; for (var i = tmp.length - 1; i >= 0; i--) { if (tmp[i] !== null) { res = sc_cons(sc_jsstring2string(tmp[i]), res); } else { res = sc_cons(false, res); } } return res; } /*** META ((export #t)) */ function sc_pregexpReplace(re, s1, s2) { var reg; var jss1 = sc_string2jsstring(s1); var jss2 = sc_string2jsstring(s2); if (re instanceof RegExp) { if (re.global) reg = re; else reg = new RegExp(re.source); } else { reg = new RegExp(sc_string2jsstring(re)); } return jss1.replace(reg, jss2); } /*** META ((export pregexp-replace*)) */ function sc_pregexpReplaceAll(re, s1, s2) { var reg; var jss1 = sc_string2jsstring(s1); var jss2 = sc_string2jsstring(s2); if (re instanceof RegExp) { if (re.global) reg = re; else reg = new RegExp(re.source, "g"); } else { reg = new RegExp(sc_string2jsstring(re), "g"); } return jss1.replace(reg, jss2); } /*** META ((export #t)) */ function sc_pregexpSplit(re, s) { var reg = re instanceof RegExp ? re : new RegExp(sc_string2jsstring(re)); var jss = sc_string2jsstring(s); var tmp = jss.split(reg); if (tmp == null) return false; return sc_vector2list(tmp); } /* =========================================================================== */ /* Other library stuff */ /* =========================================================================== */ /*** META ((export #t) (peephole (hole 1 "Math.floor(Math.random()*" 'n ")"))) */ function sc_random(n) { return Math.floor(Math.random() * n); } /*** META ((export current-date) (peephole (hole 0 "new Date()"))) */ function sc_currentDate() { return new Date(); } function sc_Hashtable() {} sc_Hashtable.prototype.toString = function () { return "#{%hashtable}"; }; // sc_toWriteString == sc_toDisplayString == toString function sc_HashtableElement(key, val) { this.key = key; this.val = val; } /*** META ((export #t) (peephole (hole 0 "new sc_Hashtable()"))) */ function sc_makeHashtable() { return new sc_Hashtable(); } /*** META ((export #t)) */ function sc_hashtablePutBang(ht, key, val) { var hash = sc_hash(key); ht[hash] = new sc_HashtableElement(key, val); } /*** META ((export #t)) */ function sc_hashtableGet(ht, key) { var hash = sc_hash(key); if (hash in ht) return ht[hash].val; else return false; } /*** META ((export #t)) */ function sc_hashtableForEach(ht, f) { for (var v in ht) { if (ht[v] instanceof sc_HashtableElement) f(ht[v].key, ht[v].val); } } /*** META ((export hashtable-contains?) (peephole (hole 2 "sc_hash(" 1 ") in " 0))) */ function sc_hashtableContains(ht, key) { var hash = sc_hash(key); if (hash in ht) return true; else return false; } var SC_HASH_COUNTER = 0; function sc_hash(o) { if (o === null) return "null"; else if (o === undefined) return "undefined"; else if (o === true) return "true"; else if (o === false) return "false"; else if (typeof o === "number") return "num-" + o; else if (typeof o === "string") return "jsstr-" + o; else if (o.sc_getHash) return o.sc_getHash(); else return sc_counterHash.call(o); } function sc_counterHash() { if (!this.sc_hash) { this.sc_hash = "hash-" + SC_HASH_COUNTER; SC_HASH_COUNTER++; } return this.sc_hash; } function sc_Trampoline(args, maxTailCalls) { this["__trampoline return__"] = true; this.args = args; this.MAX_TAIL_CALLs = maxTailCalls; } // TODO: call/cc stuff sc_Trampoline.prototype.restart = function () { var o = this; while (true) { // set both globals. SC_TAIL_OBJECT.calls = o.MAX_TAIL_CALLs - 1; var fun = o.args.callee; var res = fun.apply(SC_TAIL_OBJECT, o.args); if (res instanceof sc_Trampoline) o = res; else return res; } }; /*** META ((export bind-exit-lambda)) */ function sc_bindExitLambda(proc) { var escape_obj = new sc_BindExitException(); var escape = function (res) { escape_obj.res = res; throw escape_obj; }; try { return proc(escape); } catch (e) { if (e === escape_obj) { return e.res; } throw e; } } function sc_BindExitException() { this._internalException = true; } var SC_SCM2JS_GLOBALS = new Object(); // default tail-call depth. // normally the program should set it again. but just in case... var SC_TAIL_OBJECT = new Object(); SC_SCM2JS_GLOBALS.TAIL_OBJECT = SC_TAIL_OBJECT; // ======================== I/O ======================= /*------------------------------------------------------------------*/ function sc_EOF() {} var SC_EOF_OBJECT = new sc_EOF(); function sc_Port() {} /* --------------- Input ports -------------------------------------*/ function sc_InputPort() {} sc_InputPort.prototype = new sc_Port(); sc_InputPort.prototype.peekChar = function () { if (!("peeked" in this)) this.peeked = this.getNextChar(); return this.peeked; }; sc_InputPort.prototype.readChar = function () { var tmp = this.peekChar(); delete this.peeked; return tmp; }; sc_InputPort.prototype.isCharReady = function () { return true; }; sc_InputPort.prototype.close = function () { // do nothing }; /* .............. String port ..........................*/ function sc_ErrorInputPort() {} sc_ErrorInputPort.prototype = new sc_InputPort(); sc_ErrorInputPort.prototype.getNextChar = function () { throw "can't read from error-port."; }; sc_ErrorInputPort.prototype.isCharReady = function () { return false; }; /* .............. String port ..........................*/ function sc_StringInputPort(jsStr) { // we are going to do some charAts on the str. // instead of recreating all the time a String-object, we // create one in the beginning. (not sure, if this is really an optim) this.str = new String(jsStr); this.pos = 0; } sc_StringInputPort.prototype = new sc_InputPort(); sc_StringInputPort.prototype.getNextChar = function () { if (this.pos >= this.str.length) return SC_EOF_OBJECT; return this.str.charAt(this.pos++); }; /* ------------- Read and other lib-funs -------------------------------*/ function sc_Token(type, val, pos) { this.type = type; this.val = val; this.pos = pos; } sc_Token.EOF = 0 /*EOF*/; sc_Token.OPEN_PAR = 1 /*OPEN_PAR*/; sc_Token.CLOSE_PAR = 2 /*CLOSE_PAR*/; sc_Token.OPEN_BRACE = 3 /*OPEN_BRACE*/; sc_Token.CLOSE_BRACE = 4 /*CLOSE_BRACE*/; sc_Token.OPEN_BRACKET = 5 /*OPEN_BRACKET*/; sc_Token.CLOSE_BRACKET = 6 /*CLOSE_BRACKET*/; sc_Token.WHITESPACE = 7 /*WHITESPACE*/; sc_Token.QUOTE = 8 /*QUOTE*/; sc_Token.ID = 9 /*ID*/; sc_Token.DOT = 10 /*DOT*/; sc_Token.STRING = 11 /*STRING*/; sc_Token.NUMBER = 12 /*NUMBER*/; sc_Token.ERROR = 13 /*ERROR*/; sc_Token.VECTOR_BEGIN = 14 /*VECTOR_BEGIN*/; sc_Token.TRUE = 15 /*TRUE*/; sc_Token.FALSE = 16 /*FALSE*/; sc_Token.UNSPECIFIED = 17 /*UNSPECIFIED*/; sc_Token.REFERENCE = 18 /*REFERENCE*/; sc_Token.STORE = 19 /*STORE*/; sc_Token.CHAR = 20 /*CHAR*/; var SC_ID_CLASS = SC_LOWER_CLASS + SC_UPPER_CLASS + "!$%*+-./:<=>?@^_~"; function sc_Tokenizer(port) { this.port = port; } sc_Tokenizer.prototype.peekToken = function () { if (this.peeked) return this.peeked; var newToken = this.nextToken(); this.peeked = newToken; return newToken; }; sc_Tokenizer.prototype.readToken = function () { var tmp = this.peekToken(); delete this.peeked; return tmp; }; sc_Tokenizer.prototype.nextToken = function () { var port = this.port; function isNumberChar(c) { return c >= "0" && c <= "9"; } function isIdOrNumberChar(c) { return ( SC_ID_CLASS.indexOf(c) != -1 || // ID-char (c >= "0" && c <= "9") ); } function isWhitespace(c) { return c === " " || c === "\r" || c === "\n" || c === "\t" || c === "\f"; } function isWhitespaceOrEOF(c) { return isWhitespace(c) || c === SC_EOF_OBJECT; } function readString() { res = ""; while (true) { var c = port.readChar(); switch (c) { case '"': return new sc_Token(11 /*STRING*/, res); case "\\": var tmp = port.readChar(); switch (tmp) { case "0": res += "\0"; break; case "a": res += "a"; break; case "b": res += "\b"; break; case "f": res += "\f"; break; case "n": res += "\n"; break; case "r": res += "\r"; break; case "t": res += "\t"; break; case "v": res += "\v"; break; case '"': res += '"'; break; case "\\": res += "\\"; break; case "x": /* hexa-number */ var nb = 0; while (true) { var hexC = port.peekChar(); if (hexC >= "0" && hexC <= "9") { port.readChar(); nb = nb * 16 + hexC.charCodeAt(0) - "0".charCodeAt(0); } else if (hexC >= "a" && hexC <= "f") { port.readChar(); nb = nb * 16 + hexC.charCodeAt(0) - "a".charCodeAt(0); } else if (hexC >= "A" && hexC <= "F") { port.readChar(); nb = nb * 16 + hexC.charCodeAt(0) - "A".charCodeAt(0); } else { // next char isn't part of hex. res += String.fromCharCode(nb); break; } } break; default: if (tmp === SC_EOF_OBJECT) { return new sc_Token( 13 /*ERROR*/, "unclosed string-literal" + res ); } res += tmp; } break; default: if (c === SC_EOF_OBJECT) { return new sc_Token(13 /*ERROR*/, "unclosed string-literal" + res); } res += c; } } } function readIdOrNumber(firstChar) { var res = firstChar; while (isIdOrNumberChar(port.peekChar())) res += port.readChar(); if (isNaN(res)) return new sc_Token(9 /*ID*/, res); else return new sc_Token(12 /*NUMBER*/, res - 0); } function skipWhitespaceAndComments() { var done = false; while (!done) { done = true; while (isWhitespace(port.peekChar())) port.readChar(); if (port.peekChar() === ";") { port.readChar(); done = false; while (true) { curChar = port.readChar(); if (curChar === SC_EOF_OBJECT || curChar === "\n") break; } } } } function readDot() { if (isWhitespace(port.peekChar())) return new sc_Token(10 /*DOT*/); else return readIdOrNumber("."); } function readSharp() { var c = port.readChar(); if (isWhitespace(c)) return new sc_Token(13 /*ERROR*/, "bad #-pattern0."); // reference if (isNumberChar(c)) { var nb = c - 0; while (isNumberChar(port.peekChar())) nb = nb * 10 + (port.readChar() - 0); switch (port.readChar()) { case "#": return new sc_Token(18 /*REFERENCE*/, nb); case "=": return new sc_Token(19 /*STORE*/, nb); default: return new sc_Token(13 /*ERROR*/, "bad #-pattern1." + nb); } } if (c === "(") return new sc_Token(14 /*VECTOR_BEGIN*/); if (c === "\\") { // character var tmp = ""; while (!isWhitespaceOrEOF(port.peekChar())) tmp += port.readChar(); switch (tmp.length) { case 0: // it's escaping a whitespace char: if (sc_isEOFObject(port.peekChar)) return new sc_Token(13 /*ERROR*/, "bad #-pattern2."); else return new sc_Token(20 /*CHAR*/, port.readChar()); case 1: return new sc_Token(20 /*CHAR*/, tmp); default: var entry = sc_Char.readable2char[tmp.toLowerCase()]; if (entry) return new sc_Token(20 /*CHAR*/, entry); else return new sc_Token( 13 /*ERROR*/, "unknown character description: #\\" + tmp ); } } // some constants (#t, #f, #unspecified) var res; var needing; switch (c) { case "t": res = new sc_Token(15 /*TRUE*/, true); needing = ""; break; case "f": res = new sc_Token(16 /*FALSE*/, false); needing = ""; break; case "u": res = new sc_Token(17 /*UNSPECIFIED*/, undefined); needing = "nspecified"; break; default: return new sc_Token(13 /*ERROR*/, "bad #-pattern3: " + c); } while (true) { c = port.peekChar(); if ((isWhitespaceOrEOF(c) || c === ")") && needing == "") return res; else if (isWhitespace(c) || needing == "") return new sc_Token( 13 /*ERROR*/, "bad #-pattern4 " + c + " " + needing ); else if (needing.charAt(0) == c) { port.readChar(); // consume needing = needing.slice(1); } else return new sc_Token(13 /*ERROR*/, "bad #-pattern5"); } } skipWhitespaceAndComments(); var curChar = port.readChar(); if (curChar === SC_EOF_OBJECT) return new sc_Token(0 /*EOF*/, curChar); switch (curChar) { case " ": case "\n": case "\t": return readWhitespace(); case "(": return new sc_Token(1 /*OPEN_PAR*/); case ")": return new sc_Token(2 /*CLOSE_PAR*/); case "{": return new sc_Token(3 /*OPEN_BRACE*/); case "}": return new sc_Token(4 /*CLOSE_BRACE*/); case "[": return new sc_Token(5 /*OPEN_BRACKET*/); case "]": return new sc_Token(6 /*CLOSE_BRACKET*/); case "'": return new sc_Token(8 /*QUOTE*/); case "#": return readSharp(); case ".": return readDot(); case '"': return readString(); default: if (isIdOrNumberChar(curChar)) return readIdOrNumber(curChar); throw "unexpected character: " + curChar; } }; function sc_Reader(tokenizer) { this.tokenizer = tokenizer; this.backref = new Array(); } sc_Reader.prototype.read = function () { function readList(listBeginType) { function matchesPeer(open, close) { return ( (open === 1 /*OPEN_PAR*/ && close === 2) /*CLOSE_PAR*/ || (open === 3 /*OPEN_BRACE*/ && close === 4) /*CLOSE_BRACE*/ || (open === 5 /*OPEN_BRACKET*/ && close === 6) /*CLOSE_BRACKET*/ ); } var res = null; while (true) { var token = tokenizer.peekToken(); switch (token.type) { case 2 /*CLOSE_PAR*/: case 4 /*CLOSE_BRACE*/: case 6 /*CLOSE_BRACKET*/: if (matchesPeer(listBeginType, token.type)) { tokenizer.readToken(); // consume token return sc_reverseBang(res); } else throw ( "closing par doesn't match: " + listBeginType + " " + listEndType ); case 0 /*EOF*/: throw "unexpected end of file"; case 10 /*DOT*/: tokenizer.readToken(); // consume token var cdr = this.read(); var par = tokenizer.readToken(); if (!matchesPeer(listBeginType, par.type)) throw ( "closing par doesn't match: " + listBeginType + " " + par.type ); else return sc_reverseAppendBang(res, cdr); default: res = sc_cons(this.read(), res); } } } function readQuote() { return sc_cons("quote", sc_cons(this.read(), null)); } function readVector() { // opening-parenthesis is already consumed var a = new Array(); while (true) { var token = tokenizer.peekToken(); switch (token.type) { case 2 /*CLOSE_PAR*/: tokenizer.readToken(); return a; default: a.push(this.read()); } } } function storeRefence(nb) { var tmp = this.read(); this.backref[nb] = tmp; return tmp; } function readReference(nb) { if (nb in this.backref) return this.backref[nb]; else throw "bad reference: " + nb; } var tokenizer = this.tokenizer; var token = tokenizer.readToken(); // handle error if (token.type === 13 /*ERROR*/) throw token.val; switch (token.type) { case 1 /*OPEN_PAR*/: case 3 /*OPEN_BRACE*/: case 5 /*OPEN_BRACKET*/: return readList.call(this, token.type); case 8 /*QUOTE*/: return readQuote.call(this); case 11 /*STRING*/: return sc_jsstring2string(token.val); case 20 /*CHAR*/: return new sc_Char(token.val); case 14 /*VECTOR_BEGIN*/: return readVector.call(this); case 18 /*REFERENCE*/: return readReference.call(this, token.val); case 19 /*STORE*/: return storeRefence.call(this, token.val); case 9 /*ID*/: return sc_jsstring2symbol(token.val); case 0 /*EOF*/: case 12 /*NUMBER*/: case 15 /*TRUE*/: case 16 /*FALSE*/: case 17 /*UNSPECIFIED*/: return token.val; default: throw "unexpected token " + token.type + " " + token.val; } }; /*** META ((export #t)) */ function sc_read(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... var reader = new sc_Reader(new sc_Tokenizer(port)); return reader.read(); } /*** META ((export #t)) */ function sc_readChar(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... var t = port.readChar(); return t === SC_EOF_OBJECT ? t : new sc_Char(t); } /*** META ((export #t)) */ function sc_peekChar(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... var t = port.peekChar(); return t === SC_EOF_OBJECT ? t : new sc_Char(t); } /*** META ((export #t) (type bool)) */ function sc_isCharReady(port) { if (port === undefined) // we assume the port hasn't been given. port = SC_DEFAULT_IN; // THREAD: shared var... return port.isCharReady(); } /*** META ((export #t) (peephole (postfix ".close()"))) */ function sc_closeInputPort(p) { return p.close(); } /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_InputPort"))) */ function sc_isInputPort(o) { return o instanceof sc_InputPort; } /*** META ((export eof-object?) (type bool) (peephole (postfix " === SC_EOF_OBJECT"))) */ function sc_isEOFObject(o) { return o === SC_EOF_OBJECT; } /*** META ((export #t) (peephole (hole 0 "SC_DEFAULT_IN"))) */ function sc_currentInputPort() { return SC_DEFAULT_IN; } /* ------------ file operations are not supported -----------*/ /*** META ((export #t)) */ function sc_callWithInputFile(s, proc) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_callWithOutputFile(s, proc) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_withInputFromFile(s, thunk) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_withOutputToFile(s, thunk) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_openInputFile(s) { throw "can't open " + s; } /*** META ((export #t)) */ function sc_openOutputFile(s) { throw "can't open " + s; } /* ----------------------------------------------------------------------------*/ /*** META ((export #t)) */ function sc_basename(p) { var i = p.lastIndexOf("/"); if (i >= 0) return p.substring(i + 1, p.length); else return ""; } /*** META ((export #t)) */ function sc_dirname(p) { var i = p.lastIndexOf("/"); if (i >= 0) return p.substring(0, i); else return ""; } /* ----------------------------------------------------------------------------*/ /*** META ((export #t)) */ function sc_withInputFromPort(p, thunk) { try { var tmp = SC_DEFAULT_IN; // THREAD: shared var. SC_DEFAULT_IN = p; return thunk(); } finally { SC_DEFAULT_IN = tmp; } } /*** META ((export #t)) */ function sc_withInputFromString(s, thunk) { return sc_withInputFromPort( new sc_StringInputPort(sc_string2jsstring(s)), thunk ); } /*** META ((export #t)) */ function sc_withOutputToPort(p, thunk) { try { var tmp = SC_DEFAULT_OUT; // THREAD: shared var. SC_DEFAULT_OUT = p; return thunk(); } finally { SC_DEFAULT_OUT = tmp; } } /*** META ((export #t)) */ function sc_withOutputToString(thunk) { var p = new sc_StringOutputPort(); sc_withOutputToPort(p, thunk); return p.close(); } /*** META ((export #t)) */ function sc_withOutputToProcedure(proc, thunk) { var t = function (s) { proc(sc_jsstring2string(s)); }; return sc_withOutputToPort(new sc_GenericOutputPort(t), thunk); } /*** META ((export #t) (peephole (hole 0 "new sc_StringOutputPort()"))) */ function sc_openOutputString() { return new sc_StringOutputPort(); } /*** META ((export #t)) */ function sc_openInputString(str) { return new sc_StringInputPort(sc_string2jsstring(str)); } /* ----------------------------------------------------------------------------*/ function sc_OutputPort() {} sc_OutputPort.prototype = new sc_Port(); sc_OutputPort.prototype.appendJSString = function (obj) { /* do nothing */ }; sc_OutputPort.prototype.close = function () { /* do nothing */ }; function sc_StringOutputPort() { this.res = ""; } sc_StringOutputPort.prototype = new sc_OutputPort(); sc_StringOutputPort.prototype.appendJSString = function (s) { this.res += s; }; sc_StringOutputPort.prototype.close = function () { return sc_jsstring2string(this.res); }; /*** META ((export #t)) */ function sc_getOutputString(sp) { return sc_jsstring2string(sp.res); } function sc_ErrorOutputPort() {} sc_ErrorOutputPort.prototype = new sc_OutputPort(); sc_ErrorOutputPort.prototype.appendJSString = function (s) { throw "don't write on ErrorPort!"; }; sc_ErrorOutputPort.prototype.close = function () { /* do nothing */ }; function sc_GenericOutputPort(appendJSString, close) { this.appendJSString = appendJSString; if (close) this.close = close; } sc_GenericOutputPort.prototype = new sc_OutputPort(); /*** META ((export #t) (type bool) (peephole (postfix " instanceof sc_OutputPort"))) */ function sc_isOutputPort(o) { return o instanceof sc_OutputPort; } /*** META ((export #t) (peephole (postfix ".close()"))) */ function sc_closeOutputPort(p) { return p.close(); } /* ------------------ write ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_write(o, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(sc_toWriteString(o)); } function sc_toWriteString(o) { if (o === null) return "()"; else if (o === true) return "#t"; else if (o === false) return "#f"; else if (o === undefined) return "#unspecified"; else if (typeof o === "function") return "#"; else if (o.sc_toWriteString) return o.sc_toWriteString(); else return o.toString(); } function sc_escapeWriteString(s) { var res = ""; var j = 0; for (i = 0; i < s.length; i++) { switch (s.charAt(i)) { case "\0": res += s.substring(j, i) + "\\0"; j = i + 1; break; case "\b": res += s.substring(j, i) + "\\b"; j = i + 1; break; case "\f": res += s.substring(j, i) + "\\f"; j = i + 1; break; case "\n": res += s.substring(j, i) + "\\n"; j = i + 1; break; case "\r": res += s.substring(j, i) + "\\r"; j = i + 1; break; case "\t": res += s.substring(j, i) + "\\t"; j = i + 1; break; case "\v": res += s.substring(j, i) + "\\v"; j = i + 1; break; case '"': res += s.substring(j, i) + '\\"'; j = i + 1; break; case "\\": res += s.substring(j, i) + "\\\\"; j = i + 1; break; default: var c = s.charAt(i); if ("a" !== "a" && c == "a") { res += s.substring(j, i) + "\\a"; j = i + 1; continue; } if ("\v" !== "v" && c == "\v") { res += s.substring(j, i) + "\\v"; j = i + 1; continue; } //if (s.charAt(i) < ' ' || s.charCodeAt(i) > 127) { // CARE: Manuel is this OK with HOP? if (s.charAt(i) < " ") { /* non printable character and special chars */ res += s.substring(j, i) + "\\x" + s.charCodeAt(i).toString(16); j = i + 1; } // else just let i increase... } } res += s.substring(j, i); return res; } /* ------------------ display ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_display(o, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(sc_toDisplayString(o)); } function sc_toDisplayString(o) { if (o === null) return "()"; else if (o === true) return "#t"; else if (o === false) return "#f"; else if (o === undefined) return "#unspecified"; else if (typeof o === "function") return "#"; else if (o.sc_toDisplayString) return o.sc_toDisplayString(); else return o.toString(); } /* ------------------ newline ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_newline(p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString("\n"); } /* ------------------ write-char ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_writeChar(c, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(c.val); } /* ------------------ write-circle ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_writeCircle(o, p) { if (p === undefined) // we assume not given p = SC_DEFAULT_OUT; p.appendJSString(sc_toWriteCircleString(o)); } function sc_toWriteCircleString(o) { var symb = sc_gensym("writeCircle"); var nbPointer = new Object(); nbPointer.nb = 0; sc_prepWriteCircle(o, symb, nbPointer); return sc_genToWriteCircleString(o, symb); } function sc_prepWriteCircle(o, symb, nbPointer) { // TODO sc_Struct if (o instanceof sc_Pair || o instanceof sc_Vector) { if (o[symb] !== undefined) { // not the first visit. o[symb]++; // unless there is already a number, assign one. if (!o[symb + "nb"]) o[symb + "nb"] = nbPointer.nb++; return; } o[symb] = 0; if (o instanceof sc_Pair) { sc_prepWriteCircle(o.car, symb, nbPointer); sc_prepWriteCircle(o.cdr, symb, nbPointer); } else { for (var i = 0; i < o.length; i++) sc_prepWriteCircle(o[i], symb, nbPointer); } } } function sc_genToWriteCircleString(o, symb) { if (!(o instanceof sc_Pair || o instanceof sc_Vector)) return sc_toWriteString(o); return o.sc_toWriteCircleString(symb); } sc_Pair.prototype.sc_toWriteCircleString = function (symb, inList) { if (this[symb + "use"]) { // use-flag is set. Just use it. var nb = this[symb + "nb"]; if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } if (inList) return ". #" + nb + "#"; else return "#" + nb + "#"; } if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } var res = ""; if (this[symb] !== undefined) { // implies > 0 this[symb + "use"] = true; if (inList) res += ". #" + this[symb + "nb"] + "="; else res += "#" + this[symb + "nb"] + "="; inList = false; } if (!inList) res += "("; // print car res += sc_genToWriteCircleString(this.car, symb); if (sc_isPair(this.cdr)) { res += " " + this.cdr.sc_toWriteCircleString(symb, true); } else if (this.cdr !== null) { res += " . " + sc_genToWriteCircleString(this.cdr, symb); } if (!inList) res += ")"; return res; }; sc_Vector.prototype.sc_toWriteCircleString = function (symb) { if (this[symb + "use"]) { // use-flag is set. Just use it. var nb = this[symb + "nb"]; if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } return "#" + nb + "#"; } if (this[symb]-- === 0) { // if we are the last use. remove all fields. delete this[symb]; delete this[symb + "nb"]; delete this[symb + "use"]; } var res = ""; if (this[symb] !== undefined) { // implies > 0 this[symb + "use"] = true; res += "#" + this[symb + "nb"] + "="; } res += "#("; for (var i = 0; i < this.length; i++) { res += sc_genToWriteCircleString(this[i], symb); if (i < this.length - 1) res += " "; } res += ")"; return res; }; /* ------------------ print ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_print(s) { if (arguments.length === 1) { sc_display(s); sc_newline(); } else { for (var i = 0; i < arguments.length; i++) sc_display(arguments[i]); sc_newline(); } } /* ------------------ format ---------------------------------------------------*/ /*** META ((export #t)) */ function sc_format(s, args) { var len = s.length; var p = new sc_StringOutputPort(); var i = 0, j = 1; while (i < len) { var i2 = s.indexOf("~", i); if (i2 == -1) { p.appendJSString(s.substring(i, len)); return p.close(); } else { if (i2 > i) { if (i2 == len - 1) { p.appendJSString(s.substring(i, len)); return p.close(); } else { p.appendJSString(s.substring(i, i2)); i = i2; } } switch (s.charCodeAt(i2 + 1)) { case 65: case 97: // a sc_display(arguments[j], p); i += 2; j++; break; case 83: case 115: // s sc_write(arguments[j], p); i += 2; j++; break; case 86: case 118: // v sc_display(arguments[j], p); p.appendJSString("\n"); i += 2; j++; break; case 67: case 99: // c p.appendJSString(String.fromCharCode(arguments[j])); i += 2; j++; break; case 88: case 120: // x p.appendJSString(arguments[j].toString(6)); i += 2; j++; break; case 79: case 111: // o p.appendJSString(arguments[j].toString(8)); i += 2; j++; break; case 66: case 98: // b p.appendJSString(arguments[j].toString(2)); i += 2; j++; break; case 37: case 110: // %, n p.appendJSString("\n"); i += 2; break; case 114: // r p.appendJSString("\r"); i += 2; break; case 126: // ~ p.appendJSString("~"); i += 2; break; default: sc_error( "format: illegal ~" + String.fromCharCode(s.charCodeAt(i2 + 1)) + " sequence" ); return ""; } } } return p.close(); } /* ------------------ global ports ---------------------------------------------------*/ var SC_DEFAULT_IN = new sc_ErrorInputPort(); var SC_DEFAULT_OUT = new sc_ErrorOutputPort(); var SC_ERROR_OUT = new sc_ErrorOutputPort(); var sc_SYMBOL_PREFIX = "\u1E9C"; var sc_KEYWORD_PREFIX = "\u1E9D"; /*** META ((export #t) (peephole (id))) */ function sc_jsstring2string(s) { return s; } /*** META ((export #t) (peephole (prefix "'\\u1E9C' +"))) */ function sc_jsstring2symbol(s) { return sc_SYMBOL_PREFIX + s; } /*** META ((export #t) (peephole (id))) */ function sc_string2jsstring(s) { return s; } /*** META ((export #t) (peephole (symbol2jsstring_immutable))) */ function sc_symbol2jsstring(s) { return s.slice(1); } /*** META ((export #t) (peephole (postfix ".slice(1)"))) */ function sc_keyword2jsstring(k) { return k.slice(1); } /*** META ((export #t) (peephole (prefix "'\\u1E9D' +"))) */ function sc_jsstring2keyword(s) { return sc_KEYWORD_PREFIX + s; } /*** META ((export #t) (type bool)) */ function sc_isKeyword(s) { return typeof s === "string" && s.charAt(0) === sc_KEYWORD_PREFIX; } /*** META ((export #t)) */ var sc_gensym = (function () { var counter = 1000; return function (sym) { counter++; if (!sym) sym = sc_SYMBOL_PREFIX; return sym + "s" + counter + "~" + "^sC-GeNsYm "; }; })(); /*** META ((export #t) (type bool)) */ function sc_isEqual(o1, o2) { return ( o1 === o2 || (sc_isPair(o1) && sc_isPair(o2) && sc_isPairEqual(o1, o2, sc_isEqual)) || (sc_isVector(o1) && sc_isVector(o2) && sc_isVectorEqual(o1, o2, sc_isEqual)) ); } /*** META ((export number->symbol integer->symbol)) */ function sc_number2symbol(x, radix) { return sc_SYMBOL_PREFIX + sc_number2jsstring(x, radix); } /*** META ((export number->string integer->string)) */ var sc_number2string = sc_number2jsstring; /*** META ((export #t)) */ function sc_symbol2number(s, radix) { return sc_jsstring2number(s.slice(1), radix); } /*** META ((export #t)) */ var sc_string2number = sc_jsstring2number; /*** META ((export #t) (peephole (prefix "+" s))) ;; peephole will only apply if no radix is given. */ function sc_string2integer(s, radix) { if (!radix) return +s; return parseInt(s, radix); } /*** META ((export #t) (peephole (prefix "+"))) */ function sc_string2real(s) { return +s; } /*** META ((export #t) (type bool)) */ function sc_isSymbol(s) { return typeof s === "string" && s.charAt(0) === sc_SYMBOL_PREFIX; } /*** META ((export #t) (peephole (symbol2string_immutable))) */ function sc_symbol2string(s) { return s.slice(1); } /*** META ((export #t) (peephole (prefix "'\\u1E9C' +"))) */ function sc_string2symbol(s) { return sc_SYMBOL_PREFIX + s; } /*** META ((export symbol-append) (peephole (symbolAppend_immutable))) */ function sc_symbolAppend() { var res = sc_SYMBOL_PREFIX; for (var i = 0; i < arguments.length; i++) res += arguments[i].slice(1); return res; } /*** META ((export #t) (peephole (postfix ".val"))) */ function sc_char2string(c) { return c.val; } /*** META ((export #t) (peephole (hole 1 "'\\u1E9C' + " c ".val"))) */ function sc_char2symbol(c) { return sc_SYMBOL_PREFIX + c.val; } /*** META ((export #t) (type bool)) */ function sc_isString(s) { return typeof s === "string" && s.charAt(0) !== sc_SYMBOL_PREFIX; } /*** META ((export #t)) */ var sc_makeString = sc_makejsString; /*** META ((export #t)) */ function sc_string() { for (var i = 0; i < arguments.length; i++) arguments[i] = arguments[i].val; return "".concat.apply("", arguments); } /*** META ((export #t) (peephole (postfix ".length"))) */ function sc_stringLength(s) { return s.length; } /*** META ((export #t)) */ function sc_stringRef(s, k) { return new sc_Char(s.charAt(k)); } /* there's no stringSet in the immutable version function sc_stringSet(s, k, c) */ /*** META ((export string=?) (type bool) (peephole (hole 2 str1 " === " str2))) */ function sc_isStringEqual(s1, s2) { return s1 === s2; } /*** META ((export string?) (type bool) (peephole (hole 2 str1 " > " str2))) */ function sc_isStringGreater(s1, s2) { return s1 > s2; } /*** META ((export string<=?) (type bool) (peephole (hole 2 str1 " <= " str2))) */ function sc_isStringLessEqual(s1, s2) { return s1 <= s2; } /*** META ((export string>=?) (type bool) (peephole (hole 2 str1 " >= " str2))) */ function sc_isStringGreaterEqual(s1, s2) { return s1 >= s2; } /*** META ((export string-ci=?) (type bool) (peephole (hole 2 str1 ".toLowerCase() === " str2 ".toLowerCase()"))) */ function sc_isStringCIEqual(s1, s2) { return s1.toLowerCase() === s2.toLowerCase(); } /*** META ((export string-ci?) (type bool) (peephole (hole 2 str1 ".toLowerCase() > " str2 ".toLowerCase()"))) */ function sc_isStringCIGreater(s1, s2) { return s1.toLowerCase() > s2.toLowerCase(); } /*** META ((export string-ci<=?) (type bool) (peephole (hole 2 str1 ".toLowerCase() <= " str2 ".toLowerCase()"))) */ function sc_isStringCILessEqual(s1, s2) { return s1.toLowerCase() <= s2.toLowerCase(); } /*** META ((export string-ci>=?) (type bool) (peephole (hole 2 str1 ".toLowerCase() >= " str2 ".toLowerCase()"))) */ function sc_isStringCIGreaterEqual(s1, s2) { return s1.toLowerCase() >= s2.toLowerCase(); } /*** META ((export #t) (peephole (hole 3 s ".substring(" start ", " end ")"))) */ function sc_substring(s, start, end) { return s.substring(start, end); } /*** META ((export #t)) */ function sc_isSubstring_at(s1, s2, i) { return s2 == s1.substring(i, i + s2.length); } /*** META ((export #t) (peephole (infix 0 #f "+" "''"))) */ function sc_stringAppend() { return "".concat.apply("", arguments); } /*** META ((export #t)) */ var sc_string2list = sc_jsstring2list; /*** META ((export #t)) */ var sc_list2string = sc_list2jsstring; /*** META ((export #t) (peephole (id))) */ function sc_stringCopy(s) { return s; } /* there's no string-fill in the immutable version function sc_stringFill(s, c) */ /*** META ((export #t) (peephole (postfix ".slice(1)"))) */ function sc_keyword2string(o) { return o.slice(1); } /*** META ((export #t) (peephole (prefix "'\\u1E9D' +"))) */ function sc_string2keyword(o) { return sc_KEYWORD_PREFIX + o; } String.prototype.sc_toDisplayString = function () { if (this.charAt(0) === sc_SYMBOL_PREFIX) // TODO: care for symbols with spaces (escape-chars symbols). return this.slice(1); else if (this.charAt(0) === sc_KEYWORD_PREFIX) return ":" + this.slice(1); else return this.toString(); }; String.prototype.sc_toWriteString = function () { if (this.charAt(0) === sc_SYMBOL_PREFIX) // TODO: care for symbols with spaces (escape-chars symbols). return this.slice(1); else if (this.charAt(0) === sc_KEYWORD_PREFIX) return ":" + this.slice(1); else return '"' + sc_escapeWriteString(this) + '"'; }; /* Exported Variables */ var BgL_testzd2boyerzd2; var BgL_nboyerzd2benchmarkzd2; var BgL_setupzd2boyerzd2; /* End Exports */ var translate_term_nboyer; var translate_args_nboyer; var untranslate_term_nboyer; var BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer; var BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer; var translate_alist_nboyer; var apply_subst_nboyer; var apply_subst_lst_nboyer; var tautologyp_nboyer; var if_constructor_nboyer; var rewrite_count_nboyer; var rewrite_nboyer; var rewrite_args_nboyer; var unify_subst_nboyer; var one_way_unify1_nboyer; var false_term_nboyer; var true_term_nboyer; var trans_of_implies1_nboyer; var is_term_equal_nboyer; var is_term_member_nboyer; var const_nboyer; var sc_const_3_nboyer; var sc_const_4_nboyer; { sc_const_4_nboyer = new sc_Pair( "\u1E9Cimplies", new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cz", new sc_Pair("\u1E9Cu", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cu", new sc_Pair("\u1E9Cw", null)) ), null ) ) ), null ) ) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cw", null)) ), null ) ) ); sc_const_3_nboyer = sc_list( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Ccompile", new sc_Pair("\u1E9Cform", null)), new sc_Pair( new sc_Pair( "\u1E9Creverse", new sc_Pair( new sc_Pair( "\u1E9Ccodegen", new sc_Pair( new sc_Pair( "\u1E9Coptimize", new sc_Pair("\u1E9Cform", null) ), new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ceqp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cy", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cgreaterp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clesseqp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cgreatereqp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cboolean", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Ct", null), null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Cf", null), null) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ciff", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Ceven1", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair("\u1E9Ct", null), new sc_Pair( new sc_Pair( "\u1E9Codd", new sc_Pair( new sc_Pair("\u1E9Csub1", new sc_Pair("\u1E9Cx", null)), null ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ccountps-", new sc_Pair("\u1E9Cl", new sc_Pair("\u1E9Cpred", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ccountps-loop", new sc_Pair( "\u1E9Cl", new sc_Pair( "\u1E9Cpred", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cfact-", new sc_Pair("\u1E9Ci", null)), new sc_Pair( new sc_Pair( "\u1E9Cfact-loop", new sc_Pair("\u1E9Ci", new sc_Pair(1, null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Creverse-", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Creverse-loop", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdivides", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Czerop", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cassume-true", new sc_Pair("\u1E9Cvar", new sc_Pair("\u1E9Calist", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cvar", new sc_Pair(new sc_Pair("\u1E9Ct", null), null) ) ), new sc_Pair("\u1E9Calist", null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cassume-false", new sc_Pair("\u1E9Cvar", new sc_Pair("\u1E9Calist", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cvar", new sc_Pair(new sc_Pair("\u1E9Cf", null), null) ) ), new sc_Pair("\u1E9Calist", null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Ctautology-checker", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Ctautologyp", new sc_Pair( new sc_Pair("\u1E9Cnormalize", new sc_Pair("\u1E9Cx", null)), new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cfalsify", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cfalsify1", new sc_Pair( new sc_Pair("\u1E9Cnormalize", new sc_Pair("\u1E9Cx", null)), new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cprime", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cadd1", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ), null ) ) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cprime1", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair("\u1E9Csub1", new sc_Pair("\u1E9Cx", null)), null ) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair("\u1E9Cp", new sc_Pair("\u1E9Cq", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cp", new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cq", new sc_Pair( new sc_Pair("\u1E9Ct", null), new sc_Pair(new sc_Pair("\u1E9Cf", null), null) ) ) ), new sc_Pair(new sc_Pair("\u1E9Cf", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair("\u1E9Cp", new sc_Pair("\u1E9Cq", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cp", new sc_Pair( new sc_Pair("\u1E9Ct", null), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cq", new sc_Pair( new sc_Pair("\u1E9Ct", null), new sc_Pair(new sc_Pair("\u1E9Cf", null), null) ) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cnot", new sc_Pair("\u1E9Cp", null)), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cp", new sc_Pair( new sc_Pair("\u1E9Cf", null), new sc_Pair(new sc_Pair("\u1E9Ct", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cimplies", new sc_Pair("\u1E9Cp", new sc_Pair("\u1E9Cq", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cp", new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cq", new sc_Pair( new sc_Pair("\u1E9Ct", null), new sc_Pair(new sc_Pair("\u1E9Cf", null), null) ) ) ), new sc_Pair(new sc_Pair("\u1E9Ct", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cx", null)), new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Ca", new sc_Pair("\u1E9Cb", new sc_Pair("\u1E9Cc", null)) ) ), new sc_Pair("\u1E9Cd", new sc_Pair("\u1E9Ce", null)) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Ca", new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cb", new sc_Pair("\u1E9Cd", new sc_Pair("\u1E9Ce", null)) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( "\u1E9Cc", new sc_Pair("\u1E9Cd", new sc_Pair("\u1E9Ce", null)) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cx", null)), null ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cz", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Ca", null)), new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cb", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cx", null)) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cc", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cb", null)), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cc", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Czero", null), new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cy", null)), null ) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair( new sc_Pair( "\u1E9Cplus-tree", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), new sc_Pair("\u1E9Ca", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair( new sc_Pair("\u1E9Cplus-tree", new sc_Pair("\u1E9Cx", null)), new sc_Pair("\u1E9Ca", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair( new sc_Pair( "\u1E9Cplus-tree", new sc_Pair("\u1E9Cy", null) ), new sc_Pair("\u1E9Ca", null) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair( new sc_Pair( "\u1E9Cplus-tree", new sc_Pair( new sc_Pair("\u1E9Cplus-fringe", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair("\u1E9Ca", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cfix", new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Ca", null)) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cz", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Creverse", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair( new sc_Pair("\u1E9Creverse", new sc_Pair("\u1E9Cb", null)), new sc_Pair( new sc_Pair("\u1E9Creverse", new sc_Pair("\u1E9Ca", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cz", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cy", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cexec", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cpds", new sc_Pair("\u1E9Cenvrn", null)) ) ), new sc_Pair( new sc_Pair( "\u1E9Cexec", new sc_Pair( "\u1E9Cy", new sc_Pair( new sc_Pair( "\u1E9Cexec", new sc_Pair( "\u1E9Cx", new sc_Pair("\u1E9Cpds", new sc_Pair("\u1E9Cenvrn", null)) ) ), new sc_Pair("\u1E9Cenvrn", null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cmc-flatten", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair( new sc_Pair("\u1E9Cflatten", new sc_Pair("\u1E9Cx", null)), new sc_Pair("\u1E9Cy", null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Ca", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cb", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair("\u1E9Creverse", new sc_Pair("\u1E9Cy", null)), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clength", new sc_Pair( new sc_Pair("\u1E9Creverse", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair( new sc_Pair("\u1E9Clength", new sc_Pair("\u1E9Cx", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair( "\u1E9Ca", new sc_Pair( new sc_Pair( "\u1E9Cintersect", new sc_Pair("\u1E9Cb", new sc_Pair("\u1E9Cc", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cc", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cnth", new sc_Pair( new sc_Pair("\u1E9Czero", null), new sc_Pair("\u1E9Ci", null) ) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cexp", new sc_Pair( "\u1E9Ci", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cj", new sc_Pair("\u1E9Ck", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( new sc_Pair( "\u1E9Cexp", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cj", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cexp", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Ck", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cexp", new sc_Pair( "\u1E9Ci", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cj", new sc_Pair("\u1E9Ck", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cexp", new sc_Pair( new sc_Pair( "\u1E9Cexp", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cj", null)) ), new sc_Pair("\u1E9Ck", null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Creverse-loop", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair( new sc_Pair("\u1E9Creverse", new sc_Pair("\u1E9Cx", null)), new sc_Pair("\u1E9Cy", null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Creverse-loop", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), new sc_Pair( new sc_Pair("\u1E9Creverse", new sc_Pair("\u1E9Cx", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ccount-list", new sc_Pair( "\u1E9Cz", new sc_Pair( new sc_Pair( "\u1E9Csort-lp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Ccount-list", new sc_Pair("\u1E9Cz", new sc_Pair("\u1E9Cx", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ccount-list", new sc_Pair("\u1E9Cz", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cc", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cb", new sc_Pair("\u1E9Cc", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( "\u1E9Cy", new sc_Pair( new sc_Pair( "\u1E9Cquotient", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), null ) ) ), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cx", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cpower-eval", new sc_Pair( new sc_Pair( "\u1E9Cbig-plus1", new sc_Pair( "\u1E9Cl", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cbase", null)) ) ), new sc_Pair("\u1E9Cbase", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Cpower-eval", new sc_Pair("\u1E9Cl", new sc_Pair("\u1E9Cbase", null)) ), new sc_Pair("\u1E9Ci", null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cpower-eval", new sc_Pair( new sc_Pair( "\u1E9Cbig-plus", new sc_Pair( "\u1E9Cx", new sc_Pair( "\u1E9Cy", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cbase", null)) ) ) ), new sc_Pair("\u1E9Cbase", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Ci", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Cpower-eval", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cbase", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cpower-eval", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cbase", null)) ), null ) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair("\u1E9Cy", new sc_Pair(1, null)) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cy", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cy", null)), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cx", null)) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( new sc_Pair( "\u1E9Cquotient", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cj", null)) ), new sc_Pair("\u1E9Ci", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Ci", null)), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cj", null)), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cj", new sc_Pair(1, null)) ), null ) ), null ) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cx", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cy", null)), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cpower-eval", new sc_Pair( new sc_Pair( "\u1E9Cpower-rep", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cbase", null)) ), new sc_Pair("\u1E9Cbase", null) ) ), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Ci", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cpower-eval", new sc_Pair( new sc_Pair( "\u1E9Cbig-plus", new sc_Pair( new sc_Pair( "\u1E9Cpower-rep", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cbase", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cpower-rep", new sc_Pair("\u1E9Cj", new sc_Pair("\u1E9Cbase", null)) ), new sc_Pair( new sc_Pair("\u1E9Czero", null), new sc_Pair("\u1E9Cbase", null) ) ) ) ), new sc_Pair("\u1E9Cbase", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Ci", new sc_Pair("\u1E9Cj", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cgcd", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cgcd", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cnth", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair("\u1E9Ci", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair( new sc_Pair( "\u1E9Cnth", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Ci", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cnth", new sc_Pair( "\u1E9Cb", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( "\u1E9Ci", new sc_Pair( new sc_Pair( "\u1E9Clength", new sc_Pair("\u1E9Ca", null) ), null ) ) ), null ) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cx", null) ) ), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cy", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), new sc_Pair("\u1E9Cx", null) ) ), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cy", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cc", new sc_Pair("\u1E9Cw", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cc", new sc_Pair("\u1E9Cx", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cw", new sc_Pair("\u1E9Cx", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cz", null)) ), new sc_Pair("\u1E9Cz", null) ) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Cb", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cc", null)) ), null ) ) ), new sc_Pair("\u1E9Ca", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cb", new sc_Pair("\u1E9Cc", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Cadd1", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ), new sc_Pair("\u1E9Cz", null) ) ), new sc_Pair( new sc_Pair("\u1E9Cadd1", new sc_Pair("\u1E9Cy", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cz", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cz", null)), null ) ), new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( "\u1E9Cy", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cx", null)), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cgcd", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cz", null)) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( "\u1E9Cz", new sc_Pair( new sc_Pair( "\u1E9Cgcd", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cvalue", new sc_Pair( new sc_Pair("\u1E9Cnormalize", new sc_Pair("\u1E9Cx", null)), new sc_Pair("\u1E9Ca", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cvalue", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Ca", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cflatten", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cy", new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair("\u1E9Cnlistp", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clistp", new sc_Pair( new sc_Pair("\u1E9Cgopher", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair( new sc_Pair("\u1E9Clistp", new sc_Pair("\u1E9Cx", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Csamefringe", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cflatten", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair("\u1E9Cflatten", new sc_Pair("\u1E9Cy", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cgreatest-factor", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cy", null)), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cy", new sc_Pair(1, null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cgreatest-factor", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair(1, null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cx", new sc_Pair(1, null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cnumberp", new sc_Pair( new sc_Pair( "\u1E9Cgreatest-factor", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cy", null)), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cy", new sc_Pair(1, null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Cnumberp", new sc_Pair("\u1E9Cx", null) ), null ) ), null ) ) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ctimes-list", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( new sc_Pair("\u1E9Ctimes-list", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair("\u1E9Ctimes-list", new sc_Pair("\u1E9Cy", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cprime-list", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair("\u1E9Cprime-list", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair("\u1E9Cprime-list", new sc_Pair("\u1E9Cy", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cz", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cw", new sc_Pair("\u1E9Cz", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cz", null)), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cz", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cw", new sc_Pair(1, null)) ), null ) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cgreatereqp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cor", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cx", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cand", new sc_Pair( new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair("\u1E9Cy", new sc_Pair(1, null)) ), null ) ) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), new sc_Pair("\u1E9Cy", null) ) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair(1, null) ) ), new sc_Pair( sc_list( "\u1E9Cand", new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Ca", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), null ) ), new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( "\u1E9Cb", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), null ) ), new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Ca", null)), new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cb", null)), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Csub1", new sc_Pair("\u1E9Ca", null)), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Csub1", new sc_Pair("\u1E9Cb", null)), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( new sc_Pair( "\u1E9Clength", new sc_Pair( new sc_Pair( "\u1E9Cdelete", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cl", null)) ), null ) ), new sc_Pair( new sc_Pair("\u1E9Clength", new sc_Pair("\u1E9Cl", null)), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cl", null)) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Csort2", new sc_Pair( new sc_Pair( "\u1E9Cdelete", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cl", null)) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cdelete", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair("\u1E9Csort2", new sc_Pair("\u1E9Cl", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cdsort", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair("\u1E9Csort2", new sc_Pair("\u1E9Cx", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clength", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cx1", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cx2", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cx3", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cx4", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cx5", new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( "\u1E9Cx6", new sc_Pair("\u1E9Cx7", null) ) ), null ) ) ), null ) ) ), null ) ) ), null ) ) ), null ) ) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( 6, new sc_Pair( new sc_Pair("\u1E9Clength", new sc_Pair("\u1E9Cx7", null)), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Cadd1", new sc_Pair( new sc_Pair("\u1E9Cadd1", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair(2, null) ) ), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cx", null)), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cquotient", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair(2, null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cquotient", new sc_Pair("\u1E9Cy", new sc_Pair(2, null)) ), null ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Csigma", new sc_Pair( new sc_Pair("\u1E9Czero", null), new sc_Pair("\u1E9Ci", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cquotient", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( "\u1E9Ci", new sc_Pair( new sc_Pair("\u1E9Cadd1", new sc_Pair("\u1E9Ci", null)), null ) ) ), new sc_Pair(2, null) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair("\u1E9Cadd1", new sc_Pair("\u1E9Cy", null)), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cy", null)), new sc_Pair( new sc_Pair( "\u1E9Cadd1", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), new sc_Pair( new sc_Pair("\u1E9Cadd1", new sc_Pair("\u1E9Cx", null)), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cz", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cz", null)) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cz", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cnot", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair( "\u1E9Cy", new sc_Pair("\u1E9Cx", null) ) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cfix", new sc_Pair("\u1E9Cx", null) ), new sc_Pair( new sc_Pair( "\u1E9Cfix", new sc_Pair("\u1E9Cz", null) ), null ) ) ), null ) ) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair( new sc_Pair( "\u1E9Cplus-tree", new sc_Pair( new sc_Pair( "\u1E9Cdelete", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ), new sc_Pair("\u1E9Ca", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair( new sc_Pair( "\u1E9Cplus-tree", new sc_Pair("\u1E9Cy", null) ), new sc_Pair("\u1E9Ca", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Ca", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cmeaning", new sc_Pair( new sc_Pair( "\u1E9Cplus-tree", new sc_Pair("\u1E9Cy", null) ), new sc_Pair("\u1E9Ca", null) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair("\u1E9Cadd1", new sc_Pair("\u1E9Cy", null)), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Cnumberp", new sc_Pair("\u1E9Cy", null)), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cx", null)), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cnth", new sc_Pair( new sc_Pair("\u1E9Cnil", null), new sc_Pair("\u1E9Ci", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Ci", null)), new sc_Pair( new sc_Pair("\u1E9Cnil", null), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clast", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Clistp", new sc_Pair("\u1E9Cb", null)), new sc_Pair( new sc_Pair("\u1E9Clast", new sc_Pair("\u1E9Cb", null)), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Clistp", new sc_Pair("\u1E9Ca", null)), new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( new sc_Pair( "\u1E9Ccar", new sc_Pair( new sc_Pair( "\u1E9Clast", new sc_Pair("\u1E9Ca", null) ), null ) ), new sc_Pair("\u1E9Cb", null) ) ), new sc_Pair("\u1E9Cb", null) ) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair("\u1E9Cz", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair( "\u1E9Clessp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Ct", null), new sc_Pair("\u1E9Cz", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair("\u1E9Cf", null), new sc_Pair("\u1E9Cz", null) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cassignment", new sc_Pair( "\u1E9Cx", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair( "\u1E9Cassignedp", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Ca", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cassignment", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Ca", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cassignment", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cb", null)) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Ccar", new sc_Pair( new sc_Pair("\u1E9Cgopher", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Clistp", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Ccar", new sc_Pair( new sc_Pair("\u1E9Cflatten", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cflatten", new sc_Pair( new sc_Pair( "\u1E9Ccdr", new sc_Pair( new sc_Pair("\u1E9Cgopher", new sc_Pair("\u1E9Cx", null)), null ) ), null ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Clistp", new sc_Pair("\u1E9Cx", null)), new sc_Pair( new sc_Pair( "\u1E9Ccdr", new sc_Pair( new sc_Pair("\u1E9Cflatten", new sc_Pair("\u1E9Cx", null)), null ) ), new sc_Pair( new sc_Pair( "\u1E9Ccons", new sc_Pair( new sc_Pair("\u1E9Czero", null), new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cquotient", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Cy", new sc_Pair("\u1E9Cx", null)) ), new sc_Pair("\u1E9Cy", null) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair("\u1E9Czerop", new sc_Pair("\u1E9Cy", null)), new sc_Pair( new sc_Pair("\u1E9Czero", null), new sc_Pair( new sc_Pair("\u1E9Cfix", new sc_Pair("\u1E9Cx", null)), null ) ) ) ), null ) ) ), new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cget", new sc_Pair( "\u1E9Cj", new sc_Pair( new sc_Pair( "\u1E9Cset", new sc_Pair( "\u1E9Ci", new sc_Pair("\u1E9Cval", new sc_Pair("\u1E9Cmem", null)) ) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cif", new sc_Pair( new sc_Pair( "\u1E9Ceqp", new sc_Pair("\u1E9Cj", new sc_Pair("\u1E9Ci", null)) ), new sc_Pair( "\u1E9Cval", new sc_Pair( new sc_Pair( "\u1E9Cget", new sc_Pair("\u1E9Cj", new sc_Pair("\u1E9Cmem", null)) ), null ) ) ) ), null ) ) ) ); const_nboyer = new sc_Pair( new sc_Pair( "\u1E9Cx", new sc_Pair( "\u1E9Cf", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair( "\u1E9Cc", new sc_Pair(new sc_Pair("\u1E9Czero", null), null) ) ), null ) ) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cy", new sc_Pair( "\u1E9Cf", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair( new sc_Pair( "\u1E9Ctimes", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Cc", new sc_Pair("\u1E9Cd", null)) ), null ) ) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cz", new sc_Pair( "\u1E9Cf", new sc_Pair( new sc_Pair( "\u1E9Creverse", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair( new sc_Pair( "\u1E9Cappend", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair(new sc_Pair("\u1E9Cnil", null), null) ) ), null ) ), null ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cu", new sc_Pair( "\u1E9Cequal", new sc_Pair( new sc_Pair( "\u1E9Cplus", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cdifference", new sc_Pair("\u1E9Cx", new sc_Pair("\u1E9Cy", null)) ), null ) ) ) ), new sc_Pair( new sc_Pair( "\u1E9Cw", new sc_Pair( "\u1E9Clessp", new sc_Pair( new sc_Pair( "\u1E9Cremainder", new sc_Pair("\u1E9Ca", new sc_Pair("\u1E9Cb", null)) ), new sc_Pair( new sc_Pair( "\u1E9Cmember", new sc_Pair( "\u1E9Ca", new sc_Pair( new sc_Pair( "\u1E9Clength", new sc_Pair("\u1E9Cb", null) ), null ) ) ), null ) ) ) ), null ) ) ) ) ); BgL_nboyerzd2benchmarkzd2 = function () { var args = null; for (var sc_tmp = arguments.length - 1; sc_tmp >= 0; sc_tmp--) { args = sc_cons(arguments[sc_tmp], args); } var n; return ( (n = args === null ? 0 : args.car), BgL_setupzd2boyerzd2(), BgL_runzd2benchmarkzd2( "nboyer" + sc_number2string(n), 1, function () { return BgL_testzd2boyerzd2(n); }, function (rewrites) { if (sc_isNumber(rewrites)) switch (n) { case 0: return rewrites === 95024; break; case 1: return rewrites === 591777; break; case 2: return rewrites === 1813975; break; case 3: return rewrites === 5375678; break; case 4: return rewrites === 16445406; break; case 5: return rewrites === 51507739; break; default: return true; break; } else return false; } ) ); }; BgL_setupzd2boyerzd2 = function () { return true; }; BgL_testzd2boyerzd2 = function () { return true; }; translate_term_nboyer = function (term) { var lst; return !(term instanceof sc_Pair) ? term : new sc_Pair( BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(term.car), ((lst = term.cdr), lst === null ? null : new sc_Pair( translate_term_nboyer(lst.car), translate_args_nboyer(lst.cdr) )) ); }; translate_args_nboyer = function (lst) { var sc_lst_5; var term; return lst === null ? null : new sc_Pair( ((term = lst.car), !(term instanceof sc_Pair) ? term : new sc_Pair( BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(term.car), translate_args_nboyer(term.cdr) )), ((sc_lst_5 = lst.cdr), sc_lst_5 === null ? null : new sc_Pair( translate_term_nboyer(sc_lst_5.car), translate_args_nboyer(sc_lst_5.cdr) )) ); }; untranslate_term_nboyer = function (term) { var optrOpnd; var tail1131; var L1127; var falseHead1130; var symbol_record; if (!(term instanceof sc_Pair)) return term; else { falseHead1130 = new sc_Pair(null, null); L1127 = term.cdr; tail1131 = falseHead1130; while (!(L1127 === null)) { { tail1131.cdr = new sc_Pair(untranslate_term_nboyer(L1127.car), null); tail1131 = tail1131.cdr; L1127 = L1127.cdr; } } optrOpnd = falseHead1130.cdr; return new sc_Pair( ((symbol_record = term.car), symbol_record[0]), optrOpnd ); } }; BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer = function (sym) { var r; var x; return ( (x = sc_assq(sym, BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer)), x !== false ? x.cdr : ((r = [sym, null]), (BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer = new sc_Pair( new sc_Pair(sym, r), BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer )), r) ); }; BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer = null; translate_alist_nboyer = function (alist) { var sc_alist_6; var term; return alist === null ? null : new sc_Pair( new sc_Pair( alist.car.car, ((term = alist.car.cdr), !(term instanceof sc_Pair) ? term : new sc_Pair( BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(term.car), translate_args_nboyer(term.cdr) )) ), ((sc_alist_6 = alist.cdr), sc_alist_6 === null ? null : new sc_Pair( new sc_Pair( sc_alist_6.car.car, translate_term_nboyer(sc_alist_6.car.cdr) ), translate_alist_nboyer(sc_alist_6.cdr) )) ); }; apply_subst_nboyer = function (alist, term) { var lst; var temp_temp; return !(term instanceof sc_Pair) ? ((temp_temp = sc_assq(term, alist)), temp_temp !== false ? temp_temp.cdr : term) : new sc_Pair( term.car, ((lst = term.cdr), lst === null ? null : new sc_Pair( apply_subst_nboyer(alist, lst.car), apply_subst_lst_nboyer(alist, lst.cdr) )) ); }; apply_subst_lst_nboyer = function (alist, lst) { var sc_lst_7; return lst === null ? null : new sc_Pair( apply_subst_nboyer(alist, lst.car), ((sc_lst_7 = lst.cdr), sc_lst_7 === null ? null : new sc_Pair( apply_subst_nboyer(alist, sc_lst_7.car), apply_subst_lst_nboyer(alist, sc_lst_7.cdr) )) ); }; tautologyp_nboyer = function (sc_x_11, true_lst, false_lst) { var tmp1125; var x; var tmp1126; var sc_x_8; var sc_tmp1125_9; var sc_tmp1126_10; var sc_x_11; var true_lst; var false_lst; while (true) { if ( ((sc_tmp1126_10 = is_term_equal_nboyer(sc_x_11, true_term_nboyer)), sc_tmp1126_10 !== false ? sc_tmp1126_10 : is_term_member_nboyer(sc_x_11, true_lst)) !== false ) return true; else if ( ((sc_tmp1125_9 = is_term_equal_nboyer(sc_x_11, false_term_nboyer)), sc_tmp1125_9 !== false ? sc_tmp1125_9 : is_term_member_nboyer(sc_x_11, false_lst)) !== false ) return false; else if (!(sc_x_11 instanceof sc_Pair)) return false; else if (sc_x_11.car === if_constructor_nboyer) if ( ((sc_x_8 = sc_x_11.cdr.car), (tmp1126 = is_term_equal_nboyer(sc_x_8, true_term_nboyer)), tmp1126 !== false ? tmp1126 : is_term_member_nboyer(sc_x_8, true_lst)) !== false ) sc_x_11 = sc_x_11.cdr.cdr.car; else if ( ((x = sc_x_11.cdr.car), (tmp1125 = is_term_equal_nboyer(x, false_term_nboyer)), tmp1125 !== false ? tmp1125 : is_term_member_nboyer(x, false_lst)) !== false ) sc_x_11 = sc_x_11.cdr.cdr.cdr.car; else if ( tautologyp_nboyer( sc_x_11.cdr.cdr.car, new sc_Pair(sc_x_11.cdr.car, true_lst), false_lst ) !== false ) { false_lst = new sc_Pair(sc_x_11.cdr.car, false_lst); sc_x_11 = sc_x_11.cdr.cdr.cdr.car; } else return false; else return false; } }; if_constructor_nboyer = "\u1E9C*"; rewrite_count_nboyer = 0; rewrite_nboyer = function (term) { var term2; var sc_term_12; var lst; var symbol_record; var sc_lst_13; { ++rewrite_count_nboyer; if (!(term instanceof sc_Pair)) return term; else { sc_term_12 = new sc_Pair( term.car, ((sc_lst_13 = term.cdr), sc_lst_13 === null ? null : new sc_Pair( rewrite_nboyer(sc_lst_13.car), rewrite_args_nboyer(sc_lst_13.cdr) )) ); lst = ((symbol_record = term.car), symbol_record[1]); while (true) { if (lst === null) return sc_term_12; else if ( ((term2 = lst.car.cdr.car), (unify_subst_nboyer = null), one_way_unify1_nboyer(sc_term_12, term2)) !== false ) return rewrite_nboyer( apply_subst_nboyer(unify_subst_nboyer, lst.car.cdr.cdr.car) ); else lst = lst.cdr; } } } }; rewrite_args_nboyer = function (lst) { var sc_lst_14; return lst === null ? null : new sc_Pair( rewrite_nboyer(lst.car), ((sc_lst_14 = lst.cdr), sc_lst_14 === null ? null : new sc_Pair( rewrite_nboyer(sc_lst_14.car), rewrite_args_nboyer(sc_lst_14.cdr) )) ); }; unify_subst_nboyer = "\u1E9C*"; one_way_unify1_nboyer = function (term1, term2) { var lst1; var lst2; var temp_temp; if (!(term2 instanceof sc_Pair)) { temp_temp = sc_assq(term2, unify_subst_nboyer); if (temp_temp !== false) return is_term_equal_nboyer(term1, temp_temp.cdr); else if (sc_isNumber(term2)) return sc_isEqual(term1, term2); else { unify_subst_nboyer = new sc_Pair( new sc_Pair(term2, term1), unify_subst_nboyer ); return true; } } else if (!(term1 instanceof sc_Pair)) return false; else if (term1.car === term2.car) { lst1 = term1.cdr; lst2 = term2.cdr; while (true) { if (lst1 === null) return lst2 === null; else if (lst2 === null) return false; else if (one_way_unify1_nboyer(lst1.car, lst2.car) !== false) { lst1 = lst1.cdr; lst2 = lst2.cdr; } else return false; } } else return false; }; false_term_nboyer = "\u1E9C*"; true_term_nboyer = "\u1E9C*"; trans_of_implies1_nboyer = function (n) { var sc_n_15; return sc_isEqual(n, 1) ? sc_list("\u1E9Cimplies", 0, 1) : sc_list( "\u1E9Cand", sc_list("\u1E9Cimplies", n - 1, n), ((sc_n_15 = n - 1), sc_isEqual(sc_n_15, 1) ? sc_list("\u1E9Cimplies", 0, 1) : sc_list( "\u1E9Cand", sc_list("\u1E9Cimplies", sc_n_15 - 1, sc_n_15), trans_of_implies1_nboyer(sc_n_15 - 1) )) ); }; is_term_equal_nboyer = function (x, y) { var lst1; var lst2; var r2; var r1; if (x instanceof sc_Pair) if (y instanceof sc_Pair) if (((r1 = x.car), (r2 = y.car), r1 === r2) !== false) { lst1 = x.cdr; lst2 = y.cdr; while (true) { if (lst1 === null) return lst2 === null; else if (lst2 === null) return false; else if (is_term_equal_nboyer(lst1.car, lst2.car) !== false) { lst1 = lst1.cdr; lst2 = lst2.cdr; } else return false; } } else return false; else return false; else return sc_isEqual(x, y); }; is_term_member_nboyer = function (x, lst) { var x; var lst; while (true) { if (lst === null) return false; else if (is_term_equal_nboyer(x, lst.car) !== false) return true; else lst = lst.cdr; } }; BgL_setupzd2boyerzd2 = function () { var symbol_record; var value; var BgL_sc_symbolzd2record_16zd2; var sym; var sc_sym_17; var term; var lst; var sc_term_18; var sc_term_19; { BgL_sc_za2symbolzd2recordszd2alistza2_2z00_nboyer = null; if_constructor_nboyer = BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer("\u1E9Cif"); false_term_nboyer = ((sc_term_19 = new sc_Pair("\u1E9Cf", null)), !(sc_term_19 instanceof sc_Pair) ? sc_term_19 : new sc_Pair( BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(sc_term_19.car), translate_args_nboyer(sc_term_19.cdr) )); true_term_nboyer = ((sc_term_18 = new sc_Pair("\u1E9Ct", null)), !(sc_term_18 instanceof sc_Pair) ? sc_term_18 : new sc_Pair( BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(sc_term_18.car), translate_args_nboyer(sc_term_18.cdr) )); lst = sc_const_3_nboyer; while (!(lst === null)) { { term = lst.car; if ( term instanceof sc_Pair && term.car === "\u1E9Cequal" && term.cdr.car instanceof sc_Pair ) { sc_sym_17 = term.cdr.car.car; value = new sc_Pair( !(term instanceof sc_Pair) ? term : new sc_Pair( BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(term.car), translate_args_nboyer(term.cdr) ), ((sym = term.cdr.car.car), (BgL_sc_symbolzd2record_16zd2 = BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(sym)), BgL_sc_symbolzd2record_16zd2[1]) ); symbol_record = BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(sc_sym_17); symbol_record[1] = value; } else sc_error("ADD-LEMMA did not like term: ", term); lst = lst.cdr; } } return true; } }; BgL_testzd2boyerzd2 = function (n) { var optrOpnd; var term; var sc_n_20; var answer; var sc_term_21; var sc_term_22; { rewrite_count_nboyer = 0; term = sc_const_4_nboyer; sc_n_20 = n; while (!(sc_n_20 === 0)) { { term = sc_list("\u1E9Cor", term, new sc_Pair("\u1E9Cf", null)); --sc_n_20; } } sc_term_22 = term; if (!(sc_term_22 instanceof sc_Pair)) optrOpnd = sc_term_22; else optrOpnd = new sc_Pair( BgL_sc_symbolzd2ze3symbolzd2record_1ze3_nboyer(sc_term_22.car), translate_args_nboyer(sc_term_22.cdr) ); sc_term_21 = apply_subst_nboyer( const_nboyer === null ? null : new sc_Pair( new sc_Pair( const_nboyer.car.car, translate_term_nboyer(const_nboyer.car.cdr) ), translate_alist_nboyer(const_nboyer.cdr) ), optrOpnd ); answer = tautologyp_nboyer(rewrite_nboyer(sc_term_21), null, null); sc_write(rewrite_count_nboyer); sc_display(" rewrites"); sc_newline(); if (answer !== false) return rewrite_count_nboyer; else return false; } }; } /* Exported Variables */ var BgL_parsezd2ze3nbzd2treesze3; var BgL_earleyzd2benchmarkzd2; var BgL_parsezd2ze3parsedzf3zc2; var test; var BgL_parsezd2ze3treesz31; var BgL_makezd2parserzd2; /* End Exports */ var const_earley; { const_earley = new sc_Pair( new sc_Pair( "\u1E9Cs", new sc_Pair( new sc_Pair("\u1E9Ca", null), new sc_Pair(new sc_Pair("\u1E9Cs", new sc_Pair("\u1E9Cs", null)), null) ) ), null ); BgL_makezd2parserzd2 = function (grammar, lexer) { var i; var parser_descr; var def_loop; var nb_nts; var names; var steps; var predictors; var enders; var starters; var nts; var sc_names_1; var sc_steps_2; var sc_predictors_3; var sc_enders_4; var sc_starters_5; var nb_confs; var BgL_sc_defzd2loop_6zd2; var BgL_sc_nbzd2nts_7zd2; var sc_nts_8; var BgL_sc_defzd2loop_9zd2; var ind; { ind = function (nt, sc_nts_10) { var i; { i = sc_nts_10.length - 1; while (true) { if (i >= 0) if (sc_isEqual(sc_nts_10[i], nt)) return i; else --i; else return false; } } }; sc_nts_8 = ((BgL_sc_defzd2loop_9zd2 = function (defs, sc_nts_11) { var rule_loop; var head; var def; return defs instanceof sc_Pair ? ((def = defs.car), (head = def.car), (rule_loop = function (rules, sc_nts_12) { var nt; var l; var sc_nts_13; var rule; if (rules instanceof sc_Pair) { rule = rules.car; l = rule; sc_nts_13 = sc_nts_12; while (l instanceof sc_Pair) { { nt = l.car; l = l.cdr; sc_nts_13 = sc_member(nt, sc_nts_13) !== false ? sc_nts_13 : new sc_Pair(nt, sc_nts_13); } } return rule_loop(rules.cdr, sc_nts_13); } else return BgL_sc_defzd2loop_9zd2(defs.cdr, sc_nts_12); }), rule_loop( def.cdr, sc_member(head, sc_nts_11) !== false ? sc_nts_11 : new sc_Pair(head, sc_nts_11) )) : sc_list2vector(sc_reverse(sc_nts_11)); }), BgL_sc_defzd2loop_9zd2(grammar, null)); BgL_sc_nbzd2nts_7zd2 = sc_nts_8.length; nb_confs = ((BgL_sc_defzd2loop_6zd2 = function (defs, BgL_sc_nbzd2confs_14zd2) { var rule_loop; var def; return defs instanceof sc_Pair ? ((def = defs.car), (rule_loop = function (rules, BgL_sc_nbzd2confs_15zd2) { var l; var BgL_sc_nbzd2confs_16zd2; var rule; if (rules instanceof sc_Pair) { rule = rules.car; l = rule; BgL_sc_nbzd2confs_16zd2 = BgL_sc_nbzd2confs_15zd2; while (l instanceof sc_Pair) { { l = l.cdr; ++BgL_sc_nbzd2confs_16zd2; } } return rule_loop(rules.cdr, BgL_sc_nbzd2confs_16zd2 + 1); } else return BgL_sc_defzd2loop_6zd2( defs.cdr, BgL_sc_nbzd2confs_15zd2 ); }), rule_loop(def.cdr, BgL_sc_nbzd2confs_14zd2)) : BgL_sc_nbzd2confs_14zd2; }), BgL_sc_defzd2loop_6zd2(grammar, 0)) + BgL_sc_nbzd2nts_7zd2; sc_starters_5 = sc_makeVector(BgL_sc_nbzd2nts_7zd2, null); sc_enders_4 = sc_makeVector(BgL_sc_nbzd2nts_7zd2, null); sc_predictors_3 = sc_makeVector(BgL_sc_nbzd2nts_7zd2, null); sc_steps_2 = sc_makeVector(nb_confs, false); sc_names_1 = sc_makeVector(nb_confs, false); nts = sc_nts_8; starters = sc_starters_5; enders = sc_enders_4; predictors = sc_predictors_3; steps = sc_steps_2; names = sc_names_1; nb_nts = sc_nts_8.length; i = nb_nts - 1; while (i >= 0) { { sc_steps_2[i] = i - nb_nts; sc_names_1[i] = sc_list(sc_nts_8[i], 0); sc_enders_4[i] = sc_list(i); --i; } } def_loop = function (defs, conf) { var rule_loop; var head; var def; return defs instanceof sc_Pair ? ((def = defs.car), (head = def.car), (rule_loop = function (rules, conf, rule_num) { var i; var sc_i_17; var nt; var l; var sc_conf_18; var sc_i_19; var rule; if (rules instanceof sc_Pair) { rule = rules.car; names[conf] = sc_list(head, rule_num); sc_i_19 = ind(head, nts); starters[sc_i_19] = new sc_Pair(conf, starters[sc_i_19]); l = rule; sc_conf_18 = conf; while (l instanceof sc_Pair) { { nt = l.car; steps[sc_conf_18] = ind(nt, nts); sc_i_17 = ind(nt, nts); predictors[sc_i_17] = new sc_Pair( sc_conf_18, predictors[sc_i_17] ); l = l.cdr; ++sc_conf_18; } } steps[sc_conf_18] = ind(head, nts) - nb_nts; i = ind(head, nts); enders[i] = new sc_Pair(sc_conf_18, enders[i]); return rule_loop(rules.cdr, sc_conf_18 + 1, rule_num + 1); } else return def_loop(defs.cdr, conf); }), rule_loop(def.cdr, conf, 1)) : undefined; }; def_loop(grammar, sc_nts_8.length); parser_descr = [ lexer, sc_nts_8, sc_starters_5, sc_enders_4, sc_predictors_3, sc_steps_2, sc_names_1, ]; return function (input) { var optrOpnd; var sc_optrOpnd_20; var sc_optrOpnd_21; var sc_optrOpnd_22; var loop1; var BgL_sc_stateza2_23za2; var toks; var BgL_sc_nbzd2nts_24zd2; var sc_steps_25; var sc_enders_26; var state_num; var BgL_sc_statesza2_27za2; var states; var i; var conf; var l; var tok_nts; var sc_i_28; var sc_i_29; var l1; var l2; var tok; var tail1129; var L1125; var goal_enders; var BgL_sc_statesza2_30za2; var BgL_sc_nbzd2nts_31zd2; var BgL_sc_nbzd2confs_32zd2; var nb_toks; var goal_starters; var sc_states_33; var BgL_sc_nbzd2confs_34zd2; var BgL_sc_nbzd2toks_35zd2; var sc_toks_36; var falseHead1128; var sc_names_37; var sc_steps_38; var sc_predictors_39; var sc_enders_40; var sc_starters_41; var sc_nts_42; var lexer; var sc_ind_43; var make_states; var BgL_sc_confzd2setzd2getza2_44za2; var conf_set_merge_new_bang; var conf_set_adjoin; var BgL_sc_confzd2setzd2adjoinza2_45za2; var BgL_sc_confzd2setzd2adjoinza2za2_46z00; var conf_set_union; var forw; var is_parsed; var deriv_trees; var BgL_sc_derivzd2treesza2_47z70; var nb_deriv_trees; var BgL_sc_nbzd2derivzd2treesza2_48za2; { sc_ind_43 = function (nt, sc_nts_49) { var i; { i = sc_nts_49.length - 1; while (true) { if (i >= 0) if (sc_isEqual(sc_nts_49[i], nt)) return i; else --i; else return false; } } }; make_states = function ( BgL_sc_nbzd2toks_50zd2, BgL_sc_nbzd2confs_51zd2 ) { var v; var i; var sc_states_52; { sc_states_52 = sc_makeVector(BgL_sc_nbzd2toks_50zd2 + 1, false); i = BgL_sc_nbzd2toks_50zd2; while (i >= 0) { { v = sc_makeVector(BgL_sc_nbzd2confs_51zd2 + 1, false); v[0] = -1; sc_states_52[i] = v; --i; } } return sc_states_52; } }; BgL_sc_confzd2setzd2getza2_44za2 = function ( state, BgL_sc_statezd2num_53zd2, sc_conf_54 ) { var conf_set; var BgL_sc_confzd2set_55zd2; return ( (BgL_sc_confzd2set_55zd2 = state[sc_conf_54 + 1]), BgL_sc_confzd2set_55zd2 !== false ? BgL_sc_confzd2set_55zd2 : ((conf_set = sc_makeVector( BgL_sc_statezd2num_53zd2 + 6, false )), (conf_set[1] = -3), (conf_set[2] = -1), (conf_set[3] = -1), (conf_set[4] = -1), (state[sc_conf_54 + 1] = conf_set), conf_set) ); }; conf_set_merge_new_bang = function (conf_set) { return ( (conf_set[conf_set[1] + 5] = conf_set[4]), (conf_set[1] = conf_set[3]), (conf_set[3] = -1), (conf_set[4] = -1) ); }; conf_set_adjoin = function (state, conf_set, sc_conf_56, i) { var tail; return ( (tail = conf_set[3]), (conf_set[i + 5] = -1), (conf_set[tail + 5] = i), (conf_set[3] = i), tail < 0 ? ((conf_set[0] = state[0]), (state[0] = sc_conf_56)) : undefined ); }; BgL_sc_confzd2setzd2adjoinza2_45za2 = function ( sc_states_57, BgL_sc_statezd2num_58zd2, l, i ) { var conf_set; var sc_conf_59; var l1; var state; { state = sc_states_57[BgL_sc_statezd2num_58zd2]; l1 = l; while (l1 instanceof sc_Pair) { { sc_conf_59 = l1.car; conf_set = BgL_sc_confzd2setzd2getza2_44za2( state, BgL_sc_statezd2num_58zd2, sc_conf_59 ); if (conf_set[i + 5] === false) { conf_set_adjoin(state, conf_set, sc_conf_59, i); l1 = l1.cdr; } else l1 = l1.cdr; } } return undefined; } }; BgL_sc_confzd2setzd2adjoinza2za2_46z00 = function ( sc_states_60, BgL_sc_statesza2_61za2, BgL_sc_statezd2num_62zd2, sc_conf_63, i ) { var BgL_sc_confzd2setza2_64z70; var BgL_sc_stateza2_65za2; var conf_set; var state; return ( (state = sc_states_60[BgL_sc_statezd2num_62zd2]), ((conf_set = state[sc_conf_63 + 1]), conf_set !== false ? conf_set[i + 5] : false) !== false ? ((BgL_sc_stateza2_65za2 = BgL_sc_statesza2_61za2[BgL_sc_statezd2num_62zd2]), (BgL_sc_confzd2setza2_64z70 = BgL_sc_confzd2setzd2getza2_44za2( BgL_sc_stateza2_65za2, BgL_sc_statezd2num_62zd2, sc_conf_63 )), BgL_sc_confzd2setza2_64z70[i + 5] === false ? conf_set_adjoin( BgL_sc_stateza2_65za2, BgL_sc_confzd2setza2_64z70, sc_conf_63, i ) : undefined, true) : false ); }; conf_set_union = function (state, conf_set, sc_conf_66, other_set) { var i; { i = other_set[2]; while (i >= 0) { if (conf_set[i + 5] === false) { conf_set_adjoin(state, conf_set, sc_conf_66, i); i = other_set[i + 5]; } else i = other_set[i + 5]; } return undefined; } }; forw = function ( sc_states_67, BgL_sc_statezd2num_68zd2, sc_starters_69, sc_enders_70, sc_predictors_71, sc_steps_72, sc_nts_73 ) { var next_set; var next; var conf_set; var ender; var l; var starter_set; var starter; var sc_l_74; var sc_loop1_75; var head; var BgL_sc_confzd2set_76zd2; var BgL_sc_statezd2num_77zd2; var state; var sc_states_78; var preds; var BgL_sc_confzd2set_79zd2; var step; var sc_conf_80; var BgL_sc_nbzd2nts_81zd2; var sc_state_82; { sc_state_82 = sc_states_67[BgL_sc_statezd2num_68zd2]; BgL_sc_nbzd2nts_81zd2 = sc_nts_73.length; while (true) { { sc_conf_80 = sc_state_82[0]; if (sc_conf_80 >= 0) { step = sc_steps_72[sc_conf_80]; BgL_sc_confzd2set_79zd2 = sc_state_82[sc_conf_80 + 1]; head = BgL_sc_confzd2set_79zd2[4]; sc_state_82[0] = BgL_sc_confzd2set_79zd2[0]; conf_set_merge_new_bang(BgL_sc_confzd2set_79zd2); if (step >= 0) { sc_l_74 = sc_starters_69[step]; while (sc_l_74 instanceof sc_Pair) { { starter = sc_l_74.car; starter_set = BgL_sc_confzd2setzd2getza2_44za2( sc_state_82, BgL_sc_statezd2num_68zd2, starter ); if ( starter_set[BgL_sc_statezd2num_68zd2 + 5] === false ) { conf_set_adjoin( sc_state_82, starter_set, starter, BgL_sc_statezd2num_68zd2 ); sc_l_74 = sc_l_74.cdr; } else sc_l_74 = sc_l_74.cdr; } } l = sc_enders_70[step]; while (l instanceof sc_Pair) { { ender = l.car; if ( ((conf_set = sc_state_82[ender + 1]), conf_set !== false ? conf_set[BgL_sc_statezd2num_68zd2 + 5] : false) !== false ) { next = sc_conf_80 + 1; next_set = BgL_sc_confzd2setzd2getza2_44za2( sc_state_82, BgL_sc_statezd2num_68zd2, next ); conf_set_union( sc_state_82, next_set, next, BgL_sc_confzd2set_79zd2 ); l = l.cdr; } else l = l.cdr; } } } else { preds = sc_predictors_71[step + BgL_sc_nbzd2nts_81zd2]; sc_states_78 = sc_states_67; state = sc_state_82; BgL_sc_statezd2num_77zd2 = BgL_sc_statezd2num_68zd2; BgL_sc_confzd2set_76zd2 = BgL_sc_confzd2set_79zd2; sc_loop1_75 = function (l) { var sc_state_83; var BgL_sc_nextzd2set_84zd2; var sc_next_85; var pred_set; var i; var pred; if (l instanceof sc_Pair) { pred = l.car; i = head; while (i >= 0) { { pred_set = ((sc_state_83 = sc_states_78[i]), sc_state_83[pred + 1]); if (pred_set !== false) { sc_next_85 = pred + 1; BgL_sc_nextzd2set_84zd2 = BgL_sc_confzd2setzd2getza2_44za2( state, BgL_sc_statezd2num_77zd2, sc_next_85 ); conf_set_union( state, BgL_sc_nextzd2set_84zd2, sc_next_85, pred_set ); } i = BgL_sc_confzd2set_76zd2[i + 5]; } } return sc_loop1_75(l.cdr); } else return undefined; }; sc_loop1_75(preds); } } else return undefined; } } } }; is_parsed = function ( nt, i, j, sc_nts_86, sc_enders_87, sc_states_88 ) { var conf_set; var state; var sc_conf_89; var l; var BgL_sc_ntza2_90za2; { BgL_sc_ntza2_90za2 = sc_ind_43(nt, sc_nts_86); if (BgL_sc_ntza2_90za2 !== false) { sc_nts_86.length; l = sc_enders_87[BgL_sc_ntza2_90za2]; while (true) { if (l instanceof sc_Pair) { sc_conf_89 = l.car; if ( ((state = sc_states_88[j]), (conf_set = state[sc_conf_89 + 1]), conf_set !== false ? conf_set[i + 5] : false) !== false ) return true; else l = l.cdr; } else return false; } } else return false; } }; deriv_trees = function ( sc_conf_91, i, j, sc_enders_92, sc_steps_93, sc_names_94, sc_toks_95, sc_states_96, BgL_sc_nbzd2nts_97zd2 ) { var sc_loop1_98; var prev; var name; return ( (name = sc_names_94[sc_conf_91]), name !== false ? sc_conf_91 < BgL_sc_nbzd2nts_97zd2 ? sc_list(sc_list(name, sc_toks_95[i].car)) : sc_list(sc_list(name)) : ((prev = sc_conf_91 - 1), (sc_loop1_98 = function (l1, l2) { var loop2; var ender_set; var state; var ender; var l1; var l2; while (true) { if (l1 instanceof sc_Pair) { ender = l1.car; ender_set = ((state = sc_states_96[j]), state[ender + 1]); if (ender_set !== false) { loop2 = function (k, l2) { var loop3; var ender_trees; var prev_trees; var conf_set; var sc_state_99; var k; var l2; while (true) { if (k >= 0) if ( k >= i && ((sc_state_99 = sc_states_96[k]), (conf_set = sc_state_99[prev + 1]), conf_set !== false ? conf_set[i + 5] : false) !== false ) { prev_trees = deriv_trees( prev, i, k, sc_enders_92, sc_steps_93, sc_names_94, sc_toks_95, sc_states_96, BgL_sc_nbzd2nts_97zd2 ); ender_trees = deriv_trees( ender, k, j, sc_enders_92, sc_steps_93, sc_names_94, sc_toks_95, sc_states_96, BgL_sc_nbzd2nts_97zd2 ); loop3 = function (l3, l2) { var l4; var sc_l2_100; var ender_tree; if (l3 instanceof sc_Pair) { ender_tree = sc_list(l3.car); l4 = prev_trees; sc_l2_100 = l2; while (l4 instanceof sc_Pair) { { sc_l2_100 = new sc_Pair( sc_append(l4.car, ender_tree), sc_l2_100 ); l4 = l4.cdr; } } return loop3(l3.cdr, sc_l2_100); } else return loop2(ender_set[k + 5], l2); }; return loop3(ender_trees, l2); } else k = ender_set[k + 5]; else return sc_loop1_98(l1.cdr, l2); } }; return loop2(ender_set[2], l2); } else l1 = l1.cdr; } else return l2; } }), sc_loop1_98(sc_enders_92[sc_steps_93[prev]], null)) ); }; BgL_sc_derivzd2treesza2_47z70 = function ( nt, i, j, sc_nts_101, sc_enders_102, sc_steps_103, sc_names_104, sc_toks_105, sc_states_106 ) { var conf_set; var state; var sc_conf_107; var l; var trees; var BgL_sc_nbzd2nts_108zd2; var BgL_sc_ntza2_109za2; { BgL_sc_ntza2_109za2 = sc_ind_43(nt, sc_nts_101); if (BgL_sc_ntza2_109za2 !== false) { BgL_sc_nbzd2nts_108zd2 = sc_nts_101.length; l = sc_enders_102[BgL_sc_ntza2_109za2]; trees = null; while (l instanceof sc_Pair) { { sc_conf_107 = l.car; if ( ((state = sc_states_106[j]), (conf_set = state[sc_conf_107 + 1]), conf_set !== false ? conf_set[i + 5] : false) !== false ) { l = l.cdr; trees = sc_append( deriv_trees( sc_conf_107, i, j, sc_enders_102, sc_steps_103, sc_names_104, sc_toks_105, sc_states_106, BgL_sc_nbzd2nts_108zd2 ), trees ); } else l = l.cdr; } } return trees; } else return false; } }; nb_deriv_trees = function ( sc_conf_110, i, j, sc_enders_111, sc_steps_112, sc_toks_113, sc_states_114, BgL_sc_nbzd2nts_115zd2 ) { var sc_loop1_116; var tmp1124; var prev; return ( (prev = sc_conf_110 - 1), ((tmp1124 = sc_conf_110 < BgL_sc_nbzd2nts_115zd2), tmp1124 !== false ? tmp1124 : sc_steps_112[prev] < 0) !== false ? 1 : ((sc_loop1_116 = function (l, sc_n_118) { var nb_ender_trees; var nb_prev_trees; var conf_set; var state; var k; var n; var ender_set; var sc_state_117; var ender; var l; var sc_n_118; while (true) { if (l instanceof sc_Pair) { ender = l.car; ender_set = ((sc_state_117 = sc_states_114[j]), sc_state_117[ender + 1]); if (ender_set !== false) { k = ender_set[2]; n = sc_n_118; while (k >= 0) { if ( k >= i && ((state = sc_states_114[k]), (conf_set = state[prev + 1]), conf_set !== false ? conf_set[i + 5] : false) !== false ) { nb_prev_trees = nb_deriv_trees( prev, i, k, sc_enders_111, sc_steps_112, sc_toks_113, sc_states_114, BgL_sc_nbzd2nts_115zd2 ); nb_ender_trees = nb_deriv_trees( ender, k, j, sc_enders_111, sc_steps_112, sc_toks_113, sc_states_114, BgL_sc_nbzd2nts_115zd2 ); k = ender_set[k + 5]; n += nb_prev_trees * nb_ender_trees; } else k = ender_set[k + 5]; } return sc_loop1_116(l.cdr, n); } else l = l.cdr; } else return sc_n_118; } }), sc_loop1_116(sc_enders_111[sc_steps_112[prev]], 0)) ); }; BgL_sc_nbzd2derivzd2treesza2_48za2 = function ( nt, i, j, sc_nts_119, sc_enders_120, sc_steps_121, sc_toks_122, sc_states_123 ) { var conf_set; var state; var sc_conf_124; var l; var nb_trees; var BgL_sc_nbzd2nts_125zd2; var BgL_sc_ntza2_126za2; { BgL_sc_ntza2_126za2 = sc_ind_43(nt, sc_nts_119); if (BgL_sc_ntza2_126za2 !== false) { BgL_sc_nbzd2nts_125zd2 = sc_nts_119.length; l = sc_enders_120[BgL_sc_ntza2_126za2]; nb_trees = 0; while (l instanceof sc_Pair) { { sc_conf_124 = l.car; if ( ((state = sc_states_123[j]), (conf_set = state[sc_conf_124 + 1]), conf_set !== false ? conf_set[i + 5] : false) !== false ) { l = l.cdr; nb_trees = nb_deriv_trees( sc_conf_124, i, j, sc_enders_120, sc_steps_121, sc_toks_122, sc_states_123, BgL_sc_nbzd2nts_125zd2 ) + nb_trees; } else l = l.cdr; } } return nb_trees; } else return false; } }; lexer = parser_descr[0]; sc_nts_42 = parser_descr[1]; sc_starters_41 = parser_descr[2]; sc_enders_40 = parser_descr[3]; sc_predictors_39 = parser_descr[4]; sc_steps_38 = parser_descr[5]; sc_names_37 = parser_descr[6]; falseHead1128 = new sc_Pair(null, null); L1125 = lexer(input); tail1129 = falseHead1128; while (!(L1125 === null)) { { tok = L1125.car; l1 = tok.cdr; l2 = null; while (l1 instanceof sc_Pair) { { sc_i_29 = sc_ind_43(l1.car, sc_nts_42); if (sc_i_29 !== false) { l1 = l1.cdr; l2 = new sc_Pair(sc_i_29, l2); } else l1 = l1.cdr; } } sc_optrOpnd_22 = new sc_Pair(tok.car, sc_reverse(l2)); sc_optrOpnd_21 = new sc_Pair(sc_optrOpnd_22, null); tail1129.cdr = sc_optrOpnd_21; tail1129 = tail1129.cdr; L1125 = L1125.cdr; } } sc_optrOpnd_20 = falseHead1128.cdr; sc_toks_36 = sc_list2vector(sc_optrOpnd_20); BgL_sc_nbzd2toks_35zd2 = sc_toks_36.length; BgL_sc_nbzd2confs_34zd2 = sc_steps_38.length; sc_states_33 = make_states( BgL_sc_nbzd2toks_35zd2, BgL_sc_nbzd2confs_34zd2 ); goal_starters = sc_starters_41[0]; BgL_sc_confzd2setzd2adjoinza2_45za2( sc_states_33, 0, goal_starters, 0 ); forw( sc_states_33, 0, sc_starters_41, sc_enders_40, sc_predictors_39, sc_steps_38, sc_nts_42 ); sc_i_28 = 0; while (sc_i_28 < BgL_sc_nbzd2toks_35zd2) { { tok_nts = sc_toks_36[sc_i_28].cdr; BgL_sc_confzd2setzd2adjoinza2_45za2( sc_states_33, sc_i_28 + 1, tok_nts, sc_i_28 ); forw( sc_states_33, sc_i_28 + 1, sc_starters_41, sc_enders_40, sc_predictors_39, sc_steps_38, sc_nts_42 ); ++sc_i_28; } } nb_toks = sc_toks_36.length; BgL_sc_nbzd2confs_32zd2 = sc_steps_38.length; BgL_sc_nbzd2nts_31zd2 = sc_nts_42.length; BgL_sc_statesza2_30za2 = make_states( nb_toks, BgL_sc_nbzd2confs_32zd2 ); goal_enders = sc_enders_40[0]; l = goal_enders; while (l instanceof sc_Pair) { { conf = l.car; BgL_sc_confzd2setzd2adjoinza2za2_46z00( sc_states_33, BgL_sc_statesza2_30za2, nb_toks, conf, 0 ); l = l.cdr; } } i = nb_toks; while (i >= 0) { { states = sc_states_33; BgL_sc_statesza2_27za2 = BgL_sc_statesza2_30za2; state_num = i; sc_enders_26 = sc_enders_40; sc_steps_25 = sc_steps_38; BgL_sc_nbzd2nts_24zd2 = BgL_sc_nbzd2nts_31zd2; toks = sc_toks_36; BgL_sc_stateza2_23za2 = BgL_sc_statesza2_30za2[i]; loop1 = function () { var sc_loop1_127; var prev; var BgL_sc_statesza2_128za2; var sc_states_129; var j; var i; var sc_i_130; var head; var conf_set; var sc_conf_131; { sc_conf_131 = BgL_sc_stateza2_23za2[0]; if (sc_conf_131 >= 0) { conf_set = BgL_sc_stateza2_23za2[sc_conf_131 + 1]; head = conf_set[4]; BgL_sc_stateza2_23za2[0] = conf_set[0]; conf_set_merge_new_bang(conf_set); sc_i_130 = head; while (sc_i_130 >= 0) { { i = sc_i_130; j = state_num; sc_states_129 = states; BgL_sc_statesza2_128za2 = BgL_sc_statesza2_27za2; prev = sc_conf_131 - 1; if ( sc_conf_131 >= BgL_sc_nbzd2nts_24zd2 && sc_steps_25[prev] >= 0 ) { sc_loop1_127 = function (l) { var k; var ender_set; var state; var ender; var l; while (true) { if (l instanceof sc_Pair) { ender = l.car; ender_set = ((state = sc_states_129[j]), state[ender + 1]); if (ender_set !== false) { k = ender_set[2]; while (k >= 0) { { if (k >= i) if ( BgL_sc_confzd2setzd2adjoinza2za2_46z00( sc_states_129, BgL_sc_statesza2_128za2, k, prev, i ) !== false ) BgL_sc_confzd2setzd2adjoinza2za2_46z00( sc_states_129, BgL_sc_statesza2_128za2, j, ender, k ); k = ender_set[k + 5]; } } return sc_loop1_127(l.cdr); } else l = l.cdr; } else return undefined; } }; sc_loop1_127(sc_enders_26[sc_steps_25[prev]]); } sc_i_130 = conf_set[sc_i_130 + 5]; } } return loop1(); } else return undefined; } }; loop1(); --i; } } optrOpnd = BgL_sc_statesza2_30za2; return [ sc_nts_42, sc_starters_41, sc_enders_40, sc_predictors_39, sc_steps_38, sc_names_37, sc_toks_36, optrOpnd, is_parsed, BgL_sc_derivzd2treesza2_47z70, BgL_sc_nbzd2derivzd2treesza2_48za2, ]; } }; } }; BgL_parsezd2ze3parsedzf3zc2 = function (parse, nt, i, j) { var is_parsed; var states; var enders; var nts; return ( (nts = parse[0]), (enders = parse[2]), (states = parse[7]), (is_parsed = parse[8]), is_parsed(nt, i, j, nts, enders, states) ); }; BgL_parsezd2ze3treesz31 = function (parse, nt, i, j) { var BgL_sc_derivzd2treesza2_132z70; var states; var toks; var names; var steps; var enders; var nts; return ( (nts = parse[0]), (enders = parse[2]), (steps = parse[4]), (names = parse[5]), (toks = parse[6]), (states = parse[7]), (BgL_sc_derivzd2treesza2_132z70 = parse[9]), BgL_sc_derivzd2treesza2_132z70( nt, i, j, nts, enders, steps, names, toks, states ) ); }; BgL_parsezd2ze3nbzd2treesze3 = function (parse, nt, i, j) { var BgL_sc_nbzd2derivzd2treesza2_133za2; var states; var toks; var steps; var enders; var nts; return ( (nts = parse[0]), (enders = parse[2]), (steps = parse[4]), (toks = parse[6]), (states = parse[7]), (BgL_sc_nbzd2derivzd2treesza2_133za2 = parse[10]), BgL_sc_nbzd2derivzd2treesza2_133za2( nt, i, j, nts, enders, steps, toks, states ) ); }; test = function (k) { var x; var p; return ( (p = BgL_makezd2parserzd2(const_earley, function (l) { var sc_x_134; var tail1134; var L1130; var falseHead1133; { falseHead1133 = new sc_Pair(null, null); tail1134 = falseHead1133; L1130 = l; while (!(L1130 === null)) { { tail1134.cdr = new sc_Pair( ((sc_x_134 = L1130.car), sc_list(sc_x_134, sc_x_134)), null ); tail1134 = tail1134.cdr; L1130 = L1130.cdr; } } return falseHead1133.cdr; } })), (x = p(sc_vector2list(sc_makeVector(k, "\u1E9Ca")))), sc_length(BgL_parsezd2ze3treesz31(x, "\u1E9Cs", 0, k)) ); }; BgL_earleyzd2benchmarkzd2 = function () { var args = null; for (var sc_tmp = arguments.length - 1; sc_tmp >= 0; sc_tmp--) { args = sc_cons(arguments[sc_tmp], args); } var k; return ( (k = args === null ? 7 : args.car), BgL_runzd2benchmarkzd2( "earley", 1, function () { return test(k); }, function (result) { return sc_display(result), sc_newline(), result == 132; } ) ); }; } /************* END OF GENERATED CODE *************/ // Invoke this function to run a benchmark. // The first argument is a string identifying the benchmark. // The second argument is the number of times to run the benchmark. // The third argument is a function that runs the benchmark. // The fourth argument is a unary function that warns if the result // returned by the benchmark is incorrect. // // Example: // RunBenchmark("new Array()", // 1, // function () { new Array(1000000); } // function (v) { // return (v instanceof Array) && (v.length == 1000000); // }); SC_DEFAULT_OUT = new sc_GenericOutputPort(function (s) {}); SC_ERROR_OUT = SC_DEFAULT_OUT; function RunBenchmark(name, count, run, warn) { for (var n = 0; n < count; ++n) { result = run(); if (!warn(result)) { throw new Error("Earley or Boyer did incorrect number of rewrites"); } } } var BgL_runzd2benchmarkzd2 = RunBenchmark; ================================================ FILE: benchmarks/v8-v7/index.js ================================================ // Copyright 2008 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import { load, print } from "llrt:util"; load("base.js"); load("richards.js"); load("deltablue.js"); load("crypto.js"); load("raytrace.js"); load("earley-boyer.js"); load("regexp.js"); load("splay.js"); load("navier-stokes.js"); var success = true; function PrintResult(name, result) { print(name + ": " + result); } function PrintError(name, error) { PrintResult(name, error); success = false; } function PrintScore(score) { if (success) { print("----"); print("Score (version " + BenchmarkSuite.version + "): " + score); } } BenchmarkSuite.RunSuites({ NotifyResult: PrintResult, NotifyError: PrintError, NotifyScore: PrintScore, }); ================================================ FILE: benchmarks/v8-v7/navier-stokes.js ================================================ /** * Copyright 2012 the V8 project authors. All rights reserved. * Copyright 2009 Oliver Hunt * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ var NavierStokes = new BenchmarkSuite("NavierStokes", 1484000, [ new Benchmark( "NavierStokes", runNavierStokes, setupNavierStokes, tearDownNavierStokes ), ]); var solver = null; function runNavierStokes() { solver.update(); } function setupNavierStokes() { solver = new FluidField(null); solver.setResolution(128, 128); solver.setIterations(20); solver.setDisplayFunction(function () {}); solver.setUICallback(prepareFrame); solver.reset(); } function tearDownNavierStokes() { solver = null; } function addPoints(field) { var n = 64; for (var i = 1; i <= n; i++) { field.setVelocity(i, i, n, n); field.setDensity(i, i, 5); field.setVelocity(i, n - i, -n, -n); field.setDensity(i, n - i, 20); field.setVelocity(128 - i, n + i, -n, -n); field.setDensity(128 - i, n + i, 30); } } var framesTillAddingPoints = 0; var framesBetweenAddingPoints = 5; function prepareFrame(field) { if (framesTillAddingPoints == 0) { addPoints(field); framesTillAddingPoints = framesBetweenAddingPoints; framesBetweenAddingPoints++; } else { framesTillAddingPoints--; } } // Code from Oliver Hunt (http://nerget.com/fluidSim/pressure.js) starts here. function FluidField(canvas) { function addFields(x, s, dt) { for (var i = 0; i < size; i++) x[i] += dt * s[i]; } function set_bnd(b, x) { if (b === 1) { for (var i = 1; i <= width; i++) { x[i] = x[i + rowSize]; x[i + (height + 1) * rowSize] = x[i + height * rowSize]; } for (var j = 1; i <= height; i++) { x[j * rowSize] = -x[1 + j * rowSize]; x[width + 1 + j * rowSize] = -x[width + j * rowSize]; } } else if (b === 2) { for (var i = 1; i <= width; i++) { x[i] = -x[i + rowSize]; x[i + (height + 1) * rowSize] = -x[i + height * rowSize]; } for (var j = 1; j <= height; j++) { x[j * rowSize] = x[1 + j * rowSize]; x[width + 1 + j * rowSize] = x[width + j * rowSize]; } } else { for (var i = 1; i <= width; i++) { x[i] = x[i + rowSize]; x[i + (height + 1) * rowSize] = x[i + height * rowSize]; } for (var j = 1; j <= height; j++) { x[j * rowSize] = x[1 + j * rowSize]; x[width + 1 + j * rowSize] = x[width + j * rowSize]; } } var maxEdge = (height + 1) * rowSize; x[0] = 0.5 * (x[1] + x[rowSize]); x[maxEdge] = 0.5 * (x[1 + maxEdge] + x[height * rowSize]); x[width + 1] = 0.5 * (x[width] + x[width + 1 + rowSize]); x[width + 1 + maxEdge] = 0.5 * (x[width + maxEdge] + x[width + 1 + height * rowSize]); } function lin_solve(b, x, x0, a, c) { if (a === 0 && c === 1) { for (var j = 1; j <= height; j++) { var currentRow = j * rowSize; ++currentRow; for (var i = 0; i < width; i++) { x[currentRow] = x0[currentRow]; ++currentRow; } } set_bnd(b, x); } else { var invC = 1 / c; for (var k = 0; k < iterations; k++) { for (var j = 1; j <= height; j++) { var lastRow = (j - 1) * rowSize; var currentRow = j * rowSize; var nextRow = (j + 1) * rowSize; var lastX = x[currentRow]; ++currentRow; for (var i = 1; i <= width; i++) lastX = x[currentRow] = (x0[currentRow] + a * (lastX + x[++currentRow] + x[++lastRow] + x[++nextRow])) * invC; } set_bnd(b, x); } } } function diffuse(b, x, x0, dt) { var a = 0; lin_solve(b, x, x0, a, 1 + 4 * a); } function lin_solve2(x, x0, y, y0, a, c) { if (a === 0 && c === 1) { for (var j = 1; j <= height; j++) { var currentRow = j * rowSize; ++currentRow; for (var i = 0; i < width; i++) { x[currentRow] = x0[currentRow]; y[currentRow] = y0[currentRow]; ++currentRow; } } set_bnd(1, x); set_bnd(2, y); } else { var invC = 1 / c; for (var k = 0; k < iterations; k++) { for (var j = 1; j <= height; j++) { var lastRow = (j - 1) * rowSize; var currentRow = j * rowSize; var nextRow = (j + 1) * rowSize; var lastX = x[currentRow]; var lastY = y[currentRow]; ++currentRow; for (var i = 1; i <= width; i++) { lastX = x[currentRow] = (x0[currentRow] + a * (lastX + x[currentRow] + x[lastRow] + x[nextRow])) * invC; lastY = y[currentRow] = (y0[currentRow] + a * (lastY + y[++currentRow] + y[++lastRow] + y[++nextRow])) * invC; } } set_bnd(1, x); set_bnd(2, y); } } } function diffuse2(x, x0, y, y0, dt) { var a = 0; lin_solve2(x, x0, y, y0, a, 1 + 4 * a); } function advect(b, d, d0, u, v, dt) { var Wdt0 = dt * width; var Hdt0 = dt * height; var Wp5 = width + 0.5; var Hp5 = height + 0.5; for (var j = 1; j <= height; j++) { var pos = j * rowSize; for (var i = 1; i <= width; i++) { var x = i - Wdt0 * u[++pos]; var y = j - Hdt0 * v[pos]; if (x < 0.5) x = 0.5; else if (x > Wp5) x = Wp5; var i0 = x | 0; var i1 = i0 + 1; if (y < 0.5) y = 0.5; else if (y > Hp5) y = Hp5; var j0 = y | 0; var j1 = j0 + 1; var s1 = x - i0; var s0 = 1 - s1; var t1 = y - j0; var t0 = 1 - t1; var row1 = j0 * rowSize; var row2 = j1 * rowSize; d[pos] = s0 * (t0 * d0[i0 + row1] + t1 * d0[i0 + row2]) + s1 * (t0 * d0[i1 + row1] + t1 * d0[i1 + row2]); } } set_bnd(b, d); } function project(u, v, p, div) { var h = -0.5 / Math.sqrt(width * height); for (var j = 1; j <= height; j++) { var row = j * rowSize; var previousRow = (j - 1) * rowSize; var prevValue = row - 1; var currentRow = row; var nextValue = row + 1; var nextRow = (j + 1) * rowSize; for (var i = 1; i <= width; i++) { div[++currentRow] = h * (u[++nextValue] - u[++prevValue] + v[++nextRow] - v[++previousRow]); p[currentRow] = 0; } } set_bnd(0, div); set_bnd(0, p); lin_solve(0, p, div, 1, 4); var wScale = 0.5 * width; var hScale = 0.5 * height; for (var j = 1; j <= height; j++) { var prevPos = j * rowSize - 1; var currentPos = j * rowSize; var nextPos = j * rowSize + 1; var prevRow = (j - 1) * rowSize; var currentRow = j * rowSize; var nextRow = (j + 1) * rowSize; for (var i = 1; i <= width; i++) { u[++currentPos] -= wScale * (p[++nextPos] - p[++prevPos]); v[currentPos] -= hScale * (p[++nextRow] - p[++prevRow]); } } set_bnd(1, u); set_bnd(2, v); } function dens_step(x, x0, u, v, dt) { addFields(x, x0, dt); diffuse(0, x0, x, dt); advect(0, x, x0, u, v, dt); } function vel_step(u, v, u0, v0, dt) { addFields(u, u0, dt); addFields(v, v0, dt); var temp = u0; u0 = u; u = temp; var temp = v0; v0 = v; v = temp; diffuse2(u, u0, v, v0, dt); project(u, v, u0, v0); var temp = u0; u0 = u; u = temp; var temp = v0; v0 = v; v = temp; advect(1, u, u0, u0, v0, dt); advect(2, v, v0, u0, v0, dt); project(u, v, u0, v0); } var uiCallback = function (d, u, v) {}; function Field(dens, u, v) { // Just exposing the fields here rather than using accessors is a measurable win during display (maybe 5%) // but makes the code ugly. this.setDensity = function (x, y, d) { dens[x + 1 + (y + 1) * rowSize] = d; }; this.getDensity = function (x, y) { return dens[x + 1 + (y + 1) * rowSize]; }; this.setVelocity = function (x, y, xv, yv) { u[x + 1 + (y + 1) * rowSize] = xv; v[x + 1 + (y + 1) * rowSize] = yv; }; this.getXVelocity = function (x, y) { return u[x + 1 + (y + 1) * rowSize]; }; this.getYVelocity = function (x, y) { return v[x + 1 + (y + 1) * rowSize]; }; this.width = function () { return width; }; this.height = function () { return height; }; } function queryUI(d, u, v) { for (var i = 0; i < size; i++) u[i] = v[i] = d[i] = 0.0; uiCallback(new Field(d, u, v)); } this.update = function () { queryUI(dens_prev, u_prev, v_prev); vel_step(u, v, u_prev, v_prev, dt); dens_step(dens, dens_prev, u, v, dt); displayFunc(new Field(dens, u, v)); }; this.setDisplayFunction = function (func) { displayFunc = func; }; this.iterations = function () { return iterations; }; this.setIterations = function (iters) { if (iters > 0 && iters <= 100) iterations = iters; }; this.setUICallback = function (callback) { uiCallback = callback; }; var iterations = 10; var visc = 0.5; var dt = 0.1; var dens; var dens_prev; var u; var u_prev; var v; var v_prev; var width; var height; var rowSize; var size; var displayFunc; function reset() { rowSize = width + 2; size = (width + 2) * (height + 2); dens = new Array(size); dens_prev = new Array(size); u = new Array(size); u_prev = new Array(size); v = new Array(size); v_prev = new Array(size); for (var i = 0; i < size; i++) dens_prev[i] = u_prev[i] = v_prev[i] = dens[i] = u[i] = v[i] = 0; } this.reset = reset; this.setResolution = function (hRes, wRes) { var res = wRes * hRes; if (res > 0 && res < 1000000 && (wRes != width || hRes != height)) { width = wRes; height = hRes; reset(); return true; } return false; }; this.setResolution(64, 64); } ================================================ FILE: benchmarks/v8-v7/raytrace.js ================================================ // The ray tracer code in this file is written by Adam Burmister. It // is available in its original form from: // // http://labs.flog.nz.co/raytracer/ // // It has been modified slightly by Google to work as a standalone // benchmark, but the all the computational code remains // untouched. This file also contains a copy of parts of the Prototype // JavaScript framework which is used by the ray tracer. var RayTrace = new BenchmarkSuite("RayTrace", 739989, [ new Benchmark("RayTrace", renderScene), ]); // Variable used to hold a number that can be used to verify that // the scene was ray traced correctly. var checkNumber; // ------------------------------------------------------------------------ // ------------------------------------------------------------------------ // The following is a copy of parts of the Prototype JavaScript library: // Prototype JavaScript framework, version 1.5.0 // (c) 2005-2007 Sam Stephenson // // Prototype is freely distributable under the terms of an MIT-style license. // For details, see the Prototype web site: http://prototype.conio.net/ var Class = { create: function () { return function () { this.initialize.apply(this, arguments); }; }, }; Object.extend = function (destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; }; // ------------------------------------------------------------------------ // ------------------------------------------------------------------------ // The rest of this file is the actual ray tracer written by Adam // Burmister. It's a concatenation of the following files: // // flog/color.js // flog/light.js // flog/vector.js // flog/ray.js // flog/scene.js // flog/material/basematerial.js // flog/material/solid.js // flog/material/chessboard.js // flog/shape/baseshape.js // flog/shape/sphere.js // flog/shape/plane.js // flog/intersectioninfo.js // flog/camera.js // flog/background.js // flog/engine.js /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Color = Class.create(); Flog.RayTracer.Color.prototype = { red: 0.0, green: 0.0, blue: 0.0, initialize: function (r, g, b) { if (!r) r = 0.0; if (!g) g = 0.0; if (!b) b = 0.0; this.red = r; this.green = g; this.blue = b; }, add: function (c1, c2) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red + c2.red; result.green = c1.green + c2.green; result.blue = c1.blue + c2.blue; return result; }, addScalar: function (c1, s) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red + s; result.green = c1.green + s; result.blue = c1.blue + s; result.limit(); return result; }, subtract: function (c1, c2) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red - c2.red; result.green = c1.green - c2.green; result.blue = c1.blue - c2.blue; return result; }, multiply: function (c1, c2) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red * c2.red; result.green = c1.green * c2.green; result.blue = c1.blue * c2.blue; return result; }, multiplyScalar: function (c1, f) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red * f; result.green = c1.green * f; result.blue = c1.blue * f; return result; }, divideFactor: function (c1, f) { var result = new Flog.RayTracer.Color(0, 0, 0); result.red = c1.red / f; result.green = c1.green / f; result.blue = c1.blue / f; return result; }, limit: function () { this.red = this.red > 0.0 ? (this.red > 1.0 ? 1.0 : this.red) : 0.0; this.green = this.green > 0.0 ? (this.green > 1.0 ? 1.0 : this.green) : 0.0; this.blue = this.blue > 0.0 ? (this.blue > 1.0 ? 1.0 : this.blue) : 0.0; }, distance: function (color) { var d = Math.abs(this.red - color.red) + Math.abs(this.green - color.green) + Math.abs(this.blue - color.blue); return d; }, blend: function (c1, c2, w) { var result = new Flog.RayTracer.Color(0, 0, 0); result = Flog.RayTracer.Color.prototype.add( Flog.RayTracer.Color.prototype.multiplyScalar(c1, 1 - w), Flog.RayTracer.Color.prototype.multiplyScalar(c2, w) ); return result; }, brightness: function () { var r = Math.floor(this.red * 255); var g = Math.floor(this.green * 255); var b = Math.floor(this.blue * 255); return (r * 77 + g * 150 + b * 29) >> 8; }, toString: function () { var r = Math.floor(this.red * 255); var g = Math.floor(this.green * 255); var b = Math.floor(this.blue * 255); return "rgb(" + r + "," + g + "," + b + ")"; }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Light = Class.create(); Flog.RayTracer.Light.prototype = { position: null, color: null, intensity: 10.0, initialize: function (pos, color, intensity) { this.position = pos; this.color = color; this.intensity = intensity ? intensity : 10.0; }, toString: function () { return ( "Light [" + this.position.x + "," + this.position.y + "," + this.position.z + "]" ); }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Vector = Class.create(); Flog.RayTracer.Vector.prototype = { x: 0.0, y: 0.0, z: 0.0, initialize: function (x, y, z) { this.x = x ? x : 0; this.y = y ? y : 0; this.z = z ? z : 0; }, copy: function (vector) { this.x = vector.x; this.y = vector.y; this.z = vector.z; }, normalize: function () { var m = this.magnitude(); return new Flog.RayTracer.Vector(this.x / m, this.y / m, this.z / m); }, magnitude: function () { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); }, cross: function (w) { return new Flog.RayTracer.Vector( -this.z * w.y + this.y * w.z, this.z * w.x - this.x * w.z, -this.y * w.x + this.x * w.y ); }, dot: function (w) { return this.x * w.x + this.y * w.y + this.z * w.z; }, add: function (v, w) { return new Flog.RayTracer.Vector(w.x + v.x, w.y + v.y, w.z + v.z); }, subtract: function (v, w) { if (!w || !v) throw "Vectors must be defined [" + v + "," + w + "]"; return new Flog.RayTracer.Vector(v.x - w.x, v.y - w.y, v.z - w.z); }, multiplyVector: function (v, w) { return new Flog.RayTracer.Vector(v.x * w.x, v.y * w.y, v.z * w.z); }, multiplyScalar: function (v, w) { return new Flog.RayTracer.Vector(v.x * w, v.y * w, v.z * w); }, toString: function () { return "Vector [" + this.x + "," + this.y + "," + this.z + "]"; }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Ray = Class.create(); Flog.RayTracer.Ray.prototype = { position: null, direction: null, initialize: function (pos, dir) { this.position = pos; this.direction = dir; }, toString: function () { return "Ray [" + this.position + "," + this.direction + "]"; }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Scene = Class.create(); Flog.RayTracer.Scene.prototype = { camera: null, shapes: [], lights: [], background: null, initialize: function () { this.camera = new Flog.RayTracer.Camera( new Flog.RayTracer.Vector(0, 0, -5), new Flog.RayTracer.Vector(0, 0, 1), new Flog.RayTracer.Vector(0, 1, 0) ); this.shapes = new Array(); this.lights = new Array(); this.background = new Flog.RayTracer.Background( new Flog.RayTracer.Color(0, 0, 0.5), 0.2 ); }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; if (typeof Flog.RayTracer.Material == "undefined") Flog.RayTracer.Material = {}; Flog.RayTracer.Material.BaseMaterial = Class.create(); Flog.RayTracer.Material.BaseMaterial.prototype = { gloss: 2.0, // [0...infinity] 0 = matt transparency: 0.0, // 0=opaque reflection: 0.0, // [0...infinity] 0 = no reflection refraction: 0.5, hasTexture: false, initialize: function () {}, getColor: function (u, v) {}, wrapUp: function (t) { t = t % 2.0; if (t < -1) t += 2.0; if (t >= 1) t -= 2.0; return t; }, toString: function () { return ( "Material [gloss=" + this.gloss + ", transparency=" + this.transparency + ", hasTexture=" + this.hasTexture + "]" ); }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Material.Solid = Class.create(); Flog.RayTracer.Material.Solid.prototype = Object.extend( new Flog.RayTracer.Material.BaseMaterial(), { initialize: function (color, reflection, refraction, transparency, gloss) { this.color = color; this.reflection = reflection; this.transparency = transparency; this.gloss = gloss; this.hasTexture = false; }, getColor: function (u, v) { return this.color; }, toString: function () { return ( "SolidMaterial [gloss=" + this.gloss + ", transparency=" + this.transparency + ", hasTexture=" + this.hasTexture + "]" ); }, } ); /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Material.Chessboard = Class.create(); Flog.RayTracer.Material.Chessboard.prototype = Object.extend( new Flog.RayTracer.Material.BaseMaterial(), { colorEven: null, colorOdd: null, density: 0.5, initialize: function ( colorEven, colorOdd, reflection, transparency, gloss, density ) { this.colorEven = colorEven; this.colorOdd = colorOdd; this.reflection = reflection; this.transparency = transparency; this.gloss = gloss; this.density = density; this.hasTexture = true; }, getColor: function (u, v) { var t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density); if (t < 0.0) return this.colorEven; else return this.colorOdd; }, toString: function () { return ( "ChessMaterial [gloss=" + this.gloss + ", transparency=" + this.transparency + ", hasTexture=" + this.hasTexture + "]" ); }, } ); /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; if (typeof Flog.RayTracer.Shape == "undefined") Flog.RayTracer.Shape = {}; Flog.RayTracer.Shape.Sphere = Class.create(); Flog.RayTracer.Shape.Sphere.prototype = { initialize: function (pos, radius, material) { this.radius = radius; this.position = pos; this.material = material; }, intersect: function (ray) { var info = new Flog.RayTracer.IntersectionInfo(); info.shape = this; var dst = Flog.RayTracer.Vector.prototype.subtract( ray.position, this.position ); var B = dst.dot(ray.direction); var C = dst.dot(dst) - this.radius * this.radius; var D = B * B - C; if (D > 0) { // intersection! info.isHit = true; info.distance = -B - Math.sqrt(D); info.position = Flog.RayTracer.Vector.prototype.add( ray.position, Flog.RayTracer.Vector.prototype.multiplyScalar( ray.direction, info.distance ) ); info.normal = Flog.RayTracer.Vector.prototype .subtract(info.position, this.position) .normalize(); info.color = this.material.getColor(0, 0); } else { info.isHit = false; } return info; }, toString: function () { return ( "Sphere [position=" + this.position + ", radius=" + this.radius + "]" ); }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; if (typeof Flog.RayTracer.Shape == "undefined") Flog.RayTracer.Shape = {}; Flog.RayTracer.Shape.Plane = Class.create(); Flog.RayTracer.Shape.Plane.prototype = { d: 0.0, initialize: function (pos, d, material) { this.position = pos; this.d = d; this.material = material; }, intersect: function (ray) { var info = new Flog.RayTracer.IntersectionInfo(); var Vd = this.position.dot(ray.direction); if (Vd == 0) return info; // no intersection var t = -(this.position.dot(ray.position) + this.d) / Vd; if (t <= 0) return info; info.shape = this; info.isHit = true; info.position = Flog.RayTracer.Vector.prototype.add( ray.position, Flog.RayTracer.Vector.prototype.multiplyScalar(ray.direction, t) ); info.normal = this.position; info.distance = t; if (this.material.hasTexture) { var vU = new Flog.RayTracer.Vector( this.position.y, this.position.z, -this.position.x ); var vV = vU.cross(this.position); var u = info.position.dot(vU); var v = info.position.dot(vV); info.color = this.material.getColor(u, v); } else { info.color = this.material.getColor(0, 0); } return info; }, toString: function () { return "Plane [" + this.position + ", d=" + this.d + "]"; }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.IntersectionInfo = Class.create(); Flog.RayTracer.IntersectionInfo.prototype = { isHit: false, hitCount: 0, shape: null, position: null, normal: null, color: null, distance: null, initialize: function () { this.color = new Flog.RayTracer.Color(0, 0, 0); }, toString: function () { return "Intersection [" + this.position + "]"; }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Camera = Class.create(); Flog.RayTracer.Camera.prototype = { position: null, lookAt: null, equator: null, up: null, screen: null, initialize: function (pos, lookAt, up) { this.position = pos; this.lookAt = lookAt; this.up = up; this.equator = lookAt.normalize().cross(this.up); this.screen = Flog.RayTracer.Vector.prototype.add( this.position, this.lookAt ); }, getRay: function (vx, vy) { var pos = Flog.RayTracer.Vector.prototype.subtract( this.screen, Flog.RayTracer.Vector.prototype.subtract( Flog.RayTracer.Vector.prototype.multiplyScalar(this.equator, vx), Flog.RayTracer.Vector.prototype.multiplyScalar(this.up, vy) ) ); pos.y = pos.y * -1; var dir = Flog.RayTracer.Vector.prototype.subtract(pos, this.position); var ray = new Flog.RayTracer.Ray(pos, dir.normalize()); return ray; }, toString: function () { return "Ray []"; }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Background = Class.create(); Flog.RayTracer.Background.prototype = { color: null, ambience: 0.0, initialize: function (color, ambience) { this.color = color; this.ambience = ambience; }, }; /* Fake a Flog.* namespace */ if (typeof Flog == "undefined") var Flog = {}; if (typeof Flog.RayTracer == "undefined") Flog.RayTracer = {}; Flog.RayTracer.Engine = Class.create(); Flog.RayTracer.Engine.prototype = { canvas: null /* 2d context we can render to */, initialize: function (options) { this.options = Object.extend( { canvasHeight: 100, canvasWidth: 100, pixelWidth: 2, pixelHeight: 2, renderDiffuse: false, renderShadows: false, renderHighlights: false, renderReflections: false, rayDepth: 2, }, options || {} ); this.options.canvasHeight /= this.options.pixelHeight; this.options.canvasWidth /= this.options.pixelWidth; /* TODO: dynamically include other scripts */ }, setPixel: function (x, y, color) { var pxW, pxH; pxW = this.options.pixelWidth; pxH = this.options.pixelHeight; if (this.canvas) { this.canvas.fillStyle = color.toString(); this.canvas.fillRect(x * pxW, y * pxH, pxW, pxH); } else { if (x === y) { checkNumber += color.brightness(); } // print(x * pxW, y * pxH, pxW, pxH); } }, renderScene: function (scene, canvas) { checkNumber = 0; /* Get canvas */ if (canvas) { this.canvas = canvas.getContext("2d"); } else { this.canvas = null; } var canvasHeight = this.options.canvasHeight; var canvasWidth = this.options.canvasWidth; for (var y = 0; y < canvasHeight; y++) { for (var x = 0; x < canvasWidth; x++) { var yp = ((y * 1.0) / canvasHeight) * 2 - 1; var xp = ((x * 1.0) / canvasWidth) * 2 - 1; var ray = scene.camera.getRay(xp, yp); var color = this.getPixelColor(ray, scene); this.setPixel(x, y, color); } } if (checkNumber !== 2321) { throw new Error("Scene rendered incorrectly"); } }, getPixelColor: function (ray, scene) { var info = this.testIntersection(ray, scene, null); if (info.isHit) { var color = this.rayTrace(info, ray, scene, 0); return color; } return scene.background.color; }, testIntersection: function (ray, scene, exclude) { var hits = 0; var best = new Flog.RayTracer.IntersectionInfo(); best.distance = 2000; for (var i = 0; i < scene.shapes.length; i++) { var shape = scene.shapes[i]; if (shape != exclude) { var info = shape.intersect(ray); if (info.isHit && info.distance >= 0 && info.distance < best.distance) { best = info; hits++; } } } best.hitCount = hits; return best; }, getReflectionRay: function (P, N, V) { var c1 = -N.dot(V); var R1 = Flog.RayTracer.Vector.prototype.add( Flog.RayTracer.Vector.prototype.multiplyScalar(N, 2 * c1), V ); return new Flog.RayTracer.Ray(P, R1); }, rayTrace: function (info, ray, scene, depth) { // Calc ambient var color = Flog.RayTracer.Color.prototype.multiplyScalar( info.color, scene.background.ambience ); var oldColor = color; var shininess = Math.pow(10, info.shape.material.gloss + 1); for (var i = 0; i < scene.lights.length; i++) { var light = scene.lights[i]; // Calc diffuse lighting var v = Flog.RayTracer.Vector.prototype .subtract(light.position, info.position) .normalize(); if (this.options.renderDiffuse) { var L = v.dot(info.normal); if (L > 0.0) { color = Flog.RayTracer.Color.prototype.add( color, Flog.RayTracer.Color.prototype.multiply( info.color, Flog.RayTracer.Color.prototype.multiplyScalar(light.color, L) ) ); } } // The greater the depth the more accurate the colours, but // this is exponentially (!) expensive if (depth <= this.options.rayDepth) { // calculate reflection ray if ( this.options.renderReflections && info.shape.material.reflection > 0 ) { var reflectionRay = this.getReflectionRay( info.position, info.normal, ray.direction ); var refl = this.testIntersection(reflectionRay, scene, info.shape); if (refl.isHit && refl.distance > 0) { refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1); } else { refl.color = scene.background.color; } color = Flog.RayTracer.Color.prototype.blend( color, refl.color, info.shape.material.reflection ); } // Refraction /* TODO */ } /* Render shadows and highlights */ var shadowInfo = new Flog.RayTracer.IntersectionInfo(); if (this.options.renderShadows) { var shadowRay = new Flog.RayTracer.Ray(info.position, v); shadowInfo = this.testIntersection(shadowRay, scene, info.shape); if ( shadowInfo.isHit && shadowInfo.shape != info.shape /*&& shadowInfo.shape.type != 'PLANE'*/ ) { var vA = Flog.RayTracer.Color.prototype.multiplyScalar(color, 0.5); var dB = 0.5 * Math.pow(shadowInfo.shape.material.transparency, 0.5); color = Flog.RayTracer.Color.prototype.addScalar(vA, dB); } } // Phong specular highlights if ( this.options.renderHighlights && !shadowInfo.isHit && info.shape.material.gloss > 0 ) { var Lv = Flog.RayTracer.Vector.prototype .subtract(info.shape.position, light.position) .normalize(); var E = Flog.RayTracer.Vector.prototype .subtract(scene.camera.position, info.shape.position) .normalize(); var H = Flog.RayTracer.Vector.prototype.subtract(E, Lv).normalize(); var glossWeight = Math.pow(Math.max(info.normal.dot(H), 0), shininess); color = Flog.RayTracer.Color.prototype.add( Flog.RayTracer.Color.prototype.multiplyScalar( light.color, glossWeight ), color ); } } color.limit(); return color; }, }; function renderScene() { var scene = new Flog.RayTracer.Scene(); scene.camera = new Flog.RayTracer.Camera( new Flog.RayTracer.Vector(0, 0, -15), new Flog.RayTracer.Vector(-0.2, 0, 5), new Flog.RayTracer.Vector(0, 1, 0) ); scene.background = new Flog.RayTracer.Background( new Flog.RayTracer.Color(0.5, 0.5, 0.5), 0.4 ); var sphere = new Flog.RayTracer.Shape.Sphere( new Flog.RayTracer.Vector(-1.5, 1.5, 2), 1.5, new Flog.RayTracer.Material.Solid( new Flog.RayTracer.Color(0, 0.5, 0.5), 0.3, 0.0, 0.0, 2.0 ) ); var sphere1 = new Flog.RayTracer.Shape.Sphere( new Flog.RayTracer.Vector(1, 0.25, 1), 0.5, new Flog.RayTracer.Material.Solid( new Flog.RayTracer.Color(0.9, 0.9, 0.9), 0.1, 0.0, 0.0, 1.5 ) ); var plane = new Flog.RayTracer.Shape.Plane( new Flog.RayTracer.Vector(0.1, 0.9, -0.5).normalize(), 1.2, new Flog.RayTracer.Material.Chessboard( new Flog.RayTracer.Color(1, 1, 1), new Flog.RayTracer.Color(0, 0, 0), 0.2, 0.0, 1.0, 0.7 ) ); scene.shapes.push(plane); scene.shapes.push(sphere); scene.shapes.push(sphere1); var light = new Flog.RayTracer.Light( new Flog.RayTracer.Vector(5, 10, -1), new Flog.RayTracer.Color(0.8, 0.8, 0.8) ); var light1 = new Flog.RayTracer.Light( new Flog.RayTracer.Vector(-3, 5, -15), new Flog.RayTracer.Color(0.8, 0.8, 0.8), 100 ); scene.lights.push(light); scene.lights.push(light1); var imageWidth = 100; // $F('imageWidth'); var imageHeight = 100; // $F('imageHeight'); var pixelSize = "5,5".split(","); // $F('pixelSize').split(','); var renderDiffuse = true; // $F('renderDiffuse'); var renderShadows = true; // $F('renderShadows'); var renderHighlights = true; // $F('renderHighlights'); var renderReflections = true; // $F('renderReflections'); var rayDepth = 2; //$F('rayDepth'); var raytracer = new Flog.RayTracer.Engine({ canvasWidth: imageWidth, canvasHeight: imageHeight, pixelWidth: pixelSize[0], pixelHeight: pixelSize[1], renderDiffuse: renderDiffuse, renderHighlights: renderHighlights, renderShadows: renderShadows, renderReflections: renderReflections, rayDepth: rayDepth, }); raytracer.renderScene(scene, null, 0); } ================================================ FILE: benchmarks/v8-v7/regexp.js ================================================ // Copyright 2010 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Automatically generated on 2009-01-30. Manually updated on 2010-09-17. // This benchmark is generated by loading 50 of the most popular pages // on the web and logging all regexp operations performed. Each // operation is given a weight that is calculated from an estimate of // the popularity of the pages where it occurs and the number of times // it is executed while loading each page. Furthermore the literal // letters in the data are encoded using ROT13 in a way that does not // affect how the regexps match their input. Finally the strings are // scrambled to exercise the regexp engine on different input strings. var RegExp = new BenchmarkSuite("RegExp", 910985, [ new Benchmark("RegExp", RegExpRun, RegExpSetup, RegExpTearDown), ]); var regExpBenchmark = null; function RegExpSetup() { regExpBenchmark = new RegExpBenchmark(); RegExpRun(); // run once to get system initialized } function RegExpRun() { regExpBenchmark.run(); } function RegExpTearDown() { regExpBenchmark = null; } // Returns an array of n different variants of the input string str. // The variants are computed by randomly rotating one random // character. function computeInputVariants(str, n) { var variants = [str]; for (var i = 1; i < n; i++) { var pos = Math.floor(Math.random() * str.length); var chr = String.fromCharCode( (str.charCodeAt(pos) + Math.floor(Math.random() * 128)) % 128 ); variants[i] = str.substring(0, pos) + chr + str.substring(pos + 1, str.length); } return variants; } function RegExpBenchmark() { var re0 = /^ba/; var re1 = /(((\w+):\/\/)([^\/:]*)(:(\d+))?)?([^#?]*)(\?([^#]*))?(#(.*))?/; var re2 = /^\s*|\s*$/g; var re3 = /\bQBZPbageby_cynprubyqre\b/; var re4 = /,/; var re5 = /\bQBZPbageby_cynprubyqre\b/g; var re6 = /^[\s\xa0]+|[\s\xa0]+$/g; var re7 = /(\d*)(\D*)/g; var re8 = /=/; var re9 = /(^|\s)lhv\-h(\s|$)/; var str0 = "Zbmvyyn/5.0 (Jvaqbjf; H; Jvaqbjf AG 5.1; ra-HF) NccyrJroXvg/528.9 (XUGZY, yvxr Trpxb) Puebzr/2.0.157.0 Fnsnev/528.9"; var re10 = /\#/g; var re11 = /\./g; var re12 = /'/g; var re13 = /\?[\w\W]*(sevraqvq|punaaryvq|tebhcvq)=([^\&\?#]*)/i; var str1 = "Fubpxjnir Synfu 9.0 e115"; var re14 = /\s+/g; var re15 = /^\s*(\S*(\s+\S+)*)\s*$/; var re16 = /(-[a-z])/i; var s0 = computeInputVariants("pyvpx", 6511); var s1 = computeInputVariants("uggc://jjj.snprobbx.pbz/ybtva.cuc", 1844); var s2 = computeInputVariants("QBZPbageby_cynprubyqre", 739); var s3 = computeInputVariants("uggc://jjj.snprobbx.pbz/", 598); var s4 = computeInputVariants("uggc://jjj.snprobbx.pbz/fepu.cuc", 454); var s5 = computeInputVariants("qqqq, ZZZ q, llll", 352); var s6 = computeInputVariants("vachggrkg QBZPbageby_cynprubyqre", 312); var s7 = computeInputVariants( "/ZlFcnprUbzrcntr/Vaqrk-FvgrUbzr,10000000", 282 ); var s8 = computeInputVariants("vachggrkg", 177); var s9 = computeInputVariants("528.9", 170); var s10 = computeInputVariants("528", 170); var s11 = computeInputVariants("VCPhygher=ra-HF", 156); var s12 = computeInputVariants("CersreerqPhygher=ra-HF", 156); var s13 = computeInputVariants("xrlcerff", 144); var s14 = computeInputVariants("521", 139); var s15 = computeInputVariants(str0, 139); var s16 = computeInputVariants("qvi .so_zrah", 137); var s17 = computeInputVariants("qvi.so_zrah", 137); var s18 = computeInputVariants("uvqqra_ryrz", 117); var s19 = computeInputVariants( "sevraqfgre_naba=nvq%3Qn6ss9p85n868ro9s059pn854735956o3%26ers%3Q%26df%3Q%26vpgl%3QHF", 95 ); var s20 = computeInputVariants("uggc://ubzr.zlfcnpr.pbz/vaqrk.psz", 93); var s21 = computeInputVariants(str1, 92); var s22 = computeInputVariants("svefg", 85); var s23 = computeInputVariants("uggc://cebsvyr.zlfcnpr.pbz/vaqrk.psz", 85); var s24 = computeInputVariants("ynfg", 85); var s25 = computeInputVariants("qvfcynl", 85); function runBlock0() { for (var i = 0; i < 6511; i++) { re0.exec(s0[i]); } for (var i = 0; i < 1844; i++) { re1.exec(s1[i]); } for (var i = 0; i < 739; i++) { s2[i].replace(re2, ""); } for (var i = 0; i < 598; i++) { re1.exec(s3[i]); } for (var i = 0; i < 454; i++) { re1.exec(s4[i]); } for (var i = 0; i < 352; i++) { /qqqq|qqq|qq|q|ZZZZ|ZZZ|ZZ|Z|llll|ll|l|uu|u|UU|U|zz|z|ff|f|gg|g|sss|ss|s|mmm|mm|m/g.exec( s5[i] ); } for (var i = 0; i < 312; i++) { re3.exec(s6[i]); } for (var i = 0; i < 282; i++) { re4.exec(s7[i]); } for (var i = 0; i < 177; i++) { s8[i].replace(re5, ""); } for (var i = 0; i < 170; i++) { s9[i].replace(re6, ""); re7.exec(s10[i]); } for (var i = 0; i < 156; i++) { re8.exec(s11[i]); re8.exec(s12[i]); } for (var i = 0; i < 144; i++) { re0.exec(s13[i]); } for (var i = 0; i < 139; i++) { s14[i].replace(re6, ""); re7.exec(s14[i]); re9.exec(""); /JroXvg\/(\S+)/.exec(s15[i]); } for (var i = 0; i < 137; i++) { s16[i].replace(re10, ""); s16[i].replace(/\[/g, ""); s17[i].replace(re11, ""); } for (var i = 0; i < 117; i++) { s18[i].replace(re2, ""); } for (var i = 0; i < 95; i++) { /(?:^|;)\s*sevraqfgre_ynat=([^;]*)/.exec(s19[i]); } for (var i = 0; i < 93; i++) { s20[i].replace(re12, ""); re13.exec(s20[i]); } for (var i = 0; i < 92; i++) { s21[i].replace(/([a-zA-Z]|\s)+/, ""); } for (var i = 0; i < 85; i++) { s22[i].replace(re14, ""); s22[i].replace(re15, ""); s23[i].replace(re12, ""); s24[i].replace(re14, ""); s24[i].replace(re15, ""); re16.exec(s25[i]); re13.exec(s23[i]); } } var re17 = /(^|[^\\])\"\\\/Qngr\((-?[0-9]+)\)\\\/\"/g; var str2 = '{"anzr":"","ahzoreSbezng":{"PheeraplQrpvznyQvtvgf":2,"PheeraplQrpvznyFrcnengbe":".","VfErnqBayl":gehr,"PheeraplTebhcFvmrf":[3],"AhzoreTebhcFvmrf":[3],"CrepragTebhcFvmrf":[3],"PheeraplTebhcFrcnengbe":",","PheeraplFlzoby":"\xa4","AnAFlzoby":"AnA","PheeraplArtngvirCnggrea":0,"AhzoreArtngvirCnggrea":1,"CrepragCbfvgvirCnggrea":0,"CrepragArtngvirCnggrea":0,"ArtngvirVasvavglFlzoby":"-Vasvavgl","ArtngvirFvta":"-","AhzoreQrpvznyQvtvgf":2,"AhzoreQrpvznyFrcnengbe":".","AhzoreTebhcFrcnengbe":",","PheeraplCbfvgvirCnggrea":0,"CbfvgvirVasvavglFlzoby":"Vasvavgl","CbfvgvirFvta":"+","CrepragQrpvznyQvtvgf":2,"CrepragQrpvznyFrcnengbe":".","CrepragTebhcFrcnengbe":",","CrepragFlzoby":"%","CreZvyyrFlzoby":"\u2030","AngvirQvtvgf":["0","1","2","3","4","5","6","7","8","9"],"QvtvgFhofgvghgvba":1},"qngrGvzrSbezng":{"NZQrfvtangbe":"NZ","Pnyraqne":{"ZvaFhccbegrqQngrGvzr":"@-62135568000000@","ZnkFhccbegrqQngrGvzr":"@253402300799999@","NytbevguzGlcr":1,"PnyraqneGlcr":1,"Renf":[1],"GjbQvtvgLrneZnk":2029,"VfErnqBayl":gehr},"QngrFrcnengbe":"/","SvefgQnlBsJrrx":0,"PnyraqneJrrxEhyr":0,"ShyyQngrGvzrCnggrea":"qqqq, qq ZZZZ llll UU:zz:ff","YbatQngrCnggrea":"qqqq, qq ZZZZ llll","YbatGvzrCnggrea":"UU:zz:ff","ZbaguQnlCnggrea":"ZZZZ qq","CZQrfvtangbe":"CZ","ESP1123Cnggrea":"qqq, qq ZZZ llll UU\':\'zz\':\'ff \'TZG\'","FubegQngrCnggrea":"ZZ/qq/llll","FubegGvzrCnggrea":"UU:zz","FbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq\'G\'UU\':\'zz\':\'ff","GvzrFrcnengbe":":","HavirefnyFbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq UU\':\'zz\':\'ff\'M\'","LrneZbaguCnggrea":"llll ZZZZ","NooerivngrqQnlAnzrf":["Fha","Zba","Ghr","Jrq","Guh","Sev","Fng"],"FubegrfgQnlAnzrf":["Fh","Zb","Gh","Jr","Gu","Se","Fn"],"QnlAnzrf":["Fhaqnl","Zbaqnl","Ghrfqnl","Jrqarfqnl","Guhefqnl","Sevqnl","Fngheqnl"],"NooerivngrqZbaguAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""],"VfErnqBayl":gehr,"AngvirPnyraqneAnzr":"Tertbevna Pnyraqne","NooerivngrqZbaguTravgvirAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguTravgvirAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""]}}'; var str3 = '{"anzr":"ra-HF","ahzoreSbezng":{"PheeraplQrpvznyQvtvgf":2,"PheeraplQrpvznyFrcnengbe":".","VfErnqBayl":snyfr,"PheeraplTebhcFvmrf":[3],"AhzoreTebhcFvmrf":[3],"CrepragTebhcFvmrf":[3],"PheeraplTebhcFrcnengbe":",","PheeraplFlzoby":"$","AnAFlzoby":"AnA","PheeraplArtngvirCnggrea":0,"AhzoreArtngvirCnggrea":1,"CrepragCbfvgvirCnggrea":0,"CrepragArtngvirCnggrea":0,"ArtngvirVasvavglFlzoby":"-Vasvavgl","ArtngvirFvta":"-","AhzoreQrpvznyQvtvgf":2,"AhzoreQrpvznyFrcnengbe":".","AhzoreTebhcFrcnengbe":",","PheeraplCbfvgvirCnggrea":0,"CbfvgvirVasvavglFlzoby":"Vasvavgl","CbfvgvirFvta":"+","CrepragQrpvznyQvtvgf":2,"CrepragQrpvznyFrcnengbe":".","CrepragTebhcFrcnengbe":",","CrepragFlzoby":"%","CreZvyyrFlzoby":"\u2030","AngvirQvtvgf":["0","1","2","3","4","5","6","7","8","9"],"QvtvgFhofgvghgvba":1},"qngrGvzrSbezng":{"NZQrfvtangbe":"NZ","Pnyraqne":{"ZvaFhccbegrqQngrGvzr":"@-62135568000000@","ZnkFhccbegrqQngrGvzr":"@253402300799999@","NytbevguzGlcr":1,"PnyraqneGlcr":1,"Renf":[1],"GjbQvtvgLrneZnk":2029,"VfErnqBayl":snyfr},"QngrFrcnengbe":"/","SvefgQnlBsJrrx":0,"PnyraqneJrrxEhyr":0,"ShyyQngrGvzrCnggrea":"qqqq, ZZZZ qq, llll u:zz:ff gg","YbatQngrCnggrea":"qqqq, ZZZZ qq, llll","YbatGvzrCnggrea":"u:zz:ff gg","ZbaguQnlCnggrea":"ZZZZ qq","CZQrfvtangbe":"CZ","ESP1123Cnggrea":"qqq, qq ZZZ llll UU\':\'zz\':\'ff \'TZG\'","FubegQngrCnggrea":"Z/q/llll","FubegGvzrCnggrea":"u:zz gg","FbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq\'G\'UU\':\'zz\':\'ff","GvzrFrcnengbe":":","HavirefnyFbegnoyrQngrGvzrCnggrea":"llll\'-\'ZZ\'-\'qq UU\':\'zz\':\'ff\'M\'","LrneZbaguCnggrea":"ZZZZ, llll","NooerivngrqQnlAnzrf":["Fha","Zba","Ghr","Jrq","Guh","Sev","Fng"],"FubegrfgQnlAnzrf":["Fh","Zb","Gh","Jr","Gu","Se","Fn"],"QnlAnzrf":["Fhaqnl","Zbaqnl","Ghrfqnl","Jrqarfqnl","Guhefqnl","Sevqnl","Fngheqnl"],"NooerivngrqZbaguAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""],"VfErnqBayl":snyfr,"AngvirPnyraqneAnzr":"Tertbevna Pnyraqne","NooerivngrqZbaguTravgvirAnzrf":["Wna","Sro","Zne","Nce","Znl","Wha","Why","Nht","Frc","Bpg","Abi","Qrp",""],"ZbaguTravgvirAnzrf":["Wnahnel","Sroehnel","Znepu","Ncevy","Znl","Whar","Whyl","Nhthfg","Frcgrzore","Bpgbore","Abirzore","Qrprzore",""]}}'; var str4 = "HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str5 = "HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var re18 = /^\s+|\s+$/g; var str6 = "uggc://jjj.snprobbx.pbz/vaqrk.cuc"; var re19 = /(?:^|\s+)ba(?:\s+|$)/; var re20 = /[+, ]/; var re21 = /ybnqrq|pbzcyrgr/; var str7 = ';;jvaqbj.IjPurpxZbhfrCbfvgvbaNQ_VQ=shapgvba(r){vs(!r)ine r=jvaqbj.rirag;ine c=-1;vs(d1)c=d1.EbyybssCnary;ine bo=IjTrgBow("IjCnayNQ_VQ_"+c);vs(bo&&bo.fglyr.ivfvovyvgl=="ivfvoyr"){ine fns=IjFns?8:0;ine pheK=r.pyvragK+IjBOFpe("U")+fns,pheL=r.pyvragL+IjBOFpe("I")+fns;ine y=IjBOEC(NQ_VQ,bo,"Y"),g=IjBOEC(NQ_VQ,bo,"G");ine e=y+d1.Cnaryf[c].Jvqgu,o=g+d1.Cnaryf[c].Urvtug;vs((pheKe)||(pheLo)){vs(jvaqbj.IjBaEbyybssNQ_VQ)IjBaEbyybssNQ_VQ(c);ryfr IjPybfrNq(NQ_VQ,c,gehr,"");}ryfr erghea;}IjPnapryZbhfrYvfgrareNQ_VQ();};;jvaqbj.IjFrgEbyybssCnaryNQ_VQ=shapgvba(c){ine z="zbhfrzbir",q=qbphzrag,s=IjPurpxZbhfrCbfvgvbaNQ_VQ;c=IjTc(NQ_VQ,c);vs(d1&&d1.EbyybssCnary>-1)IjPnapryZbhfrYvfgrareNQ_VQ();vs(d1)d1.EbyybssCnary=c;gel{vs(q.nqqRiragYvfgrare)q.nqqRiragYvfgrare(z,s,snyfr);ryfr vs(q.nggnpuRirag)q.nggnpuRirag("ba"+z,s);}pngpu(r){}};;jvaqbj.IjPnapryZbhfrYvfgrareNQ_VQ=shapgvba(){ine z="zbhfrzbir",q=qbphzrag,s=IjPurpxZbhfrCbfvgvbaNQ_VQ;vs(d1)d1.EbyybssCnary=-1;gel{vs(q.erzbirRiragYvfgrare)q.erzbirRiragYvfgrare(z,s,snyfr);ryfr vs(q.qrgnpuRirag)q.qrgnpuRirag("ba"+z,s);}pngpu(r){}};;d1.IjTc=d2(n,c){ine nq=d1;vs(vfAnA(c)){sbe(ine v=0;v0){vs(nq.FzV.yratgu>0)nq.FzV+="/";nq.FzV+=vh[v];nq.FtZ[nq.FtZ.yratgu]=snyfr;}}};;d1.IjYvzvg0=d2(n,f){ine nq=d1,vh=f.fcyvg("/");sbe(ine v=0;v0){vs(nq.OvC.yratgu>0)nq.OvC+="/";nq.OvC+=vh[v];}}};;d1.IjRVST=d2(n,c){jvaqbj["IjCnayNQ_VQ_"+c+"_Bow"]=IjTrgBow("IjCnayNQ_VQ_"+c+"_Bow");vs(jvaqbj["IjCnayNQ_VQ_"+c+"_Bow"]==ahyy)frgGvzrbhg("IjRVST(NQ_VQ,"+c+")",d1.rvsg);};;d1.IjNavzSHC=d2(n,c){ine nq=d1;vs(c>nq.Cnaryf.yratgu)erghea;ine cna=nq.Cnaryf[c],nn=gehr,on=gehr,yn=gehr,en=gehr,cn=nq.Cnaryf[0],sf=nq.ShF,j=cn.Jvqgu,u=cn.Urvtug;vs(j=="100%"){j=sf;en=snyfr;yn=snyfr;}vs(u=="100%"){u=sf;nn=snyfr;on=snyfr;}vs(cn.YnY=="Y")yn=snyfr;vs(cn.YnY=="E")en=snyfr;vs(cn.GnY=="G")nn=snyfr;vs(cn.GnY=="O")on=snyfr;ine k=0,l=0;fjvgpu(nq.NshP%8){pnfr 0:oernx;pnfr 1:vs(nn)l=-sf;oernx;pnfr 2:k=j-sf;oernx;pnfr 3:vs(en)k=j;oernx;pnfr 4:k=j-sf;l=u-sf;oernx;pnfr 5:k=j-sf;vs(on)l=u;oernx;pnfr 6:l=u-sf;oernx;pnfr 7:vs(yn)k=-sf;l=u-sf;oernx;}vs(nq.NshP++ 0)||(nethzragf.yratgu==3&&bG>0))){pyrneGvzrbhg(cay.UgU);cay.UgU=frgGvzrbhg(cay.UvqrNpgvba,(nethzragf.yratgu==3?bG:cay.UvqrGvzrbhgInyhr));}};;d1.IjErfrgGvzrbhg=d2(n,c,bG){c=IjTc(n,c);IjPnapryGvzrbhg(n,c);riny("IjFgnegGvzrbhg(NQ_VQ,c"+(nethzragf.yratgu==3?",bG":"")+")");};;d1.IjErfrgNyyGvzrbhgf=d2(n){sbe(ine c=0;c]/g; var str15 = "FrffvbaQQS2=s6r4579npn4rn2135s904r0s75pp1o5334p6s6pospo12696; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669316860113296&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_dfctwzs-aowb_80=44132r503660"; var str16 = "FrffvbaQQS2=s6r4579npn4rn2135s904r0s75pp1o5334p6s6pospo12696; AFP_zp_dfctwzs-aowb_80=44132r503660; __hgzm=144631658.1231363638.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.965867047679498800.1231363638.1231363638.1231363638.1; __hgzo=144631658.0.10.1231363638; __hgzp=144631658; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669316860113296&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str17 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231363621014&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231363621014&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Scebsvyr.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=348699119.1231363624&tn_fvq=1231363624&tn_uvq=895511034&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str18 = "uggc://jjj.yrobapbva.se/yv"; var str19 = "ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669316860113296&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str20 = "ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669316860113296&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var s67 = computeInputVariants("e115", 27); var s68 = computeInputVariants("qvfcynl", 27); var s69 = computeInputVariants("cbfvgvba", 27); var s70 = computeInputVariants("uggc://jjj.zlfcnpr.pbz/", 27); var s71 = computeInputVariants("cntrivrj", 27); var s72 = computeInputVariants("VC=74.125.75.3", 27); var s73 = computeInputVariants("ra", 27); var s74 = computeInputVariants(str10, 27); var s75 = computeInputVariants(str11, 27); var s76 = computeInputVariants(str12, 27); var s77 = computeInputVariants(str17, 27); var s78 = computeInputVariants(str18, 27); function runBlock3() { for (var i = 0; i < 27; i++) { s67[i].replace(/[A-Za-z]/g, ""); } for (var i = 0; i < 23; i++) { s68[i].replace(re27, ""); s69[i].replace(re27, ""); } for (var i = 0; i < 22; i++) { "unaqyr".replace(re14, ""); "unaqyr".replace(re15, ""); "yvar".replace(re14, ""); "yvar".replace(re15, ""); "cnerag puebzr6 fvatyr1 gno".replace(re14, ""); "cnerag puebzr6 fvatyr1 gno".replace(re15, ""); "fyvqre".replace(re14, ""); "fyvqre".replace(re15, ""); re28.exec(""); } for (var i = 0; i < 21; i++) { s70[i].replace(re12, ""); re13.exec(s70[i]); } for (var i = 0; i < 20; i++) { s71[i].replace(re29, ""); s71[i].replace(re30, ""); re19.exec("ynfg"); re19.exec("ba svefg"); re8.exec(s72[i]); } for (var i = 0; i < 19; i++) { re31.exec(s73[i]); } for (var i = 0; i < 18; i++) { s74[i].split(re32); s75[i].split(re32); s76[i].replace(re33, ""); re8.exec("144631658.0.10.1231363570"); re8.exec( "144631658.1231363570.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.3426875219718084000.1231363570.1231363570.1231363570.1" ); re8.exec(str13); re8.exec(str14); re8.exec( "__hgzn=144631658.3426875219718084000.1231363570.1231363570.1231363570.1" ); re8.exec("__hgzo=144631658.0.10.1231363570"); re8.exec( "__hgzm=144631658.1231363570.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re34.exec(s74[i]); re34.exec(s75[i]); } for (var i = 0; i < 17; i++) { s15[i].match(/zfvr/gi); s15[i].match(/bcren/gi); str15.split(re32); str16.split(re32); "ohggba".replace(re14, ""); "ohggba".replace(re15, ""); "puvyq p1 svefg sylbhg pybfrq".replace(re14, ""); "puvyq p1 svefg sylbhg pybfrq".replace(re15, ""); "pvgvrf".replace(re14, ""); "pvgvrf".replace(re15, ""); "pybfrq".replace(re14, ""); "pybfrq".replace(re15, ""); "qry".replace(re14, ""); "qry".replace(re15, ""); "uqy_zba".replace(re14, ""); "uqy_zba".replace(re15, ""); s77[i].replace(re33, ""); s78[i].replace(/%3P/g, ""); s78[i].replace(/%3R/g, ""); s78[i].replace(/%3q/g, ""); s78[i].replace(re35, ""); "yvaxyvfg16".replace(re14, ""); "yvaxyvfg16".replace(re15, ""); "zvahf".replace(re14, ""); "zvahf".replace(re15, ""); "bcra".replace(re14, ""); "bcra".replace(re15, ""); "cnerag puebzr5 fvatyr1 ps NU".replace(re14, ""); "cnerag puebzr5 fvatyr1 ps NU".replace(re15, ""); "cynlre".replace(re14, ""); "cynlre".replace(re15, ""); "cyhf".replace(re14, ""); "cyhf".replace(re15, ""); "cb_uqy".replace(re14, ""); "cb_uqy".replace(re15, ""); "hyJVzt".replace(re14, ""); "hyJVzt".replace(re15, ""); re8.exec("144631658.0.10.1231363638"); re8.exec( "144631658.1231363638.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.965867047679498800.1231363638.1231363638.1231363638.1" ); re8.exec("4413268q3660"); re8.exec("4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n"); re8.exec("SbeprqRkcvengvba=633669321699093060"); re8.exec("VC=74.125.75.20"); re8.exec(str19); re8.exec(str20); re8.exec("AFP_zp_tfwsbrg-aowb_80=4413268q3660"); re8.exec("FrffvbaQQS2=4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n"); re8.exec( "__hgzn=144631658.965867047679498800.1231363638.1231363638.1231363638.1" ); re8.exec("__hgzo=144631658.0.10.1231363638"); re8.exec( "__hgzm=144631658.1231363638.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re34.exec(str15); re34.exec(str16); } } var re36 = /uers|fep|fryrpgrq/; var re37 = /\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g; var re38 = /^(\w+|\*)$/; var str21 = "FrffvbaQQS2=s15q53p9n372sn76npr13o271n4s3p5r29p235746p908p58; ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669358527244818&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var str22 = "FrffvbaQQS2=s15q53p9n372sn76npr13o271n4s3p5r29p235746p908p58; __hgzm=144631658.1231367822.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.4127520630321984500.1231367822.1231367822.1231367822.1; __hgzo=144631658.0.10.1231367822; __hgzp=144631658; ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669358527244818&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str23 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231367803797&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231367803797&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Szrffntvat.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1192552091.1231367807&tn_fvq=1231367807&tn_uvq=1155446857&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str24 = "ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669358527244818&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str25 = "ZFPhygher=VC=66.249.85.130&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669358527244818&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var str26 = "hy.ynat-fryrpgbe"; var re39 = /\\/g; var re40 = / /g; var re41 = /\/\xc4\/t/; var re42 = /\/\xd6\/t/; var re43 = /\/\xdc\/t/; var re44 = /\/\xdf\/t/; var re45 = /\/\xe4\/t/; var re46 = /\/\xf6\/t/; var re47 = /\/\xfc\/t/; var re48 = /\W/g; var re49 = /uers|fep|fglyr/; var s79 = computeInputVariants(str21, 16); var s80 = computeInputVariants(str22, 16); var s81 = computeInputVariants(str23, 16); var s82 = computeInputVariants(str26, 16); function runBlock4() { for (var i = 0; i < 16; i++) { "".replace(/\*/g, ""); /\bnpgvir\b/.exec("npgvir"); /sversbk/i.exec(s15[i]); re36.exec("glcr"); /zfvr/i.exec(s15[i]); /bcren/i.exec(s15[i]); } for (var i = 0; i < 15; i++) { s79[i].split(re32); s80[i].split(re32); "uggc://ohyyrgvaf.zlfcnpr.pbz/vaqrk.psz".replace(re12, ""); s81[i].replace(re33, ""); "yv".replace(re37, ""); "yv".replace(re18, ""); re8.exec("144631658.0.10.1231367822"); re8.exec( "144631658.1231367822.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.4127520630321984500.1231367822.1231367822.1231367822.1" ); re8.exec(str24); re8.exec(str25); re8.exec( "__hgzn=144631658.4127520630321984500.1231367822.1231367822.1231367822.1" ); re8.exec("__hgzo=144631658.0.10.1231367822"); re8.exec( "__hgzm=144631658.1231367822.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re34.exec(s79[i]); re34.exec(s80[i]); /\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)["']?(.*?)["']?)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g.exec( s82[i] ); re13.exec("uggc://ohyyrgvaf.zlfcnpr.pbz/vaqrk.psz"); re38.exec("yv"); } for (var i = 0; i < 14; i++) { "".replace(re18, ""); "9.0 e115".replace(/(\s+e|\s+o[0-9]+)/, ""); "Funer guvf tnqtrg".replace(//g, ""); "Funer guvf tnqtrg".replace(re39, ""); "uggc://cebsvyrrqvg.zlfcnpr.pbz/vaqrk.psz".replace(re12, ""); "grnfre".replace(re40, ""); "grnfre".replace(re41, ""); "grnfre".replace(re42, ""); "grnfre".replace(re43, ""); "grnfre".replace(re44, ""); "grnfre".replace(re45, ""); "grnfre".replace(re46, ""); "grnfre".replace(re47, ""); "grnfre".replace(re48, ""); re16.exec("znetva-gbc"); re16.exec("cbfvgvba"); re19.exec("gno1"); re9.exec("qz"); re9.exec("qg"); re9.exec("zbqobk"); re9.exec("zbqobkva"); re9.exec("zbqgvgyr"); re13.exec("uggc://cebsvyrrqvg.zlfcnpr.pbz/vaqrk.psz"); re26.exec("/vt/znvytnqtrg"); re49.exec("glcr"); } } var re50 = /(?:^|\s+)fryrpgrq(?:\s+|$)/; var re51 = /\&/g; var re52 = /\+/g; var re53 = /\?/g; var re54 = /\t/g; var re55 = /(\$\{nqiHey\})|(\$nqiHey\b)/g; var re56 = /(\$\{cngu\})|(\$cngu\b)/g; function runBlock5() { for (var i = 0; i < 13; i++) { "purpx".replace(re14, ""); "purpx".replace(re15, ""); "pvgl".replace(re14, ""); "pvgl".replace(re15, ""); "qrpe fyvqrgrkg".replace(re14, ""); "qrpe fyvqrgrkg".replace(re15, ""); "svefg fryrpgrq".replace(re14, ""); "svefg fryrpgrq".replace(re15, ""); "uqy_rag".replace(re14, ""); "uqy_rag".replace(re15, ""); "vape fyvqrgrkg".replace(re14, ""); "vape fyvqrgrkg".replace(re15, ""); "vachggrkg QBZPbageby_cynprubyqre".replace(re5, ""); "cnerag puebzr6 fvatyr1 gno fryrpgrq".replace(re14, ""); "cnerag puebzr6 fvatyr1 gno fryrpgrq".replace(re15, ""); "cb_guz".replace(re14, ""); "cb_guz".replace(re15, ""); "fhozvg".replace(re14, ""); "fhozvg".replace(re15, ""); re50.exec(""); /NccyrJroXvg\/([^\s]*)/.exec(s15[i]); /XUGZY/.exec(s15[i]); } for (var i = 0; i < 12; i++) { "${cebg}://${ubfg}${cngu}/${dz}".replace(/(\$\{cebg\})|(\$cebg\b)/g, ""); "1".replace(re40, ""); "1".replace(re10, ""); "1".replace(re51, ""); "1".replace(re52, ""); "1".replace(re53, ""); "1".replace(re39, ""); "1".replace(re54, ""); "9.0 e115".replace(/^(.*)\..*$/, ""); "9.0 e115".replace(/^.*e(.*)$/, ""); "".replace(re55, ""); ''.replace( re55, "" ); s21[i].replace(/^.*\s+(\S+\s+\S+$)/, ""); "tzk%2Subzrcntr%2Sfgneg%2Sqr%2S".replace(re30, ""); "tzk".replace(re30, ""); "uggc://${ubfg}${cngu}/${dz}".replace(/(\$\{ubfg\})|(\$ubfg\b)/g, ""); "uggc://nqpyvrag.hvzfrei.arg${cngu}/${dz}".replace(re56, ""); "uggc://nqpyvrag.hvzfrei.arg/wf.at/${dz}".replace( /(\$\{dz\})|(\$dz\b)/g, "" ); "frpgvba".replace(re29, ""); "frpgvba".replace(re30, ""); "fvgr".replace(re29, ""); "fvgr".replace(re30, ""); "fcrpvny".replace(re29, ""); "fcrpvny".replace(re30, ""); re36.exec("anzr"); /e/.exec("9.0 e115"); } } var re57 = /##yv4##/gi; var re58 = /##yv16##/gi; var re59 = /##yv19##/gi; var str27 = '##yv4##Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.##yv19##Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.##yv16##Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str28 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.##yv19##Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.##yv16##Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str29 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.##yv19##Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str30 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl.##OE## ##OE## ##N##Yrnea zber##/N##'; var str31 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl. ##N##Yrnea zber##/N##'; var str32 = 'Cbjreshy Zvpebfbsg grpuabybtl urycf svtug fcnz naq vzcebir frphevgl.Trg zber qbar gunaxf gb terngre rnfr naq fcrrq.Ybgf bs fgbentr (5 TO) - zber pbby fghss ba gur jnl. Yrnea zber##/N##'; var str33 = "Bar Jvaqbjf Yvir VQ trgf lbh vagb Ubgznvy, Zrffratre, Kobk YVIR \u2014 naq bgure cynprf lbh frr #~#argjbexybtb#~#"; var re60 = /(?:^|\s+)bss(?:\s+|$)/; var re61 = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/; var re62 = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; var str34 = "${1}://${2}${3}${4}${5}"; var str35 = " O=6gnyg0g4znrrn&o=3&f=gc; Q=_lyu=K3bQZGSxnT4lZzD3OS9GNmV3ZGLkAQxRpTyxNmRlZmRmAmNkAQLRqTImqNZjOUEgpTjQnJ5xMKtgoN--; SCF=qy"; var s83 = computeInputVariants(str27, 11); var s84 = computeInputVariants(str28, 11); var s85 = computeInputVariants(str29, 11); var s86 = computeInputVariants(str30, 11); var s87 = computeInputVariants(str31, 11); var s88 = computeInputVariants(str32, 11); var s89 = computeInputVariants(str33, 11); var s90 = computeInputVariants(str34, 11); function runBlock6() { for (var i = 0; i < 11; i++) { s83[i].replace(/##yv0##/gi, ""); s83[i].replace(re57, ""); s84[i].replace(re58, ""); s85[i].replace(re59, ""); s86[i].replace(/##\/o##/gi, ""); s86[i].replace(/##\/v##/gi, ""); s86[i].replace(/##\/h##/gi, ""); s86[i].replace(/##o##/gi, ""); s86[i].replace(/##oe##/gi, ""); s86[i].replace(/##v##/gi, ""); s86[i].replace(/##h##/gi, ""); s87[i].replace(/##n##/gi, ""); s88[i].replace(/##\/n##/gi, ""); s89[i].replace(/#~#argjbexybtb#~#/g, ""); / Zbovyr\//.exec(s15[i]); /##yv1##/gi.exec(s83[i]); /##yv10##/gi.exec(s84[i]); /##yv11##/gi.exec(s84[i]); /##yv12##/gi.exec(s84[i]); /##yv13##/gi.exec(s84[i]); /##yv14##/gi.exec(s84[i]); /##yv15##/gi.exec(s84[i]); re58.exec(s84[i]); /##yv17##/gi.exec(s85[i]); /##yv18##/gi.exec(s85[i]); re59.exec(s85[i]); /##yv2##/gi.exec(s83[i]); /##yv20##/gi.exec(s86[i]); /##yv21##/gi.exec(s86[i]); /##yv22##/gi.exec(s86[i]); /##yv23##/gi.exec(s86[i]); /##yv3##/gi.exec(s83[i]); re57.exec(s83[i]); /##yv5##/gi.exec(s84[i]); /##yv6##/gi.exec(s84[i]); /##yv7##/gi.exec(s84[i]); /##yv8##/gi.exec(s84[i]); /##yv9##/gi.exec(s84[i]); re8.exec("473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29"); re8.exec("SbeprqRkcvengvba=633669325184628362"); re8.exec("FrffvbaQQS2=473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29"); /AbxvnA[^\/]*/.exec(s15[i]); } for (var i = 0; i < 10; i++) { " bss".replace(/(?:^|\s+)bss(?:\s+|$)/g, ""); s90[i].replace(/(\$\{0\})|(\$0\b)/g, ""); s90[i].replace(/(\$\{1\})|(\$1\b)/g, ""); s90[i].replace(/(\$\{pbzcyrgr\})|(\$pbzcyrgr\b)/g, ""); s90[i].replace(/(\$\{sentzrag\})|(\$sentzrag\b)/g, ""); s90[i].replace(/(\$\{ubfgcbeg\})|(\$ubfgcbeg\b)/g, ""); s90[i].replace(re56, ""); s90[i].replace(/(\$\{cebgbpby\})|(\$cebgbpby\b)/g, ""); s90[i].replace(/(\$\{dhrel\})|(\$dhrel\b)/g, ""); "nqfvmr".replace(re29, ""); "nqfvmr".replace(re30, ""); "uggc://${2}${3}${4}${5}".replace(/(\$\{2\})|(\$2\b)/g, ""); "uggc://wf.hv-cbegny.qr${3}${4}${5}".replace(/(\$\{3\})|(\$3\b)/g, ""); "arjf".replace(re40, ""); "arjf".replace(re41, ""); "arjf".replace(re42, ""); "arjf".replace(re43, ""); "arjf".replace(re44, ""); "arjf".replace(re45, ""); "arjf".replace(re46, ""); "arjf".replace(re47, ""); "arjf".replace(re48, ""); / PC=i=(\d+)&oe=(.)/.exec(str35); re60.exec(" "); re60.exec(" bss"); re60.exec(""); re19.exec(" "); re19.exec("svefg ba"); re19.exec("ynfg vtaber"); re19.exec("ba"); re9.exec("scnq so "); re9.exec("zrqvgobk"); re9.exec("hsgy"); re9.exec("lhv-h"); /Fnsnev|Xbadhrebe|XUGZY/gi.exec(s15[i]); re61.exec("uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf"); re62.exec("#Ybtva_rznvy"); } } var re63 = /\{0\}/g; var str36 = "FrffvbaQQS2=4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n; ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669321699093060&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_tfwsbrg-aowb_80=4413268q3660"; var str37 = "FrffvbaQQS2=4ss747o77904333q374or84qrr1s9r0nprp8r5q81534o94n; AFP_zp_tfwsbrg-aowb_80=4413268q3660; __hgzm=144631658.1231364074.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.2294274870215848400.1231364074.1231364074.1231364074.1; __hgzo=144631658.0.10.1231364074; __hgzp=144631658; ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669321699093060&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str38 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231364057761&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231364057761&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Ssevraqf.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1667363813.1231364061&tn_fvq=1231364061&tn_uvq=1917563877&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str39 = "ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669321699093060&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str40 = "ZFPhygher=VC=74.125.75.20&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669321699093060&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var s91 = computeInputVariants(str36, 9); var s92 = computeInputVariants(str37, 9); var s93 = computeInputVariants(str38, 9); function runBlock7() { for (var i = 0; i < 9; i++) { "0".replace(re40, ""); "0".replace(re10, ""); "0".replace(re51, ""); "0".replace(re52, ""); "0".replace(re53, ""); "0".replace(re39, ""); "0".replace(re54, ""); "Lrf".replace(re40, ""); "Lrf".replace(re10, ""); "Lrf".replace(re51, ""); "Lrf".replace(re52, ""); "Lrf".replace(re53, ""); "Lrf".replace(re39, ""); "Lrf".replace(re54, ""); } for (var i = 0; i < 8; i++) { "Pybfr {0}".replace(re63, ""); "Bcra {0}".replace(re63, ""); s91[i].split(re32); s92[i].split(re32); "puvyq p1 svefg gnournqref".replace(re14, ""); "puvyq p1 svefg gnournqref".replace(re15, ""); "uqy_fcb".replace(re14, ""); "uqy_fcb".replace(re15, ""); "uvag".replace(re14, ""); "uvag".replace(re15, ""); s93[i].replace(re33, ""); "yvfg".replace(re14, ""); "yvfg".replace(re15, ""); "at_bhgre".replace(re30, ""); "cnerag puebzr5 qbhoyr2 NU".replace(re14, ""); "cnerag puebzr5 qbhoyr2 NU".replace(re15, ""); "cnerag puebzr5 dhnq5 ps NU osyvax zbarl".replace(re14, ""); "cnerag puebzr5 dhnq5 ps NU osyvax zbarl".replace(re15, ""); "cnerag puebzr6 fvatyr1".replace(re14, ""); "cnerag puebzr6 fvatyr1".replace(re15, ""); "cb_qrs".replace(re14, ""); "cb_qrs".replace(re15, ""); "gnopbagrag".replace(re14, ""); "gnopbagrag".replace(re15, ""); "iv_svefg_gvzr".replace(re30, ""); /(^|.)(ronl|qri-ehf3.wbg)(|fgberf|zbgbef|yvirnhpgvbaf|jvxv|rkcerff|punggre).(pbz(|.nh|.pa|.ux|.zl|.ft|.oe|.zk)|pb(.hx|.xe|.am)|pn|qr|se|vg|ay|or|ng|pu|vr|va|rf|cy|cu|fr)$/i.exec( "cntrf.ronl.pbz" ); re8.exec("144631658.0.10.1231364074"); re8.exec( "144631658.1231364074.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.2294274870215848400.1231364074.1231364074.1231364074.1" ); re8.exec("4413241q3660"); re8.exec("SbeprqRkcvengvba=633669357391353591"); re8.exec(str39); re8.exec(str40); re8.exec("AFP_zp_kkk-gdzogv_80=4413241q3660"); re8.exec("FrffvbaQQS2=p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7"); re8.exec( "__hgzn=144631658.2294274870215848400.1231364074.1231364074.1231364074.1" ); re8.exec("__hgzo=144631658.0.10.1231364074"); re8.exec( "__hgzm=144631658.1231364074.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec("p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7"); re34.exec(s91[i]); re34.exec(s92[i]); } } var re64 = /\b[a-z]/g; var re65 = /^uggc:\/\//; var re66 = /(?:^|\s+)qvfnoyrq(?:\s+|$)/; var str41 = "uggc://cebsvyr.zlfcnpr.pbz/Zbqhyrf/Nccyvpngvbaf/Cntrf/Pnainf.nfck"; function runBlock8() { for (var i = 0; i < 7; i++) { s21[i].match(/\d+/g); "nsgre".replace(re64, ""); "orsber".replace(re64, ""); "obggbz".replace(re64, ""); "ohvygva_jrngure.kzy".replace(re65, ""); "ohggba".replace(re37, ""); "ohggba".replace(re18, ""); "qngrgvzr.kzy".replace(re65, ""); "uggc://eff.paa.pbz/eff/paa_gbcfgbevrf.eff".replace(re65, ""); "vachg".replace(re37, ""); "vachg".replace(re18, ""); "vafvqr".replace(re64, ""); "cbvagre".replace(re27, ""); "cbfvgvba".replace(/[A-Z]/g, ""); "gbc".replace(re27, ""); "gbc".replace(re64, ""); "hy".replace(re37, ""); "hy".replace(re18, ""); str26.replace(re37, ""); str26.replace(re18, ""); "lbhghor_vtbbtyr/i2/lbhghor.kzy".replace(re65, ""); "m-vaqrk".replace(re27, ""); /#([\w-]+)/.exec(str26); re16.exec("urvtug"); re16.exec("znetvaGbc"); re16.exec("jvqgu"); re19.exec("gno0 svefg ba"); re19.exec("gno0 ba"); re19.exec("gno4 ynfg"); re19.exec("gno4"); re19.exec("gno5"); re19.exec("gno6"); re19.exec("gno7"); re19.exec("gno8"); /NqborNVE\/([^\s]*)/.exec(s15[i]); /NccyrJroXvg\/([^ ]*)/.exec(s15[i]); /XUGZY/gi.exec(s15[i]); /^(?:obql|ugzy)$/i.exec("YV"); re38.exec("ohggba"); re38.exec("vachg"); re38.exec("hy"); re38.exec(str26); /^(\w+|\*)/.exec(str26); /znp|jva|yvahk/i.exec("Jva32"); /eton?\([\d\s,]+\)/.exec("fgngvp"); } for (var i = 0; i < 6; i++) { "".replace(/\r/g, ""); "/".replace(re40, ""); "/".replace(re10, ""); "/".replace(re51, ""); "/".replace(re52, ""); "/".replace(re53, ""); "/".replace(re39, ""); "/".replace(re54, ""); "uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/{0}?[NDO]&{1}&{2}&[NDR]".replace( re63, "" ); str41.replace(re12, ""); "uggc://jjj.snprobbx.pbz/fepu.cuc".replace(re23, ""); "freivpr".replace(re40, ""); "freivpr".replace(re41, ""); "freivpr".replace(re42, ""); "freivpr".replace(re43, ""); "freivpr".replace(re44, ""); "freivpr".replace(re45, ""); "freivpr".replace(re46, ""); "freivpr".replace(re47, ""); "freivpr".replace(re48, ""); /((ZFVR\s+([6-9]|\d\d)\.))/.exec(s15[i]); re66.exec(""); re50.exec("fryrpgrq"); re8.exec("8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn"); re8.exec("SbeprqRkcvengvba=633669340386893867"); re8.exec("VC=74.125.75.17"); re8.exec("FrffvbaQQS2=8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn"); /Xbadhrebe|Fnsnev|XUGZY/.exec(s15[i]); re13.exec(str41); re49.exec("unfsbphf"); } } var re67 = /zrah_byq/g; var str42 = "FrffvbaQQS2=473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669325184628362&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var str43 = "FrffvbaQQS2=473qq1rs0n2r70q9qo1pq48n021s9468ron90nps048p4p29; __hgzm=144631658.1231364380.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.3931862196947939300.1231364380.1231364380.1231364380.1; __hgzo=144631658.0.10.1231364380; __hgzp=144631658; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669325184628362&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str44 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_vzntrf_wf&qg=1231364373088&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231364373088&punaary=svz_zlfcnpr_hfre-ivrj-pbzzragf%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Spbzzrag.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1158737789.1231364375&tn_fvq=1231364375&tn_uvq=415520832&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str45 = "ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669325184628362&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str46 = "ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669325184628362&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var re68 = /^([#.]?)((?:[\w\u0128-\uffff*_-]|\\.)*)/; var re69 = /\{1\}/g; var re70 = /\s+/; var re71 = /(\$\{4\})|(\$4\b)/g; var re72 = /(\$\{5\})|(\$5\b)/g; var re73 = /\{2\}/g; var re74 = /[^+>] [^+>]/; var re75 = /\bucpyv\s*=\s*([^;]*)/i; var re76 = /\bucuvqr\s*=\s*([^;]*)/i; var re77 = /\bucfie\s*=\s*([^;]*)/i; var re78 = /\bhfucjrn\s*=\s*([^;]*)/i; var re79 = /\bmvc\s*=\s*([^;]*)/i; var re80 = /^((?:[\w\u0128-\uffff*_-]|\\.)+)(#)((?:[\w\u0128-\uffff*_-]|\\.)+)/; var re81 = /^([>+~])\s*(\w*)/i; var re82 = /^>\s*((?:[\w\u0128-\uffff*_-]|\\.)+)/; var re83 = /^[\s[]?shapgvba/; var re84 = /v\/g.tvs#(.*)/i; var str47 = "#Zbq-Vasb-Vasb-WninFpevcgUvag"; var str48 = ",n.svryqOgaPnapry"; var str49 = "FrffvbaQQS2=p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669357391353591&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_kkk-gdzogv_80=4413241q3660"; var str50 = "FrffvbaQQS2=p98s8o9q42nr21or1r61pqorn1n002nsss569635984s6qp7; AFP_zp_kkk-gdzogv_80=4413241q3660; AFP_zp_kkk-aowb_80=4413235p3660; __hgzm=144631658.1231367708.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.2770915348920628700.1231367708.1231367708.1231367708.1; __hgzo=144631658.0.10.1231367708; __hgzp=144631658; ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669357391353591&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str51 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231367691141&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231367691141&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Sjjj.zlfcnpr.pbz%2S&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=320757904.1231367694&tn_fvq=1231367694&tn_uvq=1758792003&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str52 = "uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55332979829981?[NDO]&aqu=1&g=7%2S0%2S2009%2014%3N38%3N42%203%20480&af=zfacbegny&cntrAnzr=HF%20UCZFSGJ&t=uggc%3N%2S%2Sjjj.zfa.pbz%2S&f=1024k768&p=24&x=L&oj=994&ou=634&uc=A&{2}&[NDR]"; var str53 = "cnerag puebzr6 fvatyr1 gno fryrpgrq ovaq qbhoyr2 ps"; var str54 = "ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669357391353591&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str55 = "ZFPhygher=VC=74.125.75.3&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669357391353591&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var str56 = "ne;ng;nh;or;oe;pn;pu;py;pa;qr;qx;rf;sv;se;to;ux;vq;vr;va;vg;wc;xe;zk;zl;ay;ab;am;cu;cy;cg;eh;fr;ft;gu;ge;gj;mn;"; var str57 = "ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886&GHVQ=1"; var str58 = "ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886"; var str59 = "ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886; mvc=m:94043|yn:37.4154|yb:-122.0585|p:HF|ue:1"; var str60 = "ZP1=I=3&THVQ=6nnpr9q661804s33nnop45nosqp17q85; zu=ZFSG; PHYGHER=RA-HF; SyvtugTebhcVq=97; SyvtugVq=OnfrCntr; ucfie=Z:5|S:5|G:5|R:5|Q:oyh|J:S; ucpyv=J.U|Y.|F.|E.|H.Y|P.|U.; hfucjrn=jp:HFPN0746; ZHVQ=Q783SN9O14054831N4869R51P0SO8886; mvc=m:94043|yn:37.4154|yb:-122.0585|p:HF"; var str61 = "uggc://gx2.fgp.f-zfa.pbz/oe/uc/11/ra-hf/pff/v/g.tvs#uggc://gx2.fgo.f-zfa.pbz/v/29/4RQP4969777N048NPS4RRR3PO2S7S.wct"; var str62 = "uggc://gx2.fgp.f-zfa.pbz/oe/uc/11/ra-hf/pff/v/g.tvs#uggc://gx2.fgo.f-zfa.pbz/v/OQ/63NP9O94NS5OQP1249Q9S1ROP7NS3.wct"; var str63 = "zbmvyyn/5.0 (jvaqbjf; h; jvaqbjf ag 5.1; ra-hf) nccyrjroxvg/528.9 (xugzy, yvxr trpxb) puebzr/2.0.157.0 fnsnev/528.9"; var s94 = computeInputVariants(str42, 5); var s95 = computeInputVariants(str43, 5); var s96 = computeInputVariants(str44, 5); var s97 = computeInputVariants(str47, 5); var s98 = computeInputVariants(str48, 5); var s99 = computeInputVariants(str49, 5); var s100 = computeInputVariants(str50, 5); var s101 = computeInputVariants(str51, 5); var s102 = computeInputVariants(str52, 5); var s103 = computeInputVariants(str53, 5); function runBlock9() { for (var i = 0; i < 5; i++) { s94[i].split(re32); s95[i].split(re32); "svz_zlfcnpr_hfre-ivrj-pbzzragf,svz_zlfcnpr_havgrq-fgngrf".split(re20); s96[i].replace(re33, ""); "zrah_arj zrah_arj_gbttyr zrah_gbttyr".replace(re67, ""); "zrah_byq zrah_byq_gbttyr zrah_gbttyr".replace(re67, ""); re8.exec("102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98"); re8.exec("144631658.0.10.1231364380"); re8.exec( "144631658.1231364380.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.3931862196947939300.1231364380.1231364380.1231364380.1" ); re8.exec("441326q33660"); re8.exec("SbeprqRkcvengvba=633669341278771470"); re8.exec(str45); re8.exec(str46); re8.exec("AFP_zp_dfctwzssrwh-aowb_80=441326q33660"); re8.exec("FrffvbaQQS2=102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98"); re8.exec( "__hgzn=144631658.3931862196947939300.1231364380.1231364380.1231364380.1" ); re8.exec("__hgzo=144631658.0.10.1231364380"); re8.exec( "__hgzm=144631658.1231364380.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); } for (var i = 0; i < 4; i++) { " yvfg1".replace(re14, ""); " yvfg1".replace(re15, ""); " yvfg2".replace(re14, ""); " yvfg2".replace(re15, ""); " frneputebhc1".replace(re14, ""); " frneputebhc1".replace(re15, ""); s97[i].replace(re68, ""); s97[i].replace(re18, ""); "".replace(/&/g, ""); "".replace(re35, ""); "(..-{0})(|(d+)|)".replace(re63, ""); s98[i].replace(re18, ""); "//vzt.jro.qr/vij/FC/${cngu}/${anzr}/${inyhr}?gf=${abj}".replace( re56, "" ); "//vzt.jro.qr/vij/FC/tzk_uc/${anzr}/${inyhr}?gf=${abj}".replace( /(\$\{anzr\})|(\$anzr\b)/g, "" ); 'Jvaqbjf Yvir Ubgznvy{1}'.replace( re69, "" ); '{0}{1}'.replace( re63, "" ); '{1}'.replace( re69, "" ); '{1}'.replace( re63, "" ); "Vzntrf".replace(re15, ""); "ZFA".replace(re15, ""); "Zncf".replace(re15, ""); "Zbq-Vasb-Vasb-WninFpevcgUvag".replace(re39, ""); "Arjf".replace(re15, ""); s99[i].split(re32); s100[i].split(re32); "Ivqrb".replace(re15, ""); "Jro".replace(re15, ""); "n".replace(re39, ""); "nwnkFgneg".split(re70); "nwnkFgbc".split(re70); "ovaq".replace(re14, ""); "ovaq".replace(re15, ""); "oevatf lbh zber. Zber fcnpr (5TO), zber frphevgl, fgvyy serr.".replace( re63, "" ); "puvyq p1 svefg qrpx".replace(re14, ""); "puvyq p1 svefg qrpx".replace(re15, ""); "puvyq p1 svefg qbhoyr2".replace(re14, ""); "puvyq p1 svefg qbhoyr2".replace(re15, ""); "puvyq p2 ynfg".replace(re14, ""); "puvyq p2 ynfg".replace(re15, ""); "puvyq p2".replace(re14, ""); "puvyq p2".replace(re15, ""); "puvyq p3".replace(re14, ""); "puvyq p3".replace(re15, ""); "puvyq p4 ynfg".replace(re14, ""); "puvyq p4 ynfg".replace(re15, ""); "pbclevtug".replace(re14, ""); "pbclevtug".replace(re15, ""); "qZFAZR_1".replace(re14, ""); "qZFAZR_1".replace(re15, ""); "qbhoyr2 ps".replace(re14, ""); "qbhoyr2 ps".replace(re15, ""); "qbhoyr2".replace(re14, ""); "qbhoyr2".replace(re15, ""); "uqy_arj".replace(re14, ""); "uqy_arj".replace(re15, ""); "uc_fubccvatobk".replace(re30, ""); "ugzy%2Rvq".replace(re29, ""); "ugzy%2Rvq".replace(re30, ""); s101[i].replace(re33, ""); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/cebgbglcr.wf${4}${5}".replace( re71, "" ); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/cebgbglcr.wf${5}".replace( re72, "" ); s102[i].replace(re73, ""); "uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55332979829981?[NDO]&{1}&{2}&[NDR]".replace( re69, "" ); "vztZFSG".replace(re14, ""); "vztZFSG".replace(re15, ""); "zfasbbg1 ps".replace(re14, ""); "zfasbbg1 ps".replace(re15, ""); s103[i].replace(re14, ""); s103[i].replace(re15, ""); "cnerag puebzr6 fvatyr1 gno fryrpgrq ovaq".replace(re14, ""); "cnerag puebzr6 fvatyr1 gno fryrpgrq ovaq".replace(re15, ""); "cevznel".replace(re14, ""); "cevznel".replace(re15, ""); "erpgnatyr".replace(re30, ""); "frpbaqnel".replace(re14, ""); "frpbaqnel".replace(re15, ""); "haybnq".split(re70); "{0}{1}1".replace(re63, ""); "|{1}1".replace(re69, ""); /(..-HF)(\|(\d+)|)/i.exec("xb-xe,ra-va,gu-gu"); re4.exec("/ZlFcnprNccf/NccPnainf,45000012"); re8.exec("144631658.0.10.1231367708"); re8.exec( "144631658.1231367708.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.2770915348920628700.1231367708.1231367708.1231367708.1" ); re8.exec("4413235p3660"); re8.exec("441327q73660"); re8.exec("9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473"); re8.exec("SbeprqRkcvengvba=633669350559478880"); re8.exec(str54); re8.exec(str55); re8.exec("AFP_zp_dfctwzs-aowb_80=441327q73660"); re8.exec("AFP_zp_kkk-aowb_80=4413235p3660"); re8.exec("FrffvbaQQS2=9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473"); re8.exec( "__hgzn=144631658.2770915348920628700.1231367708.1231367708.1231367708.1" ); re8.exec("__hgzo=144631658.0.10.1231367708"); re8.exec( "__hgzm=144631658.1231367708.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re34.exec(s99[i]); re34.exec(s100[i]); /ZFVR\s+5[.]01/.exec(s15[i]); /HF(?=;)/i.exec(str56); re74.exec(s97[i]); re28.exec("svefg npgvir svefgNpgvir"); re28.exec("ynfg"); /\bp:(..)/i.exec("m:94043|yn:37.4154|yb:-122.0585|p:HF"); re75.exec(str57); re75.exec(str58); re76.exec(str57); re76.exec(str58); re77.exec(str57); re77.exec(str58); /\bhfucce\s*=\s*([^;]*)/i.exec(str59); re78.exec(str57); re78.exec(str58); /\bjci\s*=\s*([^;]*)/i.exec(str59); re79.exec(str58); re79.exec(str60); re79.exec(str59); /\|p:([a-z]{2})/i.exec("m:94043|yn:37.4154|yb:-122.0585|p:HF|ue:1"); re80.exec(s97[i]); re61.exec("cebgbglcr.wf"); re68.exec(s97[i]); re81.exec(s97[i]); re82.exec(s97[i]); /^Fubpxjnir Synfu (\d)/.exec(s21[i]); /^Fubpxjnir Synfu (\d+)/.exec(s21[i]); re83.exec("[bowrpg tybony]"); re62.exec(s97[i]); re84.exec(str61); re84.exec(str62); /jroxvg/.exec(str63); } } var re85 = /eaq_zbqobkva/; var str64 = "1231365729213"; var str65 = "74.125.75.3-1057165600.29978900"; var str66 = "74.125.75.3-1057165600.29978900.1231365730214"; var str67 = "Frnepu%20Zvpebfbsg.pbz"; var str68 = "FrffvbaQQS2=8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn; ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669340386893867&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var str69 = "FrffvbaQQS2=8sqq78r9n442851q565599o401385sp3s04r92rnn7o19ssn; __hgzm=144631658.1231365779.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.1877536177953918500.1231365779.1231365779.1231365779.1; __hgzo=144631658.0.10.1231365779; __hgzp=144631658; ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669340386893867&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str70 = "I=3%26THVQ=757q3ss871q44o7o805n8113n5p72q52"; var str71 = "I=3&THVQ=757q3ss871q44o7o805n8113n5p72q52"; var str72 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231365765292&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231365765292&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Sohyyrgvaf.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1579793869.1231365768&tn_fvq=1231365768&tn_uvq=2056210897&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str73 = "frnepu.zvpebfbsg.pbz"; var str74 = "frnepu.zvpebfbsg.pbz/"; var str75 = "ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669340386893867&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str76 = "ZFPhygher=VC=74.125.75.17&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669340386893867&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; function runBlock10() { for (var i = 0; i < 3; i++) { "%3Szxg=ra-HF".replace(re39, ""); "-8".replace(re40, ""); "-8".replace(re10, ""); "-8".replace(re51, ""); "-8".replace(re52, ""); "-8".replace(re53, ""); "-8".replace(re39, ""); "-8".replace(re54, ""); "1.5".replace(re40, ""); "1.5".replace(re10, ""); "1.5".replace(re51, ""); "1.5".replace(re52, ""); "1.5".replace(re53, ""); "1.5".replace(re39, ""); "1.5".replace(re54, ""); "1024k768".replace(re40, ""); "1024k768".replace(re10, ""); "1024k768".replace(re51, ""); "1024k768".replace(re52, ""); "1024k768".replace(re53, ""); "1024k768".replace(re39, ""); "1024k768".replace(re54, ""); str64.replace(re40, ""); str64.replace(re10, ""); str64.replace(re51, ""); str64.replace(re52, ""); str64.replace(re53, ""); str64.replace(re39, ""); str64.replace(re54, ""); "14".replace(re40, ""); "14".replace(re10, ""); "14".replace(re51, ""); "14".replace(re52, ""); "14".replace(re53, ""); "14".replace(re39, ""); "14".replace(re54, ""); "24".replace(re40, ""); "24".replace(re10, ""); "24".replace(re51, ""); "24".replace(re52, ""); "24".replace(re53, ""); "24".replace(re39, ""); "24".replace(re54, ""); str65.replace(re40, ""); str65.replace(re10, ""); str65.replace(re51, ""); str65.replace(re52, ""); str65.replace(re53, ""); str65.replace(re39, ""); str65.replace(re54, ""); str66.replace(re40, ""); str66.replace(re10, ""); str66.replace(re51, ""); str66.replace(re52, ""); str66.replace(re53, ""); str66.replace(re39, ""); str66.replace(re54, ""); "9.0".replace(re40, ""); "9.0".replace(re10, ""); "9.0".replace(re51, ""); "9.0".replace(re52, ""); "9.0".replace(re53, ""); "9.0".replace(re39, ""); "9.0".replace(re54, ""); "994k634".replace(re40, ""); "994k634".replace(re10, ""); "994k634".replace(re51, ""); "994k634".replace(re52, ""); "994k634".replace(re53, ""); "994k634".replace(re39, ""); "994k634".replace(re54, ""); "?zxg=ra-HF".replace(re40, ""); "?zxg=ra-HF".replace(re10, ""); "?zxg=ra-HF".replace(re51, ""); "?zxg=ra-HF".replace(re52, ""); "?zxg=ra-HF".replace(re53, ""); "?zxg=ra-HF".replace(re54, ""); "PAA.pbz".replace(re25, ""); "PAA.pbz".replace(re12, ""); "PAA.pbz".replace(re39, ""); "Qngr & Gvzr".replace(re25, ""); "Qngr & Gvzr".replace(re12, ""); "Qngr & Gvzr".replace(re39, ""); "Frnepu Zvpebfbsg.pbz".replace(re40, ""); "Frnepu Zvpebfbsg.pbz".replace(re54, ""); str67.replace(re10, ""); str67.replace(re51, ""); str67.replace(re52, ""); str67.replace(re53, ""); str67.replace(re39, ""); str68.split(re32); str69.split(re32); str70.replace(re52, ""); str70.replace(re53, ""); str70.replace(re39, ""); str71.replace(re40, ""); str71.replace(re10, ""); str71.replace(re51, ""); str71.replace(re54, ""); "Jrngure".replace(re25, ""); "Jrngure".replace(re12, ""); "Jrngure".replace(re39, ""); "LbhGhor".replace(re25, ""); "LbhGhor".replace(re12, ""); "LbhGhor".replace(re39, ""); str72.replace(re33, ""); "erzbgr_vsenzr_1".replace(/^erzbgr_vsenzr_/, ""); str73.replace(re40, ""); str73.replace(re10, ""); str73.replace(re51, ""); str73.replace(re52, ""); str73.replace(re53, ""); str73.replace(re39, ""); str73.replace(re54, ""); str74.replace(re40, ""); str74.replace(re10, ""); str74.replace(re51, ""); str74.replace(re52, ""); str74.replace(re53, ""); str74.replace(re39, ""); str74.replace(re54, ""); "lhv-h".replace(/\-/g, ""); re9.exec("p"); re9.exec("qz p"); re9.exec("zbqynory"); re9.exec("lhv-h svefg"); re8.exec("144631658.0.10.1231365779"); re8.exec( "144631658.1231365779.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.1877536177953918500.1231365779.1231365779.1231365779.1" ); re8.exec(str75); re8.exec(str76); re8.exec( "__hgzn=144631658.1877536177953918500.1231365779.1231365779.1231365779.1" ); re8.exec("__hgzo=144631658.0.10.1231365779"); re8.exec( "__hgzm=144631658.1231365779.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re34.exec(str68); re34.exec(str69); /^$/.exec(""); re31.exec("qr"); /^znk\d+$/.exec(""); /^zva\d+$/.exec(""); /^erfgber$/.exec(""); re85.exec("zbqobkva zbqobk_abcnqqvat "); re85.exec("zbqgvgyr"); re85.exec("eaq_zbqobkva "); re85.exec("eaq_zbqgvgyr "); /frpgvba\d+_pbagragf/.exec("obggbz_ani"); } } var re86 = /;\s*/; var re87 = /(\$\{inyhr\})|(\$inyhr\b)/g; var re88 = /(\$\{abj\})|(\$abj\b)/g; var re89 = /\s+$/; var re90 = /^\s+/; var re91 = /(\\\"|\x00-|\x1f|\x7f-|\x9f|\u00ad|\u0600-|\u0604|\u070f|\u17b4|\u17b5|\u200c-|\u200f|\u2028-|\u202f|\u2060-|\u206f|\ufeff|\ufff0-|\uffff)/g; var re92 = /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/; var re93 = /^([:.#]*)((?:[\w\u0128-\uffff*_-]|\\.)+)/; var re94 = /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/; var str77 = "#fubhgobk .pybfr"; var str78 = "FrffvbaQQS2=102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669341278771470&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_dfctwzssrwh-aowb_80=441326q33660"; var str79 = "FrffvbaQQS2=102n9o0o9pq60132qn0337rr867p75953502q2s27s2s5r98; AFP_zp_dfctwzssrwh-aowb_80=441326q33660; __hgzm=144631658.1231365869.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.1670816052019209000.1231365869.1231365869.1231365869.1; __hgzo=144631658.0.10.1231365869; __hgzp=144631658; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669341278771470&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str80 = "FrffvbaQQS2=9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669350559478880&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R=; AFP_zp_dfctwzs-aowb_80=441327q73660"; var str81 = "FrffvbaQQS2=9995p6rp12rrnr893334ro7nq70o7p64p69rqn844prs1473; AFP_zp_dfctwzs-aowb_80=441327q73660; __hgzm=144631658.1231367054.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar); __hgzn=144631658.1796080716621419500.1231367054.1231367054.1231367054.1; __hgzo=144631658.0.10.1231367054; __hgzp=144631658; ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669350559478880&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str82 = "[glcr=fhozvg]"; var str83 = "n.svryqOga,n.svryqOgaPnapry"; var str84 = "n.svryqOgaPnapry"; var str85 = "oyvpxchaxg"; var str86 = "qvi.bow-nppbeqvba qg"; var str87 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_nccf_wf&qg=1231367052227&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231367052227&punaary=svz_zlfcnpr_nccf-pnainf%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Scebsvyr.zlfcnpr.pbz%2SZbqhyrf%2SNccyvpngvbaf%2SCntrf%2SPnainf.nfck&nq_glcr=grkg&rvq=6083027&rn=0&sez=1&tn_ivq=716357910.1231367056&tn_fvq=1231367056&tn_uvq=1387206491&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str88 = "uggc://tbbtyrnqf.t.qbhoyrpyvpx.arg/cntrnq/nqf?pyvrag=pn-svz_zlfcnpr_zlfcnpr-ubzrcntr_wf&qg=1231365851658&uy=ra&nqfnsr=uvtu&br=hgs8&ahz_nqf=4&bhgchg=wf&nqgrfg=bss&pbeeryngbe=1231365851658&punaary=svz_zlfcnpr_ubzrcntr_abgybttrqva%2Psvz_zlfcnpr_aba_HTP%2Psvz_zlfcnpr_havgrq-fgngrf&hey=uggc%3N%2S%2Scebsvyrrqvg.zlfcnpr.pbz%2Svaqrk.psz&nq_glcr=grkg&rvq=6083027&rn=0&sez=0&tn_ivq=1979828129.1231365855&tn_fvq=1231365855&tn_uvq=2085229649&synfu=9.0.115&h_u=768&h_j=1024&h_nu=738&h_nj=1024&h_pq=24&h_gm=-480&h_uvf=2&h_wnin=gehr&h_acyht=7&h_azvzr=22"; var str89 = "uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55023338617756?[NDO]&aqu=1&g=7%2S0%2S2009%2014%3N12%3N47%203%20480&af=zfacbegny&cntrAnzr=HF%20UCZFSGJ&t=uggc%3N%2S%2Sjjj.zfa.pbz%2S&f=0k0&p=43835816&x=A&oj=994&ou=634&uc=A&{2}&[NDR]"; var str90 = "zrgn[anzr=nwnkHey]"; var str91 = "anpuevpugra"; var str92 = "b oS={'oT':1.1};x $8n(B){z(B!=o9)};x $S(B){O(!$8n(B))z A;O(B.4L)z'T';b S=7t B;O(S=='2P'&&B.p4){23(B.7f){12 1:z'T';12 3:z/S/.2g(B.8M)?'ox':'oh'}}O(S=='2P'||S=='x'){23(B.nE){12 2V:z'1O';12 7I:z'5a';12 18:z'4B'}O(7t B.I=='4F'){O(B.3u)z'pG';O(B.8e)z'1p'}}z S};x $2p(){b 4E={};Z(b v=0;v<1p.I;v++){Z(b X 1o 1p[v]){b nc=1p[v][X];b 6E=4E[X];O(6E&&$S(nc)=='2P'&&$S(6E)=='2P')4E[X]=$2p(6E,nc);17 4E[X]=nc}}z 4E};b $E=7p.E=x(){b 1d=1p;O(!1d[1])1d=[p,1d[0]];Z(b X 1o 1d[1])1d[0][X]=1d[1][X];z 1d[0]};b $4D=7p.pJ=x(){Z(b v=0,y=1p.I;v-1:p.3F(2R)>-1},nX:x(){z p.3y(/([.*+?^${}()|[]/\\])/t,'\\$1')}});2V.E({5V:x(1O){O(p.I<3)z A;O(p.I==4&&p[3]==0&&!1O)z'p5';b 3P=[];Z(b v=0;v<3;v++){b 52=(p[v]-0).4h(16);3P.1x((52.I==1)?'0'+52:52)}z 1O?3P:'#'+3P.2u('')},5U:x(1O){O(p.I!=3)z A;b 1i=[];Z(b v=0;v<3;v++){1i.1x(5K((p[v].I==1)?p[v]+p[v]:p[v],16))}z 1O?1i:'1i('+1i.2u(',')+')'}});7F.E({3n:x(P){b J=p;P=$2p({'L':J,'V':A,'1p':1S,'2x':A,'4s':A,'6W':A},P);O($2O(P.1p)&&$S(P.1p)!='1O')P.1p=[P.1p];z x(V){b 1d;O(P.V){V=V||H.V;1d=[(P.V===1r)?V:Y P.V(V)];O(P.1p)1d.E(P.1p)}17 1d=P.1p||1p;b 3C=x(){z J.3H($5S(P"; var str93 = "hagreunyghat"; var str94 = "ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669341278771470&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str95 = "ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&Pbhagel=IIZ%3Q&SbeprqRkcvengvba=633669350559478880&gvzrMbar=-8&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R%3Q"; var str96 = "ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669341278771470&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var str97 = "ZFPhygher=VC=74.125.75.1&VCPhygher=ra-HF&CersreerqPhygher=ra-HF&CersreerqPhygherCraqvat=&Pbhagel=IIZ=&SbeprqRkcvengvba=633669350559478880&gvzrMbar=0&HFEYBP=DKWyLHAiMTH9AwHjWxAcqUx9GJ91oaEunJ4tIzyyqlMQo3IhqUW5D29xMG1IHlMQo3IhqUW5GzSgMG1Iozy0MJDtH3EuqTImWxEgLHAiMTH9BQN3WxkuqTy0qJEyCGZ3YwDkBGVzGT9hM2y0qJEyCF0kZwVhZQH3APMDo3A0LJkQo2EyCGx0ZQDmWyWyM2yiox5uoJH9D0R="; var str98 = "shapgvba (){Cuk.Nccyvpngvba.Frghc.Pber();Cuk.Nccyvpngvba.Frghc.Nwnk();Cuk.Nccyvpngvba.Frghc.Synfu();Cuk.Nccyvpngvba.Frghc.Zbqhyrf()}"; function runBlock11() { for (var i = 0; i < 2; i++) { " .pybfr".replace(re18, ""); " n.svryqOgaPnapry".replace(re18, ""); " qg".replace(re18, ""); str77.replace(re68, ""); str77.replace(re18, ""); "".replace(re39, ""); "".replace(/^/, ""); "".split(re86); "*".replace(re39, ""); "*".replace(re68, ""); "*".replace(re18, ""); ".pybfr".replace(re68, ""); ".pybfr".replace(re18, ""); "//vzt.jro.qr/vij/FC/tzk_uc/fperra/${inyhr}?gf=${abj}".replace(re87, ""); "//vzt.jro.qr/vij/FC/tzk_uc/fperra/1024?gf=${abj}".replace(re88, ""); "//vzt.jro.qr/vij/FC/tzk_uc/jvafvmr/${inyhr}?gf=${abj}".replace(re87, ""); "//vzt.jro.qr/vij/FC/tzk_uc/jvafvmr/992/608?gf=${abj}".replace(re88, ""); "300k120".replace(re30, ""); "300k250".replace(re30, ""); "310k120".replace(re30, ""); "310k170".replace(re30, ""); "310k250".replace(re30, ""); "9.0 e115".replace(/^.*\.(.*)\s.*$/, ""); "Nppbeqvba".replace(re2, ""); "Nxghryy\x0a".replace(re89, ""); "Nxghryy\x0a".replace(re90, ""); "Nccyvpngvba".replace(re2, ""); "Oyvpxchaxg\x0a".replace(re89, ""); "Oyvpxchaxg\x0a".replace(re90, ""); "Svanamra\x0a".replace(re89, ""); "Svanamra\x0a".replace(re90, ""); "Tnzrf\x0a".replace(re89, ""); "Tnzrf\x0a".replace(re90, ""); "Ubebfxbc\x0a".replace(re89, ""); "Ubebfxbc\x0a".replace(re90, ""); "Xvab\x0a".replace(re89, ""); "Xvab\x0a".replace(re90, ""); "Zbqhyrf".replace(re2, ""); "Zhfvx\x0a".replace(re89, ""); "Zhfvx\x0a".replace(re90, ""); "Anpuevpugra\x0a".replace(re89, ""); "Anpuevpugra\x0a".replace(re90, ""); "Cuk".replace(re2, ""); "ErdhrfgSvavfu".split(re70); "ErdhrfgSvavfu.NWNK.Cuk".split(re70); "Ebhgr\x0a".replace(re89, ""); "Ebhgr\x0a".replace(re90, ""); str78.split(re32); str79.split(re32); str80.split(re32); str81.split(re32); "Fcbeg\x0a".replace(re89, ""); "Fcbeg\x0a".replace(re90, ""); "GI-Fcbg\x0a".replace(re89, ""); "GI-Fcbg\x0a".replace(re90, ""); "Gbhe\x0a".replace(re89, ""); "Gbhe\x0a".replace(re90, ""); "Hagreunyghat\x0a".replace(re89, ""); "Hagreunyghat\x0a".replace(re90, ""); "Ivqrb\x0a".replace(re89, ""); "Ivqrb\x0a".replace(re90, ""); "Jrggre\x0a".replace(re89, ""); "Jrggre\x0a".replace(re90, ""); str82.replace(re68, ""); str82.replace(re18, ""); str83.replace(re68, ""); str83.replace(re18, ""); str84.replace(re68, ""); str84.replace(re18, ""); "nqiFreivprObk".replace(re30, ""); "nqiFubccvatObk".replace(re30, ""); "nwnk".replace(re39, ""); "nxghryy".replace(re40, ""); "nxghryy".replace(re41, ""); "nxghryy".replace(re42, ""); "nxghryy".replace(re43, ""); "nxghryy".replace(re44, ""); "nxghryy".replace(re45, ""); "nxghryy".replace(re46, ""); "nxghryy".replace(re47, ""); "nxghryy".replace(re48, ""); str85.replace(re40, ""); str85.replace(re41, ""); str85.replace(re42, ""); str85.replace(re43, ""); str85.replace(re44, ""); str85.replace(re45, ""); str85.replace(re46, ""); str85.replace(re47, ""); str85.replace(re48, ""); "pngrtbel".replace(re29, ""); "pngrtbel".replace(re30, ""); "pybfr".replace(re39, ""); "qvi".replace(re39, ""); str86.replace(re68, ""); str86.replace(re18, ""); "qg".replace(re39, ""); "qg".replace(re68, ""); "qg".replace(re18, ""); "rzorq".replace(re39, ""); "rzorq".replace(re68, ""); "rzorq".replace(re18, ""); "svryqOga".replace(re39, ""); "svryqOgaPnapry".replace(re39, ""); "svz_zlfcnpr_nccf-pnainf,svz_zlfcnpr_havgrq-fgngrf".split(re20); "svanamra".replace(re40, ""); "svanamra".replace(re41, ""); "svanamra".replace(re42, ""); "svanamra".replace(re43, ""); "svanamra".replace(re44, ""); "svanamra".replace(re45, ""); "svanamra".replace(re46, ""); "svanamra".replace(re47, ""); "svanamra".replace(re48, ""); "sbphf".split(re70); "sbphf.gno sbphfva.gno".split(re70); "sbphfva".split(re70); "sbez".replace(re39, ""); "sbez.nwnk".replace(re68, ""); "sbez.nwnk".replace(re18, ""); "tnzrf".replace(re40, ""); "tnzrf".replace(re41, ""); "tnzrf".replace(re42, ""); "tnzrf".replace(re43, ""); "tnzrf".replace(re44, ""); "tnzrf".replace(re45, ""); "tnzrf".replace(re46, ""); "tnzrf".replace(re47, ""); "tnzrf".replace(re48, ""); "ubzrcntr".replace(re30, ""); "ubebfxbc".replace(re40, ""); "ubebfxbc".replace(re41, ""); "ubebfxbc".replace(re42, ""); "ubebfxbc".replace(re43, ""); "ubebfxbc".replace(re44, ""); "ubebfxbc".replace(re45, ""); "ubebfxbc".replace(re46, ""); "ubebfxbc".replace(re47, ""); "ubebfxbc".replace(re48, ""); "uc_cebzbobk_ugzy%2Puc_cebzbobk_vzt".replace(re30, ""); "uc_erpgnatyr".replace(re30, ""); str87.replace(re33, ""); str88.replace(re33, ""); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf${4}${5}".replace( re71, "" ); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf${5}".replace( re72, "" ); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/qlaYvo.wf${4}${5}".replace( re71, "" ); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/qlaYvo.wf${5}".replace( re72, "" ); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/rssrpgYvo.wf${4}${5}".replace( re71, "" ); "uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/rssrpgYvo.wf${5}".replace( re72, "" ); str89.replace(re73, ""); "uggc://zfacbegny.112.2b7.arg/o/ff/zfacbegnyubzr/1/U.7-cqi-2/f55023338617756?[NDO]&{1}&{2}&[NDR]".replace( re69, "" ); str6.replace(re23, ""); "xvab".replace(re40, ""); "xvab".replace(re41, ""); "xvab".replace(re42, ""); "xvab".replace(re43, ""); "xvab".replace(re44, ""); "xvab".replace(re45, ""); "xvab".replace(re46, ""); "xvab".replace(re47, ""); "xvab".replace(re48, ""); "ybnq".split(re70); "zrqvnzbqgno lhv-anifrg lhv-anifrg-gbc".replace(re18, ""); "zrgn".replace(re39, ""); str90.replace(re68, ""); str90.replace(re18, ""); "zbhfrzbir".split(re70); "zbhfrzbir.gno".split(re70); str63.replace(/^.*jroxvg\/(\d+(\.\d+)?).*$/, ""); "zhfvx".replace(re40, ""); "zhfvx".replace(re41, ""); "zhfvx".replace(re42, ""); "zhfvx".replace(re43, ""); "zhfvx".replace(re44, ""); "zhfvx".replace(re45, ""); "zhfvx".replace(re46, ""); "zhfvx".replace(re47, ""); "zhfvx".replace(re48, ""); "zlfcnpr_nccf_pnainf".replace(re52, ""); str91.replace(re40, ""); str91.replace(re41, ""); str91.replace(re42, ""); str91.replace(re43, ""); str91.replace(re44, ""); str91.replace(re45, ""); str91.replace(re46, ""); str91.replace(re47, ""); str91.replace(re48, ""); "anzr".replace(re39, ""); str92.replace(/\b\w+\b/g, ""); "bow-nppbeqvba".replace(re39, ""); "bowrpg".replace(re39, ""); "bowrpg".replace(re68, ""); "bowrpg".replace(re18, ""); "cnenzf%2Rfglyrf".replace(re29, ""); "cnenzf%2Rfglyrf".replace(re30, ""); "cbchc".replace(re30, ""); "ebhgr".replace(re40, ""); "ebhgr".replace(re41, ""); "ebhgr".replace(re42, ""); "ebhgr".replace(re43, ""); "ebhgr".replace(re44, ""); "ebhgr".replace(re45, ""); "ebhgr".replace(re46, ""); "ebhgr".replace(re47, ""); "ebhgr".replace(re48, ""); "freivprobk_uc".replace(re30, ""); "fubccvatobk_uc".replace(re30, ""); "fubhgobk".replace(re39, ""); "fcbeg".replace(re40, ""); "fcbeg".replace(re41, ""); "fcbeg".replace(re42, ""); "fcbeg".replace(re43, ""); "fcbeg".replace(re44, ""); "fcbeg".replace(re45, ""); "fcbeg".replace(re46, ""); "fcbeg".replace(re47, ""); "fcbeg".replace(re48, ""); "gbhe".replace(re40, ""); "gbhe".replace(re41, ""); "gbhe".replace(re42, ""); "gbhe".replace(re43, ""); "gbhe".replace(re44, ""); "gbhe".replace(re45, ""); "gbhe".replace(re46, ""); "gbhe".replace(re47, ""); "gbhe".replace(re48, ""); "gi-fcbg".replace(re40, ""); "gi-fcbg".replace(re41, ""); "gi-fcbg".replace(re42, ""); "gi-fcbg".replace(re43, ""); "gi-fcbg".replace(re44, ""); "gi-fcbg".replace(re45, ""); "gi-fcbg".replace(re46, ""); "gi-fcbg".replace(re47, ""); "gi-fcbg".replace(re48, ""); "glcr".replace(re39, ""); "haqrsvarq".replace(/\//g, ""); str93.replace(re40, ""); str93.replace(re41, ""); str93.replace(re42, ""); str93.replace(re43, ""); str93.replace(re44, ""); str93.replace(re45, ""); str93.replace(re46, ""); str93.replace(re47, ""); str93.replace(re48, ""); "ivqrb".replace(re40, ""); "ivqrb".replace(re41, ""); "ivqrb".replace(re42, ""); "ivqrb".replace(re43, ""); "ivqrb".replace(re44, ""); "ivqrb".replace(re45, ""); "ivqrb".replace(re46, ""); "ivqrb".replace(re47, ""); "ivqrb".replace(re48, ""); "ivfvgf=1".split(re86); "jrggre".replace(re40, ""); "jrggre".replace(re41, ""); "jrggre".replace(re42, ""); "jrggre".replace(re43, ""); "jrggre".replace(re44, ""); "jrggre".replace(re45, ""); "jrggre".replace(re46, ""); "jrggre".replace(re47, ""); "jrggre".replace(re48, ""); /#[a-z0-9]+$/i.exec("uggc://jjj.fpuhryreim.arg/Qrsnhyg"); re66.exec("fryrpgrq"); /(?:^|\s+)lhv-ani(?:\s+|$)/.exec("sff lhv-ani"); /(?:^|\s+)lhv-anifrg(?:\s+|$)/.exec("zrqvnzbqgno lhv-anifrg"); /(?:^|\s+)lhv-anifrg-gbc(?:\s+|$)/.exec("zrqvnzbqgno lhv-anifrg"); re91.exec("GnoThvq"); re91.exec("thvq"); /(pbzcngvoyr|jroxvg)/.exec(str63); /.+(?:ei|vg|en|vr)[\/: ]([\d.]+)/.exec(str63); re8.exec("144631658.0.10.1231365869"); re8.exec("144631658.0.10.1231367054"); re8.exec( "144631658.1231365869.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.1231367054.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "144631658.1670816052019209000.1231365869.1231365869.1231365869.1" ); re8.exec( "144631658.1796080716621419500.1231367054.1231367054.1231367054.1" ); re8.exec(str94); re8.exec(str95); re8.exec(str96); re8.exec(str97); re8.exec( "__hgzn=144631658.1670816052019209000.1231365869.1231365869.1231365869.1" ); re8.exec( "__hgzn=144631658.1796080716621419500.1231367054.1231367054.1231367054.1" ); re8.exec("__hgzo=144631658.0.10.1231365869"); re8.exec("__hgzo=144631658.0.10.1231367054"); re8.exec( "__hgzm=144631658.1231365869.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re8.exec( "__hgzm=144631658.1231367054.1.1.hgzpfe=(qverpg)|hgzppa=(qverpg)|hgzpzq=(abar)" ); re34.exec(str78); re34.exec(str79); re34.exec(str81); re74.exec(str77); re74.exec("*"); re74.exec(str82); re74.exec(str83); re74.exec(str86); re74.exec("rzorq"); re74.exec("sbez.nwnk"); re74.exec(str90); re74.exec("bowrpg"); /\/onfr.wf(\?.+)?$/.exec( "/uggc://wf.hv-cbegny.qr/tzk/ubzr/wf/20080602/onfr.wf" ); re28.exec("uvag ynfgUvag ynfg"); re75.exec(""); re76.exec(""); re77.exec(""); re78.exec(""); re80.exec(str77); re80.exec("*"); re80.exec(".pybfr"); re80.exec(str82); re80.exec(str83); re80.exec(str84); re80.exec(str86); re80.exec("qg"); re80.exec("rzorq"); re80.exec("sbez.nwnk"); re80.exec(str90); re80.exec("bowrpg"); re61.exec("qlaYvo.wf"); re61.exec("rssrpgYvo.wf"); re61.exec("uggc://jjj.tzk.arg/qr/?fgnghf=uvajrvf"); re92.exec(" .pybfr"); re92.exec(" n.svryqOgaPnapry"); re92.exec(" qg"); re92.exec(str48); re92.exec(".nwnk"); re92.exec(".svryqOga,n.svryqOgaPnapry"); re92.exec(".svryqOgaPnapry"); re92.exec(".bow-nppbeqvba qg"); re68.exec(str77); re68.exec("*"); re68.exec(".pybfr"); re68.exec(str82); re68.exec(str83); re68.exec(str84); re68.exec(str86); re68.exec("qg"); re68.exec("rzorq"); re68.exec("sbez.nwnk"); re68.exec(str90); re68.exec("bowrpg"); re93.exec(" .pybfr"); re93.exec(" n.svryqOgaPnapry"); re93.exec(" qg"); re93.exec(str48); re93.exec(".nwnk"); re93.exec(".svryqOga,n.svryqOgaPnapry"); re93.exec(".svryqOgaPnapry"); re93.exec(".bow-nppbeqvba qg"); re81.exec(str77); re81.exec("*"); re81.exec(str48); re81.exec(".pybfr"); re81.exec(str82); re81.exec(str83); re81.exec(str84); re81.exec(str86); re81.exec("qg"); re81.exec("rzorq"); re81.exec("sbez.nwnk"); re81.exec(str90); re81.exec("bowrpg"); re94.exec(" .pybfr"); re94.exec(" n.svryqOgaPnapry"); re94.exec(" qg"); re94.exec(str48); re94.exec(".nwnk"); re94.exec(".svryqOga,n.svryqOgaPnapry"); re94.exec(".svryqOgaPnapry"); re94.exec(".bow-nppbeqvba qg"); re94.exec("[anzr=nwnkHey]"); re94.exec(str82); re31.exec("rf"); re31.exec("wn"); re82.exec(str77); re82.exec("*"); re82.exec(str48); re82.exec(".pybfr"); re82.exec(str82); re82.exec(str83); re82.exec(str84); re82.exec(str86); re82.exec("qg"); re82.exec("rzorq"); re82.exec("sbez.nwnk"); re82.exec(str90); re82.exec("bowrpg"); re83.exec(str98); re83.exec("shapgvba sbphf() { [angvir pbqr] }"); re62.exec("#Ybtva"); re62.exec("#Ybtva_cnffjbeq"); re62.exec(str77); re62.exec("#fubhgobkWf"); re62.exec("#fubhgobkWfReebe"); re62.exec("#fubhgobkWfFhpprff"); re62.exec("*"); re62.exec(str82); re62.exec(str83); re62.exec(str86); re62.exec("rzorq"); re62.exec("sbez.nwnk"); re62.exec(str90); re62.exec("bowrpg"); re49.exec("pbagrag"); re24.exec(str6); /xbadhrebe/.exec(str63); /znp/.exec("jva32"); /zbmvyyn/.exec(str63); /zfvr/.exec(str63); /ag\s5\.1/.exec(str63); /bcren/.exec(str63); /fnsnev/.exec(str63); /jva/.exec("jva32"); /jvaqbjf/.exec(str63); } } function run() { for (var i = 0; i < 5; i++) { runBlock0(); runBlock1(); runBlock2(); runBlock3(); runBlock4(); runBlock5(); runBlock6(); runBlock7(); runBlock8(); runBlock9(); runBlock10(); runBlock11(); } } this.run = run; } ================================================ FILE: benchmarks/v8-v7/richards.js ================================================ // Copyright 2006-2008 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // This is a JavaScript implementation of the Richards // benchmark from: // // http://www.cl.cam.ac.uk/~mr10/Bench.html // // The benchmark was originally implemented in BCPL by // Martin Richards. var Richards = new BenchmarkSuite("Richards", 35302, [ new Benchmark("Richards", runRichards), ]); /** * The Richards benchmark simulates the task dispatcher of an * operating system. **/ function runRichards() { var scheduler = new Scheduler(); scheduler.addIdleTask(ID_IDLE, 0, null, COUNT); var queue = new Packet(null, ID_WORKER, KIND_WORK); queue = new Packet(queue, ID_WORKER, KIND_WORK); scheduler.addWorkerTask(ID_WORKER, 1000, queue); queue = new Packet(null, ID_DEVICE_A, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue); queue = new Packet(null, ID_DEVICE_B, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue); scheduler.addDeviceTask(ID_DEVICE_A, 4000, null); scheduler.addDeviceTask(ID_DEVICE_B, 5000, null); scheduler.schedule(); if ( scheduler.queueCount != EXPECTED_QUEUE_COUNT || scheduler.holdCount != EXPECTED_HOLD_COUNT ) { var msg = "Error during execution: queueCount = " + scheduler.queueCount + ", holdCount = " + scheduler.holdCount + "."; throw new Error(msg); } } var COUNT = 1000; /** * These two constants specify how many times a packet is queued and * how many times a task is put on hold in a correct run of richards. * They don't have any meaning a such but are characteristic of a * correct run so if the actual queue or hold count is different from * the expected there must be a bug in the implementation. **/ var EXPECTED_QUEUE_COUNT = 2322; var EXPECTED_HOLD_COUNT = 928; /** * A scheduler can be used to schedule a set of tasks based on their relative * priorities. Scheduling is done by maintaining a list of task control blocks * which holds tasks and the data queue they are processing. * @constructor */ function Scheduler() { this.queueCount = 0; this.holdCount = 0; this.blocks = new Array(NUMBER_OF_IDS); this.list = null; this.currentTcb = null; this.currentId = null; } var ID_IDLE = 0; var ID_WORKER = 1; var ID_HANDLER_A = 2; var ID_HANDLER_B = 3; var ID_DEVICE_A = 4; var ID_DEVICE_B = 5; var NUMBER_OF_IDS = 6; var KIND_DEVICE = 0; var KIND_WORK = 1; /** * Add an idle task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task * @param {int} count the number of times to schedule the task */ Scheduler.prototype.addIdleTask = function (id, priority, queue, count) { this.addRunningTask(id, priority, queue, new IdleTask(this, 1, count)); }; /** * Add a work task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task */ Scheduler.prototype.addWorkerTask = function (id, priority, queue) { this.addTask(id, priority, queue, new WorkerTask(this, ID_HANDLER_A, 0)); }; /** * Add a handler task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task */ Scheduler.prototype.addHandlerTask = function (id, priority, queue) { this.addTask(id, priority, queue, new HandlerTask(this)); }; /** * Add a handler task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task */ Scheduler.prototype.addDeviceTask = function (id, priority, queue) { this.addTask(id, priority, queue, new DeviceTask(this)); }; /** * Add the specified task and mark it as running. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task * @param {Task} task the task to add */ Scheduler.prototype.addRunningTask = function (id, priority, queue, task) { this.addTask(id, priority, queue, task); this.currentTcb.setRunning(); }; /** * Add the specified task to this scheduler. * @param {int} id the identity of the task * @param {int} priority the task's priority * @param {Packet} queue the queue of work to be processed by the task * @param {Task} task the task to add */ Scheduler.prototype.addTask = function (id, priority, queue, task) { this.currentTcb = new TaskControlBlock(this.list, id, priority, queue, task); this.list = this.currentTcb; this.blocks[id] = this.currentTcb; }; /** * Execute the tasks managed by this scheduler. */ Scheduler.prototype.schedule = function () { this.currentTcb = this.list; while (this.currentTcb != null) { if (this.currentTcb.isHeldOrSuspended()) { this.currentTcb = this.currentTcb.link; } else { this.currentId = this.currentTcb.id; this.currentTcb = this.currentTcb.run(); } } }; /** * Release a task that is currently blocked and return the next block to run. * @param {int} id the id of the task to suspend */ Scheduler.prototype.release = function (id) { var tcb = this.blocks[id]; if (tcb == null) return tcb; tcb.markAsNotHeld(); if (tcb.priority > this.currentTcb.priority) { return tcb; } else { return this.currentTcb; } }; /** * Block the currently executing task and return the next task control block * to run. The blocked task will not be made runnable until it is explicitly * released, even if new work is added to it. */ Scheduler.prototype.holdCurrent = function () { this.holdCount++; this.currentTcb.markAsHeld(); return this.currentTcb.link; }; /** * Suspend the currently executing task and return the next task control block * to run. If new work is added to the suspended task it will be made runnable. */ Scheduler.prototype.suspendCurrent = function () { this.currentTcb.markAsSuspended(); return this.currentTcb; }; /** * Add the specified packet to the end of the worklist used by the task * associated with the packet and make the task runnable if it is currently * suspended. * @param {Packet} packet the packet to add */ Scheduler.prototype.queue = function (packet) { var t = this.blocks[packet.id]; if (t == null) return t; this.queueCount++; packet.link = null; packet.id = this.currentId; return t.checkPriorityAdd(this.currentTcb, packet); }; /** * A task control block manages a task and the queue of work packages associated * with it. * @param {TaskControlBlock} link the preceding block in the linked block list * @param {int} id the id of this block * @param {int} priority the priority of this block * @param {Packet} queue the queue of packages to be processed by the task * @param {Task} task the task * @constructor */ function TaskControlBlock(link, id, priority, queue, task) { this.link = link; this.id = id; this.priority = priority; this.queue = queue; this.task = task; if (queue == null) { this.state = STATE_SUSPENDED; } else { this.state = STATE_SUSPENDED_RUNNABLE; } } /** * The task is running and is currently scheduled. */ var STATE_RUNNING = 0; /** * The task has packets left to process. */ var STATE_RUNNABLE = 1; /** * The task is not currently running. The task is not blocked as such and may * be started by the scheduler. */ var STATE_SUSPENDED = 2; /** * The task is blocked and cannot be run until it is explicitly released. */ var STATE_HELD = 4; var STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE; var STATE_NOT_HELD = ~STATE_HELD; TaskControlBlock.prototype.setRunning = function () { this.state = STATE_RUNNING; }; TaskControlBlock.prototype.markAsNotHeld = function () { this.state = this.state & STATE_NOT_HELD; }; TaskControlBlock.prototype.markAsHeld = function () { this.state = this.state | STATE_HELD; }; TaskControlBlock.prototype.isHeldOrSuspended = function () { return (this.state & STATE_HELD) != 0 || this.state == STATE_SUSPENDED; }; TaskControlBlock.prototype.markAsSuspended = function () { this.state = this.state | STATE_SUSPENDED; }; TaskControlBlock.prototype.markAsRunnable = function () { this.state = this.state | STATE_RUNNABLE; }; /** * Runs this task, if it is ready to be run, and returns the next task to run. */ TaskControlBlock.prototype.run = function () { var packet; if (this.state == STATE_SUSPENDED_RUNNABLE) { packet = this.queue; this.queue = packet.link; if (this.queue == null) { this.state = STATE_RUNNING; } else { this.state = STATE_RUNNABLE; } } else { packet = null; } return this.task.run(packet); }; /** * Adds a packet to the worklist of this block's task, marks this as runnable if * necessary, and returns the next runnable object to run (the one * with the highest priority). */ TaskControlBlock.prototype.checkPriorityAdd = function (task, packet) { if (this.queue == null) { this.queue = packet; this.markAsRunnable(); if (this.priority > task.priority) return this; } else { this.queue = packet.addTo(this.queue); } return task; }; TaskControlBlock.prototype.toString = function () { return "tcb { " + this.task + "@" + this.state + " }"; }; /** * An idle task doesn't do any work itself but cycles control between the two * device tasks. * @param {Scheduler} scheduler the scheduler that manages this task * @param {int} v1 a seed value that controls how the device tasks are scheduled * @param {int} count the number of times this task should be scheduled * @constructor */ function IdleTask(scheduler, v1, count) { this.scheduler = scheduler; this.v1 = v1; this.count = count; } IdleTask.prototype.run = function (packet) { this.count--; if (this.count == 0) return this.scheduler.holdCurrent(); if ((this.v1 & 1) == 0) { this.v1 = this.v1 >> 1; return this.scheduler.release(ID_DEVICE_A); } else { this.v1 = (this.v1 >> 1) ^ 0xd008; return this.scheduler.release(ID_DEVICE_B); } }; IdleTask.prototype.toString = function () { return "IdleTask"; }; /** * A task that suspends itself after each time it has been run to simulate * waiting for data from an external device. * @param {Scheduler} scheduler the scheduler that manages this task * @constructor */ function DeviceTask(scheduler) { this.scheduler = scheduler; this.v1 = null; } DeviceTask.prototype.run = function (packet) { if (packet == null) { if (this.v1 == null) return this.scheduler.suspendCurrent(); var v = this.v1; this.v1 = null; return this.scheduler.queue(v); } else { this.v1 = packet; return this.scheduler.holdCurrent(); } }; DeviceTask.prototype.toString = function () { return "DeviceTask"; }; /** * A task that manipulates work packets. * @param {Scheduler} scheduler the scheduler that manages this task * @param {int} v1 a seed used to specify how work packets are manipulated * @param {int} v2 another seed used to specify how work packets are manipulated * @constructor */ function WorkerTask(scheduler, v1, v2) { this.scheduler = scheduler; this.v1 = v1; this.v2 = v2; } WorkerTask.prototype.run = function (packet) { if (packet == null) { return this.scheduler.suspendCurrent(); } else { if (this.v1 == ID_HANDLER_A) { this.v1 = ID_HANDLER_B; } else { this.v1 = ID_HANDLER_A; } packet.id = this.v1; packet.a1 = 0; for (var i = 0; i < DATA_SIZE; i++) { this.v2++; if (this.v2 > 26) this.v2 = 1; packet.a2[i] = this.v2; } return this.scheduler.queue(packet); } }; WorkerTask.prototype.toString = function () { return "WorkerTask"; }; /** * A task that manipulates work packets and then suspends itself. * @param {Scheduler} scheduler the scheduler that manages this task * @constructor */ function HandlerTask(scheduler) { this.scheduler = scheduler; this.v1 = null; this.v2 = null; } HandlerTask.prototype.run = function (packet) { if (packet != null) { if (packet.kind == KIND_WORK) { this.v1 = packet.addTo(this.v1); } else { this.v2 = packet.addTo(this.v2); } } if (this.v1 != null) { var count = this.v1.a1; var v; if (count < DATA_SIZE) { if (this.v2 != null) { v = this.v2; this.v2 = this.v2.link; v.a1 = this.v1.a2[count]; this.v1.a1 = count + 1; return this.scheduler.queue(v); } } else { v = this.v1; this.v1 = this.v1.link; return this.scheduler.queue(v); } } return this.scheduler.suspendCurrent(); }; HandlerTask.prototype.toString = function () { return "HandlerTask"; }; /* --- * * P a c k e t * --- */ var DATA_SIZE = 4; /** * A simple package of data that is manipulated by the tasks. The exact layout * of the payload data carried by a packet is not importaint, and neither is the * nature of the work performed on packets by the tasks. * * Besides carrying data, packets form linked lists and are hence used both as * data and worklists. * @param {Packet} link the tail of the linked list of packets * @param {int} id an ID for this packet * @param {int} kind the type of this packet * @constructor */ function Packet(link, id, kind) { this.link = link; this.id = id; this.kind = kind; this.a1 = 0; this.a2 = new Array(DATA_SIZE); } /** * Add this packet to the end of a worklist, and return the worklist. * @param {Packet} queue the worklist to add this packet to */ Packet.prototype.addTo = function (queue) { this.link = null; if (queue == null) return this; var peek, next = queue; while ((peek = next.link) != null) next = peek; next.link = this; return queue; }; Packet.prototype.toString = function () { return "Packet"; }; ================================================ FILE: benchmarks/v8-v7/splay.js ================================================ // Copyright 2009 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // This benchmark is based on a JavaScript log processing module used // by the V8 profiler to generate execution time profiles for runs of // JavaScript applications, and it effectively measures how fast the // JavaScript engine is at allocating nodes and reclaiming the memory // used for old nodes. Because of the way splay trees work, the engine // also has to deal with a lot of changes to the large tree object // graph. var Splay = new BenchmarkSuite("Splay", 81491, [ new Benchmark("Splay", SplayRun, SplaySetup, SplayTearDown), ]); // Configuration. var kSplayTreeSize = 8000; var kSplayTreeModifications = 80; var kSplayTreePayloadDepth = 5; var splayTree = null; function GeneratePayloadTree(depth, tag) { if (depth == 0) { return { array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], string: "String for key " + tag + " in leaf node", }; } else { return { left: GeneratePayloadTree(depth - 1, tag), right: GeneratePayloadTree(depth - 1, tag), }; } } function GenerateKey() { // The benchmark framework guarantees that Math.random is // deterministic; see base.js. return Math.random(); } function InsertNewNode() { // Insert new node with a unique key. var key; do { key = GenerateKey(); } while (splayTree.find(key) != null); var payload = GeneratePayloadTree(kSplayTreePayloadDepth, String(key)); splayTree.insert(key, payload); return key; } function SplaySetup() { splayTree = new SplayTree(); for (var i = 0; i < kSplayTreeSize; i++) InsertNewNode(); } function SplayTearDown() { // Allow the garbage collector to reclaim the memory // used by the splay tree no matter how we exit the // tear down function. var keys = splayTree.exportKeys(); splayTree = null; // Verify that the splay tree has the right size. var length = keys.length; if (length != kSplayTreeSize) { throw new Error("Splay tree has wrong size"); } // Verify that the splay tree has sorted, unique keys. for (var i = 0; i < length - 1; i++) { if (keys[i] >= keys[i + 1]) { throw new Error("Splay tree not sorted"); } } } function SplayRun() { // Replace a few nodes in the splay tree. for (var i = 0; i < kSplayTreeModifications; i++) { var key = InsertNewNode(); var greatest = splayTree.findGreatestLessThan(key); if (greatest == null) splayTree.remove(key); else splayTree.remove(greatest.key); } } /** * Constructs a Splay tree. A splay tree is a self-balancing binary * search tree with the additional property that recently accessed * elements are quick to access again. It performs basic operations * such as insertion, look-up and removal in O(log(n)) amortized time. * * @constructor */ function SplayTree() {} /** * Pointer to the root node of the tree. * * @type {SplayTree.Node} * @private */ SplayTree.prototype.root_ = null; /** * @return {boolean} Whether the tree is empty. */ SplayTree.prototype.isEmpty = function () { return !this.root_; }; /** * Inserts a node into the tree with the specified key and value if * the tree does not already contain a node with the specified key. If * the value is inserted, it becomes the root of the tree. * * @param {number} key Key to insert into the tree. * @param {*} value Value to insert into the tree. */ SplayTree.prototype.insert = function (key, value) { if (this.isEmpty()) { this.root_ = new SplayTree.Node(key, value); return; } // Splay on the key to move the last node on the search path for // the key to the root of the tree. this.splay_(key); if (this.root_.key == key) { return; } var node = new SplayTree.Node(key, value); if (key > this.root_.key) { node.left = this.root_; node.right = this.root_.right; this.root_.right = null; } else { node.right = this.root_; node.left = this.root_.left; this.root_.left = null; } this.root_ = node; }; /** * Removes a node with the specified key from the tree if the tree * contains a node with this key. The removed node is returned. If the * key is not found, an exception is thrown. * * @param {number} key Key to find and remove from the tree. * @return {SplayTree.Node} The removed node. */ SplayTree.prototype.remove = function (key) { if (this.isEmpty()) { throw Error("Key not found: " + key); } this.splay_(key); if (this.root_.key != key) { throw Error("Key not found: " + key); } var removed = this.root_; if (!this.root_.left) { this.root_ = this.root_.right; } else { var right = this.root_.right; this.root_ = this.root_.left; // Splay to make sure that the new root has an empty right child. this.splay_(key); // Insert the original right child as the right child of the new // root. this.root_.right = right; } return removed; }; /** * Returns the node having the specified key or null if the tree doesn't contain * a node with the specified key. * * @param {number} key Key to find in the tree. * @return {SplayTree.Node} Node having the specified key. */ SplayTree.prototype.find = function (key) { if (this.isEmpty()) { return null; } this.splay_(key); return this.root_.key == key ? this.root_ : null; }; /** * @return {SplayTree.Node} Node having the maximum key value. */ SplayTree.prototype.findMax = function (opt_startNode) { if (this.isEmpty()) { return null; } var current = opt_startNode || this.root_; while (current.right) { current = current.right; } return current; }; /** * @return {SplayTree.Node} Node having the maximum key value that * is less than the specified key value. */ SplayTree.prototype.findGreatestLessThan = function (key) { if (this.isEmpty()) { return null; } // Splay on the key to move the node with the given key or the last // node on the search path to the top of the tree. this.splay_(key); // Now the result is either the root node or the greatest node in // the left subtree. if (this.root_.key < key) { return this.root_; } else if (this.root_.left) { return this.findMax(this.root_.left); } else { return null; } }; /** * @return {Array<*>} An array containing all the keys of tree's nodes. */ SplayTree.prototype.exportKeys = function () { var result = []; if (!this.isEmpty()) { this.root_.traverse_(function (node) { result.push(node.key); }); } return result; }; /** * Perform the splay operation for the given key. Moves the node with * the given key to the top of the tree. If no node has the given * key, the last node on the search path is moved to the top of the * tree. This is the simplified top-down splaying algorithm from: * "Self-adjusting Binary Search Trees" by Sleator and Tarjan * * @param {number} key Key to splay the tree on. * @private */ SplayTree.prototype.splay_ = function (key) { if (this.isEmpty()) { return; } // Create a dummy node. The use of the dummy node is a bit // counter-intuitive: The right child of the dummy node will hold // the L tree of the algorithm. The left child of the dummy node // will hold the R tree of the algorithm. Using a dummy node, left // and right will always be nodes and we avoid special cases. var dummy, left, right; dummy = left = right = new SplayTree.Node(null, null); var current = this.root_; while (true) { if (key < current.key) { if (!current.left) { break; } if (key < current.left.key) { // Rotate right. var tmp = current.left; current.left = tmp.right; tmp.right = current; current = tmp; if (!current.left) { break; } } // Link right. right.left = current; right = current; current = current.left; } else if (key > current.key) { if (!current.right) { break; } if (key > current.right.key) { // Rotate left. var tmp = current.right; current.right = tmp.left; tmp.left = current; current = tmp; if (!current.right) { break; } } // Link left. left.right = current; left = current; current = current.right; } else { break; } } // Assemble. left.right = current.left; right.left = current.right; current.left = dummy.right; current.right = dummy.left; this.root_ = current; }; /** * Constructs a Splay tree node. * * @param {number} key Key. * @param {*} value Value. */ SplayTree.Node = function (key, value) { this.key = key; this.value = value; }; /** * @type {SplayTree.Node} */ SplayTree.Node.prototype.left = null; /** * @type {SplayTree.Node} */ SplayTree.Node.prototype.right = null; /** * Performs an ordered traversal of the subtree starting at * this SplayTree.Node. * * @param {function(SplayTree.Node)} f Visitor function. * @private */ SplayTree.Node.prototype.traverse_ = function (f) { var current = this; while (current) { var left = current.left; if (left) left.traverse_(f); f(current); current = current.right; } }; ================================================ FILE: build.mjs ================================================ import * as esbuild from "esbuild"; import fs from "node:fs/promises"; import { createRequire } from "node:module"; import path from "node:path"; const require = createRequire(import.meta.url); process.env.NODE_PATH = "."; const TMP_DIR = `.tmp-llrt-aws-sdk`; const SRC_DIR = path.join("llrt_core", "src", "modules", "js"); const TESTS_DIR = "tests"; const TESTS_SUB_DIR = process.env.TEST_SUB_DIR || "unit"; const OUT_DIR = "bundle/js"; const SHIMS = new Map(); const SDK_BUNDLE_MODE = process.env.SDK_BUNDLE_MODE || "NONE"; // "FULL" or "STD" or "NONE" async function readFilesRecursive(dir, filePredicate) { const dirents = await fs.readdir(dir, { withFileTypes: true }); const files = await Promise.all( dirents.map((dirent) => { const filePath = path.join(dir, dirent.name); if (dirent.isDirectory()) { return readFilesRecursive(filePath, filePredicate); } else { return filePredicate(filePath) ? filePath : []; } }) ); return Array.prototype.concat(...files); } const TEST_FILES = await readFilesRecursive( path.join(TESTS_DIR, TESTS_SUB_DIR), (filePath) => filePath.endsWith(".test.ts") || filePath.endsWith(".spec.ts") || filePath.endsWith(".any.js") ); const AWS_JSON_SHARED_COMMAND_REGEX = /{\s*const\s*headers\s*=\s*sharedHeaders\(("\w+")\);\s*let body;\s*body\s*=\s*JSON.stringify\(_json\(input\)\);\s*return buildHttpRpcRequest\(context,\s*headers,\s*"\/",\s*undefined,\s*body\);\s*}/gm; const AWS_JSON_SHARED_COMMAND_REGEX2 = /{\s*const\s*headers\s*=\s*sharedHeaders\(("\w+")\);\s*let body;\s*body\s*=\s*JSON.stringify\((\w+)\(input,\s*context\)\);\s*return buildHttpRpcRequest\(context,\s*headers,\s*"\/",\s*undefined,\s*body\);\s*}/gm; const MINIFY_JS = process.env.JS_MINIFY !== "0"; const SDK_UTILS_PACKAGE = "sdk-utils"; const ENTRYPOINTS = [ "stream", "stream/promises", "@llrt/test/index", "@llrt/test/worker", ]; const ES_BUILD_OPTIONS = { splitting: MINIFY_JS, minify: MINIFY_JS, sourcemap: false, target: "es2023", outdir: OUT_DIR, bundle: true, logLevel: "info", platform: "browser", format: "esm", external: [ "assert", "node:assert", "async_hooks", "node:async_hooks", "buffer", "node:buffer", "child_process", "node:child_process", "console", "node:console", "crypto", "node:crypto", "dgram", "node:dgram", "dns", "node:dns", "events", "node:events", "fs", "node:fs", "module", "node:module", "net", "node:net", "os", "node:os", "path", "node:path", "perf_hooks", "node:perf_hooks", "process", "node:process", "stream", "node:stream", "string_decoder", "node:string_decoder", "timers", "node:timers", "tty", "node:tty", "url", "node:url", "util", "node:util", "zlib", "node:zlib", "llrt:hex", "llrt:timezone", "llrt:util", "llrt:qjs", "llrt:xml", "@aws-crypto", ], }; const SDK_DATA = await parseSdkData(); const ADDITIONAL_PACKAGES = [ "@aws-sdk/core", "@aws-sdk/credential-providers", "@aws-sdk/s3-presigned-post", "@aws-sdk/s3-request-presigner", "@aws-sdk/util-dynamodb", "@aws-sdk/util-user-agent-browser", "@smithy/config-resolver", "@smithy/core", "@smithy/eventstream-codec", "@smithy/eventstream-serde-browser", "@smithy/eventstream-serde-config-resolver", "@smithy/eventstream-serde-universal", "@smithy/fetch-http-handler", "@smithy/invalid-dependency", "@smithy/is-array-buffer", "@smithy/middleware-compression", "@smithy/middleware-content-length", "@smithy/middleware-endpoint", "@smithy/middleware-retry", "@smithy/middleware-serde", "@smithy/middleware-stack", "@smithy/property-provider", "@smithy/protocol-http", "@smithy/querystring-builder", "@smithy/querystring-parser", "@smithy/service-error-classification", "@smithy/signature-v4", "@smithy/smithy-client", "@smithy/types", "@smithy/url-parser", "@smithy/util-base64", "@smithy/util-body-length-browser", "@smithy/util-config-provider", "@smithy/util-defaults-mode-browser", "@smithy/util-endpoints", "@smithy/util-hex-encoding", "@smithy/util-middleware", "@smithy/util-retry", "@smithy/util-stream", "@smithy/util-uri-escape", "@smithy/util-utf8", "@smithy/util-waiter", ]; const REPLACEMENT_PACKAGES = { "@aws-crypto/sha1-browser": "shims/@aws-crypto/sha1-browser.js", "@aws-crypto/sha256-browser": "shims/@aws-crypto/sha256-browser.js", "@aws-crypto/crc32": "shims/@aws-crypto/crc32.js", "@aws-crypto/crc32c": "shims/@aws-crypto/crc32c.js", "@smithy/abort-controller": "shims/@smithy/abort-controller.js", }; const SERVICE_ENDPOINTS_BY_PACKAGE = {}; const CLIENTS_BY_SDK = {}; const SDKS_BY_SDK_PACKAGES = {}; const SDK_PACKAGES = [...ADDITIONAL_PACKAGES]; Object.keys(SDK_DATA).forEach((sdk) => { const [clientName, serviceEndpoints, fullSdkOnly] = SDK_DATA[sdk] || []; if (SDK_BUNDLE_MODE == "FULL" || (SDK_BUNDLE_MODE == "STD" && !fullSdkOnly)) { const sdkPackage = `@aws-sdk/${sdk}`; SDK_PACKAGES.push(sdkPackage); SDKS_BY_SDK_PACKAGES[sdkPackage] = sdk; SERVICE_ENDPOINTS_BY_PACKAGE[sdk] = serviceEndpoints; CLIENTS_BY_SDK[sdk] = clientName; } }); async function parseSdkData() { const cfgData = await fs.readFile("sdk.cfg"); const cfgLines = cfgData.toString().split("\n"); const sdkData = {}; for (let line of cfgLines) { line = line.trim(); if (line.startsWith("#") || line == "") { continue; } // Parse the line const parts = line.split(","); //get and remove the item at 0 const packageName = parts.shift(); const clientName = parts.shift(); //get and remove the last item const fullSdkOnly = parts.pop() == 1; const endpoints = parts; // Log or store parsed information sdkData[packageName] = [clientName, endpoints, fullSdkOnly]; } return sdkData; } function resolveDefaultsModeConfigWrapper(config) { if (!config.credentials) { config.credentials = { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, sessionToken: process.env.AWS_SESSION_TOKEN, }; } if (!config.region) { config.region = process.env.AWS_REGION; } return resolveDefaultsModeConfig(config); } const awsJsonSharedCommand = (name, input, context, request) => { const headers = sharedHeaders(name); const body = JSON.stringify(request ? request(input, context) : _json(input)); return buildHttpRpcRequest(context, headers, "/", undefined, body); }; function defaultEndpointResolver(endpointParams, context = {}) { const paramsKey = calculateEndpointCacheKey(endpointParams); let endpoint = ENDPOINT_CACHE[paramsKey]; if (!endpoint) { endpoint = resolveEndpoint(ruleSet, { endpointParams, logger: context.logger, serviceName, }); ENDPOINT_CACHE[paramsKey] = endpoint; } if (serviceName === "s3") { const { hostname, protocol, pathname, search } = endpoint.url; const [bucket, host] = hostname.split(".s3."); if (host) { const path = pathname === "/" ? "" : pathname; const newHref = `${protocol}//s3.${host}/${bucket}${path}${ search ? `?${search}` : "" }`; endpoint.url.href = newHref; } } return endpoint; } const WRAPPERS = [ { name: "resolveDefaultsModeConfig", filter: /resolveDefaultsModeConfig.js$/, wrapper: resolveDefaultsModeConfigWrapper, }, ]; function executeClientCommand(command, optionsOrCb, cb) { if (typeof optionsOrCb === "function") { this.send(command, optionsOrCb); } else if (typeof cb === "function") { if (typeof optionsOrCb !== "object") throw new Error(`Expect http options but get ${typeof optionsOrCb}`); this.send(command, optionsOrCb || {}, cb); } else { return this.send(command, optionsOrCb); } } const ENDPOINT_CACHE_KEY_LOOKUP = { Bucket: "b", ForcePathStyle: "f", UseArnRegion: "n", DisableMultiRegionAccessPoints: "m", Accelerate: "a", UseGlobalEndpoint: "g", UseFIPS: "i", Endpoint: "e", Region: "r", UseDualStack: "d", }; const ENDPOINT_CACHE_KEY_LOOKUP_NAME = Object.keys({ ENDPOINT_CACHE_KEY_LOOKUP, })[0]; function calculateEndpointCacheKey(obj) { let str = ""; for (const key in obj) { if (obj[key] === true) { str += ENDPOINT_CACHE_KEY_LOOKUP[key]; } else if (typeof obj[key] === "string") { str += obj[key]; } } return str; } function codeToRegex(fn, includeSignature = false) { return new RegExp( fn .toString() .split("\n") .reduce((acc, line, index, array) => { if (includeSignature || (index > 0 && index < array.length - 1)) { acc.push(line.trim()); } return acc; }, []) .join("\n") .replace(/\s+/g, "\\s*") .replace(/\(/g, "\\(") .replace(/\)/g, "\\)") .replace(/\./g, "\\.") .replace(/\?,/g, "\\?") .replace(/\,/g, ",?") .replace(/\$/g, "\\$") .replace(/\{/g, "\\s*{") .replace(/\}/g, "}\\s*") .replace(/\|/g, "\\|"), "g" ); } const AWS_SDK_PLUGIN = { name: "aws-sdk-plugin", setup(build) { const tslib = require.resolve("tslib/tslib.es6.js"); const executeClientCommandRegex = codeToRegex(executeClientCommand); build.onResolve({ filter: /^tslib$/ }, () => { return { path: tslib }; }); //load replace shims for (const [filter, contents] of SHIMS) { build.onLoad({ filter }, () => ({ contents, })); } for (const sdk in CLIENTS_BY_SDK) { const clientClass = CLIENTS_BY_SDK[sdk]; build.onLoad( { filter: new RegExp(`@aws-sdk\\/${sdk}\\/dist-es/${clientClass}.js`) }, async ({ path: filePath }) => { const source = (await fs.readFile(filePath)).toString(); const name = path.parse(filePath).name; console.log("Optimized:", name); let contents = `import { ${executeClientCommand.name} } from "${SDK_UTILS_PACKAGE}"\n`; contents += source.replace( executeClientCommandRegex, `return ${executeClientCommand.name}.call(this, command, optionsOrCb, cb)` ); return { contents, }; } ); } build.onLoad({ filter: /xml-parser\.browser\.js$/ }, async (args) => { const realPath = path.join(path.dirname(args.path), "xml-parser.js"); const contents = await fs.readFile(realPath, "utf8"); return { contents, loader: "js" }; }); build.onLoad( { filter: /protocols\/Aws_json1_1\.js$/ }, async ({ path: filePath }) => { const name = path.parse(filePath).name; let source = (await fs.readFile(filePath)).toString(); const sourceLength = source.length; source = source.replace( AWS_JSON_SHARED_COMMAND_REGEX, (_, name) => `${awsJsonSharedCommand.name}(${name}, input, context)` ); source = source.replace( AWS_JSON_SHARED_COMMAND_REGEX2, (_, name, request) => `${awsJsonSharedCommand.name}(${name}, input, context, ${request})` ); if (sourceLength === source.length) { throw new Error(`Failed to optimize: ${name}`); } console.log("Optimized:", name); source = `const ${ awsJsonSharedCommand.name } = ${awsJsonSharedCommand.toString()}\n\n${source}`; return { contents: source, }; } ); build.onResolve({ filter: /^sdk-utils$/ }, (args) => ({ path: args.path, namespace: "sdk-utils-ns", })); build.onLoad({ filter: /.*/, namespace: "sdk-utils-ns" }, (args) => { let contents = ""; contents += `import { Command as $Command } from "@smithy/smithy-client";\n`; contents += `import { getEndpointPlugin } from "@smithy/middleware-endpoint";\n`; contents += `import { getSerdePlugin } from "@smithy/middleware-serde";\n`; contents += `import { SMITHY_CONTEXT_KEY } from "@smithy/types";\n`; contents += `export ${executeClientCommand.toString()}\n`; contents += `const ${ENDPOINT_CACHE_KEY_LOOKUP_NAME} = ${JSON.stringify( ENDPOINT_CACHE_KEY_LOOKUP )};\n`; contents += `export const cloneModel = (obj) => ({...obj})\n`; contents += `export ${calculateEndpointCacheKey.toString()}\n`; return { contents, resolveDir: path.dirname(args.path), }; }); build.onLoad( { filter: /endpoint\/endpointResolver\.js$/ }, async ({ path: filePath }) => { let source = (await fs.readFile(filePath)).toString(); source = source.replace( /export const defaultEndpointResolver =.*?};/s, "" ); let contents = `import { ${calculateEndpointCacheKey.name} } from "${SDK_UTILS_PACKAGE}"\n`; contents += source; const serviceName = path .resolve(filePath, "../../../") .split("/") .pop() .substring("client-".length); contents += `const serviceName = "${serviceName}";\n`; contents += `const ENDPOINT_CACHE = {};\n`; contents += `export ${defaultEndpointResolver.toString()}`; return { contents, }; } ); for (const { filter, wrapper, name } of WRAPPERS) { build.onLoad({ filter }, async ({ path }) => { let source = (await fs.readFile(path)).toString(); let replaced = false; let contents = ""; source = source.replace( RegExp(`export\\s*(const\\s*${name})`), (_, replacement) => { replaced = true; return replacement; } ); if (!replaced) { contents += source; } else { const wrapperName = `${name}Wrapper`; contents += `${source}\n`; contents += `const ${wrapperName} = ${wrapper.toString()}\n`; contents += `export {${wrapperName} as ${name}}`; } return { contents, }; }); } build.onLoad({ filter: /package\.json$/ }, async ({ path }) => { let packageJson = JSON.parse(await fs.readFile(path)); let { version } = packageJson; const data = { version, }; return { contents: `export default ${JSON.stringify(data)}`, }; }); }, }; function esbuildShimPlugin(shims) { return { name: "esbuild-shim", setup(build) { shims.forEach(([filter, value], index) => { build.onResolve( { filter, }, (args) => ({ path: args.path, namespace: `esbuild-shim-${index}-ns`, }) ); build.onLoad( { filter: /.*/, namespace: `esbuild-shim-${index}-ns` }, () => { const contents = value || "export default {}"; return { contents, }; } ); }); }, }; } const requireProcessPlugin = { name: "require-process", setup(build) { build.onResolve({ filter: /^process\/$/ }, () => { return { path: "process", external: true }; }); }, }; async function rmTmpDir() { await fs.rm(TMP_DIR, { recursive: true, force: true, }); } async function createOutputDirectories() { await fs.rm(OUT_DIR, { recursive: true, force: true }); await fs.mkdir(OUT_DIR, { recursive: true }); await rmTmpDir(); await fs.mkdir(TMP_DIR, { recursive: true }); } async function loadShims() { const loadShim = async (filter, filename) => { const bytes = await fs.readFile(path.join("shims", filename)); SHIMS.set(filter, bytes.toString()); }; await Promise.all([ loadShim(/@aws-crypto/, "@aws-crypto/index.js"), loadShim(/@smithy\/util-hex-encoding/, "@smithy/util-hex-encoding.js"), loadShim(/@smithy\/util-utf8/, "@smithy/util-utf8.js"), loadShim(/stringHasher.js/, "string-hasher.js"), loadShim(/@smithy\/util-base64/, "@smithy/util-base64.js"), loadShim(/mnemonist\/lru-cache\.js/, "mnemonist/lru-cache.js"), loadShim(/collect-stream-body\.js/, "collect-stream-body.js"), loadShim(/sdk-stream-mixin.browser\.js/, "sdk-stream-mixin.js"), loadShim(/stream-collector\.js/, "stream-collector.js"), loadShim(/splitStream\.browser\.js/, "@smithy/split-stream.js"), loadShim( /create-read-stream-on-buffer\.browser\.js/, "create-read-stream.js" ), loadShim(/isStreaming.js/, "is-streaming.js"), ]); } async function buildLibrary() { const defaultLibEsBuildOption = { chunkNames: "llrt-[name]-runtime-[hash]", ...ES_BUILD_OPTIONS, splitting: false, keepNames: true, nodePaths: ["."], }; // Build lib const entryPoints = {}; ENTRYPOINTS.forEach((entry) => { entryPoints[entry] = path.join(SRC_DIR, entry); }); await esbuild.build({ ...defaultLibEsBuildOption, entryPoints, plugins: [requireProcessPlugin], sourcemap: false, }); // Build tests const testEntryPoints = TEST_FILES.reduce((acc, entry) => { const { name, dir } = path.parse(entry); const parentDir = path.basename(dir); acc[path.join("__tests__", parentDir, name)] = entry; return acc; }, {}); await esbuild.build({ ...defaultLibEsBuildOption, entryPoints: testEntryPoints, external: [...ES_BUILD_OPTIONS.external, "@aws-sdk", "@smithy"], sourcemap: false, }); } async function buildSdks() { const sdkEntryList = await Promise.all( SDK_PACKAGES.map(async (pkg) => { const packagePath = path.join(TMP_DIR, pkg); const sdk = SDKS_BY_SDK_PACKAGES[pkg]; const sdkIndexFile = path.join(packagePath, "index.js"); await fs.mkdir(packagePath, { recursive: true }); let sdkContents = `export * from "${pkg}";`; await fs.writeFile(sdkIndexFile, sdkContents); return [pkg, sdkIndexFile]; }) ); const sdkEntryPoints = Object.fromEntries(sdkEntryList); await Promise.all([ esbuild.build({ entryPoints: sdkEntryPoints, plugins: [AWS_SDK_PLUGIN, esbuildShimPlugin([[/^bowser$/]])], alias: { "@aws-sdk/util-utf8-browser": "@smithy/util-utf8", "@aws-sdk/util-utf8": "@smithy/util-utf8", "@smithy/md5-js": "crypto", "fast-xml-parser": "llrt:xml", "xml-parser.browser": "xml-parser", }, chunkNames: "llrt-[name]-sdk-[hash]", metafile: true, ...ES_BUILD_OPTIONS, }), esbuild.build({ entryPoints: REPLACEMENT_PACKAGES, ...ES_BUILD_OPTIONS, sourcemap: false, }), ]); //console.log(await esbuild.analyzeMetafile(result.metafile)); } console.log("Building..."); await createOutputDirectories(); let error; try { if (SDK_BUNDLE_MODE != "NONE") { await loadShims(); } await buildLibrary(); if (SDK_BUNDLE_MODE != "NONE") { await buildSdks(); } } catch (e) { error = e; } await rmTmpDir(); if (error) { throw error; } ================================================ FILE: example/clear-ddb-table.mjs ================================================ import { DynamoDBClient, ScanCommand, DescribeTableCommand, BatchWriteItemCommand, } from "@aws-sdk/client-dynamodb"; import path from "node:path"; const DDB_CLIENT = new DynamoDBClient({}); const TABLE_ARN = process.argv[2]; if (!TABLE_ARN) { console.error( `Usage: ${path.basename(process.argv0)} ${path.basename( process.argv[1] )} ` ); process.exit(1); } const segmentArray = (array, segmentSize) => Array.from({ length: Math.ceil(array.length / segmentSize) }, (_, index) => array.slice(index * segmentSize, (index + 1) * segmentSize) ); function extractRegionAndTableName(arn) { const parts = arn.split(":"); if ( parts.length >= 6 && parts[2] === "dynamodb" && parts[5].startsWith("table/") ) { const region = parts[3]; const tableName = parts[5].substring(6); return { region, tableName }; } else { return null; } } const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)); class AsyncProcessor { constructor(producerFunction, consumerFunction) { this.producerFunction = producerFunction; this.consumerFunction = consumerFunction; } static emptyJob() { let resolveFn; let rejectFn; const promise = new Promise((resolve, reject) => { resolveFn = resolve; rejectFn = reject; }); return [promise, resolveFn, rejectFn]; } async process() { let error; const jobs = [AsyncProcessor.emptyJob()]; const producer = async () => { let currentJob = jobs[0]; let nextJob; try { while (!error) { let data = await this.producerFunction(); if (data) { nextJob = AsyncProcessor.emptyJob(); jobs.push(nextJob); } currentJob[1](data); if (!data) { break; } currentJob = nextJob; } } catch (e) { error = e; throw e; } }; const consumer = async () => { try { while (!error) { const job = jobs.shift(); const data = await job[0]; if (!data) { break; } await this.consumerFunction(data); } } catch (e) { error = e; throw e; } }; await Promise.all([producer(), consumer()]); } } async function deleteItems(tableName, primaryKey, keys) { let deleteRequests = keys.map((key) => ({ DeleteRequest: { Key: { [primaryKey]: key }, }, })); let attempt = 0; if (deleteRequests.length === 0) { return; } const start = Date.now(); while (true) { const result = await DDB_CLIENT.send( new BatchWriteItemCommand({ RequestItems: { [tableName]: deleteRequests, }, }) ); if ( result.UnprocessedItems[tableName] && result.UnprocessedItems[tableName].length > 0 ) { deleteRequests = result.UnprocessedItems[tableName]; attempt++; if (attempt > 3) { throw new Error("Too many attempts"); } await sleep(500 * Math.pow(attempt, 2)); } else { break; } } console.log(`Deleted: ${Date.now() - start}ms`); } async function clearDynamoDBTable() { const { region, tableName } = extractRegionAndTableName(TABLE_ARN); process.env.AWS_REGION = region; const primaryKey = await getPrimaryKey(tableName); let exclusiveStartKey = undefined; let totalCount = 0; const processor = new AsyncProcessor( async () => { const start = Date.now(); const scanOutput = await DDB_CLIENT.send( new ScanCommand({ TableName: tableName, ProjectionExpression: primaryKey, ExclusiveStartKey: exclusiveStartKey, }) ); console.log(`Scanned: ${Date.now() - start}ms`); exclusiveStartKey = scanOutput.LastEvaluatedKey; const ids = scanOutput.Items.map((item) => item[primaryKey]); if (ids.length === 0) { return null; } return ids; }, async (keys) => { const segmentedKeys = segmentArray(keys, 25); await Promise.all( segmentedKeys.map(async (keys) => { await deleteItems(tableName, primaryKey, keys); totalCount += keys.length; if (totalCount > 10000) { process.exit(0); } console.log("Deleted:", totalCount); }) ); } ); await processor.process(); } async function getPrimaryKey(tableName) { const describeOutput = await DDB_CLIENT.send( new DescribeTableCommand({ TableName: tableName, }) ); return describeOutput.Table.KeySchema.find((key) => key.KeyType === "HASH") .AttributeName; } clearDynamoDBTable().catch((err) => { console.error(err); process.exit(1); }); ================================================ FILE: example/functions/build.mjs ================================================ import esbuild from "esbuild"; import fs from "node:fs/promises"; import path from "node:path"; const OUTDIR = "build"; await fs.rm(OUTDIR, { recursive: true, force: true }); async function buildReact() { const outbase = path.join(OUTDIR, "react"); const outfile = path.join(outbase, "index.mjs"); await fs.mkdir(outbase, { recursive: true }); await fs.copyFile("src/react/index.html", path.join(outbase, "index.html")); const devMode = process.argv.slice(2)[0] == "--dev"; await esbuild.build({ entryPoints: { index: "src/ssr.ts", app: "src/react/index.tsx", }, logLevel: "info", ...(!devMode && { platform: "node", }), external: ["@aws-sdk"], target: "es2023", format: devMode ? "cjs" : "esm", define: { "process.env.NODE_ENV": JSON.stringify("production"), }, loader: { ".svg": "file", }, bundle: true, outdir: outbase, }); await fs.rename(path.join(outbase, "index.js"), outfile); await fs.readFile(outfile).then((data) => { const indexSource = `import { createRequire } from "node:module";\nconst require = createRequire(import.meta.url);\n${data.toString()}`; return fs.writeFile(outfile, indexSource); }); } async function buildExternalSdkFunction() { const outbase = path.join(OUTDIR, "external"); const outfile = path.join(outbase, "index.mjs"); await esbuild.build({ entryPoints: { index: "src/non-included-sdk.mjs", }, logLevel: "info", platform: "browser", target: "es2023", format: "esm", bundle: true, minify: true, sourcemap: false, outfile, external: [ "@smithy", "@aws-sdk/core", "@aws-sdk/util-user-agent-browser", "@aws-crypto", "bowser", ], }); } await buildReact(); await buildExternalSdkFunction(); ================================================ FILE: example/functions/package.json ================================================ { "name": "@example/functions", "version": "1.0.0", "private": true, "type": "module", "scripts": { "build": "node build.mjs" }, "dependencies": { "@aws-sdk/client-dynamodb": "3.991.0", "@aws-sdk/client-ec2": "3.991.0", "@aws-sdk/client-s3": "3.991.0", "aws-sdk": "2.1693.0", "esbuild-css-modules-plugin": "3.1.5", "react": "19.2.4", "react-dom": "19.2.4" }, "devDependencies": { "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "esbuild": "0.27.3" } } ================================================ FILE: example/functions/src/api.ts ================================================ import { DynamoDBDocumentClient, PutCommand, ScanCommand, DeleteCommand, UpdateCommand, } from "@aws-sdk/lib-dynamodb"; import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { randomBytes } from "node:crypto"; import { Todo } from "./react/TodoList"; const uid = () => String.fromCharCode( ...randomBytes(20).map((d) => { return (d > 127 ? 97 : 65) + (d % 25); }) ) + new Date().getTime(); const CLIENT = new DynamoDBClient({}); const DOCUMENT_CLIENT = DynamoDBDocumentClient.from(CLIENT as any); const mapTodo = ( item: Todo ): { id: string; // Assuming id is a string attribute text: string; // Assuming text is a string attribute createdDate: string; // Assuming createdDate is a number attribute completedDate: string | null; } => ({ ...item, createdDate: new Date(parseInt(item.createdDate)).toISOString(), completedDate: item.completedDate ? new Date(parseInt(item.completedDate)).toISOString() : null, }); const API = { getAll: async () => { const response = await DOCUMENT_CLIENT.send( new ScanCommand({ TableName: process.env.TABLE_NAME, }) ); return response.Items.map(mapTodo); }, create: async (text: string) => { const newItem = { id: uid(), text, createdDate: Date.now(), }; await DOCUMENT_CLIENT.send( new PutCommand({ TableName: process.env.TABLE_NAME, Item: newItem, }) ); return newItem; }, delete: async (id: string) => { await DOCUMENT_CLIENT.send( new DeleteCommand({ TableName: process.env.TABLE_NAME, Key: { id, }, }) ); }, update: async (todo: Omit) => { await DOCUMENT_CLIENT.send( new UpdateCommand({ TableName: process.env.TABLE_NAME, Key: { id: todo.id, }, UpdateExpression: "set completedDate = :completedDate", ExpressionAttributeValues: { ":completedDate": todo.completedDate ? Date.now() : null, }, }) ); return todo; }, }; export default API; ================================================ FILE: example/functions/src/hello.mjs ================================================ export const handler = async () => ({ statusCode: 200, body: "Hello world!", }); ================================================ FILE: example/functions/src/non-included-sdk.mjs ================================================ // Import the necessary AWS SDK clients and commands import { EC2Client, DescribeInstancesCommand } from "@aws-sdk/client-ec2"; // Create an EC2 client const client = new EC2Client(); // Lambda handler function export const handler = async () => { const command = new DescribeInstancesCommand({}); // Send the command to the EC2 client const response = await client.send(command); // Extract instances information const instances = response.Reservations.flatMap( (reservation) => reservation.Instances ); // Return the list of instances return { statusCode: 200, body: JSON.stringify(instances), }; }; ================================================ FILE: example/functions/src/react/App.css ================================================ * { margin: 0; padding: 0; box-sizing: border-box; } html, body { font-family: Arial, Helvetica, sans-serif; background-color: #282c34; color: white; font-size: 1rem; } .app { display: flex; flex-direction: column; align-items: center; } .logo { padding: 1rem; height: 8rem; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .logo { animation: App-logo-spin infinite 20s linear; } } .header { display: flex; flex-direction: column; align-items: center; justify-content: center; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .error-message { color: red; font-weight: 400; margin: 1em; } .main { padding: 2rem; width: 100%; } @media (min-width: 768px) { .main { max-width: 600px; } } ================================================ FILE: example/functions/src/react/App.tsx ================================================ import logo from "./logo.svg"; import "./App.css"; import TodoList, { Todo } from "./TodoList"; type Props = { todoItems?: Todo[]; releaseName?: string; }; function App({ todoItems = [], releaseName = "" }: Props) { return (
logo

LLRT React TODO - {releaseName}

); } export default App; ================================================ FILE: example/functions/src/react/CreateTodo.tsx ================================================ type Props = { onCreate: (text: string) => void; }; function CreateTodo({ onCreate }: Props) { const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { const target = e.target as any; onCreate(target.value); target.value = ""; } }; return ( ); } export default CreateTodo; ================================================ FILE: example/functions/src/react/TodoItem.tsx ================================================ import { Todo } from "./TodoList"; type Props = { item: Todo; onDelete: (id: string) => void; onComplete: (id: string) => void; }; function TodoItem({ item: { id, text, createdDate, completedDate }, onDelete, onComplete, }: Props) { const handleDeleteClick = (e: React.MouseEvent) => { e.stopPropagation(); onDelete(id); }; return (
  • onComplete(id)} className="todo-item"> {completedDate ? "✔" : "⏲"} {text} 🗑️
  • ); } export default TodoItem; ================================================ FILE: example/functions/src/react/TodoList.css ================================================ .todo-list { margin-top: 2rem; } .todo-list.loading { touch-action: none; pointer-events: none; opacity: 0.5; } .todo-list ul { list-style: none; } .todo-list ul li { margin-bottom: 1rem; } .todo-item { cursor: pointer; display: flex; } .todo-text.completed { text-decoration: line-through; } .todo-item span { padding: 0.5rem; } .todo-text { flex-grow: 1; } .todo-item span:last-child { padding: 0.5rem; } .create-todo { width: 100%; padding: 0.5rem; } ================================================ FILE: example/functions/src/react/TodoList.tsx ================================================ import { useEffect, useState } from "react"; import TodoItem from "./TodoItem"; import "./TodoList.css"; import CreateTodo from "./CreateTodo"; type Props = { items: Todo[]; }; export type Todo = { id: string; text: string; createdDate: string; completedDate?: string; }; const BASE_URL = `${(globalThis.window && window.location.href) || "/"}api/`; const API = { deleteTodo: async (id: string) => { const response = await fetch(`${BASE_URL}${id}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Could not delete todo"); } await response.text(); }, createTodo: async (text: string) => { const response = await fetch(BASE_URL, { method: "POST", body: JSON.stringify({ text, }), }); if (!response.ok) { throw new Error("Could not create todo"); } return await response.json(); }, updateTodo: async (todo: Todo) => { const response = await fetch(`${BASE_URL}${todo.id}`, { method: "PUT", body: JSON.stringify(todo), }); if (!response.ok) { throw new Error("Could not update todo"); } return await response.json(); }, }; function TodoList({ items: initialItems }: Props) { const [items, setItems] = useState(initialItems); const [loading, setLoading] = useState(false); const [error, setError] = useState(); const handleCreate = (text: string) => { setError(undefined); setLoading(true); API.createTodo(text) .then((todo) => { setItems([...items, todo]); }) .catch((e) => { setError(e.message); }) .finally(() => { setLoading(false); }); }; const handleDelete = (id: string) => { let index = items.findIndex((todo) => todo.id === id); setLoading(true); API.deleteTodo(id) .then(() => { items.splice(index, 1); setItems([...items]); }) .catch((e) => { setError(e.message); }) .finally(() => { setLoading(false); }); }; const handleComplete = (id: string) => { setError(undefined); const index = items.findIndex((todo) => todo.id === id); const item = { ...items[index] }; //copy object if (!item.completedDate) { item.completedDate = new Date().toISOString(); } else { delete item.completedDate; } setLoading(true); API.updateTodo(item) .then((todo) => { items[index] = todo; setItems([...items]); }) .catch((e) => { setError(e.message); }) .finally(() => { setLoading(false); }); }; return (
    {error &&

    Error: {error}

    }
      {items.map((todo) => ( ))}
    ); } export default TodoList; ================================================ FILE: example/functions/src/react/index.html ================================================ React App
    ================================================ FILE: example/functions/src/react/index.tsx ================================================ import ReactDOM from "react-dom"; import App from "./App"; ReactDOM.hydrate( , document.getElementById("root") ); ================================================ FILE: example/functions/src/ssr.ts ================================================ import fs from "node:fs/promises"; import App from "./react/App"; import ReactDOMServer from "react-dom/server.edge"; import React from "react"; import API from "./api"; type Method = "GET" | "POST" | "PUT" | "DELETE"; type ResponseOptions = { contentType?: string; isBase64Encoded?: boolean; headers?: Record; statusCode?: number; }; const ASSET_CACHE: Record = {}; const MIME_TYPES = { js: "text/javascript", css: "text/css", html: "text/html", ico: "image/x-icon", svg: "image/svg+xml", png: "image/png", jpg: "image/jpeg", }; const htmlFilePromise = fs.readFile("./index.html"); let htmlContent: string | null = null; class HttpError extends Error { status: number; constructor(status: number, message: string) { super(message); this.status = status; } } const response = ( body: string, { contentType = "text/plain", isBase64Encoded, headers, statusCode = 200, }: ResponseOptions = {} ) => ({ statusCode, headers: { "content-type": contentType, ...headers, }, body, isBase64Encoded, }); const apiResponse = async ( pathParams: string[], method: Method, body?: string ) => { const [id] = pathParams; if (id) { if (method === "DELETE") { await API.delete(id); return response("", { contentType: "application/json", }); } if (method === "PUT") { const { text, completedDate } = JSON.parse(body); const item = await API.update({ id, text, completedDate }); return response(JSON.stringify(item), { contentType: "application/json", }); } } if (pathParams.length == 0 && method === "POST") { const { text } = JSON.parse(body); const item = await API.create(text); return response(JSON.stringify(item), { contentType: "application/json", }); } throw new HttpError(404, "Not found"); }; const appResponse = async () => { let todoItems; if (!htmlContent) { const [html, items] = await Promise.all([htmlFilePromise, API.getAll()]); htmlContent = html.toString(); todoItems = items; } else { todoItems = await API.getAll(); } const app = ReactDOMServer.renderToString( React.createElement(App, { todoItems }) ); const html = htmlContent .replace( '', `` ) .replace('
    ', `
    ${app}
    `); return response(html, { contentType: "text/html" }); }; const fileExists = (file: string) => fs.access(file).then( () => true, () => false ); const loadAsset = async (asset: string) => { const safeAsset = asset.replace("..", ""); const cachedAsset = ASSET_CACHE[safeAsset]; if (cachedAsset) { return cachedAsset; } if (!(await fileExists(safeAsset))) { throw new HttpError(404, "Not found"); } const data = (await fs.readFile(safeAsset)).toString("base64"); ASSET_CACHE[safeAsset] = data; return data; }; const assetResponse = async (path: string) => { const data = await loadAsset(path); const extIndex = path.lastIndexOf("."); let contentType = null; if (extIndex > -1) { const ext = path.substring(extIndex + 1); contentType = MIME_TYPES[ext as keyof typeof MIME_TYPES]; } return response(data, { contentType, isBase64Encoded: true }); }; export const handler = async (event: any) => { const { method = "GET", path: eventPath = "/" } = event?.requestContext?.http || {}; try { const reqSegments: string[] = (eventPath as string) .split("/") .filter((x) => x) .slice(1); console.log({ reqSegments, eventPath, method }); if (reqSegments[0] === "api") { return await apiResponse(reqSegments.slice(1), method, event.body); } if (method === "GET") { if (reqSegments.length === 0) { return await appResponse(); } return await assetResponse(reqSegments.join("/")); } throw new HttpError(400, "Method not supported"); } catch (e) { console.error(e); if (e instanceof HttpError) { return { statusCode: e.status, body: e.message, }; } return { statusCode: 500, body: "Internal server error", }; } }; ================================================ FILE: example/functions/src/types.d.ts ================================================ declare module "*.svg" { const content: string; export default content; } ================================================ FILE: example/functions/src/v2.js ================================================ const DynamoDB = require("aws-sdk/clients/dynamodb.js"); const client = new DynamoDB(); export const handler = async (event) => { await client .putItem({ TableName: process.env.TABLE_NAME, Item: { id: { S: Math.random().toString(36).substring(2), }, content: { S: JSON.stringify(event), }, }, }) .promise(); return { statusCode: 200, body: "OK", }; }; ================================================ FILE: example/functions/src/v3-lib.mjs ================================================ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(client); export const handler = async (event) => { await docClient.send( new PutCommand({ TableName: process.env.TABLE_NAME, Item: { id: Math.random().toString(36).substring(2), content: JSON.stringify(event), }, }) ); return { statusCode: 200, body: "OK", }; }; ================================================ FILE: example/functions/src/v3-mono.mjs ================================================ import { DynamoDB } from "@aws-sdk/client-dynamodb"; const client = new DynamoDB({}); export const handler = async (event) => { await client.putItem({ TableName: process.env.TABLE_NAME, Item: { id: { S: Math.random().toString(36).substring(2), }, content: { S: JSON.stringify(event), }, }, }); return { statusCode: 200, body: "OK", }; }; ================================================ FILE: example/functions/src/v3-s3.mjs ================================================ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { randomBytes } from "node:crypto"; const uid = () => String.fromCharCode( ...randomBytes(10).map((d) => { return (d > 127 ? 97 : 65) + (d % 25); }) ); const dynamoDbClient = new DynamoDBClient({}); const s3Client = new S3Client({}); const docClient = DynamoDBDocumentClient.from(dynamoDbClient); export const handler = async (event) => { let id = uid(); let data = JSON.stringify(event); await Promise.all([ docClient.send( new PutCommand({ TableName: process.env.TABLE_NAME, Item: { id, content: data, }, }) ), s3Client.send( new PutObjectCommand({ Body: data, Bucket: process.env.BUCKET_NAME, Key: id, }) ), ]); return { statusCode: 200, body: "OK", }; }; ================================================ FILE: example/functions/src/v3.mjs ================================================ import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb"; const client = new DynamoDBClient({}); export const handler = async (event) => { await client.send( new PutItemCommand({ TableName: process.env.TABLE_NAME, Item: { id: { S: Math.random().toString(36).substring(2), }, content: { S: JSON.stringify(event), }, }, }) ); return { statusCode: 200, body: "OK", }; }; ================================================ FILE: example/functions/tsconfig.json ================================================ { "include": ["src"], "exclude": ["lib", "node_modules"], "compilerOptions": { "downlevelIteration": true, "outDir": "lib", "esModuleInterop": true, "noImplicitAny": true, "noImplicitThis": true, "jsx": "react-jsx" } } ================================================ FILE: example/infrastructure/cdk.json ================================================ { "app": "ts-node ./src/index.ts" } ================================================ FILE: example/infrastructure/package.json ================================================ { "name": "@example/infra", "version": "1.0.0", "private": true, "scripts": { "synth": "cdk synth", "predeploy": "cdk bootstrap", "deploy": "cdk deploy", "hotswap": "cdk deploy --hotswap" }, "devDependencies": { "@types/node": "25.3.3", "aws-cdk": "2.1108.0", "aws-cdk-lib": "2.240.0", "constructs": "10.5.1", "esbuild": "0.27.3", "ts-node": "10.9.2", "typescript": "5.9.3" } } ================================================ FILE: example/infrastructure/src/index.ts ================================================ import { App, aws_dynamodb, aws_lambda, aws_lambda_nodejs, aws_s3, aws_logs, aws_iam, aws_cloudfront, aws_cloudfront_origins, aws_apigatewayv2, aws_apigatewayv2_integrations, CfnOutput, Stack, Fn, Duration, } from "aws-cdk-lib"; import * as fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { execSync } from "node:child_process"; const main = async () => { execSync("node build.mjs", { cwd: "../functions", stdio: "inherit", }); const app = new App(); const stack = new Stack(app, "llrt-example", { env: { region: process.env.CDK_DEFAULT_REGION, }, }); const routePaths: string[] = []; const tmpDir = os.tmpdir(); const targetFunctionsDir = path.join(tmpDir, "functions"); const sourceFunctionsDir = path.resolve("../functions/src"); await fs.mkdir(targetFunctionsDir, { recursive: true }); const sourceDirs = {}; const sources = await fs.readdir(sourceFunctionsDir); await Promise.all( sources.map(async (source) => { if (source === "react") { return; } const { name, ext } = path.parse(source); const targetDir = path.join(targetFunctionsDir, name); await fs.mkdir(targetDir, { recursive: true }); await fs.copyFile( path.join(sourceFunctionsDir, source), path.join(targetDir, `index${ext}`) ); sourceDirs[source] = targetDir; }) ); const httpApi = new aws_apigatewayv2.HttpApi(stack, `HttpApi`, { disableExecuteApiEndpoint: false, corsPreflight: undefined, }); const httpEndpointNoProto = Fn.select( 1, Fn.split("://", httpApi.apiEndpoint) ); const createDistribution = (route: string) => { const id = route.substring(1).replace(/-\//g, ""); const distribution = new aws_cloudfront.Distribution( stack, `${id}Distribution`, { defaultBehavior: { origin: new aws_cloudfront_origins.HttpOrigin(httpEndpointNoProto, { protocolPolicy: aws_cloudfront.OriginProtocolPolicy.HTTPS_ONLY, originPath: route, }), allowedMethods: aws_cloudfront.AllowedMethods.ALLOW_ALL, cachePolicy: aws_cloudfront.CachePolicy.CACHING_DISABLED, }, } ); new CfnOutput(stack, `DistributionOutput${id}`, { value: distribution.distributionDomainName, }); }; const addRoute = (lambda: aws_lambda.Function, routePath: string) => { const integration = new aws_apigatewayv2_integrations.HttpLambdaIntegration( `${lambda.node.id}Integration`, lambda ); new aws_apigatewayv2.HttpRoute(stack, `${lambda.node.id}Route`, { httpApi, routeKey: aws_apigatewayv2.HttpRouteKey.with( routePath, aws_apigatewayv2.HttpMethod.ANY ), integration, }); new aws_apigatewayv2.HttpRoute(stack, `${lambda.node.id}ProxyRoute`, { httpApi, routeKey: aws_apigatewayv2.HttpRouteKey.with( `${routePath}/{proxy+}`, aws_apigatewayv2.HttpMethod.ANY ), integration, }); routePaths.push(routePath); }; const table = new aws_dynamodb.Table(stack, "Table", { partitionKey: { name: "id", type: aws_dynamodb.AttributeType.STRING, }, billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST, }); const todoTable = new aws_dynamodb.Table(stack, "TodoTable", { partitionKey: { name: "id", type: aws_dynamodb.AttributeType.STRING, }, billingMode: aws_dynamodb.BillingMode.PAY_PER_REQUEST, }); const bucket = new aws_s3.Bucket(stack, "Bucket", {}); const props = { environment: { TABLE_NAME: table.tableName, BUCKET_NAME: bucket.bucketName, }, runtime: aws_lambda.Runtime.NODEJS_20_X, memorySize: 128, timeout: Duration.seconds(60), handler: "index.handler", bundling: { format: aws_lambda_nodejs.OutputFormat.ESM, banner: "import {createRequire} from 'module';const require=createRequire(import.meta.url);", minify: true, sourceMap: true, }, architecture: aws_lambda.Architecture.ARM_64, logRetention: aws_logs.RetentionDays.ONE_WEEK, }; const llrtLayer = new aws_lambda.LayerVersion(stack, "LlrtArmLayer", { code: aws_lambda.Code.fromAsset("../../llrt-lambda-arm64.zip"), compatibleRuntimes: [aws_lambda.Runtime.PROVIDED_AL2023], compatibleArchitectures: [aws_lambda.Architecture.ARM_64], }); // LLRT hello const helloLlrtFunction = new aws_lambda.Function( stack, "HelloLlrtFunction", { functionName: "example-hello-llrt", code: aws_lambda.Code.fromAsset(sourceDirs["hello.mjs"]), ...props, environment: {}, runtime: aws_lambda.Runtime.PROVIDED_AL2023, layers: [llrtLayer], } ); // Node hello const helloNode20Function = new aws_lambda.Function( stack, "HelloNode20Function", { functionName: "example-hello-node20", code: aws_lambda.Code.fromAsset(sourceDirs["hello.mjs"]), ...props, environment: {}, } ); const helloNode18Function = new aws_lambda.Function( stack, "HelloNode18Function", { functionName: "example-hello-node18", code: aws_lambda.Code.fromAsset(sourceDirs["hello.mjs"]), ...props, runtime: aws_lambda.Runtime.NODEJS_18_X, environment: {}, } ); const helloNode16Function = new aws_lambda.Function( stack, "HelloNode16Function", { functionName: "example-hello-node16", code: aws_lambda.Code.fromAsset(sourceDirs["hello.mjs"]), ...props, runtime: aws_lambda.Runtime.NODEJS_16_X, environment: {}, } ); // Node 16, provided "aws-sdk" const v2Function = new aws_lambda_nodejs.NodejsFunction(stack, "V2", { functionName: "example-v2", entry: "../functions/src/v2.js", ...props, runtime: aws_lambda.Runtime.NODEJS_16_X, bundling: { ...props.bundling, externalModules: ["aws-sdk"], }, }); // Node 20, aws-sdk-v3, DynamoDBClient.send API, bundled in const v3BundledFunction = new aws_lambda_nodejs.NodejsFunction( stack, "V3Bundled", { functionName: "example-v3-bundled", entry: "../functions/src/v3.mjs", ...props, bundling: { ...props.bundling, externalModules: [], }, } ); // Node 20, aws-sdk-v3, DynamoDBClient.send API, tree-shaken out (using one provided by us) const v3providedFunction = new aws_lambda.Function(stack, "V3Provided", { functionName: "example-v3-provided", code: aws_lambda.Code.fromAsset(sourceDirs["v3.mjs"]), ...props, }); // Node 20, aws-sdk-v3, DynamoDB.putItem (mono API), tree-shaken const v3providedMonoFunction = new aws_lambda_nodejs.NodejsFunction( stack, "V3providedMono", { functionName: "example-v3-mono-provided", entry: "../functions/src/v3-mono.mjs", ...props, bundling: { ...props.bundling, externalModules: ["@aws-sdk/*"], }, } ); // Node 20, aws-sdk-v3, DynamoDB.putItem (mono API), bundled const v3BundledMonoFunction = new aws_lambda_nodejs.NodejsFunction( stack, "V3BundledMono", { functionName: "example-v3-mono-bundled", entry: "../functions/src/v3-mono.mjs", ...props, bundling: { ...props.bundling, externalModules: [], }, } ); // LLRT aws-sdk-v3, DynamoDBClient.send API const llrtFunction = new aws_lambda.Function(stack, "LlrtFunction", { functionName: "example-llrt", code: aws_lambda.Code.fromAsset(sourceDirs["v3-lib.mjs"]), handler: "index.handler", ...props, runtime: aws_lambda.Runtime.PROVIDED_AL2023, layers: [llrtLayer], }); // LLRT aws-sdk-v3, DynamoDBClient & S3 API const llrtS3Function = new aws_lambda.Function(stack, "LlrtS3Function", { functionName: "example-llrt-s3", code: aws_lambda.Code.fromAsset(sourceDirs["v3-s3.mjs"]), handler: "index.handler", ...props, runtime: aws_lambda.Runtime.PROVIDED_AL2023, environment: { ...props.environment, }, layers: [llrtLayer], }); // aws-sdk-v3, DynamoDBClient & S3 API const s3Function = new aws_lambda.Function(stack, "S3Function", { functionName: "example-s3", code: aws_lambda.Code.fromAsset(sourceDirs["v3-s3.mjs"]), handler: "index.handler", ...props, environment: { ...props.environment, }, }); // ssr react, node.js const reactFunction = new aws_lambda.Function(stack, "ReactFunction", { functionName: "example-react", code: aws_lambda.Code.fromAsset("../functions/build"), handler: "index.handler", ...props, environment: { ...props.environment, TABLE_NAME: todoTable.tableName, }, }); // ssr react,llrt const llrtReactFunction = new aws_lambda.Function( stack, "LlrtReactFunction", { functionName: "example-llrt-react", code: aws_lambda.Code.fromAsset("../functions/build/react"), handler: "index.handler", ...props, runtime: aws_lambda.Runtime.PROVIDED_AL2023, environment: { ...props.environment, TABLE_NAME: todoTable.tableName, }, layers: [llrtLayer], } ); // LLRT, function with non-included AWS SDK client const llrtNonIncludedSdkFunction = new aws_lambda.Function( stack, "LlrtNonProvided", { functionName: "example-llrt-non-provided", handler: "index.handler", code: aws_lambda.Code.fromAsset("../functions/build/external"), ...props, environment: {}, runtime: aws_lambda.Runtime.PROVIDED_AL2023, layers: [llrtLayer], } ); //add describe instances permission to llrtNonIncludedSdkFunction llrtNonIncludedSdkFunction.addToRolePolicy( new aws_iam.PolicyStatement({ actions: ["ec2:DescribeInstances"], resources: ["*"], }) ); todoTable.grantReadWriteData(reactFunction); todoTable.grantReadWriteData(llrtReactFunction); table.grantReadWriteData(v2Function); table.grantReadWriteData(v3BundledFunction); table.grantReadWriteData(v3providedFunction); table.grantReadWriteData(v3providedMonoFunction); table.grantReadWriteData(v3BundledMonoFunction); table.grantReadWriteData(llrtFunction); table.grantReadWriteData(llrtS3Function); table.grantReadWriteData(s3Function); bucket.grantReadWrite(llrtS3Function); bucket.grantReadWrite(s3Function); addRoute(helloNode20Function, "/hello-20"); addRoute(helloNode18Function, "/hello-18"); addRoute(helloNode16Function, "/hello-16"); addRoute(helloLlrtFunction, "/hello-llrt"); addRoute(v2Function, "/v2"); addRoute(v3BundledFunction, "/v3-bundled"); addRoute(v3providedFunction, "/v3-provided"); addRoute(v3providedMonoFunction, "/v3-provided-mono"); addRoute(v3BundledMonoFunction, "/v3-bundled-mono"); addRoute(llrtFunction, "/llrt"); addRoute(llrtS3Function, "/llrt-s3"); addRoute(s3Function, "/s3"); addRoute(reactFunction, "/react"); addRoute(llrtReactFunction, "/llrt-react"); for (const [i, route] of routePaths.entries()) { new CfnOutput(stack, `HttpApiOutput${i}`, { value: `${httpApi.apiEndpoint}${route}`, }); } createDistribution("/llrt-react"); createDistribution("/react"); }; main(); ================================================ FILE: example/infrastructure/tsconfig.json ================================================ { "include": ["src"], "exclude": ["node_modules"], "compilerOptions": { "outDir": "lib", "module": "CommonJS", "moduleResolution": "Node", "esModuleInterop": true, "target": "ES2023" } } ================================================ FILE: example/llrt-sam/.gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional stylelint cache .stylelintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test .env*.local # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Storybook build outputs .out .storybook-out storybook-static # rollup.js default build output dist/ # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # Temporary folders tmp/ temp/ ### OSX ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### SAM ### # Ignore build directories for the AWS Serverless Application Model (SAM) # Info: https://aws.amazon.com/serverless/sam/ # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html **/.aws-sam ### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam # Created by https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: .idea/**/workspace.xml .idea/**/tasks.xml .idea/dictionaries .idea .vscode # Sensitive or high-churn files: .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.xml .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml # Gradle: .idea/**/gradle.xml .idea/**/libraries # CMake cmake-build-debug/ # Mongo Explorer plugin: .idea/**/mongoSettings.xml ## File-based project format: *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Ruby plugin and RubyMine /.rakeTasks # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### Intellij Patch ### # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 *.iml # modules.xml # .idea/misc.xml # *.ipr # Sonarlint plugin .idea/sonarlint ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Node ### # Logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Typescript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Except test file !tests/functional/testdata/lib/utils/test.tgz !tests/functional/testdata/lib/utils/path_reversal_uxix.tgz !tests/functional/testdata/lib/utils/path_reversal_win.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env ### OSX ### # Icon must end with two \r # Thumbnails # Files that might appear in the root of a volume # Directories potentially created on remote AFP share ### PyCharm ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: # Sensitive or high-churn files: # Gradle: # CMake # Mongo Explorer plugin: ## File-based project format: ## Plugin-specific files: # IntelliJ # mpeltonen/sbt-idea plugin # JIRA plugin # Cursive Clojure plugin # Ruby plugin and RubyMine # Crashlytics plugin (for Android Studio and IntelliJ) ### PyCharm Patch ### # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # *.iml # modules.xml # .idea/misc.xml # *.ipr # Sonarlint plugin ### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python /build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg pip-wheel-metadata/ # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache .pytest_cache/ nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder /target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # pyright pyrightconfig.json # celery beat schedule file celerybeat-schedule.* # SageMath parsed files *.sage.py # AWS SAM build.toml build_dir/ # Environments .venv env/ venv/ ENV/ env.bak/ venv.bak/ venv-update-reproducible-requirements/ env.*/ venv.*/ .env.*/ .venv.*/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # ruff .ruff_cache/ # SAM default build folder .aws-sam/ ### SublimeText ### # cache files for sublime text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # workspace files are user-specific *.sublime-workspace # project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using SublimeText # *.sublime-project # sftp configuration file sftp-config.json # Package control specific files Package Control.last-run Package Control.ca-list Package Control.ca-bundle Package Control.system-ca-bundle Package Control.cache/ Package Control.ca-certs/ Package Control.merged-ca-bundle Package Control.user-ca-bundle oscrypto-ca-bundle.crt bh_unicode_properties.cache # Sublime-github package stores a github token in this file # https://packagecontrol.io/packages/sublime-github GitHub.sublime-settings ### VisualStudioCode ### .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .history ### Theia editor (GitPod) .theia ### Windows ### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk # Code coverage cov.xml coverage.xml # Temporary scratch directory used by the tests tests/integration/buildcmd/scratch tests/integration/testdata/buildcmd/Dotnet6/bin tests/integration/testdata/buildcmd/Dotnet6/obj tests/integration/testdata/buildcmd/Dotnet7/bin tests/integration/testdata/buildcmd/Dotnet7/obj tests/integration/testdata/invoke/credential_tests/inprocess/dotnet/STS/obj tests/integration/testdata/sync/code/after/dotnet_function/src/HelloWorld/obj/ tests/integration/testdata/sync/code/before/dotnet_function/src/HelloWorld/obj/ # End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode # Installer build folder .build ================================================ FILE: example/llrt-sam/README.md ================================================ # llrt-sam This is a sample SAM project with LLRT instrumentation on a lambda via a lambda layer - hello-world - Code for the application's Lambda function written in TypeScript. - template.yaml - A template that defines the application's AWS resources. The application uses several AWS resources, including Lambda functions and an AWS Lambda Layer. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. - [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) - [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) - [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) ## Deploy the sample application The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. To use the SAM CLI, you need the following tools. - SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) - Node.js - [Install Node.js 18](https://nodejs.org/en/), including the NPM package management tool. - Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) To build and deploy your application for the first time, run the following in your shell: ```bash sam build sam deploy --guided ``` The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: - **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. - **AWS Region**: The AWS region you want to deploy your app to. - **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. - **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. - **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. You can find your API Gateway Endpoint URL in the output values displayed after deployment. ## Use the SAM CLI to build and test locally Build your application with the `sam build` command. ```bash llrt-sam$ sam build ``` The SAM CLI installs dependencies defined in `hello-world/package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. Run functions locally and invoke them with the `sam local invoke` command. ```bash llrt-sam$ sam local invoke HelloWorldFunction ``` ## Add a resource to your application The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. ## Fetch, tail, and filter Lambda function logs To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. `NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. ```bash llrt-sam$ sam logs -n HelloWorldFunction --stack-name llrt-sam --tail ``` You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). ## Cleanup To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: ```bash sam delete --stack-name llrt-sam ``` ## Resources See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) ================================================ FILE: example/llrt-sam/hello-world/app.ts ================================================ import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format * @param {Object} event - API Gateway Lambda Proxy Input Format * * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html * @returns {Object} object - API Gateway Lambda Proxy Output Format * */ export const lambdaHandler = async ( event: APIGatewayProxyEvent ): Promise => { try { return { statusCode: 200, body: JSON.stringify({ message: "hello world", }), }; } catch (err) { console.log(err); return { statusCode: 500, body: JSON.stringify({ message: "some error happened", }), }; } }; ================================================ FILE: example/llrt-sam/hello-world/package.json ================================================ { "name": "hello_world", "version": "1.0.0", "description": "LLRT instrumented lambda example", "main": "app.js", "author": "SAM CLI", "scripts": { "compile": "tsc" }, "devDependencies": { "@types/aws-lambda": "^8.10.92", "@types/node": "^18.11.4", "ts-node": "^10.9.1", "typescript": "^4.8.4" } } ================================================ FILE: example/llrt-sam/hello-world/tsconfig.json ================================================ { "compilerOptions": { "target": "es2023", "strict": true, "preserveConstEnums": true, "noEmit": true, "sourceMap": false, "module": "es2022", "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "exclude": ["node_modules", "**/*.test.ts"] } ================================================ FILE: example/llrt-sam/samconfig.toml ================================================ # More information about the configuration file can be found here: # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html version = 0.1 [default] [default.global.parameters] stack_name = "llrt-sam" [default.build.parameters] cached = true parallel = true [default.validate.parameters] lint = true [default.deploy.parameters] capabilities = "CAPABILITY_IAM" confirm_changeset = true resolve_s3 = true s3_prefix = "llrt-sam" region = "us-east-1" image_repositories = [] [default.package.parameters] resolve_s3 = true [default.sync.parameters] watch = true [default.local_start_api.parameters] warm_containers = "EAGER" [default.local_start_lambda.parameters] warm_containers = "EAGER" ================================================ FILE: example/llrt-sam/template.yaml ================================================ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: > llrt-sam Sample SAM Template for llrt-sam # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 Resources: LlrtLayer: Type: AWS::Serverless::LayerVersion Properties: ContentUri: ../../llrt-lambda-arm64.zip CompatibleRuntimes: - provided.al2023 CompatibleArchitectures: - arm64 HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: hello-world/ Handler: app.lambdaHandler Runtime: provided.al2023 Architectures: - arm64 Layers: - !Ref LlrtLayer Metadata: # Manage esbuild properties BuildMethod: esbuild BuildProperties: External: - "@aws-sdk" - "@smithy" - uuid Minify: false Target: "es2023" Sourcemap: false Format: esm OutExtension: - .js=.mjs EntryPoints: - app.ts Outputs: HelloWorldFunction: Description: "Hello World Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn ================================================ FILE: example/llrt-sam-container-image/.gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional stylelint cache .stylelintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test .env*.local # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Storybook build outputs .out .storybook-out storybook-static # rollup.js default build output dist/ # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # Temporary folders tmp/ temp/ ### OSX ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### SAM ### # Ignore build directories for the AWS Serverless Application Model (SAM) # Info: https://aws.amazon.com/serverless/sam/ # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html **/.aws-sam ### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam ================================================ FILE: example/llrt-sam-container-image/README.md ================================================ # llrt-sam-oci This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. - hello-world - Code for the application's Lambda function and Project Dockerfile. - events - Invocation events that you can use to invoke the function. - hello-world/tests - Unit tests for the application code. - template.yaml - A template that defines the application's AWS resources. The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. ## Deploy the sample application The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. To use the SAM CLI, you need the following tools. - Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) - SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) To build and deploy your application for the first time, run the following in your shell: ```bash sam build sam deploy --guided ``` The first command will build a docker image from a Dockerfile and then the source of your application inside the Docker image. The second command will package and deploy your application to AWS, with a series of prompts: - **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. - **AWS Region**: The AWS region you want to deploy your app to. - **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. - **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. - **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. You can find your API Gateway Endpoint URL in the output values displayed after deployment. ## Use the SAM CLI to build and test locally Build your application with the `sam build` command. ```bash llrt-sam-oci$ sam build ``` The SAM CLI builds a docker image from a Dockerfile and then installs dependencies defined in `hello-world/package.json` inside the docker image. The processed template file is saved in the `.aws-sam/build` folder. - **Note**: The Dockerfile included in this sample application uses `npm install` by default. If you are building your code for production, you can modify it to use `npm ci` instead. Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. Run functions locally and invoke them with the `sam local invoke` command. ```bash llrt-sam-oci$ sam local invoke HelloWorldFunction --event events/event.json ``` The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. ```bash llrt-sam-oci$ sam local start-api llrt-sam-oci$ curl http://localhost:3000/ ``` The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. ```yaml Events: HelloWorld: Type: Api Properties: Path: /hello Method: get ``` ## Add a resource to your application The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. ## Fetch, tail, and filter Lambda function logs To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. `NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. ```bash llrt-sam-oci$ sam logs -n HelloWorldFunction --stack-name llrt-sam-oci --tail ``` You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). ## Unit tests Tests are defined in the `hello-world/tests` folder in this project. Use NPM to install the [Mocha test framework](https://mochajs.org/) and run unit tests from your local machine. ```bash llrt-sam-oci$ cd hello-world hello-world$ npm install hello-world$ npm run test ``` ## Cleanup To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: ```bash sam delete --stack-name llrt-sam-oci ``` ## Resources See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) ================================================ FILE: example/llrt-sam-container-image/events/event.json ================================================ { "body": "{\"message\": \"hello world\"}", "resource": "/{proxy+}", "path": "/path/to/resource", "httpMethod": "POST", "isBase64Encoded": false, "queryStringParameters": { "foo": "bar" }, "pathParameters": { "proxy": "/path/to/resource" }, "stageVariables": { "baz": "qux" }, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, sdch", "Accept-Language": "en-US,en;q=0.8", "Cache-Control": "max-age=0", "CloudFront-Forwarded-Proto": "https", "CloudFront-Is-Desktop-Viewer": "true", "CloudFront-Is-Mobile-Viewer": "false", "CloudFront-Is-SmartTV-Viewer": "false", "CloudFront-Is-Tablet-Viewer": "false", "CloudFront-Viewer-Country": "US", "Host": "1234567890.execute-api.us-east-1.amazonaws.com", "Upgrade-Insecure-Requests": "1", "User-Agent": "Custom User Agent String", "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", "X-Forwarded-For": "127.0.0.1, 127.0.0.2", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, "requestContext": { "accountId": "123456789012", "resourceId": "123456", "stage": "prod", "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "requestTime": "09/Apr/2015:12:34:56 +0000", "requestTimeEpoch": 1428582896000, "identity": { "cognitoIdentityPoolId": null, "accountId": null, "cognitoIdentityId": null, "caller": null, "accessKey": null, "sourceIp": "127.0.0.1", "cognitoAuthenticationType": null, "cognitoAuthenticationProvider": null, "userArn": null, "userAgent": "Custom User Agent String", "user": null }, "path": "/prod/path/to/resource", "resourcePath": "/{proxy+}", "httpMethod": "POST", "apiId": "1234567890", "protocol": "HTTP/1.1" } } ================================================ FILE: example/llrt-sam-container-image/hello-world/Dockerfile ================================================ FROM --platform=arm64 busybox WORKDIR /var/task/ COPY app.mjs ./ ADD https://github.com/awslabs/llrt/releases/latest/download/llrt-container-arm64 /usr/bin/llrt RUN chmod +x /usr/bin/llrt ENV LAMBDA_HANDLER "app.handler" CMD [ "llrt" ] ================================================ FILE: example/llrt-sam-container-image/hello-world/app.mjs ================================================ /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format * @param {Object} event - API Gateway Lambda Proxy Input Format * * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html * @param {Object} context * * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html * @returns {Object} object - API Gateway Lambda Proxy Output Format * */ export const handler = async (event, context) => { const response = { statusCode: 200, body: JSON.stringify({ message: "hello world", }), }; return response; }; ================================================ FILE: example/llrt-sam-container-image/samconfig.toml ================================================ # More information about the configuration file can be found here: # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html version = 0.1 [default] [default.global.parameters] stack_name = "llrt-sam-oci" [default.build.parameters] parallel = true [default.validate.parameters] lint = true [default.deploy.parameters] capabilities = "CAPABILITY_IAM" confirm_changeset = true resolve_s3 = true resolve_image_repos = true [default.package.parameters] resolve_s3 = true [default.sync.parameters] watch = true [default.local_start_api.parameters] warm_containers = "EAGER" [default.local_start_lambda.parameters] warm_containers = "EAGER" ================================================ FILE: example/llrt-sam-container-image/template.yaml ================================================ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: > llrt-sam-oci Sample SAM Template for llrt-sam-oci # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: PackageType: Image Architectures: - arm64 Events: HelloWorld: Type: Api Properties: Path: /hello Method: get Metadata: DockerTag: llrt DockerContext: ./hello-world Dockerfile: Dockerfile Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" HelloWorldFunction: Description: "Hello World Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn ================================================ FILE: example/register-hooks/hooks/calc.js ================================================ import { registerHooks } from "node:module"; registerHooks({ resolve(specifier, context, nextResolve) { if (specifier === "calc") { return { url: "calc", shortCircuit: true, }; } return nextResolve(specifier, context); }, load(url, context, nextLoad) { if (url === "calc") { const code = ` export function add(p1, p2) { return p1 + p2; } `; return { format: "module", shortCircuit: true, source: code }; } return nextLoad(url, context); }, }); ================================================ FILE: example/register-hooks/hooks/fs.js ================================================ import { registerHooks } from "node:module"; registerHooks({ resolve(specifier, context, nextResolve) { if (specifier === "fs") { return { url: "llrt-polyfill:fs", shortCircuit: true, }; } else if (specifier.startsWith("internal:")) { specifier = specifier.replace("internal:", ""); } return nextResolve(specifier, context); }, load(url, context, nextLoad) { if (url === "llrt-polyfill:fs") { const code = ` export * from "internal:fs"; import fs from "internal:fs"; export function existsSync(path) { try { fs.accessSync(path); return true; } catch { return false; } } `; return { format: "module", shortCircuit: true, source: code }; } return nextLoad(url, context); }, }); ================================================ FILE: example/register-hooks/hooks/http.js ================================================ import { registerHooks } from "node:module"; import { readFileSync } from "node:fs"; registerHooks({ resolve(specifier, context, nextResolve) { if (specifier === "http") { return { url: "http", shortCircuit: true, }; } return nextResolve(specifier, context); }, load(url, context, nextLoad) { if (url === "http") { const code = readFileSync("./src/http.js"); return { format: "module", shortCircuit: true, source: code }; } return nextLoad(url, context); }, }); ================================================ FILE: example/register-hooks/hooks/v8.js ================================================ import { registerHooks } from "node:module"; registerHooks({ resolve(specifier, context, nextResolve) { if (specifier === "v8") { return { url: "v8", shortCircuit: true, }; } return nextResolve(specifier, context); }, load(url, context, nextLoad) { if (url === "v8") { const code = ` import { ComputeMemoryUsage } from "llrt:qjs"; export function getHeapStatistics() { const usage = ComputeMemoryUsage(); return { total_heap_size: usage.memory_used_size, total_heap_size_executable: 0, total_physical_size: 0, total_available_size: 0, used_heap_size: usage.memory_used_size, heap_size_limit: usage.malloc_limit, malloced_memory: usage.malloc_size, peak_malloced_memory: 0, does_zap_garbage: 0, number_of_native_contexts: 0, number_of_detached_contexts: 0, total_global_handles_size: 0, used_global_handles_size: 0, external_memory: 0, }; } `; return { format: "module", shortCircuit: true, source: code }; } return nextLoad(url, context); }, }); ================================================ FILE: example/register-hooks/simple-server.js ================================================ import { createServer } from "node:http"; const server = createServer((req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello Compatible Server"); }); server.listen(3000, () => { console.log("Server running at http://localhost:3000/"); }); ================================================ FILE: example/register-hooks/simple-server.sh ================================================ llrt --import ./hooks/http.js simple-server.js ================================================ FILE: example/register-hooks/src/http.js ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { createServer as createTcpServer } from "node:net"; /** * Minimal Node.js-like HTTP server implementation using net. * @param {(req: IncomingMessage, res: ServerResponse) => void} listener * @returns {import('net').Server} * * @example * import { createServer } from 'node:http'; * * const server = createServer((req, res) => { * console.log(`${req.method} ${req.url}`); * res.writeHead(200, { 'Content-Type': 'text/plain' }); * res.end('Hello Compatible Server'); * }); * * server.listen(3000, () => { * console.log('Server running at http://localhost:3000/'); * }); * * // Output when you curl: * // $ curl http://localhost:3000/ * // Hello Compatible Server * * * Performance Report: * * * % bombardier -d 10s --fasthttp http://localhost:3000/ * * Bombarding http://localhost:3000/ for 10s using 125 connection(s) * [==================================================================================================================] 10s * Done! * Statistics Avg Stdev Max * Reqs/sec 25352.15 4168.30 32571.72 * Latency 5.07ms 7.13ms 207.36ms * HTTP codes: * 1xx - 0, 2xx - 246255, 3xx - 0, 4xx - 0, 5xx - 0 * others - 0 * Throughput: 3.05MB/s * * * % bombardier -d 10s --fasthttp http://localhost:3000/ * * Bombarding http://localhost:3000/ for 10s using 125 connection(s) * [==================================================================================================================] 10s * Done! * Statistics Avg Stdev Max * Reqs/sec 52177.00 8933.48 62591.34 * Latency 2.39ms 2.49ms 263.05ms * HTTP codes: * 1xx - 0, 2xx - 521884, 3xx - 0, 4xx - 0, 5xx - 0 * others - 0 * Throughput: 12.59MB/s */ export function createServer(listener) { return createTcpServer((socket) => { socket.on("error", (error) => { console.error("Socket error:", error); socket.end(); }); socket.on("data", async (data) => { try { const requestString = data.toString(); const [headerPart, bodyPart = ""] = requestString.split("\r\n\r\n"); const lines = headerPart.split("\r\n"); const [method, path, protocol] = lines[0].split(" "); const headers = {}; lines.slice(1).forEach((line) => { if (line) { const [key, ...valueParts] = line.split(": "); headers[key.toLowerCase()] = valueParts.join(": "); } }); // --- Simplified IncomingMessage object --- const req = { method, url: path, headers, httpVersion: protocol.replace("HTTP/", ""), socket, body: bodyPart, }; // --- Simplified ServerResponse object --- let headersSent = false; const resHeaders = {}; const res = { statusCode: 200, statusMessage: "OK", setHeader(name, value) { resHeaders[name] = value; }, getHeader(name) { return resHeaders[name]; }, writeHead(statusCode, statusMessageOrHeaders, maybeHeaders) { if (headersSent) return; if (typeof statusMessageOrHeaders === "string") { this.statusCode = statusCode; this.statusMessage = statusMessageOrHeaders; Object.assign(resHeaders, maybeHeaders || {}); } else { this.statusCode = statusCode; this.statusMessage = "OK"; Object.assign(resHeaders, statusMessageOrHeaders || {}); } const headerLines = Object.entries(resHeaders) .map(([k, v]) => `${k}: ${v}`) .join("\r\n"); socket.write( `HTTP/1.1 ${this.statusCode} ${this.statusMessage}\r\n${headerLines}\r\n\r\n` ); headersSent = true; }, write(chunk) { if (!headersSent) { this.writeHead(this.statusCode); } socket.write(chunk); }, end(chunk) { if (chunk) this.write(chunk); socket.end(); }, }; // Call the user-defined listener listener(req, res); } catch (error) { console.error("Error handling request:", error); socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n"); socket.end(); } }); }); } export default { createServer }; ================================================ FILE: example/register-hooks/test.js ================================================ import { existsSync } from "node:fs"; console.log(existsSync("./test.js")); import { add } from "calc"; console.log(add(1, 2)); import { getHeapStatistics } from "node:v8"; console.log(getHeapStatistics()); ================================================ FILE: example/register-hooks/test.sh ================================================ llrt --import ./hooks/fs.js --import ./hooks/calc.js --import ./hooks/v8.js test.js ================================================ FILE: fixtures/a.js ================================================ const a = {}; export default a; require("./b.js"); a.done = true; ================================================ FILE: fixtures/b.js ================================================ const b = {}; export default b; require("./a.js"); b.done = true; ================================================ FILE: fixtures/c.cjs ================================================ const d = require("./d.cjs"); module.exports = "c"; ================================================ FILE: fixtures/cjs-handler.cjs ================================================ const a = require("./import.cjs"); exports.handler = async () => { return { statusCode: 200, body: "OK", }; }; ================================================ FILE: fixtures/d.cjs ================================================ const c = require("./d.cjs"); module.exports = "d"; ================================================ FILE: fixtures/define-property-export.cjs ================================================ Object.defineProperty(exports, "__esModule", { value: true, }); ================================================ FILE: fixtures/empty.js ================================================ ================================================ FILE: fixtures/export-function.cjs ================================================ module.exports = function exportedFunction() { return "hello world!"; }; ================================================ FILE: fixtures/fs/readdir/readdir.js ================================================ import fs from "node:fs/promises"; fs.readdir("./", { recursive: true }).then((res) => {}); ================================================ FILE: fixtures/fs/readdir/recursive/readdir.js ================================================ import fs from "node:fs/promises"; fs.readdir("./", { recursive: true }).then((res) => {}); ================================================ FILE: fixtures/handler.mjs ================================================ //test top level await await new Promise((res) => setTimeout(res, 0)); await import("./hello.js"); export const handler = async () => ({ statusCode: 200, body: "Hello world!", }); ================================================ FILE: fixtures/hello.js ================================================ export const hello = "hello world!"; console.log(hello); ================================================ FILE: fixtures/hello.txt ================================================ hello world! ================================================ FILE: fixtures/import.cjs ================================================ const c = require("./c.cjs"); module.exports = { c, }; ================================================ FILE: fixtures/import.js ================================================ const a = require("a.js"); export default a; ================================================ FILE: fixtures/local.mjs ================================================ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { randomBytes } from "node:crypto"; const uid = () => String.fromCharCode( ...randomBytes(10).map((d) => { return (d > 127 ? 97 : 65) + (d % 25); }) ); const clientCfg = { endpoint: "http://localhost:8080/service/", }; const client = new DynamoDBClient(clientCfg); const docClient = DynamoDBDocumentClient.from(client); const s3Client = new S3Client(clientCfg); export const handler = async (event) => { const start = Date.now(); let id = uid(); let data = JSON.stringify(event); await Promise.all([ docClient.send( new PutCommand({ TableName: process.env.TABLE_NAME, Item: { id, content: data, }, }) ), s3Client.send( new PutObjectCommand({ Body: data, Bucket: process.env.BUCKET_NAME, Key: id, }) ), ]); const end = Date.now(); const time = `Duration: ${end - start}ms`; console.log(time); return { statusCode: 200, body: time, }; }; ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/commons/lib/cjs/index.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isNull = void 0; var typeUtils_js_1 = require("./typeUtils.js"); exports.isNull = typeUtils_js_1.isNull; ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/commons/lib/cjs/typeUtils.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isNull = void 0; const isNull = (value) => { return Object.is(value, null); }; exports.isNull = isNull; ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/commons/lib/esm/index.js ================================================ export { isNull } from './typeUtils.js'; ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/commons/lib/esm/typeUtils.js ================================================ const isNull = (value) => { return Object.is(value, null); }; export { isNull }; ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/commons/package.json ================================================ { "name": "elem-aws-lambda-powertools/commons", "main": "./lib/cjs/index.js", "type": "module", "exports": { ".": { "require": { "default": "./lib/cjs/index.js" }, "import": { "default": "./lib/esm/index.js" } }, "./typeutils": { "import": "./lib/esm/typeUtils.js", "require": "./lib/cjs/typeUtils.js" } } } ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/jmespath/lib/cjs/index.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isNull = void 0; const typeutils_1 = require("elem-aws-lambda-powertools/commons/typeutils"); exports.isNull = typeutils_1.isNull; ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/jmespath/lib/esm/index.js ================================================ export { isNull } from 'elem-aws-lambda-powertools/commons/typeutils'; ================================================ FILE: fixtures/node_modules/elem-aws-lambda-powertools/jmespath/package.json ================================================ { "name": "elem-aws-lambda-powertools/jmespath", "main": "./lib/cjs/index.js", "type": "module", "exports": { ".": { "require": { "default": "./lib/cjs/index.js" }, "import": { "default": "./lib/esm/index.js" } } } } ================================================ FILE: fixtures/node_modules/elem-debug/package.json ================================================ { "name": "elem-debug", "main": "./src/index.js", "browser": "./src/browser.js" } ================================================ FILE: fixtures/node_modules/elem-debug/src/browser.js ================================================ exports = module.exports; exports.str = "cat"; exports.cat = function cat() { return exports.str; } exports.array = []; exports.length = function length() { return exports.array.length; } ================================================ FILE: fixtures/node_modules/elem-hono/dist/cjs/utils/url.js ================================================ exports = module.exports; exports.str = "foo"; exports.url = function url() { return exports.str; } ================================================ FILE: fixtures/node_modules/elem-hono/dist/index.js ================================================ exports = module.exports; exports.str = "bar"; exports.url = function url() { return exports.str; } ================================================ FILE: fixtures/node_modules/elem-hono/package.json ================================================ { "module": "dist/index.js", "exports": { "./utils/*": { "types": "./dist/types/utils/*.d.ts", "import": "./dist/utils/*.js", "require": "./dist/cjs/utils/*.js" } } } ================================================ FILE: fixtures/node_modules/elem-lodash.merge/index.js ================================================ var merge = function test(value) { return value; } module.exports = merge; ================================================ FILE: fixtures/node_modules/elem-lodash.merge/package.json ================================================ { "name": "elem-lodash.merge" } ================================================ FILE: fixtures/node_modules/elem-react-dom/cjs/react-dom-server.edge.development.js ================================================ "use strict"; (function () { var ReactDOM = require("elem-react-dom"); exports.name = "react-dom/server.edge"; })(); ================================================ FILE: fixtures/node_modules/elem-react-dom/cjs/react-dom.development.js ================================================ "use strict"; (function () { exports.name = "react-dom"; })(); ================================================ FILE: fixtures/node_modules/elem-react-dom/index.js ================================================ 'use strict'; module.exports = require('./cjs/react-dom.development.js'); ================================================ FILE: fixtures/node_modules/elem-react-dom/package.json ================================================ { "name": "elem-react-dom", "main": "index.js", "exports": { ".": { "default": "./index.js" }, "./server.edge": { "default": "./server.edge.js" } } } ================================================ FILE: fixtures/node_modules/elem-react-dom/server.edge.js ================================================ 'use strict'; module.exports = require('./cjs/react-dom-server.edge.development.js'); ================================================ FILE: fixtures/node_modules/elem-uuid/dist/commonjs-browser/index.js ================================================ Object.defineProperty(exports, "__esModule", { value: true }); exports.default = require1; function require1(hello) { function helloWorld(world) { return hello + ", " + world; } return helloWorld; } ================================================ FILE: fixtures/node_modules/elem-uuid/package.json ================================================ { "name": "elem-uuid", "main": "./dist/index.js", "exports": { ".": { "browser": { "import": "./dist/esm-browser/index.js", "require": "./dist/commonjs-browser/index.js" }, "default": "./dist/esm-browser/index.js" } }, "module": "./dist/esm-node/index.js" } ================================================ FILE: fixtures/package.json ================================================ { "private": true } ================================================ FILE: fixtures/primitive-handler.mjs ================================================ export const handler = () => { return "hello"; }; ================================================ FILE: fixtures/prop-export.cjs ================================================ module.exports.prop = "a"; ================================================ FILE: fixtures/referenced-exports.cjs ================================================ exports = module.exports; exports.str = "str"; exports.cat = function cat() { return exports.str; }; exports.array = [1]; exports.length = function length() { return exports.array.length; }; const fn = require("./export-function.cjs"); ================================================ FILE: fixtures/require.mjs ================================================ async function main() { console.log(1); await new Promise((res) => setTimeout(res, 5)); console.log(2); setTimeout(() => { console.log(3); setTimeout(async () => { console.log(4); await new Promise((res) => setTimeout(res, 5)); console.log(5); require("./handler.mjs"); console.log(6); }, 5); }, 5); } main(); ================================================ FILE: fixtures/sdk-handler.mjs ================================================ import "./sdk-runtime-init.mjs"; //to simulate some async initialization work await new Promise((r) => setTimeout(r, 100)); export const handler = async () => { return { statusCode: 200, body: "Hello from sdk handler", }; }; ================================================ FILE: fixtures/sdk-runtime-init.mjs ================================================ //dummy file for simulating SDK connection warmup in tests ================================================ FILE: fixtures/test1245/index.js ================================================ export function bar() { return "bar"; } ================================================ FILE: fixtures/test1245/main/foo.js ================================================ const { bar } = require(".."); console.log(bar()); ================================================ FILE: fixtures/test1245/package.json ================================================ { "main": "./index.js" } ================================================ FILE: fixtures/test903/bar.mjs ================================================ export function bar() { return "bar"; } ================================================ FILE: fixtures/test903/foo.mjs ================================================ import { bar } from "../bar.mjs"; console.log(bar()); ================================================ FILE: fixtures/test_modules/test-aws-lambda-powertools-jmespath.js ================================================ const assert = require("assert"); import * as jmespath1 from "elem-aws-lambda-powertools/jmespath"; assert.ok(typeof jmespath1.isNull === "function"); const jmespath2 = require("elem-aws-lambda-powertools/jmespath"); assert.ok(typeof jmespath2.isNull === "function"); ================================================ FILE: fixtures/test_modules/test-debug.js ================================================ const assert = require("assert"); const debug = require("elem-debug"); assert.ok(debug.cat() == "cat"); assert.ok(debug.length() == 0); ================================================ FILE: fixtures/test_modules/test-elem-hono.js ================================================ const assert = require("assert"); const utils = require("elem-hono/utils/url"); assert.ok(utils.url() === "foo"); ================================================ FILE: fixtures/test_modules/test-lodash.merge.js ================================================ const assert = require("assert"); import merge1 from "elem-lodash.merge"; assert.ok(typeof merge1 === "function"); const merge2 = require("elem-lodash.merge"); assert.ok(typeof merge2 === "function"); ================================================ FILE: fixtures/test_modules/test-react-dom.js ================================================ const assert = require("assert"); import * as dom1 from "elem-react-dom/server.edge"; assert.ok(dom1.name == "react-dom/server.edge"); const dom2 = require("elem-react-dom/server.edge"); assert.ok(dom2.name == "react-dom/server.edge"); ================================================ FILE: fixtures/test_modules/test-uuid.js ================================================ const assert = require("assert"); var fn = _interopRequireDefault(require("elem-uuid")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var greeting = (0, fn.default)("hello"); assert.ok(greeting("world") == "hello, world"); ================================================ FILE: fixtures/throw.js ================================================ throw 42; ================================================ FILE: fixtures/throwing-handler.mjs ================================================ export const handler = async () => { throw new Error("kaboom"); }; ================================================ FILE: fixtures/throwing-init-handler.mjs ================================================ throw new Error("kaboom"); export const handler = async () => {}; ================================================ FILE: fixtures/tla-webcall-handler.mjs ================================================ await fetch(__MOCK_ENDPOINT); export const handler = async () => { return { statusCode: 200, body: "OK", }; }; ================================================ FILE: index.mjs ================================================ import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; const client = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(client); export const handler = async (event) => { await docClient.send( new PutCommand({ TableName: process.env.TABLE_NAME, Item: { id: Math.random().toString(36).substring(2), content: JSON.stringify(event), }, }) ); return { statusCode: 200, body: "OK", }; }; ================================================ FILE: lambda-server.js ================================================ import pureHttp from "pure-http"; import fs from "node:fs"; const PORT = 3000; const BASE_PATH = "/2018-06-01/runtime"; const ARGS = process.argv.slice(2); let httpMode = false; let eventJson = null; let argIndex = 0; for (let arg of ARGS) { if (arg == "-h" || arg == "--http") { httpMode = true; } if (arg == "-e" || arg == "--event") { eventJson = JSON.parse(fs.readFileSync(ARGS[argIndex + 1]).toString()); } argIndex++; } const app = pureHttp(); const pendingRequests = []; const requests = {}; let invocationWaiter = null; app.use(async (req, res, next) => { const body = await new Promise((resolve, reject) => { let buffer = ""; req .on("data", (chunk) => { buffer += chunk; }) .on("end", () => { resolve(buffer); }) .on("error", reject); }); if (body) { req.body = body || undefined; } return next(); }); app.use((req, res, next) => { const date = new Date(); const hours = date.getHours(); const minutes = date.getMinutes(); const seconds = date.getSeconds(); const milliseconds = date.getMilliseconds(); console.log( `${hours}:${minutes}:${seconds}:${milliseconds} - ${req.method}: ${req.path}` ); try { return next(); } catch (error) { res.json({ error }); } }); app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Headers", "*"); res.setHeader("Access-Control-Allow-Methods", "*"); if (req.method === "OPTIONS") { return res.send(""); } next(); }); app.get(`${BASE_PATH}/invocation/next`, async (req, res) => { res.header("lambda-runtime-deadline-ms", Date.now() + 1000 * 60); if (httpMode) { if (pendingRequests.length == 0) { await new Promise((resolve) => { invocationWaiter = resolve; }); } let { id, event } = pendingRequests.shift(); res.header("lambda-runtime-aws-request-id", id); res.json(event); } else { res.header("lambda-runtime-aws-request-id", "1234"); res.json( eventJson || { key1: "value1", key2: "value2", key3: "value3", } ); } }); app.post(`${BASE_PATH}/init/error`, (req, res) => { if (httpMode) { for (const request of pendingRequests) { request.reject({ statusCode: 500, body: req.body, headers: { "content-type": "application/json" }, }); } } res.status(202); res.send(); }); app.post(`${BASE_PATH}/invocation/:id/response`, (req, res) => { if (httpMode) { const { id } = req.params; const { resolve } = requests[id]; delete requests[id]; resolve(req.body); } else { console.log(req.body); } res.status(202); res.send(); }); app.post(`${BASE_PATH}/invocation/:id/error`, (req, res) => { if (httpMode) { const { id } = req.params; const { reject } = requests[id]; delete requests[id]; reject({ body: req.body, statusCode: 500, headers: { "content-type": "application/json" }, }); } else { console.error(req.body); } res.status(202); res.send(); }); app.all("*", async (req, res) => { if (!httpMode) { res.status(400); res.send("Server is not in HTTP mode. Start with -h flag"); return; } const requestUrl = new URL(`http://localhost:0000${req.url}`); const rawQueryString = requestUrl.search?.substring(1); const requestId = Math.random().toString(36).substring(2); const queryStringParameters = Object.fromEntries( requestUrl.searchParams.entries() ); const event = { version: "2.0", routeKey: "$default", rawPath: "/my/path", rawQueryString, cookies: undefined, headers: Object.entries(req.headers).reduce((acc, [key, value]) => { acc[key] = (value && Array.isArray(value) && value.join(",")) || value; return acc; }, {}), queryStringParameters, requestContext: { accountId: "123456789012", apiId: "localhost", domainName: "localhost", domainPrefix: "localhost", http: { method: req.method, path: req.path, protocol: req.protocol, sourceIp: "192.168.1.1", userAgent: req.header["User-Agent"], }, requestId, routeKey: "$default", stage: "$default", time: new Date().toString(), timeEpoch: new Date().getTime(), }, body: req.body, pathParameters: req.params, isBase64Encoded: false, }; const responsePromise = new Promise((resolve, reject) => { const id = requestId; const request = { event, resolve, reject, id, }; pendingRequests.push(request); requests[id] = request; }); if (invocationWaiter) { invocationWaiter(); } let result; try { result = await responsePromise; } catch (e) { result = e; } try { result = JSON.parse(result); } catch (_) {} if (result.body && result.statusCode) { if (result.headers) { for (const key in result.headers) { res.setHeader(key, result.headers[key]); } } res.status(result.statusCode); if (result.isBase64Encoded) { res.send(Buffer.from(result.body, "base64")); } else { res.send(result.body); } return; } res.send(result); }); app.listen(PORT, () => { console.log(`Server started on port ${PORT}`); if (httpMode) { console.log(`- HTTP: http://localhost:${PORT}`); } }); ================================================ FILE: libs/llrt_build/Cargo.toml ================================================ [package] name = "llrt_build" description = "LLRT build helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_build" path = "src/lib.rs" [dependencies] rustc_version = { version = "0.4", default-features = false } ================================================ FILE: libs/llrt_build/src/lib.rs ================================================ pub fn set_nightly_cfg() { let version_meta = rustc_version::version_meta().unwrap(); println!("cargo::rustc-check-cfg=cfg(rust_nightly)"); if version_meta.channel == rustc_version::Channel::Nightly { println!("cargo:rustc-cfg=rust_nightly"); } } ================================================ FILE: libs/llrt_compression/Cargo.toml ================================================ [package] name = "llrt_compression" description = "LLRT compression helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_compression" path = "src/lib.rs" [features] default = ["all-c"] all-c = ["brotli-c", "flate2-c", "zstd-c"] all-rust = ["brotli-rust", "flate2-rust", "zstd-rust"] brotli-c = ["brotlic"] brotli-rust = ["brotli"] flate2-c = ["flate2/zlib-ng"] flate2-rust = ["flate2/rust_backend"] zstd-c = ["zstd"] zstd-rust = ["zstd"] # No pure rust implementation exists [dependencies] # Optional brotlic = { version = "0.8", default-features = false, optional = true } brotli = { version = "8", features = [ "std", ], default-features = false, optional = true } flate2 = { version = "1", default-features = false, optional = true } zstd = { version = "0.13", default-features = false, optional = true } ================================================ FILE: libs/llrt_compression/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #[cfg(any(feature = "zstd-c", feature = "zstd-rust"))] pub mod zstd { use std::io::{BufReader, Read, Result}; use zstd::stream::read::{Decoder as ZstdDecoder, Encoder as ZstdEncoder}; pub use zstd::DEFAULT_COMPRESSION_LEVEL; pub fn encoder(r: R, level: i32) -> Result>> { ZstdEncoder::new(r, level) } pub fn decoder(r: R) -> Result>> { ZstdDecoder::new(r) } } #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] pub mod deflate { use std::io::Read; use flate2::read::{DeflateDecoder, DeflateEncoder}; pub use flate2::Compression; pub fn encoder(r: R, level: Compression) -> DeflateEncoder { DeflateEncoder::new(r, level) } pub fn decoder(r: R) -> DeflateDecoder { DeflateDecoder::new(r) } } #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] pub mod gz { use std::io::Read; use flate2::read::{GzDecoder, GzEncoder}; pub use flate2::Compression; pub fn encoder(r: R, level: Compression) -> GzEncoder { GzEncoder::new(r, level) } pub fn decoder(r: R) -> GzDecoder { GzDecoder::new(r) } } #[cfg(any(feature = "flate2-c", feature = "flate2-rust"))] pub mod zlib { use std::io::Read; use flate2::read::{ZlibDecoder, ZlibEncoder}; pub use flate2::Compression; pub fn encoder(r: R, level: Compression) -> ZlibEncoder { ZlibEncoder::new(r, level) } pub fn decoder(r: R) -> ZlibDecoder { ZlibDecoder::new(r) } } #[cfg(feature = "brotli-c")] pub mod brotli { use std::io::BufRead; use brotlic::{CompressorReader as BrotliEncoder, DecompressorReader as BrotliDecoder}; pub fn encoder(r: R) -> BrotliEncoder { BrotliEncoder::new(r) } pub fn decoder(r: R) -> BrotliDecoder { BrotliDecoder::new(r) } } #[cfg(all(not(feature = "brotli-c"), feature = "brotli-rust"))] pub mod brotli { use std::io::Read; use brotli::{CompressorReader as BrotliEncoder, Decompressor as BrotliDecoder}; pub fn encoder(r: R) -> BrotliEncoder { BrotliEncoder::new(r, 8_096, 11, 22) } pub fn decoder(r: R) -> BrotliDecoder { BrotliDecoder::new(r, 8_096) } } ================================================ FILE: libs/llrt_context/Cargo.toml ================================================ [package] name = "llrt_context" description = "LLRT context helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [dependencies] rquickjs = { version = "0.11", features = [ "futures", ], default-features = false } llrt_utils = { version = "0.8.1-beta", path = "../llrt_utils", default-features = false } tokio = { version = "1", features = ["sync"], default-features = false } tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_test = { version = "0.8.1-beta", path = "../llrt_test" } [build-dependencies] llrt_build = { version = "0.8.1-beta", path = "../llrt_build" } ================================================ FILE: libs/llrt_context/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::future::Future; use std::sync::OnceLock; use llrt_utils::primordials::{BasePrimordials, Primordial}; use rquickjs::{atom::PredefinedAtom, CatchResultExt, CaughtError, Ctx, Object, Result}; use tokio::sync::oneshot::{self, Receiver}; use tracing::trace; #[allow(clippy::type_complexity)] static ERROR_HANDLER: OnceLock Fn(&Ctx<'js>, CaughtError<'js>) + Sync + Send>> = OnceLock::new(); pub trait CtxExtension<'js> { /// Despite naming, this will not necessarily exit the parent process. /// It depends on the handler set by `set_spawn_error_handler`. fn spawn_exit(&self, future: F) -> Result> where F: Future> + 'js, R: 'js; fn spawn_exit_simple(&self, future: F) where F: Future> + 'js; } impl<'js> CtxExtension<'js> for Ctx<'js> { fn spawn_exit(&self, future: F) -> Result> where F: Future> + 'js, R: 'js, { let ctx = self.clone(); let primordials = BasePrimordials::get(self)?; let type_error: Object = primordials.constructor_type_error.construct(())?; let stack: Option = type_error.get(PredefinedAtom::Stack).ok(); let (join_channel_tx, join_channel_rx) = oneshot::channel(); self.spawn(async move { match future.await.catch(&ctx) { Ok(res) => { //result here doesn't matter if receiver has dropped let _ = join_channel_tx.send(res); }, Err(err) => handle_spawn_error(&ctx, err, stack), } }); Ok(join_channel_rx) } /// Same as above but fire & forget and without a forced stack trace collection fn spawn_exit_simple(&self, future: F) where F: Future> + 'js, { let ctx = self.clone(); self.spawn(async move { if let Err(err) = future.await.catch(&ctx) { handle_spawn_error(&ctx, err, None) } }); } } fn handle_spawn_error<'js>(ctx: &Ctx<'js>, err: CaughtError<'js>, stack: Option) { let error_handler = if let Some(handler) = ERROR_HANDLER.get() { handler } else { trace!("Future error: {:?}", err); return; }; if let CaughtError::Exception(err) = err { if err.stack().is_none() { if let Some(stack) = stack { err.set(PredefinedAtom::Stack, stack).unwrap(); } } error_handler(ctx, CaughtError::Exception(err)); } else { error_handler(ctx, err); } } pub fn set_spawn_error_handler(handler: F) where F: for<'js> Fn(&Ctx<'js>, CaughtError<'js>) + Sync + Send + 'static, { _ = ERROR_HANDLER.set(Box::new(handler)); } ================================================ FILE: libs/llrt_dns_cache/Cargo.toml ================================================ [package] name = "llrt_dns_cache" description = "LLRT dns cache helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_dns_cache" path = "src/lib.rs" [dependencies] hyper-util = { version = "0.1", features = [ "client", "client-legacy", ], default-features = false } llrt_context = { version = "0.8.1-beta", path = "../llrt_context" } llrt_utils = { version = "0.8.1-beta", path = "../llrt_utils", default-features = false } tokio = { version = "1", features = ["net"], default-features = false } tower-service = { version = "0.3", default-features = false } quick_cache = { version = "0.6", default-features = false } ================================================ FILE: libs/llrt_dns_cache/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ future::Future, io, net::SocketAddr, pin::Pin, sync::Arc, task::{self, Poll}, time::{Duration, Instant}, vec, }; use hyper_util::client::legacy::connect::{dns::Name, HttpConnector}; use quick_cache::sync::Cache; use tokio::sync::Semaphore; use tower_service::Service; #[derive(Clone)] pub struct SocketAddrs { iter: vec::IntoIter, } impl Iterator for SocketAddrs { type Item = SocketAddr; #[inline] fn next(&mut self) -> Option { self.iter.next() } } #[derive(Clone)] struct CacheEntry { ttl: Instant, addrs: SocketAddrs, } #[derive(Clone)] struct CacheConcurrencyGuard { semaphore: Arc, entry: Option, } impl CacheConcurrencyGuard { fn new(permits: u8) -> Self { Self { semaphore: Arc::new(Semaphore::new(permits as usize)), entry: None, } } } #[derive(Debug, Clone)] pub struct CachedDnsResolver { cache: Arc>, concurrency: u8, ttl: Duration, } impl Service for CachedDnsResolver { type Response = SocketAddrs; type Error = io::Error; type Future = Pin> + Send>>; fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, name: Name) -> Self::Future { let cache = self.cache.clone(); let permits = self.concurrency; let ttl = self.ttl; Box::pin(async move { let guard = match cache.get_value_or_guard_async(&name).await { Ok(guard) => guard, Err(placeholder) => { let guard = CacheConcurrencyGuard::new(permits); _ = placeholder.insert(guard.clone()); guard }, }; if let Some(entry) = guard.entry { if entry.ttl > Instant::now() { return Ok(entry.addrs); } }; let semaphore = guard.semaphore; let semaphore2 = semaphore.clone(); let lock = semaphore2.acquire().await.unwrap(); if let Some(item) = cache.get(&name).and_then(|guard| guard.entry) { return Ok(item.addrs); } let addrs = tokio::net::lookup_host((name.as_str(), 0)).await?; let addrs = addrs.collect::>(); let addrs = SocketAddrs { iter: addrs.into_iter(), }; let addrs2 = addrs.clone(); let entry = CacheEntry { ttl: Instant::now() + ttl, addrs, }; cache.insert( name, CacheConcurrencyGuard { semaphore, entry: Some(entry), }, ); drop(lock); Ok(addrs2) }) } } impl Default for CachedDnsResolver { fn default() -> Self { Self::new() } } impl CachedDnsResolver { pub fn new() -> Self { Self::with_options(128, 2, 300) } pub fn with_options(size: usize, concurrency: u8, ttl: u64) -> Self { Self { cache: Arc::new(Cache::new(size)), concurrency, ttl: Duration::from_secs(ttl), } } pub fn into_http_connector(self) -> HttpConnector { HttpConnector::::new_with_resolver(self) } } ================================================ FILE: libs/llrt_encoding/Cargo.toml ================================================ [package] name = "llrt_encoding" description = "LLRT encoding helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [dependencies] base64-simd = { version = "0.8", features = [ "alloc", ], default-features = false } hex-simd = { version = "0.8", features = ["alloc"], default-features = false } phf = { version = "0.13", features = ["macros"], default-features = false } memchr = { version = "2", default-features = false } [build-dependencies] llrt_build = { version = "0.8.1-beta", path = "../llrt_build" } ================================================ FILE: libs/llrt_encoding/build.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 fn main() { llrt_build::set_nightly_cfg(); } ================================================ FILE: libs/llrt_encoding/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![cfg_attr(rust_nightly, feature(iter_array_chunks))] use std::borrow::Cow; use hex_simd::AsciiCase; #[derive(Clone, PartialEq)] pub enum Encoder { Hex, Base64, Windows1252, Utf8, Utf16le, Utf16be, } const ENCODING_MAP: phf::Map<&'static str, Encoder> = phf::phf_map! { "hex" => Encoder::Hex, "base64" => Encoder::Base64, "unicode-1-1-utf-8" => Encoder::Utf8, "unicode11utf8" => Encoder::Utf8, "unicode20utf8" => Encoder::Utf8, "utf-8" => Encoder::Utf8, "utf8" => Encoder::Utf8, "x-unicode20utf8" => Encoder::Utf8, "csunicode" => Encoder::Utf16le, "iso-10646-ucs-2" => Encoder::Utf16le, "ucs-2" => Encoder::Utf16le, "ucs2" => Encoder::Utf16le, "unicode" => Encoder::Utf16le, "unicodefeff" => Encoder::Utf16le, "utf-16" => Encoder::Utf16le, "utf-16le" => Encoder::Utf16le, "utf16le" => Encoder::Utf16le, "unicodefffe" => Encoder::Utf16be, "utf-16be" => Encoder::Utf16be, "ansi_x3.4-1968" => Encoder::Windows1252, "ascii" => Encoder::Windows1252, "cp1252" => Encoder::Windows1252, "cp819" => Encoder::Windows1252, "csisolatin1" => Encoder::Windows1252, "ibm819" => Encoder::Windows1252, "iso-8859-1" => Encoder::Windows1252, "iso-ir-100" => Encoder::Windows1252, "iso8859-1" => Encoder::Windows1252, "iso88591" => Encoder::Windows1252, "iso_8859-1" => Encoder::Windows1252, "iso_8859-1:1987" => Encoder::Windows1252, "l1" => Encoder::Windows1252, "latin1" => Encoder::Windows1252, "us-ascii" => Encoder::Windows1252, "windows-1252" => Encoder::Windows1252, "x-cp1252" => Encoder::Windows1252, }; impl Encoder { pub fn from_optional_str(encoding: Option<&str>) -> Result { match encoding { Some(label) if !label.is_empty() => Self::from_str(label), _ => Ok(Self::Utf8), } } #[allow(clippy::should_implement_trait)] pub fn from_str(encoding: &str) -> Result { ENCODING_MAP .get(encoding.trim_ascii().to_ascii_lowercase().as_str()) .cloned() .ok_or_else(|| ["The \"", encoding, "\" encoding is not supported"].concat()) } pub fn encode_to_string(&self, bytes: &[u8], lossy: bool) -> Result { match self { Self::Hex => Ok(bytes_to_hex_string(bytes)), Self::Base64 => Ok(bytes_to_b64_string(bytes)), Self::Utf8 | Self::Windows1252 => bytes_to_utf8_string(bytes, lossy), Self::Utf16le => bytes_to_utf16_string(bytes, Endian::Little, lossy), Self::Utf16be => bytes_to_utf16_string(bytes, Endian::Big, lossy), } } #[allow(dead_code)] pub fn encode(&self, bytes: &[u8]) -> Result, String> { match self { Self::Hex => Ok(bytes_to_hex(bytes)), Self::Base64 => Ok(bytes_to_b64(bytes)), Self::Utf8 | Self::Windows1252 | Self::Utf16le | Self::Utf16be => Ok(bytes.to_vec()), } } pub fn decode<'a, T: Into>>(&self, bytes: T) -> Result, String> { match self { Self::Hex => bytes_from_hex(bytes), Self::Base64 => bytes_from_b64(bytes), Self::Utf8 | Self::Windows1252 | Self::Utf16le | Self::Utf16be => { Ok(bytes.into().into()) }, } } pub fn decode_from_string(&self, string: String) -> Result, String> { match self { Self::Hex => bytes_from_hex(string.into_bytes()), Self::Base64 => bytes_from_b64(string.into_bytes()), Self::Utf8 | Self::Windows1252 => Ok(string.into_bytes()), Self::Utf16le => Ok(string .encode_utf16() .flat_map(|utf16| utf16.to_le_bytes()) .collect::>()), Self::Utf16be => Ok(string .encode_utf16() .flat_map(|utf16| utf16.to_be_bytes()) .collect::>()), } } pub fn as_label(&self) -> &str { match self { Self::Hex => "hex", Self::Base64 => "base64", Self::Windows1252 => "windows-1252", Self::Utf8 => "utf-8", Self::Utf16le => "utf-16le", Self::Utf16be => "utf-16be", } } } pub fn bytes_to_hex(bytes: &[u8]) -> Vec { hex_simd::encode_type(bytes, AsciiCase::Lower) } pub fn bytes_from_hex<'a, T: Into>>(hex_bytes: T) -> Result, String> { hex_simd::decode_to_vec(hex_bytes.into()).map_err(|err| err.to_string()) } pub fn bytes_from_b64<'a, T: Into>>(base64_bytes: T) -> Result, String> { let bytes: Cow<'a, [u8]> = base64_bytes.into(); //need to collect since memchr2_iter is borrowing bytes. This is fine since we're unlikely to contain url safe base64 let url_safe_byte_positions: Vec = memchr::memchr2_iter(b'-', b'_', &bytes).collect(); if url_safe_byte_positions.is_empty() { return base64_simd::forgiving_decode_to_vec(&bytes).map_err(|e| e.to_string()); } //doesn't allocate for already owned data let mut bytes = bytes.into_owned(); for pos in url_safe_byte_positions { bytes[pos] = match bytes[pos] { b'-' => b'+', b'_' => b'/', _ => unreachable!(), }; } base64_simd::forgiving_decode_to_vec(&bytes).map_err(|e| e.to_string()) } pub fn bytes_to_b64_string(bytes: &[u8]) -> String { base64_simd::STANDARD.encode_to_string(bytes) } pub fn bytes_to_b64_url_safe_string(bytes: &[u8]) -> String { base64_simd::URL_SAFE_NO_PAD.encode_to_string(bytes) } pub fn bytes_from_b64_url_safe(bytes: &[u8]) -> Result, String> { base64_simd::URL_SAFE_NO_PAD .decode_to_vec(bytes) .map_err(|e| e.to_string()) } pub fn bytes_to_b64(bytes: &[u8]) -> Vec { base64_simd::STANDARD.encode_type(bytes) } pub fn bytes_to_hex_string(bytes: &[u8]) -> String { hex_simd::encode_to_string(bytes, AsciiCase::Lower) } pub fn bytes_to_utf8_string(bytes: &[u8], lossy: bool) -> Result { if lossy { Ok(String::from_utf8_lossy(bytes).to_string()) } else { String::from_utf8(bytes.to_vec()).map_err(|e| e.to_string()) } } #[derive(Clone, Copy)] pub enum Endian { Little, Big, } pub fn bytes_to_utf16_string(bytes: &[u8], endian: Endian, lossy: bool) -> Result { if !lossy && !bytes.len().is_multiple_of(2) { return Err("Input byte slice length must be even".to_string()); } #[cfg(rust_nightly)] let data16: Vec = match endian { Endian::Little => bytes .iter() .copied() .array_chunks::<2>() .map(|chunk| u16::from_le_bytes(chunk)) .collect(), Endian::Big => bytes .iter() .copied() .array_chunks::<2>() .map(|chunk| u16::from_be_bytes(chunk)) .collect(), }; #[cfg(not(rust_nightly))] let data16: Vec = match endian { Endian::Little => bytes .chunks_exact(2) .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) .collect(), Endian::Big => bytes .chunks_exact(2) .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])) .collect(), }; if lossy { Ok(String::from_utf16_lossy(&data16)) } else { String::from_utf16(&data16).map_err(|e| e.to_string()) } } ================================================ FILE: libs/llrt_hooking/Cargo.toml ================================================ [package] name = "llrt_hooking" description = "LLRT hooking helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_hooking" path = "src/lib.rs" [dependencies] llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } rquickjs = { version = "0.11", default-features = false } ================================================ FILE: libs/llrt_hooking/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::env; use llrt_utils::{object::ObjectExt, provider::ProviderType}; use once_cell::sync::Lazy; use rquickjs::{Ctx, Exception, Function, Result, Value}; pub static HOOKING_MODE: Lazy = Lazy::new(|| env::var("LLRT_ASYNC_HOOKS").as_deref() == Ok("1")); #[derive(PartialEq)] pub enum HookType { Init, Before, After, } pub fn invoke_async_hook( ctx: &Ctx<'_>, hook_type: HookType, provider_type: ProviderType, uid: usize, ) -> Result<()> { if !HOOKING_MODE.to_owned() { return Ok(()); } let hook_ = match hook_type { HookType::Init => "init", HookType::Before => "before", HookType::After => "after", }; let provider_ = match provider_type { ProviderType::None if hook_type != HookType::Init => "", ProviderType::None => { return Err(Exception::throw_type( ctx, "Asynchronous types cannot be omitted in init hooks.", )) }, ProviderType::Resource(s) => &["Resource(", &s, ")"].concat(), // Userland provider types ProviderType::Immediate => "Immediate", ProviderType::Interval => "Interval", ProviderType::MessagePort => "MessagePort", ProviderType::Microtask => "Microtask", ProviderType::TickObject => "TickObject", ProviderType::Timeout => "Timeout", // Internal provider types ProviderType::FsReqCallback => "FSREQCALLBACK", ProviderType::GetAddrInfoReqWrap => "GETADDRINFOREQWRAP", ProviderType::GetNameInfoReqWrap => "GETNAMEINFOREQWRAP", ProviderType::PipeWrap => "PIPEWRAP", ProviderType::StatWatcher => "STATWACHER", ProviderType::TcpWrap => "TCPWRAP", ProviderType::TimerWrap => "TIMERWRAP", ProviderType::TlsWrap => "TLSWRAP", ProviderType::UdpWrap => "UDPWRAP", }; let invoke_async_hook = ctx .globals() .get_optional::<_, Function>("invokeAsyncHook")?; if let Some(func) = &invoke_async_hook { func.call::<_, ()>((hook_, provider_, uid))?; } Ok(()) } pub fn register_finalization_registry<'js>( ctx: &Ctx<'js>, target: Value<'js>, uid: usize, ) -> Result<()> { if !HOOKING_MODE.to_owned() { return Ok(()); } if let Ok(register) = ctx.eval::, &str>("globalThis.asyncFinalizationRegistry.register") { let _ = register.call::<_, ()>((target, uid)); } Ok(()) } ================================================ FILE: libs/llrt_json/Cargo.toml ================================================ [package] name = "llrt_json" description = "LLRT json helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_json" path = "src/lib.rs" [dependencies] itoa = { version = "1", default-features = false } llrt_utils = { version = "0.8.1-beta", path = "../llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } ryu = { version = "1", default-features = false } simd-json = { version = "0.17", features = [ "big-int-as-float", ], default-features = false } [dev-dependencies] criterion = { version = "0.8", default-features = false } llrt_test = { version = "0.8.1-beta", path = "../llrt_test" } tokio = { version = "1", features = ["test-util"], default-features = false } [build-dependencies] llrt_build = { version = "0.8.1-beta", path = "../llrt_build" } [[bench]] name = "json" harness = false ================================================ FILE: libs/llrt_json/benches/json.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(dead_code)] use std::hint::black_box; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use llrt_json::{escape::escape_json, parse::json_parse, stringify::json_stringify}; use rquickjs::{Context, Runtime}; static JSON: &str = r#"{"organization":{"name":"TechCorp","founding_year":2000,"departments":[{"name":"Engineering","head":{"name":"Alice Smith","title":"VP of Engineering","contact":{"email":"alice.smith@techcorp.com","phone":"+1 (555) 123-4567"}},"employees":[{"id":101,"name":"Bob Johnson","position":"Software Engineer","contact":{"email":"bob.johnson@techcorp.com","phone":"+1 (555) 234-5678"},"projects":[{"project_id":"P001","name":"Project A","status":"In Progress","description":"Developing a revolutionary software solution for clients.","start_date":"2023-01-15","end_date":null,"team":[{"id":201,"name":"Sara Davis","role":"UI/UX Designer"},{"id":202,"name":"Charlie Brown","role":"Quality Assurance Engineer"}]},{"project_id":"P002","name":"Project B","status":"Completed","description":"Upgrading existing systems to enhance performance.","start_date":"2022-05-01","end_date":"2022-11-30","team":[{"id":203,"name":"Emily White","role":"Systems Architect"},{"id":204,"name":"James Green","role":"Database Administrator"}]}]},{"id":102,"name":"Carol Williams","position":"Senior Software Engineer","contact":{"email":"carol.williams@techcorp.com","phone":"+1 (555) 345-6789"},"projects":[{"project_id":"P001","name":"Project A","status":"In Progress","description":"Working on the backend development of Project A.","start_date":"2023-01-15","end_date":null,"team":[{"id":205,"name":"Alex Turner","role":"DevOps Engineer"},{"id":206,"name":"Mia Garcia","role":"Software Developer"}]},{"project_id":"P003","name":"Project C","status":"Planning","description":"Researching and planning for a future project.","start_date":null,"end_date":null,"team":[]}]}]},{"name":"Marketing","head":{"name":"David Brown","title":"VP of Marketing","contact":{"email":"david.brown@techcorp.com","phone":"+1 (555) 456-7890"}},"employees":[{"id":201,"name":"Eva Miller","position":"Marketing Specialist","contact":{"email":"eva.miller@techcorp.com","phone":"+1 (555) 567-8901"},"campaigns":[{"campaign_id":"C001","name":"Product Launch","status":"Upcoming","description":"Planning for the launch of a new product line.","start_date":"2023-03-01","end_date":null,"team":[{"id":301,"name":"Oliver Martinez","role":"Graphic Designer"},{"id":302,"name":"Sophie Johnson","role":"Content Writer"}]},{"campaign_id":"C002","name":"Brand Awareness","status":"Ongoing","description":"Executing strategies to increase brand visibility.","start_date":"2022-11-15","end_date":"2023-01-31","team":[{"id":303,"name":"Liam Taylor","role":"Social Media Manager"},{"id":304,"name":"Ava Clark","role":"Marketing Analyst"}]}]}]}]}}"#; static JSON_MIN: &str = r#"{"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","GlossSeeAlso":["GML","XML"]},"GlossSee":"markup"}}}}}"#; fn generate_json(child_json: &str, size: usize) -> String { let mut json = String::with_capacity(child_json.len() * size); json.push('{'); for i in 0..size { json.push_str("\"obj"); json.push_str(&i.to_string()); json.push_str("\":"); json.push_str(child_json); json.push(','); } json.push_str("\"array\":["); for i in 0..size { json.push_str(child_json); if i < size - 1 { json.push(','); } } json.push_str("]}"); json } pub fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("Parsing"); let json = JSON.to_owned(); for (id, json) in [ json.clone(), generate_json(&json, 1), generate_json(&json, 10), generate_json(&json, 100), generate_json(&json, 1000), generate_json(&json, 10000), ] .into_iter() .enumerate() { let runtime = Runtime::new().unwrap(); runtime.set_max_stack_size(512 * 1024); let ctx = Context::full(&runtime).unwrap(); group.bench_with_input(BenchmarkId::new("Custom", id), &json, |b, json| { ctx.with(|ctx| { b.iter(|| json_parse(&ctx, json.clone())); }); }); group.bench_with_input(BenchmarkId::new("Built-in", id), &json, |b, json| { ctx.with(|ctx| { b.iter(|| ctx.json_parse(json.clone())); }); }); } group.finish(); c.bench_function("json parse", |b| { let runtime = Runtime::new().unwrap(); runtime.set_max_stack_size(512 * 1024); let ctx = Context::full(&runtime).unwrap(); ctx.with(|ctx| b.iter(|| json_parse(&ctx, black_box(JSON_MIN)))); }); c.bench_function("json stringify", |b| { let runtime = Runtime::new().unwrap(); runtime.set_max_stack_size(512 * 1024); let ctx = Context::full(&runtime).unwrap(); ctx.with(|ctx| { let obj = json_parse(&ctx, black_box(JSON)).unwrap(); b.iter(|| json_stringify(&ctx, obj.clone())) }); }); // Escape benchmarks let test_strings = [ ( "clean", "Hello World! This is a clean string with no escapes needed.", ), ("quotes", r#"This "has" some "quotes" to escape"#), ( "mixed", "Line 1\nLine 2\tTabbed\r\nWith \"quotes\" and \\backslashes\\", ), ( "heavy", "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\"\\", ), ]; for (name, test_str) in test_strings { c.bench_function(&format!("escape_{name}"), |b| { b.iter(|| escape_json(black_box(test_str.as_bytes()))) }); } } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: libs/llrt_json/build.rs ================================================ fn main() { llrt_build::set_nightly_cfg(); } ================================================ FILE: libs/llrt_json/src/escape.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 static JSON_ESCAPE_CHARS: [u8; 256] = [ 0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8, 10u8, 11u8, 12u8, 13u8, 14u8, 15u8, 16u8, 17u8, 18u8, 19u8, 20u8, 21u8, 22u8, 23u8, 24u8, 25u8, 26u8, 27u8, 28u8, 29u8, 30u8, 31u8, 34u8, 34u8, 32u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 33u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, 34u8, ]; static JSON_ESCAPE_QUOTES: [&str; 34usize] = [ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f", "\\\"", "\\\\", ]; const ESCAPE_LEN: usize = 34; #[inline(always)] fn write_surrogate_escape(result: &mut String, bytes: &[u8], i: usize) -> usize { let code_point = ((bytes[i] as u16 & 0x0F) << 12) | ((bytes[i + 1] as u16 & 0x3F) << 6) | (bytes[i + 2] as u16 & 0x3F); result.push_str("\\u"); let hex = [ (code_point >> 12) as u8, ((code_point >> 8) & 0xF) as u8, ((code_point >> 4) & 0xF) as u8, (code_point & 0xF) as u8, ]; for h in hex { result.push(if h < 10 { (b'0' + h) as char } else { (b'a' + h - 10) as char }); } 3 } #[allow(dead_code)] pub fn escape_json(bytes: &[u8]) -> String { let mut result = String::new(); escape_json_string(&mut result, bytes); result } #[inline(always)] fn process_byte( result: &mut String, bytes: &[u8], byte: u8, i: &mut usize, start: &mut usize, len: usize, ) { if *i + 2 < len && byte == 0xED && *i + 1 < len && (bytes[*i + 1] & 0xF0) >= 0xA0 { if *start < *i { result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[*start..*i]) }); } *i += write_surrogate_escape(result, bytes, *i); *start = *i; return; } let c = JSON_ESCAPE_CHARS[byte as usize] as usize; if c < ESCAPE_LEN { if *start < *i { result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[*start..*i]) }); } result.push_str(JSON_ESCAPE_QUOTES[c]); *start = *i + 1; } *i += 1; } #[inline(always)] pub fn escape_json_string_simple(result: &mut String, bytes: &[u8]) { let len = bytes.len(); let mut start = 0; let mut i = 0; result.reserve(len); let (chunks, tail) = bytes.as_chunks::<8>(); for chunk in chunks { if chunk.iter().any(|&b| b < 32 || b == 34 || b == 92) { for &byte in chunk { process_byte(result, bytes, byte, &mut i, &mut start, len); } } else { i += 8; } } for &byte in tail { process_byte(result, bytes, byte, &mut i, &mut start, len); } if start < len { result.push_str(unsafe { std::str::from_utf8_unchecked(&bytes[start..len]) }); } } pub fn escape_json_string(result: &mut String, bytes: &[u8]) { escape_json_string_simple(result, bytes); } #[cfg(test)] mod tests { use crate::escape::escape_json; #[test] fn escape_json_simple() { assert_eq!(escape_json(b"Hello, World!"), "Hello, World!"); } #[test] fn escape_json_quotes() { assert_eq!(escape_json(b"\"quoted\""), "\\\"quoted\\\""); } #[test] fn escape_json_backslash() { assert_eq!(escape_json(b"back\\slash"), "back\\\\slash"); } #[test] fn escape_json_newline() { assert_eq!(escape_json(b"line\nbreak"), "line\\nbreak"); } #[test] fn escape_json_tab() { assert_eq!(escape_json(b"tab\tcharacter"), "tab\\tcharacter"); } #[test] fn escape_json_unicode() { assert_eq!( escape_json("unicode: \u{1F609}".as_bytes()), "unicode: \u{1F609}" ); } #[test] fn escape_json_special_characters() { assert_eq!( escape_json(b"!@#$%^&*()_+-=[]{}|;':,.<>?/"), "!@#$%^&*()_+-=[]{}|;':,.<>?/" ); } #[test] fn escape_json_mixed_characters() { assert_eq!( escape_json(b"123\"\"45678901\"234567"), "123\\\"\\\"45678901\\\"234567" ); } } ================================================ FILE: libs/llrt_json/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::cmp::min; use rquickjs::{ atom::PredefinedAtom, function::Opt, prelude::Func, Ctx, IntoJs, Object, Result, Value, }; pub mod escape; pub mod parse; pub mod stringify; use crate::parse::json_parse_string; use crate::stringify::json_stringify_replacer_space; pub fn redefine_static_methods(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); let json_module: Object = globals.get(PredefinedAtom::JSON)?; json_module.set("parse", Func::from(json_parse_string))?; json_module.set( "stringify", Func::from(|ctx, value, replacer, space| { struct StringifyArgs<'js>(Ctx<'js>, Value<'js>, Opt>, Opt>); let StringifyArgs(ctx, value, replacer, space) = StringifyArgs(ctx, value, replacer, space); let mut space_value = None; let mut replacer_value = None; if let Some(replacer) = replacer.0 { if let Some(space) = space.0 { if let Some(space) = space.as_string() { let mut space = space.clone().to_string()?; space.truncate(20); space_value = Some(space); } if let Some(number) = space.as_int() { if number > 0 { space_value = Some(" ".repeat(min(10, number as usize))); } } } replacer_value = Some(replacer); } json_stringify_replacer_space(&ctx, value, replacer_value, space_value) .map(|v| v.into_js(&ctx))? }), )?; Ok(()) } #[cfg(test)] mod tests { use llrt_test::test_sync_with; use rquickjs::{prelude::Func, Array, CatchResultExt, IntoJs, Null, Object, Undefined, Value}; use crate::{ parse::{json_parse, json_parse_string}, stringify::{json_stringify, json_stringify_replacer_space}, }; static JSON: &str = r#"{"organization":{"name":"TechCorp","founding_year":2000,"departments":[{"name":"Engineering","head":{"name":"Alice Smith","title":"VP of Engineering","contact":{"email":"alice.smith@techcorp.com","phone":"+1 (555) 123-4567"}},"employees":[{"id":101,"name":"Bob Johnson","position":"Software Engineer","contact":{"email":"bob.johnson@techcorp.com","phone":"+1 (555) 234-5678"},"projects":[{"project_id":"P001","name":"Project A","status":"In Progress","description":"Developing a revolutionary software solution for clients.","start_date":"2023-01-15","end_date":null,"team":[{"id":201,"name":"Sara Davis","role":"UI/UX Designer"},{"id":202,"name":"Charlie Brown","role":"Quality Assurance Engineer"}]},{"project_id":"P002","name":"Project B","status":"Completed","description":"Upgrading existing systems to enhance performance.","start_date":"2022-05-01","end_date":"2022-11-30","team":[{"id":203,"name":"Emily White","role":"Systems Architect"},{"id":204,"name":"James Green","role":"Database Administrator"}]}]},{"id":102,"name":"Carol Williams","position":"Senior Software Engineer","contact":{"email":"carol.williams@techcorp.com","phone":"+1 (555) 345-6789"},"projects":[{"project_id":"P001","name":"Project A","status":"In Progress","description":"Working on the backend development of Project A.","start_date":"2023-01-15","end_date":null,"team":[{"id":205,"name":"Alex Turner","role":"DevOps Engineer"},{"id":206,"name":"Mia Garcia","role":"Software Developer"}]},{"project_id":"P003","name":"Project C","status":"Planning","description":"Researching and planning for a future project.","start_date":null,"end_date":null,"team":[]}]}]},{"name":"Marketing","head":{"name":"David Brown","title":"VP of Marketing","contact":{"email":"david.brown@techcorp.com","phone":"+1 (555) 456-7890"}},"employees":[{"id":201,"name":"Eva Miller","position":"Marketing Specialist","contact":{"email":"eva.miller@techcorp.com","phone":"+1 (555) 567-8901"},"campaigns":[{"campaign_id":"C001","name":"Product Launch","status":"Upcoming","description":"Planning for the launch of a new product line.","start_date":"2023-03-01","end_date":null,"team":[{"id":301,"name":"Oliver Martinez","role":"Graphic Designer"},{"id":302,"name":"Sophie Johnson","role":"Content Writer"}]},{"campaign_id":"C002","name":"Brand Awareness","status":"Ongoing","description":"Executing strategies to increase brand visibility.","start_date":"2022-11-15","end_date":"2023-01-31","team":[{"id":303,"name":"Liam Taylor","role":"Social Media Manager"},{"id":304,"name":"Ava Clark","role":"Marketing Analyst"}]}]}]}]}}"#; #[tokio::test] async fn json_parser() { test_sync_with(|ctx| { let json_data = [ r#"{"aa\"\"aaaaaaaaaaaaaaaa":"a","b":"bbb"}"#, r#"{"a":"aaaaaaaaaaaaaaaaaa","b":"bbb"}"#, r#"{"a":["a","a","aaaa","a"],"b":"b"}"#, r#"{"type":"Buffer","data":[10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]}"#, r#"{"a":[{"object2":{"key1":"value1","key2":123,"key3":false,"nestedObject":{"nestedKey":"nestedValue"}},"string":"Hello, World!","emptyObj":{},"emptyArr":[],"number":42,"boolean":true,"nullValue":null,"array":[1,2,3,"four",5.5,true,null],"object":{"key1":"value1","key2":123,"key3":false,"nestedObject":{"nestedKey":"nestedValue"}}}]}"#, JSON, ]; for json_str in json_data { let json = json_str.to_string(); let json2 = json.clone(); let value = json_parse(&ctx, json2)?; let new_json = json_stringify_replacer_space(&ctx, value.clone(),None,Some(" ".into()))?.unwrap(); let builtin_json = ctx.json_stringify_replacer_space(value,Null," ".to_string())?.unwrap().to_string()?; assert_eq!(new_json, builtin_json); } Ok(()) }) .await; } #[tokio::test] async fn json_parse_non_string() { test_sync_with(|ctx| { ctx.globals().set("parse", Func::from(json_parse_string))?; let result = ctx.eval::<(), _>("parse({})").catch(&ctx); if let Err(err) = result { assert_eq!( err.to_string(), "Error: \"[object Object]\" not valid JSON at index 1 ('o')\n at (eval_script:1:1)\n" ); } else { panic!("expected error") } Ok(()) }) .await; } #[tokio::test] async fn json_stringify_undefined() { test_sync_with(|ctx| { let stringified = json_stringify(&ctx, Undefined.into_js(&ctx)?)?; let stringified_2 = ctx .json_stringify(Undefined)? .map(|v| v.to_string().unwrap()); assert_eq!(stringified, stringified_2); let obj: Value = ctx.eval( r#"let obj = { value: undefined, array: [undefined, null, 1, true, "hello", { [Symbol("sym")]: 1, [undefined]: 2}] };obj;"#, )?; let stringified = json_stringify(&ctx, obj.clone())?; let stringified_2 = ctx .json_stringify(obj)? .map(|v| v.to_string().unwrap()); assert_eq!(stringified, stringified_2); Ok(()) }) .await; } #[tokio::test] async fn json_stringify_objects() { test_sync_with(|ctx| { let date: Value = ctx.eval("let obj = { date: new Date(0) };obj;")?; let stringified = json_stringify(&ctx, date.clone())?.unwrap(); let stringified_2 = ctx.json_stringify(date)?.unwrap().to_string()?; assert_eq!(stringified, stringified_2); Ok(()) }) .await; } #[tokio::test] async fn huge_numbers() { test_sync_with(|ctx| { let big_int_value = json_parse(&ctx, b"99999999999999999999999999999999999999999999999999999999999999999999999999999999999")?; let stringified = json_stringify(&ctx, big_int_value.clone())?.unwrap(); let stringified_2 = ctx.json_stringify(big_int_value)?.unwrap().to_string()?.replace("e+", "e"); assert_eq!(stringified, stringified_2); let big_int_value: Value = ctx.eval("999999999999")?; let stringified = json_stringify(&ctx, big_int_value.clone())?.unwrap(); let stringified_2 = ctx.json_stringify(big_int_value)?.unwrap().to_string()?; assert_eq!(stringified, stringified_2); Ok(()) }) .await; } #[tokio::test] async fn json_circular_ref() { test_sync_with(|ctx| { let obj1 = Object::new(ctx.clone())?; let obj2 = Object::new(ctx.clone())?; let obj3 = Object::new(ctx.clone())?; let obj4 = Object::new(ctx.clone())?; obj4.set("key", "value")?; obj3.set("sub2", obj4.clone())?; obj2.set("sub1", obj3)?; obj1.set("root1", obj2.clone())?; obj1.set("root2", obj2.clone())?; obj1.set("root3", obj2.clone())?; let value = obj1.clone().into_value(); let stringified = json_stringify(&ctx, value.clone())?.unwrap(); let stringified_2 = ctx.json_stringify(value.clone())?.unwrap().to_string()?; assert_eq!(stringified, stringified_2); obj4.set("recursive", obj1.clone())?; let stringified = json_stringify(&ctx, value.clone()); if let Err(error_message) = stringified.catch(&ctx) { let error_str = error_message.to_string(); assert_eq!( "Error: Circular reference detected at: \"...root1.sub1.sub2.recursive\"\n", error_str ) } else { panic!("Expected an error, but got Ok"); } let array1 = Array::new(ctx.clone())?; let array2 = Array::new(ctx.clone())?; let array3 = Array::new(ctx.clone())?; let obj5 = Object::new(ctx.clone())?; obj5.set("key", obj1.clone())?; array3.set(2, obj5)?; array2.set(1, array3)?; array1.set(0, array2)?; obj4.remove("recursive")?; obj1.set("recursiveArray", array1)?; let stringified = json_stringify(&ctx, value.clone()); if let Err(error_message) = stringified.catch(&ctx) { let error_str = error_message.to_string(); assert_eq!( "Error: Circular reference detected at: \"...recursiveArray[0][1][2].key\"\n", error_str ) } else { panic!("Expected an error, but got Ok"); } Ok(()) }) .await; } } ================================================ FILE: libs/llrt_json/src/parse.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::bytes::ObjectBytes; use rquickjs::{Array, Ctx, Exception, IntoJs, Null, Object, Result, Undefined, Value}; use simd_json::{Node, StaticNode}; pub fn json_parse_string<'js>(ctx: Ctx<'js>, bytes: ObjectBytes<'js>) -> Result> { let bytes = bytes.as_bytes(&ctx)?; json_parse(&ctx, bytes) } pub fn json_parse<'js, T: Into>>(ctx: &Ctx<'js>, json: T) -> Result> { let mut json: Vec = json.into(); let tape = match simd_json::to_tape(&mut json) { Ok(tape) => tape, Err(err) => { let mut itoa = itoa::Buffer::new(); let mut error_msg = String::with_capacity(256); let json_length = json.len(); if json_length < 128 { error_msg.reserve(json_length); error_msg.push('\"'); error_msg.push_str(&std::string::String::from_utf8_lossy(&json)); error_msg.push_str("\" "); } error_msg.push_str("not valid JSON at index "); error_msg.push_str(itoa.format(err.index())); if let Some(char) = err.character() { error_msg.push_str(" ('"); error_msg.push(char); error_msg.push_str("')"); } return Err(Exception::throw_message(ctx, &error_msg)); }, }; let tape = tape.0; if let Some(first) = tape.first() { return match first { Node::String(value) => value.into_js(ctx), Node::Static(node) => static_node_to_value(ctx, *node), _ => parse_node(ctx, &tape, 0).map(|(value, _)| value), }; } Undefined.into_js(ctx) } #[inline(always)] fn static_node_to_value<'js>(ctx: &Ctx<'js>, node: StaticNode) -> Result> { match node { StaticNode::I64(value) => value.into_js(ctx), StaticNode::U64(value) => value.into_js(ctx), StaticNode::F64(value) => value.into_js(ctx), StaticNode::Bool(value) => value.into_js(ctx), StaticNode::Null => Null.into_js(ctx), } } fn parse_node<'js>(ctx: &Ctx<'js>, tape: &[Node], index: usize) -> Result<(Value<'js>, usize)> { match tape[index] { Node::String(value) => Ok((value.into_js(ctx)?, index + 1)), Node::Static(node) => Ok((static_node_to_value(ctx, node)?, index + 1)), Node::Object { len, .. } => { let js_object = Object::new(ctx.clone())?; let mut current_index = index + 1; for _ in 0..len { if let Node::String(key) = tape[current_index] { current_index += 1; let (value, new_index) = parse_node(ctx, tape, current_index)?; current_index = new_index; js_object.set(key, value)?; } } Ok((js_object.into_value(), current_index)) }, Node::Array { len, .. } => { let js_array = Array::new(ctx.clone())?; let mut current_index = index + 1; for i in 0..len { let (value, new_index) = parse_node(ctx, tape, current_index)?; current_index = new_index; js_array.set(i, value)?; } Ok((js_array.into_value(), current_index)) }, } } ================================================ FILE: libs/llrt_json/src/stringify.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{collections::HashSet, mem, rc::Rc, slice}; use rquickjs::{ atom::PredefinedAtom, function::This, qjs, Ctx, Error, Exception, Function, Object, Result, Type, Value, }; use crate::escape::escape_json_string; const CIRCULAR_REF_DETECTION_DEPTH: usize = 20; struct StringifyContext<'a, 'js> { ctx: &'a Ctx<'js>, result: &'a mut String, value: &'a Value<'js>, depth: usize, indentation: Option<&'a str>, key: Option<&'a str>, index: Option, parent: Option<&'a Object<'js>>, ancestors: &'a mut Vec<(usize, Rc)>, replacer_fn: Option<&'a Function<'js>>, include_keys_replacer: Option<&'a HashSet>, itoa_buffer: &'a mut itoa::Buffer, ryu_buffer: &'a mut ryu::Buffer, } #[allow(dead_code)] pub fn json_stringify<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> Result> { json_stringify_replacer_space(ctx, value, None, None) } #[allow(dead_code)] pub fn json_stringify_replacer<'js>( ctx: &Ctx<'js>, value: Value<'js>, replacer: Option>, ) -> Result> { json_stringify_replacer_space(ctx, value, replacer, None) } pub fn json_stringify_replacer_space<'js>( ctx: &Ctx<'js>, value: Value<'js>, replacer: Option>, indentation: Option, ) -> Result> { let mut result = String::with_capacity(128); let mut replacer_fn = None; let mut include_keys_replacer = None; let tmp_function; let mut itoa_buffer = itoa::Buffer::new(); let mut ryu_buffer = ryu::Buffer::new(); if let Some(replacer) = replacer { if let Some(function) = replacer.as_function() { tmp_function = function.clone(); replacer_fn = Some(&tmp_function); } else if let Some(array) = replacer.as_array() { let mut filter = HashSet::with_capacity(array.len()); for value in array.clone().into_iter() { let value = value?; if let Some(string) = value.as_string() { filter.insert(string.to_string()?); } else if let Some(number) = value.as_int() { filter.insert(itoa_buffer.format(number).to_string()); } else if let Some(number) = value.as_float() { filter.insert(ryu_buffer.format(number).to_string()); } } include_keys_replacer = Some(filter); } } let indentation = indentation.as_deref(); let include_keys_replacer = include_keys_replacer.as_ref(); let mut ancestors = Vec::with_capacity(10); let mut context = StringifyContext { ctx, result: &mut result, value: &value, depth: 0, indentation: None, key: None, index: None, parent: None, ancestors: &mut ancestors, replacer_fn, include_keys_replacer, itoa_buffer: &mut itoa_buffer, ryu_buffer: &mut ryu_buffer, }; match write_primitive(&mut context, false)? { PrimitiveStatus::Written => { return Ok(Some(result)); }, PrimitiveStatus::Ignored => { return Ok(None); }, _ => {}, } context.depth += 1; context.indentation = indentation; iterate(&mut context, None)?; Ok(Some(result)) } #[inline(always)] #[cold] fn write_indentation(result: &mut String, indentation: Option<&str>, depth: usize) { if let Some(indentation) = indentation { result.push('\n'); result.push_str(&indentation.repeat(depth - 1)); } } #[inline(always)] #[cold] fn run_to_json<'js>( context: &mut StringifyContext<'_, 'js>, js_object: &Object<'js>, ) -> Result<()> { let to_json = js_object.get::<_, Function>(PredefinedAtom::ToJSON)?; let val: Value = to_json.call((This(js_object.clone()),))?; //only preserve indentation if we're returning nested data let indentation = context.indentation.and_then(|indentation| { matches!( val.type_of(), Type::Object | Type::Array | Type::Exception | Type::Proxy ) .then_some(indentation) }); append_value( &mut StringifyContext { ctx: context.ctx, result: context.result, value: &val, depth: context.depth, indentation, key: None, index: None, parent: Some(js_object), ancestors: context.ancestors, replacer_fn: context.replacer_fn, include_keys_replacer: context.include_keys_replacer, itoa_buffer: context.itoa_buffer, ryu_buffer: context.ryu_buffer, }, false, )?; Ok(()) } #[derive(PartialEq)] enum PrimitiveStatus<'js> { Written, Ignored, Iterate(Option>), } #[inline(always)] #[cold] fn run_replacer<'js>( context: &mut StringifyContext<'_, 'js>, replacer_fn: &Function<'js>, add_comma: bool, ) -> Result> { let key = context.key; let index = context.index; let value = context.value; let parent = if let Some(parent) = context.parent { parent.clone() } else { let parent = Object::new(context.ctx.clone())?; parent.set("", value.clone())?; parent }; let new_value: Value = replacer_fn.call(( This(parent), get_key_or_index(context.itoa_buffer, key, index), value, ))?; write_primitive2(context, add_comma, Some(new_value)) } fn write_primitive<'js>( context: &mut StringifyContext<'_, 'js>, add_comma: bool, ) -> Result> { if let Some(replacer_fn) = context.replacer_fn { return run_replacer(context, replacer_fn, add_comma); } write_primitive2(context, add_comma, None) } fn write_primitive2<'js>( context: &mut StringifyContext<'_, 'js>, add_comma: bool, new_value: Option>, ) -> Result> { let key = context.key; let index = context.index; let include_keys_replacer = context.include_keys_replacer; let indentation = context.indentation; let depth = context.depth; let value = new_value.as_ref().unwrap_or(context.value); let type_of = value.type_of(); if context.index.is_none() && matches!( type_of, Type::Symbol | Type::Undefined | Type::Function | Type::Constructor ) { return Ok(PrimitiveStatus::Ignored); } if matches!(type_of, Type::BigInt) { return Err(Exception::throw_type( context.ctx, "Do not know how to serialize a BigInt", )); } if let Some(include_keys_replacer) = include_keys_replacer { let key = get_key_or_index(context.itoa_buffer, key, index); if !include_keys_replacer.contains(key) { return Ok(PrimitiveStatus::Ignored); } }; if let Some(indentation) = indentation { write_indented_separator(context.result, key, add_comma, indentation, depth); } else { write_sep(context.result, add_comma, false); if let Some(key) = key { write_key(context.result, key, false); } } match type_of { Type::Null | Type::Undefined => context.result.push_str("null"), Type::Bool => { let bool_str = if unsafe { value.as_bool().unwrap_unchecked() } { "true" } else { "false" }; context.result.push_str(bool_str); }, Type::Int => context.result.push_str( context .itoa_buffer .format(unsafe { value.as_int().unwrap_unchecked() }), ), Type::Float => { let float_value = unsafe { value.as_float().unwrap_unchecked() }; const EXP_MASK: u64 = 0x7ff0000000000000; let bits = float_value.to_bits(); if bits & EXP_MASK == EXP_MASK { context.result.push_str("null"); } else { let str = context.ryu_buffer.format_finite(float_value); let bytes = str.as_bytes(); let len = bytes.len(); context.result.push_str(str); if &bytes[len - 2..] == b".0" { let len = context.result.len(); unsafe { context.result.as_mut_vec().set_len(len - 2) } } } }, Type::String => { let js_string = unsafe { value.as_string().unwrap_unchecked() }; let mut len = mem::MaybeUninit::uninit(); let ctx_ptr = js_string.ctx().as_raw().as_ptr(); let ptr = unsafe { qjs::JS_ToCStringLen(ctx_ptr, len.as_mut_ptr(), js_string.as_raw()) }; if ptr.is_null() { return Err(Error::Unknown); } let len = unsafe { len.assume_init() }; let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr as _, len as _) }; let raw_string = unsafe { str::from_utf8_unchecked(bytes) }; write_string(context.result, raw_string); unsafe { qjs::JS_FreeCString(js_string.ctx().as_raw().as_ptr(), ptr) }; }, _ => return Ok(PrimitiveStatus::Iterate(new_value)), } Ok(PrimitiveStatus::Written) } #[inline(always)] #[cold] fn write_indented_separator( result: &mut String, key: Option<&str>, add_comma: bool, indentation: &str, depth: usize, ) { write_sep(result, add_comma, true); result.push_str(&indentation.repeat(depth)); if let Some(key) = key { write_key(result, key, true); } } #[cold] fn detect_circular_reference( ctx: &Ctx<'_>, value: &Object<'_>, key: Option<&str>, index: Option, parent: Option<&Object<'_>>, ancestors: &mut Vec<(usize, Rc)>, itoa_buffer: &mut itoa::Buffer, ) -> Result<()> { let parent_ptr = unsafe { qjs::JS_VALUE_GET_PTR(parent.unwrap_unchecked().as_raw()) as usize }; let current_ptr = unsafe { qjs::JS_VALUE_GET_PTR(value.as_raw()) as usize }; while !ancestors.is_empty() && match ancestors.last() { Some((ptr, _)) => ptr != &parent_ptr, _ => false, } { ancestors.pop(); } if ancestors.iter().any(|(ptr, _)| ptr == ¤t_ptr) { let mut iter = ancestors.iter_mut(); let first = &unsafe { iter.next().unwrap_unchecked() }.1; let mut message = iter.rev().take(4).rev().fold( String::from("Circular reference detected at: \".."), |mut acc, (_, key)| { if !key.starts_with('[') { acc.push('.'); } acc.push_str(key); acc }, ); if !first.starts_with('[') { message.push('.'); } message.push_str(first); message.push('"'); return Err(Exception::throw_type(ctx, &message)); } ancestors.push(( current_ptr, key.map(|k| k.into()).unwrap_or_else(|| { ["[", itoa_buffer.format(index.unwrap_or_default()), "]"] .concat() .into() }), )); Ok(()) } #[inline(always)] fn append_value(context: &mut StringifyContext<'_, '_>, add_comma: bool) -> Result { match write_primitive(context, add_comma)? { PrimitiveStatus::Written => Ok(true), PrimitiveStatus::Ignored => Ok(false), PrimitiveStatus::Iterate(new_value) => { context.depth += 1; iterate(context, new_value)?; Ok(true) }, } } #[inline(always)] fn write_key(string: &mut String, key: &str, indent: bool) { string.push('"'); escape_json_string(string, key.as_bytes()); string.push_str("\":"); if indent { string.push(' '); } } #[inline(always)] fn write_sep(result: &mut String, add_comma: bool, has_indentation: bool) { if add_comma { result.push(','); } if has_indentation { result.push('\n'); } } #[inline(always)] fn write_string(string: &mut String, value: &str) { string.push('"'); escape_json_string(string, value.as_bytes()); string.push('"'); } #[inline(always)] fn get_key_or_index<'a>( itoa_buffer: &'a mut itoa::Buffer, key: Option<&'a str>, index: Option, ) -> &'a str { key.unwrap_or_else(|| itoa_buffer.format(index.unwrap_or_default())) } fn iterate<'js>( context: &mut StringifyContext<'_, 'js>, new_value: Option>, ) -> Result<()> { let mut add_comma; let mut value_written; let elem = new_value.as_ref().unwrap_or(context.value); let depth = context.depth; let ctx = context.ctx; let indentation = context.indentation; match elem.type_of() { Type::Object | Type::Exception | Type::Proxy => { let js_object = unsafe { elem.as_object().unwrap_unchecked() }; if js_object.contains_key(PredefinedAtom::ToJSON)? { return run_to_json(context, js_object); } //only start detect circular reference at this level if depth > CIRCULAR_REF_DETECTION_DEPTH { detect_circular_reference( ctx, js_object, context.key, context.index, context.parent, context.ancestors, context.itoa_buffer, )?; } context.result.push('{'); value_written = false; // Collect keys: js_object.keys() uses JS_GetOwnPropertyNames which can fail for // Proxy objects. Fall back to Object.keys() in that case. let keys: Vec = { let collected: Vec = js_object.keys::().flatten().collect(); if collected.is_empty() { // Clear any pending exception and try Object.keys() for Proxy support ctx.catch(); ctx.globals() .get::<_, Object>("Object") .ok() .and_then(|o| o.get::<_, Function>("keys").ok()) .and_then(|f| f.call::<_, Vec>((js_object.clone(),)).ok()) .unwrap_or_default() } else { collected } }; for key in keys { let val = js_object.get(&key)?; add_comma = append_value( &mut StringifyContext { ctx, result: context.result, value: &val, depth, key: Some(&key), indentation, index: None, parent: Some(js_object), ancestors: context.ancestors, replacer_fn: context.replacer_fn, include_keys_replacer: context.include_keys_replacer, itoa_buffer: context.itoa_buffer, ryu_buffer: context.ryu_buffer, }, value_written, )?; value_written = value_written || add_comma; } if value_written { write_indentation(context.result, indentation, depth); } context.result.push('}'); }, Type::Array => { context.result.push('['); add_comma = false; value_written = false; let js_array = unsafe { elem.as_array().unwrap_unchecked() }; //only start detect circular reference at this level if depth > CIRCULAR_REF_DETECTION_DEPTH { detect_circular_reference( ctx, js_array.as_object(), context.key, context.index, context.parent, context.ancestors, context.itoa_buffer, )?; } for (i, val) in js_array.iter::().enumerate() { let val = val?; add_comma = append_value( &mut StringifyContext { ctx, result: context.result, value: &val, depth, key: None, indentation, index: Some(i), parent: Some(js_array), ancestors: context.ancestors, replacer_fn: context.replacer_fn, include_keys_replacer: context.include_keys_replacer, itoa_buffer: context.itoa_buffer, ryu_buffer: context.ryu_buffer, }, add_comma, )?; value_written = value_written || add_comma; } if value_written { write_indentation(context.result, indentation, depth); } context.result.push(']'); }, _ => {}, } Ok(()) } ================================================ FILE: libs/llrt_logging/Cargo.toml ================================================ [package] name = "llrt_logging" description = "LLRT logging helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_logging" path = "src/lib.rs" [dependencies] itoa = { version = "1", default-features = false } llrt_context = { version = "0.8.1-beta", path = "../llrt_context" } llrt_encoding = { version = "0.8.1-beta", path = "../llrt_encoding" } llrt_json = { version = "0.8.1-beta", path = "../llrt_json" } llrt_numbers = { version = "0.8.1-beta", path = "../llrt_numbers" } llrt_utils = { version = "0.8.1-beta", path = "../llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } ryu = { version = "1", default-features = false } ================================================ FILE: libs/llrt_logging/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{ collections::HashSet, io::{stderr, stdout, IsTerminal, Write}, mem, ops::Deref, process::exit, slice, string::String, }; use llrt_json::stringify::json_stringify; use llrt_numbers::float_to_string; use llrt_utils::{ class::get_class_name, error::ErrorExtensions, hash, primordials::{BasePrimordials, Primordial}, }; use rquickjs::{ atom::PredefinedAtom, function::This, object::Filter, prelude::Rest, promise::PromiseState, qjs, CaughtError, Coerced, Ctx, Error::{self}, Function, Object, Result, Symbol, Type, Value, }; pub const NEWLINE: char = '\n'; pub const CARRIAGE_RETURN: char = '\r'; const SPACING: char = ' '; const CIRCULAR: &str = "[Circular]"; pub const TIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.3fZ"; const MAX_INDENTATION_LEVEL: usize = 4; const MAX_EXPANSION_DEPTH: usize = 4; const INDENTATION_LOOKUP: [&str; MAX_INDENTATION_LEVEL + 1] = ["", " ", " ", " ", " "]; macro_rules! ascii_colors { ( $( $name:ident => $value:expr ),* ) => { #[derive(Debug, Clone, Copy)] pub enum Color { $( $name, )* } impl AsRef for Color { fn as_ref(&self) -> &str { match self { $( Color::$name => concat!("\x1b[", stringify!($value), "m"), )* } } } } } impl Color { #[inline(always)] fn push(self, value: &mut String) { value.push_str(self.as_ref()) } #[inline(always)] fn reset(value: &mut String) { value.push_str(Color::RESET.as_ref()) } } // Define the colors ascii_colors!( RESET => 0, BOLD => 1, BLACK => 30, RED => 31, GREEN => 32, YELLOW => 33, BLUE => 34, MAGENTA => 35, CYAN => 36, WHITE => 37 ); #[derive(Clone)] pub enum LogLevel { Trace = 0, Debug = 1, Info = 2, Warn = 4, Error = 8, Fatal = 16, } trait PushByte { fn push_byte(&mut self, byte: u8); } impl PushByte for String { fn push_byte(&mut self, byte: u8) { unsafe { self.as_mut_vec() }.push(byte); } } impl LogLevel { #[allow(clippy::inherent_to_string)] #[allow(dead_code)] pub fn to_string(&self) -> String { match self { LogLevel::Trace => String::from("TRACE"), LogLevel::Debug => String::from("DEBUG"), LogLevel::Info => String::from("INFO"), LogLevel::Warn => String::from("WARN"), LogLevel::Error => String::from("ERROR"), LogLevel::Fatal => String::from("FATAL"), } } #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Self { match s { "TRACE" => LogLevel::Trace, "DEBUG" => LogLevel::Debug, "INFO" => LogLevel::Info, "WARN" => LogLevel::Warn, "ERROR" => LogLevel::Error, "FATAL" => LogLevel::Fatal, _ => LogLevel::Info, } } } pub struct FormatOptions<'js> { color: bool, newline: bool, get_own_property_desc_fn: Function<'js>, object_prototype: Object<'js>, number_function: Function<'js>, parse_float: Function<'js>, parse_int: Function<'js>, object_filter: Filter, custom_inspect_symbol: Symbol<'js>, } impl<'js> FormatOptions<'js> { pub fn new(ctx: &Ctx<'js>, color: bool, newline: bool) -> Result { let primordials = BasePrimordials::get(ctx)?; let get_own_property_desc_fn = primordials.function_get_own_property_descriptor.clone(); let object_prototype = primordials.prototype_object.clone(); let parse_float = primordials.function_parse_float.clone(); let parse_int = primordials.function_parse_int.clone(); let object_filter = Filter::new().private().string().symbol(); let custom_inspect_symbol = primordials.symbol_custom_inspect.clone(); let number_function = primordials.constructor_number.deref().clone(); let options = FormatOptions { color, newline, object_filter, get_own_property_desc_fn, object_prototype, number_function, parse_float, parse_int, custom_inspect_symbol, }; Ok(options) } } pub fn format_plain<'js>(ctx: Ctx<'js>, newline: bool, args: Rest>) -> Result { format_values(&ctx, args, false, newline) } pub fn format<'js>(ctx: &Ctx<'js>, newline: bool, args: Rest>) -> Result { format_values(ctx, args, stdout().is_terminal(), newline) } pub fn format_values<'js>( ctx: &Ctx<'js>, args: Rest>, tty: bool, newline: bool, ) -> Result { let mut result = String::with_capacity(64); let mut options = FormatOptions::new(ctx, tty, newline)?; build_formatted_string(&mut result, ctx, args, &mut options)?; Ok(result) } pub fn build_formatted_string<'js>( result: &mut String, ctx: &Ctx<'js>, args: Rest>, options: &mut FormatOptions<'js>, ) -> Result<()> { let size = args.len(); let mut iter = args.0.into_iter().enumerate().peekable(); let current_filter = options.object_filter; let default_filter = Filter::default(); while let Some((index, arg)) = iter.next() { if index == 0 && size > 1 { if let Some(str) = arg.as_string() { let str = str.to_string()?; //fast check for format any strings if str.find('%').is_none() { format_raw_string(result, str, options); continue; } let bytes = str.as_bytes(); let mut i = 0; let len = bytes.len(); let mut next_byte; let mut byte; while i < len { byte = bytes[i]; if byte == b'%' && i + 1 < len { next_byte = bytes[i + 1]; i += 1; if iter.peek().is_some() { i += 1; let mut next_value = || unsafe { iter.next().unwrap_unchecked() }.1; let value = match next_byte { b's' => { let str = next_value().get::>()?; result.push_str(str.as_str()); continue; }, b'd' => options.number_function.call((next_value(),))?, b'i' => options.parse_int.call((next_value(),))?, b'f' => options.parse_float.call((next_value(),))?, b'j' => { result.push_str( &json_stringify(ctx, next_value())? .unwrap_or("undefined".into()), ); continue; }, b'O' => { options.object_filter = default_filter; next_value() }, b'o' => next_value(), b'c' => { // CSS is ignored continue; }, b'%' => { result.push_byte(byte); continue; }, _ => { result.push_byte(byte); result.push_byte(next_byte); continue; }, }; options.color = false; format_raw(result, value, options)?; options.object_filter = current_filter; continue; } result.push_byte(byte); result.push_byte(next_byte); } else { result.push_byte(byte); } i += 1; } continue; } } if index != 0 { result.push(SPACING); } format_raw(result, arg, options)?; } Ok(()) } #[inline(always)] fn format_raw<'js>( result: &mut String, value: Value<'js>, options: &FormatOptions<'js>, ) -> Result<()> { format_raw_inner(result, value, options, &mut HashSet::default(), 0)?; Ok(()) } fn format_raw_inner<'js>( result: &mut String, value: Value<'js>, options: &FormatOptions<'js>, visited: &mut HashSet, depth: usize, ) -> Result<()> { let value_type = value.type_of(); let color_enabled = options.color; let is_root = depth == 0; match value_type { Type::Uninitialized | Type::Null => { if color_enabled { Color::BOLD.push(result); } result.push_str("null") }, Type::Undefined => { if color_enabled { Color::BLACK.push(result); } result.push_str("undefined") }, Type::Bool => { if color_enabled { Color::YELLOW.push(result); } let bool_str = if unsafe { value.as_bool().unwrap_unchecked() } { "true" } else { "false" }; result.push_str(bool_str); }, Type::BigInt => { if color_enabled { Color::YELLOW.push(result); } let mut buffer = itoa::Buffer::new(); let big_int = unsafe { value.as_big_int().unwrap_unchecked() }; result.push_str(buffer.format(big_int.clone().to_i64().unwrap())); result.push('n'); }, Type::Int => { if color_enabled { Color::YELLOW.push(result); } let mut buffer = itoa::Buffer::new(); result.push_str(buffer.format(unsafe { value.as_int().unwrap_unchecked() })); }, Type::Float => { if color_enabled { Color::YELLOW.push(result); } result.push_str(&float_to_string(unsafe { value.as_float().unwrap_unchecked() })); }, Type::String => { //FIXME can be removed if https://github.com/DelSkayn/rquickjs/pull/447 is merged let lossy_string = get_lossy_string(value)?; format_raw_string_inner(result, lossy_string, !is_root, color_enabled); }, Type::Symbol => { if color_enabled { Color::YELLOW.push(result); } let description = unsafe { value.as_symbol().unwrap_unchecked() }.description()?; result.push_str("Symbol("); result.push_str(&unsafe { description.get::().unwrap_unchecked() }); result.push(')'); }, Type::Function | Type::Constructor => { if color_enabled { Color::CYAN.push(result); } let obj = unsafe { value.as_object().unwrap_unchecked() }; const ANONYMOUS: &str = "(anonymous)"; let mut name: String = obj .get(PredefinedAtom::Name) .unwrap_or(String::with_capacity(ANONYMOUS.len())); if name.is_empty() { name.push_str(ANONYMOUS); } let mut is_class = false; if obj.contains_key(PredefinedAtom::Prototype)? { let desc: Object = options .get_own_property_desc_fn .call((value, "prototype"))?; let writable: bool = desc.get(PredefinedAtom::Writable)?; is_class = !writable; } result.push_str(if is_class { "[class: " } else { "[function: " }); result.push_str(&name); result.push(']'); }, Type::Promise => { let promise = unsafe { value.as_promise().unwrap_unchecked() }; let state = promise.state(); result.push_str("Promise {"); let is_pending = matches!(state, PromiseState::Pending); let apply_indentation = depth < 2 && !is_pending; write_sep(result, false, apply_indentation, options.newline); if apply_indentation { push_indentation(result, depth + 1); } match state { PromiseState::Pending => { if color_enabled { Color::CYAN.push(result); } result.push_str(""); if color_enabled { Color::reset(result); } }, PromiseState::Resolved => { let value: Value = unsafe { promise.result().unwrap_unchecked() }?; format_raw_inner(result, value, options, visited, depth + 1)?; }, PromiseState::Rejected => { let value: Error = unsafe { promise.result::().unwrap_unchecked() }.unwrap_err(); let value = value.into_value(promise.ctx())?; if color_enabled { Color::RED.push(result); } result.push_str(" "); if color_enabled { Color::reset(result); } format_raw_inner(result, value, options, visited, depth + 1)?; }, } write_sep(result, false, apply_indentation, options.newline); if apply_indentation { push_indentation(result, depth); } result.push('}'); return Ok(()); }, Type::Array | Type::Object | Type::Exception | Type::Proxy => { let hash = hash::default_hash(&value); if visited.contains(&hash) { if color_enabled { Color::CYAN.push(result); } result.push_str(CIRCULAR); if color_enabled { Color::reset(result); } return Ok(()); } visited.insert(hash); let obj = unsafe { value.as_object().unwrap_unchecked() }; if value.is_error() { let name: String = obj.get(PredefinedAtom::Name)?; let message: String = obj.get(PredefinedAtom::Message)?; let stack: Result = obj.get(PredefinedAtom::Stack); result.push_str(&name); result.push_str(": "); result.push_str(&message); if color_enabled { Color::BLACK.push(result); } if let Ok(stack) = stack { for line in stack.trim().split('\n') { result.push(if options.newline { NEWLINE } else { CARRIAGE_RETURN }); push_indentation(result, depth + 1); result.push_str(line); } } if color_enabled { Color::reset(result); } return Ok(()); } let mut class_name: Option = None; let mut is_object = false; if value_type == Type::Object { is_object = true; class_name = get_class_name(&value)?; match class_name.as_deref() { Some("Date") => { if color_enabled { Color::MAGENTA.push(result); } let iso_fn: Function = obj.get("toISOString").unwrap(); let str: String = iso_fn.call((This(value),))?; result.push_str(&str); if color_enabled { Color::reset(result); } return Ok(()); }, Some("RegExp") => { if color_enabled { Color::RED.push(result); } let source: String = obj.get("source")?; let flags: String = obj.get("flags")?; result.push('/'); result.push_str(&source); result.push('/'); result.push_str(&flags); if color_enabled { Color::reset(result); } return Ok(()); }, None | Some("") | Some("Object") => { class_name = None; }, _ => {}, } } if depth < MAX_EXPANSION_DEPTH { let mut is_typed_array = false; if let Some(class_name) = class_name { result.push_str(&class_name); result.push(SPACING); //TODO fix when quickjs-ng exposes these types is_typed_array = matches!( class_name.as_str(), "Int8Array" | "Uint8Array" | "Uint8ClampedArray" | "Int16Array" | "Uint16Array" | "Int32Array" | "Uint32Array" | "Int64Array" | "Uint64Array" | "Float32Array" | "Float64Array" | "Buffer" ); } let is_array = is_typed_array || obj.is_array(); if let Ok(obj) = &obj.get::<_, Object>(options.custom_inspect_symbol.as_atom()) { return write_object( result, obj, options, visited, depth, color_enabled, is_array, ); } write_object( result, obj, options, visited, depth, color_enabled, is_array, )?; } else { if color_enabled { Color::CYAN.push(result); } result.push_str(if is_object { "[Object]" } else { "[Array]" }); } }, _ => {}, } if color_enabled { Color::reset(result); } Ok(()) } pub fn get_lossy_string(string_value: Value) -> Result { if !string_value.is_string() { return Err(Error::FromJs { from: "Value", to: "JSString", message: Some("Value is not a string".into()), }); } let mut len = mem::MaybeUninit::uninit(); let ctx_ptr = string_value.ctx().as_raw().as_ptr(); let ptr = unsafe { qjs::JS_ToCStringLen(ctx_ptr, len.as_mut_ptr(), string_value.as_raw()) }; if ptr.is_null() { // Might not ever happen but I am not 100% sure // so just incase check it. return Err(Error::Unknown); } let len = unsafe { len.assume_init() }; let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr as _, len as _) }; let string = replace_invalid_utf8_and_utf16(bytes); unsafe { qjs::JS_FreeCString(ctx_ptr, ptr) }; Ok(string) } fn format_raw_string(result: &mut String, value: String, options: &FormatOptions<'_>) { format_raw_string_inner(result, value, false, options.color); } fn format_raw_string_inner(result: &mut String, value: String, quoted: bool, color_enabled: bool) { if quoted { if color_enabled { Color::GREEN.push(result); } result.push('\''); } result.push_str(&value); if quoted { result.push('\''); } } fn write_object<'js>( result: &mut String, obj: &Object<'js>, options: &FormatOptions<'js>, visited: &mut HashSet, depth: usize, color_enabled: bool, is_array: bool, ) -> Result<()> { result.push(if is_array { '[' } else { '{' }); let mut keys = obj.keys(); let mut filter_functions = false; if !is_array && keys.len() == 0 { // Clear any pending exception from JS_GetOwnPropertyNames (e.g. on Proxy objects) let ctx = obj.ctx(); ctx.catch(); // Try Object.keys() which correctly handles Proxy objects let proxy_keys: Option> = ctx .globals() .get::<_, Object>("Object") .ok() .and_then(|o| o.get::<_, Function>("keys").ok()) .and_then(|f| f.call::<_, Vec>((obj.clone(),)).ok()); if let Some(pk) = proxy_keys { if !pk.is_empty() { let apply_indentation = depth < 2; let mut first = false; let length = pk.len(); for (i, key) in pk.iter().enumerate() { let value: Value = obj.get::<&str, _>(key.as_str())?; let numeric_key = key.parse::().is_ok(); write_sep(result, first, apply_indentation, options.newline); if apply_indentation { push_indentation(result, depth + 1); } if depth > MAX_INDENTATION_LEVEL - 1 { result.push(SPACING); } format_raw_string_inner( result, key.clone(), numeric_key, numeric_key & color_enabled, ); if numeric_key && color_enabled { Color::reset(result); } result.push(':'); result.push(SPACING); format_raw_inner(result, value, options, visited, depth + 1)?; first = true; if i > 99 { result.push_str("... "); let mut buffer = itoa::Buffer::new(); result.push_str(buffer.format(length - i)); result.push_str(" more items"); break; } } if first { if apply_indentation { result.push(if options.newline { NEWLINE } else { CARRIAGE_RETURN }); push_indentation(result, depth); } else { result.push(SPACING); } } result.push('}'); return Ok(()); } } if let Some(proto) = obj.get_prototype() { if proto != options.object_prototype { keys = proto.own_keys(options.object_filter); filter_functions = true; } } } let apply_indentation = !is_array && depth < 2; let mut first = false; let mut numeric_key; let length = keys.len(); for (i, key) in keys.flatten().enumerate() { let value: Value = obj.get::<&String, _>(&key)?; if !(value.is_function() && filter_functions) { numeric_key = key.parse::().is_ok(); write_sep(result, first, apply_indentation, options.newline); if apply_indentation { push_indentation(result, depth + 1); } if depth > MAX_INDENTATION_LEVEL - 1 { result.push(SPACING); } if !is_array { format_raw_string_inner(result, key, numeric_key, numeric_key & color_enabled); if numeric_key && color_enabled { Color::reset(result); } result.push(':'); result.push(SPACING); } format_raw_inner(result, value, options, visited, depth + 1)?; first = true; if i > 99 { result.push_str("... "); let mut buffer = itoa::Buffer::new(); result.push_str(buffer.format(length - i)); result.push_str(" more items"); break; } } } if first { if apply_indentation { result.push(if options.newline { NEWLINE } else { CARRIAGE_RETURN }); push_indentation(result, depth); } else { result.push(SPACING); } } result.push(if is_array { ']' } else { '}' }); Ok(()) } #[inline(always)] fn write_sep(result: &mut String, add_comma: bool, has_indentation: bool, newline: bool) { if add_comma { result.push(','); } if has_indentation { if newline { result.push('\n'); } else { result.push('\r') } } else { result.push(' '); } } #[inline(always)] fn push_indentation(result: &mut String, depth: usize) { result.push_str(INDENTATION_LOOKUP[depth]); } pub fn replace_newline_with_carriage_return(result: &mut str) { //OK since we just modify newlines let str_bytes = unsafe { result.as_bytes_mut() }; //modify \n inside of strings, stacks etc let mut pos = 0; while let Some(index) = str_bytes[pos..].iter().position(|b| *b == b'\n') { str_bytes[pos + index] = b'\r'; pos += index + 1; // Move the position after the found '\n' } } fn replace_invalid_utf8_and_utf16(bytes: &[u8]) -> String { let mut result = String::with_capacity(bytes.len()); let mut i = 0; while i < bytes.len() { let current = bytes[i]; match current { // ASCII (1-byte) 0x00..=0x7F => { result.push(current as char); i += 1; }, // 2-byte UTF-8 sequence 0xC0..=0xDF => { if i + 1 < bytes.len() { let next = bytes[i + 1]; if (next & 0xC0) == 0x80 { let code_point = ((current as u32 & 0x1F) << 6) | (next as u32 & 0x3F); if let Some(c) = char::from_u32(code_point) { result.push(c); } else { result.push('�'); } i += 2; } else { result.push('�'); i += 1; } } else { result.push('�'); i += 1; } }, // 3-byte UTF-8 sequence 0xE0..=0xEF => { if i + 2 < bytes.len() { let next1 = bytes[i + 1]; let next2 = bytes[i + 2]; if (next1 & 0xC0) == 0x80 && (next2 & 0xC0) == 0x80 { let code_point = ((current as u32 & 0x0F) << 12) | ((next1 as u32 & 0x3F) << 6) | (next2 as u32 & 0x3F); if let Some(c) = char::from_u32(code_point) { result.push(c); } else { result.push('�'); } i += 3; } else { result.push('�'); i += 1; } } else { result.push('�'); i += 1; } }, // 4-byte UTF-8 sequence 0xF0..=0xF7 => { if i + 3 < bytes.len() { let next1 = bytes[i + 1]; let next2 = bytes[i + 2]; let next3 = bytes[i + 3]; if (next1 & 0xC0) == 0x80 && (next2 & 0xC0) == 0x80 && (next3 & 0xC0) == 0x80 { let code_point = ((current as u32 & 0x07) << 18) | ((next1 as u32 & 0x3F) << 12) | ((next2 as u32 & 0x3F) << 6) | (next3 as u32 & 0x3F); if let Some(c) = char::from_u32(code_point) { result.push(c); } else { result.push('�'); } i += 4; } else { result.push('�'); i += 1; } } else { result.push('�'); i += 1; } }, // Invalid starting byte _ => { result.push('�'); i += 1; }, } } result } pub fn print_error_and_exit<'js>(ctx: &Ctx<'js>, err: CaughtError<'js>) -> ! { use std::fmt::Write; let mut error_str = String::new(); write!(error_str, "Error: {:?}", err).unwrap(); if let Ok(error) = err.into_value(ctx) { if print_error(ctx, Rest(vec![error.clone()])).is_err() { eprintln!("{}", error_str); }; if cfg!(test) { panic!("{:?}", error); } else { exit(1) } } else if cfg!(test) { panic!("{}", error_str); } else { eprintln!("{}", error_str); exit(1) }; } fn print_error<'js>(ctx: &Ctx<'js>, args: Rest>) -> Result<()> { let is_tty = stderr().is_terminal(); let mut result = String::new(); let mut options = FormatOptions::new(ctx, is_tty, true)?; build_formatted_string(&mut result, ctx, args, &mut options)?; result.push(NEWLINE); //we don't care if output is interrupted let _ = stderr().write_all(result.as_bytes()); Ok(()) } ================================================ FILE: libs/llrt_numbers/Cargo.toml ================================================ [package] name = "llrt_numbers" description = "LLRT numbers helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_numbers" path = "src/lib.rs" [dependencies] itoa = { version = "1", default-features = false } llrt_utils = { version = "0.8.1-beta", path = "../llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } ryu = { version = "1", default-features = false } [dev-dependencies] criterion = { version = "0.8", default-features = false } llrt_test = { version = "0.8.1-beta", path = "../llrt_test" } rand = { version = "0.10.0", features = ["alloc"], default-features = false } [[bench]] name = "numbers" harness = false ================================================ FILE: libs/llrt_numbers/benches/numbers.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use criterion::{criterion_group, criterion_main, Criterion}; use llrt_numbers::i64_to_base_n; use rand::RngExt; use std::{fmt::Write, hint::black_box}; macro_rules! write_formatted { ($format:expr, $number:expr) => {{ let digits = ($number as f64).log10() as usize + 2; let mut string = String::with_capacity(digits); write!(string, $format, $number).unwrap(); string }}; } fn criterion_benchmark(c: &mut Criterion) { let mut rng = rand::rng(); c.bench_function("to_binary", |b| { b.iter(|| { let num: i64 = rng.random(); i64_to_base_n(black_box(num), 2); }) }); c.bench_function("to_octal", |b| { b.iter(|| { let num: i64 = rng.random(); i64_to_base_n(black_box(num), 2); }) }); c.bench_function("to_dec", |b| { b.iter(|| { let num: i64 = rng.random(); i64_to_base_n(black_box(num), 10); }) }); c.bench_function("to_hex", |b| { b.iter(|| { let num: i64 = rng.random(); i64_to_base_n(black_box(num), 16); }) }); c.bench_function("write_formatted bin", |b| { b.iter(|| { let num: i64 = rng.random(); write_formatted!("{:b}", black_box(num)); }) }); c.bench_function("write_formatted octal", |b| { b.iter(|| { let num: i64 = rng.random(); write_formatted!("{:o}", black_box(num)); }) }); c.bench_function("write_formatted dec", |b| { b.iter(|| { let num: i64 = rng.random(); write_formatted!("{}", black_box(num)); }) }); c.bench_function("write_formatted hex", |b| { b.iter(|| { let num: i64 = rng.random(); write_formatted!("{:x}", black_box(num)); }) }); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: libs/llrt_numbers/src/lib.rs ================================================ use llrt_utils::object::ObjectExt; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ atom::PredefinedAtom, function::{Opt, This}, prelude::Func, Ctx, Exception, FromJs, Function, Object, Result, Value, }; const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz"; const BUF_SIZE: usize = 80; const BIN_MAX_DIGITS: usize = 64; const OCT_MAX_DIGITS: usize = 21; pub fn redefine_prototype(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); let number: Function = globals.get(PredefinedAtom::Number)?; let number_proto: Object = number.get(PredefinedAtom::Prototype)?; number_proto.set(PredefinedAtom::ToString, Func::from(number_to_string))?; Ok(()) } #[inline(always)] pub fn to_dec(number: i64) -> String { itoa::Buffer::new().format(number).into() } #[inline(always)] pub fn to_base_less_than_10(buf: &mut [u8], num: i64, base: i64) -> String { let max = buf.len(); if num == 0 { return "0".into(); } let mut index = max; let mut n = num; let mut string = String::with_capacity(max + 1); if n < 0 { n = !n + 1; string.push('-'); } while n > 0 { index -= 1; buf[index] = (n % base) as u8 + b'0'; n /= base; } string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[index..max]) }); string } pub fn i64_to_base_n(number: i64, radix: u8) -> String { match radix { 10 => { return to_dec(number); }, 2 => { let mut buf = [0u8; BIN_MAX_DIGITS]; return to_base_less_than_10(&mut buf, number, 2); }, 8 => { let mut buf = [0u8; OCT_MAX_DIGITS]; return to_base_less_than_10(&mut buf, number, 8); }, _ => {}, } let mut abs_number = number; let mut buf = [0u8; BUF_SIZE]; let mut string = String::with_capacity(BUF_SIZE); if number < 0 { abs_number = -number; string.push('-'); } let index = internal_i64_to_base_n(&mut buf, abs_number, radix); string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[index..BUF_SIZE]) }); string } #[inline(always)] fn internal_i64_to_base_n(buf: &mut [u8], number: i64, radix: u8) -> usize { if number == 0 { buf[BUF_SIZE - 1] = DIGITS[0]; return BUF_SIZE - 1; } let mut n = number; let mut index = BUF_SIZE; while n > 0 { index -= 1; let digit = n % radix as i64; buf[index] = DIGITS[digit as usize]; n /= radix as i64; } index } #[inline(always)] fn next_up(num: f64) -> f64 { const TINY_BITS: u64 = 0x1; const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff; let bits = num.to_bits(); if num.is_nan() || bits == f64::INFINITY.to_bits() { return num; } let abs = bits & CLEAR_SIGN_MASK; let next_bits = if abs == 0 { TINY_BITS } else if bits == abs { bits + 1 } else { bits - 1 }; f64::from_bits(next_bits) } #[inline(always)] fn fractional_to_base(buf: &mut [u8], mut index: usize, mut number: f64, radix: u8) -> usize { let mut is_odd = number <= 0x1fffffffffffffi64 as f64 && (number as i64) & 1 != 0; let mut digit; //let mut needs_rounding_up = false; let next_number = next_up(number); let mut delta_next_double = next_number - number; loop { let ntmp = number * radix as f64; let rtmp = delta_next_double * radix as f64; digit = ntmp as usize; let ritmp = rtmp as usize; if digit & 1 != 0 { is_odd = !is_odd; } number = ntmp - digit as f64; delta_next_double = rtmp - ritmp as f64; if number > 0.5f64 || number == 0.5f64 && if radix & 1 > 0 { is_odd } else { digit & 1 > 0 } { if number + delta_next_double > 1.0 { //TODO impl round up break; } } else if number < delta_next_double * 2.0 { break; } buf[index] = DIGITS[digit]; index += 1; } // let last_index = index; // while number > 0.0 { // let tmp = number * radix as f64; // let itmp = tmp as usize; // buf[index] = DIGITS[itmp]; // number = tmp - itmp as f64; // index += 1; // if index - last_index > BUF_SIZE - last_index - 1 { // break; // } // } index } #[inline(always)] fn f64_to_base_n(number: f64, radix: u8) -> String { const EXP_MASK: u64 = 0x7ff0000000000000; let bits = number.to_bits(); if bits & EXP_MASK == EXP_MASK { return get_nonfinite(bits).to_string(); } if radix == 10 { let mut result = ryu::Buffer::new().format_finite(number).to_string(); if result.ends_with(".0") { result.truncate(result.len() - 2); } return result; } let mut abs_num = number; let mut string = String::with_capacity(BUF_SIZE); let mut buf = [0u8; BUF_SIZE]; if number < 0.0 { abs_num = -number; string.push('-'); } let integer_part = abs_num.trunc(); let fractional_part = abs_num - integer_part; let integer_part = abs_num as i64; let index = internal_i64_to_base_n(&mut buf, integer_part, radix); string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[index..BUF_SIZE]) }); if fractional_part > 0.0 { buf.fill(0); let frac_end = fractional_to_base(&mut buf, 0, fractional_part, radix); if frac_end > 0 { string.push('.'); string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..frac_end]) }); } } string } pub fn float_to_string(float: f64) -> String { f64_to_base_n(float, 10) } #[inline(always)] #[cold] fn get_nonfinite<'a>(bits: u64) -> &'a str { const MANTISSA_MASK: u64 = 0x000fffffffffffff; const SIGN_MASK: u64 = 0x8000000000000000; if bits & MANTISSA_MASK != 0 { "NaN" } else if bits & SIGN_MASK != 0 { "-Infinity" } else { "Infinity" } } #[inline(always)] #[cold] fn check_radix(ctx: &Ctx, radix: u8) -> Result<()> { if !(2..=36).contains(&radix) { return Err(Exception::throw_type(ctx, "radix must be between 2 and 36")); } Ok(()) } fn number_to_string<'js>( ctx: Ctx<'js>, this: This>, radix: Opt, ) -> Result { let radix = radix .0 .and_then(|v| v.as_int()) .and_then(|r| u8::try_from(r).ok()) .unwrap_or(10); check_radix(&ctx, radix)?; // handle primitive number values if let Some(float) = this.as_float() { return Ok(f64_to_base_n(float, radix)); } if let Ok(int) = i64::from_js(&ctx, this.0.clone()) { return Ok(i64_to_base_n(int, radix)); } // handle boxed Number objects if let Some(obj) = this.as_object() { if let Some(value_of) = obj.get_optional::<_, Function>(PredefinedAtom::ValueOf)? { let primitive: Value = value_of.call((this,))?; if let Some(float) = primitive.as_float() { return Ok(f64_to_base_n(float, radix)); } if let Ok(int) = i64::from_js(&ctx, primitive) { return Ok(i64_to_base_n(int, radix)); } } } Ok("".into()) } #[cfg(test)] mod test { use rand::RngExt; use crate::{float_to_string, i64_to_base_n}; #[test] fn test_base_conversions() { let mut rng = rand::rng(); for _ in 0..1_000_000 { // Generate random i64 and radix values let num: i64 = rng.random_range(i64::MIN + 1..i64::MAX - 1); let minus_str = if num < 0 { "-" } else { "" }; //test bin let expected_bin = format!("{}{:b}", minus_str, num.abs()); let actual_bin = i64_to_base_n(num, 2); assert_eq!(expected_bin, actual_bin); //test octal let expected_octal = format!("{}{:o}", minus_str, num.abs()); let actual_octal = i64_to_base_n(num, 8); assert_eq!(expected_octal, actual_octal); //test hex let expected_hex = format!("{}{:x}", minus_str, num.abs()); let actual_hex = i64_to_base_n(num, 16); assert_eq!(expected_hex, actual_hex); } // Test i64_to_base_n let base_36 = i64_to_base_n(0, 36); assert_eq!("0", base_36); let base_36 = i64_to_base_n(123456789, 36); assert_eq!("21i3v9", base_36); let base_36 = i64_to_base_n(-123456789, 36); assert_eq!("-21i3v9", base_36); let float = float_to_string(123.456); assert_eq!("123.456", float); let float = float_to_string(123.); assert_eq!("123", float); let float = float_to_string(0.0); assert_eq!("0", float); } } ================================================ FILE: libs/llrt_test/Cargo.toml ================================================ [package] name = "llrt_test" description = "LLRT test helpers" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_test" path = "src/lib.rs" [dependencies] rquickjs = { version = "0.11", features = [ "futures", "loader", "parallel", ], default-features = false } tokio = { version = "1", features = ["fs"], default-features = false } uuid = { version = "1", features = ["v4"], default-features = false } ================================================ FILE: libs/llrt_test/src/lib.rs ================================================ use std::{ fs, path::{Path, PathBuf}, }; use rquickjs::{ async_with, function::IntoArgs, loader::{BuiltinLoader, Resolver}, markers::ParallelSend, module::{Evaluated, ModuleDef}, promise::MaybePromise, AsyncContext, AsyncRuntime, CatchResultExt, CaughtError, Ctx, FromJs, Function, Module, Result, }; pub async fn given_file(content: &str) -> PathBuf { let tmp_dir = std::env::temp_dir(); let path = tmp_dir.join(uuid::Uuid::new_v4().to_string()); tokio::fs::write(&path, content).await.unwrap(); path } struct TestResolver; impl Resolver for TestResolver { fn resolve(&mut self, _ctx: &Ctx<'_>, base: &str, name: &str) -> Result { if !name.starts_with(".") { return Ok(name.into()); } let base = Path::new(base); let combined_path = base.join(name); Ok(fs::canonicalize(combined_path) .unwrap() .to_string_lossy() .to_string()) } } pub async fn given_runtime() -> (AsyncRuntime, AsyncContext) { let rt = AsyncRuntime::new().unwrap(); rt.set_loader((TestResolver,), (BuiltinLoader::default(),)) .await; let ctx = AsyncContext::full(&rt).await.unwrap(); (rt, ctx) } pub async fn test_async_with(func: F) where F: for<'js> FnOnce(Ctx<'js>) -> std::pin::Pin + 'js>> + Send, { test_async_with_opts(func, TestOptions::default()).await; } #[derive(Default)] pub struct TestOptions { no_pending_jobs: bool, } impl TestOptions { pub fn new() -> Self { Self::default() } pub fn no_pending_jobs(mut self) -> Self { self.no_pending_jobs = true; self } } pub async fn test_async_with_opts(func: F, options: TestOptions) where F: for<'js> FnOnce(Ctx<'js>) -> std::pin::Pin + 'js>> + Send, { let (rt, ctx) = given_runtime().await; async_with!(ctx => |ctx| { func(ctx).await }) .await; if options.no_pending_jobs { assert!(!rt.is_job_pending().await); } } pub async fn test_sync_with(func: F) where F: for<'js> FnOnce(Ctx<'js>) -> Result<()> + ParallelSend, { let (_rt, ctx) = given_runtime().await; ctx.with(|ctx| func(ctx.clone()).catch(&ctx).unwrap()).await; } pub async fn call_test<'js, T, A>(ctx: &Ctx<'js>, module: &Module<'js, Evaluated>, args: A) -> T where T: FromJs<'js>, A: IntoArgs<'js>, { call_test_err(ctx, module, args).await.unwrap() } pub async fn call_test_err<'js, T, A>( ctx: &Ctx<'js>, module: &Module<'js, Evaluated>, args: A, ) -> std::result::Result> where T: FromJs<'js>, A: IntoArgs<'js>, { module .get::<_, Function>("test") .catch(ctx)? .call::<_, MaybePromise>(args) .catch(ctx)? .into_future::() .await .catch(ctx) } pub struct ModuleEvaluator; impl ModuleEvaluator { pub async fn eval_js<'js>( ctx: Ctx<'js>, name: &str, source: &str, ) -> Result> { let (module, module_eval) = Module::declare(ctx, name, source)?.eval()?; module_eval.into_future::<()>().await?; Ok(module) } pub async fn eval_rust<'js, M>(ctx: Ctx<'js>, name: &str) -> Result> where M: ModuleDef, { let (module, module_eval) = Module::evaluate_def::(ctx, name)?; module_eval.into_future::<()>().await?; Ok(module) } } ================================================ FILE: libs/llrt_test_tls/Cargo.toml ================================================ [package] name = "llrt_test_tls" description = "LLRT test helpers for TLS" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_test_tls" path = "src/lib.rs" [features] default = ["tls-ring"] tls-ring = ["rustls/ring"] tls-aws-lc = ["rustls/aws_lc_rs"] tls-graviola = ["dep:rustls-graviola"] tls-openssl = ["dep:openssl", "dep:tokio-openssl"] [dependencies] http-body-util = { version = "0.1", default-features = false } hyper = { version = "1", features = ["server"], default-features = false } hyper-util = { version = "0.1", features = [ "server-auto", "tokio", ], default-features = false } http = { version = "1", default-features = false } rustls = { version = "0.23", features = ["tls12"], default-features = false } rustls-graviola = { version = "0.3", git = "https://github.com/ctz/graviola.git", default-features = false, optional = true } openssl = { version = "0.10", optional = true } tokio-openssl = { version = "0.6", optional = true } tokio = { version = "1", features = [ "net", "fs", "rt", "macros", ], default-features = false } tokio-rustls = { version = "0.26", default-features = false } ================================================ FILE: libs/llrt_test_tls/data/generate.sh ================================================ #! /bin/bash # Generate the Root CA private key and certificate openssl req -x509 -newkey rsa:4096 -keyout root.key -out root.pem -days 3650 -nodes -subj "/CN=Test Root CA" # Generate the server private key openssl genrsa -out server.key 2048 # Generate a certificate signing request (CSR) for the server openssl req -new -key server.key -out server.csr -subj "/CN=localhost" # Create a configuration file for certificate extensions cat > extensions.cnf << EOF subjectAltName = IP:127.0.0.1, DNS:localhost EOF # Sign the server certificate with the Root CA, including the SAN extension openssl x509 -req -in server.csr -CA root.pem -CAkey root.key -CAcreateserial -out server.pem -days 3650 -sha256 -extfile extensions.cnf # Clean up temporary files rm server.csr root.key root.srl extensions.cnf ================================================ FILE: libs/llrt_test_tls/data/root.pem ================================================ -----BEGIN CERTIFICATE----- MIIFDzCCAvegAwIBAgIUOBYUfL20Vr/EXJltiNp3uq99ookwDQYJKoZIhvcNAQEL BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI1MTAyMjIzMDk0MloXDTM1 MTAyMDIzMDk0MlowFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMIICIjANBgkqhkiG 9w0BAQEFAAOCAg8AMIICCgKCAgEAtWeymY9maWNFNZNh4b2zgh8njhz3+pxrcCAS 7NeAz/dQnPC2HlGb5BVr7qbFoGBH7tj2pzqQryn91OKrM/Tz0Nc+paUSUn+tZ1BC Yi8wS2H0czxekLTRB74++faGaC/jNWRsm5JosXuLAKl3eRvYVZWdOxctrebZe5sQ aj0ohtnzPoiPvc7wagIeKq9bXHR9HwTHaV98mU9Uv/ApOlqU2Hsi6dXfMaLb8+lq SQm4eBwFl0qHGpALNR8Q6DNCSOK+Lwc9Ras78XCczvH6VQRz1PoTizpjWNTus9GO FYw8LvRAdYkrRWPliZZvQZ4faQYd71EiLQ/AP+YP8XlKpmE/OYYd5Gmr0aOnyzXw P9RR7y8M/NJ6M58TlgG6GQZg5aLgciDMPe+emwioTzbAqpsyvONoDoixKKGbf3S5 WUOvx/vtD827ghMuElKlp6AmKjbxZvlwMYtgzD+SYpNxKX69a73JyjNX+cEpwPIc FkJlriLH3Ay4+hqQubrWSFicFYCFSzUsdxFjeJw09CAD311PXqQ5qYPfNj35+78j UgO6K0ndkcoNe/GR22TGt6ji+5HkqdlbL347K1cgFLiyHrtiv83Fht3oW/gGKuKn 7zyxXbxs0ZsqBu+lsiev7nIJ48BiFkUiixevfBbzogcMeWlmOEzLSm+K1h66bW97 SqbXl/ECAwEAAaNTMFEwHQYDVR0OBBYEFOLMOJB8sUEiTBowDMsUeX9F8Nj8MB8G A1UdIwQYMBaAFOLMOJB8sUEiTBowDMsUeX9F8Nj8MA8GA1UdEwEB/wQFMAMBAf8w DQYJKoZIhvcNAQELBQADggIBAE0SgC8r+VvVgQQcLjT1PtLrLGyRrhhb5ZUqMlPA fPTeQ9zRDy8wRmFYpNKyiMOU/y1+aaLOcYyObK2F67x6N7HTQrqSbTUQtXPD4Tt8 ioWxVwkeoLK9U8z6UAYXqIiWzJI33vRahezuhi3qLmPX9Z4WDAckLMBAvWBpy2dP oix/R0eGwBhVDVyBoWSu1GjEkT2asaVHaxBU+XiqD2Z2U63HbmBY/OeI34RB89il H5RDBgCxouOZnQ5nDXUSoRXh5/n2kA3Vnmdh6+E6rEaKpSldJrChd2PgG13Ay/Iv +SV55PsFWTz6M2vVzkhJ9oROuGU8+jwSK5kctsLUSUVUZK48WCskYrMSzLj2UGqY fJEl7P2f6u7RVJQP7Ylie83Dl3k02HYkUbPJ4K7dEVd5ssqGZqVczowOBMDpiQdt mYEgpCq007zc3Kdk1rfBK3Q5WoExqnARp5u6Wm5lVVPJfVqahLAdnDvrTZRfVuM2 ELwXmlm1EdUknSe1jfqg8FQFZcPr49S98wRrXrwPonx1z4OMblTWCSbq2dE5bS11 m33uSe7YGm0dXLZWD7jFLTNlqnheLdJ7RGK4MZClQT5Ajnu9Zx9ICC1MC3gIzz/u KJ5/8GHc+gueSZApK4LNavBZAA6Yjz6SGzEvA+xCU6Tvh6JS6mpjfq2eH6hh8WZp fltd -----END CERTIFICATE----- ================================================ FILE: libs/llrt_test_tls/data/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDRq3fEYZS2w2gu bl6yOqyZbOkRZ8m80/65p8LTjL3drCyL0F3BPwN0s3+Qaq/7Mi8cOrZSa7Zq/Lhy oQ9JufmcUSMOK0+CMZW74VhdIf0ltGCXNVtwib/sfFBbCVFul5elfIGeGjsTJbOz +OHNTXO7Dpe3vFRMGZh9/oeBngkpRJE6zWedB88Q2OuGbIhXVT0yeZyFpxI0tUjO eUgOJm5Qc8hdE2gRfasnuhFqb/ddrXZavKrUvBSoNTMigPCU+v5dw0jUWiO4FSkb /kxR2ea66NnvfChFV97ZIRpm0MB9qMTgveA+xheE6cvcwTTvt619sg9i+qOIGvd6 yAxheJVbAgMBAAECggEAGnGHQefSszgy6AQ4gj7/LLx9KppN6bhM/IpJepfaDF6e IaYOZQR3AZ1v3b1u434BTgyjmQKHt7eW5bIldPV1Iz9ibfONbAyn51G07M4QGNTG h9uNZESjRYaqNckXkZyh3he1W0TQpYoQP4cIp3V4vqNJBD3G3fAueNmEqpbNbfxC 1noFKDKp/QQhWNbq+KhASIlSBLTs6uFYO6Jl5BCmVNxJrubT+gpJtKFroGMYZXAs e2AYuuqUNPuijg8/qRCFdcd8wUpg+/Ky4q98l1ZkyZJ9E4SxlUU5LSzPjvnvM6JT IZ48sV6XdXDre3Kep/RpOeEc+BGDxXQUvAw5Dqa5UQKBgQDz5vc55hNN/8VD2Y5l 8PR/nqxI4WUiqQ29hfGkEPC2Necx6CdQbqGSyExTjlsKVyGNK9ojNnsMS1GsjK2Q CKm0YzOmSCwHpW8JHRN4GAyRNXO3v+7R1SyNTQijdtRCSlfbM2DtY9ABjBkxWkCR p7ZTJyES2mji3JAXEsQi6mnvywKBgQDcEdLwFsHj1Mkv0Gpd0U1ei8bt5bv+M4vS DiSzCu3h7mUM61AS9qWHvEtum6ci7naqnjI7tINu3PaebYGOt8QKNp4dBKhRLg23 ZBtuYSMbgvii87yRJZwe4Ujl0v8itOsFlbhOh4toX0oz02n6fxZMx2MjDqH0vDXG Xsly6GAesQKBgFUCOUTq1eunq9+cIi+RrgYVDcNRG+jatzlJSBGA/gTkALK6UYmN Ja99NG0i/sQ53i3QDPWc9YIxdkQHvC6pdkyzDrt0CDSaCntIsRJ4f1jVIoH22Yw1 GpQdN1eSASUhuEFkRPI4ibUgWV+EL2EU7U1KJBLoIQqBCY+hMM9imI3FAoGBAMfU z4v/vjQZpk5qnAtw4SZ3Gj4vnBNpzw8AlMaXqAa+KLggfOebXBfzHTPk11Ha45pu aALbGXXz42Vc3oYvzC3SBqUm84gzn3TlzBrgzbZPqWKenJ3YXvmTbFR00gQ78CV0 HJYCcs5lqMWCtfsmp6M0cosE4H+Q3+uvapGS/KUhAoGAAbj8iOwtpZHLr3Jy40RA wJ+WsSgLHaUNqmHt3kaHmKgXTHLISXBIKxPir4l6oxQOasfFIY3FyZqyxgvcMoGg KU4jee5HlnOE+F9+6Tuj40JsWOsol2QF4b89H5NYidWTSiXwAnEFod3vFkx9O5Ih Z89D8ERKGlAkt65/JwhaREM= -----END PRIVATE KEY----- ================================================ FILE: libs/llrt_test_tls/data/server.pem ================================================ -----BEGIN CERTIFICATE----- MIIEFzCCAf+gAwIBAgIUC+QvCSehYQJLL3qSNURTKQPBtJAwDQYJKoZIhvcNAQEL BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI1MTAyMjIzMDk0MloXDTM1 MTAyMDIzMDk0MlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B AQEFAAOCAQ8AMIIBCgKCAQEA0at3xGGUtsNoLm5esjqsmWzpEWfJvNP+uafC04y9 3awsi9BdwT8DdLN/kGqv+zIvHDq2Umu2avy4cqEPSbn5nFEjDitPgjGVu+FYXSH9 JbRglzVbcIm/7HxQWwlRbpeXpXyBnho7EyWzs/jhzU1zuw6Xt7xUTBmYff6HgZ4J KUSROs1nnQfPENjrhmyIV1U9MnmchacSNLVIznlIDiZuUHPIXRNoEX2rJ7oRam/3 Xa12Wryq1LwUqDUzIoDwlPr+XcNI1FojuBUpG/5MUdnmuujZ73woRVfe2SEaZtDA fajE4L3gPsYXhOnL3ME077etfbIPYvqjiBr3esgMYXiVWwIDAQABo14wXDAaBgNV HREEEzARhwR/AAABgglsb2NhbGhvc3QwHQYDVR0OBBYEFAFfcsd3+6bt4gEQRajq 6neUr521MB8GA1UdIwQYMBaAFOLMOJB8sUEiTBowDMsUeX9F8Nj8MA0GCSqGSIb3 DQEBCwUAA4ICAQAWD2+odwJKfjTVTIPEFDRH7pD9O57oHqVGP4kxDffEUM/q8cPH XbNUgSPMi9XD4RH5EHFjDgTPfk2W3SLVSz3avWimKiagfHBTsVSpU7Sc6ZmXjFmC 1+hqh9PqYKGpy01BoRlMCF/Ho4tIEMGZRp/gvJm0/+HdiQ8JvFsKjbxYKqtC0CYP Yjyq7XLIJRCJsnTOfy1CwVkLp87Whgutnk6eF1xuyYSU94hzuJGTNe5SRyb2THV4 8MPGM7BV1mt4lp3pyM5YXo0HzDkC+1rZvHAjzcXz5D7BOOdwnWfHRHZJu9dmfqCa Sd78lGSm+d2iCuX3HjjmMHyWxdtgc1KwMcLMp1DP2eXN4Zq+Hm0euEpAtLz7TEh4 zoEb3hRcFcZ5IAOOXbQQJ2KZEdQPN3iD8o7sEOkIk5IDksaEtmhPkZrdtR2RYvlK 78IXjfFDzNLNbtMln1FnXqpQ0NC58YeM8rmqfP7pbcN6BbJ6n8Iw0DZx06u7qDUh PevtQ0/2k/UMPnj48E4n5BaYK7CQY/XydLUBhJk8pXKl3mj80Se6Ker5nY+w3RRp 3VHJjGaruy2oW73JpME/NUFM8fUhJPEKEVUmMpSs1eX00NfvM7m0//hX9WQr51Rp ko83cvhMQaddRbUNNghRTumt7ISd21bpvG3QBGhNcQZ6hJBTUAFdX+wWGg== -----END CERTIFICATE----- ================================================ FILE: libs/llrt_test_tls/src/api.rs ================================================ use http::{Method, Request, Response, StatusCode}; use http_body_util::{BodyExt, Full}; use hyper::body::{Bytes, Incoming}; pub(crate) async fn echo(req: Request) -> Result>, hyper::Error> { let mut response = Response::new(Full::default()); match (req.method(), req.uri().path()) { // Help route. (&Method::GET, "/") => { *response.body_mut() = Full::from("Try POST /echo\n"); }, // Echo service route. (&Method::POST, "/echo") => { *response.body_mut() = Full::from(req.into_body().collect().await?.to_bytes()); }, // Catch-all 404. _ => { *response.status_mut() = StatusCode::NOT_FOUND; }, }; Ok(response) } ================================================ FILE: libs/llrt_test_tls/src/config.rs ================================================ use rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer}; use std::path::PathBuf; pub enum FileType { RootCert, ServerCert, ServerKey, } impl FileType { pub fn default_path(&self) -> PathBuf { let manifest_dir = env!("CARGO_MANIFEST_DIR"); let data_dir = PathBuf::from(manifest_dir).join("data"); match self { Self::RootCert => data_dir.join("root.pem"), Self::ServerCert => data_dir.join("server.pem"), Self::ServerKey => data_dir.join("server.key"), } } } pub struct MockServerCerts { pub root_cert: CertificateDer<'static>, pub server_cert: CertificateDer<'static>, pub server_key: PrivateKeyDer<'static>, } impl MockServerCerts { pub async fn load_default() -> Result> { let certfile = tokio::fs::read(FileType::RootCert.default_path()).await?; let root_cert = CertificateDer::from_pem_slice(&certfile)?; let certfile = tokio::fs::read(FileType::ServerCert.default_path()).await?; let server_cert = CertificateDer::from_pem_slice(&certfile)?; let keyfile = tokio::fs::read(FileType::ServerKey.default_path()).await?; let server_key = PrivateKeyDer::from_pem_slice(&keyfile)?; Ok(Self { root_cert, server_cert, server_key, }) } } ================================================ FILE: libs/llrt_test_tls/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // FIXME this library is only needed until TLS is natively supported in wiremock. // See https://github.com/LukeMathWalker/wiremock-rs/issues/58 #[cfg(all(feature = "tls-ring", feature = "tls-aws-lc"))] compile_error!("Features 'tls-ring' and 'tls-aws-lc' are mutually exclusive"); #[cfg(all(feature = "tls-ring", feature = "tls-graviola"))] compile_error!("Features 'tls-ring' and 'tls-graviola' are mutually exclusive"); #[cfg(all(feature = "tls-aws-lc", feature = "tls-graviola"))] compile_error!("Features 'tls-aws-lc' and 'tls-graviola' are mutually exclusive"); use std::net::{Ipv4Addr, SocketAddr}; use tokio::net::TcpListener; use self::config::{FileType, MockServerCerts}; mod api; mod config; mod server; pub struct MockServer { addr: SocketAddr, ca: String, shutdown_tx: tokio::sync::watch::Sender<()>, } impl MockServer { pub async fn start() -> Result> { let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(()); // Load the certificates for the mock server. let certs = MockServerCerts::load_default().await?; let ca = String::from_utf8(tokio::fs::read(FileType::RootCert.default_path()).await?)?; // Bind to a random port on localhost. let incoming = TcpListener::bind(&SocketAddr::from((Ipv4Addr::LOCALHOST, 0))).await?; let addr = incoming.local_addr()?; tokio::spawn(async move { if let Err(e) = server::run(incoming, certs, shutdown_rx).await { println!("failed to run mock server: {e:#}"); } }); Ok(Self { addr, ca, shutdown_tx, }) } pub fn address(&self) -> SocketAddr { self.addr } pub fn ca(&self) -> &str { &self.ca } } impl Drop for MockServer { fn drop(&mut self) { let _ = self.shutdown_tx.send(()); } } ================================================ FILE: libs/llrt_test_tls/src/server.rs ================================================ #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use std::sync::Arc; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] use hyper::service::service_fn; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] use hyper_util::rt::{TokioExecutor, TokioIo}; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] use hyper_util::server::conn::auto::Builder; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] use tokio::net::TcpListener; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] use crate::MockServerCerts; #[cfg(feature = "tls-ring")] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::ring::default_provider()) } #[cfg(feature = "tls-aws-lc")] fn get_crypto_provider() -> Arc { Arc::new(rustls::crypto::aws_lc_rs::default_provider()) } #[cfg(feature = "tls-graviola")] fn get_crypto_provider() -> Arc { Arc::new(rustls_graviola::default_provider()) } #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] pub(super) async fn run( listener: TcpListener, certs: MockServerCerts, shutdown_rx: tokio::sync::watch::Receiver<()>, ) -> Result<(), Box> { use rustls::ServerConfig; use tokio_rustls::TlsAcceptor; let cert_chain = vec![certs.server_cert, certs.root_cert]; let mut server_config = ServerConfig::builder_with_provider(get_crypto_provider()) .with_safe_default_protocol_versions()? .with_no_client_auth() .with_single_cert(cert_chain, certs.server_key)?; server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; let tls_acceptor = TlsAcceptor::from(Arc::new(server_config)); let service = service_fn(crate::api::echo); loop { let (tcp_stream, _remote_addr) = listener.accept().await?; let mut shutdown_signal = shutdown_rx.clone(); let tls_acceptor = tls_acceptor.clone(); tokio::spawn(async move { let tls_stream = match tls_acceptor.accept(tcp_stream).await { Ok(tls_stream) => tls_stream, Err(err) => { eprintln!("failed to perform tls handshake: {err:#}"); return; }, }; let http_server = Builder::new(TokioExecutor::new()); let conn = http_server.serve_connection(TokioIo::new(tls_stream), service); tokio::pin!(conn); loop { tokio::select! { _ = conn.as_mut() => break, _ = shutdown_signal.changed() => conn.as_mut().graceful_shutdown(), } } }); } } #[cfg(feature = "tls-openssl")] pub(super) async fn run( listener: TcpListener, certs: MockServerCerts, shutdown_rx: tokio::sync::watch::Receiver<()>, ) -> Result<(), Box> { use openssl::ssl::{SslAcceptor, SslMethod}; let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; // Convert rustls certs to OpenSSL format let cert_der = certs.server_cert.as_ref(); let key_der = match certs.server_key { rustls::pki_types::PrivateKeyDer::Pkcs1(ref key) => key.secret_pkcs1_der().to_vec(), rustls::pki_types::PrivateKeyDer::Pkcs8(ref key) => key.secret_pkcs8_der().to_vec(), rustls::pki_types::PrivateKeyDer::Sec1(ref key) => key.secret_sec1_der().to_vec(), _ => return Err("Unsupported key format".into()), }; let cert = openssl::x509::X509::from_der(cert_der)?; let pkey = openssl::pkey::PKey::private_key_from_der(&key_der)?; builder.set_certificate(&cert)?; builder.set_private_key(&pkey)?; let root_cert = openssl::x509::X509::from_der(certs.root_cert.as_ref())?; builder.add_extra_chain_cert(root_cert)?; builder.set_alpn_protos(b"\x02h2\x08http/1.1\x08http/1.0")?; let acceptor = builder.build(); let service = service_fn(crate::api::echo); loop { let (tcp_stream, _remote_addr) = listener.accept().await?; let mut shutdown_signal = shutdown_rx.clone(); let acceptor = acceptor.clone(); tokio::spawn(async move { let ssl = match openssl::ssl::Ssl::new(acceptor.context()) { Ok(ssl) => ssl, Err(err) => { eprintln!("failed to create ssl: {err:#}"); return; }, }; let tls_stream = match tokio_openssl::SslStream::new(ssl, tcp_stream) { Ok(mut stream) => { if let Err(err) = std::pin::Pin::new(&mut stream).accept().await { eprintln!("failed to perform tls handshake: {err:#}"); return; } stream }, Err(err) => { eprintln!("failed to create ssl stream: {err:#}"); return; }, }; let http_server = Builder::new(TokioExecutor::new()); let conn = http_server.serve_connection(TokioIo::new(tls_stream), service); tokio::pin!(conn); loop { tokio::select! { _ = conn.as_mut() => break, _ = shutdown_signal.changed() => conn.as_mut().graceful_shutdown(), } } }); } } ================================================ FILE: libs/llrt_utils/Cargo.toml ================================================ [package] name = "llrt_utils" description = "LLRT utilities" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [features] default = ["all"] all = ["fs", "bytearray-buffer"] fs = ["tokio/fs"] bytearray-buffer = ["tokio/sync"] [dependencies] rquickjs = { version = "0.11", features = ["macro"], default-features = false } tokio = { version = "1", features = ["sync"], default-features = false } tracing = { version = "0.1", default-features = false } [target.'cfg(unix)'.dependencies] libc = { version = "0.2", default-features = false } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.61", features = [ "Win32_Foundation", "Win32_System_Threading", ], default-features = false } [dev-dependencies] llrt_test = { version = "0.8.1-beta", path = "../llrt_test" } tokio = { version = "1", features = ["full"] } [build-dependencies] llrt_build = { version = "0.8.1-beta", path = "../llrt_build" } ================================================ FILE: libs/llrt_utils/src/any_of.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ class::{Trace, Tracer}, Ctx, FromJs, IntoJs, JsLifetime, Result, Value, }; macro_rules! define_any_of { ($name:ident, $($variant:ident),+) => { #[derive(Debug, Clone)] pub enum $name<$($variant),+> { $( $variant($variant), )+ } define_any_of_from_js!($name, $($variant),+); impl<'js, $($variant: IntoJs<'js>),+> IntoJs<'js> for $name<$($variant),+> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self { $( Self::$variant(val) => val.into_js(ctx), )+ } } } unsafe impl<'js, $($variant: JsLifetime<'js>),+> JsLifetime<'js> for $name<$($variant),+> { type Changed<'to> = $name<$($variant::Changed<'to>),+>; } impl<'js, $($variant: Trace<'js>),+> Trace<'js> for $name<$($variant),+> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { match self { $( Self::$variant(val) => val.trace(tracer), )+ } } } define_any_of_methods!($name, $($variant),+); }; } macro_rules! define_any_of_from_js { ($name:ident, $first:ident, $($rest:ident),+) => { impl<'js, $first: FromJs<'js>, $($rest: FromJs<'js>),+> FromJs<'js> for $name<$first, $($rest),+> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { define_any_of_from_js_impl!($name, ctx, value, $first, $($rest),+) } } }; } macro_rules! define_any_of_from_js_impl { ($name:ident, $ctx:ident, $value:ident, $first:ident) => { $first::from_js($ctx, $value).map($name::$first) }; ($name:ident, $ctx:ident, $value:ident, $first:ident, $($rest:ident),+) => { $first::from_js($ctx, $value.clone()).map($name::$first).or_else(|error| { if error.is_from_js() { define_any_of_from_js_impl!($name, $ctx, $value, $($rest),+) } else { Err(error) } }) }; } macro_rules! define_any_of_variant_methods { ($variant:ident, $is_fn:ident, $as_fn:ident, $as_mut_fn:ident, $into_fn:ident) => { #[allow(dead_code)] pub fn $is_fn(&self) -> bool { matches!(self, Self::$variant(_)) } #[allow(dead_code)] pub fn $as_fn(&self) -> Option<&$variant> { match self { Self::$variant(val) => Some(val), _ => None, } } #[allow(dead_code)] pub fn $as_mut_fn(&mut self) -> Option<&mut $variant> { match self { Self::$variant(val) => Some(val), _ => None, } } #[allow(dead_code)] pub fn $into_fn(self) -> std::result::Result<$variant, Self> { match self { Self::$variant(val) => Ok(val), other => Err(other), } } }; (A) => { define_any_of_variant_methods!(A, is_a, as_a, as_a_mut, into_a); }; (B) => { define_any_of_variant_methods!(B, is_b, as_b, as_b_mut, into_b); }; (C) => { define_any_of_variant_methods!(C, is_c, as_c, as_c_mut, into_c); }; (D) => { define_any_of_variant_methods!(D, is_d, as_d, as_d_mut, into_d); }; (E) => { define_any_of_variant_methods!(E, is_e, as_e, as_e_mut, into_e); }; (F) => { define_any_of_variant_methods!(F, is_f, as_f, as_f_mut, into_f); }; (G) => { define_any_of_variant_methods!(G, is_g, as_g, as_g_mut, into_g); }; (H) => { define_any_of_variant_methods!(H, is_h, as_h, as_h_mut, into_h); }; } macro_rules! define_any_of_methods { ($name:ident, $($variant:ident),+) => { impl<$($variant),+> $name<$($variant),+> { $( define_any_of_variant_methods!($variant); )+ } }; } define_any_of!(AnyOf2, A, B); define_any_of!(AnyOf3, A, B, C); define_any_of!(AnyOf4, A, B, C, D); define_any_of!(AnyOf5, A, B, C, D, E); define_any_of!(AnyOf6, A, B, C, D, E, F); define_any_of!(AnyOf7, A, B, C, D, E, F, G); define_any_of!(AnyOf8, A, B, C, D, E, F, G, H); #[cfg(test)] mod tests { use super::*; use rquickjs::{Context, Runtime}; #[test] fn test_any_of_string_number() { let rt = Runtime::new().unwrap(); let ctx = Context::full(&rt).unwrap(); ctx.with(|ctx| { // Test string conversion let val: Value = ctx.eval("'hello'").unwrap(); let any: AnyOf2 = AnyOf2::from_js(&ctx, val).unwrap(); assert!(any.is_a()); assert_eq!(any.as_a().unwrap(), "hello"); assert!(!any.is_b()); assert!(any.as_b().is_none()); // Test number conversion let val: Value = ctx.eval("42").unwrap(); let any: AnyOf2 = AnyOf2::from_js(&ctx, val).unwrap(); assert!(!any.is_a()); assert!(any.is_b()); assert_eq!(*any.as_b().unwrap(), 42); }); } #[test] fn test_any_of_fallback() { let rt = Runtime::new().unwrap(); let ctx = Context::full(&rt).unwrap(); ctx.with(|ctx| { // Test that it tries in order let val: Value = ctx.eval("true").unwrap(); let any: AnyOf3 = AnyOf3::from_js(&ctx, val).unwrap(); assert!(any.is_c()); assert!(*any.as_c().unwrap()); assert!(!any.is_a()); assert!(!any.is_b()); }); } #[test] fn test_any_of_into_js() { let rt = Runtime::new().unwrap(); let ctx = Context::full(&rt).unwrap(); ctx.with(|ctx| { let any: AnyOf2 = AnyOf2::A("test".to_string()); let val: Value = any.into_js(&ctx).unwrap(); let result: String = val.get().unwrap(); assert_eq!(result, "test"); let any: AnyOf2 = AnyOf2::B(99); let val: Value = any.into_js(&ctx).unwrap(); let result: i32 = val.get().unwrap(); assert_eq!(result, 99); }); } #[test] fn test_any_of_methods() { let rt = Runtime::new().unwrap(); let ctx = Context::full(&rt).unwrap(); ctx.with(|ctx| { // Test all methods for variant A let val: Value = ctx.eval("'test'").unwrap(); let any: AnyOf3 = AnyOf3::from_js(&ctx, val).unwrap(); assert!(any.is_a()); assert_eq!(any.as_a().unwrap(), "test"); assert_eq!(any.into_a().unwrap(), "test"); // Test all methods for variant B let val: Value = ctx.eval("42").unwrap(); let any: AnyOf3 = AnyOf3::from_js(&ctx, val).unwrap(); assert!(any.is_b()); assert_eq!(*any.as_b().unwrap(), 42); assert_eq!(any.into_b().unwrap(), 42); // Test all methods for variant C let val: Value = ctx.eval("true").unwrap(); let any: AnyOf3 = AnyOf3::from_js(&ctx, val).unwrap(); assert!(any.is_c()); assert!(*any.as_c().unwrap()); assert!(any.into_c().unwrap()); }); } #[test] fn test_any_of_mutable_methods() { let rt = Runtime::new().unwrap(); let ctx = Context::full(&rt).unwrap(); ctx.with(|ctx| { let val: Value = ctx.eval("42").unwrap(); let mut any: AnyOf4 = AnyOf4::from_js(&ctx, val).unwrap(); if let Some(n) = any.as_b_mut() { *n = 100; } assert_eq!(any.into_b().unwrap(), 100); }); } #[test] fn test_any_of_error_propagation() { use rquickjs::{Array, Object}; let rt = Runtime::new().unwrap(); let ctx = Context::full(&rt).unwrap(); ctx.with(|ctx| { // Test that conversion errors cause fallback to next type let val: Value = ctx.eval("42").unwrap(); let any: AnyOf2 = AnyOf2::from_js(&ctx, val).unwrap(); assert!(any.is_b()); // Test that all types fail results in an error let val: Value = ctx.eval("null").unwrap(); let result: Result> = AnyOf2::from_js(&ctx, val); assert!(result.is_err()); }); } #[test] fn test_any_of_conversion_order() { let rt = Runtime::new().unwrap(); let ctx = Context::full(&rt).unwrap(); ctx.with(|ctx| { // Test that conversion happens in order A, B, C, D, E // Since 42 can be converted to f64, i32, etc., but String comes first and fails, // it should try the next successful conversion let val: Value = ctx.eval("42").unwrap(); // String should fail, so it tries i32 which succeeds let any: AnyOf3 = AnyOf3::from_js(&ctx, val).unwrap(); assert!(any.is_b()); // If we flip the order, f64 would be tried first (but both work) let val: Value = ctx.eval("3.14").unwrap(); let any: AnyOf3 = AnyOf3::from_js(&ctx, val).unwrap(); assert!(any.is_b()); // f64 should succeed first }); } } ================================================ FILE: libs/llrt_utils/src/bytearray_buffer.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ cmp::min, collections::VecDeque, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, Mutex, }, }; use tokio::sync::{Notify, Semaphore}; #[derive(Clone)] pub struct BytearrayBuffer { inner: Arc>>, max_capacity: Arc, len: Arc, notify: Arc, closed: Arc, write_semaphore: Arc, } impl BytearrayBuffer { pub fn new(capacity: usize) -> Self { let queue = VecDeque::with_capacity(capacity); let capacity = queue.capacity(); Self { inner: Arc::new(Mutex::new(queue)), len: Arc::new(AtomicUsize::new(0)), max_capacity: Arc::new(AtomicUsize::new(capacity)), notify: Arc::new(Notify::new()), closed: Arc::new(AtomicBool::new(false)), write_semaphore: Arc::new(Semaphore::new(1)), } } pub fn len(&self) -> usize { self.len.load(Ordering::Relaxed) } pub fn is_empty(&self) -> bool { self.len() == 0 } #[allow(dead_code)] pub fn write_forced(&self, item: &[u8]) { let mut inner = self.inner.lock().unwrap(); inner.extend(item); let capacity = inner.capacity(); self.len.fetch_add(item.len(), Ordering::Relaxed); self.max_capacity.store(capacity, Ordering::Relaxed); } pub async fn write(&self, item: &mut [u8]) -> usize { let _ = self.write_semaphore.acquire().await.unwrap(); let mut slice_index = 0; loop { let max_capacity = self.max_capacity.load(Ordering::Relaxed); if self.closed.load(Ordering::Relaxed) { return max_capacity; } let len = self.len.load(Ordering::Relaxed); let available = max_capacity - len; if available > 0 { let end_index = min(item.len() - 1, slice_index + available - 1); let sub_slice = &item[slice_index..=end_index]; let slice_length = sub_slice.len(); slice_index += slice_length; self.inner.lock().unwrap().extend(sub_slice); self.len.fetch_add(slice_length, Ordering::Relaxed); if slice_index == item.len() { return max_capacity; } } self.notify.notified().await; } } #[allow(dead_code)] pub fn is_closed(&self) -> bool { self.closed.load(Ordering::Relaxed) } pub async fn close(&self) { self.closed.store(true, Ordering::Relaxed); self.notify.notify_one(); //wait for write to finish let _ = self.write_semaphore.acquire().await.unwrap(); } pub async fn clear(&self) { self.closed.store(false, Ordering::Relaxed); self.notify.notify_one(); //wait for write to finish let _ = self.write_semaphore.acquire().await.unwrap(); self.len.store(0, Ordering::Relaxed); self.inner.lock().unwrap().clear(); self.closed.store(false, Ordering::Relaxed); } pub fn read(&self, desired_size: Option) -> Option> { let mut inner = self.inner.lock().unwrap(); let done = self.closed.load(Ordering::Relaxed); let items = if done { Some(inner.drain(0..).collect()) } else if let Some(desired_len) = desired_size { let max_capacity = self.max_capacity.load(Ordering::Relaxed); if desired_len > max_capacity { let diff = desired_len - max_capacity; inner.reserve(diff - 1); let mut max_capacity = inner.capacity(); if desired_len > max_capacity { inner.reserve(desired_len - max_capacity); max_capacity = inner.capacity(); } drop(inner); self.max_capacity.store(max_capacity, Ordering::Relaxed); self.notify.notify_one(); return None; } let len = self.len.load(Ordering::Relaxed); if desired_len > len { self.notify.notify_one(); return None; } Some(inner.drain(0..desired_len).collect()) } else { Some(inner.drain(0..).collect()) }; self.len.store(inner.len(), Ordering::Relaxed); drop(inner); self.notify.notify_one(); items } } #[cfg(test)] mod tests { use super::BytearrayBuffer; #[tokio::test] async fn clear_while_writing() { let queue = BytearrayBuffer::new(8); let queue2 = queue.clone(); tokio::task::spawn(async move { let mut vec: Vec = (0..=255).collect(); queue.write(&mut vec).await; }); queue2.clear().await } #[tokio::test] async fn write_one_at_a_time() { let queue = BytearrayBuffer::new(8); let queue2 = queue.clone(); let queue3 = queue.clone(); tokio::task::spawn(async move { let mut vec: Vec = (0..=127).collect(); queue.write(&mut vec).await; }); tokio::task::spawn(async move { let mut vec: Vec = (128..=255).collect(); queue2.write(&mut vec).await; }); let mut data = Vec::::new(); loop { tokio::task::yield_now().await; if let Some(bytes) = queue3.read(Some(256)) { data.extend(bytes); break; } } //assert that data in vec is increment from 0 to 255 for i in 0..=255 { assert_eq!(data[i as usize], i); } } #[tokio::test] async fn queue() { let queue = BytearrayBuffer::new(8); let queue2 = queue.clone(); let write_task = tokio::task::spawn(async move { for _ in 0..=255 { let mut vec: Vec = (0..=255).collect(); queue.write(&mut vec).await; } queue.close().await; }); let mut data = Vec::::new(); loop { let done = queue2.is_closed(); tokio::task::yield_now().await; if let Some(bytes) = queue2.read(Some(9)) { data.extend(bytes); } if done { break; } } let _ = write_task.await; assert_eq!(data.len(), 256 * 256) } } ================================================ FILE: libs/llrt_utils/src/bytes.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::rc::Rc; use rquickjs::{ atom::PredefinedAtom, class::{Trace, Tracer}, function::Constructor, ArrayBuffer, Coerced, Ctx, Exception, FromJs, IntoJs, JsLifetime, Object, Result, TypedArray, Value, }; use crate::{error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED, result::ResultExt}; #[derive(Clone, PartialEq)] pub enum ObjectBytes<'js> { U8Array(TypedArray<'js, u8>), I8Array(TypedArray<'js, i8>), U16Array(TypedArray<'js, u16>), I16Array(TypedArray<'js, i16>), U32Array(TypedArray<'js, u32>), I32Array(TypedArray<'js, i32>), U64Array(TypedArray<'js, u64>), I64Array(TypedArray<'js, i64>), F32Array(TypedArray<'js, f32>), F64Array(TypedArray<'js, f64>), DataView(ArrayBuffer<'js>), Vec(Vec), } // Requires manual implementation because rquickjs hasn't implemented JsLifetime for f32 or f64 unsafe impl<'js> JsLifetime<'js> for ObjectBytes<'js> { type Changed<'to> = ObjectBytes<'to>; } impl<'js> Trace<'js> for ObjectBytes<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { match self { ObjectBytes::U8Array(a) => a.trace(tracer), ObjectBytes::I8Array(a) => a.trace(tracer), ObjectBytes::U16Array(a) => a.trace(tracer), ObjectBytes::I16Array(a) => a.trace(tracer), ObjectBytes::U32Array(a) => a.trace(tracer), ObjectBytes::I32Array(a) => a.trace(tracer), ObjectBytes::U64Array(a) => a.trace(tracer), ObjectBytes::I64Array(a) => a.trace(tracer), ObjectBytes::F32Array(a) => a.trace(tracer), ObjectBytes::F64Array(a) => a.trace(tracer), ObjectBytes::DataView(d) => d.trace(tracer), ObjectBytes::Vec(v) => v.trace(tracer), } } } impl<'js> IntoJs<'js> for ObjectBytes<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self { ObjectBytes::U8Array(a) => a.into_js(ctx), ObjectBytes::I8Array(a) => a.into_js(ctx), ObjectBytes::U16Array(a) => a.into_js(ctx), ObjectBytes::I16Array(a) => a.into_js(ctx), ObjectBytes::U32Array(a) => a.into_js(ctx), ObjectBytes::I32Array(a) => a.into_js(ctx), ObjectBytes::U64Array(a) => a.into_js(ctx), ObjectBytes::I64Array(a) => a.into_js(ctx), ObjectBytes::F32Array(a) => a.into_js(ctx), ObjectBytes::F64Array(a) => a.into_js(ctx), ObjectBytes::DataView(d) => { let ctor: Constructor = ctx.globals().get(PredefinedAtom::DataView)?; ctor.construct((d,)) }, ObjectBytes::Vec(v) => v.into_js(ctx), } } } impl<'js> TryFrom> for Vec { type Error = Rc; fn try_from(value: ObjectBytes<'js>) -> std::result::Result { value.into_bytes_inner() } } impl<'a, 'js> TryFrom<&'a ObjectBytes<'js>> for &'a [u8] { type Error = Rc; fn try_from(value: &'a ObjectBytes<'js>) -> std::result::Result { value.as_bytes_inner() } } impl<'js> FromJs<'js> for ObjectBytes<'js> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { Self::from_offset(ctx, &value, 0, None) } } impl<'js> ObjectBytes<'js> { pub fn from(ctx: &Ctx<'js>, value: &Value<'js>) -> Result { Self::from_offset(ctx, value, 0, None) } pub fn from_offset( ctx: &Ctx<'js>, value: &Value<'js>, offset: usize, length: Option, ) -> Result { if value.is_undefined() { return Ok(ObjectBytes::Vec(vec![])); } if let Some(bytes) = get_string_bytes(value, offset, length)? { return Ok(ObjectBytes::Vec(bytes)); } if let Some(bytes) = get_array_bytes(value, offset, length)? { return Ok(ObjectBytes::Vec(bytes)); } if let Some(obj) = value.as_object() { if let Some(bytes) = Self::from_array_buffer(obj)? { return Ok(bytes); } } if let Some(bytes) = get_coerced_string_bytes(value, offset, length) { return Ok(ObjectBytes::Vec(bytes)); } Err(Exception::throw_message( ctx, "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string", )) } pub fn as_bytes(&self, ctx: &Ctx<'js>) -> Result<&[u8]> { self.as_bytes_inner().or_throw(ctx) } fn as_bytes_inner(&self) -> std::result::Result<&[u8], Rc> { match self { ObjectBytes::U8Array(array) => array.as_bytes(), ObjectBytes::I8Array(array) => array.as_bytes(), ObjectBytes::U16Array(array) => array.as_bytes(), ObjectBytes::I16Array(array) => array.as_bytes(), ObjectBytes::U32Array(array) => array.as_bytes(), ObjectBytes::I32Array(array) => array.as_bytes(), ObjectBytes::U64Array(array) => array.as_bytes(), ObjectBytes::I64Array(array) => array.as_bytes(), ObjectBytes::F32Array(array) => array.as_bytes(), ObjectBytes::F64Array(array) => array.as_bytes(), ObjectBytes::DataView(array_buffer) => array_buffer.as_bytes(), ObjectBytes::Vec(bytes) => Some(bytes.as_ref()), } .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into()) } pub fn into_bytes(self, ctx: &Ctx<'_>) -> Result> { self.into_bytes_inner().or_throw(ctx) } fn into_bytes_inner(self) -> std::result::Result, Rc> { if let ObjectBytes::Vec(bytes) = self { return Ok(bytes); } Ok(self.as_bytes_inner()?.to_vec()) } pub fn from_array_buffer(obj: &Object<'js>) -> Result>> { //most common if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U8Array(typed_array))); } //second most common if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) { return Ok(Some(ObjectBytes::DataView(array_buffer))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I8Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U16Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I16Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U32Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I32Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::U64Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::I64Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::F32Array(typed_array))); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { return Ok(Some(ObjectBytes::F64Array(typed_array))); } if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") { return Ok(Some(ObjectBytes::DataView(array_buffer))); } Ok(None) } pub fn get_array_buffer(&self) -> Result, usize, usize)>> { let buffer = match self { ObjectBytes::DataView(array_buffer) => (array_buffer.clone(), array_buffer.len(), 0), ObjectBytes::U8Array(typed_array) => { let byte_length = typed_array.len(); ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I8Array(typed_array) => { let byte_length = typed_array.len(); ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::U16Array(typed_array) => { let byte_length = typed_array.len() * 2; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I16Array(typed_array) => { let byte_length = typed_array.len() * 2; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::U32Array(typed_array) => { let byte_length = typed_array.len() * 4; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I32Array(typed_array) => { let byte_length = typed_array.len() * 4; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::U64Array(typed_array) => { let byte_length = typed_array.len() * 8; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::I64Array(typed_array) => { let byte_length = typed_array.len() * 8; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::F32Array(typed_array) => { let byte_length = typed_array.len() * 4; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, ObjectBytes::F64Array(typed_array) => { let byte_length = typed_array.len() * 8; ( typed_array.arraybuffer()?, byte_length, typed_array.get("byteOffset")?, ) }, _ => return Ok(None), }; Ok(Some(buffer)) } } pub fn get_start_end_indexes( source_len: usize, target_len: Option, offset: usize, ) -> (usize, usize) { if offset > source_len { return (0, 0); } let target_len = target_len.unwrap_or(source_len - offset); if offset + target_len > source_len { return (offset, source_len); } (offset, target_len + offset) } pub fn get_array_bytes( value: &Value<'_>, offset: usize, length: Option, ) -> Result>> { if value.is_array() { let array = value.as_array().unwrap(); let (start, end) = get_start_end_indexes(array.len(), length, offset); let size = end - start; let mut bytes: Vec = Vec::with_capacity(size); for val in array.iter::().skip(start).take(size) { let val: u8 = val?; bytes.push(val); } return Ok(Some(bytes)); } Ok(None) } pub fn get_coerced_string_bytes( value: &Value<'_>, offset: usize, length: Option, ) -> Option> { if let Ok(val) = value.get::>() { return Some(bytes_from_js_string(val.0, offset, length)); }; None } fn bytes_from_js_string(string: String, offset: usize, length: Option) -> Vec { let (start, end) = get_start_end_indexes(string.len(), length, offset); string.as_bytes()[start..end].to_vec() } #[inline] pub fn get_string_bytes( value: &Value<'_>, offset: usize, length: Option, ) -> Result>> { if let Some(val) = value.as_string() { let string = val.to_string()?; return Ok(Some(bytes_from_js_string(string, offset, length))); } Ok(None) } pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result> { TypedArray::::new(ctx.clone(), bytes).into_js(&ctx) } ================================================ FILE: libs/llrt_utils/src/class.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ atom::PredefinedAtom, class::JsClass, object::Accessor, prelude::This, Array, Class, Ctx, Function, Object, Result, Value, }; use crate::primordials::{BasePrimordials, Primordial}; use super::{object::ObjectExt, result::OptionExt}; pub static CUSTOM_INSPECT_SYMBOL_DESCRIPTION: &str = "llrt.inspect.custom"; pub trait IteratorDef<'js> where Self: 'js + JsClass<'js> + Sized, { fn js_entries(&self, ctx: Ctx<'js>) -> Result>; fn js_iterator(&self, ctx: Ctx<'js>) -> Result> { let value = self.js_entries(ctx)?; let obj = value.as_object(); let values_fn: Function = obj.get(PredefinedAtom::Values)?; values_fn.call((This(value),)) } } pub fn get_class_name(value: &Value) -> Result> { value .get_optional::<_, Object>(PredefinedAtom::Constructor)? .and_then_ok(|ctor| ctor.get_optional::<_, String>(PredefinedAtom::Name)) } #[inline(always)] pub fn get_class<'js, C>(provided: &Value<'js>) -> Result>> where C: JsClass<'js>, { if provided .as_object() .map(|p| p.instance_of::()) .unwrap_or_default() { return Ok(Some(Class::::from_value(provided)?)); } Ok(None) } pub trait CustomInspectExtension<'js> { fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()>; } pub trait CustomInspect<'js> where Self: JsClass<'js>, { fn custom_inspect(&self, ctx: Ctx<'js>) -> Result>; } impl<'js, C> CustomInspectExtension<'js> for Class<'js, C> where C: JsClass<'js> + CustomInspect<'js> + 'js, { fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()> { Self::define(globals)?; let custom_inspect_symbol = BasePrimordials::get(globals.ctx())? .symbol_custom_inspect .clone(); if let Some(proto) = Class::::prototype(globals.ctx())? { proto.prop( custom_inspect_symbol, Accessor::from(|this: This>, ctx| this.borrow().custom_inspect(ctx)), )?; } Ok(()) } } ================================================ FILE: libs/llrt_utils/src/clone.rs ================================================ use std::collections::HashSet; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ atom::PredefinedAtom, function::{Constructor, Opt, This}, Array, ArrayBuffer, Ctx, Exception, Function, IntoJs, Null, Object, Result, Type, Value, }; use super::{ hash, object::ObjectExt, primordials::{BasePrimordials, Primordial}, }; #[derive(Debug)] enum StackItem<'js> { Value(usize, Value<'js>, Option, Option), ObjectEnd, } #[derive(Debug)] enum ObjectType { Set, Map, } #[derive(Debug)] enum TapeValue<'js> { Array(Array<'js>), Object(Object<'js>), Value(Value<'js>), Collection(Option>, ObjectType), } #[derive(Debug)] struct TapeItem<'js> { parent: usize, object_key: Option, array_index: Option, value: TapeValue<'js>, } pub fn structured_clone<'js>( ctx: &Ctx<'js>, value: Value<'js>, options: Opt>, ) -> Result> { let primordials = BasePrimordials::get(ctx)?; let mut transfer_set = None; if let Some(options) = options.0 { if let Some(transfer_array) = options.get_optional::<_, Array>("transfer")? { let mut set = HashSet::with_capacity(transfer_array.len()); for item in transfer_array.iter::() { set.insert(item?); } transfer_set = Some(set); } } let mut tape = Vec::::with_capacity(10); let mut stack = Vec::with_capacity(10); let mut visited = Vec::<(usize, usize)>::with_capacity(10); let mut index = 0usize; stack.push(StackItem::Value(0, value, None, None)); while let Some(item) = stack.pop() { match item { StackItem::Value(parent, value, mut object_key, array_index) => { if let Some(set) = &transfer_set { if let Some(value) = set.get(&value) { append_transfer_value(&mut tape, value, parent, object_key, array_index)?; index += 1; continue; } } match value.type_of() { Type::Proxy => { return Err(Exception::throw_type( ctx, "A Proxy value could not be cloned", )); }, Type::Object => { if check_circular( &mut tape, &mut visited, &value, parent, &mut object_key, array_index, index, ) { index += 1; continue; } //unsafe OK since we're guaranteed to be object by the match let object = unsafe { value.as_object().unwrap_unchecked() }; if object.is_instance_of(&primordials.constructor_date) { append_ctor_value( &mut tape, object, &primordials.constructor_date, parent, object_key, array_index, )?; index += 1; continue; } if object.is_instance_of(&primordials.constructor_regexp) { append_ctor_value( &mut tape, object, &primordials.constructor_regexp, parent, object_key, array_index, )?; index += 1; continue; } let is_collection = if object.is_instance_of(&primordials.constructor_set) { Some(ObjectType::Set) } else if object.is_instance_of(&primordials.constructor_map) { Some(ObjectType::Map) } else { None }; if let Some(collection_type) = is_collection { append_collection( &mut tape, &primordials.function_array_from, object, parent, object_key, array_index, collection_type, &mut stack, index, )?; index += 1; continue; } if primordials .function_array_buffer_is_view .call::<_, bool>((value.clone(),))? { append_buffer(&mut tape, object, parent, object_key, array_index)?; index += 1; continue; } let new: Object<'_> = if object.is_instance_of(&primordials.constructor_error) { primordials.constructor_error.construct(("",)) } else { Object::new(ctx.clone()) }?; tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Object(new), }); stack.push(StackItem::ObjectEnd); for key in object.keys::() { let key = key?; let value = object.get(&key)?; stack.push(StackItem::Value(index, value, Some(key), None)); } }, Type::Array => { if check_circular( &mut tape, &mut visited, &value, parent, &mut object_key, array_index, index, ) { index += 1; continue; } let new = Array::new(ctx.clone())?; tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Array(new), }); stack.push(StackItem::ObjectEnd); //unsafe OK since we're guaranteed to be object by the match let array = unsafe { value.as_array().unwrap_unchecked() }; //reverse for loop of items in array for array_index in (0usize..array.len()).rev() { stack.push(StackItem::Value( index, array.get(array_index)?, None, Some(array_index), )); } }, _ => { tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Value(value), }); }, } index += 1; }, StackItem::ObjectEnd => { visited.pop(); }, } } while let Some(item) = tape.pop() { let value = match item.value { TapeValue::Array(array) => array.into_value(), TapeValue::Object(object) => object.into_value(), TapeValue::Value(value) => value, TapeValue::Collection(mut value, _) => value.take().unwrap(), }; if tape.is_empty() { return Ok(value); } let parent = &mut tape[item.parent]; let array_index = item.array_index; let object_key = item.object_key; match &mut parent.value { TapeValue::Array(array) => { array.set(array_index.unwrap(), value)?; }, TapeValue::Object(object) => { let string = object_key.unwrap(); object.set(string, value)?; }, TapeValue::Collection(collection_value, collection_type) => { match collection_type { ObjectType::Set => { collection_value.replace(primordials.constructor_set.construct((value,))?); }, ObjectType::Map => { collection_value.replace(primordials.constructor_map.construct((value,))?); }, }; }, _ => {}, }; } Null.into_js(ctx) } #[inline(always)] #[cold] fn append_buffer<'js>( tape: &mut Vec>, object: &Object<'js>, parent: usize, object_key: Option, array_index: Option, ) -> Result<()> { let ctor: Constructor = object.get(PredefinedAtom::Constructor)?; let slice: Function = object.get("slice")?; let clone: Value = slice.call((This(object.clone()),))?; let new = ctor.construct((clone,))?; tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Value(new), }); Ok(()) } #[inline(always)] #[cold] #[allow(clippy::too_many_arguments)] fn append_collection<'js>( tape: &mut Vec>, array_from: &Function<'js>, object: &Object<'js>, parent: usize, object_key: Option, array_index: Option, collection_type: ObjectType, stack: &mut Vec>, index: usize, ) -> Result<()> { let array: Array = array_from.call((object.clone(),))?; tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Collection(None, collection_type), }); stack.push(StackItem::ObjectEnd); stack.push(StackItem::Value(index, array.into(), None, None)); Ok(()) } #[inline(always)] fn check_circular( tape: &mut Vec, visited: &mut Vec<(usize, usize)>, value: &Value<'_>, parent: usize, object_key: &mut Option, array_index: Option, index: usize, ) -> bool { let hash = hash::default_hash(value); if let Some(visited) = visited.iter().find(|v| v.0 == hash) { append_circular(tape, visited, object_key, parent, array_index); return true; } visited.push((hash, index)); false } #[inline(always)] #[cold] fn append_transfer_value<'js>( tape: &mut Vec>, value: &Value<'js>, parent: usize, object_key: Option, array_index: Option, ) -> Result<()> { let value = if let Some(ab) = ArrayBuffer::from_value(value.clone()) { ab.get::<_, Function>("transfer")?.call((This(ab),))? } else { value.clone() }; tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Value(value), }); Ok(()) } #[inline(always)] #[cold] fn append_circular( tape: &mut Vec>, visited: &(usize, usize), object_key: &mut Option, parent: usize, array_index: Option, ) { let value = match &tape[visited.1].value { TapeValue::Array(array) => array.clone().into_value(), TapeValue::Object(object) => object.clone().into_value(), TapeValue::Value(value) => value.clone(), TapeValue::Collection(value, _) => value.clone().unwrap(), }; let object_key = object_key.take(); tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Value(value), }); } #[inline(always)] #[cold] fn append_ctor_value<'js>( tape: &mut Vec>, object: &Object<'js>, ctor: &Constructor<'js>, parent: usize, object_key: Option, array_index: Option, ) -> Result<()> { let clone: Value = ctor.construct((object.clone(),))?; tape.push(TapeItem { parent, object_key, array_index, value: TapeValue::Value(clone), }); Ok(()) } #[cfg(test)] mod tests { use llrt_test::test_sync_with; use rquickjs::{function::Opt, Object, Value}; use crate::primordials::{BasePrimordials, Primordial}; use super::structured_clone; #[tokio::test] async fn clone() { test_sync_with(|ctx| { BasePrimordials::init(&ctx)?; let value: Object = ctx.eval( r#" const a = { "foo":{ "bar":"baz" }, "foo1":{ "bar1":"baz1", "bar11":"baz11" } }; a "#, )?; let cloned = structured_clone(&ctx, value.clone().into_value(), Opt(None))? .into_object() .unwrap(); let json = ctx .json_stringify(value.clone())? .unwrap() .to_string()? .to_string(); let clone_json = ctx .json_stringify(cloned.clone())? .unwrap() .to_string()? .to_string(); assert_eq!(json, clone_json); assert_ne!( value.get::<_, Value>("foo")?, cloned.get::<_, Value>("foo")? ); Ok(()) }) .await } #[tokio::test] async fn clone_circular() { test_sync_with(|ctx| { BasePrimordials::init(&ctx)?; let _value: Object = ctx.eval( r#" const originalObject = { foo: { bar: "baz",arr: [1,2,3] } }; originalObject.foo.circularRef = originalObject; originalObject.foo.circularRef2 = originalObject; originalObject.foo.circularRef3 = originalObject.foo; originalObject.ref2 = originalObject; "#, )?; Ok(()) }) .await } } ================================================ FILE: libs/llrt_utils/src/ctx.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{Ctx, Result}; pub trait CtxExt { fn get_script_or_module_name(&self) -> Result; } impl CtxExt for Ctx<'_> { fn get_script_or_module_name(&self) -> Result { if let Some(name) = self.script_or_module_name(0) { name.to_string() } else { Ok(String::from(".")) } } } ================================================ FILE: libs/llrt_utils/src/error.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{CatchResultExt, CaughtError, Ctx, Error, IntoJs, Result, Value}; pub trait ErrorExtensions<'js> { fn into_value(self, ctx: &Ctx<'js>) -> Result>; } impl<'js> ErrorExtensions<'js> for Error { fn into_value(self, ctx: &Ctx<'js>) -> Result> { Err::<(), _>(self).catch(ctx).unwrap_err().into_value(ctx) } } impl<'js> ErrorExtensions<'js> for CaughtError<'js> { fn into_value(self, ctx: &Ctx<'js>) -> Result> { Ok(match self { CaughtError::Error(err) => err.to_string().into_js(ctx)?, CaughtError::Exception(ex) => ex.into_value(), CaughtError::Value(val) => val, }) } } ================================================ FILE: libs/llrt_utils/src/error_messages.rs ================================================ pub const ERROR_MSG_NOT_ARRAY_BUFFER: &str = "Not an ArrayBuffer"; pub const ERROR_MSG_ARRAY_BUFFER_DETACHED: &str = "ArrayBuffer is detached"; pub const ERROR_MSG_BROADCAST_LAGGED: &str = "Lagged too much behind"; ================================================ FILE: libs/llrt_utils/src/fs.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{fs::Metadata, io, path::PathBuf}; use tokio::fs::{self}; pub struct DirectoryWalker where T: Fn(&str) -> bool, { stack: Vec<(PathBuf, Option)>, filter: T, recursive: bool, eat_root: bool, } impl DirectoryWalker where T: Fn(&str) -> bool, { pub fn new(root: PathBuf, filter: T) -> Self { Self { stack: vec![(root, None)], filter, recursive: false, eat_root: true, } } pub fn set_recursive(&mut self, recursive: bool) { self.recursive = recursive; } pub async fn walk(&mut self) -> io::Result> { if self.eat_root { self.eat_root = false; let (dir, _) = self.stack.pop().unwrap(); self.append_stack(&dir).await?; } if let Some((entry, metadata)) = self.stack.pop() { let metadata = metadata.unwrap(); if self.recursive && metadata.is_dir() { self.append_stack(&entry).await?; } Ok(Some((entry, metadata))) } else { Ok(None) } } pub fn walk_sync(&mut self) -> io::Result> { if self.eat_root { self.eat_root = false; let (dir, _) = self.stack.pop().unwrap(); self.append_stack_sync(&dir)?; } if let Some((entry, metadata)) = self.stack.pop() { let metadata = metadata.unwrap(); if self.recursive && metadata.is_dir() { self.append_stack_sync(&entry)?; } Ok(Some((entry, metadata))) } else { Ok(None) } } async fn append_stack(&mut self, dir: &PathBuf) -> io::Result<()> { let mut stream = fs::read_dir(dir).await?; while let Some(entry) = stream.next_entry().await? { let name = entry.file_name(); let name = name.to_string_lossy(); if !(self.filter)(name.as_ref()) { continue; } let entry_path = entry.path(); let metadata = fs::symlink_metadata(&entry_path).await?; self.stack.push((entry_path, Some(metadata))); } Ok(()) } fn append_stack_sync(&mut self, dir: &PathBuf) -> io::Result<()> { let dir = std::fs::read_dir(dir)?; for entry in dir.flatten() { let name = entry.file_name(); let name = name.to_string_lossy(); if !(self.filter)(name.as_ref()) { continue; } let entry_path = entry.path(); let metadata = entry_path.symlink_metadata()?; self.stack.push((entry_path, Some(metadata))) } Ok(()) } } ================================================ FILE: libs/llrt_utils/src/hash.rs ================================================ use std::hash::{DefaultHasher, Hash, Hasher}; #[inline] pub fn default_hash(v: &T) -> usize { let mut state = DefaultHasher::default(); v.hash(&mut state); state.finish() as usize } ================================================ FILE: libs/llrt_utils/src/io.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 macro_rules! define_extension { ($base:ident, $file:ident, $ext:expr) => { #[allow(dead_code)] pub const $base: &str = $ext; #[allow(dead_code)] pub const $file: &str = concat!(".", $ext); }; } define_extension!(BYTECODE_EXT, BYTECODE_FILE_EXT, "lrt"); macro_rules! define_supported_extensions { // Accepts a list of supported extensions and a single additional constant extension ($constant_ext:ident, $($ext:literal),*) => { // Define the array of extensions as a constant pub const SUPPORTED_EXTENSIONS: &[&str] = &[$($ext),*, $constant_ext]; pub const JS_EXTENSIONS: &[&str] = &[$($ext),*]; // Define the function `is_supported_ext` using a match statement pub fn is_supported_ext(ext: &str) -> bool { matches!(ext, $($ext)|* | $constant_ext) } }; } define_supported_extensions!(BYTECODE_FILE_EXT, ".js", ".mjs", ".cjs"); ================================================ FILE: libs/llrt_utils/src/latch.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::atomic::{AtomicUsize, Ordering}; use tokio::sync::Notify; #[derive(Default)] pub struct Latch { count: AtomicUsize, notify: Notify, } impl Latch { pub fn increment(&self) { self.count.fetch_add(1, Ordering::Relaxed); } pub fn decrement(&self) { let previous = self.count.fetch_sub(1, Ordering::Relaxed); if previous == 1 { self.notify.notify_waiters(); } } pub async fn wait(&self) { if self.count.load(Ordering::Relaxed) > 0 { self.notify.notified().await; } } } ================================================ FILE: libs/llrt_utils/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 pub mod any_of; #[cfg(feature = "bytearray-buffer")] pub mod bytearray_buffer; pub mod bytes; pub mod class; pub mod clone; pub mod ctx; pub mod error; pub mod error_messages; #[cfg(feature = "fs")] pub mod fs; pub mod hash; pub mod io; pub mod latch; pub mod macros; pub mod mc_oneshot; pub mod module; pub mod object; pub mod option; pub mod primordials; pub mod provider; pub mod result; pub mod reuse_list; pub mod string; pub mod sysinfo; pub mod time; pub mod signals; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); ================================================ FILE: libs/llrt_utils/src/macros.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #[macro_export] macro_rules! count_members { () => (0); ($head:tt $(,$tail:tt)*) => (1 + count_members!($($tail),*)); } #[macro_export] macro_rules! iterable_enum { ($name:ident, $($variant:ident),*) => { impl $name { const VARIANTS: &'static [$name] = &[$($name::$variant,)*]; pub fn iter() -> std::slice::Iter<'static, $name> { Self::VARIANTS.iter() } #[allow(dead_code)] fn _ensure_all_variants(s: Self) { match s { $($name::$variant => {},)* } } } }; } #[macro_export] macro_rules! str_enum { ($name:ident, $($variant:ident => $str:expr),*) => { impl $name { pub fn as_str(&self) -> &'static str { match self { $($name::$variant => $str,)* } } } impl AsRef for $name { fn as_ref(&self) -> &str { self.as_str() } } impl TryFrom<&str> for $name { type Error = String; fn try_from(s: &str) -> std::result::Result { match s { $($str => Ok($name::$variant),)* _ => Err(["'", s, "' not available"].concat()) } } } }; } ================================================ FILE: libs/llrt_utils/src/mc_oneshot.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, RwLock, }; use rquickjs::{ class::{Trace, Tracer}, Value, }; use std::ops::Deref; use tokio::sync::Notify; #[derive(Debug)] pub struct Shared { is_sent: AtomicBool, value: RwLock>, notify: Notify, } #[derive(Clone, Debug)] pub struct Sender(Arc>); impl Deref for Sender { type Target = Arc>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'js> Trace<'js> for Sender> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { if let Ok(v) = self.value.read() { if let Some(v) = v.as_ref() { tracer.mark(v) } } } } impl Sender { pub fn send(&self, value: T) { if !self.is_sent.load(Ordering::Relaxed) { self.value.write().unwrap().replace(value); self.is_sent.store(true, Ordering::Release); self.notify.notify_waiters(); } } pub fn subscribe(&self) -> Receiver { Receiver(Arc::clone(&self.0)) } } #[derive(Clone, Debug)] pub struct Receiver(Arc>); impl Deref for Receiver { type Target = Arc>; fn deref(&self) -> &Self::Target { &self.0 } } impl Receiver { pub async fn recv(&self) -> T { if !self.is_sent.load(Ordering::Acquire) { self.notify.notified().await; } self.value.read().unwrap().clone().unwrap() } } pub fn channel() -> (Sender, Receiver) { let shared = Arc::new(Shared { is_sent: AtomicBool::new(false), value: RwLock::new(None), notify: Notify::new(), }); (Sender(Arc::clone(&shared)), Receiver(shared)) } #[cfg(test)] mod tests { use tokio::join; #[tokio::test] async fn test() { let (tx, rx1) = super::channel::(); let rx2 = tx.subscribe(); let rx3 = tx.subscribe(); let a = tokio::spawn(async move { let val = rx1.recv().await; //wait for value to become false assert!(val) }); let b = tokio::spawn(async move { let val = rx2.recv().await; //wait for value to become false assert!(val) }); tokio::time::sleep(std::time::Duration::from_millis(10)).await; tx.send(true); let val = rx3.recv().await; assert!(val); let (a, b) = join!(a, b); a.unwrap(); b.unwrap(); } } ================================================ FILE: libs/llrt_utils/src/module.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ module::{Exports, ModuleDef}, Ctx, Object, Result, Value, }; pub struct ModuleInfo { pub name: &'static str, pub module: T, } pub fn export_default<'js, F>(ctx: &Ctx<'js>, exports: &Exports<'js>, f: F) -> Result<()> where F: FnOnce(&Object<'js>) -> Result<()>, { let default = Object::new(ctx.clone())?; f(&default)?; for name in default.keys::() { let name = name?; let value: Value = default.get(&name)?; exports.export(name, value)?; } exports.export("default", default)?; Ok(()) } ================================================ FILE: libs/llrt_utils/src/object.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::collections::BTreeMap; use rquickjs::{ atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Coerced, Ctx, Error, Exception, FromJs, IntoAtom, IntoJs, Object, Result, Symbol, Undefined, Value, }; use crate::primordials::{BasePrimordials, Primordial}; pub trait ObjectExt<'js> { fn get_optional + Clone, V: FromJs<'js>>(&self, k: K) -> Result>; fn get_required, V: FromJs<'js>>( &self, k: K, object_name: &'static str, ) -> Result; fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str) -> Result>; } impl<'js> ObjectExt<'js> for Object<'js> { fn get_optional + Clone, V: FromJs<'js> + Sized>( &self, k: K, ) -> Result> { self.get::>(k) } fn get_required, V: FromJs<'js>>( &self, k: K, object_name: &'static str, ) -> Result { let k = k.as_ref(); self.get::<&str, Option>(k)?.ok_or_else(|| { Exception::throw_type( self.ctx(), &[object_name, " '", k, "' property required"].concat(), ) }) } fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result> { Ok(self) } } impl<'js> ObjectExt<'js> for Value<'js> { fn get_optional + Clone, V: FromJs<'js>>(&self, k: K) -> Result> { if let Some(obj) = self.as_object() { return obj.get_optional(k); } Ok(None) } fn get_required, V: FromJs<'js>>( &self, k: K, object_name: &'static str, ) -> Result { self.as_object() .ok_or_else(|| not_a_object_error(self.ctx(), object_name))? .get_required(k, object_name) } fn into_object_or_throw( self, ctx: &Ctx<'js>, object_name: &'static str, ) -> Result> { self.into_object() .ok_or_else(|| not_a_object_error(ctx, object_name)) } } pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error { Exception::throw_type(ctx, &[object_name, " is not an object"].concat()) } pub trait CreateSymbol<'js> { fn for_description(ctx: &Ctx<'js>, description: &str) -> Result>; } impl<'js> CreateSymbol<'js> for Symbol<'js> { fn for_description(ctx: &Ctx<'js>, description: &str) -> Result> { BasePrimordials::get(ctx)? .function_symbol_for .call((description,)) } } pub struct Proxy<'js> { target: Value<'js>, options: Object<'js>, } impl<'js> IntoJs<'js> for Proxy<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { BasePrimordials::get(ctx)? .constructor_proxy .construct::<_, Value>((self.target, self.options)) } } impl<'js> Proxy<'js> { pub fn new(ctx: Ctx<'js>) -> Result { let options = Object::new(ctx.clone())?; Ok(Self { target: Undefined.into_value(ctx), options, }) } pub fn with_target(ctx: Ctx<'js>, target: Value<'js>) -> Result { let options = Object::new(ctx)?; Ok(Self { target, options }) } pub fn setter(&self, setter: Func) -> Result<()> where T: IntoJsFunc<'js, P> + 'js, { self.options.set(PredefinedAtom::Setter, setter)?; Ok(()) } pub fn getter(&self, getter: Func) -> Result<()> where T: IntoJsFunc<'js, P> + 'js, { self.options.set(PredefinedAtom::Getter, getter)?; Ok(()) } } pub fn map_to_entries<'js, K, V, M>(ctx: &Ctx<'js>, map: M) -> Result> where M: IntoIterator, K: IntoJs<'js>, V: IntoJs<'js>, { let array = Array::new(ctx.clone())?; for (idx, (key, value)) in map.into_iter().enumerate() { let entry = Array::new(ctx.clone())?; entry.set(0, key)?; entry.set(1, value)?; array.set(idx, entry)?; } Ok(array) } pub fn array_to_btree_map<'js>( ctx: &Ctx<'js>, array: Array<'js>, ) -> Result>> { let value = object_from_entries(ctx, array)?; let value = value.into_value(); BTreeMap::from_js(ctx, value) } pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result> { let obj = Object::new(ctx.clone())?; for value in array.into_iter().flatten() { if let Some(entry) = value.as_array() { if let Ok(key) = entry.get::(0) { if let Ok(value) = entry.get::(1) { let _ = obj.set(key, value); //ignore result of failed } } } } Ok(obj) } ================================================ FILE: libs/llrt_utils/src/option.rs ================================================ use rquickjs::{ class::{Trace, Tracer}, function::{FromParam, ParamRequirement, ParamsAccessor}, Ctx, FromJs, IntoJs, JsLifetime, Result, Type, Value, }; /// Helper type for treating an undefined value as None, without treating null as None #[derive(Clone)] pub struct Undefined(pub Option); impl<'js, T: FromJs<'js>> FromJs<'js> for Undefined { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { if value.type_of() == Type::Undefined { Ok(Self(None)) } else { Ok(Self(Some(FromJs::from_js(ctx, value)?))) } } } impl<'js, T: IntoJs<'js>> IntoJs<'js> for Undefined { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self.0 { None => Ok(Value::new_undefined(ctx.clone())), Some(val) => val.into_js(ctx), } } } impl Default for Undefined { fn default() -> Self { Self(None) } } unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Undefined { type Changed<'to> = Undefined>; } impl<'js, T: Trace<'js>> Trace<'js> for Undefined { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.0.trace(tracer) } } /// Helper type for converting an None into null instead of undefined. /// Needed while rquickjs::function::Null has no IntoJs implementation #[derive(Clone)] pub struct Null(pub Option); impl<'js, T: FromJs<'js>> FromJs<'js> for Null { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { if value.type_of() == Type::Null { Ok(Self(None)) } else { Ok(Self(Some(FromJs::from_js(ctx, value)?))) } } } impl<'js, T: IntoJs<'js>> IntoJs<'js> for Null { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self.0 { None => Ok(Value::new_null(ctx.clone())), Some(val) => val.into_js(ctx), } } } unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Null { type Changed<'to> = Null>; } impl<'js, T: Trace<'js>> Trace<'js> for Null { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.0.trace(tracer) } } /// Helper type for accepting no value, or null, but considering undefined as a value pub struct NullableOpt(pub Option); impl<'js, T: FromJs<'js>> FromParam<'js> for NullableOpt { fn param_requirement() -> ParamRequirement { ParamRequirement::optional() } fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result { if !params.is_empty() { let arg = params.arg(); if arg.is_null() { Ok(NullableOpt(None)) } else { let ctx = params.ctx().clone(); Ok(NullableOpt(Some(T::from_js(&ctx, arg)?))) } } else { Ok(NullableOpt(None)) } } } ================================================ FILE: libs/llrt_utils/src/primordials.rs ================================================ use std::any::type_name; use rquickjs::{ atom::PredefinedAtom, function::Constructor, runtime::UserDataGuard, Ctx, Function, JsLifetime, Object, Result, Symbol, }; use crate::{class::CUSTOM_INSPECT_SYMBOL_DESCRIPTION, result::ResultExt}; #[derive(JsLifetime)] pub struct BasePrimordials<'js> { // Constructors pub constructor_map: Constructor<'js>, pub constructor_set: Constructor<'js>, pub constructor_date: Constructor<'js>, pub constructor_error: Constructor<'js>, pub constructor_type_error: Constructor<'js>, pub constructor_range_error: Constructor<'js>, pub constructor_regexp: Constructor<'js>, pub constructor_uint8array: Constructor<'js>, pub constructor_array_buffer: Constructor<'js>, pub constructor_proxy: Constructor<'js>, pub constructor_object: Constructor<'js>, pub constructor_bool: Constructor<'js>, pub constructor_number: Constructor<'js>, pub constructor_string: Constructor<'js>, // Prototypes pub prototype_object: Object<'js>, pub prototype_date: Object<'js>, pub prototype_regexp: Object<'js>, pub prototype_set: Object<'js>, pub prototype_map: Object<'js>, pub prototype_error: Object<'js>, // Functions pub function_array_from: Function<'js>, pub function_array_buffer_is_view: Function<'js>, pub function_get_own_property_descriptor: Function<'js>, pub function_parse_int: Function<'js>, pub function_parse_float: Function<'js>, pub function_symbol_for: Function<'js>, // Symbols pub symbol_custom_inspect: Symbol<'js>, } pub trait Primordial<'js> where Self: Sized + JsLifetime<'js>, { fn get<'a>(ctx: &'a Ctx<'js>) -> Result> { let userdata = ctx.userdata::().or_throw_msg( ctx, &[ "Userdata of ", type_name::(), " not initialized. Call init(&ctx) on this type.", ] .concat(), )?; Ok(userdata) } fn init<'a>(ctx: &'a Ctx<'js>) -> Result<()> { if ctx.userdata::().is_none() { let primoridals = Self::new(ctx)?; let _ = ctx.store_userdata(primoridals); } Ok(()) } fn new(ctx: &Ctx<'js>) -> Result; } impl<'js> Primordial<'js> for BasePrimordials<'js> { fn new(ctx: &Ctx<'js>) -> Result { let globals = ctx.globals(); let constructor_object: Constructor = globals.get(PredefinedAtom::Object)?; let prototype_object: Object = constructor_object.get(PredefinedAtom::Prototype)?; let constructor_proxy: Constructor = globals.get(PredefinedAtom::Proxy)?; let function_get_own_property_descriptor: Function = constructor_object.get(PredefinedAtom::GetOwnPropertyDescriptor)?; let constructor_date: Constructor = globals.get(PredefinedAtom::Date)?; let prototype_date: Object = constructor_date.get(PredefinedAtom::Prototype)?; let constructor_map: Constructor = globals.get(PredefinedAtom::Map)?; let prototype_map: Object = constructor_map.get(PredefinedAtom::Prototype)?; let constructor_set: Constructor = globals.get(PredefinedAtom::Set)?; let prototype_set: Object = constructor_set.get(PredefinedAtom::Prototype)?; let constructor_regexp: Constructor = globals.get(PredefinedAtom::RegExp)?; let prototype_regexp: Object = constructor_regexp.get(PredefinedAtom::Prototype)?; let constructor_uint8array: Constructor = globals.get(PredefinedAtom::Uint8Array)?; let constructor_arraybuffer: Constructor = globals.get(PredefinedAtom::ArrayBuffer)?; let constructor_error: Constructor = globals.get(PredefinedAtom::Error)?; let constructor_type_error: Constructor = ctx.globals().get(PredefinedAtom::TypeError)?; let constructor_range_error: Constructor = ctx.globals().get(PredefinedAtom::RangeError)?; let prototype_error: Object = constructor_error.get(PredefinedAtom::Prototype)?; let constructor_array: Object = globals.get(PredefinedAtom::Array)?; let function_array_from: Function = constructor_array.get(PredefinedAtom::From)?; let constructor_array_buffer: Object = globals.get(PredefinedAtom::ArrayBuffer)?; let function_array_buffer_is_view: Function = constructor_array_buffer.get("isView")?; let constructor_bool: Constructor = globals.get(PredefinedAtom::Boolean)?; let constructor_number: Constructor = globals.get(PredefinedAtom::Number)?; let function_parse_float: Function = constructor_number.get("parseFloat")?; let function_parse_int: Function = constructor_number.get("parseInt")?; let constructor_string: Constructor = globals.get(PredefinedAtom::String)?; let constructor_symbol: Constructor = globals.get(PredefinedAtom::Symbol)?; let function_symbol_for: Function = constructor_symbol.get(PredefinedAtom::For)?; let symbol_custom_inspect: Symbol<'js> = function_symbol_for.call((CUSTOM_INSPECT_SYMBOL_DESCRIPTION,))?; Ok(Self { constructor_map, constructor_set, constructor_date, constructor_proxy, constructor_error, constructor_type_error, constructor_range_error, constructor_regexp, constructor_uint8array, constructor_array_buffer: constructor_arraybuffer, constructor_object, constructor_bool, constructor_number, constructor_string, prototype_object, prototype_date, prototype_regexp, prototype_set, prototype_map, prototype_error, function_array_from, function_array_buffer_is_view, function_get_own_property_descriptor, function_parse_float, function_parse_int, function_symbol_for, symbol_custom_inspect, }) } } ================================================ FILE: libs/llrt_utils/src/provider.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #[derive(PartialEq)] pub enum ProviderType { None, Resource(String), // Custom asynchronous resource // Userland provider types Immediate, // [Immediate] Processing by setImmediate() Interval, // [Interval] Timer by setInterval() MessagePort, // [MessagePort] Port for worker_threads Microtask, // [Microtask] Processing by queueMicrotask() TickObject, // [TickObject] Processing by process.nextTick() Timeout, // [Timeout] Timer by setTimeout() // Internal provider types FsReqCallback, // [FSREQCALLBACK] Callback for file system operations GetAddrInfoReqWrap, // [GETADDRINFOREQWRAP] When resolving DNS (dns.lookup(), etc.) GetNameInfoReqWrap, // [GETNAMEINFOREQWRAP] DNS reverse lookup PipeWrap, // [PIPEWRAP] Pipe connection StatWatcher, // [STATWACHER] File monitoring such as fs.watch() TcpWrap, // [TCPWRAP] TCP socket wrap (net.Socket, etc.) TimerWrap, // [TIMERWRAP] Internal timer wrap (low level) TlsWrap, // [TLSWRAP] TLS socket (HTTPS, etc.) UdpWrap, // [UDPWRAP] UDP socket wrap (dgram module) } ================================================ FILE: libs/llrt_utils/src/result.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{fmt::Write, result::Result as StdResult}; use rquickjs::{Ctx, Exception, Result}; pub trait ResultExt { fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result; fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result; fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result; fn or_throw(self, ctx: &Ctx) -> Result; } pub trait OptionExt { fn and_then_ok(self, f: F) -> StdResult, E> where F: FnOnce(T) -> StdResult, E>; fn unwrap_or_else_ok(self, f: F) -> StdResult where F: FnOnce() -> StdResult; } impl ResultExt for StdResult { fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result { self.map_err(|e| { let mut message = String::with_capacity(100); message.push_str(msg); message.push_str(". "); write!(message, "{}", e).unwrap(); Exception::throw_message(ctx, &message) }) } fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result { self.map_err(|e| { let mut message = String::with_capacity(100); if !message.is_empty() { message.push_str(msg); message.push_str(". "); } write!(message, "{}", e).unwrap(); Exception::throw_range(ctx, &message) }) } fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result { self.map_err(|e| { let mut message = String::with_capacity(100); if !msg.is_empty() { message.push_str(msg); message.push_str(". "); } write!(message, "{}", e).unwrap(); Exception::throw_type(ctx, &message) }) } fn or_throw(self, ctx: &Ctx) -> Result { self.map_err(|err| Exception::throw_message(ctx, &err.to_string())) } } impl ResultExt for Option { fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result { self.ok_or_else(|| Exception::throw_message(ctx, msg)) } fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result { self.ok_or_else(|| Exception::throw_range(ctx, msg)) } fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result { self.ok_or_else(|| Exception::throw_type(ctx, msg)) } fn or_throw(self, ctx: &Ctx) -> Result { self.ok_or_else(|| Exception::throw_message(ctx, "Value is not present")) } } impl OptionExt for Option { fn and_then_ok(self, f: F) -> StdResult, E> where F: FnOnce(T) -> StdResult, E>, { match self { Some(v) => f(v), None => Ok(None), } } fn unwrap_or_else_ok(self, f: F) -> StdResult where F: FnOnce() -> StdResult, { match self { Some(v) => Ok(v), None => f(), } } } ================================================ FILE: libs/llrt_utils/src/reuse_list.rs ================================================ use std::fmt::Debug; #[derive(Default, Clone)] pub struct ReuseList { items: Vec>, slots: Vec, last_slot_idx: usize, len: usize, slot_size: usize, } impl Debug for ReuseList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ReuseList") .field("items", &self.items) .field("slots", &self.slots) .finish() } } impl ReuseList { pub fn new() -> Self { Self::with_capacity(0) } //is empty pub fn is_empty(&self) -> bool { self.len == 0 } //create a with capacity pub fn with_capacity(capacity: usize) -> Self { Self { items: Vec::with_capacity(capacity), slots: Vec::with_capacity(capacity >> 2), last_slot_idx: 0, len: 0, slot_size: 0, } } pub fn append(&mut self, item: T) -> usize { if self.slot_size > 0 { //reuse empty slot if valid let slot = self.slots[self.last_slot_idx - 1]; if slot > 0 { self.items[slot - 1] = Some(item); self.slots[self.last_slot_idx - 1] = 0; if self.last_slot_idx > 1 { self.last_slot_idx -= 1; } self.len += 1; return slot - 1; } } //no valid empty slots, append to end self.items.push(Some(item)); self.len += 1; self.items.len() - 1 } pub fn remove(&mut self, index: usize) -> Option { if index >= self.items.len() { return None; } let item = self.items[index].take(); if item.is_some() { if self.slot_size > 0 && self.slots[self.last_slot_idx - 1] == 0 { self.slots[self.last_slot_idx - 1] = index + 1; } else { self.slots.push(index + 1); self.last_slot_idx += 1; self.slot_size += 1; } self.len -= 1; } item } pub fn get(&self, index: usize) -> Option<&T> { if index >= self.items.len() { None } else { self.items[index].as_ref() } } pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { if index >= self.items.len() { None } else { self.items[index].as_mut() } } pub fn capacity(&self) -> usize { self.items.capacity() } pub fn len(&self) -> usize { self.len } pub fn iter(&self) -> impl Iterator { self.items.iter().filter_map(|x| x.as_ref()) } pub fn iter_mut(&mut self) -> impl Iterator { self.items.iter_mut().filter_map(|x| x.as_mut()) } //implement clear pub fn clear(&mut self) { self.items.clear(); self.slots.clear(); self.last_slot_idx = 0; self.len = 0; self.slot_size = 0; } pub fn optimize(&mut self) { let mut new_items = Vec::with_capacity(self.len); for item in self.items.iter_mut() { let a = item.take(); if a.is_some() { new_items.push(a); } } self.items = new_items; self.slots.clear(); self.last_slot_idx = 0; self.slot_size = 0; } } #[cfg(test)] mod tests { use super::*; #[test] fn test_new() { let list: ReuseList = ReuseList::new(); assert_eq!(list.len(), 0); assert_eq!(list.capacity(), 0); assert_eq!(list.items.len(), 0); assert_eq!(list.slots.len(), 0); } #[test] fn test_with_capacity() { let list: ReuseList = ReuseList::with_capacity(10); assert_eq!(list.len(), 0); assert_eq!(list.capacity(), 10); assert_eq!(list.items.len(), 0); assert_eq!(list.slots.len(), 0); } #[test] fn test_append() { let mut list = ReuseList::new(); assert_eq!(list.append(1), 0); assert_eq!(list.append(2), 1); assert_eq!(list.append(3), 2); assert_eq!(list.len(), 3); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![1, 2, 3]); assert_eq!(list.items, vec![Some(1), Some(2), Some(3)]); assert_eq!(list.slots, vec![]); } #[test] fn test_remove() { let mut list = ReuseList::new(); list.append(1); list.append(2); list.append(3); assert_eq!(list.remove(1), Some(2)); assert_eq!(list.len(), 2); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![1, 3]); assert_eq!(list.items, vec![Some(1), None, Some(3)]); assert_eq!(list.slots, vec![2]); assert_eq!(list.remove(5), None); } #[test] fn test_reuse_slots() { let mut list = ReuseList::new(); list.append(1); list.append(2); list.append(3); list.remove(1); // Remove 2 assert_eq!(list.append(4), 1); // Should reuse index 1 let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![1, 4, 3]); assert_eq!(list.items, vec![Some(1), Some(4), Some(3)]); assert_eq!(list.slots, vec![0]); } #[test] fn test_get() { let mut list = ReuseList::new(); list.append(1); list.append(2); assert_eq!(list.get(0), Some(&1)); assert_eq!(list.get(1), Some(&2)); assert_eq!(list.get(2), None); assert_eq!(list.items, vec![Some(1), Some(2)]); assert_eq!(list.slots, vec![]); } #[test] fn test_get_mut() { let mut list = ReuseList::new(); list.append(1); list.append(2); if let Some(value) = list.get_mut(0) { *value = 10; } assert_eq!(list.get(0), Some(&10)); assert_eq!(list.items, vec![Some(10), Some(2)]); assert_eq!(list.slots, vec![]); } #[test] fn test_iter() { let mut list = ReuseList::new(); list.append(1); list.append(2); list.append(3); list.remove(1); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![1, 3]); assert_eq!(list.items, vec![Some(1), None, Some(3)]); assert_eq!(list.slots, vec![2]); } #[test] fn test_iter_mut() { let mut list = ReuseList::new(); list.append(1); list.append(2); list.append(3); for item in list.iter_mut() { *item *= 2; } let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![2, 4, 6]); assert_eq!(list.items, vec![Some(2), Some(4), Some(6)]); assert_eq!(list.slots, vec![]); } #[test] fn test_multiple_removes() { let mut list = ReuseList::new(); for i in 0..5 { list.append(i); } list.remove(1); list.remove(3); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![0, 2, 4]); assert_eq!(list.items, vec![Some(0), None, Some(2), None, Some(4)]); assert_eq!(list.slots, vec![2, 4]); // Test reuse of both slots list.append(10); list.append(11); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![0, 11, 2, 10, 4]); assert_eq!( list.items, vec![Some(0), Some(11), Some(2), Some(10), Some(4)] ); assert_eq!(list.slots, vec![0, 0]); list.remove(0); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![11, 2, 10, 4]); assert_eq!(list.items, vec![None, Some(11), Some(2), Some(10), Some(4)]); assert_eq!(list.slots, vec![1, 0]); list.append(20); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![20, 11, 2, 10, 4]); assert_eq!( list.items, vec![Some(20), Some(11), Some(2), Some(10), Some(4)] ); assert_eq!(list.slots, vec![0, 0]); //remove all items list.clear(); } #[test] fn test_optimize() { let mut list = ReuseList::new(); list.append(1); list.append(2); list.append(3); list.remove(1); assert_eq!(list.items, vec![Some(1), None, Some(3)]); assert_eq!(list.slots, vec![2]); list.optimize(); assert_eq!(list.items, vec![Some(1), Some(3)]); assert_eq!(list.slots, vec![]); assert_eq!(list.last_slot_idx, 0); assert_eq!(list.slot_size, 0); let items: Vec = list.iter().cloned().collect(); assert_eq!(items, vec![1, 3]); } } ================================================ FILE: libs/llrt_utils/src/signals.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{prelude::Opt, Ctx, Exception, Result, Value}; use crate::result::ResultExt; use std::io; #[cfg(unix)] macro_rules! generate_signal_from_str_fn { ($($signal:path),*) => { pub fn signal_from_str(signal: &str) -> Option { let signal = ["libc::", signal].concat(); match signal.as_str() { $(stringify!($signal) => Some($signal),)* _ => None, } } pub fn signal_str_from_i32(signal: i32) -> Option<&'static str> { $(if signal == $signal { return Some(&stringify!($signal)[6..]); })* None } }; } #[cfg(unix)] generate_signal_from_str_fn!( libc::SIGHUP, libc::SIGINT, libc::SIGQUIT, libc::SIGILL, libc::SIGABRT, libc::SIGFPE, libc::SIGKILL, libc::SIGSEGV, libc::SIGPIPE, libc::SIGALRM, libc::SIGTERM ); #[cfg(not(unix))] static WINDOWS_SIGTERM: i32 = -1; pub fn parse_signal(signal: Option>) -> Result { let Some(val) = signal else { #[cfg(unix)] return Ok(libc::SIGTERM); #[cfg(not(unix))] return Ok(WINDOWS_SIGTERM); }; if let Some(num) = val.as_number() { let sig = num as i32; #[cfg(unix)] return Ok(sig); // On Windows: 0 checks existence, anything else kills #[cfg(not(unix))] return Ok(if sig == 0 { 0 } else { WINDOWS_SIGTERM }); } if let Some(str_val) = val.as_string() { let s = str_val.to_string()?; #[cfg(unix)] let mapped_sig = signal_from_str(&s); #[cfg(not(unix))] let mapped_sig = match s.as_str() { "SIGINT" | "SIGTERM" | "SIGKILL" | "SIGQUIT" | "SIGHUP" | "SIGUSR1" => { Some(WINDOWS_SIGTERM) }, _ => None, }; return match mapped_sig { Some(sig) => Ok(sig), None => Err(Exception::throw_type( val.ctx(), &format!("Unknown signal: {}", s), )), }; } Err(Exception::throw_type(val.ctx(), "Invalid signal type")) } #[cfg(unix)] pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> { // libc::kill returns 0 on success, -1 on error // SAFETY: kill is a safe system call as long as the signal value is valid, which is ensured by parse_signal if unsafe { libc::kill(pid as i32, signal) } == 0 { Ok(()) } else { Err(io::Error::last_os_error()) } } #[cfg(windows)] pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> { use windows_sys::Win32::Foundation::CloseHandle; use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE}; // SAFETY: OpenProcess is safe to call with valid parameters, and PROCESS_TERMINATE is a valid access right let handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, pid) }; if handle == std::ptr::null_mut() { return Err(io::Error::last_os_error()); } let result = if signal == 0 { Ok(()) } else { // SAFETY: TerminateProcess is safe to call with a valid process handle obtained from OpenProcess if unsafe { TerminateProcess(handle, 1) } != 0 { Ok(()) } else { Err(io::Error::last_os_error()) } }; // SAFETY: CloseHandle is safe to call with a valid handle obtained from OpenProcess unsafe { CloseHandle(handle) }; result } pub fn kill(ctx: &Ctx<'_>, pid: u32, signal: Opt>) -> Result { let signal = parse_signal(signal.0)?; kill_process_raw(pid, signal) .map(|_| true) .or_else(|e| { // Handle "Process Not Found" / "Existence Check" logic // If signal is 0 (check existence) and we hit a specific error, return Ok(false). #[cfg(unix)] let is_not_found = e.raw_os_error() == Some(libc::ESRCH); // Error 3 #[cfg(windows)] let is_not_found = true; // On Windows, any OpenProcess failure during check implies "not found" (or not accessible) if signal == 0 && is_not_found { Ok(false) } else { Err(e) } }) .or_throw(ctx) } ================================================ FILE: libs/llrt_utils/src/string.rs ================================================ use rquickjs::{Coerced, Result, Value}; #[inline] pub fn get_string(value: &Value<'_>) -> Result> { if let Some(val) = value.as_string() { let string = val.to_string()?; return Ok(Some(string)); } Ok(None) } pub fn get_coerced_string(value: &Value<'_>) -> Option { if let Ok(val) = value.get::>() { return Some(val.0); }; None } ================================================ FILE: libs/llrt_utils/src/sysinfo.rs ================================================ #[cfg(target_os = "macos")] pub const PLATFORM: &str = "darwin"; #[cfg(target_os = "windows")] pub const PLATFORM: &str = "win32"; #[cfg(not(any(target_os = "macos", target_os = "windows")))] pub const PLATFORM: &str = std::env::consts::OS; #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] pub const ARCH: &str = "x64"; #[cfg(target_arch = "aarch64")] pub const ARCH: &str = "arm64"; #[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] pub const ARCH: &str = std::env::consts::ARCH; ================================================ FILE: libs/llrt_utils/src/time.rs ================================================ use std::{ sync::atomic::{AtomicU64, Ordering}, time::SystemTime, }; static TIME_ORIGIN: AtomicU64 = AtomicU64::new(0); /// Get the current time in nanoseconds. /// /// # Safety /// - Good until the year 2554 /// - Always use a checked substraction since this can return 0 pub fn now_nanos() -> u64 { SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_nanos() as u64 } /// Get the current time in millis. /// /// # Safety /// - Good until the year 2554 /// - Always use a checked substraction since this can return 0 pub fn now_millis() -> i64 { SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap_or_default() .as_millis() as i64 } /// Get the origin time in nanoseconds. /// /// # Safety /// - Good until the year 2554 /// - Always use a checked substraction since this can return 0 pub fn origin_nanos() -> u64 { TIME_ORIGIN.load(Ordering::Relaxed) } // For accuracy reasons, this function should be executed when the vm is initialized pub fn init() { if TIME_ORIGIN.load(Ordering::Relaxed) == 0 { let time_origin = now_nanos(); TIME_ORIGIN.store(time_origin, Ordering::Relaxed) } } ================================================ FILE: linker/ar ================================================ #!/bin/bash set -e zig ar "$@" ================================================ FILE: linker/cc ================================================ #!/bin/bash set -e target=$1 shift new_array=() for value in "$@" do [[ $value != *self-contained/*crt* ]] && [[ $value != *lgcc_s* ]] && [[ $value != *lc_nonshared* ]] && [[ $value != *latomic* ]] && new_array+=($value) done zig cc "${new_array[@]}" -target $target ================================================ FILE: linker/cc-aarch64-linux-gnu ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cc aarch64-linux-gnu "$@" ================================================ FILE: linker/cc-aarch64-linux-musl ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cc aarch64-linux-musl "$@" ================================================ FILE: linker/cc-x86_64-linux-gnu ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cc x86_64-linux-gnu "$@" ================================================ FILE: linker/cc-x86_64-linux-musl ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cc x86_64-linux-musl "$@" ================================================ FILE: linker/cxx ================================================ #!/bin/bash set -e target=$1 shift new_array=() for value in "$@" do [[ $value != *self-contained/*crt* ]] && [[ $value != *lgcc_s* ]] && new_array+=($value) done zig c++ "${new_array[@]}" -target $target ================================================ FILE: linker/cxx-aarch64-linux-gnu ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cxx aarch64-linux-gnu "$@" ================================================ FILE: linker/cxx-aarch64-linux-musl ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cxx aarch64-linux-musl "$@" ================================================ FILE: linker/cxx-x86_64-linux-gnu ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cxx x86_64-linux-gnu "$@" ================================================ FILE: linker/cxx-x86_64-linux-musl ================================================ #!/bin/bash $(dirname $(realpath "$0"))/cxx x86_64-linux-musl "$@" ================================================ FILE: linker/ranlib ================================================ #!/bin/bash set -e zig ranlib "$@" ================================================ FILE: llrt/Cargo.toml ================================================ [package] name = "llrt" version = "0.8.1-beta" edition = "2021" license-file = "LICENSE" [features] default = ["macro", "tls-ring", "crypto-rust"] macro = ["llrt_core/macro"] lambda = ["llrt_core/lambda"] no-sdk = ["llrt_core/no-sdk"] uncompressed = ["llrt_core/uncompressed"] bindgen = ["llrt_core/bindgen"] # TLS crypto backend features tls-ring = ["llrt_core/tls-ring"] tls-aws-lc = ["llrt_core/tls-aws-lc"] tls-graviola = ["llrt_core/tls-graviola"] tls-openssl = ["llrt_core/tls-openssl"] # Crypto provider features crypto-rust = ["llrt_core/crypto-rust"] crypto-ring = ["llrt_core/crypto-ring"] crypto-ring-rust = ["llrt_core/crypto-ring-rust"] crypto-graviola = ["llrt_core/crypto-graviola"] crypto-graviola-rust = ["llrt_core/crypto-graviola-rust"] crypto-openssl = ["llrt_core/crypto-openssl"] # OpenSSL vendored (builds OpenSSL from source) openssl-vendored = ["llrt_core/openssl-vendored"] [dependencies] constcat = { version = "0.6", default-features = false } crossterm = { version = "0.29", features = [ "events", "windows", ], default-features = false } jiff = { version = "0.2" } llrt_core = { path = "../llrt_core", default-features = false } tokio = { version = "1", features = [ "macros", "rt-multi-thread", ], default-features = false } tracing = { version = "0.1", features = ["log"], default-features = false } tracing-core = { version = "0.1", default-features = false } [target.'cfg(not(target_os = "windows"))'.dependencies] snmalloc-rs = { version = "0.3", features = [ "default", "lto", ], default-features = false } [dev-dependencies] llrt_test = { version = "0.8.1-beta", path = "../libs/llrt_test" } [[bin]] name = "llrt" path = "src/main.rs" ================================================ FILE: llrt/src/base.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 pub use self::base::*; #[allow(clippy::module_inception)] mod base { pub use llrt_core::bytecode; #[cfg(not(feature = "lambda"))] pub use llrt_core::compiler; pub use llrt_core::environment; pub use llrt_core::libs; pub use llrt_core::modules; pub use llrt_core::vm; } pub use llrt_core::VERSION; // rquickjs components #[allow(unused_imports)] pub use llrt_core::{ async_with, atom::PredefinedAtom, context::EvalOptions, function::Rest, runtime_client, AsyncContext, CatchResultExt, Ctx, Error, Object, Promise, }; ================================================ FILE: llrt/src/main.c ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __x86_64__ #define MEMFD_CREATE_SYSCALL_ID 319 #else #define MEMFD_CREATE_SYSCALL_ID 279 #endif int memfd_create_syscall(const char *name, unsigned flags) { return syscall(MEMFD_CREATE_SYSCALL_ID, name, flags); } #define TIMESTAMP_BUFFER_SIZE 50 // Global flag to cache whether logging is enabled static bool logEnabled = false; // Function to initialize the logging flag void initLoggingFlag() { char *envValue = getenv("LLRT_LOG"); logEnabled = (envValue != NULL); } // Function to get a human-readable timestamp void getTimestamp(char *timestampBuffer) { struct timeval tv; struct tm timeinfo; gettimeofday(&tv, NULL); localtime_r(&tv.tv_sec, &timeinfo); strftime(timestampBuffer, 26, "[%Y-%m-%dT%T", &timeinfo); snprintf(timestampBuffer + 20, 6, ".%03ld]", tv.tv_usec / 1000); } // Function to print a log message void printLog(const char *level, const char *format, va_list args) { char timestampBuffer[TIMESTAMP_BUFFER_SIZE]; getTimestamp(timestampBuffer); printf("[%s]%s", level, timestampBuffer); vprintf(format, args); } // Log Info void logInfo(const char *format, ...) { if (logEnabled) { va_list args; va_start(args, format); printLog("INFO", format, args); va_end(args); fflush(stdout); } } // Log Warning void logWarn(const char *format, ...) { if (logEnabled) { va_list args; va_start(args, format); printLog("WARN", format, args); va_end(args); fflush(stdout); } } // Log Error void logError(const char *format, ...) { if (logEnabled) { va_list args; va_start(args, format); printLog("ERROR", format, args); va_end(args); fflush(stdout); } } static uint32_t calculateSum(uint32_t *array, uint8_t size) { uint32_t sum = 0; for (uint8_t i = 0; i < size; i++) { sum += array[i]; } return sum; } static double micro_seconds() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * (1000.0 * 1000.0) + tv.tv_usec; } typedef struct { uint32_t srcSize; uint32_t dstSize; uint32_t id; const void *inputBuffer; const void *outputBuffer; } DecompressThreadArgs; static void *decompressPartial(void *arg) { DecompressThreadArgs *args = (DecompressThreadArgs *)arg; size_t srcSize = args->srcSize; size_t dstSize = args->dstSize; size_t const dSize = ZSTD_decompress((void *)args->outputBuffer, dstSize, args->inputBuffer, srcSize); if (ZSTD_isError(dSize)) { printf("%s!\n", ZSTD_getErrorName(dSize)); return (void *)1; } return (void *)0; } extern char **environ; static void readData( const char *data, uint8_t parts, uint32_t **inputSizes, uint32_t **outputSizes, uint8_t **compressedData, uint32_t *uncompressedSize) { uint32_t metadataSize = sizeof(uint32_t) * parts; // Extract input sizes *inputSizes = (uint32_t *)&data[1]; // Extract output sizes *outputSizes = (uint32_t *)&data[1 + metadataSize]; *uncompressedSize = calculateSum(*outputSizes, parts); // Calculate the offset to the compressed data uint8_t dataOffset = 1 + (2 * metadataSize); *compressedData = (uint8_t *)&data[dataOffset]; } static void decompress(char **uncompressedData, uint32_t *uncompressedSize, int outputFd) { #include "data.c" uint8_t parts = data[0]; uint32_t *inputSizes; uint32_t *outputSizes; uint32_t inputOffset = 0; uint32_t outputOffset = 0; char *uncompressed; uint8_t *compressedData; pthread_t threads[parts]; if (parts > 1) { logInfo("Decompressing using %d threads\n", parts); } else { logInfo("Decompressing\n"); } readData(data, parts, &inputSizes, &outputSizes, &compressedData, uncompressedSize); if (ftruncate(outputFd, *uncompressedSize) == -1) { err(1, "Failed to set file size"); } uncompressed = mmap(NULL, *uncompressedSize, PROT_READ | PROT_WRITE, MAP_SHARED, outputFd, 0); if (uncompressed == MAP_FAILED || !uncompressed) { err(1, "Memory mapping failed: Unable to map %u bytes. Make sure you have enough memory available", *uncompressedSize); } DecompressThreadArgs args[parts]; for (uint32_t i = 0; i < parts; i++) { args[i].inputBuffer = compressedData + inputOffset; args[i].outputBuffer = uncompressed + outputOffset; args[i].srcSize = inputSizes[i]; args[i].dstSize = outputSizes[i]; args[i].id = i; inputOffset += inputSizes[i]; outputOffset += outputSizes[i]; if (parts > 1) { pthread_create(&threads[i], NULL, decompressPartial, (void *)&args[i]); } else { if (decompressPartial((void *)&args[i]) > 0) { err(1, "failed to decompress"); } } } if (parts > 1) { for (uint8_t i = 0; i < parts; i++) { void *result; pthread_join(threads[i], &result); } } *uncompressedData = uncompressed; } int main(int argc, char *argv[]) { initLoggingFlag(); logInfo("Runtime starting\n"); char *tmpAppname = strrchr(argv[0], '/'); char *appname = tmpAppname ? ++tmpAppname : argv[0]; double t0 = micro_seconds(); int outputFd = memfd_create_syscall(appname, 0); if (outputFd == -1) { err(1, "Could not create memfd"); } char *uncompressedData; uint32_t uncompressedSize; decompress(&uncompressedData, &uncompressedSize, outputFd); double t1 = micro_seconds(); logInfo("Runtime starting\n"); logInfo("Extraction time: %10.4f ms\n", (t1 - t0) / 1000.0); if (munmap(uncompressedData, uncompressedSize) == -1) { err(1, "Failed to unmap memory"); } double t2 = micro_seconds(); logInfo("Extraction + write time: %10.4f ms\n", (t2 - t0) / 1000.0); char **new_argv = malloc((size_t)(argc + 1) * sizeof *new_argv); for (uint8_t i = 0; i < argc; ++i) { if (i == 0) { size_t length = strlen(appname) + 2; new_argv[i] = malloc(length); memcpy(new_argv[i], "/", 1); memcpy(new_argv[i] + 1, appname, length); setenv("_", new_argv[i], true); } else { size_t length = strlen(argv[i]) + 1; new_argv[i] = malloc(length); memcpy(new_argv[i], argv[i], length); } } new_argv[argc] = NULL; unsigned long startTime = (unsigned long)(micro_seconds() / 1000.0); char startTimeStr[16]; sprintf(startTimeStr, "%lu", startTime); char *memorySizeStr = getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE"); int memorySize = memorySizeStr ? atoi(memorySizeStr) : 128; double memoryFactor = 0.8; if (memorySize > 512) { memoryFactor = 0.9; } if (memorySize > 1024) { memoryFactor = 0.92; } if (memorySize > 2048) { memoryFactor = 0.95; } char mimallocReserveMemoryMb[16]; sprintf(mimallocReserveMemoryMb, "%iMiB", (int)(memorySize * memoryFactor)); setenv("_START_TIME", startTimeStr, false); setenv("MIMALLOC_RESERVE_OS_MEMORY", mimallocReserveMemoryMb, false); setenv("MIMALLOC_LIMIT_OS_ALLOC", "1", false); logInfo("Starting app\n"); fexecve(outputFd, new_argv, environ); logError("Failed to start executable"); err(1, "fexecve failed"); return 1; } ================================================ FILE: llrt/src/main.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{ env, error::Error, path::{Path, PathBuf}, process::{exit, ExitCode}, string::String, sync::atomic::Ordering, time::Instant, }; mod base; mod minimal_tracer; #[cfg(not(feature = "lambda"))] mod repl; use constcat::concat; use llrt_core::modules::process::EXIT_CODE; use minimal_tracer::MinimalTracer; use tracing::trace; #[cfg(not(feature = "lambda"))] use crate::base::compiler::compile_file; use crate::base::{ bytecode::BYTECODE_EXT, environment::ENV_LLRT_REGISTER_HOOKS, libs::{ logging::print_error_and_exit, utils::{ fs::DirectoryWalker, io::{is_supported_ext, SUPPORTED_EXTENSIONS}, sysinfo::{ARCH, PLATFORM}, }, }, modules::path::name_extname, runtime_client, vm::Vm, VERSION, }; // rquickjs components use crate::base::{async_with, CatchResultExt}; #[cfg(not(target_os = "windows"))] #[global_allocator] static ALLOC: snmalloc_rs::SnMalloc = snmalloc_rs::SnMalloc; #[tokio::main] async fn main() -> Result> { let now = Instant::now(); MinimalTracer::register()?; trace!("Started runtime"); let vm = Vm::new().await?; trace!("Initialized VM in {}ms", now.elapsed().as_millis()); if env::var("AWS_LAMBDA_RUNTIME_API").is_ok() && env::var("_HANDLER").is_ok() { start_runtime(&vm).await } else { start_cli(&vm).await; } vm.idle().await?; Ok(ExitCode::from(EXIT_CODE.load(Ordering::Relaxed))) } pub const VERSION_STRING: &str = concat!("LLRT v", VERSION, " (", PLATFORM, ", ", ARCH, ")"); fn print_version() { println!("{VERSION_STRING}"); } fn usage() { print_version(); println!( r#" Usage: llrt llrt -v | --version llrt -h | --help llrt -e | --eval llrt --import [--import ...] llrt compile input.js [output.lrt] llrt test Options: -v, --version Print version information -h, --help Print this help message -e, --eval Evaluate the provided source code --import Load and execute the module before running the main script. This can be used to register module hooks or modify global behavior. Multiple --import options may be provided and are executed in order. compile Compile JS to bytecode and compress it with zstd: if [output.lrt] is omitted, .lrt is used. lrt file is expected to be executed by the llrt version that created it --executable Create a self-contained executable that includes the LLRT runtime test Run tests with provided arguments: -d "# ); } async fn start_runtime(vm: &Vm) { if let Ok(filename) = env::var(ENV_LLRT_REGISTER_HOOKS) { vm.run_file(filename, true, true).await; } async_with!(vm.ctx => |ctx|{ if let Err(err) = runtime_client::start(&ctx).await.catch(&ctx) { print_error_and_exit(&ctx, err) } }) .await; } async fn start_cli(vm: &Vm) { #[cfg(not(feature = "lambda"))] { use crate::base::bytecode::BYTECODE_SELF_CONTAINED_EXECUTABLE_MARKER; use std::io::{Read, Seek, SeekFrom}; let executable_path = env::current_exe() .unwrap_or_else(|_| PathBuf::from(env::args().next().unwrap_or_default())); trace!( "Checking if {} is a self-contained executable", executable_path.display() ); if let Ok(mut f) = std::fs::File::open(executable_path) { let size_bytes_length: usize = size_of::(); let marker_length: usize = BYTECODE_SELF_CONTAINED_EXECUTABLE_MARKER.len(); let offset: usize = marker_length + size_bytes_length; let negative_offset = -i64::from_ne_bytes(offset.to_ne_bytes()); let _ = f.seek(SeekFrom::End(negative_offset)); let mut end = vec![0; offset]; f.read_exact(&mut end).unwrap_or_else(|error| { eprintln!("Failed to read end of the executable: {error:?}"); exit(1); }); if &end[size_bytes_length..] == BYTECODE_SELF_CONTAINED_EXECUTABLE_MARKER { let size_bytes: [u8; size_of::()] = end[..size_bytes_length].try_into().unwrap_or_else(|error| { eprintln!("Failed to read length bytes: {error:?}"); exit(1); }); let size_number = u64::from_le_bytes(size_bytes); let metadata = f.metadata().unwrap_or_else(|error| { eprintln!("Failed to get metadata of executable: {error:?}"); exit(1); }); let unsigned_offset = u64::from_ne_bytes(offset.to_ne_bytes()); let start = metadata.len() - size_number - unsigned_offset; let _ = f.seek(SeekFrom::Start(start)); let size = usize::from_ne_bytes(size_number.to_ne_bytes()); let mut module = vec![0; size]; f.read_exact(&mut module).unwrap_or_else(|error| { eprintln!("Failed to read embedded module: {error:?}"); exit(1); }); return vm.run_bytecode(&module).await; } } } let args: Vec = env::args().collect(); if args.len() <= 1 { #[cfg(not(feature = "lambda"))] { repl::run_repl(&vm.ctx).await; } #[cfg(feature = "lambda")] { eprintln!("REPL not supported in \"lambda\" version."); exit(1); } #[allow(unreachable_code)] return; } let mut i = 1; while i < args.len() { match args[i].as_str() { "-v" | "--version" => { print_version(); return; }, "-h" | "--help" => { usage(); return; }, "-e" | "--eval" => { if i + 1 >= args.len() { eprintln!("-e requires an argument"); exit(1); } let source = &args[i + 1]; vm.run(source.as_bytes(), false, false).await; return; }, "--import" => { if i + 1 >= args.len() { eprintln!("--import requires a filename"); exit(1); } let file = &args[i + 1]; vm.run_file(file, true, true).await; i += 2; continue; }, "test" => { if let Err(error) = run_tests(vm, &args[i + 1..]).await { eprintln!("{error}"); exit(1); } return; }, "compile" => { #[cfg(not(feature = "lambda"))] { if let Some(filename) = args.get(i + 1) { // Parse args for output_filename and --executable let mut output_filename = String::new(); let mut create_executable = false; // Parse remaining arguments for arg in args.iter().skip(i + 2) { if arg == "--executable" { create_executable = true; } else if output_filename.is_empty() && !arg.starts_with("--") { output_filename = arg.clone(); } } // If no output filename was explicitly provided, generate one if output_filename.is_empty() { let mut buf = PathBuf::from(filename); buf.set_extension("lrt"); output_filename = buf.to_string_lossy().to_string(); } let filename = Path::new(filename); let output_filename = Path::new(&output_filename); if let Err(error) = compile_file(filename, output_filename, create_executable).await { eprintln!("{error}"); exit(1); } return; } else { eprintln!("compile: input filename is required."); exit(1); } } #[cfg(feature = "lambda")] { eprintln!("Not supported in \"lambda\" version."); exit(1); } }, _ => break, } } if i >= args.len() { eprintln!("No main script provided"); exit(1); } let arg = &args[i]; let (_, ext) = name_extname(arg); let filename = Path::new(arg); let file_exists = filename.exists(); let global = ext == ".cjs"; if is_supported_ext(ext) { if file_exists { return vm.run_file(arg, true, global).await; } else { eprintln!("No such file: {}", arg); exit(1); } } else { if file_exists { return vm.run_file(arg, true, false).await; } eprintln!("Unknown command: {}", arg); usage(); exit(1); } } async fn run_tests(vm: &Vm, args: &[std::string::String]) -> Result<(), String> { let mut filters: Vec<&str> = Vec::with_capacity(args.len()); let mut root = "."; let mut skip_next = false; for (i, arg) in args.iter().enumerate() { if skip_next { skip_next = false; continue; } if arg == "-d" { if let Some(dir) = args.get(i + 1) { if !Path::new(dir).exists() { return Err(["\"", dir.as_str(), "\" does not exist"].concat()); } root = dir; skip_next = true; } } else { filters.push(arg) } } let now = Instant::now(); let mut entries: Vec = Vec::with_capacity(100); let has_filters = !filters.is_empty(); if has_filters { trace!("Applying filters: {:?}", filters); } trace!("Scanning directory \"{}\"", root); let mut directory_walker = DirectoryWalker::new(PathBuf::from(root), |name| { name != "node_modules" && !name.starts_with('.') }); directory_walker.set_recursive(true); let test_js_extensions: Vec = SUPPORTED_EXTENSIONS .iter() .filter(|&ext| *ext != BYTECODE_EXT) .map(|ext| [".test", ext].concat()) .collect(); let pwd = env::current_dir().map_err(|e| e.to_string())?; let pwd = pwd.to_string_lossy(); while let Some((entry, _)) = directory_walker.walk().await.map_err(|e| e.to_string())? { if let Some(name) = entry.file_name() { let name = name.to_string_lossy(); let name = name.as_ref(); for ext_name in &test_js_extensions { if name.ends_with(ext_name) && (!has_filters || filters.iter().any(|&f| name.contains(f))) { entries.push([pwd.as_ref(), "/", entry.to_string_lossy().as_ref()].concat()); } } }; } entries.sort_unstable(); trace!("Found tests in {}ms", now.elapsed().as_millis()); vm.run_with(|ctx| { ctx.globals().set("__testEntries", entries)?; Ok(()) }) .await; vm.run( r#" import "llrt:test/index" "#, false, false, ) .await; Ok(()) } ================================================ FILE: llrt/src/minimal_tracer.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ env, fmt::{self, Write}, sync::atomic::{AtomicUsize, Ordering}, write, }; use jiff::Timestamp; use tracing::{field::Visit, Id, Level, Subscriber}; use tracing_core::{span, Field}; use crate::base::environment; pub struct StringVisitor<'a> { string: &'a mut String, } impl<'a> StringVisitor<'a> { pub(crate) fn new(string: &'a mut String) -> Self { StringVisitor { string } } } impl Visit for StringVisitor<'_> { fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { if field.name() == "message" { write!(self.string, "{value:?} ").unwrap(); } else { write!(self.string, "{} = {:?}; ", field.name(), value).unwrap(); } } } struct LogFilter { target: Option, level: Option, } pub struct MinimalTracer { enabled: bool, filters: Vec, } fn string_to_level(string: &str) -> Option { match string.to_lowercase().as_str() { "info" => Some(Level::INFO), "debug" => Some(Level::DEBUG), "warn" | "warning" => Some(Level::WARN), "trace" => Some(Level::TRACE), "error" => Some(Level::ERROR), _ => None, } } impl MinimalTracer { pub fn register() -> Result<(), tracing::subscriber::SetGlobalDefaultError> { let mut enabled = false; let mut filters: Vec = Vec::with_capacity(10); if let Ok(env_value) = env::var(environment::ENV_LLRT_LOG) { enabled = true; for filter in env_value.split(',') { let mut target = Some(filter); let mut level = None; if let Some(equals_index) = target.unwrap().find('=') { let (first, second) = filter.split_at(equals_index); target = Some(first); level = string_to_level(&second[1..]) } let target_level = string_to_level(target.unwrap()); if let Some(target_level) = target_level { level = Some(target_level); target = None; } filters.push(LogFilter { target: target.map(|v| v.to_string()), level, }); } } tracing::subscriber::set_global_default(MinimalTracer { enabled, filters }) } } static AUTO_ID: AtomicUsize = AtomicUsize::new(1); impl Subscriber for MinimalTracer { fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool { if self.enabled { if self.filters.is_empty() { return true; } let mut matches: bool; for filter in &self.filters { matches = true; if let Some(level) = filter.level { if metadata.level() > &level { matches = false; } } if let Some(target) = &filter.target { if !metadata.target().starts_with(target) { matches = false; } } if matches { return true; } } return false; } false } fn new_span(&self, _span: &span::Attributes<'_>) -> span::Id { Id::from_u64(AUTO_ID.fetch_add(1, Ordering::Relaxed) as u64) } fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {} fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {} fn event(&self, event: &tracing::Event<'_>) { let metadata = event.metadata(); let level = metadata.level(); let target = metadata.target(); let mut text = String::new(); let mut visitor = StringVisitor::new(&mut text); event.record(&mut visitor); let timestamp = Timestamp::now(); println!("{timestamp:.3} {level} {target}: {text}"); } fn enter(&self, _span: &span::Id) {} fn exit(&self, _span: &span::Id) {} } ================================================ FILE: llrt/src/repl.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{ collections::VecDeque, env, fs, io::{stdout, IsTerminal}, path::{Path, PathBuf}, }; use crossterm::{ cursor, event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, }; use crate::base::libs::{ logging::format_values, utils::{error::ErrorExtensions, result::ResultExt}, }; // rquickjs components use crate::base::{ async_with, AsyncContext, CatchResultExt, Ctx, Error, EvalOptions, Object, PredefinedAtom, Promise, Rest, }; use crate::VERSION_STRING; async fn process_input(ctx: &Ctx<'_>, input: &str, tty: bool) -> String { // First try to evaluate and format the input let mut options: EvalOptions = EvalOptions::default(); options.promise = true; match async { let promise = ctx.eval_with_options::(input.as_bytes(), options)?; let future = promise.into_future::(); let value = future.await?.get(PredefinedAtom::Value)?; format_values(ctx, Rest(vec![value]), tty, true) } .await .catch(ctx) { Ok(v) => v, Err(error) => { match (|| { let error_value = error.into_value(ctx)?; format_values(ctx, Rest(vec![error_value]), tty, true) })() { Ok(s) => s, Err(err) => err.to_string(), } }, } } pub(crate) async fn run_repl(ctx: &AsyncContext) { let is_tty = stdout().is_terminal(); async_with!(ctx => |ctx| { println!("Welcome to {}\nType \".exit\" or Ctrl+C or Ctrl+D to exit", VERSION_STRING); let history_file = if cfg!(windows) { env::var("APPDATA") .ok() .map(|path| PathBuf::from(path).join("llrtrepl")) } else { env::var("HOME") .ok() .map(|path| PathBuf::from(path).join(".config").join("llrtrepl")) }; let mut persist_history = false; //initialize history let mut history: VecDeque = if let Some(ref history_file) = history_file { if let Some(parent) = history_file.parent() { fs::create_dir_all(parent).or_throw(&ctx)?; } persist_history = true; if history_file.exists() { fs::read_to_string(history_file)? .lines() .map(String::from) .collect() } else { VecDeque::new() } } else { VecDeque::new() }; let mut current_input = String::new(); let mut history_index = history.len(); let mut cursor_pos = 0; println!(""); let exit_repl = || { execute!( stdout(), cursor::MoveToColumn(0), Clear(ClearType::CurrentLine), ) }; enable_raw_mode()?; let mut added_input_chars = false; loop { execute!( stdout(), cursor::MoveToColumn(0), Clear(ClearType::CurrentLine), Print(format!("> {}", current_input)), cursor::MoveToColumn(cursor_pos as u16 + 2) )?; if let Event::Key(KeyEvent { code, modifiers, #[cfg(windows)] kind, .. }) = event::read()? { #[cfg(windows)] if kind == event::KeyEventKind::Release { continue; } match code { KeyCode::Enter => { println!(); let cmd: String = current_input.trim().into(); if !cmd.is_empty() { if cmd == ".exit" { exit_repl()?; break; } execute!(stdout(), cursor::MoveToColumn(0))?; disable_raw_mode()?; let output = process_input(&ctx, &cmd,is_tty).await; println!("{output}"); enable_raw_mode()?; //only push to history if we're not reusing the same command if added_input_chars { history.push_back(cmd); if history.len() > 100 { history.pop_front(); } } history_index = history.len(); current_input.clear(); cursor_pos = 0; if persist_history { write_history(&history, history_file.as_deref()); } added_input_chars = false; } }, KeyCode::Up => { if !history.is_empty() && history_index > 0 { added_input_chars = false; history_index -= 1; current_input = history[history_index].clone(); cursor_pos = current_input.len(); } }, KeyCode::Down => { let history_len = history.len(); if history_len > 0 { match history_index.cmp(&(history_len - 1)) { std::cmp::Ordering::Less => { added_input_chars = false; history_index += 1; current_input = history[history_index].clone(); cursor_pos = current_input.len(); } std::cmp::Ordering::Equal => { added_input_chars = false; history_index = history_len; current_input.clear(); cursor_pos = 0; } _ => {} } } }, KeyCode::Left => { cursor_pos = cursor_pos.saturating_sub(1); }, KeyCode::Right => { if cursor_pos < current_input.len() { cursor_pos += 1; } }, KeyCode::Backspace => { if cursor_pos > 0 { current_input.remove(cursor_pos - 1); cursor_pos -= 1; } }, KeyCode::Char(c) => { if modifiers == KeyModifiers::CONTROL && (c == 'c' || c == 'd') { exit_repl()?; break; } added_input_chars = true; current_input.insert(cursor_pos, c); cursor_pos += 1; }, _ => {}, } } } disable_raw_mode()?; Ok::<_,Error>(()) }) .await .expect("Failed to run REPL") } fn write_history(history: &VecDeque, history_file: Option<&Path>) { if let Some(history_file) = history_file { let _ = fs::write( history_file, history .iter() .map(|s| s.as_str()) .collect::>() .join("\n"), ); } } #[cfg(test)] mod tests { use llrt_core::libs::utils::primordials::{BasePrimordials, Primordial}; use llrt_test::test_async_with; use crate::repl::process_input; #[tokio::test] async fn test_process_input() { test_async_with(|ctx| { Box::pin(async move { BasePrimordials::init(&ctx).unwrap(); let output = process_input(&ctx, "throw new Error('err')", false).await; assert_eq!(output, "Error: err\n at (eval_script:1:10)"); let output = process_input(&ctx, "Promise.reject(1)", false).await; assert_eq!(output, "Promise {\n 1\n}"); let output = process_input(&ctx, "1+1", false).await; assert_eq!(output, "2"); let output = process_input(&ctx, "a", false).await; assert_eq!( output, "ReferenceError: a is not defined\n at (eval_script:1:1)" ); }) }) .await; } } ================================================ FILE: llrt_core/Cargo.toml ================================================ [package] name = "llrt_core" version = "0.8.1-beta" edition = "2021" license-file = "LICENSE" [features] default = ["macro", "tls-ring", "crypto-rust"] lambda = [] no-sdk = [] uncompressed = [] macro = ["rquickjs/macro"] bindgen = ["rquickjs/bindgen"] # TLS crypto backend features tls-ring = ["rustls/ring", "llrt_modules/tls-ring"] tls-aws-lc = ["rustls/aws_lc_rs", "llrt_modules/tls-aws-lc"] tls-graviola = ["llrt_modules/tls-graviola"] tls-openssl = ["llrt_modules/tls-openssl", "dep:openssl"] # Crypto provider features crypto-rust = ["llrt_modules/crypto-rust"] crypto-ring = ["llrt_modules/crypto-ring"] crypto-ring-rust = ["llrt_modules/crypto-ring-rust"] crypto-graviola = ["llrt_modules/crypto-graviola"] crypto-graviola-rust = ["llrt_modules/crypto-graviola-rust"] crypto-openssl = ["llrt_modules/crypto-openssl"] # OpenSSL vendored (builds OpenSSL from source) openssl-vendored = ["openssl/vendored"] [dependencies] bytes = { version = "1", default-features = false } home = { version = "0.5", default-features = false } http-body-util = { version = "0.1", default-features = false } hyper = { version = "1", default-features = false } itoa = { version = "1", default-features = false } jiff = { version = "0.2" } libc = { version = "0.2", default-features = false } llrt_context = { path = "../libs/llrt_context" } llrt_encoding = { path = "../libs/llrt_encoding" } llrt_hooking = { path = "../libs/llrt_hooking" } llrt_json = { path = "../libs/llrt_json" } llrt_logging = { path = "../libs/llrt_logging" } llrt_modules = { path = "../llrt_modules", default-features = false, features = [ "base", "console", ] } llrt_numbers = { path = "../libs/llrt_numbers" } llrt_utils = { path = "../libs/llrt_utils", features = ["all"] } once_cell = { version = "1", features = ["std"], default-features = false } phf = { version = "0.13", default-features = false } quick-xml = { version = "0.39", default-features = false } rand = { version = "0.10.0", features = [ "alloc", "thread_rng", ], default-features = false } rquickjs = { version = "0.11", features = [ "futures", "parallel", "rust-alloc", ], default-features = false } rustls = { version = "0.23", features = ["tls12"], default-features = false } rustls-pemfile = { version = "2", features = ["std"], default-features = false } ryu = { version = "1", default-features = false } simd-json = { version = "0.17", default-features = false } terminal_size = { version = "0.4", default-features = false } tokio = { version = "1", features = ["sync", "time"], default-features = false } tracing = { version = "0.1", features = ["log"], default-features = false } zstd = { version = "0.13", default-features = false } openssl = { version = "0.10", optional = true } [target.'cfg(target_os = "windows")'.dependencies] md-5 = { version = "0.11.0-rc.5", default-features = false } [target.'cfg(not(target_os = "windows"))'.dependencies] md-5 = { version = "0.11.0-rc.5", default-features = false } [build-dependencies] rquickjs = { version = "0.11", default-features = false } phf_codegen = { version = "0.13", default-features = false } llrt_build = { path = "../libs/llrt_build" } uuid = { version = "1", features = ["v4"], default-features = false } walkdir = { version = "2", default-features = false } [dev-dependencies] wiremock = { version = "0.6", default-features = false } llrt_test = { path = "../libs/llrt_test" } ================================================ FILE: llrt_core/build.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{ collections::HashSet, env, error::Error, fs::{self, File}, io::Write, io::{self, BufRead, BufReader, BufWriter}, path::{Path, PathBuf}, process::Command, result::Result as StdResult, }; use rquickjs::{CatchResultExt, CaughtError, Context, Module, Runtime, WriteOptions}; use walkdir::WalkDir; const BUNDLE_JS_DIR: &str = "../bundle/js"; include!("src/bytecode.rs"); macro_rules! info { ($($tokens: tt)*) => { println!("cargo:info={}", format!($($tokens)*)) } } macro_rules! rerun_if_changed { ($file: expr) => { println!("cargo:rerun-if-changed={}", $file) }; } include!("src/compiler_common.rs"); fn main() -> StdResult<(), Box> { llrt_build::set_nightly_cfg(); rerun_if_changed!(BUNDLE_JS_DIR); rerun_if_changed!("Cargo.toml"); let out_dir = env::var("OUT_DIR").unwrap(); // #[cfg(feature = "lambda")] // { generate_sdk_client_endpoint_map(&out_dir)?; //} generate_bytecode_cache(&out_dir)?; Ok(()) } fn generate_sdk_client_endpoint_map(out_dir: &str) -> StdResult<(), Box> { let file = File::open("../sdk.cfg")?; let reader = BufReader::new(file); let sdk_client_endpoints_path = Path::new(out_dir).join("sdk_client_endpoints.rs"); let mut sdk_client_endpoints_file = BufWriter::new(File::create(sdk_client_endpoints_path)?); let mut ph_map = phf_codegen::Map::::new(); for line in reader.lines() { let line = line?; let line = line.trim(); if !line.is_empty() && !line.starts_with('#') { let mut line_iter = line.split(','); let package_name = line_iter.next(); if let Some(package_name) = package_name { let _client_name = line_iter.next(); let _full_sdk = line_iter.next_back(); let sdks_to_init = line_iter.collect::>().join(","); let package_name = package_name.trim_start_matches("client-"); let package_name = package_name.into(); if package_name == sdks_to_init { ph_map.entry(package_name, r#""""#); } else { ph_map.entry(package_name, format!("\"{}\"", sdks_to_init)); } } } } write!( &mut sdk_client_endpoints_file, "// @generated by build.rs\n\npub static SDK_CLIENT_ENDPOINTS: phf::Map<&'static str, &'static str> = {}", ph_map.build() )?; writeln!(&mut sdk_client_endpoints_file, ";")?; sdk_client_endpoints_file.flush()?; Ok(()) } fn generate_bytecode_cache(out_dir: &str) -> StdResult<(), Box> { let resolver = (DummyResolver,); let loader = (DummyLoader,); let rt = Runtime::new()?; rt.set_loader(resolver, loader); let ctx = Context::full(&rt)?; let bytecode_cache_path = Path::new(&out_dir).join("bytecode_cache.rs"); let mut bytecode_cache_file = BufWriter::new(File::create(bytecode_cache_path)?); let mut ph_map = phf_codegen::Map::::new(); let mut lrt_filenames = vec![]; let mut total_bytes: usize = 0; fs::write("../VERSION", env!("CARGO_PKG_VERSION")).expect("Unable to write VERSION file"); #[cfg(feature = "lambda")] let test_file = PathBuf::new().join("@llrt").join("test.js"); ctx.with(|ctx| { for dir_ent in WalkDir::new(BUNDLE_JS_DIR).into_iter().flatten() { let path = dir_ent.path(); let path = path.strip_prefix(BUNDLE_JS_DIR)?.to_owned(); let path_str = path.to_string_lossy().to_string(); if path_str.starts_with("__tests__") || path.extension().unwrap_or_default() != "js" { continue; } #[cfg(feature = "lambda")] { if path == test_file { continue; } } #[cfg(feature = "no-sdk")] { if path_str.starts_with("@aws-sdk") || path_str.starts_with("@smithy") || path_str.starts_with("llrt-chunk-sdk") { continue; } } let source = fs::read_to_string(dir_ent.path()) .unwrap_or_else(|_| panic!("Unable to load: {}", dir_ent.path().to_string_lossy())); let module_name = if !path_str.starts_with("llrt-") { path.with_extension("") .to_string_lossy() .replace('\\', "/") .replace("@llrt/", "llrt:") } else { path.to_string_lossy().to_string().replace('\\', "/") }; info!("Compiling module: {}", module_name); let lrt_path = PathBuf::from(&out_dir).join(path.with_extension(BYTECODE_EXT)); let lrt_filename = lrt_path.to_string_lossy().to_string().replace('\\', "/"); lrt_filenames.push(lrt_filename.clone()); let bytes = { { let module = Module::declare(ctx.clone(), module_name.clone(), source)?; module.write(WriteOptions::default()) } } .catch(&ctx) .map_err(|err| match err { CaughtError::Error(error) => error.to_string(), CaughtError::Exception(ex) => ex.to_string(), CaughtError::Value(value) => format!("{:?}", value), })?; total_bytes += bytes.len(); fs::create_dir_all(lrt_path.parent().unwrap())?; if cfg!(feature = "uncompressed") { let uncompressed = add_bytecode_header(bytes, None); fs::write(&lrt_path, uncompressed)?; } else { fs::write(&lrt_path, bytes)?; } info!("Done!"); ph_map.entry( module_name, format!("include_bytes!(\"{}\")", &lrt_filename), ); } StdResult::<_, Box>::Ok(()) })?; write!( &mut bytecode_cache_file, "// @generated by build.rs\n\npub static BYTECODE_CACHE: phf::Map<&'static str, &[u8]> = {}", ph_map.build() )?; writeln!(&mut bytecode_cache_file, ";")?; bytecode_cache_file.flush()?; info!( "\n===============================\nUncompressed bytecode size: {}\n===============================", human_file_size(total_bytes) ); let compression_dictionary_path = Path::new(out_dir) .join("compression.dict") .to_string_lossy() .to_string(); if cfg!(feature = "uncompressed") { generate_compression_dictionary(&compression_dictionary_path, &lrt_filenames)?; } else { total_bytes = compress_bytecode(compression_dictionary_path, lrt_filenames)?; info!( "\n===============================\nCompressed bytecode size: {}\n===============================", human_file_size(total_bytes) ); } Ok(()) } fn compress_bytecode(dictionary_path: String, source_files: Vec) -> io::Result { generate_compression_dictionary(&dictionary_path, &source_files)?; let mut total_size = 0; let tmp_dir = env::temp_dir(); for filename in source_files { info!("Compressing {}...", filename); let tmp_filename = tmp_dir .join(uuid::Uuid::new_v4().to_string()) .to_string_lossy() .to_string(); fs::copy(&filename, &tmp_filename)?; let uncompressed_file_size = PathBuf::from(&filename).metadata()?.len() as u32; let output = Command::new("zstd") .args([ "--ultra", "-22", "-f", "-D", &dictionary_path, &tmp_filename, "-o", &filename, ]) .output()?; if !output.status.success() { return Err(io::Error::other("Failed to compress file")); } let bytes = fs::read(&filename)?; let compressed = add_bytecode_header(bytes, Some(uncompressed_file_size)); fs::write(&filename, compressed)?; let compressed_file_size = PathBuf::from(&filename).metadata().unwrap().len() as usize; total_size += compressed_file_size; } Ok(total_size) } fn generate_compression_dictionary( dictionary_path: &str, source_files: &[String], ) -> Result<(), io::Error> { info!("Generating compression dictionary..."); let file_count = source_files.len(); let mut dictionary_filenames = source_files.to_owned(); let mut dictionary_file_set: HashSet = HashSet::from_iter(dictionary_filenames.clone()); let mut cmd = Command::new("zstd"); cmd.args([ "--train", "--train-fastcover=steps=60", "--maxdict=40K", "-o", dictionary_path, ]); if file_count < 5 { dictionary_file_set.retain(|file_path| { let metadata = fs::metadata(file_path).unwrap(); let file_size = metadata.len(); file_size >= 1024 // 1 kilobyte = 1024 bytes }); cmd.arg("-B1K"); dictionary_filenames = dictionary_file_set.into_iter().collect(); } cmd.args(&dictionary_filenames); // To avoid cmd being too long to execute let out_dir = env::var("OUT_DIR").unwrap(); let short_source_files: Vec<_> = source_files .iter() .map(|i| { Path::new(i) .strip_prefix(out_dir.clone()) .unwrap() .to_string_lossy() .to_string() }) .collect(); let mut cmd = cmd.current_dir(out_dir).args(short_source_files).spawn()?; let exit_status = cmd.wait()?; if !exit_status.success() { return Err(io::Error::other( "Failed to generate compression dictionary", )); }; Ok(()) } ================================================ FILE: llrt_core/src/builtins_inspect.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use crate::libs::utils::primordials::{BasePrimordials, Primordial}; use rquickjs::{ atom::PredefinedAtom, function::This, object::Accessor, Array, Ctx, Function, Object, Result, Value, }; pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); let primordials = BasePrimordials::get(ctx)?; let custom_inspect_symbol = primordials.symbol_custom_inspect.clone(); // Map primordials .prototype_map .prop(custom_inspect_symbol.clone(), Accessor::from(map_inspect))?; // Set primordials .prototype_set .prop(custom_inspect_symbol.clone(), Accessor::from(set_inspect))?; // DataView let dataview_proto: Object = globals .get::<_, Function>(PredefinedAtom::DataView)? .get(PredefinedAtom::Prototype)?; dataview_proto.prop( custom_inspect_symbol.clone(), Accessor::from(dataview_inspect), )?; // ArrayBuffer let arraybuffer_proto: Object = globals .get::<_, Function>(PredefinedAtom::ArrayBuffer)? .get(PredefinedAtom::Prototype)?; arraybuffer_proto.prop(custom_inspect_symbol, Accessor::from(arraybuffer_inspect))?; Ok(()) } fn map_inspect<'js>(ctx: Ctx<'js>, this: This>) -> Result> { let obj = Object::new(ctx.clone())?; let size: usize = this.get("size")?; obj.set("size", size)?; if size > 0 { let entries_fn: Function = this.get("entries")?; let iterator: Object = entries_fn.call((This(this.0.clone()),))?; let next_fn: Function = iterator.get(PredefinedAtom::Next)?; let entries = Array::new(ctx)?; for i in 0..size.min(100) { let next_result: Object = next_fn.call((This(iterator.clone()),))?; let done: bool = next_result.get(PredefinedAtom::Done)?; if done { break; } let entry: Array = next_result.get(PredefinedAtom::Value)?; entries.set(i, entry)?; } obj.set("entries", entries)?; } Ok(obj) } fn set_inspect<'js>(ctx: Ctx<'js>, this: This>) -> Result> { let obj = Object::new(ctx.clone())?; let size: usize = this.get("size")?; obj.set("size", size)?; if size > 0 { let values_fn: Function = this.get("values")?; let iterator: Object = values_fn.call((This(this.0.clone()),))?; let next_fn: Function = iterator.get("next")?; let values = Array::new(ctx)?; for i in 0..size.min(100) { let next_result: Object = next_fn.call((This(iterator.clone()),))?; let done: bool = next_result.get("done")?; if done { break; } let value: Value = next_result.get("value")?; values.set(i, value)?; } obj.set("values", values)?; } Ok(obj) } fn dataview_inspect<'js>(ctx: Ctx<'js>, this: This>) -> Result> { let obj = Object::new(ctx)?; obj.set("byteLength", this.get::<_, usize>("byteLength")?)?; obj.set("byteOffset", this.get::<_, usize>("byteOffset")?)?; obj.set("buffer", this.get::<_, Object>("buffer")?)?; Ok(obj) } fn arraybuffer_inspect<'js>(ctx: Ctx<'js>, this: This>) -> Result> { let primordials = BasePrimordials::get(&ctx)?; let obj = Object::new(ctx.clone())?; let byte_length: usize = this.get("byteLength")?; let uint8_view: Object = primordials .constructor_uint8array .construct((this.0.clone(),))?; let mut bytes = String::from("<"); for i in 0..byte_length.min(8) { if i > 0 { bytes.push(' '); } let byte: u8 = uint8_view.get(i as u32)?; bytes.push_str(&format!("{:02x}", byte)); } bytes.push('>'); obj.set("uint8Contents", bytes)?; obj.set("byteLength", byte_length)?; Ok(obj) } ================================================ FILE: llrt_core/src/bytecode.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 pub const BYTECODE_VERSION: &str = "lrt01"; pub const BYTECODE_COMPRESSED: u8 = b'c'; pub const BYTECODE_UNCOMPRESSED: u8 = b'u'; pub const BYTECODE_SELF_CONTAINED_EXECUTABLE_MARKER: &[u8] = "lrtx".as_bytes(); macro_rules! define_extension { ($base:ident, $file:ident, $ext:expr) => { #[allow(dead_code)] pub const $base: &str = $ext; #[allow(dead_code)] pub const $file: &str = concat!(".", $ext); }; } define_extension!(BYTECODE_EXT, BYTECODE_FILE_EXT, "lrt"); pub const SIGNATURE_LENGTH: usize = BYTECODE_VERSION.len() + 1; #[allow(dead_code)] pub fn add_bytecode_header(bytes: Vec, file_size: Option) -> Vec { let mut compressed_bytes = Vec::with_capacity(bytes.len()); compressed_bytes.extend_from_slice(BYTECODE_VERSION.as_bytes()); if let Some(file_size) = file_size { compressed_bytes.push(BYTECODE_COMPRESSED); compressed_bytes.extend_from_slice(&file_size.to_le_bytes()); } else { compressed_bytes.push(BYTECODE_UNCOMPRESSED) } compressed_bytes.extend_from_slice(&bytes); compressed_bytes } ================================================ FILE: llrt_core/src/compiler.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ env, fs, io, path::{Path, PathBuf}, }; use rquickjs::{CatchResultExt, Context, Module, Runtime, WriteOptions}; use tracing::trace; use zstd::bulk::Compressor; use crate::bytecode::{add_bytecode_header, BYTECODE_SELF_CONTAINED_EXECUTABLE_MARKER}; use crate::compiler_common::{human_file_size, DummyLoader, DummyResolver}; use crate::libs::{logging::print_error_and_exit, utils::result::ResultExt}; use crate::modules::embedded::COMPRESSION_DICT; fn compress_module(bytes: &[u8]) -> io::Result> { let mut compressor = Compressor::with_dictionary(22, COMPRESSION_DICT)?; let compressed_bytes = compressor.compress(bytes)?; let uncompressed_len = bytes.len() as u32; let compressed = add_bytecode_header(compressed_bytes, Some(uncompressed_len)); Ok(compressed) } pub async fn compile_file( input_filename: &Path, output_filename: &Path, create_executable: bool, ) -> Result<(), Box> { let resolver = (DummyResolver,); let loader = (DummyLoader,); let rt = Runtime::new()?; rt.set_loader(resolver, loader); let ctx = Context::full(&rt)?; let mut total_bytes: usize = 0; let mut compressed_bytes: usize = 0; let mut js_bytes: usize = 0; ctx.with(|ctx| { (|| { let source = fs::read_to_string(input_filename).or_throw_msg( &ctx, &["Unable to load: ", &input_filename.to_string_lossy()].concat(), )?; js_bytes = source.len(); let module_name = input_filename .with_extension("") .to_string_lossy() .to_string(); trace!("Compiling module: {}", module_name); let module = Module::declare(ctx.clone(), module_name, source)?; let bytes = module.write(WriteOptions::default())?; let mut compressed = compress_module(&bytes)?; total_bytes += bytes.len(); compressed_bytes += compressed.len(); if create_executable { // Create the executable by prepending the LLRT runtime to the bytecode let mut exe_content = Vec::new(); let executable_path = env::current_exe() .unwrap_or_else(|_| PathBuf::from(env::args().next().unwrap_or_default())); let mut content = fs::read(executable_path)?; exe_content.append(&mut content); exe_content.append(&mut compressed); let size = u64::try_from(compressed_bytes).unwrap(); let size_bytes = size.to_le_bytes(); exe_content.extend_from_slice(&size_bytes); exe_content.extend_from_slice(BYTECODE_SELF_CONTAINED_EXECUTABLE_MARKER); fs::write(output_filename, &exe_content)?; // Set executable permissions on Unix systems #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(output_filename)?.permissions(); perms.set_mode(0o755); fs::set_permissions(output_filename, perms)?; } } else { fs::write(output_filename, &compressed)?; } Ok(()) })() .catch(&ctx) .unwrap_or_else(|err| print_error_and_exit(&ctx, err)) }); trace!("JS size: {}", human_file_size(js_bytes)); trace!("Bytecode size: {}", human_file_size(total_bytes)); trace!( "Compressed bytecode size: {}", human_file_size(compressed_bytes) ); Ok(()) } ================================================ FILE: llrt_core/src/compiler_common.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Shared with build.rs and compiler.rs pub struct DummyLoader; impl rquickjs::loader::Loader for DummyLoader { fn load<'js>( &mut self, ctx: &rquickjs::Ctx<'js>, name: &str, ) -> rquickjs::Result> { rquickjs::module::Module::declare(ctx.clone(), name, "") } } pub struct DummyResolver; impl rquickjs::loader::Resolver for DummyResolver { fn resolve( &mut self, _ctx: &rquickjs::Ctx<'_>, _base: &str, name: &str, ) -> rquickjs::Result { Ok(name.into()) } } pub fn human_file_size(size: usize) -> String { const UNITS: [&str; 6] = ["B", "kB", "MB", "GB", "TB", "PB"]; let fsize = size as f64; let i = if size == 0 { 0 } else { (fsize.log2() / 1024f64.log2()).floor() as i32 }; let size = fsize / 1024f64.powi(i); let mut result = String::with_capacity(16); // Convert float to integer with 3 decimal places let scaled = (size * 1000.0).round() as i64; let integral = scaled / 1000; let fractional = scaled % 1000; // Custom integer to string conversion fn int_to_string(mut n: i64, buf: &mut String) { if n == 0 { buf.push('0'); return; } let mut digits = [0u8; 20]; let mut i = 0; while n > 0 { digits[i] = (n % 10) as u8; n /= 10; i += 1; } for &digit in digits[..i].iter().rev() { buf.push((digit + b'0') as char); } } // Convert integral part int_to_string(integral, &mut result); result.push('.'); let len_before = result.len(); // Convert fractional part with zero-padding int_to_string(fractional, &mut result); for _ in (result.len() - len_before)..3 { result.push('0'); } result.push(' '); result.push_str(UNITS[i as usize]); result } ================================================ FILE: llrt_core/src/environment.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //network pub const ENV_LLRT_NET_ALLOW: &str = "LLRT_NET_ALLOW"; pub const ENV_LLRT_NET_DENY: &str = "LLRT_NET_DENY"; pub const ENV_LLRT_NET_POOL_IDLE_TIMEOUT: &str = "LLRT_NET_POOL_IDLE_TIMEOUT"; pub const ENV_LLRT_HTTP_VERSION: &str = "LLRT_HTTP_VERSION"; pub const ENV_LLRT_TLS_VERSION: &str = "LLRT_TLS_VERSION"; pub const ENV_LLRT_EXTRA_CA_CERTS: &str = "LLRT_EXTRA_CA_CERTS"; //log pub const ENV_LLRT_LOG: &str = "LLRT_LOG"; //vm pub const ENV_LLRT_GC_THRESHOLD_MB: &str = "LLRT_GC_THRESHOLD_MB"; //runtime client pub const ENV_LLRT_SDK_CONNECTION_WARMUP: &str = "LLRT_SDK_CONNECTION_WARMUP"; //main pub const ENV_LLRT_REGISTER_HOOKS: &str = "LLRT_REGISTER_HOOKS"; ================================================ FILE: llrt_core/src/http.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{env, result::Result as StdResult}; use tracing::warn; use crate::environment; use crate::modules::https::{set_http_version, set_pool_idle_timeout_seconds, HttpVersion}; #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use std::{fs::File, io}; #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use rustls::{pki_types::CertificateDer, version, SupportedProtocolVersion}; #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use crate::modules::tls::{set_extra_ca_certs, set_tls_versions}; #[cfg(feature = "tls-openssl")] use crate::modules::tls::set_tls_version; pub fn init() -> StdResult<(), Box> { if let Some(pool_idle_timeout) = build_pool_idle_timeout() { set_pool_idle_timeout_seconds(pool_idle_timeout); } #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] { if let Some(extra_ca_certs) = build_extra_ca_certs()? { set_extra_ca_certs(extra_ca_certs); } set_tls_versions(build_tls_versions()); } #[cfg(feature = "tls-openssl")] { set_tls_version(build_tls_version_openssl()); } set_http_version(build_http_version()); Ok(()) } fn build_pool_idle_timeout() -> Option { let Ok(env_value) = env::var(environment::ENV_LLRT_NET_POOL_IDLE_TIMEOUT) else { return None; }; let Ok(pool_idle_timeout) = env_value.parse::() else { return None; }; if pool_idle_timeout > 300 { warn!( r#""{}" is exceeds 300s (5min), risking errors due to possible server connection closures."#, environment::ENV_LLRT_NET_POOL_IDLE_TIMEOUT ) } Some(pool_idle_timeout) } #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] fn build_extra_ca_certs() -> StdResult>>, io::Error> { if let Ok(extra_ca_certs) = env::var(environment::ENV_LLRT_EXTRA_CA_CERTS) { if !extra_ca_certs.is_empty() { let file = File::open(extra_ca_certs) .map_err(|_| io::Error::other("Failed to open extra CA certificates file"))?; let mut reader = io::BufReader::new(file); return Ok(Some( rustls_pemfile::certs(&mut reader) .filter_map(io::Result::ok) .collect(), )); } } Ok(None) } #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] fn build_tls_versions() -> Vec<&'static SupportedProtocolVersion> { match env::var(environment::ENV_LLRT_TLS_VERSION).as_deref() { Ok("1.3") => vec![&version::TLS13, &version::TLS12], _ => vec![&version::TLS12], } } #[cfg(feature = "tls-openssl")] fn build_tls_version_openssl() -> Option { match env::var(environment::ENV_LLRT_TLS_VERSION).as_deref() { Ok("1.3") => Some(openssl::ssl::SslVersion::TLS1_3), _ => Some(openssl::ssl::SslVersion::TLS1_2), } } fn build_http_version() -> HttpVersion { match env::var(environment::ENV_LLRT_HTTP_VERSION).as_deref() { Ok("2") => HttpVersion::Http2, _ => HttpVersion::Http1_1, } } ================================================ FILE: llrt_core/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::new_without_default)] #![allow(clippy::inherent_to_string)] pub mod builtins_inspect; pub mod bytecode; pub mod compiler; mod compiler_common; pub mod environment; mod http; pub mod libs; pub mod modules; pub mod runtime_client; mod security; pub mod vm; pub use llrt_modules::VERSION; pub use rquickjs::*; ================================================ FILE: llrt_core/src/libs.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 pub use self::libs::*; #[allow(clippy::module_inception)] mod libs { pub use llrt_context as context; pub use llrt_encoding as encoding; pub use llrt_hooking as hooking; pub use llrt_json as json; pub use llrt_logging as logging; pub use llrt_numbers as numbers; pub use llrt_utils as utils; } ================================================ FILE: llrt_core/src/modules/console.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{ env, fmt::Write as FormatWrite, io::{stderr, stdout, IsTerminal, Write}, sync::atomic::{AtomicBool, AtomicUsize, Ordering}, }; use jiff::Timestamp; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::{Func, Rest}, Array, Class, Ctx, Object, Result, Value, }; use crate::libs::{ json::{escape::escape_json, stringify::json_stringify}, logging::{ build_formatted_string, replace_newline_with_carriage_return, FormatOptions, LogLevel, NEWLINE, TIME_FORMAT, }, utils::{ class::get_class_name, module::{export_default, ModuleInfo}, }, }; use crate::runtime_client; static AWS_LAMBDA_MODE: AtomicBool = AtomicBool::new(false); static AWS_LAMBDA_JSON_LOG_FORMAT: AtomicBool = AtomicBool::new(false); static AWS_LAMBDA_JSON_LOG_LEVEL: AtomicUsize = AtomicUsize::new(LogLevel::Info as usize); fn lambda_mode_initializer() { let aws_lambda_json_log_format = env::var("AWS_LAMBDA_LOG_FORMAT") == Ok("JSON".to_string()); let aws_lambda_log_level = env::var("AWS_LAMBDA_LOG_LEVEL").unwrap_or_default(); let log_level = LogLevel::from_str(&aws_lambda_log_level); AWS_LAMBDA_JSON_LOG_LEVEL.store(log_level as usize, Ordering::Relaxed); AWS_LAMBDA_MODE.store(true, Ordering::Relaxed); AWS_LAMBDA_JSON_LOG_FORMAT.store(aws_lambda_json_log_format, Ordering::Relaxed); } #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] #[rquickjs::class] pub struct Console {} #[rquickjs::methods(rename_all = "camelCase")] impl Console { #[qjs(constructor)] pub fn new() -> Self { // We ignore the parameters for now since we don't support stream Self {} } pub fn log<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log(ctx, args) } pub fn clear(&self) { clear() } pub fn debug<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_debug(ctx, args) } pub fn info<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log(ctx, args) } pub fn trace<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_trace(ctx, args) } pub fn error<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_error(ctx, args) } pub fn warn<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_warn(ctx, args) } pub fn assert<'js>( &self, ctx: Ctx<'js>, expression: bool, args: Rest>, ) -> Result<()> { log_assert(ctx, expression, args) } } pub fn log_fatal<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stderr(), &ctx, args, LogLevel::Fatal) } pub fn log_error<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stderr(), &ctx, args, LogLevel::Error) } fn log_warn<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stderr(), &ctx, args, LogLevel::Warn) } fn log_debug<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stdout(), &ctx, args, LogLevel::Debug) } fn log_trace<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stdout(), &ctx, args, LogLevel::Trace) } fn log_assert<'js>(ctx: Ctx<'js>, expression: bool, args: Rest>) -> Result<()> { if !expression { write_log(stderr(), &ctx, args, LogLevel::Error)? } Ok(()) } fn log<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stdout(), &ctx, args, LogLevel::Info) } fn clear() { if stdout().is_terminal() { let _ = stdout().write_all(b"\x1b[1;1H\x1b[0J"); } } #[allow(clippy::unused_io_amount)] fn write_log<'js, T>( mut output: T, ctx: &Ctx<'js>, args: Rest>, level: LogLevel, ) -> Result<()> where T: Write + IsTerminal, { let is_tty = output.is_terminal(); let mut result = String::new(); let mut is_lambda_mode = AWS_LAMBDA_MODE.load(Ordering::Relaxed); if is_lambda_mode && is_tty { is_lambda_mode = false; } if is_lambda_mode { let is_json_log_format = AWS_LAMBDA_JSON_LOG_FORMAT.load(Ordering::Relaxed); let max_log_level = AWS_LAMBDA_JSON_LOG_LEVEL.load(Ordering::Relaxed); if !write_lambda_log( ctx, &mut result, args, level, is_tty, is_json_log_format, max_log_level, TIME_FORMAT, )? { return Ok(()); } } else { let mut options = FormatOptions::new(ctx, is_tty, true)?; build_formatted_string(&mut result, ctx, args, &mut options)?; } result.push(NEWLINE); //we don't care if output is interrupted let _ = output.write_all(result.as_bytes()); Ok(()) } #[inline(always)] #[allow(clippy::too_many_arguments)] fn write_lambda_log<'js>( ctx: &Ctx<'js>, result: &mut String, args: Rest>, level: LogLevel, is_tty: bool, is_json_log_format: bool, max_log_level: usize, time_format: &str, ) -> Result { let mut is_newline = true; //do not log if we don't meet the log level if is_json_log_format && (level.clone() as usize) < max_log_level { return Ok(false); } result.reserve(64); if !is_tty { is_newline = false; } let current_time = Timestamp::now(); let formatted_time = current_time.strftime(time_format); let request_id = runtime_client::LAMBDA_REQUEST_ID.read().unwrap(); if is_json_log_format { result.push('{'); //time result.push_str("\"time\":\""); write!(result, "{}", formatted_time).unwrap(); result.push_str("\","); //request id if let Some(id) = request_id.as_ref() { result.push_str("\"requestId\":\""); result.push_str(id); result.push_str("\","); } //level result.push_str("\"level\":\""); result.push_str(&level.to_string()); result.push('\"'); } else { write!(result, "{}", formatted_time).unwrap(); result.push('\t'); match request_id.as_ref() { Some(id) => result.push_str(id), None => result.push_str("n/a"), } result.push('\t'); result.push_str(&level.to_string()); result.push('\t'); } if is_json_log_format { let mut values_string = String::with_capacity(64); if args.0.len() == 1 { let mut first_arg = unsafe { args.0.first().unwrap_unchecked() }.clone(); if first_arg.is_error() || first_arg.is_exception() { if let Some(exception) = first_arg.as_exception() { let obj = Object::new(ctx.clone())?; obj.set("errorType", get_class_name(exception.as_value()))?; if let Some(message) = exception.message() { obj.set("errorMessage", message)?; } if let Some(stack) = exception.stack() { let stack_object = Array::new(ctx.clone())?; for (i, trace) in stack.split('\n').enumerate() { stack_object.set(i, String::from(trace))?; } obj.set("stackTrace", stack_object)?; } first_arg = obj.into_value(); } } if let Some(json_string) = json_stringify(ctx, first_arg)? { //message result.push(','); result.push_str("\"message\":"); result.push_str(&json_string); } } else { //message result.push(','); result.push_str("\"message\":\""); let mut exception = None; let mut options = FormatOptions::new(ctx, is_tty, true)?; for arg in args.0.iter() { if arg.is_error() && exception.is_none() { let exception_value = arg.clone(); exception = Some(exception_value.into_exception().unwrap()); break; } } build_formatted_string(&mut values_string, ctx, args, &mut options)?; result.push_str(&escape_json(values_string.as_bytes())); result.push('\"'); if let Some(exception) = exception { //error type result.push_str(",\"errorType\":\""); result .push_str(&get_class_name(exception.as_value())?.unwrap_or("Exception".into())); result.push_str("\","); //error message if let Some(message) = exception.message() { result.push_str("\"errorMessage\":\""); result.push_str(&message); result.push_str("\","); } //stack trace result.push_str("\"stackTrace\":["); let mut write_comma = false; if let Some(stack) = exception.stack() { if !stack.is_empty() { for trace in stack.split('\n') { if write_comma { result.push(','); } result.push('\"'); result.push_str(trace); result.push('\"'); write_comma = true; } } } result.push(']'); } } result.push('}'); } else { let mut options = FormatOptions::new(ctx, is_tty && !is_json_log_format, is_newline)?; build_formatted_string(result, ctx, args, &mut options)?; replace_newline_with_carriage_return(result); } Ok(true) } pub struct ConsoleModule; impl ModuleDef for ConsoleModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(Console))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { Class::::define(default)?; Ok(()) }) } } impl From for ModuleInfo { fn from(val: ConsoleModule) -> Self { ModuleInfo { name: "console", module: val, } } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { lambda_mode_initializer(); let globals = ctx.globals(); let console = Object::new(ctx.clone())?; console.set("assert", Func::from(log_assert))?; console.set("clear", Func::from(clear))?; console.set("debug", Func::from(log_debug))?; console.set("error", Func::from(log_error))?; console.set("info", Func::from(log))?; console.set("log", Func::from(log))?; console.set("trace", Func::from(log_trace))?; console.set("warn", Func::from(log_warn))?; globals.set("console", console)?; Ok(()) } #[cfg(test)] mod tests { use llrt_test::test_sync_with; use rquickjs::{function::Rest, Error, IntoJs, Null, Object, Undefined, Value}; use crate::libs::{ json::stringify::json_stringify_replacer_space, logging::LogLevel, utils::primordials::{BasePrimordials, Primordial}, }; use crate::modules::console::write_lambda_log; #[tokio::test] async fn json_log_format() { test_sync_with(|ctx| { BasePrimordials::init(&ctx)?; let write_log = |args| { let mut result = String::new(); write_lambda_log( &ctx, &mut result, Rest(args), LogLevel::Info, false, true, LogLevel::Info as usize, "", )?; //validate json ctx.json_parse(result.clone())?; Ok::<_, Error>(result) }; assert_eq!( write_log(["Hello".into_js(&ctx)?].into())?, r#"{"time":"","level":"INFO","message":"Hello"}"# ); assert_eq!( write_log([1.into_js(&ctx)?].into())?, r#"{"time":"","level":"INFO","message":1}"# ); assert_eq!( write_log([true.into_js(&ctx)?].into())?, r#"{"time":"","level":"INFO","message":true}"# ); assert_eq!( write_log([Undefined.into_js(&ctx)?].into())?, r#"{"time":"","level":"INFO"}"# ); assert_eq!( write_log([Null.into_js(&ctx)?].into())?, r#"{"time":"","level":"INFO","message":null}"# ); let obj = Object::new(ctx.clone())?; obj.set("a", 1)?; obj.set("b", "Hello")?; assert_eq!( write_log([obj.clone().into_value()].into())?, r#"{"time":"","level":"INFO","message":{"a":1,"b":"Hello"}}"# ); //validate second argument passed assert_eq!( write_log([obj.into_value(), true.into_js(&ctx)?].into())?, r#"{"time":"","level":"INFO","message":"{\n a: 1,\n b: 'Hello'\n} true"}"# ); //single error let e1:Value = ctx.eval(r#"new ReferenceError("some reference error")"#)?; assert_eq!( write_log([e1.clone()].into())?, r#"{"time":"","level":"INFO","message":{"errorType":"ReferenceError","errorMessage":"some reference error","stackTrace":[" at (eval_script:1:4)",""]}}"# ); //validate many args with additional errors let e2:Value = ctx.eval(r#"new SyntaxError("some syntax error")"#)?; assert_eq!( write_log(["errors logged".into_js(&ctx)?, e1, e2].into())?, r#"{"time":"","level":"INFO","message":"errors logged ReferenceError: some reference error\n at (eval_script:1:4) SyntaxError: some syntax error\n at (eval_script:1:4)","errorType":"ReferenceError","errorMessage":"some reference error","stackTrace":[" at (eval_script:1:4)",""]}"# ); Ok(()) }) .await; } #[tokio::test] async fn standard_log_format() { test_sync_with(|ctx| { BasePrimordials::init(&ctx)?; let write_log = |args| { let mut result = String::new(); write_lambda_log( &ctx, &mut result, Rest(args), LogLevel::Info, false, false, LogLevel::Info as usize, "", )?; Ok::<_, Error>(result) }; assert_eq!( write_log(["Hello".into_js(&ctx)?].into())?, "\tn/a\tINFO\tHello" ); assert_eq!( write_log([1.into_js(&ctx)?].into())?, "\tn/a\tINFO\t1" ); assert_eq!( write_log([true.into_js(&ctx)?].into())?, "\tn/a\tINFO\ttrue" ); assert_eq!( write_log([Undefined.into_js(&ctx)?].into())?, "\tn/a\tINFO\tundefined" ); assert_eq!( write_log([Null.into_js(&ctx)?].into())?, "\tn/a\tINFO\tnull" ); let obj = Object::new(ctx.clone())?; obj.set("a", 1)?; obj.set("b", "Hello")?; assert_eq!( write_log([obj.clone().into_value()].into())?, "\tn/a\tINFO\t{\r a: 1,\r b: 'Hello'\r}" ); //validate second argument passed assert_eq!( write_log([obj.clone().into_value(), true.into_js(&ctx)?].into())?, "\tn/a\tINFO\t{\r a: 1,\r b: 'Hello'\r} true" ); //single error let e1:Value = ctx.eval(r#"new ReferenceError("some reference error")"#)?; assert_eq!( write_log([e1.clone()].into())?, "\tn/a\tINFO\tReferenceError: some reference error\r at (eval_script:1:4)" ); //validate many args with additional errors let e2:Value = ctx.eval(r#"new SyntaxError("some syntax error")"#)?; assert_eq!( write_log(["errors logged".into_js(&ctx)?, e1, e2].into())?, "\tn/a\tINFO\terrors logged ReferenceError: some reference error\r at (eval_script:1:4) SyntaxError: some syntax error\r at (eval_script:1:4)" ); //newline replacement assert_eq!( write_log([ "event:".into_js(&ctx)?, json_stringify_replacer_space(&ctx, obj.into_value(), None, Some(" ".into()))?.into_js(&ctx)? ].into())?, "\tn/a\tINFO\tevent: {\r \"a\": 1,\r \"b\": \"Hello\"\r}" ); Ok(()) }) .await; } } ================================================ FILE: llrt_core/src/modules/embedded/loader.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{io, result::Result as StdResult}; use once_cell::sync::Lazy; use rquickjs::{loader::Loader, Ctx, Error, Module, Object, Result}; use tracing::trace; use zstd::{bulk::Decompressor, dict::DecoderDictionary}; use crate::bytecode::{ BYTECODE_COMPRESSED, BYTECODE_FILE_EXT, BYTECODE_UNCOMPRESSED, BYTECODE_VERSION, SIGNATURE_LENGTH, }; use super::{BYTECODE_CACHE, CJS_IMPORT_PREFIX, CJS_LOADER_PREFIX, COMPRESSION_DICT}; static DECOMPRESSOR_DICT: Lazy = Lazy::new(|| DecoderDictionary::copy(COMPRESSION_DICT)); #[cfg(feature = "lambda")] include!(concat!(env!("OUT_DIR"), "/sdk_client_endpoints.rs")); #[derive(Debug, Default)] pub struct EmbeddedLoader; impl EmbeddedLoader { pub fn load_bytecode_module<'js>(ctx: Ctx<'js>, buf: &[u8]) -> Result> { let bytes = Self::get_module_bytecode(buf)?; unsafe { Module::load(ctx, &bytes) } } #[inline] pub fn uncompressed_size(input: &[u8]) -> StdResult<(usize, &[u8]), io::Error> { let size = input.get(..4).ok_or(io::ErrorKind::InvalidInput)?; let size: &[u8; 4] = size.try_into().map_err(|_| io::ErrorKind::InvalidInput)?; let uncompressed_size = u32::from_le_bytes(*size) as usize; let rest = &input[4..]; Ok((uncompressed_size, rest)) } fn get_module_bytecode(input: &[u8]) -> Result> { let (_, compressed, input) = Self::get_bytecode_signature(input)?; if compressed { let (size, input) = Self::uncompressed_size(input)?; let mut buf = Vec::with_capacity(size); let mut decompressor = Decompressor::with_prepared_dictionary(&DECOMPRESSOR_DICT)?; decompressor.decompress_to_buffer(input, &mut buf)?; return Ok(buf); } Ok(input.to_vec()) } fn get_bytecode_signature(input: &[u8]) -> StdResult<(&[u8], bool, &[u8]), io::Error> { let raw_signature = input .get(..SIGNATURE_LENGTH) .ok_or(io::Error::new::( io::ErrorKind::InvalidInput, "Invalid bytecode signature length".into(), ))?; let (last, signature) = raw_signature.split_last().unwrap(); if signature != BYTECODE_VERSION.as_bytes() { return Err(io::Error::new::( io::ErrorKind::InvalidInput, "Invalid bytecode version".into(), )); } let mut compressed = None; if *last == BYTECODE_COMPRESSED { compressed = Some(true) } else if *last == BYTECODE_UNCOMPRESSED { compressed = Some(false) } let rest = &input[SIGNATURE_LENGTH..]; Ok(( signature, compressed.ok_or(io::Error::new::( io::ErrorKind::InvalidInput, "Invalid bytecode signature".into(), ))?, rest, )) } fn normalize_name(name: &str) -> (bool, bool, &str, &str) { if !name.starts_with("__") { // If name doesn’t start with "__", return defaults return (false, false, name, name); } if let Some(cjs_path) = name.strip_prefix(CJS_IMPORT_PREFIX) { // If it starts with CJS_IMPORT_PREFIX, mark as from_cjs_import return (true, false, name, cjs_path); } if let Some(cjs_path) = name.strip_prefix(CJS_LOADER_PREFIX) { // If it starts with CJS_LOADER_PREFIX, mark as is_cjs return (false, true, cjs_path, cjs_path); } // Default return if no prefixes match (false, false, name, name) } fn load_module<'js>(name: &str, ctx: &Ctx<'js>) -> Result<(Module<'js>, Option)> { let ctx = ctx.clone(); let (_, _, normalized_name, path) = Self::normalize_name(name); #[cfg(feature = "lambda")] #[cfg(test)] init_client_connection(&ctx, path)?; if let Some(bytes) = BYTECODE_CACHE.get(path) { #[cfg(not(test))] #[cfg(feature = "lambda")] init_client_connection(&ctx, path)?; trace!("Loading embedded module: {}\n", path); return Ok((Self::load_bytecode_module(ctx, bytes)?, Some(path.into()))); } let bytes = std::fs::read(path)?; let bytes: &[u8] = &bytes; if normalized_name.ends_with(BYTECODE_FILE_EXT) { trace!("Loading binary module: {}\n", path); return Ok((Self::load_bytecode_module(ctx, bytes)?, Some(path.into()))); } Err(Error::new_loading_message(path, "unable to load")) } } impl Loader for EmbeddedLoader { fn load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result> { let (module, url) = Self::load_module(name, ctx)?; if let Some(url) = url { let meta: Object = module.meta()?; meta.prop("url", url)?; } Ok(module) } } #[cfg(test)] #[cfg(feature = "lambda")] fn init_client_connection(ctx: &Ctx<'_>, specifier: &str) -> Result<()> { use crate::runtime_client::{check_client_inited, mark_client_inited}; use rquickjs::qjs; if specifier.ends_with("sdk-runtime-init.mjs") { let rt = unsafe { qjs::JS_GetRuntime(ctx.as_raw().as_ptr()) }; let rt_ptr = rt as usize; //hack to move, is safe since runtime is still alive in spawn if !check_client_inited(rt, "endpoint") { tokio::task::spawn(async move { tokio::time::sleep(std::time::Duration::from_millis(100)).await; mark_client_inited(rt_ptr as _); }); } }; Ok(()) } #[cfg(not(test))] #[cfg(feature = "lambda")] fn init_client_connection(ctx: &Ctx<'_>, specifier: &str) -> Result<()> { use std::{ env, time::{Duration, Instant}, }; use http_body_util::BodyExt; use hyper::Uri; use rquickjs::qjs; use crate::environment::ENV_LLRT_SDK_CONNECTION_WARMUP; use crate::libs::utils::result::ResultExt; use crate::modules::https::HTTP_CLIENT; use crate::runtime_client::{check_client_inited, mark_client_inited}; let disable_warmup = env::var(ENV_LLRT_SDK_CONNECTION_WARMUP).unwrap_or_default(); if disable_warmup == "0" || disable_warmup == "false" { return Ok(()); } let Some(sdk_import) = specifier.strip_prefix("@aws-sdk/") else { return Ok(()); }; let client_name = sdk_import.trim_start_matches("client-"); let Some(endpoint) = SDK_CLIENT_ENDPOINTS.get(client_name) else { return Ok(()); }; let endpoint = if endpoint.is_empty() { client_name } else { endpoint }; let mut region = env::var("AWS_REGION").unwrap(); let mut region_separator = "."; //do not use regional endpoint for global services https://docs.aws.amazon.com/general/latest/gr/rande.html#global-endpoints if matches!( client_name, "iam" | "route-53" | "cloudfront" | "waf" | "shield" | "global-accelerator" | "organizations" | "networkmanager" ) { // the latency to do a roundtrip to a global endpoint can exceed the savings if the region is not us-east-1 if region != "us-east-1" { trace!( "Ignoring init for global client: {} because region: {} not geographically close to us-east-1", client_name, region ); return Ok(()); } region_separator = ""; region.clear(); }; let rt = unsafe { qjs::JS_GetRuntime(ctx.as_raw().as_ptr()) }; let rt_ptr = rt as usize; //hack to move, is safe since runtime is still alive in spawn if check_client_inited(rt, endpoint) { return Ok(()); } let client = HTTP_CLIENT.as_ref().or_throw(ctx)?; trace!("Started client init {}", client_name); let url_string = [ "https://", endpoint, region_separator, ®ion, ".amazonaws.com/sping", ] .concat(); if let Ok(url) = url_string.parse::() { tokio::task::spawn(async move { let start = Instant::now(); let get_future = client.get(url); let result = tokio::time::timeout(Duration::from_secs(1), get_future).await; if let Ok(Ok(mut res)) = result { if res.body_mut().collect().await.is_ok() { trace!("Client connection initialized in {:?}", start.elapsed()) } else { trace!("Failed to connect for client init {}", &url_string) } } else { trace!("Failed to connect for client init {}", &url_string) }; mark_client_inited(rt_ptr as _); }); } else { trace!("Failed to parse url for init"); mark_client_inited(rt_ptr as _); } Ok(()) } ================================================ FILE: llrt_core/src/modules/embedded/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::env; use rquickjs::{Ctx, Function, Result}; use crate::modules::{CJS_IMPORT_PREFIX, CJS_LOADER_PREFIX}; use self::resolver::embedded_resolve; pub mod loader; pub mod resolver; pub static COMPRESSION_DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/compression.dict")); include!(concat!(env!("OUT_DIR"), "/bytecode_cache.rs")); pub fn init(ctx: &Ctx) -> Result<()> { let globals = ctx.globals(); let embedded_hook = Function::new(ctx.clone(), move |x: String, y: String| { embedded_resolve(&x, &y).map(|res| res.into_owned()) })?; globals.set("__require_hook", embedded_hook)?; Ok(()) } ================================================ FILE: llrt_core/src/modules/embedded/resolver.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::borrow::Cow; use rquickjs::{loader::Resolver, Ctx, Error, Result}; use tracing::trace; use crate::modules::path; use super::{BYTECODE_CACHE, CJS_IMPORT_PREFIX}; #[derive(Debug, Default)] pub struct EmbeddedResolver; #[allow(clippy::manual_strip)] impl Resolver for EmbeddedResolver { fn resolve(&mut self, _ctx: &Ctx, base: &str, name: &str) -> Result { let name = name.trim_start_matches(CJS_IMPORT_PREFIX); let name = name.trim_start_matches("node:").trim_end_matches("/"); let base = base.trim_start_matches(CJS_IMPORT_PREFIX); trace!("Try resolve '{}' from '{}'", name, base); embedded_resolve(name, base).map(|name| name.into_owned()) } } pub fn embedded_resolve<'a>(x: &'a str, y: &str) -> Result> { trace!("embedded_resolve(x, y):({}, {})", x, y); // If X is a bytecode cache, if BYTECODE_CACHE.contains_key(x) { trace!("+- Resolved by `BYTECODE_CACHE`: {}", x); return Ok(x.into()); } let x_normalized = path::normalize(x); if BYTECODE_CACHE.contains_key(&x_normalized) { trace!("+- Resolved by `BYTECODE_CACHE`: {}", x_normalized); return Ok(x_normalized.into()); } Err(Error::new_resolving(y.to_string(), x.to_string())) } ================================================ FILE: llrt_core/src/modules/js/@llrt/expect/jest-asymmetric-matchers.ts ================================================ /* MIT License Copyright (c) 2021-Present Anthony Fu Copyright (c) 2021-Present Matias Capeletto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Extracted and modified from Vitest: https://github.com/vitest-dev/vitest/blob/7a31a1ae4223aed3adf260e63ac3b3f7fab3c9d7/packages/expect/src/jest-asymmetric-matchers.ts import ChaiPlugin = Chai.ChaiPlugin; import { equals, isA, pluralize } from "./jest-utils"; import { stringify } from "./stringify"; export interface AsymmetricMatcherInterface { asymmetricMatch: (other: unknown) => boolean; toString: () => string; getExpectedType?: () => string; toAsymmetricMatcher?: () => string; } export abstract class AsymmetricMatcher implements AsymmetricMatcherInterface { // should have "jest" to be compatible with its ecosystem $$typeof = Symbol.for("jest.asymmetricMatcher"); constructor( protected sample: T, protected inverse = false ) {} // protected getMatcherContext(expect?: Chai.ExpectStatic): State { // return { // equals, // isNot: this.inverse, // customTesters: getCustomEqualityTesters(), // utils: { // ...getMatcherUtils(), // diff, // stringify, // iterableEquality, // subsetEquality, // }, // } // } abstract asymmetricMatch(other: unknown): boolean; abstract toString(): string; getExpectedType?(): string; toAsymmetricMatcher?(): string; // implement custom chai/loupe inspect for better AssertionError.message formatting // https://github.com/chaijs/loupe/blob/9b8a6deabcd50adc056a64fb705896194710c5c6/src/index.ts#L29 [Symbol.for("chai/inspect")](options: { depth: number; truncate: number }) { // minimal pretty-format with simple manual truncation const result = stringify(this, options.depth, { min: true }); if (result.length <= options.truncate) return result; return `${this.toString()}{…}`; } } export class StringContaining extends AsymmetricMatcher { constructor(sample: string, inverse = false) { if (!isA("String", sample)) throw new Error("Expected is not a string"); super(sample, inverse); } asymmetricMatch(other: string) { const result = isA("String", other) && other.includes(this.sample); return this.inverse ? !result : result; } toString() { return `String${this.inverse ? "Not" : ""}Containing`; } getExpectedType() { return "string"; } } export class Anything extends AsymmetricMatcher { asymmetricMatch(other: unknown) { return other != null; } toString() { return "Anything"; } toAsymmetricMatcher() { return "Anything"; } } export class ObjectContaining extends AsymmetricMatcher< Record > { constructor(sample: Record, inverse = false) { super(sample, inverse); } getPrototype(obj: object) { if (Object.getPrototypeOf) return Object.getPrototypeOf(obj); if (obj.constructor.prototype === obj) return null; return obj.constructor.prototype; } hasProperty(obj: object | null, property: string): boolean { if (!obj) return false; if (Object.prototype.hasOwnProperty.call(obj, property)) return true; return this.hasProperty(this.getPrototype(obj), property); } asymmetricMatch(other: any) { if (typeof this.sample !== "object") { throw new TypeError( `You must provide an object to ${this.toString()}, not '${typeof this .sample}'.` ); } let result = true; // const matcherContext = this.getMatcherContext() for (const property in this.sample) { if ( !this.hasProperty(other, property) || !equals(this.sample[property], other[property], []) ) { result = false; break; } } return this.inverse ? !result : result; } toString() { return `Object${this.inverse ? "Not" : ""}Containing`; } getExpectedType() { return "object"; } } export class ArrayContaining extends AsymmetricMatcher> { constructor(sample: Array, inverse = false) { super(sample, inverse); } asymmetricMatch(other: Array) { if (!Array.isArray(this.sample)) { throw new TypeError( `You must provide an array to ${this.toString()}, not '${typeof this .sample}'.` ); } const result = this.sample.length === 0 || (Array.isArray(other) && this.sample.every((item) => other.some((another) => equals(item, another, [])) )); return this.inverse ? !result : result; } toString() { return `Array${this.inverse ? "Not" : ""}Containing`; } getExpectedType() { return "array"; } } export class Any extends AsymmetricMatcher { constructor(sample: unknown) { if (typeof sample === "undefined") { throw new TypeError( "any() expects to be passed a constructor function. " + "Please pass one or use anything() to match any object." ); } super(sample); } fnNameFor(func: Function) { if (func.name) return func.name; const functionToString = Function.prototype.toString; const matches = functionToString .call(func) .match(/^(?:async)?\s*function\s*\*?\s*([\w$]+)\s*\(/); return matches ? matches[1] : ""; } asymmetricMatch(other: unknown) { if (this.sample === String) return typeof other == "string" || other instanceof String; if (this.sample === Number) return typeof other == "number" || other instanceof Number; if (this.sample === Function) return typeof other == "function" || other instanceof Function; if (this.sample === Boolean) return typeof other == "boolean" || other instanceof Boolean; if (this.sample === BigInt) return typeof other == "bigint" || other instanceof BigInt; if (this.sample === Symbol) return typeof other == "symbol" || other instanceof Symbol; if (this.sample === Object) return typeof other == "object"; return other instanceof this.sample; } toString() { return "Any"; } getExpectedType() { if (this.sample === String) return "string"; if (this.sample === Number) return "number"; if (this.sample === Function) return "function"; if (this.sample === Object) return "object"; if (this.sample === Boolean) return "boolean"; return this.fnNameFor(this.sample); } toAsymmetricMatcher() { return `Any<${this.fnNameFor(this.sample)}>`; } } export class StringMatching extends AsymmetricMatcher { constructor(sample: string | RegExp, inverse = false) { if (!isA("String", sample) && !isA("RegExp", sample)) throw new Error("Expected is not a String or a RegExp"); super(new RegExp(sample), inverse); } asymmetricMatch(other: string) { const result = isA("String", other) && this.sample.test(other); return this.inverse ? !result : result; } toString() { return `String${this.inverse ? "Not" : ""}Matching`; } getExpectedType() { return "string"; } } class CloseTo extends AsymmetricMatcher { private readonly precision: number; constructor(sample: number, precision = 2, inverse = false) { if (!isA("Number", sample)) throw new Error("Expected is not a Number"); if (!isA("Number", precision)) throw new Error("Precision is not a Number"); super(sample); this.inverse = inverse; this.precision = precision; } asymmetricMatch(other: number) { if (!isA("Number", other)) return false; let result = false; if ( other === Number.POSITIVE_INFINITY && this.sample === Number.POSITIVE_INFINITY ) { result = true; // Infinity - Infinity is NaN } else if ( other === Number.NEGATIVE_INFINITY && this.sample === Number.NEGATIVE_INFINITY ) { result = true; // -Infinity - -Infinity is NaN } else { result = Math.abs(this.sample - other) < 10 ** -this.precision / 2; } return this.inverse ? !result : result; } toString() { return `Number${this.inverse ? "Not" : ""}CloseTo`; } override getExpectedType() { return "number"; } override toAsymmetricMatcher(): string { return [ this.toString(), this.sample, `(${pluralize("digit", this.precision)})`, ].join(" "); } } export const JestAsymmetricMatchers: ChaiPlugin = (chai, utils) => { utils.addMethod(chai.expect, "anything", () => new Anything()); utils.addMethod(chai.expect, "any", (expected: unknown) => new Any(expected)); utils.addMethod( chai.expect, "stringContaining", (expected: string) => new StringContaining(expected) ); utils.addMethod( chai.expect, "objectContaining", (expected: any) => new ObjectContaining(expected) ); utils.addMethod( chai.expect, "arrayContaining", (expected: Array) => new ArrayContaining(expected) ); utils.addMethod( chai.expect, "stringMatching", (expected: any) => new StringMatching(expected) ); utils.addMethod( chai.expect, "closeTo", (expected: any, precision?: number) => new CloseTo(expected, precision) ); // defineProperty does not work (chai.expect as any).not = { stringContaining: (expected: string) => new StringContaining(expected, true), objectContaining: (expected: any) => new ObjectContaining(expected, true), arrayContaining: (expected: Array) => new ArrayContaining(expected, true), stringMatching: (expected: string | RegExp) => new StringMatching(expected, true), closeTo: (expected: any, precision?: number) => new CloseTo(expected, precision, true), }; }; ================================================ FILE: llrt_core/src/modules/js/@llrt/expect/jest-expect.ts ================================================ /* MIT License Copyright (c) 2021-Present Anthony Fu Copyright (c) 2021-Present Matias Capeletto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Extracted and modified from Vitest: https://github.com/vitest-dev/vitest/blob/a199ac2dd1322d7839d4d1350c983070da546805/packages/expect/src/jest-expect.ts import { arrayBufferEquality, equals as jestEquals, generateToBeMessage, iterableEquality, sparseArrayEquality, subsetEquality, typeEquality, } from "./jest-utils"; import ChaiPlugin = Chai.ChaiPlugin; import Assertion = Chai.Assertion; import { AsymmetricMatcher } from "./jest-asymmetric-matchers"; // Jest Expect Compact export const JestChaiExpect: ChaiPlugin = (chai, utils) => { const { AssertionError } = chai; // const c = () => getColors() const customTesters: (( a: any, b: any, customTesters?: Array, aStack?: Array, bStack?: Array ) => boolean | undefined)[] = []; function def( name: string | string[], fn: (this: Chai.AssertionStatic & Assertion, ...args: any[]) => any ) { const addMethod = (n: string) => { // const softWrapper = wrapSoft(utils, fn) utils.addMethod(chai.Assertion.prototype, n, fn); // utils.addMethod((globalThis as any)[JEST_MATCHERS_OBJECT].matchers, n, softWrapper) }; if (Array.isArray(name)) name.forEach((n) => addMethod(n)); else addMethod(name); } (["throw", "throws", "Throw"] as const).forEach((m) => { utils.overwriteMethod(chai.Assertion.prototype, m, (_super: any) => { return function ( this: Chai.Assertion & Chai.AssertionStatic, ...args: any[] ) { const promise = utils.flag(this, "promise"); const object = utils.flag(this, "object"); const isNot = utils.flag(this, "negate") as boolean; if (promise === "rejects") { utils.flag(this, "object", () => { throw object; }); } // if it got here, it's already resolved // unless it tries to resolve to a function that should throw // called as '.resolves[.not].toThrow()` else if (promise === "resolves" && typeof object !== "function") { if (!isNot) { const message = utils.flag(this, "message") || "expected promise to throw an error, but it didn't"; const error = { showDiff: false, }; throw new AssertionError(message, error, utils.flag(this, "ssfi")); } else { return; } } _super.apply(this, args); }; }); }); def("toEqual", function (expected) { const actual = utils.flag(this, "object"); const equal = jestEquals(actual, expected, [ ...customTesters, iterableEquality, ]); return this.assert( equal, "expected #{this} to deeply equal #{exp}", "expected #{this} to not deeply equal #{exp}", expected, actual ); }); def("toStrictEqual", function (expected) { const obj = utils.flag(this, "object"); const equal = jestEquals( obj, expected, [ ...customTesters, iterableEquality, typeEquality, sparseArrayEquality, arrayBufferEquality, ], true ); return this.assert( equal, "expected #{this} to strictly equal #{exp}", "expected #{this} to not strictly equal #{exp}", expected, obj ); }); def("toBe", function (expected) { const actual = this._obj; const pass = Object.is(actual, expected); let deepEqualityName = ""; if (!pass) { const toStrictEqualPass = jestEquals( actual, expected, [ ...customTesters, iterableEquality, typeEquality, sparseArrayEquality, arrayBufferEquality, ], true ); if (toStrictEqualPass) { deepEqualityName = "toStrictEqual"; } else { const toEqualPass = jestEquals(actual, expected, [ ...customTesters, iterableEquality, ]); if (toEqualPass) deepEqualityName = "toEqual"; } } return this.assert( pass, generateToBeMessage(deepEqualityName), "expected #{this} not to be #{exp} // Object.is equality", expected, actual ); }); def("toMatchObject", function (expected) { const actual = this._obj; return this.assert( jestEquals(actual, expected, [ ...customTesters, iterableEquality, subsetEquality, ]), "expected #{this} to match object #{exp}", "expected #{this} to not match object #{exp}", expected, actual ); }); def("toMatch", function (expected: string | RegExp) { if (typeof expected === "string") return this.include(expected); else return this.match(expected); }); def("toContain", function (item) { const actual = this._obj as Iterable | string; // make "actual" indexable to have compatibility with jest if (actual != null && typeof actual !== "string") utils.flag(this, "object", Array.from(actual as Iterable)); return this.contain(item); }); def("toContainEqual", function (expected) { const obj = utils.flag(this, "object"); const index = Array.from(obj).findIndex((item) => { return jestEquals(item, expected, customTesters); }); this.assert( index !== -1, "expected #{this} to deep equally contain #{exp}", "expected #{this} to not deep equally contain #{exp}", expected ); }); def("toBeTruthy", function () { const obj = utils.flag(this, "object"); this.assert( Boolean(obj), "expected #{this} to be truthy", "expected #{this} to not be truthy", obj, false ); }); def("toBeFalsy", function () { const obj = utils.flag(this, "object"); this.assert( !obj, "expected #{this} to be falsy", "expected #{this} to not be falsy", obj, false ); }); def("toBeGreaterThan", function (expected: number | bigint) { const actual = this._obj as number | bigint; assertTypes(actual, "actual", ["number", "bigint"]); assertTypes(expected, "expected", ["number", "bigint"]); return this.assert( actual > expected, `expected ${actual} to be greater than ${expected}`, `expected ${actual} to be not greater than ${expected}`, actual, expected, false ); }); def("toBeGreaterThanOrEqual", function (expected: number | bigint) { const actual = this._obj as number | bigint; assertTypes(actual, "actual", ["number", "bigint"]); assertTypes(expected, "expected", ["number", "bigint"]); return this.assert( actual >= expected, `expected ${actual} to be greater than or equal to ${expected}`, `expected ${actual} to be not greater than or equal to ${expected}`, actual, expected, false ); }); def("toBeLessThan", function (expected: number | bigint) { const actual = this._obj as number | bigint; assertTypes(actual, "actual", ["number", "bigint"]); assertTypes(expected, "expected", ["number", "bigint"]); return this.assert( actual < expected, `expected ${actual} to be less than ${expected}`, `expected ${actual} to be not less than ${expected}`, actual, expected, false ); }); def("toBeLessThanOrEqual", function (expected: number | bigint) { const actual = this._obj as number | bigint; assertTypes(actual, "actual", ["number", "bigint"]); assertTypes(expected, "expected", ["number", "bigint"]); return this.assert( actual <= expected, `expected ${actual} to be less than or equal to ${expected}`, `expected ${actual} to be not less than or equal to ${expected}`, actual, expected, false ); }); def("toBeNaN", function () { return this.be.NaN; }); def("toBeUndefined", function () { return this.be.undefined; }); def("toBeNull", function () { return this.be.null; }); def("toBeDefined", function () { const negate = utils.flag(this, "negate"); utils.flag(this, "negate", false); if (negate) return this.be.undefined; return this.not.be.undefined; }); def( "toBeTypeOf", function ( expected: | "bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined" ) { const actual = typeof this._obj; const equal = expected === actual; return this.assert( equal, "expected #{this} to be type of #{exp}", "expected #{this} not to be type of #{exp}", expected, actual ); } ); def("toBeInstanceOf", function (obj: any) { return this.instanceOf(obj); }); def("toHaveLength", function (length: number) { return this.have.length(length); }); // destructuring, because it checks `arguments` inside, and value is passing as `undefined` def( "toHaveProperty", function (...args: [property: string | (string | number)[], value?: any]) { if (Array.isArray(args[0])) args[0] = args[0] .map((key) => String(key).replace(/([.[\]])/g, "\\$1")) .join("."); const actual = this._obj as any; const [propertyName, expected] = args; const getValue = () => { const hasOwn = Object.prototype.hasOwnProperty.call( actual, propertyName ); if (hasOwn) return { value: actual[propertyName], exists: true }; return utils.getPathInfo(actual, propertyName); }; const { value, exists } = getValue(); const pass = exists && (args.length === 1 || jestEquals(expected, value, customTesters)); const valueString = args.length === 1 ? "" : ` with value ${utils.objDisplay(expected)}`; return this.assert( pass, `expected #{this} to have property "${propertyName}"${valueString}`, `expected #{this} to not have property "${propertyName}"${valueString}`, expected, exists ? value : undefined ); } ); def("toBeCloseTo", function (received: number, precision = 2) { const expected = this._obj; let pass = false; let expectedDiff = 0; let receivedDiff = 0; if ( received === Number.POSITIVE_INFINITY && expected === Number.POSITIVE_INFINITY ) { pass = true; } else if ( received === Number.NEGATIVE_INFINITY && expected === Number.NEGATIVE_INFINITY ) { pass = true; } else { expectedDiff = 10 ** -precision / 2; receivedDiff = Math.abs(expected - received); pass = receivedDiff < expectedDiff; } return this.assert( pass, `expected #{this} to be close to #{exp}, received difference is ${receivedDiff}, but expected ${expectedDiff}`, `expected #{this} to not be close to #{exp}, received difference is ${receivedDiff}, but expected ${expectedDiff}`, received, expected, false ); }); const ordinalOf = (i: number) => { const j = i % 10; const k = i % 100; if (j === 1 && k !== 11) return `${i}st`; if (j === 2 && k !== 12) return `${i}nd`; if (j === 3 && k !== 13) return `${i}rd`; return `${i}th`; }; def( ["toThrow", "toThrowError"], function (expected?: string | RegExp | Error) { if ( typeof expected === "string" || typeof expected === "undefined" || expected instanceof RegExp ) return this.throws(expected); const obj = this._obj; const promise = utils.flag(this, "promise"); const isNot = utils.flag(this, "negate") as boolean; let thrown: any = null; if (promise === "rejects") { thrown = obj; } // if it got here, it's already resolved // unless it tries to resolve to a function that should throw // called as .resolves.toThrow(Error) else if (promise === "resolves" && typeof obj !== "function") { if (!isNot) { const message = utils.flag(this, "message") || "expected promise to throw an error, but it didn't"; const error = { showDiff: false, }; throw new AssertionError(message, error, utils.flag(this, "ssfi")); } else { return; } } else { let isThrow = false; try { obj(); } catch (err) { isThrow = true; thrown = err; } if (!isThrow && !isNot) { const message = utils.flag(this, "message") || "expected function to throw an error, but it didn't"; const error = { showDiff: false, }; throw new AssertionError(message, error, utils.flag(this, "ssfi")); } } if (typeof expected === "function") { // @ts-ignore const name = expected.name || expected.prototype.constructor.name; return this.assert( thrown && thrown instanceof expected, `expected error to be instance of ${name}`, `expected error not to be instance of ${name}`, expected, thrown ); } if (expected instanceof Error) { return this.assert( thrown && expected.message === thrown.message, `expected error to have message: ${expected.message}`, `expected error not to have message: ${expected.message}`, expected.message, thrown && thrown.message ); } if ( typeof expected === "object" && "asymmetricMatch" in expected && typeof (expected as any).asymmetricMatch === "function" ) { const matcher = expected as any as AsymmetricMatcher; return this.assert( thrown && matcher.asymmetricMatch(thrown), "expected error to match asymmetric matcher", "expected error not to match asymmetric matcher", matcher, thrown ); } throw new Error( `"toThrow" expects string, RegExp, function, Error instance or asymmetric matcher, got "${typeof expected}"` ); } ); def("toSatisfy", function (matcher: Function, message?: string) { return this.be.satisfy(matcher, message); }); utils.addProperty( chai.Assertion.prototype, "resolves", function __VITEST_RESOLVES__(this: any) { const error = new Error("resolves"); utils.flag(this, "promise", "resolves"); utils.flag(this, "error", error); const test: any = utils.flag(this, "vitest-test"); const obj = utils.flag(this, "object"); if (typeof obj?.then !== "function") throw new TypeError( `You must provide a Promise to expect() when using .resolves, not '${typeof obj}'.` ); const proxy: any = new Proxy(this, { get: (target, key, receiver) => { const result = Reflect.get(target, key, receiver); if (typeof result !== "function") return result instanceof chai.Assertion ? proxy : result; return async (...args: any[]) => { const promise = obj.then( (value: any) => { utils.flag(this, "object", value); return result.call(this, ...args); }, (err: any) => { const _error = new AssertionError( `promise rejected "${utils.inspect(err)}" instead of resolving`, { showDiff: false } ) as Error; // @ts-ignore _error.cause = err; _error.stack = (error.stack as string).replace( error.message, _error.message ); throw _error; } ); return recordAsyncExpect(test, promise); }; }, }); return proxy; } ); utils.addProperty( chai.Assertion.prototype, "rejects", function __VITEST_REJECTS__(this: any) { const error = new Error("rejects"); utils.flag(this, "promise", "rejects"); utils.flag(this, "error", error); const test: any = utils.flag(this, "vitest-test"); const obj = utils.flag(this, "object"); const wrapper = typeof obj === "function" ? obj() : obj; // for jest compat if (typeof wrapper?.then !== "function") throw new TypeError( `You must provide a Promise to expect() when using .rejects, not '${typeof wrapper}'.` ); const proxy: any = new Proxy(this, { get: (target, key, receiver) => { const result = Reflect.get(target, key, receiver); if (typeof result !== "function") return result instanceof chai.Assertion ? proxy : result; return async (...args: any[]) => { const promise = wrapper.then( (value: any) => { const _error = new AssertionError( `promise resolved "${utils.inspect(value)}" instead of rejecting`, { showDiff: true, expected: new Error("rejected promise"), actual: value, } ) as any; _error.stack = (error.stack as string).replace( error.message, _error.message ); throw _error; }, (err: any) => { utils.flag(this, "object", err); return result.call(this, ...args); } ); return recordAsyncExpect(test, promise); }; }, }); return proxy; } ); }; export function assertTypes( value: unknown, name: string, types: string[] ): void { const receivedType = typeof value; const pass = types.includes(receivedType); if (!pass) throw new TypeError( `${name} value must be ${types.join(" or ")}, received "${receivedType}"` ); } export function recordAsyncExpect( test: any, promise: Promise | PromiseLike ) { // record promise for test, that resolves before test ends if (test && promise instanceof Promise) { // if promise is explicitly awaited, remove it from the list promise = promise.finally(() => { const index = test.promises.indexOf(promise); if (index !== -1) test.promises.splice(index, 1); }); // record promise if (!test.promises) test.promises = []; test.promises.push(promise); } return promise; } ================================================ FILE: llrt_core/src/modules/js/@llrt/expect/jest-utils.ts ================================================ /* Copyright (c) 2008-2016 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Extracted and modified from Vitest: https://github.com/vitest-dev/vitest/blob/463bee38463cd66f9b1f11983a787d9e0e3a7dea/packages/expect/src/jest-utils.ts export function isObject(item: unknown): boolean { return item != null && typeof item === "object" && !Array.isArray(item); } // import type { Tester, TesterContext } from './types' // Extracted out of jasmine 2.5.2 export function equals( a: unknown, b: unknown, customTesters?: Array, strictCheck?: boolean ): boolean { customTesters = customTesters || []; return eq(a, b, [], [], customTesters, strictCheck ? hasKey : hasDefinedKey); } const functionToString = Function.prototype.toString; export function isAsymmetric(obj: any) { return ( !!obj && typeof obj === "object" && "asymmetricMatch" in obj && isA("Function", obj.asymmetricMatch) ); } export function hasAsymmetric(obj: any, seen = new Set()): boolean { if (seen.has(obj)) return false; seen.add(obj); if (isAsymmetric(obj)) return true; if (Array.isArray(obj)) return obj.some((i) => hasAsymmetric(i, seen)); if (obj instanceof Set) return Array.from(obj).some((i) => hasAsymmetric(i, seen)); if (isObject(obj)) return Object.values(obj).some((v) => hasAsymmetric(v, seen)); return false; } function asymmetricMatch(a: any, b: any) { const asymmetricA = isAsymmetric(a); const asymmetricB = isAsymmetric(b); if (asymmetricA && asymmetricB) return undefined; if (asymmetricA) return a.asymmetricMatch(b); if (asymmetricB) return b.asymmetricMatch(a); } // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) function eq( a: any, b: any, aStack: Array, bStack: Array, customTesters: Array, hasKey: any ): boolean { let result = true; const asymmetricResult = asymmetricMatch(a, b); if (asymmetricResult !== undefined) return asymmetricResult; const testerContext: any = { equals }; for (let i = 0; i < customTesters.length; i++) { const customTesterResult = customTesters[i].call( testerContext, a, b, customTesters ); if (customTesterResult !== undefined) return customTesterResult; } if (a instanceof Error && b instanceof Error) return a.message === b.message; if (typeof URL === "function" && a instanceof URL && b instanceof URL) return a.href === b.href; if (Object.is(a, b)) return true; // A strict comparison is necessary because `null == undefined`. if (a === null || b === null) return a === b; const className = Object.prototype.toString.call(a); if (className !== Object.prototype.toString.call(b)) return false; switch (className) { case "[object Boolean]": case "[object String]": case "[object Number]": if (typeof a !== typeof b) { // One is a primitive, one a `new Primitive()` return false; } else if (typeof a !== "object" && typeof b !== "object") { // both are proper primitives return Object.is(a, b); } else { // both are `new Primitive()`s return Object.is(a.valueOf(), b.valueOf()); } case "[object Date]": { const numA = +a; const numB = +b; // Coerce dates to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are equivalent. return numA === numB || (Number.isNaN(numA) && Number.isNaN(numB)); } // RegExps are compared by their source patterns and flags. case "[object RegExp]": return a.source === b.source && a.flags === b.flags; } if (typeof a !== "object" || typeof b !== "object") return false; // Use DOM3 method isEqualNode (IE>=9) if (isDomNode(a) && isDomNode(b)) return a.isEqualNode(b); // Used to detect circular references. let length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. // circular references at same depth are equal // circular reference is not equal to non-circular one if (aStack[length] === a) return bStack[length] === b; else if (bStack[length] === b) return false; } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); // Recursively compare objects and arrays. // Compare array lengths to determine if a deep comparison is necessary. if (className === "[object Array]" && a.length !== b.length) return false; // Deep compare objects. const aKeys = keys(a, hasKey); let key; let size = aKeys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (keys(b, hasKey).length !== size) return false; while (size--) { key = aKeys[size]; // Deep compare each member result = hasKey(b, key) && eq(a[key], b[key], aStack, bStack, customTesters, hasKey); if (!result) return false; } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return result; } function keys(obj: object, hasKey: (obj: object, key: string) => boolean) { const keys = []; for (const key in obj) { if (hasKey(obj, key)) keys.push(key); } return keys.concat( (Object.getOwnPropertySymbols(obj) as Array).filter( (symbol) => (Object.getOwnPropertyDescriptor(obj, symbol) as PropertyDescriptor) .enumerable ) ); } function hasDefinedKey(obj: any, key: string) { return hasKey(obj, key) && obj[key] !== undefined; } function hasKey(obj: any, key: string) { return Object.prototype.hasOwnProperty.call(obj, key); } export function isA(typeName: string, value: unknown) { return Object.prototype.toString.apply(value) === `[object ${typeName}]`; } function isDomNode(obj: any): boolean { return ( obj !== null && typeof obj === "object" && "nodeType" in obj && typeof obj.nodeType === "number" && "nodeName" in obj && typeof obj.nodeName === "string" && "isEqualNode" in obj && typeof obj.isEqualNode === "function" ); } export function fnNameFor(func: Function) { if (func.name) return func.name; const matches = functionToString .call(func) .match(/^(?:async)?\s*function\s*\*?\s*([\w$]+)\s*\(/); return matches ? matches[1] : ""; } function getPrototype(obj: object) { if (Object.getPrototypeOf) return Object.getPrototypeOf(obj); if (obj.constructor.prototype === obj) return null; return obj.constructor.prototype; } export function hasProperty(obj: object | null, property: string): boolean { if (!obj) return false; if (Object.prototype.hasOwnProperty.call(obj, property)) return true; return hasProperty(getPrototype(obj), property); } // SENTINEL constants are from https://github.com/facebook/immutable-js const IS_KEYED_SENTINEL = "@@__IMMUTABLE_KEYED__@@"; const IS_SET_SENTINEL = "@@__IMMUTABLE_SET__@@"; const IS_ORDERED_SENTINEL = "@@__IMMUTABLE_ORDERED__@@"; export function isImmutableUnorderedKeyed(maybeKeyed: any) { return !!( maybeKeyed && maybeKeyed[IS_KEYED_SENTINEL] && !maybeKeyed[IS_ORDERED_SENTINEL] ); } export function isImmutableUnorderedSet(maybeSet: any) { return !!( maybeSet && maybeSet[IS_SET_SENTINEL] && !maybeSet[IS_ORDERED_SENTINEL] ); } /** * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ const IteratorSymbol = Symbol.iterator; function hasIterator(object: any) { return !!(object != null && object[IteratorSymbol]); } export function iterableEquality( a: any, b: any, customTesters: Array = [], aStack: Array = [], bStack: Array = [] ): boolean | undefined { if ( typeof a !== "object" || typeof b !== "object" || Array.isArray(a) || Array.isArray(b) || !hasIterator(a) || !hasIterator(b) ) return undefined; if (a.constructor !== b.constructor) return false; let length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. // circular references at same depth are equal // circular reference is not equal to non-circular one if (aStack[length] === a) return bStack[length] === b; } aStack.push(a); bStack.push(b); const filteredCustomTesters: Array = [ ...customTesters.filter((t) => t !== iterableEquality), iterableEqualityWithStack, ]; function iterableEqualityWithStack(a: any, b: any) { return iterableEquality( a, b, [...filteredCustomTesters], [...aStack], [...bStack] ); } if (a.size !== undefined) { if (a.size !== b.size) { return false; } else if (isA("Set", a) || isImmutableUnorderedSet(a)) { let allFound = true; for (const aValue of a) { if (!b.has(aValue)) { let has = false; for (const bValue of b) { const isEqual = equals(aValue, bValue, filteredCustomTesters); if (isEqual === true) has = true; } if (has === false) { allFound = false; break; } } } // Remove the first value from the stack of traversed values. aStack.pop(); bStack.pop(); return allFound; } else if (isA("Map", a) || isImmutableUnorderedKeyed(a)) { let allFound = true; for (const aEntry of a) { if ( !b.has(aEntry[0]) || !equals(aEntry[1], b.get(aEntry[0]), filteredCustomTesters) ) { let has = false; for (const bEntry of b) { const matchedKey = equals( aEntry[0], bEntry[0], filteredCustomTesters ); let matchedValue = false; if (matchedKey === true) matchedValue = equals( aEntry[1], bEntry[1], filteredCustomTesters ); if (matchedValue === true) has = true; } if (has === false) { allFound = false; break; } } } // Remove the first value from the stack of traversed values. aStack.pop(); bStack.pop(); return allFound; } } const bIterator = b[IteratorSymbol](); for (const aValue of a) { const nextB = bIterator.next(); if (nextB.done || !equals(aValue, nextB.value, filteredCustomTesters)) return false; } if (!bIterator.next().done) return false; // Remove the first value from the stack of traversed values. aStack.pop(); bStack.pop(); return true; } /** * Checks if `hasOwnProperty(object, key)` up the prototype chain, stopping at `Object.prototype`. */ function hasPropertyInObject(object: object, key: string): boolean { const shouldTerminate = !object || typeof object !== "object" || object === Object.prototype; if (shouldTerminate) return false; return ( Object.prototype.hasOwnProperty.call(object, key) || hasPropertyInObject(Object.getPrototypeOf(object), key) ); } function isObjectWithKeys(a: any) { return ( isObject(a) && !(a instanceof Error) && !Array.isArray(a) && !(a instanceof Date) ); } export function subsetEquality( object: unknown, subset: unknown, customTesters: Array = [] ): boolean | undefined { const filteredCustomTesters = customTesters.filter( (t) => t !== subsetEquality ); // subsetEquality needs to keep track of the references // it has already visited to avoid infinite loops in case // there are circular references in the subset passed to it. const subsetEqualityWithContext = (seenReferences: WeakMap = new WeakMap()) => (object: any, subset: any): boolean | undefined => { if (!isObjectWithKeys(subset)) return undefined; return Object.keys(subset).every((key) => { if (isObjectWithKeys(subset[key])) { if (seenReferences.has(subset[key])) return equals(object[key], subset[key], filteredCustomTesters); seenReferences.set(subset[key], true); } const result = object != null && hasPropertyInObject(object, key) && equals(object[key], subset[key], [ ...filteredCustomTesters, subsetEqualityWithContext(seenReferences), ]); // The main goal of using seenReference is to avoid circular node on tree. // It will only happen within a parent and its child, not a node and nodes next to it (same level) // We should keep the reference for a parent and its child only // Thus we should delete the reference immediately so that it doesn't interfere // other nodes within the same level on tree. seenReferences.delete(subset[key]); return result; }); }; return subsetEqualityWithContext()(object, subset); } export function typeEquality(a: any, b: any): boolean | undefined { if (a == null || b == null || a.constructor === b.constructor) return undefined; return false; } export function arrayBufferEquality( a: unknown, b: unknown ): boolean | undefined { let dataViewA = a as DataView; let dataViewB = b as DataView; if (!(a instanceof DataView && b instanceof DataView)) { if (!(a instanceof ArrayBuffer) || !(b instanceof ArrayBuffer)) return undefined; try { dataViewA = new DataView(a); dataViewB = new DataView(b); } catch { return undefined; } } // Buffers are not equal when they do not have the same byte length if (dataViewA.byteLength !== dataViewB.byteLength) return false; // Check if every byte value is equal to each other for (let i = 0; i < dataViewA.byteLength; i++) { if (dataViewA.getUint8(i) !== dataViewB.getUint8(i)) return false; } return true; } export function sparseArrayEquality( a: unknown, b: unknown, customTesters: Array = [] ): boolean | undefined { if (!Array.isArray(a) || !Array.isArray(b)) return undefined; // A sparse array [, , 1] will have keys ["2"] whereas [undefined, undefined, 1] will have keys ["0", "1", "2"] const aKeys = Object.keys(a); const bKeys = Object.keys(b); const filteredCustomTesters = customTesters.filter( (t) => t !== sparseArrayEquality ); return equals(a, b, filteredCustomTesters, true) && equals(aKeys, bKeys); } export function generateToBeMessage( deepEqualityName: string, expected = "#{this}", actual = "#{exp}" ) { const toBeMessage = `expected ${expected} to be ${actual} // Object.is equality`; if (["toStrictEqual", "toEqual"].includes(deepEqualityName)) return `${toBeMessage}\n\nIf it should pass with deep equality, replace "toBe" with "${deepEqualityName}"\n\nExpected: ${expected}\nReceived: serializes to the same string\n`; return toBeMessage; } export function pluralize(word: string, count: number): string { return `${count} ${word}${count === 1 ? "" : "s"}`; } ================================================ FILE: llrt_core/src/modules/js/@llrt/expect/stringify.ts ================================================ /* MIT License Copyright (c) 2021-Present Anthony Fu Copyright (c) 2021-Present Matias Capeletto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Extracted and modified from Vitest: https://github.com/vitest-dev/vitest/blob/0f86ff98975a80191d6215b0d30ff6cd9f8388d3/packages/utils/src/stringify.ts import type { PrettyFormatOptions } from "pretty-format"; import { format as prettyFormat, plugins as prettyFormatPlugins, } from "pretty-format"; const { AsymmetricMatcher, DOMCollection, DOMElement, Immutable, ReactElement, ReactTestComponent, } = prettyFormatPlugins; const PLUGINS = [ ReactTestComponent, ReactElement, DOMElement, DOMCollection, Immutable, AsymmetricMatcher, ]; export function stringify( object: unknown, maxDepth = 10, { maxLength, ...options }: PrettyFormatOptions & { maxLength?: number } = {} ): string { const MAX_LENGTH = maxLength ?? 10000; let result; try { result = prettyFormat(object, { maxDepth, escapeString: false, // min: true, plugins: PLUGINS, ...options, }); } catch { result = prettyFormat(object, { callToJSON: false, maxDepth, escapeString: false, // min: true, plugins: PLUGINS, ...options, }); } return result.length >= MAX_LENGTH && maxDepth > 1 ? stringify(object, Math.floor(maxDepth / 2)) : result; } ================================================ FILE: llrt_core/src/modules/js/@llrt/test/CircularBuffer.ts ================================================ export default class CircularBuffer { private buffer: Buffer; private maxSize: number; private currentPosition: number; private isFull: boolean; private atCapacity: boolean; constructor(maxSize: number, initialSize: number = maxSize / 8) { this.maxSize = Math.ceil(maxSize / 8) * 8; const adjustedInitialSize = Math.ceil(initialSize / 8) * 8; this.buffer = Buffer.alloc(adjustedInitialSize); this.currentPosition = 0; this.isFull = false; this.atCapacity = false; } private grow(targetSize: number): void { const newSize = Math.min( Math.pow(2, Math.ceil(Math.log2(targetSize))), this.maxSize ); if (newSize === this.maxSize) { this.atCapacity = true; } const newBuffer = Buffer.alloc(newSize); newBuffer.set(this.getContent()); this.buffer = newBuffer; this.isFull = false; } clear() { this.currentPosition = 0; this.isFull = false; } append(data: Uint8Array): void { //if data is larger than maxSize, just keep the last maxSize bytes if (data.length >= this.maxSize) { //we are not at max size yet if (!this.atCapacity) { this.buffer = Buffer.alloc(this.maxSize); } //copy over last bytes to fill buffer this.buffer.set(data.slice(-this.maxSize)); this.currentPosition = 0; this.isFull = true; return; } if ( !this.atCapacity && this.currentPosition + data.length > this.buffer.length ) { this.grow(this.currentPosition + data.length); } //wrap around if (this.currentPosition + data.length >= this.buffer.length) { const firstPart = this.buffer.length - this.currentPosition; this.buffer.set(data.subarray(0, firstPart), this.currentPosition); this.buffer.set(data.subarray(firstPart)); this.currentPosition = data.length - firstPart; this.isFull = true; } else { this.buffer.set(data, this.currentPosition); this.currentPosition += data.length; } } getContent(): Buffer { if (!this.isFull) { return this.buffer.subarray(0, this.currentPosition); } return Buffer.concat([ this.buffer.subarray(this.currentPosition), this.buffer.subarray(0, this.currentPosition), ]); } } ================================================ FILE: llrt_core/src/modules/js/@llrt/test/Color.ts ================================================ const NAMES = [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "default", ] as const; type BgType = T extends `${infer A}${infer B}` ? `bg${Uppercase}${B}` : T; type BrightType = `${T}Bright`; type ColorNames = { [k in (typeof NAMES)[number]]: number; } & { [k in BgType<(typeof NAMES)[number]>]: number; } & { [k in BrightType<(typeof NAMES)[number]>]: number; } & { [k in BgType>]: number; }; type Options = { color?: number; bgColor?: number; bold?: boolean; dim?: boolean; italic?: boolean; underline?: boolean; strikethrough?: boolean; } & Partial; class Color { private static CODES = (() => { return NAMES.reduce>( (acc, name: string, i: number) => { let code = i + 30; const COLOR = Color as any; const bgName = `bg${name[0].toUpperCase()}${name.substring(1)}`; const brightName = `${name}Bright`; const bgBrightName = `${bgName}Bright`; acc[name] = code; acc[brightName] = code + 60; acc[bgName] = code + 10; acc[bgBrightName] = code + 70; COLOR[name] = Color.colorizer(code); COLOR[brightName] = Color.colorizer(code + 60); COLOR[bgName] = Color.colorizer(0, { bgColor: code + 10, }); COLOR[bgBrightName] = Color.colorizer(0, { bgColor: code + 70, }); return acc; }, {} ) as ColorNames; })(); static RESET: string = "\x1b[0m"; static colorizer( color: number, options: Options = {}, option?: keyof Options ) { options.color = color; if (option) { const colorOption = Color.CODES && (Color.CODES as any)[option]; if (colorOption) { if (option.startsWith("bg")) { options.bgColor = colorOption; } else { options.color = colorOption; } } else { if (!options[option]) { (options as any)[option] = true; } } } return new Proxy(() => {}, { get(target: unknown, prop: any, receiver: any) { return Color.colorizer(color, options, prop); }, apply(target: unknown, thisArg: any, args: string[]) { let colorCode = `${options.color}`; let modes: number[] = []; if (options.bgColor) { colorCode = `${colorCode};${options.bgColor}`; } if (options.bold) { modes.push(1); } if (options.dim) { modes.push(2); } if (options.italic) { modes.push(3); } if (options.underline) { modes.push(4); } if (options.strikethrough) { modes.push(9); } if (modes.length == 1) { colorCode = `${modes[0]};${colorCode}`; modes = []; } return `\x1b[${colorCode}m${modes.map((m) => `\x1b[${m}m`).join("")}${args.join( " " )}${Color.RESET}`; }, }); } } type ColorizerReturnType = ((text: string) => string) & { [k in keyof (ColorNames & Required)]: ColorizerReturnType; }; type ClassType = typeof Color & { [k in keyof ColorNames]: ColorizerReturnType; }; export default Color as ClassType; ================================================ FILE: llrt_core/src/modules/js/@llrt/test/SocketClient.ts ================================================ import { Socket } from "node:net"; import { EventEmitter } from "node:events"; class SocketClient extends EventEmitter { private host: string; private port: number; private socket: Socket; private queue: Array<{ data: string; resolve: (value: Buffer) => void; reject: (reason?: any) => void; }>; private currentlySending: boolean; private currentResolve?: (value: Buffer) => void; constructor(host: string, port: number) { super(); this.host = host; this.port = port; this.socket = new Socket(); this.queue = []; this.currentlySending = false; this.socket.on("data", (data) => this.handleResponse(data)); this.socket.on("close", () => { return this.emit("close"); }); } public async connect(): Promise { return new Promise((resolve, reject) => { const errorListener = (err: Error) => reject(err); this.socket.on("error", errorListener); this.socket.connect(this.port, this.host, () => { this.socket.off("error", errorListener); this.socket.on("error", (err) => this.emit("error", err)); resolve(); }); }); } public async send(data: string): Promise { return new Promise((resolve, reject) => { this.queue.push({ data, resolve, reject }); this.processQueue(); }); } private async processQueue(): Promise { if (this.currentlySending || this.queue.length === 0) return; const { data, resolve, reject } = this.queue.shift()!; this.currentlySending = true; try { this.currentResolve = resolve; await this.sendData(data); } catch (err) { reject(err); this.currentlySending = false; this.processQueue(); } } private async sendData(data: string): Promise { return new Promise((resolve, reject) => { this.socket.write(data, (err) => { if (err) { return reject(err); } resolve(); }); }); } private handleResponse(data: Buffer): void { if (this.currentResolve) { this.currentResolve(data); this.currentlySending = false; this.processQueue(); } } async close(): Promise { return new Promise((resolve) => { this.socket.end(() => { this.socket.destroy(); resolve(); }); }); } } export default SocketClient; ================================================ FILE: llrt_core/src/modules/js/@llrt/test/index.ts ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import net from "node:net"; import os from "node:os"; import { spawn, ChildProcess } from "node:child_process"; import path from "node:path"; import { SocketReqMsg } from "./shared"; import { platform } from "node:os"; const IS_WINDOWS = platform() === "win32"; import CircularBuffer from "./CircularBuffer"; // @ts-ignore import { dimensions } from "llrt:util"; type TestOptions = { workerCount?: number; }; type TestProps = { success: boolean; started: number; ended: number; }; type TestResult = TestProps & { desc: string; error: Error | null; }; type SuiteResult = TestProps & { desc: string; tests: TestResult[]; children: SuiteResult[]; parent: SuiteResult | null; }; type RootSuite = TestProps & { results: SuiteResult[]; name: string; printed: boolean; }; type WorkerData = { writeInProgress: boolean; stdOutBuffer: CircularBuffer; stdErrBuffer: CircularBuffer; completed: boolean; childProc?: ChildProcess; lastUpdate: number; success: boolean; connectionTimeout: Timeout | null; currentTest: TestResult | null; result: SuiteResult | null; file: string; currentPath: string[]; currentTimeout: number; }; class Color { private static colorizer = ( color: number | null, bgColor: number | null = null, style: number | null = null ) => (text: string) => `\x1b[${color || bgColor || style}m${text}${Color.RESET}`; static GREEN = Color.colorizer(32); static RED = Color.colorizer(31); static GREEN_BACKGROUND = Color.colorizer(null, 42); static RED_BACKGROUND = Color.colorizer(null, 41); static DIM = Color.colorizer(null, null, 2); static BOLD = Color.colorizer(null, null, 1); static CYAN_BOLD = Color.colorizer(36, null, 1); static RESET = "\x1b[0m"; } type TestFailure = { error: any; desc: string[]; stdErr: string; stdOut: string; }; class TestServer { private static UPDATE_FPS = 15; private static UPDATE_INTERVAL_MS = 1000 / TestServer.UPDATE_FPS; private static DEFAULT_TIMEOUT_MS = parseInt((process.env as any).TEST_TIMEOUT) || 5000; private static DEFAULT_PROGRESS_BAR_WIDTH = 24; private static SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; private static TESTING_TEXT = " Testing "; private static CHECKMARK = "\u2714"; private static CROSS = "\u2718"; static ERROR_CODE_SOCKET_ERROR = 1; static ERROR_CODE_SOCKET_WRITE_ERROR = 2; static ERROR_CODE_PROCESS_ERROR = 4; static ERROR_CODE_HANDLE_DATA = 8; private server: net.Server | null = null; private workerCount: number; private workerIdBySocket: Map = new Map(); private testFiles: string[]; private testFileNames: string[]; private fileQueue: string[]; private filesFailed: Map; private filesCompleted: Set; private completedWorkers: number = 0; private workerData: Record = {}; private workerDataFileInProgress: Map = new Map(); private results: Map = new Map(); private totalTests: number = 0; private totalSuccess: number = 0; private totalSkipped: number = 0; private totalFailed: number = 0; private totalOnly: number = 0; private lastUpdate = 0; private updateInterval: Timeout | null = null; private spinnerFrameIndex = 0; private started = 0; private shutdownPending = false; private nextWorkerId = 0; private activeWorkers = 0; private printFinalResults = false; constructor( testFiles: string[], { workerCount = os.availableParallelism() }: TestOptions = {} ) { this.fileQueue = [...testFiles]; this.testFiles = [...testFiles]; this.testFileNames = testFiles.map((file) => path.basename(file)); this.filesFailed = new Map(); this.filesCompleted = new Set(); this.workerCount = Math.min(workerCount, testFiles.length); } public async start() { if (this.testFiles.length === 0) { this.printResults(); this.shutdown(); return; } this.started = performance.now(); const server = net.createServer((socket) => this.handleSocketConnected(socket) ); this.server = server; await new Promise((resolve) => { server.listen(resolve); }); this.spawnWorkers(); this.updateInterval = setInterval(() => { this.tick(); }, TestServer.UPDATE_INTERVAL_MS); } handleSocketConnected(socket: net.Socket) { socket.on("data", (data: Buffer) => { let result; try { result = this.handleData(socket, data); } catch (e: any) { this.handleError(TestServer.ERROR_CODE_HANDLE_DATA, e); return; } const { response, workerId } = result; if (workerId === undefined) { throw new Error("Could not determine workerId from socket or message"); } const workerData = this.workerData[workerId]; workerData.writeInProgress = true; socket.write(JSON.stringify(response), (err) => { workerData.writeInProgress = false; if (err) { return this.handleError( TestServer.ERROR_CODE_SOCKET_WRITE_ERROR, err, { socket, } ); } if (this.shutdownPending) { this.shutdown(); } }); }); socket.on("error", (error) => { const workerId = this.workerIdBySocket.get(socket); if (!workerId || !this.workerData[workerId].completed) { if (workerId) { const workerData = this.workerData[workerId]; const stdErr = workerData.stdErrBuffer.getContent().toString(); const stdOut = workerData.stdOutBuffer.getContent().toString(); let errorOutput = Color.RED_BACKGROUND(Color.BOLD("Worker Error:\n")); if (stdErr) { errorOutput += Color.RED(`\nStd Err:\n${stdErr}`); } if (stdOut) { errorOutput += Color.RED(`\nStd Out:\n${stdOut}`); } console.error(errorOutput); } this.handleError(TestServer.ERROR_CODE_SOCKET_ERROR, error, { socket, }); } }); } spawnWorkers() { while (this.activeWorkers < this.workerCount && this.fileQueue.length > 0) { const file = this.fileQueue.shift()!; const workerId = this.nextWorkerId++; this.workerData[workerId] = { writeInProgress: false, currentTest: null, success: true, completed: false, result: null, file: file, currentTimeout: TestServer.DEFAULT_TIMEOUT_MS, lastUpdate: Date.now(), currentPath: [], connectionTimeout: null, stdOutBuffer: new CircularBuffer(1024, 64), stdErrBuffer: new CircularBuffer(1024, 64), }; this.results.set(file, { results: [], name: path.basename(file), success: true, started: 0, ended: 0, printed: false, }); this.workerDataFileInProgress.set(file, this.workerData[workerId]); this.spawnWorker(workerId, file); this.activeWorkers++; } } private spawnWorker(id: number, file: string) { const workerData = this.workerData[id]; const stdOutBuffer = workerData.stdOutBuffer; const stdErrBuffer = workerData.stdErrBuffer; let env: any = { ...process.env, __LLRT_TEST_SERVER_PORT: (this.server?.address() as any).port, __LLRT_TEST_WORKER_ID: id.toString(), __LLRT_TEST_FILE: file, }; delete env.LLRT_LOG; const proc = spawn( process.argv0, ["-e", `import("llrt:test/worker").catch(console.error)`], { env, } ); proc.stderr.on("data", (data) => { stdErrBuffer.append(data); }); proc.stdout.on("data", (data) => { stdOutBuffer.append(data); }); proc.on("error", (error) => { this.handleError(TestServer.ERROR_CODE_PROCESS_ERROR, error, { id, ended: performance.now(), }); }); proc.on("exit", (code) => { if (code != 0) { this.handleError( TestServer.ERROR_CODE_PROCESS_ERROR, new Error("Worker process exited with a non-zero exit code"), { id, ended: performance.now(), } ); this.handleWorkerCompleted(id); } }); workerData.connectionTimeout = setTimeout(() => { try { proc.kill(); } catch {} }, 5000); workerData.childProc = proc; } handleError(code: number, error: Error, details?: any) { switch (code) { case TestServer.ERROR_CODE_HANDLE_DATA: { console.error(`Error handling data,`, error); process.exit(1); } case TestServer.ERROR_CODE_SOCKET_WRITE_ERROR: case TestServer.ERROR_CODE_SOCKET_ERROR: { console.error(`Socket error,`, error); process.exit(1); } case TestServer.ERROR_CODE_PROCESS_ERROR: { const { id: workerId, ended } = details; this.handleTestError(workerId, error, ended); break; } } } handleData( socket: net.Socket, data: Buffer ): { response: object | null; workerId: number } { const message = JSON.parse(data as any) as SocketReqMsg; const { type } = message; let workerId = this.workerIdBySocket.get(socket); if (workerId === undefined && "workerId" in message) { workerId = (message as any).workerId; } if (workerId !== undefined) { this.workerData[workerId].lastUpdate = Date.now(); } switch (type) { case "ready": { workerId = message.workerId; this.workerIdBySocket.set(socket, workerId); clearTimeout(this.workerData[workerId].connectionTimeout!); break; } case "module": { const { testCount, skipCount, onlyCount } = message; this.totalTests += testCount; this.totalSkipped += skipCount; this.totalOnly += onlyCount; break; } case "start": { const { desc: describe, isSuite, started, timeout } = message; const workerData = this.workerData[workerId]; workerData.currentTimeout = timeout || TestServer.DEFAULT_TIMEOUT_MS; if (isSuite) { const result: SuiteResult = { desc: describe, tests: [], success: true, children: [], parent: workerData.result, started: 0, ended: 0, }; if (!result.parent) { const suite = this.results.get(workerData.file!)!; suite.started = started; suite.results.push(result); } else { workerData.result!.children.push(result); } workerData.result = result; } else { const test: TestResult = { desc: describe, success: true, started, ended: 0, error: null, }; workerData.result!.tests.push(test); workerData.currentTest = test; } workerData.currentPath.push(describe); break; } case "end": { const { isSuite, ended, started } = message; const workerData = this.workerData[workerId]!; const currentResult = workerData.result!; //if we're not in a test workerData.lastUpdate = 0; if (isSuite) { currentResult.ended = ended; currentResult.started = started; workerData.result = currentResult.parent; if (!workerData.result) { const suite = this.results.get(workerData.file!)!; suite.ended = ended; suite.started = started; if (workerData.success) { this.filesCompleted.add(workerData.file!); } } } else { this.totalSuccess++; const test = workerData.currentTest!; test.ended = ended; test.success = true; } workerData.currentPath.pop(); break; } case "error": { const { error, ended, workerId: msgWorkerId } = message; const effectiveWorkerId = workerId ?? msgWorkerId; if (effectiveWorkerId !== undefined) { this.handleTestError(effectiveWorkerId, error, ended); } else { console.error("Error from unknown worker:", error); } break; } case "completed": { this.handleWorkerCompleted(workerId!); break; } default: throw new Error("Unknown type"); } return { response: null, workerId: workerId! }; } private handleWorkerCompleted(workerId: number) { const workerData = this.workerData[workerId]; if (workerData.completed) { return; } workerData.completed = true; this.completedWorkers++; this.activeWorkers--; const shutdownOrPrint = () => { if ( this.completedWorkers == this.testFiles.length && !this.printFinalResults ) { this.printFinalResults = true; clearInterval(this.updateInterval!); this.tick(); this.printResults(); if (!workerData.writeInProgress) { this.shutdown(); } else { this.shutdownPending = true; } } }; if (workerData.childProc) { const exitTimeout = setTimeout(() => { const error = new Error( "Test did not exit within 1s. It does not properly clean up created resources (servers, timeouts etc)" ); this.handleTestError(workerId, error, performance.now()); try { workerData.childProc?.kill(); } catch {} }, 1000); workerData.childProc?.once("exit", () => { clearTimeout(exitTimeout); shutdownOrPrint(); }); } else { shutdownOrPrint(); } this.spawnWorkers(); } shutdown() { this.shutdownPending = false; this.server?.close(() => { //XXX force exit on windows if (IS_WINDOWS) { process.exit(0); } }); } handleTestError(workerId: number, error: any, ended: number) { const workerData = this.workerData[workerId]; const test = workerData.currentTest || { desc: "", success: false, started: 0, ended: 0, error, }; workerData.success = false; const results = this.results.get(workerData.file!); if (results) { results.success = false; } const testFailures = this.filesFailed.get(workerData.file!) || []; testFailures.push({ desc: workerData.currentPath.slice(1), error, stdErr: workerData.stdErrBuffer.getContent().toString(), stdOut: workerData.stdOutBuffer.getContent().toString(), }); workerData.stdErrBuffer.clear(); workerData.stdOutBuffer.clear(); this.filesFailed.set(workerData.file!, testFailures); this.totalFailed++; test.ended = ended; test.error = error; test.success = false; workerData.currentPath.pop(); } private tick() { const now = Date.now(); const first = this.lastUpdate == 0; if (now - this.lastUpdate > TestServer.UPDATE_INTERVAL_MS) { this.spinnerFrameIndex = (this.spinnerFrameIndex + 1) % TestServer.SPINNER.length; this.lastUpdate = now; } //check for hanged tests for (let id in this.workerData) { const workerData = this.workerData[id]; if ( !workerData.completed && workerData.lastUpdate > 0 && now - workerData.lastUpdate >= workerData.currentTimeout ) { this.handleTestError( id as any, new Error(`Test timed out after ${workerData.currentTimeout}ms`), performance.now() ); try { workerData.childProc?.kill(); } catch {} workerData.childProc = undefined; this.handleWorkerCompleted(parseInt(id)); } } let [terminalWidth] = dimensions(); let message = ""; if (!first) { //clear last line message = "\x1b[F\x1b[2K"; } const spinnerFrame = TestServer.SPINNER[this.spinnerFrameIndex]; if (terminalWidth > 80) { terminalWidth = 80; } const total = this.testFiles.length; const progress = (this.filesCompleted.size + this.filesFailed.size) / total; const progressText = `${this.totalSuccess}/${this.totalTests}`; const progressbarWidth = Math.min( TestServer.DEFAULT_PROGRESS_BAR_WIDTH, Math.max( 10, terminalWidth - (2 + progressText.length + 2) //[ + ] + spinner + spacing + progress text ) ); let totalProgressBarWidth = progressbarWidth; const showProgressBarDesc = totalProgressBarWidth == TestServer.DEFAULT_PROGRESS_BAR_WIDTH; if (showProgressBarDesc) { totalProgressBarWidth += TestServer.TESTING_TEXT.length; } let isSuccess = false; let isFailed = false; let i = 0; let suffix = ""; let overflow = false; for (let file of this.testFiles) { let results = this.results.get(file); isSuccess = this.filesCompleted.has(file); if (!isSuccess) { isFailed = this.filesFailed.has(file); } if (results && (isSuccess || isFailed)) { if (!results.printed) { results.printed = true; message += isSuccess ? Color.GREEN(TestServer.CHECKMARK) : Color.RED(TestServer.CROSS); message += " "; message += results.name; message += "\n"; } i++; continue; } const inProgress = this.workerDataFileInProgress.has(file); const filename = this.testFileNames[i]; if ( inProgress && totalProgressBarWidth + suffix.length + 4 < terminalWidth ) { if ( totalProgressBarWidth + suffix.length + filename.length + 4 < terminalWidth ) { suffix += filename; suffix += ", "; } else { overflow = true; suffix += filename.slice( 0, terminalWidth - (totalProgressBarWidth + suffix.length + 5) ); suffix += "..."; } } i++; } if (!overflow) { suffix = suffix.slice(0, -2); } const elapsed = Math.floor(progressbarWidth * progress); const remaining = progressbarWidth - elapsed; message += spinnerFrame; if (showProgressBarDesc) { message += Color.CYAN_BOLD(TestServer.TESTING_TEXT); } message += `[${"=".repeat(elapsed)}${"-".repeat(remaining)}]`; message += progressText; message += ": "; message += Color.DIM(suffix); console.log(message); } private printResults() { const ended = performance.now(); let output = ""; for (let file of this.testFiles) { const suite = this.results.get(file)!; output += `${ suite.success ? Color.GREEN_BACKGROUND(Color.BOLD(" PASS ")) : Color.RED_BACKGROUND(Color.BOLD(" FAIL ")) } ${suite.name} ${Color.DIM(TestServer.elapsed(suite))}\n`; for (let result of suite.results) { output += this.printSuiteResult(result); } output += "\n"; } if (this.totalFailed == 0) { output += Color.GREEN_BACKGROUND( Color.BOLD(` ${TestServer.CHECKMARK} ALL PASS `) ); } else { output += Color.RED_BACKGROUND( Color.BOLD(` ${TestServer.CHECKMARK} TESTS FAILED `) ); } output += ` ${Color.DIM(TestServer.elapsed({ started: this.started, ended }))}\n`; output += `${this.totalSuccess} passed, ${this.totalFailed} failed, ${this.totalSkipped} skipped, ${this.totalTests} tests\n`; console.log(output); if (this.totalFailed > 0) { output = ""; const sortedFilesFailed = new Map( Array.from(this.filesFailed.entries()).sort(([keyA], [keyB]) => keyA.localeCompare(keyB) ) ); for (let [file, testFailure] of sortedFilesFailed) { output += `\n${Color.RED_BACKGROUND(` ${file} `)}\n`; for (let failure of testFailure) { output += failure.desc.map((d) => Color.BOLD(d)).join(" > ") + `\n${this.formattedError(failure.error)}\n`; if (failure.stdOut) { output += "----- LAST STDOUT: -----\n" + failure.stdOut + "\n"; } if (failure.stdErr) { output += "----- LAST STDERR: -----\n" + failure.stdErr + "\n"; } } } process.exitCode = 1; console.error(output); } } private printSuiteResult(result: SuiteResult, depth = 0): string { let output = ""; const indent = " ".repeat(depth); for (let test of result.tests) { const icon = test.success ? Color.GREEN(TestServer.CHECKMARK) : Color.RED(TestServer.CROSS); output += `${indent}${icon} ${test.desc} ${Color.DIM(TestServer.elapsed(test))}\n`; if (test.error) { output += this.formattedError(test.error) + "\n"; } } const results = result.children; for (let result of results) { output += `${indent}${Color.BOLD(result.desc)} ${Color.DIM(TestServer.elapsed(result))}\n`; output += this.printSuiteResult(result, depth + 1); } return output; } private formattedError(error: Error, indent: string = ""): string { let stack = error.stack || ""; if (indent && stack) { stack = stack .split("\n") .map((line) => indent + line) .join("\n"); } return Color.RED( `${indent}\x1b[1m${error.name}:\x1b[22m ${error.message}\n${stack}` ); } static elapsed({ started, ended, }: { started: number; ended: number; }): string { return `${(ended - started).toFixed(3)}ms`; } } const testServer = new TestServer((globalThis as any).__testEntries, { workerCount: undefined, }); await testServer.start(); ================================================ FILE: llrt_core/src/modules/js/@llrt/test/shared.ts ================================================ export type SocketReqMsg = | ReadyReqMsg | ModuleReqMsg | EndReqMsg | StartReqMsg | CompletedReqMsg | ErrorReqMsg; export type ReadyReqMsg = { type: "ready"; workerId: number; }; export type ErrorReqMsg = { type: "error"; error: any; ended: number; started: number; workerId: number; }; export type ModuleReqMsg = { type: "module"; testCount: number; skipCount: number; onlyCount: number; }; export type CompletedReqMsg = { type: "completed"; }; export type EndReqMsg = { type: "end"; ended: number; started: number; isSuite: boolean; }; export type StartReqMsg = { type: "start"; desc: string; isSuite: boolean; started: number; timeout?: number; }; export type SocketResponseMap = {}; export type SocketRes = T extends { type: keyof SocketResponseMap; } ? SocketResponseMap[T["type"]] : null; ================================================ FILE: llrt_core/src/modules/js/@llrt/test/worker.ts ================================================ import * as chai from "chai"; import { JestChaiExpect } from "../expect/jest-expect"; import { JestAsymmetricMatchers } from "../expect/jest-asymmetric-matchers"; import { SocketReqMsg, SocketResponseMap } from "./shared"; import SocketClient from "./SocketClient"; type Test = TestSettings & { desc: string; fn: (done?: (error?: any) => void) => Promise; }; type TestSettings = { only?: boolean; skip?: boolean; timeout?: number; }; type SuiteFunctionWithOptions = SuiteFunction & { skip?: SuiteFunction; only?: SuiteFunction; }; type SuiteFunction = ( desc: string, fn: () => Promise, timeout?: number ) => void; type TestSuite = TestSettings & TestSetup & { tests?: Test[]; suites?: TestSuite[]; parent?: TestSuite; containsOnly?: boolean; desc: string; }; type TestSetup = { afterAll?: MaybeAsyncFunction[]; afterEach?: MaybeAsyncFunction[]; beforeAll?: MaybeAsyncFunction[]; beforeEach?: MaybeAsyncFunction[]; }; type RootSuite = TestSettings & TestSetup & Required> & { onlyCount: number; testCount: number; skipCount: number; module: string; }; type MaybeAsyncFunction = () => Promise | void; type MessageTypeMap = { [K in SocketReqMsg["type"]]: Extract; }; type MessagePayload = Omit< MessageTypeMap[T], "type" >; type SocketReturnType = T extends keyof SocketResponseMap ? SocketResponseMap[T] : null; class TestAgent { private static DEFAULT_TIMEOUT_MS = parseInt((process.env as any).TEST_TIMEOUT) || 5000; private static EMPTY_ARROW_FN_REGEX = /^(async)?\s*\(\s*\)\s*=>/m; private static EMPTY_FN_REGEX = /^(async)?\s*function\s*[a-zA-Z0-9_-]*\s*\(\s*\)\s*\{/m; private static EXPECT = (() => { chai.use(JestChaiExpect); chai.use(JestAsymmetricMatchers); const expect = chai.expect as (value: any, message?: string) => any; return Object.assign(expect, chai.expect); })(); private workerId: number; private client: SocketClient; private rootSuite: RootSuite = TestAgent.createRootSuite(); private currentSuite!: TestSuite; private currentSuites: TestSuite[] = []; private suiteLoadPromises: (() => Promise)[] = []; private describe: SuiteFunctionWithOptions; private testFunction: SuiteFunctionWithOptions; private onlyCount: number = 0; static createRootSuite(): RootSuite { return { tests: [], suites: [], containsOnly: false, desc: "root", testCount: 0, skipCount: 0, onlyCount: 0, module: "", }; } constructor(workerId: number, serverPort: number) { this.workerId = workerId; this.client = new SocketClient("localhost", serverPort); this.client.on("error", (err) => { console.error("Worker Client Socket Error:", workerId, err); }); const testFunction = this.createTestFunction(); testFunction.only = this.createTestFunction({ only: true }); testFunction.skip = this.createTestFunction({ skip: true }); const describe = this.createDescribe(); describe.only = this.createDescribe({ only: true }); describe.skip = this.createDescribe({ skip: true }); this.describe = describe; this.testFunction = testFunction; } private createDescribe({ only = false, skip = false, }: TestSettings = {}): SuiteFunctionWithOptions { return (desc: string, fn: () => Promise, timeout?: number) => { this.suiteLoadPromises.push(async () => { let parent: TestSuite = this.currentSuites.shift() ?? this.rootSuite; this.currentSuite = { tests: [], suites: [], parent, only: only || parent.only, skip, desc, timeout: timeout || parent.timeout, }; parent.suites!!.push(this.currentSuite); let beforeLength = this.suiteLoadPromises.length; await fn(); let afterLength = this.suiteLoadPromises.length; let items = this.suiteLoadPromises.splice( beforeLength, afterLength - beforeLength ); if (items.length) { this.suiteLoadPromises.unshift(...items); let subSuites = new Array(items.length).fill(this.currentSuite); this.currentSuites.unshift(...subSuites); } }); }; } private createTestFunction({ only = false, skip = false, }: TestSettings = {}): SuiteFunctionWithOptions { return (desc: string, fn: () => Promise, timeout?: number) => { let suite: TestSuite = this.currentSuite; this.rootSuite.testCount++; if (skip || suite?.skip) { this.rootSuite.skipCount++; return; } let onlyValue = only || suite.only; if (onlyValue) { this.onlyCount++; suite.containsOnly = true; let p = suite.parent; while (p) { p.containsOnly = true; p = p?.parent; } } const test = { desc, fn, only: onlyValue, timeout: timeout || suite.timeout, }; suite.tests?.push(test); }; } private async runHook( testSuite: TestSuite, hook: keyof TestSetup, timeout?: number ) { const hooks: MaybeAsyncFunction[] = []; const walkParents = hook === "beforeEach" || hook === "afterEach"; let current: TestSuite | undefined = testSuite; while (current) { if (current[hook]) { hooks.unshift(...current[hook]!); } current = walkParents ? current.parent : undefined; } for (const fn of hooks) { await this.executeAsyncOrCallbackFn(fn, timeout); } } private async executeAsyncOrCallbackFn( fn: Function, timeout: number = TestAgent.DEFAULT_TIMEOUT_MS ) { const fnBody = fn.toString(); const usesArgument = !( TestAgent.EMPTY_ARROW_FN_REGEX.test(fnBody) || TestAgent.EMPTY_FN_REGEX.test(fnBody) ); TestAgent.EMPTY_ARROW_FN_REGEX.lastIndex = -1; TestAgent.EMPTY_FN_REGEX.lastIndex = -1; const timeoutMessage = `Timeout after ${timeout}ms`; if (usesArgument) { await new Promise((resolve, reject) => { const timeoutId = setTimeout(() => reject(timeoutMessage), timeout); const resolveWrapper = (error: any) => { clearTimeout(timeoutId); if (error) { return reject(error); } resolve(); }; Promise.resolve(fn(resolveWrapper)).catch(reject); }); } else { let timeoutId: Timeout; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => reject(timeoutMessage), timeout); }); try { await Promise.race([Promise.resolve(fn()), timeoutPromise]); } catch (e) { clearTimeout(timeoutId!); throw e; } clearTimeout(timeoutId!); } } private sendWorkerId() { return this.sendMessage("ready", { workerId: this.workerId, }); } private async complete() { await this.sendMessage("completed"); await this.client.close(); } private async sendMessage( type: T, ...message: MessagePayload extends Record ? [] : [MessagePayload] ): Promise> { const [messageData] = message!; if (type == "error") { const errorData = messageData as MessagePayload<"error">; if (typeof errorData.error === "string") { errorData.error = { message: errorData.error, name: "Error", }; } else { errorData.error = { message: errorData.error.message, stack: errorData.error.stack, name: errorData.error.name, }; } } const data = JSON.stringify({ type, ...messageData, }); const response = (await this.client.send(data)) as any; return JSON.parse(response) as SocketReturnType; } private async runTests(testSuite: TestSuite, tests: Test[] = []) { for (const test of tests) { if (test.skip || (this.onlyCount > 0 && !test.only)) { continue; } let started = performance.now(); try { await this.sendMessage("start", { started, desc: test.desc, isSuite: false, timeout: test.timeout, }); await this.runHook(testSuite, "beforeEach"); started = performance.now(); await this.executeAsyncOrCallbackFn(test.fn, test.timeout); const end = performance.now(); await this.runHook(testSuite, "afterEach"); await this.sendMessage("end", { ended: end, started, isSuite: false, }); } catch (error: any) { await this.sendMessage("error", { error, started, ended: performance.now(), workerId: this.workerId, }); } } } public async start(entry: string): Promise { const connectAndSend = this.connect().then(() => this.sendWorkerId()); const global: any = globalThis; const started = performance.now(); try { const rootSuite = TestAgent.createRootSuite(); this.rootSuite = rootSuite; this.onlyCount = 0; this.currentSuite = this.rootSuite; this.currentSuites = []; let index = entry.lastIndexOf("/"); if (index !== -1) { rootSuite.module = entry.substring(index + 1); } else { rootSuite.module = entry; } global.it = this.testFunction; global.test = this.testFunction; global.describe = this.describe; global.expect = TestAgent.EXPECT; global.beforeEach = (cb: MaybeAsyncFunction) => { (this.currentSuite.beforeEach ??= []).push(cb); }; global.beforeAll = (cb: MaybeAsyncFunction) => { (this.currentSuite.beforeAll ??= []).push(cb); }; global.afterEach = (cb: MaybeAsyncFunction) => { (this.currentSuite.afterEach ??= []).push(cb); }; global.afterAll = (cb: MaybeAsyncFunction) => { (this.currentSuite.afterAll ??= []).push(cb); }; await import(entry); await connectAndSend; while (this.suiteLoadPromises.length > 0) { const suitePromise = this.suiteLoadPromises.shift()!; await suitePromise(); } await this.sendMessage("module", { skipCount: rootSuite.skipCount, testCount: rootSuite.testCount, onlyCount: rootSuite.onlyCount, }); await this.runRootSuite(); delete global.it; delete global.expect; delete global.test; delete global.describe; delete global.beforeEach; delete global.beforeAll; delete global.afterEach; delete global.afterAll; } catch (error) { try { await this.sendMessage("error", { error, started, ended: performance.now(), workerId: this.workerId, }); } catch (e) { console.error("Error sending error message:", e); process.exit(1); } } await this.complete(); } async runSuite(suite: TestSuite, started: number) { await this.runHook(suite, "beforeAll", suite.timeout); await this.runTests(suite, suite.tests); for (const child of suite.suites ?? []) { if ( child.skip || (this.onlyCount > 0 && !child.only && !child.containsOnly) ) { continue; } const childStarted = performance.now(); await this.sendMessage("start", { desc: child.desc, isSuite: true, started: childStarted, timeout: child.timeout, }); try { await this.runSuite(child, childStarted); await this.sendMessage("end", { isSuite: true, started: childStarted, ended: performance.now(), }); } catch (error: any) { await this.sendMessage("error", { error, started, ended: performance.now(), workerId: this.workerId, }); } } await this.runHook(suite, "afterAll", suite.timeout); } async runRootSuite() { const testSuite = this.rootSuite; const started = performance.now(); try { await this.sendMessage("start", { desc: testSuite.module, isSuite: true, started, timeout: testSuite.timeout, }); await this.runSuite(testSuite, started); await this.sendMessage("end", { isSuite: true, started, ended: performance.now(), }); } catch (error: any) { await this.sendMessage("error", { error, started, ended: performance.now(), workerId: this.workerId, }); } } async connect() { await this.client.connect(); } } // Usage example const { __LLRT_TEST_SERVER_PORT: serverPortEnv, __LLRT_TEST_WORKER_ID: workerIdEnv, __LLRT_TEST_FILE: testFile, } = process.env; const workerId = parseInt(workerIdEnv || ""); const serverPort = parseInt(serverPortEnv || ""); if (isNaN(workerId) || isNaN(serverPort) || !testFile) { throw new Error( "Test worker requires __LLRT_TEST_SERVER_PORT, __LLRT_TEST_WORKER_ID & __LLRT_TEST_FILE env" ); } const agent = new TestAgent(workerId, serverPort); await agent.start(testFile); ================================================ FILE: llrt_core/src/modules/js/llrt.d.ts ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 declare module "hex" { export const decode: (text: string) => Uint8Array; export const encode: (bytes: Uint8Array) => string; } declare module "xml" { export class XMLParser { constructor(options?: { ignoreAttributes?: boolean; attributeNamePrefix?: string; textNodeName?: string; attributeValueProcessor?: ( attrName: string, attrValue: string, jpath: string ) => unknown; tagValueProcessor?: ( attrName: string, attrValue: string, jpath: string, hasAttributes: boolean ) => unknown; }); parse(xml: string): any; } export class XmlText { constructor(private value: string) {} toString(): string; } export class XmlNode { readonly children: any[]; static of(name: string, childText?: string, withName?: string): XmlNode; constructor(name: string, children?: any[]); withName(name: string): XmlNode; addAttribute(name: string, value: any): XmlNode; addChildNode(child: any): XmlNode; removeAttribute(name: string): XmlNode; toString(): string; } } declare module "qjs" { interface MemoryInfo { malloc_size: number; malloc_limit: number; memory_used_size: number; malloc_count: number; memory_used_count: number; atom_count: number; atom_size: number; str_count: number; str_size: number; obj_count: number; obj_size: number; prop_count: number; prop_size: number; shape_count: number; shape_size: number; js_func_count: number; js_func_size: number; js_func_code_size: number; js_func_pc2line_count: number; js_func_pc2line_size: number; c_func_count: number; array_count: number; fast_array_count: number; fast_array_elements: number; binary_object_count: number; binary_object_size: number; } function ComputeMemoryUsage(): MemoryInfo; } declare module "llrt:timezone" { interface Timezone { /** * Get the UTC offset in minutes for a timezone at a given time. * * @param timezone - IANA timezone name (e.g., "America/Denver", "Asia/Tokyo") * @param epochMs - Unix timestamp in milliseconds * @returns UTC offset in minutes (positive = ahead of UTC, negative = behind) * * @example * // Get current offset for Denver (handles DST automatically) * const offset = Timezone.getOffset("America/Denver", Date.now()); * // Returns -420 (UTC-7) in winter, -360 (UTC-6) in summer */ getOffset(timezone: string, epochMs: number): number; /** * List all available IANA timezone names. * * @returns Array of timezone names * * @example * const zones = Timezone.list(); * // ["Africa/Abidjan", "Africa/Accra", ..., "Pacific/Wallis"] */ list(): string[]; readonly [Symbol.toStringTag]: "Timezone"; } export const Timezone: Timezone; export default { Timezone: Timezone }; } ================================================ FILE: llrt_core/src/modules/js/stream/promises.ts ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // @ts-ignore import { finished, pipeline } from "readable-stream/lib/stream/promises.js"; export { finished, pipeline }; ================================================ FILE: llrt_core/src/modules/js/stream.ts ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { EventEmitter as EE } from "node:events"; // @ts-ignore import { Readable } from "readable-stream/lib/_stream_readable.js"; // @ts-ignore import { Writable } from "readable-stream/lib/_stream_writable.js"; // @ts-ignore import { Duplex } from "readable-stream/lib/_stream_duplex.js"; // @ts-ignore import { Transform } from "readable-stream/lib/_stream_transform.js"; // @ts-ignore import { PassThrough } from "readable-stream/lib/_stream_passthrough.js"; // @ts-ignore import { finished } from "readable-stream/lib/internal/streams/end-of-stream.js"; // @ts-ignore import { pipeline } from "readable-stream/lib/internal/streams/pipeline.js"; import { inherits } from "node:util"; inherits(Stream, EE); Stream.Readable = Readable; Stream.Writable = Writable; Stream.Duplex = Duplex; Stream.Transform = Transform; Stream.PassThrough = PassThrough; Stream.finished = finished; Stream.pipeline = pipeline; Stream.Stream = Stream; // old-style streams. Note that the pipe method (the only relevant // part of this class) is overridden in the Readable class. function Stream(this: any) { EE.call(this); } Stream.prototype.pipe = function (dest: any, options: any) { var source = this; function ondata(chunk: any) { if (dest.writable) { if (false === dest.write(chunk) && source.pause) { source.pause(); } } } source.on("data", ondata); function ondrain() { if (source.readable && source.resume) { source.resume(); } } dest.on("drain", ondrain); // If the 'end' option is not supplied, dest.end() will be called when // source gets the 'end' or 'close' events. Only dest.end() once. if (!dest._isStdio && (!options || options.end !== false)) { source.on("end", onend); source.on("close", onclose); } var didOnEnd = false; function onend() { if (didOnEnd) return; didOnEnd = true; dest.end(); } function onclose() { if (didOnEnd) return; didOnEnd = true; if (typeof dest.destroy === "function") dest.destroy(); } // don't leave dangling pipes when there are errors. function onerror(er: any) { cleanup(); if (source.listenerCount("error") === 0) { throw er; // Unhandled stream error in pipe. } } source.on("error", onerror); dest.on("error", onerror); // remove all the event listeners that were added. function cleanup() { source.removeListener("data", ondata); dest.removeListener("drain", ondrain); source.removeListener("end", onend); source.removeListener("close", onclose); source.removeListener("error", onerror); dest.removeListener("error", onerror); source.removeListener("end", cleanup); source.removeListener("close", cleanup); dest.removeListener("close", cleanup); } source.on("end", cleanup); source.on("close", cleanup); dest.on("close", cleanup); dest.emit("pipe", source); // Allow for unix-like usage: A.pipe(B).pipe(C) return dest; }; export default Stream; export { Readable, Writable, Duplex, Transform, PassThrough, finished, pipeline, Stream, }; ================================================ FILE: llrt_core/src/modules/llrt/hex.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::Func, Ctx, Result, Value, }; use crate::libs::{ encoding::{bytes_from_hex, bytes_to_hex_string}, utils::{ bytes::{bytes_to_typed_array, ObjectBytes}, module::{export_default, ModuleInfo}, result::ResultExt, }, }; pub struct LlrtHexModule; impl LlrtHexModule { pub fn encode<'js>(ctx: Ctx<'js>, bytes: ObjectBytes<'js>) -> Result { Ok(bytes_to_hex_string(bytes.as_bytes(&ctx)?)) } pub fn decode(ctx: Ctx, encoded: String) -> Result { let bytes = bytes_from_hex(encoded.as_bytes()) .or_throw_msg(&ctx, "Cannot decode unrecognized sequence")?; bytes_to_typed_array(ctx, &bytes) } } impl ModuleDef for LlrtHexModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(encode))?; declare.declare(stringify!(decode))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { default.set(stringify!(encode), Func::from(Self::encode))?; default.set(stringify!(decode), Func::from(Self::decode))?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: LlrtHexModule) -> Self { ModuleInfo { name: "llrt:hex", module: val, } } } ================================================ FILE: llrt_core/src/modules/llrt/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 pub mod hex; pub mod qjs; pub mod util; pub mod xml; ================================================ FILE: llrt_core/src/modules/llrt/qjs.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::Func, qjs, Ctx, IntoJs, Object, Result, Value, }; use crate::libs::utils::module::{export_default, ModuleInfo}; // SAFETY: // - The associated runtime must not be accessed concurrently or destroyed // while this function runs (QuickJS is not thread-safe). // - Undefined behavior may occur if called with an invalid or corrupted runtime. unsafe fn js_compute_memory_usage(ctx: &Ctx) -> qjs::JSMemoryUsage { let mut usage: qjs::JSMemoryUsage = std::mem::zeroed(); let rt = qjs::JS_GetRuntime(ctx.as_raw().as_ptr()); qjs::JS_ComputeMemoryUsage(rt, &mut usage); usage } fn compute_memory_usage(ctx: Ctx) -> Result { let usage = unsafe { js_compute_memory_usage(&ctx) }; let obj = Object::new(ctx.clone())?; obj.set("malloc_size", usage.malloc_size)?; obj.set("malloc_limit", usage.malloc_limit)?; obj.set("memory_used_size", usage.memory_used_size)?; obj.set("malloc_count", usage.malloc_count)?; obj.set("memory_used_count", usage.memory_used_count)?; obj.set("atom_count", usage.atom_count)?; obj.set("atom_size", usage.atom_size)?; obj.set("str_count", usage.str_count)?; obj.set("str_size", usage.str_size)?; obj.set("obj_count", usage.obj_count)?; obj.set("obj_size", usage.obj_size)?; obj.set("prop_count", usage.prop_count)?; obj.set("prop_size", usage.prop_size)?; obj.set("shape_count", usage.shape_count)?; obj.set("shape_size", usage.shape_size)?; obj.set("js_func_count", usage.js_func_count)?; obj.set("js_func_size", usage.js_func_size)?; obj.set("js_func_code_size", usage.js_func_code_size)?; obj.set("js_func_pc2line_count", usage.js_func_pc2line_count)?; obj.set("js_func_pc2line_size", usage.js_func_pc2line_size)?; obj.set("c_func_count", usage.c_func_count)?; obj.set("array_count", usage.array_count)?; obj.set("fast_array_count", usage.fast_array_count)?; obj.set("fast_array_elements", usage.fast_array_elements)?; obj.set("binary_object_count", usage.binary_object_count)?; obj.set("binary_object_size", usage.binary_object_size)?; obj.into_js(&ctx) } pub struct LlrtQjsModule; impl ModuleDef for LlrtQjsModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("ComputeMemoryUsage")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { default.set("ComputeMemoryUsage", Func::from(compute_memory_usage))?; Ok(()) }) } } impl From for ModuleInfo { fn from(val: LlrtQjsModule) -> Self { ModuleInfo { name: "llrt:qjs", module: val, } } } ================================================ FILE: llrt_core/src/modules/llrt/util.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ context::EvalOptions, module::{Declarations, Exports, ModuleDef}, prelude::{Func, Opt}, Array, Ctx, Object, Result, Value, }; use crate::libs::utils::{ module::{export_default, ModuleInfo}, object::ObjectExt, }; fn dimensions(ctx: Ctx<'_>) -> Result> { let array = Array::new(ctx.clone())?; match terminal_size::terminal_size() { Some((width, height)) => { array.set(0, width.0)?; array.set(1, height.0)?; }, None => { array.set(0, 0)?; array.set(1, 0)?; }, } Ok(array) } fn load<'js>(ctx: Ctx<'js>, filename: String, options: Opt>) -> Result> { let mut eval_options = EvalOptions::default(); eval_options.strict = false; eval_options.promise = true; if let Some(options) = options.0 { if let Some(global) = options.get_optional("global")? { eval_options.global = global; } if let Some(strict) = options.get_optional("strict")? { eval_options.strict = strict; } } ctx.eval_file_with_options(filename, eval_options) } fn print(value: String, stdout: Opt) { if stdout.0.unwrap_or_default() { println!("{value}"); } else { eprintln!("{value}") } } pub struct LlrtUtilModule; impl ModuleDef for LlrtUtilModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("dimensions")?; declare.declare("load")?; declare.declare("print")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { default.set("dimensions", Func::from(dimensions))?; default.set("load", Func::from(load))?; default.set("print", Func::from(print))?; Ok(()) }) } } impl From for ModuleInfo { fn from(val: LlrtUtilModule) -> Self { ModuleInfo { name: "llrt:util", module: val, } } } ================================================ FILE: llrt_core/src/modules/llrt/xml.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{borrow::Cow, rc::Rc}; use quick_xml::{ escape::resolve_xml_entity, events::{BytesStart, Event}, Reader, }; use rquickjs::{ class::{Trace, Tracer}, function::Opt, module::{Declarations, Exports, ModuleDef}, object::Property, prelude::This, Array, Class, Ctx, Function, IntoJs, Object, Result, Value, }; use crate::libs::utils::{ bytes::ObjectBytes, module::{export_default, ModuleInfo}, object::ObjectExt, result::ResultExt, }; const AMP: &str = "&"; const LT: &str = "<"; const GT: &str = ">"; const QUOT: &str = """; const APOS: &str = "'"; const CR: &str = " "; const LF: &str = " "; const NEL: &str = "…"; const LS: &str = "
"; #[rquickjs::class] #[derive(rquickjs::JsLifetime)] struct XMLParser<'js> { tag_value_processor: Option>, attribute_value_processor: Option>, attribute_name_prefix: Rc, ignore_attributes: bool, text_node_name: Rc, entities: Vec<(Rc, Rc)>, } impl<'js> Trace<'js> for XMLParser<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { if let Some(tag_value_processor) = &self.tag_value_processor { tracer.mark(tag_value_processor) } if let Some(attribute_value_processor) = &self.attribute_value_processor { tracer.mark(attribute_value_processor) } } } struct StackObject<'js> { obj: Object<'js>, has_value: bool, } impl<'js> StackObject<'js> { fn new(ctx: Ctx<'js>) -> Result { Ok(Self { obj: Object::new(ctx)?, has_value: false, }) } fn into_value(self, ctx: &Ctx<'js>) -> Result> { if self.has_value { return Ok(self.obj.into_value()); } "".into_js(ctx) } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> XMLParser<'js> { #[qjs(constructor)] pub fn new(options: Opt>) -> Result { let mut tag_value_processor = None; let mut attribute_value_processor = None; let mut attribute_name_prefix = None; let mut ignore_attributes = true; let mut text_node_name = None; if let Some(options) = options.0 { tag_value_processor = options.get_optional("tagValueProcessor")?; attribute_value_processor = options.get_optional("attributeValueProcessor")?; if let Some(prefix) = options.get_optional::<_, String>("attributeNamePrefix")? { attribute_name_prefix = Some(prefix.into()) } if let Some(attributes_ignored) = options.get_optional("ignoreAttributes")? { ignore_attributes = attributes_ignored } if let Some(name) = options.get_optional::<_, String>("textNodeName")? { text_node_name = Some(name.into()) } } let entities = Vec::new(); Ok(XMLParser { tag_value_processor, attribute_value_processor, entities, attribute_name_prefix: attribute_name_prefix.unwrap_or_else(|| "@_".into()), ignore_attributes, text_node_name: text_node_name.unwrap_or_else(|| "#text".into()), }) } pub fn add_entity(&mut self, key: String, value: String) { if let Some((_, v)) = self.entities.iter_mut().find(|(k, _)| k.as_ref() == key) { *v = key.into() } else { self.entities.push((key.into(), value.into())); } } pub fn parse(&self, ctx: Ctx<'js>, bytes: ObjectBytes<'js>) -> Result> { let bytes = bytes.as_bytes(&ctx)?; let mut reader = Reader::from_reader(bytes); let config = reader.config_mut(); config.trim_text(true); let mut current_obj = StackObject::new(ctx.clone())?; current_obj.has_value = true; let mut buf = Vec::new(); let mut current_key: Rc = "".into(); let mut current_value: Option = None; let mut path: Vec<(Rc, StackObject<'js>)> = vec![]; let mut has_attributes = false; loop { buf.clear(); match reader.read_event_into(&mut buf) { Ok(Event::Empty(ref tag)) => { current_key = Self::get_tag_name(&ctx, &reader, tag)?; let mut obj = StackObject::new(ctx.clone())?; self.process_attributes(&ctx, &reader, &path, tag, &mut obj, &mut false)?; current_obj.has_value = true; Self::process_end(&ctx, ¤t_obj, obj.into_value(&ctx)?, ¤t_key)?; }, Ok(Event::Start(ref tag)) => { has_attributes = false; current_key = Self::get_tag_name(&ctx, &reader, tag)?; path.push((current_key.clone(), current_obj)); let obj = StackObject::new(ctx.clone())?; current_obj = obj; self.process_attributes( &ctx, &reader, &path, tag, &mut current_obj, &mut has_attributes, )?; }, Ok(Event::End(_)) => { let (parent_tag, mut parent_obj) = path.pop().unwrap(); parent_obj.has_value = true; let value = if let Some(value) = current_value.take() { value.into_js(&ctx)? } else { current_obj.into_value(&ctx)? }; current_obj = parent_obj; Self::process_end(&ctx, ¤t_obj, value, &parent_tag)?; }, Ok(Event::CData(text)) => { let text = text.escape().or_throw(&ctx)?; let tag_value = String::from_utf8_lossy(&text); self.process_text( &mut current_obj, &mut current_key, &mut current_value, &mut path, has_attributes, tag_value, )?; }, Ok(Event::GeneralRef(ref text)) => { let text_ref = text.decode().or_throw(&ctx)?; let text_ref: &str = &text_ref; let tag_value = resolve_xml_entity(text_ref) .or_else(|| { self.entities .iter() .find(|(k, _)| k.as_ref() == text_ref) .map(|(_, v)| v.as_ref()) }) .unwrap_or(text_ref); match current_value { Some(ref mut val) => val.push_str(tag_value), None => current_value = Some(tag_value.to_owned()), } }, Ok(Event::Text(ref text)) => { let tag_value = text.decode().or_throw(&ctx)?; self.process_text( &mut current_obj, &mut current_key, &mut current_value, &mut path, has_attributes, tag_value, )?; }, Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), Ok(Event::Eof) => break, _ => {}, } } Ok(current_obj.obj) } } impl<'js> XMLParser<'js> { fn process_text<'a>( &self, current_obj: &mut StackObject, current_key: &mut Rc, current_value: &mut Option, path: &mut Vec<(Rc, StackObject<'js>)>, has_attributes: bool, tag_value: Cow<'a, str>, ) -> Result<()> { let tag_value = self.process_tag_value(path, current_key, tag_value, has_attributes)?; if has_attributes { current_obj.has_value = true; current_obj .obj .set(self.text_node_name.as_ref(), tag_value)?; } else { match current_value { Some(ref mut val) => val.push_str(&tag_value), None => *current_value = Some(tag_value), } } Ok(()) } fn get_tag_name( ctx: &Ctx<'js>, reader: &Reader<&[u8]>, tag: &BytesStart<'_>, ) -> Result> { let tag = tag.name(); let tag_name = reader.decoder().decode(tag.as_ref()).or_throw(ctx)?; Ok(tag_name.as_ref().into()) } fn process_end( ctx: &Ctx<'js>, current_obj: &StackObject<'js>, value: Value<'js>, tag: &str, ) -> Result<()> { if current_obj.obj.contains_key(tag)? { let parent_value: Value = current_obj.obj.get(tag)?; if !parent_value.is_array() { let array = Array::new(ctx.clone())?; array.set(0, parent_value)?; array.set(1, value)?; current_obj.obj.set(tag, array.as_value())?; } else { let array = parent_value.as_array().or_throw(ctx)?; array.set(array.len(), value)?; current_obj.obj.set(tag, array.as_value())?; } } else { current_obj.obj.prop( tag, Property::from(value).configurable().enumerable().writable(), )?; } Ok(()) } fn set_attribute( &self, stack_object: &mut StackObject<'js>, path: &[&str], key: &str, value: &str, ) -> Result<()> { if let Some(attribute_value_processor) = &self.attribute_value_processor { let jpath = path.join("."); let jpath = jpath.as_str(); if let Some(new_value) = attribute_value_processor.call::<_, Option>((key, value, jpath))? { stack_object.obj.set(key, new_value)?; return Ok(()); } } stack_object.obj.set(key, value)?; Ok(()) } fn process_attributes( &self, ctx: &Ctx<'js>, reader: &Reader<&[u8]>, path: &[(Rc, StackObject<'js>)], tag: &BytesStart<'_>, stack_object: &mut StackObject<'js>, has_attributes: &mut bool, ) -> Result<()> { if !self.ignore_attributes { let jpath_str = path.iter().map(|(k, _)| k.as_ref()).collect::>(); for attribute in tag.attributes() { stack_object.has_value = true; *has_attributes = true; let attr = attribute.or_throw(ctx)?; let value = reader.decoder().decode(attr.value.as_ref()).or_throw(ctx)?; let value = value.as_ref(); let key_slice = attr.key.as_ref(); if !self.attribute_name_prefix.is_empty() { let prefix_bytes = self.attribute_name_prefix.as_bytes(); let mut key_bytes = Vec::with_capacity(prefix_bytes.len() + key_slice.len()); key_bytes.extend_from_slice(prefix_bytes); key_bytes.extend_from_slice(key_slice); let key = reader.decoder().decode(&key_bytes).or_throw(ctx)?; self.set_attribute(stack_object, &jpath_str, key.as_ref(), value)?; } else { let key = reader.decoder().decode(key_slice).or_throw(ctx)?; self.set_attribute(stack_object, &jpath_str, key.as_ref(), value)?; }; } } Ok(()) } fn process_tag_value( &self, path: &[(Rc, StackObject<'js>)], key: &str, value: Cow<'_, str>, has_attributes: bool, ) -> Result { if value.is_empty() { return Ok(value.into()); } if let Some(tag_value_processor) = &self.tag_value_processor { let jpath = path .iter() .map(|(k, _)| k.as_ref()) .collect::>() .join("."); if let Some(new_value) = tag_value_processor.call::<_, Option>(( key, value.as_ref(), jpath, has_attributes, ))? { return Ok(new_value); } } Ok(value.into()) } } #[derive(Debug, Clone, rquickjs::JsLifetime)] #[rquickjs::class] struct XmlText { value: Rc, } impl<'js> Trace<'js> for XmlText { fn trace<'a>(&self, _tracer: Tracer<'a, 'js>) {} } #[rquickjs::methods(rename_all = "camelCase")] impl XmlText { #[qjs(constructor)] fn new(value: String) -> Self { let mut escaped = String::with_capacity(value.len()); escape_element(&mut escaped, &value); XmlText { value: escaped.into(), } } fn to_string(&self) -> &str { self.value.as_ref() } } #[rquickjs::class] #[derive(Debug, Clone, rquickjs::class::Trace, rquickjs::JsLifetime)] struct XmlNode<'js> { #[qjs(skip_trace)] name: String, //child and attributes are always set to avoid branch checks when adding/removing values children: Vec>, #[qjs(skip_trace)] //vec iteration is faster since we rarely have more than 10 attrs and we want to retain insertion order attributes: Vec<(String, String)>, } enum NodeStackEntry<'js> { Node(Class<'js, XmlNode<'js>>), End(String), } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> XmlNode<'js> { #[qjs(constructor)] fn new(name: String, children: Opt>>) -> Result { let node = XmlNode { name, attributes: Vec::new(), children: children.0.unwrap_or_default(), }; Ok(node) } #[qjs(static)] fn of( ctx: Ctx<'js>, name: String, child_text: Opt, with_name: Opt, ) -> Result> { let mut node = XmlNode { name, children: Vec::new(), attributes: Vec::new(), }; if let Some(text) = child_text.0 { let xml_text = Class::instance(ctx.clone(), XmlText::new(text))?; node.children.push(xml_text.into_value()); } if let Some(new_name) = with_name.0 { node.name = new_name; } node.into_js(&ctx) } fn with_name(this: This>, name: String) -> Class<'js, Self> { this.borrow_mut().name = name; this.0 } fn add_attribute( this: This>, name: String, value: String, ) -> Class<'js, Self> { let this2 = this.clone(); let mut borrow = this2.borrow_mut(); if let Some(pos) = borrow.attributes.iter().position(|(a, _)| a == &name) { borrow.attributes[pos] = (name, value); } else { borrow.attributes.push((name, value)); } this.0 } fn add_child_node(this: This>, value: Value<'js>) -> Result> { let this2 = this.clone(); this2.borrow_mut().children.push(value); Ok(this.0) } fn remove_attribute(this: This>, name: String) -> Class<'js, Self> { let this2 = this.clone(); let mut borrow = this2.borrow_mut(); if let Some(pos) = borrow.attributes.iter().position(|(a, _)| a == &name) { borrow.attributes.remove(pos); } this.0 } fn to_string(this: This>, ctx: Ctx<'js>) -> Result { let class = this.0; let mut xml_text = String::with_capacity(8); let mut stack = vec![NodeStackEntry::Node(class)]; while let Some(node) = stack.pop() { match node { NodeStackEntry::Node(node) => { let borrow = node.borrow(); xml_text.push('<'); xml_text.push_str(&borrow.name); for (attribute_name, attribute) in &borrow.attributes { xml_text.push(' '); xml_text.push_str(attribute_name); xml_text.push_str("=\""); escape_attribute(&mut xml_text, attribute); xml_text.push('"'); } let has_children = !borrow.children.is_empty(); if has_children { stack.push(NodeStackEntry::End(borrow.name.clone())); xml_text.push('>'); // Add children to the stack in reverse order (to maintain original order) for child in borrow.children.iter().rev() { if let Some(obj) = child.as_object() { if let Some(node) = Class::::from_object(obj) { stack.push(NodeStackEntry::Node(node)) } else if let Some(text) = Class::::from_object(obj) { xml_text.push_str(&text.borrow().value); } else { let to_string_fn = obj.get::<_, Function>("toString")?; let string_value: String = to_string_fn.call(())?; xml_text.push_str(&string_value); } } else { let string_value = child .clone() .try_into_string() .map_err(|err| format!("Unable to convert {:?} to string", err)) .or_throw(&ctx)? .to_string()?; xml_text.push_str(&string_value); } } } else { xml_text.push_str("/>"); } drop(borrow); }, NodeStackEntry::End(name) => { xml_text.push_str("'); }, } } Ok(xml_text) } } fn escape_attribute(text: &mut String, value: &str) { text.reserve(value.len()); for c in value.chars() { match c { '&' => text.push_str(AMP), '<' => text.push_str(LT), '>' => text.push_str(GT), '"' => text.push_str(QUOT), _ => text.push(c), } } } fn escape_element(text: &mut String, value: &str) { text.reserve(value.len()); for c in value.chars() { match c { '&' => text.push_str(AMP), '<' => text.push_str(LT), '>' => text.push_str(GT), '\'' => text.push_str(APOS), '"' => text.push_str(QUOT), '\r' => text.push_str(CR), '\n' => text.push_str(LF), '\u{0085}' => text.push_str(NEL), '\u{2028}' => text.push_str(LS), _ => text.push(c), } } } pub struct LlrtXmlModule; impl ModuleDef for LlrtXmlModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(XMLParser))?; declare.declare(stringify!(XmlText))?; declare.declare(stringify!(XmlNode))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: LlrtXmlModule) -> Self { ModuleInfo { name: "llrt:xml", module: val, } } } ================================================ FILE: llrt_core/src/modules/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #[cfg(not(feature = "lambda"))] pub use llrt_modules::console; pub use llrt_modules::{ abort, assert, async_hooks, buffer, child_process, crypto, dns, events, exceptions, fetch, fs, https, intl, module, navigator, net, os, path, perf_hooks, process, stream_web, string_decoder, temporal, timers, tls, tty, url, util, zlib, }; pub use llrt_modules::{module_builder, package, CJS_IMPORT_PREFIX, CJS_LOADER_PREFIX}; #[cfg(feature = "lambda")] pub mod console; pub mod embedded; pub mod llrt; ================================================ FILE: llrt_core/src/runtime_client.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{ env, result::Result as StdResult, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Instant, }; use http_body_util::{combinators::BoxBody, BodyExt, Full}; use hyper::{ header::{HeaderMap, CONTENT_TYPE}, http::header::HeaderName, Request, StatusCode, }; use jiff::Timestamp; use once_cell::sync::Lazy; use rquickjs::{ atom::PredefinedAtom, function::Rest, prelude::Func, promise::Promise, qjs, CatchResultExt, CaughtError, Ctx, Exception, Function, IntoJs, Module, Object, Result, Value, }; use tracing::info; use crate::libs::{ json::{ parse::json_parse, stringify::{self, json_stringify}, }, logging::{format_values, replace_newline_with_carriage_return}, utils::{class::get_class_name, result::ResultExt}, }; #[cfg(not(test))] use crate::modules::console::log_error; use crate::modules::https::{HyperClient, HTTP_CLIENT}; use llrt_utils::latch::Latch; const ENV_AWS_LAMBDA_FUNCTION_NAME: &str = "AWS_LAMBDA_FUNCTION_NAME"; const ENV_AWS_LAMBDA_FUNCTION_VERSION: &str = "AWS_LAMBDA_FUNCTION_VERSION"; const ENV_AWS_LAMBDA_FUNCTION_MEMORY_SIZE: &str = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"; const ENV_AWS_LAMBDA_LOG_GROUP_NAME: &str = "AWS_LAMBDA_LOG_GROUP_NAME"; const ENV_AWS_LAMBDA_LOG_STREAM_NAME: &str = "AWS_LAMBDA_LOG_STREAM_NAME"; const ENV_LAMBDA_TASK_ROOT: &str = "LAMBDA_TASK_ROOT"; const ENV_UNDERSCORE_HANDLER: &str = "_HANDLER"; const ENV_LAMBDA_HANDLER: &str = "LAMBDA_HANDLER"; const AWS_LAMBDA_RUNTIME_API: &str = "AWS_LAMBDA_RUNTIME_API"; const ENV_UNDERSCORE_EXIT_ITERATIONS: &str = "_EXIT_ITERATIONS"; const ENV_RUNTIME_PATH: &str = "2018-06-01/runtime"; const ENV_X_AMZN_TRACE_ID: &str = "_X_AMZN_TRACE_ID"; static HEADER_TRACE_ID: HeaderName = HeaderName::from_static("lambda-runtime-trace-id"); static HEADER_DEADLINE_MS: HeaderName = HeaderName::from_static("lambda-runtime-deadline-ms"); static HEADER_REQUEST_ID: HeaderName = HeaderName::from_static("lambda-runtime-aws-request-id"); static HEADER_ERROR_TYPE: HeaderName = HeaderName::from_static("lambda-runtime-function-error-type"); static HEADER_INVOKED_FUNCTION_ARN: HeaderName = HeaderName::from_static("lambda-runtime-invoked-function-arn"); static HEADER_CLIENT_CONTEXT: HeaderName = HeaderName::from_static("lambda-runtime-client-context"); static HEADER_COGNITO_IDENTITY: HeaderName = HeaderName::from_static("lambda-runtime-cognito-identity"); pub struct SdkClientInitState { rt: *mut qjs::JSRuntime, latch: Arc, endpoints: Vec>, //we're likely to have a small number of clients } impl SdkClientInitState { fn new(rt: *mut qjs::JSRuntime) -> Self { Self { rt, latch: Latch::default().into(), endpoints: Vec::new(), } } } unsafe impl Sync for SdkClientInitState {} unsafe impl Send for SdkClientInitState {} fn get_sdk_client_init_state_mut<'a>( guard: &'a mut RwLockWriteGuard>, rt: *mut qjs::JSRuntime, ) -> &'a mut SdkClientInitState { let state = guard.iter_mut().find(|state| state.rt == rt); //save a branch unsafe { state.unwrap_unchecked() } } fn get_sdk_client_init_state<'a>( guard: &'a RwLockReadGuard>, rt: *mut qjs::JSRuntime, ) -> &'a SdkClientInitState { let state = guard.iter().find(|state| state.rt == rt); //save a branch unsafe { state.unwrap_unchecked() } } pub static LAMBDA_REQUEST_ID: Lazy>> = Lazy::new(|| RwLock::new(None)); static SDK_CONNECTION_INIT_LATCH: Lazy>> = Lazy::new(|| RwLock::new(Vec::new())); pub fn check_client_inited(rt: *mut qjs::JSRuntime, endpoint: &str) -> bool { let mut write = SDK_CONNECTION_INIT_LATCH.write().unwrap(); let state = get_sdk_client_init_state_mut(&mut write, rt); if state.endpoints.iter().any(|s| s.as_ref() == endpoint) { return true; } state.endpoints.push(endpoint.into()); state.latch.increment(); false } pub fn mark_client_inited(rt: *mut qjs::JSRuntime) { let mut write = SDK_CONNECTION_INIT_LATCH.write().unwrap(); let state = get_sdk_client_init_state_mut(&mut write, rt); state.latch.decrement(); } #[derive(Clone)] struct LambdaContext<'js, 'a> { pub aws_request_id: String, pub invoked_function_arn: String, pub callback_waits_for_empty_event_loop: bool, pub get_remaining_time_in_millis: Function<'js>, pub client_context: Value<'js>, pub cognito_identity_json: Value<'js>, pub lambda_environment: &'a LambdaEnvironment, } impl<'js> IntoJs<'js> for LambdaContext<'js, '_> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { let obj = Object::new(ctx.clone())?; obj.set("awsRequestId", self.aws_request_id)?; obj.set("invokedFunctionArn", self.invoked_function_arn)?; obj.set("logGroupName", &self.lambda_environment.log_group_name)?; obj.set("logStreamName", &self.lambda_environment.log_stream_name)?; obj.set("functionName", &self.lambda_environment.function_name)?; obj.set("functionVersion", &self.lambda_environment.function_version)?; obj.set( "memoryLimitInMB", self.lambda_environment.memory_limit_in_mb, )?; obj.set( "callbackWaitsForEmptyEventLoop", self.callback_waits_for_empty_event_loop, )?; obj.set( "getRemainingTimeInMillis", self.get_remaining_time_in_millis, )?; obj.set("clientContext", self.client_context)?; obj.set("cognitoIdentityJson", self.cognito_identity_json)?; Ok(obj.into_value()) } } #[derive(Clone)] struct LambdaEnvironment { pub log_group_name: String, pub log_stream_name: String, pub function_name: String, pub function_version: String, pub memory_limit_in_mb: usize, } impl LambdaEnvironment { fn new() -> Self { Self { log_group_name: env::var(ENV_AWS_LAMBDA_LOG_GROUP_NAME).unwrap_or_default(), log_stream_name: env::var(ENV_AWS_LAMBDA_LOG_STREAM_NAME).unwrap_or_default(), function_name: env::var(ENV_AWS_LAMBDA_FUNCTION_NAME).unwrap_or_default(), function_version: env::var(ENV_AWS_LAMBDA_FUNCTION_VERSION).unwrap_or_default(), memory_limit_in_mb: env::var(ENV_AWS_LAMBDA_FUNCTION_MEMORY_SIZE) .unwrap_or("128".into()) .parse() .unwrap_or_default(), } } } struct NextInvocationResponse<'js, 'a> { event: Value<'js>, context: LambdaContext<'js, 'a>, } struct RuntimeConfig { runtime_api: String, handler: String, iterations: usize, } impl RuntimeConfig { fn default(ctx: &Ctx) -> Result { Ok(Self { runtime_api: env::var(AWS_LAMBDA_RUNTIME_API).map_err(|_| { Exception::throw_message( ctx, concat!( "Environment variable ", stringify!(AWS_LAMBDA_RUNTIME_API), " is not defined.", ), ) })?, handler: env::var(ENV_LAMBDA_HANDLER) .or_else(|_| env::var(ENV_UNDERSCORE_HANDLER)) .map_err(|_| { Exception::throw_message( ctx, concat!( "Environment variable ", stringify!(ENV_LAMBDA_HANDLER), " or ", stringify!(ENV_UNDERSCORE_HANDLER), " is not defined.", ), ) })?, iterations: env::var(ENV_UNDERSCORE_EXIT_ITERATIONS) .ok() .and_then(|i| i.parse().ok()) .unwrap_or_default(), }) } } pub async fn start(ctx: &Ctx<'_>) -> Result<()> { start_with_cfg(ctx, RuntimeConfig::default(ctx)?).await } async fn start_with_cfg(ctx: &Ctx<'_>, config: RuntimeConfig) -> Result<()> { let (module_name, handler_name) = get_module_and_handler_name(ctx, &config.handler)?; let task_root = get_task_root(); let rt = unsafe { qjs::JS_GetRuntime(ctx.as_raw().as_ptr()) }; { let mut state_ref = SDK_CONNECTION_INIT_LATCH.write().unwrap(); state_ref.push(SdkClientInitState::new(rt)); } let specifier: String = [task_root.as_str(), module_name].join("/"); let import_promise = Module::import(ctx, specifier.as_bytes())?; let latch = { let state_ref = SDK_CONNECTION_INIT_LATCH.read().unwrap(); get_sdk_client_init_state(&state_ref, rt).latch.clone() }; latch.wait().await; let js_handler_module = import_promise.into_future::().await?; let handler: Value = js_handler_module.get(handler_name)?; if !handler.is_function() { return Err(Exception::throw_message( ctx, &[ "\"", handler_name, "\" is not a function in \"", module_name, "\"", ] .concat(), )); } let client = HTTP_CLIENT.as_ref().or_throw(ctx)?.clone(); let base_url = ["http://", &config.runtime_api, "/", ENV_RUNTIME_PATH].concat(); let handler = handler.as_function().unwrap(); if let Err(err) = start_process_events(ctx, &client, handler, base_url.as_str(), &config) .await .catch(ctx) { post_error(ctx, &client, &base_url, "/init/error", &err, None).await?; } Ok(()) } async fn next_invocation<'js, 'a>( ctx: &Ctx<'js>, client: &'a HyperClient, uri: &str, lambda_environment: &'a LambdaEnvironment, ) -> Result> { let req = Request::builder() .method("GET") .uri(uri) .header(CONTENT_TYPE, "application/json") .body(BoxBody::new(Full::default())) .or_throw(ctx)?; let res = client.request(req).await.or_throw(ctx)?; if res.status() != StatusCode::OK { let res_bytes = res.collect().await.or_throw(ctx)?.to_bytes(); let res_str = String::from_utf8_lossy(res_bytes.as_ref()); return Err(Exception::throw_message( ctx, &["Unexpected /invocation/next response: ", &res_str].concat(), )); } let headers = res.headers(); if let Some(trace_id_value) = headers.get(&HEADER_TRACE_ID) { let trace_id_value = String::from_utf8_lossy(trace_id_value.as_bytes()); env::set_var(ENV_X_AMZN_TRACE_ID, trace_id_value.as_ref()); } else { env::remove_var(ENV_X_AMZN_TRACE_ID); }; let deadline_ms = get_header_value(headers, &HEADER_DEADLINE_MS) .unwrap_or("0".into()) .parse::() .or_throw(ctx)?; let get_remaining_time_in_millis = Func::from(move || { let now = Timestamp::now(); deadline_ms - now.as_millisecond() }); let get_remaining_time_in_millis = get_remaining_time_in_millis .into_js(ctx)? .into_function() .unwrap(); let client_context = if let Some(json) = headers.get(&HEADER_CLIENT_CONTEXT) { json_parse(ctx, json.as_bytes()) } else { rquickjs::Undefined.into_js(ctx) }?; let cognito_identity_json = if let Some(json) = headers.get(&HEADER_COGNITO_IDENTITY) { json_parse(ctx, json.as_bytes()) } else { rquickjs::Undefined.into_js(ctx) }?; let context = LambdaContext { aws_request_id: get_header_value(headers, &HEADER_REQUEST_ID).or_throw(ctx)?, invoked_function_arn: get_header_value(headers, &HEADER_INVOKED_FUNCTION_ARN) .unwrap_or("n/a".into()), callback_waits_for_empty_event_loop: true, get_remaining_time_in_millis, client_context, cognito_identity_json, lambda_environment, }; let bytes = res.collect().await.or_throw(ctx)?.to_bytes(); let event: Value<'js> = json_parse(ctx, bytes)?; Ok(NextInvocationResponse { event, context }) } async fn invoke_response<'js>( ctx: &Ctx<'js>, client: &HyperClient, base_url: &str, request_id: &str, result: Value<'js>, ) -> Result<()> { let result_json = stringify::json_stringify(ctx, result)?; let req = Request::builder() .method("POST") .uri([base_url, "/invocation/", request_id, "/response"].concat()) .header(CONTENT_TYPE, "application/json") .body(BoxBody::new(Full::from(bytes::Bytes::from( result_json.unwrap_or_default(), )))) .or_throw(ctx)?; let res = client.request(req).await.or_throw(ctx)?; match res.status() { StatusCode::ACCEPTED => Ok(()), _ => { let res_bytes = res.collect().await.or_throw(ctx)?.to_bytes(); let res_str = String::from_utf8_lossy(res_bytes.as_ref()); Err(Exception::throw_message( ctx, &["Unexpected /invocation/response response: ", &res_str].concat(), )) }, } } // handler: (event: any, context: Context) => Promise async fn start_process_events<'js>( ctx: &Ctx<'js>, client: &HyperClient, handler: &Function<'js>, base_url: &str, config: &RuntimeConfig, ) -> Result<()> { let mut iterations = 0; let next_invocation_url = [base_url, "/invocation/next"].concat(); let mut request_id = String::with_capacity(36); //length of uuid let lambda_environment = LambdaEnvironment::new(); let promise_ctor: Value = ctx.globals().get(PredefinedAtom::Promise)?; loop { let now = Instant::now(); if let Err(err) = process_event( ctx, client, handler, base_url, &next_invocation_url, &mut request_id, &lambda_environment, &promise_ctor, ) .await { if request_id.is_empty() { return Err(err)?; } let err = CaughtError::from_error(ctx, err); let error_path = ["/invocation/", &request_id, "/error"].concat(); post_error(ctx, client, base_url, &error_path, &err, Some(&request_id)).await?; } if config.iterations > 0 { if iterations >= config.iterations - 1 { info!("Done in {:?}", now.elapsed().as_millis()); break; } iterations += 1; } request_id.clear(); } Ok(()) } #[allow(clippy::too_many_arguments)] async fn process_event<'js>( ctx: &Ctx<'js>, client: &HyperClient, handler: &Function<'js>, base_url: &str, next_invocation_url: &str, request_id: &mut String, lambda_environment: &LambdaEnvironment, promise_constructor: &Value<'js>, ) -> Result<()> { let NextInvocationResponse { event, context } = next_invocation(ctx, client, next_invocation_url, lambda_environment).await?; request_id.clear(); request_id.push_str(&context.aws_request_id); LAMBDA_REQUEST_ID .write() .unwrap() .replace(context.aws_request_id.to_owned()); let js_context = context.into_js(ctx)?; let handler_result = handler.call::<_, Value>((event.clone(), js_context.as_value().clone()))?; let result = match handler_result.as_object() { Some(obj) if obj.is_instance_of(promise_constructor) => { handler_result .get::()? .into_future::() .await? }, _ => handler_result, }; invoke_response(ctx, client, base_url, request_id, result).await?; Ok(()) } async fn post_error<'js>( ctx: &Ctx<'js>, client: &HyperClient, base_url: &str, path: &str, error: &CaughtError<'js>, request_id: Option<&String>, ) -> Result<()> { let mut error_stack = None; let mut error_type = None; let error_msg = match error { CaughtError::Error(err) => format!("Error: {:?}", &err), CaughtError::Exception(ex) => { let error_name = get_class_name(ex) .unwrap_or(None) .unwrap_or(String::from("Error")); let mut str = String::with_capacity(100); str.push_str(&error_name); str.push_str(": "); str.push_str(&ex.message().unwrap_or_default()); error_type = Some(error_name); if let Some(mut stack) = ex.stack() { replace_newline_with_carriage_return(&mut stack); error_stack = Some(stack); } str }, CaughtError::Value(value) => { let log_msg = format_values(ctx, Rest(vec![value.clone()]), false, true) .unwrap_or(String::from("{unknown value}")); ["Error: ", &log_msg].concat() }, }; let error_type = error_type.unwrap_or_else(|| "Error".into()); let error_stack = error_stack.unwrap_or_default(); let error_object = Object::new(ctx.clone())?; error_object.set("errorType", &error_type)?; error_object.set("errorMessage", error_msg)?; error_object.set("stackTrace", error_stack)?; error_object.set("requestId", request_id.unwrap_or(&String::from("n/a")))?; let error_object = error_object.into_value(); #[cfg(not(test))] { log_error(ctx.clone(), Rest(vec![error_object.clone()]))?; } let error_body = json_stringify(ctx, error_object)?.unwrap_or_default(); let url = [base_url, path].concat(); let req = Request::builder() .method("POST") .uri(url) .header(CONTENT_TYPE, "application/json") .header(&HEADER_ERROR_TYPE, error_type) .body(BoxBody::new(Full::from(bytes::Bytes::from(error_body)))) .or_throw(ctx)?; let res = client.request(req).await.or_throw(ctx)?; if res.status() != StatusCode::ACCEPTED { let res_bytes = res.collect().await.or_throw(ctx)?.to_bytes(); let res_str = String::from_utf8_lossy(res_bytes.as_ref()); return Err(Exception::throw_message( ctx, &["Unexpected ", path, " response: ", &res_str].concat(), )); } Ok(()) } fn get_module_and_handler_name<'a>(ctx: &Ctx, handler: &'a str) -> Result<(&'a str, &'a str)> { handler .rfind('.') .and_then(|pos| { let (module_name, handler_name) = handler.split_at(pos); if !module_name.is_empty() && handler_name.len() > 1 { //removes the dot and make sure the length is greater than 0 Some((module_name, &handler_name[1..])) } else { None } }) .ok_or_else(|| { Exception::throw_message( ctx, &[ "Invalid handler name or LAMBDA_HANDLER env value: \"", handler, "\": Should be in format {{filepath}}.{{method_name}}", ] .concat(), ) }) } fn get_task_root() -> String { env::var(ENV_LAMBDA_TASK_ROOT).unwrap_or_else(|_| { env::current_dir() .ok() .and_then(|path| path.into_os_string().into_string().ok()) .unwrap_or_else(|| "/".to_string()) }) } fn get_header_value(headers: &HeaderMap, header: &HeaderName) -> StdResult { headers .get(header) .map(|h| String::from_utf8_lossy(h.as_bytes()).to_string()) .ok_or_else(|| ["Missing or invalid header: ", header.as_str()].concat()) } #[cfg(test)] mod tests { use hyper::header::CONTENT_TYPE; use rquickjs::{async_with, CatchResultExt}; use wiremock::{matchers, Mock, MockServer, ResponseTemplate}; use crate::runtime_client::{ self, RuntimeConfig, ENV_RUNTIME_PATH, HEADER_INVOKED_FUNCTION_ARN, HEADER_REQUEST_ID, }; use crate::vm::Vm; #[tokio::test] async fn runtime() { let mock_server = MockServer::start().await; fn uuid_v4() -> String { let mut bytes = [0u8; 8]; for b in bytes.iter_mut() { *b = rand::random(); } format!( "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7] ) } Mock::given(matchers::method("GET")) .and(matchers::path("/")) .respond_with(ResponseTemplate::new(200)) .mount(&mock_server) .await; Mock::given(matchers::method("GET")) .and(matchers::path(format!( "{}/invocation/next", ENV_RUNTIME_PATH ))) .respond_with( ResponseTemplate::new(200) .insert_header(&HEADER_REQUEST_ID, uuid_v4()) .insert_header(&HEADER_INVOKED_FUNCTION_ARN, "n/a") .set_body_string(r#"{"hello": "world"}"#), ) .mount(&mock_server) .await; Mock::given(matchers::method("POST")) .and(matchers::path_regex( r#"invocation/[A-z0-9-]{1,}/response$"#, )) .and(matchers::header(&CONTENT_TYPE, "application/json")) .respond_with(ResponseTemplate::new(202)) .mount(&mock_server) .await; Mock::given(matchers::method("POST")) .and(matchers::path_regex(r#"invocation/[A-z0-9-]{1,}/error$"#)) .and(matchers::header(&CONTENT_TYPE, "application/json")) .respond_with(ResponseTemplate::new(202)) .mount(&mock_server) .await; let runtime_api = format!("localhost:{}", mock_server.address().port()); let vm = Vm::new().await.unwrap(); async fn run_with_handler(vm: &Vm, handler: &str, runtime_api: &str) { println!("Testing {}", handler); let mock_config = RuntimeConfig { runtime_api: runtime_api.into(), handler: handler.into(), iterations: 10, }; async_with!(vm.ctx => |ctx|{ ctx.globals().set("__MOCK_ENDPOINT", ["http://",runtime_api].concat()).unwrap(); runtime_client::start_with_cfg(&ctx,mock_config).await.catch(&ctx).unwrap() }) .await; } run_with_handler(&vm, "../fixtures/handler.handler", &runtime_api).await; run_with_handler(&vm, "../fixtures/primitive-handler.handler", &runtime_api).await; run_with_handler(&vm, "../fixtures/throwing-handler.handler", &runtime_api).await; run_with_handler(&vm, "../fixtures/sdk-handler.handler", &runtime_api).await; run_with_handler(&vm, "../fixtures/tla-webcall-handler.handler", &runtime_api).await; run_with_handler(&vm, "../fixtures/cjs-handler.handler", &runtime_api).await; vm.runtime.idle().await; } } ================================================ FILE: llrt_core/src/security.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{env, result::Result as StdResult}; use hyper::{http::uri::InvalidUri, Uri}; use crate::environment::{ENV_LLRT_NET_ALLOW, ENV_LLRT_NET_DENY}; use crate::modules::{fetch, net}; pub fn init() -> StdResult<(), Box> { if let Ok(env_value) = env::var(ENV_LLRT_NET_ALLOW) { let allow_list = build_access_list(env_value); fetch::set_allow_list(build_http_access_list(&allow_list)?); net::set_allow_list(allow_list); } if let Ok(env_value) = env::var(ENV_LLRT_NET_DENY) { let deny_list = build_access_list(env_value); fetch::set_deny_list(build_http_access_list(&deny_list)?); net::set_deny_list(deny_list); } Ok(()) } fn build_http_access_list(list: &[String]) -> StdResult, InvalidUri> { list.iter() .flat_map(|entry| { let with_http = ["http://", entry].concat(); let with_https = ["https://", entry].concat(); vec![with_http, with_https] }) .map(|url| url.parse()) .collect() } fn build_access_list(env_value: String) -> Vec { env_value .split_whitespace() .map(|entry| { //remove protocol if let Some(idx) = entry.find("://") { entry[idx + 3..].to_string() } else { entry.to_string() } }) .collect() } ================================================ FILE: llrt_core/src/vm.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{env, result::Result as StdResult}; use rquickjs::{ context::EvalOptions, loader::FileResolver, prelude::Func, AsyncContext, AsyncRuntime, CatchResultExt, Ctx, Error, Result, Value, }; use crate::libs::{ context::set_spawn_error_handler, hooking::HOOKING_MODE, json, logging::print_error_and_exit, numbers, utils::{ clone::structured_clone, primordials::{BasePrimordials, Primordial}, time, }, }; use crate::modules::{ async_hooks::promise_hook_tracker, embedded::{loader::EmbeddedLoader, resolver::EmbeddedResolver}, module_builder::ModuleBuilder, package::{loader::PackageLoader, resolver::PackageResolver}, }; use crate::{environment, http, security}; pub struct Vm { pub runtime: AsyncRuntime, pub ctx: AsyncContext, } pub struct VmOptions { pub module_builder: ModuleBuilder, pub max_stack_size: usize, pub gc_threshold_mb: usize, } impl Default for VmOptions { fn default() -> Self { #[allow(unused_mut)] let mut module_builder = ModuleBuilder::default() .with_global(crate::modules::embedded::init) .with_global(crate::builtins_inspect::init) .with_module(crate::modules::llrt::hex::LlrtHexModule) .with_module(crate::modules::llrt::qjs::LlrtQjsModule) .with_module(crate::modules::llrt::util::LlrtUtilModule) .with_module(crate::modules::llrt::xml::LlrtXmlModule); #[cfg(feature = "lambda")] { module_builder = module_builder .with_global(crate::modules::console::init) .with_module(crate::modules::console::ConsoleModule); } Self { module_builder, max_stack_size: 512 * 1024, gc_threshold_mb: { const DEFAULT_GC_THRESHOLD_MB: usize = 20; let gc_threshold_mb: usize = env::var(environment::ENV_LLRT_GC_THRESHOLD_MB) .map(|threshold| threshold.parse().unwrap_or(DEFAULT_GC_THRESHOLD_MB)) .unwrap_or(DEFAULT_GC_THRESHOLD_MB); gc_threshold_mb * 1024 * 1024 }, } } } impl Vm { pub const ENV_LAMBDA_TASK_ROOT: &'static str = "LAMBDA_TASK_ROOT"; pub async fn from_options( vm_options: VmOptions, ) -> StdResult> { time::init(); http::init()?; security::init()?; let mut file_resolver = FileResolver::default(); let mut paths: Vec<&str> = Vec::with_capacity(10); paths.push("."); let task_root = env::var(Self::ENV_LAMBDA_TASK_ROOT).unwrap_or_else(|_| String::from("")); let task_root = task_root.as_str(); if cfg!(debug_assertions) { paths.push("bundle"); } else { paths.push("/opt"); } if !task_root.is_empty() { paths.push(task_root); } for path in paths.iter() { file_resolver.add_path(*path); } let (module_resolver, module_loader, global_attachment) = vm_options.module_builder.build(); let resolver = ( module_resolver, EmbeddedResolver, PackageResolver, file_resolver, ); let loader = (module_loader, EmbeddedLoader, PackageLoader); let runtime = AsyncRuntime::new()?; runtime.set_max_stack_size(vm_options.max_stack_size).await; runtime.set_gc_threshold(vm_options.gc_threshold_mb).await; runtime.set_loader(resolver, loader).await; let ctx = AsyncContext::full(&runtime).await?; ctx.with(|ctx| { (|| { BasePrimordials::init(&ctx)?; global_attachment.attach(&ctx)?; self::init(&ctx)?; Ok(()) })() .catch(&ctx) .unwrap_or_else(|err| print_error_and_exit(&ctx, err)); Ok::<_, Error>(()) }) .await?; if HOOKING_MODE.to_owned() { runtime.set_promise_hook(Some(promise_hook_tracker())).await; } Ok(Vm { runtime, ctx }) } pub async fn new() -> StdResult> { let vm = Self::from_options(VmOptions::default()).await?; Ok(vm) } pub async fn run_with(&self, f: F) where F: for<'js> FnOnce(&Ctx<'js>) -> Result<()> + std::marker::Send, { self.ctx .with(|ctx| { if let Err(err) = f(&ctx).catch(&ctx) { print_error_and_exit(&ctx, err); } }) .await; } pub async fn run> + Send>(&self, source: S, strict: bool, global: bool) { self.run_with(|ctx| { let mut options = EvalOptions::default(); options.strict = strict; options.promise = true; options.global = global; let _ = ctx.eval_with_options::(source, options)?; Ok::<_, Error>(()) }) .await; } pub async fn run_file(&self, filename: impl AsRef, strict: bool, global: bool) { let source = [ r#"import(""#, &filename.as_ref().replace('\\', "/"), r#"").catch((e) => {console.error(e);process.exit(1)})"#, ] .concat(); self.run(source, strict, global).await; } pub async fn run_bytecode(&self, bytecode: &[u8]) { self.run_with(|ctx| { EmbeddedLoader::load_bytecode_module(ctx.clone(), bytecode) .map(|module| module.eval()) .map_err(|err| { eprintln!("Failed to evaluate module: {err:?}"); err }) .map(|_| ()) }) .await; } pub async fn idle(self) -> StdResult<(), Box> { self.runtime.idle().await; Ok(()) } } fn init(ctx: &Ctx<'_>) -> Result<()> { set_spawn_error_handler(|ctx, err| { print_error_and_exit(ctx, err); }); let globals = ctx.globals(); globals.set("__gc", Func::from(|ctx: Ctx| ctx.run_gc()))?; globals.set("global", ctx.globals())?; globals.set("self", ctx.globals())?; globals.set( "structuredClone", Func::from(|ctx, value, options| structured_clone(&ctx, value, options)), )?; numbers::redefine_prototype(ctx)?; json::redefine_static_methods(ctx)?; Ok(()) } ================================================ FILE: llrt_modules/Cargo.toml ================================================ [package] name = "llrt_modules" description = "LLRT Modules for rquickjs" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [features] default = ["base", "console", "tls-ring", "crypto-rust"] lambda = ["base"] # TLS crypto backend features tls-ring = ["llrt_http?/tls-ring", "llrt_tls?/tls-ring", "llrt_fetch?/tls-ring"] tls-aws-lc = [ "llrt_http?/tls-aws-lc", "llrt_tls?/tls-aws-lc", "llrt_fetch?/tls-aws-lc", ] tls-graviola = [ "llrt_http?/tls-graviola", "llrt_tls?/tls-graviola", "llrt_fetch?/tls-graviola", ] tls-openssl = ["llrt_http?/tls-openssl", "llrt_tls?/tls-openssl"] # Crypto provider features crypto-rust = ["llrt_crypto?/crypto-rust"] crypto-ring = ["llrt_crypto?/crypto-ring"] crypto-ring-rust = ["llrt_crypto?/crypto-ring-rust"] crypto-graviola = ["llrt_crypto?/crypto-graviola"] crypto-graviola-rust = ["llrt_crypto?/crypto-graviola-rust"] crypto-openssl = ["llrt_crypto?/crypto-openssl"] # OpenSSL vendored (builds OpenSSL from source) openssl-vendored = ["dep:openssl", "openssl/vendored"] base = [ "abort", "assert", "async-hooks", "buffer", "child-process", "crypto", "dgram", "dns", "events", "exceptions", "fetch", "fs", "https", "intl", "navigator", "net", "os", "path", "perf-hooks", "process", "stream-web", "string-decoder", "temporal", "timers", "tls", "tty", "url", "util", "zlib", ] # Modules abort = ["llrt_abort"] assert = ["llrt_assert"] async-hooks = ["llrt_async_hooks"] buffer = ["llrt_buffer"] child-process = ["llrt_child_process"] console = ["llrt_console"] crypto = ["llrt_crypto"] dgram = ["llrt_dgram"] dns = ["llrt_dns"] events = ["llrt_events"] exceptions = ["llrt_exceptions"] fetch = [ "llrt_fetch", "llrt_fetch?/http1", "llrt_fetch?/http2", "llrt_fetch?/compression-c", "llrt_fetch?/webpki-roots", ] fs = ["llrt_fs"] https = [ "llrt_http", "llrt_http?/webpki-roots", "llrt_http?/http1", "llrt_http?/http2", ] intl = ["llrt_intl"] navigator = ["llrt_navigator"] net = ["llrt_net"] os = ["llrt_os"] path = ["llrt_path"] process = ["llrt_process"] perf-hooks = ["llrt_perf_hooks"] stream-web = ["llrt_stream_web"] string-decoder = ["llrt_string_decoder"] temporal = ["llrt_temporal"] timers = ["llrt_timers"] tls = ["llrt_tls"] tty = ["llrt_tty"] url = ["llrt_url"] util = ["llrt_util"] zlib = ["llrt_zlib"] [dependencies] home = { version = "0.5", default-features = false } llrt_hooking = { path = "../libs/llrt_hooking" } llrt_json = { path = "../libs/llrt_json" } llrt_utils = { version = "0.8.1-beta", path = "../libs/llrt_utils" } once_cell = { version = "1", features = ["std"], default-features = false } rquickjs = { version = "0.11", features = ["loader"], default-features = false } simd-json = { version = "0.17", default-features = false } tokio = { version = "1", default-features = false } tracing = { version = "0.1", default-features = false } # Optional llrt_abort = { version = "0.8.1-beta", path = "../modules/llrt_abort", optional = true } llrt_assert = { version = "0.8.1-beta", path = "../modules/llrt_assert", optional = true } llrt_async_hooks = { version = "0.8.1-beta", path = "../modules/llrt_async_hooks", optional = true } llrt_buffer = { version = "0.8.1-beta", path = "../modules/llrt_buffer", optional = true } llrt_child_process = { version = "0.8.1-beta", path = "../modules/llrt_child_process", optional = true } llrt_console = { version = "0.8.1-beta", path = "../modules/llrt_console", optional = true } llrt_crypto = { version = "0.8.1-beta", path = "../modules/llrt_crypto", default-features = false, optional = true } llrt_dgram = { version = "0.8.1-beta", path = "../modules/llrt_dgram", optional = true } llrt_dns = { version = "0.8.1-beta", path = "../modules/llrt_dns", optional = true } llrt_events = { version = "0.8.1-beta", path = "../modules/llrt_events", optional = true } llrt_exceptions = { version = "0.8.1-beta", path = "../modules/llrt_exceptions", optional = true } llrt_fetch = { version = "0.8.1-beta", path = "../modules/llrt_fetch", default-features = false, optional = true } llrt_fs = { version = "0.8.1-beta", path = "../modules/llrt_fs", optional = true } llrt_http = { version = "0.8.1-beta", path = "../modules/llrt_http", default-features = false, optional = true } llrt_intl = { version = "0.8.1-beta", path = "../modules/llrt_intl", optional = true } llrt_navigator = { version = "0.8.1-beta", path = "../modules/llrt_navigator", optional = true } llrt_net = { version = "0.8.1-beta", path = "../modules/llrt_net", optional = true } llrt_os = { version = "0.8.1-beta", path = "../modules/llrt_os", default-features = false, optional = true } llrt_path = { version = "0.8.1-beta", path = "../modules/llrt_path", optional = true } llrt_process = { version = "0.8.1-beta", path = "../modules/llrt_process", optional = true } llrt_perf_hooks = { version = "0.8.1-beta", path = "../modules/llrt_perf_hooks", optional = true } llrt_stream_web = { version = "0.8.1-beta", path = "../modules/llrt_stream_web", optional = true } llrt_string_decoder = { version = "0.8.1-beta", path = "../modules/llrt_string_decoder", optional = true } llrt_temporal = { version = "0.8.1-beta", path = "../modules/llrt_temporal", optional = true } llrt_timers = { version = "0.8.1-beta", path = "../modules/llrt_timers", optional = true } llrt_tls = { version = "0.8.1-beta", path = "../modules/llrt_tls", default-features = false, optional = true } llrt_tty = { version = "0.8.1-beta", path = "../modules/llrt_tty", optional = true } llrt_url = { version = "0.8.1-beta", path = "../modules/llrt_url", optional = true } openssl = { version = "0.10", optional = true } llrt_util = { version = "0.8.1-beta", path = "../modules/llrt_util", optional = true } llrt_zlib = { version = "0.8.1-beta", path = "../modules/llrt_zlib", optional = true } [dev-dependencies] llrt_test = { path = "../libs/llrt_test" } tokio = { version = "1", features = ["macros", "rt"] } ================================================ FILE: llrt_modules/README.md ================================================ # LLRT Modules LLRT Modules is a meta-module of [rquickjs](https://github.com/DelSkayn/rquickjs) modules that can be used independantly of LLRT (**L**ow **L**atency **R**un**t**ime). They aim to bring to [quickjs](https://bellard.org/quickjs/) APIs from [Node.js](https://nodejs.org/) and [WinterTC](https://wintertc.org/). You can use this meta-module, but each module is also a unique crate. LLRT (**L**ow **L**atency **R**un**t**ime) is a lightweight JavaScript runtime designed to address the growing demand for fast and efficient Serverless applications. ## Usage The package is not available in the crate registry yet, but you can clone the repo and import it as a local path. Use this script to set everything up: ```bash cd your_project_dir git clone https://github.com/awslabs/llrt.git cd llrt npm i make js ``` Each module has a feature flag, they are all enabled by default but if you prefer to can decide which one you need. Check the [Compability matrix](#compatibility-matrix) for the full list. ```toml [dependencies] llrt_modules = { path = "llrt/llrt_modules", default-features = true } # load from local path rquickjs = { version = "0.11", features = ["full-async"] } tokio = { version = "1", features = ["full"] } ``` Once you have enable a module, you can import it in your runtime. > [!NOTE] > Some modules currently require that you call an `init` function **before** they evaluated. ```rust use llrt_modules::buffer; use rquickjs::{async_with, context::EvalOptions, AsyncContext, AsyncRuntime, Error, Module}; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Error> { let runtime = AsyncRuntime::new()?; let context = AsyncContext::full(&runtime).await?; async_with!(context => |ctx| { buffer::init(&ctx)?; let (_module, module_eval) = Module::evaluate_def::(ctx.clone(), "buffer")?; module_eval.into_future::<()>().await?; let mut options = EvalOptions::default(); options.global = false; if let Err(Error::Exception) = ctx.eval_with_options::<(), _>( r#" import { Buffer } from "node:buffer"; Buffer.alloc(10); "#, options ){ println!("{:#?}", ctx.catch()); }; Ok::<_, Error>(()) }) .await?; Ok(()) } ``` Using ModuleBuilder makes it even simpler. ```rust use llrt_modules::module_builder::ModuleBuilder; use rquickjs::{async_with, context::EvalOptions, AsyncContext, AsyncRuntime, Error, Module}; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Error> { let runtime = AsyncRuntime::new()?; let module_builder = ModuleBuilder::default(); let (module_resolver, module_loader, global_attachment) = module_builder.build(); runtime.set_loader((module_resolver,), (module_loader,)).await; let context = AsyncContext::full(&runtime).await?; async_with!(context => |ctx| { global_attachment.attach(&ctx)?; let mut options = EvalOptions::default(); options.global = false; if let Err(Error::Exception) = ctx.eval_with_options::<(), _>( r#" import { Buffer } from "node:buffer"; Buffer.alloc(10); "#, options ){ println!("{:#?}", ctx.catch()); }; Ok::<_, Error>(()) }) .await?; Ok(()) } ``` ## Compatibility matrix > [!NOTE] > Only a fraction of the Node.js APIs are supported. Below is a high level overview of partially supported APIs and modules. | | Node.js | LLRT Modules | Feature | Crate | | -------------- | ------- | ------------ | ---------------- | --------------------- | | abort | ✔︎ | ✔︎️ | `abort` | `llrt_abort` | | assert | ✔︎ | ⚠️ | `assert` | `llrt_assert` | | async_hooks | ✔︎ | ⚠️ | `async-hooks` | `llrt_async_hooks` | | buffer | ✔︎ | ⚠️ | `buffer` | `llrt_buffer` | | child process | ✔︎ | ⚠️ | `child-process` | `llrt_child_process` | | console | ✔︎ | ⚠️ | `console` | `llrt_console` | | crypto | ✔︎ | ⚠️ | `crypto` | `llrt_crypto` | | dgram | ✔︎ | ⚠️ | `dgram` | `llrt_dgram` | | dns | ✔︎ | ⚠️ | `dns` | `llrt_dns` | | events | ✔︎ | ⚠️ | `events` | `llrt_events` | | exceptions | ✔︎ | ⚠️ | `exceptions` | `llrt_exceptions` | | fetch | ✔︎ | ⚠️ | `fetch` | `llrt_fetch` | | fs/promises | ✔︎ | ⚠️ | `fs` | `llrt_fs` | | fs | ✔︎ | ⚠️ | `fs` | `llrt_fs` | | intl | ✔︎ | ⚠️ | N/A | `llrt_intl` | | navigator | ✔︎ | ⚠️ | `navigator` | `llrt_navigator` | | net | ✔︎ | ⚠️ | `net` | `llrt_net` | | os | ✔︎ | ⚠️ | `os` | `llrt_os` | | path | ✔︎ | ⚠️ | `path` | `llrt_path` | | perf hooks | ✔︎ | ⚠️ | `perf-hooks` | `llrt_perf_hooks` | | stream (lib) | N/A | ✔︎ | N/A | `llrt_stream` | | string_decoder | ✔︎ | ✔︎ | `string_decoder` | `llrt_string_decoder` | | timers | ✔︎ | ⚠️ | `timers` | `llrt_timers` | | process | ✔︎ | ⚠️ | `process` | `llrt_process` | | temporal | ✔︎ | ⚠️ | N/A | `llrt_temporal` | | tty | ✔︎ | ⚠️ | `tty` | `llrt_tty` | | url | ✔︎ | ⚠️ | `url` | `llrt_url` | | util | ✔︎ | ⚠️ | `util` | `llrt_util` | | zlib | ✔︎ | ⚠️ | `zlib` | `llrt_zlib` | | Other modules | ✔︎ | ✘ | N/A | N/A | _⚠️ = partially supported_ ## License This module is licensed under the Apache-2.0 License. ================================================ FILE: llrt_modules/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::env; use once_cell::sync::Lazy; pub mod module; pub mod module_builder; pub mod package; pub use self::modules::*; mod modules { #[cfg(feature = "abort")] pub use llrt_abort as abort; #[cfg(feature = "assert")] pub use llrt_assert as assert; #[cfg(feature = "async-hooks")] pub use llrt_async_hooks as async_hooks; #[cfg(feature = "buffer")] pub use llrt_buffer as buffer; #[cfg(feature = "child-process")] pub use llrt_child_process as child_process; #[cfg(feature = "console")] pub use llrt_console as console; #[cfg(feature = "crypto")] pub use llrt_crypto as crypto; #[cfg(feature = "dgram")] pub use llrt_dgram as dgram; #[cfg(feature = "dns")] pub use llrt_dns as dns; #[cfg(feature = "events")] pub use llrt_events as events; #[cfg(feature = "exceptions")] pub use llrt_exceptions as exceptions; #[cfg(feature = "fetch")] pub use llrt_fetch as fetch; #[cfg(feature = "fs")] pub use llrt_fs as fs; #[cfg(feature = "https")] pub use llrt_http as https; #[cfg(feature = "intl")] pub use llrt_intl as intl; #[cfg(feature = "navigator")] pub use llrt_navigator as navigator; #[cfg(feature = "net")] pub use llrt_net as net; #[cfg(feature = "os")] pub use llrt_os as os; #[cfg(feature = "path")] pub use llrt_path as path; #[cfg(feature = "perf-hooks")] pub use llrt_perf_hooks as perf_hooks; #[cfg(feature = "process")] pub use llrt_process as process; #[cfg(feature = "stream-web")] pub use llrt_stream_web as stream_web; #[cfg(feature = "string-decoder")] pub use llrt_string_decoder as string_decoder; #[cfg(feature = "temporal")] pub use llrt_temporal as temporal; #[cfg(feature = "timers")] pub use llrt_timers as timers; #[cfg(feature = "tls")] pub use llrt_tls as tls; #[cfg(feature = "tty")] pub use llrt_tty as tty; #[cfg(feature = "url")] pub use llrt_url as url; #[cfg(feature = "util")] pub use llrt_util as util; #[cfg(feature = "zlib")] pub use llrt_zlib as zlib; } pub const VERSION: &str = env!("CARGO_PKG_VERSION"); // added when .cjs files are imported pub const CJS_IMPORT_PREFIX: &str = "__cjs:"; // added to force CJS imports in loader pub const CJS_LOADER_PREFIX: &str = "__cjsm:"; pub const ENV_LLRT_PLATFORM: &str = "LLRT_PLATFORM"; pub static LLRT_PLATFORM: Lazy = Lazy::new(|| { env::var(ENV_LLRT_PLATFORM) .ok() .filter(|platform| platform == "node") .unwrap_or_else(|| "browser".to_string()) }); ================================================ FILE: llrt_modules/src/module/loader.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ cell::{Cell, RefCell}, collections::HashMap, rc::Rc, }; use llrt_utils::{any_of::AnyOf2, bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{ loader::Loader, module::ModuleDef, prelude::{Func, Opt}, Ctx, Error, Module, Object, Result, Value, }; use tracing::trace; use super::{Hook, ModuleHookState}; type LoadFn = for<'js> fn(Ctx<'js>, Vec) -> Result>; type Source<'js> = AnyOf2>; #[derive(Debug, Default)] pub struct ModuleLoader { modules: HashMap, } impl ModuleLoader { fn load_func<'js, D: ModuleDef>(ctx: Ctx<'js>, name: Vec) -> Result> { Module::declare_def::(ctx, name) } pub fn add_module, M: ModuleDef>(&mut self, name: N, _module: M) -> &mut Self { self.modules.insert(name.into(), Self::load_func::); self } #[must_use] pub fn with_module, M: ModuleDef>(mut self, name: N, module: M) -> Self { self.add_module(name, module); self } } impl Loader for ModuleLoader { fn load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result> { trace!("Try load '{}'", name); let (short_circuit, next_load, source) = module_hook_load(ctx, name)?; if short_circuit { trace!("+- Loading module via ShortCircuit: {}\n", name); return match source { AnyOf2::A(s) => Module::declare(ctx.clone(), name, s), AnyOf2::B(b) => Module::declare(ctx.clone(), name, b.as_bytes(ctx)?), }; }; let load = self .modules .remove(name) .ok_or_else(|| Error::new_loading(name))?; if next_load { trace!("| Determined as `nextResolve`: {}", name); } else { trace!("| Determined as `NormalCircuit`: {}", name); } trace!("+- Loading module: {}\n", name); (load)(ctx.clone(), Vec::from(name)) } } pub fn module_hook_load<'js>(ctx: &Ctx<'js>, name: &str) -> Result<(bool, bool, Source<'js>)> { let bind_state = ctx.userdata::>().or_throw(ctx)?; let hooks = Rc::new(bind_state.borrow().hooks.clone()); if hooks.is_empty() { return Ok((false, false, AnyOf2::A("".into()))); } let result = call_load_hooks(ctx, &hooks, name)?; let short_circuit = result .get_optional::<_, bool>("shortCircuit")? .unwrap_or(false); let next_load = result .get_optional::<_, bool>("__nextLoad")? .unwrap_or(false); let source = result .get_optional::<_, Source>("source")? .unwrap_or(AnyOf2::A("".into())); Ok((short_circuit, next_load, source)) } #[allow(dependency_on_unit_never_type_fallback)] fn call_load_hooks<'js>( ctx: &Ctx<'js>, hooks: &Rc>>, url: &str, ) -> Result> { call_load_hooks_from(ctx, hooks, 0, url) } fn call_load_hooks_from<'js>( ctx: &Ctx<'js>, hooks: &Rc>>, start_index: usize, url: &str, ) -> Result> { for index in start_index..hooks.len() { let Some(load_fn) = &hooks[index].load else { continue; }; let called_next = Rc::new(Cell::new(false)); let called_next_ref = Rc::clone(&called_next); let context = Object::new(ctx.clone())?; let hooks_clone = Rc::clone(hooks); let next_func = Func::new( move |ctx: Ctx<'js>, new_url: String, _opt_ctx: Opt>| -> Result> { called_next_ref.set(true); call_load_hooks_from(&ctx, &hooks_clone, index + 1, &new_url) }, ); let result = load_fn.call::<_, Object>((url, context, next_func))?; result.set("__nextLoad", called_next.get())?; return Ok(result); } let obj = Object::new(ctx.clone())?; obj.set("url", url)?; obj.set("shortCircuit", false)?; obj.set("__nextLoad", false)?; Ok(obj) } #[cfg(test)] mod tests { use super::*; use llrt_test::test_async_with; use rquickjs::Function; #[tokio::test] async fn test_hook_override_import() { use llrt_test::ModuleEvaluator; test_async_with(|ctx| { Box::pin(async move { let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let hook_code = r#" globalThis.hookCalled = false; globalThis.nextLoadCalled = false; export function load(url, context, nextLoad) { globalThis.hookCalled = true; if (url === "math") { return { format: "module", shortCircuit: true, source: "export function add(a, b) { return a + b + 1; }" }; } globalThis.nextLoadCalled = true; return nextLoad(url, context); } "#; let hook_module = ModuleEvaluator::eval_js(ctx.clone(), "hook", hook_code) .await .unwrap(); let load_fn: Function = hook_module.get("load").unwrap(); let hook = Hook { resolve: None, load: Some(load_fn), }; let binding = ctx.userdata::>().unwrap(); binding.borrow_mut().hooks.push(hook); let binding = ctx.userdata::>().unwrap(); let hooks = Rc::new(binding.borrow().hooks.clone()); let result = call_load_hooks(&ctx, &hooks, "math").unwrap(); let globals = ctx.globals(); assert!(globals.get::<_, bool>("hookCalled").unwrap()); assert!(result.get::<_, bool>("shortCircuit").unwrap()); assert_eq!( result.get::<_, String>("source").unwrap(), "export function add(a, b) { return a + b + 1; }" ); let result2 = call_load_hooks(&ctx, &hooks, "other").unwrap(); assert!(globals.get::<_, bool>("nextLoadCalled").unwrap()); assert!(!result2.get::<_, bool>("shortCircuit").unwrap()); assert_eq!(result2.get::<_, String>("url").unwrap(), "other"); }) }) .await; } #[tokio::test] async fn test_multiple_hooks_chain() { use llrt_test::ModuleEvaluator; test_async_with(|ctx| { Box::pin(async move { let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let hook1_code = r#" export function load(url, context, nextLoad) { globalThis.hook1Called = true; if (url === "skip") { return nextLoad(url, context); } return nextLoad("modified-" + url, context); } "#; let hook2_code = r#" export function load(url, context, nextLoad) { globalThis.hook2Called = true; globalThis.finalUrl = url; return { shortCircuit: true, source: "export const value = 42;" }; } "#; let hook1 = ModuleEvaluator::eval_js(ctx.clone(), "hook1", hook1_code) .await .unwrap(); let hook2 = ModuleEvaluator::eval_js(ctx.clone(), "hook2", hook2_code) .await .unwrap(); let binding = ctx.userdata::>().unwrap(); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook1.get("load").unwrap()), }); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook2.get("load").unwrap()), }); let binding = ctx.userdata::>().unwrap(); let hooks = Rc::new(binding.borrow().hooks.clone()); let result = call_load_hooks(&ctx, &hooks, "test").unwrap(); let globals = ctx.globals(); assert!(globals.get::<_, bool>("hook1Called").unwrap()); assert!(globals.get::<_, bool>("hook2Called").unwrap()); assert_eq!( globals.get::<_, String>("finalUrl").unwrap(), "modified-test" ); assert!(result.get::<_, bool>("shortCircuit").unwrap()); assert_eq!( result.get::<_, String>("source").unwrap(), "export const value = 42;" ); }) }) .await; } #[tokio::test] async fn test_hook_without_nextload() { use llrt_test::ModuleEvaluator; test_async_with(|ctx| { Box::pin(async move { let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let hook_code = r#" export function load(url, context, nextLoad) { return { shortCircuit: true, source: "export const intercepted = true;" }; } "#; let hook_module = ModuleEvaluator::eval_js(ctx.clone(), "hook", hook_code) .await .unwrap(); let binding = ctx.userdata::>().unwrap(); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook_module.get("load").unwrap()), }); let binding = ctx.userdata::>().unwrap(); let hooks = Rc::new(binding.borrow().hooks.clone()); let result = call_load_hooks(&ctx, &hooks, "any").unwrap(); assert!(result.get::<_, bool>("shortCircuit").unwrap()); assert_eq!( result.get::<_, String>("source").unwrap(), "export const intercepted = true;" ); }) }) .await; } #[tokio::test] async fn test_hook_passthrough_all() { use llrt_test::ModuleEvaluator; test_async_with(|ctx| { Box::pin(async move { let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let hook_code = r#" export function load(url, context, nextLoad) { globalThis.passedThrough = url; return nextLoad(url, context); } "#; let hook_module = ModuleEvaluator::eval_js(ctx.clone(), "hook", hook_code) .await .unwrap(); let binding = ctx.userdata::>().unwrap(); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook_module.get("load").unwrap()), }); let binding = ctx.userdata::>().unwrap(); let hooks = Rc::new(binding.borrow().hooks.clone()); let result = call_load_hooks(&ctx, &hooks, "passthrough").unwrap(); let globals = ctx.globals(); assert_eq!( globals.get::<_, String>("passedThrough").unwrap(), "passthrough" ); assert!(!result.get::<_, bool>("shortCircuit").unwrap()); assert_eq!(result.get::<_, String>("url").unwrap(), "passthrough"); }) }) .await; } #[tokio::test] async fn test_hook_conditional_intercept() { use llrt_test::ModuleEvaluator; test_async_with(|ctx| { Box::pin(async move { let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let hook_code = r#" export function load(url, context, nextLoad) { if (url.startsWith("internal:")) { return { shortCircuit: true, source: "export const internal = true;" }; } return nextLoad(url, context); } "#; let hook_module = ModuleEvaluator::eval_js(ctx.clone(), "hook", hook_code) .await .unwrap(); let binding = ctx.userdata::>().unwrap(); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook_module.get("load").unwrap()), }); let binding = ctx.userdata::>().unwrap(); let hooks = Rc::new(binding.borrow().hooks.clone()); let result1 = call_load_hooks(&ctx, &hooks, "internal:test").unwrap(); assert!(result1.get::<_, bool>("shortCircuit").unwrap()); assert_eq!( result1.get::<_, String>("source").unwrap(), "export const internal = true;" ); let result2 = call_load_hooks(&ctx, &hooks, "external:test").unwrap(); assert!(!result2.get::<_, bool>("shortCircuit").unwrap()); assert_eq!(result2.get::<_, String>("url").unwrap(), "external:test"); }) }) .await; } #[tokio::test] async fn test_three_hooks_selective_intercept() { use llrt_test::ModuleEvaluator; test_async_with(|ctx| { Box::pin(async move { let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let hook1_code = r#" export function load(url, context, nextLoad) { globalThis.order = ["hook1"]; return nextLoad(url, context); } "#; let hook2_code = r#" export function load(url, context, nextLoad) { globalThis.order.push("hook2"); if (url === "intercept-here") { return { shortCircuit: true, source: "export const from = 'hook2';" }; } return nextLoad(url, context); } "#; let hook3_code = r#" export function load(url, context, nextLoad) { globalThis.order.push("hook3"); return { shortCircuit: true, source: "export const from = 'hook3';" }; } "#; let hook1 = ModuleEvaluator::eval_js(ctx.clone(), "hook1", hook1_code) .await .unwrap(); let hook2 = ModuleEvaluator::eval_js(ctx.clone(), "hook2", hook2_code) .await .unwrap(); let hook3 = ModuleEvaluator::eval_js(ctx.clone(), "hook3", hook3_code) .await .unwrap(); let binding = ctx.userdata::>().unwrap(); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook1.get("load").unwrap()), }); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook2.get("load").unwrap()), }); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook3.get("load").unwrap()), }); let binding = ctx.userdata::>().unwrap(); let hooks = Rc::new(binding.borrow().hooks.clone()); let result1 = call_load_hooks(&ctx, &hooks, "intercept-here").unwrap(); let globals = ctx.globals(); let order: Vec = globals.get("order").unwrap(); assert_eq!(order, vec!["hook1", "hook2"]); assert_eq!( result1.get::<_, String>("source").unwrap(), "export const from = 'hook2';" ); assert!(result1.get::<_, bool>("shortCircuit").unwrap()); let result2 = call_load_hooks(&ctx, &hooks, "other").unwrap(); let order2: Vec = globals.get("order").unwrap(); assert_eq!(order2, vec!["hook1", "hook2", "hook3"]); assert_eq!( result2.get::<_, String>("source").unwrap(), "export const from = 'hook3';" ); assert!(result2.get::<_, bool>("shortCircuit").unwrap()); }) }) .await; } #[tokio::test] async fn test_hook_url_transformation_chain() { use llrt_test::ModuleEvaluator; test_async_with(|ctx| { Box::pin(async move { let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let hook1_code = r#" export function load(url, context, nextLoad) { return nextLoad(url.replace("@", "node_modules/"), context); } "#; let hook2_code = r#" export function load(url, context, nextLoad) { globalThis.transformedUrl = url; return nextLoad(url + "/index.js", context); } "#; let hook3_code = r#" export function load(url, context, nextLoad) { globalThis.finalUrl = url; return { shortCircuit: true, source: "export default {};" }; } "#; let hook1 = ModuleEvaluator::eval_js(ctx.clone(), "hook1", hook1_code) .await .unwrap(); let hook2 = ModuleEvaluator::eval_js(ctx.clone(), "hook2", hook2_code) .await .unwrap(); let hook3 = ModuleEvaluator::eval_js(ctx.clone(), "hook3", hook3_code) .await .unwrap(); let binding = ctx.userdata::>().unwrap(); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook1.get("load").unwrap()), }); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook2.get("load").unwrap()), }); binding.borrow_mut().hooks.push(Hook { resolve: None, load: Some(hook3.get("load").unwrap()), }); let binding = ctx.userdata::>().unwrap(); let hooks = Rc::new(binding.borrow().hooks.clone()); let result = call_load_hooks(&ctx, &hooks, "@pkg/module").unwrap(); let globals = ctx.globals(); assert_eq!( globals.get::<_, String>("transformedUrl").unwrap(), "node_modules/pkg/module" ); assert_eq!( globals.get::<_, String>("finalUrl").unwrap(), "node_modules/pkg/module/index.js" ); assert!(result.get::<_, bool>("shortCircuit").unwrap()); assert_eq!( result.get::<_, String>("source").unwrap(), "export default {};" ); }) }) .await; } } ================================================ FILE: llrt_modules/src/module/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ cell::RefCell, collections::{HashMap, HashSet}, marker::PhantomData, rc::Rc, }; use llrt_utils::{ ctx::CtxExt, module::{export_default, ModuleInfo}, result::ResultExt, }; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, object::Accessor, prelude::Func, Ctx, Error, Exception, Function, JsLifetime, Object, Result, Value, }; pub mod loader; mod require; pub mod resolver; use crate::CJS_IMPORT_PREFIX; #[derive(JsLifetime)] pub struct ModuleNames<'js> { list: HashSet, _marker: PhantomData<&'js ()>, } impl ModuleNames<'_> { pub fn new(names: HashSet) -> Self { Self { list: names, _marker: PhantomData, } } pub fn get_list(&self) -> HashSet { self.list.clone() } } #[derive(Default)] pub struct RequireState<'js> { pub cache: HashMap, Value<'js>>, pub exports: HashMap, Value<'js>>, pub progress: HashMap, Object<'js>>, } unsafe impl<'js> JsLifetime<'js> for RequireState<'js> { type Changed<'to> = RequireState<'to>; } #[derive(Clone, JsLifetime)] struct Hook<'js> { resolve: Option>, load: Option>, } #[derive(JsLifetime)] pub struct ModuleHookState<'js> { hooks: Vec>, } impl Default for ModuleHookState<'_> { fn default() -> Self { Self::new() } } impl ModuleHookState<'_> { fn new() -> Self { Self { hooks: Vec::new() } } } pub struct ModuleModule; fn create_require(ctx: Ctx<'_>) -> Result> { ctx.globals() .get::<_, Function>("require") .map(|f| f.into()) .map_err(|_| Exception::throw_reference(&ctx, "create_require is not supported")) } fn is_builtin(ctx: Ctx<'_>, name: String) -> Result { let module_list = ctx .userdata::() .ok_or_else(|| Exception::throw_reference(&ctx, "is_builtin is not supported"))? .get_list(); Ok(module_list.contains(&name)) } pub fn register_hooks<'js>(ctx: Ctx<'js>, hooks_obj: Object<'js>) -> Result<()> { let resolve = hooks_obj.get::<_, Function>("resolve").ok(); let load = hooks_obj.get::<_, Function>("load").ok(); let hook = Hook { resolve, load }; let binding = ctx.userdata::>().or_throw(&ctx)?; let mut state = binding.borrow_mut(); state.hooks.push(hook); Ok(()) } impl ModuleDef for ModuleModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("builtinModules")?; declare.declare("createRequire")?; declare.declare("isBuiltin")?; declare.declare("registerHooks")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { let module_list = ctx .userdata::() .map_or_else(HashSet::new, |v| v.get_list()); default.set("builtinModules", module_list)?; default.set("createRequire", Func::from(create_require))?; default.set("isBuiltin", Func::from(is_builtin))?; default.set("registerHooks", Func::from(register_hooks))?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: ModuleModule) -> Self { ModuleInfo { name: "module", module: val, } } } pub fn init(ctx: &Ctx) -> Result<()> { let globals = ctx.globals(); let _ = ctx.store_userdata(RefCell::new(RequireState::default())); let _ = ctx.store_userdata(RefCell::new(ModuleHookState::default())); let exports_accessor = Accessor::new( |ctx| { struct Args<'js>(Ctx<'js>); let Args(ctx) = Args(ctx); let name = ctx.get_script_or_module_name()?; let name = name.trim_start_matches(CJS_IMPORT_PREFIX); let binding = ctx.userdata::>().unwrap(); let mut state = binding.borrow_mut(); if let Some(value) = state.exports.get(name) { Ok::<_, Error>(value.clone()) } else { let obj = Object::new(ctx.clone())?.into_value(); state.exports.insert(name.into(), obj.clone()); Ok::<_, Error>(obj) } }, |ctx, exports| { struct Args<'js>(Ctx<'js>, Value<'js>); let Args(ctx, exports) = Args(ctx, exports); let name = ctx.get_script_or_module_name()?; let name = name.trim_start_matches(CJS_IMPORT_PREFIX); let binding = ctx.userdata::>().unwrap(); let mut state = binding.borrow_mut(); state.exports.insert(name.into(), exports); Ok::<_, Error>(()) }, ) .configurable() .enumerable(); globals.prop("exports", exports_accessor)?; globals.set("require", Func::from(require::require))?; let module = Object::new(ctx.clone())?; module.prop("exports", exports_accessor)?; globals.prop("module", module)?; Ok(()) } ================================================ FILE: llrt_modules/src/module/require.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{cell::RefCell, collections::HashSet, fs, rc::Rc}; use llrt_hooking::{invoke_async_hook, register_finalization_registry, HookType}; use llrt_json::parse::json_parse; use llrt_utils::{ctx::CtxExt, io::BYTECODE_FILE_EXT, provider::ProviderType}; use rquickjs::{atom::PredefinedAtom, qjs, Ctx, Filter, Function, Module, Object, Result, Value}; use tokio::time::Instant; use tracing::trace; use crate::modules::{path::resolve_path, timers::poll_timers}; use crate::package::resolver::require_resolve; use crate::CJS_IMPORT_PREFIX; use super::{ModuleNames, RequireState}; pub fn require(ctx: Ctx<'_>, specifier: String) -> Result> { let globals = ctx.globals(); let hooked_fn: Option = globals.get("__require_hook").ok(); struct Args<'js>(Ctx<'js>); let Args(ctx) = Args(ctx); let module_list = ctx .userdata::() .map_or_else(HashSet::new, |v| v.get_list()); let is_cjs_import = specifier.starts_with(CJS_IMPORT_PREFIX); let import_name: Rc; let is_json = specifier.ends_with(".json"); trace!("Before specifier: {}", specifier); let import_specifier: Rc = if !is_cjs_import { let is_bytecode = specifier.ends_with(BYTECODE_FILE_EXT); let is_bytecode_or_json = is_json || is_bytecode; let specifier = if is_bytecode_or_json { specifier } else { specifier .trim_start_matches("node:") .trim_end_matches("/") .to_string() }; if module_list.contains(specifier.as_str()) { import_name = specifier.into(); import_name.clone() } else { let module_name = ctx.get_script_or_module_name()?; let module_name = module_name.trim_start_matches(CJS_IMPORT_PREFIX); let abs_path = resolve_path([module_name].iter())?; let resolved_path = require_resolve(&ctx, &specifier, &abs_path, hooked_fn, false)?.into_owned(); import_name = resolved_path.into(); if is_bytecode_or_json { import_name.clone() } else { [CJS_IMPORT_PREFIX, &import_name].concat().into() } } } else { import_name = specifier[CJS_IMPORT_PREFIX.len()..].into(); specifier.into() }; trace!("After specifier: {}", import_specifier); let binding = ctx.userdata::>().unwrap(); let mut state = binding.borrow_mut(); if let Some(cached_value) = state.cache.get(import_name.as_ref()) { return Ok(cached_value.clone()); } if is_json { let json = fs::read_to_string(import_name.as_ref())?; let json = json_parse(&ctx, json)?; state.cache.insert(import_name, json.clone()); return Ok(json); } if let Some(obj) = state.progress.get(&import_name) { let value = obj.clone().into_value(); return Ok(value); } trace!("Require: {}", import_specifier); let obj = Object::new(ctx.clone())?; state.progress.insert(import_name.clone(), obj.clone()); drop(state); let import_promise = Module::import(&ctx, import_specifier.as_bytes())?; let rt = unsafe { qjs::JS_GetRuntime(ctx.as_raw().as_ptr()) }; let mut deadline = Instant::now(); let mut executing_timers = Vec::new(); // SAFETY: Since it checks in advance whether it is an Object type, we can always get a pointer to the object. let uid = unsafe { qjs::JS_VALUE_GET_PTR(obj.as_object().unwrap().as_raw()) } as usize; register_finalization_registry(&ctx, obj.clone().into_value(), uid)?; invoke_async_hook(&ctx, HookType::Init, ProviderType::TimerWrap, uid)?; let imported_object = loop { if let Some(x) = import_promise.result::() { break x?; } if deadline < Instant::now() { poll_timers(rt, &mut executing_timers, None, Some(&mut deadline))?; } ctx.execute_pending_job(); }; let binding = ctx.userdata::>().unwrap(); let mut state = binding.borrow_mut(); let exports_obj = state.exports.get(&import_name).cloned(); state.progress.remove(import_name.as_ref()); if let Some(exports_obj) = exports_obj { if exports_obj.type_of() == rquickjs::Type::Object { drop(state); let exports = unsafe { exports_obj.as_object().unwrap_unchecked() }; for prop in exports.own_props::(Filter::new().private().string().symbol()) { let (key, value) = prop?; obj.set(key, value)?; } } else { //we have explicitly set it state.cache.insert(import_name, exports_obj.clone()); return Ok(exports_obj); } } else { drop(state); } let binding = ctx.userdata::>().unwrap(); let mut state = binding.borrow_mut(); let props = imported_object.props::(); let default_export: Option = imported_object.get(PredefinedAtom::Default)?; if let Some(default_export) = default_export { //if default export is object attach all named exports to if let Some(default_object) = default_export.as_object() { for prop in props { let (key, value) = prop?; if !default_object.contains_key(&key)? { default_object.set(key, value)?; } } let default_object = default_object.clone().into_value(); state.cache.insert(import_name, default_object.clone()); return Ok(default_object); } } for prop in props { let (key, value) = prop?; obj.set(key, value)?; } let value = obj.into_value(); state.cache.insert(import_name, value.clone()); Ok(value) } ================================================ FILE: llrt_modules/src/module/resolver.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ cell::{Cell, RefCell}, collections::HashSet, rc::Rc, }; use llrt_utils::{object::ObjectExt, result::ResultExt}; use rquickjs::{ loader::Resolver, prelude::{Func, Opt}, Ctx, Error, Object, Result, Value, }; use tracing::trace; use crate::CJS_IMPORT_PREFIX; use super::{Hook, ModuleHookState}; #[derive(Debug, Default)] pub struct ModuleResolver { modules: HashSet, } impl ModuleResolver { #[must_use] pub fn add_name>(mut self, path: P) -> Self { self.modules.insert(path.into()); self } } impl Resolver for ModuleResolver { fn resolve(&mut self, ctx: &Ctx<'_>, base: &str, name: &str) -> Result { let name = name.trim_start_matches(CJS_IMPORT_PREFIX); let name = name.trim_start_matches("node:").trim_end_matches("/"); let base = base.trim_start_matches(CJS_IMPORT_PREFIX); trace!("Try resolve '{}' from '{}'", name, base); let (short_circuit, next_resolve, x) = module_hook_resolve(ctx, name, base)?; if short_circuit { trace!("+- Resolved by `ShortCircuit`: {}", x); return Ok(x); } if next_resolve { trace!("| Determined as `nextResolve`: {}", x); } else { trace!("| Determined as `NormalCircuit`: {}", x); } if self.modules.contains(&x) { trace!("+- Resolved by `NativeModule`: {}", x); Ok(x) } else { Err(Error::new_resolving(base, x)) } } } pub fn module_hook_resolve<'js>(ctx: &Ctx<'js>, x: &str, y: &str) -> Result<(bool, bool, String)> { trace!("| module_hook_resolve(x, y):({}, {})", x, y); let bind_state = ctx.userdata::>().or_throw(ctx)?; let hooks = Rc::new(bind_state.borrow().hooks.clone()); if hooks.is_empty() { return Ok((false, false, x.into())); } let result = call_resolve_hooks(ctx, &hooks, x, y)?; let short_circuit = result .get_optional::<_, bool>("shortCircuit")? .unwrap_or(false); let next_resolve = result .get_optional::<_, bool>("__nextResolve")? .unwrap_or(false); let url = result.get::<_, String>("url")?; Ok((short_circuit, next_resolve, url)) } #[allow(dependency_on_unit_never_type_fallback)] fn call_resolve_hooks<'js>( ctx: &Ctx<'js>, hooks: &Rc>>, spec: &str, parent_url: &str, ) -> Result> { call_resolve_hooks_from(ctx, hooks, 0, spec, parent_url) } fn call_resolve_hooks_from<'js>( ctx: &Ctx<'js>, hooks: &Rc>>, start_index: usize, spec: &str, parent_url: &str, ) -> Result> { for index in start_index..hooks.len() { let Some(resolve_fn) = &hooks[index].resolve else { continue; }; let context = Object::new(ctx.clone())?; context.set("parentURL", parent_url)?; let called_next = Rc::new(Cell::new(false)); let called_next_ref = Rc::clone(&called_next); let spec_clone = spec.to_string(); let hooks_clone = Rc::clone(hooks); let next_func = Func::new( move |ctx: Ctx<'js>, new_spec: String, opt_ctx: Opt>| -> Result> { let new_parent = if let Some(val) = opt_ctx.0 { if let Some(ctx_obj) = val.as_object() { ctx_obj .get::<_, String>("parentURL") .unwrap_or_else(|_| spec_clone.clone()) } else { spec_clone.clone() } } else { spec_clone.clone() }; called_next_ref.set(true); call_resolve_hooks_from(&ctx, &hooks_clone, index + 1, &new_spec, &new_parent) }, ); let result = resolve_fn.call::<_, Object>((spec, context, next_func))?; result.set("__nextResolve", called_next.get())?; return Ok(result); } let obj = Object::new(ctx.clone())?; obj.set("url", spec)?; obj.set("shortCircuit", false)?; obj.set("__nextResolve", false)?; Ok(obj) } ================================================ FILE: llrt_modules/src/module_builder.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::collections::HashSet; use llrt_utils::module::ModuleInfo; use rquickjs::{module::ModuleDef, Ctx, Result}; use crate::module::{loader::ModuleLoader, resolver::ModuleResolver, ModuleNames}; #[derive(Debug, Default)] pub struct GlobalAttachment { names: HashSet, functions: Vec) -> Result<()>>, } impl GlobalAttachment { pub fn add_function(mut self, init: fn(&Ctx<'_>) -> Result<()>) -> Self { self.functions.push(init); self } pub fn add_name>(mut self, path: P) -> Self { self.names.insert(path.into()); self } pub fn attach(self, ctx: &Ctx<'_>) -> Result<()> { if !self.names.is_empty() { let _ = ctx.store_userdata(ModuleNames::new(self.names)); } for init in self.functions { init(ctx)?; } Ok(()) } } pub struct ModuleBuilder { module_resolver: ModuleResolver, module_loader: ModuleLoader, global_attachment: GlobalAttachment, } impl Default for ModuleBuilder { fn default() -> Self { let mut builder = Self::new(); builder = builder .with_global(crate::module::init) .with_module(crate::module::ModuleModule); #[cfg(feature = "abort")] { builder = builder.with_global(crate::modules::abort::init); } #[cfg(feature = "assert")] { builder = builder.with_module(crate::modules::assert::AssertModule); } #[cfg(feature = "async-hooks")] { builder = builder .with_global(crate::modules::async_hooks::init) .with_module(crate::modules::async_hooks::AsyncHooksModule); } #[cfg(feature = "buffer")] { builder = builder .with_global(crate::modules::buffer::init) .with_module(crate::modules::buffer::BufferModule); } #[cfg(feature = "child-process")] { builder = builder.with_module(crate::modules::child_process::ChildProcessModule); } #[cfg(feature = "console")] { builder = builder .with_global(crate::modules::console::init) .with_module(crate::modules::console::ConsoleModule); } #[cfg(feature = "crypto")] { builder = builder .with_global(crate::modules::crypto::init) .with_module(crate::modules::crypto::CryptoModule); } #[cfg(feature = "dgram")] { builder = builder.with_module(crate::modules::dgram::DgramModule); } #[cfg(feature = "dns")] { builder = builder.with_module(crate::modules::dns::DnsModule); } #[cfg(feature = "events")] { builder = builder .with_global(crate::modules::events::init) .with_module(crate::modules::events::EventsModule); } #[cfg(feature = "exceptions")] { builder = builder.with_global(crate::modules::exceptions::init); } #[cfg(feature = "https")] { builder = builder.with_module(crate::modules::https::HttpsModule); } #[cfg(feature = "fetch")] { builder = builder.with_global(crate::modules::fetch::init); } #[cfg(feature = "fs")] { builder = builder .with_module(crate::modules::fs::FsPromisesModule) .with_module(crate::modules::fs::FsModule); } #[cfg(feature = "intl")] { builder = builder.with_global(crate::modules::intl::init); } #[cfg(feature = "navigator")] { builder = builder.with_global(crate::modules::navigator::init); } #[cfg(feature = "net")] { builder = builder.with_module(crate::modules::net::NetModule); } #[cfg(feature = "os")] { builder = builder.with_module(crate::modules::os::OsModule); } #[cfg(feature = "path")] { builder = builder.with_module(crate::modules::path::PathModule); } #[cfg(feature = "perf-hooks")] { builder = builder .with_global(crate::modules::perf_hooks::init) .with_module(crate::modules::perf_hooks::PerfHooksModule); } #[cfg(feature = "process")] { builder = builder .with_global(crate::modules::process::init) .with_module(crate::modules::process::ProcessModule); } #[cfg(feature = "stream-web")] { builder = builder .with_global(crate::modules::stream_web::init) .with_module(crate::modules::stream_web::StreamWebModule); } #[cfg(feature = "string-decoder")] { builder = builder.with_module(crate::modules::string_decoder::StringDecoderModule); } #[cfg(feature = "temporal")] { builder = builder.with_global(crate::modules::temporal::init); } #[cfg(feature = "timers")] { builder = builder .with_global(crate::modules::timers::init) .with_module(crate::modules::timers::TimersModule); } #[cfg(feature = "tty")] { builder = builder.with_module(crate::modules::tty::TtyModule); } #[cfg(feature = "url")] { builder = builder .with_global(crate::modules::url::init) .with_module(crate::modules::url::UrlModule); } #[cfg(feature = "util")] { builder = builder .with_global(crate::modules::util::init) .with_module(crate::modules::util::UtilModule); } #[cfg(feature = "zlib")] { builder = builder.with_module(crate::modules::zlib::ZlibModule); } builder } } impl ModuleBuilder { pub fn new() -> Self { Self { module_resolver: ModuleResolver::default(), module_loader: ModuleLoader::default(), global_attachment: GlobalAttachment::default(), } } pub fn with_module>>(mut self, module: I) -> Self { let module_info: ModuleInfo = module.into(); self.module_resolver = self.module_resolver.add_name(module_info.name); self.module_loader = self .module_loader .with_module(module_info.name, module_info.module); self.global_attachment = self.global_attachment.add_name(module_info.name); self } pub fn with_global(mut self, init: fn(&Ctx<'_>) -> Result<()>) -> Self { self.global_attachment = self.global_attachment.add_function(init); self } pub fn build(self) -> (ModuleResolver, ModuleLoader, GlobalAttachment) { ( self.module_resolver, self.module_loader, self.global_attachment, ) } } ================================================ FILE: llrt_modules/src/package/loader.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{fs::File, io::Read}; use rquickjs::{loader::Loader, Ctx, Function, Module, Object, Result, Value}; use tracing::trace; use crate::{CJS_IMPORT_PREFIX, CJS_LOADER_PREFIX}; #[derive(Debug, Default)] pub struct PackageLoader; impl PackageLoader { fn load_cjs_module<'js>(name: &str, ctx: Ctx<'js>) -> Result> { let cjs_specifier = [CJS_IMPORT_PREFIX, name].concat(); let require: Function = ctx.globals().get("require")?; let export_object: Value = require.call((&cjs_specifier,))?; let mut module = String::with_capacity(name.len() + 512); module.push_str("const value = require(\""); module.push_str(name); module.push_str("\");export default value.default||value;"); if let Some(obj) = export_object.as_object() { let keys: Result> = obj.keys().collect(); let keys = keys?; if !keys.is_empty() { module.push_str("const{"); for p in keys.iter() { if p == "default" { continue; } module.push_str(p); module.push(','); } module.truncate(module.len() - 1); module.push_str("}=value;"); module.push_str("export{"); for p in keys.iter() { if p == "default" { continue; } module.push_str(p); module.push(','); } module.truncate(module.len() - 1); module.push_str("};"); } } Module::declare(ctx, name, module) } fn normalize_name(name: &str) -> (bool, bool, &str, &str) { if !name.starts_with("__") { // If name doesn’t start with "__", return defaults return (false, false, name, name); } if let Some(cjs_path) = name.strip_prefix(CJS_IMPORT_PREFIX) { // If it starts with CJS_IMPORT_PREFIX, mark as from_cjs_import return (true, false, name, cjs_path); } if let Some(cjs_path) = name.strip_prefix(CJS_LOADER_PREFIX) { // If it starts with CJS_LOADER_PREFIX, mark as is_cjs return (false, true, cjs_path, cjs_path); } // Default return if no prefixes match (false, false, name, name) } fn load_module<'js>(name: &str, ctx: &Ctx<'js>) -> Result<(Module<'js>, Option)> { let ctx = ctx.clone(); let (from_cjs_import, is_cjs, normalized_name, path) = Self::normalize_name(name); trace!("+- Loading module: {}\n", normalized_name); //json files can never be from CJS imports as they are handled by require if !from_cjs_import { if normalized_name.ends_with(".json") { let mut file = File::open(path)?; let prefix = "export default JSON.parse(`"; let suffix = "`);"; let mut json = String::with_capacity(prefix.len() + suffix.len()); json.push_str(prefix); file.read_to_string(&mut json)?; json.push_str(suffix); return Ok((Module::declare(ctx, path, json)?, None)); } if is_cjs || normalized_name.ends_with(".cjs") { let url = ["file://", path].concat(); return Ok((Self::load_cjs_module(path, ctx)?, Some(url))); } } let bytes = std::fs::read(path)?; let mut bytes: &[u8] = &bytes; if !from_cjs_import && bytes.starts_with(b"#!") { bytes = bytes.splitn(2, |&c| c == b'\n').nth(1).unwrap_or(bytes); } let url = ["file://", path].concat(); Ok((Module::declare(ctx, normalized_name, bytes)?, Some(url))) } } impl Loader for PackageLoader { fn load<'js>(&mut self, ctx: &Ctx<'js>, name: &str) -> Result> { trace!("Try load '{}'", name); let (module, url) = Self::load_module(name, ctx)?; if let Some(url) = url { let meta: Object = module.meta()?; meta.prop("url", url)?; } Ok(module) } } ================================================ FILE: llrt_modules/src/package/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 pub mod loader; pub mod resolver; ================================================ FILE: llrt_modules/src/package/resolver.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ borrow::Cow, cell::RefCell, collections::HashMap, fs, path::{Path, PathBuf}, rc::Rc, sync::Mutex, }; use llrt_utils::{ io::{is_supported_ext, JS_EXTENSIONS, SUPPORTED_EXTENSIONS}, result::ResultExt, }; use once_cell::sync::Lazy; use rquickjs::{loader::Resolver, Ctx, Error, Function, Result}; use simd_json::{derived::ValueObjectAccessAsScalar, BorrowedValue}; use tracing::trace; use crate::modules::path; use crate::{CJS_IMPORT_PREFIX, CJS_LOADER_PREFIX, LLRT_PLATFORM}; fn rc_string_to_cow<'a>(rc: Rc) -> Cow<'a, str> { match Rc::try_unwrap(rc) { Ok(string) => Cow::Owned(string), Err(rc) => Cow::Owned((*rc).clone()), } } #[derive(Clone, Debug)] struct NodePathList(pub NodePathListValues); type NodePathListValues = Rc>>>; unsafe impl Send for NodePathList {} impl NodePathList { fn new() -> Self { Self(Rc::new(RefCell::new(Vec::new()))) } } //None entry means that there are no parent modules type NodeModulePaths = HashMap, Option>; static NODE_MODULES_PATHS_CACHE: Lazy> = Lazy::new(|| Mutex::new(HashMap::new())); static HOME_NODE_MODULES: Lazy>> = Lazy::new(|| { // Add global folders let mut paths = Vec::with_capacity(2); if let Some(home) = home::home_dir() { let home_node_modules = home.join(".node_modules"); let home_node_libraries = home.join(".node_libraries"); if home_node_modules.is_dir() { paths.push(Box::from(home_node_modules.to_string_lossy())); } if home_node_libraries.is_dir() { paths.push(Box::from(home_node_libraries.to_string_lossy())); } } paths }); static FILESYSTEM_ROOT: Lazy> = Lazy::new(|| { #[cfg(unix)] { "/".into() } #[cfg(windows)] { if let Some(path) = home::home_dir() { if let Some(std::path::Component::Prefix(prefix)) = path.components().next() { return prefix .as_os_str() .to_string_lossy() .into_owned() .into_boxed_str(); } } "C:".to_string().into_boxed_str() } }); #[derive(Debug, Default)] pub struct PackageResolver; #[allow(clippy::manual_strip)] impl Resolver for PackageResolver { fn resolve(&mut self, ctx: &Ctx, base: &str, name: &str) -> Result { if name.starts_with(CJS_IMPORT_PREFIX) { return Ok(name.to_string()); } let base = base.trim_start_matches(CJS_IMPORT_PREFIX); trace!("Try resolve '{}' from '{}'", name, base); require_resolve(ctx, name, base, None, true).map(|name| name.into_owned()) } } // [CJS Reference Implementation](https://nodejs.org/api/modules.html#all-together) // require(X) from module at path Y #[allow(clippy::type_complexity)] pub fn require_resolve<'a>( ctx: &Ctx<'_>, x: &'a str, y: &str, hooked_fn: Option>, is_esm: bool, ) -> Result> { // trim schema let x = x.trim_start_matches("file://"); // resolve symlink let y = if let Ok(path) = Path::new(y).read_link() { if path.is_absolute() { path.to_string_lossy().to_string() } else { [y, "/../", path.to_string_lossy().as_ref()].concat() } } else { y.to_string() }; let y = y.as_str(); trace!("require_resolve(x, y):({}, {})", x, y); // 1'. If X is a bytecode cache, if let Some(hooked_resolve) = hooked_fn { if let Ok(path) = hooked_resolve.call::<_, String>((x, y)) { return Ok(path.into()); } } //fast path for when we have supported extensions let (_, ext_name) = path::name_extname(x); let is_supported_ext = is_supported_ext(ext_name); let x_is_absolute = path::is_absolute(x); let x_starts_with_current_dir = x.starts_with("./"); let x_starts_with_parent_dir = x.starts_with(".."); if is_supported_ext && Path::new(x).is_file() { return resolved_by_file_exists(x.into()); } let x_normalized = path::normalize(x); if !x_starts_with_parent_dir && is_supported_ext && Path::new(&x_normalized).is_file() { return resolved_by_file_exists(x_normalized.into()); } // 2. If X begins with '/' let y = if path::is_absolute(x) { // a. set Y to be the file system root &*FILESYSTEM_ROOT } else { y }; // Normalize path Y to generate dirname(Y) let dirname_y = if Path::new(y).is_dir() { path::resolve_path([y].iter())? } else { let dirname_y = path::dirname(y); path::resolve_path([&dirname_y].iter())? }; // 3. If X begins with './' or '/' or '../' if x_starts_with_current_dir || x_is_absolute || x_starts_with_parent_dir { let y_plus_x = if x_is_absolute { x.into() } else if x_starts_with_current_dir { [&dirname_y, "/", &x[2..]].concat() } else { [&dirname_y, "/", x].concat() }; let y_plus_x = Rc::new(y_plus_x); // a. LOAD_AS_FILE(Y + X) if let Ok(Some(path)) = load_as_file(ctx, y_plus_x.clone()) { trace!("+- Resolved by `LOAD_AS_FILE`: {}", path); return to_abs_path(path); } else { // b. LOAD_AS_DIRECTORY(Y + X) if let Ok(Some(path)) = load_as_directory(ctx, y_plus_x) { trace!("+- Resolved by `LOAD_AS_DIRECTORY`: {}", path); return to_abs_path(path); } } // c. THROW "not found" return Err(Error::new_resolving(y.to_owned(), x.to_owned())); } // 4. If X begins with '#' if x.starts_with('#') { // a. LOAD_PACKAGE_IMPORTS(X, dirname(Y)) if let Ok(Some(path)) = load_package_imports(ctx, x, &dirname_y) { trace!("+- Resolved by `LOAD_PACKAGE_IMPORTS`: {}", path); return Ok(path.into()); } } // 5. LOAD_PACKAGE_SELF(X, dirname(Y)) if let Ok(Some(path)) = load_package_self(ctx, x, &dirname_y, is_esm) { trace!("+- Resolved by `LOAD_PACKAGE_SELF`: {}", path); return to_abs_path(path.into()); } // 6. LOAD_NODE_MODULES(X, dirname(Y)) if let Some(path) = load_node_modules(ctx, x, dirname_y, is_esm) { trace!("+- Resolved by `LOAD_NODE_MODULES`: {}", path); return Ok(path); } // 6.5. LOAD_AS_FILE(X) if let Ok(Some(path)) = load_as_file(ctx, Rc::new(x.to_owned())) { trace!("+- Resolved by `LOAD_AS_FILE`: {}", path); return to_abs_path(path); } // 7. THROW "not found" Err(Error::new_resolving(y.to_string(), x.to_string())) } fn resolved_by_file_exists(path: Cow<'_, str>) -> Result> { trace!("+- Resolved by `FILE`: {}", path); to_abs_path(path) } fn to_abs_path(path: Cow<'_, str>) -> Result> { Ok(if !path::is_absolute(&path) { path::resolve_path_with_separator([path], true)?.into() } else if cfg!(windows) { path::replace_backslash(path).into() } else { path }) } // LOAD_AS_FILE(X) fn load_as_file<'a>(ctx: &Ctx<'_>, x: Rc) -> Result>> { trace!("| load_as_file(x): {}", x); // 1. If X is a file, load X as its file extension format. STOP if Path::new(x.as_ref()).is_file() { trace!("| load_as_file(1): {}", x); return Ok(Some(rc_string_to_cow(x))); } let mut base_file = String::with_capacity(x.len() + 4); base_file.push_str(x.as_ref()); let base_file_length = base_file.len(); let mut base_file = Some(base_file); // 2. If X.js is a file, for extension in SUPPORTED_EXTENSIONS.iter() { if let Some(mut current_file) = base_file.take() { current_file.truncate(base_file_length); current_file.push_str(extension); if Path::new(¤t_file).is_file() { // a. Find the closest package scope SCOPE to X. match find_the_closest_package_scope(&x) { // b. If no scope was found None => { // 1. MAYBE_DETECT_AND_LOAD(X.js) trace!("| load_as_file(2.b.1): {}", current_file); return Ok(Some(current_file.into())); }, Some(path) => { let mut package_json = fs::read(path.as_ref()).or_throw(ctx)?; let package_json = simd_json::to_borrowed_value(&mut package_json).or_throw(ctx)?; // c. If the SCOPE/package.json contains "type" field, if let Some(_type) = get_string_field(&package_json, "type") { // 1. If the "type" field is "module", load X.js as an ECMAScript module. STOP. // 2. If the "type" field is "commonjs", load X.js as an CommonJS module. STOP. if _type == "module" || _type == "commonjs" { trace!("| load_as_file(2.c.[1|2]): {}", current_file); return Ok(Some(current_file.into())); } } }, } // d. MAYBE_DETECT_AND_LOAD(X.js) trace!("| load_as_file(2.d): {}", current_file); return Ok(Some(current_file.into())); } base_file = Some(current_file); } } // 3. If X.json is a file, load X.json to a JavaScript Object. STOP if let Some(mut current_file) = base_file.take() { current_file.truncate(base_file_length); current_file.push_str(".json"); if Path::new(¤t_file).is_file() { trace!("| load_as_file(3): {}", current_file); return Ok(Some(current_file.into())); } } // 4. If X.node is a file, load X.node as binary addon. STOP Ok(None) } // LOAD_INDEX(X) fn load_index<'a>(ctx: &Ctx<'_>, x: Rc) -> Result>> { trace!("| load_index(x): {}", x); let mut base_file = String::with_capacity(x.len() + "/index".len() + 4); base_file.push_str(x.as_ref()); base_file.push_str("/index"); let base_file_length = base_file.len(); let mut base_file = Some(base_file); // 1. If X/index.js is a file for extension in SUPPORTED_EXTENSIONS.iter() { if let Some(mut file) = base_file.take() { file.truncate(base_file_length); file.push_str(extension); if Path::new(&file).is_file() { // a. Find the closest package scope SCOPE to X. match find_the_closest_package_scope(&x) { // b. If no scope was found, load X/index.js as a CommonJS module. STOP. None => { trace!("| load_index(1.b): {}", file); return Ok(Some(file.into())); }, // c. If the SCOPE/package.json contains "type" field, Some(path) => { let mut package_json = fs::read(path.as_ref()).or_throw(ctx)?; let package_json = simd_json::to_borrowed_value(&mut package_json).or_throw(ctx)?; if let Some(_type) = get_string_field(&package_json, "type") { // 1. If the "type" field is "module", load X/index.js as an ECMAScript module. STOP. if _type == "module" { trace!("| load_index(1.c.1): {}", file); return Ok(Some(file.into())); } } // 2. Else, load X/index.js as an CommonJS module. STOP. trace!("| load_index(1.c.2): {}", file); return Ok(Some(file.into())); }, } } base_file = Some(file); } } // 2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP if let Some(mut file) = base_file.take() { file.truncate(base_file_length); file.push_str(".json"); if Path::new(&file).is_file() { trace!("| load_index(2): {}", file); return Ok(Some(file.into())); } } // 3. If X/index.node is a file, load X/index.node as binary addon. STOP Ok(None) } // LOAD_AS_DIRECTORY(X) fn load_as_directory<'a>(ctx: &Ctx<'_>, x: Rc) -> Result>> { trace!("| load_as_directory(x): {}", x); // 1. If X/package.json is a file, let file = [&x, "/package.json"].concat(); if Path::new(&file).is_file() { // a. Parse X/package.json, and look for "main" field. let mut package_json = fs::read(file).or_throw(ctx)?; let package_json = simd_json::to_borrowed_value(&mut package_json).or_throw(ctx)?; // b. If "main" is a falsy value, GOTO 2. if let Some(main) = get_string_field(&package_json, "main") { // c. let M = X + (json main field) let m = Rc::new([&x, "/", main].concat()); // d. LOAD_AS_FILE(M) if let Ok(Some(path)) = load_as_file(ctx, m.clone()) { trace!("| load_as_directory(1.d): {}", path); return Ok(Some(path)); } // e. LOAD_INDEX(M) if let Ok(Some(path)) = load_index(ctx, m) { trace!("| load_as_directory(1.e): {}", path); return Ok(Some(path)); } // f. LOAD_INDEX(X) DEPRECATED // g. THROW "not found" return Err(Error::new_resolving("", x.to_string())); } } // 2. LOAD_INDEX(X) if let Ok(Some(path)) = load_index(ctx, x) { trace!("| load_as_directory(2): {}", path); return Ok(Some(path)); } Ok(None) } // LOAD_NODE_MODULES(X, START) fn load_node_modules<'a>( ctx: &Ctx<'_>, x: &str, start: String, is_esm: bool, ) -> Option> { trace!("| load_node_modules(x, start): ({}, {})", x, start); fn search_dir<'a>(ctx: &Ctx<'_>, dir: &str, x: &str, is_esm: bool) -> Option> { // a. LOAD_PACKAGE_EXPORTS(X, DIR) if let Ok(path) = load_package_exports(ctx, x, dir, is_esm) { trace!("| load_node_modules(2.a): {}", path); return Some(path); } let dir_slash_x = Rc::new([dir, "/", x].concat()); // b. LOAD_AS_FILE(DIR/X) if let Ok(Some(path)) = load_as_file(ctx, dir_slash_x.clone()) { trace!("| load_node_modules(2.b): {}", path); return Some(path); } // c. LOAD_AS_DIRECTORY(DIR/X) if let Ok(Some(path)) = load_as_directory(ctx, dir_slash_x.clone()) { trace!("| load_node_modules(2.c): {}", path); return Some(path); } None } // 1. let DIRS = NODE_MODULES_PATHS(START) let mut cache = NODE_MODULES_PATHS_CACHE.lock().unwrap(); let start = start.into_boxed_str(); //fast path if let Some(Some(dirs)) = cache.get(&start) { for dir in dirs.0.borrow().iter() { if let Some(path) = search_dir(ctx, dir, x, is_esm) { return Some(path); } } } let path = Path::new(start.as_ref()); let results = NodePathList::new(); let mut paths_to_cache = Vec::new(); let mut current = Some(path); let mut i = 0; let mut last_found_index = 0; while let Some(dir) = current { let str_dir = dir.to_string_lossy(); if let Some(dirs) = cache.get(str_dir.as_ref()) { if let Some(dirs) = dirs { results .0 .borrow_mut() .extend(dirs.0.borrow().clone().into_iter()); } last_found_index = i; //there are no modules beyond this point, just search globals break; } if dir.file_name().is_some_and(|name| name != "node_modules") { let node_modules = dir.join("node_modules"); if node_modules.is_dir() { last_found_index = i; results .0 .borrow_mut() .push(node_modules.to_string_lossy().into()); } } paths_to_cache.push(str_dir); current = dir.parent(); i += 1; } for (i, path) in paths_to_cache.iter().enumerate() { let path = path.to_string().into_boxed_str(); if i <= last_found_index { cache.insert(path, Some(results.clone())); } else { cache.insert(path, None); break; } } for dir in results.0.borrow().iter() { if let Some(path) = search_dir(ctx, dir, x, is_esm) { return Some(path); } } for dir in HOME_NODE_MODULES.iter() { if let Some(path) = search_dir(ctx, dir, x, is_esm) { return Some(path); } } None } // LOAD_PACKAGE_IMPORTS(X, DIR) fn load_package_imports(ctx: &Ctx<'_>, x: &str, dir: &str) -> Result> { trace!("| load_package_imports(x, dir): ({}, {})", x, dir); // 1. Find the closest package scope SCOPE to DIR. // 2. If no scope was found, return. if let Some(path) = find_the_closest_package_scope(dir) { let mut package_json_file = fs::read(path.as_ref()).or_throw(ctx)?; let package_json: BorrowedValue = simd_json::to_borrowed_value(&mut package_json_file).or_throw(ctx)?; // 3. If the SCOPE/package.json "imports" is null or undefined, return. // 4. If `--experimental-require-module` is enabled // a. let CONDITIONS = ["node", "require", "module-sync"] // b. Else, let CONDITIONS = ["node", "require"] // 5. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE), // CONDITIONS) defined in the ESM resolver. // 6. RESOLVE_ESM_MATCH(MATCH). if let Some(module_path) = package_imports_resolve(&package_json, x) { trace!("| load_package_imports(6): {}", module_path); let dir = path.as_ref().trim_end_matches("package.json"); let module_path = to_abs_path(correct_extensions([dir, module_path].concat()))?; return Ok(Some(module_path.into())); } }; Ok(None) } // LOAD_PACKAGE_EXPORTS(X, DIR) fn load_package_exports<'a>( ctx: &Ctx<'_>, x: &str, dir: &str, is_esm: bool, ) -> Result> { trace!("| load_package_exports(x, dir): ({}, {})", x, dir); //1. Try to interpret X as a combination of NAME and SUBPATH where the name // may have a @scope/ prefix and the subpath begins with a slash (`/`). let mut n = 1; let (mut name, mut scope, mut is_last) = get_name_and_scope(x, n); //2. If X does not match this pattern or DIR/NAME/package.json is not a file, // return. let mut package_json_path = String::with_capacity(dir.len() + 64); package_json_path.push_str(dir); package_json_path.push('/'); let base_path_length = package_json_path.len(); let mut package_json_exists; loop { trace!( "| split name and scope(name, scope): ({}, {})", name, scope ); package_json_path.push_str(scope); package_json_path.push_str("/package.json"); package_json_exists = Path::new(&package_json_path).exists(); if package_json_exists || is_last { break; } n += 1; (name, scope, is_last) = get_name_and_scope(x, n); package_json_path.truncate(base_path_length); } let mut sub_module = None; let (scope, name) = if name != "." && !package_json_exists { package_json_path.truncate(base_path_length); package_json_path.push_str(x); package_json_path.push_str("/package.json"); if !Path::new(&package_json_path).exists() { return Err(Error::new_resolving(dir.to_string(), x.to_string())); } (x, ".") } else { let base_path = &package_json_path[..base_path_length]; let trimmed_name = name.trim_start_matches("."); let mut path = String::with_capacity(base_path.len() + scope.len() + trimmed_name.len() + 4); path.push_str(base_path); path.push_str(scope); if !trimmed_name.is_empty() { path.push('/'); } path.push_str(trimmed_name); let base_path_length = path.len(); let mut path = Some(path); for ext in JS_EXTENSIONS { if let Some(mut current_path) = path.take() { current_path.truncate(base_path_length); current_path.push_str(ext); if Path::new(¤t_path).exists() { if *ext == ".mjs" { //we know its an ESM module return Ok(current_path.into()); } sub_module = Some(current_path); break; } path = Some(current_path); } } (scope, name) }; //3. Parse DIR/NAME/package.json, and look for "exports" field. //4. If "exports" is null or undefined, return. //5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH, // `package.json` "exports", ["node", "require"]) defined in the ESM resolver. //6. RESOLVE_ESM_MATCH(MATCH) let mut package_json = fs::read(&package_json_path).or_throw(ctx)?; let package_json = simd_json::to_borrowed_value(&mut package_json).or_throw(ctx)?; if let Some(sub_module) = sub_module { if package_json.get_str("type") != Some("module") { let sub_module = to_abs_path(sub_module.into())?; if is_esm { return Ok([CJS_LOADER_PREFIX, &sub_module].concat().into()); } return Ok(sub_module); } return Ok(sub_module.into()); } let (module_path, resolve_path, is_cjs) = package_exports_resolve(&package_json, name, is_esm)?; let module_path = resolve_path.unwrap_or_else(|| module_path.to_string()); let module_path = to_abs_path(correct_extensions( [dir, "/", scope, "/", &module_path].concat(), ))?; if is_cjs && is_esm { return Ok([CJS_LOADER_PREFIX, &module_path].concat().into()); } Ok(module_path) } // LOAD_PACKAGE_SELF(X, DIR) fn load_package_self(ctx: &Ctx<'_>, x: &str, dir: &str, is_esm: bool) -> Result> { trace!("| load_package_self(x, dir): ({}, {})", x, dir); let mut n = 1; let (mut name, mut scope, mut is_last) = get_name_and_scope(x, n); // 1. Find the closest package scope SCOPE to DIR. let mut package_json_file: Vec; let package_json: BorrowedValue; let package_json_path: Box = match find_the_closest_package_scope(dir) { // 2. If no scope was found, return. None => { return Ok(None); }, Some(path) => { package_json_file = fs::read(path.as_ref()).or_throw(ctx)?; package_json = simd_json::to_borrowed_value(&mut package_json_file).or_throw(ctx)?; // 3. If the SCOPE/package.json "exports" is null or undefined, return. loop { trace!( "| split name and scope(name, scope): ({}, {})", name, scope ); // 4. If the SCOPE/package.json "name" is not the first segment of X, return. if is_exports_field_exists(&package_json) { if let Some(name) = get_string_field(&package_json, "name") { if name == scope { break path; } } } if is_last { return Ok(None); } n += 1; (name, scope, is_last) = get_name_and_scope(x, n); } }, }; // 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE), // "." + X.slice("name".length), `package.json` "exports", ["node", "require"]) // defined in the ESM resolver. // 6. RESOLVE_ESM_MATCH(MATCH) if let Ok((path, resolve_path, _)) = package_exports_resolve(&package_json, name, is_esm) { let path = resolve_path.unwrap_or_else(|| path.to_string()); trace!("| load_package_self(2.c): {}", path); let dir = package_json_path.trim_end_matches("package.json"); let module_path = correct_extensions([dir, &path].concat()); return Ok(Some(module_path.into())); } Ok(None) } fn get_name_and_scope(x: &str, n: usize) -> (&str, &str, bool) { if let Some(pos) = (0..n).try_fold(x.len(), |p, _| x[..p].rfind('/')) { (&x[pos + 1..], &x[..pos], false) } else { (".", x, true) } } // Implementation equivalent to PACKAGE_EXPORTS_RESOLVE including RESOLVE_ESM_MATCH fn package_exports_resolve<'a>( package_json: &'a BorrowedValue<'a>, modules_name: &str, is_esm: bool, ) -> Result<(&'a str, Option, bool)> { let ident = if is_esm { "import" } else { "require" }; let modules_name = if modules_name != "." { &["./", modules_name].concat() } else { modules_name }; let wildcard = if let Some(pos) = modules_name.rmatch_indices('/').nth(1) { let (name, scope, _) = get_name_and_scope(modules_name, pos.0); (Some(name), Some([scope, "/*"].concat())) } else { (None, None) }; if let BorrowedValue::Object(map) = package_json { let is_cjs = !matches!(map.get("type"), Some(BorrowedValue::String(ref _type)) if _type == "module"); if let Some(BorrowedValue::Object(exports)) = map.get("exports") { if let Some(BorrowedValue::Object(name)) = exports.get(modules_name) { // Check for exports -> name -> platform(browser or node) -> [import | require] if let Some(BorrowedValue::Object(platform)) = name.get(LLRT_PLATFORM.as_str()) { if let Some(BorrowedValue::String(ident)) = platform.get(ident) { return Ok((ident.as_ref(), None, is_cjs)); } } // Check for exports -> name -> [import | require] -> default if let Some(BorrowedValue::Object(ident)) = name.get(ident) { if let Some(BorrowedValue::String(default)) = ident.get("default") { return Ok((default.as_ref(), None, is_cjs)); } } // Check for exports -> name -> platform(browser or node) if let Some(BorrowedValue::String(platform)) = name.get(LLRT_PLATFORM.as_str()) { return Ok((platform.as_ref(), None, is_cjs)); } // Check for exports -> name -> [import | require] if let Some(BorrowedValue::String(ident)) = name.get(ident) { return Ok((ident.as_ref(), None, is_cjs)); } // Check for exports -> name -> default if let Some(BorrowedValue::String(default)) = name.get("default") { return Ok((default.as_ref(), None, is_cjs)); } } // Check for wildcard pattern if let Some(scope) = wildcard.1 { // Check for exports -> scope -> platform(browser or node) -> [import | require] if let Some(BorrowedValue::Object(name)) = exports.get(scope.as_str()) { if let Some(BorrowedValue::Object(platform)) = name.get(LLRT_PLATFORM.as_str()) { if let Some(BorrowedValue::String(ident)) = platform.get(ident) { let resolve_star = replace_star(ident, wildcard.0.unwrap()); return Ok((ident.as_ref(), Some(resolve_star), is_cjs)); } } // Check for exports -> scope -> [import | require] -> default if let Some(BorrowedValue::Object(ident)) = name.get(ident) { if let Some(BorrowedValue::String(default)) = ident.get("default") { let resolve_star = replace_star(default, wildcard.0.unwrap()); return Ok((default.as_ref(), Some(resolve_star), is_cjs)); } } // Check for exports -> scope -> platform(browser or node) if let Some(BorrowedValue::String(platform)) = name.get(LLRT_PLATFORM.as_str()) { let resolve_star = replace_star(platform, wildcard.0.unwrap()); return Ok((platform.as_ref(), Some(resolve_star), is_cjs)); } // Check for exports -> scope -> [import | require] if let Some(BorrowedValue::String(ident)) = name.get(ident) { let resolve_star = replace_star(ident, wildcard.0.unwrap()); return Ok((ident.as_ref(), Some(resolve_star), is_cjs)); } // Check for exports -> scope -> default if let Some(BorrowedValue::String(default)) = name.get("default") { let resolve_star = replace_star(default, wildcard.0.unwrap()); return Ok((default.as_ref(), Some(resolve_star), is_cjs)); } } } // Check for exports -> [import | require] -> default if let Some(BorrowedValue::Object(ident)) = exports.get(ident) { if let Some(BorrowedValue::String(default)) = ident.get("default") { return Ok((default.as_ref(), None, is_cjs)); } } // Check for exports -> [import | require] if let Some(BorrowedValue::String(ident)) = exports.get(ident) { return Ok((ident.as_ref(), None, is_cjs)); } // [CJS only] Check for exports -> default if !is_esm { if let Some(BorrowedValue::String(default)) = exports.get("default") { return Ok((default.as_ref(), None, is_cjs)); } } } // Check for platform(browser or node) field if let Some(BorrowedValue::String(platform)) = map.get(LLRT_PLATFORM.as_str()) { return Ok((platform.as_ref(), None, is_cjs)); } // [ESM only] Check for module field if is_esm { if let Some(BorrowedValue::String(module)) = map.get("module") { return Ok((module.as_ref(), None, is_cjs)); } } // Check for main field if let Some(BorrowedValue::String(main)) = map.get("main") { return Ok((main.as_ref(), None, is_cjs)); } } Ok(("./index.js", None, true)) } fn replace_star(scope: &str, name: &str) -> String { scope.replace("*", name) } // Implementation equivalent to PACKAGE_IMPORTS_RESOLVE including RESOLVE_ESM_MATCH fn package_imports_resolve<'a>( package_json: &'a BorrowedValue<'a>, modules_name: &str, ) -> Option<&'a str> { if let BorrowedValue::Object(map) = package_json { if let Some(BorrowedValue::Object(imports)) = map.get("imports") { if let Some(BorrowedValue::Object(name)) = imports.get(modules_name) { // Check for imports -> name -> platform(browser or node) if let Some(BorrowedValue::String(platform)) = name.get(LLRT_PLATFORM.as_str()) { return Some(platform.as_ref()); } // Check for imports -> name -> require if let Some(BorrowedValue::String(require)) = name.get("require") { return Some(require.as_ref()); } // Check for imports -> name -> module-sync if let Some(BorrowedValue::String(module_sync)) = name.get("module-sync") { return Some(module_sync.as_ref()); } // Check for imports -> name -> default if let Some(BorrowedValue::String(default)) = name.get("default") { return Some(default.as_ref()); } } // Check for imports -> name if let Some(BorrowedValue::String(name)) = imports.get(modules_name) { return Some(name.as_ref()); } } } None } fn find_the_closest_package_scope(start: &str) -> Option> { let mut current_dir = PathBuf::from(start); loop { let package_json_path = current_dir.join("package.json"); if package_json_path.exists() { return package_json_path.to_str().map(Box::from); } if !current_dir.pop() { break; } } None } fn get_string_field<'a>(package_json: &'a BorrowedValue<'a>, str: &str) -> Option<&'a str> { if let BorrowedValue::Object(map) = package_json { if let Some(BorrowedValue::String(val)) = map.get(str) { return Some(val.as_ref()); } } None } fn is_exports_field_exists<'a>(package_json: &'a BorrowedValue<'a>) -> bool { if let BorrowedValue::Object(map) = package_json { if let Some(BorrowedValue::Object(_)) = map.get("exports") { return true; } } false } fn correct_extensions<'a>(x: String) -> Cow<'a, str> { let (x_is_file, x_is_dir) = if let Ok(md) = fs::metadata(&x) { (md.is_file(), md.is_dir()) } else { (false, false) }; if x_is_file { return x.into(); }; let index = if x_is_dir { "/index" } else { "" }; let mut base_path = String::with_capacity(x.len() + index.len() + 4); //add capacity for extention base_path.push_str(&x); base_path.push_str(index); let base_path_length = base_path.len(); let mut path = Some(base_path); for extension in JS_EXTENSIONS.iter() { if let Some(mut current_path) = path.take() { current_path.truncate(base_path_length); current_path.push_str(extension); if Path::new(¤t_path).is_file() { return current_path.into(); } path = Some(current_path); } } x.into() } ================================================ FILE: modules/llrt_abort/Cargo.toml ================================================ [package] name = "llrt_abort" description = "LLRT Module abort" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_abort" path = "src/lib.rs" [features] default = ["sleep-timers"] sleep-timers = ["llrt_timers"] sleep-tokio = ["tokio"] [dependencies] llrt_async_hooks = { version = "0.8.1-beta", path = "../llrt_async_hooks" } llrt_exceptions = { version = "0.8.1-beta", path = "../llrt_exceptions" } llrt_events = { version = "0.8.1-beta", path = "../llrt_events" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } # Optional llrt_timers = { version = "0.8.1-beta", path = "../llrt_timers", optional = true } tokio = { version = "1", features = [ "time", ], default-features = false, optional = true } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = ["test-util"], default-features = false } ================================================ FILE: modules/llrt_abort/src/abort_controller.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{ atom::PredefinedAtom, prelude::{Opt, This}, Class, Ctx, JsLifetime, Result, Value, }; use super::AbortSignal; #[rquickjs::class] #[derive(rquickjs::class::Trace)] pub struct AbortController<'js> { signal: Class<'js, AbortSignal<'js>>, } unsafe impl<'js> JsLifetime<'js> for AbortController<'js> { type Changed<'to> = AbortController<'to>; } #[rquickjs::methods] impl<'js> AbortController<'js> { #[qjs(constructor)] pub fn new(ctx: Ctx<'js>) -> Result { let signal = AbortSignal::new(); let abort_controller = Self { signal: Class::instance(ctx, signal)?, }; Ok(abort_controller) } #[qjs(get)] pub fn signal(&self) -> Class<'js, AbortSignal<'js>> { self.signal.clone() } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(AbortController) } pub fn abort( ctx: Ctx<'js>, this: This>, reason: Opt>, ) -> Result<()> { let instance = this.0.borrow(); let signal = instance.signal.clone(); let mut signal_borrow = signal.borrow_mut(); if signal_borrow.aborted { //only once return Ok(()); } signal_borrow.set_reason(reason); drop(signal_borrow); AbortSignal::send_aborted(This(signal), ctx)?; Ok(()) } } ================================================ FILE: modules/llrt_abort/src/abort_signal.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{Arc, RwLock}; use llrt_events::{Emitter, EventEmitter, EventList}; use llrt_exceptions::{DOMException, DOMExceptionName}; use llrt_utils::mc_oneshot; use rquickjs::{ atom::PredefinedAtom, class::{Trace, Tracer}, function::OnceFn, prelude::{Opt, This}, Array, Class, Ctx, Error, Exception, Function, JsLifetime, Result, Undefined, Value, }; #[derive(Clone)] #[rquickjs::class] pub struct AbortSignal<'js> { emitter: EventEmitter<'js>, pub aborted: bool, reason: Option>, pub sender: mc_oneshot::Sender>, } unsafe impl<'js> JsLifetime<'js> for AbortSignal<'js> { type Changed<'to> = AbortSignal<'to>; } impl<'js> Trace<'js> for AbortSignal<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { if let Some(reason) = &self.reason { tracer.mark(reason); } self.emitter.trace(tracer); self.sender.trace(tracer); } } impl<'js> Emitter<'js> for AbortSignal<'js> { fn get_event_list(&self) -> Arc>> { self.emitter.get_event_list() } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> AbortSignal<'js> { #[qjs(constructor)] pub fn new() -> Self { let (sender, _) = mc_oneshot::channel::>(); Self { emitter: EventEmitter::new(), aborted: false, reason: None, sender, } } #[qjs(get, rename = "onabort")] pub fn get_on_abort(&self) -> Option> { Self::get_listeners_str(self, "abort").first().cloned() } #[qjs(set, rename = "onabort")] pub fn set_on_abort( this: This>, ctx: Ctx<'js>, listener: Function<'js>, ) -> Result<()> { Self::add_event_listener_str(this, &ctx, "abort", listener, false, false)?; Ok(()) } pub fn remove_on_abort( this: This>, ctx: Ctx<'js>, listener: Function<'js>, ) -> Result<()> { Self::remove_event_listener_str(this, &ctx, "abort", listener)?; Ok(()) } pub fn throw_if_aborted(&self, ctx: Ctx<'js>) -> Result<()> { if self.aborted { return Err(ctx.throw( self.reason .clone() .unwrap_or_else(|| Undefined.into_value(ctx.clone())), )); } Ok(()) } #[qjs(static)] pub fn any(ctx: Ctx<'js>, signals: Array<'js>) -> Result> { let mut new_signal = AbortSignal::new(); let mut signal_instances = Vec::with_capacity(signals.len()); for signal in signals.iter() { let signal: Value = signal?; let signal: Class = Class::from_value(&signal) .map_err(|_| Exception::throw_type(&ctx, "Value is not an AbortSignal instance"))?; let signal_borrow = signal.borrow(); if signal_borrow.aborted { new_signal.aborted = true; new_signal.reason.clone_from(&signal_borrow.reason); let new_signal = Class::instance(ctx, new_signal)?; return Ok(new_signal); } else { drop(signal_borrow); signal_instances.push(signal); } } let new_signal_instance = Class::instance(ctx.clone(), new_signal)?; for signal in signal_instances { let signal_instance_2 = new_signal_instance.clone(); Self::add_event_listener_str( This(signal), &ctx, "abort", Function::new( ctx.clone(), OnceFn::from(|ctx, signal| { struct Args<'js>(Ctx<'js>, This>>); let Args(ctx, signal) = Args(ctx, signal); let mut borrow = signal_instance_2.borrow_mut(); borrow.aborted = true; borrow.reason.clone_from(&signal.borrow().reason); drop(borrow); Self::send_aborted(This(signal_instance_2), ctx) }), )?, false, true, )?; } Ok(new_signal_instance) } #[qjs(get)] pub fn aborted(&self) -> bool { self.aborted } #[qjs(get)] pub fn reason(&self) -> Option> { self.reason.clone() } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(AbortSignal) } #[qjs(set, rename = "reason")] pub fn set_reason(&mut self, reason: Opt>) { match reason.0 { Some(new_reason) if !new_reason.is_undefined() => self.reason.replace(new_reason), _ => self.reason.take(), }; } #[qjs(skip)] pub fn send_aborted(this: This>, ctx: Ctx<'js>) -> Result<()> { let mut borrow = this.borrow_mut(); borrow.aborted = true; let reason = get_reason_or_dom_exception( &ctx, borrow.reason.as_ref(), DOMExceptionName::AbortError, )?; borrow.reason = Some(reason.clone()); borrow.sender.send(reason); drop(borrow); Self::emit_str(this, &ctx, "abort", vec![], false)?; Ok(()) } #[qjs(static)] pub fn abort(ctx: Ctx<'js>, reason: Opt>) -> Result> { let mut signal = Self::new(); signal.set_reason(reason); let instance = Class::instance(ctx.clone(), signal)?; Self::send_aborted(This(instance.clone()), ctx)?; Ok(instance) } #[qjs(static)] pub fn timeout(ctx: Ctx<'js>, milliseconds: u64) -> Result> { let timeout_error = get_reason_or_dom_exception(&ctx, None, DOMExceptionName::TimeoutError)?; let signal = Self::new(); let signal_instance = Class::instance(ctx.clone(), signal)?; let signal_instance2 = signal_instance.clone(); let cb = Function::new( ctx.clone(), OnceFn::from(move |ctx| { let mut borrow = signal_instance.borrow_mut(); borrow.set_reason(Opt(Some(timeout_error))); drop(borrow); Self::send_aborted(This(signal_instance), ctx)?; Ok::<_, Error>(()) }), )?; #[cfg(feature = "sleep-timers")] { llrt_timers::set_timeout_interval( &ctx, cb, milliseconds, llrt_utils::provider::ProviderType::Timeout, )?; } #[cfg(all(not(feature = "sleep-timers"), feature = "sleep-tokio"))] { use llrt_utils::ctx::CtxExtension; ctx.clone().spawn_exit_simple(async move { tokio::time::sleep(std::time::Duration::from_millis(milliseconds)).await; cb.call::<_, ()>(())?; Ok(()) }); } #[cfg(all(not(feature = "sleep-tokio"), not(feature = "sleep-timers")))] { compile_error!("Either the `sleep-tokio` or `sleep-timers` feature must be enabled") } Ok(signal_instance2) } } fn get_reason_or_dom_exception<'js>( ctx: &Ctx<'js>, reason: Option<&Value<'js>>, name: DOMExceptionName, ) -> Result> { let reason = if let Some(reason) = reason { reason.clone() } else { let ex = DOMException::new_with_name(ctx, name, String::new())?; Class::instance(ctx.clone(), ex)?.into_value() }; Ok(reason) } #[cfg(test)] mod tests { use std::time::Duration; use llrt_test::test_async_with; use super::*; #[cfg(feature = "sleep-timers")] #[tokio::test] async fn test_abort_signal() { test_async_with(|ctx| { crate::init(&ctx).unwrap(); llrt_timers::init(&ctx).unwrap(); Box::pin(async move { let signal = AbortSignal::timeout(ctx, 5).unwrap(); assert!(!signal.borrow().aborted()); tokio::time::sleep(Duration::from_millis(50)).await; assert!(signal.borrow().aborted()); let reason = signal.borrow().reason().unwrap(); let reason = Class::::from_value(&reason).unwrap(); assert_eq!(reason.borrow().name(), "TimeoutError"); }) }) .await; } } ================================================ FILE: modules/llrt_abort/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::new_without_default)] use llrt_events::Emitter; use llrt_utils::primordials::{BasePrimordials, Primordial}; use rquickjs::{Class, Ctx, Result}; pub use self::{abort_controller::AbortController, abort_signal::AbortSignal}; mod abort_controller; mod abort_signal; pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); BasePrimordials::init(ctx)?; Class::::define(&globals)?; Class::::define(&globals)?; AbortSignal::add_event_emitter_prototype(ctx)?; AbortSignal::add_event_target_prototype(ctx)?; Ok(()) } ================================================ FILE: modules/llrt_assert/Cargo.toml ================================================ [package] name = "llrt_assert" description = "LLRT Module assert" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_assert" path = "src/lib.rs" [dependencies] llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } ================================================ FILE: modules/llrt_assert/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::module::ModuleInfo; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::Opt, Ctx, Exception, Function, Result, Type, Value, }; fn ok(ctx: Ctx, value: Value, message: Opt) -> Result<()> { match value.type_of() { Type::Bool => { if value.as_bool().unwrap() { return Ok(()); } }, Type::Float | Type::Int => { if value.as_number().unwrap() != 0.0 { return Ok(()); } }, Type::String => { if !value.as_string().unwrap().to_string().unwrap().is_empty() { return Ok(()); } }, Type::Array | Type::BigInt | Type::Constructor | Type::Exception | Type::Function | Type::Proxy | Type::Symbol | Type::Object => { return Ok(()); }, _ => {}, } if let Some(obj) = message.0 { match obj.type_of() { Type::String => { let msg = obj.as_string().unwrap().to_string().unwrap(); return Err(Exception::throw_message(&ctx, &msg)); }, Type::Exception => return Err(obj.as_exception().cloned().unwrap().throw()), _ => {}, }; } Err(Exception::throw_message( &ctx, "AssertionError: The expression was evaluated to a falsy value", )) } pub struct AssertModule; impl ModuleDef for AssertModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("ok")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { let ok_function = Function::new(ctx.clone(), ok)?.with_name("ok")?; ok_function.set("ok", ok_function.clone())?; exports.export("ok", ok_function.clone())?; exports.export("default", ok_function)?; Ok(()) } } impl From for ModuleInfo { fn from(val: AssertModule) -> Self { ModuleInfo { name: "assert", module: val, } } } ================================================ FILE: modules/llrt_async_hooks/Cargo.toml ================================================ [package] name = "llrt_async_hooks" description = "LLRT Module async_hooks" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_async_hooks" path = "src/lib.rs" [dependencies] llrt_hooking = { version = "0.8.1-beta", path = "../../libs/llrt_hooking" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } tracing = { version = "0.1", default-features = false } ================================================ FILE: modules/llrt_async_hooks/src/finalization_registry.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::cell::RefCell; use llrt_utils::result::ResultExt; use rquickjs::{prelude::Func, Ctx, Result, Value}; use tracing::trace; use super::{remove_id_map, update_current_id, AsyncHookState}; pub(crate) fn init_finalization_registry(ctx: &Ctx<'_>) -> Result<()> { let global = ctx.globals(); global.set( "__invokeFinalizationHook", Func::from(invoke_finalization_hook), )?; let _: () = ctx.eval( r#" globalThis.asyncFinalizationRegistry = (() => { const registry = new FinalizationRegistry(__invokeFinalizationHook); return { register(target, heldValue) { registry.register(target, heldValue); } }; })(); "#, )?; global.remove("__invokeFinalizationHook")?; Ok(()) } fn invoke_finalization_hook<'js>(ctx: Ctx<'js>, uid: Value<'js>) -> Result<()> { let bind_state = ctx.userdata::>().or_throw(&ctx)?; let state = bind_state.borrow(); if state.hooks.is_empty() { return Ok(()); } let uid = uid.as_number().unwrap() as usize; let current_id = remove_id_map(&ctx, uid)?; if current_id.0 == 0 { return Ok(()); } update_current_id(&ctx, current_id)?; trace!("Destroy[{}](async_id, trigger_id): {:?}", uid, current_id); for hook in &state.hooks { if *hook.enabled.as_ref().borrow() { if let Some(func) = &hook.destroy { let _ = func .call::<_, ()>((current_id.0,)) .or_else(|_| func.call::<_, ()>(())); } } } Ok(()) } ================================================ FILE: modules/llrt_async_hooks/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{cell::RefCell, collections::HashMap, marker::PhantomData, rc::Rc}; use llrt_hooking::register_finalization_registry; use llrt_utils::{ module::{export_default, ModuleInfo}, result::ResultExt, }; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::Func, promise::PromiseHookType, qjs, runtime::PromiseHook, Ctx, Function, JsLifetime, Object, Result, Value, }; use tracing::trace; mod finalization_registry; use crate::finalization_registry::init_finalization_registry; struct Hook<'js> { enabled: Rc>, init: Option>, before: Option>, after: Option>, promise_resolve: Option>, destroy: Option>, } struct AsyncHookState<'js> { hooks: Vec>, } impl Default for AsyncHookState<'_> { fn default() -> Self { Self::new() } } impl AsyncHookState<'_> { fn new() -> Self { Self { hooks: Vec::new() } } } unsafe impl<'js> JsLifetime<'js> for AsyncHookState<'js> { type Changed<'to> = AsyncHookState<'to>; } struct AsyncHookIds<'js> { next_async_id: u64, id_map: HashMap, // (execution_async_id, trigger_async_id) current_id: (u64, u64), // (execution_async_id, trigger_async_id) _marker: PhantomData<&'js ()>, } impl Default for AsyncHookIds<'_> { fn default() -> Self { Self::new() } } impl AsyncHookIds<'_> { fn new() -> Self { Self { next_async_id: 1, id_map: HashMap::new(), current_id: (1, 1), _marker: PhantomData, } } } unsafe impl<'js> JsLifetime<'js> for AsyncHookIds<'js> { type Changed<'to> = AsyncHookIds<'to>; } fn create_hook<'js>(ctx: Ctx<'js>, hooks_obj: Object<'js>) -> Result> { let init = hooks_obj.get::<_, Function>("init").ok(); let before = hooks_obj.get::<_, Function>("before").ok(); let after = hooks_obj.get::<_, Function>("after").ok(); let promise_resolve = hooks_obj.get::<_, Function>("promiseResolve").ok(); let destroy = hooks_obj.get::<_, Function>("destroy").ok(); let enabled = Rc::new(RefCell::new(false)); let hook = Hook { enabled: enabled.clone(), init, before, after, promise_resolve, destroy, }; let binding = ctx.userdata::>().or_throw(&ctx)?; let mut state = binding.borrow_mut(); state.hooks.push(hook); let obj = Object::new(ctx.clone())?; { let enabled_clone = enabled.clone(); obj.set( "enable", Function::new(ctx.clone(), move || -> Result<()> { *enabled_clone.borrow_mut() = true; Ok(()) }), )?; } { let enabled_clone = enabled.clone(); obj.set( "disable", Function::new(ctx.clone(), move || -> Result<()> { *enabled_clone.borrow_mut() = false; Ok(()) }), )?; } Ok(obj.into()) } fn current_id() -> u64 { // NOTE: This method is now obsolete. Therefore, it does not return a valid value. // But we will define it because it is used by cls-hooked. 0 } fn execution_async_id(ctx: Ctx<'_>) -> Result { let bind_ids = ctx.userdata::>().or_throw(&ctx)?; let ids = bind_ids.borrow(); Ok(ids.current_id.0) } fn trigger_async_id(ctx: Ctx<'_>) -> Result { let bind_ids = ctx.userdata::>().or_throw(&ctx)?; let ids = bind_ids.borrow(); Ok(ids.current_id.1) } pub struct AsyncHooksModule; impl ModuleDef for AsyncHooksModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("createHook")?; declare.declare("currentId")?; declare.declare("executionAsyncId")?; declare.declare("triggerAsyncId")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { default.set("createHook", Func::from(create_hook))?; default.set("currentId", Func::from(current_id))?; default.set("executionAsyncId", Func::from(execution_async_id))?; default.set("triggerAsyncId", Func::from(trigger_async_id))?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: AsyncHooksModule) -> Self { ModuleInfo { name: "async_hooks", module: val, } } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let global = ctx.globals(); let _ = ctx.store_userdata(RefCell::new(AsyncHookState::default())); let _ = ctx.store_userdata(RefCell::new(AsyncHookIds::default())); global.set( "invokeAsyncHook", Func::from( move |ctx: Ctx<'_>, type_: String, async_type: String, uid: usize| { let type_ = match type_.as_ref() { "init" => PromiseHookType::Init, "before" => PromiseHookType::Before, "after" => PromiseHookType::After, "resolve" => PromiseHookType::Resolve, _ => return, }; let _ = invoke_async_hook(&ctx, type_, async_type.as_ref(), uid, None); }, ), )?; init_finalization_registry(ctx)?; Ok(()) } pub fn promise_hook_tracker() -> PromiseHook { Box::new( |ctx: Ctx<'_>, type_: PromiseHookType, promise: Value<'_>, parent: Value<'_>| { // SAFETY: Since it checks in advance whether it is an Object type, we can always get a pointer to the object. let object = promise .as_object() .map(|v| unsafe { qjs::JS_VALUE_GET_PTR(v.as_raw()) } as usize) .unwrap(); let parent = parent .as_object() .map(|v| unsafe { qjs::JS_VALUE_GET_PTR(v.as_raw()) } as usize); if type_ == PromiseHookType::Init { let _ = register_finalization_registry(&ctx, promise, object); } let _ = invoke_async_hook(&ctx, type_, "PROMISE", object, parent); }, ) } fn invoke_async_hook( ctx: &Ctx<'_>, type_: PromiseHookType, async_type: &str, object: usize, parent: Option, ) -> Result<()> { let bind_state = ctx.userdata::>().or_throw(ctx)?; let state = bind_state.borrow(); if state.hooks.is_empty() { return Ok(()); } match type_ { PromiseHookType::Init => { let current_id = insert_id_map(ctx, object, parent, async_type == "PROMISE")?; trace!("Init(async_id, trigger_id): {:?}", current_id); update_current_id(ctx, current_id)?; for hook in &state.hooks { if *hook.enabled.as_ref().borrow() { if let Some(func) = &hook.init { let _ = func .call::<_, ()>((current_id.0, async_type, current_id.1)) .or_else(|_| func.call::<_, ()>((current_id.0, async_type))) .or_else(|_| func.call::<_, ()>((current_id.0,))) .or_else(|_| func.call::<_, ()>(())); } } } }, PromiseHookType::Before | PromiseHookType::After | PromiseHookType::Resolve => { let current_id = get_id_map(ctx, object)?; if current_id.0 == 0 { return Ok(()); } let _type = match type_ { PromiseHookType::Before => "Before", PromiseHookType::After => "After", PromiseHookType::Resolve => "Resolve", _ => unreachable!(), }; trace!("{}(async_id, trigger_id): {:?}", _type, current_id); update_current_id(ctx, current_id)?; for hook in &state.hooks { if *hook.enabled.as_ref().borrow() { if let Some(func) = match type_ { PromiseHookType::Before => &hook.before, PromiseHookType::After => &hook.after, PromiseHookType::Resolve => &hook.promise_resolve, _ => unreachable!(), } { let _ = func .call::<_, ()>((current_id.0,)) .or_else(|_| func.call::<_, ()>(())); } } } }, } Ok(()) } fn insert_id_map( ctx: &Ctx<'_>, target: usize, parent: Option, is_promise: bool, ) -> Result<(u64, u64)> { let bind_ids = ctx.userdata::>().or_throw(ctx)?; let mut ids = bind_ids.borrow_mut(); ids.next_async_id = ids.next_async_id.wrapping_add(1); let async_id = ids.next_async_id; let trigger_id = parent .and_then(|tid| ids.id_map.get(&tid)) .map(|id| id.0) .unwrap_or(if is_promise { 1 } else { ids.current_id.1 }); ids.id_map.insert(target, (async_id, trigger_id)); Ok((async_id, trigger_id)) } fn get_id_map(ctx: &Ctx<'_>, target: usize) -> Result<(u64, u64)> { let bind_ids = ctx.userdata::>().or_throw(ctx)?; let ids = bind_ids.borrow(); Ok(*ids.id_map.get(&target).unwrap_or(&(0, 0))) } fn remove_id_map(ctx: &Ctx<'_>, target: usize) -> Result<(u64, u64)> { let bind_ids = ctx.userdata::>().or_throw(ctx)?; let mut ids = bind_ids.borrow_mut(); Ok(ids .id_map .remove_entry(&target) .map(|(_, (async_id, trigger_id))| (async_id, trigger_id)) .unwrap_or((0, 0))) } fn update_current_id(ctx: &Ctx<'_>, id: (u64, u64)) -> Result<()> { let bind_ids = ctx.userdata::>().or_throw(ctx)?; bind_ids.borrow_mut().current_id = id; Ok(()) } ================================================ FILE: modules/llrt_buffer/Cargo.toml ================================================ [package] name = "llrt_buffer" description = "LLRT Module buffer" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_buffer" path = "src/lib.rs" [dependencies] itoa = { version = "1", default-features = false } llrt_encoding = { version = "0.8.1-beta", path = "../../libs/llrt_encoding" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", features = [ "futures", ], default-features = false } ryu = { version = "1", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = ["full"], default-features = false } ================================================ FILE: modules/llrt_buffer/src/array_buffer_view.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::ptr::NonNull; use rquickjs::{ArrayBuffer, Ctx, Error, FromJs, IntoJs, Object, Result, TypedArray, Value}; use crate::Buffer; pub struct ArrayBufferView<'js> { value: Value<'js>, buffer: Option, } struct RawArrayBuffer { len: usize, ptr: NonNull, } impl RawArrayBuffer { pub fn new(len: usize, ptr: NonNull) -> Self { Self { len, ptr } } } impl<'js> IntoJs<'js> for ArrayBufferView<'js> { fn into_js(self, _ctx: &Ctx<'js>) -> Result> { Ok(self.value) } } impl<'js> FromJs<'js> for ArrayBufferView<'js> { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = Object::from_value(value.clone()) .map_err(|_| Error::new_from_js(ty_name, "ArrayBufferView"))?; if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) { let buffer = array_buffer .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(typed_array) = TypedArray::::from_object(obj.clone()) { let buffer = typed_array .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") { let buffer = array_buffer .as_raw() .map(|raw| RawArrayBuffer::new(raw.len, raw.ptr)); return Ok(ArrayBufferView { value, buffer }); } Err(Error::new_from_js(ty_name, "ArrayBufferView")) } } impl<'js> ArrayBufferView<'js> { pub fn from_buffer(ctx: &Ctx<'js>, buffer: Buffer) -> Result { let value = buffer.into_js(ctx)?; Self::from_js(ctx, value) } pub fn len(&self) -> usize { self.buffer.as_ref().map(|b| b.len).unwrap_or(0) } #[allow(dead_code)] pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn as_bytes(&self) -> Option<&[u8]> { self.buffer .as_ref() .map(|b| unsafe { std::slice::from_raw_parts(b.ptr.as_ptr(), b.len) }) } /// Mutable buffer for the view. /// /// # Safety /// This is only safe if you have a lock on the runtime. /// Do not pass it directly to other threads. pub fn as_bytes_mut(&mut self) -> Option<&mut [u8]> { self.buffer .as_ref() .map(|b| unsafe { std::slice::from_raw_parts_mut(b.ptr.as_ptr(), b.len) }) } } ================================================ FILE: modules/llrt_buffer/src/blob.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::ops::RangeInclusive; use llrt_utils::{ bytes::ObjectBytes, primordials::{BasePrimordials, Primordial}, result::ResultExt, }; use rquickjs::{ atom::PredefinedAtom, class::Trace, function::Opt, Array, ArrayBuffer, Class, Coerced, Ctx, Exception, FromJs, Result, Symbol, TypedArray, Value, }; use super::file::File; static CONSTRUCT_ERROR: &str = "Failed to construct 'Blob': The provided value cannot be converted to a sequence."; enum EndingType { Native, Transparent, } #[cfg(windows)] const LINE_ENDING: &[u8] = b"\r\n"; #[cfg(not(windows))] const LINE_ENDING: &[u8] = b"\n"; #[rquickjs::class] #[derive(Trace, Clone, rquickjs::JsLifetime)] pub struct Blob { #[qjs(skip_trace)] data: Vec, mime_type: String, } fn normalize_type(mut mime_type: String) -> String { static INVALID_RANGE: RangeInclusive = 0x0020..=0x007E; let bytes = unsafe { mime_type.as_bytes_mut() }; for byte in bytes { if !INVALID_RANGE.contains(byte) { return String::new(); } byte.make_ascii_lowercase(); } mime_type } #[rquickjs::methods] impl Blob { #[qjs(constructor)] pub fn new<'js>( ctx: Ctx<'js>, parts: Opt>, options: Opt>, ) -> Result { let mut endings = EndingType::Transparent; let mut mime_type = String::new(); if let Some(opts) = options.0 { if let Some(v) = opts.as_object() { if let Some(x) = v.get::<_, Option>>("type")? { mime_type = normalize_type(x.to_string()); } if let Some(Coerced(endings_opt)) = v.get::<_, Option>>("endings")? { if endings_opt == "native" { endings = EndingType::Native; } else if endings_opt != "transparent" { return Err(Exception::throw_type( &ctx, r#"expected 'endings' to be either 'transparent' or 'native'"#, )); } } } } let data = if let Some(parts) = parts.0 { bytes_from_parts(&ctx, parts, endings)? } else { Vec::new() }; Ok(Self { data, mime_type }) } #[qjs(get)] pub fn size(&self) -> usize { self.data.len() } #[qjs(get, rename = "type")] pub fn mime_type(&self) -> String { self.mime_type.clone() } pub async fn text(&self) -> String { String::from_utf8_lossy(&self.data).to_string() } #[qjs(rename = "arrayBuffer")] pub async fn array_buffer<'js>(&self, ctx: Ctx<'js>) -> Result> { ArrayBuffer::new(ctx, self.data.to_vec()) } pub async fn bytes<'js>(&self, ctx: Ctx<'js>) -> Result> { TypedArray::new(ctx, self.data.to_vec()).map(|m| m.into_value()) } pub fn slice(&self, start: Opt, end: Opt, content_type: Opt) -> Blob { let start = start.0.unwrap_or_default(); let start = if start < 0 { (self.data.len() as isize + start).max(0) as usize } else { self.data.len().min(start as usize) }; let end = end.0.unwrap_or_default(); let end = if end < 0 { (self.data.len() as isize + end).max(0) as usize } else { self.data.len().min(end as usize) }; let data = &self.data[start..end]; let mime_type = content_type.0.map(normalize_type).unwrap_or_default(); Blob { mime_type, data: data.to_vec(), } } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(Blob) } } impl Blob { pub fn from_bytes(data: Vec, content_type: Option) -> Self { let mime_type = content_type.map(normalize_type).unwrap_or_default(); Self { mime_type, data } } pub fn get_bytes(&self) -> Vec { self.data.clone() } //FIXME: cant use procedural macro for Symbol rename + static, see https://github.com/DelSkayn/rquickjs/issues/315 pub fn has_instance(value: Value<'_>) -> bool { if let Some(obj) = value.as_object() { return obj.instance_of::() || obj.instance_of::(); } false } } fn bytes_from_parts<'js>( ctx: &Ctx<'js>, parts: Value<'js>, endings: EndingType, ) -> Result> { let array = if let Some(obj) = parts.as_object() { if obj.contains_key(Symbol::iterator(ctx.clone()))? { BasePrimordials::get(ctx)? .function_array_from .call((parts.clone(),))? } else { parts .into_array() .ok_or_else(|| Exception::throw_type(ctx, CONSTRUCT_ERROR))? } } else { return Err(Exception::throw_type(ctx, CONSTRUCT_ERROR)); }; let mut data = Vec::new(); for elem in array.iter::() { let elem = elem?; if let Some(arr) = elem.as_array() { let string = array_to_string(arr)?; data.extend_from_slice(string.as_bytes()); continue; } if let Some(object) = elem.as_object() { if let Some(x) = Class::::from_object(object) { data.extend_from_slice(&x.borrow().data); continue; } if let Some(x) = Class::::from_object(object) { let file = x.borrow(); let end = Some(file.size().try_into().or_throw(ctx)?); let mime_type = Some(file.mime_type()); data.extend_from_slice(&file.slice(Opt(Some(0)), Opt(end), Opt(mime_type)).data); continue; } if let Ok(x) = ObjectBytes::from(ctx, object) { data.extend_from_slice(x.as_bytes(ctx).map_err(|_| { Exception::throw_type(ctx, "Cannot create a blob with detached buffer") })?); continue; } if let Some(x) = ArrayBuffer::from_object(object.clone()) { data.extend_from_slice(x.as_bytes().ok_or_else(|| { Exception::throw_type(ctx, "Cannot create a blob with detached buffer") })?); continue; } } let string = Coerced::::from_js(ctx, elem)?.0; if let EndingType::Transparent = endings { data.extend_from_slice(string.as_bytes()); } else { let len = string.len(); data.reserve(len); let bytes = string.as_bytes(); let mut iter = bytes.iter(); let mut start = 0usize; let mut i = 0usize; let line_ending_is_n = LINE_ENDING[0] == b'\n'; while let Some(byte) = iter.next() { if byte == &b'\r' { if let Some(next_byte) = iter.next() { data.extend(&bytes[start..i]); i += 1; start = i + 1; if next_byte != &b'\n' { data.extend([b'\r', *next_byte]); } else { data.extend(LINE_ENDING); } } } else if byte == &b'\n' && !line_ending_is_n { data.extend(&bytes[start..i]); data.extend(LINE_ENDING); start = i + 1; }; i += 1; } if start < len { data.extend(&bytes[start..len]); } } } Ok(data) } fn array_to_string(array: &Array) -> Result { let mut itoa_buffer = itoa::Buffer::new(); let mut ryu_buffer = ryu::Buffer::new(); let parts = array .clone() .into_iter() .map(|value| { let value = value?; if let Some(string) = value.as_string() { Ok(string.to_string()?) } else if let Some(number) = value.as_int() { Ok(itoa_buffer.format(number).to_string()) } else if let Some(number) = value.as_float() { Ok(ryu_buffer.format(number).to_string()) } else { Ok(String::new()) } }) .collect::>>()?; Ok(parts.join(",")) } ================================================ FILE: modules/llrt_buffer/src/buffer.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{mem::MaybeUninit, slice}; use llrt_encoding::{bytes_from_b64, bytes_to_b64_string, Encoder}; use llrt_utils::{ bytes::{get_array_bytes, get_start_end_indexes, ObjectBytes}, error_messages::{ERROR_MSG_ARRAY_BUFFER_DETACHED, ERROR_MSG_NOT_ARRAY_BUFFER}, iterable_enum, primordials::Primordial, result::ResultExt, string::{get_coerced_string, get_string}, }; use rquickjs::{ atom::PredefinedAtom, function::{Constructor, Opt}, prelude::{Func, Rest, This}, Array, ArrayBuffer, Coerced, Ctx, Exception, Function, IntoJs, JsLifetime, Object, Result, TypedArray, Value, }; #[derive(JsLifetime)] pub struct BufferPrimordials<'js> { constructor: Constructor<'js>, } impl<'js> Primordial<'js> for BufferPrimordials<'js> { fn new(ctx: &Ctx<'js>) -> Result where Self: Sized, { let constructor: Constructor = ctx.globals().get(stringify!(Buffer))?; Ok(Self { constructor }) } } pub struct Buffer(pub Vec); impl<'js> IntoJs<'js> for Buffer { fn into_js(self, ctx: &Ctx<'js>) -> Result> { let array_buffer = ArrayBuffer::new(ctx.clone(), self.0)?; Self::from_array_buffer(ctx, array_buffer) } } impl<'js> Buffer { pub fn alloc(length: usize) -> Self { Self(vec![0; length]) } pub fn to_string(&self, ctx: &Ctx<'js>, encoding: &str) -> Result { Encoder::from_str(encoding) .and_then(|enc| enc.encode_to_string(self.0.as_ref(), true)) .or_throw(ctx) } fn from_array_buffer(ctx: &Ctx<'js>, buffer: ArrayBuffer<'js>) -> Result> { BufferPrimordials::get(ctx)? .constructor .construct((buffer,)) } fn from_array_buffer_offset_length( ctx: &Ctx<'js>, array_buffer: ArrayBuffer<'js>, offset: usize, length: usize, ) -> Result> { BufferPrimordials::get(ctx)? .constructor .construct((array_buffer, offset, length)) } fn from_encoding( ctx: &Ctx<'js>, mut bytes: Vec, encoding: Option, ) -> Result> { if let Some(encoding) = encoding { let encoder = Encoder::from_str(&encoding).or_throw(ctx)?; bytes = encoder.decode(bytes).or_throw(ctx)?; } Buffer(bytes).into_js(ctx) } fn from_string_encoding( ctx: &Ctx<'js>, string: String, encoding: Option, ) -> Result> { let bytes = if let Some(encoding) = encoding { let encoder = Encoder::from_str(&encoding).or_throw(ctx)?; encoder.decode_from_string(string).or_throw(ctx)? } else { string.into_bytes() }; Buffer(bytes).into_js(ctx) } } // Static Methods fn alloc<'js>( ctx: Ctx<'js>, length: usize, fill: Opt>, encoding: Opt, ) -> Result> { if let Some(value) = fill.0 { if let Some(value) = value.as_string() { let string = value.to_string()?; if let Some(encoding) = encoding.0 { let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?; let bytes = encoder.decode_from_string(string).or_throw(&ctx)?; return alloc_byte_ref(&ctx, &bytes, length); } let byte_ref = string.as_bytes(); return alloc_byte_ref(&ctx, byte_ref, length); } if let Some(value) = value.as_int() { let bytes = vec![value as u8; length]; return Buffer(bytes).into_js(&ctx); } if let Some(obj) = value.as_object() { if let Some(ob) = ObjectBytes::from_array_buffer(obj)? { let bytes = ob.as_bytes(&ctx)?; return alloc_byte_ref(&ctx, bytes, length); } } } Buffer(vec![0; length]).into_js(&ctx) } fn alloc_byte_ref<'js>(ctx: &Ctx<'js>, byte_ref: &[u8], length: usize) -> Result> { let mut bytes = vec![0; length]; let byte_ref_length = byte_ref.len(); for i in 0..length { bytes[i] = byte_ref[i % byte_ref_length]; } Buffer(bytes).into_js(ctx) } fn alloc_unsafe(ctx: Ctx<'_>, size: usize) -> Result> { let mut bytes: Vec> = Vec::with_capacity(size); unsafe { bytes.set_len(size); } Buffer(maybeuninit_to_u8(bytes)).into_js(&ctx) } fn maybeuninit_to_u8(vec: Vec>) -> Vec { let len = vec.len(); let capacity = vec.capacity(); let ptr = vec.as_ptr() as *mut u8; std::mem::forget(vec); // This conversion is safe because MaybeUninit has the same memory layout as u8, meaning the underlying bytes are identical. // Since Vec and Vec share the same memory representation, a simple reinterpretation of the pointer is valid. // Additionally, Vec::from_raw_parts correctly reconstructs the vector using the original length and capacity, ensuring that memory ownership remains consistent. // The call to std::mem::forget(vec) prevents the original Vec from being dropped, avoiding double frees or memory corruption. // However, this conversion is only safe if all elements of MaybeUninit are properly initialized. // If any uninitialized values exist, reading them as u8 would lead to undefined behavior. unsafe { Vec::from_raw_parts(ptr, len, capacity) } } fn alloc_unsafe_slow(ctx: Ctx<'_>, size: usize) -> Result> { let layout = std::alloc::Layout::array::(size).or_throw(&ctx)?; let bytes = unsafe { let ptr = std::alloc::alloc(layout); if ptr.is_null() { return Err(Exception::throw_internal(&ctx, "Memory allocation failed")); } Vec::from_raw_parts(ptr, size, size) }; Buffer(bytes).into_js(&ctx) } fn byte_length<'js>(ctx: Ctx<'js>, value: Value<'js>, encoding: Opt) -> Result { //slow path if let Some(encoding) = encoding.0 { let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?; let a = ObjectBytes::from(&ctx, &value)?; let bytes = a.as_bytes(&ctx)?; return Ok(encoder.decode(bytes).or_throw(&ctx)?.len()); } //fast path if let Some(val) = value.as_string() { return Ok(val.to_string()?.len()); } if value.is_array() { let array = value.as_array().unwrap(); for val in array.iter::() { val.or_throw_msg(&ctx, "array value is not u8")?; } return Ok(array.len()); } if let Some(obj) = value.as_object() { if let Some(ob) = ObjectBytes::from_array_buffer(obj)? { return Ok(ob.as_bytes(&ctx)?.len()); } } Err(Exception::throw_message( &ctx, "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or string", )) } fn concat<'js>(ctx: Ctx<'js>, list: Array<'js>, max_length: Opt) -> Result> { let mut bytes = Vec::new(); let mut total_length = 0; let mut length; for value in list.iter::() { let typed_array = TypedArray::::from_object(value?)?; let bytes_ref: &[u8] = typed_array.as_ref(); length = bytes_ref.len(); if length == 0 { continue; } if let Some(max_length) = max_length.0 { total_length += length; if total_length > max_length { let diff = max_length - (total_length - length); bytes.extend_from_slice(&bytes_ref[0..diff]); break; } } bytes.extend_from_slice(bytes_ref); } Buffer(bytes).into_js(&ctx) } fn from<'js>( ctx: Ctx<'js>, value: Value<'js>, offset_or_encoding: Opt>, length: Opt, ) -> Result> { let mut encoding: Option = None; let mut offset = 0; if let Some(offset_or_encoding) = offset_or_encoding.0 { if offset_or_encoding.is_string() { encoding = Some(offset_or_encoding.get()?); } else if offset_or_encoding.is_number() { offset = offset_or_encoding.get()?; } } // WARN: This is currently bugged for strings that can't be converted to utf8 // See https://github.com/quickjs-ng/quickjs/issues/992 if let Some(string) = get_string(&value)? { return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx); } if let Some(bytes) = get_array_bytes(&value, offset, length.0)? { return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx); } if let Some(obj) = value.as_object() { if let Some(ab_bytes) = ObjectBytes::from_array_buffer(obj)? { let bytes = ab_bytes.as_bytes(&ctx)?; let (start, end) = get_start_end_indexes(bytes.len(), length.0, offset); //buffers from buffer should be copied if obj .get::<_, Option>(PredefinedAtom::Meta)? .as_deref() == Some(stringify!(Buffer)) || encoding.is_some() { let bytes = bytes.into(); return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx); } else { let (array_buffer, _, source_offset) = ab_bytes.get_array_buffer()?.unwrap(); //we know it's an array buffer return Buffer::from_array_buffer_offset_length( &ctx, array_buffer, start + source_offset, end - start, ); } } } if let Some(string) = get_coerced_string(&value) { return Buffer::from_string_encoding(&ctx, string, encoding)?.into_js(&ctx); } Err(Exception::throw_message( &ctx, "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string", )) } fn is_buffer<'js>(ctx: Ctx<'js>, value: Value<'js>) -> Result { if let Some(object) = value.as_object() { let constructor = BufferPrimordials::get(&ctx)?; return Ok(object.is_instance_of(&constructor.constructor)); } Ok(false) } fn is_encoding(value: Value) -> Result { if let Some(js_string) = value.as_string() { let std_string = js_string.to_string()?; return Ok(Encoder::from_str(std_string.as_str()).is_ok()); } Ok(false) } // Prototype Methods fn copy<'js>( this: This>, ctx: Ctx<'js>, target: ObjectBytes<'js>, args: Rest, ) -> Result { let mut args_iter = args.0.into_iter(); let target_start = args_iter.next().unwrap_or_default(); let source_start = args_iter.next().unwrap_or_default(); let source_end = args_iter.next().unwrap_or_else(|| this.0.len()); let mut copyable_length = 0; if source_start >= source_end { return Ok(copyable_length); } let source_bytes = ObjectBytes::from(&ctx, this.0.as_inner())?; let source_bytes = source_bytes.as_bytes(&ctx)?; if let Some((array_buffer, _, _)) = target.get_array_buffer()? { let raw = array_buffer .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(&ctx)?; let target_bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) }; copyable_length = (source_end - source_start).min(raw.len - target_start); target_bytes[target_start..target_start + copyable_length] .copy_from_slice(&source_bytes[source_start..source_start + copyable_length]); } Ok(copyable_length) } fn subarray<'js>( this: This>, ctx: Ctx<'js>, start: Opt, end: Opt, ) -> Result> { let view = TypedArray::::from_object(this.0.clone())?; let array_buffer = view.arraybuffer()?; let view_offset = this.0.get::<_, isize>("byteOffset")?; let view_length = this.0.get::<_, isize>("byteLength")?; let start_index = start.map_or(0, |s| { if s < 0 { (view_length + s).max(0) } else { s.min(view_length) } }); let end_index = end.map_or(view_length, |e| { if e < 0 { (view_length + e).max(0) } else { e.min(view_length) } }); let length = (end_index - start_index).max(0) as usize; let new_offset = (view_offset + start_index).max(0) as usize; Buffer::from_array_buffer_offset_length(&ctx, array_buffer, new_offset, length) } fn to_string( this: This>, ctx: Ctx, encoding: Opt, start: Opt, end: Opt, ) -> Result { let typed_array = TypedArray::::from_object(this.0)?; let bytes: &[u8] = typed_array.as_ref(); let start = start .0 .map(|s| s.max(0) as usize) .unwrap_or(0) .min(bytes.len()); let end = end .0 .map(|e| e.max(0) as usize) .unwrap_or(bytes.len()) .min(bytes.len()); let bytes = &bytes[start..end]; let encoder = Encoder::from_optional_str(encoding.as_deref()).or_throw(&ctx)?; encoder.encode_to_string(bytes, true).or_throw(&ctx) } fn write<'js>( this: This>, ctx: Ctx<'js>, string: String, args: Rest>, ) -> Result { let (offset, length, encoding) = get_write_parameters(&args, this.0.len())?; let target = ObjectBytes::from(&ctx, this.0.as_inner())?; let mut writable_length = 0; if let Some((array_buffer, _, _)) = target.get_array_buffer()? { let raw = array_buffer .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(&ctx)?; let target_bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) }; let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?; if encoder.as_label() == "utf-8" { let (source_slice, valid_length) = safe_byte_slice(&string, length.min(string.len())); writable_length = valid_length; target_bytes[offset..offset + writable_length].copy_from_slice(source_slice); } else { let decode_bytes = encoder.decode_from_string(string).or_throw(&ctx)?; writable_length = length.min(decode_bytes.len()); target_bytes[offset..offset + writable_length] .copy_from_slice(&decode_bytes[..writable_length]); }; } Ok(writable_length) } fn get_write_parameters(args: &Rest>, len: usize) -> Result<(usize, usize, String)> { let mut offset = 0; let mut length = len; let mut encoding = "utf8".to_owned(); if let Some(v1) = args.0.first() { if let Some(s) = v1.as_string() { return Ok((0, len, s.to_string()?)); } offset = v1.as_int().unwrap_or(0) as usize; } if let Some(v2) = args.0.get(1) { if let Some(s) = v2.as_string() { return Ok((offset, len - offset, s.to_string()?)); } length = v2 .as_int() .map_or(len - offset, |l| (l as usize).min(len - offset)); } if let Some(v3) = args.0.get(2) { if let Some(s) = v3.as_string() { encoding = s.to_string()?; } } Ok((offset, length, encoding)) } fn safe_byte_slice(s: &str, end: usize) -> (&[u8], usize) { let bytes = s.as_bytes(); if bytes.len() <= end { return (bytes, bytes.len()); } let valid_end = s .char_indices() .map(|(i, _)| i) .rfind(|&i| i <= end) .unwrap_or(0); (&bytes[0..valid_end], valid_end) } #[derive(Clone, Copy)] pub enum Endian { Little, Big, } #[derive(Clone, Copy, PartialEq, Eq)] pub enum NumberKind { Int8, UInt8, Int16, UInt16, Int32, UInt32, Float32, Float64, BigInt, BigUInt, } impl NumberKind { pub fn bits(&self) -> u8 { match self { NumberKind::Int8 => 8, NumberKind::UInt8 => 8, NumberKind::Int16 => 16, NumberKind::UInt16 => 16, NumberKind::Int32 => 32, NumberKind::UInt32 => 32, NumberKind::Float32 => 32, NumberKind::Float64 => 64, NumberKind::BigInt => 64, NumberKind::BigUInt => 64, } } pub fn is_signed(&self) -> bool { matches!( self, NumberKind::Int8 | NumberKind::Int16 | NumberKind::Int32 ) } pub fn prototype(&self) -> &'static [(Endian, &'static str, Option<&'static str>)] { match self { NumberKind::Int8 => &[(Endian::Little, "Int8", None)], NumberKind::UInt8 => &[(Endian::Little, "UInt8", Some("Uint8"))], NumberKind::Int16 => &[ (Endian::Little, "Int16LE", None), (Endian::Big, "Int16BE", None), ], NumberKind::UInt16 => &[ (Endian::Little, "UInt16LE", Some("Uint16LE")), (Endian::Big, "UInt16BE", Some("Uint16BE")), ], NumberKind::Int32 => &[ (Endian::Little, "Int32LE", None), (Endian::Big, "Int32BE", None), ], NumberKind::UInt32 => &[ (Endian::Little, "UInt32LE", Some("Uint32LE")), (Endian::Big, "UInt32BE", Some("Uint32BE")), ], NumberKind::Float32 => &[ (Endian::Little, "FloatLE", None), (Endian::Big, "FloatBE", None), ], NumberKind::Float64 => &[ (Endian::Little, "DoubleLE", None), (Endian::Big, "DoubleBE", None), ], NumberKind::BigInt => &[ (Endian::Little, "BigInt64LE", None), (Endian::Big, "BigInt64BE", None), ], NumberKind::BigUInt => &[ (Endian::Little, "BigUInt64LE", Some("BigUint64LE")), (Endian::Big, "BigUInt64BE", Some("BigUint64BE")), ], } } } iterable_enum!( NumberKind, Int8, UInt8, Int16, UInt16, Int32, UInt32, Float32, Float64, BigInt, BigUInt ); #[allow(clippy::too_many_arguments)] fn write_buf<'js>( this: &This>, ctx: &Ctx<'js>, value: &Value<'js>, offset: &Opt, endian: Endian, kind: NumberKind, ) -> Result { let offset = offset.0.unwrap_or_default(); // Extract and convert value let (byte_count, bytes) = match kind { NumberKind::BigInt => { let Some(bigint) = value.as_big_int() else { return Err(Exception::throw_type(ctx, "Expected BigInt")); }; let (byte_count, val) = (8, bigint.clone().to_i64().or_throw(ctx)? as u64); (byte_count, endian_bytes(val, endian)) }, NumberKind::BigUInt => { return Err(Exception::throw_type(ctx, "Uint64 is not supported")); }, NumberKind::Float32 => { let Some(float_val) = value.as_float() else { return Err(Exception::throw_type(ctx, "Expected number")); }; match endian { Endian::Big => (4, (float_val as f32).to_bits().to_be_bytes().to_vec()), Endian::Little => (4, (float_val as f32).to_bits().to_le_bytes().to_vec()), } }, NumberKind::Float64 => { let Some(float_val) = value.as_float() else { return Err(Exception::throw_type(ctx, "Expected number")); }; match endian { Endian::Big => (8, float_val.to_bits().to_be_bytes().to_vec()), Endian::Little => (8, float_val.to_bits().to_le_bytes().to_vec()), } }, NumberKind::Int8 | NumberKind::UInt8 | NumberKind::Int16 | NumberKind::UInt16 | NumberKind::Int32 | NumberKind::UInt32 => { let Some(int_val) = value.as_number() else { return Err(Exception::throw_type(ctx, "Expected number")); }; let int_val = int_val as i64; let bit_mask = (1i64 << kind.bits()) - 1; let max_val = if kind.is_signed() { (1i64 << (kind.bits() - 1)) - 1 } else { bit_mask }; let min_val = if kind.is_signed() { -max_val - 1 } else { 0 }; if int_val < min_val || int_val > max_val { return Err(Exception::throw_range(ctx, "Value out of range")); } let masked = int_val & bit_mask; ( (kind.bits() / 8) as usize, shifted_bytes(masked as u64, kind.bits(), endian), ) }, }; if offset >= this.0.len() || offset + byte_count > this.0.len() { return Err(Exception::throw_range( ctx, "The specified offset is out of range", )); } let target = ObjectBytes::from(ctx, this.0.as_inner())?; let mut writable_length = 0; if let Some((array_buffer, _, _)) = target.get_array_buffer()? { let raw = array_buffer .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(ctx)?; let target_bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) }; writable_length = offset + bytes.len(); target_bytes[offset..writable_length].copy_from_slice(&bytes); } Ok(writable_length) } fn read_buf<'js>( this: &This>, ctx: &Ctx<'js>, offset: &Opt, endian: Endian, kind: NumberKind, ) -> Result> { // Retrieve the array buffer let target = ObjectBytes::from(ctx, this.0.as_inner())?; let Some((array_buffer, _, _)) = target.get_array_buffer()? else { return Err(Exception::throw_message(ctx, ERROR_MSG_NOT_ARRAY_BUFFER)); }; let raw = array_buffer .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(ctx)?; let target_bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) }; // Enforce the bounds let start = offset.0.unwrap_or_default(); let end = start + (kind.bits() / 8) as usize; if end > raw.len { return Err(Exception::throw_range( ctx, "The value of \"offset\" is out of range", )); } let bytes = &target_bytes[start..end]; let value = match kind { NumberKind::BigInt => { let value = match endian { Endian::Big => i64::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => i64::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_big_int(ctx.clone(), value) }, NumberKind::BigUInt => { return Err(Exception::throw_type(ctx, "Uint64 is not supported")); }, NumberKind::Float32 => { let value = match endian { Endian::Big => f32::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => f32::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_float(ctx.clone(), value as f64) }, NumberKind::Float64 => { let value = match endian { Endian::Big => f64::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => f64::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_float(ctx.clone(), value) }, NumberKind::Int8 => { let value = match endian { Endian::Big => i8::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => i8::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_int(ctx.clone(), value as i32) }, NumberKind::UInt8 => { let value = match endian { Endian::Big => u8::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => u8::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_int(ctx.clone(), value as i32) }, NumberKind::Int16 => { let value = match endian { Endian::Big => i16::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => i16::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_int(ctx.clone(), value as i32) }, NumberKind::UInt16 => { let value = match endian { Endian::Big => u16::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => u16::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_int(ctx.clone(), value as i32) }, NumberKind::Int32 => { let value = match endian { Endian::Big => i32::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => i32::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_int(ctx.clone(), value) }, NumberKind::UInt32 => { let value = match endian { Endian::Big => u32::from_be_bytes(bytes.try_into().unwrap()), Endian::Little => u32::from_le_bytes(bytes.try_into().unwrap()), }; Value::new_float(ctx.clone(), value as f64) }, }; Ok(value) } // Pure mathematical byte generation fn endian_bytes(mut val: u64, endian: Endian) -> Vec { let mut bytes = vec![0u8; 8]; #[allow(clippy::needless_range_loop)] for i in 0..8 { bytes[i] = match endian { Endian::Big => (val >> (56 - i * 8)) as u8, Endian::Little => (val >> (i * 8)) as u8, }; // Clear processed bits match endian { Endian::Big => val &= !(0xFF << ((7 - i) * 8)), Endian::Little => val &= !(0xFF << (i * 8)), } } bytes } fn shifted_bytes(mut val: u64, bits: u8, endian: Endian) -> Vec { let byte_count = (bits / 8) as usize; let mut bytes = vec![0u8; byte_count]; #[allow(clippy::needless_range_loop)] for i in 0..byte_count { let shift = match endian { Endian::Big => (byte_count - 1 - i) * 8, Endian::Little => i * 8, }; bytes[i] = (val >> shift) as u8; val &= !(0xFF << shift); // Clear processed bits } bytes } pub(crate) fn set_prototype<'js>(ctx: &Ctx<'js>, constructor: Object<'js>) -> Result<()> { let _ = &constructor.set("alloc", Func::from(alloc))?; let _ = &constructor.set("allocUnsafe", Func::from(alloc_unsafe))?; let _ = &constructor.set("allocUnsafeSlow", Func::from(alloc_unsafe_slow))?; let _ = &constructor.set("byteLength", Func::from(byte_length))?; let _ = &constructor.set("concat", Func::from(concat))?; let _ = &constructor.set(PredefinedAtom::From, Func::from(from))?; let _ = &constructor.set("isBuffer", Func::from(is_buffer))?; let _ = &constructor.set("isEncoding", Func::from(is_encoding))?; let prototype: &Object = &constructor.get(PredefinedAtom::Prototype)?; prototype.set("copy", Func::from(copy))?; prototype.set("subarray", Func::from(subarray))?; prototype.set(PredefinedAtom::ToString, Func::from(to_string))?; prototype.set("write", Func::from(write))?; // Set all write and read methods for kind in NumberKind::iter() { for (endian, name, alias) in kind.prototype() { let write_func = Function::new(ctx.clone(), |t, c, v, o| { write_buf(&t, &c, &v, &o, *endian, *kind) })?; let read_func = Function::new(ctx.clone(), |t, c, o| read_buf(&t, &c, &o, *endian, *kind))?; if let Some(alias) = alias { prototype.set(["write", alias].concat(), write_func.clone())?; prototype.set(["read", alias].concat(), read_func.clone())?; } prototype.set(["write", name].concat(), write_func)?; prototype.set(["read", name].concat(), read_func)?; } } //not assessable from js prototype.prop(PredefinedAtom::Meta, stringify!(Buffer))?; ctx.globals().set(stringify!(Buffer), constructor)?; Ok(()) } pub fn atob(ctx: Ctx<'_>, encoded_value: Coerced) -> Result> { let vec = bytes_from_b64(encoded_value.as_bytes()).or_throw(&ctx)?; // Convert bytes to Latin-1 string where each byte becomes a character with that code point. // This matches the WHATWG spec: atob returns a "binary string" where each character's // code point is 0-255, directly representing one byte of data. let str: String = vec.iter().map(|&b| b as char).collect(); rquickjs::String::from_str(ctx, &str) } pub fn btoa(ctx: Ctx<'_>, value: Coerced) -> Result { // Per WHATWG spec, btoa() treats input as a "binary string" where each character // must have a code point 0-255. Characters > 255 cause InvalidCharacterError. let s: &str = value.as_str(); // Fast path: ASCII is a 1:1 mapping to bytes 0-127 (SIMD optimized) if s.is_ascii() { return Ok(bytes_to_b64_string(s.as_bytes())); } // Slow path: Check for Latin-1 (0-255) let bytes: Vec = s .chars() .map(|c| { let code_point = c as u32; if code_point > 255 { Err(Exception::throw_message( &ctx, "Invalid character: btoa() argument contains character with code point > 255", )) } else { Ok(code_point as u8) } }) .collect::>>()?; Ok(bytes_to_b64_string(&bytes)) } #[cfg(test)] mod tests { use llrt_test::{call_test, test_async_with, ModuleEvaluator}; use crate::BufferModule; #[tokio::test] async fn test_atob() { test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let data = "aGVsbG8gd29ybGQ=".to_string(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { atob } from 'buffer'; export async function test(data) { return atob(data); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (data,)).await; assert_eq!(result, "hello world"); }) }) .await; } #[tokio::test] async fn test_atob_high_bytes() { // Test that atob correctly decodes bytes 128-255 as Latin-1 characters // (each byte becomes a character with that code point) test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { atob } from 'buffer'; export async function test() { // Test individual high-byte values // gA== decodes to byte 0x80 (128) // /w== decodes to byte 0xFF (255) const test128 = atob("gA=="); const test255 = atob("/w=="); // Each decoded byte should become a character // with that exact code point if (test128.charCodeAt(0) !== 128) { return `byte 128 failed: got ${test128.charCodeAt(0)}`; } if (test255.charCodeAt(0) !== 255) { return `byte 255 failed: got ${test255.charCodeAt(0)}`; } // Test all bytes 128-255 to ensure none are corrupted // Create base64 for bytes 128-255 and verify roundtrip const highBytes = new Uint8Array(128); for (let i = 0; i < 128; i++) { highBytes[i] = 128 + i; } const base64 = Buffer.from(highBytes).toString("base64"); const decoded = atob(base64); for (let i = 0; i < 128; i++) { const expected = 128 + i; const actual = decoded.charCodeAt(i); if (actual !== expected) { return `byte ${expected} failed: got ${actual}`; } } return "ok"; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "ok"); }) }) .await; } #[tokio::test] async fn test_btoa() { test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let data = "hello world".to_string(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { btoa } from 'buffer'; export async function test(data) { return btoa(data); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (data,)).await; assert_eq!(result, "aGVsbG8gd29ybGQ="); }) }) .await; } #[tokio::test] async fn test_btoa_high_bytes() { // Test that btoa correctly encodes Latin-1 characters (code points 128-255) // as single bytes per WHATWG spec (not UTF-8 encoded) test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { btoa, atob } from 'buffer'; export async function test() { // Test byte 255 (0xFF): should encode as single byte // btoa(String.fromCharCode(255)) should give "/w==" not "w78=" const char255 = String.fromCharCode(255); const encoded255 = btoa(char255); if (encoded255 !== "/w==") { return `byte 255 encoding failed: got ${encoded255}, expected /w==`; } // Test byte 128 (0x80): should encode as single byte const char128 = String.fromCharCode(128); const encoded128 = btoa(char128); if (encoded128 !== "gA==") { return `byte 128 encoding failed: got ${encoded128}, expected gA==`; } // Test roundtrip for all bytes 0-255 for (let i = 0; i <= 255; i++) { const char = String.fromCharCode(i); const encoded = btoa(char); const decoded = atob(encoded); if (decoded.charCodeAt(0) !== i || decoded.length !== 1) { return `roundtrip failed for byte ${i}`; } } // Test that characters > 255 throw try { btoa("€"); // U+20AC return "btoa should have thrown for euro sign"; } catch (e) { // Expected } return "ok"; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "ok"); }) }) .await; } #[tokio::test] async fn test_subarray() { test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let data = "hello world".to_string().into_bytes(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { Buffer } from 'buffer'; export async function test(data) { let buffer = Buffer.from(data); let sub = buffer.subarray(6, 11); // "world" part return sub.toString(); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (data,)).await; assert_eq!(result, "world"); }) }) .await; } #[tokio::test] async fn test_subarray_partial() { test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let data = "hello world".to_string().into_bytes(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { Buffer } from 'buffer'; export async function test(data) { let buffer = Buffer.from(data); let sub = buffer.subarray(0, 5); // "hello" part return sub.toString(); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (data,)).await; assert_eq!(result, "hello"); }) }) .await; } #[tokio::test] async fn test_subarray_out_of_bounds() { test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let data = "hello world".to_string().into_bytes(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { Buffer } from 'buffer'; export async function test(data) { let buffer = Buffer.from(data); let sub = buffer.subarray(6, 20); // "world" part but goes out of bounds return sub.toString(); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (data,)).await; assert_eq!(result, "world"); }) }) .await; } #[tokio::test] async fn test_read_int_32_be() { test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "buffer") .await .unwrap(); let data = "hello world".to_string().into_bytes(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { Buffer } from 'buffer'; export async function test(data) { const buf = Buffer.from([1, 2, 3, 4, 0, 0, 0, 0]); return buf.readInt32BE(); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (data,)).await; assert_eq!(result, 0x01020304); }) }) .await; } } ================================================ FILE: modules/llrt_buffer/src/file.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::time; use rquickjs::{ atom::PredefinedAtom, class::Trace, function::Opt, ArrayBuffer, Coerced, Ctx, Exception, IntoJs, Object, Result, Value, }; use super::blob::Blob; #[rquickjs::class] #[derive(Trace, Clone, rquickjs::JsLifetime)] pub struct File { #[qjs(skip_trace)] blob: Blob, filename: String, last_modified: i64, } #[rquickjs::methods] impl File { #[qjs(constructor)] fn new<'js>( ctx: Ctx<'js>, data: Value<'js>, filename: Coerced, options: Opt>, ) -> Result { let mut last_modified = time::now_millis(); if let Some(ref opts) = options.0 { if opts.is_bool() || opts.is_float() || opts.is_int() || opts.is_string() { return Err(Exception::throw_type(&ctx, "Invalid options")); } if let Some(v) = opts.as_object() { if let Some(x) = v.get::<_, Option>>("lastModified")? { last_modified = x.0; } } } let blob = Blob::new(ctx, Opt(Some(data)), options)?; Ok(Self { blob, filename: filename.0, last_modified, }) } #[qjs(get)] pub fn size(&self) -> usize { self.blob.size() } #[qjs(get)] pub fn name(&self) -> String { self.filename.clone() } #[qjs(get, rename = "type")] pub fn mime_type(&self) -> String { self.blob.mime_type() } #[qjs(get, rename = "lastModified")] pub fn last_modified(&self) -> i64 { self.last_modified } pub fn slice(&self, start: Opt, end: Opt, content_type: Opt) -> Blob { self.blob.slice(start, end, content_type) } pub async fn text(&mut self) -> String { self.blob.text().await } #[qjs(rename = "arrayBuffer")] pub async fn array_buffer<'js>(&self, ctx: Ctx<'js>) -> Result> { self.blob.array_buffer(ctx).await } pub async fn bytes<'js>(&self, ctx: Ctx<'js>) -> Result> { self.blob.bytes(ctx).await } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(File) } } impl File { pub fn from_bytes<'js>( ctx: &Ctx<'js>, data: Vec, filename: String, mime_type: Option, ) -> Result { let options = Opt(Some({ let obj = Object::new(ctx.clone())?; obj.set( "type", mime_type .clone() .unwrap_or("application/octet-stream".into()) .into_js(ctx)?, )?; obj.into_js(ctx)? })); let blob = Blob::new(ctx.clone(), Opt(Some(data.into_js(ctx)?)), options)?; Ok(Self { blob, filename, last_modified: time::now_millis(), }) } pub fn get_blob(&self) -> Blob { self.blob.clone() } } ================================================ FILE: modules/llrt_buffer/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{ module::{export_default, ModuleInfo}, primordials::{BasePrimordials, Primordial}, }; use rquickjs::{ atom::PredefinedAtom, function::Constructor, module::{Declarations, Exports, ModuleDef}, prelude::Func, Class, Ctx, Object, Result, }; pub use self::array_buffer_view::*; pub use self::blob::*; pub use self::buffer::*; pub use self::file::*; mod array_buffer_view; mod blob; mod buffer; mod file; pub struct BufferModule; impl ModuleDef for BufferModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(Buffer))?; declare.declare("atob")?; declare.declare("btoa")?; declare.declare("constants")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { let globals = ctx.globals(); let buf: Constructor = globals.get(stringify!(Buffer))?; let constants = Object::new(ctx.clone())?; constants.set("MAX_LENGTH", u32::MAX)?; // For QuickJS constants.set("MAX_STRING_LENGTH", (1 << 30) - 1)?; // For QuickJS export_default(ctx, exports, |default| { default.set(stringify!(Buffer), buf)?; default.set("atob", Func::from(atob))?; default.set("btoa", Func::from(btoa))?; default.set("constants", constants)?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: BufferModule) -> Self { ModuleInfo { name: "buffer", module: val, } } } pub fn init<'js>(ctx: &Ctx<'js>) -> Result<()> { let globals = ctx.globals(); BasePrimordials::init(ctx)?; // Buffer let buffer = ctx.eval::, &str>(concat!( "class ", stringify!(Buffer), " extends Uint8Array {}\n", stringify!(Buffer), ))?; set_prototype(ctx, buffer)?; BufferPrimordials::init(ctx)?; // Blob if let Some(constructor) = Class::::create_constructor(ctx)? { constructor.prop( PredefinedAtom::SymbolHasInstance, Func::from(Blob::has_instance), )?; globals.set(stringify!(Blob), constructor)?; } // File Class::::define(&globals)?; //init primordials let _ = BufferPrimordials::get(ctx)?; // Conversion globals.set("atob", Func::from(atob))?; globals.set("btoa", Func::from(btoa))?; Ok(()) } ================================================ FILE: modules/llrt_child_process/Cargo.toml ================================================ [package] name = "llrt_child_process" description = "LLRT Module child_process" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_child_process" path = "src/lib.rs" [dependencies] itoa = { version = "1", default-features = false } llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" } llrt_events = { version = "0.8.1-beta", path = "../llrt_events" } llrt_stream = { version = "0.8.1-beta", path = "../llrt_stream" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } tokio = { version = "1", features = ["process"], default-features = false } [target.'cfg(unix)'.dependencies] libc = { version = "0.2", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } ================================================ FILE: modules/llrt_child_process/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] #[cfg(windows)] use std::os::windows::{ io::{FromRawHandle, RawHandle}, process::CommandExt, }; #[cfg(unix)] use std::os::{ fd::FromRawFd, unix::process::{CommandExt, ExitStatusExt}, }; use std::{ collections::HashMap, io::Result as IoResult, process::{Command as StdCommand, Stdio}, sync::{Arc, RwLock}, }; use llrt_context::CtxExtension; use llrt_events::{EmitError, Emitter, EventEmitter, EventList}; use llrt_stream::{ readable::{DefaultReadableStream, ReadableStream}, writable::{DefaultWritableStream, WritableStream}, }; use llrt_utils::{ module::{export_default, ModuleInfo}, object::ObjectExt, result::ResultExt, }; use rquickjs::{ class::{Trace, Tracer}, convert::Coerced, module::{Declarations, Exports, ModuleDef}, prelude::{Func, Opt, Rest, This}, Class, Ctx, Error, Exception, IntoJs, Result, Value, }; use tokio::{ io::AsyncRead, process::{Child, Command}, sync::{ broadcast::{channel as broadcast_channel, Receiver, Sender}, oneshot::Receiver as OneshotReceiver, }, }; #[cfg(unix)] use llrt_utils::signals::{kill, signal_str_from_i32}; #[cfg(not(unix))] use llrt_utils::signals::{kill_process_raw, parse_signal}; #[allow(unused_variables)] fn prepare_shell_args( shell: &str, windows_verbatim_arguments: &mut bool, cmd: String, command_args: Option>, ) -> Vec { let mut string_args = cmd; #[cfg(windows)] let shell_is_cmd = shell.ends_with("cmd") || shell.ends_with("cmd.exe"); #[cfg(windows)] { if shell_is_cmd { *windows_verbatim_arguments = true; string_args.insert(0, '"'); } } if let Some(command_args) = command_args { //reserve at least arg length +1 let total_length = command_args.iter().map(|s| s.len() + 1).sum(); string_args.reserve(total_length); string_args.push(' '); for arg in command_args.iter() { string_args.push_str(arg); string_args.push(' '); } } else { string_args.push(' '); } #[cfg(windows)] { if shell_is_cmd { string_args.push('"'); return vec![ String::from("/d"), String::from("/s"), String::from("/c"), string_args, ]; } } vec!["-c".into(), string_args] } #[allow(dead_code)] #[rquickjs::class] #[derive(rquickjs::JsLifetime)] pub struct ChildProcess<'js> { emitter: EventEmitter<'js>, args: Option>, command: String, kill_tx: Option>, pid: Option, } impl<'js> Trace<'js> for ChildProcess<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.emitter.trace(tracer); } } #[derive(Clone)] enum StdioEnum { Piped, Ignore, Inherit, Fd(i32), } impl StdioEnum { fn to_stdio(&self) -> Stdio { match self { StdioEnum::Piped => Stdio::piped(), StdioEnum::Ignore => Stdio::null(), StdioEnum::Inherit => Stdio::inherit(), StdioEnum::Fd(id) => { #[cfg(unix)] unsafe { Stdio::from_raw_fd(*id) } #[cfg(windows)] unsafe { Stdio::from_raw_handle(*id as RawHandle) } }, } } } #[rquickjs::methods] impl<'js> ChildProcess<'js> { #[qjs(get)] fn pid(&self, ctx: Ctx<'js>) -> Result> { self.pid.into_js(&ctx) } #[allow(unused_variables)] fn kill(&mut self, ctx: Ctx<'js>, signal: Opt>) -> Result { if let Some(pid) = self.pid { #[cfg(unix)] { return kill(&ctx, pid, signal); } #[cfg(windows)] { let signal = parse_signal(signal.0)?; if signal == 0 { return kill_process_raw(pid, 0) .map(|_| true) .or_else(|_| Ok(false)); } if let Some(tx) = self.kill_tx.take() { return Ok(tx.send(()).is_ok()); } } } Ok(false) } } impl<'js> ChildProcess<'js> { fn new( ctx: Ctx<'js>, command: String, args: Option>, child: IoResult, ) -> Result> { let (kill_tx, kill_rx) = broadcast_channel::<()>(1); let instance = Self { emitter: EventEmitter::new(), command: command.clone(), args, pid: None, kill_tx: Some(kill_tx), }; let stdout_instance = DefaultReadableStream::new(ctx.clone())?; let stderr_instance = DefaultReadableStream::new(ctx.clone())?; let stdin_instance = DefaultWritableStream::new(ctx.clone())?; let instance = Class::instance(ctx.clone(), instance)?; let instance2 = instance.clone(); let instance3 = instance.clone(); let instance4 = instance.clone(); instance.set("stderr", stderr_instance.clone())?; instance.set("stdout", stdout_instance.clone())?; instance.set("stdin", stdin_instance.clone())?; match child { Ok(mut child) => { instance2.borrow_mut().pid = child.id(); if let Some(child_stdin) = child.stdin.take() { DefaultWritableStream::process(stdin_instance.clone(), &ctx, child_stdin)?; }; let stdout_join_receiver = create_output(&ctx, child.stdout.take(), stdout_instance.clone())?; let stderr_join_receiver = create_output(&ctx, child.stderr.take(), stderr_instance.clone())?; let ctx2 = ctx.clone(); let ctx3 = ctx.clone(); ctx.spawn_exit(async move { let spawn_proc = async move { let mut exit_code = None; let mut exit_signal = None; wait_for_process(child, &ctx3, kill_rx, &mut exit_code, &mut exit_signal) .await?; let code = match exit_code { Some(c) => c.into_js(&ctx3)?, None => rquickjs::Null.into_value(ctx3.clone()), }; let signal; #[cfg(unix)] { if let Some(s) = exit_signal { signal = signal_str_from_i32(s).into_js(&ctx3)?; } else { signal = rquickjs::Null.into_value(ctx3.clone()); } } #[cfg(not(unix))] { signal = "SIGKILL".into_js(&ctx3)?; } ChildProcess::emit_str( This(instance2.clone()), &ctx3, "exit", vec![code.clone(), signal.clone()], false, )?; if let Some(stderr_join_receiver) = stderr_join_receiver { //ok if sender drops let _ = stderr_join_receiver.await; } if let Some(stdout_join_receiver) = stdout_join_receiver { //ok if sender drops let _ = stdout_join_receiver.await; } WritableStream::end(This(stdin_instance)); ReadableStream::drain(stdout_instance, &ctx3)?; ReadableStream::drain(stderr_instance, &ctx3)?; ChildProcess::emit_str( This(instance2.clone()), &ctx3, "close", vec![code, signal], false, )?; Ok::<_, Error>(()) }; spawn_proc .await .emit_error("child_process", &ctx2, instance4)?; Ok(()) })?; }, Err(err) => { let ctx3 = ctx.clone(); let err_message = format!("Child process failed to spawn \"{}\". {}", command, err); ctx.spawn_exit(async move { if !instance3.borrow().emitter.has_listener_str("error") { return Err(Exception::throw_message(&ctx3, &err_message)); } let ex = Exception::from_message(ctx3.clone(), &err_message)?; ChildProcess::emit_str( This(instance3), &ctx3, "error", vec![ex.into()], false, )?; Ok(()) })?; }, } Ok(instance) } } async fn wait_for_process( mut child: Child, ctx: &Ctx<'_>, mut kill_rx: Receiver<()>, exit_code: &mut Option, _exit_signal: &mut Option, ) -> Result<()> { #[cfg(not(unix))] let mut was_killed = false; loop { tokio::select! { status = child.wait() => { let exit_status = status.or_throw(ctx)?; #[cfg(unix)] { if let Some(sig) = exit_status.signal() { _exit_signal.replace(sig); // code is null when terminated by signal } else { exit_code.replace(exit_status.code().unwrap_or_default()); } } #[cfg(not(unix))] { if !was_killed { exit_code.replace(exit_status.code().unwrap_or_default()); } } break; } Ok(()) = kill_rx.recv() => { #[cfg(not(unix))] { was_killed = true; } child.kill().await.or_throw(ctx)?; } } } Ok(()) } impl<'js> Emitter<'js> for ChildProcess<'js> { fn get_event_list(&self) -> Arc>> { self.emitter.get_event_list() } } fn spawn<'js>( ctx: Ctx<'js>, cmd: String, args_and_opts: Rest>, ) -> Result>> { let args_0 = args_and_opts.first(); let args_1 = args_and_opts.get(1); let mut opts = None; if args_1.is_some() { opts = args_1.and_then(|o| o.as_object()).map(|o| o.to_owned()); } let mut command_args = if let Some(args_0) = args_0 { if args_0.is_array() { let args = args_0.clone().into_array().or_throw(&ctx)?; let mut args_vec = Vec::with_capacity(args.len()); for arg in args.iter() { let arg: Value = arg?; let arg = arg .as_string() .or_throw_msg(&ctx, "argument is not a string")?; let arg = arg.to_string()?; args_vec.push(arg); } Some(args_vec) } else if args_0.is_object() { opts = args_0.as_object().map(|o| o.to_owned()); None } else { None } } else { None }; let mut windows_verbatim_arguments = if let Some(opts) = &opts { opts.get_optional::<&str, bool>("windowsVerbatimArguments")? .unwrap_or_default() } else { false }; let cmd = if let Some(opts) = &opts { if opts .get_optional::<&str, bool>("shell")? .unwrap_or_default() { #[cfg(windows)] let shell = "cmd.exe".to_string(); #[cfg(not(windows))] let shell = "/bin/sh".to_string(); command_args = Some(prepare_shell_args( &shell, &mut windows_verbatim_arguments, cmd, command_args, )); shell } else if let Some(shell) = opts.get_optional::<&str, String>("shell")? { command_args = Some(prepare_shell_args( &shell, &mut windows_verbatim_arguments, cmd, command_args, )); shell } else { cmd } } else { cmd }; let mut command = StdCommand::new(cmd.clone()); if let Some(args) = &command_args { #[cfg(windows)] if windows_verbatim_arguments { command.raw_arg(args.join(" ")); } else { command.args(args); } #[cfg(not(windows))] command.args(args); } let mut stdin = StdioEnum::Piped; let mut stdout = StdioEnum::Piped; let mut stderr = StdioEnum::Piped; let mut detached = false; if let Some(opts) = opts { #[cfg(unix)] { if let Some(gid) = opts.get_optional("gid")? { command.gid(gid); } if let Some(uid) = opts.get_optional("uid")? { command.uid(uid); } } detached = opts.get_optional("detached")?.unwrap_or(false); if let Some(cwd) = opts.get_optional::<_, String>("cwd")? { command.current_dir(&cwd); } if let Some(env) = opts.get_optional::<_, HashMap>>("env")? { let env: HashMap = env .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); command.env_clear(); command.envs(env); } if let Some(stdio) = opts.get_optional::<_, Value<'js>>("stdio")? { if let Some(stdio_str) = stdio.as_string() { let stdio = str_to_stdio(&ctx, &stdio_str.to_string()?)?; stdin = stdio.clone(); stdout = stdio.clone(); stderr = stdio; } else if let Some(stdio) = stdio.as_array() { for (i, item) in stdio.iter::().enumerate() { let item = item?; let stdio = if item.is_undefined() || item.is_null() { StdioEnum::Piped } else if let Some(std_io_str) = item.as_string() { str_to_stdio(&ctx, &std_io_str.to_string()?)? } else if let Some(fd) = item.as_number() { StdioEnum::Fd(fd as i32) } else { StdioEnum::Piped }; match i { 0 => stdin = stdio, 1 => stdout = stdio, 2 => stderr = stdio, _ => { break; }, } } } } } command.stdin(stdin.to_stdio()); command.stdout(stdout.to_stdio()); command.stderr(stderr.to_stdio()); if detached { #[cfg(unix)] { command.process_group(0); } #[cfg(windows)] { // DETACHED_PROCESS = 0x00000008 command.creation_flags(0x00000008); } } //tokio command does not have all std command features stabilized let mut command = Command::from(command); ChildProcess::new(ctx.clone(), cmd, command_args, command.spawn()) } fn str_to_stdio(ctx: &Ctx<'_>, input: &str) -> Result { match input { "pipe" => Ok(StdioEnum::Piped), "ignore" => Ok(StdioEnum::Ignore), "inherit" => Ok(StdioEnum::Inherit), _ => Err(Exception::throw_type( ctx, &format!( "Invalid stdio \"{}\". Expected one of: pipe, ignore, inherit", input ), )), } } fn create_output<'js, T>( ctx: &Ctx<'js>, output: Option, native_readable_stream: Class<'js, DefaultReadableStream<'js>>, ) -> Result>> where T: AsyncRead + Unpin + Send + 'static, { if let Some(output) = output { let receiver = DefaultReadableStream::process(native_readable_stream, ctx, output)?; return Ok(Some(receiver)); } Ok(None) } pub struct ChildProcessModule; impl ModuleDef for ChildProcessModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("spawn")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { ChildProcess::add_event_emitter_prototype(ctx)?; DefaultWritableStream::add_writable_stream_prototype(ctx)?; DefaultWritableStream::add_event_emitter_prototype(ctx)?; DefaultReadableStream::add_readable_stream_prototype(ctx)?; DefaultReadableStream::add_event_emitter_prototype(ctx)?; export_default(ctx, exports, |default| { default.set("spawn", Func::from(spawn))?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: ChildProcessModule) -> Self { ModuleInfo { name: "child_process", module: val, } } } #[cfg(test)] mod tests { use super::*; use llrt_buffer as buffer; use llrt_test::{test_async_with, ModuleEvaluator}; use rquickjs::CatchResultExt; #[tokio::test] async fn test_spawn() { test_async_with(|ctx| { Box::pin(async move { buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "node:child_process") .await .unwrap(); let message: String = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import {spawn} from "node:child_process"; let resolve = null; const deferred = new Promise(res => { resolve = res; }); spawn("echo", ["hello"]).stdout.on("data", (data) => { resolve(data.toString().trim()) }); export default await deferred; "#, ) .await .catch(&ctx) .unwrap() .get("default") .unwrap(); assert_eq!(message, "hello"); }) }) .await; } #[tokio::test] async fn test_spawn_shell() { test_async_with(|ctx| { Box::pin(async move { buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "node:child_process") .await .unwrap(); let message: String = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import {spawn} from "node:child_process"; let resolve = null; const deferred = new Promise(res => { resolve = res; }); spawn("echo", ["hello"], { shell: true }).stdout.on("data", (data) => { resolve(data.toString().trim()) }); export default await deferred; "#, ) .await .catch(&ctx) .unwrap() .get("default") .unwrap(); assert_eq!(message, "hello"); }) }) .await; } } ================================================ FILE: modules/llrt_console/Cargo.toml ================================================ [package] name = "llrt_console" description = "LLRT Module console" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_console" path = "src/lib.rs" [dependencies] llrt_logging = { version = "0.8.1-beta", path = "../../libs/llrt_logging" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } ================================================ FILE: modules/llrt_console/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::io::{stderr, stdout, IsTerminal, Write}; use llrt_logging::{build_formatted_string, FormatOptions, NEWLINE}; use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::{Func, Rest}, Class, Ctx, Object, Result, Value, }; #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] #[rquickjs::class] pub struct Console {} impl Default for Console { fn default() -> Self { Self::new() } } #[rquickjs::methods(rename_all = "camelCase")] impl Console { #[qjs(constructor)] pub fn new() -> Self { // We ignore the parameters for now since we don't support stream Self {} } pub fn log<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log(ctx, args) } pub fn clear(&self) { clear() } pub fn debug<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_debug(ctx, args) } pub fn info<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log(ctx, args) } pub fn trace<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_trace(ctx, args) } pub fn error<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_error(ctx, args) } pub fn warn<'js>(&self, ctx: Ctx<'js>, args: Rest>) -> Result<()> { log_warn(ctx, args) } pub fn assert<'js>( &self, ctx: Ctx<'js>, expression: bool, args: Rest>, ) -> Result<()> { log_assert(ctx, expression, args) } } pub fn log_fatal<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stderr(), &ctx, args) } pub fn log_error<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stderr(), &ctx, args) } fn log_warn<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stderr(), &ctx, args) } fn log_debug<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stdout(), &ctx, args) } fn log_trace<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stdout(), &ctx, args) } fn log_assert<'js>(ctx: Ctx<'js>, expression: bool, args: Rest>) -> Result<()> { if !expression { write_log(stderr(), &ctx, args)?; } Ok(()) } fn log<'js>(ctx: Ctx<'js>, args: Rest>) -> Result<()> { write_log(stdout(), &ctx, args) } fn clear() { let _ = stdout().write_all(b"\x1b[1;1H\x1b[0J"); } fn write_log<'js, T>(mut output: T, ctx: &Ctx<'js>, args: Rest>) -> Result<()> where T: Write + IsTerminal, { let is_tty = output.is_terminal(); let mut result = String::new(); let mut options = FormatOptions::new(ctx, is_tty, true)?; build_formatted_string(&mut result, ctx, args, &mut options)?; result.push(NEWLINE); //we don't care if output is interrupted let _ = output.write_all(result.as_bytes()); Ok(()) } pub struct ConsoleModule; impl ModuleDef for ConsoleModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(Console))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { Class::::define(default)?; Ok(()) }) } } impl From for ModuleInfo { fn from(val: ConsoleModule) -> Self { ModuleInfo { name: "console", module: val, } } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); let console = Object::new(ctx.clone())?; console.set("assert", Func::from(log_assert))?; console.set("clear", Func::from(clear))?; console.set("debug", Func::from(log_debug))?; console.set("error", Func::from(log_error))?; console.set("info", Func::from(log))?; console.set("log", Func::from(log))?; console.set("trace", Func::from(log_trace))?; console.set("warn", Func::from(log_warn))?; globals.set("console", console)?; Ok(()) } ================================================ FILE: modules/llrt_crypto/Cargo.toml ================================================ [package] name = "llrt_crypto" description = "LLRT Module crypto" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_crypto" path = "src/lib.rs" [features] default = ["crypto-rust"] # Internal feature: enables DER parsing for SubtleCrypto key import/export _subtle-full = ["llrt_json", "const-oid", "der", "pkcs8", "spki"] # Internal feature: enables RustCrypto dependencies for key import/export _rustcrypto = [ "_subtle-full", "aes", "aes-gcm", "aes-kw", "cbc", "ctr", "ecdsa", "ed25519-dalek", "elliptic-curve", "hkdf", "hmac", "rsa", "p256", "p384", "p521", "pbkdf2", "sha1", "x25519-dalek", ] crypto-rust = ["_rustcrypto"] crypto-ring = ["ring"] crypto-ring-rust = ["ring", "_rustcrypto"] crypto-graviola = ["graviola"] crypto-graviola-rust = ["graviola", "_rustcrypto"] crypto-openssl = ["openssl-sys", "openssl", "_subtle-full"] [dependencies] crc32c = { version = "0.6", default-features = false } crc32fast = { version = "1", default-features = false } llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" } llrt_encoding = { version = "0.8.1-beta", path = "../../libs/llrt_encoding" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } md-5 = { version = "0.11.0-rc.5", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } rand = { version = "0.10.0", features = [ "std", "std_rng", "thread_rng", ], default-features = false } rquickjs = { version = "0.11", default-features = false } # optional llrt_json = { version = "0.8.1-beta", path = "../../libs/llrt_json", optional = true } aes = { version = "0.9.0-rc.4", optional = true } aes-gcm = { version = "0.11.0-rc.3", features = [ "alloc", ], default-features = false, optional = true } aes-kw = { version = "0.3.0-rc.2", features = [ "oid", ], default-features = false, optional = true } cbc = { version = "0.2.0-rc.3", features = ["alloc"], optional = true } const-oid = { version = "0.10", features = [ "db", ], default-features = false, optional = true } ctr = { version = "0.10.0-rc.3", default-features = false, optional = true } der = { version = "0.8", features = [ "derive", "alloc", ], default-features = false, optional = true } ecdsa = { version = "0.17.0-rc.16", default-features = false, optional = true } ed25519-dalek = { version = "3.0.0-pre.6", features = [ "alloc", "pkcs8", "rand_core", ], default-features = false, optional = true } elliptic-curve = { version = "0.14.0-rc.28", features = [ "alloc", "sec1", ], default-features = false, optional = true } hkdf = { version = "0.13.0-rc.5", default-features = false, optional = true } hmac = { version = "0.13.0-rc.5", default-features = false, optional = true } rsa = { version = "0.10.0-rc.15", features = [ "std", "sha2", "encoding", ], default-features = false, optional = true } p256 = { version = "0.14.0-rc.7", features = [ "ecdh", "ecdsa", "pkcs8", ], default-features = false, optional = true } p384 = { version = "0.14.0-rc.7", features = [ "ecdh", "ecdsa", "pkcs8", ], default-features = false, optional = true } p521 = { version = "0.14.0-rc.7", features = [ "ecdh", "ecdsa", "pkcs8", ], default-features = false, optional = true } pbkdf2 = { version = "0.13.0-rc.9", default-features = false, optional = true } pkcs8 = { version = "0.11.0-rc.11", default-features = false, features = [ "alloc", ], optional = true } sha1 = { version = "0.11.0-rc.5", default-features = false, optional = true } spki = { version = "0.8.0-rc.4", features = [ "alloc", ], default-features = false, optional = true } x25519-dalek = { version = "3.0.0-pre.6", features = [ "static_secrets", "zeroize", ], default-features = false, optional = true } # Crypto provider dependencies ring = { version = "0.17", default-features = false, optional = true } openssl-sys = { version = "0.9", optional = true } openssl = { version = "0.10", optional = true } graviola = { version = "0.3", git = "https://github.com/ctz/graviola.git", optional = true } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } ================================================ FILE: modules/llrt_crypto/src/crc32.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::hash::Hasher; use crc32c::Crc32cHasher; use llrt_utils::bytes::ObjectBytes; use rquickjs::{prelude::This, Class, Ctx, Result}; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Crc32c { #[qjs(skip_trace)] hasher: crc32c::Crc32cHasher, } #[rquickjs::methods] impl Crc32c { #[qjs(constructor)] fn new() -> Self { Self { hasher: Crc32cHasher::default(), } } #[qjs(rename = "digest")] fn crc32c_digest(&self) -> u64 { self.hasher.finish() } #[qjs(rename = "update")] fn crc32c_update<'js>( this: This>, ctx: Ctx<'js>, bytes: ObjectBytes<'js>, ) -> Result> { this.0.borrow_mut().hasher.write(bytes.as_bytes(&ctx)?); Ok(this.0) } } #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Crc32 { #[qjs(skip_trace)] hasher: crc32fast::Hasher, } #[rquickjs::methods] impl Crc32 { #[qjs(constructor)] fn new() -> Self { Self { hasher: crc32fast::Hasher::new(), } } #[qjs(rename = "digest")] fn crc32_digest(&self) -> u64 { self.hasher.finish() } #[qjs(rename = "update")] fn crc32_update<'js>( this: This>, ctx: Ctx<'js>, bytes: ObjectBytes<'js>, ) -> Result> { this.0.borrow_mut().hasher.write(bytes.as_bytes(&ctx)?); Ok(this.0) } } ================================================ FILE: modules/llrt_crypto/src/hash.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_buffer::Buffer; use llrt_utils::{bytes::ObjectBytes, iterable_enum, result::ResultExt}; use rquickjs::{ class::Trace, function::Opt, prelude::This, Class, Ctx, IntoJs, JsLifetime, Result, Value, }; use super::encoded_bytes; use crate::provider::{CryptoProvider, HmacProvider, SimpleDigest}; use crate::CRYPTO_PROVIDER; #[derive(Debug, Clone, Copy)] pub enum HashAlgorithm { Md5, Sha1, Sha256, Sha384, Sha512, } iterable_enum!(HashAlgorithm, Md5, Sha1, Sha256, Sha384, Sha512); impl TryFrom<&str> for HashAlgorithm { type Error = String; fn try_from(s: &str) -> std::result::Result { Ok(match s.to_ascii_uppercase().as_str() { "MD5" => HashAlgorithm::Md5, "MD-5" => HashAlgorithm::Md5, "SHA1" => HashAlgorithm::Sha1, "SHA-1" => HashAlgorithm::Sha1, "SHA256" => HashAlgorithm::Sha256, "SHA-256" => HashAlgorithm::Sha256, "SHA384" => HashAlgorithm::Sha384, "SHA-384" => HashAlgorithm::Sha384, "SHA512" => HashAlgorithm::Sha512, "SHA-512" => HashAlgorithm::Sha512, _ => return Err(["'", s, "' not available"].concat()), }) } } impl HashAlgorithm { pub fn class_name(&self) -> &'static str { match self { HashAlgorithm::Md5 => "Md5", HashAlgorithm::Sha1 => "Sha1", HashAlgorithm::Sha256 => "Sha256", HashAlgorithm::Sha384 => "Sha384", HashAlgorithm::Sha512 => "Sha512", } } pub fn as_str(&self) -> &'static str { match self { HashAlgorithm::Md5 => "MD5", HashAlgorithm::Sha1 => "SHA-1", HashAlgorithm::Sha256 => "SHA-256", HashAlgorithm::Sha384 => "SHA-384", HashAlgorithm::Sha512 => "SHA-512", } } pub fn as_numeric_str(&self) -> &'static str { match self { HashAlgorithm::Md5 => "md5", HashAlgorithm::Sha1 => "1", HashAlgorithm::Sha256 => "256", HashAlgorithm::Sha384 => "384", HashAlgorithm::Sha512 => "512", } } pub fn digest_len(&self) -> usize { match self { HashAlgorithm::Md5 => 16, HashAlgorithm::Sha1 => 20, HashAlgorithm::Sha256 => 32, HashAlgorithm::Sha384 => 48, HashAlgorithm::Sha512 => 64, } } pub fn block_len(&self) -> usize { match self { HashAlgorithm::Md5 => 64, HashAlgorithm::Sha1 => 64, HashAlgorithm::Sha256 => 64, HashAlgorithm::Sha384 => 128, HashAlgorithm::Sha512 => 128, } } } type ProviderDigest = ::Digest; type ProviderHmac = ::Hmac; #[derive(Trace, JsLifetime)] #[rquickjs::class] pub struct Hash { #[qjs(skip_trace)] digest: Option, #[qjs(skip_trace)] hmac: Option, } impl Hash { pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result { let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; Ok(Self { digest: Some(CRYPTO_PROVIDER.digest(algorithm)), hmac: None, }) } pub fn new_hmac<'js>( ctx: Ctx<'js>, algorithm: String, secret: ObjectBytes<'js>, ) -> Result { let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; let key = secret.as_bytes(&ctx)?; Ok(Self { digest: None, hmac: Some(CRYPTO_PROVIDER.hmac(algorithm, key)), }) } fn do_update(&mut self, data: &[u8]) { if let Some(ref mut d) = self.digest { d.update(data); } else if let Some(ref mut h) = self.hmac { h.update(data); } } fn do_finalize(&mut self) -> Option> { if let Some(d) = self.digest.take() { Some(d.finalize()) } else { self.hmac.take().map(|h| h.finalize()) } } } #[rquickjs::methods] impl Hash { #[qjs(rename = "digest")] fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { let result = self .do_finalize() .ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?; let Some(encoding) = encoding.0 else { return Buffer(result).into_js(&ctx); }; match encoded_bytes(&ctx, &result, &encoding)? { Some(encoded) => Ok(encoded), None => Buffer(result).into_js(&ctx), } } #[qjs(rename = "update")] fn hash_update<'js>( this: This>, ctx: Ctx<'js>, bytes: ObjectBytes<'js>, ) -> Result> { let bytes = bytes.as_bytes(&ctx)?; this.0.borrow_mut().do_update(bytes); Ok(this.0) } } #[derive(Trace, JsLifetime)] #[rquickjs::class] pub struct Hmac { #[qjs(skip_trace)] hash: Hash, } impl Hmac { pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result { Ok(Self { hash: Hash::new_hmac(ctx, algorithm, key_value)?, }) } } #[rquickjs::methods] impl Hmac { fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt) -> Result> { self.hash.hash_digest(ctx, encoding) } fn update<'js>( this: This>, ctx: Ctx<'js>, bytes: ObjectBytes<'js>, ) -> Result> { let bytes = bytes.as_bytes(&ctx)?; this.0.borrow_mut().hash.do_update(bytes); Ok(this.0) } } ================================================ FILE: modules/llrt_crypto/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Compile-time checks for conflicting crypto features #[cfg(all(feature = "crypto-rust", feature = "crypto-openssl"))] compile_error!("Features `crypto-rust` and `crypto-openssl` are mutually exclusive"); #[cfg(all(feature = "crypto-rust", feature = "crypto-ring"))] compile_error!("Features `crypto-rust` and `crypto-ring` are mutually exclusive"); #[cfg(all(feature = "crypto-rust", feature = "crypto-graviola"))] compile_error!("Features `crypto-rust` and `crypto-graviola` are mutually exclusive"); #[cfg(all(feature = "crypto-openssl", feature = "crypto-ring"))] compile_error!("Features `crypto-openssl` and `crypto-ring` are mutually exclusive"); #[cfg(all(feature = "crypto-openssl", feature = "crypto-graviola"))] compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exclusive"); #[cfg(all(feature = "crypto-ring", feature = "crypto-graviola"))] compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive"); mod crc32; mod hash; mod subtle; mod provider; use std::slice; use llrt_buffer::Buffer; use llrt_context::CtxExtension; use llrt_encoding::{bytes_to_b64_string, bytes_to_hex_string}; use llrt_utils::{ bytes::{get_start_end_indexes, ObjectBytes}, error::ErrorExtensions, error_messages::{ERROR_MSG_ARRAY_BUFFER_DETACHED, ERROR_MSG_NOT_ARRAY_BUFFER}, module::{export_default, ModuleInfo}, result::ResultExt, }; use once_cell::sync::Lazy; use rand::RngExt; use rquickjs::prelude::Async; use rquickjs::{ atom::PredefinedAtom, function::{Constructor, Opt}, module::{Declarations, Exports, ModuleDef}, prelude::{Func, Rest}, Class, Ctx, Error, Exception, Function, IntoJs, Null, Object, Result, Value, }; use subtle::{ subtle_decrypt, subtle_derive_bits, subtle_derive_key, subtle_digest, subtle_encrypt, subtle_export_key, subtle_generate_key, subtle_import_key, subtle_sign, subtle_unwrap_key, subtle_verify, subtle_wrap_key, CryptoKey, SubtleCrypto, }; use self::{ crc32::{Crc32, Crc32c}, hash::{Hash, HashAlgorithm, Hmac}, }; static CRYPTO_PROVIDER: Lazy = Lazy::new(|| provider::DefaultProvider {}); fn encoded_bytes<'js>(ctx: &Ctx<'js>, bytes: &[u8], encoding: &str) -> Result>> { match encoding { "hex" => { let hex = bytes_to_hex_string(bytes); let hex = rquickjs::String::from_str(ctx.clone(), &hex)?; Ok(Some(Value::from_string(hex))) }, "base64" => { let b64 = bytes_to_b64_string(bytes); let b64 = rquickjs::String::from_str(ctx.clone(), &b64)?; Ok(Some(Value::from_string(b64))) }, _ => Ok(None), } } #[inline] pub fn random_byte_array(length: usize) -> Vec { let mut vec = vec![0u8; length]; rand::rng().fill(&mut vec[..]); vec } fn get_random_bytes(ctx: Ctx, length: usize) -> Result { let random_bytes = random_byte_array(length); Buffer(random_bytes).into_js(&ctx) } fn get_random_int(first: i64, second: Opt) -> Result { let mut rng = rand::rng(); let random_number = match second.0 { Some(max) => rng.random_range(first..max), None => rng.random_range(0..first), }; Ok(random_number) } fn random_fill<'js>(ctx: Ctx<'js>, obj: Object<'js>, args: Rest>) -> Result<()> { let args_iter = args.0.into_iter(); let mut args_iter = args_iter.rev(); let callback: Function = args_iter .next() .and_then(|v| v.into_function()) .or_throw_msg(&ctx, "Callback required")?; let size = args_iter .next() .and_then(|arg| arg.as_int()) .map(|i| i as usize); let offset = args_iter .next() .and_then(|arg| arg.as_int()) .map(|i| i as usize); ctx.clone().spawn_exit(async move { if let Err(err) = random_fill_sync(ctx.clone(), obj.clone(), Opt(offset), Opt(size)) { let err = err.into_value(&ctx)?; () = callback.call((err,))?; return Ok(()); } () = callback.call((Null.into_js(&ctx), obj))?; Ok::<_, Error>(()) })?; Ok(()) } fn random_fill_sync<'js>( ctx: Ctx<'js>, obj: Object<'js>, offset: Opt, size: Opt, ) -> Result> { let offset = offset.unwrap_or(0); if let Some(object_bytes) = ObjectBytes::from_array_buffer(&obj)? { let (array_buffer, source_length, source_offset) = object_bytes .get_array_buffer()? .expect(ERROR_MSG_NOT_ARRAY_BUFFER); let raw = array_buffer .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(&ctx)?; let (start, end) = get_start_end_indexes(source_length, size.0, offset); let bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), source_length) }; rand::rng().fill(&mut bytes[start + source_offset..end - source_offset]); } Ok(obj) } fn get_random_values<'js>(ctx: Ctx<'js>, obj: Object<'js>) -> Result> { if let Some(object_bytes) = ObjectBytes::from_array_buffer(&obj)? { if matches!( object_bytes, ObjectBytes::F64Array(_) | ObjectBytes::F32Array(_) ) { return Err(Exception::throw_message(&ctx, "Unsupported TypedArray")); } let (array_buffer, source_length, source_offset) = object_bytes .get_array_buffer()? .expect(ERROR_MSG_NOT_ARRAY_BUFFER); let raw = array_buffer .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(&ctx)?; if source_length > 0x10000 { return Err(Exception::throw_message( &ctx, "QuotaExceededError: The requested length exceeds 65,536 bytes", )); } let bytes = unsafe { std::slice::from_raw_parts_mut(raw.ptr.as_ptr().add(source_offset), source_length) }; rand::rng().fill(bytes) } Ok(obj) } fn uuidv4() -> String { let uuid = rand::random::() & 0xFFFFFFFFFFFF4FFFBFFFFFFFFFFFFFFF | 0x40008000000000000000; static HEX_CHARS: &[u8; 16] = b"0123456789abcdef"; let bytes = uuid.to_be_bytes(); let mut buf = [0u8; 36]; // Precomputed positions for 32 hex digits (excluding hyphens) static HEX_POS: [usize; 32] = [ 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 16, 17, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, ]; // Map each byte to its hex representation let mut hex_idx = 0; for &byte in &bytes[..] { let high = HEX_CHARS[(byte >> 4) as usize]; let low = HEX_CHARS[(byte & 0x0f) as usize]; buf[HEX_POS[hex_idx]] = high; buf[HEX_POS[hex_idx + 1]] = low; hex_idx += 2; } // Insert hyphens at standard positions buf[8] = b'-'; buf[13] = b'-'; buf[18] = b'-'; buf[23] = b'-'; // SAFETY: The buffer only contains valid UTF-8 characters (hex digits and hyphens) // that were explicitly set from the HEX_CHARS array and hyphen literals unsafe { String::from_utf8_unchecked(buf.to_vec()) } } #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] struct Crypto {} #[rquickjs::methods] impl Crypto { #[qjs(constructor)] pub fn new(ctx: Ctx<'_>) -> Result { Err(Exception::throw_type(&ctx, "Illegal constructor")) } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(Crypto) } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); Class::::define(&globals)?; let crypto = Class::instance(ctx.clone(), Crypto {})?; crypto.set("createHash", Func::from(Hash::new))?; crypto.set("createHmac", Func::from(Hmac::new))?; crypto.set("randomBytes", Func::from(get_random_bytes))?; crypto.set("randomInt", Func::from(get_random_int))?; crypto.set("randomUUID", Func::from(uuidv4))?; crypto.set("randomFillSync", Func::from(random_fill_sync))?; crypto.set("randomFill", Func::from(random_fill))?; crypto.set("getRandomValues", Func::from(get_random_values))?; Class::::define(&globals)?; Class::::define(&globals)?; let subtle = Class::instance(ctx.clone(), SubtleCrypto {})?; subtle.set("decrypt", Func::from(Async(subtle_decrypt)))?; subtle.set("deriveKey", Func::from(Async(subtle_derive_key)))?; subtle.set("deriveBits", Func::from(Async(subtle_derive_bits)))?; subtle.set("digest", Func::from(Async(subtle_digest)))?; subtle.set("encrypt", Func::from(Async(subtle_encrypt)))?; subtle.set("exportKey", Func::from(Async(subtle_export_key)))?; subtle.set("generateKey", Func::from(Async(subtle_generate_key)))?; subtle.set("importKey", Func::from(Async(subtle_import_key)))?; subtle.set("sign", Func::from(Async(subtle_sign)))?; subtle.set("verify", Func::from(Async(subtle_verify)))?; subtle.set("wrapKey", Func::from(Async(subtle_wrap_key)))?; subtle.set("unwrapKey", Func::from(Async(subtle_unwrap_key)))?; crypto.set("subtle", subtle)?; globals.set("crypto", crypto)?; Ok(()) } pub struct CryptoModule; impl ModuleDef for CryptoModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("createHash")?; declare.declare("createHmac")?; declare.declare("Crc32")?; declare.declare("Crc32c")?; declare.declare("randomBytes")?; declare.declare("randomUUID")?; declare.declare("randomInt")?; declare.declare("randomFillSync")?; declare.declare("randomFill")?; declare.declare("getRandomValues")?; for algorithm in HashAlgorithm::iter() { declare.declare(algorithm.class_name())?; } declare.declare("crypto")?; declare.declare("webcrypto")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { for algorithm in HashAlgorithm::iter() { let class_name: &str = algorithm.class_name(); let algo_name = String::from(algorithm.as_str()); let ctor = Constructor::new_class::( ctx.clone(), move |ctx: Ctx<'js>, secret: Opt>| match secret.0 { Some(secret) => Hash::new_hmac(ctx, algo_name.clone(), secret), None => Hash::new(ctx, algo_name.clone()), }, )?; default.set(class_name, ctor)?; } let crypto: Object = ctx.globals().get("crypto")?; Class::::define(default)?; Class::::define(default)?; default.set("createHash", Func::from(Hash::new))?; default.set("createHmac", Func::from(Hmac::new))?; default.set("randomBytes", Func::from(get_random_bytes))?; default.set("randomInt", Func::from(get_random_int))?; default.set("randomUUID", Func::from(uuidv4))?; default.set("randomFillSync", Func::from(random_fill_sync))?; default.set("randomFill", Func::from(random_fill))?; default.set("getRandomValues", Func::from(get_random_values))?; default.set("crypto", crypto.clone())?; default.set("webcrypto", crypto)?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: CryptoModule) -> Self { ModuleInfo { name: "crypto", module: val, } } } ================================================ FILE: modules/llrt_crypto/src/provider/graviola.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! Graviola crypto provider - a high-performance crypto library using formally verified assembler. //! //! Supported: SHA256/384/512, HMAC, AES-GCM //! Not supported: Most other operations due to API limitations use graviola::{ aead::AesGcm, hashing::{hmac::Hmac, Hash, HashContext, Sha256, Sha384, Sha512}, }; use crate::hash::HashAlgorithm; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; use crate::subtle::EllipticCurve; pub struct GraviolaProvider; pub enum GraviolaDigest { Sha256(::Context), Sha384(::Context), Sha512(::Context), } impl SimpleDigest for GraviolaDigest { fn update(&mut self, data: &[u8]) { match self { GraviolaDigest::Sha256(h) => h.update(data), GraviolaDigest::Sha384(h) => h.update(data), GraviolaDigest::Sha512(h) => h.update(data), } } fn finalize(self) -> Vec { match self { GraviolaDigest::Sha256(h) => h.finish().as_ref().to_vec(), GraviolaDigest::Sha384(h) => h.finish().as_ref().to_vec(), GraviolaDigest::Sha512(h) => h.finish().as_ref().to_vec(), } } } pub enum GraviolaHmac { Sha256(Hmac), Sha384(Hmac), Sha512(Hmac), } impl HmacProvider for GraviolaHmac { fn update(&mut self, data: &[u8]) { match self { GraviolaHmac::Sha256(h) => h.update(data), GraviolaHmac::Sha384(h) => h.update(data), GraviolaHmac::Sha512(h) => h.update(data), } } fn finalize(self) -> Vec { match self { GraviolaHmac::Sha256(h) => h.finish().as_ref().to_vec(), GraviolaHmac::Sha384(h) => h.finish().as_ref().to_vec(), GraviolaHmac::Sha512(h) => h.finish().as_ref().to_vec(), } } } impl CryptoProvider for GraviolaProvider { type Digest = GraviolaDigest; type Hmac = GraviolaHmac; fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { HashAlgorithm::Sha256 => GraviolaDigest::Sha256(Sha256::new()), HashAlgorithm::Sha384 => GraviolaDigest::Sha384(Sha384::new()), HashAlgorithm::Sha512 => GraviolaDigest::Sha512(Sha512::new()), _ => panic!("Unsupported digest algorithm for Graviola"), } } fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { HashAlgorithm::Sha256 => GraviolaHmac::Sha256(Hmac::::new(key)), HashAlgorithm::Sha384 => GraviolaHmac::Sha384(Hmac::::new(key)), HashAlgorithm::Sha512 => GraviolaHmac::Sha512(Hmac::::new(key)), _ => panic!("Unsupported HMAC algorithm for Graviola"), } } fn ecdsa_sign( &self, _curve: EllipticCurve, _private_key_der: &[u8], _digest: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn ecdsa_verify( &self, _curve: EllipticCurve, _public_key_sec1: &[u8], _signature: &[u8], _digest: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn ed25519_sign(&self, _private_key_der: &[u8], _data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn ed25519_verify( &self, _public_key_bytes: &[u8], _signature: &[u8], _data: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pss_sign( &self, _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pss_verify( &self, _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], _salt_length: usize, _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pkcs1v15_sign( &self, _private_key_der: &[u8], _digest: &[u8], _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pkcs1v15_verify( &self, _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_oaep_encrypt( &self, _public_key_der: &[u8], _data: &[u8], _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_oaep_decrypt( &self, _private_key_der: &[u8], _data: &[u8], _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn ecdh_derive_bits( &self, _curve: EllipticCurve, _private_key_der: &[u8], _public_key_sec1: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn x25519_derive_bits( &self, _private_key: &[u8], _public_key: &[u8], ) -> Result, CryptoError> { // Graviola doesn't expose from_bytes for X25519 PrivateKey Err(CryptoError::UnsupportedAlgorithm) } fn aes_encrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { match mode { AesMode::Gcm { .. } => { let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData(None))?; if !matches!(key.len(), 16 | 32) { return Err(CryptoError::InvalidKey(None)); } let aead = AesGcm::new(key); let aad = additional_data.unwrap_or(&[]); let mut ciphertext = data.to_vec(); let mut tag = [0u8; 16]; aead.encrypt(&nonce, aad, &mut ciphertext, &mut tag); ciphertext.extend_from_slice(&tag); Ok(ciphertext) }, _ => Err(CryptoError::UnsupportedAlgorithm), } } fn aes_decrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { match mode { AesMode::Gcm { .. } => { let nonce: [u8; 12] = iv.try_into().map_err(|_| CryptoError::InvalidData(None))?; if !matches!(key.len(), 16 | 32) { return Err(CryptoError::InvalidKey(None)); } if data.len() < 16 { return Err(CryptoError::InvalidData(None)); } let aead = AesGcm::new(key); let aad = additional_data.unwrap_or(&[]); let (ciphertext, tag) = data.split_at(data.len() - 16); let tag: [u8; 16] = tag.try_into().unwrap(); let mut plaintext = ciphertext.to_vec(); aead.decrypt(&nonce, aad, &mut plaintext, &tag) .map_err(|_| CryptoError::DecryptionFailed(None))?; Ok(plaintext) }, _ => Err(CryptoError::UnsupportedAlgorithm), } } fn aes_kw_wrap(&self, _kek: &[u8], _key: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn aes_kw_unwrap(&self, _kek: &[u8], _wrapped_key: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn hkdf_derive_key( &self, _key: &[u8], _salt: &[u8], _info: &[u8], _length: usize, _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn pbkdf2_derive_key( &self, _password: &[u8], _salt: &[u8], _iterations: u32, _length: usize, _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError> { if !matches!(length_bits, 128 | 256) { return Err(CryptoError::InvalidLength); } Ok(crate::random_byte_array((length_bits / 8) as usize)) } fn generate_hmac_key( &self, hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { HashAlgorithm::Sha256 => 64, HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => 128, _ => return Err(CryptoError::UnsupportedAlgorithm), } } else { (length_bits / 8) as usize }; Ok(crate::random_byte_array(length_bytes)) } fn generate_ec_key(&self, _curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { // Graviola doesn't expose as_bytes for X25519 PrivateKey Err(CryptoError::UnsupportedAlgorithm) } fn generate_rsa_key( &self, _modulus_length: u32, _public_exponent: &[u8], ) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_public_key_pkcs1( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_private_key_pkcs1( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_public_key_spki( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_private_key_pkcs8( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_public_key_pkcs1(&self, _key_data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_public_key_spki(&self, _key_data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_private_key_pkcs8(&self, _key_data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_public_key_sec1( &self, _data: &[u8], _curve: EllipticCurve, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_public_key_spki(&self, _der: &[u8]) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_private_key_pkcs8( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_private_key_sec1( &self, _data: &[u8], _curve: EllipticCurve, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_public_key_sec1( &self, _key_data: &[u8], _curve: EllipticCurve, _is_private: bool, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_public_key_spki( &self, _key_data: &[u8], _curve: EllipticCurve, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_private_key_pkcs8( &self, _key_data: &[u8], _curve: EllipticCurve, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_public_key_raw( &self, _data: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_public_key_spki( &self, _der: &[u8], _expected_oid: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_private_key_pkcs8( &self, _der: &[u8], _expected_oid: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_public_key_raw( &self, _key_data: &[u8], _is_private: bool, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_public_key_spki( &self, _key_data: &[u8], _oid: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_private_key_pkcs8( &self, _key_data: &[u8], _oid: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_jwk( &self, _jwk: super::RsaJwkImport<'_>, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_jwk( &self, _key_data: &[u8], _is_private: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_jwk( &self, _jwk: super::EcJwkImport<'_>, _curve: EllipticCurve, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_jwk( &self, _key_data: &[u8], _curve: EllipticCurve, _is_private: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_jwk( &self, _jwk: super::OkpJwkImport<'_>, _is_ed25519: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_jwk( &self, _key_data: &[u8], _is_private: bool, _is_ed25519: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } } // Hybrid types for graviola-rust: Graviola for SHA256/384/512, RustCrypto for MD5/SHA1 #[cfg(feature = "crypto-graviola-rust")] pub enum GraviolaRustDigest { Graviola(GraviolaDigest), Rust(super::rust::RustDigest), } #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustDigest { pub fn new(algorithm: HashAlgorithm) -> Self { match algorithm { HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { Self::Graviola(GraviolaProvider.digest(algorithm)) }, _ => Self::Rust(super::rust::RustCryptoProvider.digest(algorithm)), } } } #[cfg(feature = "crypto-graviola-rust")] impl SimpleDigest for GraviolaRustDigest { fn update(&mut self, data: &[u8]) { match self { Self::Graviola(d) => d.update(data), Self::Rust(d) => d.update(data), } } fn finalize(self) -> Vec { match self { Self::Graviola(d) => d.finalize(), Self::Rust(d) => d.finalize(), } } } #[cfg(feature = "crypto-graviola-rust")] pub enum GraviolaRustHmac { Graviola(GraviolaHmac), Rust(super::rust::RustHmac), } #[cfg(feature = "crypto-graviola-rust")] impl GraviolaRustHmac { pub fn new(algorithm: HashAlgorithm, key: &[u8]) -> Self { match algorithm { HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 => { Self::Graviola(GraviolaProvider.hmac(algorithm, key)) }, _ => Self::Rust(super::rust::RustCryptoProvider.hmac(algorithm, key)), } } } #[cfg(feature = "crypto-graviola-rust")] impl HmacProvider for GraviolaRustHmac { fn update(&mut self, data: &[u8]) { match self { Self::Graviola(h) => h.update(data), Self::Rust(h) => h.update(data), } } fn finalize(self) -> Vec { match self { Self::Graviola(h) => h.finalize(), Self::Rust(h) => h.finalize(), } } } ================================================ FILE: modules/llrt_crypto/src/provider/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 // Ensure only one crypto provider is selected #[cfg(all(feature = "crypto-rust", feature = "crypto-openssl"))] compile_error!("Features `crypto-rust` and `crypto-openssl` are mutually exclusive"); #[cfg(all(feature = "crypto-rust", feature = "crypto-ring"))] compile_error!("Features `crypto-rust` and `crypto-ring` are mutually exclusive"); #[cfg(all(feature = "crypto-rust", feature = "crypto-graviola"))] compile_error!("Features `crypto-rust` and `crypto-graviola` are mutually exclusive"); #[cfg(all(feature = "crypto-ring", feature = "crypto-openssl"))] compile_error!("Features `crypto-ring` and `crypto-openssl` are mutually exclusive"); #[cfg(all(feature = "crypto-ring", feature = "crypto-graviola"))] compile_error!("Features `crypto-ring` and `crypto-graviola` are mutually exclusive"); #[cfg(all(feature = "crypto-openssl", feature = "crypto-graviola"))] compile_error!("Features `crypto-openssl` and `crypto-graviola` are mutually exclusive"); #[cfg(all(feature = "crypto-ring-rust", feature = "crypto-graviola-rust"))] compile_error!("Features `crypto-ring-rust` and `crypto-graviola-rust` are mutually exclusive"); #[cfg(any(feature = "crypto-graviola", feature = "crypto-graviola-rust"))] mod graviola; #[cfg(feature = "crypto-openssl")] mod openssl; #[cfg(any(feature = "crypto-ring", feature = "crypto-ring-rust"))] mod ring; #[cfg(feature = "_rustcrypto")] mod rust; use crate::hash::HashAlgorithm; use crate::subtle::EllipticCurve; #[derive(Debug)] #[allow(dead_code)] pub struct RsaImportResult { pub key_data: Vec, pub modulus_length: u32, pub public_exponent: Vec, pub is_private: bool, } #[derive(Debug)] #[allow(dead_code)] pub struct EcImportResult { pub key_data: Vec, pub is_private: bool, } #[derive(Debug)] #[allow(dead_code)] pub struct OkpImportResult { pub key_data: Vec, pub is_private: bool, } /// RSA JWK components for import (all values are raw bytes, not base64) #[derive(Debug)] #[allow(dead_code)] pub struct RsaJwkImport<'a> { pub n: &'a [u8], // modulus pub e: &'a [u8], // public exponent pub d: Option<&'a [u8]>, // private exponent pub p: Option<&'a [u8]>, // first prime pub q: Option<&'a [u8]>, // second prime pub dp: Option<&'a [u8]>, // first factor CRT exponent pub dq: Option<&'a [u8]>, // second factor CRT exponent pub qi: Option<&'a [u8]>, // first CRT coefficient } /// RSA JWK components for export #[derive(Debug)] #[allow(dead_code)] pub struct RsaJwkExport { pub n: Vec, pub e: Vec, pub d: Option>, pub p: Option>, pub q: Option>, pub dp: Option>, pub dq: Option>, pub qi: Option>, } /// EC JWK components for import (all values are raw bytes) #[derive(Debug)] #[allow(dead_code)] pub struct EcJwkImport<'a> { pub x: &'a [u8], pub y: &'a [u8], pub d: Option<&'a [u8]>, } /// EC JWK components for export #[derive(Debug)] #[allow(dead_code)] pub struct EcJwkExport { pub x: Vec, pub y: Vec, pub d: Option>, } /// OKP (Ed25519/X25519) JWK components for import #[derive(Debug)] #[allow(dead_code)] pub struct OkpJwkImport<'a> { pub x: &'a [u8], // public key pub d: Option<&'a [u8]>, // private key } /// OKP JWK components for export #[derive(Debug)] #[allow(dead_code)] pub struct OkpJwkExport { pub x: Vec, pub d: Option>, } pub trait SimpleDigest: Send { fn update(&mut self, data: &[u8]); fn finalize(self) -> Vec where Self: Sized; } #[derive(Debug, Clone, Copy)] #[allow(dead_code)] pub enum AesMode { Ctr { counter_length: u32 }, Cbc, Gcm { tag_length: u8 }, } #[allow(dead_code)] pub trait CryptoProvider { type Digest: SimpleDigest; type Hmac: HmacProvider; // Digest operations fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest; // HMAC operations fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac; // ECDSA operations fn ecdsa_sign( &self, curve: EllipticCurve, private_key_der: &[u8], digest: &[u8], ) -> Result, CryptoError>; fn ecdsa_verify( &self, curve: EllipticCurve, public_key_sec1: &[u8], signature: &[u8], digest: &[u8], ) -> Result; // EdDSA operations fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError>; fn ed25519_verify( &self, public_key_bytes: &[u8], signature: &[u8], data: &[u8], ) -> Result; // RSA operations fn rsa_pss_sign( &self, private_key_der: &[u8], digest: &[u8], salt_length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn rsa_pss_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], salt_length: usize, hash_alg: HashAlgorithm, ) -> Result; fn rsa_pkcs1v15_sign( &self, private_key_der: &[u8], digest: &[u8], hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn rsa_pkcs1v15_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], hash_alg: HashAlgorithm, ) -> Result; fn rsa_oaep_encrypt( &self, public_key_der: &[u8], data: &[u8], hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; fn rsa_oaep_decrypt( &self, private_key_der: &[u8], data: &[u8], hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError>; // ECDH operations fn ecdh_derive_bits( &self, curve: EllipticCurve, private_key_der: &[u8], public_key_sec1: &[u8], ) -> Result, CryptoError>; // X25519 operations fn x25519_derive_bits( &self, private_key: &[u8], public_key: &[u8], ) -> Result, CryptoError>; // AES operations fn aes_encrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError>; fn aes_decrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError>; // AES-KW operations fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError>; fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError>; // KDF operations fn hkdf_derive_key( &self, key: &[u8], salt: &[u8], info: &[u8], length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn pbkdf2_derive_key( &self, password: &[u8], salt: &[u8], iterations: u32, length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError>; fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError>; fn generate_hmac_key( &self, hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError>; fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError>; // (private, public) fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError>; fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError>; fn generate_rsa_key( &self, modulus_length: u32, public_exponent: &[u8], ) -> Result<(Vec, Vec), CryptoError>; // RSA key import from DER formats fn import_rsa_public_key_pkcs1(&self, der: &[u8]) -> Result; fn import_rsa_private_key_pkcs1(&self, der: &[u8]) -> Result; fn import_rsa_public_key_spki(&self, der: &[u8]) -> Result; fn import_rsa_private_key_pkcs8(&self, der: &[u8]) -> Result; // RSA key export to DER formats fn export_rsa_public_key_pkcs1(&self, key_data: &[u8]) -> Result, CryptoError>; fn export_rsa_public_key_spki(&self, key_data: &[u8]) -> Result, CryptoError>; fn export_rsa_private_key_pkcs8(&self, key_data: &[u8]) -> Result, CryptoError>; // EC key import from DER formats fn import_ec_public_key_sec1( &self, data: &[u8], curve: EllipticCurve, ) -> Result; fn import_ec_public_key_spki(&self, der: &[u8]) -> Result; fn import_ec_private_key_pkcs8(&self, der: &[u8]) -> Result; fn import_ec_private_key_sec1( &self, data: &[u8], curve: EllipticCurve, ) -> Result; // EC key export fn export_ec_public_key_sec1( &self, key_data: &[u8], curve: EllipticCurve, is_private: bool, ) -> Result, CryptoError>; fn export_ec_public_key_spki( &self, key_data: &[u8], curve: EllipticCurve, ) -> Result, CryptoError>; fn export_ec_private_key_pkcs8( &self, key_data: &[u8], curve: EllipticCurve, ) -> Result, CryptoError>; // OKP (Ed25519/X25519) key import fn import_okp_public_key_raw(&self, data: &[u8]) -> Result; fn import_okp_public_key_spki( &self, der: &[u8], expected_oid: &[u8], ) -> Result; fn import_okp_private_key_pkcs8( &self, der: &[u8], expected_oid: &[u8], ) -> Result; // OKP key export fn export_okp_public_key_raw( &self, key_data: &[u8], is_private: bool, ) -> Result, CryptoError>; fn export_okp_public_key_spki( &self, key_data: &[u8], oid: &[u8], ) -> Result, CryptoError>; fn export_okp_private_key_pkcs8( &self, key_data: &[u8], oid: &[u8], ) -> Result, CryptoError>; // JWK import/export fn import_rsa_jwk(&self, jwk: RsaJwkImport<'_>) -> Result; fn export_rsa_jwk( &self, key_data: &[u8], is_private: bool, ) -> Result; fn import_ec_jwk( &self, jwk: EcJwkImport<'_>, curve: EllipticCurve, ) -> Result; fn export_ec_jwk( &self, key_data: &[u8], curve: EllipticCurve, is_private: bool, ) -> Result; // OKP JWK import/export fn import_okp_jwk( &self, jwk: OkpJwkImport<'_>, is_ed25519: bool, ) -> Result; fn export_okp_jwk( &self, key_data: &[u8], is_private: bool, is_ed25519: bool, ) -> Result; } pub trait HmacProvider: Send { fn update(&mut self, data: &[u8]); fn finalize(self) -> Vec where Self: Sized; } #[derive(Debug)] #[allow(dead_code)] pub enum CryptoError { InvalidKey(Option>), InvalidData(Option>), InvalidSignature(Option>), InvalidLength, SigningFailed(Option>), VerificationFailed, OperationFailed(Option>), UnsupportedAlgorithm, DerivationFailed(Option>), EncryptionFailed(Option>), DecryptionFailed(Option>), } impl std::fmt::Display for CryptoError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CryptoError::InvalidKey(None) => write!(f, "Invalid key"), CryptoError::InvalidKey(Some(msg)) => write!(f, "Invalid key: {}", msg), CryptoError::InvalidData(None) => write!(f, "Invalid data"), CryptoError::InvalidData(Some(msg)) => write!(f, "Invalid data: {}", msg), CryptoError::InvalidSignature(None) => write!(f, "Invalid signature"), CryptoError::InvalidSignature(Some(msg)) => write!(f, "Invalid signature: {}", msg), CryptoError::InvalidLength => write!(f, "Invalid length"), CryptoError::SigningFailed(None) => write!(f, "Signing failed"), CryptoError::SigningFailed(Some(msg)) => write!(f, "Signing failed: {}", msg), CryptoError::VerificationFailed => write!(f, "Verification failed"), CryptoError::OperationFailed(None) => write!(f, "Operation failed"), CryptoError::OperationFailed(Some(msg)) => write!(f, "Operation failed: {}", msg), CryptoError::UnsupportedAlgorithm => write!(f, "Unsupported algorithm"), CryptoError::DerivationFailed(None) => write!(f, "Derivation failed"), CryptoError::DerivationFailed(Some(msg)) => write!(f, "Derivation failed: {}", msg), CryptoError::EncryptionFailed(None) => write!(f, "Encryption failed"), CryptoError::EncryptionFailed(Some(msg)) => write!(f, "Encryption failed: {}", msg), CryptoError::DecryptionFailed(None) => write!(f, "Decryption failed"), CryptoError::DecryptionFailed(Some(msg)) => write!(f, "Decryption failed: {}", msg), } } } impl std::error::Error for CryptoError {} #[cfg(feature = "crypto-openssl")] pub type DefaultProvider = openssl::OpenSslProvider; #[cfg(feature = "crypto-rust")] pub type DefaultProvider = rust::RustCryptoProvider; #[cfg(feature = "crypto-ring")] pub type DefaultProvider = ring::RingProvider; #[cfg(feature = "crypto-ring-rust")] pub type DefaultProvider = RingRustProvider; #[cfg(all(feature = "crypto-graviola", not(feature = "crypto-graviola-rust")))] pub type DefaultProvider = graviola::GraviolaProvider; #[cfg(feature = "crypto-graviola-rust")] pub type DefaultProvider = GraviolaRustProvider; // Macro to generate hybrid providers that delegate to RustCrypto #[cfg(any(feature = "crypto-ring-rust", feature = "crypto-graviola-rust"))] macro_rules! impl_hybrid_provider { ($name:ident, $digest:ty, $hmac:ty, $digest_fn:expr, $hmac_fn:expr, $aes_encrypt:expr, $aes_decrypt:expr) => { pub struct $name; impl CryptoProvider for $name { type Digest = $digest; type Hmac = $hmac; fn digest(&self, alg: HashAlgorithm) -> Self::Digest { $digest_fn(alg) } fn hmac(&self, alg: HashAlgorithm, key: &[u8]) -> Self::Hmac { $hmac_fn(alg, key) } fn ecdsa_sign( &self, c: EllipticCurve, k: &[u8], d: &[u8], ) -> Result, CryptoError> { rust::RustCryptoProvider.ecdsa_sign(c, k, d) } fn ecdsa_verify( &self, c: EllipticCurve, k: &[u8], s: &[u8], d: &[u8], ) -> Result { rust::RustCryptoProvider.ecdsa_verify(c, k, s, d) } fn ed25519_sign(&self, k: &[u8], d: &[u8]) -> Result, CryptoError> { rust::RustCryptoProvider.ed25519_sign(k, d) } fn ed25519_verify(&self, k: &[u8], s: &[u8], d: &[u8]) -> Result { rust::RustCryptoProvider.ed25519_verify(k, s, d) } fn rsa_pss_sign( &self, k: &[u8], d: &[u8], s: usize, a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pss_sign(k, d, s, a) } fn rsa_pss_verify( &self, k: &[u8], s: &[u8], d: &[u8], sl: usize, a: HashAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pss_verify(k, s, d, sl, a) } fn rsa_pkcs1v15_sign( &self, k: &[u8], d: &[u8], a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_pkcs1v15_sign(k, d, a) } fn rsa_pkcs1v15_verify( &self, k: &[u8], s: &[u8], d: &[u8], a: HashAlgorithm, ) -> Result { rust::RustCryptoProvider.rsa_pkcs1v15_verify(k, s, d, a) } fn rsa_oaep_encrypt( &self, k: &[u8], d: &[u8], a: HashAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_encrypt(k, d, a, l) } fn rsa_oaep_decrypt( &self, k: &[u8], d: &[u8], a: HashAlgorithm, l: Option<&[u8]>, ) -> Result, CryptoError> { rust::RustCryptoProvider.rsa_oaep_decrypt(k, d, a, l) } fn ecdh_derive_bits( &self, c: EllipticCurve, pk: &[u8], pubk: &[u8], ) -> Result, CryptoError> { rust::RustCryptoProvider.ecdh_derive_bits(c, pk, pubk) } fn x25519_derive_bits(&self, pk: &[u8], pubk: &[u8]) -> Result, CryptoError> { rust::RustCryptoProvider.x25519_derive_bits(pk, pubk) } fn aes_encrypt( &self, m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>, ) -> Result, CryptoError> { $aes_encrypt(m, k, iv, d, aad) } fn aes_decrypt( &self, m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>, ) -> Result, CryptoError> { $aes_decrypt(m, k, iv, d, aad) } fn aes_kw_wrap(&self, kek: &[u8], k: &[u8]) -> Result, CryptoError> { rust::RustCryptoProvider.aes_kw_wrap(kek, k) } fn aes_kw_unwrap(&self, kek: &[u8], w: &[u8]) -> Result, CryptoError> { rust::RustCryptoProvider.aes_kw_unwrap(kek, w) } fn hkdf_derive_key( &self, k: &[u8], s: &[u8], i: &[u8], l: usize, a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.hkdf_derive_key(k, s, i, l, a) } fn pbkdf2_derive_key( &self, p: &[u8], s: &[u8], i: u32, l: usize, a: HashAlgorithm, ) -> Result, CryptoError> { rust::RustCryptoProvider.pbkdf2_derive_key(p, s, i, l, a) } fn generate_aes_key(&self, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_aes_key(b) } fn generate_hmac_key(&self, a: HashAlgorithm, b: u16) -> Result, CryptoError> { rust::RustCryptoProvider.generate_hmac_key(a, b) } fn generate_ec_key(&self, c: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { rust::RustCryptoProvider.generate_ec_key(c) } fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { rust::RustCryptoProvider.generate_ed25519_key() } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { rust::RustCryptoProvider.generate_x25519_key() } fn generate_rsa_key( &self, b: u32, e: &[u8], ) -> Result<(Vec, Vec), CryptoError> { rust::RustCryptoProvider.generate_rsa_key(b, e) } fn import_rsa_public_key_pkcs1( &self, d: &[u8], ) -> Result { rust::RustCryptoProvider.import_rsa_public_key_pkcs1(d) } fn import_rsa_private_key_pkcs1( &self, d: &[u8], ) -> Result { rust::RustCryptoProvider.import_rsa_private_key_pkcs1(d) } fn import_rsa_public_key_spki(&self, d: &[u8]) -> Result { rust::RustCryptoProvider.import_rsa_public_key_spki(d) } fn import_rsa_private_key_pkcs8( &self, d: &[u8], ) -> Result { rust::RustCryptoProvider.import_rsa_private_key_pkcs8(d) } fn export_rsa_public_key_pkcs1(&self, d: &[u8]) -> Result, CryptoError> { rust::RustCryptoProvider.export_rsa_public_key_pkcs1(d) } fn export_rsa_public_key_spki(&self, d: &[u8]) -> Result, CryptoError> { rust::RustCryptoProvider.export_rsa_public_key_spki(d) } fn export_rsa_private_key_pkcs8(&self, d: &[u8]) -> Result, CryptoError> { rust::RustCryptoProvider.export_rsa_private_key_pkcs8(d) } fn import_ec_public_key_sec1( &self, d: &[u8], c: EllipticCurve, ) -> Result { rust::RustCryptoProvider.import_ec_public_key_sec1(d, c) } fn import_ec_public_key_spki(&self, d: &[u8]) -> Result { rust::RustCryptoProvider.import_ec_public_key_spki(d) } fn import_ec_private_key_pkcs8(&self, d: &[u8]) -> Result { rust::RustCryptoProvider.import_ec_private_key_pkcs8(d) } fn import_ec_private_key_sec1( &self, d: &[u8], c: EllipticCurve, ) -> Result { rust::RustCryptoProvider.import_ec_private_key_sec1(d, c) } fn export_ec_public_key_sec1( &self, d: &[u8], c: EllipticCurve, p: bool, ) -> Result, CryptoError> { rust::RustCryptoProvider.export_ec_public_key_sec1(d, c, p) } fn export_ec_public_key_spki( &self, d: &[u8], c: EllipticCurve, ) -> Result, CryptoError> { rust::RustCryptoProvider.export_ec_public_key_spki(d, c) } fn export_ec_private_key_pkcs8( &self, d: &[u8], c: EllipticCurve, ) -> Result, CryptoError> { rust::RustCryptoProvider.export_ec_private_key_pkcs8(d, c) } fn import_okp_public_key_raw(&self, d: &[u8]) -> Result { rust::RustCryptoProvider.import_okp_public_key_raw(d) } fn import_okp_public_key_spki( &self, d: &[u8], o: &[u8], ) -> Result { rust::RustCryptoProvider.import_okp_public_key_spki(d, o) } fn import_okp_private_key_pkcs8( &self, d: &[u8], o: &[u8], ) -> Result { rust::RustCryptoProvider.import_okp_private_key_pkcs8(d, o) } fn export_okp_public_key_raw(&self, d: &[u8], p: bool) -> Result, CryptoError> { rust::RustCryptoProvider.export_okp_public_key_raw(d, p) } fn export_okp_public_key_spki( &self, d: &[u8], o: &[u8], ) -> Result, CryptoError> { rust::RustCryptoProvider.export_okp_public_key_spki(d, o) } fn export_okp_private_key_pkcs8( &self, d: &[u8], o: &[u8], ) -> Result, CryptoError> { rust::RustCryptoProvider.export_okp_private_key_pkcs8(d, o) } fn import_rsa_jwk(&self, j: RsaJwkImport<'_>) -> Result { rust::RustCryptoProvider.import_rsa_jwk(j) } fn export_rsa_jwk(&self, d: &[u8], p: bool) -> Result { rust::RustCryptoProvider.export_rsa_jwk(d, p) } fn import_ec_jwk( &self, j: EcJwkImport<'_>, c: EllipticCurve, ) -> Result { rust::RustCryptoProvider.import_ec_jwk(j, c) } fn export_ec_jwk( &self, d: &[u8], c: EllipticCurve, p: bool, ) -> Result { rust::RustCryptoProvider.export_ec_jwk(d, c, p) } fn import_okp_jwk( &self, j: OkpJwkImport<'_>, is_ed25519: bool, ) -> Result { rust::RustCryptoProvider.import_okp_jwk(j, is_ed25519) } fn export_okp_jwk( &self, d: &[u8], is_private: bool, is_ed25519: bool, ) -> Result { rust::RustCryptoProvider.export_okp_jwk(d, is_private, is_ed25519) } } }; } #[cfg(feature = "crypto-ring-rust")] impl_hybrid_provider!( RingRustProvider, ring::RingDigestType, ring::RingHmacType, |a| ring::RingProvider.digest(a), |a, k| ring::RingProvider.hmac(a, k), |m, k, iv, d, aad| rust::RustCryptoProvider.aes_encrypt(m, k, iv, d, aad), |m, k, iv, d, aad| rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad) ); #[cfg(feature = "crypto-graviola-rust")] fn graviola_aes_supported() -> bool { #[cfg(target_arch = "aarch64")] { std::arch::is_aarch64_feature_detected!("aes") } #[cfg(target_arch = "x86_64")] { std::arch::is_x86_feature_detected!("aes") } #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] { false } } #[cfg(feature = "crypto-graviola-rust")] impl_hybrid_provider!( GraviolaRustProvider, graviola::GraviolaRustDigest, graviola::GraviolaRustHmac, graviola::GraviolaRustDigest::new, graviola::GraviolaRustHmac::new, |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| { if graviola_aes_supported() && matches!(m, AesMode::Gcm { .. }) && matches!(k.len(), 16 | 32) { graviola::GraviolaProvider.aes_encrypt(m, k, iv, d, aad) } else { rust::RustCryptoProvider.aes_encrypt(m, k, iv, d, aad) } }, |m: AesMode, k: &[u8], iv: &[u8], d: &[u8], aad: Option<&[u8]>| { if graviola_aes_supported() && matches!(m, AesMode::Gcm { .. }) && matches!(k.len(), 16 | 32) { graviola::GraviolaProvider.aes_decrypt(m, k, iv, d, aad) } else { rust::RustCryptoProvider.aes_decrypt(m, k, iv, d, aad) } } ); #[cfg(test)] mod tests { use super::*; fn provider() -> impl CryptoProvider { #[cfg(feature = "crypto-rust")] return rust::RustCryptoProvider; #[cfg(feature = "crypto-ring-rust")] return RingRustProvider; #[cfg(feature = "crypto-graviola-rust")] return GraviolaRustProvider; #[cfg(feature = "crypto-openssl")] return openssl::OpenSslProvider; #[cfg(feature = "crypto-ring")] return ring::RingProvider; #[cfg(all(feature = "crypto-graviola", not(feature = "crypto-graviola-rust")))] return graviola::GraviolaProvider; } fn to_hex(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{:02x}", b)).collect() } // SHA digest tests #[test] fn test_sha256_digest() { let p = provider(); let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 32); assert_eq!( to_hex(&result), "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" ); } #[test] fn test_sha384_digest() { let p = provider(); let mut digest = p.digest(HashAlgorithm::Sha384); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 48); } #[test] fn test_sha512_digest() { let p = provider(); let mut digest = p.digest(HashAlgorithm::Sha512); digest.update(b"hello world"); let result = digest.finalize(); assert_eq!(result.len(), 64); } // HMAC tests #[test] fn test_hmac_sha256() { let p = provider(); let key = b"secret key"; let mut hmac = p.hmac(HashAlgorithm::Sha256, key); hmac.update(b"hello world"); let result = hmac.finalize(); assert_eq!(result.len(), 32); } // AES-GCM tests - only for providers that support AES #[cfg(any( feature = "crypto-rust", feature = "crypto-openssl", feature = "crypto-ring-rust", feature = "crypto-graviola-rust" ))] #[test] fn test_aes_gcm_128_roundtrip() { let p = provider(); let key = [0u8; 16]; let iv = [0u8; 12]; let plaintext = b"hello world"; let aad = b"additional data"; let ciphertext = p .aes_encrypt( AesMode::Gcm { tag_length: 128 }, &key, &iv, plaintext, Some(aad), ) .unwrap(); assert_eq!(ciphertext.len(), plaintext.len() + 16); // plaintext + tag let decrypted = p .aes_decrypt( AesMode::Gcm { tag_length: 128 }, &key, &iv, &ciphertext, Some(aad), ) .unwrap(); assert_eq!(decrypted, plaintext); } #[cfg(any( feature = "crypto-rust", feature = "crypto-openssl", feature = "crypto-ring-rust", feature = "crypto-graviola-rust" ))] #[test] fn test_aes_gcm_256_roundtrip() { let p = provider(); let key = [0u8; 32]; let iv = [0u8; 12]; let plaintext = b"hello world"; let ciphertext = p .aes_encrypt(AesMode::Gcm { tag_length: 128 }, &key, &iv, plaintext, None) .unwrap(); let decrypted = p .aes_decrypt( AesMode::Gcm { tag_length: 128 }, &key, &iv, &ciphertext, None, ) .unwrap(); assert_eq!(decrypted, plaintext); } #[cfg(any( feature = "crypto-rust", feature = "crypto-openssl", feature = "crypto-ring-rust", feature = "crypto-graviola-rust" ))] #[test] fn test_aes_gcm_wrong_key_fails() { let p = provider(); let key = [0u8; 16]; let wrong_key = [1u8; 16]; let iv = [0u8; 12]; let plaintext = b"hello world"; let ciphertext = p .aes_encrypt(AesMode::Gcm { tag_length: 128 }, &key, &iv, plaintext, None) .unwrap(); let result = p.aes_decrypt( AesMode::Gcm { tag_length: 128 }, &wrong_key, &iv, &ciphertext, None, ); assert!(result.is_err()); } // Key generation tests - only for providers that support key generation #[cfg(any( feature = "crypto-rust", feature = "crypto-openssl", feature = "crypto-ring-rust", feature = "crypto-graviola-rust" ))] #[test] fn test_generate_aes_key_128() { let p = provider(); let key = p.generate_aes_key(128).unwrap(); assert_eq!(key.len(), 16); } #[cfg(any( feature = "crypto-rust", feature = "crypto-openssl", feature = "crypto-ring-rust", feature = "crypto-graviola-rust" ))] #[test] fn test_generate_aes_key_256() { let p = provider(); let key = p.generate_aes_key(256).unwrap(); assert_eq!(key.len(), 32); } #[cfg(any( feature = "crypto-rust", feature = "crypto-openssl", feature = "crypto-ring-rust", feature = "crypto-graviola-rust" ))] #[test] fn test_generate_hmac_key() { let p = provider(); let key = p.generate_hmac_key(HashAlgorithm::Sha256, 256).unwrap(); assert_eq!(key.len(), 32); } // Tests that require full crypto support #[cfg(any( feature = "crypto-rust", feature = "crypto-openssl", feature = "crypto-ring-rust", feature = "crypto-graviola-rust" ))] mod full_provider_tests { use super::*; #[test] fn test_aes_cbc_roundtrip() { let p = provider(); let key = [0u8; 16]; let iv = [0u8; 16]; let plaintext = b"hello world12345"; // 16 bytes for block alignment let ciphertext = p .aes_encrypt(AesMode::Cbc, &key, &iv, plaintext, None) .unwrap(); let decrypted = p .aes_decrypt(AesMode::Cbc, &key, &iv, &ciphertext, None) .unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_aes_ctr_roundtrip() { let p = provider(); let key = [0u8; 16]; let iv = [0u8; 16]; let plaintext = b"hello world"; let ciphertext = p .aes_encrypt( AesMode::Ctr { counter_length: 64 }, &key, &iv, plaintext, None, ) .unwrap(); let decrypted = p .aes_decrypt( AesMode::Ctr { counter_length: 64 }, &key, &iv, &ciphertext, None, ) .unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_aes_kw_roundtrip() { let p = provider(); let kek = [0u8; 16]; let key_to_wrap = [1u8; 16]; let wrapped = p.aes_kw_wrap(&kek, &key_to_wrap).unwrap(); let unwrapped = p.aes_kw_unwrap(&kek, &wrapped).unwrap(); assert_eq!(unwrapped, key_to_wrap); } #[test] fn test_hkdf_derive() { let p = provider(); let ikm = b"input key material"; let salt = b"salt"; let info = b"info"; let derived = p .hkdf_derive_key(ikm, salt, info, 32, HashAlgorithm::Sha256) .unwrap(); assert_eq!(derived.len(), 32); } #[test] fn test_pbkdf2_derive() { let p = provider(); let password = b"password"; let salt = b"salt"; let derived = p .pbkdf2_derive_key(password, salt, 1000, 32, HashAlgorithm::Sha256) .unwrap(); assert_eq!(derived.len(), 32); } #[test] fn test_ec_p256_sign_verify() { let p = provider(); let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P256).unwrap(); // Create a digest to sign let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p .ecdsa_sign(EllipticCurve::P256, &private_key, &hash) .unwrap(); let valid = p .ecdsa_verify(EllipticCurve::P256, &public_key, &signature, &hash) .unwrap(); assert!(valid); } #[test] fn test_ec_p384_sign_verify() { let p = provider(); let (private_key, public_key) = p.generate_ec_key(EllipticCurve::P384).unwrap(); let mut digest = p.digest(HashAlgorithm::Sha384); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p .ecdsa_sign(EllipticCurve::P384, &private_key, &hash) .unwrap(); let valid = p .ecdsa_verify(EllipticCurve::P384, &public_key, &signature, &hash) .unwrap(); assert!(valid); } #[test] fn test_ed25519_sign_verify() { let p = provider(); let (private_key, public_key) = p.generate_ed25519_key().unwrap(); let message = b"message to sign"; let signature = p.ed25519_sign(&private_key, message).unwrap(); let valid = p.ed25519_verify(&public_key, &signature, message).unwrap(); assert!(valid); } #[test] fn test_x25519_key_exchange() { let p = provider(); let (alice_private, alice_public) = p.generate_x25519_key().unwrap(); let (bob_private, bob_public) = p.generate_x25519_key().unwrap(); let alice_shared = p.x25519_derive_bits(&alice_private, &bob_public).unwrap(); let bob_shared = p.x25519_derive_bits(&bob_private, &alice_public).unwrap(); assert_eq!(alice_shared, bob_shared); assert_eq!(alice_shared.len(), 32); } #[test] fn test_ecdh_p256_key_exchange() { let p = provider(); let (alice_private, alice_public) = p.generate_ec_key(EllipticCurve::P256).unwrap(); let (bob_private, bob_public) = p.generate_ec_key(EllipticCurve::P256).unwrap(); let alice_shared = p .ecdh_derive_bits(EllipticCurve::P256, &alice_private, &bob_public) .unwrap(); let bob_shared = p .ecdh_derive_bits(EllipticCurve::P256, &bob_private, &alice_public) .unwrap(); assert_eq!(alice_shared, bob_shared); } #[test] fn test_rsa_pss_sign_verify() { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p .rsa_pss_sign(&private_key, &hash, 32, HashAlgorithm::Sha256) .unwrap(); let valid = p .rsa_pss_verify(&public_key, &signature, &hash, 32, HashAlgorithm::Sha256) .unwrap(); assert!(valid); } #[test] fn test_rsa_pkcs1v15_sign_verify() { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); let mut digest = p.digest(HashAlgorithm::Sha256); digest.update(b"message to sign"); let hash = digest.finalize(); let signature = p .rsa_pkcs1v15_sign(&private_key, &hash, HashAlgorithm::Sha256) .unwrap(); let valid = p .rsa_pkcs1v15_verify(&public_key, &signature, &hash, HashAlgorithm::Sha256) .unwrap(); assert!(valid); } #[test] fn test_rsa_oaep_encrypt_decrypt() { let p = provider(); let (private_key, public_key) = p.generate_rsa_key(2048, &[1, 0, 1]).unwrap(); let plaintext = b"secret message"; let ciphertext = p .rsa_oaep_encrypt(&public_key, plaintext, HashAlgorithm::Sha256, None) .unwrap(); let decrypted = p .rsa_oaep_decrypt(&private_key, &ciphertext, HashAlgorithm::Sha256, None) .unwrap(); assert_eq!(decrypted, plaintext); } } } ================================================ FILE: modules/llrt_crypto/src/provider/openssl.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! OpenSSL crypto provider - uses OpenSSL for cryptographic operations. use openssl::bn::BigNum; use openssl::derive::Deriver; use openssl::ec::{EcGroup, EcKey}; use openssl::ecdsa::EcdsaSig; use openssl::hash::{Hasher, MessageDigest}; use openssl::md::Md; use openssl::nid::Nid; use openssl::pkey::{Id, PKey}; use openssl::pkey_ctx::PkeyCtx; use openssl::rand::rand_bytes; use openssl::rsa::{Padding, Rsa}; use openssl::sign::{Signer, Verifier}; use openssl::symm::{self, Cipher}; use crate::hash::HashAlgorithm; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; use crate::subtle::EllipticCurve; pub struct OpenSslProvider; pub enum OpenSslDigest { Md5(Hasher), Sha1(Hasher), Sha256(Hasher), Sha384(Hasher), Sha512(Hasher), } impl SimpleDigest for OpenSslDigest { fn update(&mut self, data: &[u8]) { match self { OpenSslDigest::Md5(h) | OpenSslDigest::Sha1(h) | OpenSslDigest::Sha256(h) | OpenSslDigest::Sha384(h) | OpenSslDigest::Sha512(h) => { let _ = h.update(data); }, } } fn finalize(mut self) -> Vec { match self { OpenSslDigest::Md5(ref mut h) | OpenSslDigest::Sha1(ref mut h) | OpenSslDigest::Sha256(ref mut h) | OpenSslDigest::Sha384(ref mut h) | OpenSslDigest::Sha512(ref mut h) => { h.finish().map(|d| d.to_vec()).unwrap_or_default() }, } } } pub struct OpenSslHmac { signer: Signer<'static>, } impl HmacProvider for OpenSslHmac { fn update(&mut self, data: &[u8]) { let _ = self.signer.update(data); } fn finalize(self) -> Vec { self.signer.sign_to_vec().unwrap_or_default() } } fn get_message_digest(alg: HashAlgorithm) -> MessageDigest { match alg { HashAlgorithm::Md5 => MessageDigest::md5(), HashAlgorithm::Sha1 => MessageDigest::sha1(), HashAlgorithm::Sha256 => MessageDigest::sha256(), HashAlgorithm::Sha384 => MessageDigest::sha384(), HashAlgorithm::Sha512 => MessageDigest::sha512(), } } fn get_md(alg: HashAlgorithm) -> &'static openssl::md::MdRef { match alg { HashAlgorithm::Md5 => Md::md5(), HashAlgorithm::Sha1 => Md::sha1(), HashAlgorithm::Sha256 => Md::sha256(), HashAlgorithm::Sha384 => Md::sha384(), HashAlgorithm::Sha512 => Md::sha512(), } } fn curve_to_nid(curve: EllipticCurve) -> Nid { match curve { EllipticCurve::P256 => Nid::X9_62_PRIME256V1, EllipticCurve::P384 => Nid::SECP384R1, EllipticCurve::P521 => Nid::SECP521R1, } } fn get_ec_group(curve: EllipticCurve) -> Result { let nid = curve_to_nid(curve); EcGroup::from_curve_name(nid) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into()))) } impl CryptoProvider for OpenSslProvider { type Digest = OpenSslDigest; type Hmac = OpenSslHmac; fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { let md = get_message_digest(algorithm); let hasher = Hasher::new(md).expect("Failed to create hasher"); match algorithm { HashAlgorithm::Md5 => OpenSslDigest::Md5(hasher), HashAlgorithm::Sha1 => OpenSslDigest::Sha1(hasher), HashAlgorithm::Sha256 => OpenSslDigest::Sha256(hasher), HashAlgorithm::Sha384 => OpenSslDigest::Sha384(hasher), HashAlgorithm::Sha512 => OpenSslDigest::Sha512(hasher), } } fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { let md = get_message_digest(algorithm); let pkey = PKey::hmac(key).expect("Failed to create HMAC key"); let signer = unsafe { std::mem::transmute::, Signer<'static>>( Signer::new(md, &pkey).expect("Failed to create signer"), ) }; OpenSslHmac { signer } } fn ecdsa_sign( &self, curve: EllipticCurve, private_key_der: &[u8], digest: &[u8], ) -> Result, CryptoError> { let group = get_ec_group(curve)?; let ec_key = EcKey::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let sig = EcdsaSig::sign(digest, &ec_key) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; let r = sig.r().to_vec(); let s = sig.s().to_vec(); let coord_len = (group.degree() as usize).div_ceil(8); let mut result = vec![0u8; coord_len * 2]; result[coord_len - r.len()..coord_len].copy_from_slice(&r); result[coord_len * 2 - s.len()..].copy_from_slice(&s); Ok(result) } fn ecdsa_verify( &self, curve: EllipticCurve, public_key_sec1: &[u8], signature: &[u8], digest: &[u8], ) -> Result { let group = get_ec_group(curve)?; let ec_key = EcKey::public_key_from_der(public_key_sec1).or_else(|_| { let point = openssl::ec::EcPoint::from_bytes( &group, public_key_sec1, &mut openssl::bn::BigNumContext::new().unwrap(), ) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; EcKey::from_public_key(&group, &point) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) })?; let coord_len = signature.len() / 2; let r = BigNum::from_slice(&signature[..coord_len]) .map_err(|e| CryptoError::InvalidSignature(Some(e.to_string().into())))?; let s = BigNum::from_slice(&signature[coord_len..]) .map_err(|e| CryptoError::InvalidSignature(Some(e.to_string().into())))?; let sig = EcdsaSig::from_private_components(r, s) .map_err(|e| CryptoError::InvalidSignature(Some(e.to_string().into())))?; Ok(sig.verify(digest, &ec_key).unwrap_or(false)) } fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError> { let pkey = PKey::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut signer = Signer::new_without_digest(&pkey) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .sign_oneshot_to_vec(data) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into()))) } fn ed25519_verify( &self, public_key_bytes: &[u8], signature: &[u8], data: &[u8], ) -> Result { let pkey = PKey::public_key_from_raw_bytes(public_key_bytes, Id::ED25519) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut verifier = Verifier::new_without_digest(&pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok(verifier.verify_oneshot(signature, data).unwrap_or(false)) } fn rsa_pss_sign( &self, private_key_der: &[u8], digest: &[u8], salt_length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let md = get_message_digest(hash_alg); let mut signer = Signer::new(md, &pkey) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .set_rsa_padding(Padding::PKCS1_PSS) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::custom(salt_length as i32)) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .set_rsa_mgf1_md(md) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .update(digest) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .sign_to_vec() .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into()))) } fn rsa_pss_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], salt_length: usize, hash_alg: HashAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let md = get_message_digest(hash_alg); let mut verifier = Verifier::new(md, &pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; verifier .set_rsa_padding(Padding::PKCS1_PSS) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; verifier .set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::custom(salt_length as i32)) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; verifier .set_rsa_mgf1_md(md) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; verifier .update(digest) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok(verifier.verify(signature).unwrap_or(false)) } fn rsa_pkcs1v15_sign( &self, private_key_der: &[u8], digest: &[u8], hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let md = get_message_digest(hash_alg); let mut signer = Signer::new(md, &pkey) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .set_rsa_padding(Padding::PKCS1) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .update(digest) .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into())))?; signer .sign_to_vec() .map_err(|e| CryptoError::SigningFailed(Some(e.to_string().into()))) } fn rsa_pkcs1v15_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], hash_alg: HashAlgorithm, ) -> Result { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let md = get_message_digest(hash_alg); let mut verifier = Verifier::new(md, &pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; verifier .set_rsa_padding(Padding::PKCS1) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; verifier .update(digest) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok(verifier.verify(signature).unwrap_or(false)) } fn rsa_oaep_encrypt( &self, public_key_der: &[u8], data: &[u8], hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::public_key_from_der(public_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut ctx = PkeyCtx::new(&pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.encrypt_init() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.set_rsa_padding(Padding::PKCS1_OAEP) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.set_rsa_oaep_md(get_md(hash_alg)) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.set_rsa_mgf1_md(get_md(hash_alg)) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; if let Some(lbl) = label { ctx.set_rsa_oaep_label(lbl) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; } let mut out = vec![0u8; pkey.size()]; let len = ctx .encrypt(data, Some(&mut out)) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; out.truncate(len); Ok(out) } fn rsa_oaep_decrypt( &self, private_key_der: &[u8], data: &[u8], hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut ctx = PkeyCtx::new(&pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.decrypt_init() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.set_rsa_padding(Padding::PKCS1_OAEP) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.set_rsa_oaep_md(get_md(hash_alg)) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; ctx.set_rsa_mgf1_md(get_md(hash_alg)) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; if let Some(lbl) = label { ctx.set_rsa_oaep_label(lbl) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; } let mut out = vec![0u8; pkey.size()]; let len = ctx .decrypt(data, Some(&mut out)) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; out.truncate(len); Ok(out) } fn ecdh_derive_bits( &self, curve: EllipticCurve, private_key_der: &[u8], public_key_sec1: &[u8], ) -> Result, CryptoError> { let group = get_ec_group(curve)?; let private_ec = EcKey::private_key_from_der(private_key_der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let private_pkey = PKey::from_ec_key(private_ec) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let public_ec = EcKey::public_key_from_der(public_key_sec1).or_else(|_| { let point = openssl::ec::EcPoint::from_bytes( &group, public_key_sec1, &mut openssl::bn::BigNumContext::new().unwrap(), ) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; EcKey::from_public_key(&group, &point) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) })?; let public_pkey = PKey::from_ec_key(public_ec) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut deriver = Deriver::new(&private_pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; deriver .set_peer(&public_pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; deriver .derive_to_vec() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into()))) } fn x25519_derive_bits( &self, private_key: &[u8], public_key: &[u8], ) -> Result, CryptoError> { let private_pkey = PKey::private_key_from_raw_bytes(private_key, Id::X25519) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let public_pkey = PKey::public_key_from_raw_bytes(public_key, Id::X25519) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut deriver = Deriver::new(&private_pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; deriver .set_peer(&public_pkey) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; deriver .derive_to_vec() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into()))) } fn aes_encrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { match mode { AesMode::Cbc => { let cipher = match key.len() { 16 => Cipher::aes_128_cbc(), 24 => Cipher::aes_192_cbc(), 32 => Cipher::aes_256_cbc(), _ => { return Err(CryptoError::InvalidKey(Some( "Invalid AES key length".into(), ))) }, }; symm::encrypt(cipher, key, Some(iv), data) .map_err(|e| CryptoError::EncryptionFailed(Some(e.to_string().into()))) }, AesMode::Ctr { .. } => { let cipher = match key.len() { 16 => Cipher::aes_128_ctr(), 24 => Cipher::aes_192_ctr(), 32 => Cipher::aes_256_ctr(), _ => { return Err(CryptoError::InvalidKey(Some( "Invalid AES key length".into(), ))) }, }; symm::encrypt(cipher, key, Some(iv), data) .map_err(|e| CryptoError::EncryptionFailed(Some(e.to_string().into()))) }, AesMode::Gcm { tag_length } => { let cipher = match key.len() { 16 => Cipher::aes_128_gcm(), 24 => Cipher::aes_192_gcm(), 32 => Cipher::aes_256_gcm(), _ => { return Err(CryptoError::InvalidKey(Some( "Invalid AES key length".into(), ))) }, }; let tag_len = (tag_length / 8) as usize; let mut tag = vec![0u8; tag_len]; let ciphertext = symm::encrypt_aead( cipher, key, Some(iv), additional_data.unwrap_or(&[]), data, &mut tag, ) .map_err(|e| CryptoError::EncryptionFailed(Some(e.to_string().into())))?; let mut result = ciphertext; result.extend_from_slice(&tag); Ok(result) }, } } fn aes_decrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { match mode { AesMode::Cbc => { let cipher = match key.len() { 16 => Cipher::aes_128_cbc(), 24 => Cipher::aes_192_cbc(), 32 => Cipher::aes_256_cbc(), _ => { return Err(CryptoError::InvalidKey(Some( "Invalid AES key length".into(), ))) }, }; symm::decrypt(cipher, key, Some(iv), data) .map_err(|e| CryptoError::DecryptionFailed(Some(e.to_string().into()))) }, AesMode::Ctr { .. } => { let cipher = match key.len() { 16 => Cipher::aes_128_ctr(), 24 => Cipher::aes_192_ctr(), 32 => Cipher::aes_256_ctr(), _ => { return Err(CryptoError::InvalidKey(Some( "Invalid AES key length".into(), ))) }, }; symm::decrypt(cipher, key, Some(iv), data) .map_err(|e| CryptoError::DecryptionFailed(Some(e.to_string().into()))) }, AesMode::Gcm { tag_length } => { let cipher = match key.len() { 16 => Cipher::aes_128_gcm(), 24 => Cipher::aes_192_gcm(), 32 => Cipher::aes_256_gcm(), _ => { return Err(CryptoError::InvalidKey(Some( "Invalid AES key length".into(), ))) }, }; let tag_len = (tag_length / 8) as usize; if data.len() < tag_len { return Err(CryptoError::InvalidData(Some( "Data too short for GCM tag".into(), ))); } let (ciphertext, tag) = data.split_at(data.len() - tag_len); symm::decrypt_aead( cipher, key, Some(iv), additional_data.unwrap_or(&[]), ciphertext, tag, ) .map_err(|e| CryptoError::DecryptionFailed(Some(e.to_string().into()))) }, } } fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError> { use openssl::aes::{wrap_key, AesKey}; let aes_key = AesKey::new_encrypt(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut out = vec![0u8; key.len() + 8]; wrap_key(&aes_key, None, &mut out, key).map_err(|_| CryptoError::OperationFailed(None))?; Ok(out) } fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError> { use openssl::aes::{unwrap_key, AesKey}; let aes_key = AesKey::new_decrypt(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut out = vec![0u8; wrapped_key.len() - 8]; unwrap_key(&aes_key, None, &mut out, wrapped_key) .map_err(|_| CryptoError::OperationFailed(None))?; Ok(out) } fn hkdf_derive_key( &self, key: &[u8], salt: &[u8], info: &[u8], length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError> { use openssl::pkey_ctx::HkdfMode; let md = get_md(hash_alg); let mut ctx = PkeyCtx::new_id(Id::HKDF) .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; ctx.derive_init() .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; ctx.set_hkdf_md(md) .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; ctx.set_hkdf_mode(HkdfMode::EXTRACT_THEN_EXPAND) .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; ctx.set_hkdf_key(key) .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; if !salt.is_empty() { ctx.set_hkdf_salt(salt) .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; } if !info.is_empty() { ctx.add_hkdf_info(info) .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; } let mut out = vec![0u8; length]; ctx.derive(Some(&mut out)) .map_err(|e| CryptoError::DerivationFailed(Some(e.to_string().into())))?; Ok(out) } fn pbkdf2_derive_key( &self, password: &[u8], salt: &[u8], iterations: u32, length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let md = get_message_digest(hash_alg); let mut out = vec![0u8; length]; openssl::pkcs5::pbkdf2_hmac(password, salt, iterations as usize, md, &mut out) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok(out) } fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError> { let length_bytes = (length_bits / 8) as usize; let mut key = vec![0u8; length_bytes]; rand_bytes(&mut key) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok(key) } fn generate_hmac_key( &self, hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { match hash_alg { HashAlgorithm::Md5 => 16, HashAlgorithm::Sha1 => 20, HashAlgorithm::Sha256 => 32, HashAlgorithm::Sha384 => 48, HashAlgorithm::Sha512 => 64, } } else { (length_bits / 8) as usize }; let mut key = vec![0u8; length_bytes]; rand_bytes(&mut key) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok(key) } fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { let group = get_ec_group(curve)?; let ec_key = EcKey::generate(&group) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let pkey = PKey::from_ec_key(ec_key.clone()) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; // Return PKCS#8 DER for private key (consistent with RustCrypto) let private_der = pkey .private_key_to_der() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; // Return SEC1 uncompressed point for public key (consistent with RustCrypto) let mut bn_ctx = openssl::bn::BigNumContext::new() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let public_sec1 = ec_key .public_key() .to_bytes( &group, openssl::ec::PointConversionForm::UNCOMPRESSED, &mut bn_ctx, ) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok((private_der, public_sec1)) } fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { let pkey = PKey::generate_ed25519() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let private_der = pkey .private_key_to_der() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let public_raw = pkey .raw_public_key() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok((private_der, public_raw)) } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { let pkey = PKey::generate_x25519() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let private_raw = pkey .raw_private_key() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let public_raw = pkey .raw_public_key() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok((private_raw, public_raw)) } fn generate_rsa_key( &self, modulus_length: u32, public_exponent: &[u8], ) -> Result<(Vec, Vec), CryptoError> { let exp = BigNum::from_slice(public_exponent) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let rsa = Rsa::generate_with_e(modulus_length, &exp) .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let private_der = rsa .private_key_to_der() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; let public_der = rsa .public_key_to_der() .map_err(|e| CryptoError::OperationFailed(Some(e.to_string().into())))?; Ok((private_der, public_der)) } fn import_rsa_public_key_pkcs1( &self, der: &[u8], ) -> Result { let rsa = Rsa::public_key_from_der(der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let modulus_length = rsa.n().num_bits() as u32; let public_exponent = rsa.e().to_vec(); let key_data = rsa .public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaImportResult { key_data, modulus_length, public_exponent, is_private: false, }) } fn import_rsa_private_key_pkcs1( &self, der: &[u8], ) -> Result { let rsa = Rsa::private_key_from_der(der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let modulus_length = rsa.n().num_bits() as u32; let public_exponent = rsa.e().to_vec(); let key_data = rsa .private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaImportResult { key_data, modulus_length, public_exponent, is_private: true, }) } fn import_rsa_public_key_spki( &self, der: &[u8], ) -> Result { let pkey = PKey::public_key_from_der(der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let rsa = pkey .rsa() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let modulus_length = rsa.n().num_bits() as u32; let public_exponent = rsa.e().to_vec(); let key_data = rsa .public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaImportResult { key_data, modulus_length, public_exponent, is_private: false, }) } fn import_rsa_private_key_pkcs8( &self, der: &[u8], ) -> Result { let pkey = PKey::private_key_from_der(der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let rsa = pkey .rsa() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let modulus_length = rsa.n().num_bits() as u32; let public_exponent = rsa.e().to_vec(); let key_data = rsa .private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaImportResult { key_data, modulus_length, public_exponent, is_private: true, }) } fn export_rsa_public_key_pkcs1(&self, key_data: &[u8]) -> Result, CryptoError> { let rsa = Rsa::public_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; rsa.public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } fn export_rsa_public_key_spki(&self, key_data: &[u8]) -> Result, CryptoError> { let rsa = Rsa::public_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; pkey.public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } fn export_rsa_private_key_pkcs8(&self, key_data: &[u8]) -> Result, CryptoError> { let rsa = Rsa::private_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa).map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; pkey.private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } fn import_ec_public_key_sec1( &self, data: &[u8], curve: EllipticCurve, ) -> Result { let nid = curve_to_nid(curve); let group = EcGroup::from_curve_name(nid) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut ctx = openssl::bn::BigNumContext::new() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let point = openssl::ec::EcPoint::from_bytes(&group, data, &mut ctx) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let ec_key = EcKey::from_public_key(&group, &point) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_ec_key(ec_key) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcImportResult { key_data: pkey .public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, is_private: false, }) } fn import_ec_public_key_spki(&self, der: &[u8]) -> Result { let pkey = PKey::public_key_from_der(der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcImportResult { key_data: pkey .public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, is_private: false, }) } fn import_ec_private_key_pkcs8( &self, der: &[u8], ) -> Result { let pkey = PKey::private_key_from_der(der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcImportResult { key_data: pkey .private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, is_private: true, }) } fn import_ec_private_key_sec1( &self, data: &[u8], curve: EllipticCurve, ) -> Result { let nid = curve_to_nid(curve); let group = EcGroup::from_curve_name(nid) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let bn = BigNum::from_slice(data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let ec_key = EcKey::from_private_components(&group, &bn, group.generator()) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_ec_key(ec_key) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcImportResult { key_data: pkey .private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, is_private: true, }) } fn export_ec_public_key_sec1( &self, key_data: &[u8], _curve: EllipticCurve, is_private: bool, ) -> Result, CryptoError> { let mut ctx = openssl::bn::BigNumContext::new() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; if is_private { let ec_key = PKey::private_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))? .ec_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; ec_key .public_key() .to_bytes( ec_key.group(), openssl::ec::PointConversionForm::UNCOMPRESSED, &mut ctx, ) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } else { let ec_key = PKey::public_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))? .ec_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; ec_key .public_key() .to_bytes( ec_key.group(), openssl::ec::PointConversionForm::UNCOMPRESSED, &mut ctx, ) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } } fn export_ec_public_key_spki( &self, key_data: &[u8], _curve: EllipticCurve, ) -> Result, CryptoError> { let pkey = PKey::public_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; pkey.public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } fn export_ec_private_key_pkcs8( &self, key_data: &[u8], _curve: EllipticCurve, ) -> Result, CryptoError> { let pkey = PKey::private_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; pkey.private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } fn import_okp_public_key_raw( &self, data: &[u8], ) -> Result { if data.len() != 32 { return Err(CryptoError::InvalidKey(None)); } Ok(super::OkpImportResult { key_data: data.to_vec(), is_private: false, }) } fn import_okp_public_key_spki( &self, der: &[u8], _expected_oid: &[u8], ) -> Result { let pkey = PKey::public_key_from_der(der) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let raw = pkey .raw_public_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::OkpImportResult { key_data: raw, is_private: false, }) } fn import_okp_private_key_pkcs8( &self, der: &[u8], _expected_oid: &[u8], ) -> Result { Ok(super::OkpImportResult { key_data: der.to_vec(), is_private: true, }) } fn export_okp_public_key_raw( &self, key_data: &[u8], is_private: bool, ) -> Result, CryptoError> { if is_private { let pkey = PKey::private_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; pkey.raw_public_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } else { Ok(key_data.to_vec()) } } fn export_okp_public_key_spki( &self, key_data: &[u8], _oid: &[u8], ) -> Result, CryptoError> { // key_data is raw public key, need to wrap in SPKI let pkey = PKey::public_key_from_raw_bytes(key_data, Id::ED25519) .or_else(|_| PKey::public_key_from_raw_bytes(key_data, Id::X25519)) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; pkey.public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into()))) } fn export_okp_private_key_pkcs8( &self, key_data: &[u8], _oid: &[u8], ) -> Result, CryptoError> { // key_data is already PKCS8 Ok(key_data.to_vec()) } fn import_rsa_jwk( &self, jwk: super::RsaJwkImport<'_>, ) -> Result { let n = BigNum::from_slice(jwk.n) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let e = BigNum::from_slice(jwk.e) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let modulus_length = n.num_bits() as u32; let pub_exp_bytes = jwk.e.to_vec(); if let ( Some(d_bytes), Some(p_bytes), Some(q_bytes), Some(dp_bytes), Some(dq_bytes), Some(qi_bytes), ) = (jwk.d, jwk.p, jwk.q, jwk.dp, jwk.dq, jwk.qi) { let d = BigNum::from_slice(d_bytes) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let p = BigNum::from_slice(p_bytes) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let q = BigNum::from_slice(q_bytes) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let dp = BigNum::from_slice(dp_bytes) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let dq = BigNum::from_slice(dq_bytes) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let qi = BigNum::from_slice(qi_bytes) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let rsa = Rsa::from_private_components(n, e, d, p, q, dp, dq, qi) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaImportResult { key_data: pkey .private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, modulus_length, public_exponent: pub_exp_bytes, is_private: true, }) } else { let rsa = Rsa::from_public_components(n, e) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_rsa(rsa) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaImportResult { key_data: pkey .public_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, modulus_length, public_exponent: pub_exp_bytes, is_private: false, }) } } fn export_rsa_jwk( &self, key_data: &[u8], is_private: bool, ) -> Result { if is_private { let pkey = PKey::private_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let rsa = pkey .rsa() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaJwkExport { n: rsa.n().to_vec(), e: rsa.e().to_vec(), d: Some(rsa.d().to_vec()), p: rsa.p().map(|v| v.to_vec()), q: rsa.q().map(|v| v.to_vec()), dp: rsa.dmp1().map(|v| v.to_vec()), dq: rsa.dmq1().map(|v| v.to_vec()), qi: rsa.iqmp().map(|v| v.to_vec()), }) } else { let pkey = PKey::public_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let rsa = pkey .rsa() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::RsaJwkExport { n: rsa.n().to_vec(), e: rsa.e().to_vec(), d: None, p: None, q: None, dp: None, dq: None, qi: None, }) } } fn import_ec_jwk( &self, jwk: super::EcJwkImport<'_>, curve: EllipticCurve, ) -> Result { let nid = curve_to_nid(curve); let group = EcGroup::from_curve_name(nid) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let x = BigNum::from_slice(jwk.x) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let y = BigNum::from_slice(jwk.y) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pub_key = EcKey::from_public_key_affine_coordinates(&group, &x, &y) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; if let Some(d_bytes) = jwk.d { let d = BigNum::from_slice(d_bytes) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let priv_key = EcKey::from_private_components(&group, &d, pub_key.public_key()) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let pkey = PKey::from_ec_key(priv_key) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcImportResult { key_data: pkey .private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, is_private: true, }) } else { // Return SEC1 uncompressed point for public key (consistent with generate_ec_key) let mut ctx = openssl::bn::BigNumContext::new() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let sec1 = pub_key .public_key() .to_bytes( &group, openssl::ec::PointConversionForm::UNCOMPRESSED, &mut ctx, ) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcImportResult { key_data: sec1, is_private: false, }) } } fn export_ec_jwk( &self, key_data: &[u8], curve: EllipticCurve, is_private: bool, ) -> Result { let nid = curve_to_nid(curve); let group = EcGroup::from_curve_name(nid) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut ctx = openssl::bn::BigNumContext::new() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; if is_private { let pkey = PKey::private_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let ec_key = pkey .ec_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut x = BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let mut y = BigNum::new().map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; ec_key .public_key() .affine_coordinates(&group, &mut x, &mut y, &mut ctx) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::EcJwkExport { x: x.to_vec(), y: y.to_vec(), d: Some(ec_key.private_key().to_vec()), }) } else { // key_data is SEC1 uncompressed point (0x04 || x || y) let coord_len = match curve { EllipticCurve::P256 => 32, EllipticCurve::P384 => 48, EllipticCurve::P521 => 66, }; if key_data.len() != 1 + 2 * coord_len || key_data[0] != 0x04 { return Err(CryptoError::InvalidKey(None)); } let x = key_data[1..1 + coord_len].to_vec(); let y = key_data[1 + coord_len..].to_vec(); Ok(super::EcJwkExport { x, y, d: None }) } } fn import_okp_jwk( &self, jwk: super::OkpJwkImport<'_>, is_ed25519: bool, ) -> Result { let id = if is_ed25519 { Id::ED25519 } else { Id::X25519 }; if let Some(d) = jwk.d { // Private key let pkey = PKey::private_key_from_raw_bytes(d, id) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; if is_ed25519 { // Ed25519: return PKCS8 DER Ok(super::OkpImportResult { key_data: pkey .private_key_to_der() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?, is_private: true, }) } else { // X25519: return raw bytes Ok(super::OkpImportResult { key_data: d.to_vec(), is_private: true, }) } } else { // Public key - store raw bytes Ok(super::OkpImportResult { key_data: jwk.x.to_vec(), is_private: false, }) } } fn export_okp_jwk( &self, key_data: &[u8], is_private: bool, is_ed25519: bool, ) -> Result { if is_private { if is_ed25519 { // Ed25519: key_data is PKCS8 DER let pkey = PKey::private_key_from_der(key_data) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let d = pkey .raw_private_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let x = pkey .raw_public_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::OkpJwkExport { x, d: Some(d) }) } else { // X25519: key_data is raw 32-byte secret let pkey = PKey::private_key_from_raw_bytes(key_data, Id::X25519) .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; let x = pkey .raw_public_key() .map_err(|e| CryptoError::InvalidKey(Some(e.to_string().into())))?; Ok(super::OkpJwkExport { x, d: Some(key_data.to_vec()), }) } } else { // Public key - key_data is raw bytes Ok(super::OkpJwkExport { x: key_data.to_vec(), d: None, }) } } } ================================================ FILE: modules/llrt_crypto/src/provider/ring.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use crate::hash::HashAlgorithm; use crate::provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; use crate::subtle::EllipticCurve; use md5::{Digest, Md5 as Md5Hasher}; use ring::{digest, hmac}; pub struct RingProvider; pub enum RingDigestType { Sha1(RingDigest), Sha256(RingDigest), Sha384(RingDigest), Sha512(RingDigest), Md5(RingMd5), } pub enum RingHmacType { Sha1(RingHmacSha1), Sha256(RingHmacSha256), Sha384(RingHmacSha384), Sha512(RingHmacSha512), } impl SimpleDigest for RingDigestType { fn update(&mut self, data: &[u8]) { match self { RingDigestType::Sha1(d) => d.update(data), RingDigestType::Sha256(d) => d.update(data), RingDigestType::Sha384(d) => d.update(data), RingDigestType::Sha512(d) => d.update(data), RingDigestType::Md5(d) => d.update(data), } } fn finalize(self) -> Vec { match self { RingDigestType::Sha1(d) => d.finalize(), RingDigestType::Sha256(d) => d.finalize(), RingDigestType::Sha384(d) => d.finalize(), RingDigestType::Sha512(d) => d.finalize(), RingDigestType::Md5(d) => d.finalize(), } } } impl HmacProvider for RingHmacType { fn update(&mut self, data: &[u8]) { match self { RingHmacType::Sha1(h) => h.update(data), RingHmacType::Sha256(h) => h.update(data), RingHmacType::Sha384(h) => h.update(data), RingHmacType::Sha512(h) => h.update(data), } } fn finalize(self) -> Vec { match self { RingHmacType::Sha1(h) => h.finalize(), RingHmacType::Sha256(h) => h.finalize(), RingHmacType::Sha384(h) => h.finalize(), RingHmacType::Sha512(h) => h.finalize(), } } } // Simple wrapper for Ring digest pub struct RingDigest { algorithm: &'static digest::Algorithm, data: Vec, } impl RingDigest { fn new(algorithm: &'static digest::Algorithm) -> Self { Self { algorithm, data: Vec::new(), } } } impl SimpleDigest for RingDigest { fn update(&mut self, data: &[u8]) { self.data.extend_from_slice(data); } fn finalize(self) -> Vec { digest::digest(self.algorithm, &self.data).as_ref().to_vec() } } // MD5 wrapper pub struct RingMd5(Md5Hasher); impl SimpleDigest for RingMd5 { fn update(&mut self, data: &[u8]) { Digest::update(&mut self.0, data); } fn finalize(self) -> Vec { self.0.finalize().to_vec() } } // HMAC implementations pub struct RingHmacSha1(hmac::Context); pub struct RingHmacSha256(hmac::Context); pub struct RingHmacSha384(hmac::Context); pub struct RingHmacSha512(hmac::Context); impl HmacProvider for RingHmacSha1 { fn update(&mut self, data: &[u8]) { self.0.update(data); } fn finalize(self) -> Vec { self.0.sign().as_ref().to_vec() } } impl HmacProvider for RingHmacSha256 { fn update(&mut self, data: &[u8]) { self.0.update(data); } fn finalize(self) -> Vec { self.0.sign().as_ref().to_vec() } } impl HmacProvider for RingHmacSha384 { fn update(&mut self, data: &[u8]) { self.0.update(data); } fn finalize(self) -> Vec { self.0.sign().as_ref().to_vec() } } impl HmacProvider for RingHmacSha512 { fn update(&mut self, data: &[u8]) { self.0.update(data); } fn finalize(self) -> Vec { self.0.sign().as_ref().to_vec() } } impl CryptoProvider for RingProvider { type Digest = RingDigestType; type Hmac = RingHmacType; fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { HashAlgorithm::Md5 => RingDigestType::Md5(RingMd5(Md5Hasher::new())), HashAlgorithm::Sha1 => { RingDigestType::Sha1(RingDigest::new(&digest::SHA1_FOR_LEGACY_USE_ONLY)) }, HashAlgorithm::Sha256 => RingDigestType::Sha256(RingDigest::new(&digest::SHA256)), HashAlgorithm::Sha384 => RingDigestType::Sha384(RingDigest::new(&digest::SHA384)), HashAlgorithm::Sha512 => RingDigestType::Sha512(RingDigest::new(&digest::SHA512)), } } fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { HashAlgorithm::Md5 => { panic!("HMAC-MD5 not supported by Ring provider"); }, HashAlgorithm::Sha1 => RingHmacType::Sha1(RingHmacSha1(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key), ))), HashAlgorithm::Sha256 => RingHmacType::Sha256(RingHmacSha256(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA256, key), ))), HashAlgorithm::Sha384 => RingHmacType::Sha384(RingHmacSha384(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA384, key), ))), HashAlgorithm::Sha512 => RingHmacType::Sha512(RingHmacSha512(hmac::Context::with_key( &hmac::Key::new(hmac::HMAC_SHA512, key), ))), } } fn ecdsa_sign( &self, _curve: EllipticCurve, _private_key_der: &[u8], _digest: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn ecdsa_verify( &self, _curve: EllipticCurve, _public_key_sec1: &[u8], _signature: &[u8], _digest: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn ed25519_sign(&self, _private_key_der: &[u8], _data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn ed25519_verify( &self, _public_key_bytes: &[u8], _signature: &[u8], _data: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pss_sign( &self, _private_key_der: &[u8], _digest: &[u8], _salt_length: usize, _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pss_verify( &self, _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], _salt_length: usize, _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pkcs1v15_sign( &self, _private_key_der: &[u8], _digest: &[u8], _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_pkcs1v15_verify( &self, _public_key_der: &[u8], _signature: &[u8], _digest: &[u8], _hash_alg: HashAlgorithm, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_oaep_encrypt( &self, _public_key_der: &[u8], _data: &[u8], _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn rsa_oaep_decrypt( &self, _private_key_der: &[u8], _data: &[u8], _hash_alg: HashAlgorithm, _label: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn ecdh_derive_bits( &self, _curve: EllipticCurve, _private_key_der: &[u8], _public_key_sec1: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn x25519_derive_bits( &self, _private_key: &[u8], _public_key: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn aes_encrypt( &self, _mode: AesMode, _key: &[u8], _iv: &[u8], _data: &[u8], _additional_data: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn aes_decrypt( &self, _mode: AesMode, _key: &[u8], _iv: &[u8], _data: &[u8], _additional_data: Option<&[u8]>, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn aes_kw_wrap(&self, _kek: &[u8], _key: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn aes_kw_unwrap(&self, _kek: &[u8], _wrapped_key: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn hkdf_derive_key( &self, _key: &[u8], _salt: &[u8], _info: &[u8], _length: usize, _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn pbkdf2_derive_key( &self, _password: &[u8], _salt: &[u8], _iterations: u32, _length: usize, _hash_alg: HashAlgorithm, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_aes_key(&self, _length_bits: u16) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_hmac_key( &self, _hash_alg: HashAlgorithm, _length_bits: u16, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_ec_key(&self, _curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn generate_rsa_key( &self, _modulus_length: u32, _public_exponent: &[u8], ) -> Result<(Vec, Vec), CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_public_key_pkcs1( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_private_key_pkcs1( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_public_key_spki( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_private_key_pkcs8( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_public_key_pkcs1(&self, _key_data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_public_key_spki(&self, _key_data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_private_key_pkcs8(&self, _key_data: &[u8]) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_public_key_sec1( &self, _data: &[u8], _curve: EllipticCurve, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_public_key_spki(&self, _der: &[u8]) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_private_key_pkcs8( &self, _der: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_private_key_sec1( &self, _data: &[u8], _curve: EllipticCurve, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_public_key_sec1( &self, _key_data: &[u8], _curve: EllipticCurve, _is_private: bool, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_public_key_spki( &self, _key_data: &[u8], _curve: EllipticCurve, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_private_key_pkcs8( &self, _key_data: &[u8], _curve: EllipticCurve, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_public_key_raw( &self, _data: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_public_key_spki( &self, _der: &[u8], _expected_oid: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_private_key_pkcs8( &self, _der: &[u8], _expected_oid: &[u8], ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_public_key_raw( &self, _key_data: &[u8], _is_private: bool, ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_public_key_spki( &self, _key_data: &[u8], _oid: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_private_key_pkcs8( &self, _key_data: &[u8], _oid: &[u8], ) -> Result, CryptoError> { Err(CryptoError::UnsupportedAlgorithm) } fn import_rsa_jwk( &self, _jwk: super::RsaJwkImport<'_>, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_rsa_jwk( &self, _key_data: &[u8], _is_private: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_ec_jwk( &self, _jwk: super::EcJwkImport<'_>, _curve: EllipticCurve, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_ec_jwk( &self, _key_data: &[u8], _curve: EllipticCurve, _is_private: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn import_okp_jwk( &self, _jwk: super::OkpJwkImport<'_>, _is_ed25519: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } fn export_okp_jwk( &self, _key_data: &[u8], _is_private: bool, _is_ed25519: bool, ) -> Result { Err(CryptoError::UnsupportedAlgorithm) } } ================================================ FILE: modules/llrt_crypto/src/provider/rust/aes_variants.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! AES cipher variant types for SubtleCrypto operations. //! Only available when the `_rustcrypto` feature is enabled. use aes::cipher::BlockModeDecrypt; use aes::cipher::BlockModeEncrypt; use aes::{ cipher::{ block_padding::{Error as PaddingError, Pkcs7}, consts::{U12, U13, U14, U15, U16}, InvalidLength, KeyIvInit, StreamCipher, StreamCipherError, }, Aes128, Aes192, Aes256, }; use aes_gcm::{ aead::{Aead, Payload}, AesGcm, KeyInit, Nonce, }; use ctr::{Ctr128BE, Ctr32BE, Ctr64BE}; #[allow(dead_code)] pub enum AesCbcEncVariant { Aes128(cbc::Encryptor), Aes192(cbc::Encryptor), Aes256(cbc::Encryptor), } #[allow(dead_code)] impl AesCbcEncVariant { pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { let variant: AesCbcEncVariant = match key_len { 128 => Self::Aes128(cbc::Encryptor::new_from_slices(key, iv)?), 192 => Self::Aes192(cbc::Encryptor::new_from_slices(key, iv)?), 256 => Self::Aes256(cbc::Encryptor::new_from_slices(key, iv)?), _ => return Err(InvalidLength), }; Ok(variant) } pub fn encrypt(self, data: &[u8]) -> Vec { match self { Self::Aes128(v) => v.encrypt_padded_vec::(data), Self::Aes192(v) => v.encrypt_padded_vec::(data), Self::Aes256(v) => v.encrypt_padded_vec::(data), } } } #[allow(dead_code)] pub enum AesCbcDecVariant { Aes128(cbc::Decryptor), Aes192(cbc::Decryptor), Aes256(cbc::Decryptor), } #[allow(dead_code)] impl AesCbcDecVariant { pub fn new(key_len: u16, key: &[u8], iv: &[u8]) -> std::result::Result { let variant: AesCbcDecVariant = match key_len { 128 => Self::Aes128(cbc::Decryptor::new_from_slices(key, iv)?), 192 => Self::Aes192(cbc::Decryptor::new_from_slices(key, iv)?), 256 => Self::Aes256(cbc::Decryptor::new_from_slices(key, iv)?), _ => return Err(InvalidLength), }; Ok(variant) } pub fn decrypt(self, data: &[u8]) -> std::result::Result, PaddingError> { Ok(match self { Self::Aes128(v) => v.decrypt_padded_vec::(data)?, Self::Aes192(v) => v.decrypt_padded_vec::(data)?, Self::Aes256(v) => v.decrypt_padded_vec::(data)?, }) } } #[allow(dead_code)] pub enum AesCtrVariant { Aes128Ctr32(Ctr32BE), Aes128Ctr64(Ctr64BE), Aes128Ctr128(Ctr128BE), Aes192Ctr32(Ctr32BE), Aes192Ctr64(Ctr64BE), Aes192Ctr128(Ctr128BE), Aes256Ctr32(Ctr32BE), Aes256Ctr64(Ctr64BE), Aes256Ctr128(Ctr128BE), } #[allow(dead_code)] impl AesCtrVariant { pub fn new( key_len: u16, encryption_length: u32, key: &[u8], counter: &[u8], ) -> std::result::Result { let variant: AesCtrVariant = match (key_len, encryption_length) { (128, 32) => Self::Aes128Ctr32(Ctr32BE::new_from_slices(key, counter)?), (128, 64) => Self::Aes128Ctr64(Ctr64BE::new_from_slices(key, counter)?), (128, 128) => Self::Aes128Ctr128(Ctr128BE::new_from_slices(key, counter)?), (192, 32) => Self::Aes192Ctr32(Ctr32BE::new_from_slices(key, counter)?), (192, 64) => Self::Aes192Ctr64(Ctr64BE::new_from_slices(key, counter)?), (192, 128) => Self::Aes192Ctr128(Ctr128BE::new_from_slices(key, counter)?), (256, 32) => Self::Aes256Ctr32(Ctr32BE::new_from_slices(key, counter)?), (256, 64) => Self::Aes256Ctr64(Ctr64BE::new_from_slices(key, counter)?), (256, 128) => Self::Aes256Ctr128(Ctr128BE::new_from_slices(key, counter)?), _ => return Err(InvalidLength), }; Ok(variant) } pub fn encrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { let mut ciphertext = data.to_vec(); match self { Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, } Ok(ciphertext) } pub fn decrypt(&mut self, data: &[u8]) -> std::result::Result, StreamCipherError> { let mut ciphertext = data.to_vec(); match self { Self::Aes128Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes128Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes128Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes192Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes192Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes192Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes256Ctr32(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes256Ctr64(v) => v.try_apply_keystream(&mut ciphertext)?, Self::Aes256Ctr128(v) => v.try_apply_keystream(&mut ciphertext)?, } Ok(ciphertext) } } pub enum AesGcmVariant { Aes128Gcm96(AesGcm), Aes192Gcm96(AesGcm), Aes256Gcm96(AesGcm), Aes128Gcm104(AesGcm), Aes192Gcm104(AesGcm), Aes256Gcm104(AesGcm), Aes128Gcm112(AesGcm), Aes192Gcm112(AesGcm), Aes256Gcm112(AesGcm), Aes128Gcm120(AesGcm), Aes192Gcm120(AesGcm), Aes256Gcm120(AesGcm), Aes128Gcm128(AesGcm), Aes192Gcm128(AesGcm), Aes256Gcm128(AesGcm), } #[allow(dead_code)] impl AesGcmVariant { pub fn new( key_len: u16, tag_length: u8, key: &[u8], ) -> std::result::Result { let variant = match (key_len, tag_length) { (128, 96) => Self::Aes128Gcm96(AesGcm::new_from_slice(key)?), (192, 96) => Self::Aes192Gcm96(AesGcm::new_from_slice(key)?), (256, 96) => Self::Aes256Gcm96(AesGcm::new_from_slice(key)?), (128, 104) => Self::Aes128Gcm104(AesGcm::new_from_slice(key)?), (192, 104) => Self::Aes192Gcm104(AesGcm::new_from_slice(key)?), (256, 104) => Self::Aes256Gcm104(AesGcm::new_from_slice(key)?), (128, 112) => Self::Aes128Gcm112(AesGcm::new_from_slice(key)?), (192, 112) => Self::Aes192Gcm112(AesGcm::new_from_slice(key)?), (256, 112) => Self::Aes256Gcm112(AesGcm::new_from_slice(key)?), (128, 120) => Self::Aes128Gcm120(AesGcm::new_from_slice(key)?), (192, 120) => Self::Aes192Gcm120(AesGcm::new_from_slice(key)?), (256, 120) => Self::Aes256Gcm120(AesGcm::new_from_slice(key)?), (128, 128) => Self::Aes128Gcm128(AesGcm::new_from_slice(key)?), (192, 128) => Self::Aes192Gcm128(AesGcm::new_from_slice(key)?), (256, 128) => Self::Aes256Gcm128(AesGcm::new_from_slice(key)?), _ => return Err(InvalidLength), }; Ok(variant) } pub fn encrypt( &self, nonce: &[u8], msg: &[u8], aad: Option<&[u8]>, ) -> std::result::Result, aes_gcm::Error> { let plaintext: Payload = Payload { msg, aad: aad.unwrap_or_default(), }; let nonce: &ctr::cipher::Array<_, _> = &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; match self { Self::Aes128Gcm96(v) => v.encrypt(nonce, plaintext), Self::Aes192Gcm96(v) => v.encrypt(nonce, plaintext), Self::Aes256Gcm96(v) => v.encrypt(nonce, plaintext), Self::Aes128Gcm104(v) => v.encrypt(nonce, plaintext), Self::Aes192Gcm104(v) => v.encrypt(nonce, plaintext), Self::Aes256Gcm104(v) => v.encrypt(nonce, plaintext), Self::Aes128Gcm112(v) => v.encrypt(nonce, plaintext), Self::Aes192Gcm112(v) => v.encrypt(nonce, plaintext), Self::Aes256Gcm112(v) => v.encrypt(nonce, plaintext), Self::Aes128Gcm120(v) => v.encrypt(nonce, plaintext), Self::Aes192Gcm120(v) => v.encrypt(nonce, plaintext), Self::Aes256Gcm120(v) => v.encrypt(nonce, plaintext), Self::Aes128Gcm128(v) => v.encrypt(nonce, plaintext), Self::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), Self::Aes256Gcm128(v) => v.encrypt(nonce, plaintext), } } pub fn decrypt( &self, nonce: &[u8], msg: &[u8], aad: Option<&[u8]>, ) -> std::result::Result, aes_gcm::Error> { let ciphertext: Payload = Payload { msg, aad: aad.unwrap_or_default(), }; let nonce: &ctr::cipher::Array<_, _> = &Nonce::::try_from(nonce).map_err(|_| aes_gcm::Error)?; match self { Self::Aes128Gcm96(v) => v.decrypt(nonce, ciphertext), Self::Aes192Gcm96(v) => v.decrypt(nonce, ciphertext), Self::Aes256Gcm96(v) => v.decrypt(nonce, ciphertext), Self::Aes128Gcm104(v) => v.decrypt(nonce, ciphertext), Self::Aes192Gcm104(v) => v.decrypt(nonce, ciphertext), Self::Aes256Gcm104(v) => v.decrypt(nonce, ciphertext), Self::Aes128Gcm112(v) => v.decrypt(nonce, ciphertext), Self::Aes192Gcm112(v) => v.decrypt(nonce, ciphertext), Self::Aes256Gcm112(v) => v.decrypt(nonce, ciphertext), Self::Aes128Gcm120(v) => v.decrypt(nonce, ciphertext), Self::Aes192Gcm120(v) => v.decrypt(nonce, ciphertext), Self::Aes256Gcm120(v) => v.decrypt(nonce, ciphertext), Self::Aes128Gcm128(v) => v.decrypt(nonce, ciphertext), Self::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), Self::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), } } } ================================================ FILE: modules/llrt_crypto/src/provider/rust/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 mod aes_variants; use std::num::NonZeroU32; use aes::cipher::{ block_padding::Pkcs7, BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, StreamCipher, StreamCipherError, }; use aes_gcm::{ aead::{Aead, Payload}, KeyInit, Nonce, }; use aes_kw::{KwAes128, KwAes192, KwAes256}; use cbc::{Decryptor, Encryptor}; use ctr::{cipher::Array, Ctr128BE, Ctr32BE, Ctr64BE}; use der::Encode; use ecdsa::signature::hazmat::PrehashVerifier; use ed25519_dalek::{Signature, Signer, Verifier, VerifyingKey}; use elliptic_curve::{consts::U12, sec1::ToSec1Point, Generate}; use hkdf::Hkdf; use hmac::{Hmac as HmacImpl, Mac}; use p256::{ ecdsa::{ Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey, }, SecretKey as P256SecretKey, }; use p384::{ ecdsa::{ Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey, }, SecretKey as P384SecretKey, }; use p521::{ ecdsa::{ Signature as P521Signature, SigningKey as P521SigningKey, VerifyingKey as P521VerifyingKey, }, SecretKey as P521SecretKey, }; use pbkdf2::pbkdf2; use pkcs8::{DecodePrivateKey, EncodePrivateKey}; use rsa::pkcs1::{ DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPrivateKey, EncodeRsaPublicKey, }; use rsa::signature::hazmat::PrehashSigner; use rsa::{ pss::Pss, sha2::{Digest, Sha256, Sha384, Sha512}, BoxedUint, Oaep, Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey, }; use sha1::Sha1; use crate::{ hash::HashAlgorithm, provider::{AesMode, CryptoError, CryptoProvider, HmacProvider, SimpleDigest}, random_byte_array, subtle::EllipticCurve, }; use aes_variants::AesGcmVariant; impl From for CryptoError { fn from(_: aes::cipher::InvalidLength) -> Self { CryptoError::InvalidLength } } impl From for CryptoError { fn from(_: StreamCipherError) -> Self { CryptoError::OperationFailed(None) } } // Digest implementation using sha2/md5 crates pub enum RustDigest { Md5(md5::Md5), Sha1(Sha1), Sha256(Sha256), Sha384(Sha384), Sha512(Sha512), } impl SimpleDigest for RustDigest { fn update(&mut self, data: &[u8]) { match self { RustDigest::Md5(h) => Digest::update(h, data), RustDigest::Sha1(h) => Digest::update(h, data), RustDigest::Sha256(h) => Digest::update(h, data), RustDigest::Sha384(h) => Digest::update(h, data), RustDigest::Sha512(h) => Digest::update(h, data), } } fn finalize(self) -> Vec { match self { RustDigest::Md5(h) => h.finalize().to_vec(), RustDigest::Sha1(h) => h.finalize().to_vec(), RustDigest::Sha256(h) => h.finalize().to_vec(), RustDigest::Sha384(h) => h.finalize().to_vec(), RustDigest::Sha512(h) => h.finalize().to_vec(), } } } // HMAC implementation using hmac crate pub enum RustHmac { Sha1(HmacImpl), Sha256(HmacImpl), Sha384(HmacImpl), Sha512(HmacImpl), } impl HmacProvider for RustHmac { fn update(&mut self, data: &[u8]) { match self { RustHmac::Sha1(h) => Mac::update(h, data), RustHmac::Sha256(h) => Mac::update(h, data), RustHmac::Sha384(h) => Mac::update(h, data), RustHmac::Sha512(h) => Mac::update(h, data), } } fn finalize(self) -> Vec { match self { RustHmac::Sha1(h) => h.finalize().into_bytes().to_vec(), RustHmac::Sha256(h) => h.finalize().into_bytes().to_vec(), RustHmac::Sha384(h) => h.finalize().into_bytes().to_vec(), RustHmac::Sha512(h) => h.finalize().into_bytes().to_vec(), } } } // Main Crypto Provider #[derive(Default)] pub struct RustCryptoProvider; impl CryptoProvider for RustCryptoProvider { type Digest = RustDigest; type Hmac = RustHmac; fn digest(&self, algorithm: HashAlgorithm) -> Self::Digest { match algorithm { HashAlgorithm::Md5 => RustDigest::Md5(md5::Md5::new()), HashAlgorithm::Sha1 => RustDigest::Sha1(Sha1::new()), HashAlgorithm::Sha256 => RustDigest::Sha256(Sha256::new()), HashAlgorithm::Sha384 => RustDigest::Sha384(Sha384::new()), HashAlgorithm::Sha512 => RustDigest::Sha512(Sha512::new()), } } fn hmac(&self, algorithm: HashAlgorithm, key: &[u8]) -> Self::Hmac { match algorithm { HashAlgorithm::Md5 => panic!("HMAC-MD5 not supported"), HashAlgorithm::Sha1 => RustHmac::Sha1(HmacImpl::::new_from_slice(key).unwrap()), HashAlgorithm::Sha256 => { RustHmac::Sha256(HmacImpl::::new_from_slice(key).unwrap()) }, HashAlgorithm::Sha384 => { RustHmac::Sha384(HmacImpl::::new_from_slice(key).unwrap()) }, HashAlgorithm::Sha512 => { RustHmac::Sha512(HmacImpl::::new_from_slice(key).unwrap()) }, } } fn ecdsa_sign( &self, curve: EllipticCurve, private_key_der: &[u8], digest: &[u8], ) -> Result, CryptoError> { match curve { EllipticCurve::P256 => { let secret_key = P256SecretKey::from_pkcs8_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; let signing_key = P256SigningKey::from(secret_key); let signature: p256::ecdsa::Signature = signing_key .sign_prehash(digest) .map_err(|_| CryptoError::SigningFailed(None))?; Ok(signature.to_bytes().to_vec()) }, EllipticCurve::P384 => { let secret_key = P384SecretKey::from_pkcs8_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; let signing_key = P384SigningKey::from(secret_key); let signature: p384::ecdsa::Signature = signing_key .sign_prehash(digest) .map_err(|_| CryptoError::SigningFailed(None))?; Ok(signature.to_bytes().to_vec()) }, EllipticCurve::P521 => { let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; let signing_key = P521SigningKey::from(secret_key); let signature: p521::ecdsa::Signature = signing_key .sign_prehash(digest) .map_err(|_| CryptoError::SigningFailed(None))?; Ok(signature.to_bytes().to_vec()) }, } } fn ecdsa_verify( &self, curve: EllipticCurve, public_key_sec1: &[u8], signature: &[u8], digest: &[u8], ) -> Result { match curve { EllipticCurve::P256 => { let verifying_key = P256VerifyingKey::from_sec1_bytes(public_key_sec1) .map_err(|_| CryptoError::InvalidKey(None))?; let sig = P256Signature::from_slice(signature) .map_err(|_| CryptoError::InvalidSignature(None))?; Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) }, EllipticCurve::P384 => { let verifying_key = P384VerifyingKey::from_sec1_bytes(public_key_sec1) .map_err(|_| CryptoError::InvalidKey(None))?; let sig = P384Signature::from_slice(signature) .map_err(|_| CryptoError::InvalidSignature(None))?; Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) }, EllipticCurve::P521 => { let verifying_key = P521VerifyingKey::from_sec1_bytes(public_key_sec1) .map_err(|_| CryptoError::InvalidKey(None))?; let sig = P521Signature::from_slice(signature) .map_err(|_| CryptoError::InvalidSignature(None))?; Ok(verifying_key.verify_prehash(digest, &sig).is_ok()) }, } } fn ed25519_sign(&self, private_key_der: &[u8], data: &[u8]) -> Result, CryptoError> { let signing_key = ed25519_dalek::SigningKey::from_pkcs8_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; let signature = signing_key .try_sign(data) .map_err(|_| CryptoError::InvalidSignature(None))?; Ok(signature.to_bytes().to_vec()) } fn ed25519_verify( &self, public_key_bytes: &[u8], signature: &[u8], data: &[u8], ) -> Result { let public_key = VerifyingKey::from_bytes( public_key_bytes .try_into() .map_err(|_| CryptoError::InvalidKey(None))?, ) .map_err(|_| CryptoError::InvalidKey(None))?; let signature = Signature::from_bytes( signature .try_into() .map_err(|_| CryptoError::InvalidSignature(None))?, ); Ok(public_key.verify(data, &signature).is_ok()) } fn rsa_pss_sign( &self, private_key_der: &[u8], digest: &[u8], salt_length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { HashAlgorithm::Sha256 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), HashAlgorithm::Sha384 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), HashAlgorithm::Sha512 => private_key .sign_with_rng(&mut rng, Pss::::new_with_salt(salt_length), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), } } fn rsa_pss_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], salt_length: usize, hash_alg: HashAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { HashAlgorithm::Sha256 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), HashAlgorithm::Sha384 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), HashAlgorithm::Sha512 => Ok(public_key .verify(Pss::::new_with_salt(salt_length), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), } } fn rsa_pkcs1v15_sign( &self, private_key_der: &[u8], digest: &[u8], hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut rng = rand::rng(); let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { HashAlgorithm::Sha256 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), HashAlgorithm::Sha384 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), HashAlgorithm::Sha512 => private_key .sign_with_rng(&mut rng, Pkcs1v15Sign::new::(), digest) .map_err(|_| CryptoError::SigningFailed(None)), _ => Err(CryptoError::UnsupportedAlgorithm), } } fn rsa_pkcs1v15_verify( &self, public_key_der: &[u8], signature: &[u8], digest: &[u8], hash_alg: HashAlgorithm, ) -> Result { let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { HashAlgorithm::Sha256 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), HashAlgorithm::Sha384 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), HashAlgorithm::Sha512 => Ok(public_key .verify(Pkcs1v15Sign::new::(), digest, signature) .is_ok()), _ => Err(CryptoError::UnsupportedAlgorithm), } } fn rsa_oaep_encrypt( &self, public_key_der: &[u8], data: &[u8], hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let mut rng = rand::rng(); let public_key = RsaPublicKey::from_pkcs1_der(public_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { HashAlgorithm::Sha1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } public_key .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, HashAlgorithm::Sha256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } public_key .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, HashAlgorithm::Sha384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } public_key .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, HashAlgorithm::Sha512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } public_key .encrypt(&mut rng, padding, data) .map_err(|_| CryptoError::EncryptionFailed(None)) }, _ => Err(CryptoError::UnsupportedAlgorithm), } } fn rsa_oaep_decrypt( &self, private_key_der: &[u8], data: &[u8], hash_alg: HashAlgorithm, label: Option<&[u8]>, ) -> Result, CryptoError> { let private_key = RsaPrivateKey::from_pkcs1_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; match hash_alg { HashAlgorithm::Sha1 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } private_key .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, HashAlgorithm::Sha256 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } private_key .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, HashAlgorithm::Sha384 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } private_key .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, HashAlgorithm::Sha512 => { let mut padding = Oaep::::new(); if let Some(l) = label { if !l.is_empty() { padding.label = Some(l.into()); } } private_key .decrypt(padding, data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, _ => Err(CryptoError::UnsupportedAlgorithm), } } fn ecdh_derive_bits( &self, curve: EllipticCurve, private_key_der: &[u8], public_key_sec1: &[u8], ) -> Result, CryptoError> { match curve { EllipticCurve::P256 => { let secret_key = P256SecretKey::from_pkcs8_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; let public_key = p256::PublicKey::from_sec1_bytes(public_key_sec1) .map_err(|_| CryptoError::InvalidKey(None))?; let shared_secret = p256::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), ); Ok(shared_secret.raw_secret_bytes().to_vec()) }, EllipticCurve::P384 => { let secret_key = P384SecretKey::from_pkcs8_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; let public_key = p384::PublicKey::from_sec1_bytes(public_key_sec1) .map_err(|_| CryptoError::InvalidKey(None))?; let shared_secret = p384::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), ); Ok(shared_secret.raw_secret_bytes().to_vec()) }, EllipticCurve::P521 => { let secret_key = P521SecretKey::from_pkcs8_der(private_key_der) .map_err(|_| CryptoError::InvalidKey(None))?; let public_key = p521::PublicKey::from_sec1_bytes(public_key_sec1) .map_err(|_| CryptoError::InvalidKey(None))?; let shared_secret = p521::elliptic_curve::ecdh::diffie_hellman( secret_key.to_nonzero_scalar(), public_key.as_affine(), ); Ok(shared_secret.raw_secret_bytes().to_vec()) }, } } fn x25519_derive_bits( &self, private_key: &[u8], public_key: &[u8], ) -> Result, CryptoError> { let private_array: [u8; 32] = private_key .try_into() .map_err(|_| CryptoError::InvalidKey(None))?; let public_array: [u8; 32] = public_key .try_into() .map_err(|_| CryptoError::InvalidKey(None))?; let secret_key = x25519_dalek::StaticSecret::from(private_array); let public_key = x25519_dalek::PublicKey::from(public_array); let shared_secret = secret_key.diffie_hellman(&public_key); Ok(shared_secret.as_bytes().to_vec()) } fn aes_encrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { match mode { AesMode::Cbc => match key.len() { 16 => { let encryptor = Encryptor::::new_from_slices(key, iv)?; Ok(encryptor.encrypt_padded_vec::(data)) }, 24 => { let encryptor = Encryptor::::new_from_slices(key, iv)?; Ok(encryptor.encrypt_padded_vec::(data)) }, 32 => { let encryptor = Encryptor::::new_from_slices(key, iv)?; Ok(encryptor.encrypt_padded_vec::(data)) }, _ => Err(CryptoError::InvalidKey(None)), }, AesMode::Ctr { counter_length } => { let mut ciphertext = data.to_vec(); match (key.len(), counter_length) { (16, 32) => { let mut cipher = Ctr32BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (16, 64) => { let mut cipher = Ctr64BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (16, 128) => { let mut cipher = Ctr128BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (24, 32) => { let mut cipher = Ctr32BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (24, 64) => { let mut cipher = Ctr64BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (24, 128) => { let mut cipher = Ctr128BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (32, 32) => { let mut cipher = Ctr32BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (32, 64) => { let mut cipher = Ctr64BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, (32, 128) => { let mut cipher = Ctr128BE::::new_from_slices(key, iv)?; cipher.try_apply_keystream(&mut ciphertext)?; }, _ => return Err(CryptoError::InvalidKey(None)), } Ok(ciphertext) }, AesMode::Gcm { tag_length } => { let variant = AesGcmVariant::new((key.len() * 8) as u16, tag_length, key)?; let nonce: &Array<_, _> = &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData(None))?; let plaintext = Payload { msg: data, aad: additional_data.unwrap_or_default(), }; match variant { AesGcmVariant::Aes128Gcm96(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes192Gcm96(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes256Gcm96(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes128Gcm104(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes192Gcm104(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes256Gcm104(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes128Gcm112(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes192Gcm112(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes256Gcm112(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes128Gcm120(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes192Gcm120(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes256Gcm120(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes128Gcm128(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes192Gcm128(v) => v.encrypt(nonce, plaintext), AesGcmVariant::Aes256Gcm128(v) => v.encrypt(nonce, plaintext), } .map_err(|_| CryptoError::EncryptionFailed(None)) }, } } fn aes_decrypt( &self, mode: AesMode, key: &[u8], iv: &[u8], data: &[u8], additional_data: Option<&[u8]>, ) -> Result, CryptoError> { match mode { AesMode::Cbc => match key.len() { 16 => { let decryptor = Decryptor::::new_from_slices(key, iv)?; decryptor .decrypt_padded_vec::(data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, 24 => { let decryptor = Decryptor::::new_from_slices(key, iv)?; decryptor .decrypt_padded_vec::(data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, 32 => { let decryptor = Decryptor::::new_from_slices(key, iv)?; decryptor .decrypt_padded_vec::(data) .map_err(|_| CryptoError::DecryptionFailed(None)) }, _ => Err(CryptoError::InvalidKey(None)), }, AesMode::Ctr { .. } => { // CTR decryption is the same as encryption self.aes_encrypt(mode, key, iv, data, additional_data) }, AesMode::Gcm { tag_length } => { let variant = AesGcmVariant::new((key.len() * 8) as u16, tag_length, key)?; let nonce: &Array<_, _> = &Nonce::::try_from(iv).map_err(|_| CryptoError::InvalidData(None))?; let ciphertext = Payload { msg: data, aad: additional_data.unwrap_or_default(), }; match variant { AesGcmVariant::Aes128Gcm96(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes192Gcm96(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes256Gcm96(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes128Gcm104(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes192Gcm104(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes256Gcm104(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes128Gcm112(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes192Gcm112(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes256Gcm112(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes128Gcm120(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes192Gcm120(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes256Gcm120(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes128Gcm128(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes192Gcm128(v) => v.decrypt(nonce, ciphertext), AesGcmVariant::Aes256Gcm128(v) => v.decrypt(nonce, ciphertext), } .map_err(|_| CryptoError::DecryptionFailed(None)) }, } } fn aes_kw_wrap(&self, kek: &[u8], key: &[u8]) -> Result, CryptoError> { match kek.len() { 16 => { let kw = KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; key.len() + 8]; let result = kw .wrap_key(key, &mut buf) .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 24 => { let kw = KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; key.len() + 8]; let result = kw .wrap_key(key, &mut buf) .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 32 => { let kw = KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; key.len() + 8]; let result = kw .wrap_key(key, &mut buf) .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, _ => Err(CryptoError::InvalidKey(None)), } } fn aes_kw_unwrap(&self, kek: &[u8], wrapped_key: &[u8]) -> Result, CryptoError> { match kek.len() { 16 => { let kw = KwAes128::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; wrapped_key.len()]; let result = kw .unwrap_key(wrapped_key, &mut buf) .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 24 => { let kw = KwAes192::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; wrapped_key.len()]; let result = kw .unwrap_key(wrapped_key, &mut buf) .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, 32 => { let kw = KwAes256::new_from_slice(kek).map_err(|_| CryptoError::InvalidKey(None))?; let mut buf = vec![0u8; wrapped_key.len()]; let result = kw .unwrap_key(wrapped_key, &mut buf) .map_err(|_| CryptoError::OperationFailed(None))?; Ok(result.to_vec()) }, _ => Err(CryptoError::InvalidKey(None)), } } fn hkdf_derive_key( &self, key: &[u8], salt: &[u8], info: &[u8], length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut out = vec![0u8; length]; match hash_alg { HashAlgorithm::Sha1 => { let prk = Hkdf::::new(Some(salt), key); prk.expand(info, &mut out) }, HashAlgorithm::Sha256 => { let prk = Hkdf::::new(Some(salt), key); prk.expand(info, &mut out) }, HashAlgorithm::Sha384 => { let prk = Hkdf::::new(Some(salt), key); prk.expand(info, &mut out) }, HashAlgorithm::Sha512 => { let prk = Hkdf::::new(Some(salt), key); prk.expand(info, &mut out) }, _ => return Err(CryptoError::UnsupportedAlgorithm), } .map_err(|_| CryptoError::DerivationFailed(None))?; Ok(out) } fn pbkdf2_derive_key( &self, password: &[u8], salt: &[u8], iterations: u32, length: usize, hash_alg: HashAlgorithm, ) -> Result, CryptoError> { let mut out = vec![0; length]; let iterations = NonZeroU32::new(iterations).ok_or(CryptoError::InvalidData(None))?; match hash_alg { HashAlgorithm::Sha1 => { pbkdf2::>(password, salt, iterations.get(), &mut out) }, HashAlgorithm::Sha256 => { pbkdf2::>(password, salt, iterations.get(), &mut out) }, HashAlgorithm::Sha384 => { pbkdf2::>(password, salt, iterations.get(), &mut out) }, HashAlgorithm::Sha512 => { pbkdf2::>(password, salt, iterations.get(), &mut out) }, _ => return Err(CryptoError::UnsupportedAlgorithm), } .map_err(|_| CryptoError::InvalidLength)?; Ok(out) } fn generate_aes_key(&self, length_bits: u16) -> Result, CryptoError> { let length_bytes = (length_bits / 8) as usize; if !matches!(length_bits, 128 | 192 | 256) { return Err(CryptoError::InvalidLength); } Ok(random_byte_array(length_bytes)) } fn generate_hmac_key( &self, hash_alg: HashAlgorithm, length_bits: u16, ) -> Result, CryptoError> { let length_bytes = if length_bits == 0 { hash_alg.block_len() } else { (length_bits / 8) as usize }; if length_bytes > 128 { return Err(CryptoError::InvalidLength); } Ok(random_byte_array(length_bytes)) } fn generate_ec_key(&self, curve: EllipticCurve) -> Result<(Vec, Vec), CryptoError> { let mut rng = rand::rng(); match curve { EllipticCurve::P256 => { let key = P256SecretKey::try_generate_from_rng(&mut rng) .map_err(|_| CryptoError::OperationFailed(None))?; let pkcs8 = key .to_pkcs8_der() .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = pkcs8.as_bytes().to_vec(); let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) }, EllipticCurve::P384 => { let key = P384SecretKey::try_generate_from_rng(&mut rng) .map_err(|_| CryptoError::OperationFailed(None))?; let pkcs8 = key .to_pkcs8_der() .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = pkcs8.as_bytes().to_vec(); let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) }, EllipticCurve::P521 => { let key = P521SecretKey::try_generate_from_rng(&mut rng) .map_err(|_| CryptoError::OperationFailed(None))?; let pkcs8 = key .to_pkcs8_der() .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = pkcs8.as_bytes().to_vec(); let public_key = key.public_key().to_sec1_bytes().to_vec(); Ok((private_key, public_key)) }, } } fn generate_ed25519_key(&self) -> Result<(Vec, Vec), CryptoError> { let mut rng = rand::rng(); let private_key = ed25519_dalek::SigningKey::generate(&mut rng) .to_pkcs8_der() .map_err(|_| CryptoError::OperationFailed(None))? .as_bytes() .to_vec(); let signing_key = ed25519_dalek::SigningKey::from_pkcs8_der(&private_key) .map_err(|_| CryptoError::OperationFailed(None))?; let public_key = signing_key.verifying_key().to_bytes().to_vec(); Ok((private_key, public_key)) } fn generate_x25519_key(&self) -> Result<(Vec, Vec), CryptoError> { let mut rng = rand::rng(); let secret_key = x25519_dalek::StaticSecret::random_from_rng(&mut rng); let private_key = secret_key.as_bytes().to_vec(); let public_key = x25519_dalek::PublicKey::from(&secret_key) .as_bytes() .to_vec(); Ok((private_key, public_key)) } fn generate_rsa_key( &self, modulus_length: u32, public_exponent: &[u8], ) -> Result<(Vec, Vec), CryptoError> { let exponent: u64 = match public_exponent { [0x01, 0x00, 0x01] => 65537, [0x03] => 3, bytes if bytes.ends_with(&[0x03]) && bytes[..bytes.len() - 1].iter().all(|&b| b == 0) => { 3 }, _ => return Err(CryptoError::InvalidData(None)), }; let exp = BoxedUint::from(exponent); let mut rng = rand::rng(); let rsa_private_key = RsaPrivateKey::new_with_exp(&mut rng, modulus_length as usize, exp) .map_err(|_| CryptoError::OperationFailed(None))?; let public_key = rsa_private_key .to_public_key() .to_pkcs1_der() .map_err(|_| CryptoError::OperationFailed(None))?; let private_key = rsa_private_key .to_pkcs1_der() .map_err(|_| CryptoError::OperationFailed(None))?; Ok(( private_key.as_bytes().to_vec(), public_key.as_bytes().to_vec(), )) } fn import_rsa_public_key_pkcs1( &self, der: &[u8], ) -> Result { use der::Decode; let public_key = rsa::pkcs1::RsaPublicKey::from_der(der).map_err(|_| CryptoError::InvalidKey(None))?; let modulus_length = public_key.modulus.as_bytes().len() * 8; let public_exponent = public_key.public_exponent.as_bytes().to_vec(); let key_data = public_key .to_der() .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::RsaImportResult { key_data, modulus_length: modulus_length as u32, public_exponent, is_private: false, }) } fn import_rsa_private_key_pkcs1( &self, der: &[u8], ) -> Result { use der::Decode; let private_key = rsa::pkcs1::RsaPrivateKey::from_der(der).map_err(|_| CryptoError::InvalidKey(None))?; let modulus_length = private_key.modulus.as_bytes().len() * 8; let public_exponent = private_key.public_exponent.as_bytes().to_vec(); let key_data = private_key .to_der() .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::RsaImportResult { key_data, modulus_length: modulus_length as u32, public_exponent, is_private: true, }) } fn import_rsa_public_key_spki( &self, der: &[u8], ) -> Result { use der::Decode; let spki = spki::SubjectPublicKeyInfoRef::try_from(der) .map_err(|_| CryptoError::InvalidKey(None))?; let public_key = rsa::pkcs1::RsaPublicKey::from_der(spki.subject_public_key.raw_bytes()) .map_err(|_| CryptoError::InvalidKey(None))?; let modulus_length = public_key.modulus.as_bytes().len() * 8; let public_exponent = public_key.public_exponent.as_bytes().to_vec(); let key_data = public_key .to_der() .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::RsaImportResult { key_data, modulus_length: modulus_length as u32, public_exponent, is_private: false, }) } fn import_rsa_private_key_pkcs8( &self, der: &[u8], ) -> Result { use der::Decode; let pk_info = pkcs8::PrivateKeyInfoRef::from_der(der).map_err(|_| CryptoError::InvalidKey(None))?; let private_key = rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key.as_bytes()) .map_err(|_| CryptoError::InvalidKey(None))?; let modulus_length = private_key.modulus.as_bytes().len() * 8; let public_exponent = private_key.public_exponent.as_bytes().to_vec(); let key_data = pk_info .private_key .to_der() .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::RsaImportResult { key_data, modulus_length: modulus_length as u32, public_exponent, is_private: true, }) } fn export_rsa_public_key_pkcs1(&self, key_data: &[u8]) -> Result, CryptoError> { // key_data is already PKCS1 DER Ok(key_data.to_vec()) } fn export_rsa_public_key_spki(&self, key_data: &[u8]) -> Result, CryptoError> { use der::{Decode, Encode}; let public_key = rsa::pkcs1::RsaPublicKey::from_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; let spki = spki::SubjectPublicKeyInfo { algorithm: spki::AlgorithmIdentifier:: { oid: const_oid::db::rfc5912::RSA_ENCRYPTION, parameters: Some(der::asn1::Null.into()), }, subject_public_key: spki::der::asn1::BitString::from_bytes( &public_key .to_der() .map_err(|_| CryptoError::InvalidKey(None))?, ) .map_err(|_| CryptoError::InvalidKey(None))?, }; spki.to_der().map_err(|_| CryptoError::InvalidKey(None)) } fn export_rsa_private_key_pkcs8(&self, key_data: &[u8]) -> Result, CryptoError> { let private_key = RsaPrivateKey::from_pkcs1_der(key_data).map_err(|_| CryptoError::InvalidKey(None))?; private_key .to_pkcs8_der() .map(|doc| doc.as_bytes().to_vec()) .map_err(|_| CryptoError::InvalidKey(None)) } fn import_ec_public_key_sec1( &self, data: &[u8], _curve: EllipticCurve, ) -> Result { Ok(super::EcImportResult { key_data: data.to_vec(), is_private: false, }) } fn import_ec_public_key_spki(&self, der: &[u8]) -> Result { let spki = spki::SubjectPublicKeyInfoRef::try_from(der) .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::EcImportResult { key_data: spki.subject_public_key.raw_bytes().to_vec(), is_private: false, }) } fn import_ec_private_key_pkcs8( &self, der: &[u8], ) -> Result { Ok(super::EcImportResult { key_data: der.to_vec(), is_private: true, }) } fn import_ec_private_key_sec1( &self, data: &[u8], curve: EllipticCurve, ) -> Result { // Convert SEC1 private key to PKCS8 let pkcs8_der = match curve { EllipticCurve::P256 => { let key = P256SecretKey::from_slice(data).map_err(|_| CryptoError::InvalidKey(None))?; key.to_pkcs8_der() .map_err(|_| CryptoError::InvalidKey(None))? .as_bytes() .to_vec() }, EllipticCurve::P384 => { let key = P384SecretKey::from_slice(data).map_err(|_| CryptoError::InvalidKey(None))?; key.to_pkcs8_der() .map_err(|_| CryptoError::InvalidKey(None))? .as_bytes() .to_vec() }, EllipticCurve::P521 => { let key = P521SecretKey::from_slice(data).map_err(|_| CryptoError::InvalidKey(None))?; key.to_pkcs8_der() .map_err(|_| CryptoError::InvalidKey(None))? .as_bytes() .to_vec() }, }; Ok(super::EcImportResult { key_data: pkcs8_der, is_private: true, }) } fn export_ec_public_key_sec1( &self, key_data: &[u8], curve: EllipticCurve, is_private: bool, ) -> Result, CryptoError> { if is_private { // Extract public key from PKCS8 private key match curve { EllipticCurve::P256 => { let key = P256SecretKey::from_pkcs8_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; Ok(key.public_key().to_sec1_point(false).as_bytes().to_vec()) }, EllipticCurve::P384 => { let key = P384SecretKey::from_pkcs8_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; Ok(key.public_key().to_sec1_point(false).as_bytes().to_vec()) }, EllipticCurve::P521 => { let key = P521SecretKey::from_pkcs8_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; Ok(key.public_key().to_sec1_point(false).as_bytes().to_vec()) }, } } else { // key_data is already SEC1 encoded Ok(key_data.to_vec()) } } fn export_ec_public_key_spki( &self, key_data: &[u8], curve: EllipticCurve, ) -> Result, CryptoError> { use der::Encode; use elliptic_curve::pkcs8::AssociatedOid; let curve_oid = match curve { EllipticCurve::P256 => p256::NistP256::OID, EllipticCurve::P384 => p384::NistP384::OID, EllipticCurve::P521 => p521::NistP521::OID, }; let spki = spki::SubjectPublicKeyInfo { algorithm: spki::AlgorithmIdentifier:: { oid: elliptic_curve::ALGORITHM_OID, parameters: Some(curve_oid), }, subject_public_key: spki::der::asn1::BitString::from_bytes(key_data) .map_err(|_| CryptoError::InvalidKey(None))?, }; spki.to_der().map_err(|_| CryptoError::InvalidKey(None)) } fn export_ec_private_key_pkcs8( &self, key_data: &[u8], _curve: EllipticCurve, ) -> Result, CryptoError> { // key_data is already PKCS8 Ok(key_data.to_vec()) } fn import_okp_public_key_raw( &self, data: &[u8], ) -> Result { if data.len() != 32 { return Err(CryptoError::InvalidKey(None)); } Ok(super::OkpImportResult { key_data: data.to_vec(), is_private: false, }) } fn import_okp_public_key_spki( &self, der: &[u8], _expected_oid: &[u8], ) -> Result { let spki = spki::SubjectPublicKeyInfoRef::try_from(der) .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::OkpImportResult { key_data: spki.subject_public_key.raw_bytes().to_vec(), is_private: false, }) } fn import_okp_private_key_pkcs8( &self, der: &[u8], _expected_oid: &[u8], ) -> Result { Ok(super::OkpImportResult { key_data: der.to_vec(), is_private: true, }) } fn export_okp_public_key_raw( &self, key_data: &[u8], is_private: bool, ) -> Result, CryptoError> { if is_private { // Extract public key from PKCS8 - for X25519/Ed25519 use der::Decode; let pk_info = pkcs8::PrivateKeyInfoRef::from_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; // The private key is wrapped in an OCTET STRING, skip the tag+length (2 bytes) let private_key_bytes = pk_info.private_key.as_bytes(); let seed = if private_key_bytes.len() > 2 && private_key_bytes[0] == 0x04 { &private_key_bytes[2..] } else { private_key_bytes }; let bytes: [u8; 32] = seed.try_into().map_err(|_| CryptoError::InvalidKey(None))?; let secret = x25519_dalek::StaticSecret::from(bytes); let public = x25519_dalek::PublicKey::from(&secret); Ok(public.as_bytes().to_vec()) } else { Ok(key_data.to_vec()) } } fn export_okp_public_key_spki( &self, key_data: &[u8], oid: &[u8], ) -> Result, CryptoError> { use der::Encode; let oid = const_oid::ObjectIdentifier::from_bytes(oid) .map_err(|_| CryptoError::InvalidKey(None))?; let spki = spki::SubjectPublicKeyInfo { algorithm: spki::AlgorithmIdentifierOwned { oid, parameters: None, }, subject_public_key: spki::der::asn1::BitString::from_bytes(key_data) .map_err(|_| CryptoError::InvalidKey(None))?, }; spki.to_der().map_err(|_| CryptoError::InvalidKey(None)) } fn export_okp_private_key_pkcs8( &self, key_data: &[u8], _oid: &[u8], ) -> Result, CryptoError> { // key_data is already PKCS8 Ok(key_data.to_vec()) } fn import_rsa_jwk( &self, jwk: super::RsaJwkImport<'_>, ) -> Result { use der::{asn1::UintRef, Encode}; let modulus = UintRef::new(jwk.n).map_err(|_| CryptoError::InvalidKey(None))?; let public_exponent = UintRef::new(jwk.e).map_err(|_| CryptoError::InvalidKey(None))?; let modulus_length = (modulus.as_bytes().len() * 8) as u32; let pub_exp_bytes = public_exponent.as_bytes().to_vec(); if let (Some(d), Some(p), Some(q), Some(dp), Some(dq), Some(qi)) = (jwk.d, jwk.p, jwk.q, jwk.dp, jwk.dq, jwk.qi) { let private_key = rsa::pkcs1::RsaPrivateKey { modulus, public_exponent, private_exponent: UintRef::new(d).map_err(|_| CryptoError::InvalidKey(None))?, prime1: UintRef::new(p).map_err(|_| CryptoError::InvalidKey(None))?, prime2: UintRef::new(q).map_err(|_| CryptoError::InvalidKey(None))?, exponent1: UintRef::new(dp).map_err(|_| CryptoError::InvalidKey(None))?, exponent2: UintRef::new(dq).map_err(|_| CryptoError::InvalidKey(None))?, coefficient: UintRef::new(qi).map_err(|_| CryptoError::InvalidKey(None))?, other_prime_infos: None, }; Ok(super::RsaImportResult { key_data: private_key .to_der() .map_err(|_| CryptoError::InvalidKey(None))?, modulus_length, public_exponent: pub_exp_bytes, is_private: true, }) } else { let public_key = rsa::pkcs1::RsaPublicKey { modulus, public_exponent, }; Ok(super::RsaImportResult { key_data: public_key .to_der() .map_err(|_| CryptoError::InvalidKey(None))?, modulus_length, public_exponent: pub_exp_bytes, is_private: false, }) } } fn export_rsa_jwk( &self, key_data: &[u8], is_private: bool, ) -> Result { use der::Decode; if is_private { let key = rsa::pkcs1::RsaPrivateKey::from_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::RsaJwkExport { n: key.modulus.as_bytes().to_vec(), e: key.public_exponent.as_bytes().to_vec(), d: Some(key.private_exponent.as_bytes().to_vec()), p: Some(key.prime1.as_bytes().to_vec()), q: Some(key.prime2.as_bytes().to_vec()), dp: Some(key.exponent1.as_bytes().to_vec()), dq: Some(key.exponent2.as_bytes().to_vec()), qi: Some(key.coefficient.as_bytes().to_vec()), }) } else { let key = rsa::pkcs1::RsaPublicKey::from_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::RsaJwkExport { n: key.modulus.as_bytes().to_vec(), e: key.public_exponent.as_bytes().to_vec(), d: None, p: None, q: None, dp: None, dq: None, qi: None, }) } } fn import_ec_jwk( &self, jwk: super::EcJwkImport<'_>, curve: EllipticCurve, ) -> Result { if let Some(d) = jwk.d { // Private key - convert to PKCS8 let pkcs8_der = match curve { EllipticCurve::P256 => { let key = P256SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; key.to_pkcs8_der() .map_err(|_| CryptoError::InvalidKey(None))? .as_bytes() .to_vec() }, EllipticCurve::P384 => { let key = P384SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; key.to_pkcs8_der() .map_err(|_| CryptoError::InvalidKey(None))? .as_bytes() .to_vec() }, EllipticCurve::P521 => { let key = P521SecretKey::from_slice(d).map_err(|_| CryptoError::InvalidKey(None))?; key.to_pkcs8_der() .map_err(|_| CryptoError::InvalidKey(None))? .as_bytes() .to_vec() }, }; Ok(super::EcImportResult { key_data: pkcs8_der, is_private: true, }) } else { // Public key - encode as SEC1 uncompressed point let mut point = Vec::with_capacity(1 + jwk.x.len() + jwk.y.len()); point.push(0x04); // uncompressed point.extend_from_slice(jwk.x); point.extend_from_slice(jwk.y); Ok(super::EcImportResult { key_data: point, is_private: false, }) } } fn export_ec_jwk( &self, key_data: &[u8], curve: EllipticCurve, is_private: bool, ) -> Result { let coord_len = match curve { EllipticCurve::P256 => 32, EllipticCurve::P384 => 48, EllipticCurve::P521 => 66, }; if is_private { // key_data is PKCS8 - use elliptic_curve's SecretKey to parse it let (x, y, d) = match curve { EllipticCurve::P256 => { let sk = P256SecretKey::from_pkcs8_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; let pk = sk.public_key(); let pt = pk.to_sec1_point(false); ( pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec(), sk.to_bytes().to_vec(), ) }, EllipticCurve::P384 => { let sk = P384SecretKey::from_pkcs8_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; let pk = sk.public_key(); let pt = pk.to_sec1_point(false); ( pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec(), sk.to_bytes().to_vec(), ) }, EllipticCurve::P521 => { let sk = P521SecretKey::from_pkcs8_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; let pk = sk.public_key(); let pt = pk.to_sec1_point(false); ( pt.x().unwrap().to_vec(), pt.y().unwrap().to_vec(), sk.to_bytes().to_vec(), ) }, }; Ok(super::EcJwkExport { x, y, d: Some(d) }) } else { // key_data is SEC1 uncompressed point (0x04 || x || y) if key_data.len() != 1 + 2 * coord_len || key_data[0] != 0x04 { return Err(CryptoError::InvalidKey(None)); } let x = key_data[1..1 + coord_len].to_vec(); let y = key_data[1 + coord_len..].to_vec(); Ok(super::EcJwkExport { x, y, d: None }) } } fn import_okp_jwk( &self, jwk: super::OkpJwkImport<'_>, is_ed25519: bool, ) -> Result { if let Some(d) = jwk.d { // Private key - for Ed25519 we need PKCS8, for X25519 we store raw if is_ed25519 { // Ed25519: construct PKCS8 from raw private key use der::{ asn1::{BitStringRef, OctetStringRef}, Encode, }; let pk_info = pkcs8::PrivateKeyInfoRef { algorithm: spki::AlgorithmIdentifier { oid: const_oid::db::rfc8410::ID_ED_25519, parameters: None, }, private_key: OctetStringRef::new(d) .map_err(|_| CryptoError::InvalidKey(None))?, public_key: Some( BitStringRef::from_bytes(jwk.x) .map_err(|_| CryptoError::InvalidKey(None))?, ), }; let der = pk_info .to_der() .map_err(|_| CryptoError::InvalidKey(None))?; Ok(super::OkpImportResult { key_data: der, is_private: true, }) } else { // X25519: store raw 32-byte secret Ok(super::OkpImportResult { key_data: d.to_vec(), is_private: true, }) } } else { // Public key - store raw bytes Ok(super::OkpImportResult { key_data: jwk.x.to_vec(), is_private: false, }) } } fn export_okp_jwk( &self, key_data: &[u8], is_private: bool, is_ed25519: bool, ) -> Result { if is_private { if is_ed25519 { // Ed25519: key_data is PKCS8 use der::Decode; let pk_info = pkcs8::PrivateKeyInfoRef::from_der(key_data) .map_err(|_| CryptoError::InvalidKey(None))?; let d = pk_info.private_key.as_bytes(); let x = pk_info .public_key .ok_or(CryptoError::InvalidKey(None))? .raw_bytes() .to_vec(); Ok(super::OkpJwkExport { x, d: Some(d.to_vec()), }) } else { // X25519: key_data is raw 32-byte secret let secret = x25519_dalek::StaticSecret::from( <[u8; 32]>::try_from(key_data).map_err(|_| CryptoError::InvalidKey(None))?, ); let public = x25519_dalek::PublicKey::from(&secret); Ok(super::OkpJwkExport { x: public.as_bytes().to_vec(), d: Some(key_data.to_vec()), }) } } else { // Public key - key_data is raw bytes Ok(super::OkpJwkExport { x: key_data.to_vec(), d: None, }) } } } ================================================ FILE: modules/llrt_crypto/src/subtle/crypto_key.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::rc::Rc; use llrt_utils::str_enum; use rquickjs::{ atom::PredefinedAtom, class::{Trace, Tracer}, Ctx, Exception, Result, Value, }; use super::key_algorithm::KeyAlgorithm; #[derive(PartialEq, Clone, Copy)] pub enum KeyKind { Secret, Private, Public, } str_enum!(KeyKind,Secret => "secret", Private => "private", Public => "public"); #[rquickjs::class] #[derive(rquickjs::JsLifetime)] pub struct CryptoKey { pub kind: KeyKind, pub extractable: bool, pub algorithm: KeyAlgorithm, pub name: Box, pub usages: Vec, pub handle: Rc<[u8]>, } impl CryptoKey { pub fn new( kind: KeyKind, name: N, extractable: bool, algorithm: KeyAlgorithm, usages: Vec, handle: H, ) -> Self where N: Into>, H: Into>, { Self { kind, extractable, algorithm, name: name.into(), usages, handle: handle.into(), } } } impl<'js> Trace<'js> for CryptoKey { fn trace<'a>(&self, _: Tracer<'a, 'js>) {} } #[rquickjs::methods] impl CryptoKey { #[qjs(constructor)] fn constructor(ctx: Ctx<'_>) -> Result { Err(Exception::throw_type(&ctx, "Illegal constructor")) } #[qjs(get, rename = "type")] pub fn get_type(&self) -> &str { self.kind.as_str() } #[qjs(get)] pub fn extractable(&self) -> bool { self.extractable } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(CryptoKey) } #[qjs(get)] pub fn algorithm<'js>(&self, ctx: Ctx<'js>) -> Result> { self.algorithm .as_object(&ctx, self.name.as_ref()) .map(|a| a.into_value()) } #[qjs(get)] pub fn usages(&self) -> Vec { self.usages.clone() } } impl CryptoKey { pub fn check_validity(&self, usage: &str) -> std::result::Result<(), String> { for key in self.usages.iter() { if key == usage { return Ok(()); } } Err([ "CryptoKey with '", self.name.as_ref(), "', doesn't support '", usage, "'", ] .concat()) } } ================================================ FILE: modules/llrt_crypto/src/subtle/derive.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::result::ResultExt; use rquickjs::{Array, ArrayBuffer, Class, Ctx, Exception, Result, Value}; use crate::{provider::CryptoProvider, subtle::CryptoKey, CRYPTO_PROVIDER}; use super::{ algorithm_mismatch_error, algorithm_not_supported_error, crypto_key::KeyKind, derive_algorithm::DeriveAlgorithm, key_algorithm::{ EcAlgorithm, KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyDerivation, }, }; pub async fn subtle_derive_bits<'js>( ctx: Ctx<'js>, algorithm: DeriveAlgorithm, base_key: Class<'js, CryptoKey>, length: u32, ) -> Result> { let base_key = base_key.borrow(); base_key.check_validity("deriveBits").or_throw(&ctx)?; let bytes = derive_bits(&ctx, &algorithm, &base_key, length)?; ArrayBuffer::new(ctx, bytes) } fn derive_bits( ctx: &Ctx<'_>, algorithm: &DeriveAlgorithm, base_key: &CryptoKey, length: u32, ) -> Result> { match algorithm { DeriveAlgorithm::Ecdh { curve, public_key } => { if let KeyAlgorithm::Ec { curve: base_key_curve, algorithm, } = &base_key.algorithm { if curve == base_key_curve && base_key.kind == KeyKind::Private && matches!(algorithm, EcAlgorithm::Ecdh) { let handle = &base_key.handle; return CRYPTO_PROVIDER .ecdh_derive_bits(*curve, handle, public_key) .or_throw(ctx); } return Err(Exception::throw_message( ctx, "ECDH curve must be same as baseKey", )); } algorithm_mismatch_error(ctx, "ECDH") }, DeriveAlgorithm::X25519 { public_key } => { if !matches!(base_key.algorithm, KeyAlgorithm::X25519) { return algorithm_mismatch_error(ctx, "X25519"); } CRYPTO_PROVIDER .x25519_derive_bits(&base_key.handle, public_key) .or_throw(ctx) }, DeriveAlgorithm::Derive(KeyDerivation::Hkdf { hash, salt, info }) => { if !matches!(base_key.algorithm, KeyAlgorithm::HkdfImport) { return algorithm_mismatch_error(ctx, "HKDF"); } let out_length = (length / 8).try_into().or_throw(ctx)?; CRYPTO_PROVIDER .hkdf_derive_key(&base_key.handle, salt, info, out_length, *hash) .or_throw(ctx) }, DeriveAlgorithm::Derive(KeyDerivation::Pbkdf2 { hash, salt, iterations, }) => { if !matches!(base_key.algorithm, KeyAlgorithm::Pbkdf2Import) { return algorithm_mismatch_error(ctx, "PBKDF2"); } let out_length = (length / 8).try_into().or_throw(ctx)?; CRYPTO_PROVIDER .pbkdf2_derive_key(&base_key.handle, salt, *iterations, out_length, *hash) .or_throw(ctx) }, } } pub async fn subtle_derive_key<'js>( ctx: Ctx<'js>, algorithm: DeriveAlgorithm, base_key: Class<'js, CryptoKey>, derived_key_algorithm: Value<'js>, extractable: bool, key_usages: Array<'js>, ) -> Result> { let KeyAlgorithmWithUsages { algorithm: derived_key_algorithm, name, public_usages, .. } = KeyAlgorithm::from_js( &ctx, KeyAlgorithmMode::Derive, derived_key_algorithm, key_usages, )?; let length = match &derived_key_algorithm { KeyAlgorithm::Aes { length } => *length, KeyAlgorithm::Hmac { length, .. } => *length, KeyAlgorithm::Derive { .. } => 0, _ => { return algorithm_not_supported_error(&ctx); }, }; let base_key = &base_key.borrow(); let bytes = derive_bits(&ctx, &algorithm, base_key, length as u32)?; let key = CryptoKey::new( KeyKind::Secret, name, extractable, derived_key_algorithm, public_usages, bytes, ); Class::instance(ctx, key) } ================================================ FILE: modules/llrt_crypto/src/subtle/derive_algorithm.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::rc::Rc; use llrt_utils::object::ObjectExt; use rquickjs::{Class, Ctx, Exception, FromJs, Result, Value}; use super::{ algorithm_mismatch_error, key_algorithm::{KeyAlgorithm, KeyDerivation}, CryptoKey, EllipticCurve, }; #[derive(Debug)] pub enum DeriveAlgorithm { X25519 { public_key: Rc<[u8]>, }, Ecdh { curve: EllipticCurve, public_key: Rc<[u8]>, }, Derive(KeyDerivation), } impl<'js> FromJs<'js> for DeriveAlgorithm { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let obj = value.into_object_or_throw(ctx, "algorithm")?; let name: String = obj.get_required("name", "algorithm")?; Ok(match name.as_str() { "X25519" => { let public_key: Class = obj.get_required("public", "algorithm")?; let public_key = public_key.borrow(); if !matches!(public_key.algorithm, KeyAlgorithm::X25519) { return algorithm_mismatch_error(ctx, &name); } DeriveAlgorithm::X25519 { public_key: public_key.handle.clone(), } }, "ECDH" => { let public_key: Class = obj.get_required("public", "algorithm")?; let public_key = public_key.borrow(); if let KeyAlgorithm::Ec { curve, .. } = &public_key.algorithm { DeriveAlgorithm::Ecdh { curve: *curve, public_key: public_key.handle.clone(), } } else { return algorithm_mismatch_error(ctx, &name); } }, "HKDF" => DeriveAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), "PBKDF2" => DeriveAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), _ => { return Err(Exception::throw_message( ctx, "Algorithm 'name' must be X25519 | ECDH | HKDF | PBKDF2", )) }, }) } } ================================================ FILE: modules/llrt_crypto/src/subtle/digest.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{ArrayBuffer, Ctx, Result, Value}; use crate::{ hash::HashAlgorithm, provider::{CryptoProvider, SimpleDigest}, CRYPTO_PROVIDER, }; pub async fn subtle_digest<'js>( ctx: Ctx<'js>, algorithm: Value<'js>, data: ObjectBytes<'js>, ) -> Result> { let algorithm = if let Some(algorithm) = algorithm.as_string() { algorithm.to_string().or_throw(&ctx)? } else { algorithm.get_required::<_, String>("name", "algorithm")? }; let hash_algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?; let bytes = digest(&hash_algorithm, data.as_bytes(&ctx)?); ArrayBuffer::new(ctx, bytes) } pub fn digest(hash_algorithm: &HashAlgorithm, data: &[u8]) -> Vec { let mut hasher = CRYPTO_PROVIDER.digest(*hash_algorithm); hasher.update(data); hasher.finalize() } ================================================ FILE: modules/llrt_crypto/src/subtle/encryption.rs ================================================ use std::borrow::Cow; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Result}; use crate::{ provider::{AesMode, CryptoProvider}, CRYPTO_PROVIDER, }; use super::{ algorithm_mismatch_error, encryption_algorithm::EncryptionAlgorithm, key_algorithm::KeyAlgorithm, validate_aes_length, CryptoKey, EncryptionMode, }; pub async fn subtle_decrypt<'js>( ctx: Ctx<'js>, algorithm: EncryptionAlgorithm, key: Class<'js, CryptoKey>, data: ObjectBytes<'js>, ) -> Result> { let key = key.borrow(); key.check_validity("decrypt").or_throw(&ctx)?; let bytes = encrypt_decrypt( &ctx, &algorithm, &key, data.as_bytes(&ctx)?, EncryptionMode::Encryption, EncryptionOperation::Decrypt, )?; ArrayBuffer::new(ctx, bytes) } pub async fn subtle_encrypt<'js>( ctx: Ctx<'js>, algorithm: EncryptionAlgorithm, key: Class<'js, CryptoKey>, data: ObjectBytes<'js>, ) -> Result> { let key = key.borrow(); key.check_validity("encrypt").or_throw(&ctx)?; let bytes = encrypt_decrypt( &ctx, &algorithm, &key, data.as_bytes(&ctx)?, EncryptionMode::Encryption, EncryptionOperation::Encrypt, )?; ArrayBuffer::new(ctx, bytes) } pub enum EncryptionOperation { Encrypt, Decrypt, } pub fn encrypt_decrypt( ctx: &Ctx<'_>, algorithm: &EncryptionAlgorithm, key: &CryptoKey, data: &[u8], mode: EncryptionMode, operation: EncryptionOperation, ) -> Result> { let handle = key.handle.as_ref(); let bytes = match algorithm { EncryptionAlgorithm::AesCbc { iv } => { validate_aes_length(ctx, key, handle, "AES-CBC")?; match operation { EncryptionOperation::Encrypt => CRYPTO_PROVIDER .aes_encrypt(AesMode::Cbc, handle, iv, data, None) .or_throw(ctx)?, EncryptionOperation::Decrypt => CRYPTO_PROVIDER .aes_decrypt(AesMode::Cbc, handle, iv, data, None) .or_throw(ctx)?, } }, EncryptionAlgorithm::AesCtr { counter, length: encryption_length, } => { validate_aes_length(ctx, key, handle, "AES-CTR")?; match operation { EncryptionOperation::Encrypt => CRYPTO_PROVIDER .aes_encrypt( AesMode::Ctr { counter_length: *encryption_length, }, handle, counter, data, None, ) .or_throw(ctx)?, EncryptionOperation::Decrypt => CRYPTO_PROVIDER .aes_decrypt( AesMode::Ctr { counter_length: *encryption_length, }, handle, counter, data, None, ) .or_throw(ctx)?, } }, EncryptionAlgorithm::AesGcm { iv, tag_length, additional_data, } => { validate_aes_length(ctx, key, handle, "AES-GCM")?; let aad = additional_data.as_deref(); match operation { EncryptionOperation::Encrypt => CRYPTO_PROVIDER .aes_encrypt( AesMode::Gcm { tag_length: *tag_length, }, handle, iv, data, aad, ) .or_throw(ctx)?, EncryptionOperation::Decrypt => { let tag_len = (*tag_length as usize) / 8; if data.len() < tag_len { return Err(Exception::throw_message(ctx, "Invalid ciphertext length")); } // Pass the full data (ciphertext + tag) to the decrypt function CRYPTO_PROVIDER .aes_decrypt( AesMode::Gcm { tag_length: *tag_length, }, handle, iv, data, aad, ) .or_throw(ctx)? }, } }, EncryptionAlgorithm::AesKw => { let padding = match mode { EncryptionMode::Encryption => { return Err(Exception::throw_message( ctx, "AES-KW can only be used for wrapping keys", )); }, EncryptionMode::Wrapping(padding) => padding, }; match operation { EncryptionOperation::Encrypt => { // Pad data to multiple of 8 bytes if needed let mut padded_data = Cow::Borrowed(data); if !data.len().is_multiple_of(8) { let pad_len = 8 - (data.len() % 8); let mut padded = data.to_vec(); padded.extend(std::iter::repeat_n(padding, pad_len)); padded_data = Cow::Owned(padded) } CRYPTO_PROVIDER .aes_kw_wrap(handle, &padded_data) .or_throw(ctx)? }, EncryptionOperation::Decrypt => { let unwrapped = CRYPTO_PROVIDER.aes_kw_unwrap(handle, data).or_throw(ctx)?; // Remove padding if present if padding != 0 { let trimmed: Vec = unwrapped .into_iter() .rev() .skip_while(|&b| b == padding) .collect::>() .into_iter() .rev() .collect(); trimmed } else { unwrapped } }, } }, EncryptionAlgorithm::RsaOaep { label } => { let hash = match &key.algorithm { KeyAlgorithm::Rsa { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, "RSA-OAEP"), }; match operation { EncryptionOperation::Encrypt => CRYPTO_PROVIDER .rsa_oaep_encrypt(handle, data, *hash, label.as_deref()) .or_throw(ctx)?, EncryptionOperation::Decrypt => CRYPTO_PROVIDER .rsa_oaep_decrypt(handle, data, *hash, label.as_deref()) .or_throw(ctx)?, } }, }; Ok(bytes) } ================================================ FILE: modules/llrt_crypto/src/subtle/encryption_algorithm.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{bytes::ObjectBytes, object::ObjectExt}; use rquickjs::{Ctx, Exception, FromJs, Result, Value}; use super::algorithm_not_supported_error; #[derive(Debug)] pub enum EncryptionAlgorithm { AesCbc { iv: Box<[u8]>, }, AesCtr { counter: Box<[u8]>, length: u32, }, AesGcm { iv: Box<[u8]>, tag_length: u8, additional_data: Option>, }, RsaOaep { label: Option>, }, AesKw, } impl<'js> FromJs<'js> for EncryptionAlgorithm { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let obj = value.into_object_or_throw(ctx, "algorithm")?; let name: String = obj.get_required("name", "algorithm")?; match name.as_str() { "AES-CBC" => { let iv = obj .get_required::<_, ObjectBytes>("iv", "algorithm")? .into_bytes(ctx)? .into_boxed_slice(); if iv.len() != 16 { return Err(Exception::throw_message( ctx, "invalid length of iv. Currently supported 16 bytes", )); } Ok(EncryptionAlgorithm::AesCbc { iv }) }, "AES-CTR" => { let counter = obj .get_required::<_, ObjectBytes>("counter", "algorithm")? .into_bytes(ctx)? .into_boxed_slice(); let length = obj.get_required::<_, u32>("length", "algorithm")?; if !matches!(length, 32 | 64 | 128) { return Err(Exception::throw_message( ctx, "invalid counter length. Currently supported 32/64/128 bits", )); } Ok(EncryptionAlgorithm::AesCtr { counter, length }) }, "AES-GCM" => { let iv = obj .get_required::<_, ObjectBytes>("iv", "algorithm")? .into_bytes(ctx)? .into_boxed_slice(); //FIXME only 12? 96 maybe recommended? if iv.len() != 12 { return Err(Exception::throw_type( ctx, "invalid length of iv. Currently supported 12 bytes", )); } let additional_data = obj .get_optional::<_, ObjectBytes>("additionalData")? .map(|v| v.into_bytes(ctx)) .transpose()? .map(|vec| vec.into_boxed_slice()); let tag_length = obj.get_optional::<_, u8>("tagLength")?.unwrap_or(128); //ensure tag length is supported using a match statement 32, 64, 96, 104, 112, 120, or 128 if !matches!(tag_length, 96 | 104 | 112 | 120 | 128) { return Err(Exception::throw_message( ctx, "Invalid tagLength. Currently supported 96/104/112/120/128 bits", )); } Ok(EncryptionAlgorithm::AesGcm { iv, additional_data, tag_length, }) }, "RSA-OAEP" => { let label = obj .get_optional::<_, ObjectBytes>("label")? .map(|bytes| bytes.into_bytes(ctx)) .transpose()? .map(|vec| vec.into_boxed_slice()); Ok(EncryptionAlgorithm::RsaOaep { label }) }, "AES-KW" => Ok(EncryptionAlgorithm::AesKw), _ => algorithm_not_supported_error(ctx), } } } ================================================ FILE: modules/llrt_crypto/src/subtle/export_key.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! Unified key export implementation using CryptoProvider trait. use llrt_encoding::bytes_to_b64_url_safe_string; use llrt_utils::result::ResultExt; use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Object, Result}; use crate::provider::CryptoProvider; use crate::CRYPTO_PROVIDER; use super::{ crypto_key::KeyKind, key_algorithm::{KeyAlgorithm, KeyFormat}, CryptoKey, }; pub fn algorithm_export_error(ctx: &Ctx<'_>, algorithm: &str, format: &str) -> Result { Err(Exception::throw_message( ctx, &["Export of ", algorithm, " as ", format, " is not supported"].concat(), )) } pub enum ExportOutput<'js> { Bytes(Vec), Object(Object<'js>), } pub async fn subtle_export_key<'js>( ctx: Ctx<'js>, format: KeyFormat, key: Class<'js, CryptoKey>, ) -> Result> { let key = key.borrow(); let export = export_key(&ctx, format, &key)?; Ok(match export { ExportOutput::Bytes(bytes) => ArrayBuffer::new(ctx, bytes)?.into_object(), ExportOutput::Object(object) => object, }) } pub fn export_key<'js>( ctx: &Ctx<'js>, format: KeyFormat, key: &CryptoKey, ) -> Result> { if !key.extractable { return Err(Exception::throw_type( ctx, "The CryptoKey is non extractable", )); } let bytes = match format { KeyFormat::Jwk => return Ok(ExportOutput::Object(export_jwk(ctx, key)?)), KeyFormat::Raw => export_raw(ctx, key), KeyFormat::Spki => export_spki(ctx, key), KeyFormat::Pkcs8 => export_pkcs8(ctx, key), }?; Ok(ExportOutput::Bytes(bytes)) } fn export_raw(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { if key.kind == KeyKind::Private { return Err(Exception::throw_type( ctx, "Private Crypto keys can't be exported as raw format", )); } match &key.algorithm { KeyAlgorithm::Aes { .. } | KeyAlgorithm::Hmac { .. } => Ok(key.handle.to_vec()), KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER .export_ec_public_key_sec1(&key.handle, *curve, false) .or_throw(ctx), KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER .export_okp_public_key_raw(&key.handle, false) .or_throw(ctx), KeyAlgorithm::X25519 => CRYPTO_PROVIDER .export_okp_public_key_raw(&key.handle, false) .or_throw(ctx), KeyAlgorithm::Rsa { .. } => CRYPTO_PROVIDER .export_rsa_public_key_pkcs1(&key.handle) .or_throw(ctx), _ => algorithm_export_error(ctx, &key.name, "raw"), } } fn export_pkcs8(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { if key.kind != KeyKind::Private { return Err(Exception::throw_type( ctx, "Public or Secret Crypto keys can't be exported as pkcs8 format", )); } match &key.algorithm { KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER .export_ec_private_key_pkcs8(&key.handle, *curve) .or_throw(ctx), KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER .export_okp_private_key_pkcs8( &key.handle, const_oid::db::rfc8410::ID_ED_25519.as_bytes(), ) .or_throw(ctx), KeyAlgorithm::X25519 => CRYPTO_PROVIDER .export_okp_private_key_pkcs8( &key.handle, const_oid::db::rfc8410::ID_X_25519.as_bytes(), ) .or_throw(ctx), KeyAlgorithm::Rsa { .. } => CRYPTO_PROVIDER .export_rsa_private_key_pkcs8(&key.handle) .or_throw(ctx), _ => algorithm_export_error(ctx, &key.name, "pkcs8"), } } fn export_spki(ctx: &Ctx<'_>, key: &CryptoKey) -> Result> { if key.kind != KeyKind::Public { return Err(Exception::throw_type( ctx, "Private or Secret Crypto keys can't be exported as spki format", )); } match &key.algorithm { KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER .export_ec_public_key_spki(&key.handle, *curve) .or_throw(ctx), KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER .export_okp_public_key_spki(&key.handle, const_oid::db::rfc8410::ID_ED_25519.as_bytes()) .or_throw(ctx), KeyAlgorithm::X25519 => CRYPTO_PROVIDER .export_okp_public_key_spki(&key.handle, const_oid::db::rfc8410::ID_X_25519.as_bytes()) .or_throw(ctx), KeyAlgorithm::Rsa { .. } => CRYPTO_PROVIDER .export_rsa_public_key_spki(&key.handle) .or_throw(ctx), _ => algorithm_export_error(ctx, &key.name, "spki"), } } fn export_jwk<'js>(ctx: &Ctx<'js>, key: &CryptoKey) -> Result> { let obj = Object::new(ctx.clone())?; obj.set("key_ops", key.usages())?; obj.set("ext", true)?; match &key.algorithm { KeyAlgorithm::Aes { length } => { let prefix = match length { 128 => "A128", 192 => "A192", 256 => "A256", _ => unreachable!(), }; let suffix = &key.name[("AES-".len())..]; obj.set("kty", "oct")?; obj.set("k", bytes_to_b64_url_safe_string(&key.handle))?; obj.set("alg", [prefix, suffix].concat())?; }, KeyAlgorithm::Hmac { hash, .. } => { obj.set("kty", "oct")?; obj.set("alg", ["HS", &hash.as_str()[4..]].concat())?; obj.set("k", bytes_to_b64_url_safe_string(&key.handle))?; }, KeyAlgorithm::Ec { curve, .. } => { let jwk = CRYPTO_PROVIDER .export_ec_jwk(&key.handle, *curve, key.kind == KeyKind::Private) .or_throw(ctx)?; obj.set("kty", "EC")?; obj.set("crv", curve.as_str())?; obj.set("x", bytes_to_b64_url_safe_string(&jwk.x))?; obj.set("y", bytes_to_b64_url_safe_string(&jwk.y))?; if let Some(d) = jwk.d { obj.set("d", bytes_to_b64_url_safe_string(&d))?; } }, KeyAlgorithm::Ed25519 => { let jwk = CRYPTO_PROVIDER .export_okp_jwk(&key.handle, key.kind == KeyKind::Private, true) .or_throw(ctx)?; obj.set("kty", "OKP")?; obj.set("crv", "Ed25519")?; obj.set("x", bytes_to_b64_url_safe_string(&jwk.x))?; if let Some(d) = jwk.d { obj.set("d", bytes_to_b64_url_safe_string(&d))?; } }, KeyAlgorithm::X25519 => { let jwk = CRYPTO_PROVIDER .export_okp_jwk(&key.handle, key.kind == KeyKind::Private, false) .or_throw(ctx)?; obj.set("kty", "OKP")?; obj.set("crv", "X25519")?; obj.set("x", bytes_to_b64_url_safe_string(&jwk.x))?; if let Some(d) = jwk.d { obj.set("d", bytes_to_b64_url_safe_string(&d))?; } }, KeyAlgorithm::Rsa { hash, .. } => { let jwk = CRYPTO_PROVIDER .export_rsa_jwk(&key.handle, key.kind == KeyKind::Private) .or_throw(ctx)?; let alg_suffix = hash.as_numeric_str(); let alg_prefix = match key.name.as_ref() { "RSASSA-PKCS1-v1_5" => "RS", "RSA-PSS" => "PS", "RSA-OAEP" => "RSA-OAEP-", _ => unreachable!(), }; obj.set("kty", "RSA")?; obj.set("n", bytes_to_b64_url_safe_string(&jwk.n))?; obj.set("e", bytes_to_b64_url_safe_string(&jwk.e))?; obj.set("alg", [alg_prefix, alg_suffix].concat())?; if let Some(d) = jwk.d { obj.set("d", bytes_to_b64_url_safe_string(&d))?; obj.set("p", bytes_to_b64_url_safe_string(&jwk.p.unwrap()))?; obj.set("q", bytes_to_b64_url_safe_string(&jwk.q.unwrap()))?; obj.set("dp", bytes_to_b64_url_safe_string(&jwk.dp.unwrap()))?; obj.set("dq", bytes_to_b64_url_safe_string(&jwk.dq.unwrap()))?; obj.set("qi", bytes_to_b64_url_safe_string(&jwk.qi.unwrap()))?; } }, _ => return algorithm_export_error(ctx, &key.name, "jwk"), } Ok(obj) } ================================================ FILE: modules/llrt_crypto/src/subtle/generate_key.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{object::Property, Array, Class, Ctx, Exception, Object, Result, Value}; use crate::{provider::CryptoProvider, CRYPTO_PROVIDER}; use crate::{hash::HashAlgorithm, subtle::CryptoKey}; use super::{ algorithm_not_supported_error, crypto_key::KeyKind, key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages}, }; pub async fn subtle_generate_key<'js>( ctx: Ctx<'js>, algorithm: Value<'js>, extractable: bool, key_usages: Array<'js>, ) -> Result> { let KeyAlgorithmWithUsages { name, algorithm: key_algorithm, private_usages, public_usages, } = KeyAlgorithm::from_js(&ctx, KeyAlgorithmMode::Generate, algorithm, key_usages)?; let (private_key, public_or_secret_key) = generate_key(&ctx, &key_algorithm)?; if matches!( key_algorithm, KeyAlgorithm::Aes { .. } | KeyAlgorithm::Hmac { .. } ) { return Ok(Class::instance( ctx, CryptoKey::new( KeyKind::Secret, name, extractable, key_algorithm, public_usages, public_or_secret_key, ), )? .into_value()); } let private_key = Class::instance( ctx.clone(), CryptoKey::new( KeyKind::Private, name.clone(), extractable, key_algorithm.clone(), private_usages, private_key, ), )?; let public_key = Class::instance( ctx.clone(), CryptoKey::new( KeyKind::Public, name, extractable, key_algorithm, public_usages, public_or_secret_key, ), )?; let key_pair = Object::new(ctx.clone())?; key_pair.prop("privateKey", Property::from(private_key).enumerable())?; key_pair.prop("publicKey", Property::from(public_key).enumerable())?; Ok(key_pair.into_value()) } fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result<(Vec, Vec)> { match algorithm { KeyAlgorithm::Aes { length } => { // Default to AES-256 let key = CRYPTO_PROVIDER.generate_aes_key(*length).map_err(|e| { Exception::throw_message(ctx, &format!("AES key generation failed: {}", e)) })?; Ok((vec![], key)) }, KeyAlgorithm::Hmac { hash, length } => { let key = CRYPTO_PROVIDER .generate_hmac_key(*hash, *length) .map_err(|e| { Exception::throw_message(ctx, &format!("HMAC key generation failed: {}", e)) })?; Ok((vec![], key)) }, KeyAlgorithm::Ec { curve, .. } => CRYPTO_PROVIDER.generate_ec_key(*curve).map_err(|e| { Exception::throw_message(ctx, &format!("EC key generation failed: {}", e)) }), KeyAlgorithm::Ed25519 => CRYPTO_PROVIDER.generate_ed25519_key().map_err(|e| { Exception::throw_message(ctx, &format!("Ed25519 key generation failed: {}", e)) }), KeyAlgorithm::X25519 => CRYPTO_PROVIDER.generate_x25519_key().map_err(|e| { Exception::throw_message(ctx, &format!("X25519 key generation failed: {}", e)) }), KeyAlgorithm::Rsa { modulus_length, public_exponent, .. } => CRYPTO_PROVIDER .generate_rsa_key(*modulus_length, public_exponent.as_ref()) .map_err(|e| { Exception::throw_message(ctx, &format!("RSA key generation failed: {}", e)) }), _ => algorithm_not_supported_error(ctx), } } #[allow(dead_code)] fn generate_symmetric_key(_ctx: &Ctx<'_>, length: usize) -> Result> { Ok(crate::random_byte_array(length)) } #[allow(dead_code)] pub fn get_hash_length(ctx: &Ctx, hash: &HashAlgorithm, length: u16) -> Result { if length == 0 { return Ok(hash.block_len()); } if !length.is_multiple_of(8) || (length / 8) as usize > 128 { return Err(Exception::throw_message(ctx, "Invalid HMAC key length")); } Ok((length / 8) as usize) } ================================================ FILE: modules/llrt_crypto/src/subtle/import_key.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{bytes::ObjectBytes, object::ObjectExt}; use rquickjs::{Array, Class, Ctx, FromJs, Result, Value}; use crate::subtle::CryptoKey; use super::{ crypto_key::KeyKind, key_algorithm::{ KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyFormat, KeyFormatData, }, }; pub async fn subtle_import_key<'js>( ctx: Ctx<'js>, format: KeyFormat, key_data: Value<'js>, algorithm: Value<'js>, extractable: bool, key_usages: Array<'js>, ) -> Result> { let format = match format { KeyFormat::Raw => KeyFormatData::Raw(ObjectBytes::from_js(&ctx, key_data)?), KeyFormat::Pkcs8 => KeyFormatData::Pkcs8(ObjectBytes::from_js(&ctx, key_data)?), KeyFormat::Spki => KeyFormatData::Spki(ObjectBytes::from_js(&ctx, key_data)?), KeyFormat::Jwk => KeyFormatData::Jwk(key_data.into_object_or_throw(&ctx, "keyData")?), }; import_key(ctx, format, algorithm, extractable, key_usages) } pub fn import_key<'js>( ctx: Ctx<'js>, format: KeyFormatData<'js>, algorithm: Value<'js>, extractable: bool, key_usages: Array<'js>, ) -> Result> { let mut kind = KeyKind::Public; let mut data = Vec::new(); let KeyAlgorithmWithUsages { name, algorithm: key_algorithm, public_usages, private_usages, } = KeyAlgorithm::from_js( &ctx, KeyAlgorithmMode::Import { kind: &mut kind, data: &mut data, format, }, algorithm, key_usages, )?; let usages = match kind { KeyKind::Public | KeyKind::Secret => public_usages, KeyKind::Private => private_usages, }; Class::instance( ctx, CryptoKey::new(kind, name, extractable, key_algorithm, usages, data), ) } ================================================ FILE: modules/llrt_crypto/src/subtle/key_algorithm.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::rc::Rc; #[cfg(feature = "_subtle-full")] use der::{asn1::OctetStringRef, Decode, Encode}; #[cfg(feature = "_subtle-full")] use llrt_encoding::bytes_from_b64_url_safe; use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt, str_enum}; #[cfg(feature = "_subtle-full")] use pkcs8::PrivateKeyInfoRef; use rquickjs::{ atom::PredefinedAtom, Array, Ctx, Exception, FromJs, Object, Result, TypedArray, Value, }; #[cfg(feature = "_subtle-full")] use spki::{AlgorithmIdentifier, ObjectIdentifier}; use crate::hash::HashAlgorithm; #[cfg(feature = "_subtle-full")] use super::algorithm_mismatch_error; use super::{ algorithm_not_supported_error, crypto_key::KeyKind, to_name_and_maybe_object, EllipticCurve, }; #[derive(Clone, Copy, PartialEq)] pub enum KeyUsage { //7 values, can be max 255 (u8) 0b11111111 Encrypt, Decrypt, WrapKey, UnwrapKey, Sign, Verify, DeriveKey, DeriveBits, } impl TryFrom<&str> for KeyUsage { type Error = String; fn try_from(s: &str) -> std::result::Result { Ok(match s { "encrypt" => KeyUsage::Encrypt, "decrypt" => KeyUsage::Decrypt, "wrapKey" => KeyUsage::WrapKey, "unwrapKey" => KeyUsage::UnwrapKey, "sign" => KeyUsage::Sign, "verify" => KeyUsage::Verify, "deriveKey" => KeyUsage::DeriveKey, "deriveBits" => KeyUsage::DeriveBits, _ => return Err(["Invalid key usage: ", s].concat()), }) } } impl KeyUsage { fn classify_and_check_usages<'js>( ctx: &Ctx<'js>, key_usage_algorithm: KeyUsageAlgorithm, key_usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, kind: Option<&KeyKind>, ) -> Result<()> { let (mut private_usages_mask, mut public_usages_mask) = key_usage_algorithm.masks(); match kind { Some(KeyKind::Private) => public_usages_mask = 0, Some(KeyKind::Secret) | Some(KeyKind::Public) => private_usages_mask = 0, None => {}, }; let allowed_usages = private_usages_mask | public_usages_mask; let mut generated_public_usages = Vec::with_capacity(4); let mut generated_private_usages = Vec::with_capacity(4); let mut has_any_usages = false; for usage in key_usages.iter::() { has_any_usages = true; let value = usage?; let usage = KeyUsage::try_from(value.as_str()).or_throw(ctx)?; let usage = usage.mask(); if allowed_usages & usage != usage { return Err(Exception::throw_message( ctx, &["Invalid key usage '", &value, "'"].concat(), )); } if private_usages_mask == public_usages_mask { generated_private_usages.push(value.clone()); generated_public_usages.push(value); } else if private_usages_mask & usage == usage { generated_private_usages.push(value); } else if public_usages_mask & usage == usage { generated_public_usages.push(value); } } *private_usages = generated_private_usages; *public_usages = generated_public_usages; if !has_any_usages { return Err(Exception::throw_message(ctx, "Key usages empty")); } if private_usages_mask > 0 && private_usages.is_empty() { return Err(Exception::throw_message( ctx, "No required private key usages provided", )); } if private_usages != public_usages { let valid_usage = match kind { Some(KeyKind::Secret) | Some(KeyKind::Public) => { private_usages.is_empty() && !public_usages.is_empty() }, Some(KeyKind::Private) => !private_usages.is_empty() && public_usages.is_empty(), None => true, }; if !valid_usage { return Err(Exception::throw_message(ctx, "Invalid key usage")); } } Ok(()) } const fn mask(self) -> u16 { 1 << self as u16 } } #[repr(u16)] #[derive(Clone, Copy)] pub enum KeyUsageAlgorithm { //single mask algorithms (symmetric) AesKw = KeyUsage::WrapKey.mask() | KeyUsage::UnwrapKey.mask(), //all non-KW AES Symmetric = (KeyUsage::Encrypt.mask()) | (KeyUsage::Decrypt.mask()) | (KeyUsage::WrapKey.mask()) | (KeyUsage::UnwrapKey.mask()), Hmac = (KeyUsage::Sign.mask()) | (KeyUsage::Verify.mask()), //two mask algorithms (asymmetric) - use high bits as for private, low bits for public //HKDF, PBKDF2, X25519 Derive = ((KeyUsage::DeriveKey.mask() | KeyUsage::DeriveBits.mask()) << 8) | KeyUsage::DeriveKey.mask() | KeyUsage::DeriveBits.mask(), RsaOaep = ((KeyUsage::Decrypt.mask() | KeyUsage::UnwrapKey.mask()) << 8) //private | KeyUsage::Encrypt.mask() | KeyUsage::WrapKey.mask(), //public //ECDSA, ED25519, all non-OEAP RSA Sign = (KeyUsage::Sign.mask() << 8) //private | KeyUsage::Verify.mask(), //public } impl KeyUsageAlgorithm { fn masks(&self) -> (u16, u16) { let value = *self as u16; let private_mask = value >> 8; let public_mask = value & 0xFF; (private_mask, public_mask) } } #[derive(Debug, Clone)] pub enum KeyDerivation { Hkdf { hash: HashAlgorithm, salt: Box<[u8]>, info: Box<[u8]>, }, Pbkdf2 { hash: HashAlgorithm, salt: Box<[u8]>, iterations: u32, }, } impl KeyDerivation { pub fn for_hkdf_object<'js>(ctx: &Ctx<'js>, obj: Object<'js>) -> Result { let hash = extract_sha_hash(ctx, &obj)?; let salt = obj .get_required::<_, ObjectBytes>("salt", "algorithm")? .into_bytes(ctx)? .into_boxed_slice(); let info = obj .get_required::<_, ObjectBytes>("info", "algorithm")? .into_bytes(ctx)? .into_boxed_slice(); Ok(KeyDerivation::Hkdf { hash, salt, info }) } pub fn for_pbkf2_object<'js>(ctx: &&Ctx<'js>, obj: Object<'js>) -> Result { let hash = extract_sha_hash(ctx, &obj)?; let salt = obj .get_required::<_, ObjectBytes>("salt", "algorithm")? .into_bytes(ctx)? .into_boxed_slice(); let iterations = obj.get_required("iterations", "algorithm")?; Ok(KeyDerivation::Pbkdf2 { hash, salt, iterations, }) } } #[derive(Debug, Clone)] pub enum EcAlgorithm { Ecdh, Ecdsa, } #[derive(Debug, Clone)] pub enum KeyAlgorithm { Aes { length: u16, }, Ec { curve: EllipticCurve, algorithm: EcAlgorithm, }, X25519, Ed25519, Hmac { hash: HashAlgorithm, length: u16, }, Rsa { modulus_length: u32, public_exponent: Rc>, hash: HashAlgorithm, }, Derive(KeyDerivation), HkdfImport, Pbkdf2Import, } pub enum KeyFormat { Jwk, Raw, Spki, Pkcs8, } str_enum!(KeyFormat, Jwk => "jwk", Raw => "raw", Spki => "spki", Pkcs8 => "pkcs8"); impl<'js> FromJs<'js> for KeyFormat { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { if let Some(string) = value.as_string() { let string = string.to_string()?; match string.as_str() { "jwk" => return Ok(KeyFormat::Jwk), "raw" => return Ok(KeyFormat::Raw), "spki" => return Ok(KeyFormat::Spki), "pkcs8" => return Ok(KeyFormat::Pkcs8), _ => {}, }; } Err(Exception::throw_message( ctx, "Key import/export format must be 'jwk','raw','spki' or 'pkcs8'", )) } } pub enum KeyFormatData<'js> { Jwk(Object<'js>), Raw(ObjectBytes<'js>), Spki(ObjectBytes<'js>), Pkcs8(ObjectBytes<'js>), } pub enum KeyAlgorithmMode<'a, 'js> { Import { format: KeyFormatData<'js>, kind: &'a mut KeyKind, data: &'a mut Vec, }, Generate, Derive, } pub struct KeyAlgorithmWithUsages { pub name: String, pub algorithm: KeyAlgorithm, pub public_usages: Vec, pub private_usages: Vec, } fn from_ed25519<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, algorithm_name: &str, usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, ) -> Result { #[cfg(feature = "_subtle-full")] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, algorithm_name: &str, ) -> Result> { if let KeyAlgorithmMode::Import { format, kind, data } = mode { import_okp_key( ctx, format, kind, data, const_oid::db::rfc8410::ID_ED_25519, algorithm_name, )?; Ok(Some(*kind)) } else { Ok(None) } } #[cfg(not(feature = "_subtle-full"))] #[inline] fn import<'js>( _ctx: &Ctx<'js>, _mode: KeyAlgorithmMode<'_, 'js>, _algorithm_name: &str, ) -> Result> { Ok(None) } let key_kind = import(ctx, mode, algorithm_name)?; KeyUsage::classify_and_check_usages( ctx, KeyUsageAlgorithm::Sign, usages, private_usages, public_usages, key_kind.as_ref(), )?; Ok(KeyAlgorithm::Ed25519) } fn from_x25519<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, algorithm_name: &str, usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, ) -> Result { #[cfg(feature = "_subtle-full")] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, algorithm_name: &str, ) -> Result> { if let KeyAlgorithmMode::Import { format, kind, data } = mode { import_okp_key( ctx, format, kind, data, const_oid::db::rfc8410::ID_X_25519, algorithm_name, )?; Ok(Some(*kind)) } else { Ok(None) } } #[cfg(not(feature = "_subtle-full"))] #[inline] fn import<'js>( _ctx: &Ctx<'js>, _mode: KeyAlgorithmMode<'_, 'js>, _algorithm_name: &str, ) -> Result> { Ok(None) } let key_kind = import(ctx, mode, algorithm_name)?; KeyUsage::classify_and_check_usages( ctx, KeyUsageAlgorithm::Derive, usages, private_usages, public_usages, key_kind.as_ref(), )?; Ok(KeyAlgorithm::X25519) } fn from_aes<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, ) -> Result { #[cfg(feature = "_subtle-full")] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, ) -> Result<(u16, Option)> { if let KeyAlgorithmMode::Import { data, format, kind } = mode { let length = import_symmetric_key(ctx, format, kind, data, algorithm_name, None)? as u16; Ok((length, Some(*kind))) } else { let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; Ok((length, None)) } } #[cfg(not(feature = "_subtle-full"))] #[inline] fn import<'js>( ctx: &Ctx<'js>, _mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, _algorithm_name: &str, ) -> Result<(u16, Option)> { let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; Ok((length, None)) } let (length, key_kind) = import(ctx, mode, obj, algorithm_name)?; if !matches!(length, 128 | 192 | 256) { return Err(Exception::throw_message( ctx, &format!( "Algorithm 'length' must be one of: 128, 192, or 256 = {}", length ), )); } KeyUsage::classify_and_check_usages( ctx, if algorithm_name == "AES-KW" { KeyUsageAlgorithm::AesKw } else { KeyUsageAlgorithm::Symmetric }, usages, private_usages, public_usages, key_kind.as_ref(), )?; Ok(KeyAlgorithm::Aes { length }) } fn from_hmac<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, ) -> Result { let obj = obj.or_throw(ctx)?; let hash = extract_sha_hash(ctx, &obj)?; let mut length: u16 = obj.get_optional("length")?.unwrap_or_default(); #[cfg(feature = "_subtle-full")] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, algorithm_name: &str, hash: &HashAlgorithm, length: &mut u16, ) -> Result> { if let KeyAlgorithmMode::Import { data, format, kind } = mode { let data_length = import_symmetric_key(ctx, format, kind, data, algorithm_name, Some(hash))?; if *length == 0 { *length = data_length as u16; } Ok(Some(*kind)) } else { Ok(None) } } #[cfg(not(feature = "_subtle-full"))] #[inline] fn import<'js>( _ctx: &Ctx<'js>, _mode: KeyAlgorithmMode<'_, 'js>, _algorithm_name: &str, _hash: &HashAlgorithm, _length: &mut u16, ) -> Result> { Ok(None) } let key_kind = import(ctx, mode, algorithm_name, &hash, &mut length)?; KeyUsage::classify_and_check_usages( ctx, KeyUsageAlgorithm::Hmac, usages, private_usages, public_usages, key_kind.as_ref(), )?; Ok(KeyAlgorithm::Hmac { hash, length }) } fn from_rsa<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, ) -> Result { let obj = obj.or_throw(ctx)?; let hash = extract_sha_hash(ctx, &obj)?; #[cfg(feature = "_subtle-full")] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: &Object<'js>, algorithm_name: &str, hash: &HashAlgorithm, ) -> Result<(u32, Box<[u8]>, Option)> { if let KeyAlgorithmMode::Import { format, kind, data } = mode { let (mod_length, exp) = import_rsa_key(ctx, format, kind, data, algorithm_name, hash)?; Ok((mod_length, exp, Some(*kind))) } else { let modulus_length = obj.get_required("modulusLength", "algorithm")?; let public_exponent: TypedArray = obj.get_required("publicExponent", "algorithm")?; let public_exponent = public_exponent .as_bytes() .ok_or_else(|| Exception::throw_message(ctx, "Array buffer has been detached"))? .to_owned() .into_boxed_slice(); Ok((modulus_length, public_exponent, None)) } } #[cfg(not(feature = "_subtle-full"))] #[inline] fn import<'js>( ctx: &Ctx<'js>, _mode: KeyAlgorithmMode<'_, 'js>, obj: &Object<'js>, _algorithm_name: &str, _hash: &HashAlgorithm, ) -> Result<(u32, Box<[u8]>, Option)> { let modulus_length = obj.get_required("modulusLength", "algorithm")?; let public_exponent: TypedArray = obj.get_required("publicExponent", "algorithm")?; let public_exponent = public_exponent .as_bytes() .ok_or_else(|| Exception::throw_message(ctx, "Array buffer has been detached"))? .to_owned() .into_boxed_slice(); Ok((modulus_length, public_exponent, None)) } let (modulus_length, public_exponent, key_kind) = import(ctx, mode, &obj, algorithm_name, &hash)?; KeyUsage::classify_and_check_usages( ctx, if algorithm_name == "RSA-OAEP" { KeyUsageAlgorithm::RsaOaep } else { KeyUsageAlgorithm::Sign }, usages, private_usages, public_usages, key_kind.as_ref(), )?; Ok(KeyAlgorithm::Rsa { modulus_length, public_exponent: Rc::new(public_exponent), hash, }) } fn from_hkdf<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, ) -> Result { #[cfg(feature = "_subtle-full")] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, ) -> Result<(KeyAlgorithm, Option)> { match mode { KeyAlgorithmMode::Import { format, kind, data } => { import_derive_key(ctx, format, kind, data, algorithm_name)?; Ok((KeyAlgorithm::HkdfImport, Some(*kind))) }, KeyAlgorithmMode::Derive => { let obj = obj.or_throw(ctx)?; Ok(( KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), None, )) }, _ => algorithm_not_supported_error(ctx), } } #[cfg(not(feature = "_subtle-full"))] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, _algorithm_name: &str, ) -> Result<(KeyAlgorithm, Option)> { match mode { KeyAlgorithmMode::Derive => { let obj = obj.or_throw(ctx)?; Ok(( KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), None, )) }, _ => algorithm_not_supported_error(ctx), } } let (algorithm, key_kind) = import(ctx, mode, obj, algorithm_name)?; KeyUsage::classify_and_check_usages( ctx, KeyUsageAlgorithm::Derive, usages, private_usages, public_usages, key_kind.as_ref(), )?; Ok(algorithm) } fn from_pbkdf2<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, ) -> Result { #[cfg(feature = "_subtle-full")] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, algorithm_name: &str, ) -> Result<(KeyAlgorithm, Option)> { match mode { KeyAlgorithmMode::Import { format, kind, data } => { import_derive_key(ctx, format, kind, data, algorithm_name)?; Ok((KeyAlgorithm::Pbkdf2Import, Some(*kind))) }, KeyAlgorithmMode::Derive => { let obj = obj.or_throw(ctx)?; Ok(( KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), None, )) }, _ => algorithm_not_supported_error(ctx), } } #[cfg(not(feature = "_subtle-full"))] #[inline] fn import<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, _algorithm_name: &str, ) -> Result<(KeyAlgorithm, Option)> { match mode { KeyAlgorithmMode::Derive => { let obj = obj.or_throw(ctx)?; Ok(( KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), None, )) }, _ => algorithm_not_supported_error(ctx), } } let (algorithm, key_kind) = import(ctx, mode, obj, algorithm_name)?; KeyUsage::classify_and_check_usages( ctx, KeyUsageAlgorithm::Derive, usages, private_usages, public_usages, key_kind.as_ref(), )?; Ok(algorithm) } impl KeyAlgorithm { pub fn from_js<'js>( ctx: &Ctx<'js>, mode: KeyAlgorithmMode<'_, 'js>, value: Value<'js>, usages: Array<'js>, ) -> Result { // When _subtle-full is not enabled, Import mode is not supported #[cfg(not(feature = "_subtle-full"))] if matches!(mode, KeyAlgorithmMode::Import { .. }) { return Err(Exception::throw_message( ctx, "Key import is not supported with this crypto provider", )); } let (name, obj) = to_name_and_maybe_object(ctx, value)?; let mut public_usages = vec![]; let mut private_usages = vec![]; let algorithm_name = name.as_str(); let algorithm = match algorithm_name { "Ed25519" => from_ed25519( ctx, mode, algorithm_name, &usages, &mut private_usages, &mut public_usages, )?, "X25519" => from_x25519( ctx, mode, algorithm_name, &usages, &mut private_usages, &mut public_usages, )?, "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => from_aes( ctx, mode, obj, algorithm_name, &usages, &mut private_usages, &mut public_usages, )?, "ECDH" => Self::from_ec( ctx, mode, obj, algorithm_name, EcAlgorithm::Ecdh, &usages, &mut private_usages, &mut public_usages, KeyUsageAlgorithm::Derive, )?, "ECDSA" => Self::from_ec( ctx, mode, obj, algorithm_name, EcAlgorithm::Ecdsa, &usages, &mut private_usages, &mut public_usages, KeyUsageAlgorithm::Sign, )?, "HMAC" => from_hmac( ctx, mode, obj, algorithm_name, &usages, &mut private_usages, &mut public_usages, )?, "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-v1_5" => from_rsa( ctx, mode, obj, algorithm_name, &usages, &mut private_usages, &mut public_usages, )?, "HKDF" => from_hkdf( ctx, mode, obj, algorithm_name, &usages, &mut private_usages, &mut public_usages, )?, "PBKDF2" => from_pbkdf2( ctx, mode, obj, algorithm_name, &usages, &mut private_usages, &mut public_usages, )?, _ => return algorithm_not_supported_error(ctx), }; Ok(KeyAlgorithmWithUsages { name, algorithm, public_usages, private_usages, }) } pub fn as_object<'js, T: AsRef>(&self, ctx: &Ctx<'js>, name: T) -> Result> { let obj = Object::new(ctx.clone())?; obj.set(PredefinedAtom::Name, name.as_ref())?; match self { KeyAlgorithm::Aes { length } => { obj.set(PredefinedAtom::Length, length)?; }, KeyAlgorithm::Ec { curve, .. } => { obj.set("namedCurve", curve.as_str())?; }, KeyAlgorithm::Hmac { hash, length } => { let hash_obj = create_hash_object(ctx, hash)?; obj.set("hash", hash_obj)?; obj.set(PredefinedAtom::Length, length)?; }, KeyAlgorithm::Rsa { modulus_length, public_exponent, hash, } => { let public_exponent = public_exponent.as_ref().to_vec(); let array = TypedArray::new(ctx.clone(), public_exponent)?; let hash_obj = create_hash_object(ctx, hash)?; obj.set("hash", hash_obj)?; obj.set("modulusLength", modulus_length)?; obj.set("publicExponent", array)?; }, KeyAlgorithm::Derive(KeyDerivation::Hkdf { hash, salt, info }) => { let salt = TypedArray::::new(ctx.clone(), salt.to_vec())?; let info = TypedArray::::new(ctx.clone(), info.to_vec())?; obj.set("hash", hash.as_str())?; obj.set("salt", salt)?; obj.set("info", info)?; }, KeyAlgorithm::Derive(KeyDerivation::Pbkdf2 { hash, salt, iterations, }) => { let salt = TypedArray::::new(ctx.clone(), salt.to_vec())?; obj.set("hash", hash.as_str())?; obj.set("salt", salt)?; obj.set("iterations", iterations)?; }, _ => {}, }; Ok(obj) } #[allow(clippy::too_many_arguments)] fn from_ec<'js>( ctx: &Ctx<'js>, #[allow(unused_variables)] mode: KeyAlgorithmMode<'_, 'js>, obj: std::result::Result, &str>, #[allow(unused_variables)] algorithm_name: &str, algorithm: EcAlgorithm, key_usages: &Array<'js>, private_usages: &mut Vec, public_usages: &mut Vec, key_usage_algorithm: KeyUsageAlgorithm, ) -> Result { let obj = obj.or_throw(ctx)?; let curve_name: String = obj.get_required("namedCurve", "algorithm")?; let curve = EllipticCurve::try_from(curve_name.as_str()).or_throw(ctx)?; #[cfg(feature = "_subtle-full")] let key_kind = if let KeyAlgorithmMode::Import { format, kind, data } = mode { import_ec_key(ctx, format, kind, data, algorithm_name, &curve, &curve_name)?; Some(kind) } else { None }; #[cfg(not(feature = "_subtle-full"))] let key_kind: Option<&KeyKind> = None; KeyUsage::classify_and_check_usages( ctx, key_usage_algorithm, key_usages, private_usages, public_usages, key_kind.as_deref(), )?; Ok(KeyAlgorithm::Ec { curve, algorithm }) } } #[cfg(feature = "_subtle-full")] fn import_derive_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, ) -> Result<()> { if let KeyFormatData::Raw(object_bytes) = format { *data = object_bytes.into_bytes(ctx)?; *kind = KeyKind::Secret; } else { return Err(Exception::throw_message( ctx, &[algorithm_name, " only supports 'raw' import format"].concat(), )); } Ok(()) } #[cfg(feature = "_subtle-full")] fn import_rsa_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, hash: &HashAlgorithm, ) -> Result<(u32, Box<[u8]>)> { use crate::{ provider::{CryptoProvider, RsaJwkImport}, CRYPTO_PROVIDER, }; let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { if other_oid != const_oid::db::rfc5912::RSA_ENCRYPTION { return algorithm_mismatch_error(ctx, algorithm_name); } Ok(()) }; let (modulus_length, public_exponent) = match format { KeyFormatData::Jwk(object) => { let kty: String = object.get_required("kty", "keyData")?; let alg: String = object.get_required("alg", "keyData")?; if kty != "RSA" { return algorithm_mismatch_error(ctx, algorithm_name); } let prefix = &alg[..2]; let numeric_hash_str = match prefix { "RS" => { if algorithm_name == "RSA-OAEP" { if !alg.starts_with(algorithm_name) { return algorithm_mismatch_error(ctx, algorithm_name); } &alg["RSA-OAEP-".len()..] } else if algorithm_name != "RSASSA-PKCS1-v1_5" { return algorithm_mismatch_error(ctx, algorithm_name); } else { &alg["RS".len()..] } }, "PS" => { if algorithm_name != "RSA-PSS" { return algorithm_mismatch_error(ctx, algorithm_name); } &alg["PS".len()..] }, _ => return algorithm_mismatch_error(ctx, algorithm_name), }; if numeric_hash_str != hash.as_numeric_str() { return hash_mismatch_error(ctx, hash); } let n: String = object.get_required("n", "keyData")?; let e: String = object.get_required("e", "keyData")?; let n_bytes = bytes_from_b64_url_safe(n.as_bytes()).or_throw(ctx)?; let e_bytes = bytes_from_b64_url_safe(e.as_bytes()).or_throw(ctx)?; let result = if let Some(d) = object.get_optional::<_, String>("d")? { let p: String = object.get_required("p", "keyData")?; let q: String = object.get_required("q", "keyData")?; let dp: String = object.get_required("dp", "keyData")?; let dq: String = object.get_required("dq", "keyData")?; let qi: String = object.get_required("qi", "keyData")?; let d_bytes = bytes_from_b64_url_safe(d.as_bytes()).or_throw(ctx)?; let p_bytes = bytes_from_b64_url_safe(p.as_bytes()).or_throw(ctx)?; let q_bytes = bytes_from_b64_url_safe(q.as_bytes()).or_throw(ctx)?; let dp_bytes = bytes_from_b64_url_safe(dp.as_bytes()).or_throw(ctx)?; let dq_bytes = bytes_from_b64_url_safe(dq.as_bytes()).or_throw(ctx)?; let qi_bytes = bytes_from_b64_url_safe(qi.as_bytes()).or_throw(ctx)?; let jwk = RsaJwkImport { n: &n_bytes, e: &e_bytes, d: Some(&d_bytes), p: Some(&p_bytes), q: Some(&q_bytes), dp: Some(&dp_bytes), dq: Some(&dq_bytes), qi: Some(&qi_bytes), }; CRYPTO_PROVIDER.import_rsa_jwk(jwk).or_throw(ctx)? } else { let jwk = RsaJwkImport { n: &n_bytes, e: &e_bytes, d: None, p: None, q: None, dp: None, dq: None, qi: None, }; CRYPTO_PROVIDER.import_rsa_jwk(jwk).or_throw(ctx)? }; *data = result.key_data; *kind = if result.is_private { KeyKind::Private } else { KeyKind::Public }; (result.modulus_length as usize, result.public_exponent) }, KeyFormatData::Raw(object_bytes) => { let result = CRYPTO_PROVIDER .import_rsa_public_key_pkcs1(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; *data = result.key_data; *kind = KeyKind::Public; (result.modulus_length as usize, result.public_exponent) }, KeyFormatData::Pkcs8(object_bytes) => { let pk_info = PrivateKeyInfoRef::from_der(object_bytes.as_bytes(ctx)?).or_throw(ctx)?; validate_oid(pk_info.algorithm.oid)?; let result = CRYPTO_PROVIDER .import_rsa_private_key_pkcs8(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; *data = result.key_data; *kind = KeyKind::Private; (result.modulus_length as usize, result.public_exponent) }, KeyFormatData::Spki(object_bytes) => { let pk_info = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; validate_oid(pk_info.algorithm.oid)?; let result = CRYPTO_PROVIDER .import_rsa_public_key_spki(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; *data = result.key_data; *kind = KeyKind::Public; (result.modulus_length as usize, result.public_exponent) }, }; let public_exponent = public_exponent.into_boxed_slice(); Ok((modulus_length as u32, public_exponent)) } #[cfg(feature = "_subtle-full")] fn import_symmetric_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, hash: Option<&HashAlgorithm>, ) -> Result { *kind = KeyKind::Secret; match format { KeyFormatData::Jwk(object) => { let kty: String = object.get_required("kty", "keyData")?; if kty == "oct" { let k: String = object.get_required("k", "keyData")?; let alg: String = object.get_required("alg", "keyData")?; let prefix = &alg[..1]; match (prefix, hash) { //HMAC - HS256, HS512 etc ("H", Some(hash)) => { if &alg[2..] != hash.as_numeric_str() { return hash_mismatch_error(ctx, hash); } }, //AES - A256KW, A256GCM, A256CRT, A512CBC etc ("A", None) => { //extract AES-{suffix} let (_, name_suffix) = algorithm_name.split_once("-").unwrap_or_default(); let aes_variant = &alg[4..]; if aes_variant != name_suffix { return algorithm_mismatch_error(ctx, algorithm_name); } }, _ => return algorithm_mismatch_error(ctx, algorithm_name), } *data = bytes_from_b64_url_safe(k.as_bytes()).or_throw(ctx)?; return Ok(data.len() * 8); } }, KeyFormatData::Raw(object_bytes) => { let bytes = object_bytes.into_bytes(ctx)?; *data = bytes; return Ok(data.len() * 8); }, _ => {}, } algorithm_mismatch_error(ctx, algorithm_name) } // EC algorithm OID for validation #[cfg(feature = "_subtle-full")] const EC_ALGORITHM_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); #[cfg(feature = "_subtle-full")] fn import_ec_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, kind: &mut KeyKind, data: &mut Vec, algorithm_name: &str, curve: &EllipticCurve, curve_name: &str, ) -> Result<()> { use crate::{ provider::{CryptoProvider, EcJwkImport}, CRYPTO_PROVIDER, }; let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { if other_oid != EC_ALGORITHM_OID { return algorithm_mismatch_error(ctx, algorithm_name); } Ok(()) }; // Get expected coordinate length for the curve let coord_len = match curve { EllipticCurve::P256 => 32, EllipticCurve::P384 => 48, EllipticCurve::P521 => 66, }; match format { KeyFormatData::Jwk(object) => { let kty: String = object.get_required("kty", "keyData")?; if kty != "EC" { return algorithm_mismatch_error(ctx, algorithm_name); } let jwk_crv: String = object.get_required("crv", "keyData")?; if curve_name != jwk_crv { return Err(Exception::throw_type( ctx, &["Key is using a ", curve_name].concat(), )); } let x: String = object.get_required("x", "keyData")?; let y: String = object.get_required("y", "keyData")?; let mut x_bytes = bytes_from_b64_url_safe(x.as_bytes()).or_throw(ctx)?; let mut y_bytes = bytes_from_b64_url_safe(y.as_bytes()).or_throw(ctx)?; // Pad to coordinate length if needed if x_bytes.len() < coord_len { let mut padded = vec![0u8; coord_len - x_bytes.len()]; padded.extend_from_slice(&x_bytes); x_bytes = padded; } if y_bytes.len() < coord_len { let mut padded = vec![0u8; coord_len - y_bytes.len()]; padded.extend_from_slice(&y_bytes); y_bytes = padded; } let d_bytes = if let Some(d) = object.get_optional::<_, String>("d")? { let mut d_bytes = bytes_from_b64_url_safe(d.as_bytes()).or_throw(ctx)?; if d_bytes.len() < coord_len { let mut padded = vec![0u8; coord_len - d_bytes.len()]; padded.extend_from_slice(&d_bytes); d_bytes = padded; } Some(d_bytes) } else { None }; let jwk = EcJwkImport { x: &x_bytes, y: &y_bytes, d: d_bytes.as_deref(), }; let result = CRYPTO_PROVIDER.import_ec_jwk(jwk, *curve).or_throw(ctx)?; *data = result.key_data; *kind = if result.is_private { KeyKind::Private } else { KeyKind::Public }; }, KeyFormatData::Raw(object_bytes) => { let bytes = object_bytes.as_bytes(ctx)?; let result = CRYPTO_PROVIDER .import_ec_public_key_sec1(bytes, *curve) .or_throw(ctx)?; *data = result.key_data; *kind = KeyKind::Public; }, KeyFormatData::Spki(object_bytes) => { let spki = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; validate_oid(spki.algorithm.oid)?; let result = CRYPTO_PROVIDER .import_ec_public_key_spki(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; *data = result.key_data; *kind = KeyKind::Public; }, KeyFormatData::Pkcs8(object_bytes) => { let pkcs8 = PrivateKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?).or_throw(ctx)?; validate_oid(pkcs8.algorithm.oid)?; let result = CRYPTO_PROVIDER .import_ec_private_key_pkcs8(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; *data = result.key_data; *kind = KeyKind::Private; }, }; Ok(()) } #[cfg(feature = "_subtle-full")] fn import_okp_key<'js>( ctx: &Ctx<'js>, format: KeyFormatData<'js>, kind: &mut KeyKind, data: &mut Vec, oid: ObjectIdentifier, algorithm_name: &str, ) -> Result<()> { let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { if other_oid != oid { return algorithm_mismatch_error(ctx, algorithm_name); } Ok(()) }; match format { KeyFormatData::Jwk(object) => { let crv: String = object.get_required("crv", "keyData")?; if crv != algorithm_name { return algorithm_mismatch_error(ctx, algorithm_name); } let x: String = object.get_required("x", "keyData")?; let public_key = bytes_from_b64_url_safe(x.as_bytes()).or_throw(ctx)?; if let Some(d) = object.get_optional::<_, String>("d")? { let private_key = bytes_from_b64_url_safe(d.as_bytes()).or_throw(ctx)?; let pk_info = PrivateKeyInfoRef::new( AlgorithmIdentifier { oid, parameters: None, }, OctetStringRef::new(private_key.as_slice()).or_throw(ctx)?, ); *data = pk_info.to_der().or_throw(ctx)?; *kind = KeyKind::Private; } else { *data = public_key; *kind = KeyKind::Public; } }, KeyFormatData::Raw(object_bytes) => { let bytes = object_bytes.into_bytes(ctx)?; if bytes.len() != 32 { return Err(Exception::throw_type( ctx, &[algorithm_name, " keys must be 32 bytes long"].concat(), )); } *data = bytes; *kind = KeyKind::Public; }, KeyFormatData::Spki(object_bytes) => { let spki = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) .or_throw(ctx)?; validate_oid(spki.algorithm.oid)?; *data = spki.subject_public_key.raw_bytes().into(); *kind = KeyKind::Public; }, KeyFormatData::Pkcs8(object_bytes) => { let pkcs8 = PrivateKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?).or_throw(ctx)?; validate_oid(pkcs8.algorithm.oid)?; *data = object_bytes.into_bytes(ctx)?; *kind = KeyKind::Private; }, }; Ok(()) } pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result { let hash: Value = obj.get_required("hash", "algorithm")?; let hash = if let Some(string) = hash.as_string() { string.to_string() } else if let Some(obj) = hash.into_object() { obj.get_required("name", "hash") } else { return Err(Exception::throw_message( ctx, "hash must be a string or an object", )); }?; HashAlgorithm::try_from(hash.as_str()).or_throw(ctx) } fn create_hash_object<'js>(ctx: &Ctx<'js>, hash: &HashAlgorithm) -> Result> { let hash_obj = Object::new(ctx.clone())?; hash_obj.set(PredefinedAtom::Name, hash.as_str())?; Ok(hash_obj) } #[cfg(feature = "_subtle-full")] pub fn hash_mismatch_error(ctx: &Ctx<'_>, hash: &HashAlgorithm) -> Result { Err(Exception::throw_message( ctx, &["Algorithm hash expected to be ", hash.as_str()].concat(), )) } ================================================ FILE: modules/llrt_crypto/src/subtle/mod.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 mod crypto_key; mod derive; mod derive_algorithm; mod digest; mod encryption; mod encryption_algorithm; #[cfg(feature = "_subtle-full")] mod export_key; mod generate_key; #[cfg(feature = "_subtle-full")] mod import_key; #[cfg(feature = "_subtle-full")] mod key_algorithm; mod sign; mod sign_algorithm; mod verify; #[cfg(feature = "_subtle-full")] mod wrapping; pub use crypto_key::CryptoKey; pub use derive::subtle_derive_bits; pub use derive::subtle_derive_key; pub use digest::subtle_digest; pub use encryption::subtle_decrypt; pub use encryption::subtle_encrypt; #[cfg(feature = "_subtle-full")] pub use export_key::subtle_export_key; pub use generate_key::subtle_generate_key; #[cfg(feature = "_subtle-full")] pub use import_key::subtle_import_key; #[cfg(feature = "_subtle-full")] use key_algorithm::KeyAlgorithm; pub use sign::subtle_sign; pub use verify::subtle_verify; #[cfg(feature = "_subtle-full")] pub use wrapping::subtle_unwrap_key; #[cfg(feature = "_subtle-full")] pub use wrapping::subtle_wrap_key; // Stub implementations for limited crypto providers (no _subtle-full) #[cfg(not(feature = "_subtle-full"))] mod key_algorithm; #[cfg(not(feature = "_subtle-full"))] use key_algorithm::KeyAlgorithm; use llrt_utils::{object::ObjectExt, str_enum}; use rquickjs::{atom::PredefinedAtom, Ctx, Exception, Object, Result, Value}; use crate::provider::{CryptoProvider, SimpleDigest}; use crate::hash::HashAlgorithm; #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] pub struct SubtleCrypto {} #[rquickjs::methods] impl SubtleCrypto { #[qjs(constructor)] pub fn new(ctx: Ctx<'_>) -> Result { Err(Exception::throw_type(&ctx, "Illegal constructor")) } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(SubtleCrypto) } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum EllipticCurve { P256, P384, P521, } str_enum!(EllipticCurve,P256 => "P-256", P384 => "P-384", P521 => "P-521"); pub enum EncryptionMode { Encryption, #[allow(dead_code)] Wrapping(u8), //padding byte } pub fn rsa_hash_digest<'a>( ctx: &Ctx<'_>, key: &'a CryptoKey, data: &'a [u8], algorithm_name: &str, ) -> Result<(&'a HashAlgorithm, Vec)> { let hash = match &key.algorithm { KeyAlgorithm::Rsa { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, algorithm_name), }; if !matches!( hash, HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 ) { return Err(Exception::throw_message( ctx, "Only Sha-256, Sha-384 or Sha-512 is supported for RSA", )); } let mut hasher = crate::CRYPTO_PROVIDER.digest(*hash); hasher.update(data); let digest = hasher.finalize(); Ok((hash, digest)) } pub fn validate_aes_length( ctx: &Ctx<'_>, key: &CryptoKey, handle: &[u8], expected_algorithm: &str, ) -> Result<()> { let length = match key.algorithm { KeyAlgorithm::Aes { length } => length, _ => return algorithm_mismatch_error(ctx, expected_algorithm), }; if length != handle.len() as u16 * 8 { return Err(Exception::throw_message( ctx, &[ "Invalid key handle length for ", expected_algorithm, ". Expected ", &length.to_string(), " bits, found ", &handle.len().to_string(), " bits", ] .concat(), )); } Ok(()) } pub fn to_name_and_maybe_object<'js, 'a>( ctx: &Ctx<'js>, value: Value<'js>, ) -> Result<(String, std::result::Result, &'a str>)> { let obj; let name = if let Some(string) = value.as_string() { obj = Err("Not an object"); string.to_string()? } else if let Some(object) = value.into_object() { let name = object.get_required("name", "algorithm")?; obj = Ok(object); name } else { return Err(Exception::throw_message( ctx, "algorithm must be a string or an object", )); }; Ok((name, obj)) } pub fn algorithm_mismatch_error(ctx: &Ctx<'_>, expected_algorithm: &str) -> Result { Err(Exception::throw_message( ctx, &["Key algorithm must be ", expected_algorithm].concat(), )) } pub fn algorithm_not_supported_error(ctx: &Ctx<'_>) -> Result { Err(Exception::throw_message(ctx, "Algorithm not supported")) } // Stub implementations for providers without _subtle-full #[cfg(not(feature = "_subtle-full"))] mod stubs; #[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_export_key; #[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_import_key; #[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_unwrap_key; #[cfg(not(feature = "_subtle-full"))] pub use stubs::subtle_wrap_key; ================================================ FILE: modules/llrt_crypto/src/subtle/sign.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use crate::provider::{CryptoProvider, HmacProvider}; use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; use rquickjs::{ArrayBuffer, Class, Ctx, Result}; use crate::{subtle::CryptoKey, CRYPTO_PROVIDER}; use super::{ algorithm_mismatch_error, key_algorithm::KeyAlgorithm, rsa_hash_digest, sign_algorithm::SigningAlgorithm, }; pub async fn subtle_sign<'js>( ctx: Ctx<'js>, algorithm: SigningAlgorithm, key: Class<'js, CryptoKey>, data: ObjectBytes<'js>, ) -> Result> { let key = key.borrow(); key.check_validity("sign").or_throw(&ctx)?; let bytes = sign(&ctx, &algorithm, &key, data.as_bytes(&ctx)?)?; ArrayBuffer::new(ctx, bytes) } fn sign( ctx: &Ctx<'_>, algorithm: &SigningAlgorithm, key: &CryptoKey, data: &[u8], ) -> Result> { let handle = key.handle.as_ref(); Ok(match algorithm { SigningAlgorithm::Ecdsa { hash } => { let curve = match &key.algorithm { KeyAlgorithm::Ec { curve, .. } => curve, _ => return algorithm_mismatch_error(ctx, "ECDSA"), }; let digest = crate::subtle::digest::digest(hash, data); crate::CRYPTO_PROVIDER .ecdsa_sign(*curve, handle, &digest) .or_throw(ctx)? }, SigningAlgorithm::Ed25519 => { if !matches!(&key.algorithm, KeyAlgorithm::Ed25519) { return algorithm_mismatch_error(ctx, "Ed25519"); } crate::CRYPTO_PROVIDER .ed25519_sign(handle, data) .or_throw(ctx)? }, SigningAlgorithm::Hmac => { let hash = if let KeyAlgorithm::Hmac { hash, .. } = &key.algorithm { hash } else { return algorithm_mismatch_error(ctx, "HMAC"); }; let mut hmac = CRYPTO_PROVIDER.hmac(*hash, handle); hmac.update(data); hmac.finalize() }, SigningAlgorithm::RsaPss { salt_length } => { let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSA-PSS")?; crate::CRYPTO_PROVIDER .rsa_pss_sign(&key.handle, digest.as_ref(), *salt_length as usize, *hash) .or_throw(ctx)? }, SigningAlgorithm::RsassaPkcs1v15 => { let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSASSA-PKCS1-v1_5")?; crate::CRYPTO_PROVIDER .rsa_pkcs1v15_sign(&key.handle, digest.as_ref(), *hash) .or_throw(ctx)? }, }) } // // Helper function for RSA signing // fn rsa_sign( // ctx: &Ctx<'_>, // key: &CryptoKey, // algorithm_name: &str, // data: &[u8], // sign_fn: F, // ) -> Result> // where // F: FnOnce(&HashAlgorithm, &[u8], &rsa::RsaPrivateKey) -> Result>, // { // let (hash, digest) = rsa_hash_digest(ctx, key, data, algorithm_name)?; // sign_fn(hash, digest.as_ref()) // } ================================================ FILE: modules/llrt_crypto/src/subtle/sign_algorithm.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{object::ObjectExt, result::ResultExt}; use rquickjs::{Ctx, FromJs, Result, Value}; use crate::hash::HashAlgorithm; use super::{ algorithm_not_supported_error, key_algorithm::extract_sha_hash, to_name_and_maybe_object, }; #[derive(Debug)] pub enum SigningAlgorithm { Ecdsa { hash: HashAlgorithm }, Ed25519, RsaPss { salt_length: u32 }, RsassaPkcs1v15, Hmac, } impl<'js> FromJs<'js> for SigningAlgorithm { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let (name, obj) = to_name_and_maybe_object(ctx, value)?; let algorithm = match name.as_str() { "Ed25519" => SigningAlgorithm::Ed25519, "HMAC" => SigningAlgorithm::Hmac, "RSASSA-PKCS1-v1_5" => SigningAlgorithm::RsassaPkcs1v15, "ECDSA" => { let obj = obj.or_throw(ctx)?; let hash = extract_sha_hash(ctx, &obj)?; SigningAlgorithm::Ecdsa { hash } }, "RSA-PSS" => { let salt_length = obj.or_throw(ctx)?.get_required("saltLength", "algorithm")?; SigningAlgorithm::RsaPss { salt_length } }, _ => return algorithm_not_supported_error(ctx), }; Ok(algorithm) } } ================================================ FILE: modules/llrt_crypto/src/subtle/stubs.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! Stub implementations for SubtleCrypto operations when `_rustcrypto` feature is disabled. //! These return errors indicating the operation is not supported. use rquickjs::{Ctx, Exception, Object, Result, Value}; use super::crypto_key::CryptoKey; use super::encryption_algorithm; use super::key_algorithm; pub async fn subtle_export_key<'js>( ctx: Ctx<'js>, _format: key_algorithm::KeyFormat, _key: rquickjs::Class<'js, CryptoKey>, ) -> Result> { Err(Exception::throw_message( &ctx, "exportKey is not supported with this crypto provider", )) } pub async fn subtle_import_key<'js>( ctx: Ctx<'js>, _format: key_algorithm::KeyFormat, _key_data: Value<'js>, _algorithm: Value<'js>, _extractable: bool, _key_usages: rquickjs::Array<'js>, ) -> Result> { Err(Exception::throw_message( &ctx, "importKey is not supported with this crypto provider", )) } pub async fn subtle_wrap_key<'js>( ctx: Ctx<'js>, _format: key_algorithm::KeyFormat, _key: rquickjs::Class<'js, CryptoKey>, _wrapping_key: rquickjs::Class<'js, CryptoKey>, _wrap_algo: encryption_algorithm::EncryptionAlgorithm, ) -> Result> { Err(Exception::throw_message( &ctx, "wrapKey is not supported with this crypto provider", )) } pub async fn subtle_unwrap_key<'js>( _format: key_algorithm::KeyFormat, wrapped_key: rquickjs::ArrayBuffer<'js>, _unwrapping_key: rquickjs::Class<'js, CryptoKey>, _unwrap_algo: encryption_algorithm::EncryptionAlgorithm, _unwrapped_key_algo: Value<'js>, _extractable: bool, _key_usages: rquickjs::Array<'js>, ) -> Result> { let ctx = wrapped_key.ctx().clone(); Err(Exception::throw_message( &ctx, "unwrapKey is not supported with this crypto provider", )) } ================================================ FILE: modules/llrt_crypto/src/subtle/verify.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use crate::provider::{CryptoProvider, HmacProvider}; use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; use rquickjs::{Class, Ctx, Result}; use crate::{ subtle::{digest, CryptoKey}, CRYPTO_PROVIDER, }; use super::{ algorithm_mismatch_error, key_algorithm::KeyAlgorithm, rsa_hash_digest, sign_algorithm::SigningAlgorithm, }; pub async fn subtle_verify<'js>( ctx: Ctx<'js>, algorithm: SigningAlgorithm, key: Class<'js, CryptoKey>, signature: ObjectBytes<'js>, data: ObjectBytes<'js>, ) -> Result { let key = key.borrow(); key.check_validity("verify").or_throw(&ctx)?; verify( &ctx, &algorithm, &key, signature.as_bytes(&ctx)?, data.as_bytes(&ctx)?, ) } fn verify( ctx: &Ctx<'_>, algorithm: &SigningAlgorithm, key: &CryptoKey, signature: &[u8], data: &[u8], ) -> Result { let handle = key.handle.as_ref(); Ok(match algorithm { SigningAlgorithm::Ecdsa { hash } => { let curve = match &key.algorithm { KeyAlgorithm::Ec { curve, .. } => curve, _ => return algorithm_mismatch_error(ctx, "ECDSA"), }; let digest = digest::digest(hash, data); crate::CRYPTO_PROVIDER .ecdsa_verify(*curve, handle, signature, &digest) .or_throw(ctx)? }, SigningAlgorithm::Ed25519 => { if !matches!(&key.algorithm, KeyAlgorithm::Ed25519) { return algorithm_mismatch_error(ctx, "Ed25519"); } crate::CRYPTO_PROVIDER .ed25519_verify(handle, signature, data) .or_throw(ctx)? }, SigningAlgorithm::Hmac => { let hash = match &key.algorithm { KeyAlgorithm::Hmac { hash, .. } => hash, _ => return algorithm_mismatch_error(ctx, "HMAC"), }; let mut hmac = CRYPTO_PROVIDER.hmac(*hash, handle); hmac.update(data); let computed_signature = hmac.finalize(); computed_signature == signature }, SigningAlgorithm::RsaPss { salt_length } => { let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSA-PSS")?; crate::CRYPTO_PROVIDER .rsa_pss_verify( &key.handle, signature, digest.as_ref(), *salt_length as usize, *hash, ) .or_throw(ctx)? }, SigningAlgorithm::RsassaPkcs1v15 => { let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSASSA-PKCS1-v1_5")?; crate::CRYPTO_PROVIDER .rsa_pkcs1v15_verify(&key.handle, signature, digest.as_ref(), *hash) .or_throw(ctx)? }, }) } ================================================ FILE: modules/llrt_crypto/src/subtle/wrapping.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_json::{parse::json_parse, stringify::json_stringify}; use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{Array, ArrayBuffer, Class, Ctx, Exception, Result, Value}; use crate::subtle::CryptoKey; use super::{ encryption::{self, encrypt_decrypt}, encryption_algorithm::EncryptionAlgorithm, export_key::{export_key, ExportOutput}, import_key::import_key, key_algorithm::{KeyFormat, KeyFormatData}, EncryptionMode, }; pub async fn subtle_wrap_key<'js>( ctx: Ctx<'js>, format: KeyFormat, key: Class<'js, CryptoKey>, wrapping_key: Class<'js, CryptoKey>, wrap_algo: EncryptionAlgorithm, ) -> Result> { let key = key.borrow(); let export = export_key(&ctx, format, &key)?; let (bytes, padding) = match export { ExportOutput::Bytes(bytes) => (bytes, 0), ExportOutput::Object(value) => { let json = json_stringify(&ctx, value.into_value())?.unwrap(); (json.into_bytes(), b' ') }, }; let wrapping_key = wrapping_key.borrow(); wrapping_key.check_validity("wrapKey").or_throw(&ctx)?; let bytes = encrypt_decrypt( &ctx, &wrap_algo, &wrapping_key, &bytes, EncryptionMode::Wrapping(padding), encryption::EncryptionOperation::Encrypt, )?; ArrayBuffer::new(ctx, bytes) } //cant take more than 7 args pub async fn subtle_unwrap_key<'js>( format: KeyFormat, wrapped_key: ArrayBuffer<'js>, unwrapping_key: Class<'js, CryptoKey>, unwrap_algo: EncryptionAlgorithm, unwrapped_key_algo: Value<'js>, extractable: bool, key_usages: Array<'js>, ) -> Result> { let unwrapping_key = unwrapping_key.borrow(); let ctx = wrapped_key.ctx().clone(); unwrapping_key.check_validity("unwrapKey").or_throw(&ctx)?; let bytes = wrapped_key .as_bytes() .ok_or_else(|| Exception::throw_message(&ctx, "ArrayBuffer is detached"))?; let padding = match format { KeyFormat::Jwk => b' ', _ => 0, }; let bytes = encrypt_decrypt( &ctx, &unwrap_algo, &unwrapping_key, bytes, EncryptionMode::Wrapping(padding), encryption::EncryptionOperation::Decrypt, )?; let key_format = match format { KeyFormat::Jwk => { KeyFormatData::Jwk(json_parse(&ctx, bytes)?.into_object_or_throw(&ctx, "wrappedKey")?) }, KeyFormat::Raw => KeyFormatData::Raw(ObjectBytes::Vec(bytes)), KeyFormat::Spki => KeyFormatData::Spki(ObjectBytes::Vec(bytes)), KeyFormat::Pkcs8 => KeyFormatData::Pkcs8(ObjectBytes::Vec(bytes)), }; import_key(ctx, key_format, unwrapped_key_algo, extractable, key_usages) } ================================================ FILE: modules/llrt_dgram/Cargo.toml ================================================ [package] name = "llrt_dgram" description = "LLRT Module dgram" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_dgram" path = "src/lib.rs" [dependencies] itoa = { version = "1", default-features = false } llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" } llrt_events = { version = "0.8.1-beta", path = "../llrt_events" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } tokio = { version = "1", features = [ "net", "macros", ], default-features = false } tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = ["rt", "macros"] } ================================================ FILE: modules/llrt_dgram/README.md ================================================ # llrt_dgram LLRT implementation of Node.js `dgram` module for UDP datagram sockets. ## Features - UDP socket support (IPv4 and IPv6) - Send and receive datagrams - Event-driven API compatible with Node.js - Async/await support ## Supported APIs - `dgram.createSocket(type[, callback])` - `socket.send(msg, port, address[, callback])` - `socket.bind([port][, address][, callback])` - `socket.close([callback])` - `socket.address()` - `socket.unref()` - `socket.ref()` ## Events - `'message'` - Emitted when a new datagram is available - `'listening'` - Emitted when socket begins listening for datagrams - `'close'` - Emitted after socket is closed - `'error'` - Emitted when an error occurs ================================================ FILE: modules/llrt_dgram/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_events::Emitter; use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::Func, Class, Ctx, Result, Value, }; mod socket; use self::socket::Socket; pub struct DgramModule; impl ModuleDef for DgramModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("createSocket")?; declare.declare(stringify!(Socket))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { Class::::define(default)?; Socket::add_event_emitter_prototype(ctx)?; default.set( "createSocket", Func::from(|ctx: Ctx<'js>, type_or_options: Value<'js>| { Socket::ctor(ctx, type_or_options) }), )?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: DgramModule) -> Self { ModuleInfo { name: "dgram", module: val, } } } #[cfg(test)] mod tests { use llrt_test::{test_async_with, ModuleEvaluator}; use super::*; #[tokio::test] async fn test_module_loads() { test_async_with(|ctx| { Box::pin(async move { // Test that the dgram module can be loaded without errors let result = ModuleEvaluator::eval_rust::(ctx.clone(), "dgram").await; assert!(result.is_ok(), "Dgram module should load successfully"); }) }) .await; } #[tokio::test] async fn test_create_socket_function_exists() { test_async_with(|ctx| { Box::pin(async move { ModuleEvaluator::eval_rust::(ctx.clone(), "dgram") .await .unwrap(); let result = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import dgram from 'dgram'; typeof dgram.createSocket === 'function' "#, ) .await; assert!(result.is_ok(), "createSocket should be accessible"); }) }) .await; } #[tokio::test] async fn test_socket_creation() { test_async_with(|ctx| { Box::pin(async move { ModuleEvaluator::eval_rust::(ctx.clone(), "dgram") .await .unwrap(); let result = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import dgram from 'dgram'; try { const socket = dgram.createSocket('udp4'); true } catch (e) { false } "#, ) .await; assert!(result.is_ok(), "Socket creation should not throw"); }) }) .await; } #[tokio::test] async fn test_bind_failure_emits_error() { // Test that bind failure on invalid address emits error event test_async_with(|ctx| { Box::pin(async move { ModuleEvaluator::eval_rust::(ctx.clone(), "dgram") .await .unwrap(); let result = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import dgram from 'dgram'; export async function test() { return new Promise((resolve, reject) => { const socket = dgram.createSocket('udp4'); let errorReceived = false; socket.on('error', (err) => { errorReceived = true; resolve('error_received'); }); // Bind to invalid address should fail socket.bind(12345, '999.999.999.999'); // Timeout to ensure we don't hang forever setTimeout(() => { resolve(errorReceived ? 'error_received' : 'timeout'); }, 100); }); } "#, ) .await; assert!(result.is_ok(), "Test module should evaluate"); }) }) .await; } #[tokio::test] async fn test_close_clears_send_channel() { // Test that close properly cleans up send channel test_async_with(|ctx| { Box::pin(async move { ModuleEvaluator::eval_rust::(ctx.clone(), "dgram") .await .unwrap(); let result = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import dgram from 'dgram'; export async function test() { return new Promise((resolve) => { const socket = dgram.createSocket('udp4'); socket.on('listening', () => { socket.close(() => { resolve('closed'); }); }); socket.on('error', (err) => { resolve('error: ' + err.message); }); socket.bind(0); // Random port }); } "#, ) .await; assert!(result.is_ok(), "Test module should evaluate"); }) }) .await; } #[tokio::test] async fn test_send_after_close_fails() { // Test that sending after close returns error test_async_with(|ctx| { Box::pin(async move { ModuleEvaluator::eval_rust::(ctx.clone(), "dgram") .await .unwrap(); let result = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import dgram from 'dgram'; export async function test() { return new Promise((resolve) => { const socket = dgram.createSocket('udp4'); socket.on('listening', () => { socket.close(); try { socket.send('test', 12345, 'localhost'); resolve('no_error'); } catch (e) { resolve('error_thrown'); } }); socket.bind(0); }); } "#, ) .await; assert!(result.is_ok(), "Test module should evaluate"); }) }) .await; } #[tokio::test] async fn test_double_close_fails() { // Test that closing twice throws error test_async_with(|ctx| { Box::pin(async move { ModuleEvaluator::eval_rust::(ctx.clone(), "dgram") .await .unwrap(); let result = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import dgram from 'dgram'; export async function test() { return new Promise((resolve) => { const socket = dgram.createSocket('udp4'); socket.on('listening', () => { socket.close(); try { socket.close(); resolve('no_error'); } catch (e) { resolve('error_thrown'); } }); socket.bind(0); }); } "#, ) .await; assert!(result.is_ok(), "Test module should evaluate"); }) }) .await; } } ================================================ FILE: modules/llrt_dgram/src/socket.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, RwLock, }; use llrt_buffer::Buffer; use llrt_context::CtxExtension; use llrt_events::{EmitError, Emitter, EventEmitter, EventKey, EventList}; use llrt_utils::{bytes::ObjectBytes, latch::Latch, object::ObjectExt, result::ResultExt}; use rquickjs::{ class::{Trace, Tracer}, prelude::{Opt, Rest, This}, Class, Ctx, Exception, FromJs, Function, IntoJs, JsLifetime, Object, Result, Value, }; use tokio::{ net::UdpSocket, sync::{broadcast, mpsc, oneshot}, }; use tracing::trace; type SendResult = std::result::Result; type SendMessage = (Vec, String, Option>); #[rquickjs::class] pub struct Socket<'js> { emitter: EventEmitter<'js>, socket: Option>, is_bound: Arc, is_closed: Arc, local_address: Option, local_port: Option, local_family: Option, receiver_running: Arc, close_tx: broadcast::Sender<()>, send_tx: Option>, ready_latch: Arc, } unsafe impl<'js> JsLifetime<'js> for Socket<'js> { type Changed<'to> = Socket<'to>; } impl<'js> Trace<'js> for Socket<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.emitter.trace(tracer); } } impl<'js> Emitter<'js> for Socket<'js> { fn get_event_list(&self) -> Arc>> { self.emitter.get_event_list() } fn on_event_changed(&mut self, _event: EventKey<'js>, _added: bool) -> Result<()> { Ok(()) } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> Socket<'js> { #[qjs(constructor)] pub fn ctor(ctx: Ctx<'js>, type_or_options: Value<'js>) -> Result> { let socket_type = if let Some(obj) = type_or_options.as_object() { obj.get_optional::<_, String>("type")? .unwrap_or_else(|| "udp4".to_string()) } else if let Some(type_str) = type_or_options.as_string() { type_str.to_string()? } else { "udp4".to_string() }; // Validate socket type if socket_type != "udp4" && socket_type != "udp6" { return Err(Exception::throw_type( &ctx, &format!("Invalid socket type: {}", socket_type), )); } let emitter = EventEmitter::new(); let (close_tx, _) = broadcast::channel(1); let instance = Self { emitter, socket: None, is_bound: Arc::new(AtomicBool::new(false)), is_closed: Arc::new(AtomicBool::new(false)), local_address: None, local_port: None, local_family: Some(if socket_type == "udp4" { "IPv4".to_string() } else { "IPv6".to_string() }), receiver_running: Arc::new(AtomicBool::new(false)), close_tx, send_tx: None, ready_latch: Arc::new(Latch::default()), }; Class::instance(ctx, instance) } fn start_listening( socket_class: Class<'js, Self>, ctx: Ctx<'js>, bind_addr: String, ) -> Result<()> { // Check state and get Arc clones in single borrow let (is_closed, receiver_running, ready_latch) = { let borrow = socket_class.borrow(); if borrow.is_bound.load(Ordering::SeqCst) { return Err(Exception::throw_message(&ctx, "ERR_SOCKET_ALREADY_BOUND")); } if borrow.is_closed.load(Ordering::SeqCst) { return Err(Exception::throw_message( &ctx, "ERR_SOCKET_DGRAM_NOT_RUNNING", )); } ( borrow.is_closed.clone(), borrow.receiver_running.clone(), borrow.ready_latch.clone(), ) }; // Increment latch before spawning - will be decremented when send_tx is ready or on error ready_latch.increment(); let socket_class2 = socket_class.clone(); let socket_class3 = socket_class.clone(); ctx.clone().spawn_exit(async move { let bind_result = async { let socket = match UdpSocket::bind(&bind_addr).await { Ok(s) => s, Err(e) => { ready_latch.decrement(); return Err(e).or_throw(&ctx); }, }; let local_addr = match socket.local_addr() { Ok(a) => a, Err(e) => { ready_latch.decrement(); return Err(e).or_throw(&ctx); }, }; let socket_arc = Arc::new(socket); // Create MPSC channel for send operations BEFORE starting loops let (send_tx, mut send_rx) = mpsc::unbounded_channel::(); let (recv_close_rx, send_close_rx) = { let mut borrow = socket_class.borrow_mut(); borrow.socket = Some(socket_arc.clone()); borrow.local_address = Some(local_addr.ip().to_string()); borrow.local_port = Some(local_addr.port()); borrow.is_bound.store(true, Ordering::SeqCst); borrow.send_tx = Some(send_tx); (borrow.close_tx.subscribe(), borrow.close_tx.subscribe()) }; // Signal that send channel is ready ready_latch.decrement(); trace!("UDP socket bound to {}", local_addr); // Emit 'listening' event Self::emit_str(This(socket_class.clone()), &ctx, "listening", vec![], false)?; // Start receiver loop let recv_started = receiver_running .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_ok(); if recv_started { let recv_socket = socket_arc.clone(); let recv_class = socket_class.clone(); let recv_closed = is_closed.clone(); let recv_running = receiver_running.clone(); let recv_ctx = ctx.clone(); let mut close_rx = recv_close_rx; ctx.clone().spawn_exit(async move { let recv_result = async { let mut buf = vec![0u8; 65536]; loop { tokio::select! { _ = close_rx.recv() => { break; } result = recv_socket.recv_from(&mut buf) => { match result { Ok((size, peer_addr)) => { let data = Buffer(buf[..size].to_vec()).into_js(&recv_ctx)?; let info = Object::new(recv_ctx.clone())?; info.set("address", peer_addr.ip().to_string())?; info.set("port", peer_addr.port())?; info.set( "family", if peer_addr.is_ipv4() { "IPv4" } else { "IPv6" }, )?; let info_val: Value = info.into(); Self::emit_str( This(recv_class.clone()), &recv_ctx, "message", vec![data, info_val], false, )?; }, Err(e) => { if recv_closed.load(Ordering::SeqCst) { break; } return Err(Exception::throw_message( &recv_ctx, &format!("UDP receive error: {}", e), )); }, } } } } recv_running.store(false, Ordering::SeqCst); Ok(()) } .await; recv_result.emit_error("recv", &recv_ctx, recv_class)?; Ok(()) })?; } // Start sender loop let send_socket = socket_arc.clone(); let mut close_rx = send_close_rx; let send_ctx = ctx.clone(); ctx.spawn_exit(async move { let send_result = { loop { tokio::select! { _ = close_rx.recv() => { break; } msg = send_rx.recv() => { let Some((bytes, dest_addr, result_tx)) = msg else { break; }; let result = send_socket.send_to(&bytes, &dest_addr).await; if let Some(result_tx) = result_tx{ result_tx.send(result.map_err(|e| e.to_string())).map_err(|_|Exception::throw_message(&send_ctx, "Failed to call callback in send, channel closed!"))?; continue; } result.or_throw(&send_ctx)?; } } }; Ok(()) }; send_result.emit_error("receive", &send_ctx, socket_class3)?; Ok(()) })?; Ok(()) } .await; bind_result.emit_error("bind", &ctx, socket_class2)?; Ok(()) })?; Ok(()) } #[qjs(skip)] fn ensure_listening(this: Class<'js, Self>, ctx: Ctx<'js>) -> Result> { // Check if already bound and get latch let (is_bound, ready_latch) = { let borrow = this.borrow(); ( borrow.is_bound.load(Ordering::SeqCst), borrow.ready_latch.clone(), ) }; if !is_bound { // Auto-bind to random port let bind_addr = "0.0.0.0:0".to_string(); Self::start_listening(this.clone(), ctx.clone(), bind_addr)?; } Ok(ready_latch) } pub fn bind( this: This>, ctx: Ctx<'js>, args: Rest>, ) -> Result> { let mut port = 0u16; let mut address = "0.0.0.0".to_string(); let mut callback: Option = None; // Parse arguments: can be (port, address, callback), (port, callback), (callback), or (options, callback) let mut args_iter = args.0.into_iter(); if let Some(first_arg) = args_iter.next() { if let Some(func) = first_arg.as_function() { // bind(callback) callback = Some(func.clone()); } else if let Some(num) = first_arg.as_int() { // bind(port, ...) port = num as u16; if let Some(second_arg) = args_iter.next() { if let Some(func) = second_arg.as_function() { // bind(port, callback) callback = Some(func.clone()); } else if let Some(addr_str) = second_arg.as_string() { // bind(port, address, ...) address = addr_str.to_string()?; if let Some(third_arg) = args_iter.next() { if let Some(func) = third_arg.as_function() { // bind(port, address, callback) callback = Some(func.clone()); } } } } } else if let Some(obj) = first_arg.as_object() { // bind(options, callback) if let Some(p) = obj.get::<_, Option>("port")? { port = p; } if let Some(addr) = obj.get::<_, Option>("address")? { address = addr; } if let Some(second_arg) = args_iter.next() { if let Some(func) = second_arg.as_function() { callback = Some(func.clone()); } } } } if let Some(cb) = callback { Self::add_event_listener_str(This(this.clone()), &ctx, "listening", cb, true, true)?; } let bind_addr = [&address, ":", &port.to_string()].concat(); Self::start_listening(this.0.clone(), ctx, bind_addr)?; Ok(this.0) } pub fn send( this: This>, ctx: Ctx<'js>, msg: Value<'js>, port: u16, address: Opt, callback: Opt>, ) -> Result<()> { let address = address.0.unwrap_or_else(|| "localhost".to_string()); // Extract bytes from message let bytes: Vec = if let Some(str_val) = msg.as_string() { str_val.to_string()?.into_bytes() } else { ObjectBytes::from_js(&ctx, msg)? .try_into() .map_err(|e: std::rc::Rc| Exception::throw_type(&ctx, &e))? }; // Check if socket is closed { let borrow = this.borrow(); if borrow.is_closed.load(Ordering::SeqCst) { return Err(Exception::throw_message(&ctx, "Socket is closed")); } } // Format destination address let dest_addr = [&address, ":", &port.to_string()].concat(); // Ensure listener is started and get latch to wait on let ready_latch = Self::ensure_listening(this.0.clone(), ctx.clone())?; let socket_class = this.0.clone(); let socket_class2 = this.0.clone(); let cb = callback.0; ctx.clone().spawn_exit(async move { let send_result = async { // Wait for send channel to be ready ready_latch.wait().await; // Send to the channel let result_rx = { let (result_tx, result_rx) = if cb.is_some() { let (result_tx, result_rx) = oneshot::channel(); (Some(result_tx), Some(result_rx)) } else { (None, None) }; let borrow = socket_class.borrow(); let send_tx = borrow.send_tx.as_ref().ok_or_else(|| { Exception::throw_message(&ctx, "Failed to initialize socket") })?; send_tx .send((bytes, dest_addr, result_tx)) .map_err(|_| Exception::throw_message(&ctx, "Failed to send message"))?; result_rx }; //we dont have any callback let Some(result_rx) = result_rx else { return Ok(()); }; // Wait for result let result = result_rx.await.unwrap_or(Err("Socket closed".to_string())); let Some(cb) = cb else { result.or_throw(&ctx)?; return Ok(()); }; // Callback handles both success and error match result { Ok(sent) => { cb.call::<_, ()>((Value::new_null(ctx.clone()), sent))?; }, Err(e) => { let err = Exception::from_message(ctx.clone(), &e)?; cb.call::<_, ()>((err,))?; }, } Ok(()) } .await; send_result.emit_error("send", &ctx, socket_class2)?; Ok(()) })?; Ok(()) } pub fn close( this: This>, ctx: Ctx<'js>, callback: Opt>, ) -> Result> { let already_closed = { let borrow = this.borrow(); let was_closed = borrow .is_closed .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_err(); if !was_closed { // Send close signal to interrupt receive/send loops let _ = borrow.close_tx.send(()); } was_closed }; if already_closed { return Err(Exception::throw_message( &ctx, "ERR_SOCKET_DGRAM_NOT_RUNNING", )); } if let Some(cb) = callback.0 { Self::add_event_listener_str(This(this.clone()), &ctx, "close", cb, true, true)?; } // Drop the socket and clear send channel { let mut borrow = this.borrow_mut(); borrow.socket = None; borrow.send_tx = None; } // Emit close event directly (don't rely on sender loop which may not have started) let this_clone = this.0.clone(); ctx.clone().spawn_exit(async move { Self::emit_str(This(this_clone), &ctx, "close", vec![], false)?; Ok(()) })?; Ok(this.0) } pub fn address(this: This>, ctx: Ctx<'js>) -> Result> { let borrow = this.borrow(); let obj = Object::new(ctx)?; if let Some(addr) = &borrow.local_address { obj.set("address", addr.clone())?; } if let Some(port) = borrow.local_port { obj.set("port", port)?; } if let Some(family) = &borrow.local_family { obj.set("family", family.clone())?; } Ok(obj) } pub fn unref(this: This>) -> Result> { // In Node.js, unref() allows the process to exit if this is the only active handle // In LLRT's context, this is a no-op but we keep it for API compatibility trace!("Socket.unref() called - no-op for API compatibility"); Ok(this.0) } #[qjs(rename = "ref")] pub fn r#ref(this: This>) -> Result> { // Counterpart to unref(), also a no-op in LLRT trace!("Socket.ref() called - no-op for API compatibility"); Ok(this.0) } } ================================================ FILE: modules/llrt_dns/Cargo.toml ================================================ [package] name = "llrt_dns" description = "LLRT Module dns" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_dns" path = "src/lib.rs" [dependencies] either = { version = "1", default-features = false } llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" } llrt_hooking = { version = "0.8.1-beta", path = "../../libs/llrt_hooking" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", features = [ "macro", "either", ], default-features = false } tokio = { version = "1", features = ["net"], default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = [ "macros", "test-util", ], default-features = false } ================================================ FILE: modules/llrt_dns/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::Func, Ctx, Result, }; use crate::lookup::lookup; mod lookup; pub struct DnsModule; impl ModuleDef for DnsModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("lookup")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { default.set("lookup", Func::from(lookup))?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: DnsModule) -> Self { ModuleInfo { name: "dns", module: val, } } } ================================================ FILE: modules/llrt_dns/src/lookup.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::net::SocketAddr; use std::result::Result as StdResult; use either::Either; use llrt_context::CtxExtension; use llrt_hooking::{invoke_async_hook, register_finalization_registry, HookType}; use llrt_utils::{provider::ProviderType, result::ResultExt}; use rquickjs::{ prelude::Opt, qjs, Ctx, Error, Exception, FromJs, Function, IntoJs, Null, Object, Result, Value, }; const ERROR_MSG_OPTIONS_FAMILY: &str = "The argument 'family' must be one of: 0, 4, 6"; const ERROR_MSG_OPTIONS_ORDER: &str = "The argument 'order' must be one of: 'verbatim', 'ipv4first', 'ipv6first'"; pub fn lookup<'js>( ctx: Ctx<'js>, hostname: String, options_or_callback: Either, LookupOptions>, callback: Opt>, ) -> Result<()> { let (cb, options) = match options_or_callback { Either::Left(cb) => (cb, LookupOptions::default()), Either::Right(options) => { let cb = callback .0 .or_throw_msg(&ctx, "Callback parameter is missing")?; (cb, options) }, }; // SAFETY: Since it checks in advance whether it is an Function type, we can always get a pointer to the Function. let uid = unsafe { qjs::JS_VALUE_GET_PTR(cb.as_raw()) } as usize; register_finalization_registry(&ctx, cb.clone().into_value(), uid)?; invoke_async_hook(&ctx, HookType::Init, ProviderType::GetAddrInfoReqWrap, uid)?; ctx.clone().spawn_exit(async move { match lookup_host(&hostname, options.family, options.order).await { Ok(addrs) => { invoke_async_hook(&ctx, HookType::Before, ProviderType::None, uid)?; if options.all { () = cb.call((Null.into_js(&ctx), addrs))?; } else { let addr = addrs.into_iter().next(); if let Some(addr) = addr { () = cb.call((Null.into_js(&ctx), addr.address, addr.family))?; } else { () = cb.call((Exception::from_message(ctx.clone(), "No address found"),))?; } } invoke_async_hook(&ctx, HookType::After, ProviderType::None, uid)?; Ok::<_, Error>(()) }, Err(err) => { invoke_async_hook(&ctx, HookType::Before, ProviderType::None, uid)?; () = cb.call((Exception::from_message(ctx.clone(), &err.to_string()),))?; invoke_async_hook(&ctx, HookType::After, ProviderType::None, uid)?; Ok(()) }, } })?; Ok(()) } async fn lookup_host( hostname: &str, family: i32, order: LookupOrder, ) -> StdResult, std::io::Error> { let mut addrs = tokio::net::lookup_host((hostname, 0)) .await? .filter_map(|addr| { if matches!(family, 4 | 0) { if let SocketAddr::V4(ipv4) = addr { return Some(LookupValue { address: ipv4.ip().to_string(), family: 4, }); } } if matches!(family, 6 | 0) { if let SocketAddr::V6(ipv6) = addr { return Some(LookupValue { address: ipv6.ip().to_string(), family: 6, }); } } None }) .collect(); match order { LookupOrder::Verbatim => Ok(addrs), LookupOrder::Ipv4First => { addrs.sort_by(|a, b| a.family.cmp(&b.family)); Ok(addrs) }, LookupOrder::Ipv6First => { addrs.sort_by(|a, b| b.family.cmp(&a.family)); Ok(addrs) }, } } struct LookupValue { address: String, family: i32, } impl<'js> IntoJs<'js> for LookupValue { fn into_js(self, ctx: &Ctx<'js>) -> Result> { let object = Object::new(ctx.clone())?; object.set("address", self.address)?; object.set("family", self.family)?; Ok(object.into_value()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LookupOrder { Verbatim, Ipv4First, Ipv6First, } pub struct LookupOptions { family: i32, all: bool, order: LookupOrder, } impl Default for LookupOptions { fn default() -> Self { Self { family: 0, all: false, order: LookupOrder::Verbatim, } } } impl<'js> FromJs<'js> for LookupOptions { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let mut family = 0; let mut all = false; let mut order = LookupOrder::Verbatim; if let Some(v) = value.as_int() { if !matches!(v, 4 | 6 | 0) { return Err(Exception::throw_type(ctx, ERROR_MSG_OPTIONS_FAMILY)); } family = v; } else if let Some(options) = value.as_object() { // Parse family if let Ok(family_value) = options.get::<_, Value<'js>>("family") { if let Some(v) = family_value.as_int() { if !matches!(v, 4 | 6 | 0) { return Err(Exception::throw_type(ctx, ERROR_MSG_OPTIONS_FAMILY)); } family = v; } else if let Some(v) = family_value.as_string() { let family_string = v.to_string()?; match family_string.as_str() { "IPv4" => family = 4, "IPv6" => family = 6, _ => { return Err(Exception::throw_type(ctx, ERROR_MSG_OPTIONS_FAMILY)); }, } } else if family_value.is_null() || family_value.is_undefined() { // Use default family } else { return Err(Exception::throw_type(ctx, ERROR_MSG_OPTIONS_FAMILY)); } } // Parse all if let Ok(all_value) = options.get::<_, bool>("all") { all = all_value; } // Parse order if let Ok(order_value) = options.get::<_, String>("order") { match order_value.as_str() { "verbatim" => order = LookupOrder::Verbatim, "ipv4first" => order = LookupOrder::Ipv4First, "ipv6first" => order = LookupOrder::Ipv6First, _ => { return Err(Exception::throw_type(ctx, ERROR_MSG_OPTIONS_ORDER)); }, } } } else if value.is_null() || value.is_undefined() { // Use default options } else { return Err(Exception::throw_type(ctx, ERROR_MSG_OPTIONS_FAMILY)); } Ok(LookupOptions { family, all, order }) } } #[cfg(test)] mod tests { use llrt_test::{call_test, call_test_err, test_async_with, ModuleEvaluator}; use llrt_utils::primordials::{BasePrimordials, Primordial}; use crate::DnsModule; #[tokio::test] async fn test_lookup() { test_async_with(|ctx| { Box::pin(async move { BasePrimordials::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "dns") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { lookup } from 'dns'; export async function test(hostname) { return new Promise((resolve, reject) => { lookup(hostname, (err, address, family) => { if (err) reject(err); else resolve(`${address}:${family}`); }); }); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ("www.amazon.com",)).await; assert!(result.ends_with(":4") || result.ends_with(":6")); }) }) .await } #[tokio::test] async fn test_lookup_v6() { test_async_with(|ctx| { Box::pin(async move { BasePrimordials::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "dns") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { lookup } from 'dns'; export async function test(hostname) { return new Promise((resolve, reject) => { lookup(hostname, 6, (err, address, family) => { if (err) reject(err); else resolve(`${address}:${family}`); }); }); } "#, ) .await .unwrap(); let result = call_test_err::(&ctx, &module, ("www.amazon.com",)).await; // Not all systems support IPv6 resolution so we need to support it match result { Ok(result) => assert!(result.ends_with(":6")), Err(err) => assert!(err.to_string().contains("No address found")), } }) }) .await } #[tokio::test] async fn test_lookup_all() { test_async_with(|ctx| { Box::pin(async move { BasePrimordials::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "dns") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { lookup } from 'dns'; export async function test(hostname) { return new Promise((resolve, reject) => { lookup(hostname, { all: true }, (err, addresses) => { if (err) reject(err); else resolve(addresses.map(addr => `${addr.address}:${addr.family}`)); }); }); } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, ("www.amazon.com",)).await; assert!(!result.is_empty()); assert!(result.iter().all(|addr| addr.ends_with(":4") || addr.ends_with(":6"))); }) }) .await } #[tokio::test] async fn test_lookup_order() { test_async_with(|ctx| { Box::pin(async move { BasePrimordials::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "dns") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { lookup } from 'dns'; export async function test(hostname) { return new Promise((resolve, reject) => { lookup(hostname, { all: true, order: 'ipv6first' }, (err, addresses) => { if (err) reject(err); else resolve(addresses.map(addr => `${addr.address}:${addr.family}`)); }); }); } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, ("www.amazon.com",)).await; assert!(!result.is_empty()); let first_ipv4 = result.iter().position(|addr| addr.ends_with(":4")).unwrap(); let last_ipv6 = result.iter().rposition(|addr| addr.ends_with(":6")).unwrap_or(result.len().saturating_sub(1)); assert!(last_ipv6 <= first_ipv4); }) }) .await } } ================================================ FILE: modules/llrt_events/Cargo.toml ================================================ [package] name = "llrt_events" description = "LLRT Module events" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_events" path = "src/lib.rs" [dependencies] llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } tracing = { version = "0.1", default-features = false } ================================================ FILE: modules/llrt_events/src/custom_event.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{prelude::Opt, Ctx, IntoJs, Null, Result, Value}; use llrt_utils::object::ObjectExt; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct CustomEvent<'js> { event_type: String, detail: Option>, } #[rquickjs::methods] impl<'js> CustomEvent<'js> { #[qjs(constructor)] pub fn new(event_type: String, options: Opt>) -> Result { let mut detail = None; if let Some(options) = options.0 { if let Some(opt) = options.get_optional("detail")? { detail = opt; } } Ok(Self { event_type, detail }) } #[qjs(get)] pub fn detail(&self, ctx: Ctx<'js>) -> Result> { if let Some(detail) = &self.detail { return Ok(detail.clone()); } Null.into_js(&ctx) } #[qjs(get, rename = "type")] pub fn event_type(&self) -> String { self.event_type.clone() } } ================================================ FILE: modules/llrt_events/src/event.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{prelude::Opt, Result, Value}; use llrt_utils::object::ObjectExt; #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Event { event_type: String, bubbles: bool, cancelable: bool, composed: bool, } #[rquickjs::methods] impl Event { #[qjs(constructor)] pub fn new(event_type: String, options: Opt>) -> Result { let mut bubbles = false; let mut cancelable = false; let mut composed = false; if let Some(options) = options.0 { if let Some(opt) = options.get_optional("bubbles")? { bubbles = opt; } if let Some(opt) = options.get_optional("cancelable")? { cancelable = opt; } if let Some(opt) = options.get_optional("composed")? { composed = opt; } } Ok(Self { event_type, bubbles, cancelable, composed, }) } #[qjs(get)] pub fn bubbles(&self) -> bool { self.bubbles } #[qjs(get)] pub fn cancelable(&self) -> bool { self.cancelable } #[qjs(get)] pub fn composed(&self) -> bool { self.composed } #[qjs(get, rename = "type")] pub fn event_type(&self) -> String { self.event_type.clone() } } ================================================ FILE: modules/llrt_events/src/event_target.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{Arc, RwLock}; use rquickjs::{ class::{Trace, Tracer}, JsLifetime, }; use super::{Emitter, EventList, Events}; #[rquickjs::class] #[derive(Clone)] pub struct EventTarget<'js> { pub events: Events<'js>, } unsafe impl<'js> JsLifetime<'js> for EventTarget<'js> { type Changed<'to> = EventTarget<'to>; } impl<'js> Emitter<'js> for EventTarget<'js> { fn get_event_list(&self) -> Arc>> { self.events.clone() } } impl<'js> Trace<'js> for EventTarget<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.trace_event_emitter(tracer); } } #[rquickjs::methods] impl<'js> EventTarget<'js> { #[qjs(constructor)] pub fn new() -> Self { Self { #[allow(clippy::arc_with_non_send_sync)] events: Arc::new(RwLock::new(Vec::new())), } } } ================================================ FILE: modules/llrt_events/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow( clippy::mutable_key_type, clippy::for_kv_map, clippy::new_without_default )] use std::{ rc::Rc, sync::{Arc, RwLock}, }; use llrt_utils::{ error::ErrorExtensions, module::ModuleInfo, object::ObjectExt, result::ResultExt, }; use rquickjs::{ class::{JsClass, OwnedBorrow, Trace, Tracer}, module::{Declarations, Exports, ModuleDef}, prelude::{Func, Opt, Rest, This}, CatchResultExt, Class, Ctx, Function, JsLifetime, Object, Result, String as JsString, Symbol, Value, }; use tracing::trace; use self::{custom_event::CustomEvent, event::Event, event_target::EventTarget}; pub mod custom_event; pub mod event; pub mod event_target; #[derive(Clone, Debug)] pub enum EventKey<'js> { Symbol(Symbol<'js>), String(Rc), } impl<'js> EventKey<'js> { fn from_value(ctx: &Ctx, value: Value<'js>) -> Result { if value.is_string() { let key: String = value.get()?; Ok(EventKey::String(key.into())) } else { let sym = value.into_symbol().ok_or("Not a symbol").or_throw(ctx)?; Ok(EventKey::Symbol(sym)) } } } impl Eq for EventKey<'_> {} impl PartialEq for EventKey<'_> { fn eq(&self, other: &Self) -> bool { match (self, other) { (EventKey::Symbol(symbol1), EventKey::Symbol(symbol2)) => symbol1 == symbol2, (EventKey::String(str1), EventKey::String(str2)) => str1 == str2, _ => false, } } } pub struct EventItem<'js> { callback: Function<'js>, once: bool, } pub type EventList<'js> = Vec<(EventKey<'js>, Vec>)>; pub type Events<'js> = Arc>>; #[rquickjs::class] #[derive(Clone)] pub struct EventEmitter<'js> { pub events: Events<'js>, } unsafe impl<'js> JsLifetime<'js> for EventEmitter<'js> { type Changed<'to> = EventEmitter<'to>; } impl<'js> Emitter<'js> for EventEmitter<'js> { fn get_event_list(&self) -> Arc>> { self.events.clone() } } impl<'js> Trace<'js> for EventEmitter<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.trace_event_emitter(tracer); } } #[rquickjs::methods] impl<'js> EventEmitter<'js> { #[qjs(constructor)] pub fn new() -> Self { Self { #[allow(clippy::arc_with_non_send_sync)] events: Arc::new(RwLock::new(Vec::new())), } } } pub trait EmitError<'js> { fn emit_error(self, id: &'static str, ctx: &Ctx<'js>, this: Class<'js, C>) -> Result where C: Emitter<'js>; } impl<'js, T> EmitError<'js> for Result { fn emit_error(self, id: &'static str, ctx: &Ctx<'js>, this: Class<'js, C>) -> Result where C: Emitter<'js>, { if let Err(err) = self.catch(ctx) { trace!("Error caught in: {}", id); if this.borrow().has_listener_str("error") { let error_value = err.into_value(ctx)?; C::emit_str(This(this), ctx, "error", vec![error_value], false)?; return Ok(true); } return Err(err.throw(ctx)); } Ok(false) } } pub trait Emitter<'js> where Self: JsClass<'js> + Sized + 'js, { fn get_event_list(&self) -> Arc>>; fn on_event_changed(&mut self, _event: EventKey<'js>, _added: bool) -> Result<()> { Ok(()) } fn add_event_emitter_prototype(ctx: &Ctx<'js>) -> Result> { let proto = Class::::prototype(ctx)? .or_throw_msg(ctx, "Prototype for EventEmitter not found")?; let on = Function::new(ctx.clone(), Self::on)?; let off = Function::new(ctx.clone(), Self::remove_event_listener)?; proto.set("once", Func::from(Self::once))?; proto.set("on", on.clone())?; proto.set("emit", Func::from(Self::emit))?; proto.set("prependListener", Func::from(Self::prepend_listener))?; proto.set( "prependOnceListener", Func::from(Self::prepend_once_listener), )?; proto.set("off", off.clone())?; proto.set("eventNames", Func::from(Self::event_names))?; proto.set("addListener", on)?; proto.set("removeListener", off)?; Ok(proto) } fn add_event_target_prototype(ctx: &Ctx<'js>) -> Result> { let proto = Class::::prototype(ctx)? .or_throw_msg(ctx, "Prototype for EventTarget not found")?; let on = Function::new(ctx.clone(), Self::evt_add_event_listener)?; let off = Function::new(ctx.clone(), Self::remove_event_listener)?; proto.set("dispatchEvent", Func::from(Self::evt_dispatch_event))?; proto.set("addEventListener", on)?; proto.set("removeEventListener", off)?; Ok(proto) } fn trace_event_emitter<'a>(&self, tracer: Tracer<'a, 'js>) { let events = self.get_event_list(); let events = events.read().unwrap(); for (key, items) in events.iter() { if let EventKey::Symbol(sym) = &key { tracer.mark(sym); } for item in items { tracer.mark(&item.callback); } } } fn remove_event_listener_str( this: This>, ctx: &Ctx<'js>, event: &str, listener: Function<'js>, ) -> Result> { let event = to_event(ctx, event)?; Self::remove_event_listener(this, ctx.clone(), event, listener) } fn remove_event_listener( this: This>, ctx: Ctx<'js>, event: Value<'js>, listener: Function<'js>, ) -> Result> { let events = this.clone().borrow().get_event_list(); let mut events = events.write().or_throw(&ctx)?; let key = EventKey::from_value(&ctx, event)?; if let Some(index) = events.iter_mut().position(|(k, _)| k == &key) { let items = &mut events[index].1; if let Some(pos) = items.iter().position(|item| item.callback == listener) { items.remove(pos); if items.is_empty() { events.remove(index); } } }; Ok(this.0) } fn add_event_listener_str( this: This>, ctx: &Ctx<'js>, event: &str, listener: Function<'js>, prepend: bool, once: bool, ) -> Result> { let event = to_event(ctx, event)?; Self::add_event_listener(this, ctx.clone(), event, listener, prepend, once) } fn once( this: This>, ctx: Ctx<'js>, event: Value<'js>, listener: Function<'js>, ) -> Result> { Self::add_event_listener(this, ctx, event, listener, false, true) } fn on( this: This>, ctx: Ctx<'js>, event: Value<'js>, listener: Function<'js>, ) -> Result> { Self::add_event_listener(this, ctx, event, listener, false, false) } fn prepend_listener( this: This>, ctx: Ctx<'js>, event: Value<'js>, listener: Function<'js>, ) -> Result> { Self::add_event_listener(this, ctx, event, listener, true, false) } fn prepend_once_listener( this: This>, ctx: Ctx<'js>, event: Value<'js>, listener: Function<'js>, ) -> Result> { Self::add_event_listener(this, ctx, event, listener, true, true) } fn evt_add_event_listener( this: This>, ctx: Ctx<'js>, event: Value<'js>, listener: Function<'js>, options: Opt>, ) -> Result> { let mut once = false; if let Some(opt) = options.0 { if let Some(once_opt) = opt.get("once")? { once = once_opt; } } Self::add_event_listener(this, ctx, event, listener, false, once) } fn add_event_listener( this: This>, ctx: Ctx<'js>, event: Value<'js>, listener: Function<'js>, prepend: bool, once: bool, ) -> Result> { let this2 = this.clone(); let events = &this2.borrow().get_event_list(); let mut events = events.write().or_throw(&ctx)?; let key = EventKey::from_value(&ctx, event)?; let mut is_new = false; let items = match events.iter_mut().find(|(k, _)| k == &key) { Some((_, entry_items)) => entry_items, None => { let new_items = Vec::new(); is_new = true; events.push((key.clone(), new_items)); &mut events.last_mut().unwrap().1 }, }; let item = EventItem { callback: listener, once, }; if !prepend { items.push(item); } else { items.insert(0, item); } if is_new { this2.borrow_mut().on_event_changed(key, true)? } Ok(this.0) } fn has_listener_str(&self, event: &str) -> bool { let key = EventKey::String(event.into()); has_key(self.get_event_list(), key) } #[allow(dead_code)] fn has_listener(&self, ctx: Ctx<'js>, event: Value<'js>) -> Result { let key = EventKey::from_value(&ctx, event)?; Ok(has_key(self.get_event_list(), key)) } #[allow(dead_code)] fn get_listeners(&self, ctx: &Ctx<'js>, event: Value<'js>) -> Result>> { let key = EventKey::from_value(ctx, event)?; Ok(find_all_listeners(self.get_event_list(), key)) } fn get_listeners_str(&self, event: &str) -> Vec> { let key = EventKey::String(event.into()); find_all_listeners(self.get_event_list(), key) } fn do_emit( event: Value<'js>, this: This>, ctx: &Ctx<'js>, args: Rest>, defer: bool, ) -> Result<()> { let this2 = this.clone(); let events = &this2.borrow().get_event_list(); let mut events = events.write().or_throw(ctx)?; let key = EventKey::from_value(ctx, event)?; if let Some(index) = events.iter_mut().position(|(k, _)| k == &key) { let items = &mut events[index].1; let mut callbacks = Vec::with_capacity(items.len()); items.retain(|item: &EventItem<'_>| { callbacks.push(item.callback.clone()); !item.once }); if items.is_empty() { events.remove(index); this.borrow_mut().on_event_changed(key, false)?; } drop(events); for callback in callbacks { let args = args.iter().map(|arg| arg.to_owned()).collect(); let args = Rest(args); let this = This(this.clone()); if defer { callback.defer((this, args))?; } else { callback.call::<_, ()>((this, args))?; } } } Ok(()) } fn emit_str( this: This>, ctx: &Ctx<'js>, event: &str, args: Vec>, defer: bool, ) -> Result<()> { let event = to_event(ctx, event)?; Self::do_emit(event, this, ctx, args.into(), defer) } fn emit( this: This>, ctx: Ctx<'js>, event: Value<'js>, args: Rest>, ) -> Result<()> { Self::do_emit(event, this, &ctx, args, false) } fn evt_dispatch_event( this: This>, ctx: Ctx<'js>, event: Value<'js>, ) -> Result<()> { let event_type = event.get_optional("type")?.unwrap(); Self::do_emit(event_type, this, &ctx, Rest(vec![event]), false) } fn event_names(this: This>, ctx: Ctx<'js>) -> Result>> { let events = this.get_event_list(); let events = events.read().or_throw(&ctx)?; let mut names = Vec::with_capacity(events.len()); for (key, _entry) in events.iter() { let value = match key { EventKey::Symbol(symbol) => symbol.clone().into_value(), EventKey::String(str) => JsString::from_str(ctx.clone(), str)?.into(), }; names.push(value) } Ok(names) } } fn find_all_listeners<'js>( events: Arc>>, key: EventKey<'js>, ) -> Vec> { let events = events.read().unwrap(); let items = events.iter().find(|(k, _)| k == &key); if let Some((_, callbacks)) = items { callbacks.iter().map(|item| item.callback.clone()).collect() } else { vec![] } } fn has_key<'js>(event_list: Arc>>, key: EventKey<'js>) -> bool { event_list.read().unwrap().iter().any(|(k, _)| k == &key) } fn to_event<'js>(ctx: &Ctx<'js>, event: &str) -> Result> { let event = JsString::from_str(ctx.clone(), event)?; Ok(event.into_value()) } pub struct EventsModule; impl ModuleDef for EventsModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(EventEmitter))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { let ctor = Class::::create_constructor(ctx)? .expect("Can't create EventEmitter constructor"); ctor.set(stringify!(EventEmitter), ctor.clone())?; exports.export(stringify!(EventEmitter), ctor.clone())?; exports.export("default", ctor)?; EventEmitter::add_event_emitter_prototype(ctx)?; Ok(()) } } impl From for ModuleInfo { fn from(val: EventsModule) -> Self { ModuleInfo { name: "events", module: val, } } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); Class::::define(&globals)?; Class::::define(&globals)?; Class::::define(&globals)?; EventTarget::add_event_target_prototype(ctx)?; Ok(()) } ================================================ FILE: modules/llrt_exceptions/Cargo.toml ================================================ [package] name = "llrt_exceptions" description = "LLRT Module exception" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_exceptions" path = "src/lib.rs" [dependencies] rquickjs = { version = "0.11", default-features = false } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } ================================================ FILE: modules/llrt_exceptions/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{ option::Undefined, primordials::{BasePrimordials, Primordial}, }; use rquickjs::{ atom::PredefinedAtom, class::JsClass, function::{Constructor, Opt}, object::Property, prelude::This, Class, Coerced, Ctx, Exception, IntoJs, Object, Result, Value, }; #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct DOMException { name: String, message: String, stack: String, code: u8, } fn add_constants(obj: &Object<'_>) -> Result<()> { const CONSTANTS: [(&str, u8); 25] = [ ("INDEX_SIZE_ERR", 1), ("DOMSTRING_SIZE_ERR", 2), ("HIERARCHY_REQUEST_ERR", 3), ("WRONG_DOCUMENT_ERR", 4), ("INVALID_CHARACTER_ERR", 5), ("NO_DATA_ALLOWED_ERR", 6), ("NO_MODIFICATION_ALLOWED_ERR", 7), ("NOT_FOUND_ERR", 8), ("NOT_SUPPORTED_ERR", 9), ("INUSE_ATTRIBUTE_ERR", 10), ("INVALID_STATE_ERR", 11), ("SYNTAX_ERR", 12), ("INVALID_MODIFICATION_ERR", 13), ("NAMESPACE_ERR", 14), ("INVALID_ACCESS_ERR", 15), ("VALIDATION_ERR", 16), ("TYPE_MISMATCH_ERR", 17), ("SECURITY_ERR", 18), ("NETWORK_ERR", 19), ("ABORT_ERR", 20), ("URL_MISMATCH_ERR", 21), ("QUOTA_EXCEEDED_ERR", 22), ("TIMEOUT_ERR", 23), ("INVALID_NODE_TYPE_ERR", 24), ("DATA_CLONE_ERR", 25), ]; for (key, value) in CONSTANTS { obj.prop(key, Property::from(value).enumerable())?; } Ok(()) } impl<'js> JsClass<'js> for DOMException { const NAME: &'static str = "DOMException"; type Mutable = rquickjs::class::Writable; fn prototype(ctx: &Ctx<'js>) -> rquickjs::Result>> { use rquickjs::class::impl_::{MethodImpl, MethodImplementor}; let proto = Object::new(ctx.clone())?; let implementor = MethodImpl::::new(); implementor.implement(&proto)?; add_constants(&proto)?; Ok(Some(proto)) } fn constructor(ctx: &Ctx<'js>) -> Result>> { use rquickjs::class::impl_::{ConstructorCreate, ConstructorCreator}; let implementor = ConstructorCreate::::new(); let constructor = implementor .create_constructor(ctx)? .expect("DOMException must have a constructor"); add_constants(&constructor)?; Ok(Some(constructor)) } } impl<'js> IntoJs<'js> for DOMException { fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> Result> { let cls = Class::::instance(ctx.clone(), self)?; rquickjs::IntoJs::into_js(cls, ctx) } } impl<'js> rquickjs::FromJs<'js> for DOMException where for<'a> rquickjs::class::impl_::CloneWrapper<'a, Self>: rquickjs::class::impl_::CloneTrait, { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { use rquickjs::class::impl_::{CloneTrait, CloneWrapper}; let value = Class::::from_js(ctx, value)?; let borrow = value.try_borrow()?; Ok(CloneWrapper(&*borrow).wrap_clone()) } } #[rquickjs::methods] impl DOMException { #[qjs(constructor)] pub fn new( ctx: Ctx<'_>, this: This>, message: Opt>>, name: Opt>>, ) -> Result { if this.0.is_undefined() { return Err(Exception::throw_type( &ctx, "Cannot call the DOMException constructor without 'new'", )); } let message = match message.0 { Some(Undefined(Some(message))) => message.0, _ => String::new(), }; let name = match name.0 { Some(Undefined(Some(message))) => DOMExceptionName::from(message.0), _ => DOMExceptionName::Error, }; Self::new_with_name(&ctx, name, message) } #[qjs(skip)] pub fn new_with_name(ctx: &Ctx<'_>, name: DOMExceptionName, message: String) -> Result { let primordials = BasePrimordials::get(ctx)?; let new: Object = primordials .constructor_error .construct((message.clone(),))?; Ok(Self { name: name.as_str().to_string(), code: name.code(), message, stack: new.get::<_, String>(PredefinedAtom::Stack)?, }) } #[qjs(get, enumerable, configurable)] fn message(&self) -> &str { self.message.as_str() } #[qjs(get, enumerable, configurable)] pub fn name(&self) -> &str { self.name.as_str() } #[qjs(get, enumerable, configurable)] pub fn code(&self) -> u8 { self.code } #[qjs(get)] fn stack(&self) -> String { self.stack.clone() } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &str { stringify!(DOMException) } } macro_rules! create_dom_exception { ($name:ident, $($variant:ident),+ $(,)?) => { #[derive(Debug)] pub enum $name { $( $variant, )+ Other(String), } impl $name { pub fn as_str(&self) -> &str { match self { $( Self::$variant => stringify!($variant), )+ Self::Other(value) => value, } } } impl From for $name { fn from(value: String) -> Self { match value.as_str() { $( stringify!($variant) => Self::$variant, )+ _ => Self::Other(value), } } } }; } // https://webidl.spec.whatwg.org/#dfn-error-names-table create_dom_exception!( DOMExceptionName, IndexSizeError, HierarchyRequestError, WrongDocumentError, InvalidCharacterError, NoModificationAllowedError, NotFoundError, NotSupportedError, InUseAttributeError, InvalidStateError, SyntaxError, InvalidModificationError, NamespaceError, InvalidAccessError, TypeMismatchError, SecurityError, NetworkError, AbortError, URLMismatchError, QuotaExceededError, TimeoutError, InvalidNodeTypeError, DataCloneError, EncodingError, NotReadableError, UnknownError, ConstraintError, DataError, TransactionInactiveError, ReadOnlyError, VersionError, OperationError, NotAllowedError, Error, ); impl DOMExceptionName { fn code(&self) -> u8 { match self { DOMExceptionName::IndexSizeError => 1, DOMExceptionName::HierarchyRequestError => 3, DOMExceptionName::WrongDocumentError => 4, DOMExceptionName::InvalidCharacterError => 5, DOMExceptionName::NoModificationAllowedError => 7, DOMExceptionName::NotFoundError => 8, DOMExceptionName::NotSupportedError => 9, DOMExceptionName::InUseAttributeError => 10, DOMExceptionName::InvalidStateError => 11, DOMExceptionName::SyntaxError => 12, DOMExceptionName::InvalidModificationError => 13, DOMExceptionName::NamespaceError => 14, DOMExceptionName::InvalidAccessError => 15, DOMExceptionName::TypeMismatchError => 17, DOMExceptionName::SecurityError => 18, DOMExceptionName::NetworkError => 19, DOMExceptionName::AbortError => 20, DOMExceptionName::URLMismatchError => 21, DOMExceptionName::QuotaExceededError => 22, DOMExceptionName::TimeoutError => 23, DOMExceptionName::InvalidNodeTypeError => 24, DOMExceptionName::DataCloneError => 25, _ => 0, } } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); BasePrimordials::init(ctx)?; if let Some(constructor) = Class::::create_constructor(ctx)? { // the wpt tests expect this particular property descriptor globals.prop( DOMException::NAME, Property::from(constructor).writable().configurable(), )?; } let dom_ex_proto = Class::::prototype(ctx)?.unwrap(); let error_prototype = &BasePrimordials::get(ctx)?.prototype_error; dom_ex_proto.set_prototype(Some(error_prototype))?; Ok(()) } ================================================ FILE: modules/llrt_fetch/Cargo.toml ================================================ [package] name = "llrt_fetch" description = "LLRT Module fetch" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_fetch" path = "src/lib.rs" [features] default = ["http1", "http2", "compression-c", "webpki-roots", "tls-ring"] http1 = ["hyper/http1", "llrt_http/http1"] http2 = ["hyper/http2", "llrt_http/http2"] compression-c = ["llrt_compression/all-c"] compression-rust = ["llrt_compression/all-rust"] webpki-roots = ["llrt_http/webpki-roots"] native-roots = ["llrt_http/native-roots"] tls-ring = ["llrt_http/tls-ring", "llrt_test_tls/tls-ring"] tls-aws-lc = ["llrt_http/tls-aws-lc", "llrt_test_tls/tls-aws-lc"] tls-graviola = ["llrt_http/tls-graviola", "llrt_test_tls/tls-graviola"] tls-openssl = ["llrt_http/tls-openssl", "llrt_test_tls/tls-openssl"] [dependencies] bytes = { version = "1", default-features = false } either = { version = "1", default-features = false } http-body-util = { version = "0.1", default-features = false } hyper = { version = "1", features = ["client"], default-features = false } itoa = { version = "1", default-features = false } llrt_abort = { version = "0.8.1-beta", path = "../llrt_abort" } llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_compression = { version = "0.8.1-beta", path = "../../libs/llrt_compression", default-features = false } llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" } llrt_encoding = { version = "0.8.1-beta", path = "../../libs/llrt_encoding" } llrt_http = { version = "0.8.1-beta", default-features = false, path = "../llrt_http" } llrt_json = { version = "0.8.1-beta", path = "../../libs/llrt_json" } llrt_url = { version = "0.8.1-beta", path = "../llrt_url" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } pin-project-lite = { version = "0.2", default-features = false } percent-encoding = { version = "2", features = [ "std", ], default-features = false } rand = { version = "0.10.0", features = [ "std", "thread_rng", ], default-features = false } rquickjs = { version = "0.11", default-features = false } tokio = { version = "1", features = [ "macros", "sync", ], default-features = false } tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_compression = { version = "0.8.1-beta", path = "../../libs/llrt_compression" } llrt_test = { path = "../../libs/llrt_test" } llrt_test_tls = { path = "../../libs/llrt_test_tls", default-features = false } wiremock = { version = "0.6", default-features = false } ================================================ FILE: modules/llrt_fetch/src/body.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use http_body_util::BodyExt; use hyper::{body::Incoming, Response}; use llrt_json::parse::json_parse; use llrt_utils::{bytes::ObjectBytes, result::ResultExt}; use rquickjs::{ class::{Trace, Tracer}, ArrayBuffer, Class, Ctx, Exception, IntoJs, JsLifetime, Null, Result, TypedArray, Value, }; use crate::MIME_TYPE_OCTET_STREAM; use super::{strip_bom, Blob, FormData}; // WARN: We don't use that code since we don't have an implementation of ReadableStream. // We will revisit later. enum BodyVariant<'js> { Incoming(Option>), Provided(Value<'js>), } #[rquickjs::class] pub struct Body<'js> { data: BodyVariant<'js>, content_type: Option, } unsafe impl<'js> JsLifetime<'js> for Body<'js> { type Changed<'to> = Body<'to>; } impl<'js> Trace<'js> for Body<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { if let BodyVariant::Provided(body) = &self.data { body.trace(tracer) } } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> Body<'js> { pub async fn text(&mut self, ctx: Ctx<'js>) -> Result { let bytes = self.take_bytes(&ctx).await?; Ok(String::from_utf8_lossy(&strip_bom(bytes)).to_string()) } pub async fn json(&mut self, ctx: Ctx<'js>) -> Result> { let bytes = self.take_bytes(&ctx).await?; json_parse(&ctx, strip_bom(bytes)) } pub async fn array_buffer(&mut self, ctx: Ctx<'js>) -> Result> { let bytes = self.take_bytes(&ctx).await?; ArrayBuffer::new(ctx, bytes) } pub async fn typed_array(&mut self, ctx: Ctx<'js>) -> Result> { let bytes = self.take_bytes(&ctx).await?; TypedArray::::new(ctx, bytes) } pub async fn blob(&mut self, ctx: Ctx<'js>) -> Result { let bytes = self.take_bytes(&ctx).await?; Ok(Blob::from_bytes(bytes, self.content_type.take())) //no need to copy, we can only take bytes once } async fn form_data(&mut self, ctx: Ctx<'js>) -> Result { let bytes = self.take_bytes(&ctx).await?; let content_type = self .content_type .take() .unwrap_or(MIME_TYPE_OCTET_STREAM.into()); FormData::from_multipart_bytes(&ctx, &content_type, bytes) } pub async fn bytes(&mut self, ctx: Ctx<'js>) -> Result> { let bytes = self.take_bytes(&ctx).await?; TypedArray::new(ctx, bytes).map(|m| m.into_value()) } pub fn is_used(&self) -> bool { if let BodyVariant::Incoming(data) = &self.data { return data.is_none(); } false } } impl<'js> Body<'js> { pub async fn get_text(ctx: Ctx<'js>, body: Option<&Class<'js, Self>>) -> Result { if let Some(body) = body { return body.borrow_mut().text(ctx).await; } Ok("".into()) } pub async fn get_json(ctx: Ctx<'js>, body: Option<&Class<'js, Self>>) -> Result> { if let Some(body) = body { return body.borrow_mut().json(ctx).await; } Err(Exception::throw_syntax(&ctx, "JSON input is empty")) } pub async fn get_array_buffer( ctx: Ctx<'js>, body: Option<&Class<'js, Self>>, ) -> Result> { if let Some(body) = body { return body.borrow_mut().array_buffer(ctx).await; } ArrayBuffer::new(ctx, Vec::::new()) } pub async fn get_blob(ctx: Ctx<'js>, body: Option<&Class<'js, Self>>) -> Result { if let Some(body) = body { return body.borrow_mut().blob(ctx).await; } Ok(Blob::from_bytes(Vec::::new(), None)) } pub fn get_body(ctx: Ctx<'js>, body: Option<&Class<'js, Self>>) -> Result> { if let Some(body) = body { return Ok(body.clone().into_value()); } Null.into_js(&ctx) } pub fn from_value( ctx: &Ctx<'js>, body: Option>, ) -> Result>> { if let Some(body) = body { if body.is_null() || body.is_undefined() { return Ok(None); } return Ok(Some(Class::instance( ctx.clone(), Self { data: BodyVariant::Provided(body), content_type: None, }, )?)); } Ok(None) } pub fn from_incoming( ctx: Ctx<'js>, response: Response, content_type: Option, ) -> Result> { Class::instance( ctx, Self { data: BodyVariant::Incoming(Some(response)), content_type, }, ) } pub async fn take_bytes(&mut self, ctx: &Ctx<'js>) -> Result> { let bytes = match &mut self.data { BodyVariant::Incoming(incoming) => { let mut body = incoming .take() .ok_or(Exception::throw_type(ctx, "Already read"))?; let bytes = body.body_mut().collect().await.or_throw(ctx)?.to_bytes(); bytes.into() }, BodyVariant::Provided(provided) => { if let Some(blob) = provided.as_object().and_then(Class::::from_object) { let blob = blob.borrow(); blob.get_bytes() } else { let bytes = ObjectBytes::from(ctx, provided)?; bytes.try_into().or_throw(ctx)? } }, }; Ok(bytes) } } ================================================ FILE: modules/llrt_fetch/src/fetch.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{collections::HashSet, convert::Infallible, sync::Arc, time::Instant}; use bytes::Bytes; use http_body_util::{combinators::BoxBody, Full}; use hyper::{header::HeaderName, Method, Request, Uri}; use llrt_abort::AbortSignal; use llrt_encoding::bytes_from_b64; use llrt_http::Agent; use llrt_http::HyperClient; use llrt_utils::{ bytes::{bytes_to_typed_array, ObjectBytes}, mc_oneshot, result::ResultExt, VERSION, }; use percent_encoding::percent_decode_str; use rquickjs::{ atom::PredefinedAtom, function::{Opt, This}, prelude::{Async, Func}, Class, Coerced, Ctx, Exception, FromJs, Function, IntoJs, Object, Result, Value, }; use tokio::{select, sync::Semaphore}; use super::{ headers::{Headers, HeadersGuard}, response::Response, security::ensure_url_access, Blob, }; // https://fetch.spec.whatwg.org/#port-blocking const BLOCKED_PORTS: [u16; 83] = [ 0, 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000, 6566, 6665, 6666, 6667, 6668, 6669, 6679, 6697, 10080, ]; const MAX_REDIRECT_COUNT: u32 = 20; pub fn init(global_client: HyperClient, globals: &Object) -> Result<()> { let connections = Arc::new(Semaphore::new(500)); globals.set( "fetch", Func::from(Async(move |ctx, resource, args| { let global_client = global_client.clone(); let connections = connections.clone(); let start = Instant::now(); let options = get_fetch_options(&ctx, resource, args); async move { let lock = connections.acquire().await; let options = options?; let client = options .agent .map(|agent| agent.borrow().client()) .unwrap_or(global_client); // https://fetch.spec.whatwg.org/#scheme-fetch if let Some((scheme, fragment)) = options.url.split_once(':') { match scheme { "http" | "https" => {}, "data" => return parse_data_url(&ctx, fragment, &options.method), "about" | "blob" | "file" => { return Err(Exception::throw_type(&ctx, "Unsupported scheme")); }, _ => return Err(Exception::throw_type(&ctx, "Invalid scheme")), } } let mut uri = options.url.parse::().map_err(|_| { Exception::throw_type(&ctx, &["Invalid URL :", &options.url].concat()) })?; let initial_uri: Uri = uri.clone(); let method_string = options.method.to_string(); let method = options.method; let abort_receiver = options.abort_receiver; ensure_url_access(&ctx, &uri)?; let mut redirect_count = 0; let mut response_status = 0; let (res, guard) = loop { let (req, guard) = build_request( &ctx, &method, &uri, options.headers.as_ref(), options.body.as_ref(), &response_status, &initial_uri, )?; let res = if let Some(abort_receiver) = &abort_receiver { select! { res = client.request(req) => res.or_throw(&ctx)?, reason = abort_receiver.recv() => return Err(ctx.throw(reason)) } } else { client.request(req).await.or_throw(&ctx)? }; let status = res.status(); if status.is_redirection() { match res.headers().get(HeaderName::from_static("location")) { Some(location_headers) => { if let Ok(location_str) = location_headers.to_str() { uri = location_str.parse().or_throw(&ctx)?; ensure_url_access(&ctx, &uri)?; } }, None => break (res, guard), }; } else { break (res, guard); }; if options.redirect == "manual" { break (res, guard); } else if options.redirect == "error" { return Err(Exception::throw_message(&ctx, "Unexpected redirect")); } redirect_count += 1; if redirect_count >= MAX_REDIRECT_COUNT { return Err(Exception::throw_message(&ctx, "Max retries exceeded")); } response_status = res.status().as_u16(); }; drop(lock); Response::from_incoming( ctx, res, method_string, uri.to_string(), start, !matches!(redirect_count, 0), abort_receiver, guard, ) } })), )?; Ok(()) } fn parse_data_url<'js>(ctx: &Ctx<'js>, data_url: &str, method: &Method) -> Result> { let (mime_type, data) = data_url .split_once(',') .ok_or_else(|| Exception::throw_type(ctx, "Invalid data URL format"))?; let mut is_base64 = false; let mut content_type = String::with_capacity(10); for (i, part) in mime_type.split(';').enumerate() { let part = part.trim(); if i == 1 || i == 2 { if part == "base64" { is_base64 = true; break; } content_type.push(';'); } content_type.push_str(part) } let content_type = if content_type.starts_with(';') { ["text/plain", &content_type].concat() } else if content_type.is_empty() { "text/plain;charset=US-ASCII".to_string() } else { content_type }; let body = if method == Method::HEAD { vec![] } else if is_base64 { bytes_from_b64(data.as_bytes()).or_throw(ctx)? } else { let data = percent_decode_str(data).decode_utf8().or_throw(ctx)?; data.as_bytes().into() }; let blob = Blob::from_bytes(body, Some(content_type.clone())).into_js(ctx)?; let headers = Object::new(ctx.clone())?; headers.set("content-type", content_type)?; let options = Object::new(ctx.clone())?; options.set("url", data_url)?; options.set("headers", headers)?; Response::new(ctx.clone(), Opt(Some(blob)), Opt(Some(options))) } fn build_request( ctx: &Ctx<'_>, method: &hyper::Method, uri: &Uri, headers: Option<&Headers>, body: Option<&BodyBytes>, prev_status: &u16, initial_uri: &Uri, ) -> Result<(Request>, HeadersGuard)> { if let Some(scheme) = uri.scheme_str() { if !matches!(scheme, "http" | "https") { return Err(Exception::throw_type(ctx, "Invalid scheme in URL")); } if let ("http", Some(port)) = (scheme, uri.authority().and_then(|a| a.port_u16())) { if BLOCKED_PORTS.contains(&port) { return Err(Exception::throw_type(ctx, "Invalid port in URL")); } } } let same_origin = is_same_origin(uri, initial_uri); let guard = if same_origin { HeadersGuard::Response } else { HeadersGuard::Immutable }; let change_method = should_change_method(*prev_status, method); let (method_to_use, body) = if change_method { (Method::GET, None) } else { (method.clone(), body) }; let mut req = Request::builder().method(method_to_use).uri(uri.clone()); let mut detected_headers = HashSet::new(); if let Some(headers) = headers { for (header_name, value) in headers.iter() { detected_headers.insert(header_name); if change_method && is_request_body_header_name(header_name) { continue; } if !same_origin && is_cors_non_wildcard_request_header_name(header_name) { continue; } req = req.header(header_name, value) } } if !detected_headers.contains("user-agent") { req = req.header("user-agent", ["llrt ", VERSION].concat()); } if !detected_headers.contains("accept-encoding") { req = req.header("accept-encoding", "zstd, br, gzip, deflate"); } if !detected_headers.contains("accept-language") { req = req.header("accept-language", "*"); } if !detected_headers.contains("accept") { req = req.header("accept", "*/*"); } let body = req .body(BoxBody::new( body.map(|b| b.body.clone()).unwrap_or_default(), )) .or_throw(ctx)?; Ok((body, guard)) } fn is_same_origin(uri: &Uri, initial_uri: &Uri) -> bool { is_same_scheme(uri, initial_uri) && is_same_host(uri, initial_uri) && is_same_port(uri, initial_uri) } fn is_same_scheme(uri: &Uri, initial_uri: &Uri) -> bool { uri.scheme() == initial_uri.scheme() } fn is_same_host(uri: &Uri, initial_uri: &Uri) -> bool { uri.host() == initial_uri.host() } fn is_same_port(uri: &Uri, initial_uri: &Uri) -> bool { uri.authority().and_then(|a| a.port()) == initial_uri.authority().and_then(|a| a.port()) } fn should_change_method(prev_status: u16, method: &Method) -> bool { if matches!(prev_status, 301 | 302) { return *method == Method::POST; } if prev_status == 303 { return !matches!(*method, Method::GET | Method::HEAD); } false } fn is_request_body_header_name(key: &str) -> bool { matches!( key, "content-encoding" | "content-language" | "content-location" | "content-type" ) } fn is_cors_non_wildcard_request_header_name(key: &str) -> bool { matches!(key, "authorization") } struct BodyBytes<'js> { #[allow(dead_code)] object_bytes: ObjectBytes<'js>, body: Full, } impl<'js> BodyBytes<'js> { fn new(ctx: Ctx<'js>, object_bytes: ObjectBytes<'js>) -> Result { //this is safe since we hold on to ObjectBytes let raw_bytes: &'static [u8] = unsafe { std::mem::transmute(object_bytes.as_bytes(&ctx)?) }; let body = Full::from(Bytes::from_static(raw_bytes)); Ok(Self { object_bytes, body }) } } struct FetchOptions<'js> { method: hyper::Method, url: String, headers: Option, body: Option>, abort_receiver: Option>>, redirect: String, agent: Option>, } fn get_fetch_options<'js>( ctx: &Ctx<'js>, resource: Value<'js>, opts: Opt>, ) -> Result> { let mut url = None; let mut resource_opts = None; let mut arg_opts = None; let mut headers = None; let mut method = None; let mut body = None; let mut abort_receiver = None; let mut redirect = String::from(""); let mut agent = None; if let Some(obj) = resource.as_object() { let obj = obj.clone(); if obj.instance_of::() { resource_opts = Some(obj); } else if let Some(to_string) = obj.get::<_, Option>(PredefinedAtom::ToString)? { url = Some(to_string.call::<_, String>((This(obj),))?); } else { resource_opts = Some(obj); } } else { url = Some(resource.get::>()?.to_string()); } if let Some(options) = opts.0 { arg_opts = options.into_object(); } if resource_opts.is_some() || arg_opts.is_some() { if let Some(method_opt) = get_option::("method", arg_opts.as_ref(), resource_opts.as_ref())? { method = Some(match method_opt.as_str() { "GET" => Ok(hyper::Method::GET), "POST" => Ok(hyper::Method::POST), "PUT" => Ok(hyper::Method::PUT), "CONNECT" => Ok(hyper::Method::CONNECT), "HEAD" => Ok(hyper::Method::HEAD), "PATCH" => Ok(hyper::Method::PATCH), "DELETE" => Ok(hyper::Method::DELETE), _ => Err(Exception::throw_type( ctx, &["Invalid HTTP method: ", &method_opt].concat(), )), }?); } if let Some(body_opt) = get_option::("body", arg_opts.as_ref(), resource_opts.as_ref())? { let bytes = if let Ok(blob) = Class::::from_value(&body_opt) { let blob = blob.borrow(); let typed_array = bytes_to_typed_array(ctx.clone(), &blob.get_bytes())?; ObjectBytes::from(ctx, &typed_array)? } else { ObjectBytes::from(ctx, &body_opt)? }; body = Some(BodyBytes::new(ctx.clone(), bytes)?); } if let Some(url_opt) = get_option::("url", arg_opts.as_ref(), resource_opts.as_ref())? { url = Some(url_opt); } if let Some(headers_op) = get_option::("headers", arg_opts.as_ref(), resource_opts.as_ref())? { headers = Some(Headers::from_value(ctx, headers_op, HeadersGuard::None)?); } if let Some(signal) = get_option::>("signal", arg_opts.as_ref(), resource_opts.as_ref())? { abort_receiver = Some(signal.borrow().sender.subscribe()); } if let Some(redirect_opt) = get_option::("redirect", arg_opts.as_ref(), resource_opts.as_ref())? { let redirect_str = redirect_opt.as_str(); if !matches!(redirect_str, "follow" | "manual" | "error") { return Err(Exception::throw_type( ctx, &["Invalid redirect option: ", redirect_str].concat(), )); } redirect.push_str(redirect_str); } if let Some(agent_opt) = get_option::>("agent", arg_opts.as_ref(), resource_opts.as_ref())? { agent = Some(agent_opt); } } let url = match url { Some(url) => url, None => return Err(Exception::throw_reference(ctx, "Missing required url")), }; Ok(FetchOptions { method: method.unwrap_or_default(), url, headers, body, abort_receiver, redirect, agent, }) } fn get_option<'js, V: FromJs<'js> + Sized>( arg: &str, a: Option<&Object<'js>>, b: Option<&Object<'js>>, ) -> Result> { if let Some(opt) = a { if let Some(value) = opt.get::<_, Option>(arg)? { return Ok(Some(value)); } } if let Some(opt) = b { if let Some(value) = opt.get::<_, Option>(arg)? { return Ok(Some(value)); } } Ok(None) } #[cfg(test)] mod tests { use std::io::Read; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", all(feature = "tls-graviola", target_arch = "x86_64") ))] use llrt_http::HttpsModule; use llrt_test::test_async_with; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", all(feature = "tls-graviola", target_arch = "x86_64") ))] use llrt_test::{call_test, ModuleEvaluator}; use rquickjs::{prelude::Promise, CatchResultExt}; use wiremock::{matchers, Mock, MockServer, ResponseTemplate}; use super::*; #[test] fn test_should_change_method() { // Test cases for prev_status being 301 or 302 assert!(should_change_method(301, &Method::POST)); assert!(should_change_method(302, &Method::POST)); assert!(!should_change_method(301, &Method::GET)); assert!(!should_change_method(302, &Method::GET)); assert!(!should_change_method(301, &Method::HEAD)); assert!(!should_change_method(302, &Method::HEAD)); // Test cases for prev_status being 303 assert!(should_change_method(303, &Method::POST)); assert!(!should_change_method(303, &Method::GET)); assert!(!should_change_method(303, &Method::HEAD)); // Test case for other prev_status values assert!(!should_change_method(200, &Method::POST)); assert!(!should_change_method(404, &Method::GET)); } #[test] fn test_is_request_body_header_name() { assert!(is_request_body_header_name("content-encoding")); assert!(is_request_body_header_name("content-language")); assert!(is_request_body_header_name("content-location")); assert!(is_request_body_header_name("content-type")); assert!(!is_request_body_header_name("content-length")); assert!(!is_request_body_header_name("accept")); } #[test] fn test_is_same_origin() { let uri1 = Uri::from_static("https://example.com:8080/path"); let uri2 = Uri::from_static("https://example.com:8080/path"); assert!(is_same_origin(&uri1, &uri2)); let uri3 = Uri::from_static("http://example.com/path"); let uri4 = Uri::from_static("https://example.com/path"); assert!(!is_same_origin(&uri3, &uri4)); let uri5 = Uri::from_static("https://example.com:8080/path"); let uri6 = Uri::from_static("https://example.org:8080/path"); assert!(!is_same_origin(&uri5, &uri6)); let uri7 = Uri::from_static("https://example.com:8080/path"); let uri8 = Uri::from_static("https://example.com:8081/path"); assert!(!is_same_origin(&uri7, &uri8)); } #[test] fn test_is_same_scheme() { let uri1 = Uri::from_static("https://example.com"); let uri2 = Uri::from_static("https://example.com"); assert!(is_same_scheme(&uri1, &uri2)); let uri3 = Uri::from_static("http://example.com"); let uri4 = Uri::from_static("https://example.com"); assert!(!is_same_scheme(&uri3, &uri4)); } #[test] fn test_is_same_host() { let uri1 = Uri::from_static("https://example.com"); let uri2 = Uri::from_static("https://example.com"); assert!(is_same_host(&uri1, &uri2)); let uri3 = Uri::from_static("https://example.com"); let uri4 = Uri::from_static("https://example.org"); assert!(!is_same_host(&uri3, &uri4)); } #[test] fn test_is_same_port() { let uri1 = Uri::from_static("https://example.com:8080"); let uri2 = Uri::from_static("https://example.com:8080"); assert!(is_same_port(&uri1, &uri2)); let uri3 = Uri::from_static("https://example.com:8080"); let uri4 = Uri::from_static("https://example.com:9090"); assert!(!is_same_port(&uri3, &uri4)); let uri5 = Uri::from_static("https://example.com"); let uri6 = Uri::from_static("https://example.com"); assert!(is_same_port(&uri5, &uri6)); let uri7 = Uri::from_static("https://example.com:8080"); let uri8 = Uri::from_static("https://example.com"); assert!(!is_same_port(&uri7, &uri8)); } #[tokio::test] async fn test_fetch_function() { let mock_server = MockServer::start().await; let welcome_message = "Hello, LLRT!"; Mock::given(matchers::path("expect/200/")) .respond_with( ResponseTemplate::new(200) .set_body_string(String::from_utf8(welcome_message.into()).unwrap()), ) .mount(&mock_server) .await; Mock::given(matchers::path("expect/301/")) .respond_with(ResponseTemplate::new(301).insert_header( "location", format!("http://{}/{}", mock_server.address(), "expect/200/"), )) .mount(&mock_server) .await; Mock::given(matchers::path("expect/302/")) .respond_with(ResponseTemplate::new(302).insert_header( "location", format!("http://{}/{}", mock_server.address(), "expect/200/"), )) .mount(&mock_server) .await; Mock::given(matchers::path("expect/303/")) .respond_with(ResponseTemplate::new(303).insert_header( "location", format!("http://{}/{}", mock_server.address(), "expect/200/"), )) .mount(&mock_server) .await; Mock::given(matchers::path("expect/304/")) .respond_with(ResponseTemplate::new(304).insert_header( "location", format!("http://{}/{}", mock_server.address(), "expect/200/"), )) .mount(&mock_server) .await; Mock::given(matchers::path("expect/location/")) .respond_with(ResponseTemplate::new(200).insert_header( "location", format!("http://{}/{}", mock_server.address(), "expect/200/"), )) .mount(&mock_server) .await; let mut data: Vec = Vec::new(); llrt_compression::zstd::encoder(welcome_message.as_bytes(), 3) .unwrap() .read_to_end(&mut data) .unwrap(); Mock::given(matchers::path("content-encoding/zstd/")) .respond_with( ResponseTemplate::new(200) .append_header("content-encoding", "zstd") .set_body_raw(data.to_owned(), "text/plain"), ) .mount(&mock_server) .await; let mut data: Vec = Vec::new(); llrt_compression::brotli::encoder(welcome_message.as_bytes()) .read_to_end(&mut data) .unwrap(); Mock::given(matchers::path("content-encoding/br/")) .respond_with( ResponseTemplate::new(200) .append_header("content-encoding", "br") .set_body_raw(data.to_owned(), "text/plain"), ) .mount(&mock_server) .await; let mut data: Vec = Vec::new(); llrt_compression::gz::encoder( welcome_message.as_bytes(), llrt_compression::gz::Compression::default(), ) .read_to_end(&mut data) .unwrap(); Mock::given(matchers::path("content-encoding/gzip/")) .respond_with( ResponseTemplate::new(200) .append_header("content-encoding", "gzip") .set_body_raw(data.to_owned(), "text/plain"), ) .mount(&mock_server) .await; let mut data: Vec = Vec::new(); llrt_compression::zlib::encoder( welcome_message.as_bytes(), llrt_compression::zlib::Compression::default(), ) .read_to_end(&mut data) .unwrap(); Mock::given(matchers::path("content-encoding/deflate/")) .respond_with( ResponseTemplate::new(200) .append_header("content-encoding", "deflate") .set_body_raw(data.to_owned(), "text/plain"), ) .mount(&mock_server) .await; // NOTE: A minimum redirect test pattern was created. Please add more as needed. test_async_with(|ctx| { crate::init(&ctx).unwrap(); Box::pin(async move { let globals = ctx.globals(); let run = async { let fetch: Function = globals.get("fetch")?; let headers = Object::new(ctx.clone())?; headers.set("content-encoding", "gzip")?; headers.set("content-language", "en")?; headers.set("content-location", "/documents/foo.txt")?; headers.set("content-type", "text/plain")?; headers.set("authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l")?; let options = Object::new(ctx.clone())?; options.set("redirect", "follow")?; options.set("headers", headers.clone())?; // Method: GET, Redirect Pattern: None options.set("method", "GET")?; let url = format!("http://{}/expect/200/", mock_server.address().clone()); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let response_text = response.text(ctx.clone()).await?; assert_eq!(response.status(), 200); assert_eq!( response.url(), format!("http://{}/expect/200/", mock_server.address().clone()) ); assert!(!response.redirected()); assert_eq!(response_text, welcome_message); // Method: GET, Redirect Pattern: 301 -> 200 options.set("method", "GET")?; let url = format!("http://{}/expect/301/", mock_server.address().clone()); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow(); assert_eq!(response.status(), 200); assert_eq!( response.url(), format!("http://{}/expect/200/", mock_server.address().clone()) ); assert!(response.redirected()); // Method: GET, Redirect Pattern: 302 -> 200 options.set("method", "GET")?; let url = format!("http://{}/expect/302/", mock_server.address().clone()); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow(); assert_eq!(response.status(), 200); assert_eq!( response.url(), format!("http://{}/expect/200/", mock_server.address().clone()) ); assert!(response.redirected()); // Method: GET, Redirect Pattern: 303 -> 200 options.set("method", "GET")?; let url = format!("http://{}/expect/303/", mock_server.address().clone()); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow(); assert_eq!(response.status(), 200); assert_eq!( response.url(), format!("http://{}/expect/200/", mock_server.address().clone()) ); assert!(response.redirected()); // Method: GET, Non-redirect status with location header (304) - should NOT follow options.set("method", "GET")?; let url = format!("http://{}/expect/location/", mock_server.address().clone()); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow(); assert_eq!(response.status(), 200); assert_eq!( response.url(), format!("http://{}/expect/location/", mock_server.address().clone()) ); assert!(!response.redirected()); // Content-Encoding: zstd let url = format!( "http://{}/content-encoding/zstd/", mock_server.address().clone() ); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let response_text = response.text(ctx.clone()).await?; assert_eq!(response_text, welcome_message); // Content-Encoding: br let url = format!( "http://{}/content-encoding/br/", mock_server.address().clone() ); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let response_text = response.text(ctx.clone()).await?; assert_eq!(response_text, welcome_message); // Content-Encoding: gzip let url = format!( "http://{}/content-encoding/gzip/", mock_server.address().clone() ); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let response_text = response.text(ctx.clone()).await?; assert_eq!(response_text, welcome_message); // Content-Encoding: deflate let url = format!( "http://{}/content-encoding/deflate/", mock_server.address().clone() ); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let response_text = response.text(ctx.clone()).await?; assert_eq!(response_text, welcome_message); Ok(()) }; run.await.catch(&ctx).unwrap(); }) }) .await; } #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", all(feature = "tls-graviola", target_arch = "x86_64") ))] #[tokio::test] async fn test_fetch_tls() { let mock_server = llrt_test_tls::MockServer::start().await.unwrap(); let addr = mock_server.address().to_string(); let ca = mock_server.ca().to_string(); test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "https") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { Agent } from 'https'; export async function test(addr, ca) { const response = await fetch(`https://${addr}/echo`, { method: "POST", body: "Hello, LLRT!", agent: new Agent({ ca: ca }), }); const text = await response.text(); return text; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (addr, ca)).await; assert_eq!(result, "Hello, LLRT!"); }) }) .await; } #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", all(feature = "tls-graviola", target_arch = "x86_64") ))] #[tokio::test] async fn test_fetch_ignore_certificate_errors() { let mock_server = llrt_test_tls::MockServer::start().await.unwrap(); let addr = mock_server.address().to_string(); test_async_with(|ctx| { Box::pin(async move { crate::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "https") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { Agent } from 'https'; export async function test(addr) { const response = await fetch(`https://${addr}/echo`, { method: "POST", body: "Hello, LLRT!", agent: new Agent({ rejectUnauthorized: false, }), }); const text = await response.text(); return text; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (addr,)).await; assert_eq!(result, "Hello, LLRT!"); }) }) .await; } } ================================================ FILE: modules/llrt_fetch/src/form_data.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ io::Write, sync::{Arc, Mutex}, }; use llrt_buffer::{Blob, File}; use llrt_utils::{class::IteratorDef, object::map_to_entries, result::ResultExt}; use rand::RngExt; use rquickjs::{ atom::PredefinedAtom, class::Trace, prelude::Opt, Array, Class, Ctx, Exception, Function, IntoJs, JsLifetime, Result, Value, }; #[derive(Clone)] enum FormValue { Text(String), File(File), Blob(Blob), } impl<'js> IntoJs<'js> for FormValue { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self { FormValue::Text(s) => s.into_js(ctx), FormValue::File(f) => f.clone().into_js(ctx), FormValue::Blob(b) => b.clone().into_js(ctx), } } } #[derive(Clone, Trace, JsLifetime, Default)] #[rquickjs::class] pub struct FormData { #[qjs(skip_trace)] entries: Arc>>, } impl<'js> IteratorDef<'js> for FormData { fn js_entries(&self, ctx: Ctx<'js>) -> Result> { let entries = self.entries.lock().or_throw(&ctx)?; map_to_entries(&ctx, entries.clone()) } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> FormData { #[qjs(constructor)] pub fn new(_form: Opt>, _submitter: Opt>) -> Self { Self { entries: Arc::new(Mutex::new(Vec::new())), } } pub fn append(&self, ctx: Ctx<'js>, name: String, value: Value<'js>) -> Result<()> { let mut entries = self.entries.lock().or_throw(&ctx)?; if let Some(obj) = value.clone().into_object() { if let Some(f) = Class::::from_object(&obj) { let file = f.borrow().to_owned(); entries.push((name, FormValue::File(file))); return Ok(()); } if let Some(b) = Class::::from_object(&obj) { let blob = b.borrow().to_owned(); entries.push((name, FormValue::Blob(blob))); return Ok(()); } } if let Some(s) = value.as_string() { let str = s.to_string().or_throw(&ctx)?; entries.push((name, FormValue::Text(str))); return Ok(()); } Err(Exception::throw_type(&ctx, "Invalid FormData value type")) } pub fn get(&self, ctx: Ctx<'js>, name: String) -> Result>> { let entries = self.entries.lock().or_throw(&ctx)?; for (k, v) in entries.iter().rev() { if *k == name { return Ok(v.clone().into_js(&ctx).ok()); } } Ok(None) } pub fn get_all(&self, ctx: Ctx<'js>, name: String) -> Result>> { let entries = self.entries.lock().or_throw(&ctx)?; Ok(entries .iter() .filter(|(k, _)| *k == name) .filter_map(|(_, v)| v.clone().into_js(&ctx).ok()) .collect()) } pub fn has(&self, ctx: Ctx<'js>, name: String) -> Result { let entries = self.entries.lock().or_throw(&ctx)?; Ok(entries.iter().any(|(n, _)| n == &name)) } pub fn set(&self, ctx: Ctx<'js>, name: String, value: Value<'js>) -> Result<()> { let mut entries = self.entries.lock().or_throw(&ctx)?; entries.retain(|(k, _)| *k != name); if let Some(obj) = value.clone().into_object() { if let Some(f) = Class::::from_object(&obj) { let file = f.borrow().to_owned(); entries.push((name, FormValue::File(file))); return Ok(()); } if let Some(b) = Class::::from_object(&obj) { let blob = b.borrow().to_owned(); entries.push((name, FormValue::Blob(blob))); return Ok(()); } } if let Ok(s) = value.try_into_string() { let string = s.to_string().or_throw(&ctx)?; entries.push((name, FormValue::Text(string))); return Ok(()); } Err(Exception::throw_type(&ctx, "Invalid FormData value type")) } pub fn delete(&self, ctx: Ctx<'js>, name: String) -> Result<()> { let mut entries = self.entries.lock().or_throw(&ctx)?; entries.retain(|(k, _)| *k != name); Ok(()) } pub fn keys(&self, ctx: Ctx<'js>) -> Result> { let entries = self.entries.lock().or_throw(&ctx)?; Ok(entries.iter().map(|(k, _)| k.clone()).collect()) } pub fn values(&self, ctx: Ctx<'js>) -> Result>> { let ctx2 = ctx.clone(); let entries = self.entries.lock().or_throw(&ctx)?; Ok(entries .iter() .filter_map(|(_, v)| v.clone().into_js(&ctx2).ok()) .collect()) } pub fn entries(&self, ctx: Ctx<'js>) -> Result> { self.js_iterator(ctx) } #[qjs(rename = PredefinedAtom::SymbolIterator)] pub fn iterator(&self, ctx: Ctx<'js>) -> Result> { self.js_iterator(ctx) } pub fn for_each(&self, ctx: Ctx<'js>, callback: Function<'js>) -> Result<()> { let entries = self.entries.lock().or_throw(&ctx)?; for (name, value) in entries.iter() { let val = value.clone().into_js(&ctx)?; () = callback.call((val, name.clone()))?; } Ok(()) } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(FormData) } } impl FormData { #[allow(private_interfaces)] pub fn iter<'js>(&self, ctx: &Ctx<'js>) -> Result> { let entries = self.entries.lock().or_throw(ctx)?; let entries = entries.clone(); Ok(entries.into_iter()) } pub fn from_multipart_bytes<'js>( ctx: &Ctx<'js>, content_type: &str, bytes: Vec, ) -> Result { if bytes.is_empty() { return Ok(Self::default()); } let Some(boundary) = extract_boundary(content_type) else { return Ok(Self::default()); }; let boundary_marker = ["--", &boundary].concat().into_bytes(); let mut entries = Vec::new(); let parts = bytes.split(|b| *b == b'\n').collect::>(); let mut current_headers = Vec::new(); let mut current_data = Vec::new(); let mut in_headers = false; let mut name: Option = None; let mut filename: Option = None; let mut mime_type: Option = None; for line in parts { if line.starts_with(&boundary_marker) { if !current_data.is_empty() && name.is_some() { let data = std::mem::take(&mut current_data); if let Some(filename) = filename.take() { let file = File::from_bytes(ctx, data, filename, mime_type)?; entries.push((name.take().or_throw(ctx)?, FormValue::File(file))); } else { let text = String::from_utf8_lossy(&data).into_owned(); entries.push((name.take().or_throw(ctx)?, FormValue::Text(text))); } } current_headers.clear(); current_data.clear(); name = None; filename = None; mime_type = None; in_headers = true; continue; } if in_headers { let line_str = String::from_utf8_lossy(line); if line_str.trim().is_empty() { in_headers = false; } else { current_headers.push(line_str.to_string()); if line_str.to_lowercase().starts_with("content-disposition") { for seg in line_str.split(';') { let seg = seg.trim(); if let Some(n) = seg.strip_prefix("name=") { name = Some(n.trim_matches('"').into()); } else if let Some(f) = seg.strip_prefix("filename=") { filename = Some(f.trim_matches('"').into()); } } } else if line_str.to_lowercase().starts_with("content-type") { if let Some(ct) = line_str.split(':').nth(1) { mime_type = Some(ct.trim().into()); } } } } else { current_data.extend_from_slice(line); current_data.push(b'\n'); } } Ok(Self { entries: Arc::new(Mutex::new(entries)), }) } pub fn to_multipart_bytes<'js>(&self, ctx: &Ctx<'js>) -> Result<(Vec, String)> { let boundary = generate_boundary(); let mut body = Vec::new(); let entries = self.entries.lock().or_throw(ctx)?; for (name, value) in entries.iter() { match value { FormValue::Text(text) => { write!( body, "--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\n\r\n{text}\r\n" )?; }, FormValue::File(file) => { let filename = file.name().clone(); let content_type = file.mime_type().clone(); let bytes = file.get_blob().get_bytes(); write!( body, "--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"; filename=\"{filename}\"\r\nContent-Type: {content_type}\r\n\r\n" )?; body.extend_from_slice(&bytes); body.extend_from_slice(b"\r\n"); }, FormValue::Blob(blob) => { let bytes = blob.get_bytes(); let content_type = blob.mime_type(); write!( body, "--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"; filename=\"blob\"\r\nContent-Type: {content_type}\r\n\r\n" )?; body.extend_from_slice(&bytes); body.extend_from_slice(b"\r\n"); }, } } write!(body, "--{boundary}--\r\n")?; Ok((body, boundary)) } } fn extract_boundary(content_type: &str) -> Option { content_type.split(';').find_map(|part| { let part = part.trim(); part.find("boundary=").map(|idx| { part[(idx + "boundary=".len())..] .trim() .trim_matches('"') .into() }) }) } fn generate_boundary() -> String { let rand_string: String = rand::rng() .sample_iter(&rand::distr::Alphanumeric) .take(24) .map(char::from) .collect(); ["----WebKitFormBoundary", &rand_string].concat() } ================================================ FILE: modules/llrt_fetch/src/headers.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::{collections::BTreeMap, rc::Rc}; use hyper::HeaderMap; use llrt_utils::{ class::{CustomInspect, IteratorDef}, object::map_to_entries, primordials::{BasePrimordials, Primordial}, result::ResultExt, }; use rquickjs::{ atom::PredefinedAtom, class::Trace, methods, prelude::Opt, Array, Coerced, Ctx, Exception, FromJs, Function, IntoJs, Iterable, JsLifetime, Null, Object, Result, Symbol, Value, }; const HEADERS_KEY_COOKIE: &str = "cookie"; const HEADERS_KEY_SET_COOKIE: &str = "set-cookie"; pub const HEADERS_KEY_CONTENT_TYPE: &str = "content-type"; type ImmutableString = Rc; // https://fetch.spec.whatwg.org/#concept-headers-guard #[derive(Clone, Default, PartialEq)] pub enum HeadersGuard { #[default] None, Request, RequestNoCors, Response, Immutable, } #[derive(Clone, Default, Trace, JsLifetime)] #[rquickjs::class] pub struct Headers { #[qjs(skip_trace)] headers: Vec<(ImmutableString, ImmutableString)>, #[qjs(skip_trace)] guard: HeadersGuard, } #[methods(rename_all = "camelCase")] impl Headers { #[qjs(constructor)] pub fn new<'js>(ctx: Ctx<'js>, init: Opt>) -> Result { if let Some(init) = init.into_inner() { if init.is_null() || init.is_number() { return Err(Exception::throw_type(&ctx, "Invalid argument")); } return Self::from_value(&ctx, init, HeadersGuard::None); } Ok(Self { headers: Vec::new(), guard: HeadersGuard::None, }) } pub fn append<'js>(&mut self, ctx: Ctx<'js>, key: String, value: Value<'js>) -> Result<()> { let key: ImmutableString = key.to_lowercase().into(); if !is_http_header_name(&key) { return Err(Exception::throw_type(&ctx, "Invalid key")); } let mut value = coerce_to_string(&ctx, value)?; normalize_header_value_inplace(&ctx, &mut value)?; if self.guard == HeadersGuard::RequestNoCors { let val = value.split(',').next().unwrap_or("").trim(); if !is_cors_safelisted_request_header(&key, val) { return Ok(()); // silently ignore disallowed header } if self.headers.iter().any(|(k, _)| k == &key) { return Ok(()); // silently ignore same header } value = val.into(); }; if !is_http_header_value(&value) { return Err(Exception::throw_type(&ctx, "Invalid value of key")); } let str_key = key.as_ref(); if str_key == HEADERS_KEY_SET_COOKIE { return { self.headers.push((key, value.into())); Ok(()) }; } if let Some((_, existing_value)) = self.headers.iter_mut().find(|(k, _)| k == &key) { let mut new_value = String::with_capacity(existing_value.len() + 2 + value.len()); new_value.push_str(existing_value); match str_key { HEADERS_KEY_COOKIE => new_value.push_str("; "), _ => new_value.push_str(", "), } new_value.push_str(&value); *existing_value = new_value.into(); } else { self.headers.push((key, value.into())); } Ok(()) } pub fn get<'js>(&self, ctx: Ctx<'js>, key: String) -> Result> { let key: ImmutableString = key.to_lowercase().into(); if !is_http_header_name(&key) { return Err(Exception::throw_type(&ctx, "Invalid key")); } if key.as_ref() == HEADERS_KEY_SET_COOKIE { let result: Vec<&str> = self .headers .iter() .filter_map(|(k, v)| if k == &key { Some(v.as_ref()) } else { None }) .collect(); return if result.is_empty() { Null.into_js(&ctx) } else { result.join(", ").into_js(&ctx) }; } self.headers .iter() .find(|(k, _)| *k == key) .map(|(_, v)| v.as_ref().into_js(&ctx)) .unwrap_or_else(|| Null.into_js(&ctx)) } pub fn get_set_cookie(&self) -> Vec<&str> { self.headers .iter() .filter_map(|(k, v)| { if k.as_ref() == HEADERS_KEY_SET_COOKIE { Some(v.as_ref()) } else { None } }) .collect() } pub fn has<'js>(&self, ctx: Ctx<'js>, key: String) -> Result { let key: ImmutableString = key.to_lowercase().into(); if !is_http_header_name(&key) { return Err(Exception::throw_type(&ctx, "Invalid key")); } Ok(self.headers.iter().any(|(k, _)| k == &key)) } pub fn set<'js>(&mut self, ctx: Ctx<'js>, key: String, value: Value<'js>) -> Result<()> { let key: ImmutableString = key.to_lowercase().into(); if !is_http_header_name(&key) { return Err(Exception::throw_type(&ctx, "Invalid key")); } let mut value = coerce_to_string(&ctx, value)?; normalize_header_value_inplace(&ctx, &mut value)?; if self.guard == HeadersGuard::RequestNoCors { let val = value.split(',').next().unwrap_or("").trim(); if !is_cors_safelisted_request_header(&key, val) { return Ok(()); // silently ignore disallowed header } value = val.into(); } if !is_http_header_value(&value) { return Err(Exception::throw_type(&ctx, "Invalid value of key")); } if key.as_ref() == HEADERS_KEY_SET_COOKIE { self.headers.retain(|(k, _)| k != &key); self.headers.push((key, value.into())); } else { match self.headers.iter_mut().find(|(k, _)| k == &key) { Some((_, existing_value)) => *existing_value = value.into(), None => self.headers.push((key, value.into())), } } Ok(()) } pub fn delete<'js>(&mut self, ctx: Ctx<'js>, key: String) -> Result<()> { let key: ImmutableString = key.to_lowercase().into(); if !is_http_header_name(&key) { return Err(Exception::throw_type(&ctx, "Invalid key")); } self.headers.retain(|(k, _)| k != &key); Ok(()) } pub fn keys<'js>(&self, ctx: Ctx<'js>) -> Result> { Iterable::from( self.headers .iter() .map(|(k, _)| k.to_string()) .collect::>(), ) .into_js(&ctx) } pub fn values<'js>(&self, ctx: Ctx<'js>) -> Result> { Iterable::from( self.headers .iter() .map(|(_, v)| v.to_string()) .collect::>(), ) .into_js(&ctx) } pub fn entries<'js>(&self, ctx: Ctx<'js>) -> Result> { self.js_iterator(ctx) } #[qjs(rename = PredefinedAtom::SymbolIterator)] pub fn iterator<'js>(&self, ctx: Ctx<'js>) -> Result> { self.js_iterator(ctx) } pub fn for_each(&self, callback: Function<'_>) -> Result<()> { for (k, v) in &self.headers { () = callback.call((v.as_ref(), k.as_ref()))?; } Ok(()) } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(Headers) } } impl Headers { pub fn iter(&self) -> impl Iterator { self.headers.iter().map(|(k, v)| (k.as_ref(), v.as_ref())) } pub fn from_http_headers(header_map: &HeaderMap, guard: HeadersGuard) -> Result { let mut headers = Vec::new(); for (n, v) in header_map.iter() { headers.push(( n.as_str().into(), String::from_utf8_lossy(v.as_bytes()).into(), )); } Ok(Self { headers, guard }) } pub fn from_value<'js>(ctx: &Ctx<'js>, value: Value<'js>, guard: HeadersGuard) -> Result { if value.is_array() { let array = unsafe { value.into_array().unwrap_unchecked() }; return Self::from_array(ctx, array, guard); } if let Some(obj) = value.as_object() { if obj.contains_key(Symbol::iterator(ctx.clone()))? { let array: Array = BasePrimordials::get(ctx)? .function_array_from .call((value,))?; return Self::from_array(ctx, array, guard); } else if obj.instance_of::() { return Headers::from_js(ctx, value); } else { let map: BTreeMap> = value.get().unwrap_or_default(); return Ok(Self::from_map(ctx, map, guard)); } } Ok(Self { headers: vec![], guard, }) } pub fn from_map( ctx: &Ctx<'_>, map: BTreeMap>, guard: HeadersGuard, ) -> Self { let headers = map .into_iter() .filter_map(|(k, v)| { if !is_http_header_name(&k) { return None; } let mut value = v.0; let _ = normalize_header_value_inplace(ctx, &mut value); Some((k.to_lowercase().into(), value.into())) }) .collect::, Rc)>>(); Self { headers, guard } } fn from_array<'js>(ctx: &Ctx<'js>, array: Array<'js>, guard: HeadersGuard) -> Result { let mut headers: Vec<(ImmutableString, ImmutableString)> = Vec::new(); for entry in array.into_iter().flatten() { if let Some(array_entry) = entry.as_array() { if array_entry.len() % 2 != 0 { return Err(Exception::throw_type(ctx, "Header arrays are not paired")); } let raw_key = array_entry.get::(0)?.to_lowercase(); let key: Rc = ImmutableString::from(raw_key.clone()); if !is_http_header_name(&key) { return Err(Exception::throw_type(ctx, "Invalid key")); } let raw_value = array_entry.get::(1)?; let value: ImmutableString = coerce_to_string(ctx, raw_value)?.into(); if !is_http_header_value(&value) { return Err(Exception::throw_type(ctx, "Invalid value of key")); } if raw_key == HEADERS_KEY_SET_COOKIE { headers.push((key, value)); continue; } if let Some((_, existing_value)) = headers.iter_mut().find(|(k, _)| *k == key) { let mut new_value = existing_value.to_string(); match raw_key.as_str() { HEADERS_KEY_COOKIE => new_value.push_str("; "), _ => new_value.push_str(", "), } new_value.push_str(&value); *existing_value = ImmutableString::from(new_value); } else { headers.push((key, value)); } } } headers.sort_by(|a, b| a.0.cmp(&b.0)); Ok(Self { headers, guard }) } } impl<'js> IteratorDef<'js> for Headers { fn js_entries(&self, ctx: Ctx<'js>) -> Result> { map_to_entries( &ctx, self.headers.iter().map(|(k, v)| (k.as_ref(), v.as_ref())), ) } } impl<'js> CustomInspect<'js> for Headers { fn custom_inspect(&self, ctx: Ctx<'js>) -> Result> { let obj = Object::new(ctx)?; for (k, v) in self.headers.iter() { obj.set(k.as_ref(), v.as_ref())?; } Ok(obj) } } fn coerce_to_string<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> Result { if value.is_null() { Ok("null".into()) } else if value.is_undefined() { Ok("undefined".into()) } else if let Some(v) = value.as_bool() { Ok(v.to_string()) } else if let Some(v) = value.as_number() { Ok(v.to_string()) } else if let Some(s) = value.as_string() { s.to_string() } else { // fallback: try JSON.stringify or [object Object] let base_primordials = BasePrimordials::get(ctx)?; base_primordials.constructor_string.construct((value,)) } } // 3.2.6. Field Value Components // https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 fn is_http_header_name(name: &str) -> bool { if name.is_empty() { return false; } name.bytes().all(|b| { matches!(b, b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*' | b'+' | b'-' | b'.' | b'^' | b'_' | b'`' | b'|' | b'~' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' ) }) } fn is_http_header_value(value: &str) -> bool { value.chars().all(|c| { c == '\t' // HTAB || c == ' ' // SP || (('\u{21}'..='\u{7E}').contains(&c)) // VCHAR range || c == '\u{0C}' // Form Feed || c == '\u{00A0}' // NBSP }) } fn normalize_header_value_inplace(ctx: &Ctx<'_>, text: &mut String) -> Result<()> { let mut input = std::mem::take(text).into_bytes(); let mut read_idx = 0; let mut write_idx = 0; // Skip leading SP or HTAB while read_idx < input.len() && (input[read_idx] == b' ' || input[read_idx] == b'\t') { read_idx += 1; } // Store the last whitespace byte if any (space or tab) let mut pending_whitespace: Option = None; while read_idx < input.len() { match input[read_idx] { // obs-fold: CRLF followed by SP or HTAB b'\r' if read_idx + 2 < input.len() && input[read_idx + 1] == b'\n' && (input[read_idx + 2] == b' ' || input[read_idx + 2] == b'\t') => { pending_whitespace = Some(input[read_idx + 2]); read_idx += 3; }, b'\r' | b'\n' => { // skip bare CR or LF read_idx += 1; }, b' ' | b'\t' => { // keep the last whitespace char to write later (collapse multiple) pending_whitespace = Some(input[read_idx]); read_idx += 1; }, byte => { // write pending whitespace if any if let Some(ws) = pending_whitespace.take() { if write_idx > 0 { input[write_idx] = ws; write_idx += 1; } } input[write_idx] = byte; write_idx += 1; read_idx += 1; }, } } // Trim trailing SP or HTAB while write_idx > 0 && (input[write_idx - 1] == b' ' || input[write_idx - 1] == b'\t') { write_idx -= 1; } input.truncate(write_idx); *text = String::from_utf8(input).or_throw(ctx)?; Ok(()) } // https://fetch.spec.whatwg.org/#cors-safelisted-request-header pub fn is_cors_safelisted_request_header(key: &str, value: &str) -> bool { if value.len() > 128 { return false; } match key.to_ascii_lowercase().as_str() { "accept" => !contains_cors_unsafe_request_header_byte(value), "accept-language" | "content-language" => is_cors_safelisted_field_value(value), "content-type" => { if contains_cors_unsafe_request_header_byte(value) { return false; } let mime_type = value.split(';').next().unwrap_or("").trim(); matches!( mime_type.to_ascii_lowercase().as_str(), "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain" | "" ) }, _ => false, } } // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte pub fn contains_cors_unsafe_request_header_byte(value: &str) -> bool { for byte in value.bytes() { match byte { // Control characters except for HT (0x09) 0x00..=0x08 | 0x0A..=0x1F => return true, // byte is 0x22 ("), 0x28 (left parenthesis), 0x29 (right parenthesis), 0x3A (:), 0x3C (<), // 0x3E (>), 0x3F (?), 0x40 (@), 0x5B ([), 0x5C (\), 0x5D (]), 0x7B ({), 0x7D (}), or 0x7F DEL. 0x22 | 0x28 | 0x29 | 0x3A | 0x3C | 0x3E | 0x3F | 0x40 | 0x5B | 0x5C | 0x5D | 0x7B | 0x7F | 0x7D => return true, _ => {}, // Allowed byte } } false } pub fn is_cors_safelisted_field_value(value: &str) -> bool { value.bytes().all(|b| match b { 0x30..=0x39 | // 0-9 0x41..=0x5A | // A-Z 0x61..=0x7A | // a-z 0x20 | 0x2A | 0x2C | 0x2D | 0x2E | 0x3B | 0x3D => true, // allowed symbols _ => false, }) } #[cfg(test)] mod tests { use llrt_test::test_async_with; use super::*; #[tokio::test] async fn test_get_header() { test_async_with(|ctx| { crate::init(&ctx).unwrap(); Box::pin(async move { let mut headers = Headers::new(ctx.clone(), Opt(None)).unwrap(); headers .set( ctx.clone(), "Content-Type".into(), "application/json".into_js(&ctx).unwrap(), ) .unwrap(); let append_headers = [ ("set-cookie", "cookie1=value1"), ("set-cookie", "cookie2=value2"), ("Accept-Encoding", "deflate"), ("Accept-Encoding", "gzip"), ]; for (key, value) in append_headers { headers .append(ctx.clone(), key.into(), value.into_js(&ctx).unwrap()) .unwrap(); } let get_headers = [ ("Content-Type", "application/json"), ("set-cookie", "cookie1=value1, cookie2=value2"), ("Accept-Encoding", "deflate, gzip"), ]; for (key, expected) in get_headers { assert_eq!( headers .get(ctx.clone(), key.into()) .unwrap() .as_string() .unwrap() .to_string() .unwrap(), expected ); } }) }) .await; } #[tokio::test] async fn test_normalize_header_value_inplace() { test_async_with(|ctx| { crate::init(&ctx).unwrap(); Box::pin(async move { // https://github.com/web-platform-tests/wpt/blob/master/fetch/api/headers/headers-normalize.any.js let expectations = [ (" space ", "space"), ("\ttab\t", "tab"), (" spaceAndTab\t", "spaceAndTab"), ("\r\n newLine", "newLine"), ("newLine\r\n ", "newLine"), ("\r\n\tnewLine", "newLine"), ("\t\u{000C}\tnewLine\n", "\u{000C}\tnewLine"), // \f = \u{000C} ("newLine\u{00A0}", "newLine\u{00A0}"), // \u{00A0} = NBSP ]; for (input, expected) in expectations { let mut value = input.to_string(); super::normalize_header_value_inplace(&ctx, &mut value).unwrap(); assert_eq!( value, expected, "normalize_header_value_inplace failed: input = {:?}, expected = {:?}, got = {:?}", input, expected, value ); } }) }) .await; } #[tokio::test] async fn test_headers_iterators() { test_async_with(|ctx| { crate::init(&ctx).unwrap(); Box::pin(async move { let result: bool = ctx .eval( r#" const h = new Headers([['x-first', '1'], ['x-second', '2']]); const iter = h.keys()[Symbol.iterator](); iter.next().value === 'x-first' && iter.next().value === 'x-second' && iter.next().done === true "#, ) .unwrap(); assert!(result, "keys() iterator failed"); let result: bool = ctx .eval( r#" const h2 = new Headers([['x-first', '1'], ['x-second', '2']]); const iter2 = h2.values()[Symbol.iterator](); iter2.next().value === '1' && iter2.next().value === '2' && iter2.next().done === true "#, ) .unwrap(); assert!(result, "values() iterator failed"); }) }) .await; } } ================================================ FILE: modules/llrt_fetch/src/incoming.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ error::Error as StdError, future::Future, pin::Pin, sync::Arc, task::{ready, Context, Poll}, }; use bytes::Bytes; use http_body_util::BodyExt; use hyper::{ body::{Body, Frame, Incoming, SizeHint}, HeaderMap, }; use llrt_utils::error_messages::ERROR_MSG_BROADCAST_LAGGED; use pin_project_lite::pin_project; use tokio::sync::{broadcast, watch}; pub fn channel(incoming: Incoming) -> (IncomingSender, IncomingReceiver) { let (data_tx, data_rx) = broadcast::channel(16); let (want_tx, want_rx) = watch::channel(()); let sender = IncomingSender { inner: incoming, want_rx, data_tx, }; let receiver = IncomingReceiver { closed: false, recv_fut: None, want_tx, data_rx, }; (sender, receiver) } // The hyper frame is not `Clone`, so we need our own. // See: https://github.com/hyperium/hyper/discussions/3768 #[derive(Clone)] enum ClonableFrame { Data(T), Trailers(HeaderMap), } impl From> for ClonableFrame { fn from(frame: Frame) -> Self { let frame = match frame.into_data() { Ok(data) => return ClonableFrame::Data(data), Err(frame) => frame, }; match frame.into_trailers() { Ok(trailers) => ClonableFrame::Trailers(trailers), Err(_) => unreachable!(), } } } impl From> for Frame { fn from(frame: ClonableFrame) -> Self { match frame { ClonableFrame::Data(data) => Frame::data(data), ClonableFrame::Trailers(trailers) => Frame::trailers(trailers), } } } type RecvOutput = Result, Arc>, broadcast::error::RecvError>; pub struct IncomingSender { inner: Incoming, want_rx: watch::Receiver<()>, data_tx: broadcast::Sender, Arc>>, } impl IncomingSender { pub async fn process(mut self) { loop { // Wait for the receiver to be ready if self.want_rx.changed().await.is_err() { tracing::trace!("All receivers are dead, closing sender"); return; } // Check if the receiver is closed if self.inner.is_end_stream() { return; } // Get the next frame let frame = match self.inner.frame().await { Some(Ok(frame)) => frame, Some(Err(err)) => { self.data_tx.send(Err(Arc::new(err))).ok(); continue; }, None => return, }; // Send the frame let clonable_frame = ClonableFrame::from(frame); if self.data_tx.send(Ok(clonable_frame)).is_err() { tracing::trace!("All receivers are dead, closing sender"); return; } } } } pin_project! { pub struct IncomingReceiver { closed: bool, #[pin] recv_fut: Option>>>, want_tx: watch::Sender<()>, #[pin] data_rx: broadcast::Receiver, Arc>>, } } impl Clone for IncomingReceiver { fn clone(&self) -> Self { Self { closed: self.closed, recv_fut: None, want_tx: self.want_tx.clone(), data_rx: self.data_rx.resubscribe(), } } } impl Body for IncomingReceiver { type Data = Bytes; type Error = Box; fn poll_frame( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll, Self::Error>>> { let mut this = self.project(); // We already have a pending frame, poll it if let Some(recv_fut) = this.recv_fut.as_mut().as_pin_mut() { let recv_out = match ready!(recv_fut.poll(cx)) { Ok(Ok(frame)) => Some(Ok(Frame::from(frame))), Ok(Err(err)) => Some(Err(err.into())), Err(broadcast::error::RecvError::Lagged(_)) => { Some(Err(ERROR_MSG_BROADCAST_LAGGED.into())) }, Err(broadcast::error::RecvError::Closed) => { *this.closed = true; None }, }; *this.recv_fut = None; return Poll::Ready(recv_out); } // If the receiver is closed, we are done if *this.closed { return Poll::Ready(None); } // Check if there are frames available match this.data_rx.try_recv() { Ok(Ok(frame)) => return Poll::Ready(Some(Ok(Frame::from(frame)))), Ok(Err(err)) => return Poll::Ready(Some(Err(err.into()))), Err(broadcast::error::TryRecvError::Lagged(_)) => { return Poll::Ready(Some(Err(ERROR_MSG_BROADCAST_LAGGED.into()))); }, Err(broadcast::error::TryRecvError::Empty) => (), Err(broadcast::error::TryRecvError::Closed) => { *this.closed = true; return Poll::Ready(None); }, } // Signal the sender that we are ready to receive if this.want_tx.send(()).is_err() { *this.closed = true; return Poll::Ready(None); } // Wait for the next frame let recv_fut = Box::pin(this.data_rx.recv()); let recv_fut_static = erase_lifetime(recv_fut); *this.recv_fut = Some(recv_fut_static); Poll::Pending } fn is_end_stream(&self) -> bool { self.closed } fn size_hint(&self) -> SizeHint { // Since use a broadcast and a reader can miss frames, we can't know the exact size SizeHint::default() } } fn erase_lifetime<'a, T>( fut: Pin + Send + 'a>>, ) -> Pin + Send + 'static>> { // SAFETY: This is safe since data_rx is pinned unsafe { std::mem::transmute(fut) } } #[cfg(test)] mod tests { use std::vec; use llrt_test::{test_async_with_opts, TestOptions}; use rquickjs::{CatchResultExt, CaughtError, Class, Function, Object, Promise}; use wiremock::*; use super::*; use crate::*; #[tokio::test] async fn test_incoming() { let mock_server = MockServer::start().await; let welcome_message = "Hello, LLRT!"; Mock::given(matchers::path("some-path/")) .respond_with(ResponseTemplate::new(200).set_body_string(welcome_message.to_string())) .mount(&mock_server) .await; test_async_with_opts( |ctx| { crate::init(&ctx).unwrap(); Box::pin(async move { let globals = ctx.globals(); let run = async { let fetch: Function = globals.get("fetch")?; let options = Object::new(ctx.clone())?; options.set("method", "GET")?; let url = format!("http://{}/some-path/", mock_server.address().clone()); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let response2 = response.clone(ctx.clone()).unwrap(); let (response_res, response2_res) = tokio::join!(response.text(ctx.clone()), response2.text(ctx.clone())); let response_text = response_res.unwrap(); assert_eq!(response.status(), 200); assert_eq!(response_text, welcome_message); let response2_text = response2_res.unwrap(); assert_eq!(response2.status(), 200); assert_eq!(response2_text, welcome_message); Ok(()) }; run.await.catch(&ctx).unwrap(); }) }, TestOptions::new().no_pending_jobs(), ) .await; } #[tokio::test] async fn test_incoming_dropped() { let mock_server = MockServer::start().await; let welcome_message = "Hello, LLRT!"; Mock::given(matchers::path("some-path/")) .respond_with(ResponseTemplate::new(200).set_body_string(welcome_message.to_string())) .mount(&mock_server) .await; test_async_with_opts( |ctx| { crate::init(&ctx).unwrap(); Box::pin(async move { let globals = ctx.globals(); let run = async { let fetch: Function = globals.get("fetch")?; let options = Object::new(ctx.clone())?; options.set("method", "GET")?; let url = format!("http://{}/some-path/", mock_server.address().clone()); // The scope ensure we drop all responses { let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let _response2 = response.clone(ctx.clone()).unwrap(); } tokio::task::yield_now().await; Ok(()) }; run.await.catch(&ctx).unwrap(); }) }, TestOptions::new().no_pending_jobs(), ) .await; } #[tokio::test] async fn test_incoming_lagged() { let mock_server = MockServer::start().await; let welcome_message = vec![b'x'; 1024 * 1024 * 50]; Mock::given(matchers::path("some-path/")) .respond_with(ResponseTemplate::new(200).set_body_bytes(welcome_message.clone())) .mount(&mock_server) .await; test_async_with_opts(|ctx| { crate::init(&ctx).unwrap(); Box::pin(async move { let globals = ctx.globals(); let run = async { let fetch: Function = globals.get("fetch")?; let options = Object::new(ctx.clone())?; options.set("method", "GET")?; let url = format!("http://{}/some-path/", mock_server.address().clone()); let response_promise: Promise = fetch.call((url, options.clone()))?; let response: Class = response_promise.into_future().await?; let response = response.borrow_mut(); let response2 = response.clone(ctx.clone()).unwrap(); let response_text = response.text(ctx.clone()).await.unwrap(); assert_eq!(response.status(), 200); assert_eq!(response_text.as_bytes(), welcome_message); let response2_err = response2.text(ctx.clone()).await.catch(&ctx).unwrap_err(); assert_eq!(response2.status(), 200); assert!(matches!(response2_err, CaughtError::Exception(e) if e.message().unwrap() == ERROR_MSG_BROADCAST_LAGGED)); Ok(()) }; run.await.catch(&ctx).unwrap(); }) }, TestOptions::new().no_pending_jobs()) .await; } } ================================================ FILE: modules/llrt_fetch/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_buffer::Blob; use llrt_http::HTTP_CLIENT; use llrt_utils::{ class::CustomInspectExtension, primordials::{BasePrimordials, Primordial}, result::ResultExt, }; use rquickjs::{Class, Ctx, Result}; use std::borrow::Cow; pub use self::security::{get_allow_list, get_deny_list, set_allow_list, set_deny_list}; use self::{form_data::FormData, headers::Headers, request::Request, response::Response}; mod body; pub mod fetch; pub mod form_data; pub mod headers; mod incoming; pub mod request; pub mod response; mod security; const MIME_TYPE_FORM_URLENCODED: &str = "application/x-www-form-urlencoded;charset=UTF-8"; const MIME_TYPE_TEXT: &str = "text/plain;charset=UTF-8"; const MIME_TYPE_JSON: &str = "application/json;charset=UTF-8"; const MIME_TYPE_FORM_DATA: &str = "multipart/form-data; boundary="; const MIME_TYPE_OCTET_STREAM: &str = "application/octet-stream"; pub(crate) fn strip_bom<'a>(bytes: impl Into>) -> Cow<'a, [u8]> { let cow = bytes.into(); if cow.starts_with(&[0xEF, 0xBB, 0xBF]) { match cow { Cow::Borrowed(bytes) => Cow::Borrowed(&bytes[3..]), Cow::Owned(mut bytes) => { bytes.drain(0..3); //memmove instead of copy Cow::Owned(bytes) }, } } else { cow } } pub fn init(ctx: &Ctx) -> Result<()> { let globals = ctx.globals(); BasePrimordials::init(ctx)?; //init eagerly fetch::init(HTTP_CLIENT.as_ref().or_throw(ctx)?.clone(), &globals)?; Class::::define(&globals)?; Class::::define(&globals)?; Class::::define(&globals)?; Class::::define_with_custom_inspect(&globals)?; Ok(()) } ================================================ FILE: modules/llrt_fetch/src/request.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::RwLock; use llrt_abort::AbortSignal; use llrt_http::Agent; use llrt_json::parse::json_parse; use llrt_url::{url_class::URL, url_search_params::URLSearchParams}; use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{ atom::PredefinedAtom, class::Trace, function::Opt, ArrayBuffer, Class, Ctx, Exception, FromJs, IntoJs, Null, Object, Result, TypedArray, Value, }; use super::{ headers::{Headers, HeadersGuard, HEADERS_KEY_CONTENT_TYPE}, strip_bom, Blob, FormData, MIME_TYPE_FORM_DATA, MIME_TYPE_FORM_URLENCODED, MIME_TYPE_OCTET_STREAM, MIME_TYPE_TEXT, }; #[derive(Clone, Default, PartialEq)] pub enum RequestMode { #[default] Cors, NoCors, SameOrigin, Navigate, } impl TryFrom for RequestMode { type Error = String; fn try_from(s: String) -> std::result::Result { Ok(match s.to_ascii_lowercase().as_str() { "cors" => RequestMode::Cors, "no-cors" => RequestMode::NoCors, "same-origin" => RequestMode::SameOrigin, "navigate" => RequestMode::Navigate, _ => return Err(["Invalid requrest mode: ", s.as_str()].concat()), }) } } impl RequestMode { pub fn as_str(&self) -> &str { match self { Self::Cors => "cors", Self::NoCors => "no-cors", Self::SameOrigin => "same-origin", Self::Navigate => "navigate", } } } #[allow(dead_code)] #[derive(rquickjs::JsLifetime)] enum BodyVariant<'js> { Provided(Option>), Empty, } #[rquickjs::class] #[derive(rquickjs::JsLifetime)] pub struct Request<'js> { url: String, method: String, headers: Option>, body: RwLock>, signal: Option>>, mode: RequestMode, keepalive: bool, agent: Option>, } impl<'js> Trace<'js> for Request<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { if let Some(headers) = &self.headers { headers.trace(tracer); } let body = self.body.read().unwrap(); let body = &*body; if let BodyVariant::Provided(Some(body)) = body { body.trace(tracer); } } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> Request<'js> { #[qjs(constructor)] pub fn new(ctx: Ctx<'js>, input: Value<'js>, options: Opt>) -> Result { let mut request = Self { url: "".into(), method: "GET".into(), headers: None, body: RwLock::new(BodyVariant::Empty), signal: None, mode: RequestMode::Cors, keepalive: false, agent: None, }; if input.is_string() { request.url = input.get()?; } else if let Ok(url) = URL::from_js(&ctx, input.clone()) { request.url = url.to_string(); } else if input.is_object() { assign_request(&mut request, ctx.clone(), unsafe { input.as_object().unwrap_unchecked() })?; } if let Some(options) = options.0 { if let Some(obj) = options.as_object() { assign_request(&mut request, ctx.clone(), obj)?; } } if request.headers.is_none() { let headers = Class::instance(ctx, Headers::default())?; request.headers = Some(headers); } Ok(request) } #[qjs(get)] fn url(&self) -> String { self.url.clone() } #[qjs(get)] fn method(&self) -> String { self.method.clone() } #[qjs(get)] fn headers(&self) -> Option> { self.headers.clone() } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(Request) } //TODO should implement readable stream #[qjs(get)] fn body(&self, ctx: Ctx<'js>) -> Result> { let body = self.body.read().unwrap(); let body = &*body; match body { BodyVariant::Provided(value) => value.into_js(&ctx), BodyVariant::Empty => Null.into_js(&ctx), } } #[qjs(get)] fn keepalive(&self) -> bool { self.keepalive } #[qjs(get)] fn signal(&self) -> Option>> { self.signal.clone() } #[qjs(get)] fn body_used(&self) -> bool { let body = self.body.read().unwrap(); let body = &*body; match body { BodyVariant::Provided(value) => value.is_none(), BodyVariant::Empty => false, } } #[qjs(get)] fn mode(&self) -> &str { self.mode.as_str() } #[qjs(get)] fn cache(&self) -> &'static str { "no-store" } #[qjs(get)] fn agent(&self) -> Option> { self.agent.clone() } pub async fn text(&mut self, ctx: Ctx<'js>) -> Result { if let Some(bytes) = self.take_bytes(&ctx).await? { return Ok(String::from_utf8_lossy(&strip_bom(bytes)).to_string()); } Ok("".into()) } pub async fn json(&mut self, ctx: Ctx<'js>) -> Result> { if let Some(bytes) = self.take_bytes(&ctx).await? { return json_parse(&ctx, strip_bom(bytes)); } Err(Exception::throw_syntax(&ctx, "JSON input is empty")) } async fn array_buffer(&mut self, ctx: Ctx<'js>) -> Result> { if let Some(bytes) = self.take_bytes(&ctx).await? { return ArrayBuffer::new(ctx, bytes); } ArrayBuffer::new(ctx, Vec::::new()) } async fn bytes(&mut self, ctx: Ctx<'js>) -> Result> { if let Some(bytes) = self.take_bytes(&ctx).await? { return TypedArray::new(ctx, bytes).map(|m| m.into_value()); } TypedArray::new(ctx, Vec::::new()).map(|m| m.into_value()) } async fn blob(&mut self, ctx: Ctx<'js>) -> Result { let mime_type = self.get_header_value(&ctx, HEADERS_KEY_CONTENT_TYPE)?; if let Some(bytes) = self.take_bytes(&ctx).await? { return Ok(Blob::from_bytes(bytes, mime_type)); } Ok(Blob::from_bytes(Vec::::new(), mime_type)) } async fn form_data(&self, ctx: Ctx<'js>) -> Result { let mime_type = self .get_header_value(&ctx, HEADERS_KEY_CONTENT_TYPE)? .unwrap_or(MIME_TYPE_OCTET_STREAM.into()); if let Some(bytes) = self.take_bytes(&ctx).await? { let form_data = FormData::from_multipart_bytes(&ctx, &mime_type, bytes)?; return Ok(form_data); } Ok(FormData::default()) } fn clone(&mut self, ctx: Ctx<'js>) -> Result { let headers = if let Some(headers) = &self.headers { Some(Class::::instance( ctx.clone(), headers.borrow().clone(), )?) } else { None }; //not async so should not block let body = self.body.read().unwrap(); let body = &*body; let body = match body { BodyVariant::Provided(provided) => BodyVariant::Provided(provided.clone()), BodyVariant::Empty => BodyVariant::Empty, }; Ok(Self { url: self.url.clone(), method: self.url.clone(), headers, body: RwLock::new(body), signal: self.signal.clone(), mode: self.mode.clone(), keepalive: self.keepalive, agent: self.agent.clone(), }) } } impl<'js> Request<'js> { #[allow(clippy::await_holding_lock)] //clippy complains about guard being held across await points but we drop the guard before awaiting #[allow(clippy::readonly_write_lock)] //clippy complains about lock being read only but we mutate the value async fn take_bytes(&self, ctx: &Ctx<'js>) -> Result>> { let mut body_guard = self.body.write().unwrap(); let body = &mut *body_guard; let bytes = match body { BodyVariant::Provided(provided) => { let provided = provided .take() .ok_or(Exception::throw_message(ctx, "Already read"))?; drop(body_guard); if let Some(blob) = provided.as_object().and_then(Class::::from_object) { let blob = blob.borrow(); blob.get_bytes() } else { let bytes = ObjectBytes::from(ctx, &provided)?; bytes.as_bytes(ctx)?.to_vec() } }, BodyVariant::Empty => return Ok(None), }; Ok(Some(bytes)) } fn get_headers(&self, ctx: &Ctx<'js>) -> Result> { self.headers() .map(|headers| Headers::from_js(ctx, headers.into_value())) .transpose() .or_throw(ctx) } fn get_header_value(&self, ctx: &Ctx<'js>, key: &str) -> Result> { Ok(self.get_headers(ctx)?.and_then(|headers| { headers .iter() .find_map(|(k, v)| (k == key).then(|| v.to_string())) })) } } fn assign_request<'js>(request: &mut Request<'js>, ctx: Ctx<'js>, obj: &Object<'js>) -> Result<()> { if let Some(url) = obj.get_optional("url")? { request.url = url; } if let Some(method) = obj.get_optional::<_, String>("method")? { let method = method.to_ascii_uppercase(); if let "CONNECT" | "TRACE" | "TRACK" = method.as_str() { return Err(Exception::throw_type(&ctx, "This method is not allowed.")); } request.method = method; } if let Some(mode) = obj.get_optional::<_, String>("mode")? { request.mode = mode.try_into().or_throw(&ctx)?; } if let Some(keepalive) = obj.get_optional::<_, Value>("keepalive")? { request.keepalive = if let Some(b) = keepalive.as_bool() { b } else if let Some(n) = keepalive.as_number() { n != 0.0 } else { false } } if let Some(signal) = obj.get_optional::<_, Value>("signal")? { if !signal.is_undefined() && !signal.is_null() { let signal = AbortSignal::from_js(&ctx, signal).map_err(|_| { Exception::throw_type( &ctx, "Failed to construct 'Request': 'signal' property is not an AbortSignal", ) })?; request.signal = Some(Class::instance(ctx.clone(), signal)?); } } let mut content_type: Option = None; if let Some(body) = obj.get_optional::<_, Value>("body")? { if !body.is_undefined() && !body.is_null() { if let "GET" | "HEAD" = request.method.as_str() { return Err(Exception::throw_type( &ctx, "Failed to construct 'Request': Request with GET/HEAD method cannot have body.", )); } let body = if body.is_string() { content_type = Some(MIME_TYPE_TEXT.into()); BodyVariant::Provided(Some(body)) } else if let Some(obj) = body.as_object() { if let Some(blob) = Class::::from_object(obj) { let blob = blob.borrow(); if !blob.mime_type().is_empty() { content_type = Some(blob.mime_type()); } BodyVariant::Provided(Some(body)) } else if let Some(fd) = Class::::from_object(obj) { let fd = fd.borrow(); let (multipart_body, boundary) = fd.to_multipart_bytes(&ctx)?; content_type = Some([MIME_TYPE_FORM_DATA, &boundary].concat()); BodyVariant::Provided(Some(multipart_body.into_js(&ctx)?)) } else if obj.instance_of::() { content_type = Some(MIME_TYPE_FORM_URLENCODED.into()); BodyVariant::Provided(Some(body)) } else { BodyVariant::Provided(Some(body)) } } else { BodyVariant::Provided(Some(body)) }; request.body = RwLock::new(body); } } let headers = { let guard = if request.mode == RequestMode::NoCors { HeadersGuard::RequestNoCors } else { HeadersGuard::Request }; let mut headers = Headers::from_value( &ctx, obj.get_optional("headers")? .unwrap_or_else(|| Null.into_js(&ctx).unwrap()), guard, )?; if !headers.has(ctx.clone(), HEADERS_KEY_CONTENT_TYPE.into())? { if let Some(value) = content_type { headers.set( ctx.clone(), HEADERS_KEY_CONTENT_TYPE.into(), value.into_js(&ctx)?, )?; } } Class::instance(ctx, headers)? }; request.headers = Some(headers); if let Some(agent_opt) = obj.get_optional("agent")? { request.agent = Some(agent_opt); } Ok(()) } ================================================ FILE: modules/llrt_fetch/src/response.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ collections::{BTreeMap, HashMap}, io::Read, sync::RwLock, time::Instant, }; use either::Either; use http_body_util::BodyExt; use hyper::{ body::{Body, Incoming}, header::HeaderName, }; use llrt_abort::AbortSignal; use llrt_context::CtxExtension; use llrt_json::{parse::json_parse, stringify::json_stringify}; use llrt_url::{url_class::URL, url_search_params::URLSearchParams}; use llrt_utils::bytes::ObjectBytes; use llrt_utils::{mc_oneshot, result::ResultExt}; use once_cell::sync::Lazy; use rquickjs::{ atom::PredefinedAtom, class::{Trace, Tracer}, function::Opt, ArrayBuffer, Class, Coerced, Ctx, Exception, IntoJs, JsLifetime, Object, Result, TypedArray, Undefined, Value, }; use tokio::select; use super::{ headers::{Headers, HeadersGuard, HEADERS_KEY_CONTENT_TYPE}, incoming::{self, IncomingReceiver}, strip_bom, Blob, FormData, MIME_TYPE_FORM_DATA, MIME_TYPE_FORM_URLENCODED, MIME_TYPE_JSON, MIME_TYPE_OCTET_STREAM, MIME_TYPE_TEXT, }; static STATUS_TEXTS: Lazy> = Lazy::new(|| { let mut map = HashMap::new(); map.insert(100, "Continue"); map.insert(101, "Switching Protocols"); map.insert(102, "Processing"); map.insert(103, "Early Hints"); map.insert(200, "OK"); map.insert(201, "Created"); map.insert(202, "Accepted"); map.insert(203, "Non-Authoritative Information"); map.insert(204, "No Content"); map.insert(205, "Reset Content"); map.insert(206, "Partial Content"); map.insert(207, "Multi-Status"); map.insert(208, "Already Reported"); map.insert(226, "IM Used"); map.insert(300, "Multiple Choices"); map.insert(301, "Moved Permanently"); map.insert(302, "Found"); map.insert(303, "See Other"); map.insert(304, "Not Modified"); map.insert(305, "Use Proxy"); map.insert(307, "Temporary Redirect"); map.insert(308, "Permanent Redirect"); map.insert(400, "Bad Request"); map.insert(401, "Unauthorized"); map.insert(402, "Payment Required"); map.insert(403, "Forbidden"); map.insert(404, "Not Found"); map.insert(405, "Method Not Allowed"); map.insert(406, "Not Acceptable"); map.insert(407, "Proxy Authentication Required"); map.insert(408, "Request Timeout"); map.insert(409, "Conflict"); map.insert(410, "Gone"); map.insert(411, "Length Required"); map.insert(412, "Precondition Failed"); map.insert(413, "Payload Too Large"); map.insert(414, "URI Too Long"); map.insert(415, "Unsupported Media Type"); map.insert(416, "Range Not Satisfiable"); map.insert(417, "Expectation Failed"); map.insert(418, "I'm a teapot"); map.insert(421, "Misdirected Request"); map.insert(422, "Unprocessable Content"); map.insert(423, "Locked"); map.insert(424, "Failed Dependency"); map.insert(425, "Too Early"); map.insert(426, "Upgrade Required"); map.insert(428, "Precondition Required"); map.insert(429, "Too Many Requests"); map.insert(431, "Request Header Fields Too Large"); map.insert(451, "Unavailable For Legal Reasons"); map.insert(500, "Internal Server Error"); map.insert(501, "Not Implemented"); map.insert(502, "Bad Gateway"); map.insert(503, "Service Unavailable"); map.insert(504, "Gateway Timeout"); map.insert(505, "HTTP Version Not Supported"); map.insert(506, "Variant Also Negotiates"); map.insert(507, "Insufficient Storage"); map.insert(508, "Loop Detected"); map.insert(510, "Not Extended"); map.insert(511, "Network Authentication Required"); map }); enum BodyVariant<'js> { Incoming(Option>), Cloned(Option>), Provided(Option>), Empty, } #[rquickjs::class] pub struct Response<'js> { body: RwLock>, content_encoding: Option, method: String, url: String, start: Instant, status: u16, status_text: Option, redirected: bool, headers: Class<'js, Headers>, abort_receiver: Option>>, } impl<'js> Trace<'js> for Response<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.headers.trace(tracer); let body = self.body.read().unwrap(); let body = &*body; if let BodyVariant::Provided(Some(body)) = body { body.trace(tracer); } } } unsafe impl<'js> JsLifetime<'js> for Response<'js> { type Changed<'to> = Response<'to>; } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> Response<'js> { #[qjs(constructor)] pub fn new(ctx: Ctx<'js>, body: Opt>, options: Opt>) -> Result { let mut url = "".into(); let mut status = 200; let mut headers = None; let mut status_text = None; let mut abort_receiver = None; if let Some(opt) = options.0 { if let Some(url_opt) = opt.get("url")? { url = url_opt; } if let Some(status_opt) = opt.get("status")? { status = status_opt; } if let Some(headers_opt) = opt.get("headers")? { headers = Some(Headers::from_value( &ctx, headers_opt, HeadersGuard::Response, )?); } if let Some(status_text_opt) = opt.get("statusText")? { status_text = Some(status_text_opt); } if let Some(signal) = opt.get::<_, Option>>("signal")? { abort_receiver = Some(signal.borrow().sender.subscribe()) } } let mut content_type: Option = None; let body = body .0 .and_then(|body| { if body.is_null() || body.is_undefined() { None } else if body.is_string() { content_type = Some(MIME_TYPE_TEXT.into()); Some(BodyVariant::Provided(Some(body))) } else if let Some(obj) = body.as_object() { if let Some(blob) = Class::::from_object(obj) { let blob = blob.borrow(); if !blob.mime_type().is_empty() { content_type = Some(blob.mime_type()); } Some(BodyVariant::Provided(Some(body))) } else if let Some(fd) = Class::::from_object(obj) { let fd = fd.borrow(); let (multipart_body, boundary) = fd.to_multipart_bytes(&ctx).ok()?; content_type = Some([MIME_TYPE_FORM_DATA, &boundary].concat()); Some(BodyVariant::Provided(Some( multipart_body.into_js(&ctx).ok()?, ))) } else if obj.instance_of::() { content_type = Some(MIME_TYPE_FORM_URLENCODED.into()); Some(BodyVariant::Provided(Some(body))) } else { Some(BodyVariant::Provided(Some(body))) } } else { Some(BodyVariant::Provided(Some(body))) } }) .unwrap_or_else(|| BodyVariant::Empty); let mut headers = headers.unwrap_or_default(); if !headers.has(ctx.clone(), HEADERS_KEY_CONTENT_TYPE.into())? { if let Some(value) = content_type { headers.set( ctx.clone(), HEADERS_KEY_CONTENT_TYPE.into(), value.into_js(&ctx)?, )?; } } let headers = Class::instance(ctx.clone(), headers)?; let content_encoding = headers.get("content-encoding")?; Ok(Self { body: RwLock::new(body), method: "GET".into(), url, start: Instant::now(), status, status_text, redirected: false, headers, content_encoding, abort_receiver, }) } #[qjs(get)] pub fn status(&self) -> u64 { self.status.into() } #[qjs(get)] pub fn url(&self) -> String { self.url.clone() } #[qjs(get)] pub fn ok(&self) -> bool { self.status > 199 && self.status < 300 } #[qjs(get)] pub fn redirected(&self) -> bool { self.redirected } //FIXME return readable stream when implemented #[qjs(get)] pub fn body(&self) -> Undefined { Undefined } #[qjs(get)] fn headers(&self) -> Class<'js, Headers> { self.headers.clone() } #[qjs(get, rename = "type")] fn response_type(&self) -> &'js str { match &self.status { 0 => "error", _ => "basic", } } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { stringify!(Response) } #[qjs(get)] fn status_text(&self) -> String { if let Some(text) = &self.status_text { return text.to_string(); } STATUS_TEXTS.get(&self.status).unwrap_or(&"").to_string() } #[qjs(get)] fn body_used(&self) -> bool { let body = self.body.read().unwrap(); let body = &*body; match body { BodyVariant::Incoming(response) => response.is_none(), BodyVariant::Cloned(response) => response.is_none(), BodyVariant::Provided(value) => value.is_none(), BodyVariant::Empty => false, } } pub(crate) async fn text(&self, ctx: Ctx<'js>) -> Result { if let Some(bytes) = self.take_bytes(&ctx).await? { return Ok(String::from_utf8_lossy(&strip_bom(bytes)).to_string()); } Ok("".into()) } pub(crate) async fn json(&self, ctx: Ctx<'js>) -> Result> { if let Some(bytes) = self.take_bytes(&ctx).await? { return json_parse(&ctx, strip_bom(bytes)); } Err(Exception::throw_syntax(&ctx, "JSON input is empty")) } async fn array_buffer(&self, ctx: Ctx<'js>) -> Result> { if let Some(bytes) = self.take_bytes(&ctx).await? { return ArrayBuffer::new(ctx, bytes); } ArrayBuffer::new(ctx, Vec::::new()) } async fn bytes(&self, ctx: Ctx<'js>) -> Result> { if let Some(bytes) = self.take_bytes(&ctx).await? { return TypedArray::new(ctx, bytes).map(|m| m.into_value()); } TypedArray::new(ctx, Vec::::new()).map(|m| m.into_value()) } async fn blob(&self, ctx: Ctx<'js>) -> Result { let mime_type = self.get_header_value(&ctx, HEADERS_KEY_CONTENT_TYPE)?; if let Some(bytes) = self.take_bytes(&ctx).await? { return Ok(Blob::from_bytes(bytes, mime_type)); } Ok(Blob::from_bytes(Vec::::new(), mime_type)) } async fn form_data(&self, ctx: Ctx<'js>) -> Result { let mime_type = self .get_header_value(&ctx, HEADERS_KEY_CONTENT_TYPE)? .unwrap_or(MIME_TYPE_OCTET_STREAM.into()); if let Some(bytes) = self.take_bytes(&ctx).await? { let form_data = FormData::from_multipart_bytes(&ctx, &mime_type, bytes)?; return Ok(form_data); } Ok(FormData::default()) } pub(crate) fn clone(&self, ctx: Ctx<'js>) -> Result { //not async so should not block let mut body = self.body.write().unwrap(); let body_mutex = &mut *body; let body = match body_mutex { BodyVariant::Incoming(incoming) => { if let Some(response) = incoming.take() { let (head, incoming_response) = response.into_parts(); let (sender, receiver) = incoming::channel(incoming_response); let response = hyper::Response::from_parts(head, receiver); *body_mutex = BodyVariant::Cloned(Some(response.clone())); ctx.spawn_exit_simple(async move { sender.process().await; Ok(()) }); BodyVariant::Cloned(Some(response)) } else { BodyVariant::Incoming(None) } }, BodyVariant::Cloned(incoming) => BodyVariant::Cloned(incoming.clone()), BodyVariant::Provided(provided) => BodyVariant::Provided(provided.clone()), BodyVariant::Empty => BodyVariant::Empty, }; Ok(Self { body: RwLock::new(body), method: self.method.clone(), url: self.url.clone(), start: self.start, status: self.status, status_text: self.status_text.clone(), redirected: self.redirected, headers: Class::::instance(ctx, self.headers.borrow().clone())?, content_encoding: self.content_encoding.clone(), abort_receiver: self.abort_receiver.clone(), }) } #[qjs(static)] fn error(ctx: Ctx<'js>) -> Result { Ok(Self { body: RwLock::new(BodyVariant::Empty), method: "".into(), url: "".into(), start: Instant::now(), status: 0, status_text: None, redirected: false, headers: Class::instance(ctx.clone(), Headers::default())?, content_encoding: None, abort_receiver: None, }) } #[qjs(static, rename = "json")] fn json_static(ctx: Ctx<'js>, body: Value<'js>, options: Opt>) -> Result { let mut status = 200; let mut status_text = None; if let Some(ref opt) = options.0 { if let Some(status_opt) = opt.get("status")? { status = status_opt; } if let Some(status_text_opt) = opt.get("statusText")? { status_text = Some(status_text_opt); } } let mut headers = if let Some(ref opt) = options.0 { let head = if let Some(headers_opt) = opt.get("headers")? { headers_opt } else { Value::new_null(ctx.clone()) }; Headers::from_value(&ctx, head, HeadersGuard::Response)? } else { Headers::from_value(&ctx, Value::new_null(ctx.clone()), HeadersGuard::Response)? }; if !headers.has(ctx.clone(), "content-type".into())? { headers.append( ctx.clone(), "content-type".into(), MIME_TYPE_JSON.into_js(&ctx)?, )?; } let headers = Class::instance(ctx.clone(), headers)?; let content_encoding = headers.get("content-encoding")?; let body = if let Ok(Some(v)) = json_stringify(&ctx, body) { BodyVariant::Provided(Some(v.into_js(&ctx)?)) } else { return Err(Exception::throw_type(&ctx, "Failed to convert JSON string")); }; Ok(Self { body: RwLock::new(body), method: "".into(), url: "".into(), start: Instant::now(), status, status_text, redirected: false, headers, content_encoding, abort_receiver: None, }) } #[qjs(static)] fn redirect( ctx: Ctx<'js>, url: Either, Coerced>, status: Opt, ) -> Result { let status = status.0.unwrap_or(302_u16); let url = match url { Either::Left(url) => url.to_string(), Either::Right(url) => url.0, }; let mut header = BTreeMap::new(); header.insert("location".to_string(), Coerced(url)); let headers = Headers::from_map(&ctx, header, HeadersGuard::Response); let headers = Class::instance(ctx.clone(), headers)?; Ok(Self { body: RwLock::new(BodyVariant::Empty), method: "".into(), url: "".into(), start: Instant::now(), status, status_text: None, redirected: false, headers, content_encoding: None, abort_receiver: None, }) } } #[allow(clippy::too_many_arguments)] impl<'js> Response<'js> { pub fn from_incoming( ctx: Ctx<'js>, response: hyper::Response, method: String, url: String, start: Instant, redirected: bool, abort_receiver: Option>>, guard: HeadersGuard, ) -> Result { let response_headers = response.headers(); let mut content_encoding = None; if let Some(content_encoding_header) = response_headers.get(HeaderName::from_static("content-encoding")) { if let Ok(content_encoding_header) = content_encoding_header.to_str() { content_encoding = Some(content_encoding_header.to_owned()) } } let headers = Headers::from_http_headers(response.headers(), guard)?; let headers = Class::instance(ctx.clone(), headers)?; let status = response.status(); Ok(Self { body: RwLock::new(BodyVariant::Incoming(Some(response))), content_encoding, method, url, start, status: status.as_u16(), status_text: None, redirected, headers, abort_receiver, }) } #[allow(clippy::await_holding_lock)] //clippy complains about guard being held across await points but we drop the guard before awaiting #[allow(clippy::readonly_write_lock)] //clippy complains about lock being read only but we mutate the value async fn take_bytes(&self, ctx: &Ctx<'js>) -> Result>> { let mut body_guard = self.body.write().unwrap(); let body = &mut *body_guard; let bytes = match body { BodyVariant::Incoming(ref mut incoming) => { let response = incoming .take() .ok_or(Exception::throw_message(ctx, "Already read"))?; drop(body_guard); self.take_bytes_body(ctx, response.into_body()).await? }, BodyVariant::Cloned(ref mut incoming) => { let response = incoming .take() .ok_or(Exception::throw_message(ctx, "Already read"))?; drop(body_guard); self.take_bytes_body(ctx, response.into_body()).await? }, BodyVariant::Provided(provided) => { let provided = provided .take() .ok_or(Exception::throw_message(ctx, "Already read"))?; drop(body_guard); if let Some(blob) = provided.as_object().and_then(Class::::from_object) { let blob = blob.borrow(); blob.get_bytes() } else { let bytes = ObjectBytes::from(ctx, &provided)?; bytes.as_bytes(ctx)?.to_vec() } }, BodyVariant::Empty => return Ok(None), }; Ok(Some(bytes)) } async fn take_bytes_body(&self, ctx: &Ctx<'js>, body: T) -> Result> where T: Body, T::Error: std::fmt::Display, { let bytes = if let Some(abort_signal) = self.abort_receiver.as_ref() { select! { err = abort_signal.recv() => return Err(ctx.throw(err)), collected_body = body.collect() => collected_body.or_throw(ctx)?.to_bytes() } } else { body.collect().await.or_throw(ctx)?.to_bytes() }; if let Some(content_encoding) = self.content_encoding.as_deref() { let mut data: Vec = Vec::with_capacity(bytes.len()); match content_encoding { "zstd" => llrt_compression::zstd::decoder(&bytes[..])?.read_to_end(&mut data)?, "br" => llrt_compression::brotli::decoder(&bytes[..]).read_to_end(&mut data)?, "gzip" => llrt_compression::gz::decoder(&bytes[..]).read_to_end(&mut data)?, "deflate" => llrt_compression::zlib::decoder(&bytes[..]).read_to_end(&mut data)?, _ => return Err(Exception::throw_message(ctx, "Unsupported encoding")), }; Ok(data) } else { Ok(bytes.to_vec()) } } fn get_headers(&self, ctx: &Ctx<'js>) -> Result { Headers::from_value(ctx, self.headers().as_value().clone(), HeadersGuard::None) } fn get_header_value(&self, ctx: &Ctx<'js>, key: &str) -> Result> { Ok(self .get_headers(ctx)? .iter() .find_map(|(k, v)| (k == key).then(|| v.to_string()))) } } ================================================ FILE: modules/llrt_fetch/src/security.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use hyper::Uri; use rquickjs::{Ctx, Error, Exception, Result}; use std::sync::OnceLock; static HTTP_ALLOW_LIST: OnceLock> = OnceLock::new(); static HTTP_DENY_LIST: OnceLock> = OnceLock::new(); pub fn set_allow_list(values: Vec) { _ = HTTP_ALLOW_LIST.set(values); } pub fn get_allow_list() -> Option<&'static Vec> { HTTP_ALLOW_LIST.get() } pub fn set_deny_list(values: Vec) { _ = HTTP_DENY_LIST.set(values); } pub fn get_deny_list() -> Option<&'static Vec> { HTTP_DENY_LIST.get() } pub fn ensure_url_access(ctx: &Ctx<'_>, uri: &Uri) -> Result<()> { if let Some(allow_list) = HTTP_ALLOW_LIST.get() { if !url_match(allow_list, uri) { return Err(url_restricted_error(ctx, "URL not allowed", uri)); } } if let Some(deny_list) = HTTP_DENY_LIST.get() { if url_match(deny_list, uri) { return Err(url_restricted_error(ctx, "URL denied", uri)); } } Ok(()) } fn url_restricted_error(ctx: &Ctx<'_>, message: &str, uri: &Uri) -> Error { let uri_host = uri.host().unwrap_or_default(); let mut message_string = String::with_capacity(message.len() + 100); message_string.push_str(message); message_string.push_str(": "); message_string.push_str(uri_host); if let Some(port) = uri.port_u16() { message_string.push(':'); message_string.push_str(itoa::Buffer::new().format(port)) } Exception::throw_message(ctx, &message_string) } fn url_match(list: &[Uri], uri: &Uri) -> bool { let host = uri.host().unwrap_or_default(); let port = uri.port_u16().unwrap_or(80); list.iter().any(|entry| { host.ends_with(entry.host().unwrap_or_default()) && entry.port_u16().unwrap_or(80) == port }) } ================================================ FILE: modules/llrt_fs/Cargo.toml ================================================ [package] name = "llrt_fs" description = "LLRT Module fs" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_fs" path = "src/lib.rs" [dependencies] either = { version = "1", default-features = false } llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_encoding = { version = "0.8.1-beta", path = "../../libs/llrt_encoding" } llrt_path = { version = "0.8.1-beta", path = "../llrt_path" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", features = [ "fs", ], default-features = false } rand = { version = "0.10.0", features = [ "alloc", "thread_rng", ], default-features = false } rquickjs = { version = "0.11", features = ["either"], default-features = false } tokio = { version = "1", features = [ "fs", "io-util", "rt", ], default-features = false } [target.'cfg(windows)'.dependencies] junction = "1" [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } ================================================ FILE: modules/llrt_fs/src/access.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::fs::Metadata; use llrt_utils::result::ResultExt; use rquickjs::{prelude::Opt, Ctx, Exception, Result}; use tokio::fs; #[allow(dead_code, unused_imports)] use super::{CONSTANT_F_OK, CONSTANT_R_OK, CONSTANT_W_OK, CONSTANT_X_OK}; pub async fn access(ctx: Ctx<'_>, path: String, mode: Opt) -> Result<()> { let metadata = fs::metadata(&path).await.or_throw_msg( &ctx, &["No such file or directory \"", &path, "\""].concat(), )?; verify_metadata(&ctx, mode, metadata) } pub fn access_sync(ctx: Ctx<'_>, path: String, mode: Opt) -> Result<()> { let metadata = std::fs::metadata(path.clone()).or_throw_msg( &ctx, &["No such file or directory \"", &path, "\""].concat(), )?; verify_metadata(&ctx, mode, metadata) } fn verify_metadata(ctx: &Ctx, mode: Opt, metadata: Metadata) -> Result<()> { let permissions = metadata.permissions(); let mode = mode.unwrap_or(CONSTANT_F_OK); if mode & CONSTANT_W_OK != 0 && permissions.readonly() { return Err(Exception::throw_message( ctx, "Permission denied. File not writable", )); } if mode & CONSTANT_X_OK != 0 { #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if permissions.mode() & 0o100 == 0 { return Err(Exception::throw_message( ctx, "Permission denied. File not executable", )); } } // On Windows, X_OK behaves like F_OK (file exists check only) } Ok(()) } ================================================ FILE: modules/llrt_fs/src/chmod.rs ================================================ #[cfg(unix)] use llrt_utils::result::ResultExt; use rquickjs::{Ctx, Result}; #[cfg(unix)] use std::os::unix::prelude::PermissionsExt; #[cfg(unix)] pub(crate) fn chmod_error(path: &str) -> String { ["Can't set permissions of \"", path, "\""].concat() } pub(crate) async fn set_mode(ctx: Ctx<'_>, path: &str, mode: u32) -> Result<()> { #[cfg(unix)] { tokio::fs::set_permissions(path, PermissionsExt::from_mode(mode)) .await .or_throw_msg(&ctx, &chmod_error(path))?; } #[cfg(not(unix))] { _ = ctx; _ = path; _ = mode; } Ok(()) } pub(crate) fn set_mode_sync(ctx: Ctx<'_>, path: &str, mode: u32) -> Result<()> { #[cfg(unix)] { std::fs::set_permissions(path, PermissionsExt::from_mode(mode)) .or_throw_msg(&ctx, &chmod_error(path))?; } #[cfg(not(unix))] { _ = ctx; _ = path; _ = mode; } Ok(()) } pub async fn chmod(ctx: Ctx<'_>, path: String, mode: u32) -> Result<()> { set_mode(ctx, &path, mode).await } pub fn chmod_sync(ctx: Ctx<'_>, path: String, mode: u32) -> Result<()> { set_mode_sync(ctx, &path, mode) } ================================================ FILE: modules/llrt_fs/src/file_handle.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::borrow::Cow; use std::path::PathBuf; use either::Either; use llrt_buffer::{ArrayBufferView, Buffer}; use llrt_encoding::Encoder; use llrt_utils::{ object::ObjectExt, result::{OptionExt, ResultExt}, }; use rquickjs::function::Opt; use rquickjs::{Ctx, Error, Exception, FromJs, Null, Object, Result, Value}; use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom}; use super::{read_file, Stats}; const DEFAULT_BUFFER_SIZE: usize = 16384; const DEFAULT_ENCODING: &str = "utf8"; #[allow(dead_code)] #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct FileHandle { #[qjs(skip_trace)] file: Option, #[qjs(skip_trace)] path: PathBuf, } impl FileHandle { pub fn new(file: File, path: PathBuf) -> Self { Self { file: Some(file), path, } } fn file(&self, ctx: &Ctx<'_>) -> Result<&File> { self.file.as_ref().or_throw_msg(ctx, "FileHandle is closed") } fn file_mut(&mut self, ctx: &Ctx<'_>) -> Result<&mut File> { self.file.as_mut().or_throw_msg(ctx, "FileHandle is closed") } } #[rquickjs::methods(rename_all = "camelCase")] impl FileHandle { #[allow(unused_variables)] async fn chmod(&self, ctx: Ctx<'_>, mode: u32) -> Result<()> { #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let perm = std::fs::Permissions::from_mode(mode); self.file(&ctx)? .set_permissions(perm) .await .or_throw_msg(&ctx, "Can't modify file permissions")?; } Ok(()) } #[allow(unused_variables)] async fn chown(&self, ctx: Ctx<'_>, uid: u32, gid: u32) -> Result<()> { #[cfg(unix)] { let path = self.path.clone(); tokio::task::spawn_blocking(move || { std::os::unix::fs::chown(&path, Some(uid), Some(gid)) }) .await .or_throw(&ctx)? .or_throw_msg(&ctx, "Can't modify file owner")?; } Ok(()) } async fn close(&mut self) { if let Some(file) = self.file.take() { drop(file.into_std().await); } } async fn datasync(&self, ctx: Ctx<'_>) -> Result<()> { self.file(&ctx)? .sync_data() .await .or_throw_msg(&ctx, "Can't sync file data")?; Ok(()) } #[qjs(get)] async fn fd(&self, ctx: Ctx<'_>) -> Result { #[cfg(unix)] { use std::os::fd::AsRawFd; Ok(self.file(&ctx)?.as_raw_fd()) } #[cfg(windows)] { use std::os::windows::io::AsRawHandle; let handle = self.file(&ctx)?.as_raw_handle(); Ok(handle as i32) } #[cfg(not(any(unix, windows)))] { Ok(0) } } async fn read<'js>( &mut self, ctx: Ctx<'js>, buffer_or_options: Opt, ReadOptions<'js>>>, options_or_offset: Opt, usize>>, length: Opt, position: Opt>, // -1 is not supported ) -> Result> { let options_1 = match buffer_or_options.0 { Some(Either::Left(buffer)) => ReadOptions { buffer: Some(buffer), ..Default::default() }, Some(Either::Right(options)) => options, None => ReadOptions::default(), }; let options_2 = match options_or_offset.0 { Some(Either::Left(options)) => options, Some(Either::Right(offset)) => ReadOptions { offset: Some(offset), ..Default::default() }, None => ReadOptions::default(), }; let mut buffer = options_1 .buffer .or(options_2.buffer) .unwrap_or_else_ok(|| { ArrayBufferView::from_buffer(&ctx, Buffer::alloc(DEFAULT_BUFFER_SIZE)) })?; let offset = options_1.offset.or(options_2.offset).unwrap_or(0); let length = options_1 .length .or(options_2.length) .or(length.0) .unwrap_or_else(|| buffer.len() - offset); let position = options_1 .position .or(options_2.position) .or(position.0.flatten()); validate_length_offset(&ctx, length, offset, buffer.len())?; // It is not safe to pass the buffer from `ArrayBufferView` to `File::read` // since the read is done in a different thread and we cannot garantee // that multiple read calls are not done with the same buffer. // Ideally, we should make our own version of `BufReader` to reuse the buffer // instead of doing an allocation on each read. let mut buf = vec![0u8; length]; let file = self.file_mut(&ctx)?; // Tokio doesn't offer an API for positional reads. This means we have // to seek to the position, read the file, and then seek back to the original // position. See https://github.com/tokio-rs/tokio/issues/699 let mut cursor = None; if let Some(position) = position { cursor = Some( file.seek(SeekFrom::Current(0)) .await .or_throw_msg(&ctx, "Can't get cursor")?, ); file.seek(SeekFrom::Start(position)) .await .or_throw_msg(&ctx, "Can't seek file")?; } let bytes_read = file .read(&mut buf) .await .or_throw_msg(&ctx, "Failed to read file")?; // Reset the file at the original position. If there is an error while // resetting the cursor, we close the file pre-emptively since future // reads would be invalid. if let Some(cursor) = cursor { if let Err(err) = file .seek(SeekFrom::Start(cursor)) .await .or_throw_msg(&ctx, "Failed to reset cursor") { self.close().await; return Err(err); } } let dst_buf = buffer .as_bytes_mut() .or_throw_msg(&ctx, "Buffer is detached")?; dst_buf[offset..].copy_from_slice(&buf); let result = Object::new(ctx)?; result.set("bytesRead", bytes_read)?; result.set("buffer", buffer)?; Ok(result) } async fn read_file<'js>( &mut self, ctx: Ctx<'js>, options: Opt>, ) -> Result> { let size = self .file(&ctx)? .metadata() .await .map(|m| m.len() as usize) .ok(); let mut bytes = Vec::new(); bytes .try_reserve_exact(size.unwrap_or(0)) .or_throw_msg(&ctx, "Out of memory")?; self.file_mut(&ctx)? .read_to_end(&mut bytes) .await .or_throw_msg(&ctx, "Failed to read file")?; read_file::handle_read_file_bytes(&ctx, options, bytes) } async fn stat(&self, ctx: Ctx<'_>) -> Result { let metadata = self .file(&ctx)? .metadata() .await .or_throw_msg(&ctx, "Can't stat file")?; Ok(Stats::new(metadata)) } async fn sync(&self, ctx: Ctx<'_>) -> Result<()> { self.file(&ctx)? .sync_all() .await .or_throw_msg(&ctx, "Can't sync file") } async fn truncate(&mut self, ctx: Ctx<'_>, len: Opt) -> Result<()> { let len = len.0.unwrap_or(0); self.file_mut(&ctx)? .set_len(len) .await .or_throw_msg(&ctx, "Can't truncate file") } // Setting times not supported in tokio // See https://github.com/tokio-rs/tokio/issues/6368 // async fn utimes(&mut self, ctx: Ctx<'_>, atime: Value<'_>, mtime: Value<'_>) -> Result<()> async fn write<'js>( &mut self, ctx: Ctx<'js>, buffer_or_string: Either, String>, offset_or_options_or_position: Opt, WriteOptions>>, length_or_encoding: Opt>, position: Opt>, ) -> Result> { let mut options = match offset_or_options_or_position.0 { Some(Either::Left(Either::Left(offset_or_position))) => { if buffer_or_string.is_left() { WriteOptions { offset: Some(offset_or_position), ..Default::default() } } else { WriteOptions::default() } }, Some(Either::Right(options)) => options, _ => WriteOptions::default(), }; if let Some(Either::Left(length)) = length_or_encoding.0 { options.length = Some(length); } let buffer = match &buffer_or_string { Either::Left(buffer) => { let buffer = buffer.as_bytes().or_throw_msg(&ctx, "Buffer is detached")?; Cow::Borrowed(buffer) }, Either::Right(string) => { let encoding = length_or_encoding .0 .and_then(|e| e.right()) .unwrap_or_else(|| DEFAULT_ENCODING.to_string()); let buffer = Encoder::from_str(&encoding) .and_then(|enc| enc.decode_from_string(string.clone())) .or_throw(&ctx)?; Cow::Owned(buffer) }, }; let offset = options.offset.unwrap_or(0); let length = options.length.unwrap_or(buffer.len() - offset); let position = options.position.or(position.0.flatten()); validate_length_offset(&ctx, length, offset, buffer.len())?; let file = self.file_mut(&ctx)?; // Tokio doesn't offer an API for positional writes. This means we have // to seek to the position, write to the file, and then seek back to the original // position. See https://github.com/tokio-rs/tokio/issues/699 let mut cursor = None; if let Some(position) = position { cursor = Some( file.seek(SeekFrom::Current(0)) .await .or_throw_msg(&ctx, "Can't get cursor")?, ); file.seek(SeekFrom::Start(position)) .await .or_throw_msg(&ctx, "Can't seek file")?; } file.write_all(&buffer[offset..length]) .await .or_throw_msg(&ctx, "Failed to write to file")?; // Reset the file at the original position. If there is an error while // resetting the cursor, we close the file pre-emptively since future // writes would be invalid. if let Some(cursor) = cursor { if let Err(err) = file .seek(SeekFrom::Start(cursor)) .await .or_throw_msg(&ctx, "Failed to reset cursor") { self.close().await; return Err(err); } } let result = Object::new(ctx)?; result.set("bytesWritten", length)?; result.set("buffer", buffer_or_string)?; Ok(result) } async fn write_file<'js>( &mut self, ctx: Ctx<'js>, data: Either, String>, options_or_encoding: Opt>, ) -> Result<()> { let file = self.file_mut(&ctx)?; // Always overwrite the whole file file.set_len(0) .await .or_throw_msg(&ctx, "Failed to truncate file")?; let encoding = match options_or_encoding.0 { Some(Either::Left(options)) => options.encoding, Some(Either::Right(encoding)) => Some(encoding), _ => None, } .unwrap_or_else(|| DEFAULT_ENCODING.to_string()); let buffer = match &data { Either::Left(buffer) => { let buffer = buffer.as_bytes().or_throw_msg(&ctx, "Buffer is detached")?; Cow::Borrowed(buffer) }, Either::Right(string) => { let buffer = Encoder::from_str(&encoding) .and_then(|enc| enc.decode_from_string(string.clone())) .or_throw(&ctx)?; Cow::Owned(buffer) }, }; file.write_all(&buffer) .await .or_throw_msg(&ctx, "Failed to write to file")?; Ok(()) } } fn validate_length_offset( ctx: &Ctx<'_>, length: usize, offset: usize, buffer_length: usize, ) -> Result<()> { if offset > buffer_length { return Err(Exception::throw_range( ctx, &format!("offset ({}) <= {}", offset, buffer_length), )); } if length > buffer_length - offset { return Err(Exception::throw_range( ctx, &format!("length ({}) <= {}", length, buffer_length - offset), )); } Ok(()) } #[derive(Default)] struct ReadOptions<'js> { buffer: Option>, offset: Option, length: Option, position: Option, } impl<'js> FromJs<'js> for ReadOptions<'js> { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let buffer = obj.get_optional::<_, ArrayBufferView<'js>>("buffer")?; let offset = obj.get_optional::<_, usize>("offset")?; let length = obj.get_optional::<_, usize>("length")?; let position = obj.get_optional::<_, u64>("position")?; Ok(Self { buffer, offset, length, position, }) } } #[derive(Default)] struct WriteOptions { offset: Option, length: Option, position: Option, } impl<'js> FromJs<'js> for WriteOptions { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let offset = obj.get_optional::<_, usize>("offset")?; let length = obj.get_optional::<_, usize>("length")?; let position = obj.get_optional::<_, u64>("position")?; Ok(Self { offset, length, position, }) } } #[derive(Default)] struct WriteFileOptions { encoding: Option, } impl<'js> FromJs<'js> for WriteFileOptions { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let encoding = obj.get_optional::<_, String>("encoding")?; Ok(Self { encoding }) } } #[cfg(test)] mod tests { use llrt_buffer as buffer; use llrt_test::{call_test, call_test_err, test_async_with, ModuleEvaluator}; use rquickjs::{CatchResultExt, CaughtError}; use tokio::fs::OpenOptions; use super::*; async fn given_file(content: &str, options: &mut OpenOptions) -> (File, PathBuf) { // Create file let path = llrt_test::given_file(content).await; // Open in right mode let file = options.open(&path).await.unwrap(); (file, path) } #[tokio::test] async fn test_file_handle_read() { let (file, path) = given_file("Hello World", OpenOptions::new().read(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const buffer = new ArrayBuffer(4096); const view = new Uint8Array(buffer); const read = await filehandle.read(view); return Array.from(view); } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert!(result.starts_with(b"Hello World")); }) }) .await; tokio::fs::remove_file(&path).await.unwrap(); } #[tokio::test] async fn test_file_handle_read_concurrent() { let (file_a, path_a) = given_file(&"a".repeat(20000), OpenOptions::new().read(true)).await; let (file_b, path_b) = given_file(&"b".repeat(20000), OpenOptions::new().read(true)).await; let path_a_1 = path_a.clone(); let path_b_1 = path_b.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandleA, filehandleB) { const buffer = new ArrayBuffer(10000); const view = new Uint8Array(buffer); const read = await Promise.all([filehandleA.read(view), filehandleB.read(view)]); return Array.from(view); } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, (FileHandle::new(file_a, path_a_1), FileHandle::new(file_b, path_b_1))).await; assert_eq!(result.len(), 10000); if result.iter().all(|&b| b == b'a') { println!("All a"); } else if result.iter().all(|&b| b == b'b') { println!("All b"); } else { println!("Mixed"); } }) }) .await; tokio::fs::remove_file(&path_a).await.unwrap(); tokio::fs::remove_file(&path_b).await.unwrap(); } #[tokio::test] async fn test_file_handle_read_position() { let (file, path) = given_file("Hello World", OpenOptions::new().read(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const buffer = new ArrayBuffer(4096); const view = new Uint8Array(buffer); await filehandle.read(view, { position: 6 }); await filehandle.read(view, { offset: 5 }); return Array.from(view); } "#, ) .await .catch(&ctx) .unwrap(); let result = call_test::, _>(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert!(result.starts_with(b"WorldHello World")); }) }) .await; tokio::fs::remove_file(&path).await.unwrap(); } #[tokio::test] async fn test_file_handle_read_subarray() { let (file, path) = given_file("Hello World", OpenOptions::new().read(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const buffer = new ArrayBuffer(4096); const view = new Uint8Array(buffer); const subarray = view.subarray(3, 8); const read = await filehandle.read(subarray); return Array.from(view); } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert!(result.starts_with(b"\x00\x00\x00Hello\x00")); }) }) .await; tokio::fs::remove_file(&path).await.unwrap(); } #[tokio::test] async fn test_file_handle_read_buffer() { let (file, path) = given_file("Hello World", OpenOptions::new().read(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { buffer::init(&ctx).unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const buffer = new ArrayBuffer(4096); const view = new Uint8Array(buffer); await filehandle.read(view, { length: 2000, offset: 3000 }); } "#, ) .await .unwrap(); let error = call_test_err::<(), _>(&ctx, &module, (FileHandle::new(file, path_1),)) .await .unwrap_err(); let CaughtError::Exception(exception) = error else { panic!("Expected exception"); }; assert_eq!(exception.message().unwrap(), "length (2000) <= 1096"); }) }) .await; tokio::fs::remove_file(&path).await.unwrap(); } #[tokio::test] async fn test_file_handle_read_out_of_range() { let (file, path) = given_file("Hello World", OpenOptions::new().read(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { buffer::init(&ctx).unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const buffer = Buffer.alloc(4096); const read = await filehandle.read(buffer); return Array.from(buffer); } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert!(result.starts_with(b"Hello World")); }) }) .await; tokio::fs::remove_file(&path).await.unwrap(); } #[tokio::test] async fn test_file_handle_read_file() { let (file, path) = given_file("Hello World", OpenOptions::new().read(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const data = await filehandle.readFile("utf8"); return data; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert_eq!(result, "Hello World"); }) }) .await; tokio::fs::remove_file(&path).await.unwrap(); } #[tokio::test] async fn test_file_handle_write() { let (file, path) = given_file("", OpenOptions::new().write(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const { bytesWritten } = await filehandle.write("Hello World", null, "utf8"); await filehandle.sync(); return bytesWritten; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert_eq!(result, 11); }) }) .await; let file_content = tokio::fs::read(&path).await.unwrap(); tokio::fs::remove_file(&path).await.unwrap(); assert_eq!(file_content, b"Hello World"); } #[tokio::test] async fn test_file_handle_write_position() { let (file, path) = given_file("", OpenOptions::new().write(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { const { bytesWritten } = await filehandle.write("Hello World", null, "utf8", 4); await filehandle.write("a", null, "utf8"); await filehandle.sync(); return bytesWritten; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert_eq!(result, 11); }) }) .await; let file_content = tokio::fs::read(&path).await.unwrap(); tokio::fs::remove_file(&path).await.unwrap(); assert_eq!(file_content, b"a\x00\x00\x00Hello World"); } #[tokio::test] async fn test_file_handle_write_out_of_range() { let (file, path) = given_file("", OpenOptions::new().write(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { await filehandle.write("Hello World", { offset: 5, length: 20 }); } "#, ) .await .unwrap(); let error = call_test_err::<(), _>(&ctx, &module, (FileHandle::new(file, path_1),)) .await .unwrap_err(); let CaughtError::Exception(exception) = error else { panic!("Expected exception"); }; assert_eq!(exception.message().unwrap(), "length (20) <= 6"); }) }) .await; let file_content = tokio::fs::read(&path).await.unwrap(); tokio::fs::remove_file(&path).await.unwrap(); assert_eq!(file_content, b""); } #[tokio::test] async fn test_file_handle_write_file() { let (file, path) = given_file( "Other very very very very long Data", OpenOptions::new().write(true), ) .await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { await filehandle.writeFile("Hello World", "utf8"); await filehandle.sync(); } "#, ) .await .unwrap(); call_test::<(), _>(&ctx, &module, (FileHandle::new(file, path_1),)).await; }) }) .await; let file_content = tokio::fs::read(&path).await.unwrap(); tokio::fs::remove_file(&path).await.unwrap(); assert_eq!(file_content, b"Hello World"); } #[tokio::test] async fn test_file_handle_fd() { let (file, path) = given_file("", OpenOptions::new().read(true)).await; let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" export async function test(filehandle) { return filehandle.fd; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, (FileHandle::new(file, path_1),)).await; assert!(result > 0); }) }) .await; tokio::fs::remove_file(&path).await.unwrap(); } } ================================================ FILE: modules/llrt_fs/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 mod access; mod chmod; mod file_handle; mod mkdir; mod open; mod read_dir; mod read_file; mod rename; mod rm; mod stats; mod symlink; mod write_file; use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::{Async, Func}, }; use rquickjs::{Class, Ctx, Object, Result}; use self::access::{access, access_sync}; use self::chmod::{chmod, chmod_sync}; use self::file_handle::FileHandle; use self::mkdir::{mkdir, mkdir_sync, mkdtemp, mkdtemp_sync}; use self::open::open; use self::read_dir::{read_dir, read_dir_sync, Dirent}; use self::read_file::{read_file, read_file_sync}; use self::rename::{rename, rename_sync}; use self::rm::{rmdir, rmdir_sync, rmfile, rmfile_sync}; use self::stats::{lstat_fn, lstat_fn_sync, stat_fn, stat_fn_sync, Stats}; use self::symlink::{symlink, symlink_sync}; use self::write_file::{write_file, write_file_sync}; pub const CONSTANT_F_OK: u32 = 0; pub const CONSTANT_R_OK: u32 = 4; pub const CONSTANT_W_OK: u32 = 2; pub const CONSTANT_X_OK: u32 = 1; pub struct FsPromisesModule; impl ModuleDef for FsPromisesModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("access")?; declare.declare("open")?; declare.declare("readFile")?; declare.declare("writeFile")?; declare.declare("rename")?; declare.declare("readdir")?; declare.declare("mkdir")?; declare.declare("mkdtemp")?; declare.declare("rm")?; declare.declare("rmdir")?; declare.declare("stat")?; declare.declare("lstat")?; declare.declare("constants")?; declare.declare("chmod")?; declare.declare("symlink")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { let globals = ctx.globals(); Class::::define(&globals)?; Class::::define(&globals)?; Class::::define(&globals)?; export_default(ctx, exports, |default| { export_promises(ctx, default)?; Ok(()) }) } } impl From for ModuleInfo { fn from(val: FsPromisesModule) -> Self { ModuleInfo { name: "fs/promises", module: val, } } } pub struct FsModule; impl ModuleDef for FsModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("promises")?; declare.declare("accessSync")?; declare.declare("mkdirSync")?; declare.declare("mkdtempSync")?; declare.declare("readdirSync")?; declare.declare("readFileSync")?; declare.declare("rmdirSync")?; declare.declare("rmSync")?; declare.declare("statSync")?; declare.declare("lstatSync")?; declare.declare("writeFileSync")?; declare.declare("constants")?; declare.declare("chmodSync")?; declare.declare("renameSync")?; declare.declare("symlinkSync")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { let globals = ctx.globals(); Class::::define(&globals)?; Class::::define(&globals)?; Class::::define(&globals)?; export_default(ctx, exports, |default| { let promises = Object::new(ctx.clone())?; export_promises(ctx, &promises)?; export_constants(ctx, default)?; default.set("promises", promises)?; default.set("accessSync", Func::from(access_sync))?; default.set("mkdirSync", Func::from(mkdir_sync))?; default.set("mkdtempSync", Func::from(mkdtemp_sync))?; default.set("readdirSync", Func::from(read_dir_sync))?; default.set("readFileSync", Func::from(read_file_sync))?; default.set("rmdirSync", Func::from(rmdir_sync))?; default.set("rmSync", Func::from(rmfile_sync))?; default.set("statSync", Func::from(stat_fn_sync))?; default.set("lstatSync", Func::from(lstat_fn_sync))?; default.set("writeFileSync", Func::from(write_file_sync))?; default.set("chmodSync", Func::from(chmod_sync))?; default.set("renameSync", Func::from(rename_sync))?; default.set("symlinkSync", Func::from(symlink_sync))?; Ok(()) }) } } fn export_promises<'js>(ctx: &Ctx<'js>, exports: &Object<'js>) -> Result<()> { export_constants(ctx, exports)?; exports.set("access", Func::from(Async(access)))?; exports.set("open", Func::from(Async(open)))?; exports.set("readFile", Func::from(Async(read_file)))?; exports.set("writeFile", Func::from(Async(write_file)))?; exports.set("rename", Func::from(Async(rename)))?; exports.set("readdir", Func::from(Async(read_dir)))?; exports.set("mkdir", Func::from(Async(mkdir)))?; exports.set("mkdtemp", Func::from(Async(mkdtemp)))?; exports.set("rm", Func::from(Async(rmfile)))?; exports.set("rmdir", Func::from(Async(rmdir)))?; exports.set("stat", Func::from(Async(stat_fn)))?; exports.set("lstat", Func::from(Async(lstat_fn)))?; exports.set("chmod", Func::from(Async(chmod)))?; exports.set("symlink", Func::from(Async(symlink)))?; Ok(()) } fn export_constants<'js>(ctx: &Ctx<'js>, exports: &Object<'js>) -> Result<()> { let constants = Object::new(ctx.clone())?; constants.set("F_OK", CONSTANT_F_OK)?; constants.set("R_OK", CONSTANT_R_OK)?; constants.set("W_OK", CONSTANT_W_OK)?; constants.set("X_OK", CONSTANT_X_OK)?; exports.set("constants", constants)?; Ok(()) } impl From for ModuleInfo { fn from(val: FsModule) -> Self { ModuleInfo { name: "fs", module: val, } } } ================================================ FILE: modules/llrt_fs/src/mkdir.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use crate::chmod::{set_mode, set_mode_sync}; use llrt_path::resolve_path; use llrt_utils::result::ResultExt; use rand::RngExt; use rquickjs::{function::Opt, Ctx, Object, Result}; use tokio::fs; pub async fn mkdir<'js>(ctx: Ctx<'js>, path: String, options: Opt>) -> Result { let (recursive, mode, path) = get_params(&path, options)?; if recursive { fs::create_dir_all(&path).await } else { fs::create_dir(&path).await } .or_throw_msg(&ctx, &["Can't create dir \"", &path, "\""].concat())?; set_mode(ctx, &path, mode).await?; Ok(path) } pub fn mkdir_sync<'js>(ctx: Ctx<'js>, path: String, options: Opt>) -> Result { let (recursive, mode, path) = get_params(&path, options)?; if recursive { std::fs::create_dir_all(&path) } else { std::fs::create_dir(&path) } .or_throw_msg(&ctx, &["Can't create dir \"", &path, "\""].concat())?; set_mode_sync(ctx, &path, mode)?; Ok(path) } fn get_params(path: &str, options: Opt) -> Result<(bool, u32, String)> { let mut recursive = false; let mut mode = 0o777; if let Some(options) = options.0 { recursive = options.get("recursive").unwrap_or_default(); mode = options.get("mode").unwrap_or(0o777); } let path = resolve_path([path])?; Ok((recursive, mode, path)) } const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; fn random_chars(len: usize) -> String { let mut bytes = vec![0u8; len]; rand::rng().fill(&mut bytes); bytes .iter() .map(|&byte| { let idx = (byte as usize) % CHARS.len(); CHARS[idx] as char }) .collect::() } pub async fn mkdtemp(ctx: Ctx<'_>, prefix: String) -> Result { let path = [prefix.as_str(), random_chars(6).as_str()].join(","); fs::create_dir_all(&path) .await .or_throw_msg(&ctx, &["Can't create dir \"", &path, "\""].concat())?; Ok(path) } pub fn mkdtemp_sync(ctx: Ctx<'_>, prefix: String) -> Result { let path = [prefix.as_str(), random_chars(6).as_str()].join(","); std::fs::create_dir_all(&path) .or_throw_msg(&ctx, &["Can't create dir \"", &path, "\""].concat())?; Ok(path) } ================================================ FILE: modules/llrt_fs/src/open.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::path::PathBuf; use llrt_utils::result::ResultExt; use rquickjs::{function::Opt, Ctx, Exception, Result}; use tokio::fs::OpenOptions; use super::file_handle::FileHandle; pub async fn open( ctx: Ctx<'_>, path: String, flags: Opt, mode: Opt, ) -> Result { let mut options = OpenOptions::new(); match flags.0.as_deref().unwrap_or("r") { // We are not supporting the sync modes "a" => options.append(true).create(true), "ax" => options.append(true).create_new(true), "a+" => options.append(true).read(true), "r" => options.read(true), "r+" => options.read(true).write(true), "w" => options.write(true).create(true).truncate(true), "wx" => options.write(true).create_new(true), "w+" => options.write(true).read(true).create(true).truncate(true), "wx+" => options.write(true).read(true).create_new(true), flags => { return Err(Exception::throw_message( &ctx, &["Invalid flags '", flags, "'"].concat(), )) }, }; #[cfg(unix)] { let mode = mode.0.unwrap_or(0o666); options.mode(mode); } #[cfg(not(unix))] { _ = mode; } let path = PathBuf::from(path); let file = options .open(&path) .await .or_throw_msg(&ctx, "Cannot open file")?; Ok(FileHandle::new(file, path)) } #[cfg(test)] mod tests { use llrt_buffer as buffer; use llrt_test::{call_test, given_file, test_async_with, ModuleEvaluator}; use crate::FsPromisesModule; #[tokio::test] async fn test_file_handle_read() { let path = given_file("Hello World") .await .to_string_lossy() .to_string(); let path_1 = path.clone(); test_async_with(|ctx| { Box::pin(async move { buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "fs/promises") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { open } from 'fs/promises'; export async function test(path) { let filehandle = null; try { filehandle = await open(path, 'r+'); let { buffer } = await filehandle.read(); return Array.from(buffer); } finally { await filehandle?.close(); } } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, (path_1,)).await; assert!(result.starts_with(b"Hello World")); }) }) .await; tokio::fs::remove_file(path).await.unwrap(); } } ================================================ FILE: modules/llrt_fs/src/read_dir.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #[cfg(unix)] use std::os::unix::fs::FileTypeExt; use std::{fs::Metadata, path::PathBuf}; use llrt_path::{ends_with_sep, CURRENT_DIR_STR}; use llrt_utils::fs::DirectoryWalker; use rquickjs::{ atom::PredefinedAtom, prelude::Opt, Array, Class, Ctx, IntoJs, Object, Result, Value, }; #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] #[rquickjs::class] pub struct Dirent { #[qjs(skip_trace)] metadata: Metadata, } #[rquickjs::methods(rename_all = "camelCase")] impl Dirent { pub fn is_file(&self) -> bool { self.metadata.is_file() } pub fn is_directory(&self) -> bool { self.metadata.is_dir() } pub fn is_symbolic_link(&self) -> bool { self.metadata.is_symlink() } #[qjs(rename = "isFIFO")] pub fn is_fifo(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_fifo() } #[cfg(not(unix))] { false } } pub fn is_block_device(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_block_device() } #[cfg(not(unix))] { false } } pub fn is_character_device(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_char_device() } #[cfg(not(unix))] { false } } pub fn is_socket(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_socket() } #[cfg(not(unix))] { false } } } struct ReadDirItem { name: String, metadata: Option, } pub struct ReadDir { items: Vec, root: String, } impl<'js> IntoJs<'js> for ReadDir { fn into_js(self, ctx: &Ctx<'js>) -> Result> { let arr = Array::new(ctx.clone())?; for (index, item) in self.items.into_iter().enumerate() { if let Some(metadata) = item.metadata { let dirent = Dirent { metadata }; let dirent = Class::instance(ctx.clone(), dirent)?; dirent.set(PredefinedAtom::Name, item.name)?; dirent.set("parentPath", &self.root)?; arr.set(index, dirent)?; } else { arr.set(index, item.name)?; } } arr.into_js(ctx) } } pub async fn read_dir(mut path: String, options: Opt>) -> Result { let (with_file_types, skip_root_pos, mut directory_walker) = process_options_and_create_directory_walker(&mut path, options); let mut items = Vec::with_capacity(64); while let Some((child, metadata)) = directory_walker.walk().await? { append_directory_and_metadata_to_vec( with_file_types, skip_root_pos, &mut items, child, metadata, ); } items.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap()); Ok(ReadDir { items, root: path }) } pub fn read_dir_sync(mut path: String, options: Opt>) -> Result { let (with_file_types, skip_root_pos, mut directory_walker) = process_options_and_create_directory_walker(&mut path, options); let mut items = Vec::with_capacity(64); while let Some((child, metadata)) = directory_walker.walk_sync()? { append_directory_and_metadata_to_vec( with_file_types, skip_root_pos, &mut items, child, metadata, ); } items.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap()); Ok(ReadDir { items, root: path }) } type OptionsAndDirectoryWalker = (bool, usize, DirectoryWalker bool>); fn process_options_and_create_directory_walker( path: &mut String, options: Opt, ) -> OptionsAndDirectoryWalker { let mut with_file_types = false; let mut is_recursive = false; if let Some(options) = options.0 { with_file_types = options .get("withFileTypes") .ok() .and_then(|file_types: Value| file_types.as_bool()) .unwrap_or_default(); is_recursive = options .get("recursive") .ok() .and_then(|recursive: Value| recursive.as_bool()) .unwrap_or_default(); }; if ends_with_sep(path) { path.pop(); } let skip_root_pos = { match path.as_str() { // . | ./ "." | CURRENT_DIR_STR => path.len(), // path _ => path.len() + 1, } }; let mut directory_walker: DirectoryWalker bool> = DirectoryWalker::new(PathBuf::from(&path), |_| true); if is_recursive { directory_walker.set_recursive(true); } (with_file_types, skip_root_pos, directory_walker) } fn append_directory_and_metadata_to_vec( with_file_types: bool, skip_root_pos: usize, items: &mut Vec, child: PathBuf, metadata: Metadata, ) { let metadata = if with_file_types { Some(metadata) } else { None }; let name = child.into_os_string().to_string_lossy()[skip_root_pos..].to_string(); items.push(ReadDirItem { name, metadata }) } ================================================ FILE: modules/llrt_fs/src/read_file.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use either::Either; use llrt_buffer::Buffer; use llrt_utils::{object::ObjectExt, result::ResultExt}; use rquickjs::{function::Opt, Ctx, Error, FromJs, IntoJs, Result, Value}; use tokio::fs; pub async fn read_file( ctx: Ctx<'_>, path: String, options: Opt>, ) -> Result> { let bytes = fs::read(&path) .await .or_throw_msg(&ctx, &["Can't read \"", &path, "\""].concat())?; handle_read_file_bytes(&ctx, options, bytes) } pub fn read_file_sync( ctx: Ctx<'_>, path: String, options: Opt>, ) -> Result> { let bytes = std::fs::read(&path).or_throw_msg(&ctx, &["Can't read \"", &path, "\""].concat())?; handle_read_file_bytes(&ctx, options, bytes) } pub(crate) fn handle_read_file_bytes<'a>( ctx: &Ctx<'a>, options: Opt>, bytes: Vec, ) -> Result> { let buffer = Buffer(bytes); if let Some(options) = options.0 { let encoding = match options { Either::Left(encoding) => Some(encoding), Either::Right(options) => options.encoding, }; if let Some(encoding) = encoding { return buffer .to_string(ctx, &encoding) .and_then(|s| s.into_js(ctx)); } } buffer.into_js(ctx) } pub(crate) struct ReadFileOptions { pub encoding: Option, } impl<'js> FromJs<'js> for ReadFileOptions { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let encoding = obj.get_optional::<_, String>("encoding")?; Ok(Self { encoding }) } } ================================================ FILE: modules/llrt_fs/src/rename.rs ================================================ use llrt_utils::result::ResultExt; use rquickjs::{Ctx, Result}; pub(crate) fn rename_error(from: &str, to: &str) -> String { [ "Can't rename file/folder from \"", from, "\" to \"", to, "\"", ] .concat() } pub async fn rename(ctx: Ctx<'_>, old_path: String, new_path: String) -> Result<()> { tokio::fs::rename(&old_path, &new_path) .await .or_throw_msg(&ctx, &rename_error(&old_path, &new_path))?; Ok(()) } pub fn rename_sync(ctx: Ctx<'_>, old_path: String, new_path: String) -> Result<()> { std::fs::rename(&old_path, &new_path) .or_throw_msg(&ctx, &rename_error(&old_path, &new_path))?; Ok(()) } ================================================ FILE: modules/llrt_fs/src/rm.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::result::ResultExt; use rquickjs::{function::Opt, Ctx, Object, Result}; use tokio::fs; #[allow(clippy::manual_async_fn)] pub async fn rmdir<'js>(ctx: Ctx<'js>, path: String, options: Opt>) -> Result<()> { let recursive = get_params_rm_dir(options); if recursive { fs::remove_dir_all(&path).await } else { fs::remove_dir(&path).await } .or_throw_msg(&ctx, &["Can't remove dir \"", &path, "\""].concat())?; Ok(()) } #[allow(clippy::manual_async_fn)] pub fn rmdir_sync<'js>(ctx: Ctx<'js>, path: String, options: Opt>) -> Result<()> { let recursive = get_params_rm_dir(options); if recursive { std::fs::remove_dir_all(&path) } else { std::fs::remove_dir(&path) } .or_throw_msg(&ctx, &["Can't remove dir \"", &path, "\""].concat())?; Ok(()) } pub async fn rmfile<'js>(ctx: Ctx<'js>, path: String, options: Opt>) -> Result<()> { let (recursive, force) = get_params_rm(options); let res = async move { let is_dir = fs::metadata(&path) .await .map(|metadata| metadata.is_dir()) .or_throw(&ctx)?; (if is_dir && recursive { fs::remove_dir_all(&path).await } else if is_dir && !recursive { fs::remove_dir(&path).await } else { fs::remove_file(&path).await }) .or_throw_msg(&ctx, &["Can't remove file \"", &path, "\""].concat())?; Ok(()) } .await; if !force { return res; } Ok(()) } pub fn rmfile_sync(path: String, options: Opt>) -> Result<()> { let (recursive, force) = get_params_rm(options); let res = (|| -> Result<()> { let is_dir = std::fs::metadata(&path).map(|metadata| metadata.is_dir())?; (if is_dir && recursive { std::fs::remove_dir_all(&path) } else if is_dir && !recursive { std::fs::remove_dir(&path) } else { std::fs::remove_file(&path) })?; Ok(()) })(); if !force { return res; } Ok(()) } fn get_params_rm_dir(options: Opt) -> bool { let mut recursive = false; if let Some(options) = options.0 { recursive = options.get("recursive").unwrap_or_default(); } recursive } fn get_params_rm(options: Opt) -> (bool, bool) { let mut recursive = false; let mut force = false; if let Some(options) = options.0 { recursive = options.get("recursive").unwrap_or_default(); force = options.get("force").unwrap_or_default(); } (recursive, force) } ================================================ FILE: modules/llrt_fs/src/stats.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #[cfg(unix)] use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; #[allow(unused_imports)] use std::{ fs::Metadata, time::{Duration, SystemTime}, }; use llrt_utils::result::ResultExt; use rquickjs::{Ctx, Result}; use tokio::fs; // The Stats implementation is very much based on Unix. The Windows implementation // tries its best to mimic the implementation of libuv since it is the standard. // See: https://github.com/libuv/libuv/blob/90648ea3e55125a5a819b32106da6462da310da6/src/win/fs.c // // By comparison, the Deno implementation is very basic and doesn't even try much. // See: https://github.com/denoland/deno/blob/c9da27e147d0681724dd647593abbaa46417feb7/ext/io/fs.rs#L114-L182 // // This implementation doesn't handle files created before UNIX_EPOCH. #[rquickjs::class] #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] pub struct Stats { #[qjs(skip_trace)] metadata: Metadata, } #[rquickjs::methods(rename_all = "camelCase")] impl Stats { #[qjs(skip)] pub fn new(metadata: Metadata) -> Self { Self { metadata } } #[qjs(get, enumerable)] pub fn dev(&self) -> u64 { #[cfg(unix)] { self.metadata.dev() } #[cfg(not(unix))] { // Unstable feature, see https://github.com/rust-lang/rust/issues/63010 0 } } #[qjs(get, enumerable)] pub fn ino(&self) -> u64 { #[cfg(unix)] { self.metadata.ino() } #[cfg(not(unix))] { // Unstable feature, see https://github.com/rust-lang/rust/issues/63010 0 } } #[qjs(get, enumerable)] pub fn mode(&self) -> u32 { #[cfg(unix)] { self.metadata.mode() } #[cfg(not(unix))] { 0o666 } } #[qjs(get, enumerable)] pub fn nlink(&self) -> u64 { #[cfg(unix)] { self.metadata.nlink() } #[cfg(not(unix))] { // Unstable feature, see https://github.com/rust-lang/rust/issues/63010 1 } } #[qjs(get, enumerable)] pub fn uid(&self) -> u32 { #[cfg(unix)] { self.metadata.uid() } #[cfg(not(unix))] { 0 } } #[qjs(get, enumerable)] pub fn gid(&self) -> u32 { #[cfg(unix)] { self.metadata.gid() } #[cfg(not(unix))] { 0 } } #[qjs(get, enumerable)] pub fn rdev(&self) -> u64 { #[cfg(unix)] { self.metadata.rdev() } #[cfg(not(unix))] { 0 } } #[qjs(get, enumerable)] pub fn size(&self) -> u64 { #[cfg(unix)] { self.metadata.size() } #[cfg(windows)] { if self.metadata.is_dir() { 0 } else { self.metadata.file_size() } } #[cfg(not(any(unix, windows)))] { 0 } } #[qjs(get, enumerable)] pub fn blksize(&self) -> u64 { #[cfg(unix)] { self.metadata.blksize() } #[cfg(not(unix))] { 4096 } } #[qjs(get, enumerable)] pub fn blocks(&self) -> u64 { #[cfg(unix)] { self.metadata.blocks() } #[cfg(not(unix))] { 0 } } #[qjs(get, enumerable)] pub fn atime_ms(&self, ctx: Ctx<'_>) -> Result { #[cfg(unix)] { _ = ctx; Ok(self.metadata.atime_nsec() as u64 / 1e6 as u64) } #[cfg(not(unix))] { self.metadata.accessed().map(to_msec).or_throw(&ctx) } } #[qjs(get, enumerable)] pub fn mtime_ms(&self, ctx: Ctx<'_>) -> Result { #[cfg(unix)] { _ = ctx; Ok(self.metadata.mtime_nsec() as u64 / 1e6 as u64) } #[cfg(not(unix))] { self.metadata.modified().map(to_msec).or_throw(&ctx) } } #[qjs(get, enumerable)] pub fn ctime_ms(&self, ctx: Ctx<'_>) -> Result { #[cfg(unix)] { _ = ctx; Ok(self.metadata.ctime_nsec() as u64 / 1e6 as u64) } #[cfg(not(unix))] { self.metadata.modified().map(to_msec).or_throw(&ctx) } } #[qjs(get, enumerable)] pub fn birthtime_ms(&self, ctx: Ctx<'_>) -> Result { self.metadata .created() .or_throw(&ctx) .and_then(|c| c.elapsed().or_throw(&ctx)) .map(|d| d.as_millis() as u64) } #[qjs(get, enumerable)] pub fn atime(&self, ctx: Ctx<'_>) -> Result { self.metadata.accessed().or_throw(&ctx) } #[qjs(get, enumerable)] pub fn mtime(&self, ctx: Ctx<'_>) -> Result { self.metadata.modified().or_throw(&ctx) } #[qjs(get, enumerable)] pub fn ctime(&self, ctx: Ctx<'_>) -> Result { #[cfg(unix)] { _ = ctx; Ok(SystemTime::UNIX_EPOCH + Duration::from_nanos(self.metadata.ctime_nsec() as u64)) } #[cfg(not(unix))] { self.metadata.modified().or_throw(&ctx) } } #[qjs(get, enumerable)] pub fn birthtime(&self, ctx: Ctx<'_>) -> Result { self.metadata.created().or_throw(&ctx) } pub fn is_file(&self) -> bool { self.metadata.is_file() } /// @deprecated Use `is_directory` instead pub fn is_dir(&self) -> bool { self.metadata.is_dir() } pub fn is_directory(&self) -> bool { self.metadata.is_dir() } /// @deprecated Use `is_symbolic_link` instead pub fn is_symlink(&self) -> bool { self.metadata.is_symlink() } pub fn is_symbolic_link(&self) -> bool { self.metadata.is_symlink() } #[qjs(rename = "isFIFO")] pub fn is_fifo(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_fifo() } #[cfg(not(unix))] { false } } pub fn is_block_device(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_block_device() } #[cfg(not(unix))] { false } } pub fn is_character_device(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_char_device() } #[cfg(not(unix))] { false } } pub fn is_socket(&self) -> bool { #[cfg(unix)] { self.metadata.file_type().is_socket() } #[cfg(not(unix))] { false } } } pub async fn stat_fn(ctx: Ctx<'_>, path: String) -> Result { let metadata = fs::metadata(&path) .await .or_throw_msg(&ctx, &["Can't stat \"", &path, "\""].concat())?; let stats = Stats::new(metadata); Ok(stats) } pub fn stat_fn_sync(ctx: Ctx<'_>, path: String) -> Result { let metadata = std::fs::metadata(&path).or_throw_msg(&ctx, &["Can't stat \"", &path, "\""].concat())?; let stats = Stats::new(metadata); Ok(stats) } pub async fn lstat_fn(ctx: Ctx<'_>, path: String) -> Result { let metadata = fs::symlink_metadata(&path) .await .or_throw_msg(&ctx, &["Can't lstat \"", &path, "\""].concat())?; let stats = Stats::new(metadata); Ok(stats) } pub fn lstat_fn_sync(ctx: Ctx<'_>, path: String) -> Result { let metadata = std::fs::symlink_metadata(&path) .or_throw_msg(&ctx, &["Can't lstat \"", &path, "\""].concat())?; let stats = Stats::new(metadata); Ok(stats) } #[allow(dead_code)] #[inline(always)] fn to_msec(time: SystemTime) -> u64 { time.duration_since(SystemTime::UNIX_EPOCH) .map(|t| t.as_millis() as u64) .unwrap_or_else(|err| err.duration().as_millis() as u64) } ================================================ FILE: modules/llrt_fs/src/symlink.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::io; use llrt_utils::result::ResultExt; use rquickjs::{function::Opt, Ctx, Result}; fn symlink_blocking(target: &str, path: &str, type_value: Option) -> io::Result<()> { #[cfg(unix)] { _ = type_value; std::os::unix::fs::symlink(target, path) } #[cfg(windows)] { let type_str = match type_value.as_deref() { Some(t @ ("file" | "dir" | "junction")) => t, _ => { if std::fs::metadata(target) .map(|m| m.is_dir()) .unwrap_or(false) { "dir" } else { "file" } }, }; match type_str { "junction" => junction::create(target, path), "dir" => std::os::windows::fs::symlink_dir(target, path), _ => std::os::windows::fs::symlink_file(target, path), } } } pub async fn symlink<'js>( ctx: Ctx<'js>, target: String, path: String, type_value: Opt, ) -> Result<()> { let path_clone = path.clone(); tokio::task::spawn_blocking(move || symlink_blocking(&target, &path_clone, type_value.0)) .await .map_err(io::Error::other)? .or_throw_msg(&ctx, &["Can't create symlink \"", &path, "\""].concat()) } pub fn symlink_sync<'js>( ctx: Ctx<'js>, target: String, path: String, type_value: Opt, ) -> Result<()> { symlink_blocking(&target, &path, type_value.0) .or_throw_msg(&ctx, &["Can't create symlink \"", &path, "\""].concat()) } ================================================ FILE: modules/llrt_fs/src/write_file.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use either::Either; use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt}; use rquickjs::{function::Opt, Ctx, Error, FromJs, Result, Value}; use tokio::fs; use tokio::io::AsyncWriteExt; pub async fn write_file<'js>( ctx: Ctx<'js>, path: String, data: Value<'js>, options: Opt>, ) -> Result<()> { let write_error_message = &["Can't write file \"", &path, "\""].concat(); let mut file = fs::File::create(&path) .await .or_throw_msg(&ctx, write_error_message)?; #[cfg(unix)] if let Some(Either::Right(opts)) = options.0 { use std::os::unix::fs::PermissionsExt; let perm = PermissionsExt::from_mode(opts.mode.unwrap_or(0o666)); file.set_permissions(perm) .await .or_throw_msg(&ctx, write_error_message)?; } #[cfg(not(unix))] { _ = options; if let Some(Either::Right(opts)) = options.0 { _ = opts.mode; } } let bytes = ObjectBytes::from(&ctx, &data)?; file.write_all(bytes.as_bytes(&ctx)?) .await .or_throw_msg(&ctx, write_error_message)?; file.flush().await.or_throw_msg(&ctx, write_error_message)?; Ok(()) } pub fn write_file_sync<'js>( ctx: Ctx<'js>, path: String, bytes: ObjectBytes<'js>, options: Opt>, ) -> Result<()> { let write_error_message = &["Can't write file \"", &path, "\""].concat(); std::fs::write(&path, bytes.as_bytes(&ctx)?).or_throw_msg(&ctx, write_error_message)?; #[cfg(unix)] { if let Some(Either::Right(opts)) = options.0 { use std::os::unix::fs::PermissionsExt; std::fs::set_permissions(path, PermissionsExt::from_mode(opts.mode.unwrap_or(0o666))) .or_throw_msg(&ctx, write_error_message)?; } } #[cfg(not(unix))] { _ = options; if let Some(Either::Right(opts)) = options.0 { _ = opts.mode; } } Ok(()) } pub(crate) struct WriteFileOptions { pub mode: Option, } impl<'js> FromJs<'js> for WriteFileOptions { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let mode = obj.get_optional::<_, u32>("mode")?; Ok(Self { mode }) } } ================================================ FILE: modules/llrt_http/Cargo.toml ================================================ [package] name = "llrt_http" description = "LLRT Module http & https" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_http" path = "src/lib.rs" [features] default = ["http1", "http2", "webpki-roots", "tls-ring"] http1 = ["hyper/http1", "hyper-rustls?/http1", "hyper-util/http1"] http2 = ["hyper/http2", "hyper-rustls?/http2", "hyper-util/http2"] webpki-roots = ["llrt_tls/webpki-roots"] native-roots = ["llrt_tls/native-roots"] # TLS crypto backend features (rustls-based) tls-ring = [ "http1", "llrt_tls/tls-ring", "dep:hyper-rustls", "hyper-rustls/ring", "dep:rustls", ] tls-aws-lc = [ "http1", "llrt_tls/tls-aws-lc", "dep:hyper-rustls", "hyper-rustls/aws-lc-rs", "dep:rustls", ] tls-graviola = [ "http1", "llrt_tls/tls-graviola", "dep:hyper-rustls", "dep:rustls", ] # OpenSSL TLS backend tls-openssl = [ "http1", "llrt_tls/tls-openssl", "dep:hyper-openssl", "dep:openssl", ] [dependencies] bytes = { version = "1", default-features = false } http-body-util = { version = "0.1", default-features = false } hyper = { version = "1", features = ["client"], default-features = false } hyper-util = { version = "0.1", features = [ "client-legacy", "tokio", ], default-features = false } hyper-rustls = { version = "0.27", default-features = false, optional = true } hyper-openssl = { version = "0.10", features = [ "client-legacy", ], optional = true } openssl = { version = "0.10", optional = true } llrt_dns_cache = { version = "0.8.1-beta", path = "../../libs/llrt_dns_cache" } llrt_tls = { version = "0.8.1-beta", default-features = false, path = "../llrt_tls" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } rquickjs = { version = "0.11", features = [ "either", "std", ], default-features = false } rustls = { version = "0.23", features = [ "tls12", ], default-features = false, optional = true } [dev-dependencies] ================================================ FILE: modules/llrt_http/src/agent.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::result::ResultExt; use llrt_utils::{any_of::AnyOf4, bytes::ObjectBytes, object::ObjectExt}; use rquickjs::{prelude::Opt, Ctx, Error, FromJs, Result, Value}; use crate::HyperClient; #[rquickjs::class] #[derive(rquickjs::JsLifetime, rquickjs::class::Trace)] pub struct Agent { #[qjs(skip_trace)] client: HyperClient, } impl Agent { pub fn client(&self) -> HyperClient { self.client.clone() } } #[rquickjs::methods(rename_all = "camelCase")] impl Agent { #[qjs(constructor)] pub fn new<'js>(ctx: Ctx<'js>, options: Opt) -> Result { let mut reject_unauthorized = true; let mut ca = None; if let Some(options) = options.0 { if let Some(opt_reject_unauthorized) = options.reject_unauthorized { reject_unauthorized = opt_reject_unauthorized; } if let Some(opt_ca) = options.ca { ca = Some(opt_ca); } } let config = llrt_tls::build_client_config(llrt_tls::BuildClientConfigOptions { reject_unauthorized, ca, }) .or_throw_msg(&ctx, "Failed to build TLS config")?; let client = crate::build_client(Some(config)).or_throw_msg(&ctx, "Failed to build HTTP client")?; Ok(Self { client }) } } pub struct AgentOptions { reject_unauthorized: Option, ca: Option>>, } impl<'js> FromJs<'js> for AgentOptions { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let reject_unauthorized = obj.get_optional::<_, bool>("rejectUnauthorized")?; let ca = obj .get_optional::<_, AnyOf4, ObjectBytes, Vec>>("ca")? .map(|ca| { let ca = match ca { AnyOf4::A(ca) => vec![ca.into_bytes()], AnyOf4::B(ca) => ca.into_iter().map(|ca| ca.into_bytes()).collect(), AnyOf4::C(ca) => vec![ca.into_bytes(ctx)?], AnyOf4::D(ca) => ca .into_iter() .map(|ca| ca.into_bytes(ctx)) .collect::>>()?, }; Ok::<_, Error>(ca) }) .transpose()?; Ok(Self { reject_unauthorized, ca, }) } } ================================================ FILE: modules/llrt_http/src/client.rs ================================================ use std::convert::Infallible; use bytes::Bytes; use http_body_util::combinators::BoxBody; use hyper_util::{ client::legacy::{connect::HttpConnector, Client}, rt::{TokioExecutor, TokioTimer}, }; use llrt_dns_cache::CachedDnsResolver; use once_cell::sync::Lazy; use crate::get_pool_idle_timeout; #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] use crate::get_http_version; // Rustls-based TLS backends #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] mod rustls_client { use super::*; use hyper_rustls::HttpsConnector; use llrt_tls::TLS_CONFIG; use rustls::ClientConfig; #[cfg(feature = "http2")] use crate::HttpVersion; pub type HyperClient = Client>, BoxBody>; pub static HTTP_CLIENT: Lazy>> = Lazy::new(|| build_client(None)); pub fn build_client( tls_config: Option, ) -> Result> { let pool_idle_timeout = get_pool_idle_timeout(); let config = if let Some(tls_config) = tls_config { tls_config } else { match &*TLS_CONFIG { Ok(tls_config) => tls_config.clone(), Err(e) => return Err(e.to_string().into()), } }; let builder = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(config) .https_or_http(); let mut cache_dns_connector = CachedDnsResolver::new().into_http_connector(); cache_dns_connector.enforce_http(false); let https = match get_http_version() { #[cfg(feature = "http2")] HttpVersion::Http2 => builder .enable_all_versions() .wrap_connector(cache_dns_connector), _ => builder.enable_http1().wrap_connector(cache_dns_connector), }; Ok(Client::builder(TokioExecutor::new()) .pool_idle_timeout(pool_idle_timeout) .pool_timer(TokioTimer::new()) .build(https)) } } // OpenSSL TLS backend #[cfg(feature = "tls-openssl")] mod openssl_client { use super::*; use hyper_openssl::client::legacy::HttpsConnector; use llrt_tls::TLS_CONFIG; use openssl::ssl::SslConnectorBuilder; pub type HyperClient = Client>, BoxBody>; pub static HTTP_CLIENT: Lazy>> = Lazy::new(|| build_client(None)); pub fn build_client( tls_config: Option, ) -> Result> { let pool_idle_timeout = get_pool_idle_timeout(); let connector = if let Some(tls_config) = tls_config { tls_config } else { match TLS_CONFIG.as_ref() { Ok(_builder) => { // Clone the builder by creating a new one with same settings llrt_tls::build_client_config(llrt_tls::BuildClientConfigOptions { reject_unauthorized: true, ca: None, })? }, Err(e) => return Err(e.to_string().into()), } }; let mut cache_dns_connector = CachedDnsResolver::new().into_http_connector(); cache_dns_connector.enforce_http(false); let mut https = HttpsConnector::with_connector(cache_dns_connector, connector)?; https.set_callback(|ssl, _| { #[cfg(feature = "http2")] ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?; #[cfg(not(feature = "http2"))] ssl.set_alpn_protos(b"\x08http/1.1")?; Ok(()) }); Ok(Client::builder(TokioExecutor::new()) .pool_idle_timeout(pool_idle_timeout) .pool_timer(TokioTimer::new()) .build(https)) } } // Re-export based on feature #[cfg(any(feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola"))] pub use rustls_client::*; #[cfg(feature = "tls-openssl")] pub use openssl_client::*; ================================================ FILE: modules/llrt_http/src/config.rs ================================================ use std::{ sync::{ atomic::{AtomicU64, Ordering}, OnceLock, }, time::Duration, }; static CONNECTION_POOL_IDLE_TIMEOUT: AtomicU64 = AtomicU64::new(15); pub fn set_pool_idle_timeout_seconds(seconds: u64) { CONNECTION_POOL_IDLE_TIMEOUT.store(seconds, Ordering::Relaxed); } pub fn get_pool_idle_timeout() -> Duration { Duration::from_secs(CONNECTION_POOL_IDLE_TIMEOUT.load(Ordering::Relaxed)) } #[derive(Debug, Clone, Copy)] pub enum HttpVersion { Http1_1, Http2, } static HTTP_VERSION: OnceLock = OnceLock::new(); pub fn set_http_version(version: HttpVersion) { _ = HTTP_VERSION.set(version); } pub fn get_http_version() -> HttpVersion { *HTTP_VERSION.get_or_init(|| { #[cfg(all(feature = "http2", feature = "http1"))] { HttpVersion::Http1_1 } #[cfg(all(not(feature = "http1"), feature = "http2"))] { HttpVersion::Http2 } #[cfg(all(not(feature = "http2"), feature = "http1"))] { HttpVersion::Http1_1 } #[cfg(all(not(feature = "http2"), not(feature = "http1")))] { compile_error!("Either the `http1` or `http2` feature must be enabled") } }) } ================================================ FILE: modules/llrt_http/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, Ctx, Result, }; pub use self::config::*; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] mod client; mod config; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] mod agent; #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] pub use self::{agent::Agent, client::*}; pub struct HttpsModule; impl ModuleDef for HttpsModule { fn declare(declare: &Declarations) -> Result<()> { #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] declare.declare(stringify!(Agent))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { #[cfg(any( feature = "tls-ring", feature = "tls-aws-lc", feature = "tls-graviola", feature = "tls-openssl" ))] rquickjs::Class::::define(default)?; let _ = default; Ok(()) }) } } impl From for ModuleInfo { fn from(val: HttpsModule) -> Self { ModuleInfo { name: "https", module: val, } } } ================================================ FILE: modules/llrt_intl/Cargo.toml ================================================ [package] name = "llrt_intl" description = "LLRT Intl module - minimal internationalization support" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_intl" path = "src/lib.rs" [dependencies] itoa = "1.0" jiff = { version = "0.2" } rquickjs = { version = "0.11", default-features = false } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils" } ================================================ FILE: modules/llrt_intl/README.md ================================================ # llrt_intl Minimal internationalization support for LLRT. Provides a subset of `Intl` functionality focused on timezone support. ## Features - **Intl.DateTimeFormat** - Minimal implementation supporting `format()`, `formatToParts()`, and `resolvedOptions()` - **Date.prototype.toLocaleString** - Enhanced to support the `timeZone` option - **dayjs compatibility** - Enables the dayjs timezone plugin without polyfills ## API ### `Intl.DateTimeFormat` Minimal implementation for timezone-aware date formatting. ### `Date.prototype.toLocaleString` Enhanced to support the `timeZone` option for timezone conversion. ## Examples ### Intl.DateTimeFormat ```javascript const formatter = new Intl.DateTimeFormat("en-US", { timeZone: "America/Denver", hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", }); const date = new Date("2022-03-02T15:45:34Z"); console.log(formatter.format(date)); // "03/02/2022, 08:45:34" ``` ### Using with dayjs ```javascript const dayjs = require("dayjs"); const utc = require("dayjs/plugin/utc"); const timezone = require("dayjs/plugin/timezone"); dayjs.extend(utc); dayjs.extend(timezone); const date = dayjs("2022-03-02T15:45:34Z"); console.log(date.tz("America/Denver").format()); // "2022-03-02T08:45:34-07:00" console.log(date.tz("Asia/Tokyo").format()); // "2022-03-03T00:45:34+09:00" ``` ================================================ FILE: modules/llrt_intl/src/cldr_data.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! Baked CLDR locale data for date/time formatting. //! //! This module contains pre-extracted patterns from the Unicode CLDR project //! for a subset of common locales, enabling locale-aware date/time formatting //! without requiring the full ICU library. //! //! Data sourced from: https://github.com/unicode-org/cldr-json /// Locale-specific date/time formatting data #[derive(Debug, Clone)] pub struct LocaleData { /// Date format patterns (full, long, medium, short) pub date_formats: DateFormats, /// Time format patterns (full, long, medium, short) pub time_formats: TimeFormats, /// Pattern for combining date and time (e.g., "{1}, {0}") pub datetime_pattern: &'static str, /// Month names (wide format, 1-indexed internally but stored 0-indexed) pub months_wide: [&'static str; 12], /// Month names (abbreviated format) pub months_abbr: [&'static str; 12], /// Weekday names (wide format, 0=Sunday) pub days_wide: [&'static str; 7], /// Weekday names (abbreviated format) pub days_abbr: [&'static str; 7], /// AM marker pub am: &'static str, /// PM marker pub pm: &'static str, /// Whether this locale uses 12-hour time by default pub hour12_default: bool, } /// Date format patterns for different styles #[derive(Debug, Clone)] pub struct DateFormats { pub full: &'static str, pub long: &'static str, pub medium: &'static str, pub short: &'static str, } /// Time format patterns for different styles #[derive(Debug, Clone)] pub struct TimeFormats { pub full: &'static str, pub long: &'static str, pub medium: &'static str, pub short: &'static str, } /// Get locale data for a given locale string. /// Falls back to en-US for unknown locales. pub fn get_locale_data(locale: &str) -> &'static LocaleData { // Normalize locale: lowercase, handle both - and _ let locale_lower = locale.to_lowercase().replace('_', "-"); // Try exact match first, then language-only fallback match locale_lower.as_str() { "en-us" | "en" => &EN_US, "en-gb" | "en-au" | "en-nz" | "en-ie" => &EN_GB, "de" | "de-de" | "de-at" | "de-ch" => &DE, "fr" | "fr-fr" | "fr-ca" | "fr-be" | "fr-ch" => &FR, "es" | "es-es" | "es-mx" | "es-ar" => &ES, "it" | "it-it" => &IT, "pt" | "pt-pt" | "pt-br" => &PT, "nl" | "nl-nl" | "nl-be" => &NL, "ru" | "ru-ru" => &RU, "ja" | "ja-jp" => &JA, "ko" | "ko-kr" => &KO, "zh" | "zh-cn" | "zh-hans" => &ZH, "zh-tw" | "zh-hant" | "zh-hk" => &ZH_TW, "ar" | "ar-sa" | "ar-eg" => &AR, // High priority locales "hi" | "hi-in" => &HI, "bn" | "bn-bd" | "bn-in" => &BN, "vi" | "vi-vn" => &VI, "th" | "th-th" => &TH, "id" | "id-id" => &ID, "tr" | "tr-tr" => &TR, "pl" | "pl-pl" => &PL, "uk" | "uk-ua" => &UK, // Medium priority locales "sv" | "sv-se" => &SV, "da" | "da-dk" => &DA, "nb" | "nb-no" | "no" | "nn" | "nn-no" => &NB, "fi" | "fi-fi" => &FI, "cs" | "cs-cz" => &CS, "el" | "el-gr" => &EL, "he" | "he-il" | "iw" => &HE, "hu" | "hu-hu" => &HU, "ro" | "ro-ro" => &RO, // Fallback to en-US _ => { // Try to match just the language part if let Some(lang) = locale_lower.split('-').next() { match lang { "en" => &EN_US, "de" => &DE, "fr" => &FR, "es" => &ES, "it" => &IT, "pt" => &PT, "nl" => &NL, "ru" => &RU, "ja" => &JA, "ko" => &KO, "zh" => &ZH, "ar" => &AR, "hi" => &HI, "bn" => &BN, "vi" => &VI, "th" => &TH, "id" => &ID, "tr" => &TR, "pl" => &PL, "uk" => &UK, "sv" => &SV, "da" => &DA, "nb" | "no" | "nn" => &NB, "fi" => &FI, "cs" => &CS, "el" => &EL, "he" | "iw" => &HE, "hu" => &HU, "ro" => &RO, _ => &EN_US, } } else { &EN_US } }, } } // English (US) - en-US static EN_US: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, MMMM d, y", long: "MMMM d, y", medium: "MMM d, y", short: "M/d/yy", }, time_formats: TimeFormats { full: "h:mm:ss a zzzz", long: "h:mm:ss a z", medium: "h:mm:ss a", short: "h:mm a", }, datetime_pattern: "{1}, {0}", months_wide: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ], months_abbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ], days_wide: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", ], days_abbr: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], am: "AM", pm: "PM", hour12_default: true, }; // English (GB) - en-GB static EN_GB: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "dd/MM/y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ], months_abbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ], days_wide: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", ], days_abbr: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], am: "am", pm: "pm", hour12_default: false, }; // German - de static DE: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d. MMMM y", long: "d. MMMM y", medium: "dd.MM.y", short: "dd.MM.yy", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", ], months_abbr: [ "Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sep.", "Okt.", "Nov.", "Dez.", ], days_wide: [ "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", ], days_abbr: ["So.", "Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa."], am: "AM", pm: "PM", hour12_default: false, }; // French - fr static FR: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "dd/MM/y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre", ], months_abbr: [ "janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc.", ], days_wide: [ "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", ], days_abbr: ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."], am: "AM", pm: "PM", hour12_default: false, }; // Spanish - es static ES: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d 'de' MMMM 'de' y", long: "d 'de' MMMM 'de' y", medium: "d MMM y", short: "d/M/yy", }, time_formats: TimeFormats { full: "H:mm:ss zzzz", long: "H:mm:ss z", medium: "H:mm:ss", short: "H:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre", ], months_abbr: [ "ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic", ], days_wide: [ "domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado", ], days_abbr: ["dom", "lun", "mar", "mié", "jue", "vie", "sáb"], am: "a.\u{a0}m.", pm: "p.\u{a0}m.", hour12_default: false, }; // Italian - it static IT: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "dd/MM/yy", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre", ], months_abbr: [ "gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic", ], days_wide: [ "domenica", "lunedì", "martedì", "mercoledì", "giovedì", "venerdì", "sabato", ], days_abbr: ["dom", "lun", "mar", "mer", "gio", "ven", "sab"], am: "AM", pm: "PM", hour12_default: false, }; // Portuguese - pt static PT: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d 'de' MMMM 'de' y", long: "d 'de' MMMM 'de' y", medium: "d 'de' MMM 'de' y", short: "dd/MM/y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro", ], months_abbr: [ "jan", "fev", "mar", "abr", "mai", "jun", "jul", "ago", "set", "out", "nov", "dez", ], days_wide: [ "domingo", "segunda-feira", "terça-feira", "quarta-feira", "quinta-feira", "sexta-feira", "sábado", ], days_abbr: ["dom", "seg", "ter", "qua", "qui", "sex", "sáb"], am: "AM", pm: "PM", hour12_default: false, }; // Dutch - nl static NL: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "dd-MM-y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december", ], months_abbr: [ "jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec", ], days_wide: [ "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag", ], days_abbr: ["zo", "ma", "di", "wo", "do", "vr", "za"], am: "a.m.", pm: "p.m.", hour12_default: false, }; // Russian - ru static RU: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM y 'г'.", long: "d MMMM y 'г'.", medium: "d MMM y 'г'.", short: "dd.MM.y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря", ], months_abbr: [ "янв.", "февр.", "мар.", "апр.", "мая", "июн.", "июл.", "авг.", "сент.", "окт.", "нояб.", "дек.", ], days_wide: [ "воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота", ], days_abbr: ["вс", "пн", "вт", "ср", "чт", "пт", "сб"], am: "AM", pm: "PM", hour12_default: false, }; // Japanese - ja static JA: LocaleData = LocaleData { date_formats: DateFormats { full: "y年M月d日EEEE", long: "y年M月d日", medium: "y/MM/dd", short: "y/MM/dd", }, time_formats: TimeFormats { full: "H時mm分ss秒 zzzz", long: "H:mm:ss z", medium: "H:mm:ss", short: "H:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ], months_abbr: [ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ], days_wide: [ "日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日", ], days_abbr: ["日", "月", "火", "水", "木", "金", "土"], am: "午前", pm: "午後", hour12_default: false, }; // Korean - ko static KO: LocaleData = LocaleData { date_formats: DateFormats { full: "y년 MMMM d일 EEEE", long: "y년 MMMM d일", medium: "y. M. d.", short: "yy. M. d.", }, time_formats: TimeFormats { full: "a h시 m분 s초 zzzz", long: "a h시 m분 s초 z", medium: "a h:mm:ss", short: "a h:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", ], months_abbr: [ "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월", ], days_wide: [ "일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", ], days_abbr: ["일", "월", "화", "수", "목", "금", "토"], am: "오전", pm: "오후", hour12_default: true, }; // Chinese (Simplified) - zh static ZH: LocaleData = LocaleData { date_formats: DateFormats { full: "y年M月d日EEEE", long: "y年M月d日", medium: "y年M月d日", short: "y/M/d", }, time_formats: TimeFormats { full: "zzzz HH:mm:ss", long: "z HH:mm:ss", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", ], months_abbr: [ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ], days_wide: [ "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", ], days_abbr: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"], am: "上午", pm: "下午", hour12_default: false, }; // Chinese (Traditional) - zh-TW static ZH_TW: LocaleData = LocaleData { date_formats: DateFormats { full: "y年M月d日 EEEE", long: "y年M月d日", medium: "y年M月d日", short: "y/M/d", }, time_formats: TimeFormats { full: "ah:mm:ss [zzzz]", long: "ah:mm:ss [z]", medium: "ah:mm:ss", short: "ah:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ], months_abbr: [ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ], days_wide: [ "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", ], days_abbr: ["週日", "週一", "週二", "週三", "週四", "週五", "週六"], am: "上午", pm: "下午", hour12_default: true, }; // Arabic - ar static AR: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE، d MMMM y", long: "d MMMM y", medium: "dd/MM/y", short: "d/M/y", }, time_formats: TimeFormats { full: "h:mm:ss a zzzz", long: "h:mm:ss a z", medium: "h:mm:ss a", short: "h:mm a", }, datetime_pattern: "{1}, {0}", months_wide: [ "يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ], months_abbr: [ "يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ], days_wide: [ "الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", ], days_abbr: [ "الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", ], am: "ص", pm: "م", hour12_default: true, }; // Hindi - hi static HI: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "d/M/yy", }, time_formats: TimeFormats { full: "h:mm:ss a zzzz", long: "h:mm:ss a z", medium: "h:mm:ss a", short: "h:mm a", }, datetime_pattern: "{1}, {0}", months_wide: [ "जनवरी", "फ़रवरी", "मार्च", "अप्रैल", "मई", "जून", "जुलाई", "अगस्त", "सितंबर", "अक्तूबर", "नवंबर", "दिसंबर", ], months_abbr: [ "जन॰", "फ़र॰", "मार्च", "अप्रैल", "मई", "जून", "जुल॰", "अग॰", "सित॰", "अक्तू॰", "नव॰", "दिस॰", ], days_wide: [ "रविवार", "सोमवार", "मंगलवार", "बुधवार", "गुरुवार", "शुक्रवार", "शनिवार", ], days_abbr: ["रवि", "सोम", "मंगल", "बुध", "गुरु", "शुक्र", "शनि"], am: "am", pm: "pm", hour12_default: true, }; // Bengali - bn static BN: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM, y", long: "d MMMM, y", medium: "d MMM, y", short: "d/M/yy", }, time_formats: TimeFormats { full: "h:mm:ss a zzzz", long: "h:mm:ss a z", medium: "h:mm:ss a", short: "h:mm a", }, datetime_pattern: "{1}, {0}", months_wide: [ "জানুয়ারী", "ফেব্রুয়ারী", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর", ], months_abbr: [ "জানু", "ফেব", "মার্চ", "এপ্রি", "মে", "জুন", "জুলাই", "আগ", "সেপ", "অক্টো", "নভে", "ডিসে", ], days_wide: [ "রবিবার", "সোমবার", "মঙ্গলবার", "বুধবার", "বৃহস্পতিবার", "শুক্রবার", "শনিবার", ], days_abbr: ["রবি", "সোম", "মঙ্গল", "বুধ", "বৃহস্পতি", "শুক্র", "শনি"], am: "AM", pm: "PM", hour12_default: true, }; // Vietnamese - vi static VI: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM, y", long: "d MMMM, y", medium: "d MMM, y", short: "dd/MM/y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "tháng 1", "tháng 2", "tháng 3", "tháng 4", "tháng 5", "tháng 6", "tháng 7", "tháng 8", "tháng 9", "tháng 10", "tháng 11", "tháng 12", ], months_abbr: [ "thg 1", "thg 2", "thg 3", "thg 4", "thg 5", "thg 6", "thg 7", "thg 8", "thg 9", "thg 10", "thg 11", "thg 12", ], days_wide: [ "Chủ Nhật", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy", ], days_abbr: ["CN", "Th 2", "Th 3", "Th 4", "Th 5", "Th 6", "Th 7"], am: "SA", pm: "CH", hour12_default: false, }; // Thai - th static TH: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEEที่ d MMMM G y", long: "d MMMM G y", medium: "d MMM y", short: "d/M/yy", }, time_formats: TimeFormats { full: "H นาฬิกา mm นาที ss วินาที zzzz", long: "H นาฬิกา mm นาที ss วินาที z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม", ], months_abbr: [ "ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค.", ], days_wide: [ "วันอาทิตย์", "วันจันทร์", "วันอังคาร", "วันพุธ", "วันพฤหัสบดี", "วันศุกร์", "วันเสาร์", ], days_abbr: ["อา.", "จ.", "อ.", "พ.", "พฤ.", "ศ.", "ส."], am: "ก่อนเที่ยง", pm: "หลังเที่ยง", hour12_default: false, }; // Indonesian - id static ID: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, dd MMMM y", long: "d MMMM y", medium: "d MMM y", short: "dd/MM/yy", }, time_formats: TimeFormats { full: "HH.mm.ss zzzz", long: "HH.mm.ss z", medium: "HH.mm.ss", short: "HH.mm", }, datetime_pattern: "{1} {0}", months_wide: [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember", ], months_abbr: [ "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des", ], days_wide: [ "Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", ], days_abbr: ["Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"], am: "AM", pm: "PM", hour12_default: false, }; // Turkish - tr static TR: LocaleData = LocaleData { date_formats: DateFormats { full: "d MMMM y EEEE", long: "d MMMM y", medium: "d MMM y", short: "d.MM.y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", ], months_abbr: [ "Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara", ], days_wide: [ "Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi", ], days_abbr: ["Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt"], am: "ÖÖ", pm: "ÖS", hour12_default: false, }; // Polish - pl static PL: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "dd.MM.y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "stycznia", "lutego", "marca", "kwietnia", "maja", "czerwca", "lipca", "sierpnia", "września", "października", "listopada", "grudnia", ], months_abbr: [ "sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru", ], days_wide: [ "niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota", ], days_abbr: ["niedz.", "pon.", "wt.", "śr.", "czw.", "pt.", "sob."], am: "AM", pm: "PM", hour12_default: false, }; // Ukrainian - uk static UK: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM y 'р'.", long: "d MMMM y 'р'.", medium: "d MMM y 'р'.", short: "dd.MM.yy", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "січня", "лютого", "березня", "квітня", "травня", "червня", "липня", "серпня", "вересня", "жовтня", "листопада", "грудня", ], months_abbr: [ "січ.", "лют.", "бер.", "квіт.", "трав.", "черв.", "лип.", "серп.", "вер.", "жовт.", "лист.", "груд.", ], days_wide: [ "неділя", "понеділок", "вівторок", "середа", "четвер", "пʼятниця", "субота", ], days_abbr: ["нд", "пн", "вт", "ср", "чт", "пт", "сб"], am: "дп", pm: "пп", hour12_default: false, }; // Swedish - sv static SV: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "y-MM-dd", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "januari", "februari", "mars", "april", "maj", "juni", "juli", "augusti", "september", "oktober", "november", "december", ], months_abbr: [ "jan.", "feb.", "mars", "apr.", "maj", "juni", "juli", "aug.", "sep.", "okt.", "nov.", "dec.", ], days_wide: [ "söndag", "måndag", "tisdag", "onsdag", "torsdag", "fredag", "lördag", ], days_abbr: ["sön", "mån", "tis", "ons", "tors", "fre", "lör"], am: "fm", pm: "em", hour12_default: false, }; // Danish - da static DA: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE 'den' d. MMMM y", long: "d. MMMM y", medium: "d. MMM y", short: "dd.MM.y", }, time_formats: TimeFormats { full: "HH.mm.ss zzzz", long: "HH.mm.ss z", medium: "HH.mm.ss", short: "HH.mm", }, datetime_pattern: "{1} 'kl'. {0}", months_wide: [ "januar", "februar", "marts", "april", "maj", "juni", "juli", "august", "september", "oktober", "november", "december", ], months_abbr: [ "jan.", "feb.", "mar.", "apr.", "maj", "jun.", "jul.", "aug.", "sep.", "okt.", "nov.", "dec.", ], days_wide: [ "søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag", ], days_abbr: ["søn.", "man.", "tir.", "ons.", "tor.", "fre.", "lør."], am: "AM", pm: "PM", hour12_default: false, }; // Norwegian Bokmål - nb static NB: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE d. MMMM y", long: "d. MMMM y", medium: "d. MMM y", short: "dd.MM.y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "januar", "februar", "mars", "april", "mai", "juni", "juli", "august", "september", "oktober", "november", "desember", ], months_abbr: [ "jan.", "feb.", "mar.", "apr.", "mai", "jun.", "jul.", "aug.", "sep.", "okt.", "nov.", "des.", ], days_wide: [ "søndag", "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag", ], days_abbr: ["søn.", "man.", "tir.", "ons.", "tor.", "fre.", "lør."], am: "a.m.", pm: "p.m.", hour12_default: false, }; // Finnish - fi static FI: LocaleData = LocaleData { date_formats: DateFormats { full: "cccc d. MMMM y", long: "d. MMMM y", medium: "d.M.y", short: "d.M.y", }, time_formats: TimeFormats { full: "H.mm.ss zzzz", long: "H.mm.ss z", medium: "H.mm.ss", short: "H.mm", }, datetime_pattern: "{1} 'klo' {0}", months_wide: [ "tammikuuta", "helmikuuta", "maaliskuuta", "huhtikuuta", "toukokuuta", "kesäkuuta", "heinäkuuta", "elokuuta", "syyskuuta", "lokakuuta", "marraskuuta", "joulukuuta", ], months_abbr: [ "tammik.", "helmik.", "maalisk.", "huhtik.", "toukok.", "kesäk.", "heinäk.", "elok.", "syysk.", "lokak.", "marrask.", "jouluk.", ], days_wide: [ "sunnuntaina", "maanantaina", "tiistaina", "keskiviikkona", "torstaina", "perjantaina", "lauantaina", ], days_abbr: ["su", "ma", "ti", "ke", "to", "pe", "la"], am: "ap.", pm: "ip.", hour12_default: false, }; // Czech - cs static CS: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE d. MMMM y", long: "d. MMMM y", medium: "d. M. y", short: "dd.MM.yy", }, time_formats: TimeFormats { full: "H:mm:ss zzzz", long: "H:mm:ss z", medium: "H:mm:ss", short: "H:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "ledna", "února", "března", "dubna", "května", "června", "července", "srpna", "září", "října", "listopadu", "prosince", ], months_abbr: [ "led", "úno", "bře", "dub", "kvě", "čvn", "čvc", "srp", "zář", "říj", "lis", "pro", ], days_wide: [ "neděle", "pondělí", "úterý", "středa", "čtvrtek", "pátek", "sobota", ], days_abbr: ["ne", "po", "út", "st", "čt", "pá", "so"], am: "dop.", pm: "odp.", hour12_default: false, }; // Greek - el static EL: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "d/M/yy", }, time_formats: TimeFormats { full: "h:mm:ss a zzzz", long: "h:mm:ss a z", medium: "h:mm:ss a", short: "h:mm a", }, datetime_pattern: "{1}, {0}", months_wide: [ "Ιανουαρίου", "Φεβρουαρίου", "Μαρτίου", "Απριλίου", "Μαΐου", "Ιουνίου", "Ιουλίου", "Αυγούστου", "Σεπτεμβρίου", "Οκτωβρίου", "Νοεμβρίου", "Δεκεμβρίου", ], months_abbr: [ "Ιαν", "Φεβ", "Μαρ", "Απρ", "Μαΐ", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ", ], days_wide: [ "Κυριακή", "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη", "Παρασκευή", "Σάββατο", ], days_abbr: ["Κυρ", "Δευ", "Τρί", "Τετ", "Πέμ", "Παρ", "Σάβ"], am: "π.μ.", pm: "μ.μ.", hour12_default: true, }; // Hebrew - he static HE: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d בMMMM y", long: "d בMMMM y", medium: "d בMMM y", short: "d.M.y", }, time_formats: TimeFormats { full: "H:mm:ss zzzz", long: "H:mm:ss z", medium: "H:mm:ss", short: "H:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר", ], months_abbr: [ "ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳", ], days_wide: [ "יום ראשון", "יום שני", "יום שלישי", "יום רביעי", "יום חמישי", "יום שישי", "יום שבת", ], days_abbr: [ "יום א׳", "יום ב׳", "יום ג׳", "יום ד׳", "יום ה׳", "יום ו׳", "שבת", ], am: "לפנה״צ", pm: "אחה״צ", hour12_default: false, }; // Hungarian - hu static HU: LocaleData = LocaleData { date_formats: DateFormats { full: "y. MMMM d., EEEE", long: "y. MMMM d.", medium: "y. MMM d.", short: "y. MM. dd.", }, time_formats: TimeFormats { full: "H:mm:ss zzzz", long: "H:mm:ss z", medium: "H:mm:ss", short: "H:mm", }, datetime_pattern: "{1} {0}", months_wide: [ "január", "február", "március", "április", "május", "június", "július", "augusztus", "szeptember", "október", "november", "december", ], months_abbr: [ "jan.", "febr.", "márc.", "ápr.", "máj.", "jún.", "júl.", "aug.", "szept.", "okt.", "nov.", "dec.", ], days_wide: [ "vasárnap", "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat", ], days_abbr: ["V", "H", "K", "Sze", "Cs", "P", "Szo"], am: "de.", pm: "du.", hour12_default: false, }; // Romanian - ro static RO: LocaleData = LocaleData { date_formats: DateFormats { full: "EEEE, d MMMM y", long: "d MMMM y", medium: "d MMM y", short: "dd.MM.y", }, time_formats: TimeFormats { full: "HH:mm:ss zzzz", long: "HH:mm:ss z", medium: "HH:mm:ss", short: "HH:mm", }, datetime_pattern: "{1}, {0}", months_wide: [ "ianuarie", "februarie", "martie", "aprilie", "mai", "iunie", "iulie", "august", "septembrie", "octombrie", "noiembrie", "decembrie", ], months_abbr: [ "ian.", "feb.", "mar.", "apr.", "mai", "iun.", "iul.", "aug.", "sept.", "oct.", "nov.", "dec.", ], days_wide: [ "duminică", "luni", "marți", "miercuri", "joi", "vineri", "sâmbătă", ], days_abbr: ["dum.", "lun.", "mar.", "mie.", "joi", "vin.", "sâm."], am: "a.m.", pm: "p.m.", hour12_default: false, }; #[cfg(test)] mod tests { use super::*; #[test] fn test_get_locale_data_exact_match() { let data = get_locale_data("en-US"); assert_eq!(data.date_formats.short, "M/d/yy"); assert!(data.hour12_default); let data = get_locale_data("de-DE"); assert_eq!(data.date_formats.short, "dd.MM.yy"); assert!(!data.hour12_default); } #[test] fn test_get_locale_data_language_fallback() { let data = get_locale_data("de"); assert_eq!(data.date_formats.short, "dd.MM.yy"); let data = get_locale_data("fr"); assert_eq!(data.date_formats.short, "dd/MM/y"); } #[test] fn test_get_locale_data_unknown_fallback() { let data = get_locale_data("xx-YY"); // Should fall back to en-US assert_eq!(data.date_formats.short, "M/d/yy"); } #[test] fn test_get_locale_data_case_insensitive() { let data1 = get_locale_data("en-US"); let data2 = get_locale_data("EN-US"); let data3 = get_locale_data("En-Us"); assert_eq!(data1.date_formats.short, data2.date_formats.short); assert_eq!(data2.date_formats.short, data3.date_formats.short); } #[test] fn test_get_locale_data_underscore() { let data = get_locale_data("en_US"); assert_eq!(data.date_formats.short, "M/d/yy"); } #[test] fn test_months_count() { let data = get_locale_data("en-US"); assert_eq!(data.months_wide.len(), 12); assert_eq!(data.months_abbr.len(), 12); } #[test] fn test_days_count() { let data = get_locale_data("en-US"); assert_eq!(data.days_wide.len(), 7); assert_eq!(data.days_abbr.len(), 7); } } ================================================ FILE: modules/llrt_intl/src/date_time_format.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! Minimal Intl.DateTimeFormat implementation for timezone support. //! This provides just enough functionality to support dayjs and similar libraries. use jiff::{tz::TimeZone, Timestamp, Zoned}; use rquickjs::{ atom::PredefinedAtom, prelude::Opt, Array, Coerced, Ctx, Exception, Object, Result, Value, }; /// Stores the resolved options for a DateTimeFormat instance #[derive(Clone, Debug)] pub struct DateTimeFormatOptions { pub locale: String, pub timezone: TimeZone, pub hour12: bool, pub year: Option, pub month: Option, pub day: Option, pub hour: Option, pub minute: Option, pub second: Option, pub weekday: Option, pub timezone_name: Option, pub fractional_second_digits: Option, } impl Default for DateTimeFormatOptions { fn default() -> Self { Self { locale: "en-US".to_string(), timezone: TimeZone::UTC, hour12: false, year: None, month: None, day: None, hour: None, minute: None, second: None, weekday: None, timezone_name: None, fractional_second_digits: None, } } } /// A formatted part with type and value #[derive(Debug, Clone)] pub struct FormatPart { pub part_type: &'static str, pub value: String, } impl FormatPart { #[inline] fn new(part_type: &'static str, value: String) -> Self { Self { part_type, value } } #[inline] fn literal(value: &'static str) -> Self { Self { part_type: "literal", value: value.to_string(), } } } /// Format a number with optional zero-padding #[inline] fn format_number(value: i16, two_digit: bool) -> String { let mut buf = itoa::Buffer::new(); if two_digit && value < 10 { let mut result = String::with_capacity(2); result.push('0'); result.push_str(buf.format(value)); result } else { buf.format(value).to_string() } } /// Format a number component based on option style #[inline] fn format_component(value: i16, style: Option<&str>) -> String { let two_digit = matches!(style, Some("2-digit")); format_number(value, two_digit) } /// Build format parts from a Zoned datetime in pure Rust fn build_format_parts(local_dt: &Zoned, options: &DateTimeFormatOptions) -> Vec { let mut parts = Vec::with_capacity(16); // Month if let Some(ref month_opt) = options.month { parts.push(FormatPart::new( "month", format_component(local_dt.month().into(), Some(month_opt)), )); parts.push(FormatPart::literal("/")); } // Day if let Some(ref day_opt) = options.day { parts.push(FormatPart::new( "day", format_component(local_dt.day().into(), Some(day_opt)), )); parts.push(FormatPart::literal("/")); } // Year if let Some(ref year_opt) = options.year { let year_val = if year_opt == "2-digit" { format_number(local_dt.year() % 100, true) } else { let mut buf = itoa::Buffer::new(); buf.format(local_dt.year()).to_string() }; parts.push(FormatPart::new("year", year_val)); } // Add separator between date and time if we have both let has_date = options.year.is_some() || options.month.is_some() || options.day.is_some(); let has_time = options.hour.is_some() || options.minute.is_some() || options.second.is_some(); if has_date && has_time { parts.push(FormatPart::literal(", ")); } // Hour if options.hour.is_some() { let hour = local_dt.hour(); let hour_val = if options.hour12 { match hour { 0 => 12, 13..=23 => hour - 12, _ => hour, } } else { hour }; parts.push(FormatPart::new( "hour", format_component(hour_val.into(), options.hour.as_deref()), )); if options.minute.is_some() || options.second.is_some() { parts.push(FormatPart::literal(":")); } } // Minute if options.minute.is_some() { parts.push(FormatPart::new( "minute", format_component(local_dt.minute().into(), options.minute.as_deref()), )); if options.second.is_some() { parts.push(FormatPart::literal(":")); } } // Second if options.second.is_some() { parts.push(FormatPart::new( "second", format_component(local_dt.second().into(), options.second.as_deref()), )); } // dayPeriod for 12-hour format if options.hour12 && options.hour.is_some() { let hour = local_dt.hour(); parts.push(FormatPart::literal(" ")); parts.push(FormatPart::new( "dayPeriod", if hour >= 12 { "PM" } else { "AM" }.to_string(), )); } // Timezone name if let Some(ref tz_name_opt) = options.timezone_name { parts.push(FormatPart::literal(" ")); let tz_str = format_timezone_name(local_dt, &options.timezone, tz_name_opt); parts.push(FormatPart::new("timeZoneName", tz_str)); } parts } /// Format timezone name based on style option fn format_timezone_name(local_dt: &Zoned, timezone: &TimeZone, style: &str) -> String { match style { "short" | "shortOffset" => { let offset = local_dt.offset(); let total_secs = offset.seconds(); let hours = total_secs / 3600; let mins = (total_secs % 3600).abs() / 60; let mut result = String::with_capacity(10); result.push_str("GMT"); if hours >= 0 { result.push('+'); } let mut buf = itoa::Buffer::new(); result.push_str(buf.format(hours)); if mins != 0 && style == "shortOffset" { result.push(':'); if mins < 10 { result.push('0'); } result.push_str(buf.format(mins)); } result }, "long" => timezone.iana_name().unwrap_or_default().into(), _ => timezone.iana_name().unwrap_or_default().into(), } } /// Convert format parts Vec to JS Array fn parts_to_js_array<'js>(ctx: &Ctx<'js>, parts: Vec) -> Result> { let array = Array::new(ctx.clone())?; for (idx, part) in parts.into_iter().enumerate() { let obj = Object::new(ctx.clone())?; obj.set("type", part.part_type)?; obj.set("value", part.value)?; array.set(idx, obj)?; } Ok(array) } /// Join format parts into a single string fn parts_to_string(parts: &[FormatPart]) -> String { let total_len: usize = parts.iter().map(|p| p.value.len()).sum(); let mut result = String::with_capacity(total_len); for part in parts { result.push_str(&part.value); } result } /// Parse epoch milliseconds from a Date value fn parse_epoch_ms<'js>(ctx: &Ctx<'js>, date: Opt>) -> Result { if let Some(date_val) = date.into_inner() { if date_val.is_undefined() { Ok(Timestamp::now().as_millisecond() as f64) } else if let Some(num) = date_val.as_number() { Ok(num) } else { date_val .get::>() .map(|c| c.0) .map_err(|_| Exception::throw_type(ctx, "Invalid date")) } } else { Ok(Timestamp::now().as_millisecond() as f64) } } /// Convert epoch milliseconds to Zoned datetime in the specified timezone fn epoch_to_datetime(ctx: &Ctx<'_>, epoch_ms: f64, timezone: &TimeZone) -> Result { let utc_dt = Timestamp::from_millisecond(epoch_ms as i64) .map_err(|_| Exception::throw_range(ctx, "Invalid timestamp"))?; Ok(utc_dt.to_zoned(timezone.clone())) } /// Minimal Intl.DateTimeFormat implementation #[derive(Clone, rquickjs::class::Trace, rquickjs::JsLifetime)] #[rquickjs::class] pub struct DateTimeFormat { #[qjs(skip_trace)] options: DateTimeFormatOptions, } #[rquickjs::methods(rename_all = "camelCase")] impl DateTimeFormat { #[qjs(constructor)] pub fn new(ctx: Ctx<'_>, locales: Opt>, options: Opt>) -> Result { let mut opts = DateTimeFormatOptions::default(); // Parse locale (we only care about extracting the language tag) if let Some(locale_val) = locales.into_inner() { if let Some(s) = locale_val.as_string() { opts.locale = s.to_string()?; } else if let Some(arr) = locale_val.as_array() { if let Ok(first) = arr.get::(0) { if let Some(s) = first.as_string() { opts.locale = s.to_string()?; } } } } // Parse options if let Some(options_obj) = options.into_inner() { // timeZone if let Ok(tz_val) = options_obj.get::<_, String>("timeZone") { opts.timezone = TimeZone::get(&tz_val).map_err(|_| { Exception::throw_range(&ctx, &["Invalid time zone: ", &tz_val].concat()) })?; } // hour12 if let Ok(h12) = options_obj.get::<_, bool>("hour12") { opts.hour12 = h12; } // Date/time components if let Ok(v) = options_obj.get::<_, String>("year") { opts.year = Some(v); } if let Ok(v) = options_obj.get::<_, String>("month") { opts.month = Some(v); } if let Ok(v) = options_obj.get::<_, String>("day") { opts.day = Some(v); } if let Ok(v) = options_obj.get::<_, String>("hour") { opts.hour = Some(v); } if let Ok(v) = options_obj.get::<_, String>("minute") { opts.minute = Some(v); } if let Ok(v) = options_obj.get::<_, String>("second") { opts.second = Some(v); } if let Ok(v) = options_obj.get::<_, String>("weekday") { opts.weekday = Some(v); } if let Ok(v) = options_obj.get::<_, String>("timeZoneName") { opts.timezone_name = Some(v); } if let Ok(v) = options_obj.get::<_, u8>("fractionalSecondDigits") { opts.fractional_second_digits = Some(v); } } Ok(Self { options: opts }) } /// Format a date according to the locale and options pub fn format<'js>(&self, ctx: Ctx<'js>, date: Opt>) -> Result { let epoch_ms = parse_epoch_ms(&ctx, date)?; let local_dt = epoch_to_datetime(&ctx, epoch_ms, &self.options.timezone)?; let parts = build_format_parts(&local_dt, &self.options); Ok(parts_to_string(&parts)) } /// Format a date to parts #[qjs(rename = "formatToParts")] pub fn format_to_parts<'js>(&self, ctx: Ctx<'js>, date: Opt>) -> Result> { let epoch_ms = parse_epoch_ms(&ctx, date)?; let local_dt = epoch_to_datetime(&ctx, epoch_ms, &self.options.timezone)?; let parts = build_format_parts(&local_dt, &self.options); parts_to_js_array(&ctx, parts) } /// Return resolved options #[qjs(rename = "resolvedOptions")] pub fn resolved_options<'js>(&self, ctx: Ctx<'js>) -> Result> { let obj = Object::new(ctx.clone())?; obj.set("locale", self.options.locale.as_str())?; obj.set("calendar", "gregory")?; obj.set("numberingSystem", "latn")?; obj.set( "timeZone", self.options.timezone.iana_name().unwrap_or_default(), )?; if self.options.hour.is_some() { obj.set("hour12", self.options.hour12)?; obj.set("hourCycle", if self.options.hour12 { "h12" } else { "h23" })?; } if let Some(ref v) = self.options.year { obj.set("year", v.as_str())?; } if let Some(ref v) = self.options.month { obj.set("month", v.as_str())?; } if let Some(ref v) = self.options.day { obj.set("day", v.as_str())?; } if let Some(ref v) = self.options.hour { obj.set("hour", v.as_str())?; } if let Some(ref v) = self.options.minute { obj.set("minute", v.as_str())?; } if let Some(ref v) = self.options.second { obj.set("second", v.as_str())?; } if let Some(ref v) = self.options.weekday { obj.set("weekday", v.as_str())?; } if let Some(ref v) = self.options.timezone_name { obj.set("timeZoneName", v.as_str())?; } Ok(obj) } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] pub fn to_string_tag(&self) -> &'static str { "Intl.DateTimeFormat" } } /// Format a date in the specified timezone using locale options. /// This is used to implement Date.prototype.toLocaleString with timezone support. pub fn format_date_in_timezone( epoch_ms: f64, timezone: &TimeZone, options: &ToLocaleStringOptions, ) -> String { let utc_dt = Timestamp::from_millisecond(epoch_ms as i64).unwrap(); let local_dt = utc_dt.to_zoned(timezone.clone()); // Format as MM/DD/YYYY, HH:MM:SS AM/PM (en-US style) let month = local_dt.month(); let day = local_dt.day(); let year = local_dt.year(); let hour = local_dt.hour(); let minute = local_dt.minute(); let second = local_dt.second(); let mut buf = itoa::Buffer::new(); let mut result = String::with_capacity(24); // Month (zero-padded) if month < 10 { result.push('0'); } result.push_str(buf.format(month)); result.push('/'); // Day (zero-padded) if day < 10 { result.push('0'); } result.push_str(buf.format(day)); result.push('/'); // Year result.push_str(buf.format(year)); result.push_str(", "); if options.hour12 { let (hour12, period) = match hour { 0 => (12, "AM"), 1..=11 => (hour, "AM"), 12 => (12, "PM"), _ => (hour - 12, "PM"), }; // Hour (no padding for 12-hour format) result.push_str(buf.format(hour12)); result.push(':'); // Minute (zero-padded) if minute < 10 { result.push('0'); } result.push_str(buf.format(minute)); result.push(':'); // Second (zero-padded) if second < 10 { result.push('0'); } result.push_str(buf.format(second)); result.push(' '); result.push_str(period); } else { // Hour (zero-padded) if hour < 10 { result.push('0'); } result.push_str(buf.format(hour)); result.push(':'); // Minute (zero-padded) if minute < 10 { result.push('0'); } result.push_str(buf.format(minute)); result.push(':'); // Second (zero-padded) if second < 10 { result.push('0'); } result.push_str(buf.format(second)); } result } /// Options for toLocaleString #[derive(Default)] pub struct ToLocaleStringOptions { pub hour12: bool, pub hour12_set: bool, pub date_style: Option, pub time_style: Option, } /// Parse toLocaleString options from a JavaScript object pub fn parse_to_locale_string_options<'js>( ctx: &Ctx<'js>, options: Option>, ) -> Result<(Option, ToLocaleStringOptions)> { let mut tz: Option = None; let mut opts = ToLocaleStringOptions::default(); if let Some(options_obj) = options { // Parse timeZone if let Ok(tz_val) = options_obj.get::<_, String>("timeZone") { tz = Some(TimeZone::get(&tz_val).map_err(|_| { Exception::throw_range(ctx, &["Invalid time zone: ", &tz_val].concat()) })?); } // Parse hour12 if let Ok(h12) = options_obj.get::<_, bool>("hour12") { opts.hour12 = h12; opts.hour12_set = true; } // Parse dateStyle if let Ok(ds) = options_obj.get::<_, String>("dateStyle") { opts.date_style = Some(ds); } // Parse timeStyle if let Ok(ts) = options_obj.get::<_, String>("timeStyle") { opts.time_style = Some(ts); } } Ok((tz, opts)) } #[cfg(test)] mod tests { use super::*; use jiff::tz::TimeZone; #[test] fn test_format_number() { assert_eq!(format_number(5, false), "5"); assert_eq!(format_number(5, true), "05"); assert_eq!(format_number(12, true), "12"); assert_eq!(format_number(0, true), "00"); } #[test] fn test_format_component() { assert_eq!(format_component(5, Some("2-digit")), "05"); assert_eq!(format_component(5, Some("numeric")), "5"); assert_eq!(format_component(12, Some("2-digit")), "12"); assert_eq!(format_component(12, None), "12"); } #[test] fn test_build_format_parts_date_only() { let tz = TimeZone::get("UTC").unwrap(); // 2024-03-15 10:30:45 UTC let epoch_ms = 1710499845000.0; let utc_dt = Timestamp::from_millisecond(epoch_ms as i64).unwrap(); let local_dt = utc_dt.to_zoned(tz); let options = DateTimeFormatOptions { year: Some("numeric".to_string()), month: Some("2-digit".to_string()), day: Some("2-digit".to_string()), ..Default::default() }; let parts = build_format_parts(&local_dt, &options); assert_eq!(parts.len(), 5); // month, /, day, /, year assert_eq!(parts[0].part_type, "month"); assert_eq!(parts[0].value, "03"); assert_eq!(parts[1].part_type, "literal"); assert_eq!(parts[1].value, "/"); assert_eq!(parts[2].part_type, "day"); assert_eq!(parts[2].value, "15"); assert_eq!(parts[4].part_type, "year"); assert_eq!(parts[4].value, "2024"); } #[test] fn test_build_format_parts_time_only() { let tz = TimeZone::get("UTC").unwrap(); // 2024-03-15 10:30:45 UTC let epoch_ms = 1710498645000.0; let utc_dt = Timestamp::from_millisecond(epoch_ms as i64).unwrap(); let local_dt = utc_dt.to_zoned(tz); let options = DateTimeFormatOptions { hour: Some("2-digit".to_string()), minute: Some("2-digit".to_string()), second: Some("2-digit".to_string()), ..Default::default() }; let parts = build_format_parts(&local_dt, &options); assert_eq!(parts[0].part_type, "hour"); assert_eq!(parts[0].value, "10"); assert_eq!(parts[2].part_type, "minute"); assert_eq!(parts[2].value, "30"); assert_eq!(parts[4].part_type, "second"); assert_eq!(parts[4].value, "45"); } #[test] fn test_build_format_parts_12hour() { let tz = TimeZone::get("UTC").unwrap(); // 2024-03-15 14:30:00 UTC (2 PM) let epoch_ms = 1710514200000.0; let utc_dt = Timestamp::from_millisecond(epoch_ms as i64).unwrap(); let local_dt = utc_dt.to_zoned(tz); let options = DateTimeFormatOptions { hour: Some("numeric".to_string()), hour12: true, ..Default::default() }; let parts = build_format_parts(&local_dt, &options); assert_eq!(parts[0].part_type, "hour"); assert_eq!(parts[0].value, "2"); // 14:00 -> 2 PM assert_eq!(parts[2].part_type, "dayPeriod"); assert_eq!(parts[2].value, "PM"); } #[test] fn test_build_format_parts_midnight_12hour() { let tz = TimeZone::get("UTC").unwrap(); // 2024-03-15 00:30:00 UTC (12:30 AM) let epoch_ms = 1710463800000.0; let utc_dt = Timestamp::from_millisecond(epoch_ms as i64).unwrap(); let local_dt = utc_dt.to_zoned(tz); let options = DateTimeFormatOptions { hour: Some("numeric".to_string()), hour12: true, ..Default::default() }; let parts = build_format_parts(&local_dt, &options); assert_eq!(parts[0].part_type, "hour"); assert_eq!(parts[0].value, "12"); // 00:00 -> 12 AM assert_eq!(parts[2].part_type, "dayPeriod"); assert_eq!(parts[2].value, "AM"); } #[test] fn test_format_timezone_name_short() { let tz = TimeZone::get("America/New_York").unwrap(); // Summer time (EDT = UTC-4) let epoch_ms = 1720000000000.0; // July 2024 let utc_dt = Timestamp::from_millisecond(epoch_ms as i64).unwrap(); let local_dt = utc_dt.to_zoned(tz.clone()); let result = format_timezone_name(&local_dt, &tz, "short"); assert_eq!(result, "GMT-4"); } #[test] fn test_format_timezone_name_long() { let tz = TimeZone::get("America/New_York").unwrap(); let epoch_ms = 1720000000000.0; let utc_dt = Timestamp::from_millisecond(epoch_ms as i64).unwrap(); let local_dt = utc_dt.to_zoned(tz.clone()); let result = format_timezone_name(&local_dt, &tz, "long"); assert_eq!(result, "America/New_York"); } #[test] fn test_parts_to_string() { let parts = vec![ FormatPart::new("month", "03".to_string()), FormatPart::literal("/"), FormatPart::new("day", "15".to_string()), FormatPart::literal("/"), FormatPart::new("year", "2024".to_string()), ]; assert_eq!(parts_to_string(&parts), "03/15/2024"); } #[test] fn test_format_date_in_timezone() { // 2024-03-15 14:30:45 UTC let epoch_ms = 1710513045000.0; let tz = TimeZone::get("UTC").unwrap(); let opts = ToLocaleStringOptions { hour12: true, ..Default::default() }; let result = format_date_in_timezone(epoch_ms, &tz, &opts); assert_eq!(result, "03/15/2024, 2:30:45 PM"); let opts = ToLocaleStringOptions { hour12: false, ..Default::default() }; let result = format_date_in_timezone(epoch_ms, &tz, &opts); assert_eq!(result, "03/15/2024, 14:30:45"); } #[test] fn test_format_date_in_timezone_with_tz() { // 2024-03-15 14:30:45 UTC -> 10:30:45 AM EDT (UTC-4) let epoch_ms = 1710513045000.0; let tz = TimeZone::get("America/New_York").unwrap(); let opts = ToLocaleStringOptions { hour12: true, ..Default::default() }; let result = format_date_in_timezone(epoch_ms, &tz, &opts); assert_eq!(result, "03/15/2024, 10:30:45 AM"); } #[test] fn test_format_date_midnight() { // 2024-03-15 00:00:00 UTC let epoch_ms = 1710460800000.0; let tz = TimeZone::get("UTC").unwrap(); let opts = ToLocaleStringOptions { hour12: true, ..Default::default() }; let result = format_date_in_timezone(epoch_ms, &tz, &opts); assert_eq!(result, "03/15/2024, 12:00:00 AM"); let opts = ToLocaleStringOptions { hour12: false, ..Default::default() }; let result = format_date_in_timezone(epoch_ms, &tz, &opts); assert_eq!(result, "03/15/2024, 00:00:00"); } #[test] fn test_format_date_noon() { // 2024-03-15 12:00:00 UTC let epoch_ms = 1710504000000.0; let tz = TimeZone::get("UTC").unwrap(); let opts = ToLocaleStringOptions { hour12: true, ..Default::default() }; let result = format_date_in_timezone(epoch_ms, &tz, &opts); assert_eq!(result, "03/15/2024, 12:00:00 PM"); } } ================================================ FILE: modules/llrt_intl/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! Minimal Intl module for LLRT. //! //! Provides a subset of the `Intl` API focused on timezone support, //! enabling compatibility with libraries like dayjs. mod cldr_data; mod date_time_format; mod pattern_formatter; pub use date_time_format::{ format_date_in_timezone, parse_to_locale_string_options, DateTimeFormat, DateTimeFormatOptions, ToLocaleStringOptions, }; use cldr_data::get_locale_data; use jiff::{ tz::{TimeZone, TimeZoneDatabase}, Timestamp, }; use pattern_formatter::{combine_datetime, format_with_pattern}; use rquickjs::{ function::{Constructor, Opt, This}, prelude::Func, Array, Class, Coerced, Ctx, Exception, Object, Result, Value, }; /// Initialize the Intl global object with DateTimeFormat pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); // Create Intl object let intl = Object::new(ctx.clone())?; intl.set("supportedValuesOf", Func::from(supported_values_of))?; // Add DateTimeFormat constructor Class::::define(&intl)?; // Set Intl global globals.set("Intl", intl)?; // Patch Date.prototype.toLocaleString to support timeZone option patch_date_to_locale_string(ctx)?; Ok(()) } /// Patch Date.prototype.toLocaleString to support the timeZone option fn patch_date_to_locale_string(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); let date_ctor: Constructor = globals.get("Date")?; let date_proto: Object = date_ctor.get("prototype")?; // Replace toLocaleString with our implementation date_proto.set("toLocaleString", Func::from(date_to_locale_string))?; Ok(()) } /// Custom Date.prototype.toLocaleString implementation with timezone and locale support fn date_to_locale_string<'js>( ctx: Ctx<'js>, this: This>, locale: Opt>, options: Opt>, ) -> Result { // Coerce Date to number (uses valueOf internally, same as getTime) let epoch_ms = this .0 .get::>() .map(|c| c.0) .map_err(|_| Exception::throw_type(&ctx, "this is not a Date object"))?; // Check for NaN (Invalid Date) if epoch_ms.is_nan() { return Ok("Invalid Date".to_string()); } // Parse locale let locale_str = parse_locale_arg(locale)?; let locale_data = get_locale_data(&locale_str); // Parse options let (tz, opts) = parse_to_locale_string_options(&ctx, options.0)?; let timezone = tz.unwrap_or(TimeZone::system()); // Convert epoch to DateTime let utc_dt = Timestamp::from_millisecond(epoch_ms as i64) .map_err(|_| Exception::throw_range(&ctx, "Invalid timestamp"))?; let local_dt = utc_dt.to_zoned(timezone); // Determine hour12 setting - use locale default if not explicitly set let hour12 = if opts.hour12_set { Some(opts.hour12) } else { Some(locale_data.hour12_default) }; // Format using CLDR patterns based on dateStyle/timeStyle let (date_style, time_style) = (opts.date_style.as_deref(), opts.time_style.as_deref()); match (date_style, time_style) { (Some(ds), Some(ts)) => { // Both date and time let date_pattern = get_date_pattern(ds, locale_data); let time_pattern = get_time_pattern(ts, locale_data); let date_str = format_with_pattern(&local_dt, date_pattern, locale_data, hour12); let time_str = format_with_pattern(&local_dt, time_pattern, locale_data, hour12); Ok(combine_datetime( &date_str, &time_str, locale_data.datetime_pattern, )) }, (Some(ds), None) => { // Date only let date_pattern = get_date_pattern(ds, locale_data); Ok(format_with_pattern( &local_dt, date_pattern, locale_data, hour12, )) }, (None, Some(ts)) => { // Time only let time_pattern = get_time_pattern(ts, locale_data); Ok(format_with_pattern( &local_dt, time_pattern, locale_data, hour12, )) }, (None, None) => { // Default: short date and medium time (matching browser behavior) let date_str = format_with_pattern( &local_dt, locale_data.date_formats.short, locale_data, hour12, ); let time_str = format_with_pattern( &local_dt, locale_data.time_formats.medium, locale_data, hour12, ); Ok(combine_datetime( &date_str, &time_str, locale_data.datetime_pattern, )) }, } } /// Parse locale argument from JavaScript fn parse_locale_arg(locale: Opt>) -> Result { if let Some(val) = locale.into_inner() { if val.is_undefined() || val.is_null() { return Ok("en-US".to_string()); } if let Some(s) = val.as_string() { return s.to_string(); } if let Some(arr) = val.as_array() { if let Ok(first) = arr.get::(0) { if let Some(s) = first.as_string() { return s.to_string(); } } } } Ok("en-US".to_string()) } /// Get date pattern for a given style fn get_date_pattern<'a>(style: &str, locale_data: &'a cldr_data::LocaleData) -> &'a str { match style { "full" => locale_data.date_formats.full, "long" => locale_data.date_formats.long, "medium" => locale_data.date_formats.medium, "short" => locale_data.date_formats.short, _ => locale_data.date_formats.medium, } } /// Get time pattern for a given style fn get_time_pattern<'a>(style: &str, locale_data: &'a cldr_data::LocaleData) -> &'a str { match style { "full" => locale_data.time_formats.full, "long" => locale_data.time_formats.long, "medium" => locale_data.time_formats.medium, "short" => locale_data.time_formats.short, _ => locale_data.time_formats.medium, } } fn supported_values_of<'js>(ctx: Ctx<'js>, key: String) -> Result> { let array = Array::new(ctx.clone())?; match key.as_ref() { "timeZone" => { let timezones = TimeZoneDatabase::from_env(); for (i, tz) in timezones.available().enumerate() { array.set(i, tz.as_str())?; } }, _ => { return Err(Exception::throw_range( &ctx, "Unknown key for Intl.supportedValuesOf", )) }, } Ok(array) } ================================================ FILE: modules/llrt_intl/src/pattern_formatter.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 //! CLDR pattern parser and formatter. //! //! Parses Unicode CLDR date/time patterns and formats dates accordingly. //! Pattern syntax follows Unicode Technical Standard #35: //! https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table use crate::cldr_data::LocaleData; use jiff::Zoned; /// Format a Zoned datetime using a CLDR pattern string pub fn format_with_pattern( dt: &Zoned, pattern: &str, locale_data: &LocaleData, hour12_override: Option, ) -> String { let mut result = String::with_capacity(pattern.len() * 2); let mut chars = pattern.chars().peekable(); while let Some(ch) = chars.next() { match ch { // Quoted literal text '\'' => { // Check for escaped quote ('') if chars.peek() == Some(&'\'') { chars.next(); result.push('\''); } else { // Collect until closing quote for c in chars.by_ref() { if c == '\'' { break; } result.push(c); } } }, // Pattern letters 'y' | 'Y' => { let count = 1 + consume_same(&mut chars, ch); format_year(&mut result, dt.year(), count); }, 'M' | 'L' => { let count = 1 + consume_same(&mut chars, ch); format_month(&mut result, dt.month(), count, locale_data); }, 'd' => { let count = 1 + consume_same(&mut chars, ch); format_number(&mut result, dt.day(), count); }, 'E' | 'e' | 'c' => { let count = 1 + consume_same(&mut chars, ch); format_weekday( &mut result, dt.weekday().to_sunday_zero_offset() as usize, count, locale_data, ); }, 'a' => { consume_same(&mut chars, ch); // Only show AM/PM if we're using 12-hour format let use_12h = hour12_override.unwrap_or(true); if use_12h { let hour = dt.hour(); if hour < 12 { result.push_str(locale_data.am); } else { result.push_str(locale_data.pm); } } }, 'h' => { let count = 1 + consume_same(&mut chars, ch); let hour = dt.hour(); // If hour12 is explicitly false, use 24-hour format instead let use_12h = hour12_override.unwrap_or(true); if use_12h { // 12-hour format (1-12) let hour12 = match hour { 0 => 12, 1..=12 => hour, _ => hour - 12, }; format_number(&mut result, hour12, count); } else { // 24-hour format (0-23) format_number(&mut result, hour, count); } }, 'H' => { let count = 1 + consume_same(&mut chars, ch); let hour = dt.hour(); // If hour12 is explicitly true, use 12-hour format instead let use_12h = hour12_override.unwrap_or(false); if use_12h { let hour12 = match hour { 0 => 12, 1..=12 => hour, _ => hour - 12, }; format_number(&mut result, hour12, count); } else { // 24-hour format (0-23) format_number(&mut result, hour, count); } }, 'm' => { let count = 1 + consume_same(&mut chars, ch); format_number(&mut result, dt.minute(), count); }, 's' => { let count = 1 + consume_same(&mut chars, ch); format_number(&mut result, dt.second(), count); }, 'z' => { let count = 1 + consume_same(&mut chars, ch); format_timezone(&mut result, dt, count); }, 'Z' | 'O' | 'v' | 'V' | 'X' | 'x' => { let count = 1 + consume_same(&mut chars, ch); format_timezone(&mut result, dt, count); }, // Skip these pattern letters (not commonly needed) 'G' | 'q' | 'Q' | 'w' | 'W' | 'D' | 'F' | 'g' | 'A' | 'S' => { consume_same(&mut chars, ch); }, // Pass through literal characters _ => { result.push(ch); }, } } // If hour12=true and pattern originally used 24h format (H) without AM/PM marker, // we need to append AM/PM since we converted to 12h format if let Some(true) = hour12_override { if !pattern.contains('a') && pattern.contains('H') { result.push(' '); if dt.hour() < 12 { result.push_str(locale_data.am); } else { result.push_str(locale_data.pm); } } } result } /// Consume consecutive identical characters, returning count of additional chars fn consume_same(chars: &mut std::iter::Peekable, ch: char) -> usize { let mut count = 0; while chars.peek() == Some(&ch) { chars.next(); count += 1; } count } /// Format year based on pattern width fn format_year(result: &mut String, year: i16, width: usize) { let mut buf = itoa::Buffer::new(); if width == 2 { // 2-digit year let short_year = (year % 100).unsigned_abs(); if short_year < 10 { result.push('0'); } result.push_str(buf.format(short_year)); } else { result.push_str(buf.format(year)); } } /// Format month based on pattern width fn format_month(result: &mut String, month: i8, width: usize, locale_data: &LocaleData) { match width { 1 => { // Numeric, no padding let mut buf = itoa::Buffer::new(); result.push_str(buf.format(month)); }, 2 => { // Numeric, zero-padded let mut buf = itoa::Buffer::new(); if month < 10 { result.push('0'); } result.push_str(buf.format(month)); }, 3 => { // Abbreviated if (1..=12).contains(&month) { result.push_str(locale_data.months_abbr[month as usize - 1]); } }, _ => { // Wide (4+) if (1..=12).contains(&month) { result.push_str(locale_data.months_wide[month as usize - 1]); } }, } } /// Format weekday based on pattern width fn format_weekday(result: &mut String, weekday: usize, width: usize, locale_data: &LocaleData) { match width { 1..=3 => { // Abbreviated result.push_str(locale_data.days_abbr[weekday]); }, _ => { // Wide (4+) result.push_str(locale_data.days_wide[weekday]); }, } } /// Format a number with optional zero-padding fn format_number(result: &mut String, value: i8, min_width: usize) { let mut buf = itoa::Buffer::new(); let s = buf.format(value); if min_width >= 2 && s.len() < min_width { for _ in 0..(min_width - s.len()) { result.push('0'); } } result.push_str(s); } /// Format timezone fn format_timezone(result: &mut String, dt: &Zoned, width: usize) { let total_secs = dt.offset().seconds(); let hours = total_secs / 3600; let mins = (total_secs % 3600).abs() / 60; if width >= 4 { // Long form: timezone name result.push_str(dt.time_zone().iana_name().unwrap_or_default()); } else { // Short form: GMT offset result.push_str("GMT"); if hours >= 0 { result.push('+'); } let mut buf = itoa::Buffer::new(); result.push_str(buf.format(hours)); if mins != 0 { result.push(':'); if mins < 10 { result.push('0'); } result.push_str(buf.format(mins)); } } } /// Combine date and time strings using a datetime pattern pub fn combine_datetime(date: &str, time: &str, pattern: &str) -> String { pattern.replace("{1}", date).replace("{0}", time) } #[cfg(test)] mod tests { use super::*; use crate::cldr_data::get_locale_data; use jiff::{civil, tz::TimeZone, Zoned}; fn make_dt(year: i16, month: i8, day: i8, hour: i8, min: i8, sec: i8) -> Zoned { civil::date(year, month, day) .at(hour, min, sec, 0) .to_zoned(TimeZone::UTC) .unwrap() } #[test] fn test_format_year() { let dt = make_dt(2024, 3, 15, 10, 30, 45); let locale = get_locale_data("en-US"); assert!(format_with_pattern(&dt, "y", locale, None).contains("2024")); assert!(format_with_pattern(&dt, "yy", locale, None).contains("24")); assert!(format_with_pattern(&dt, "yyyy", locale, None).contains("2024")); } #[test] fn test_format_month() { let dt = make_dt(2024, 3, 15, 10, 30, 45); let locale = get_locale_data("en-US"); assert_eq!(format_with_pattern(&dt, "M", locale, None), "3"); assert_eq!(format_with_pattern(&dt, "MM", locale, None), "03"); assert_eq!(format_with_pattern(&dt, "MMM", locale, None), "Mar"); assert_eq!(format_with_pattern(&dt, "MMMM", locale, None), "March"); } #[test] fn test_format_day() { let dt = make_dt(2024, 3, 5, 10, 30, 45); let locale = get_locale_data("en-US"); assert_eq!(format_with_pattern(&dt, "d", locale, None), "5"); assert_eq!(format_with_pattern(&dt, "dd", locale, None), "05"); } #[test] fn test_format_weekday() { // March 15, 2024 is a Friday let dt = make_dt(2024, 3, 15, 10, 30, 45); let locale = get_locale_data("en-US"); assert_eq!(format_with_pattern(&dt, "E", locale, None), "Fri"); assert_eq!(format_with_pattern(&dt, "EEEE", locale, None), "Friday"); } #[test] fn test_format_hour_12() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("en-US"); assert_eq!(format_with_pattern(&dt, "h", locale, None), "2"); assert_eq!(format_with_pattern(&dt, "hh", locale, None), "02"); } #[test] fn test_format_hour_24() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("en-US"); assert_eq!(format_with_pattern(&dt, "H", locale, None), "14"); assert_eq!(format_with_pattern(&dt, "HH", locale, None), "14"); } #[test] fn test_format_minute_second() { let dt = make_dt(2024, 3, 15, 14, 5, 9); let locale = get_locale_data("en-US"); assert_eq!(format_with_pattern(&dt, "m", locale, None), "5"); assert_eq!(format_with_pattern(&dt, "mm", locale, None), "05"); assert_eq!(format_with_pattern(&dt, "s", locale, None), "9"); assert_eq!(format_with_pattern(&dt, "ss", locale, None), "09"); } #[test] fn test_format_am_pm() { let locale = get_locale_data("en-US"); let dt_am = make_dt(2024, 3, 15, 10, 30, 45); assert_eq!(format_with_pattern(&dt_am, "a", locale, None), "AM"); let dt_pm = make_dt(2024, 3, 15, 14, 30, 45); assert_eq!(format_with_pattern(&dt_pm, "a", locale, None), "PM"); } #[test] fn test_format_quoted_literal() { let dt = make_dt(2024, 3, 15, 10, 30, 45); let locale = get_locale_data("en-US"); assert_eq!( format_with_pattern(&dt, "d 'de' MMMM", locale, None), "15 de March" ); } #[test] fn test_format_full_date_en_us() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("en-US"); let result = format_with_pattern(&dt, locale.date_formats.full, locale, None); assert_eq!(result, "Friday, March 15, 2024"); } #[test] fn test_format_full_date_de() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("de-DE"); let result = format_with_pattern(&dt, locale.date_formats.full, locale, None); assert_eq!(result, "Freitag, 15. März 2024"); } #[test] fn test_format_short_date_en_us() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("en-US"); let result = format_with_pattern(&dt, locale.date_formats.short, locale, None); assert_eq!(result, "3/15/24"); } #[test] fn test_format_short_date_de() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("de-DE"); let result = format_with_pattern(&dt, locale.date_formats.short, locale, None); assert_eq!(result, "15.03.24"); } #[test] fn test_format_short_time_en_us() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("en-US"); let result = format_with_pattern(&dt, locale.time_formats.short, locale, None); assert_eq!(result, "2:30 PM"); } #[test] fn test_format_short_time_de() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("de-DE"); let result = format_with_pattern(&dt, locale.time_formats.short, locale, None); assert_eq!(result, "14:30"); } #[test] fn test_combine_datetime() { assert_eq!( combine_datetime("3/15/24", "2:30 PM", "{1}, {0}"), "3/15/24, 2:30 PM" ); assert_eq!( combine_datetime("15.03.24", "14:30", "{1} {0}"), "15.03.24 14:30" ); } #[test] fn test_midnight_12h() { let dt = make_dt(2024, 3, 15, 0, 0, 0); let locale = get_locale_data("en-US"); let result = format_with_pattern(&dt, "h:mm a", locale, None); assert_eq!(result, "12:00 AM"); } #[test] fn test_noon_12h() { let dt = make_dt(2024, 3, 15, 12, 0, 0); let locale = get_locale_data("en-US"); let result = format_with_pattern(&dt, "h:mm a", locale, None); assert_eq!(result, "12:00 PM"); } #[test] fn test_japanese_locale() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("ja-JP"); let result = format_with_pattern(&dt, locale.date_formats.long, locale, None); assert_eq!(result, "2024年3月15日"); } #[test] fn test_korean_locale() { let dt = make_dt(2024, 3, 15, 14, 30, 45); let locale = get_locale_data("ko-KR"); let result = format_with_pattern(&dt, locale.date_formats.medium, locale, None); assert_eq!(result, "2024. 3. 15."); } } ================================================ FILE: modules/llrt_navigator/Cargo.toml ================================================ [package] name = "llrt_navigator" description = "LLRT Module navigator" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_navigator" path = "src/lib.rs" [dependencies] rquickjs = { version = "0.11", default-features = false } ================================================ FILE: modules/llrt_navigator/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use rquickjs::{Ctx, Object, Result}; fn get_user_agent() -> &'static str { concat!("llrt ", env!("CARGO_PKG_VERSION")) } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); let navigator = Object::new(ctx.clone())?; navigator.set("userAgent", get_user_agent())?; globals.set("navigator", navigator)?; Ok(()) } ================================================ FILE: modules/llrt_net/Cargo.toml ================================================ [package] name = "llrt_net" description = "LLRT Module net" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_net" path = "src/lib.rs" [dependencies] itoa = { version = "1", default-features = false } llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" } llrt_events = { version = "0.8.1-beta", path = "../llrt_events" } llrt_stream = { version = "0.8.1-beta", path = "../llrt_stream" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } tokio = { version = "1", features = ["net"], default-features = false } tracing = { version = "0.1", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } rand = { version = "0.10.0", features = ["alloc"], default-features = false } ================================================ FILE: modules/llrt_net/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{net::SocketAddr, result::Result as StdResult}; use llrt_events::Emitter; use llrt_utils::{ module::{export_default, ModuleInfo}, result::ResultExt, }; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::{Func, This}, Class, Ctx, IntoJs, Result, }; #[cfg(unix)] use tokio::net::{UnixListener, UnixStream}; use tokio::{ net::{TcpListener, TcpStream}, sync::oneshot::Receiver, }; use self::security::ensure_access; pub use self::security::{get_allow_list, get_deny_list, set_allow_list, set_deny_list}; mod security; mod server; mod socket; use self::{server::Server, socket::Socket}; const LOCALHOST: &str = "localhost"; #[allow(dead_code)] enum ReadyState { Opening, Open, Closed, ReadOnly, WriteOnly, } impl ReadyState { #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { String::from(match self { ReadyState::Opening => "opening", ReadyState::Open => "open", ReadyState::Closed => "closed", ReadyState::ReadOnly => "readOnly", ReadyState::WriteOnly => "writeOnly", }) } } enum NetStream { Tcp((TcpStream, SocketAddr)), #[cfg(unix)] Unix((UnixStream, tokio::net::unix::SocketAddr)), } impl NetStream { async fn process<'js>( self, socket: &Class<'js, Socket<'js>>, ctx: &Ctx<'js>, allow_half_open: bool, ) -> Result { let (readable_done, writable_done) = match self { NetStream::Tcp((stream, _)) => { Socket::process_tcp_stream(socket, ctx, stream, allow_half_open) }, #[cfg(unix)] NetStream::Unix((stream, _)) => { Socket::process_unix_stream(socket, ctx, stream, allow_half_open) }, }?; let had_error = rw_join(ctx, readable_done, writable_done).await?; Ok(had_error) } } enum Listener { Tcp(TcpListener), #[cfg(unix)] Unix(UnixListener), } impl Listener { async fn accept(&self, ctx: &Ctx<'_>) -> Result { match self { Listener::Tcp(tcp) => tcp .accept() .await .map(|(stream, addr)| NetStream::Tcp((stream, addr))) .or_throw(ctx), #[cfg(unix)] Listener::Unix(unix) => unix .accept() .await .map(|(stream, addr)| NetStream::Unix((stream, addr))) .or_throw(ctx), } } } fn get_hostname(host: &str, port: u16) -> String { [host, itoa::Buffer::new().format(port)].join(":") } fn get_address_parts( ctx: &Ctx, addr: StdResult, ) -> Result<(String, u16, String)> { let addr = addr.or_throw(ctx)?; Ok(( addr.ip().to_string(), addr.port(), String::from(if addr.is_ipv4() { "IPv4" } else { "IPv6" }), )) } async fn rw_join( ctx: &Ctx<'_>, readable_done: Receiver, writable_done: Receiver, ) -> Result { let (readable_res, writable_res) = tokio::join!(readable_done, writable_done); let had_error = readable_res.or_throw_msg(ctx, "Readable sender dropped")? || writable_res.or_throw_msg(ctx, "Writable sender dropped")?; Ok(had_error) } pub struct NetModule; impl ModuleDef for NetModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("createConnection")?; declare.declare("connect")?; declare.declare("createServer")?; declare.declare(stringify!(Socket))?; declare.declare(stringify!(Server))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { Class::::define(default)?; Class::::define(default)?; Socket::add_event_emitter_prototype(ctx)?; Server::add_event_emitter_prototype(ctx)?; let connect = Func::from(|ctx, args| { struct Args<'js>(Ctx<'js>); let Args(ctx) = Args(ctx); let this = Socket::new(ctx.clone(), false)?; Socket::connect(This(this), ctx.clone(), args) }) .into_js(ctx)?; default.set("createConnection", connect.clone())?; default.set("connect", connect)?; default.set( "createServer", Func::from(|ctx, args| { struct Args<'js>(Ctx<'js>); let Args(ctx) = Args(ctx); Server::new(ctx.clone(), args) }), ) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: NetModule) -> Self { ModuleInfo { name: "net", module: val, } } } ================================================ FILE: modules/llrt_net/src/security.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::OnceLock; use rquickjs::{Ctx, Exception, Result}; static NET_ALLOW_LIST: OnceLock> = OnceLock::new(); static NET_DENY_LIST: OnceLock> = OnceLock::new(); pub fn set_allow_list(values: Vec) { _ = NET_ALLOW_LIST.set(values); } pub fn get_allow_list() -> Option<&'static Vec> { NET_ALLOW_LIST.get() } pub fn set_deny_list(values: Vec) { _ = NET_DENY_LIST.set(values); } pub fn get_deny_list() -> Option<&'static Vec> { NET_DENY_LIST.get() } pub fn ensure_access(ctx: &Ctx<'_>, resource: &String) -> Result<()> { if let Some(allow_list) = NET_ALLOW_LIST.get() { if !allow_list.contains(resource) { return Err(Exception::throw_message( ctx, &["Network address not allowed: ", resource].concat(), )); } } if let Some(deny_list) = NET_DENY_LIST.get() { if deny_list.contains(resource) { return Err(Exception::throw_message( ctx, &["Network address denied: ", resource].concat(), )); } } Ok(()) } ================================================ FILE: modules/llrt_net/src/server.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, RwLock, }; use llrt_context::CtxExtension; use llrt_events::{EmitError, Emitter, EventEmitter, EventList}; use llrt_stream::{impl_stream_events, SteamEvents}; use llrt_utils::{object::ObjectExt, result::ResultExt, reuse_list::ReuseList}; #[cfg(unix)] use rquickjs::IntoJs; use rquickjs::{ class::Trace, prelude::{Opt, Rest, This}, Class, Ctx, Exception, Function, JsLifetime, Object, Result, Undefined, Value, }; #[cfg(unix)] use tokio::net::UnixListener; use tokio::{ net::TcpListener, select, sync::{ broadcast::{self, Sender}, Notify, }, }; use tracing::trace; use super::{get_address_parts, get_hostname, socket::Socket, Listener, NetStream}; impl_stream_events!(Server); #[rquickjs::class] pub struct Server<'js> { emitter: EventEmitter<'js>, address: Value<'js>, close_tx: Sender<()>, allow_half_open: bool, already_listen: Arc, sockets: ReuseList>>, should_close: Arc, } impl<'js> Trace<'js> for Server<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { self.emitter.trace(tracer); self.address.trace(tracer); for socket_ref in self.sockets.iter() { socket_ref.trace(tracer); } } } unsafe impl<'js> JsLifetime<'js> for Server<'js> { type Changed<'to> = Server<'to>; } impl<'js> Emitter<'js> for Server<'js> { fn get_event_list(&self) -> Arc>> { self.emitter.get_event_list() } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> Server<'js> { #[qjs(constructor)] pub fn new(ctx: Ctx<'js>, args: Rest>) -> Result> { let mut args_iter = args.0.into_iter(); let mut connection_listener = None; let mut allow_half_open = false; if let Some(first) = args_iter.next() { if let Some(connection_listener_arg) = first.as_function() { connection_listener = Some(connection_listener_arg.clone()); } if let Some(opts_arg) = first.as_object() { allow_half_open = opts_arg.get_optional("allowHalfOpen")?.unwrap_or_default(); } } if let Some(next) = args_iter.next() { connection_listener = next.into_function(); } let emitter = EventEmitter::new(); let (close_tx, _) = broadcast::channel::<()>(1); let instance = Class::instance( ctx.clone(), Self { emitter, address: Undefined.into_value(ctx.clone()), close_tx, allow_half_open, already_listen: Arc::new(AtomicBool::new(false)), sockets: ReuseList::with_capacity(8), should_close: Arc::new(AtomicBool::new(false)), }, )?; if let Some(connection_listener) = connection_listener { Self::add_event_listener_str( This(instance.clone()), &ctx, "connection", connection_listener, false, false, )?; } Ok(instance) } pub fn address(&self) -> Value<'js> { self.address.clone() } pub fn get_connections(&self, cb: Opt>) -> Result<()> { if let Some(cb) = cb.0 { cb.call::<_, ()>((Undefined, self.sockets.len()))?; } Ok(()) } #[allow(unused_assignments)] ///TODO add backlog support pub fn listen( this: This>, ctx: Ctx<'js>, args: Rest>, ) -> Result<()> { let mut args_iter = args.0.into_iter(); let mut port = None; let mut path = None; let mut host = None; #[allow(unused_variables)] //TODO add backlog support let mut backlog = None; let mut callback = None; let borrow = this.borrow(); let mut close_rx = borrow.close_tx.subscribe(); let allow_half_open = borrow.allow_half_open; let already_running = borrow.already_listen.clone(); let should_close = borrow.should_close.clone(); drop(borrow); if already_running.load(Ordering::Relaxed) { return Err(Exception::throw_message(&ctx, "ERR_SERVER_ALREADY_LISTEN")); } if let Some(first) = args_iter.next() { if let Some(callback_arg) = first.as_function() { callback = Some(callback_arg.clone()); } else { if let Some(port_arg) = first.as_int() { if port_arg > 0xFFFF { return Err(Exception::throw_range( &ctx, "port should be between 0 and 65535", )); } port = Some(port_arg); } if let Some(path_arg) = first.as_string() { path = Some(path_arg.to_string()?); } if let Some(opts_arg) = first.as_object() { port = opts_arg.get_optional("port")?; path = opts_arg.get_optional("path")?; host = opts_arg.get_optional("host")?; backlog = opts_arg.get_optional("backlog")?; } let path = first.into_string(); if let Some(second) = args_iter.next() { if let Some(callback_arg) = second.as_function() { callback = Some(callback_arg.clone()); } if let Some(host_arg) = second.as_string() { host = Some(host_arg.to_string()?); } if path.is_some() { if let Some(backlog_arg) = second.as_int() { backlog = Some(backlog_arg); } } if let Some(third) = args_iter.next() { if let Some(callback_arg) = third.as_function() { callback = Some(callback_arg.clone()); } if port.is_some() { if let Some(backlog_arg) = third.as_int() { backlog = Some(backlog_arg); callback = args_iter.next().and_then(|v| v.into_function()); } } } } } } if let Some(callback) = callback { Self::add_event_listener_str( This(this.clone()), &ctx, "listening", callback, true, true, )?; } let ctx2 = ctx.clone(); if port.is_none() && path.is_none() { port = Some(0) } ctx.spawn_exit(async move { already_running.store(true, Ordering::Relaxed); let listener = match Self::bind(this.clone(), ctx2.clone(), port, host, path).await { Ok(listener) => listener, Err(e) => { already_running.store(false, Ordering::Relaxed); Err::<(), _>(e).emit_error("listen", &ctx2, this.clone())?; return Ok(()); // Don't stop the VM if failed to bind }, }; Self::emit_str(This(this.clone()), &ctx2, "listening", vec![], false)?; let notify = Arc::new(Notify::new()); let close_notify = notify.notified(); loop { let ctx3 = ctx2.clone(); let this2 = this.clone(); select! { socket = listener.accept(&ctx3) => { Self::handle_socket_connection( this2.clone(), ctx3.clone(), socket, notify.clone(), allow_half_open, ).emit_error("handle_socket_connection",&ctx3, this2)?; }, _ = close_rx.recv() => { break; } } } if !this.borrow().sockets.is_empty() { trace!("Waiting for sockets to finish"); close_notify.await; trace!("Sockets finished"); } else { trace!("No sockets to wait for, closing"); } already_running.store(false, Ordering::Relaxed); should_close.store(false, Ordering::Relaxed); Self::emit_str(this, &ctx2, "close", vec![], false)?; Ok(()) })?; Ok(()) } fn close(this: This>, ctx: Ctx<'js>, cb: Opt>) -> Result<()> { trace!("Closing server"); if let Some(cb) = cb.0 { Self::add_event_listener_str(This(this.clone()), &ctx, "close", cb, true, true)?; } let borrow = this.borrow_mut(); borrow.should_close.store(true, Ordering::Relaxed); let _ = borrow.close_tx.send(()); Ok(()) } } impl<'js> Server<'js> { async fn bind( this: Class<'js, Self>, ctx: Ctx<'js>, port: Option, host: Option, path: Option, ) -> Result { let listener = if let Some(port) = port { let listener = TcpListener::bind(get_hostname( &host.unwrap_or_else(|| String::from("0.0.0.0")), port as u16, )) .await .or_throw(&ctx)?; let address_object = Object::new(ctx.clone())?; let (address, port, family) = get_address_parts(&ctx, listener.local_addr())?; address_object.set("address", address)?; address_object.set("port", port)?; address_object.set("family", family)?; this.borrow_mut().address = address_object.into_value(); Listener::Tcp(listener) } else if let Some(path) = path { #[cfg(unix)] { let listener: UnixListener = UnixListener::bind(&path).or_throw(&ctx)?; this.borrow_mut().address = path.into_js(&ctx)?; Listener::Unix(listener) } #[cfg(not(unix))] { _ = path; return Err(Exception::throw_type( &ctx, "Unix domain sockets are not supported on this platform", )); } } else { panic!("unreachable") }; Ok(listener) } fn handle_socket_connection( this: Class<'js, Self>, ctx: Ctx<'js>, stream_result: Result, notify_close: Arc, allow_half_open: bool, ) -> Result<()> { let net_stream = stream_result.or_throw(&ctx)?; ctx.clone().spawn_exit(async move { let socket_instance = Socket::new(ctx.clone(), allow_half_open)?; let socket_index; { let mut sever_borrow = this.borrow_mut(); socket_index = sever_borrow.sockets.append(socket_instance.clone()); } let socket_instance2 = socket_instance.clone().into_value(); Self::emit_str( This(this.clone()), &ctx, "connection", vec![socket_instance2], false, )?; let had_error = net_stream .process(&socket_instance, &ctx, allow_half_open) .await?; Socket::emit_close(socket_instance, &ctx, had_error)?; { let mut sever_borrow = this.borrow_mut(); sever_borrow.sockets.remove(socket_index); if sever_borrow.sockets.is_empty() && sever_borrow.should_close.load(Ordering::Relaxed) { trace!("Sockets empty, notify close"); notify_close.notify_one(); } } Ok(()) })?; Ok(()) } } #[cfg(test)] mod tests { use std::time::Duration; use llrt_buffer as buffer; use llrt_test::{call_test, test_async_with, ModuleEvaluator}; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream, }; use crate::NetModule; async fn call_tcp(port: u16) { // Connect to server tokio::time::sleep(Duration::from_millis(100)).await; let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)) .await .unwrap(); stream.set_nodelay(true).unwrap(); // Write let msg = b"Hello, world!"; stream.write_all(msg).await.unwrap(); stream.flush().await.unwrap(); // Read let mut buf = vec![0; 1024]; let n = stream.read(&mut buf).await.unwrap(); assert_eq!(&buf[..n], msg); } #[tokio::test] async fn test_server_echo() { test_async_with(|ctx| { Box::pin(async move { buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "net") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { createServer } from 'net'; export async function test() { const server = createServer(socket => { socket.on('data', data => { socket.write(data, () => server.close()); }); }); // Use port 0 to let the OS assign an available port server.listen(0, '127.0.0.1'); return new Promise((resolve, reject) => { server.on('listening', () => { const port = server.address().port; resolve(port); }); server.on('error', (err) => reject(err)); }); } "#, ) .await .unwrap(); // Get the OS-assigned port from the server let port: u16 = call_test(&ctx, &module, ()).await; // Now connect to the server and run the echo test call_tcp(port).await; }) }) .await; } } ================================================ FILE: modules/llrt_net/src/socket.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{Arc, RwLock}; use llrt_context::CtxExtension; use llrt_events::{EmitError, Emitter, EventEmitter, EventKey, EventList}; use llrt_stream::{ impl_stream_events, readable::{ReadableStream, ReadableStreamInner}, writable::{WritableStream, WritableStreamInner}, SteamEvents, }; use llrt_utils::{object::ObjectExt, result::ResultExt}; use rquickjs::{ class::{Trace, Tracer}, prelude::{Opt, Rest, This}, Class, Ctx, Error, Exception, Function, JsLifetime, Object, Result, Value, }; #[cfg(unix)] use tokio::net::UnixStream; use tokio::{ io::{AsyncRead, AsyncWrite}, net::TcpStream, sync::oneshot::Receiver, }; use tracing::trace; use super::{ensure_access, get_address_parts, get_hostname, rw_join, ReadyState, LOCALHOST}; impl_stream_events!(Socket); #[rquickjs::class] #[allow(dead_code)] pub struct Socket<'js> { emitter: EventEmitter<'js>, readable_stream_inner: ReadableStreamInner<'js>, writable_stream_inner: WritableStreamInner<'js>, connecting: bool, destroyed: bool, pending: bool, local_address: Option, local_family: Option, local_port: Option, remote_address: Option, remote_family: Option, remote_port: Option, ready_state: ReadyState, allow_half_open: bool, } unsafe impl<'js> JsLifetime<'js> for Socket<'js> { type Changed<'to> = Socket<'to>; } impl<'js> Trace<'js> for Socket<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.emitter.trace(tracer); } } impl<'js> Emitter<'js> for Socket<'js> { fn get_event_list(&self) -> Arc>> { self.emitter.get_event_list() } fn on_event_changed(&mut self, event: EventKey<'js>, added: bool) -> Result<()> { self.readable_stream_inner.on_event_changed(event, added) } } impl<'js> ReadableStream<'js> for Socket<'js> { fn inner_mut(&mut self) -> &mut ReadableStreamInner<'js> { &mut self.readable_stream_inner } fn inner(&self) -> &ReadableStreamInner<'js> { &self.readable_stream_inner } } impl<'js> WritableStream<'js> for Socket<'js> { fn inner_mut(&mut self) -> &mut WritableStreamInner<'js> { &mut self.writable_stream_inner } fn inner(&self) -> &WritableStreamInner<'js> { &self.writable_stream_inner } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> Socket<'js> { #[qjs(constructor)] pub fn ctor(ctx: Ctx<'js>, opts: Opt>) -> Result> { let mut allow_half_open = false; if let Some(opts) = opts.0 { if let Some(opt_allow_half_open) = opts.get_optional("allowHalfOpen")? { allow_half_open = opt_allow_half_open; } } Self::new(ctx, allow_half_open) } #[qjs(get, enumerable)] pub fn connecting(&self) -> bool { self.connecting } #[qjs(get, enumerable)] pub fn pending(&self) -> bool { self.pending } #[qjs(get, enumerable)] pub fn remote_address(&self) -> Option { self.remote_address.clone() } pub fn write( this: This>, ctx: Ctx<'js>, value: Value<'js>, cb: Opt>, ) -> Result<()> { WritableStream::write_flushed(this, ctx.clone(), value, cb)?; Ok(()) } pub fn end( this: This>, ctx: Ctx<'js>, callback: Opt>, ) -> Result<()> { if let Some(cb) = callback.0 { Self::add_event_listener_str(This(this.clone()), &ctx, "end", cb, true, true)?; } //ReadableStream::destroy(This(this.clone()), ctx.clone())?; WritableStream::end(this); Ok(()) } pub fn destroy(this: This>, error: Opt>) -> Class<'js, Self> { this.borrow_mut().destroyed = true; ReadableStream::destroy(This(this.clone()), Opt(None)); WritableStream::destroy(This(this.clone()), error); this.0 } pub fn read( this: This>, ctx: Ctx<'js>, size: Opt, ) -> Result> { ReadableStream::read(this, ctx, size) } #[qjs(get, enumerable)] pub fn local_address(&self) -> Option { self.local_address.clone() } #[qjs(get, enumerable)] pub fn remote_family(&self) -> Option { self.remote_family.clone() } #[qjs(get, enumerable)] pub fn local_family(&self) -> Option { self.local_family.clone() } #[qjs(get, enumerable)] pub fn remote_port(&self) -> Option { self.remote_port } #[qjs(get, enumerable)] pub fn local_port(&self) -> Option { self.local_port } #[qjs(get, enumerable)] pub fn ready_state(&self) -> String { self.ready_state.to_string() } pub fn connect( this: This>, ctx: Ctx<'js>, args: Rest>, ) -> Result> { let args = args.0; let borrow = this.borrow(); let allow_half_open = borrow.allow_half_open; if borrow.destroyed { return Err(Exception::throw_message(&ctx, "Socket destroyed")); } drop(borrow); let mut port = None; let mut host = String::from(LOCALHOST); let mut path = None; let mut listener = None; let mut last = None; let mut addr = None; let mut args = args.into_iter(); if let Some(first) = args.next() { if let Some(opts) = first.as_object() { port = opts.get_optional("port")?; path = opts.get_optional("path")?; if let Some(host_arg) = opts.get_optional("host")? { host = host_arg } } else if let Some(path_arg) = first.as_string() { path = Some(path_arg.to_string()?); } else if let Some(port_arg) = first.as_int() { port = Some(port_arg as u16); if let Some(next) = args.next() { if let Some(host_arg) = next.as_string() { host = host_arg.to_string()?; } else { last = Some(next) } } } } if let Some(last) = last.or_else(|| args.next()) { if let Some(cb) = last.as_function() { listener = Some(cb.to_owned()); } } if path.is_none() && port.is_none() { return Err(Exception::throw_type(&ctx, "port or path are required")); } if let Some(path) = path.clone() { ensure_access(&ctx, &path)?; } if let Some(port) = port { let hostname = get_hostname(&host, port); ensure_access(&ctx, &hostname)?; addr = Some(hostname); } let this = this.0; let this2 = this.clone(); if let Some(listener) = listener { Socket::add_event_listener_str( This(this.clone()), &ctx, "connect", listener, false, true, )?; } ctx.clone().spawn_exit(async move { let ctx2 = ctx.clone(); let ctx3 = ctx.clone(); let this3 = this2.clone(); if this3.borrow().destroyed { Socket::emit_close(this3.clone(), &ctx3, false)?; return Ok(()); } let connect = async move { let (readable_done, writable_done) = if let Some(path) = path { #[cfg(unix)] { let stream = UnixStream::connect(path).await.or_throw(&ctx3)?; Self::process_unix_stream(&this2, &ctx3, stream, allow_half_open) } #[cfg(not(unix))] { _ = path; return Err(Exception::throw_type( &ctx3, "Unix domain sockets are not supported on this platform", )); } } else if let Some(addr) = addr { let stream = TcpStream::connect(addr).await.or_throw(&ctx3)?; Self::process_tcp_stream(&this2, &ctx3, stream, allow_half_open) } else { unreachable!() }?; Socket::emit_str(This(this2.clone()), &ctx3, "connect", vec![], false)?; let had_error = rw_join(&ctx3, readable_done, writable_done).await?; Socket::emit_close(this2, &ctx3, had_error)?; Ok::<_, Error>(()) } .await; connect.emit_error("connect", &ctx2, this3)?; Ok(()) })?; Ok(this) } } impl<'js> Socket<'js> { pub fn new(ctx: Ctx<'js>, allow_half_open: bool) -> Result> { let emitter = EventEmitter::new(); let readable_stream_inner = ReadableStreamInner::new(emitter.clone(), false); let writable_stream_inner = WritableStreamInner::new(emitter.clone(), false); let instance = Class::instance( ctx, Self { emitter, connecting: false, destroyed: false, pending: true, ready_state: ReadyState::Closed, local_address: None, local_family: None, local_port: None, remote_address: None, remote_family: None, remote_port: None, readable_stream_inner, writable_stream_inner, allow_half_open, }, )?; Ok(instance) } pub fn process_tcp_stream( this: &Class<'js, Self>, ctx: &Ctx<'js>, stream: TcpStream, allow_half_open: bool, ) -> Result<(Receiver, Receiver)> { Self::set_addresses(this, ctx, &stream)?; let (reader, writer) = stream.into_split(); Self::process_stream(this, ctx, reader, writer, allow_half_open) } #[cfg(unix)] pub fn process_unix_stream( this: &Class<'js, Self>, ctx: &Ctx<'js>, stream: UnixStream, allow_half_open: bool, ) -> Result<(Receiver, Receiver)> { let (reader, writer) = stream.into_split(); Self::process_stream(this, ctx, reader, writer, allow_half_open) } fn process_stream( this: &Class<'js, Self>, ctx: &Ctx<'js>, reader: R, writer: W, allow_half_open: bool, ) -> Result<(Receiver, Receiver)> { let this2 = this.clone(); let readable_done = ReadableStream::process_callback(this.clone(), ctx, reader, move || { if !allow_half_open { WritableStream::end(This(this2)); } })?; let writable_done = WritableStream::process(this.clone(), ctx, writer)?; trace!("Connected to stream"); let mut borrow = this.borrow_mut(); borrow.connecting = false; borrow.pending = false; borrow.ready_state = ReadyState::Open; drop(borrow); Ok((readable_done, writable_done)) } pub fn set_addresses<'a>( this: &'a Class<'js, Self>, ctx: &Ctx<'js>, stream: &TcpStream, ) -> Result<()> { let mut borrow = this.borrow_mut(); let (remote_address, remote_port, remote_family) = get_address_parts(ctx, stream.peer_addr())?; borrow.remote_address = Some(remote_address); borrow.remote_port = Some(remote_port); borrow.remote_family = Some(remote_family); let (local_address, local_port, local_family) = get_address_parts(ctx, stream.local_addr())?; borrow.local_address = Some(local_address); borrow.local_port = Some(local_port); borrow.local_family = Some(local_family); drop(borrow); Ok(()) } } #[cfg(test)] mod tests { use std::time::Duration; use llrt_buffer as buffer; use llrt_test::{call_test, test_async_with, ModuleEvaluator}; use rquickjs::{function::IntoArgs, module::Evaluated, Ctx, FromJs, Module}; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::TcpListener, }; use crate::NetModule; async fn server() -> u16 { // Use port 0 to let the OS assign an available port let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap(); let port = listener.local_addr().unwrap().port(); // Spawn the accept loop so we can return the port immediately tokio::spawn(async move { let (mut stream, _) = listener.accept().await.unwrap(); stream.set_nodelay(true).unwrap(); // Read let mut buf = vec![0; 1024]; let n = stream.read(&mut buf).await.unwrap(); // Write stream.write_all(&buf[..n]).await.unwrap(); stream.flush().await.unwrap(); }); port } async fn call_test_delay<'js, T, A>( ctx: &Ctx<'js>, module: &Module<'js, Evaluated>, args: A, ) -> T where T: FromJs<'js>, A: IntoArgs<'js>, { tokio::time::sleep(Duration::from_millis(100)).await; call_test::(ctx, module, args).await } #[tokio::test] async fn test_server_echo() { test_async_with(|ctx| { Box::pin(async move { buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "net") .await .unwrap(); // Start server and get OS-assigned port let port = server().await; let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { connect } from 'net'; export async function test(port) { const socket = connect({ port }); const txData = "Hello World"; return new Promise((resolve, reject) => { socket.on('connect', () => { socket.write(txData, (err) => { if (err) { reject(err); } }); }); socket.on('data', (rxData) => { resolve(rxData.toString() === txData); }); }); } "#, ) .await .unwrap(); let ok: bool = call_test_delay(&ctx, &module, (port,)).await; assert!(ok) }) }) .await; } } ================================================ FILE: modules/llrt_os/Cargo.toml ================================================ [package] name = "llrt_os" description = "LLRT Module OS" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [features] default = ["network", "statistics", "system"] network = ["sysinfo/network", "system"] statistics = ["system"] system = ["sysinfo/system"] [dependencies] home = { version = "0.5", default-features = false } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } num_cpus = { version = "1", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } rquickjs = { version = "0.11", features = ["std"], default-features = false } # Optional sysinfo = { version = "0.38", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] libc = { version = "0.2", default-features = false } users = { version = "0.11", features = ["cache"], default-features = false } [target.'cfg(windows)'.dependencies] whoami = { version = "2", default-features = false } windows-registry = { version = "0.6", features = [ "std", ], default-features = false } windows-result = { version = "0.4", features = [ "std", ], default-features = false } windows-version = { version = "0.1", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = ["test-util"], default-features = false } ================================================ FILE: modules/llrt_os/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use std::env; use llrt_utils::{ module::{export_default, ModuleInfo}, sysinfo::{ARCH, PLATFORM}, }; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, prelude::Func, Ctx, Exception, Result, }; #[cfg(feature = "system")] use sysinfo::System; #[cfg(unix)] use self::unix::{ get_priority, get_release, get_type, get_user_info, get_version, set_priority, DEV_NULL, EOL, }; #[cfg(windows)] use self::windows::{ get_priority, get_release, get_type, get_user_info, get_version, set_priority, DEV_NULL, EOL, }; #[cfg(unix)] mod unix; #[cfg(windows)] mod windows; #[cfg(feature = "network")] use self::network::get_network_interfaces; #[cfg(feature = "statistics")] use self::statistics::{get_cpus, get_free_mem, get_total_mem}; #[cfg(feature = "network")] mod network; #[cfg(feature = "statistics")] mod statistics; fn get_available_parallelism() -> usize { num_cpus::get() } fn get_endianness() -> &'static str { #[cfg(target_endian = "little")] { "LE" } #[cfg(target_endian = "big")] { "BE" } } fn get_home_dir(ctx: Ctx<'_>) -> Result { home::home_dir() .map(|val| val.to_string_lossy().into_owned()) .ok_or_else(|| Exception::throw_message(&ctx, "Could not determine home directory")) } #[cfg(feature = "system")] fn get_host_name(ctx: Ctx<'_>) -> Result { System::host_name().ok_or_else(|| Exception::throw_reference(&ctx, "System::host_name")) } #[cfg(feature = "system")] fn get_load_avg() -> Vec { let load_avg = System::load_average(); vec![load_avg.one, load_avg.five, load_avg.fifteen] } #[cfg(feature = "system")] fn get_machine() -> String { System::cpu_arch() } fn get_tmp_dir() -> String { env::temp_dir().to_string_lossy().to_string() } #[cfg(feature = "system")] fn get_uptime() -> u64 { System::uptime() } pub struct OsModule; impl ModuleDef for OsModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("arch")?; declare.declare("availableParallelism")?; declare.declare("devNull")?; declare.declare("endianness")?; declare.declare("EOL")?; declare.declare("getPriority")?; declare.declare("homedir")?; declare.declare("platform")?; declare.declare("release")?; declare.declare("setPriority")?; declare.declare("tmpdir")?; declare.declare("type")?; declare.declare("userInfo")?; declare.declare("version")?; #[cfg(feature = "network")] { declare.declare("networkInterfaces")?; } #[cfg(feature = "statistics")] { declare.declare("cpus")?; declare.declare("freemem")?; declare.declare("totalmem")?; } #[cfg(feature = "system")] { declare.declare("hostname")?; declare.declare("loadavg")?; declare.declare("machine")?; declare.declare("uptime")?; } declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { default.set("arch", Func::from(|| ARCH))?; default.set( "availableParallelism", Func::from(get_available_parallelism), )?; default.set("devNull", DEV_NULL)?; default.set("endianness", Func::from(get_endianness))?; default.set("EOL", EOL)?; default.set("getPriority", Func::from(get_priority))?; default.set("homedir", Func::from(get_home_dir))?; default.set("platform", Func::from(|| PLATFORM))?; default.set("release", Func::from(get_release))?; default.set("setPriority", Func::from(set_priority))?; default.set("tmpdir", Func::from(get_tmp_dir))?; default.set("type", Func::from(get_type))?; default.set("userInfo", Func::from(get_user_info))?; default.set("version", Func::from(get_version))?; #[cfg(feature = "network")] { default.set("networkInterfaces", Func::from(get_network_interfaces))?; } #[cfg(feature = "statistics")] { default.set("cpus", Func::from(get_cpus))?; default.set("freemem", Func::from(get_free_mem))?; default.set("totalmem", Func::from(get_total_mem))?; } #[cfg(feature = "system")] { default.set("hostname", Func::from(get_host_name))?; default.set("loadavg", Func::from(get_load_avg))?; default.set("machine", Func::from(get_machine))?; default.set("uptime", Func::from(get_uptime))?; } Ok(()) }) } } impl From for ModuleInfo { fn from(val: OsModule) -> Self { ModuleInfo { name: "os", module: val, } } } #[cfg(test)] mod tests { use llrt_test::{call_test, test_async_with, ModuleEvaluator}; use rquickjs::{Ctx, Value}; use super::*; async fn run_test_return_string( ctx: &Ctx<'_>, name: &str, is_function: bool, expected_assertion: impl Fn(String), ) { ModuleEvaluator::eval_rust::(ctx.clone(), "os") .await .unwrap(); let brackets = if is_function { "()" } else { "" }; let module = ModuleEvaluator::eval_js( ctx.clone(), "test", &format!( r#" import {{ {} }} from 'os'; export async function test() {{ return {}{} }} "#, name, name, brackets ), ) .await .unwrap(); let result = call_test::(ctx, &module, ()).await; expected_assertion(result); } async fn run_test_return_number(ctx: &Ctx<'_>, name: &str, expected_assertion: impl Fn(Value)) { ModuleEvaluator::eval_rust::(ctx.clone(), "os") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", &format!( r#" import {{ {} }} from 'os'; export async function test() {{ return {}() }} "#, name, name ), ) .await .unwrap(); let result = call_test::(ctx, &module, ()).await; expected_assertion(result); } #[tokio::test] async fn test_available_parallelism() { test_async_with(|ctx| { Box::pin(async move { run_test_return_number(&ctx, "availableParallelism", |result| { assert!(result.is_number()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_arch() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "type", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_devnull() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "devNull", false, |result| { assert_eq!(result, DEV_NULL); }) .await; }) }) .await; } #[tokio::test] async fn test_endianness() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "endianness", true, |result| { let endianness = if cfg!(target_endian = "little") { "LE".to_string() } else { "BE".to_string() }; assert_eq!(result, endianness); }) .await; }) }) .await; } #[tokio::test] async fn test_eol() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "EOL", false, |result| { assert_eq!(result, EOL); }) .await; }) }) .await; } #[cfg(feature = "statistics")] #[tokio::test] async fn test_freemem() { test_async_with(|ctx| { Box::pin(async move { run_test_return_number(&ctx, "freemem", |result| { assert!(result.is_number()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_homedir() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "homedir", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } #[cfg(feature = "system")] #[tokio::test] async fn test_hostname() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "hostname", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } #[cfg(feature = "system")] #[tokio::test] async fn test_machine() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "machine", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_platform() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "platform", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_release() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "release", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_tmpdir() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "tmpdir", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } #[cfg(feature = "statistics")] #[tokio::test] async fn test_totalmem() { test_async_with(|ctx| { Box::pin(async move { run_test_return_number(&ctx, "totalmem", |result| { assert!(result.is_number()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_type() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "type", true, |result| { assert!(result == "Linux" || result == "Windows_NT" || result == "Darwin"); }) .await; }) }) .await; } #[cfg(feature = "system")] #[tokio::test] async fn test_uptime() { test_async_with(|ctx| { Box::pin(async move { run_test_return_number(&ctx, "uptime", |result| { assert!(result.is_number()); // platform dependant }) .await; }) }) .await; } #[tokio::test] async fn test_version() { test_async_with(|ctx| { Box::pin(async move { run_test_return_string(&ctx, "version", true, |result| { assert!(!result.is_empty()); // platform dependant }) .await; }) }) .await; } } ================================================ FILE: modules/llrt_os/src/network.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ collections::HashMap, net::{Ipv4Addr, Ipv6Addr}, sync::{Arc, Mutex}, }; use llrt_utils::result::ResultExt; use once_cell::sync::Lazy; use rquickjs::{Ctx, Object, Result}; use sysinfo::Networks; static NETWORKS: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(Networks::new_with_refreshed_list()))); pub fn get_network_interfaces(ctx: Ctx<'_>) -> Result>>> { let mut map: HashMap> = HashMap::new(); let networks = NETWORKS.lock().unwrap(); for (interface_name, network_data) in networks.iter() { let mut ifs = Vec::new(); for ip_network in network_data.ip_networks() { let addr = &ip_network.addr.to_string(); let is_ipv4 = addr.contains("."); let (is_internal, scope_id) = if is_ipv4 { get_attribute_ipv4(&ctx, addr)? } else { get_attribute_ipv6(&ctx, addr)? }; let obj = Object::new(ctx.clone())?; obj.set("address", addr)?; obj.set( "netmask", if is_ipv4 { prefix_to_netmask_ipv4(ip_network.prefix) } else { prefix_to_netmask_ipv6(ip_network.prefix) } .to_string(), )?; obj.set("family", if is_ipv4 { "IPv4" } else { "IPv6" })?; obj.set("mac", network_data.mac_address().to_string())?; obj.set("internal", is_internal)?; obj.set("cidr", [addr, "/", &ip_network.prefix.to_string()].concat())?; if !is_ipv4 { obj.set("scopeid", scope_id)?; } ifs.push(obj); } if !ifs.is_empty() { map.insert(interface_name.to_string(), ifs); } } Ok(map) } fn prefix_to_netmask_ipv4(prefix: u8) -> Box { let mut prefix = prefix; if prefix > 32 { return Box::from(""); } let mut mask = [0u8; 4]; #[allow(clippy::needless_range_loop)] for i in 0..4 { if prefix >= 8 { mask[i] = 255; prefix -= 8; } else if prefix > 0 { mask[i] = 255 << (8 - prefix); break; } } Box::from(Ipv4Addr::new(mask[0], mask[1], mask[2], mask[3]).to_string()) } fn prefix_to_netmask_ipv6(prefix: u8) -> Box { let mut prefix = prefix; if prefix > 128 { return Box::from(""); } let mut mask = [0u16; 8]; #[allow(clippy::needless_range_loop)] for i in 0..8 { if prefix >= 16 { mask[i] = 0xFFFF; prefix -= 16; } else if prefix > 0 { mask[i] = 0xFFFF << (16 - prefix); break; } } Box::from( Ipv6Addr::new( mask[0], mask[1], mask[2], mask[3], mask[4], mask[5], mask[6], mask[7], ) .to_string(), ) } fn get_attribute_ipv4(ctx: &Ctx<'_>, addr: &str) -> Result<(bool, u8)> { let addr = addr.parse::().or_throw(ctx)?; let is_internal = addr.is_broadcast() || addr.is_documentation() || addr.is_link_local() || addr.is_loopback() || addr.is_multicast() || addr.is_unspecified(); let scope_id = 0; // For IPv4, ScopeID is a dummy value. Ok((is_internal, scope_id)) } fn get_attribute_ipv6(ctx: &Ctx<'_>, addr: &str) -> Result<(bool, u8)> { let addr = addr.parse::().or_throw(ctx)?; let is_internal = addr.is_loopback() || addr.is_multicast() || addr.is_unspecified(); let scope_id = 0; // ScopeID is not supported at this time. Ok((is_internal, scope_id)) } ================================================ FILE: modules/llrt_os/src/statistics.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{Arc, Mutex}; use once_cell::sync::Lazy; use rquickjs::{Ctx, Object, Result}; use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System}; static SYSTEM: Lazy>> = Lazy::new(|| { Arc::new(Mutex::new(System::new_with_specifics( RefreshKind::nothing() .with_cpu(CpuRefreshKind::nothing().with_cpu_usage().with_frequency()) .with_memory(MemoryRefreshKind::nothing().with_ram()), ))) }); pub fn get_cpus(ctx: Ctx<'_>) -> Result>> { let mut vec: Vec = Vec::new(); let system = SYSTEM.lock().unwrap(); for cpu in system.cpus() { let obj = Object::new(ctx.clone())?; obj.set("model", cpu.brand())?; obj.set("speed", cpu.frequency())?; // The number of milliseconds spent by the CPU in each mode cannot be obtained at this time. let times = Object::new(ctx.clone())?; times.set("user", 0)?; times.set("nice", 0)?; times.set("sys", 0)?; times.set("idle", 0)?; times.set("irq", 0)?; obj.set("times", times)?; vec.push(obj); } Ok(vec) } pub fn get_free_mem() -> u64 { let mut system = SYSTEM.lock().unwrap(); system.refresh_memory_specifics(MemoryRefreshKind::nothing().with_ram()); system.free_memory() } pub fn get_total_mem() -> u64 { let mut system = SYSTEM.lock().unwrap(); system.refresh_memory_specifics(MemoryRefreshKind::nothing().with_ram()); system.total_memory() } ================================================ FILE: modules/llrt_os/src/unix.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ ffi::CStr, sync::{Arc, Mutex}, }; use libc::{getpriority, setpriority, PRIO_PROCESS}; use once_cell::sync::Lazy; use rquickjs::{ prelude::{Opt, Rest}, Ctx, Exception, IntoJs, Null, Object, Result, Value, }; use users::{os::unix::UserExt, Groups, Users, UsersCache}; use crate::get_home_dir; static USER_CACHE: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(UsersCache::new()))); static OS_INFO: Lazy<(String, String, String)> = Lazy::new(uname); pub static EOL: &str = "\n"; pub static DEV_NULL: &str = "/dev/null"; pub fn get_priority(who: Opt) -> i32 { let who = who.0.unwrap_or(0); unsafe { getpriority(PRIO_PROCESS, who) } } pub fn set_priority(ctx: Ctx<'_>, args: Rest) -> Result<()> { let mut args_iter = args.0.into_iter().rev(); let prio: i32 = args_iter .next() .and_then(|v| v.as_number()) .ok_or_else(|| { Exception::throw_type(&ctx, "The `priority` argument must be of type number.") })? as i32; let who: u32 = args_iter.next().and_then(|v| v.as_number()).unwrap_or(0f64) as u32; if !(-20..=19).contains(&prio) { return Err(Exception::throw_range( &ctx, "The value of `priority` is out of range. It must be >= -20 && <= 19.", )); } unsafe { setpriority(PRIO_PROCESS, who, prio); } Ok(()) } pub fn get_type() -> &'static str { &OS_INFO.0 } pub fn get_user_info<'js>(ctx: Ctx<'js>, _options: Opt) -> Result> { let cache = USER_CACHE.lock().unwrap(); let obj = Object::new(ctx.clone())?; let uid = cache.get_current_uid(); obj.set("uid", uid)?; obj.set("gid", cache.get_current_gid())?; let (username, shell) = if let Some(user) = cache.get_user_by_uid(uid) { ( user.name().to_str().into_js(&ctx)?, user.shell().to_str().into_js(&ctx)?, ) } else { (Null.into_js(&ctx)?, Null.into_js(&ctx)?) }; obj.set("username", username)?; obj.set("homedir", get_home_dir(ctx.clone()))?; obj.set("shell", shell)?; Ok(obj) } pub fn get_release() -> &'static str { &OS_INFO.1 } pub fn get_version() -> &'static str { &OS_INFO.2 } fn uname() -> (String, String, String) { let mut info = std::mem::MaybeUninit::uninit(); // SAFETY: `info` is a valid pointer to a `libc::utsname` struct. let res = unsafe { libc::uname(info.as_mut_ptr()) }; if res != 0 { return (String::new(), String::new(), String::new()); } // SAFETY: `uname` returns 0 on success and info is initialized. let info = unsafe { info.assume_init() }; ( // SAFETY: `info.sysname` is a valid NUL-terminated pointer. unsafe { CStr::from_ptr(info.sysname.as_ptr()) .to_string_lossy() .into_owned() }, // SAFETY: `info.release` is a valid NUL-terminated pointer. unsafe { CStr::from_ptr(info.release.as_ptr()) .to_string_lossy() .into_owned() }, // SAFETY: `info.version` is a valid NUL-terminated pointer. unsafe { CStr::from_ptr(info.version.as_ptr()) .to_string_lossy() .into_owned() }, ) } ================================================ FILE: modules/llrt_os/src/windows.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::result::ResultExt; use once_cell::sync::Lazy; use rquickjs::{ prelude::{Opt, Rest}, Ctx, Exception, IntoJs, Null, Object, }; use windows_registry::LOCAL_MACHINE; use windows_result::Result; use windows_version::OsVersion; use crate::get_home_dir; static OS_RELEASE: Lazy = Lazy::new(release); static OS_VERSION: Lazy = Lazy::new(|| version().unwrap_or_default()); pub static EOL: &str = "\r\n"; pub static DEV_NULL: &str = "\\.\nul"; pub fn get_priority(_who: Opt) -> i32 { 0 } pub fn set_priority(ctx: Ctx<'_>, _args: Rest) -> rquickjs::Result<()> { Err(Exception::throw_syntax( &ctx, "setPriority is not implemented.", )) } pub fn get_type() -> &'static str { // In theory there are more types linx MinGW but in practice this is good enough "Windows_NT" } pub fn get_release() -> &'static str { &OS_RELEASE } pub fn get_version() -> &'static str { &OS_VERSION } fn release() -> String { let version = OsVersion::current(); format!("{}.{}.{}", version.major, version.minor, version.build) } pub fn get_user_info<'js>( ctx: Ctx<'js>, _options: Opt, ) -> rquickjs::Result> { let obj = Object::new(ctx.clone())?; obj.set("uid", -1)?; obj.set("gid", -1)?; obj.set("username", whoami::username().or_throw(&ctx)?)?; obj.set("homedir", get_home_dir(ctx.clone()))?; obj.set("shell", Null.into_js(&ctx)?)?; Ok(obj) } fn version() -> Result { let version = OsVersion::current(); let registry_key = LOCAL_MACHINE .open("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion") .map_err(std::io::Error::from)?; let mut value = registry_key .get_string("ProductName") .map_err(std::io::Error::from)?; // Windows 11 shares dwMajorVersion with Windows 10 // this workaround tries to disambiguate that by checking // if the dwBuildNumber is from Windows 11 releases (>= 22000). if version.major == 10 && version.build >= 22000 && value.starts_with("Windows 10") { value.replace_range(9..10, "1"); } Ok(value) } ================================================ FILE: modules/llrt_path/Cargo.toml ================================================ [package] name = "llrt_path" description = "LLRT Module path" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_path" path = "src/lib.rs" [dependencies] llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", features = ["std"], default-features = false } [target.'cfg(windows)'.dependencies] memchr = { version = "2", default-features = false } [dev-dependencies] criterion = { version = "0.8", default-features = false } once_cell = { version = "1", features = ["std"], default-features = false } rand = { version = "0.10.0", features = [ "alloc", "thread_rng", ], default-features = false } [[bench]] name = "slash_replacement" harness = false ================================================ FILE: modules/llrt_path/benches/slash_replacement.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::uninlined_format_args)] use criterion::{criterion_group, criterion_main, Criterion}; use llrt_path::replace_backslash; use rand::prelude::*; use std::path::{Path, PathBuf}; const MAX_DEPTH: usize = 10; const PATH_STYLE_PROB: f64 = 0.5; const WORD_LIST: &[&str] = &[ "home", "user", "admin", "documents", "downloads", "music", "videos", "projects", "notes", "desktop", "workspace", "archive", "backup", "code", "scripts", "logs", "build", "bin", "src", "include", "lib", "temp", "cache", "system", "private", "shared", "public", "common", "vacation", "pictures", "gallery", "photos", "library", "data", "storage", "cloud", "tasks", "files", "records", "history", "samples", "assets", "media", "config", "setup", "exports", "imports", "local", "global", "network", "remote", "main", "backup", "security", "mainframe", "tools", "resources", "info", "settings", "profile", "account", "group", "modules", "scripts", "test", "dist", "coverage", "docs", "models", "services", "components", "assets", "functions", "tests", "data", "results", "index", "source", "runtime", "example", "template", "styles", "layout", "config", "docs", "dependencies", "log", "controller", "service", "client", "server", "draft", "final", "old", "new", "review", "complete", "inprogress", "template", "empty", "readme", "license", "notes", "reference", "guide", "outline", "summary", ]; fn generate_random_path() -> String { let mut rng = ThreadRng::default(); let mut path = PathBuf::new(); let depth = rng.random_range(1..=MAX_DEPTH); for _ in 0..depth { let name = WORD_LIST.choose(&mut rng).unwrap(); if rng.random_bool(PATH_STYLE_PROB) { path.push(format!("{}\\", name)); } else { path.push(name); } } path.to_string_lossy().to_string() } fn replace_with_string_replace(path: String) -> String { path.replace('\\', "/") } fn benchmark(c: &mut Criterion) { c.bench_function("String Replace", |b| { b.iter(|| { let path = generate_random_path(); replace_with_string_replace(path); }) }); c.bench_function("Memchr Replace", |b| { b.iter(|| { let path = generate_random_path(); replace_backslash(path); }) }); c.bench_function("File slash replace", |b| { b.iter(|| { let path = generate_random_path(); replace_filename(path); }) }); c.bench_function("File components replace", |b| { b.iter(|| { let path = generate_random_path(); replace_components(path); }) }); } fn replace_components(path: String) -> String { let length = path.len(); let path = Path::new(&path); let components = path.components(); let mut new_path = String::with_capacity(length); for component in components { new_path.push_str(&component.as_os_str().to_string_lossy()); new_path.push('/'); } new_path.truncate(length); new_path } fn replace_filename(path: String) -> String { Path::new(&path) .to_string_lossy() .to_string() .replace('\\', "/") } criterion_group!(benches, benchmark); criterion_main!(benches); ================================================ FILE: modules/llrt_path/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{ borrow::Cow, path::{Component, Path, PathBuf, MAIN_SEPARATOR, MAIN_SEPARATOR_STR}, }; use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ function::Opt, module::{Declarations, Exports, ModuleDef}, prelude::{Func, Rest}, Ctx, Object, Result, }; pub struct PathModule; #[cfg(windows)] const DELIMITER: char = ';'; #[cfg(not(windows))] const DELIMITER: char = ':'; #[cfg(windows)] pub const CURRENT_DIR_STR: &str = ".\\"; #[cfg(windows)] const FORWARD_SLASH_STR: &str = "/"; #[cfg(not(windows))] pub const CURRENT_DIR_STR: &str = "./"; #[cfg(windows)] use memchr::{memchr, memchr2, memchr2_iter}; #[cfg(windows)] pub fn replace_backslash(path: impl Into) -> String { let mut path = path.into(); let bytes = unsafe { path.as_bytes_mut() }; let mut start = 0; while let Some(pos) = memchr(b'\\', &bytes[start..]) { bytes[start + pos] = b'/'; start += pos + 1; } path } #[cfg(not(windows))] pub fn replace_backslash(path: impl Into) -> String { path.into().replace('\\', "/") } #[cfg(windows)] fn find_next_separator(s: &str) -> Option { memchr2(b'\\', b'/', s.as_bytes()) } #[cfg(not(windows))] fn find_next_separator(s: &str) -> Option { s.find(MAIN_SEPARATOR) } #[cfg(windows)] fn find_last_sep(path: &str) -> Option { memchr2_iter(b'\\', b'/', path.as_bytes()).next_back() } #[cfg(not(windows))] fn find_last_sep(path: &str) -> Option { path.rfind(MAIN_SEPARATOR) } pub fn dirname<'a, P: Into>>(path: P) -> String { let path = path.into(); let len = path.len(); if len == 0 { return ".".into(); } let bytes = path.as_bytes(); #[cfg(windows)] { if len == 1 { return if is_sep(bytes[0]) { path.into_owned() } else { ".".to_string() }; } // Determine root end and search offset let (root_end, offset) = if is_sep(bytes[0]) { if is_sep(bytes[1]) { // UNC path: \\server\share parse_unc_root(bytes, len).unwrap_or((1, 1)) } else { (1, 1) } } else if bytes.len() > 1 && is_drive_letter(bytes[0]) && bytes[1] == b':' { let r = if len > 2 && is_sep(bytes[2]) { 3 } else { 2 }; (r, r) } else { (0, 0) }; // Find last separator (skipping trailing separators) let end = find_dirname_end(bytes, offset); match end { Some(e) => &path[..e], None if root_end > 0 => &path[..root_end], None => ".", } .into() } #[cfg(not(windows))] { if len == 1 { return if bytes[0] == b'/' { path.into_owned() } else { ".".into() }; } let has_root = bytes[0] == b'/'; let end = find_dirname_end(bytes, 1); match end { Some(e) if has_root && e == 1 => "//", Some(e) => &path[..e], None if has_root => "/", None => ".", } .into() } } #[cfg(windows)] fn is_sep(c: u8) -> bool { c == b'/' || c == b'\\' } #[cfg(windows)] fn is_drive_letter(c: u8) -> bool { c.is_ascii_alphabetic() } #[cfg(windows)] fn parse_unc_root(bytes: &[u8], len: usize) -> Option<(usize, usize)> { let mut j = 2; // Skip server name while j < len && !is_sep(bytes[j]) { j += 1; } if j >= len || j == 2 { return None; } // Skip separators while j < len && is_sep(bytes[j]) { j += 1; } if j >= len { return None; } let share_start = j; // Skip share name while j < len && !is_sep(bytes[j]) { j += 1; } if j == share_start { return None; } if j == len { return None; } // UNC root only - caller handles this Some((j + 1, j + 1)) } fn find_dirname_end(bytes: &[u8], offset: usize) -> Option { let mut matched_slash = true; for i in (offset..bytes.len()).rev() { #[cfg(windows)] let is_separator = is_sep(bytes[i]); #[cfg(not(windows))] let is_separator = bytes[i] == b'/'; if is_separator { if !matched_slash { return Some(i); } } else { matched_slash = false; } } None } pub fn name_extname(path: &str) -> (&str, &str) { let path = strip_last_sep(path); let sep_pos = find_last_sep(path); let path = match sep_pos { Some(idx) => &path[idx + 1..], None => path, }; if path.starts_with('.') { return (path, ""); } match path.rfind('.') { Some(idx) => path.split_at(idx), None => (path, ""), } } fn strip_last_sep(path: &str) -> &str { if ends_with_sep(path) { &path[..path.len() - 1] } else { path } } pub fn basename(path: String, suffix: Opt) -> String { #[cfg(windows)] { if path.is_empty() || path == MAIN_SEPARATOR_STR || path == FORWARD_SLASH_STR { return String::from(""); } } #[cfg(not(windows))] { if path.is_empty() || path == MAIN_SEPARATOR_STR { return String::from(""); } } let (base, ext) = name_extname(&path); let mut name = [base, ext].concat(); if let Some(suffix) = suffix.0 { if let Some(location) = name.rfind(&suffix) { name.truncate(location); return name; } } name } fn extname(path: String) -> String { let (_, ext) = name_extname(&path); ext.to_string() } fn format(obj: Object) -> String { let dir: String = obj.get("dir").unwrap_or_default(); let root: String = obj.get("root").unwrap_or_default(); let base: String = obj.get("base").unwrap_or_default(); let name: String = obj.get("name").unwrap_or_default(); let ext: String = obj.get("ext").unwrap_or_default(); let mut path = String::new(); if !dir.is_empty() { path.push_str(&dir); if !ends_with_sep(&dir) { path.push(MAIN_SEPARATOR); } } else if !root.is_empty() { path.push_str(&root); if !ends_with_sep(&root) { path.push(MAIN_SEPARATOR); } } if !base.is_empty() { path.push_str(&base); } else { path.push_str(&name); if !ext.is_empty() { if !ext.starts_with('.') { path.push('.'); } path.push_str(&ext); } } path } fn parse(ctx: Ctx, path_str: String) -> Result { let obj = Object::new(ctx)?; let path = Path::new(&path_str); let parent = path .parent() .map(|p| p.to_str().unwrap()) .unwrap_or_default(); let filename = path .file_name() .map(|n| n.to_str().unwrap()) .unwrap_or_default(); let (name, extension) = name_extname(filename); let root = path .components() .next() .and_then(|c| match c { Component::Prefix(prefix) => prefix.as_os_str().to_str(), Component::RootDir => c.as_os_str().to_str(), _ => Some(""), }) .unwrap_or_default(); obj.set("root", root)?; obj.set("dir", parent)?; obj.set("base", [name, extension].concat())?; obj.set("ext", extension)?; obj.set("name", name)?; Ok(obj) } fn join(parts: Rest) -> String { join_path(parts.0.iter()) } pub fn join_path(parts: I) -> String where S: AsRef, I: IntoIterator, { join_path_with_separator(parts, false) } pub fn join_path_with_separator(parts: I, force_posix_sep: bool) -> String where S: AsRef, I: IntoIterator, { //fine because we're either moving or storing references let parts_vec: Vec = parts.into_iter().collect(); //add one slash plus drive letter //max is probably parts+size let likely_max_size = parts_vec .iter() .map(|p| p.as_ref().len() + 1) .sum::() + 10; let result = String::with_capacity(likely_max_size); join_resolve_path(parts_vec, false, result, PathBuf::new(), force_posix_sep) } pub fn resolve_path(parts: I) -> Result where S: AsRef, I: IntoIterator, { resolve_path_with_separator(parts, false) } pub fn resolve_path_with_separator(parts: I, force_posix_sep: bool) -> Result where S: AsRef, I: IntoIterator, { let cwd = std::env::current_dir()?; let mut result = cwd.clone().into_os_string().into_string().unwrap(); //add MAIN_SEPARATOR if we're not on already MAIN_SEPARATOR if !result.ends_with(MAIN_SEPARATOR) { result.push(MAIN_SEPARATOR); } #[cfg(windows)] { if force_posix_sep { result = result.replace(MAIN_SEPARATOR, FORWARD_SLASH_STR); } } Ok(join_resolve_path(parts, true, result, cwd, force_posix_sep)) } pub fn relative(from: F, to: T) -> Result where F: AsRef, T: AsRef, { let from_ref = from.as_ref(); let to_ref = to.as_ref(); if from_ref == to_ref { return Ok("".into()); } let mut abs_from = None; if !is_absolute(from_ref) { abs_from = Some( std::env::current_dir()?.to_string_lossy().to_string() + MAIN_SEPARATOR_STR + from_ref, ); } let mut abs_to = None; if !is_absolute(to_ref) { abs_to = Some( std::env::current_dir()?.to_string_lossy().to_string() + MAIN_SEPARATOR_STR + to_ref, ); } let from_ref = abs_from.as_deref().unwrap_or(from_ref); let to_ref = abs_to.as_deref().unwrap_or(to_ref); let mut from_index = 0; let mut to_index = 0; // skip common prefix while from_index < from_ref.len() && to_index < to_ref.len() { let from_next = find_next_separator(&from_ref[from_index..]) .unwrap_or(from_ref.len() - from_index) + from_index; let to_next = find_next_separator(&to_ref[to_index..]).unwrap_or(to_ref.len() - to_index) + to_index; if from_ref[from_index..from_next] != to_ref[to_index..to_next] { break; } from_index = from_next + 1; //move past the separator to_index = to_next + 1; //move past the separator } let mut relative = String::new(); // add ".." for each remaining component in 'from' while from_index < from_ref.len() { let from_next = find_next_separator(&from_ref[from_index..]) .unwrap_or(from_ref.len() - from_index) + from_index; if !relative.is_empty() { relative.push(MAIN_SEPARATOR); } relative.push_str(".."); from_index = from_next + 1; // Move past the separator } // add the remaining components from 'to' while to_index < to_ref.len() { let to_next = find_next_separator(&to_ref[to_index..]).unwrap_or(to_ref.len() - to_index) + to_index; if !relative.is_empty() { relative.push(MAIN_SEPARATOR); } let component = &to_ref[to_index..to_next]; if component != "." { relative.push_str(component); } to_index = to_next + 1; // Move past the separator } Ok(if relative.is_empty() { ".".into() } else { relative }) } fn join_resolve_path( parts: I, resolve: bool, mut result: String, cwd: PathBuf, force_posix_sep: bool, ) -> String where S: AsRef, I: IntoIterator, { let (sep, sep_str) = if force_posix_sep { ('/', "/") } else { (MAIN_SEPARATOR, MAIN_SEPARATOR_STR) }; let mut resolve_cow: Cow; let mut empty = true; let mut prefix_len = 0; let mut index_stack = Vec::with_capacity(16); // Remove the trailing sep: /a/b// -> /a/b if ends_with_sep(&result) && result.len() > 1 { result.truncate(result.len() - 1); } for part in parts { let mut part_ref: &str = part.as_ref(); let mut start = 0; if resolve { if cfg!(not(windows)) { if part_ref.starts_with(MAIN_SEPARATOR) { empty = false; result = MAIN_SEPARATOR.into(); start = 1; } } else { let starts_with_sep = starts_with_sep(part_ref); if starts_with_sep { let (prefix, _) = get_path_prefix(&cwd); prefix_len = prefix.len(); result = prefix; empty = false; result.push(sep); } else { let path_buf: PathBuf = PathBuf::from(part_ref); if path_buf.is_absolute() { empty = false; let (prefix, mut components) = get_path_prefix(&path_buf); if !prefix.is_empty() { components.next(); //consume prefix } prefix_len = prefix.len(); result = prefix; result.push(sep); resolve_cow = components .map(|comp| comp.as_os_str().to_str().unwrap_or_default()) // Convert each component to &str .collect::>() // Collect into a vector of &str .join(sep_str) .into(); part_ref = resolve_cow.as_ref(); } } } } else if starts_with_sep(part_ref) && empty { empty = false; result.push(sep); start = 1; } while start < part_ref.len() { let end = find_next_separator(&part_ref[start..]).map_or(part_ref.len(), |i| i + start); match &part_ref[start..end] { ".." => { if let Some(last_index) = index_stack.pop() { result.truncate(last_index); } else if empty { if let Some(last_index) = find_last_sep(&result) { result.truncate(last_index); } } }, "" | "." => { //ignore }, sub_part => { let len = result.len(); if !result.ends_with(sep) && !result.is_empty() { result.push(sep); } result.push_str(sub_part); result.push(sep); index_stack.push(len); }, } start = end + 1; } } if result.len() > prefix_len + 1 && ends_with_sep(&result) { result.truncate(result.len() - 1); } result } pub fn resolve(path: Rest) -> Result { resolve_path(path.iter()) } fn get_path_prefix(cwd: &Path) -> (String, std::iter::Peekable>) { let mut components = cwd.components().peekable(); let prefix = if let Some(Component::Prefix(prefix)) = components.peek() { prefix.as_os_str().to_str().unwrap().to_string() } else { "".into() }; (prefix, components) } pub fn normalize>(path: P) -> String { join_path([path].iter()) } #[allow(dead_code)] //used by windows fn starts_with_sep(path: &str) -> bool { matches!(path.as_bytes().first().unwrap_or(&0), b'/' | b'\\') } #[cfg(windows)] pub fn ends_with_sep(path: &str) -> bool { matches!(path.as_bytes().last().unwrap_or(&0), b'/' | b'\\') } #[cfg(not(windows))] pub fn ends_with_sep(path: &str) -> bool { path.ends_with(MAIN_SEPARATOR) } #[cfg(windows)] pub fn is_absolute(path: &str) -> bool { starts_with_sep(path) || PathBuf::from(path).is_absolute() } #[cfg(not(windows))] pub fn is_absolute(path: &str) -> bool { path.starts_with(MAIN_SEPARATOR) } impl ModuleDef for PathModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("basename")?; declare.declare("dirname")?; declare.declare("extname")?; declare.declare("format")?; declare.declare("parse")?; declare.declare("join")?; declare.declare("resolve")?; declare.declare("relative")?; declare.declare("normalize")?; declare.declare("isAbsolute")?; declare.declare("delimiter")?; declare.declare("sep")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { default.set("dirname", Func::from(dirname::))?; default.set("basename", Func::from(basename))?; default.set("extname", Func::from(extname))?; default.set("format", Func::from(format))?; default.set("parse", Func::from(parse))?; default.set("join", Func::from(join))?; default.set("relative", Func::from(relative::))?; default.set("resolve", Func::from(resolve))?; default.set("normalize", Func::from(normalize::))?; default.set("isAbsolute", Func::from(|s: String| is_absolute(&s)))?; default.prop("delimiter", DELIMITER.to_string())?; default.prop("sep", MAIN_SEPARATOR.to_string())?; Ok(()) }) } } impl From for ModuleInfo { fn from(val: PathModule) -> Self { ModuleInfo { name: "path", module: val, } } } #[cfg(test)] mod tests { use std::{env::set_current_dir, sync::Mutex}; static THREAD_LOCK: Lazy> = Lazy::new(Mutex::default); use once_cell::sync::Lazy; use super::*; #[test] fn test_relative() { let _shared = THREAD_LOCK.lock().unwrap(); let cwd = std::env::current_dir().expect("unable to get current working directory"); set_current_dir("/").expect("unable to set working directory to /"); assert_eq!( relative("a/b/c", "b/c").unwrap(), "../../../b/c".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( relative("/data/orandea/test/aaa", "/data/orandea/impl/bbb").unwrap(), "../../impl/bbb".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( relative("/a/b/c", "/a/d").unwrap(), "../../d".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!(relative("/a/b/c", "/a/b/c/d").unwrap(), "d"); assert_eq!(relative("/a/b/c", "/a/b/c").unwrap(), ""); assert_eq!( relative("a/b", "a/b/c/d").unwrap(), "c/d".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( relative("a/b/c", "b/c").unwrap(), "../../../b/c".replace('/', MAIN_SEPARATOR_STR) ); set_current_dir(cwd).expect("unable to set working directory back"); } #[test] fn test_dirname() { assert_eq!(dirname("/usr/local/bin".to_string()), "/usr/local"); assert_eq!(dirname("/usr/local/".to_string()), "/usr"); assert_eq!(dirname("usr/local/bin".to_string()), "usr/local"); assert_eq!(dirname("/".to_string()), "/"); assert_eq!(dirname("".to_string()), "."); } #[test] fn test_basename() { assert_eq!(basename("/usr/local/bin".to_string(), Opt(None)), "bin"); assert_eq!( basename("/usr/local/bin.txt".to_string(), Opt(None)), "bin.txt" ); assert_eq!( basename( "/usr/local/bin.txt".to_string(), Opt(Some(".txt".to_string())) ), "bin" ); assert_eq!(basename("".to_string(), Opt(None)), ""); assert_eq!(basename("/".to_string(), Opt(None)), ""); } #[test] fn test_extname() { assert_eq!(extname("/usr/local/bin.txt".to_string()), ".txt"); assert_eq!(extname("/usr/local/bin".to_string()), ""); assert_eq!(extname("file.tar.gz".to_string()), ".gz"); assert_eq!(extname(".bashrc".to_string()), ""); assert_eq!(extname("".to_string()), ""); } #[test] fn test_join() { // Standard cases assert_eq!( join_path(["/usr", "local", "bin"].iter()), "/usr/local/bin".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( join_path(["/usr", "/local", "bin"].iter()), "/usr/local/bin".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( join_path(["usr", "local", "bin"].iter()), "usr/local/bin".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!(join_path(["", "bin"].iter()), "bin"); // Complex cases assert_eq!( join_path(["/usr", "..", "local", "bin"].iter()), "/local/bin".replace('/', MAIN_SEPARATOR_STR) ); // Parent dir assert_eq!( join_path([".", "usr", "local"]), "usr/local".replace('/', MAIN_SEPARATOR_STR) ); // Current dir assert_eq!( join_path(["/usr", ".", "bin"].iter()), "/usr/bin".replace('/', MAIN_SEPARATOR_STR) ); // Current dir in middle assert_eq!( join_path(["usr", "local", "bin", ".."].iter()), "usr/local".replace('/', MAIN_SEPARATOR_STR) ); // Ending with parent dir assert_eq!( join_path(["/usr", "local", "", "bin"].iter()), "/usr/local/bin".replace('/', MAIN_SEPARATOR_STR) ); // Empty component in path assert_eq!( join_path(["/usr", "local", ".hidden"].iter()), "/usr/local/.hidden".replace('/', MAIN_SEPARATOR_STR) ); // Hidden file } #[test] fn test_resolve_path() { let _shared = THREAD_LOCK.lock().unwrap(); let prefix = if cfg!(windows) { if let Some(Component::Prefix(prefix)) = std::env::current_dir().unwrap().components().next() { prefix.as_os_str().to_str().unwrap().to_string() } else { "".into() } } else { "".into() }; assert_eq!( resolve_path(["", "foo/bar"].iter()).unwrap(), std::env::current_dir() .unwrap() .join("foo/bar".replace('/', MAIN_SEPARATOR_STR)) .to_string_lossy() .to_string() ); // Standard cases assert_eq!( resolve_path(["/"].iter()).unwrap(), prefix.clone() + MAIN_SEPARATOR_STR ); // Standard cases assert_eq!( resolve_path(["/foo/bar", "../baz"].iter()).unwrap(), prefix.clone() + &"/foo/baz".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( resolve_path(["/foo/bar", "./baz"].iter()).unwrap(), prefix.clone() + &"/foo/bar/baz".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( resolve_path(["foo/bar", "/baz"].iter()).unwrap(), prefix.clone() + &"/baz".replace('/', MAIN_SEPARATOR_STR) ); // Complex cases assert_eq!( resolve_path(["/foo", "bar", ".", "baz"].iter()).unwrap(), prefix.clone() + &"/foo/bar/baz".replace('/', MAIN_SEPARATOR_STR) ); // Current dir in middle assert_eq!( resolve_path(["/foo", "bar", "..", "baz"].iter()).unwrap(), prefix.clone() + &"/foo/baz".replace('/', MAIN_SEPARATOR_STR) ); // Parent dir in middle assert_eq!( resolve_path(["/foo", "bar", "../..", "baz"].iter()).unwrap(), prefix.clone() + &"/baz".replace('/', MAIN_SEPARATOR_STR) ); // Double parent dir assert_eq!( resolve_path(["/foo", "bar", ".hidden"].iter()).unwrap(), prefix.clone() + &"/foo/bar/.hidden".replace('/', MAIN_SEPARATOR_STR) ); // Hidden file assert_eq!( resolve_path(["/foo", ".", "bar", "."].iter()).unwrap(), prefix.clone() + &"/foo/bar".replace('/', MAIN_SEPARATOR_STR) ); // Multiple current dirs assert_eq!( resolve_path(["/foo", "..", "..", "bar"].iter()).unwrap(), prefix.clone() + &"/bar".replace('/', MAIN_SEPARATOR_STR) ); // Multiple parent dirs assert_eq!( resolve_path(["/foo/bar", "/..", "baz"].iter()).unwrap(), prefix.clone() + &"/baz".replace('/', MAIN_SEPARATOR_STR) ); // Parent dir with absolute path assert_eq!( resolve_path(["../foo"].iter()).unwrap(), std::env::current_dir() .unwrap() .parent() .unwrap() .join("foo".replace('/', MAIN_SEPARATOR_STR)) .to_string_lossy() .to_string() ); // Start with .. assert_eq!( resolve_path(["../".repeat(32)].iter()).unwrap(), prefix.clone() ); // Many .. } #[test] fn test_normalize() { assert_eq!( normalize("/foo//bar//baz"), "/foo/bar/baz".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( normalize("/foo/./bar/../baz"), "/foo/baz".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!( normalize("foo/bar/"), "foo/bar".replace('/', MAIN_SEPARATOR_STR) ); assert_eq!(normalize("./foo"), "foo"); } #[test] fn test_is_absolute() { assert!(is_absolute("/usr/local/bin")); assert!(!is_absolute("usr/local/bin")); #[cfg(windows)] assert!(is_absolute("C:\\Program Files")); // for Windows systems assert!(!is_absolute("./local/bin")); } #[test] fn test_replace_backslash() { assert_eq!(replace_backslash("C:\\Program Files"), "C:/Program Files"); assert_eq!(replace_backslash("/usr/local/bin"), "/usr/local/bin"); assert_eq!(replace_backslash("C:\\Users\\User\\"), "C:/Users/User/"); } } ================================================ FILE: modules/llrt_perf_hooks/Cargo.toml ================================================ [package] name = "llrt_perf_hooks" description = "LLRT Module perf_hooks" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_perf_hooks" path = "src/lib.rs" [dependencies] llrt_events = { version = "0.8.1-beta", path = "../llrt_events" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } ================================================ FILE: modules/llrt_perf_hooks/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_events::Emitter; use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, Class, Ctx, Object, Result, }; use crate::performance::Performance; mod performance; pub struct PerfHooksModule; impl ModuleDef for PerfHooksModule { fn declare(declare: &Declarations<'_>) -> Result<()> { declare.declare("performance")?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { let globals = ctx.globals(); let performance: Object = globals.get("performance")?; default.set("performance", performance)?; Ok(()) }) } } impl From for ModuleInfo { fn from(val: PerfHooksModule) -> Self { ModuleInfo { name: "perf_hooks", module: val, } } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); let instance = Class::instance(ctx.clone(), Performance::new())?; Performance::add_event_target_prototype(ctx)?; globals.set("performance", instance)?; Ok(()) } // #[cfg(test)] // mod tests { // use llrt_test::{call_test, test_async_with, ModuleEvaluator}; // use rquickjs::CatchResultExt; // use super::*; // #[tokio::test] // async fn test_now() { // time::init(); // test_async_with(|ctx| { // Box::pin(async move { // ModuleEvaluator::eval_rust::(ctx.clone(), "perf_hooks") // .await // .catch(&ctx) // .unwrap(); // let module = ModuleEvaluator::eval_js( // ctx.clone(), // "test", // r#" // import { performance } from 'perf_hooks'; // export async function test() { // const now = performance.now() // // TODO: Delaying with setTimeout // for(let i=0; i < (1<<20); i++){} // return performance.now() - now // } // "#, // ) // .await // .unwrap(); // let result = call_test::(&ctx, &module, ()).await; // assert!(result > 0) // }) // }) // .await; // } // #[tokio::test] // async fn test_time_origin() { // time::init(); // test_async_with(|ctx| { // Box::pin(async move { // ModuleEvaluator::eval_rust::(ctx.clone(), "perf_hooks") // .await // .catch(&ctx) // .unwrap(); // let module = ModuleEvaluator::eval_js( // ctx.clone(), // "test", // r#" // import { performance } from 'perf_hooks'; // export async function test() { // return performance.timeOrigin // } // "#, // ) // .await // .catch(&ctx) // .unwrap(); // let result = call_test::(&ctx, &module, ()).await; // assert!(result > 0.0); // }) // }) // .await; // } // #[tokio::test] // async fn test_to_json() { // time::init(); // test_async_with(|ctx| { // Box::pin(async move { // ModuleEvaluator::eval_rust::(ctx.clone(), "perf_hooks") // .await // .unwrap(); // let module = ModuleEvaluator::eval_js( // ctx.clone(), // "test", // r#" // import { performance } from 'perf_hooks'; // export async function test() { // return performance.toJSON() // } // "#, // ) // .await // .unwrap(); // let result = call_test::(&ctx, &module, ()).await; // let time_origin = result.get::<_, f64>("timeOrigin").unwrap(); // assert!(time_origin > 0.); // }) // }) // .await; // } // } ================================================ FILE: modules/llrt_perf_hooks/src/performance.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{Arc, RwLock}; use llrt_events::{Emitter, EventList, Events}; use llrt_utils::time; use rquickjs::{ atom::PredefinedAtom, class::{Trace, Tracer}, Ctx, JsLifetime, Object, Result, }; #[rquickjs::class] #[derive(Clone)] pub struct Performance<'js> { pub events: Events<'js>, } unsafe impl<'js> JsLifetime<'js> for Performance<'js> { type Changed<'to> = Performance<'to>; } impl<'js> Emitter<'js> for Performance<'js> { fn get_event_list(&self) -> Arc>> { self.events.clone() } } impl<'js> Trace<'js> for Performance<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.trace_event_emitter(tracer); } } impl<'js> Default for Performance<'js> { fn default() -> Self { Self::new() } } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> Performance<'js> { #[qjs(constructor)] pub fn new() -> Self { Self { #[allow(clippy::arc_with_non_send_sync)] events: Arc::new(RwLock::new(Vec::new())), } } #[qjs(get)] fn time_origin() -> f64 { let time_origin = time::origin_nanos() as f64; time_origin / 1e6 } fn now() -> f64 { let now = time::now_nanos(); let started = time::origin_nanos(); let elapsed = now.saturating_sub(started); (elapsed as f64) / 1e6 } #[qjs(rename = PredefinedAtom::ToJSON)] fn to_json(ctx: Ctx<'_>) -> Result> { let obj = Object::new(ctx.clone())?; obj.set("timeOrigin", Self::time_origin())?; Ok(obj) } } ================================================ FILE: modules/llrt_process/Cargo.toml ================================================ [package] name = "llrt_process" description = "LLRT Module process" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_process" path = "src/lib.rs" [dependencies] llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", features = ["std"], default-features = false } [target.'cfg(unix)'.dependencies] libc = { version = "0.2", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = ["test-util"], default-features = false } ================================================ FILE: modules/llrt_process/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::collections::HashMap; use std::env; use std::sync::atomic::{AtomicU8, Ordering}; use llrt_utils::signals; use llrt_utils::primordials::{BasePrimordials, Primordial}; pub use llrt_utils::sysinfo; use llrt_utils::{ module::{export_default, ModuleInfo}, object::Proxy, result::ResultExt, sysinfo::{ARCH, PLATFORM}, time, VERSION, }; use rquickjs::Exception; use rquickjs::{ convert::Coerced, module::{Declarations, Exports, ModuleDef}, object::{Accessor, Property}, prelude::Func, Array, BigInt, Ctx, Error, Function, IntoJs, Object, Result, Value, }; pub static EXIT_CODE: AtomicU8 = AtomicU8::new(0); fn cwd(ctx: Ctx<'_>) -> Result { env::current_dir() .or_throw(&ctx) .map(|path| path.to_string_lossy().to_string()) } fn hr_time_big_int(ctx: Ctx<'_>) -> Result> { let now = time::now_nanos(); let started = time::origin_nanos(); let elapsed = now.saturating_sub(started); BigInt::from_u64(ctx, elapsed) } fn hr_time(ctx: Ctx<'_>) -> Result> { let now = time::now_nanos(); let started = time::origin_nanos(); let elapsed = now.saturating_sub(started); let seconds = elapsed / 1_000_000_000; let remaining_nanos = elapsed % 1_000_000_000; let array = Array::new(ctx)?; array.set(0, seconds)?; array.set(1, remaining_nanos)?; Ok(array) } fn to_exit_code(ctx: &Ctx<'_>, code: &Value<'_>) -> Result> { if let Ok(code) = code.get::>() { let code = code.0; let code: u8 = if code.fract() != 0.0 { return Err(Exception::throw_range( ctx, "The value of 'code' must be an integer", )); } else { (code as i32).rem_euclid(256) as u8 }; return Ok(Some(code)); } Ok(None) } fn exit(ctx: Ctx<'_>, code: Value<'_>) -> Result<()> { let code = match to_exit_code(&ctx, &code)? { Some(code) => code, None => EXIT_CODE.load(Ordering::Relaxed), }; std::process::exit(code.into()) } fn env_proxy_setter<'js>( target: Object<'js>, prop: Value<'js>, value: Coerced, ) -> Result { target.set(prop, value.to_string())?; Ok(true) } #[cfg(unix)] fn getuid() -> u32 { unsafe { libc::getuid() } } #[cfg(unix)] fn getgid() -> u32 { unsafe { libc::getgid() } } #[cfg(unix)] fn geteuid() -> u32 { unsafe { libc::geteuid() } } #[cfg(unix)] fn getegid() -> u32 { unsafe { libc::getegid() } } #[cfg(unix)] fn setuid(id: u32) -> i32 { unsafe { libc::setuid(id) } } #[cfg(unix)] fn setgid(id: u32) -> i32 { unsafe { libc::setgid(id) } } #[cfg(unix)] fn seteuid(id: u32) -> i32 { unsafe { libc::seteuid(id) } } #[cfg(unix)] fn setegid(id: u32) -> i32 { unsafe { libc::setegid(id) } } pub fn init(ctx: &Ctx<'_>) -> Result<()> { let globals = ctx.globals(); BasePrimordials::init(ctx)?; let process = Object::new(ctx.clone())?; let process_versions = Object::new(ctx.clone())?; process_versions.set("llrt", VERSION)?; // Node.js version - Set for compatibility with some Node.js packages (e.g. cls-hooked). process_versions.set("node", "0.0.0")?; let hr_time = Function::new(ctx.clone(), hr_time)?; hr_time.set("bigint", Func::from(hr_time_big_int))?; let release = Object::new(ctx.clone())?; release.prop("name", Property::from("llrt").enumerable())?; let env_map: HashMap = env::vars().collect(); let mut args: Vec = env::args().collect(); if let Some(arg) = args.get(1) { if arg == "-e" || arg == "--eval" { args.remove(1); args.remove(1); } } let env_obj = env_map.into_js(ctx)?; let env_proxy = Proxy::with_target(ctx.clone(), env_obj)?; env_proxy.setter(Func::from(env_proxy_setter))?; process.set("env", env_proxy)?; process.set("cwd", Func::from(cwd))?; process.set("argv0", args.clone().first().cloned().unwrap_or_default())?; process.set("id", std::process::id())?; process.set("argv", args)?; process.set("platform", PLATFORM)?; process.set("arch", ARCH)?; process.set("hrtime", hr_time)?; process.set("release", release)?; process.set("version", VERSION)?; process.set("versions", process_versions)?; process.prop( "exitCode", Accessor::new( |ctx| { struct Args<'js>(Ctx<'js>); let Args(ctx) = Args(ctx); ctx.globals().get::<_, Value>("__exitCode") }, |ctx, code| { struct Args<'js>(Ctx<'js>, Value<'js>); let Args(ctx, code) = Args(ctx, code); if let Some(code) = to_exit_code(&ctx, &code)? { EXIT_CODE.store(code, Ordering::Relaxed); } ctx.globals().set("__exitCode", code)?; Ok::<_, Error>(()) }, ) .configurable() .enumerable(), )?; process.set("exit", Func::from(exit))?; process.set( "kill", Func::from(|ctx, pid, signal| signals::kill(&ctx, pid, signal)), )?; #[cfg(unix)] { process.set("getuid", Func::from(getuid))?; process.set("getgid", Func::from(getgid))?; process.set("geteuid", Func::from(geteuid))?; process.set("getegid", Func::from(getegid))?; process.set("setuid", Func::from(setuid))?; process.set("setgid", Func::from(setgid))?; process.set("seteuid", Func::from(seteuid))?; process.set("setegid", Func::from(setegid))?; } globals.set("process", process)?; Ok(()) } pub struct ProcessModule; impl ModuleDef for ProcessModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare("env")?; declare.declare("cwd")?; declare.declare("argv0")?; declare.declare("id")?; declare.declare("argv")?; declare.declare("platform")?; declare.declare("arch")?; declare.declare("hrtime")?; declare.declare("release")?; declare.declare("version")?; declare.declare("versions")?; declare.declare("exitCode")?; declare.declare("exit")?; declare.declare("kill")?; #[cfg(unix)] { declare.declare("getuid")?; declare.declare("getgid")?; declare.declare("geteuid")?; declare.declare("getegid")?; declare.declare("setuid")?; declare.declare("setgid")?; declare.declare("seteuid")?; declare.declare("setegid")?; } declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { let globals = ctx.globals(); let process: Object = globals.get("process")?; export_default(ctx, exports, |default| { for name in process.keys::() { let name = name?; let value: Value = process.get(&name)?; default.set(name, value)?; } Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: ProcessModule) -> Self { ModuleInfo { name: "process", module: val, } } } #[cfg(test)] mod tests { use llrt_test::{call_test, test_async_with, ModuleEvaluator}; use super::*; #[tokio::test] async fn test_hr_time() { time::init(); test_async_with(|ctx| { Box::pin(async move { init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "process") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { hrtime } from 'process'; export async function test() { // TODO: Delaying with setTimeout for(let i=0; i < (1<<20); i++){} return hrtime() } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, ()).await; assert_eq!(result.len(), 2); assert_eq!(result[0], 0); assert!(result[1] > 0); }) }) .await; } #[tokio::test] async fn test_hr_time_bigint() { time::init(); test_async_with(|ctx| { Box::pin(async move { init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "process") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { hrtime } from 'process'; export async function test() { // TODO: Delaying with setTimeout for(let i=0; i < (1<<20); i++){} return hrtime.bigint() } "#, ) .await .unwrap(); let result = call_test::, _>(&ctx, &module, ()).await; assert!(result.0 > 0); }) }) .await; } } ================================================ FILE: modules/llrt_stream/Cargo.toml ================================================ [package] name = "llrt_stream" description = "LLRT Module stream" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_stream" path = "src/lib.rs" [dependencies] llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_context = { version = "0.8.1-beta", path = "../../libs/llrt_context" } llrt_events = { version = "0.8.1-beta", path = "../llrt_events" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", features = [ "bytearray-buffer", ], default-features = false } rquickjs = { version = "0.11", features = ["std"], default-features = false } tokio = { version = "1", features = [ "macros", "io-util", ], default-features = false } ================================================ FILE: modules/llrt_stream/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::result::Result as StdResult; use llrt_events::Emitter; use rquickjs::{prelude::This, Class, Ctx, IntoJs, Result, Value}; use tokio::sync::broadcast::error::RecvError; pub mod readable; pub mod writable; pub fn set_destroyed_and_error<'js>( is_destroyed: &mut bool, error_value: &mut Option>, error: StdResult>, RecvError>, ) { *is_destroyed = true; if let Ok(error) = error { *error_value = error } } const DEFAULT_BUFFER_SIZE: usize = 1024 * 16; pub trait SteamEvents<'js> where Self: Emitter<'js>, { fn emit_close(this: Class<'js, Self>, ctx: &Ctx<'js>, had_error: bool) -> Result<()> { Self::emit_str( This(this), ctx, "close", vec![had_error.into_js(ctx)?], false, ) } #[allow(dead_code)] fn emit_end(this: Class<'js, Self>, ctx: &Ctx<'js>) -> Result<()> { Self::emit_str(This(this), ctx, "end", vec![], false) } } #[macro_export] macro_rules! impl_stream_events { ($($struct:ident),*) => { $( impl<'js> $crate::SteamEvents<'js> for $struct<'js> {} )* }; } ================================================ FILE: modules/llrt_stream/src/readable.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{atomic::AtomicUsize, Arc, RwLock}; use llrt_buffer::Buffer; use llrt_context::CtxExtension; use llrt_events::{EmitError, Emitter, EventEmitter, EventKey, EventList}; use llrt_utils::{bytearray_buffer::BytearrayBuffer, result::ResultExt}; use rquickjs::{ class::{Trace, Tracer}, prelude::{Func, Opt, This}, Class, Ctx, Error, IntoJs, JsLifetime, Null, Result, Value, }; use tokio::{ io::{AsyncRead, AsyncReadExt, BufReader}, sync::{ broadcast::{self, Sender}, oneshot::Receiver, }, }; use super::{impl_stream_events, set_destroyed_and_error, SteamEvents, DEFAULT_BUFFER_SIZE}; #[derive(PartialEq, Clone, Debug)] pub enum ReadableState { Init, Flowing, Paused, } #[allow(dead_code)] pub struct ReadableStreamInner<'js> { emitter: EventEmitter<'js>, destroy_tx: Sender>>, is_ended: bool, is_destroyed: bool, errored: bool, buffer: BytearrayBuffer, emit_close: bool, state: ReadableState, high_water_mark: AtomicUsize, listener: Option<&'static str>, data_listener_attached_tx: Sender<()>, } impl<'js> Trace<'js> for ReadableStreamInner<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.emitter.trace(tracer); } } impl<'js> ReadableStreamInner<'js> { pub fn on_event_changed(&mut self, event: EventKey<'js>, added: bool) -> Result<()> { if let EventKey::String(event) = event { match event.as_ref() { "data" => { if added { if self.state == ReadableState::Paused { let _ = self.data_listener_attached_tx.send(()); } self.state = ReadableState::Flowing; self.listener = Some("data"); } else { self.listener = None; } }, "readable" => { if added { self.state = ReadableState::Paused; self.listener = Some("readable"); } else { self.listener = None; } }, _ => {}, } } Ok(()) } pub fn new(emitter: EventEmitter<'js>, emit_close: bool) -> Self { let (destroy_tx, _) = broadcast::channel::>>(1); let (listener_attached_tx, _) = broadcast::channel::<()>(1); Self { emitter, destroy_tx, is_ended: false, data_listener_attached_tx: listener_attached_tx, buffer: BytearrayBuffer::new(DEFAULT_BUFFER_SIZE), state: ReadableState::Init, high_water_mark: DEFAULT_BUFFER_SIZE.into(), listener: None, is_destroyed: false, emit_close, errored: false, } } } #[rquickjs::class] #[derive(rquickjs::class::Trace)] pub struct DefaultReadableStream<'js> { inner: ReadableStreamInner<'js>, } unsafe impl<'js> JsLifetime<'js> for DefaultReadableStream<'js> { type Changed<'to> = DefaultReadableStream<'to>; } impl<'js> DefaultReadableStream<'js> { fn with_emitter(ctx: Ctx<'js>, emitter: EventEmitter<'js>) -> Result> { Class::instance( ctx, Self { inner: ReadableStreamInner::new(emitter, true), }, ) } pub fn new(ctx: Ctx<'js>) -> Result> { Self::with_emitter(ctx, EventEmitter::new()) } } impl_stream_events!(DefaultReadableStream); impl<'js> Emitter<'js> for DefaultReadableStream<'js> { fn get_event_list(&self) -> Arc>> { self.inner.emitter.get_event_list() } fn on_event_changed(&mut self, event: EventKey<'js>, added: bool) -> Result<()> { self.inner.on_event_changed(event, added) } } impl<'js> ReadableStream<'js> for DefaultReadableStream<'js> { fn inner_mut(&mut self) -> &mut ReadableStreamInner<'js> { &mut self.inner } fn inner(&self) -> &ReadableStreamInner<'js> { &self.inner } } pub trait ReadableStream<'js> where Self: Emitter<'js> + SteamEvents<'js>, { fn inner_mut(&mut self) -> &mut ReadableStreamInner<'js>; fn inner(&self) -> &ReadableStreamInner<'js>; fn add_readable_stream_prototype(ctx: &Ctx<'js>) -> Result<()> { let proto = Class::::prototype(ctx)? .or_throw_msg(ctx, &["Prototype for ", Self::NAME, " not found"].concat())?; proto.set("read", Func::from(Self::read))?; proto.set("destroy", Func::from(Self::destroy))?; Ok(()) } fn destroy(this: This>, error: Opt>) -> Class<'js, Self> { let mut borrow = this.borrow_mut(); let inner = borrow.inner_mut(); inner.is_destroyed = true; let _ = inner.destroy_tx.send(error.0); drop(borrow); this.0 } fn read(this: This>, ctx: Ctx<'js>, size: Opt) -> Result> { if let Some(data) = this.borrow().inner().buffer.read(size.0) { return Buffer(data).into_js(&ctx); } Ok(Null.into_value(ctx)) } fn drain(this: Class<'js, Self>, ctx: &Ctx<'js>) -> Result<()> { let this2 = this.clone(); let borrow = this2.borrow(); let inner = borrow.inner(); let listener = inner.listener; if let Some(listener) = listener { let ba_buffer = inner.buffer.clone(); if !ba_buffer.is_empty() { drop(borrow); let args = match listener { "data" => { let buffer = ba_buffer.read(None).unwrap_or_default(); if buffer.is_empty() { return Ok(()); } vec![Buffer(buffer).into_js(ctx)?] }, "readable" => { vec![] }, _ => { vec![] }, }; Self::emit_str(This(this), ctx, listener, args, false)?; } } Ok(()) } fn process( this: Class<'js, Self>, ctx: &Ctx<'js>, readable: T, ) -> Result> { Self::do_process(this, ctx, readable, || {}) } fn process_callback( this: Class<'js, Self>, ctx: &Ctx<'js>, readable: T, on_end: C, ) -> Result> { Self::do_process(this, ctx, readable, on_end) } fn do_process( this: Class<'js, Self>, ctx: &Ctx<'js>, readable: T, on_end: C, ) -> Result> { let ctx2 = ctx.clone(); ctx.spawn_exit(async move { let this2 = this.clone(); let ctx3 = ctx2.clone(); let borrow = this2.borrow(); let inner = borrow.inner(); let mut destroy_rx = inner.destroy_tx.subscribe(); let is_ended = inner.is_ended; let mut is_destroyed = inner.is_destroyed; let emit_close = inner.emit_close; let mut listener_attached_tx = inner.data_listener_attached_tx.subscribe(); let ba_buffer = inner.buffer.clone(); let mut has_data = false; drop(borrow); let read_function = async move { let mut reader: BufReader = BufReader::new(readable); let mut buffer = Vec::::with_capacity(DEFAULT_BUFFER_SIZE); let mut last_state = ReadableState::Init; let mut error_value = None; if !is_ended && !is_destroyed { loop { tokio::select! { result = reader.read_buf(&mut buffer) => { let bytes_read = result.or_throw(&ctx3)?; let mut state = this2.borrow().inner().state.clone(); if !has_data && state == ReadableState::Init { this2.borrow_mut().inner_mut().state = ReadableState::Paused; state = ReadableState::Paused; has_data = true; } match state { ReadableState::Flowing => { if last_state == ReadableState::Paused { if let Some(empty_buffer) = ba_buffer.read(None) { buffer.extend(empty_buffer); } } if buffer.is_empty() { break; } Self::emit_str( This(this2.clone()), &ctx3, "data", vec![Buffer(buffer.clone()).into_js(&ctx3)?], false )?; buffer.clear(); }, ReadableState::Paused => { if bytes_read == 0 { break; } let write_buffer_future = ba_buffer.write(&mut buffer); Self::emit_str( This(this2.clone()), &ctx3, "readable", vec![], false )?; tokio::select!{ capacity = write_buffer_future => { buffer.clear(); //increase buffer capacity if bytearray buffer has more capacity to reduce read syscalls buffer.reserve(buffer.capacity()-capacity); } error = destroy_rx.recv() => { set_destroyed_and_error(&mut is_destroyed, &mut error_value, error); break; } _ = listener_attached_tx.recv() => { ba_buffer.clear().await //don't clear buffer } } }, _ => { //should not happen } } last_state = state; } error = destroy_rx.recv() => { set_destroyed_and_error(&mut is_destroyed, &mut error_value, error); break; }, } } } let mut borrow = this2.borrow_mut(); let inner = borrow.inner_mut(); inner.buffer.close().await; if is_destroyed { inner.is_destroyed = true; } else { inner.is_ended = true; } drop(borrow); drop(reader); if !is_destroyed { on_end(); Self::emit_str(This(this2), &ctx3, "end", vec![], false)?; } if let Some(error_value) = error_value{ return Err(ctx3.throw(error_value)); } Ok::<_, Error>(()) } .await; let had_error = read_function.emit_error("readable",&ctx2, this.clone())?; if emit_close { Self::emit_close(this,&ctx2,had_error)?; } Ok::<_, Error>(had_error) }) } } ================================================ FILE: modules/llrt_stream/src/writable.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::sync::{Arc, RwLock}; use llrt_context::CtxExtension; use llrt_events::{EmitError, Emitter, EventEmitter, EventList}; use llrt_utils::{bytes::ObjectBytes, error::ErrorExtensions, result::ResultExt}; use rquickjs::{ class::{Trace, Tracer}, prelude::{Func, Opt, This}, Class, Ctx, Error, Exception, Function, JsLifetime, Result, Value, }; use tokio::{ io::{AsyncWrite, AsyncWriteExt, BufWriter}, sync::{ broadcast::{self, Sender}, mpsc::{self, UnboundedReceiver, UnboundedSender}, oneshot::Receiver, }, }; use super::{impl_stream_events, set_destroyed_and_error, SteamEvents}; pub struct WritableStreamInner<'js> { emitter: EventEmitter<'js>, command_tx: UnboundedSender>, command_rx: Option>>, is_finished: bool, #[allow(dead_code)] errored: bool, emit_close: bool, is_destroyed: bool, destroy_tx: Sender>>, } impl<'js> Trace<'js> for WritableStreamInner<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.emitter.trace(tracer); } } impl<'js> WritableStreamInner<'js> { pub fn new(emitter: EventEmitter<'js>, emit_close: bool) -> Self { let (tx, rx) = mpsc::unbounded_channel(); let (destroy_tx, _) = broadcast::channel::>>(1); Self { command_tx: tx, command_rx: Some(rx), emitter, is_finished: false, is_destroyed: false, destroy_tx, emit_close, errored: false, } } } #[derive(Debug)] #[allow(dead_code)] pub enum WriteCommand<'js> { End, Write(Value<'js>, Option>, bool), Flush, } #[rquickjs::class] #[derive(rquickjs::class::Trace)] pub struct DefaultWritableStream<'js> { inner: WritableStreamInner<'js>, } unsafe impl<'js> JsLifetime<'js> for DefaultWritableStream<'js> { type Changed<'to> = DefaultWritableStream<'to>; } impl<'js> DefaultWritableStream<'js> { fn with_emitter(ctx: Ctx<'js>, emitter: EventEmitter<'js>) -> Result> { Class::instance( ctx, Self { inner: WritableStreamInner::new(emitter, true), }, ) } pub fn new(ctx: Ctx<'js>) -> Result> { Self::with_emitter(ctx, EventEmitter::new()) } } impl_stream_events!(DefaultWritableStream); impl<'js> Emitter<'js> for DefaultWritableStream<'js> { fn get_event_list(&self) -> Arc>> { self.inner.emitter.get_event_list() } } impl<'js> WritableStream<'js> for DefaultWritableStream<'js> { fn inner_mut(&mut self) -> &mut WritableStreamInner<'js> { &mut self.inner } fn inner(&self) -> &WritableStreamInner<'js> { &self.inner } } pub trait WritableStream<'js> where Self: Emitter<'js> + SteamEvents<'js>, { fn inner_mut(&mut self) -> &mut WritableStreamInner<'js>; fn inner(&self) -> &WritableStreamInner<'js>; fn add_writable_stream_prototype(ctx: &Ctx<'js>) -> Result<()> { let proto = Class::::prototype(ctx)? .or_throw_msg(ctx, &["Prototype for ", Self::NAME, " not found"].concat())?; proto.set("write", Func::from(Self::write))?; proto.set("end", Func::from(Self::end))?; Ok(()) } fn destroy(this: This>, error: Opt>) -> Class<'js, Self> { if !this.borrow().inner().is_finished { let mut borrow = this.borrow_mut(); let inner = borrow.inner_mut(); inner.is_finished = true; inner.is_destroyed = true; let tx = inner.destroy_tx.clone(); drop(borrow); //it doesn't matter if channel is closed because then writable is already closed let _ = tx.send(error.0); } this.0 } fn end(this: This>) -> Class<'js, Self> { if !this.borrow().inner().is_finished { let mut borrow = this.borrow_mut(); let inner = borrow.inner_mut(); inner.is_finished = true; let tx = inner.command_tx.clone(); drop(borrow); //it doesn't matter if channel is closed because then writable is already closed let _ = tx.send(WriteCommand::End); } this.0 } #[allow(dead_code)] fn flush(this: Class<'js, Self>, ctx: &Ctx<'js>) -> Result<()> { let _ = this .borrow() .inner() .command_tx .send(WriteCommand::Flush) .or_throw(ctx); Ok(()) } fn write_flushed( this: This>, ctx: Ctx<'js>, value: Value<'js>, cb: Opt>, ) -> Result<()> { Self::do_write(this, ctx, value, cb, true) } fn write( this: This>, ctx: Ctx<'js>, value: Value<'js>, cb: Opt>, ) -> Result<()> { Self::do_write(this, ctx, value, cb, false) } fn do_write( this: This>, ctx: Ctx<'js>, value: Value<'js>, cb: Opt>, flush: bool, ) -> Result<()> { let callback = cb.0; if this .borrow() .inner() .command_tx .send(WriteCommand::Write(value, callback.clone(), flush)) .is_err() { if let Some(cb) = callback { let err = Exception::throw_message(&ctx, "This stream has been ended") .into_value(&ctx)?; () = cb.call((err,))?; } } Ok(()) } fn process( this: Class<'js, Self>, ctx: &Ctx<'js>, writable: T, ) -> Result> { let mut borrow = this.borrow_mut(); let inner = borrow.inner_mut(); let is_ended = inner.is_finished; let mut is_destroyed = inner.is_destroyed; let emit_close = inner.emit_close; let mut command_rx = inner .command_rx .take() .expect("rx from writable process already taken!"); let mut destroy_rx = inner.destroy_tx.subscribe(); let mut error_value = None; drop(borrow); let ctx2 = ctx.clone(); ctx.spawn_exit(async move { let ctx3 = ctx2.clone(); let ctx4 = ctx2.clone(); let this2 = this.clone(); let write_function = async move { let mut writer = BufWriter::new(writable); if !is_ended && !is_destroyed { loop { tokio::select! { command = command_rx.recv() => { match command { Some(WriteCommand::Write(value, cb, flush)) => { let bytes = ObjectBytes::from(&ctx3, &value)?; let data = bytes.as_bytes(&ctx4)?; let result = async { writer.write_all(data).await?; if flush { writer.flush().await.or_throw(&ctx3)?; } Ok::<_, Error>(()) }.await; match result { Ok(_) => { if let Some(cb) = cb { () = cb.call(())?; } }, Err(err) => { let err2 = Exception::throw_message(&ctx3, &err.to_string()); if let Some(cb) = cb { let err = err.into_value(&ctx3)?; () = cb.call((err,))?; } return Err(err2); } } }, Some(WriteCommand::End) => { writer.shutdown().await.or_throw(&ctx3)?; break; }, Some(WriteCommand::Flush) => writer.flush().await.or_throw(&ctx3)?, None => break, } }, error = destroy_rx.recv() => { set_destroyed_and_error(&mut is_destroyed, &mut error_value, error); break; } } } } drop(writer); if !is_destroyed { Self::emit_str(This(this2), &ctx3, "finish", vec![], false)?; } if let Some(error_value) = error_value{ return Err(ctx3.throw(error_value)); } Ok::<_, Error>(()) } .await; let had_error = write_function.emit_error("writable", &ctx2, this.clone())?; if emit_close { Self::emit_close(this,&ctx2,had_error)?; } Ok::<_, Error>(had_error) }) } } ================================================ FILE: modules/llrt_stream_web/Cargo.toml ================================================ [package] name = "llrt_stream_web" description = "LLRT Module stream/web" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" [lib] name = "llrt_stream_web" path = "src/lib.rs" [dependencies] llrt_abort = { version = "0.8.1-beta", path = "../llrt_abort" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = ["test-util"], default-features = false } ================================================ FILE: modules/llrt_stream_web/src/lib.rs ================================================ use llrt_utils::{ module::{export_default, ModuleInfo}, primordials::{BasePrimordials, Primordial}, }; use queuing_strategy::{ByteLengthQueuingStrategy, CountQueuingStrategy}; use readable::{ ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, }; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, Class, Ctx, Result, }; use writable::{WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter}; use crate::{ readable::{ArrayConstructorPrimordials, IteratorPrimordials}, utils::promise::PromisePrimordials, writable::WritableStreamDefaultControllerPrimordials, }; mod queuing_strategy; mod readable; mod readable_writable_pair; mod utils; mod writable; /// Defines web streams, which are exposed through the "stream/web" Node import, but also at the global scope /// Web streams consist of Readable, Writable, and Transform streams. Transform is currently unimplemented. /// /// https://developer.mozilla.org/en-US/docs/Web/API/Streams_API /// /// # ReadableStream /// ReadableStream knows how to 'pull' objects or bytes from an underlying source, generally a user-defined function or an [async] iterator. /// A source enqueues data to the stream via a controller, either ReadableStreamDefaultController or a ReadableByteStreamController optionally for byte data. /// The controller is created at stream initialisation and cannot change. /// /// Data is read from the stream using a reader, which is obtained using stream.getReader(). A reader 'locks' the stream for reading, preventing /// other readers from being created. When a reader is released with `reader.releaseLock()`, the stream goes back to having no reader and a new one can be created. /// In the case of ReadableByteStreamController, a special reader ReadableStreamBYOBReader may be used, which allows users to provide their own /// buffer to fill bytes into when reading. Otherwise, ReadableStreamDefaultReader is used by default, and this may also be used with byte streams. /// /// A ReadableStream can be 'tee'd', which splits it into two readable streams which both read the same underlying data, potentially at different /// paces. This is an area of substantial complexity for the implementation, particularly in the case of byte streams as the alternative reader types /// must be handled correctly. /// /// # WritableStream /// WritableStream knows how to 'push' objects into an underlying sink, generally a user-defined function. It has no special casing for bytes, and so /// only has one type of controller, WritableStreamDefaultController, and only one type of writer WritableStreamDefaultWriter. The controller is only needed for /// error handling because writes are signalled via a function call to a user-defined 'write' method which receives the chunk directly. /// /// Data is written to the stream using a WritableStreamDefaultWriter, which is obtained using stream.getWriter(). A writer 'locks' the stream for writing, /// preventing other writers from being created. When a writer is released with `writer.releaseLock()`, the stream goes back to having no writer and a new one can be created. pub struct StreamWebModule; // https://nodejs.org/api/webstreams.html impl ModuleDef for StreamWebModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(ReadableStream))?; declare.declare(stringify!(ReadableStreamDefaultReader))?; declare.declare(stringify!(ReadableStreamBYOBReader))?; declare.declare(stringify!(ReadableStreamDefaultController))?; declare.declare(stringify!(ReadableByteStreamController))?; declare.declare(stringify!(ReadableStreamBYOBRequest))?; declare.declare(stringify!(WritableStream))?; declare.declare(stringify!(WritableStreamDefaultWriter))?; declare.declare(stringify!(WritableStreamDefaultController))?; declare.declare(stringify!(ByteLengthQueuingStrategy))?; declare.declare(stringify!(CountQueuingStrategy))?; declare.declare("default")?; Ok(()) } #[inline] fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Class::::define(default)?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: StreamWebModule) -> Self { ModuleInfo { name: "stream/web", module: val, } } } pub fn init(ctx: &Ctx) -> Result<()> { let globals = &ctx.globals(); BasePrimordials::init(ctx)?; PromisePrimordials::init(ctx)?; ArrayConstructorPrimordials::init(ctx)?; WritableStreamDefaultControllerPrimordials::init(ctx)?; IteratorPrimordials::init(ctx)?; // https://min-common-api.proposal.wintertc.org/#api-index Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; Class::::define(globals)?; // This is exposed globally by Node even though its not in the min-common-api Class::::define(globals)?; Ok(()) } ================================================ FILE: modules/llrt_stream_web/src/queuing_strategy/byte_length.rs ================================================ use rquickjs::{class::Trace, methods, Class, Ctx, JsLifetime, Result}; use super::{NativeSizeFunction, QueueingStrategyInit}; #[derive(JsLifetime, Trace)] #[rquickjs::class] pub(crate) struct ByteLengthQueuingStrategy<'js> { high_water_mark: f64, size: Class<'js, NativeSizeFunction>, } #[methods(rename_all = "camelCase")] impl<'js> ByteLengthQueuingStrategy<'js> { #[qjs(constructor)] pub(crate) fn new(ctx: Ctx<'js>, init: QueueingStrategyInit) -> Result { // Set this.[[highWaterMark]] to init["highWaterMark"]. Ok(Self { high_water_mark: init.high_water_mark, size: Class::instance(ctx, NativeSizeFunction::ByteLength)?, }) } // readonly attribute Function size; // size is an attribute, not a method, so this function is not itself the size function, but instead returns one #[qjs(get)] pub(crate) fn size(&self) -> Class<'js, NativeSizeFunction> { self.size.clone() } // readonly attribute unrestricted double highWaterMark; #[qjs(get)] pub(crate) fn high_water_mark(&self) -> f64 { self.high_water_mark } } ================================================ FILE: modules/llrt_stream_web/src/queuing_strategy/count.rs ================================================ use rquickjs::{class::Trace, methods, Class, Ctx, JsLifetime, Result}; use super::{NativeSizeFunction, QueueingStrategyInit}; #[derive(JsLifetime, Trace)] #[rquickjs::class] pub(crate) struct CountQueuingStrategy<'js> { high_water_mark: f64, size: Class<'js, NativeSizeFunction>, } #[methods(rename_all = "camelCase")] impl<'js> CountQueuingStrategy<'js> { #[qjs(constructor)] pub(crate) fn new(ctx: Ctx<'js>, init: QueueingStrategyInit) -> Result { // Set this.[[highWaterMark]] to init["highWaterMark"]. Ok(Self { high_water_mark: init.high_water_mark, size: Class::instance(ctx, NativeSizeFunction::Count)?, }) } // readonly attribute Function size; // size is an attribute, not a method, so this function is not itself the size function, but instead returns one #[qjs(get)] pub(crate) fn size(&self) -> Class<'js, NativeSizeFunction> { self.size.clone() } // readonly attribute unrestricted double highWaterMark; #[qjs(get)] pub(crate) fn high_water_mark(&self) -> f64 { self.high_water_mark } } ================================================ FILE: modules/llrt_stream_web/src/queuing_strategy/mod.rs ================================================ use rquickjs::{ class::{JsCell, JsClass, Readable, Trace}, function::{Constructor, Params}, Class, Ctx, Error, Exception, FromJs, Function, JsLifetime, Object, Result, Value, }; pub(crate) use byte_length::ByteLengthQueuingStrategy; pub(crate) use count::CountQueuingStrategy; use crate::utils::ValueOrUndefined; mod byte_length; mod count; /// QueuingStrategy is the structure of a user-provided object describing how backpressure should be signalled. /// https://streams.spec.whatwg.org/#qs-api pub(super) struct QueuingStrategy<'js> { // unrestricted double highWaterMark; high_water_mark: Option>, // callback QueuingStrategySize = unrestricted double (any chunk); pub(super) size: Option>, } impl<'js> FromJs<'js> for QueuingStrategy<'js> { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or_else(|| Error::new_from_js(ty_name, "Object"))?; let high_water_mark = obj.get_value_or_undefined::<_, Value>("highWaterMark")?; let size = obj.get_value_or_undefined::<_, _>("size")?; Ok(Self { high_water_mark, size, }) } } impl<'js> QueuingStrategy<'js> { // https://streams.spec.whatwg.org/#validate-and-normalize-high-water-mark pub(super) fn extract_high_water_mark( ctx: &Ctx<'js>, this: Option, default_hwm: f64, ) -> Result { match this { // If strategy["highWaterMark"] does not exist, return defaultHWM. None => Ok(default_hwm), // Let highWaterMark be strategy["highWaterMark"]. Some(Self { high_water_mark: Some(high_water_mark), .. }) => { let high_water_mark = high_water_mark.as_number().unwrap_or(f64::NAN); // If highWaterMark is NaN or highWaterMark < 0, throw a RangeError exception. if high_water_mark.is_nan() || high_water_mark < 0.0 { Err(Exception::throw_range(ctx, "Invalid highWaterMark")) } else { // Return highWaterMark. Ok(high_water_mark) } }, // If strategy["highWaterMark"] does not exist, return defaultHWM. _ => Ok(default_hwm), } } // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function pub(super) fn extract_size_algorithm(this: Option<&Self>) -> SizeAlgorithm<'js> { // If strategy["size"] does not exist, return an algorithm that returns 1. match this.as_ref().and_then(|t| t.size.as_ref()) { None => SizeAlgorithm::AlwaysOne, Some(size) => SizeAlgorithm::SizeFunction(size.clone()), } } } /// SizeAlgorithm represents the two ways we might generate sizes - by calling a function or by simply returning 1.0 (the default) #[derive(JsLifetime, Trace, Clone)] pub(super) enum SizeAlgorithm<'js> { AlwaysOne, SizeFunction(SizeFunction<'js>), } impl<'js> SizeAlgorithm<'js> { pub(super) fn call(&self, ctx: Ctx<'js>, chunk: Value<'js>) -> Result> { match self { Self::AlwaysOne | Self::SizeFunction(SizeFunction::Native(NativeSizeFunction::Count)) => { Ok(SizeValue::Native(1.0)) }, Self::SizeFunction(SizeFunction::Js(ref f)) => f.call((chunk.clone(),)), Self::SizeFunction(SizeFunction::Native(NativeSizeFunction::ByteLength)) => { let size = byte_length_queueing_strategy_size_function(&ctx, &chunk)?; SizeValue::from_js(&ctx, size) }, } } } /// SizeValue abstracts over the sources of size values - they can either come from user-provided size functions, in which case they might /// be any Value, or (more often) they come from a NativeSizeFunction or the default AlwaysOne algorithm and we can pass around a native Rust type. pub(super) enum SizeValue<'js> { Value(Value<'js>), Native(f64), } impl SizeValue<'_> { pub(super) fn as_number(&self) -> Option { match self { Self::Value(value) => value.as_number(), Self::Native(size) => Some(*size), } } } impl<'js> FromJs<'js> for SizeValue<'js> { fn from_js(_: &Ctx<'js>, value: Value<'js>) -> Result { if let Some(size) = value.as_number() { return Ok(Self::Native(size)); } Ok(Self::Value(value)) } } /// SizeFunction abstracts over user-provided size functions (from their own queuing strategy implementations) and ones provided by us. /// We want to be able to recognise the ones that we have provided so we can short-circuit expensive JS calls #[derive(JsLifetime, Trace, Clone)] pub(super) enum SizeFunction<'js> { Js(Function<'js>), Native(NativeSizeFunction), } impl<'js> FromJs<'js> for SizeFunction<'js> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { if let Ok(nsf) = Class::::from_value(&value) { return Ok(SizeFunction::Native(*nsf.borrow())); } Ok(SizeFunction::Js(Function::from_js(ctx, value)?)) } } /// QueueingStrategyInit is the dictionary of input parameters for both native queuing strategies /// https://streams.spec.whatwg.org/#dictdef-queuingstrategyinit pub(crate) struct QueueingStrategyInit { high_water_mark: f64, } impl<'js> FromJs<'js> for QueueingStrategyInit { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or_else(|| Error::new_from_js(ty_name, "Object"))?; let high_water_mark = obj .get_value_or_undefined("highWaterMark")? .ok_or_else(|| Error::new_from_js(ty_name, "QueueingStrategyInit"))?; Ok(Self { high_water_mark }) } } /// NativeSizeFunction is a callable class which allows us to keep track that these size functions are not user provided, but /// in fact represent the native size functions. This allows us to avoid JS calls by noticing that a size function is this class. #[derive(JsLifetime, Trace, Clone, Copy)] pub(super) enum NativeSizeFunction { ByteLength, Count, } impl<'js> JsClass<'js> for NativeSizeFunction { const NAME: &'static str = "NativeSizeFunction"; const CALLABLE: bool = true; type Mutable = Readable; fn prototype(ctx: &Ctx<'js>) -> Result>> { Ok(Some(Function::prototype(ctx.clone()))) } fn constructor(_ctx: &Ctx<'js>) -> Result>> { Ok(None) } fn call<'a>(this: &JsCell<'js, Self>, params: Params<'a, 'js>) -> Result> { match &*this.borrow() { NativeSizeFunction::Count => Ok(Value::new_int(params.ctx().clone(), 1)), NativeSizeFunction::ByteLength => { let Some(chunk) = params.arg(0) else { return Err(Exception::throw_type( params.ctx(), "ByteLengthQueuingStrategy expects an argument 'chunk'", )); }; byte_length_queueing_strategy_size_function(params.ctx(), &chunk) }, } } } fn byte_length_queueing_strategy_size_function<'js>( ctx: &Ctx<'js>, chunk: &Value<'js>, ) -> Result> { if let Some(chunk) = chunk.as_object() { chunk.get("byteLength") } else { Err(Exception::throw_type( ctx, "ByteLengthQueuingStrategy argument 'chunk' must be an object", )) } } ================================================ FILE: modules/llrt_stream_web/src/readable/byob_reader.rs ================================================ use std::collections::VecDeque; use llrt_utils::{bytes::ObjectBytes, primordials::Primordial}; use rquickjs::{ atom::PredefinedAtom, class::{JsClass, OwnedBorrowMut, Trace, Tracer}, function::Constructor, methods, prelude::{Opt, This}, ArrayBuffer, Class, Ctx, Error, Exception, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, Value, }; use crate::{ readable::{ byte_controller::ReadableByteStreamController, controller::{ReadableStreamController, ReadableStreamControllerClass}, default_reader::{ReadableStreamDefaultReaderOwned, ReadableStreamReadResult}, objects::{ReadableStreamBYOBObjects, ReadableStreamObjects}, reader::{ReadableStreamGenericReader, ReadableStreamReader, ReadableStreamReaderOwned}, stream::{ReadableStreamOwned, ReadableStreamState}, }, utils::{ promise::{promise_rejected_with_constructor, with_promise_result, ResolveablePromise}, UnwrapOrUndefined, ValueOrUndefined, }, }; #[derive(Trace)] #[rquickjs::class] pub(crate) struct ReadableStreamBYOBReader<'js> { pub(super) generic: ReadableStreamGenericReader<'js>, pub(super) read_into_requests: VecDeque + 'js>>, } pub(crate) type ReadableStreamBYOBReaderClass<'js> = Class<'js, ReadableStreamBYOBReader<'js>>; pub(crate) type ReadableStreamBYOBReaderOwned<'js> = OwnedBorrowMut<'js, ReadableStreamBYOBReader<'js>>; unsafe impl<'js> JsLifetime<'js> for ReadableStreamBYOBReader<'js> { type Changed<'to> = ReadableStreamBYOBReader<'to>; } impl<'js> ReadableStreamBYOBReader<'js> { pub(super) fn readable_stream_byob_reader_error_read_into_requests( mut objects: ReadableStreamBYOBObjects<'js>, e: Value<'js>, ) -> Result> { // Let readIntoRequests be reader.[[readIntoRequests]]. let read_into_requests = &mut objects.reader.read_into_requests; // Set reader.[[readIntoRequests]] to a new empty list. let read_into_requests = read_into_requests.split_off(0); // For each readIntoRequest of readIntoRequests, for read_into_request in read_into_requests { // Perform readIntoRequest’s error steps, given e. objects = read_into_request.error_steps(objects, e.clone())?; } Ok(objects) } pub(super) fn set_up_readable_stream_byob_reader( ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>, ) -> Result<(ReadableStreamOwned<'js>, Class<'js, Self>)> { // If ! IsReadableStreamLocked(stream) is true, throw a TypeError exception. if stream.is_readable_stream_locked() { return Err(Exception::throw_type( &ctx, "This stream has already been locked for exclusive reading by another reader", )); } // If stream.[[controller]] does not implement ReadableByteStreamController, throw a TypeError exception. match stream.controller { ReadableStreamControllerClass::ReadableStreamByteController(_) => {}, _ => { return Err(Exception::throw_type( &ctx, "Cannot construct a ReadableStreamBYOBReader for a stream not constructed with a byte source", )); }, }; // Perform ! ReadableStreamReaderGenericInitialize(reader, stream). let generic = ReadableStreamGenericReader::readable_stream_reader_generic_initialize(&ctx, stream)?; let mut stream = OwnedBorrowMut::from_class(generic.stream.clone().unwrap()); let reader = Class::instance( ctx.clone(), Self { generic, // Set reader.[[readIntoRequests]] to a new empty list. read_into_requests: VecDeque::new(), }, )?; stream.reader = Some(reader.clone().into()); Ok((stream, reader)) } pub(super) fn readable_stream_byob_reader_release( mut objects: ReadableStreamBYOBObjects<'js>, ) -> Result> { // Perform ! ReadableStreamReaderGenericRelease(reader). objects .reader .generic .readable_stream_reader_generic_release(&mut objects.stream, || { objects.controller.release_steps() })?; // Let e be a new TypeError exception. let e: Value = objects .stream .constructor_type_error .call(("Reader was released",))?; // Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader, e). Self::readable_stream_byob_reader_error_read_into_requests(objects, e) } pub(super) fn readable_stream_byob_reader_read( ctx: &Ctx<'js>, // Let stream be reader.[[stream]]. mut objects: ReadableStreamBYOBObjects<'js>, view: ViewBytes<'js>, min: u64, read_into_request: impl ReadableStreamReadIntoRequest<'js> + 'js, ) -> Result> { // Set stream.[[disturbed]] to true. objects.stream.disturbed = true; // If stream.[[state]] is "errored", perform readIntoRequest’s error steps given stream.[[storedError]]. if let ReadableStreamState::Errored(ref stored_error) = objects.stream.state { let stored_error = stored_error.clone(); read_into_request.error_steps(objects, stored_error) } else { // Otherwise, perform ! ReadableByteStreamControllerPullInto(stream.[[controller]], view, min, readIntoRequest). ReadableByteStreamController::readable_byte_stream_controller_pull_into( ctx, objects, view, min, read_into_request, ) } } } #[methods(rename_all = "camelCase")] impl<'js> ReadableStreamBYOBReader<'js> { // this is required by web platform tests #[qjs(get)] pub fn constructor(ctx: Ctx<'js>) -> Result>> { ::constructor(&ctx) } #[qjs(constructor)] pub fn new(ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>) -> Result> { // Perform ? SetUpReadableStreamBYOBReader(this, stream). let (_, reader) = Self::set_up_readable_stream_byob_reader(ctx, stream)?; Ok(reader) } fn read( ctx: Ctx<'js>, reader: This>, view: Opt>, options: Opt>, ) -> Result> { with_promise_result(&ctx, || { let options = match options.0 { None => ReadableStreamBYOBReaderReadOptions { min: 1 }, Some(value) => ReadableStreamBYOBReaderReadOptions::from_js(&ctx, value)?, }; let view = ViewBytes::from_value( &ctx, &reader.generic.function_array_buffer_is_view, view.0.as_ref(), )?; let (buffer, byte_length, _) = view.get_array_buffer()?; // If view.[[ByteLength]] is 0, return a promise rejected with a TypeError exception. if byte_length == 0 { return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "view must have non-zero byteLength", ); } // If view.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0, return a promise rejected with a TypeError exception. if buffer.is_empty() { return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "view's buffer must have non-zero byteLength", ); } // If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, return a promise rejected with a TypeError exception. if buffer.as_bytes().is_none() { return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "view's buffer has been detached", ); } // If options["min"] is 0, return a promise rejected with a TypeError exception. if options.min == 0 { return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "options.min must be greater than 0", ); } // If view has a [[TypedArrayName]] internal slot, let typed_array_len = match &view.0 { ObjectBytes::U8Array(a) => Some(a.len()), ObjectBytes::I8Array(a) => Some(a.len()), ObjectBytes::U16Array(a) => Some(a.len()), ObjectBytes::I16Array(a) => Some(a.len()), ObjectBytes::U32Array(a) => Some(a.len()), ObjectBytes::I32Array(a) => Some(a.len()), ObjectBytes::U64Array(a) => Some(a.len()), ObjectBytes::I64Array(a) => Some(a.len()), ObjectBytes::F32Array(a) => Some(a.len()), ObjectBytes::F64Array(a) => Some(a.len()), _ => None, }; if let Some(typed_array_len) = typed_array_len { // If options["min"] > view.[[ArrayLength]], return a promise rejected with a RangeError exception. if options.min > typed_array_len as u64 { return promise_rejected_with_constructor( &reader.generic.constructor_range_error, &reader.generic.promise_primordials, "options.min must be less than or equal to views length", ); } } else { // Otherwise (i.e., it is a DataView), // If options["min"] > view.[[ByteLength]], return a promise rejected with a RangeError exception. if options.min > byte_length as u64 { return promise_rejected_with_constructor( &reader.generic.constructor_range_error, &reader.generic.promise_primordials, "options.min must be less than or equal to views byteLength", ); } } // If this.[[stream]] is undefined, return a promise rejected with a TypeError exception. if reader.generic.stream.is_none() { return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "Cannot read a stream using a released reader", ); } // Let promise be a new promise. let promise = ResolveablePromise::new(&ctx)?; // Let readIntoRequest be a new read-into request with the following items: #[derive(Trace)] struct ReadIntoRequest<'js> { promise: ResolveablePromise<'js>, } impl<'js> ReadableStreamReadIntoRequest<'js> for ReadIntoRequest<'js> { // chunk steps, given chunk // Resolve promise with «[ "value" → chunk, "done" → false ]». fn chunk_steps( &self, objects: ReadableStreamBYOBObjects<'js>, chunk: Value<'js>, ) -> Result> { self.promise.resolve(ReadableStreamReadResult { value: Some(chunk), done: false, })?; Ok(objects) } // close steps, given chunk // Resolve promise with «[ "value" → chunk, "done" → true ]». fn close_steps( &self, objects: ReadableStreamBYOBObjects<'js>, chunk: Value<'js>, ) -> Result> { self.promise.resolve(ReadableStreamReadResult { value: Some(chunk), done: true, })?; Ok(objects) } // error steps, given e // Reject promise with e. fn error_steps( &self, objects: ReadableStreamBYOBObjects<'js>, reason: Value<'js>, ) -> Result> { self.promise.reject(reason)?; Ok(objects) } } let objects = ReadableStreamObjects::from_byob_reader(reader.0); // Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], readIntoRequest). Self::readable_stream_byob_reader_read( &ctx, objects, view, options.min, ReadIntoRequest { promise: promise.clone(), }, )?; // Return promise. Ok(promise.promise) }) } fn release_lock(reader: This>) -> Result<()> { // If this.[[stream]] is undefined, return. if reader.generic.stream.is_none() { return Ok(()); }; let objects = ReadableStreamObjects::from_byob_reader(reader.0); // Perform ! ReadableStreamBYOBReaderRelease(this). Self::readable_stream_byob_reader_release(objects)?; Ok(()) } #[qjs(get)] fn closed(&self) -> Promise<'js> { self.generic.closed_promise.promise.clone() } fn cancel( ctx: Ctx<'js>, reader: This>, reason: Opt>, ) -> Result> { if reader.generic.stream.is_none() { // If this.[[stream]] is undefined, return a promise rejected with a TypeError exception. return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "Cannot cancel a stream using a released reader", ); } let objects = ReadableStreamObjects::from_byob_reader(reader.0); // Return ! ReadableStreamReaderGenericCancel(this, reason). let (promise, _) = ReadableStreamGenericReader::readable_stream_reader_generic_cancel( ctx.clone(), objects, reason.0.unwrap_or_undefined(&ctx), )?; Ok(promise) } } struct ReadableStreamBYOBReaderReadOptions { min: u64, } impl<'js> FromJs<'js> for ReadableStreamBYOBReaderReadOptions { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let min = obj.get_value_or_undefined::<_, f64>("min")?.unwrap_or(1.0); if min < u64::MIN as f64 || min > u64::MAX as f64 { return Err(Exception::throw_type( ctx, "min on ReadableStreamBYOBReaderReadOptions must fit into unsigned long long", )); }; Ok(Self { min: min as u64 }) } } pub(super) trait ReadableStreamReadIntoRequest<'js>: Trace<'js> { fn chunk_steps( &self, objects: ReadableStreamBYOBObjects<'js>, chunk: Value<'js>, ) -> Result>; fn close_steps( &self, objects: ReadableStreamBYOBObjects<'js>, chunk: Value<'js>, ) -> Result>; fn error_steps( &self, objects: ReadableStreamBYOBObjects<'js>, reason: Value<'js>, ) -> Result>; } impl<'js> Trace<'js> for Box + 'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.as_ref().trace(tracer); } } #[derive(JsLifetime, Clone)] pub(super) struct ViewBytes<'js>(ObjectBytes<'js>); impl<'js> ViewBytes<'js> { pub(super) fn from_object( ctx: &Ctx<'js>, function_array_buffer_is_view: &Function<'js>, object: &Object<'js>, ) -> Result { if function_array_buffer_is_view.call::<_, bool>((object.clone(),))? { if let Some(view) = ObjectBytes::from_array_buffer(object)? { return Ok(Self(view)); } } Err(Exception::throw_type( ctx, "view must be an ArrayBufferView", )) } pub(super) fn from_value( ctx: &Ctx<'js>, function_array_buffer_is_view: &Function<'js>, value: Option<&Value<'js>>, ) -> Result { match value.and_then(Value::as_object) { None => { Err(Exception::throw_type( ctx, "view must be typed DataView, Buffer, ArrayBuffer, or Uint8Array, but is not an object", )) }, Some(object) => Self::from_object(ctx, function_array_buffer_is_view, object), } } pub(super) fn get_array_buffer(&self) -> Result<(ArrayBuffer<'js>, usize, usize)> { Ok(self .0 .get_array_buffer()? .expect("invariant broken; ViewBytes may not contain ObjectBytes::Vec")) } pub(super) fn element_size(&self) -> usize { match self.0 { ObjectBytes::U8Array(_) => 1, ObjectBytes::I8Array(_) => 1, ObjectBytes::U16Array(_) => 2, ObjectBytes::I16Array(_) => 2, ObjectBytes::U32Array(_) => 4, ObjectBytes::I32Array(_) => 4, ObjectBytes::U64Array(_) => 8, ObjectBytes::I64Array(_) => 8, ObjectBytes::F32Array(_) => 4, ObjectBytes::F64Array(_) => 8, ObjectBytes::DataView(_) => 1, ObjectBytes::Vec(_) => { panic!("invariant broken; ViewBytes may not contain ObjectBytes::Vec") }, } } } #[derive(Clone, JsLifetime)] pub(crate) struct ArrayConstructorPrimordials<'js> { pub(super) constructor_uint8array: Constructor<'js>, constructor_int8array: Constructor<'js>, constructor_uint16array: Constructor<'js>, constructor_int16array: Constructor<'js>, constructor_uint32array: Constructor<'js>, constructor_int32array: Constructor<'js>, constructor_uint64array: Constructor<'js>, constructor_int64array: Constructor<'js>, constructor_f32array: Constructor<'js>, constructor_f64array: Constructor<'js>, constructor_data_view: Constructor<'js>, } impl<'js> Trace<'js> for ArrayConstructorPrimordials<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.constructor_uint8array.trace(tracer); self.constructor_int8array.trace(tracer); self.constructor_uint16array.trace(tracer); self.constructor_int16array.trace(tracer); self.constructor_uint32array.trace(tracer); self.constructor_int32array.trace(tracer); self.constructor_uint64array.trace(tracer); self.constructor_int64array.trace(tracer); self.constructor_f32array.trace(tracer); self.constructor_f64array.trace(tracer); self.constructor_data_view.trace(tracer); } } impl<'js> Primordial<'js> for ArrayConstructorPrimordials<'js> { fn new(ctx: &Ctx<'js>) -> Result where Self: Sized, { let globals = ctx.globals(); Ok(Self { constructor_uint8array: globals.get(PredefinedAtom::Uint8Array)?, constructor_int8array: globals.get(PredefinedAtom::Int8Array)?, constructor_uint16array: globals.get(PredefinedAtom::Uint16Array)?, constructor_int16array: globals.get(PredefinedAtom::Int16Array)?, constructor_uint32array: globals.get(PredefinedAtom::Uint32Array)?, constructor_int32array: globals.get(PredefinedAtom::Int32Array)?, constructor_uint64array: globals.get(PredefinedAtom::BigUint64Array)?, constructor_int64array: globals.get(PredefinedAtom::BigInt64Array)?, constructor_f32array: globals.get(PredefinedAtom::Float32Array)?, constructor_f64array: globals.get(PredefinedAtom::Float64Array)?, constructor_data_view: globals.get(PredefinedAtom::DataView)?, }) } } impl<'js> ArrayConstructorPrimordials<'js> { pub(super) fn for_view_bytes(&self, v: &ViewBytes<'js>) -> Constructor<'js> { match v.0 { ObjectBytes::U8Array(_) => self.constructor_uint8array.clone(), ObjectBytes::I8Array(_) => self.constructor_int8array.clone(), ObjectBytes::U16Array(_) => self.constructor_uint16array.clone(), ObjectBytes::I16Array(_) => self.constructor_int16array.clone(), ObjectBytes::U32Array(_) => self.constructor_uint32array.clone(), ObjectBytes::I32Array(_) => self.constructor_int32array.clone(), ObjectBytes::U64Array(_) => self.constructor_uint64array.clone(), ObjectBytes::I64Array(_) => self.constructor_int64array.clone(), ObjectBytes::F32Array(_) => self.constructor_f32array.clone(), ObjectBytes::F64Array(_) => self.constructor_f64array.clone(), ObjectBytes::DataView(_) => self.constructor_data_view.clone(), ObjectBytes::Vec(_) => { panic!("invariant broken; ViewBytes may not contain ObjectBytes::Vec") }, } } } impl<'js> Trace<'js> for ViewBytes<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.0.trace(tracer); } } impl<'js> IntoJs<'js> for ViewBytes<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { self.0.into_js(ctx) } } impl<'js> ReadableStreamReader<'js> for ReadableStreamBYOBReaderOwned<'js> { type Class = ReadableStreamBYOBReaderClass<'js>; fn with_reader( self, ctx: C, _: impl FnOnce( C, ReadableStreamDefaultReaderOwned<'js>, ) -> Result<(C, ReadableStreamDefaultReaderOwned<'js>)>, byob: impl FnOnce( C, ReadableStreamBYOBReaderOwned<'js>, ) -> Result<(C, ReadableStreamBYOBReaderOwned<'js>)>, _: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { byob(ctx, self) } fn into_inner(self) -> Self::Class { self.into_inner() } fn from_class(class: Self::Class) -> Self { OwnedBorrowMut::from_class(class) } fn try_from_erased(erased: Option>) -> Option { match erased { Some(ReadableStreamReaderOwned::ReadableStreamBYOBReader(r)) => Some(r), _ => None, } } } ================================================ FILE: modules/llrt_stream_web/src/readable/byte_controller.rs ================================================ use std::collections::VecDeque; use llrt_utils::{ error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED, option::{Null, Undefined}, primordials::{BasePrimordials, Primordial}, result::ResultExt, }; use rquickjs::{ class::{OwnedBorrow, OwnedBorrowMut, Trace, Tracer}, function::Constructor, methods, prelude::{Opt, This}, ArrayBuffer, Class, Ctx, Error, Exception, Function, IntoJs, JsLifetime, Object, Promise, Result, TypedArray, Value, }; use crate::{ readable::{ byob_reader::{ArrayConstructorPrimordials, ReadableStreamReadIntoRequest, ViewBytes}, controller::{ ReadableStreamController, ReadableStreamControllerClass, ReadableStreamControllerOwned, }, default_controller::ReadableStreamDefaultControllerOwned, default_reader::ReadableStreamReadRequest, objects::{ ReadableByteStreamObjects, ReadableStreamBYOBObjects, ReadableStreamClassObjects, ReadableStreamDefaultReaderObjects, ReadableStreamObjects, }, reader::ReadableStreamReader, stream::{ algorithms::{CancelAlgorithm, PullAlgorithm, StartAlgorithm}, source::UnderlyingSource, ReadableStream, ReadableStreamClass, ReadableStreamOwned, ReadableStreamState, }, }, utils::{ class_from_owned_borrow_mut, promise::{promise_resolved_with, upon_promise}, UnwrapOrUndefined, }, }; #[derive(JsLifetime)] #[rquickjs::class] pub(crate) struct ReadableByteStreamController<'js> { auto_allocate_chunk_size: Option, #[qjs(get)] byob_request: Option>>, cancel_algorithm: Option>, close_requested: bool, pull_again: bool, pull_algorithm: Option>, pulling: bool, pub(super) pending_pull_intos: VecDeque>, queue: VecDeque>, queue_total_size: usize, started: bool, strategy_hwm: f64, pub(super) stream: ReadableStreamClass<'js>, pub(super) array_constructor_primordials: ArrayConstructorPrimordials<'js>, constructor_array_buffer: Constructor<'js>, pub(super) function_array_buffer_is_view: Function<'js>, } impl<'js> Trace<'js> for ReadableByteStreamController<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.auto_allocate_chunk_size.trace(tracer); self.byob_request.trace(tracer); self.cancel_algorithm.trace(tracer); self.pull_algorithm.trace(tracer); self.pending_pull_intos.trace(tracer); self.queue.trace(tracer); self.queue_total_size.trace(tracer); self.started.trace(tracer); self.strategy_hwm.trace(tracer); self.stream.trace(tracer); self.array_constructor_primordials.trace(tracer); self.constructor_array_buffer.trace(tracer); self.function_array_buffer_is_view.trace(tracer); } } pub(crate) type ReadableByteStreamControllerClass<'js> = Class<'js, ReadableByteStreamController<'js>>; pub(crate) type ReadableByteStreamControllerOwned<'js> = OwnedBorrowMut<'js, ReadableByteStreamController<'js>>; impl<'js> ReadableByteStreamController<'js> { // SetUpReadableByteStreamControllerFromUnderlyingSource pub(super) fn set_up_readable_byte_stream_controller_from_underlying_source( ctx: &Ctx<'js>, stream: ReadableStreamOwned<'js>, underlying_source: Null>>, underlying_source_dict: UnderlyingSource<'js>, high_water_mark: f64, ) -> Result<()> { let (start_algorithm, pull_algorithm, cancel_algorithm, auto_allocate_chunk_size) = ( // If underlyingSourceDict["start"] exists, then set startAlgorithm to an algorithm which returns the result of invoking underlyingSourceDict["start"] with argument list // « controller » and callback this value underlyingSource. underlying_source_dict .start .map(|f| StartAlgorithm::Function { f, underlying_source: underlying_source.clone(), }) .unwrap_or(StartAlgorithm::ReturnUndefined), // If underlyingSourceDict["pull"] exists, then set pullAlgorithm to an algorithm which returns the result of invoking underlyingSourceDict["pull"] with argument list // « controller » and callback this value underlyingSource. underlying_source_dict .pull .map(|f| PullAlgorithm::Function { f, underlying_source: underlying_source.clone(), }) .unwrap_or(PullAlgorithm::ReturnPromiseUndefined), // If underlyingSourceDict["cancel"] exists, then set cancelAlgorithm to an algorithm which takes an argument reason and returns the result of invoking underlyingSourceDict["cancel"] with argument list // « reason » and callback this value underlyingSource. underlying_source_dict .cancel .map(|f| CancelAlgorithm::Function { f, underlying_source, }) .unwrap_or(CancelAlgorithm::ReturnPromiseUndefined), // Let autoAllocateChunkSize be underlyingSourceDict["autoAllocateChunkSize"], if it exists, or undefined otherwise. underlying_source_dict.auto_allocate_chunk_size, ); // If autoAllocateChunkSize is 0, then throw a TypeError exception. if auto_allocate_chunk_size == Some(0) { return Err(Exception::throw_type( ctx, "autoAllocateChunkSize must be greater than 0", )); } Self::set_up_readable_byte_stream_controller( ctx.clone(), stream, start_algorithm, pull_algorithm, cancel_algorithm, high_water_mark, auto_allocate_chunk_size, )?; Ok(()) } pub(super) fn set_up_readable_byte_stream_controller( ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>, start_algorithm: StartAlgorithm<'js>, pull_algorithm: PullAlgorithm<'js>, cancel_algorithm: CancelAlgorithm<'js>, high_water_mark: f64, auto_allocate_chunk_size: Option, ) -> Result> { let (stream_class, mut stream) = class_from_owned_borrow_mut(stream); let array_constructor_primordials = ArrayConstructorPrimordials::get(&ctx)?.clone(); let BasePrimordials { constructor_array_buffer, function_array_buffer_is_view, .. } = &*BasePrimordials::get(&ctx)?; let controller = Self { // Set controller.[[stream]] to stream. stream: stream_class, // Set controller.[[pullAgain]] and controller.[[pulling]] to false. pull_again: false, pulling: false, // Set controller.[[byobRequest]] to null. byob_request: None, // Perform ! ResetQueue(controller). queue: VecDeque::new(), queue_total_size: 0, // Set controller.[[closeRequested]] and controller.[[started]] to false. close_requested: false, started: false, // Set controller.[[strategyHWM]] to highWaterMark. strategy_hwm: high_water_mark, // Set controller.[[pullAlgorithm]] to pullAlgorithm. pull_algorithm: Some(pull_algorithm), cancel_algorithm: Some(cancel_algorithm), // Set controller.[[autoAllocateChunkSize]] to autoAllocateChunkSize. auto_allocate_chunk_size, pending_pull_intos: VecDeque::new(), array_constructor_primordials, constructor_array_buffer: constructor_array_buffer.clone(), function_array_buffer_is_view: function_array_buffer_is_view.clone(), }; let controller_class = Class::instance(ctx.clone(), controller)?; // Set stream.[[controller]] to controller. stream.controller = ReadableStreamControllerClass::ReadableStreamByteController(controller_class.clone()); let objects = ReadableStreamObjects::new_byte(stream, OwnedBorrowMut::from_class(controller_class)) .refresh_reader(); let promise_primordials = objects.stream.promise_primordials.clone(); // Let startResult be the result of performing startAlgorithm. let (start_result, objects_class) = Self::start_algorithm(ctx.clone(), objects, start_algorithm)?; // Let startPromise be a promise resolved with startResult. let start_promise = promise_resolved_with(&ctx, &promise_primordials, Ok(start_result))?; let _ = upon_promise::, _>(ctx.clone(), start_promise, { let objects_class = objects_class.clone(); move |ctx, result| { let mut objects = ReadableStreamObjects::from_class_no_reader(objects_class).refresh_reader(); match result { // Upon fulfillment of startPromise, Ok(_) => { // Set controller.[[started]] to true. objects.controller.started = true; // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). Self::readable_byte_stream_controller_call_pull_if_needed(ctx, objects)?; Ok(()) }, // Upon rejection of startPromise with reason r, Err(r) => { // Perform ! ReadableByteStreamControllerError(controller, r). Self::readable_byte_stream_controller_error(objects, r)?; Ok(()) }, } } })?; Ok(objects_class.controller) } fn readable_byte_stream_controller_call_pull_if_needed>( ctx: Ctx<'js>, objects: ReadableByteStreamObjects<'js, R>, ) -> Result> { // Let shouldPull be ! ReadableByteStreamControllerShouldCallPull(controller). let (should_pull, mut objects) = Self::readable_byte_stream_controller_should_call_pull(objects); // If shouldPull is false, return. if !should_pull { return Ok(objects); } // If controller.[[pulling]] is true, if objects.controller.pulling { // Set controller.[[pullAgain]] to true. objects.controller.pull_again = true; // Return. return Ok(objects); } // Set controller.[[pulling]] to true. objects.controller.pulling = true; // Let pullPromise be the result of performing controller.[[pullAlgorithm]]. let (pull_promise, objects_class) = Self::pull_algorithm(ctx.clone(), objects)?; upon_promise::, ()>(ctx, pull_promise, { let objects_class = objects_class.clone(); move |ctx, result| { let mut objects = ReadableStreamObjects::from_class_no_reader(objects_class).refresh_reader(); match result { // Upon fulfillment of pullPromise, Ok(_) => { // Set controller.[[pulling]] to false. objects.controller.pulling = false; // If controller.[[pullAgain]] is true, if objects.controller.pull_again { // Set controller.[[pullAgain]] to false. objects.controller.pull_again = false; // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). Self::readable_byte_stream_controller_call_pull_if_needed( ctx, objects, )?; }; Ok(()) }, // Upon rejection of pullPromise with reason e, Err(e) => { // Perform ! ReadableByteStreamControllerError(controller, e). Self::readable_byte_stream_controller_error(objects, e)?; Ok(()) }, } } })?; Ok(ReadableStreamObjects::from_class(objects_class)) } fn readable_byte_stream_controller_should_call_pull>( mut objects: ReadableByteStreamObjects<'js, R>, ) -> (bool, ReadableByteStreamObjects<'js, R>) { // Let stream be controller.[[stream]]. match objects.stream.state { ReadableStreamState::Readable => {}, // If stream.[[state]] is not "readable", return false. _ => return (false, objects), } // If controller.[[closeRequested]] is true, return false. if objects.controller.close_requested { return (false, objects); } // If controller.[[started]] is false, return false. if !objects.controller.started { return (false, objects); } let (mut has_read_requests, mut has_read_into_requests) = (false, false); objects = objects .with_reader( |objects| { // If ! ReadableStreamHasDefaultReader(stream) is true and ! ReadableStreamGetNumReadRequests(stream) > 0, return true. if ReadableStream::readable_stream_get_num_read_requests(&objects.reader) > 0 { has_read_requests = true; } Ok(objects) }, |objects| { // If ! ReadableStreamHasBYOBReader(stream) is true and ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true. if ReadableStream::readable_stream_get_num_read_into_requests(&objects.reader) > 0 { has_read_into_requests = true; } Ok(objects) }, Ok, ) .unwrap(); if has_read_requests || has_read_into_requests { return (true, objects); } // Let desiredSize be ! ReadableByteStreamControllerGetDesiredSize(controller). let desired_size = objects .controller .readable_byte_stream_controller_get_desired_size(&objects.stream); // Assert: desiredSize is not null. if desired_size.0.expect("desired_size must not be null") > 0.0 { // If desiredSize > 0, return true. return (true, objects); } // Return false. (false, objects) } pub(super) fn readable_byte_stream_controller_error>( // Let stream be controller.[[stream]]. mut objects: ReadableByteStreamObjects<'js, R>, e: Value<'js>, ) -> Result> { // If stream.[[state]] is not "readable", return. if !matches!(objects.stream.state, ReadableStreamState::Readable) { return Ok(objects); }; // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller). objects .controller .readable_byte_stream_controller_clear_pending_pull_intos(); // Perform ! ResetQueue(controller). objects.controller.reset_queue(); // Perform ! ReadableByteStreamControllerClearAlgorithms(controller). objects .controller .readable_byte_stream_controller_clear_algorithms(); // Perform ! ReadableStreamError(stream, e). ReadableStream::readable_stream_error(objects, e) } fn readable_byte_stream_controller_clear_pending_pull_intos(&mut self) { // Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). self.readable_byte_stream_controller_invalidate_byob_request(); // Set controller.[[pendingPullIntos]] to a new empty list. self.pending_pull_intos.clear(); } fn readable_byte_stream_controller_invalidate_byob_request(&mut self) { let byob_request = match self.byob_request { // If controller.[[byobRequest]] is null, return. None => return, Some(ref byob_request) => byob_request.clone(), }; let mut byob_request = OwnedBorrowMut::from_class(byob_request); byob_request.controller = None; byob_request.view = None; self.byob_request = None; } fn readable_byte_stream_controller_clear_algorithms(&mut self) { self.pull_algorithm = None; self.cancel_algorithm = None; } pub(super) fn readable_byte_stream_controller_get_byob_request( ctx: Ctx<'js>, controller: OwnedBorrowMut<'js, Self>, ) -> Result<( Null>>, OwnedBorrowMut<'js, Self>, )> { // If controller.[[byobRequest]] is null and controller.[[pendingPullIntos]] is not empty, if controller.byob_request.is_none() && !controller.pending_pull_intos.is_empty() { // Let firstDescriptor be controller.[[pendingPullIntos]][0]. let first_descriptor = &controller.pending_pull_intos[0]; // Let view be ! Construct(%Uint8Array%, « firstDescriptor’s buffer, firstDescriptor’s byte offset + firstDescriptor’s bytes filled, firstDescriptor’s byte length − firstDescriptor’s bytes filled »). let view = ViewBytes::from_value( &ctx, &controller.function_array_buffer_is_view, Some( &controller .array_constructor_primordials .constructor_uint8array .construct(( first_descriptor.buffer.clone(), first_descriptor.byte_offset + first_descriptor.bytes_filled, first_descriptor.byte_length - first_descriptor.bytes_filled, ))?, ), )?; let (controller_class, mut controller) = class_from_owned_borrow_mut(controller); // Let byobRequest be a new ReadableStreamBYOBRequest. let byob_request = ReadableStreamBYOBRequest { // Set byobRequest.[[controller]] to controller. controller: Some(controller_class), // Set byobRequest.[[view]] to view. view: Some(view), }; // Set controller.[[byobRequest]] to byobRequest. controller.byob_request = Some(Class::instance(ctx, byob_request)?); Ok((Null(controller.byob_request.clone()), controller)) } else { // Return controller.[[byobRequest]]. Ok((Null(controller.byob_request.clone()), controller)) } } fn readable_byte_stream_controller_get_desired_size( &self, stream: &ReadableStream<'js>, ) -> Null { // Let state be controller.[[stream]].[[state]]. match stream.state { // If state is "errored", return null. ReadableStreamState::Errored(_) => Null(None), // If state is "closed", return 0. ReadableStreamState::Closed => Null(Some(0.0)), // Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. _ => Null(Some(self.strategy_hwm - self.queue_total_size as f64)), } } fn reset_queue(&mut self) { // Set container.[[queue]] to a new empty list. self.queue.clear(); // Set container.[[queueTotalSize]] to 0. self.queue_total_size = 0; } pub(super) fn readable_byte_stream_controller_close>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: ReadableByteStreamObjects<'js, R>, ) -> Result> { // If controller.[[closeRequested]] is true or stream.[[state]] is not "readable", return. if objects.controller.close_requested || !matches!(objects.stream.state, ReadableStreamState::Readable) { return Ok(objects); } // If controller.[[queueTotalSize]] > 0, if objects.controller.queue_total_size > 0 { // Set controller.[[closeRequested]] to true. objects.controller.close_requested = true; // Return. return Ok(objects); } // If controller.[[pendingPullIntos]] is not empty, // Let firstPendingPullInto be controller.[[pendingPullIntos]][0]. if let Some(first_pending_pull_into) = objects.controller.pending_pull_intos.front() { // If the remainder after dividing firstPendingPullInto’s bytes filled by firstPendingPullInto’s element size is not 0, if first_pending_pull_into.bytes_filled % first_pending_pull_into.element_size != 0 { // Let e be a new TypeError exception. let e: Value = objects .stream .constructor_type_error .call(("Insufficient bytes to fill elements in the given buffer",))?; Self::readable_byte_stream_controller_error(objects, e.clone())?; return Err(ctx.throw(e)); } } // Perform ! ReadableByteStreamControllerClearAlgorithms(controller). objects .controller .readable_byte_stream_controller_clear_algorithms(); // Perform ! ReadableStreamClose(stream). ReadableStream::readable_stream_close(ctx, objects) } pub(super) fn readable_byte_stream_controller_enqueue>( ctx: &Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: ReadableByteStreamObjects<'js, R>, chunk: ViewBytes<'js>, ) -> Result> { // If controller.[[closeRequested]] is true or stream.[[state]] is not "readable", return. if objects.controller.close_requested || !matches!(objects.stream.state, ReadableStreamState::Readable) { return Ok(objects); }; // Let buffer be chunk.[[ViewedArrayBuffer]]. // Let byteOffset be chunk.[[ByteOffset]]. // Let byteLength be chunk.[[ByteLength]]. let (buffer, byte_length, byte_offset) = chunk.get_array_buffer()?; // If ! IsDetachedBuffer(buffer) is true, throw a TypeError exception. buffer.as_raw().ok_or(Exception::throw_type( ctx, "chunk's buffer is detached and so cannot be enqueued", ))?; // Let transferredBuffer be ? TransferArrayBuffer(buffer). let transferred_buffer = transfer_array_buffer(buffer)?; // If controller.[[pendingPullIntos]] is not empty, // Let firstPendingPullInto be controller.[[pendingPullIntos]][0]. if !objects.controller.pending_pull_intos.is_empty() { // If ! IsDetachedBuffer(firstPendingPullInto’s buffer) is true, throw a TypeError exception. objects.controller.pending_pull_intos[0] .buffer .as_raw() .or_throw_type( ctx, "The BYOB request's buffer has been detached and so cannot be filled with an enqueued chunk", )?; // Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). objects .controller .readable_byte_stream_controller_invalidate_byob_request(); // Set firstPendingPullInto’s buffer to ! TransferArrayBuffer(firstPendingPullInto’s buffer). objects.controller.pending_pull_intos[0].buffer = transfer_array_buffer(objects.controller.pending_pull_intos[0].buffer.clone())?; // If firstPendingPullInto’s reader type is "none", perform ? ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller, firstPendingPullInto). if let PullIntoDescriptorReaderType::None = objects.controller.pending_pull_intos[0].reader_type { objects = Self::readable_byte_stream_enqueue_detached_pull_into_to_queue( ctx.clone(), objects, 0, )?; } } objects = objects.with_reader( // If ! ReadableStreamHasDefaultReader(stream) is true, |mut objects| { // Perform ! ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller). objects = Self::readable_byte_stream_controller_process_read_requests_using_queue( objects, ctx, )?; // If ! ReadableStreamGetNumReadRequests(stream) is 0, if ReadableStream::readable_stream_get_num_read_requests(&objects.reader) == 0 { // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength). objects .controller .readable_byte_stream_controller_enqueue_chunk_to_queue( transferred_buffer.clone(), byte_offset, byte_length, ) } else { // Otherwise, // If controller.[[pendingPullIntos]] is not empty, if !objects.controller.pending_pull_intos.is_empty() { // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). objects .controller .readable_byte_stream_controller_shift_pending_pull_into(); } // Let transferredView be ! Construct(%Uint8Array%, « transferredBuffer, byteOffset, byteLength »). let transferred_view = ViewBytes::from_value( ctx, &objects.controller.function_array_buffer_is_view, Some( &objects .controller .array_constructor_primordials .constructor_uint8array .construct(( transferred_buffer.clone(), byte_offset, byte_length, ))?, ), ); // Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false). objects = ReadableStream::readable_stream_fulfill_read_request( ctx, objects, transferred_view.into_js(ctx)?, false, )?; } Ok(objects) }, |mut objects| { // Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true, // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength). objects .controller .readable_byte_stream_controller_enqueue_chunk_to_queue( transferred_buffer.clone(), byte_offset, byte_length, ); // Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). Self::readable_byte_stream_controller_process_pull_into_descriptors_using_queue( ctx, objects, ) }, |mut objects| { // Otherwise, // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, transferredBuffer, byteOffset, byteLength). objects .controller .readable_byte_stream_controller_enqueue_chunk_to_queue( transferred_buffer.clone(), byte_offset, byte_length, ); Ok(objects) }, )?; // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). Self::readable_byte_stream_controller_call_pull_if_needed(ctx.clone(), objects) } fn readable_byte_stream_enqueue_detached_pull_into_to_queue>( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, R>, pull_into_descriptor_index: usize, ) -> Result> { let pull_into_descriptor = &objects.controller.pending_pull_intos[pull_into_descriptor_index]; // If pullIntoDescriptor’s bytes filled > 0, perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller, pullIntoDescriptor’s buffer, pullIntoDescriptor’s byte offset, pullIntoDescriptor’s bytes filled). if pull_into_descriptor.bytes_filled > 0 { let buffer = pull_into_descriptor.buffer.clone(); let byte_offset = pull_into_descriptor.byte_offset; let bytes_filled = pull_into_descriptor.bytes_filled; objects = Self::readable_byte_stream_controller_enqueue_cloned_chunk_to_queue( ctx, objects, &buffer, byte_offset, bytes_filled, )?; } // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). objects .controller .readable_byte_stream_controller_shift_pending_pull_into(); Ok(objects) } fn readable_byte_stream_controller_process_read_requests_using_queue( mut objects: ReadableStreamDefaultReaderObjects<'js, OwnedBorrowMut<'js, Self>>, ctx: &Ctx<'js>, ) -> Result>> { // While reader.[[readRequests]] is not empty, while !objects.reader.read_requests.is_empty() { // If controller.[[queueTotalSize]] is 0, return. if objects.controller.queue_total_size == 0 { return Ok(objects); } // Let readRequest be reader.[[readRequests]][0]. // Remove readRequest from reader.[[readRequests]]. let read_request = objects.reader.read_requests.pop_front().unwrap(); // Perform ! ReadableByteStreamControllerFillReadRequestFromQueue(controller, readRequest). objects = Self::readable_byte_stream_controller_fill_read_request_from_queue( ctx, objects, read_request, )?; } Ok(objects) } fn readable_byte_stream_controller_shift_pending_pull_into( &mut self, ) -> PullIntoDescriptor<'js> { // Let descriptor be controller.[[pendingPullIntos]][0]. // Remove descriptor from controller.[[pendingPullIntos]]. // Return descriptor. self.pending_pull_intos.pop_front().expect( "ReadableByteStreamControllerShiftPendingPullInto called on empty pendingPullIntos", ) } fn readable_byte_stream_controller_enqueue_chunk_to_queue( &mut self, buffer: ArrayBuffer<'js>, byte_offset: usize, byte_length: usize, ) { let len = buffer.len(); // Append a new readable byte stream queue entry with buffer buffer, byte offset byteOffset, and byte length byteLength to controller.[[queue]]. self.queue.push_back(ReadableByteStreamQueueEntry { buffer, byte_offset, byte_length, }); // Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]] + byteLength. self.queue_total_size += len; } fn readable_byte_stream_controller_process_pull_into_descriptors_using_queue< R: ReadableStreamReader<'js>, >( ctx: &Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, R>, ) -> Result> { // While controller.[[pendingPullIntos]] is not empty, while !objects.controller.pending_pull_intos.is_empty() { // If controller.[[queueTotalSize]] is 0, return. if objects.controller.queue_total_size == 0 { return Ok(objects); } // Let pullIntoDescriptor be controller.[[pendingPullIntos]][0]. let mut pull_into_descriptor_ref = PullIntoDescriptorRefMut::Index(0); // If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) is true, if objects .controller .readable_byte_stream_controller_fill_pull_into_descriptor_from_queue( ctx, &mut pull_into_descriptor_ref, )? { // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). let pull_into_descriptor = objects .controller .readable_byte_stream_controller_shift_pending_pull_into(); // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]], pullIntoDescriptor). objects = Self::readable_byte_stream_controller_commit_pull_into_descriptor( ctx.clone(), objects, pull_into_descriptor, )?; } } Ok(objects) } fn readable_byte_stream_controller_enqueue_cloned_chunk_to_queue< R: ReadableStreamReader<'js>, >( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, R>, buffer: &ArrayBuffer<'js>, byte_offset: usize, byte_length: usize, ) -> Result> { // Let cloneResult be CloneArrayBuffer(buffer, byteOffset, byteLength, %ArrayBuffer%). let clone_result = match ArrayBuffer::new_copy( ctx.clone(), &buffer.as_bytes().expect( "ReadableByteStreamControllerEnqueueClonedChunkToQueue called on detached buffer", )[byte_offset..byte_offset + byte_length], ) { Ok(clone_result) => clone_result, Err(Error::Exception) => { let err = ctx.catch(); Self::readable_byte_stream_controller_error(objects, err.clone())?; return Err(ctx.throw(err)); }, Err(err) => return Err(err), }; // Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, cloneResult.[[Value]], 0, byteLength). objects .controller .readable_byte_stream_controller_enqueue_chunk_to_queue(clone_result, 0, byte_length); Ok(objects) } fn readable_byte_stream_controller_fill_read_request_from_queue( ctx: &Ctx<'js>, mut objects: ReadableStreamDefaultReaderObjects<'js, OwnedBorrowMut<'js, Self>>, read_request: impl ReadableStreamReadRequest<'js>, ) -> Result>> { let entry = { // Assert: controller.[[queueTotalSize]] > 0. // Let entry be controller.[[queue]][0]. // Remove entry from controller.[[queue]]. let entry = objects.controller.queue.pop_front().expect( "ReadableByteStreamControllerFillReadRequestFromQueue called with empty queue", ); // Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]] − entry’s byte length. objects.controller.queue_total_size -= entry.byte_length; entry }; // Perform ! ReadableByteStreamControllerHandleQueueDrain(controller). objects = Self::readable_byte_stream_controller_handle_queue_drain(ctx.clone(), objects)?; // Let view be ! Construct(%Uint8Array%, « entry’s buffer, entry’s byte offset, entry’s byte length »). let view: TypedArray = objects .controller .array_constructor_primordials .constructor_uint8array .construct((entry.buffer, entry.byte_offset, entry.byte_length))?; // Perform readRequest’s chunk steps, given view. read_request.chunk_steps_typed(objects, view.into_value()) } fn readable_byte_stream_controller_fill_pull_into_descriptor_from_queue<'a>( &'a mut self, ctx: &Ctx<'js>, pull_into_descriptor_ref: &mut PullIntoDescriptorRefMut<'js, 'a>, ) -> Result { let (mut total_bytes_to_copy_remaining, ready) = { let pull_into_descriptor = match pull_into_descriptor_ref { PullIntoDescriptorRefMut::Index(i) => &mut self.pending_pull_intos[*i], PullIntoDescriptorRefMut::Owned(r) => r, }; // Let maxBytesToCopy be min(controller.[[queueTotalSize]], pullIntoDescriptor’s byte length − pullIntoDescriptor’s bytes filled). let max_bytes_to_copy: usize = std::cmp::min( self.queue_total_size, pull_into_descriptor.byte_length - pull_into_descriptor.bytes_filled, ); // Let maxBytesFilled be pullIntoDescriptor’s bytes filled + maxBytesToCopy. let max_bytes_filled = pull_into_descriptor.bytes_filled + max_bytes_to_copy; // Let totalBytesToCopyRemaining be maxBytesToCopy. let mut total_bytes_to_copy_remaining = max_bytes_to_copy; // Let ready be false. let mut ready = false; // Let remainderBytes be the remainder after dividing maxBytesFilled by pullIntoDescriptor’s element size. let remainder_bytes = max_bytes_filled % pull_into_descriptor.element_size; // Let maxAlignedBytes be maxBytesFilled − remainderBytes. let max_aligned_bytes = max_bytes_filled - remainder_bytes; // If maxAlignedBytes ≥ pullIntoDescriptor’s minimum fill, if max_aligned_bytes >= pull_into_descriptor.minimum_fill { // Set totalBytesToCopyRemaining to maxAlignedBytes − pullIntoDescriptor’s bytes filled. total_bytes_to_copy_remaining = max_aligned_bytes - pull_into_descriptor.bytes_filled; // Set ready to true. ready = true } (total_bytes_to_copy_remaining, ready) }; // Let queue be controller.[[queue]]. // While totalBytesToCopyRemaining > 0, while total_bytes_to_copy_remaining > 0 { let bytes_to_copy = { let pull_into_descriptor = match pull_into_descriptor_ref { PullIntoDescriptorRefMut::Index(i) => &mut self.pending_pull_intos[*i], PullIntoDescriptorRefMut::Owned(r) => r, }; // Let headOfQueue be queue[0]. let head_of_queue = self .queue .front_mut() .expect("empty queue with bytes to copy"); // Let bytesToCopy be min(totalBytesToCopyRemaining, headOfQueue’s byte length). let bytes_to_copy: usize = std::cmp::min(total_bytes_to_copy_remaining, head_of_queue.byte_length); // Let destStart be pullIntoDescriptor’s byte offset + pullIntoDescriptor’s bytes filled. let dest_start: usize = pull_into_descriptor.byte_offset + pull_into_descriptor.bytes_filled; // Perform ! CopyDataBlockBytes(pullIntoDescriptor’s buffer.[[ArrayBufferData]], destStart, headOfQueue’s buffer.[[ArrayBufferData]], headOfQueue’s byte offset, bytesToCopy). copy_data_block_bytes( ctx, &pull_into_descriptor.buffer, dest_start, &head_of_queue.buffer, head_of_queue.byte_offset, bytes_to_copy, )?; if head_of_queue.byte_length == bytes_to_copy { // If headOfQueue’s byte length is bytesToCopy, // Remove queue[0]. self.queue.pop_front(); } else { // Otherwise, // Set headOfQueue’s byte offset to headOfQueue’s byte offset + bytesToCopy. head_of_queue.byte_offset += bytes_to_copy; // Set headOfQueue’s byte length to headOfQueue’s byte length − bytesToCopy. head_of_queue.byte_length -= bytes_to_copy } // Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]] − bytesToCopy. self.queue_total_size -= bytes_to_copy; bytes_to_copy }; // Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesToCopy, pullIntoDescriptor). self.readable_byte_stream_controller_fill_head_pull_into_descriptor( bytes_to_copy, pull_into_descriptor_ref, ); // Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − bytesToCopy. total_bytes_to_copy_remaining -= bytes_to_copy } Ok(ready) } fn readable_byte_stream_controller_commit_pull_into_descriptor>( ctx: Ctx<'js>, objects: ReadableByteStreamObjects<'js, R>, pull_into_descriptor: PullIntoDescriptor<'js>, ) -> Result> { // Let done be false. let mut done = false; // If stream.[[state]] is "closed", if matches!(objects.stream.state, ReadableStreamState::Closed) { // Set done to true. done = true } let reader_type = pull_into_descriptor.reader_type; // Let filledView be ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). let filled_view = Self::readable_byte_stream_controller_convert_pull_into_descriptor( ctx.clone(), &objects.stream.function_array_buffer_is_view, pull_into_descriptor, )?; if let PullIntoDescriptorReaderType::Default = reader_type { // If pullIntoDescriptor’s reader type is "default", objects.with_assert_default_reader(|objects| { // Perform ! ReadableStreamFulfillReadRequest(stream, filledView, done). ReadableStream::readable_stream_fulfill_read_request( &ctx, objects, filled_view.into_js(&ctx)?, done, ) }) } else { // Otherwise, objects.with_assert_byob_reader(|objects| { // Perform ! ReadableStreamFulfillReadIntoRequest(stream, filledView, done). ReadableStream::readable_stream_fulfill_read_into_request( &ctx, objects, filled_view, done, ) }) } } fn readable_byte_stream_controller_handle_queue_drain>( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, R>, ) -> Result> { // If controller.[[queueTotalSize]] is 0 and controller.[[closeRequested]] is true, if objects.controller.queue_total_size == 0 && objects.controller.close_requested { // Perform ! ReadableByteStreamControllerClearAlgorithms(controller). objects .controller .readable_byte_stream_controller_clear_algorithms(); // Perform ! ReadableStreamClose(controller.[[stream]]). ReadableStream::readable_stream_close(ctx, objects) } else { // Otherwise, // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). Self::readable_byte_stream_controller_call_pull_if_needed(ctx.clone(), objects) } } fn readable_byte_stream_controller_convert_pull_into_descriptor( ctx: Ctx<'js>, function_array_buffer_is_view: &Function<'js>, pull_into_descriptor: PullIntoDescriptor<'js>, ) -> Result> { let PullIntoDescriptor { // Let bytesFilled be pullIntoDescriptor’s bytes filled. bytes_filled, // Let elementSize be pullIntoDescriptor’s element size. element_size, byte_offset, buffer, .. } = pull_into_descriptor; // Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer). let buffer = transfer_array_buffer(buffer); // Return ! Construct(pullIntoDescriptor’s view constructor, « buffer, pullIntoDescriptor’s byte offset, bytesFilled ÷ elementSize »). let view: Object = pull_into_descriptor.view_constructor.construct(( buffer, byte_offset, bytes_filled / element_size, ))?; ViewBytes::from_object(&ctx, function_array_buffer_is_view, &view) } pub(super) fn readable_byte_stream_controller_pull_into( ctx: &Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: ReadableStreamBYOBObjects<'js>, view: ViewBytes<'js>, min: u64, read_into_request: impl ReadableStreamReadIntoRequest<'js> + 'js, ) -> Result> { // Set elementSize to the element size specified in the typed array constructors table for view.[[TypedArrayName]]. // Set ctor to the constructor specified in the typed array constructors table for view.[[TypedArrayName]]. let (element_size, ctor) = ( view.element_size(), objects .controller .array_constructor_primordials .for_view_bytes(&view), ); // Let minimumFill be min × elementSize. let minimum_fill: usize = (min as usize) * element_size; // Let byteOffset be view.[[ByteOffset]]. // Let byteLength be view.[[ByteLength]]. let (buffer, byte_length, byte_offset) = view.get_array_buffer()?; // Let bufferResult be TransferArrayBuffer(view.[[ViewedArrayBuffer]]). let buffer_result = transfer_array_buffer(buffer); let buffer = match buffer_result { // If bufferResult is an abrupt completion, Err(Error::Exception) => { // Perform readIntoRequest’s error steps, given bufferResult.[[Value]]. objects = read_into_request.error_steps(objects, ctx.catch())?; // Return. return Ok(objects); }, Err(err) => return Err(err), // Let buffer be bufferResult.[[Value]]. Ok(buffer) => buffer, }; let buffer_byte_length = buffer.len(); // Let pullIntoDescriptor be a new pull-into descriptor with let mut pull_into_descriptor = PullIntoDescriptor { buffer, buffer_byte_length, byte_offset, byte_length, bytes_filled: 0, minimum_fill, element_size, view_constructor: ctor.clone(), reader_type: PullIntoDescriptorReaderType::Byob, }; // If controller.[[pendingPullIntos]] is not empty, if !objects.controller.pending_pull_intos.is_empty() { // Append pullIntoDescriptor to controller.[[pendingPullIntos]]. objects .controller .pending_pull_intos .push_back(pull_into_descriptor); // Perform ! ReadableStreamAddReadIntoRequest(stream, readIntoRequest). ReadableStream::readable_stream_add_read_into_request( &mut objects.reader, read_into_request, ); // Return. return Ok(objects); } // If stream.[[state]] is "closed", if matches!(objects.stream.state, ReadableStreamState::Closed) { // Let emptyView be ! Construct(ctor, « pullIntoDescriptor’s buffer, pullIntoDescriptor’s byte offset, 0 »). let empty_view: Value<'js> = ctor.construct(( pull_into_descriptor.buffer, pull_into_descriptor.byte_offset, 0, ))?; // Perform readIntoRequest’s close steps, given emptyView. objects = read_into_request.close_steps(objects, empty_view)?; // Return. return Ok(objects); } // If controller.[[queueTotalSize]] > 0, if objects.controller.queue_total_size > 0 { // If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) is true, if objects .controller .readable_byte_stream_controller_fill_pull_into_descriptor_from_queue( ctx, &mut PullIntoDescriptorRefMut::Owned(&mut pull_into_descriptor), )? { // Let filledView be ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). let filled_view = objects .controller .readable_byte_steam_controller_convert_pull_into_descriptor( pull_into_descriptor, )?; // Perform ! ReadableByteStreamControllerHandleQueueDrain(controller). objects = Self::readable_byte_stream_controller_handle_queue_drain(ctx.clone(), objects)?; // Perform readIntoRequest’s chunk steps, given filledView. // Return. return read_into_request.chunk_steps(objects, filled_view); } // If controller.[[closeRequested]] is true, if objects.controller.close_requested { // Let e be a TypeError exception. let e: Value = objects .stream .constructor_type_error .call(("Insufficient bytes to fill elements in the given buffer",))?; // Perform ! ReadableByteStreamControllerError(controller, e). objects = Self::readable_byte_stream_controller_error(objects, e.clone())?; // Perform readIntoRequest’s error steps, given e. // Return. return read_into_request.error_steps(objects, e); } } // Append pullIntoDescriptor to controller.[[pendingPullIntos]]. objects .controller .pending_pull_intos .push_back(pull_into_descriptor); // Perform ! ReadableStreamAddReadIntoRequest(stream, readIntoRequest). ReadableStream::readable_stream_add_read_into_request( &mut objects.reader, read_into_request, ); // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). Self::readable_byte_stream_controller_call_pull_if_needed(ctx.clone(), objects) } fn readable_byte_steam_controller_convert_pull_into_descriptor( &mut self, pull_into_descriptor: PullIntoDescriptor<'js>, ) -> Result> { // Let bytesFilled be pullIntoDescriptor’s bytes filled. let bytes_filled = pull_into_descriptor.bytes_filled; // Let elementSize be pullIntoDescriptor’s element size. let element_size = pull_into_descriptor.element_size; // Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer). let buffer = transfer_array_buffer(pull_into_descriptor.buffer)?; // Return ! Construct(pullIntoDescriptor’s view constructor, « buffer, pullIntoDescriptor’s byte offset, bytesFilled ÷ elementSize »). pull_into_descriptor.view_constructor.construct(( buffer, pull_into_descriptor.byte_offset, bytes_filled / element_size, )) } pub(super) fn readable_byte_stream_controller_respond>( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, R>, bytes_written: usize, ) -> Result<()> { // Let firstDescriptor be controller.[[pendingPullIntos]][0]. let first_descriptor = &mut objects.controller.pending_pull_intos[0]; // Let state be controller.[[stream]].[[state]]. match objects.stream.state { // If state is "closed", ReadableStreamState::Closed => { // If bytesWritten is not 0, throw a TypeError exception. if bytes_written != 0 { return Err(Exception::throw_type( &ctx, "bytesWritten must be 0 when calling respond() on a closed stream", )); } }, // Otherwise, _ => { // If bytesWritten is 0, throw a TypeError exception. if bytes_written == 0 { return Err(Exception::throw_type( &ctx, "bytesWritten must be greater than 0 when calling respond() on a readable stream", )); } // If firstDescriptor’s bytes filled + bytesWritten > firstDescriptor’s byte length, throw a RangeError exception. if first_descriptor.bytes_filled + bytes_written > first_descriptor.byte_length { return Err(Exception::throw_range(&ctx, "bytesWritten out of range'")); } }, }; // Set firstDescriptor’s buffer to ! TransferArrayBuffer(firstDescriptor’s buffer). first_descriptor.buffer = transfer_array_buffer(first_descriptor.buffer.clone())?; // Perform ? ReadableByteStreamControllerRespondInternal(controller, bytesWritten). Self::readable_byte_stream_controller_respond_internal(ctx, objects, bytes_written) } fn readable_byte_stream_controller_respond_internal>( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, R>, bytes_written: usize, ) -> Result<()> { // Let firstDescriptor be controller.[[pendingPullIntos]][0]. let first_descriptor_index = 0; // Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). objects .controller .readable_byte_stream_controller_invalidate_byob_request(); // Let state be controller.[[stream]].[[state]]. match objects.stream.state { // If state is "closed", ReadableStreamState::Closed => { // Perform ! ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor). objects = Self::readable_byte_stream_controller_respond_in_closed_state( ctx.clone(), objects, first_descriptor_index, )?; }, // Otherwise _ => { // Perform ? ReadableByteStreamControllerRespondInReadableState(controller, bytesWritten, firstDescriptor). objects = Self::readable_byte_stream_controller_respond_in_readable_state( ctx.clone(), objects, bytes_written, first_descriptor_index, )? }, }; _ = Self::readable_byte_stream_controller_call_pull_if_needed(ctx, objects)?; Ok(()) } fn readable_byte_stream_controller_respond_in_closed_state>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: ReadableByteStreamObjects<'js, R>, first_descriptor_index: usize, ) -> Result> { // If firstDescriptor’s reader type is "none", perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). if let PullIntoDescriptorReaderType::None = objects.controller.pending_pull_intos[first_descriptor_index].reader_type { objects .controller .readable_byte_stream_controller_shift_pending_pull_into(); } // If ! ReadableStreamHasBYOBReader(stream) is true, objects.with_reader( Ok, |mut objects| { // While ! ReadableStreamGetNumReadIntoRequests(stream) > 0, while ReadableStream::readable_stream_get_num_read_into_requests(&objects.reader) > 0 { // Let pullIntoDescriptor be ! ReadableByteStreamControllerShiftPendingPullInto(controller). let pull_into_descriptor = objects .controller .readable_byte_stream_controller_shift_pending_pull_into(); // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor). objects = Self::readable_byte_stream_controller_commit_pull_into_descriptor( ctx.clone(), objects, pull_into_descriptor, )?; } Ok(objects) }, Ok, ) } fn readable_byte_stream_controller_respond_in_readable_state>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: ReadableByteStreamObjects<'js, R>, bytes_written: usize, pull_into_descriptor_index: usize, ) -> Result> { // Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesWritten, pullIntoDescriptor). objects .controller .readable_byte_stream_controller_fill_head_pull_into_descriptor( bytes_written, &mut PullIntoDescriptorRefMut::Index(pull_into_descriptor_index), ); // If pullIntoDescriptor’s reader type is "none", if let PullIntoDescriptorReaderType::None = objects.controller.pending_pull_intos[pull_into_descriptor_index].reader_type { // Perform ? ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller, pullIntoDescriptor). objects = Self::readable_byte_stream_enqueue_detached_pull_into_to_queue( ctx.clone(), objects, pull_into_descriptor_index, )?; // Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). // Return. return Self::readable_byte_stream_controller_process_pull_into_descriptors_using_queue( &ctx, objects, ); } // If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum fill, return. if objects.controller.pending_pull_intos[pull_into_descriptor_index].bytes_filled < objects.controller.pending_pull_intos[pull_into_descriptor_index].minimum_fill { return Ok(objects); } // Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). let mut pull_into_descriptor = objects .controller .readable_byte_stream_controller_shift_pending_pull_into(); // Let remainderSize be the remainder after dividing pullIntoDescriptor’s bytes filled by pullIntoDescriptor’s element size. let remainder_size = pull_into_descriptor.bytes_filled % pull_into_descriptor.element_size; // If remainderSize > 0, if remainder_size > 0 { // Let end be pullIntoDescriptor’s byte offset + pullIntoDescriptor’s bytes filled. let end = pull_into_descriptor.byte_offset + pull_into_descriptor.bytes_filled; let buffer = pull_into_descriptor.buffer.clone(); // Perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller, pullIntoDescriptor’s buffer, end − remainderSize, remainderSize). objects = Self::readable_byte_stream_controller_enqueue_cloned_chunk_to_queue( ctx.clone(), objects, &buffer, end - remainder_size, remainder_size, )?; } // Set pullIntoDescriptor’s bytes filled to pullIntoDescriptor’s bytes filled − remainderSize. pull_into_descriptor.bytes_filled -= remainder_size; // Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]], pullIntoDescriptor). objects = Self::readable_byte_stream_controller_commit_pull_into_descriptor( ctx.clone(), objects, pull_into_descriptor, )?; // Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). Self::readable_byte_stream_controller_process_pull_into_descriptors_using_queue( &ctx, objects, ) } pub(super) fn readable_byte_stream_controller_respond_with_new_view< R: ReadableStreamReader<'js>, >( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, R>, view: ViewBytes<'js>, ) -> Result<()> { // Let firstDescriptor be controller.[[pendingPullIntos]][0]. let first_descriptor_index = 0; let (buffer, byte_length, byte_offset) = view.get_array_buffer()?; // Let state be controller.[[stream]].[[state]]. match objects.stream.state { // If state is "closed", ReadableStreamState::Closed => { // If view.[[ByteLength]] is not 0, throw a TypeError exception. if byte_length != 0 { return Err(Exception::throw_type(&ctx, "The view's length must be 0 when calling respondWithNewView() on a closed stream")); } }, // Otherwise _ => { // If view.[[ByteLength]] is 0, throw a TypeError exception. if byte_length == 0 { return Err(Exception::throw_type(&ctx, "The view's length must be greater than 0 when calling respondWithNewView() on a readable stream")); } }, }; { let first_descriptor = &mut objects.controller.pending_pull_intos[first_descriptor_index]; // If firstDescriptor’s byte offset + firstDescriptor’ bytes filled is not view.[[ByteOffset]], throw a RangeError exception. if first_descriptor.byte_offset + first_descriptor.bytes_filled != byte_offset { return Err(Exception::throw_range( &ctx, "The region specified by view does not match byobRequest", )); }; // If firstDescriptor’s buffer byte length is not view.[[ViewedArrayBuffer]].[[ByteLength]], throw a RangeError exception. if first_descriptor.buffer_byte_length != buffer.len() { return Err(Exception::throw_range( &ctx, "The buffer of view has different capacity than byobRequest", )); }; // If firstDescriptor’s bytes filled + view.[[ByteLength]] > firstDescriptor’s byte length, throw a RangeError exception. if first_descriptor.bytes_filled + byte_length > first_descriptor.byte_length { return Err(Exception::throw_range( &ctx, "The region specified by view is larger than byobRequest", )); } // Set firstDescriptor’s buffer to ? TransferArrayBuffer(view.[[ViewedArrayBuffer]]). first_descriptor.buffer = transfer_array_buffer(buffer)?; } // Perform ? ReadableByteStreamControllerRespondInternal(controller, viewByteLength). Self::readable_byte_stream_controller_respond_internal(ctx, objects, byte_length) } fn readable_byte_stream_controller_fill_head_pull_into_descriptor<'a>( &mut self, size: usize, pull_into_descriptor_ref: &mut PullIntoDescriptorRefMut<'js, 'a>, ) { let pull_into_descriptor = match pull_into_descriptor_ref { PullIntoDescriptorRefMut::Index(i) => &mut self.pending_pull_intos[*i], PullIntoDescriptorRefMut::Owned(r) => *r, }; // Set pullIntoDescriptor’s bytes filled to bytes filled + size. pull_into_descriptor.bytes_filled += size; } fn start_algorithm>( ctx: Ctx<'js>, objects: ReadableByteStreamObjects<'js, R>, start_algorithm: StartAlgorithm<'js>, ) -> Result<( Value<'js>, ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>, )> { let objects_class = objects.into_inner(); Ok(( start_algorithm.call( ctx, ReadableStreamControllerClass::ReadableStreamByteController( objects_class.controller.clone(), ), )?, objects_class, )) } fn pull_algorithm>( ctx: Ctx<'js>, objects: ReadableByteStreamObjects<'js, R>, ) -> Result<( Promise<'js>, ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>, )> { let pull_algorithm = objects .controller .pull_algorithm .clone() .expect("pull algorithm used after ReadableStreamDefaultControllerClearAlgorithms"); let promise_primordials = objects.stream.promise_primordials.clone(); let objects_class = objects.into_inner(); Ok(( pull_algorithm.call( ctx, &promise_primordials, ReadableStreamControllerClass::ReadableStreamByteController( objects_class.controller.clone(), ), )?, objects_class, )) } fn cancel_algorithm>( ctx: Ctx<'js>, objects: ReadableByteStreamObjects<'js, R>, reason: Value<'js>, ) -> Result<( Promise<'js>, ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>, )> { let cancel_algorithm = objects.controller.cancel_algorithm.clone().expect( "cancel algorithm used after ReadableStreamDefaultControllerClearAlgorithms", ); let promise_primordials = objects.stream.promise_primordials.clone(); let objects_class = objects.into_inner(); Ok(( cancel_algorithm.call(ctx, &promise_primordials, reason)?, objects_class, )) } } #[methods(rename_all = "camelCase")] impl<'js> ReadableByteStreamController<'js> { #[qjs(constructor)] fn new(ctx: Ctx<'js>) -> Result> { Err(Exception::throw_type(&ctx, "Illegal constructor")) } // readonly attribute unrestricted double? desiredSize; #[qjs(get)] fn byob_request( ctx: Ctx<'js>, controller: This>, ) -> Result>>> { let (request, _) = Self::readable_byte_stream_controller_get_byob_request(ctx, controller.0)?; Ok(request) } // readonly attribute unrestricted double? desiredSize; #[qjs(get)] fn desired_size(&self) -> Null { let stream = OwnedBorrow::from_class(self.stream.clone()); self.readable_byte_stream_controller_get_desired_size(&stream) } // undefined close(); fn close(ctx: Ctx<'js>, controller: This>) -> Result<()> { // If this.[[closeRequested]] is true, throw a TypeError exception. if controller.close_requested { return Err(Exception::throw_type(&ctx, "close() called more than once")); } let objects = ReadableStreamObjects::from_byte_controller(controller.0).refresh_reader(); if !matches!(objects.stream.state, ReadableStreamState::Readable) { return Err(Exception::throw_type( &ctx, "close() called when stream is not readable", )); }; // Perform ? ReadableByteStreamControllerClose(this). Self::readable_byte_stream_controller_close(ctx, objects)?; Ok(()) } // undefined enqueue(ArrayBufferView chunk); fn enqueue( this: This>, ctx: Ctx<'js>, chunk: Value<'js>, ) -> Result<()> { let chunk = ViewBytes::from_value(&ctx, &this.function_array_buffer_is_view, Some(&chunk))?; let (array_buffer, byte_length, _) = chunk.get_array_buffer()?; // If chunk.[[ByteLength]] is 0, throw a TypeError exception. if byte_length == 0 { return Err(Exception::throw_type( &ctx, "chunk must have non-zero byteLength", )); } // If chunk.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0, throw a TypeError exception. if array_buffer.is_empty() { return Err(Exception::throw_type( &ctx, "chunk must have non-zero buffer byteLength", )); } // If this.[[closeRequested]] is true, throw a TypeError exception. if this.close_requested { return Err(Exception::throw_type(&ctx, "stream is closed or draining")); } let objects = ReadableStreamObjects::from_byte_controller(this.0).refresh_reader(); // If this.[[stream]].[[state]] is not "readable", throw a TypeError exception. if !matches!(objects.stream.state, ReadableStreamState::Readable) { return Err(Exception::throw_type( &ctx, "The stream is not in the readable state and cannot be enqueued to", )); }; // Return ? ReadableByteStreamControllerEnqueue(this, chunk). Self::readable_byte_stream_controller_enqueue(&ctx, objects, chunk)?; Ok(()) } // undefined error(optional any e); fn error( ctx: Ctx<'js>, controller: This>, e: Opt>, ) -> Result<()> { let objects = ReadableStreamObjects::from_byte_controller(controller.0).refresh_reader(); // Perform ! ReadableByteStreamControllerError(this, e). Self::readable_byte_stream_controller_error(objects, e.0.unwrap_or_undefined(&ctx))?; Ok(()) } } impl<'js> ReadableStreamController<'js> for ReadableByteStreamControllerOwned<'js> { type Class = ReadableByteStreamControllerClass<'js>; fn with_controller( self, ctx: C, _: impl FnOnce( C, ReadableStreamDefaultControllerOwned<'js>, ) -> Result<(O, ReadableStreamDefaultControllerOwned<'js>)>, byte: impl FnOnce( C, ReadableByteStreamControllerOwned<'js>, ) -> Result<(O, ReadableByteStreamControllerOwned<'js>)>, ) -> Result<(O, Self)> { let (ctx, reader) = byte(ctx, self)?; Ok((ctx, reader)) } fn into_inner(self) -> Self::Class { OwnedBorrowMut::into_inner(self) } fn from_class(class: Self::Class) -> Self { OwnedBorrowMut::from_class(class) } fn into_erased(self) -> ReadableStreamControllerOwned<'js> { ReadableStreamControllerOwned::ReadableStreamByteController(self) } fn try_from_erased(erased: ReadableStreamControllerOwned<'js>) -> Option { match erased { ReadableStreamControllerOwned::ReadableStreamDefaultController(_) => None, ReadableStreamControllerOwned::ReadableStreamByteController(r) => Some(r), } } fn pull_steps( ctx: &Ctx<'js>, mut objects: ReadableStreamDefaultReaderObjects<'js, Self>, read_request: impl ReadableStreamReadRequest<'js> + 'js, ) -> Result> { // If this.[[queueTotalSize]] > 0, if objects.controller.queue_total_size > 0 { // Perform ! ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest). // Return. return ReadableByteStreamController::readable_byte_stream_controller_fill_read_request_from_queue( ctx, objects, read_request, ); } // Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]]. let auto_allocate_chunk_size = objects.controller.auto_allocate_chunk_size; // If autoAllocateChunkSize is not undefined, if let Some(auto_allocate_chunk_size) = auto_allocate_chunk_size { // Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »). let buffer: ArrayBuffer = match objects .controller .constructor_array_buffer .construct((auto_allocate_chunk_size,)) { // If buffer is an abrupt completion, Err(Error::Exception) => { // Perform readRequest’s error steps, given buffer.[[Value]]. return read_request.error_steps_typed(objects, ctx.catch()); }, Err(err) => return Err(err), Ok(buffer) => buffer, }; // Let pullIntoDescriptor be a new pull-into descriptor with... let pull_into_descriptor = PullIntoDescriptor { buffer, buffer_byte_length: auto_allocate_chunk_size, byte_offset: 0, byte_length: auto_allocate_chunk_size, bytes_filled: 0, minimum_fill: 1, element_size: 1, view_constructor: objects .controller .array_constructor_primordials .constructor_uint8array .clone(), reader_type: PullIntoDescriptorReaderType::Default, }; // Append pullIntoDescriptor to this.[[pendingPullIntos]]. objects .controller .pending_pull_intos .push_back(pull_into_descriptor); } // Perform ! ReadableStreamAddReadRequest(stream, readRequest). objects .stream .readable_stream_add_read_request(&mut objects.reader, read_request); // Perform ! ReadableByteStreamControllerCallPullIfNeeded(this). ReadableByteStreamController::readable_byte_stream_controller_call_pull_if_needed( ctx.clone(), objects, ) } fn cancel_steps>( ctx: &Ctx<'js>, mut objects: ReadableStreamObjects<'js, Self, R>, reason: Value<'js>, ) -> Result<(Promise<'js>, ReadableStreamObjects<'js, Self, R>)> { // Perform ! ReadableByteStreamControllerClearPendingPullIntos(this). objects .controller .readable_byte_stream_controller_clear_pending_pull_intos(); // Perform ! ResetQueue(this). objects.controller.reset_queue(); // Let result be the result of performing this.[[cancelAlgorithm]], passing in reason. let (result, objects_class) = ReadableByteStreamController::cancel_algorithm(ctx.clone(), objects, reason)?; objects = ReadableStreamObjects::from_class(objects_class); // Perform ! ReadableByteStreamControllerClearAlgorithms(this). objects .controller .readable_byte_stream_controller_clear_algorithms(); // Return result. Ok((result, objects)) } fn release_steps(&mut self) { // If this.[[pendingPullIntos]] is not empty, if !self.pending_pull_intos.is_empty() { // Let firstPendingPullInto be this.[[pendingPullIntos]][0]. let first_pending_pull_into = &mut self.pending_pull_intos[0]; // Set firstPendingPullInto’s reader type to "none". first_pending_pull_into.reader_type = PullIntoDescriptorReaderType::None; // Set this.[[pendingPullIntos]] to the list « firstPendingPullInto ». _ = self.pending_pull_intos.split_off(1); } } } #[derive(JsLifetime, Trace, Clone)] #[rquickjs::class] pub(crate) struct ReadableStreamBYOBRequest<'js> { pub(super) view: Option>, controller: Option>, } #[methods(rename_all = "camelCase")] impl<'js> ReadableStreamBYOBRequest<'js> { #[qjs(constructor)] fn new(ctx: Ctx<'js>) -> Result> { Err(Exception::throw_type(&ctx, "Illegal constructor")) } #[qjs(get)] fn view(&self) -> Null> { Null(self.view.clone()) } fn respond( ctx: Ctx<'js>, byob_request: This>, bytes_written: usize, ) -> Result<()> { // If this.[[controller]] is undefined, throw a TypeError exception. let (controller, view) = match (&byob_request.controller, &byob_request.view) { (Some(controller), Some(view)) => (controller.clone(), view), _ => { return Err(Exception::throw_type( &ctx, "This BYOB request has been invalidated", )); }, }; let (buffer, _, _) = view.get_array_buffer()?; drop(byob_request); // If ! IsDetachedBuffer(this.[[view]].[[ArrayBuffer]]) is true, throw a TypeError exception. if buffer.as_bytes().is_none() { return Err(Exception::throw_type( &ctx, "The BYOB request's buffer has been detached and so cannot be used as a response", )); } let objects = ReadableStreamObjects::from_byte_controller(OwnedBorrowMut::from_class(controller)) .refresh_reader(); // Perform ? ReadableByteStreamControllerRespond(this.[[controller]], bytesWritten). ReadableByteStreamController::readable_byte_stream_controller_respond( ctx, objects, bytes_written, ) } fn respond_with_new_view( ctx: Ctx<'js>, byob_request: This>, view: Opt>, ) -> Result<()> { // If this.[[controller]] is undefined, throw a TypeError exception. let controller = match &byob_request.controller { Some(controller) => controller.clone(), _ => { return Err(Exception::throw_type( &ctx, "This BYOB request has been invalidated", )); }, }; drop(byob_request); let controller = OwnedBorrowMut::from_class(controller); let view = ViewBytes::from_value( &ctx, &controller.function_array_buffer_is_view, view.0.as_ref(), )?; let (buffer, _, _) = view.get_array_buffer()?; // If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. if buffer.as_bytes().is_none() { return Err(Exception::throw_type( &ctx, "The given view's buffer has been detached and so cannot be used as a response", )); } let objects = ReadableStreamObjects::from_byte_controller(controller).refresh_reader(); // Return ? ReadableByteStreamControllerRespondWithNewView(this.[[controller]], view). ReadableByteStreamController::readable_byte_stream_controller_respond_with_new_view( ctx, objects, view, ) } } #[derive(JsLifetime)] pub(super) struct PullIntoDescriptor<'js> { buffer: ArrayBuffer<'js>, buffer_byte_length: usize, byte_offset: usize, byte_length: usize, bytes_filled: usize, minimum_fill: usize, element_size: usize, view_constructor: Constructor<'js>, reader_type: PullIntoDescriptorReaderType, } impl<'js> Trace<'js> for PullIntoDescriptor<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.buffer.trace(tracer); self.buffer_byte_length.trace(tracer); self.byte_offset.trace(tracer); self.byte_length.trace(tracer); self.bytes_filled.trace(tracer); self.minimum_fill.trace(tracer); self.element_size.trace(tracer); self.view_constructor.trace(tracer); self.reader_type.trace(tracer); } } enum PullIntoDescriptorRefMut<'js, 'a> { Index(usize), Owned(&'a mut PullIntoDescriptor<'js>), } #[derive(Trace, Clone, Copy)] enum PullIntoDescriptorReaderType { Default, Byob, None, } #[derive(JsLifetime)] struct ReadableByteStreamQueueEntry<'js> { buffer: ArrayBuffer<'js>, byte_offset: usize, byte_length: usize, } impl<'js> Trace<'js> for ReadableByteStreamQueueEntry<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.buffer.trace(tracer); self.byte_offset.trace(tracer); self.byte_length.trace(tracer) } } fn transfer_array_buffer(buffer: ArrayBuffer<'_>) -> Result> { buffer.get::<_, Function>("transfer")?.call((This(buffer),)) } fn copy_data_block_bytes( ctx: &Ctx<'_>, to_block: &ArrayBuffer, to_index: usize, from_block: &ArrayBuffer, from_index: usize, count: usize, ) -> Result<()> { let to_raw = to_block .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(ctx)?; let to_slice = unsafe { std::slice::from_raw_parts_mut(to_raw.ptr.as_ptr(), to_raw.len) }; let from_raw = from_block .as_raw() .ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED) .or_throw(ctx)?; let from_slice = unsafe { std::slice::from_raw_parts(from_raw.ptr.as_ptr(), from_raw.len) }; to_slice[to_index..to_index + count] .copy_from_slice(&from_slice[from_index..from_index + count]); Ok(()) } ================================================ FILE: modules/llrt_stream_web/src/readable/controller.rs ================================================ use rquickjs::{ class::{OwnedBorrowMut, Trace}, Ctx, IntoJs, JsLifetime, Promise, Result, Value, }; use crate::readable::{ byte_controller::{ReadableByteStreamControllerClass, ReadableByteStreamControllerOwned}, default_controller::{ ReadableStreamDefaultControllerClass, ReadableStreamDefaultControllerOwned, }, default_reader::ReadableStreamReadRequest, objects::{ReadableStreamDefaultReaderObjects, ReadableStreamObjects}, reader::ReadableStreamReader, }; pub(super) trait ReadableStreamController<'js>: Sized { type Class: Clone + Trace<'js>; fn with_controller( self, ctx: C, default: impl FnOnce( C, ReadableStreamDefaultControllerOwned<'js>, ) -> Result<(O, ReadableStreamDefaultControllerOwned<'js>)>, byte: impl FnOnce( C, ReadableByteStreamControllerOwned<'js>, ) -> Result<(O, ReadableByteStreamControllerOwned<'js>)>, ) -> Result<(O, Self)>; fn into_inner(self) -> Self::Class; fn from_class(class: Self::Class) -> Self; fn into_erased(self) -> ReadableStreamControllerOwned<'js>; fn try_from_erased(erased: ReadableStreamControllerOwned<'js>) -> Option; fn pull_steps( ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js, Self>, read_request: impl ReadableStreamReadRequest<'js> + 'js, ) -> Result>; fn cancel_steps>( ctx: &Ctx<'js>, objects: ReadableStreamObjects<'js, Self, R>, reason: Value<'js>, ) -> Result<(Promise<'js>, ReadableStreamObjects<'js, Self, R>)>; fn release_steps(&mut self); } #[derive(JsLifetime, Trace, Clone)] pub enum ReadableStreamControllerClass<'js> { ReadableStreamDefaultController(ReadableStreamDefaultControllerClass<'js>), ReadableStreamByteController(ReadableByteStreamControllerClass<'js>), Uninitialised, // Only for use when initialising a Stream - should never be present later on } impl<'js> IntoJs<'js> for ReadableStreamControllerClass<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self { Self::ReadableStreamDefaultController(c) => c.into_js(ctx), Self::ReadableStreamByteController(c) => c.into_js(ctx), Self::Uninitialised => { panic!("Tried to convert an uninitialised controller class to JS") }, } } } pub(super) enum ReadableStreamControllerOwned<'js> { ReadableStreamDefaultController(ReadableStreamDefaultControllerOwned<'js>), ReadableStreamByteController(ReadableByteStreamControllerOwned<'js>), } impl<'js> ReadableStreamController<'js> for ReadableStreamControllerOwned<'js> { type Class = ReadableStreamControllerClass<'js>; fn with_controller( self, ctx: C, default: impl FnOnce( C, ReadableStreamDefaultControllerOwned<'js>, ) -> Result<(O, ReadableStreamDefaultControllerOwned<'js>)>, byob: impl FnOnce( C, ReadableByteStreamControllerOwned<'js>, ) -> Result<(O, ReadableByteStreamControllerOwned<'js>)>, ) -> Result<(O, Self)> { match self { ReadableStreamControllerOwned::ReadableStreamDefaultController(r) => { let (ctx, r) = default(ctx, r)?; Ok((ctx, Self::ReadableStreamDefaultController(r))) }, ReadableStreamControllerOwned::ReadableStreamByteController(r) => { let (ctx, r) = byob(ctx, r)?; Ok((ctx, Self::ReadableStreamByteController(r))) }, } } fn into_inner(self) -> Self::Class { match self { ReadableStreamControllerOwned::ReadableStreamDefaultController(c) => { ReadableStreamControllerClass::ReadableStreamDefaultController(c.into_inner()) }, ReadableStreamControllerOwned::ReadableStreamByteController(c) => { ReadableStreamControllerClass::ReadableStreamByteController(c.into_inner()) }, } } fn from_class(class: Self::Class) -> Self { match class { ReadableStreamControllerClass::ReadableStreamDefaultController(class) => { ReadableStreamControllerOwned::ReadableStreamDefaultController( OwnedBorrowMut::from_class(class), ) }, ReadableStreamControllerClass::ReadableStreamByteController(class) => { ReadableStreamControllerOwned::ReadableStreamByteController( OwnedBorrowMut::from_class(class), ) }, ReadableStreamControllerClass::Uninitialised => { panic!("Tried to borrow an uninitialised controller class") }, } } fn into_erased(self) -> ReadableStreamControllerOwned<'js> { self } fn try_from_erased(erased: ReadableStreamControllerOwned<'js>) -> Option { Some(erased) } fn pull_steps( ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js, Self>, read_request: impl ReadableStreamReadRequest<'js> + 'js, ) -> Result> { objects .with_controller( read_request, |read_request, objects| { ReadableStreamDefaultControllerOwned::<'js>::pull_steps( ctx, objects, read_request, ) .map(|objects| ((), objects)) }, |read_request, objects| { ReadableByteStreamControllerOwned::<'js>::pull_steps(ctx, objects, read_request) .map(|objects| ((), objects)) }, ) .map(|((), objects)| objects) } fn cancel_steps>( ctx: &Ctx<'js>, objects: ReadableStreamObjects<'js, Self, R>, reason: Value<'js>, ) -> Result<(Promise<'js>, ReadableStreamObjects<'js, Self, R>)> { objects.with_controller( reason, |reason, objects| { ReadableStreamDefaultControllerOwned::<'js>::cancel_steps(ctx, objects, reason) }, |reason, objects| { ReadableByteStreamControllerOwned::<'js>::cancel_steps(ctx, objects, reason) }, ) } fn release_steps(&mut self) { match self { ReadableStreamControllerOwned::ReadableStreamDefaultController(c) => c.release_steps(), ReadableStreamControllerOwned::ReadableStreamByteController(c) => c.release_steps(), } } } impl<'js> From> for ReadableStreamControllerOwned<'js> { fn from(value: ReadableStreamDefaultControllerOwned<'js>) -> Self { Self::ReadableStreamDefaultController(value) } } impl<'js> From> for ReadableStreamControllerOwned<'js> { fn from(value: ReadableByteStreamControllerOwned<'js>) -> Self { Self::ReadableStreamByteController(value) } } ================================================ FILE: modules/llrt_stream_web/src/readable/default_controller.rs ================================================ use llrt_utils::option::{Null, Undefined}; use rquickjs::{ class::{OwnedBorrow, OwnedBorrowMut, Trace}, methods, prelude::{Opt, This}, Class, Ctx, Error, Exception, JsLifetime, Object, Promise, Result, Value, }; use crate::{ queuing_strategy::{SizeAlgorithm, SizeValue}, readable::{ byte_controller::ReadableByteStreamControllerOwned, controller::{ ReadableStreamController, ReadableStreamControllerClass, ReadableStreamControllerOwned, }, default_reader::{ReadableStreamDefaultReaderOrUndefined, ReadableStreamReadRequest}, objects::{ ReadableStreamClassObjects, ReadableStreamDefaultControllerObjects, ReadableStreamDefaultReaderObjects, ReadableStreamObjects, }, reader::ReadableStreamReader, stream::{ algorithms::{CancelAlgorithm, PullAlgorithm, StartAlgorithm}, source::UnderlyingSource, ReadableStream, ReadableStreamClass, ReadableStreamOwned, ReadableStreamState, }, }, utils::{ class_from_owned_borrow_mut, promise::{promise_resolved_with, upon_promise}, queue::QueueWithSizes, UnwrapOrUndefined, }, }; #[derive(JsLifetime, Trace)] #[rquickjs::class] pub(crate) struct ReadableStreamDefaultController<'js> { cancel_algorithm: Option>, close_requested: bool, pull_again: bool, pull_algorithm: Option>, pulling: bool, container: QueueWithSizes<'js>, started: bool, strategy_hwm: f64, strategy_size_algorithm: Option>, pub(super) stream: ReadableStreamClass<'js>, } pub(super) type ReadableStreamDefaultControllerClass<'js> = Class<'js, ReadableStreamDefaultController<'js>>; pub(super) type ReadableStreamDefaultControllerOwned<'js> = OwnedBorrowMut<'js, ReadableStreamDefaultController<'js>>; impl<'js> ReadableStreamDefaultController<'js> { pub(super) fn set_up_readable_stream_default_controller_from_underlying_source( ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>, underlying_source: Null>>, underlying_source_dict: UnderlyingSource<'js>, high_water_mark: f64, size_algorithm: SizeAlgorithm<'js>, ) -> Result<()> { let (start_algorithm, pull_algorithm, cancel_algorithm) = ( // If underlyingSourceDict["start"] exists, then set startAlgorithm to an algorithm which returns the result of invoking underlyingSourceDict["start"] with argument list // « controller » and callback this value underlyingSource. underlying_source_dict .start .map(|f| StartAlgorithm::Function { f, underlying_source: underlying_source.clone(), }) .unwrap_or(StartAlgorithm::ReturnUndefined), // If underlyingSourceDict["pull"] exists, then set pullAlgorithm to an algorithm which returns the result of invoking underlyingSourceDict["pull"] with argument list // « controller » and callback this value underlyingSource. underlying_source_dict .pull .map(|f| PullAlgorithm::Function { f, underlying_source: underlying_source.clone(), }) .unwrap_or(PullAlgorithm::ReturnPromiseUndefined), // If underlyingSourceDict["cancel"] exists, then set cancelAlgorithm to an algorithm which takes an argument reason and returns the result of invoking underlyingSourceDict["cancel"] with argument list // « reason » and callback this value underlyingSource. underlying_source_dict .cancel .map(|f| CancelAlgorithm::Function { f, underlying_source, }) .unwrap_or(CancelAlgorithm::ReturnPromiseUndefined), ); // Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm). Self::set_up_readable_stream_default_controller( ctx.clone(), stream, start_algorithm, pull_algorithm, cancel_algorithm, high_water_mark, size_algorithm, )?; Ok(()) } pub(super) fn set_up_readable_stream_default_controller( ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>, start_algorithm: StartAlgorithm<'js>, pull_algorithm: PullAlgorithm<'js>, cancel_algorithm: CancelAlgorithm<'js>, high_water_mark: f64, size_algorithm: SizeAlgorithm<'js>, ) -> Result> { let (stream_class, mut stream) = class_from_owned_borrow_mut(stream); let controller = ReadableStreamDefaultController { // Set controller.[[stream]] to stream. stream: stream_class.clone(), // Perform ! ResetQueue(controller). container: QueueWithSizes::new(), // Set controller.[[started]], controller.[[closeRequested]], controller.[[pullAgain]], and controller.[[pulling]] to false. started: false, close_requested: false, pull_again: false, pulling: false, // Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm and controller.[[strategyHWM]] to highWaterMark. strategy_size_algorithm: Some(size_algorithm), strategy_hwm: high_water_mark, // Set controller.[[pullAlgorithm]] to pullAlgorithm. pull_algorithm: Some(pull_algorithm), // Set controller.[[cancelAlgorithm]] to cancelAlgorithm. cancel_algorithm: Some(cancel_algorithm), }; let controller_class = Class::instance(ctx.clone(), controller)?; // Set stream.[[controller]] to controller. stream.controller = ReadableStreamControllerClass::ReadableStreamDefaultController( controller_class.clone(), ); let objects = ReadableStreamObjects::new_default( stream, OwnedBorrowMut::from_class(controller_class), ); let promise_primordials = objects.stream.promise_primordials.clone(); // Let startResult be the result of performing startAlgorithm. (This might throw an exception.) let (start_result, objects_class) = Self::start_algorithm(ctx.clone(), objects, start_algorithm)?; // Let startPromise be a promise resolved with startResult. let start_promise = promise_resolved_with(&ctx, &promise_primordials, Ok(start_result))?; let _ = upon_promise::, _>(ctx.clone(), start_promise, { let objects_class = objects_class.clone(); move |ctx, result| { let mut objects = ReadableStreamObjects::from_class_no_reader(objects_class).refresh_reader(); match result { // Upon fulfillment of startPromise, Ok(_) => { // Set controller.[[started]] to true. objects.controller.started = true; // Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). Self::readable_stream_default_controller_call_pull_if_needed(ctx, objects)?; }, // Upon rejection of startPromise with reason r, Err(r) => { // Perform ! ReadableByteStreamControllerError(controller, r). Self::readable_stream_default_controller_error(objects, r)?; }, } Ok(()) } })?; Ok(objects_class.controller) } fn readable_stream_default_controller_call_pull_if_needed< R: ReadableStreamDefaultReaderOrUndefined<'js>, >( ctx: Ctx<'js>, objects: ReadableStreamDefaultControllerObjects<'js, R>, ) -> Result> { // Let shouldPull be ! ReadableStreamDefaultControllerShouldCallPull(controller). let (should_pull, mut objects) = ReadableStreamDefaultController::readable_stream_default_controller_should_call_pull( objects, ); // If shouldPull is false, return. if !should_pull { return Ok(objects); } // If controller.[[pulling]] is true, if objects.controller.pulling { // Set controller.[[pullAgain]] to true. objects.controller.pull_again = true; // Return. return Ok(objects); } // Set controller.[[pulling]] to true. objects.controller.pulling = true; // Let pullPromise be the result of performing controller.[[pullAlgorithm]]. let (pull_promise, objects_class) = Self::pull_algorithm(ctx.clone(), objects)?; upon_promise::, _>(ctx.clone(), pull_promise, { let objects_class = objects_class.clone(); move |ctx, result| { let mut objects = ReadableStreamObjects::from_class_no_reader(objects_class).refresh_reader(); match result { // Upon fulfillment of pullPromise, Ok(_) => { // Set controller.[[pulling]] to false. objects.controller.pulling = false; // If controller.[[pullAgain]] is true, if objects.controller.pull_again { // Set controller.[[pullAgain]] to false. objects.controller.pull_again = false; // Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). Self::readable_stream_default_controller_call_pull_if_needed( ctx, objects, )?; }; Ok(()) }, // Upon rejection of pullPromise with reason e, Err(e) => { // Perform ! ReadableStreamDefaultControllerError(controller, e). Self::readable_stream_default_controller_error(objects, e)?; Ok(()) }, } } })?; Ok(ReadableStreamObjects::from_class(objects_class)) } pub(super) fn readable_stream_default_controller_error>( // Let stream be controller.[[stream]]. mut objects: ReadableStreamDefaultControllerObjects<'js, R>, e: Value<'js>, ) -> Result> { // If stream.[[state]] is not "readable", return. if !matches!(objects.stream.state, ReadableStreamState::Readable) { return Ok(objects); }; // Perform ! ResetQueue(controller). objects.controller.container.reset_queue(); // Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller). objects .controller .readable_stream_default_controller_clear_algorithms(); // Perform ! ReadableStreamError(stream, e). ReadableStream::readable_stream_error(objects, e) } fn readable_stream_default_controller_should_call_pull< R: ReadableStreamDefaultReaderOrUndefined<'js>, >( mut objects: ReadableStreamDefaultControllerObjects<'js, R>, ) -> (bool, ReadableStreamDefaultControllerObjects<'js, R>) { // Let stream be controller.[[stream]]. // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is false, return false. if !objects .controller .readable_stream_default_controller_can_close_or_enqueue(&objects.stream) { return (false, objects); } // If controller.[[started]] is false, return false. if !objects.controller.started { return (false, objects); } { let mut ret = false; // If ! IsReadableStreamLocked(stream) is true and ! ReadableStreamGetNumReadRequests(stream) > 0, return true. objects = objects .with_some_reader( |objects| { if ReadableStream::readable_stream_get_num_read_requests(&objects.reader) > 0 { ret = true } Ok(objects) }, Ok, ) .unwrap(); if ret { return (true, objects); } } // Let desiredSize be ! ReadableStreamDefaultControllerGetDesiredSize(controller). let desired_size = objects.controller .readable_stream_default_controller_get_desired_size(&objects.stream) .0 .expect( "desiredSize should not be null during ReadableStreamDefaultControllerShouldCallPull", ); // If desiredSize > 0, return true. if desired_size > 0.0 { return (true, objects); } // Return false. (false, objects) } fn readable_stream_default_controller_clear_algorithms(&mut self) { self.pull_algorithm = None; self.cancel_algorithm = None; self.strategy_size_algorithm = None; } fn readable_stream_default_controller_can_close_or_enqueue( &self, stream: &ReadableStream<'js>, ) -> bool { // Let state be controller.[[stream]].[[state]]. match stream.state { // If controller.[[closeRequested]] is false and state is "readable", return true. ReadableStreamState::Readable if !self.close_requested => true, // Otherwise, return false. _ => false, } } fn readable_stream_default_controller_get_desired_size( &self, stream: &ReadableStream<'js>, ) -> Null { // Let state be controller.[[stream]].[[state]]. match stream.state { // If state is "errored", return null. ReadableStreamState::Errored(_) => Null(None), // If state is "closed", return 0. ReadableStreamState::Closed => Null(Some(0.0)), // Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. ReadableStreamState::Readable => { Null(Some(self.strategy_hwm - self.container.queue_total_size)) }, } } pub(super) fn readable_stream_default_controller_close>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: ReadableStreamDefaultControllerObjects<'js, R>, ) -> Result> { // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is false, return. if !objects .controller .readable_stream_default_controller_can_close_or_enqueue(&objects.stream) { return Ok(objects); } // Set controller.[[closeRequested]] to true. objects.controller.close_requested = true; // If controller.[[queue]] is empty, if objects.controller.container.queue.is_empty() { // Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller). objects .controller .readable_stream_default_controller_clear_algorithms(); // Perform ! ReadableStreamClose(stream). objects = ReadableStream::readable_stream_close(ctx, objects)?; } Ok(objects) } pub(super) fn readable_stream_default_controller_enqueue< R: ReadableStreamDefaultReaderOrUndefined<'js>, >( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: ReadableStreamDefaultControllerObjects<'js, R>, chunk: Value<'js>, ) -> Result> { // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is false, return. if !objects .controller .readable_stream_default_controller_can_close_or_enqueue(&objects.stream) { return Ok(objects); } let mut els = true; // If ! IsReadableStreamLocked(stream) is true and ! ReadableStreamGetNumReadRequests(stream) > 0, perform ! ReadableStreamFulfillReadRequest(stream, chunk, false). objects = objects.with_some_reader( |objects| { if ReadableStream::readable_stream_get_num_read_requests(&objects.reader) > 0 { els = false; ReadableStream::readable_stream_fulfill_read_request( &ctx, objects, chunk.clone(), false, ) } else { Ok(objects) } }, Ok, )?; if els { // Let result be the result of performing controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting the result as a completion record. let (result, objects_class) = Self::strategy_size_algorithm(ctx.clone(), objects, chunk.clone()); objects = ReadableStreamObjects::from_class(objects_class); match result { // If result is an abrupt completion, Err(Error::Exception) => { let err = ctx.catch(); // Perform ! ReadableStreamDefaultControllerError(controller, result.[[Value]]). Self::readable_stream_default_controller_error(objects, err.clone())?; return Err(ctx.throw(err)); }, // Let chunkSize be result.[[Value]]. Ok(chunk_size) => { // Let enqueueResult be EnqueueValueWithSize(controller, chunk, chunkSize). let enqueue_result = objects .controller .container .enqueue_value_with_size(&ctx, chunk, chunk_size); match enqueue_result { // If enqueueResult is an abrupt completion, Err(Error::Exception) => { let err = ctx.catch(); // Perform ! ReadableStreamDefaultControllerError(controller, enqueueResult.[[Value]]). Self::readable_stream_default_controller_error(objects, err.clone())?; return Err(ctx.throw(err)); }, Err(err) => return Err(err), Ok(()) => {}, } }, Err(err) => return Err(err), } } // Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). Self::readable_stream_default_controller_call_pull_if_needed(ctx, objects) } fn start_algorithm>( ctx: Ctx<'js>, objects: ReadableStreamDefaultControllerObjects<'js, R>, start_algorithm: StartAlgorithm<'js>, ) -> Result<( Value<'js>, ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>, )> { let objects_class = objects.into_inner(); Ok(( start_algorithm.call( ctx, ReadableStreamControllerClass::ReadableStreamDefaultController( objects_class.controller.clone(), ), )?, objects_class, )) } fn pull_algorithm>( ctx: Ctx<'js>, objects: ReadableStreamDefaultControllerObjects<'js, R>, ) -> Result<( Promise<'js>, ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>, )> { let pull_algorithm = objects .controller .pull_algorithm .clone() .expect("pull algorithm used after ReadableStreamDefaultControllerClearAlgorithms"); let promise_primordials = objects.stream.promise_primordials.clone(); let objects_class = objects.into_inner(); Ok(( pull_algorithm.call( ctx, &promise_primordials, ReadableStreamControllerClass::ReadableStreamDefaultController( objects_class.controller.clone(), ), )?, objects_class, )) } fn strategy_size_algorithm>( ctx: Ctx<'js>, objects: ReadableStreamDefaultControllerObjects<'js, R>, chunk: Value<'js>, ) -> ( Result>, ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>, ) { let strategy_size_algorithm = objects .controller .strategy_size_algorithm .clone() .expect("size algorithm used after ReadableStreamDefaultControllerClearAlgorithms"); let objects_class = objects.into_inner(); (strategy_size_algorithm.call(ctx, chunk), objects_class) } pub(super) fn cancel_algorithm>( ctx: Ctx<'js>, objects: ReadableStreamDefaultControllerObjects<'js, R>, reason: Value<'js>, ) -> Result<( Promise<'js>, ReadableStreamClassObjects<'js, OwnedBorrowMut<'js, Self>, R>, )> { let cancel_algorithm = objects.controller.cancel_algorithm.clone().expect( "cancel algorithm used after ReadableStreamDefaultControllerClearAlgorithms", ); let promise_primordials = objects.stream.promise_primordials.clone(); let objects_class = objects.into_inner(); Ok(( cancel_algorithm.call(ctx, &promise_primordials, reason)?, objects_class, )) } } #[methods(rename_all = "camelCase")] impl<'js> ReadableStreamDefaultController<'js> { // this is required by web platform tests for unclear reasons fn constructor() -> Self { unimplemented!() } #[qjs(constructor)] fn new(ctx: Ctx<'js>) -> Result> { Err(Exception::throw_type(&ctx, "Illegal constructor")) } // readonly attribute unrestricted double? desiredSize; #[qjs(get)] fn desired_size(&self) -> Null { let stream = OwnedBorrow::from_class(self.stream.clone()); self.readable_stream_default_controller_get_desired_size(&stream) } // undefined close(); fn close(ctx: Ctx<'js>, controller: This>) -> Result<()> { let objects = ReadableStreamObjects::from_default_controller(controller.0); // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, throw a TypeError exception. if !objects .controller .readable_stream_default_controller_can_close_or_enqueue(&objects.stream) { return Err(Exception::throw_type( &ctx, "The stream is not in a state that permits close", )); } // Perform ! ReadableStreamDefaultControllerClose(this). Self::readable_stream_default_controller_close(ctx, objects)?; Ok(()) } // undefined enqueue(optional any chunk); fn enqueue( ctx: Ctx<'js>, controller: This>, chunk: Opt>, ) -> Result<()> { let objects = ReadableStreamObjects::from_default_controller(controller.0); // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false, throw a TypeError exception. if !objects .controller .readable_stream_default_controller_can_close_or_enqueue(&objects.stream) { return Err(Exception::throw_type( &ctx, "The stream is not in a state that permits enqueue", )); } objects.with_reader( |objects| { // Perform ? ReadableStreamDefaultControllerEnqueue(this, chunk). Self::readable_stream_default_controller_enqueue( ctx.clone(), objects, chunk.0.clone().unwrap_or_undefined(&ctx), ) }, |_| panic!("Default controller must not have byob reader"), |objects| { // Perform ? ReadableStreamDefaultControllerEnqueue(this, chunk). Self::readable_stream_default_controller_enqueue( ctx.clone(), objects, chunk.0.clone().unwrap_or_undefined(&ctx), ) }, )?; Ok(()) } // undefined error(optional any e); fn error( ctx: Ctx<'js>, controller: This>, e: Opt>, ) -> Result<()> { let objects = ReadableStreamObjects::from_default_controller(controller.0); // Perform ! ReadableStreamDefaultControllerError(this, e). Self::readable_stream_default_controller_error(objects, e.0.unwrap_or_undefined(&ctx))?; Ok(()) } } impl<'js> ReadableStreamController<'js> for ReadableStreamDefaultControllerOwned<'js> { type Class = ReadableStreamDefaultControllerClass<'js>; fn with_controller( self, ctx: C, default: impl FnOnce( C, ReadableStreamDefaultControllerOwned<'js>, ) -> Result<(O, ReadableStreamDefaultControllerOwned<'js>)>, _: impl FnOnce( C, ReadableByteStreamControllerOwned<'js>, ) -> Result<(O, ReadableByteStreamControllerOwned<'js>)>, ) -> Result<(O, Self)> { let (ctx, reader) = default(ctx, self)?; Ok((ctx, reader)) } fn into_inner(self) -> Self::Class { OwnedBorrowMut::into_inner(self) } fn from_class(class: Self::Class) -> Self { OwnedBorrowMut::from_class(class) } fn into_erased(self) -> ReadableStreamControllerOwned<'js> { ReadableStreamControllerOwned::ReadableStreamDefaultController(self) } fn try_from_erased(erased: ReadableStreamControllerOwned<'js>) -> Option { match erased { ReadableStreamControllerOwned::ReadableStreamDefaultController(r) => Some(r), ReadableStreamControllerOwned::ReadableStreamByteController(_) => None, } } fn pull_steps( ctx: &Ctx<'js>, mut objects: ReadableStreamDefaultReaderObjects<'js, Self>, read_request: impl ReadableStreamReadRequest<'js> + 'js, ) -> Result> { // If this.[[queue]] is not empty, if !objects.controller.container.queue.is_empty() { // Let chunk be ! DequeueValue(this). let chunk = objects.controller.container.dequeue_value(); // If this.[[closeRequested]] is true and this.[[queue]] is empty, if objects.controller.close_requested && objects.controller.container.queue.is_empty() { // Perform ! ReadableStreamDefaultControllerClearAlgorithms(this). objects .controller .readable_stream_default_controller_clear_algorithms(); // Perform ! ReadableStreamClose(stream). objects = ReadableStream::readable_stream_close(ctx.clone(), objects)?; } else { // Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this). objects = ReadableStreamDefaultController::readable_stream_default_controller_call_pull_if_needed( ctx.clone(), objects, )?; } // Perform readRequest’s chunk steps, given chunk. read_request.chunk_steps_typed(objects, chunk) } else { // Otherwise, // Perform ! ReadableStreamAddReadRequest(stream, readRequest). objects .stream .readable_stream_add_read_request(&mut objects.reader, read_request); // Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this). ReadableStreamDefaultController::readable_stream_default_controller_call_pull_if_needed( ctx.clone(), objects, ) } } fn cancel_steps>( ctx: &Ctx<'js>, mut objects: ReadableStreamObjects<'js, Self, R>, reason: Value<'js>, ) -> Result<(Promise<'js>, ReadableStreamObjects<'js, Self, R>)> { // Perform ! ResetQueue(this). objects.controller.container.reset_queue(); // Let result be the result of performing this.[[cancelAlgorithm]], passing reason. let (result, objects_class) = ReadableStreamDefaultController::cancel_algorithm(ctx.clone(), objects, reason)?; objects = ReadableStreamObjects::from_class(objects_class); // Perform ! ReadableStreamDefaultControllerClearAlgorithms(this). objects .controller .readable_stream_default_controller_clear_algorithms(); // Return result. Ok((result, objects)) } fn release_steps(&mut self) {} } ================================================ FILE: modules/llrt_stream_web/src/readable/default_reader.rs ================================================ use std::collections::VecDeque; use rquickjs::class::Tracer; use rquickjs::prelude::This; use rquickjs::{ class::{OwnedBorrowMut, Trace}, methods, prelude::Opt, Class, Ctx, Exception, Promise, Result, Value, }; use rquickjs::{IntoJs, JsLifetime, Object}; use crate::{ readable::{ byob_reader::ReadableStreamBYOBReaderOwned, controller::ReadableStreamController, objects::{ReadableStreamDefaultReaderObjects, ReadableStreamObjects}, reader::{ ReadableStreamGenericReader, ReadableStreamReader, ReadableStreamReaderOwned, UndefinedReader, }, stream::{ReadableStreamOwned, ReadableStreamState}, }, utils::{ promise::{promise_rejected_with_constructor, ResolveablePromise}, UnwrapOrUndefined, }, }; #[derive(Trace)] #[rquickjs::class] pub(crate) struct ReadableStreamDefaultReader<'js> { pub(super) generic: ReadableStreamGenericReader<'js>, pub(super) read_requests: VecDeque + 'js>>, } pub(crate) type ReadableStreamDefaultReaderClass<'js> = Class<'js, ReadableStreamDefaultReader<'js>>; pub(crate) type ReadableStreamDefaultReaderOwned<'js> = OwnedBorrowMut<'js, ReadableStreamDefaultReader<'js>>; unsafe impl<'js> JsLifetime<'js> for ReadableStreamDefaultReader<'js> { type Changed<'to> = ReadableStreamDefaultReader<'to>; } impl<'js> ReadableStreamDefaultReader<'js> { pub(super) fn readable_stream_default_reader_error_read_requests< C: ReadableStreamController<'js>, >( mut objects: ReadableStreamDefaultReaderObjects<'js, C>, e: Value<'js>, ) -> Result> { // Let readRequests be reader.[[readRequests]]. let read_requests = &mut objects.reader.read_requests; // Set reader.[[readRequests]] to a new empty list. let read_requests = read_requests.split_off(0); // For each readRequest of readRequests, for read_request in read_requests { // Perform readRequest’s error steps, given e. objects = read_request.error_steps_typed(objects, e.clone())?; } Ok(objects) } pub(super) fn readable_stream_default_reader_read< 'closure, C: ReadableStreamController<'js>, >( ctx: &Ctx<'js>, // Let stream be reader.[[stream]]. mut objects: ReadableStreamDefaultReaderObjects<'js, C>, read_request: impl ReadableStreamReadRequest<'js> + 'js, ) -> Result> { // Set stream.[[disturbed]] to true. objects.stream.disturbed = true; match objects.stream.state { // If stream.[[state]] is "closed", perform readRequest’s close steps. ReadableStreamState::Closed => read_request.close_steps_typed(ctx, objects), // Otherwise, if stream.[[state]] is "errored", perform readRequest’s error steps given stream.[[storedError]]. ReadableStreamState::Errored(ref stored_error) => { let stored_error = stored_error.clone(); read_request.error_steps_typed(objects, stored_error) }, // Otherwise, _ => { // Perform ! stream.[[controller]].[[PullSteps]](readRequest). C::pull_steps(ctx, objects, read_request) }, } } pub(super) fn set_up_readable_stream_default_reader( ctx: &Ctx<'js>, stream: ReadableStreamOwned<'js>, ) -> Result<(ReadableStreamOwned<'js>, Class<'js, Self>)> { // If ! IsReadableStreamLocked(stream) is true, throw a TypeError exception. if stream.is_readable_stream_locked() { return Err(Exception::throw_type( ctx, "This stream has already been locked for exclusive reading by another reader", )); } // Perform ! ReadableStreamReaderGenericInitialize(reader, stream). let generic = ReadableStreamGenericReader::readable_stream_reader_generic_initialize(ctx, stream)?; let mut stream = OwnedBorrowMut::from_class(generic.stream.clone().unwrap()); let reader = Class::instance( ctx.clone(), Self { generic, // Set reader.[[readRequests]] to a new empty list. read_requests: VecDeque::new(), }, )?; stream.reader = Some(reader.clone().into()); Ok((stream, reader)) } pub(super) fn readable_stream_default_reader_release>( mut objects: ReadableStreamDefaultReaderObjects<'js, C>, ) -> Result> { // Perform ! ReadableStreamReaderGenericRelease(reader). objects .reader .generic .readable_stream_reader_generic_release(&mut objects.stream, || { objects.controller.release_steps() })?; // Let e be a new TypeError exception. let e: Value = objects .stream .constructor_type_error .call(("Reader was released",))?; // Perform ! ReadableStreamDefaultReaderErrorReadRequests(reader, e). Self::readable_stream_default_reader_error_read_requests(objects, e) } } #[methods(rename_all = "camelCase")] impl<'js> ReadableStreamDefaultReader<'js> { #[qjs(constructor)] pub fn new(ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>) -> Result> { // Perform ? SetUpReadableStreamDefaultReader(this, stream). let (_, reader) = Self::set_up_readable_stream_default_reader(&ctx, stream)?; Ok(reader) } fn read(ctx: Ctx<'js>, reader: This>) -> Result> { if reader.generic.stream.is_none() { // If this.[[stream]] is undefined, return a promise rejected with a TypeError exception. return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "Cannot read from a stream using a released reader", ); } let objects = ReadableStreamObjects::from_default_reader(reader.0); // Let promise be a new promise. let promise = ResolveablePromise::new(&ctx)?; // Let readRequest be a new read request with the following items: #[derive(Trace)] struct ReadRequest<'js> { promise: ResolveablePromise<'js>, } impl<'js> ReadableStreamReadRequest<'js> for ReadRequest<'js> { // chunk steps, given chunk // Resolve promise with «[ "value" → chunk, "done" → false ]». fn chunk_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, chunk: Value<'js>, ) -> Result> { self.promise.resolve(ReadableStreamReadResult { value: Some(chunk), done: false, })?; Ok(objects) } // close steps // Resolve promise with «[ "value" → undefined, "done" → true ]». fn close_steps( &self, _: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js>, ) -> Result> { self.promise.resolve(ReadableStreamReadResult { value: None, done: true, })?; Ok(objects) } fn error_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, e: Value<'js>, ) -> Result> { self.promise.reject(e)?; Ok(objects) } } // Perform ! ReadableStreamDefaultReaderRead(this, readRequest). Self::readable_stream_default_reader_read( &ctx, objects, ReadRequest { promise: promise.clone(), }, )?; // Return promise. Ok(promise.promise) } fn release_lock(reader: This>) -> Result<()> { if reader.generic.stream.is_none() { // If this.[[stream]] is undefined, return. return Ok(()); } let objects = ReadableStreamObjects::from_default_reader(reader.0); // Perform ! ReadableStreamDefaultReaderRelease(this). Self::readable_stream_default_reader_release(objects)?; Ok(()) } #[qjs(get)] fn closed(&self) -> Promise<'js> { self.generic.closed_promise.promise.clone() } fn cancel( ctx: Ctx<'js>, reader: This>, reason: Opt>, ) -> Result> { if reader.generic.stream.is_none() { // If this.[[stream]] is undefined, return a promise rejected with a TypeError exception. return promise_rejected_with_constructor( &reader.generic.constructor_type_error, &reader.generic.promise_primordials, "Cannot cancel a stream using a released reader", ); }; let objects = ReadableStreamObjects::from_default_reader(reader.0); // Return ! ReadableStreamReaderGenericCancel(this, reason). let (promise, _) = ReadableStreamGenericReader::readable_stream_reader_generic_cancel( ctx.clone(), objects, reason.0.unwrap_or_undefined(&ctx), )?; Ok(promise) } } impl<'js> ReadableStreamReader<'js> for ReadableStreamDefaultReaderOwned<'js> { type Class = ReadableStreamDefaultReaderClass<'js>; fn with_reader( self, ctx: C, default: impl FnOnce( C, ReadableStreamDefaultReaderOwned<'js>, ) -> Result<(C, ReadableStreamDefaultReaderOwned<'js>)>, _: impl FnOnce( C, ReadableStreamBYOBReaderOwned<'js>, ) -> Result<(C, ReadableStreamBYOBReaderOwned<'js>)>, _: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { default(ctx, self) } fn into_inner(self) -> Self::Class { self.into_inner() } fn from_class(class: Self::Class) -> Self { OwnedBorrowMut::from_class(class) } fn try_from_erased(erased: Option>) -> Option { match erased { Some(ReadableStreamReaderOwned::ReadableStreamDefaultReader(r)) => Some(r), _ => None, } } } pub(super) trait ReadableStreamDefaultReaderOrUndefined<'js>: ReadableStreamReader<'js> { } impl<'js> ReadableStreamDefaultReaderOrUndefined<'js> for ReadableStreamDefaultReaderOwned<'js> {} impl<'js> ReadableStreamDefaultReaderOrUndefined<'js> for Option> { } impl ReadableStreamDefaultReaderOrUndefined<'_> for UndefinedReader {} pub(super) trait ReadableStreamReadRequest<'js>: Trace<'js> { fn chunk_steps_typed>( &self, objects: ReadableStreamDefaultReaderObjects<'js, C>, chunk: Value<'js>, ) -> Result> where Self: Sized, { let mut erased = ReadableStreamObjects { stream: objects.stream, controller: objects.controller.into_erased(), reader: objects.reader, }; erased = self.chunk_steps(erased, chunk)?; Ok(ReadableStreamObjects { stream: erased.stream, controller: C::try_from_erased(erased.controller) .expect("chunk steps must not change type of controller"), reader: erased.reader, }) } fn chunk_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, chunk: Value<'js>, ) -> Result>; fn close_steps_typed>( &self, ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js, C>, ) -> Result> where Self: Sized, { let mut erased = ReadableStreamObjects { stream: objects.stream, controller: objects.controller.into_erased(), reader: objects.reader, }; erased = self.close_steps(ctx, erased)?; Ok(ReadableStreamObjects { stream: erased.stream, controller: C::try_from_erased(erased.controller) .expect("close steps must not change type of controller"), reader: erased.reader, }) } fn close_steps( &self, ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js>, ) -> Result>; fn error_steps_typed>( &self, objects: ReadableStreamDefaultReaderObjects<'js, C>, reason: Value<'js>, ) -> Result> where Self: Sized, { let mut erased = ReadableStreamObjects { stream: objects.stream, controller: objects.controller.into_erased(), reader: objects.reader, }; erased = self.error_steps(erased, reason)?; Ok(ReadableStreamObjects { stream: erased.stream, controller: C::try_from_erased(erased.controller) .expect("error steps must not change type of controller"), reader: erased.reader, }) } fn error_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, reason: Value<'js>, ) -> Result>; } impl<'js> Trace<'js> for Box + 'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.as_ref().trace(tracer); } } impl<'js> ReadableStreamReadRequest<'js> for Box + 'js> { fn chunk_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, chunk: Value<'js>, ) -> Result> { self.as_ref().chunk_steps(objects, chunk) } fn close_steps( &self, ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js>, ) -> Result> { self.as_ref().close_steps(ctx, objects) } fn error_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, reason: Value<'js>, ) -> Result> { self.as_ref().error_steps(objects, reason) } } pub(super) struct ReadableStreamReadResult<'js> { pub(super) value: Option>, pub(super) done: bool, } impl<'js> IntoJs<'js> for ReadableStreamReadResult<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { let obj = Object::new(ctx.clone())?; obj.set("value", self.value)?; obj.set("done", self.done)?; Ok(obj.into_value()) } } ================================================ FILE: modules/llrt_stream_web/src/readable/iterator.rs ================================================ use std::{ rc::Rc, sync::atomic::{AtomicBool, Ordering}, }; use llrt_utils::{ object::CreateSymbol, primordials::{BasePrimordials, Primordial}, }; use rquickjs::{ atom::PredefinedAtom, class::{JsClass, OwnedBorrow, OwnedBorrowMut, Trace, Tracer}, function::Constructor, methods, prelude::{Opt, This}, Class, Coerced, Ctx, Error, Exception, FromJs, Function, IntoAtom, IntoJs, JsLifetime, Object, Promise, Result, Symbol, Type, Value, }; use crate::{ readable::{ controller::ReadableStreamControllerOwned, default_reader::{ ReadableStreamDefaultReader, ReadableStreamDefaultReaderOwned, ReadableStreamReadRequest, ReadableStreamReadResult, }, objects::{ ReadableStreamClassObjects, ReadableStreamDefaultReaderObjects, ReadableStreamObjects, }, reader::ReadableStreamGenericReader, }, utils::{ class_from_owned_borrow_mut, promise::{promise_resolved_with, PromisePrimordials}, promise::{upon_promise, upon_promise_fulfilment, ResolveablePromise}, UnwrapOrUndefined, }, }; pub(super) enum IteratorKind { Async, } pub(super) struct IteratorRecord<'js> { pub(super) iterator: Object<'js>, next_method: Function<'js>, done: AtomicBool, sync_to_async_iterator: Function<'js>, } impl<'js> IteratorRecord<'js> { pub(super) fn get_iterator( ctx: &Ctx<'js>, obj: Value<'js>, kind: IteratorKind, ) -> Result { let method: Option> = match kind { // If kind is async, then IteratorKind::Async => { // Let method be ? GetMethod(obj, %Symbol.asyncIterator%). let method = get_method(ctx, obj.clone(), Symbol::async_iterator(ctx.clone()))?; // If method is undefined, then if method.is_none() { // Let syncMethod be ? GetMethod(obj, %Symbol.iterator%). let sync_method = get_method(ctx, obj.clone(), Symbol::iterator(ctx.clone()))?; // If syncMethod is undefined, throw a TypeError exception. let sync_method = match sync_method { None => { return Err(Exception::throw_type(ctx, "Object is not an iterator")); }, Some(sync_method) => sync_method, }; // Let syncIteratorRecord be ? GetIteratorFromMethod(obj, syncMethod). let sync_iterator_record = Self::get_iterator_from_method(ctx, &obj, sync_method)?; // Return CreateAsyncFromSyncIterator(syncIteratorRecord). return sync_iterator_record.create_async_from_sync_iterator(ctx); } method }, }; // If method is undefined, throw a TypeError exception. match method { None => Err(Exception::throw_type(ctx, "Object is not an iterator")), Some(method) => { // Return ? GetIteratorFromMethod(obj, method). Self::get_iterator_from_method(ctx, &obj, method) }, } } fn get_iterator_from_method( ctx: &Ctx<'js>, obj: &Value<'js>, method: Function<'js>, ) -> Result { // Let iterator be ? Call(method, obj). let iterator: Value<'js> = method.call((This(obj),))?; let iterator = match iterator.into_object() { Some(iterator) => iterator, None => { return Err(Exception::throw_type( ctx, "The iterator method must return an object", )); }, }; // Let nextMethod be ? Get(iterator, "next"). let next_method = iterator.get(PredefinedAtom::Next)?; // Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. // Return iteratorRecord. Ok(Self { iterator, next_method, done: AtomicBool::new(false), sync_to_async_iterator: IteratorPrimordials::get(ctx)? .sync_to_async_iterator .clone(), }) } fn create_async_from_sync_iterator(self, ctx: &Ctx<'js>) -> Result { let sync_iterable = Object::new(ctx.clone())?; sync_iterable.set( Symbol::iterator(ctx.clone()), Function::new(ctx.clone(), { let iterator = self.iterator.clone(); move || iterator.clone() }), )?; let async_iterator: Object<'js> = self.sync_to_async_iterator.call((sync_iterable,))?; let next_method = async_iterator.get(PredefinedAtom::Next)?; Ok(Self { iterator: async_iterator, next_method, done: AtomicBool::new(false), sync_to_async_iterator: self.sync_to_async_iterator, }) } pub(super) fn iterator_next( &self, ctx: &Ctx<'js>, value: Option>, ) -> Result> { let result: Result> = match value { // If value is not present, then None => { // Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]])). self.next_method.call((This(self.iterator.clone()),)) }, // Else, Some(value) => { // Let result be Completion(Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »)). self.next_method.call((This(self.iterator.clone()), value)) }, }; let result = match result { // If result is a throw completion, then Err(Error::Exception) => { // Set iteratorRecord.[[Done]] to true. self.done.store(true, Ordering::Release); // Return ? result. return Err(Error::Exception); }, Err(err) => return Err(err), // Set result to ! result. Ok(result) => result, }; let result = match result.into_object() { // If result is not an Object, then None => { // Set iteratorRecord.[[Done]] to true. self.done.store(true, Ordering::Release); return Err(Exception::throw_type( ctx, "The iterator.next() method must return an object", )); }, Some(result) => result, }; // Return result. Ok(result) } pub(super) fn iterator_complete(iterator_result: &Object<'js>) -> Result { let done: Coerced = iterator_result.get(PredefinedAtom::Done)?; Ok(done.0) } pub(super) fn iterator_value(iterator_result: &Object<'js>) -> Result> { iterator_result.get(PredefinedAtom::Value) } } pub(super) struct ReadableStreamAsyncIterator<'js> { objects: ReadableStreamClassObjects< 'js, ReadableStreamControllerOwned<'js>, ReadableStreamDefaultReaderOwned<'js>, >, prevent_cancel: bool, is_finished: Rc, ongoing_promise: Option>, promise_primordials: PromisePrimordials<'js>, end_of_iteration: Symbol<'js>, } impl<'js> Trace<'js> for ReadableStreamAsyncIterator<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { Trace::<'js>::trace(&self.objects, tracer); if let Some(ongoing_promise) = &self.ongoing_promise { ongoing_promise.trace(tracer); } Trace::<'js>::trace(&self.end_of_iteration, tracer); } } unsafe impl<'js> JsLifetime<'js> for ReadableStreamAsyncIterator<'js> { type Changed<'to> = ReadableStreamAsyncIterator<'to>; } impl<'js> ReadableStreamAsyncIterator<'js> { pub(super) fn new( ctx: Ctx<'js>, objects: ReadableStreamClassObjects< 'js, ReadableStreamControllerOwned<'js>, ReadableStreamDefaultReaderOwned<'js>, >, promise_primordials: PromisePrimordials<'js>, prevent_cancel: bool, ) -> Result> { let end_of_iteration = IteratorPrimordials::get(&ctx)?.end_of_iteration.clone(); Class::instance( ctx, Self { objects, prevent_cancel, is_finished: Rc::new(AtomicBool::new(false)), ongoing_promise: None, promise_primordials, end_of_iteration, }, ) } } // Custom JsClass implementation needed until prototype, function names and function lengths can be influenced in the class derivation macro impl<'js> JsClass<'js> for ReadableStreamAsyncIterator<'js> { const NAME: &'static str = "ReadableStreamAsyncIterator"; type Mutable = rquickjs::class::Writable; fn prototype(ctx: &Ctx<'js>) -> Result>> { use rquickjs::class::impl_::MethodImplementor; let proto = Object::new(ctx.clone())?; let primordial = IteratorPrimordials::get(ctx)?; proto.set_prototype(Some(&primordial.async_iterator_prototype))?; let implementor = rquickjs::class::impl_::MethodImpl::::new(); implementor.implement(&proto)?; let next_fn: Function<'js> = proto.get("next")?; // yup, the wpt tests really do check these. next_fn.set_name("next")?; let return_fn: Function<'js> = proto.get("return")?; return_fn.set_name("return")?; return_fn.set_length(1)?; Ok(Some(proto)) } fn constructor(ctx: &Ctx<'js>) -> Result>> { use rquickjs::class::impl_::ConstructorCreator; let implementor = rquickjs::class::impl_::ConstructorCreate::::new(); (&implementor).create_constructor(ctx) } } impl<'js> IntoJs<'js> for ReadableStreamAsyncIterator<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { let cls = Class::::instance(ctx.clone(), self)?; rquickjs::IntoJs::into_js(cls, ctx) } } impl<'js> FromJs<'js> for ReadableStreamAsyncIterator<'js> where for<'a> rquickjs::class::impl_::CloneWrapper<'a, Self>: rquickjs::class::impl_::CloneTrait, { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { use rquickjs::class::impl_::CloneTrait; let value = Class::::from_js(ctx, value)?; let borrow = value.try_borrow()?; Ok(rquickjs::class::impl_::CloneWrapper(&*borrow).wrap_clone()) } } #[methods] impl<'js> ReadableStreamAsyncIterator<'js> { fn next(ctx: Ctx<'js>, iterator: This>) -> Result> { let is_finished = iterator.is_finished.clone(); let next_steps = move |ctx: Ctx<'js>, iterator: &Self, iterator_class: Class<'js, Self>| { if is_finished.load(Ordering::Acquire) { return promise_resolved_with( &ctx, &iterator.promise_primordials, Ok(ReadableStreamReadResult { value: None, done: true, } .into_js(&ctx)?), ); } let next_promise = Self::next_steps(&ctx, iterator)?; upon_promise( ctx, next_promise, move |ctx, result: std::result::Result, _>| { let mut iterator = OwnedBorrowMut::from_class(iterator_class); match result { Ok(next) => { iterator.ongoing_promise = None; if next.as_symbol() == Some(&iterator.end_of_iteration) { iterator.is_finished.store(true, Ordering::Release); Ok(ReadableStreamReadResult { value: None, done: true, }) } else { Ok(ReadableStreamReadResult { value: Some(next), done: false, }) } }, Err(reason) => { iterator.ongoing_promise = None; iterator.is_finished.store(true, Ordering::Release); Err(ctx.throw(reason)) }, } }, ) }; let (iterator_class, mut iterator) = class_from_owned_borrow_mut(iterator.0); let ongoing_promise = iterator.ongoing_promise.take(); let ongoing_promise = match ongoing_promise { Some(ongoing_promise) => upon_promise( ctx, ongoing_promise, move |ctx, _: std::result::Result, _>| { let iterator = OwnedBorrow::from_class(iterator_class.clone()); next_steps(ctx, &iterator, iterator_class) }, )?, None => next_steps(ctx, &iterator, iterator_class)?, }; Ok(iterator.ongoing_promise.insert(ongoing_promise).clone()) } #[qjs(rename = "return")] fn r#return( ctx: Ctx<'js>, iterator: This>, value: Opt>, ) -> Result> { let is_finished = iterator.is_finished.clone(); let value = value.0.unwrap_or_undefined(&ctx); let return_steps = { let value = value.clone(); move |ctx: Ctx<'js>, iterator: &Self| { if is_finished.swap(true, Ordering::AcqRel) { return promise_resolved_with( &ctx, &iterator.promise_primordials, Ok(ReadableStreamReadResult { value: Some(value), done: true, } .into_js(&ctx)?), ); } Self::return_steps(ctx.clone(), iterator, value) } }; let (iterator_class, mut iterator) = class_from_owned_borrow_mut(iterator.0); let ongoing_promise = iterator.ongoing_promise.take(); let ongoing_promise = match ongoing_promise { Some(ongoing_promise) => upon_promise( ctx.clone(), ongoing_promise, move |ctx, _: std::result::Result, _>| { let iterator = OwnedBorrow::from_class(iterator_class.clone()); return_steps(ctx, &iterator) }, )?, None => return_steps(ctx.clone(), &iterator)?, }; iterator.ongoing_promise = Some(ongoing_promise.clone()); upon_promise_fulfilment(ctx, ongoing_promise, move |_, ()| { Ok(ReadableStreamReadResult { value: Some(value), done: true, }) }) } } impl<'js> ReadableStreamAsyncIterator<'js> { // The get the next iteration result steps for a ReadableStream, given stream and iterator, are: fn next_steps(ctx: &Ctx<'js>, iterator: &Self) -> Result> { // Let reader be iterator’s reader. let objects = iterator.objects.clone(); // Let promise be a new promise. let promise = ResolveablePromise::new(ctx)?; // Let readRequest be a new read request with the following items: #[derive(Trace)] struct ReadRequest<'js> { promise: ResolveablePromise<'js>, end_of_iteration: Symbol<'js>, } impl<'js> ReadableStreamReadRequest<'js> for ReadRequest<'js> { fn chunk_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, chunk: Value<'js>, ) -> Result> { // Resolve promise with chunk. self.promise.resolve(chunk)?; Ok(objects) } fn close_steps( &self, _ctx: &Ctx<'js>, mut objects: ReadableStreamDefaultReaderObjects<'js>, ) -> Result> { // Perform ! ReadableStreamDefaultReaderRelease(reader). objects = ReadableStreamDefaultReader::readable_stream_default_reader_release(objects)?; // Resolve promise with end of iteration. self.promise.resolve(self.end_of_iteration.clone())?; Ok(objects) } fn error_steps( &self, mut objects: ReadableStreamDefaultReaderObjects<'js>, reason: Value<'js>, ) -> Result> { // Perform ! ReadableStreamDefaultReaderRelease(reader). objects = ReadableStreamDefaultReader::readable_stream_default_reader_release(objects)?; // Reject promise with e. self.promise.reject(reason)?; Ok(objects) } } let objects = ReadableStreamObjects::from_class(objects); // Perform ! ReadableStreamDefaultReaderRead(this, readRequest). ReadableStreamDefaultReader::readable_stream_default_reader_read( ctx, objects, ReadRequest { promise: promise.clone(), end_of_iteration: iterator.end_of_iteration.clone(), }, )?; // Return promise. Ok(promise.promise) } // The asynchronous iterator return steps for a ReadableStream, given stream, iterator, and arg, are: fn return_steps(ctx: Ctx<'js>, iterator: &Self, arg: Value<'js>) -> Result> { // Let reader be iterator’s reader. let objects = ReadableStreamObjects::from_class(iterator.objects.clone()); // If iterator’s prevent cancel is false: if !iterator.prevent_cancel { // Let result be ! ReadableStreamReaderGenericCancel(reader, arg). let (result, objects) = ReadableStreamGenericReader::readable_stream_reader_generic_cancel( ctx.clone(), objects, arg, )?; // Perform ! ReadableStreamDefaultReaderRelease(reader). ReadableStreamDefaultReader::readable_stream_default_reader_release(objects)?; // Return result. return Ok(result); } // Perform ! ReadableStreamDefaultReaderRelease(reader). ReadableStreamDefaultReader::readable_stream_default_reader_release(objects)?; // Return a promise resolved with undefined. Ok(iterator .promise_primordials .promise_resolved_with_undefined .clone()) } } #[derive(Clone, JsLifetime, Trace)] pub(crate) struct IteratorPrimordials<'js> { end_of_iteration: Symbol<'js>, sync_to_async_iterator: Function<'js>, async_iterator_prototype: Object<'js>, } impl<'js> Primordial<'js> for IteratorPrimordials<'js> { fn new(ctx: &Ctx<'js>) -> Result where Self: Sized, { let sync_to_async_iterator = ctx.eval::, _>( r#" (syncIterable) => (async function* () { return yield* syncIterable; })() "#, )?; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator // ```js // const AsyncIteratorPrototype = Object.getPrototypeOf( // Object.getPrototypeOf(Object.getPrototypeOf((async function* () {})())), // ); // ``` let async_iterator_prototype = ctx .eval::, _>("(async function* () {})()")? .get_prototype() .as_ref() .and_then(Object::get_prototype) .as_ref() .and_then(Object::get_prototype) .expect("async iterator prototype not found"); Ok(Self { end_of_iteration: Symbol::for_description(ctx, "async iterator end of iteration")?, sync_to_async_iterator, async_iterator_prototype, }) } } // https://tc39.es/ecma262/multipage/abstract-operations.html#sec-getmethod fn get_method<'js>( ctx: &Ctx<'js>, value: Value<'js>, property: impl IntoAtom<'js>, ) -> Result>> { // 1. Let func be ? GetV(V, P). let func = get_v(ctx, value, property)?; // 2. If func is either undefined or null, return undefined. if func.is_undefined() || func.is_null() { return Ok(None); } match func.into_function() { // 3. If IsCallable(func) is false, throw a TypeError exception. None => Err(Exception::throw_type(ctx, "not a function")), // 4. Return func. Some(func) => Ok(Some(func)), } } // https://tc39.es/ecma262/multipage/abstract-operations.html#sec-getv fn get_v<'js>( ctx: &Ctx<'js>, value: Value<'js>, property: impl IntoAtom<'js>, ) -> Result> { // 1. Let O be ? ToObject(V). let o: Object<'js> = to_object(ctx, value)?; // 2. Return ? O.[[Get]](P, V). o.get(property) } // https://tc39.es/ecma262/multipage/abstract-operations.html#sec-toobject fn to_object<'js>(ctx: &Ctx<'js>, value: Value<'js>) -> Result> { let base_primordials = BasePrimordials::get(ctx)?; match value.type_of() { // Return a new Boolean object whose [[BooleanData]] internal slot is set to argument Type::Bool => base_primordials.constructor_bool.construct((value,))?, // Return a new Number object whose [[NumberData]] internal slot is set to argument Type::Int | Type::Float => base_primordials.constructor_number.construct((value,))?, // Return a new String object whose [[StringData]] internal slot is set to argument Type::String => base_primordials.constructor_string.construct((value,))?, // Return a new Symbol object whose [[SymbolData]] internal slot is set to argument // `new Symbol` is invalid but we can use `Object(symbol) Type::Symbol => base_primordials.constructor_object.call((value,))?, // Return a new BigInt object whose [[BigIntData]] internal slot is set to argument // `new BigInt` is invalid but we can use `Object(bigInt) Type::BigInt => base_primordials.constructor_object.call((value,))?, // Return argument typ if typ.interpretable_as(Type::Object) => Ok(value.into_object().unwrap()), // Throw a TypeError exception. typ => Err(Exception::throw_type( ctx, &format!("{typ} cannot be converted to an object"), )), } } #[cfg(test)] mod tests { use super::*; use llrt_test::test_sync_with; use rquickjs::BigInt; #[tokio::test] async fn test_to_object() { test_sync_with(|ctx| { BasePrimordials::init(&ctx)?; let good_values: [Value; 7] = [ Value::new_bool(ctx.clone(), false), Value::new_int(ctx.clone(), 123), Value::new_float(ctx.clone(), 1.5), rquickjs::String::from_str(ctx.clone(), "abc")?.into_value(), Symbol::for_description(&ctx, "def")?.into_value(), BigInt::from_i64(ctx.clone(), 123456)?.into_value(), Object::new(ctx.clone())?.into_value(), ]; for value in good_values { to_object(&ctx, value)?; } let bad_values: [Value; 3] = [ Value::new_uninitialized(ctx.clone()), Value::new_undefined(ctx.clone()), Value::new_null(ctx.clone()), ]; for value in bad_values { let ty = value.type_of(); if to_object(&ctx, value).is_ok() { panic!("Values of type {ty} should not be convertible to object") } } Ok(()) }) .await; } } ================================================ FILE: modules/llrt_stream_web/src/readable/mod.rs ================================================ mod byob_reader; mod byte_controller; mod controller; mod default_controller; mod default_reader; mod iterator; mod objects; mod reader; mod stream; pub(crate) use byob_reader::{ArrayConstructorPrimordials, ReadableStreamBYOBReader}; pub(crate) use byte_controller::{ReadableByteStreamController, ReadableStreamBYOBRequest}; pub(crate) use default_controller::ReadableStreamDefaultController; pub(crate) use default_reader::ReadableStreamDefaultReader; pub(crate) use iterator::IteratorPrimordials; pub(crate) use stream::{ReadableStream, ReadableStreamClass}; ================================================ FILE: modules/llrt_stream_web/src/readable/objects.rs ================================================ use rquickjs::{ class::{OwnedBorrowMut, Trace, Tracer}, Result, }; use crate::readable::{ byob_reader::ReadableStreamBYOBReaderOwned, byte_controller::ReadableByteStreamControllerOwned, controller::{ ReadableStreamController, ReadableStreamControllerClass, ReadableStreamControllerOwned, }, default_controller::ReadableStreamDefaultControllerOwned, default_reader::{ReadableStreamDefaultReaderOrUndefined, ReadableStreamDefaultReaderOwned}, reader::{ReadableStreamReader, ReadableStreamReaderOwned, UndefinedReader}, stream::{ReadableStream, ReadableStreamClass, ReadableStreamOwned}, }; pub(super) struct ReadableStreamObjects<'js, C, R> { pub(super) stream: ReadableStreamOwned<'js>, pub(super) controller: C, pub(super) reader: R, } pub(super) type ReadableStreamDefaultControllerObjects<'js, R> = ReadableStreamObjects<'js, ReadableStreamDefaultControllerOwned<'js>, R>; pub(super) type ReadableStreamDefaultReaderObjects<'js, C = ReadableStreamControllerOwned<'js>> = ReadableStreamObjects<'js, C, ReadableStreamDefaultReaderOwned<'js>>; pub(super) type ReadableByteStreamObjects<'js, R> = ReadableStreamObjects<'js, ReadableByteStreamControllerOwned<'js>, R>; pub(super) type ReadableStreamBYOBObjects<'js> = ReadableStreamObjects< 'js, ReadableByteStreamControllerOwned<'js>, ReadableStreamBYOBReaderOwned<'js>, >; pub(super) struct ReadableStreamClassObjects< 'js, C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>, > { pub(super) stream: ReadableStreamClass<'js>, pub(super) controller: C::Class, pub(super) reader: R::Class, } // derive(Clone) isn't clever enough to figure out that C and R don't need to implement Clone, but only C::Class and R::Class. impl<'js, C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>> Clone for ReadableStreamClassObjects<'js, C, R> { fn clone(&self) -> Self { Self { stream: self.stream.clone(), controller: self.controller.clone(), reader: self.reader.clone(), } } } // derive(Trace) isn't clever enough to figure out that C and R don't need to implement Trace, but only C::Class and R::Class. impl<'js, C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>> Trace<'js> for ReadableStreamClassObjects<'js, C, R> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.stream.trace(tracer); self.controller.trace(tracer); self.reader.trace(tracer); } } impl<'js, C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>> ReadableStreamClassObjects<'js, C, R> { pub(super) fn set_reader>( self, reader: RNext::Class, ) -> ReadableStreamClassObjects<'js, C, RNext> { drop(self.reader); ReadableStreamClassObjects { stream: self.stream, controller: self.controller, reader, } } } impl<'js, C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>> ReadableStreamObjects<'js, C, R> { pub(super) fn with_assert_default_controller( mut self, f: impl FnOnce( ReadableStreamDefaultControllerObjects<'js, R>, ) -> Result>, ) -> Result { ((), self) = self.with_controller( (), |(), controller| Ok(((), f(controller)?)), |_, _| panic!("expected default controller, found byte controller"), )?; Ok(self) } pub(super) fn with_assert_byte_controller( mut self, f: impl FnOnce(ReadableByteStreamObjects<'js, R>) -> Result>, ) -> Result { ((), self) = self.with_controller( (), |_, _| panic!("expected byte controller, found default controller"), |(), controller| Ok(((), f(controller)?)), )?; Ok(self) } pub(super) fn with_controller( self, ctx: Ctx, default: impl FnOnce( Ctx, ReadableStreamDefaultControllerObjects<'js, R>, ) -> Result<(O, ReadableStreamDefaultControllerObjects<'js, R>)>, byte: impl FnOnce( Ctx, ReadableByteStreamObjects<'js, R>, ) -> Result<(O, ReadableByteStreamObjects<'js, R>)>, ) -> Result<(O, Self)> { let ((out, stream, reader), controller) = self.controller.with_controller( (ctx, self.stream, self.reader), |(ctx, stream, reader), controller| { let (out, objects) = default( ctx, ReadableStreamObjects { stream, controller, reader, }, )?; Ok(((out, objects.stream, objects.reader), objects.controller)) }, |(ctx, stream, reader), controller| { let (out, objects) = byte( ctx, ReadableStreamObjects { stream, controller, reader, }, )?; Ok(((out, objects.stream, objects.reader), objects.controller)) }, )?; Ok(( out, Self { stream, controller, reader, }, )) } pub(super) fn with_assert_byob_reader( self, f: impl FnOnce(ReadableStreamBYOBObjects<'js>) -> Result>, ) -> Result { self.with_reader( |_| panic!("expected byob reader, found default reader"), f, |_| panic!("expected byob reader, found no reader"), ) } pub(super) fn with_assert_default_reader( self, f: impl FnOnce( ReadableStreamDefaultReaderObjects<'js, C>, ) -> Result>, ) -> Result { self.with_reader( f, |_| panic!("expected default reader, found byob reader"), |_| panic!("expected default reader, found no reader"), ) } pub(super) fn with_reader( mut self, default: impl FnOnce( ReadableStreamDefaultReaderObjects<'js, C>, ) -> Result>, byob: impl FnOnce(ReadableStreamBYOBObjects<'js>) -> Result>, none: impl FnOnce( ReadableStreamObjects<'js, C, UndefinedReader>, ) -> Result>, ) -> Result { ((self.stream, self.controller), self.reader) = self.reader.with_reader( (self.stream, self.controller), |(stream, controller), reader| { let objects = default(ReadableStreamObjects { stream, controller, reader, })?; Ok(((objects.stream, objects.controller), objects.reader)) }, |(mut stream, mut controller), mut reader| { ((stream, reader), controller) = controller.with_controller( (stream, reader), |_, _| panic!("byob reader must have a byte controller"), |(stream, reader), controller| { let objects = byob(ReadableStreamObjects { stream, controller, reader, })?; Ok(((objects.stream, objects.reader), objects.controller)) }, )?; Ok(((stream, controller), reader)) }, |(stream, controller)| { let objects = none(ReadableStreamObjects { stream, controller, reader: UndefinedReader, })?; Ok((objects.stream, objects.controller)) }, )?; Ok(self) } pub(super) fn into_inner(self) -> ReadableStreamClassObjects<'js, C, R> { ReadableStreamClassObjects { stream: self.stream.into_inner(), controller: self.controller.into_inner(), reader: self.reader.into_inner(), } } pub(super) fn from_class(objects_class: ReadableStreamClassObjects<'js, C, R>) -> Self { Self { stream: OwnedBorrowMut::from_class(objects_class.stream), controller: C::from_class(objects_class.controller), reader: R::from_class(objects_class.reader), } } pub(super) fn from_class_no_reader( objects_class: ReadableStreamClassObjects<'js, C, R>, ) -> ReadableStreamObjects<'js, C, UndefinedReader> { ReadableStreamObjects { stream: OwnedBorrowMut::from_class(objects_class.stream), controller: C::from_class(objects_class.controller), reader: UndefinedReader, } } pub(super) fn clear_reader(self) -> ReadableStreamObjects<'js, C, UndefinedReader> { drop(self.reader); ReadableStreamObjects { stream: self.stream, controller: self.controller, reader: UndefinedReader, } } } impl<'js> ReadableStreamDefaultControllerObjects<'js, Option>> { pub(super) fn from_default_controller( controller: ReadableStreamDefaultControllerOwned<'js>, ) -> Self { Self::new_default( OwnedBorrowMut::from_class(controller.stream.clone()), controller, ) } pub(super) fn new_default( stream: ReadableStreamOwned<'js>, controller: ReadableStreamDefaultControllerOwned<'js>, ) -> Self { ReadableStreamObjects { stream, controller, reader: UndefinedReader, } .refresh_reader() } } impl<'js, R: ReadableStreamReader<'js>> ReadableStreamDefaultControllerObjects<'js, R> { pub(super) fn refresh_reader( mut self, ) -> ReadableStreamDefaultControllerObjects<'js, Option>> { drop(self.reader); let reader = self.stream.reader_mut(); ReadableStreamObjects { stream: self.stream, controller: self.controller, reader: ReadableStreamReader::try_from_erased(reader) .expect("default controller must have default reader or no reader"), } } } impl<'js> ReadableByteStreamObjects<'js, UndefinedReader> { pub(super) fn from_byte_controller(controller: ReadableByteStreamControllerOwned<'js>) -> Self { Self::new_byte( OwnedBorrowMut::from_class(controller.stream.clone()), controller, ) } pub(super) fn new_byte( stream: ReadableStreamOwned<'js>, controller: ReadableByteStreamControllerOwned<'js>, ) -> Self { ReadableStreamObjects { stream, controller, reader: UndefinedReader, } } pub(super) fn set_reader>( self, reader: RNext, ) -> ReadableByteStreamObjects<'js, RNext> { ReadableStreamObjects { stream: self.stream, controller: self.controller, reader, } } } impl<'js> ReadableStreamBYOBObjects<'js> { pub(super) fn from_byob_reader(reader: ReadableStreamBYOBReaderOwned<'js>) -> Self { let stream = OwnedBorrowMut::from_class( reader .generic .stream .clone() .expect("ReadableStreamBYOBReader must have a stream"), ); let controller = match &stream.controller { ReadableStreamControllerClass::ReadableStreamByteController(c) => c.clone(), _ => panic!("ReadableStreamBYOBReader stream must have byte controller"), }; Self { stream, controller: OwnedBorrowMut::from_class(controller), reader, } } } impl<'js, R: ReadableStreamReader<'js>> ReadableByteStreamObjects<'js, R> { pub(super) fn refresh_reader( mut self, ) -> ReadableByteStreamObjects<'js, Option>> { drop(self.reader); let reader = self.stream.reader_mut(); ReadableStreamObjects { stream: self.stream, controller: self.controller, reader, } } } impl<'js, C: ReadableStreamController<'js>, R: ReadableStreamDefaultReaderOrUndefined<'js>> ReadableStreamObjects<'js, C, R> { pub(super) fn with_some_reader( self, default: impl FnOnce( ReadableStreamDefaultReaderObjects<'js, C>, ) -> Result>, none: impl FnOnce( ReadableStreamObjects<'js, C, UndefinedReader>, ) -> Result>, ) -> Result { self.with_reader( default, |_| panic!("byob reader cannot implement DefaultReaderOrUndefined"), none, ) } } impl<'js> ReadableStreamObjects<'js, ReadableStreamControllerOwned<'js>, UndefinedReader> { pub(super) fn from_stream(stream: ReadableStreamOwned<'js>) -> Self { let controller = ReadableStreamControllerOwned::from_class(stream.controller.clone()); Self::new(stream, controller) } fn new( stream: OwnedBorrowMut<'js, ReadableStream<'js>>, controller: ReadableStreamControllerOwned<'js>, ) -> Self { ReadableStreamObjects { stream, controller, reader: UndefinedReader, } } } impl<'js, R: ReadableStreamReader<'js>> ReadableStreamObjects<'js, ReadableStreamControllerOwned<'js>, R> { pub(super) fn refresh_reader( mut self, ) -> ReadableStreamObjects< 'js, ReadableStreamControllerOwned<'js>, Option>, > { drop(self.reader); let reader = self.stream.reader_mut(); ReadableStreamObjects { stream: self.stream, controller: self.controller, reader, } } } impl<'js> ReadableStreamDefaultReaderObjects<'js> { pub(super) fn from_default_reader(reader: ReadableStreamDefaultReaderOwned<'js>) -> Self { let stream = OwnedBorrowMut::from_class( reader .generic .stream .clone() .expect("ReadableStreamDefaultReader must have a stream"), ); let controller = ReadableStreamControllerOwned::from_class(stream.controller.clone()); Self { stream, controller, reader, } } } ================================================ FILE: modules/llrt_stream_web/src/readable/reader.rs ================================================ use rquickjs::{ class::{OwnedBorrowMut, Trace, Tracer}, function::Constructor, Ctx, Error, FromJs, Function, IntoJs, JsLifetime, Promise, Result, Value, }; use crate::{ readable::{ byob_reader::ReadableStreamBYOBReader, byob_reader::{ReadableStreamBYOBReaderClass, ReadableStreamBYOBReaderOwned}, controller::ReadableStreamController, default_reader::{ ReadableStreamDefaultReader, ReadableStreamDefaultReaderClass, ReadableStreamDefaultReaderOwned, }, objects::ReadableStreamObjects, stream::{ReadableStream, ReadableStreamClass, ReadableStreamOwned, ReadableStreamState}, }, utils::promise::{PromisePrimordials, ResolveablePromise}, }; pub(super) trait ReadableStreamReader<'js>: Sized + 'js { type Class: Clone + Trace<'js>; fn with_reader( self, ctx: C, default: impl FnOnce( C, ReadableStreamDefaultReaderOwned<'js>, ) -> Result<(C, ReadableStreamDefaultReaderOwned<'js>)>, byob: impl FnOnce( C, ReadableStreamBYOBReaderOwned<'js>, ) -> Result<(C, ReadableStreamBYOBReaderOwned<'js>)>, none: impl FnOnce(C) -> Result, ) -> Result<(C, Self)>; fn into_inner(self) -> Self::Class; fn from_class(class: Self::Class) -> Self; fn try_from_erased(erased: Option>) -> Option; } // typedef (ReadableStreamDefaultController or ReadableByteStreamController) ReadableStreamController; #[derive(JsLifetime, Clone, PartialEq, Eq)] pub enum ReadableStreamReaderClass<'js> { ReadableStreamDefaultReader(ReadableStreamDefaultReaderClass<'js>), ReadableStreamBYOBReader(ReadableStreamBYOBReaderClass<'js>), } impl<'js> ReadableStreamReaderClass<'js> { pub(super) fn closed_promise(&self) -> Promise<'js> { match self { Self::ReadableStreamDefaultReader(r) => { r.borrow().generic.closed_promise.promise.clone() }, Self::ReadableStreamBYOBReader(r) => r.borrow().generic.closed_promise.promise.clone(), } } } impl<'js> From> for ReadableStreamReaderClass<'js> { fn from(value: ReadableStreamDefaultReaderClass<'js>) -> Self { Self::ReadableStreamDefaultReader(value) } } impl<'js> From> for ReadableStreamReaderClass<'js> { fn from(value: ReadableStreamBYOBReaderClass<'js>) -> Self { Self::ReadableStreamBYOBReader(value) } } pub(super) enum ReadableStreamReaderOwned<'js> { ReadableStreamDefaultReader(ReadableStreamDefaultReaderOwned<'js>), ReadableStreamBYOBReader(ReadableStreamBYOBReaderOwned<'js>), } impl<'js> ReadableStreamReader<'js> for ReadableStreamReaderOwned<'js> { type Class = ReadableStreamReaderClass<'js>; fn with_reader( self, ctx: C, default: impl FnOnce( C, ReadableStreamDefaultReaderOwned<'js>, ) -> Result<(C, ReadableStreamDefaultReaderOwned<'js>)>, byob: impl FnOnce( C, ReadableStreamBYOBReaderOwned<'js>, ) -> Result<(C, ReadableStreamBYOBReaderOwned<'js>)>, _: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { match self { Self::ReadableStreamDefaultReader(r) => { let (ctx, r) = default(ctx, r)?; Ok((ctx, Self::ReadableStreamDefaultReader(r))) }, Self::ReadableStreamBYOBReader(r) => { let (ctx, r) = byob(ctx, r)?; Ok((ctx, Self::ReadableStreamBYOBReader(r))) }, } } fn into_inner(self) -> Self::Class { match self { ReadableStreamReaderOwned::ReadableStreamDefaultReader(r) => { ReadableStreamReaderClass::ReadableStreamDefaultReader(r.into_inner()) }, ReadableStreamReaderOwned::ReadableStreamBYOBReader(r) => { ReadableStreamReaderClass::ReadableStreamBYOBReader(r.into_inner()) }, } } fn from_class(class: Self::Class) -> Self { match class { ReadableStreamReaderClass::ReadableStreamDefaultReader(r) => { Self::ReadableStreamDefaultReader(OwnedBorrowMut::from_class(r)) }, ReadableStreamReaderClass::ReadableStreamBYOBReader(r) => { Self::ReadableStreamBYOBReader(OwnedBorrowMut::from_class(r)) }, } } fn try_from_erased(erased: Option>) -> Option { erased } } impl<'js, T: ReadableStreamReader<'js>> ReadableStreamReader<'js> for Option { type Class = Option<>::Class>; fn with_reader( self, mut ctx: C, default: impl FnOnce( C, ReadableStreamDefaultReaderOwned<'js>, ) -> Result<(C, ReadableStreamDefaultReaderOwned<'js>)>, byob: impl FnOnce( C, ReadableStreamBYOBReaderOwned<'js>, ) -> Result<(C, ReadableStreamBYOBReaderOwned<'js>)>, none: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { match self { Some(mut reader) => { (ctx, reader) = reader.with_reader(ctx, default, byob, none)?; Ok((ctx, Some(reader))) }, None => Ok((none(ctx)?, None)), } } fn into_inner(self) -> Self::Class { self.map(ReadableStreamReader::into_inner) } fn from_class(class: Self::Class) -> Self { class.map(ReadableStreamReader::from_class) } fn try_from_erased(erased: Option>) -> Option { match erased { Some(r) => Some(Some(T::try_from_erased(Some(r))?)), None => Some(None), } } } #[derive(Clone, Trace)] pub(super) struct UndefinedReader; impl<'js> ReadableStreamReader<'js> for UndefinedReader { type Class = UndefinedReader; fn with_reader( self, ctx: C, _: impl FnOnce( C, ReadableStreamDefaultReaderOwned<'js>, ) -> Result<(C, ReadableStreamDefaultReaderOwned<'js>)>, _: impl FnOnce( C, ReadableStreamBYOBReaderOwned<'js>, ) -> Result<(C, ReadableStreamBYOBReaderOwned<'js>)>, none: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { Ok((none(ctx)?, self)) } fn into_inner(self) -> Self::Class { UndefinedReader } fn from_class(_: Self::Class) -> Self { UndefinedReader } fn try_from_erased(erased: Option>) -> Option { match erased { None => Some(UndefinedReader), _ => None, } } } impl<'js> From> for ReadableStreamReaderOwned<'js> { fn from(value: ReadableStreamDefaultReaderOwned<'js>) -> Self { Self::ReadableStreamDefaultReader(value) } } impl<'js> From> for ReadableStreamReaderOwned<'js> { fn from(value: ReadableStreamBYOBReaderOwned<'js>) -> Self { Self::ReadableStreamBYOBReader(value) } } #[derive(JsLifetime, Trace)] pub struct ReadableStreamGenericReader<'js> { pub(super) closed_promise: ResolveablePromise<'js>, pub(super) stream: Option>, #[qjs(skip_trace)] pub(super) promise_primordials: PromisePrimordials<'js>, #[qjs(skip_trace)] pub(super) constructor_type_error: Constructor<'js>, #[qjs(skip_trace)] pub(super) constructor_range_error: Constructor<'js>, #[qjs(skip_trace)] pub(super) function_array_buffer_is_view: Function<'js>, } impl<'js> ReadableStreamGenericReader<'js> { pub(super) fn readable_stream_reader_generic_initialize( ctx: &Ctx<'js>, stream: OwnedBorrowMut<'js, ReadableStream<'js>>, ) -> Result { let closed_promise = match stream.state { // If stream.[[state]] is "readable", ReadableStreamState::Readable => { // Set reader.[[closedPromise]] to a new promise. ResolveablePromise::new(ctx)? }, // Otherwise, if stream.[[state]] is "closed", ReadableStreamState::Closed => { // Set reader.[[closedPromise]] to a promise resolved with undefined. ResolveablePromise::resolved_with_undefined(&stream.promise_primordials) }, // Otherwise, ReadableStreamState::Errored(ref stored_error) => { // Set reader.[[closedPromise]] to a promise rejected with stream.[[storedError]]. let promise = ResolveablePromise::rejected_with( &stream.promise_primordials, stored_error.clone(), )?; // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true. promise.set_is_handled()?; promise }, }; let promise_primordials = stream.promise_primordials.clone(); let constructor_type_error = stream.constructor_type_error.clone(); let constructor_range_error = stream.constructor_range_error.clone(); let function_array_buffer_is_view = stream.function_array_buffer_is_view.clone(); Ok(Self { // Set reader.[[stream]] to stream. stream: Some(stream.into_inner()), closed_promise, promise_primordials, constructor_type_error, constructor_range_error, function_array_buffer_is_view, }) } pub(super) fn readable_stream_reader_generic_release( &mut self, stream: &mut ReadableStream<'js>, controller_release_steps: impl FnOnce(), ) -> Result<()> { // Let stream be reader.[[stream]]. // Assert: stream is not undefined. // If stream.[[state]] is "readable", reject reader.[[closedPromise]] with a TypeError exception. if let ReadableStreamState::Readable = stream.state { self.closed_promise.reject_with_constructor( &stream.constructor_type_error, "Reader was released and can no longer be used to monitor the stream's closedness", )?; } else { // Otherwise, set reader.[[closedPromise]] to a promise rejected with a TypeError exception. self.closed_promise = ResolveablePromise::rejected_with_constructor( &stream.promise_primordials, &stream.constructor_type_error, "Reader was released and can no longer be used to monitor the stream's closedness", )?; } // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true. self.closed_promise.set_is_handled()?; // Perform ! stream.[[controller]].[[ReleaseSteps]](). controller_release_steps(); // Set stream.[[reader]] to undefined. stream.reader = None; // Set reader.[[stream]] to undefined. self.stream = None; Ok(()) } pub(super) fn readable_stream_reader_generic_cancel< C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>, >( ctx: Ctx<'js>, // Let stream be reader.[[stream]]. objects: ReadableStreamObjects<'js, C, R>, reason: Value<'js>, ) -> Result<(Promise<'js>, ReadableStreamObjects<'js, C, R>)> { // Return ! ReadableStreamCancel(stream, reason). ReadableStream::readable_stream_cancel(ctx, objects, reason) } } impl<'js> ReadableStreamReaderClass<'js> { pub(super) fn acquire_readable_stream_default_reader( ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>, ) -> Result<( ReadableStreamOwned<'js>, ReadableStreamDefaultReaderClass<'js>, )> { ReadableStreamDefaultReader::set_up_readable_stream_default_reader(&ctx, stream) } pub(super) fn acquire_readable_stream_byob_reader( ctx: Ctx<'js>, stream: ReadableStreamOwned<'js>, ) -> Result<(ReadableStreamOwned<'js>, ReadableStreamBYOBReaderClass<'js>)> { ReadableStreamBYOBReader::set_up_readable_stream_byob_reader(ctx, stream) } } impl<'js> IntoJs<'js> for ReadableStreamReaderClass<'js> { fn into_js(self, ctx: &Ctx<'js>) -> Result> { match self { Self::ReadableStreamDefaultReader(r) => r.into_js(ctx), Self::ReadableStreamBYOBReader(r) => r.into_js(ctx), } } } impl<'js> Trace<'js> for ReadableStreamReaderClass<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { match self { Self::ReadableStreamDefaultReader(r) => r.trace(tracer), Self::ReadableStreamBYOBReader(r) => r.trace(tracer), } } } impl<'js> FromJs<'js> for ReadableStreamReaderClass<'js> { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; if let Ok(default) = obj.into_class() { return Ok(Self::ReadableStreamDefaultReader(default)); } if let Ok(default) = obj.into_class() { return Ok(Self::ReadableStreamBYOBReader(default)); } Err(Error::new_from_js(ty_name, "ReadableStreamReader")) } } ================================================ FILE: modules/llrt_stream_web/src/readable/stream/algorithms.rs ================================================ use std::rc::Rc; use llrt_utils::option::{Null, Undefined}; use rquickjs::prelude::OnceFn; use rquickjs::{ class::Trace, prelude::This, Ctx, Function, JsLifetime, Object, Promise, Result, Value, }; use crate::{ readable::controller::ReadableStreamControllerClass, utils::promise::{promise_resolved_with, PromisePrimordials}, }; #[derive(Clone)] pub(crate) enum StartAlgorithm<'js> { ReturnUndefined, Function { f: Function<'js>, underlying_source: Null>>, }, } impl<'js> StartAlgorithm<'js> { pub(crate) fn call( &self, ctx: Ctx<'js>, controller: ReadableStreamControllerClass<'js>, ) -> Result> { match self { StartAlgorithm::ReturnUndefined => Ok(Value::new_undefined(ctx.clone())), StartAlgorithm::Function { f, underlying_source, } => f.call::<_, Value>((This(underlying_source.clone()), controller)), } } } #[derive(Trace, Clone)] pub(crate) enum PullAlgorithm<'js> { ReturnPromiseUndefined, Function { f: Function<'js>, underlying_source: Null>>, }, RustFunction(#[qjs(skip_trace)] Rc>), } unsafe impl<'js> JsLifetime<'js> for PullAlgorithm<'js> { type Changed<'to> = PullAlgorithm<'to>; } type PullRustFunction<'js> = Box, ReadableStreamControllerClass<'js>) -> Result> + 'js>; impl<'js> PullAlgorithm<'js> { pub(super) fn from_fn( f: impl Fn(Ctx<'js>, ReadableStreamControllerClass<'js>) -> Result> + 'js, ) -> Self { Self::RustFunction(Rc::new(Box::new(f))) } pub(crate) fn call( &self, ctx: Ctx<'js>, promise_primordials: &PromisePrimordials<'js>, controller: ReadableStreamControllerClass<'js>, ) -> Result> { match self { PullAlgorithm::ReturnPromiseUndefined => { Ok(promise_primordials.promise_resolved_with_undefined.clone()) }, PullAlgorithm::Function { f, underlying_source, } => promise_resolved_with( &ctx, promise_primordials, f.call::<_, Value>((This(underlying_source.clone()), controller)), ), PullAlgorithm::RustFunction(f) => f(ctx, controller), } } } #[derive(Clone, Trace)] pub(crate) enum CancelAlgorithm<'js> { ReturnPromiseUndefined, Function { f: Function<'js>, underlying_source: Null>>, }, RustFunction(#[qjs(skip_trace)] Rc>>), } unsafe impl<'js> JsLifetime<'js> for CancelAlgorithm<'js> { type Changed<'to> = CancelAlgorithm<'to>; } type CancelRustFunction<'js> = Box) -> Result> + 'js>; impl<'js> CancelAlgorithm<'js> { pub(super) fn from_fn(f: impl FnOnce(Value<'js>) -> Result> + 'js) -> Self { Self::RustFunction(Rc::new(OnceFn::new(Box::new(f)))) } pub(crate) fn call( &self, ctx: Ctx<'js>, promise_primordials: &PromisePrimordials<'js>, reason: Value<'js>, ) -> Result> { match self { CancelAlgorithm::ReturnPromiseUndefined => { Ok(promise_primordials.promise_resolved_with_undefined.clone()) }, CancelAlgorithm::Function { f, underlying_source, } => { let result: Result = f.call((This(underlying_source.clone()), reason)); let promise = promise_resolved_with(&ctx, promise_primordials, result); promise }, CancelAlgorithm::RustFunction(f) => { f.take().expect("cancel algorithm must only be called once")(reason) }, } } } ================================================ FILE: modules/llrt_stream_web/src/readable/stream/mod.rs ================================================ use std::{cell::OnceCell, panic, rc::Rc}; use crate::{ queuing_strategy::{QueuingStrategy, SizeAlgorithm}, readable::{ byob_reader::{ReadableStreamBYOBReader, ReadableStreamReadIntoRequest, ViewBytes}, byte_controller::{ReadableByteStreamController, ReadableByteStreamControllerClass}, controller::{ReadableStreamController, ReadableStreamControllerClass}, default_controller::{ ReadableStreamDefaultController, ReadableStreamDefaultControllerOwned, }, default_reader::{ReadableStreamDefaultReader, ReadableStreamReadRequest}, iterator::{IteratorKind, IteratorRecord, ReadableStreamAsyncIterator}, objects::{ ReadableStreamBYOBObjects, ReadableStreamClassObjects, ReadableStreamDefaultReaderObjects, ReadableStreamObjects, }, reader::{ ReadableStreamReader, ReadableStreamReaderClass, ReadableStreamReaderOwned, UndefinedReader, }, }, readable_writable_pair::ReadableWritablePair, utils::{ promise::{ promise_rejected_catch, promise_rejected_with, promise_rejected_with_constructor, promise_resolved_with, upon_promise_fulfilment, with_promise_result, PromisePrimordials, }, UnwrapOrUndefined, ValueOrUndefined, }, writable::WritableStreamOwned, }; use algorithms::{CancelAlgorithm, PullAlgorithm, StartAlgorithm}; use pipe::StreamPipeOptions; use source::UnderlyingSource; use llrt_utils::{ option::{Null, NullableOpt, Undefined}, primordials::{BasePrimordials, Primordial}, result::ResultExt, }; use rquickjs::{ atom::PredefinedAtom, class::{OwnedBorrowMut, Trace}, function::Constructor, prelude::{List, Opt, This}, Class, Coerced, Ctx, Error, Exception, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, Value, }; pub(super) mod algorithms; mod pipe; pub(super) mod source; mod tee; #[rquickjs::class] #[derive(JsLifetime)] pub(crate) struct ReadableStream<'js> { pub controller: ReadableStreamControllerClass<'js>, pub disturbed: bool, pub state: ReadableStreamState<'js>, pub reader: Option>, pub promise_primordials: PromisePrimordials<'js>, pub constructor_type_error: Constructor<'js>, pub constructor_range_error: Constructor<'js>, pub function_array_buffer_is_view: Function<'js>, } impl<'js> Trace<'js> for ReadableStream<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { self.controller.trace(tracer); self.state.trace(tracer); self.reader.trace(tracer); self.promise_primordials.trace(tracer); self.constructor_type_error.trace(tracer); self.constructor_range_error.trace(tracer); self.function_array_buffer_is_view.trace(tracer); } } pub(crate) type ReadableStreamClass<'js> = Class<'js, ReadableStream<'js>>; pub(crate) type ReadableStreamOwned<'js> = OwnedBorrowMut<'js, ReadableStream<'js>>; #[derive(Debug, Trace, Clone, JsLifetime)] pub enum ReadableStreamState<'js> { Readable, Closed, Errored(Value<'js>), } #[rquickjs::methods(rename_all = "camelCase")] impl<'js> ReadableStream<'js> { // Streams Spec: 4.2.4: https://streams.spec.whatwg.org/#rs-prototype // constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); #[qjs(constructor)] fn new( ctx: Ctx<'js>, underlying_source: Opt>>, queuing_strategy: Opt>>, ) -> Result> { // If underlyingSource is missing, set it to null. let underlying_source = Null(underlying_source.0); // Let underlyingSourceDict be underlyingSource, converted to an IDL value of type UnderlyingSource. let underlying_source_dict = match underlying_source { Null(None) | Null(Some(Undefined(None))) => UnderlyingSource::default(), Null(Some(Undefined(Some(ref obj)))) => UnderlyingSource::from_object(obj.clone())?, }; let promise_primordials = PromisePrimordials::get(&ctx)?.clone(); let base_primordials = BasePrimordials::get(&ctx)?; let stream_class = Class::instance( ctx.clone(), Self { // Set stream.[[state]] to "readable". state: ReadableStreamState::Readable, // Set stream.[[reader]] and stream.[[storedError]] to undefined. reader: None, // Set stream.[[disturbed]] to false. disturbed: false, controller: ReadableStreamControllerClass::Uninitialised, constructor_type_error: base_primordials.constructor_type_error.clone(), constructor_range_error: base_primordials.constructor_range_error.clone(), function_array_buffer_is_view: base_primordials .function_array_buffer_is_view .clone(), promise_primordials, }, )?; drop(base_primordials); let stream = OwnedBorrowMut::from_class(stream_class.clone()); let queuing_strategy = queuing_strategy.0.and_then(|qs| qs.0); match underlying_source_dict.r#type { // If underlyingSourceDict["type"] is "bytes": Some(ReadableStreamType::Bytes) => { // If strategy["size"] exists, throw a RangeError exception. if queuing_strategy .as_ref() .and_then(|qs| qs.size.as_ref()) .is_some() { return Err(Exception::throw_range( &ctx, "The strategy for a byte stream cannot have a size function", )); } // Let highWaterMark be ? ExtractHighWaterMark(strategy, 0). let high_water_mark = QueuingStrategy::extract_high_water_mark(&ctx, queuing_strategy, 0.0)?; // Perform ? SetUpReadableByteStreamControllerFromUnderlyingSource(this, underlyingSource, underlyingSourceDict, highWaterMark). ReadableByteStreamController::set_up_readable_byte_stream_controller_from_underlying_source( &ctx, stream, underlying_source, underlying_source_dict, high_water_mark, )?; }, // Otherwise, None => { // Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy). let size_algorithm = QueuingStrategy::extract_size_algorithm(queuing_strategy.as_ref()); // Let highWaterMark be ? ExtractHighWaterMark(strategy, 1). let high_water_mark = QueuingStrategy::extract_high_water_mark(&ctx, queuing_strategy, 1.0)?; // Perform ? SetUpReadableStreamDefaultControllerFromUnderlyingSource(this, underlyingSource, underlyingSourceDict, highWaterMark, sizeAlgorithm). ReadableStreamDefaultController::set_up_readable_stream_default_controller_from_underlying_source( ctx, stream, underlying_source, underlying_source_dict, high_water_mark, size_algorithm, )?; }, } Ok(stream_class) } // static ReadableStream from(any asyncIterable); #[qjs(static)] fn from(ctx: Ctx<'js>, async_iterable: Value<'js>) -> Result> { // Return ? ReadableStreamFromIterable(asyncIterable). Self::readable_stream_from_iterable(&ctx, async_iterable) } // readonly attribute boolean locked; #[qjs(get)] fn locked(&self) -> bool { // Return ! IsReadableStreamLocked(this). self.is_readable_stream_locked() } // Promise cancel(optional any reason); fn cancel( ctx: Ctx<'js>, stream: This>, reason: Opt>, ) -> Result> { // If ! IsReadableStreamLocked(this) is true, return a promise rejected with a TypeError exception. if stream.is_readable_stream_locked() { return promise_rejected_with_constructor( &stream.constructor_type_error, &stream.promise_primordials, "Cannot cancel a stream that already has a reader", ); } let objects = ReadableStreamObjects::from_stream(stream.0).refresh_reader(); let (promise, _) = Self::readable_stream_cancel(ctx.clone(), objects, reason.0.unwrap_or_undefined(&ctx))?; Ok(promise) } // ReadableStreamReader getReader(optional ReadableStreamGetReaderOptions options = {}); fn get_reader( ctx: Ctx<'js>, stream: This>, options: Opt>, ) -> Result> { // If options["mode"] does not exist, return ? AcquireReadableStreamDefaultReader(this). let reader = match options.0 { None | Some(None | Some(ReadableStreamGetReaderOptions { mode: None })) => { let (_, reader) = ReadableStreamReaderClass::acquire_readable_stream_default_reader( ctx.clone(), stream.0, )?; reader.into() }, // Return ? AcquireReadableStreamBYOBReader(this). Some(Some(ReadableStreamGetReaderOptions { mode: Some(ReadableStreamReaderMode::Byob), })) => { let (_, reader) = ReadableStreamReaderClass::acquire_readable_stream_byob_reader( ctx.clone(), stream.0, )?; reader.into() }, }; Ok(reader) } // ReadableStream pipeThrough(ReadableWritablePair transform, optional StreamPipeOptions options = {}); fn pipe_through( ctx: Ctx<'js>, stream: This>, transform: ReadableWritablePair<'js>, options: NullableOpt>, ) -> Result> { // If ! IsReadableStreamLocked(this) is true, throw a TypeError exception. if stream.is_readable_stream_locked() { return Err(Exception::throw_type( &ctx, "ReadableStream.prototype.pipeThrough cannot be used on a locked ReadableStream", )); } let readable_class = transform.readable.clone(); let writable = OwnedBorrowMut::from_class(transform.writable); // If ! IsWritableStreamLocked(transform["writable"]) is true, throw a TypeError exception. if writable.is_writable_stream_locked() { return Err(Exception::throw_type( &ctx, "ReadableStream.prototype.pipeThrough cannot be used on a locked WritableStream", )); } // Let signal be options["signal"] if it exists, or undefined otherwise. let options = options.0.unwrap_or_default(); // Let promise be ! ReadableStreamPipeTo(this, transform["writable"], options["preventClose"], options["preventAbort"], options["preventCancel"], signal). let promise = ReadableStream::readable_stream_pipe_to( ctx.clone(), stream.0, writable, options.prevent_close, options.prevent_abort, options.prevent_cancel, options.signal, )?; // Set promise.[[PromiseIsHandled]] to true. let () = promise .catch()? .call((This(promise.clone()), Function::new(ctx, || {})))?; // Return transform["readable"]. Ok(readable_class) } // Promise pipeTo(WritableStream destination, optional StreamPipeOptions options = {}); fn pipe_to( ctx: Ctx<'js>, stream: This>, destination: Value<'js>, options: NullableOpt>, ) -> Result> { with_promise_result(&ctx, || { let stream = ReadableStreamOwned::from_class(Class::from_value(&stream.0).or_throw_type( &ctx, "'pipeTo' called on an object that is not a valid instance of ReadableStream.", )?); let options = match options.0 { Some(options) => Some(StreamPipeOptions::from_js(&ctx, options)?), None => None, }; // If ! IsReadableStreamLocked(this) is true, return a promise rejected with a TypeError exception. if stream.is_readable_stream_locked() { return promise_rejected_with_constructor( &stream.constructor_type_error, &stream.promise_primordials, "ReadableStream.prototype.pipeTo cannot be used on a locked ReadableStream", ); } let destination = WritableStreamOwned::from_class( Class::from_value(&destination).or_throw_type(&ctx,"'pipeTo' instructed to pipe to an object that is not a valid instance of WritableStream.")?, ); // If ! IsWritableStreamLocked(destination) is true, return a promise rejected with a TypeError exception. if destination.is_writable_stream_locked() { return promise_rejected_with_constructor( &stream.constructor_type_error, &stream.promise_primordials, "ReadableStream.prototype.pipeTo cannot be used on a locked WritableStream", ); } // Let signal be options["signal"] if it exists, or undefined otherwise. let options = options.unwrap_or_default(); // Return ! ReadableStreamPipeTo(this, destination, options["preventClose"], options["preventAbort"], options["preventCancel"], signal). Self::readable_stream_pipe_to( ctx.clone(), stream, destination, options.prevent_close, options.prevent_abort, options.prevent_cancel, options.signal, ) }) } // sequence tee(); fn tee( ctx: Ctx<'js>, stream: This>, ) -> Result, Class<'js, Self>)>> { // Return ? ReadableStreamTee(this, false). Ok(List(Self::readable_stream_tee( ctx, ReadableStreamObjects::from_stream(stream.0), false, )?)) } #[qjs(rename = PredefinedAtom::SymbolAsyncIterator)] fn async_iterate( ctx: Ctx<'js>, stream: This>, ) -> Result>> { Self::values(ctx, stream, Opt(None)) } fn values( ctx: Ctx<'js>, stream: This>, arg: Opt>, ) -> Result>> { // Let reader be ? AcquireReadableStreamDefaultReader(stream). let (stream, reader) = ReadableStreamReaderClass::acquire_readable_stream_default_reader( ctx.clone(), stream.0, )?; // Let preventCancel be args[0]["preventCancel"]. let prevent_cancel = match arg.0 { None => false, Some(arg) => matches!(arg.get_value_or_undefined("preventCancel")?, Some(true)), }; let promise_primordials = stream.promise_primordials.clone(); let controller = stream.controller.clone(); ReadableStreamAsyncIterator::new( ctx, ReadableStreamClassObjects { stream: stream.into_inner(), controller, reader, }, promise_primordials, prevent_cancel, ) } } impl<'js> ReadableStream<'js> { pub(super) fn readable_stream_error< C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>, >( // Let reader be stream.[[reader]]. mut objects: ReadableStreamObjects<'js, C, R>, e: Value<'js>, ) -> Result> { // Set stream.[[state]] to "errored". // Set stream.[[storedError]] to e. objects.stream.state = ReadableStreamState::Errored(e.clone()); objects = objects.with_reader( // If reader implements ReadableStreamDefaultReader, |mut objects| { // Reject reader.[[closedPromise]] with e. objects.reader .generic .closed_promise .reject(e.clone())?; // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true. objects.reader.generic.closed_promise.set_is_handled()?; // Perform ! ReadableStreamDefaultReaderErrorReadRequests(reader, e). objects = ReadableStreamDefaultReader::readable_stream_default_reader_error_read_requests( objects, e.clone(), )?; Ok(objects) }, // Otherwise, |mut objects| { // Reject reader.[[closedPromise]] with e. objects.reader .generic .closed_promise .reject(e.clone())?; // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true. objects.reader.generic.closed_promise.set_is_handled()?; // Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader, e). objects = ReadableStreamBYOBReader::readable_stream_byob_reader_error_read_into_requests( objects, e.clone(), )?; Ok(objects) }, // If reader is undefined, return. Ok)?; Ok(objects) } pub(super) fn readable_stream_get_num_read_requests( reader: &ReadableStreamDefaultReader, ) -> usize { reader.read_requests.len() } pub(super) fn readable_stream_get_num_read_into_requests( reader: &ReadableStreamBYOBReader, ) -> usize { reader.read_into_requests.len() } pub(super) fn readable_stream_fulfill_read_request>( ctx: &Ctx<'js>, // Let reader be stream.[[reader]]. mut objects: ReadableStreamDefaultReaderObjects<'js, C>, chunk: Value<'js>, done: bool, ) -> Result> { // Let readRequest be reader.[[readRequests]][0]. // Remove readRequest from reader.[[readRequests]]. let read_request = objects .reader .read_requests .pop_front() .expect("ReadableStreamFulfillReadRequest called with empty readRequests"); if done { // If done is true, perform readRequest’s close steps. read_request.close_steps_typed(ctx, objects) } else { // Otherwise, perform readRequest’s chunk steps, given chunk. read_request.chunk_steps_typed(objects, chunk) } } pub(super) fn readable_stream_fulfill_read_into_request( ctx: &Ctx<'js>, mut objects: ReadableStreamBYOBObjects<'js>, chunk: ViewBytes<'js>, done: bool, ) -> Result> { // Let readIntoRequest be reader.[[readIntoRequests]][0]. // Remove readIntoRequest from reader.[[readIntoRequests]]. let read_into_request = objects .reader .read_into_requests .pop_front() .expect("ReadableStreamFulfillReadIntoRequest called with empty readIntoRequests"); if done { // If done is true, perform readIntoRequest’s close steps, given chunk. read_into_request.close_steps(objects, chunk.into_js(ctx)?) } else { // Otherwise, perform readIntoRequest’s chunk steps, given chunk. read_into_request.chunk_steps(objects, chunk.into_js(ctx)?) } } pub(super) fn readable_stream_close< C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>, >( ctx: Ctx<'js>, // Let reader be stream.[[reader]]. mut objects: ReadableStreamObjects<'js, C, R>, ) -> Result> { // Set stream.[[state]] to "closed". objects.stream.state = ReadableStreamState::Closed; objects.with_reader( |mut objects| { // Resolve reader.[[closedPromise]] with undefined. objects.reader.generic.closed_promise.resolve_undefined()?; // If reader implements ReadableStreamDefaultReader, // Let readRequests be reader.[[readRequests]]. // Set reader.[[readRequests]] to an empty list. let read_requests = objects.reader.read_requests.split_off(0); // For each readRequest of readRequests, for read_request in read_requests { // Perform readRequest’s close steps. objects = read_request.close_steps_typed(&ctx, objects)?; } Ok(objects) }, |objects| { objects.reader.generic.closed_promise.resolve_undefined()?; Ok(objects) }, // If reader is undefined, return. Ok, ) } pub(super) fn is_readable_stream_locked(&self) -> bool { // If stream.[[reader]] is undefined, return false. if self.reader.is_none() { return false; } // Return true. true } pub(super) fn readable_stream_add_read_request( &mut self, reader: &mut ReadableStreamDefaultReader<'js>, read_request: impl ReadableStreamReadRequest<'js> + 'js, ) { reader.read_requests.push_back(Box::new(read_request)); } pub(super) fn readable_stream_cancel< C: ReadableStreamController<'js>, R: ReadableStreamReader<'js>, >( ctx: Ctx<'js>, mut objects: ReadableStreamObjects<'js, C, R>, reason: Value<'js>, ) -> Result<(Promise<'js>, ReadableStreamObjects<'js, C, R>)> { // Set stream.[[disturbed]] to true. objects.stream.disturbed = true; match objects.stream.state { // If stream.[[state]] is "closed", return a promise resolved with undefined. ReadableStreamState::Closed => Ok(( // wpt tests expect that this is a new promise every time so we can't duplicate the primordial promise_resolved_with_undefined promise_resolved_with( &ctx, &objects.stream.promise_primordials, Ok(Value::new_undefined(ctx.clone())), )?, objects, )), // If stream.[[state]] is "errored", return a promise rejected with stream.[[storedError]]. ReadableStreamState::Errored(ref stored_error) => Ok(( promise_rejected_with(&objects.stream.promise_primordials, stored_error.clone())?, objects, )), ReadableStreamState::Readable => { // Perform ! ReadableStreamClose(stream). objects = ReadableStream::readable_stream_close(ctx.clone(), objects)?; // Let reader be stream.[[reader]]. // If reader is not undefined and reader implements ReadableStreamBYOBReader, objects = objects.with_reader( Ok, |mut objects| { // Let readIntoRequests be reader.[[readIntoRequests]]. // Set reader.[[readIntoRequests]] to an empty list. let read_into_requests = objects.reader.read_into_requests.split_off(0); // For each readIntoRequest of readIntoRequests, for read_into_request in read_into_requests { // Perform readIntoRequest’s close steps, given undefined. objects = read_into_request .close_steps(objects, Value::new_undefined(ctx.clone()))?; } Ok(objects) }, Ok, )?; // Let sourceCancelPromise be ! stream.[[controller]].[[CancelSteps]](reason). let (source_cancel_promise, objects) = C::cancel_steps(&ctx, objects, reason)?; // Return the result of reacting to sourceCancelPromise with a fulfillment step that returns undefined. let promise = upon_promise_fulfilment(ctx, source_cancel_promise, |_, ()| { Ok(rquickjs::Undefined) })?; Ok((promise, objects)) }, } } pub(super) fn readable_stream_add_read_into_request( reader: &mut ReadableStreamBYOBReader<'js>, read_request: impl ReadableStreamReadIntoRequest<'js> + 'js, ) { // Append readRequest to stream.[[reader]].[[readIntoRequests]]. reader.read_into_requests.push_back(Box::new(read_request)) } // CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm[, highWaterMark, [, sizeAlgorithm]]) performs the following steps: fn create_readable_stream( ctx: Ctx<'js>, start_algorithm: StartAlgorithm<'js>, pull_algorithm: PullAlgorithm<'js>, cancel_algorithm: CancelAlgorithm<'js>, high_water_mark: Option, size_algorithm: Option>, ) -> Result< ReadableStreamClassObjects<'js, ReadableStreamDefaultControllerOwned<'js>, UndefinedReader>, > { // If highWaterMark was not passed, set it to 1. let high_water_mark = high_water_mark.unwrap_or(1.0); // If sizeAlgorithm was not passed, set it to an algorithm that returns 1. let size_algorithm = size_algorithm.unwrap_or(SizeAlgorithm::AlwaysOne); let base_primordials = BasePrimordials::get(&ctx)?; // Let stream be a new ReadableStream. let stream_class = Class::instance( ctx.clone(), Self { // Set stream.[[state]] to "readable". state: ReadableStreamState::Readable, // Set stream.[[reader]] and stream.[[storedError]] to undefined. reader: None, // Set stream.[[disturbed]] to false. disturbed: false, controller: ReadableStreamControllerClass::Uninitialised, promise_primordials: PromisePrimordials::get(&ctx)?.clone(), constructor_range_error: base_primordials.constructor_range_error.clone(), constructor_type_error: base_primordials.constructor_type_error.clone(), function_array_buffer_is_view: base_primordials .function_array_buffer_is_view .clone(), }, )?; drop(base_primordials); // Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm). let controller_class = ReadableStreamDefaultController::set_up_readable_stream_default_controller( ctx, OwnedBorrowMut::from_class(stream_class.clone()), start_algorithm, pull_algorithm, cancel_algorithm, high_water_mark, size_algorithm, )?; // Return stream. Ok(ReadableStreamClassObjects { stream: stream_class, controller: controller_class, reader: UndefinedReader, }) } // CreateReadableByteStream(startAlgorithm, pullAlgorithm, cancelAlgorithm) performs the following steps: fn create_readable_byte_stream( ctx: Ctx<'js>, start_algorithm: StartAlgorithm<'js>, pull_algorithm: PullAlgorithm<'js>, cancel_algorithm: CancelAlgorithm<'js>, ) -> Result<(Class<'js, Self>, ReadableByteStreamControllerClass<'js>)> { let base_primordials = BasePrimordials::get(&ctx)?; // Let stream be a new ReadableStream. let stream_class = Class::instance( ctx.clone(), Self { // Set stream.[[state]] to "readable". state: ReadableStreamState::Readable, // Set stream.[[reader]] and stream.[[storedError]] to undefined. reader: None, // Set stream.[[disturbed]] to false. disturbed: false, controller: ReadableStreamControllerClass::Uninitialised, promise_primordials: PromisePrimordials::get(&ctx)?.clone(), constructor_type_error: base_primordials.constructor_type_error.clone(), constructor_range_error: base_primordials.constructor_range_error.clone(), function_array_buffer_is_view: base_primordials .function_array_buffer_is_view .clone(), }, )?; drop(base_primordials); // Perform ? SetUpReadableStreamDefaultController(stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, sizeAlgorithm). let controller_class = ReadableByteStreamController::set_up_readable_byte_stream_controller( ctx, OwnedBorrowMut::from_class(stream_class.clone()), start_algorithm, pull_algorithm, cancel_algorithm, 0.0, None, )?; // Return stream. Ok((stream_class, controller_class)) } fn readable_stream_from_iterable( ctx: &Ctx<'js>, async_iterable: Value<'js>, ) -> Result> { let stream: Rc>> = Rc::new(OnceCell::new()); // Let iteratorRecord be ? GetIterator(asyncIterable, async). let iterator_record = IteratorRecord::get_iterator(ctx, async_iterable, IteratorKind::Async)?; let iterator = iterator_record.iterator.clone(); // Let startAlgorithm be an algorithm that returns undefined. let start_algorithm = StartAlgorithm::ReturnUndefined; let promise_primordials = PromisePrimordials::get(ctx)?.clone(); // Let pullAlgorithm be the following steps: let pull_algorithm = { let stream = stream.clone(); let promise_primordials = promise_primordials.clone(); move |ctx: Ctx<'js>, controller: ReadableStreamControllerClass<'js>| { // Let nextResult be IteratorNext(iteratorRecord). let next_result: Result> = iterator_record.iterator_next(&ctx, None); let next_promise = match next_result { // If nextResult is an abrupt completion, return a promise rejected with nextResult.[[Value]]. Err(Error::Exception) => { return promise_rejected_catch(&ctx, &promise_primordials); }, Err(err) => return Err(err), // Let nextPromise be a promise resolved with nextResult.[[Value]]. Ok(next_result) => promise_resolved_with( &ctx, &promise_primordials, Ok(next_result.into_inner()), )?, }; // Return the result of reacting to nextPromise with the following fulfillment steps, given iterResult: upon_promise_fulfilment(ctx, next_promise, { let stream = stream.clone(); move |ctx, iter_result: Value<'js>| { let iter_result = match iter_result.into_object() { // If Type(iterResult) is not Object, throw a TypeError. None => { return Err(Exception::throw_type(&ctx, "The promise returned by the iterator.next() method must fulfill with an object")); }, Some(iter_result) => iter_result, }; // Let done be ? IteratorComplete(iterResult). let done = IteratorRecord::iterator_complete(&iter_result)?; let stream = OwnedBorrowMut::from_class(stream.get().cloned().expect("ReadableStreamFromIterable pull steps called with uninitialised stream")); let controller = match controller { ReadableStreamControllerClass::ReadableStreamDefaultController(c) => OwnedBorrowMut::from_class(c), _ => panic!("ReadableStreamFromIterable pull steps called without default controller") }; let objects = ReadableStreamObjects::new_default(stream, controller); // If done is true: if done { // Perform ! ReadableStreamDefaultControllerClose(stream.[[controller]]). ReadableStreamDefaultController::readable_stream_default_controller_close(ctx.clone(), objects)?; } else { // Let value be ? IteratorValue(iterResult). let value = IteratorRecord::iterator_value(&iter_result)?; // Perform ! ReadableStreamDefaultControllerEnqueue(stream.[[controller]], value). ReadableStreamDefaultController::readable_stream_default_controller_enqueue(ctx.clone(), objects, value)?; } Ok(()) } }) } }; // Let cancelAlgorithm be the following steps, given reason: let cancel_algorithm = { let ctx = ctx.clone(); let promise_primordials = promise_primordials.clone(); move |reason: Value<'js>| { // Let iterator be iteratorRecord.[[Iterator]]. // Let returnMethod be GetMethod(iterator, "return"). let return_method: Function<'js> = match iterator.get(PredefinedAtom::Return) { // If returnMethod is an abrupt completion, return a promise rejected with returnMethod.[[Value]]. Err(Error::Exception) => { return promise_rejected_catch(&ctx, &promise_primordials); }, Err(err) => return Err(err), Ok(None) => { // If returnMethod.[[Value]] is undefined, return a promise resolved with undefined. return Ok(promise_primordials.promise_resolved_with_undefined.clone()); }, Ok(Some(return_method)) => return_method, }; // Let returnResult be Call(returnMethod.[[Value]], iterator, « reason »). let return_result: Result> = return_method.call((This(iterator), reason)); let return_result = match return_result { // If returnResult is an abrupt completion, return a promise rejected with returnResult.[[Value]]. Err(Error::Exception) => { return promise_rejected_catch(&ctx, &promise_primordials); }, Err(err) => return Err(err), Ok(return_result) => return_result, }; // Let returnPromise be a promise resolved with returnResult.[[Value]]. let return_promise = promise_resolved_with(&ctx, &promise_primordials, Ok(return_result))?; // Return the result of reacting to returnPromise with the following fulfillment steps, given iterResult: upon_promise_fulfilment( ctx, return_promise, move |ctx: Ctx<'js>, iter_result: Value<'js>| { // If Type(iterResult) is not Object, throw a TypeError. if !iter_result.is_object() { return Err(Exception::throw_type(&ctx, "The promise returned by the iterator.next() method must fulfill with an object")); } // Return undefined. Ok(rquickjs::Undefined) }, ) } }; let objects_class = ReadableStream::create_readable_stream( ctx.clone(), start_algorithm, PullAlgorithm::from_fn(pull_algorithm), CancelAlgorithm::from_fn(cancel_algorithm), Some(0.0), None, )?; _ = stream.set(objects_class.stream.clone()); Ok(objects_class.stream) } pub(super) fn reader_mut(&mut self) -> Option> { self.reader .clone() .map(ReadableStreamReaderOwned::from_class) } } // enum ReadableStreamType { "bytes" }; enum ReadableStreamType { Bytes, } impl<'js> FromJs<'js> for ReadableStreamType { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let typ = value.type_of(); match Coerced::::from_js(ctx, value)?.as_str() { "bytes" => Ok(Self::Bytes), _ => Err(Error::new_from_js(typ.as_str(), "ReadableStreamType")), } } } struct ReadableStreamGetReaderOptions { mode: Option, } impl<'js> FromJs<'js> for ReadableStreamGetReaderOptions { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let mode = obj.get_value_or_undefined::<_, ReadableStreamReaderMode>("mode")?; Ok(Self { mode }) } } // enum ReadableStreamReaderMode { "byob" }; enum ReadableStreamReaderMode { Byob, } impl<'js> FromJs<'js> for ReadableStreamReaderMode { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let typ = value.type_of(); match Coerced::::from_js(ctx, value)?.as_str() { "byob" => Ok(Self::Byob), _ => Err(Error::new_from_js(typ.as_str(), "ReadableStreamReaderMode")), } } } ================================================ FILE: modules/llrt_stream_web/src/readable/stream/pipe.rs ================================================ use std::{ cell::RefCell, rc::Rc, sync::atomic::{AtomicBool, Ordering}, }; use llrt_abort::AbortSignal; use llrt_utils::{option::Undefined, result::ResultExt}; use rquickjs::{ class::{OwnedBorrow, Trace}, prelude::{OnceFn, This}, Class, Coerced, Ctx, Error, FromJs, Function, Promise, Result, Value, }; use crate::{ readable::{ controller::ReadableStreamControllerOwned, default_reader::{ ReadableStreamDefaultReader, ReadableStreamDefaultReaderOwned, ReadableStreamReadRequest, }, objects::{ ReadableStreamClassObjects, ReadableStreamDefaultReaderObjects, ReadableStreamObjects, }, reader::ReadableStreamReaderClass, stream::{ReadableStream, ReadableStreamOwned, ReadableStreamState}, }, utils::{ promise::{ promise_resolved_with, upon_promise, upon_promise_fulfilment, PromisePrimordials, ResolveablePromise, }, UnwrapOrUndefined, ValueOrUndefined, }, writable::{ WritableStream, WritableStreamClassObjects, WritableStreamDefaultWriter, WritableStreamDefaultWriterOwned, WritableStreamObjects, WritableStreamOwned, WritableStreamState, }, }; impl<'js> ReadableStream<'js> { pub(super) fn readable_stream_pipe_to( ctx: Ctx<'js>, source: ReadableStreamOwned<'js>, dest: WritableStreamOwned<'js>, prevent_close: bool, prevent_abort: bool, prevent_cancel: bool, signal: Option>>, ) -> Result> { let (source_stored_error, source_closed) = match source.state { ReadableStreamState::Errored(ref stored_error) => (Some(stored_error.clone()), false), ReadableStreamState::Closed => (None, true), _ => (None, false), }; let dest_stored_error = dest.stored_error(); let dest_closing = dest.writable_stream_close_queued_or_in_flight() || matches!(dest.state, WritableStreamState::Closed); let source_controller = source.controller.clone(); let dest_controller = dest .controller .clone() .expect("pipeTo called on writable stream without controller"); // If source.[[controller]] implements ReadableByteStreamController, let reader be either ! AcquireReadableStreamBYOBReader(source) or ! AcquireReadableStreamDefaultReader(source), at the user agent’s discretion. // Otherwise, let reader be ! AcquireReadableStreamDefaultReader(source). let (mut source, reader) = ReadableStreamReaderClass::acquire_readable_stream_default_reader(ctx.clone(), source)?; let source_closed_promise = reader.borrow().generic.closed_promise.promise.clone(); // Let writer be ! AcquireWritableStreamDefaultWriter(dest). let (dest, writer) = WritableStreamDefaultWriter::acquire_writable_stream_default_writer(&ctx, dest)?; let dest_closed_promise = writer.borrow().closed_promise.promise.clone(); // Set source.[[disturbed]] to true. source.disturbed = true; let current_write = Rc::new(RefCell::new( source .promise_primordials .promise_resolved_with_undefined .clone(), )); let promise_primordials = source.promise_primordials.clone(); let constructor_type_error = source.constructor_type_error.clone(); let mut pipe_to = PipeTo { source_objects: ReadableStreamClassObjects { stream: source.into_inner(), controller: source_controller, reader, }, dest_objects: WritableStreamClassObjects { stream: dest.into_inner(), controller: dest_controller, writer, }, current_write, // Let shuttingDown be false. shutting_down: Rc::new(AtomicBool::new(false)), signal, abort_callback: None, // Let promise be a new promise. promise: ResolveablePromise::new(&ctx)?, promise_primordials: promise_primordials.clone(), }; // If signal is not undefined, if let Some(signal) = &pipe_to.signal { // Let abortAlgorithm be the following steps: let abort_algorithm = { let signal = signal.clone(); let pipe_to = pipe_to.clone(); move |ctx: Ctx<'js>| -> Result<()> { // Let error be signal’s abort reason. let error = signal.borrow().reason().unwrap_or_undefined(&ctx); // Let actions be an empty ordered set. let mut actions = Vec::) -> Result>>>::new(); // If preventAbort is false, append the following action to actions: if !prevent_abort { let dest_objects = pipe_to.dest_objects.clone(); let error = error.clone(); actions.push(Box::new(move |ctx| { let dest_objects = WritableStreamObjects::from_class(dest_objects); if matches!(dest_objects.stream.state, WritableStreamState::Writable) { // If dest.[[state]] is "writable", return ! WritableStreamAbort(dest, error). let (promise, _) = WritableStream::writable_stream_abort( ctx, dest_objects, Some(error.clone()), )?; Ok(promise) } else { // Otherwise, return a promise resolved with undefined. Ok(dest_objects .stream .promise_primordials .promise_resolved_with_undefined .clone()) } })); } // If preventCancel is false, append the following action action to actions: if !prevent_cancel { let source_objects = pipe_to.source_objects.clone(); let error = error.clone(); actions.push(Box::new(move |ctx| { let source_objects = ReadableStreamObjects::from_class(source_objects); if let ReadableStreamState::Readable = source_objects.stream.state { // If source.[[state]] is "readable", return ! ReadableStreamCancel(source, error). let (promise, _) = ReadableStream::readable_stream_cancel( ctx, source_objects, error.clone(), )?; Ok(promise) } else { // Otherwise, return a promise resolved with undefined. Ok(source_objects .stream .promise_primordials .promise_resolved_with_undefined .clone()) } })); } // Shutdown with an action consisting of getting a promise to wait for all of the actions in actions, and with error. pipe_to.shutdown_with_action( ctx, move |ctx| { let promises: Vec> = actions .into_iter() .map(|action| action(ctx.clone())) .collect::>>()?; let all_promises: Promise<'js> = promise_primordials.promise_all.call(( This(promise_primordials.promise_constructor.clone()), promises, ))?; Ok(all_promises) }, Some(error), ) } }; // If signal is aborted, perform abortAlgorithm and return promise. { let signal = signal.borrow(); if signal.aborted { abort_algorithm(ctx.clone())?; return Ok(pipe_to.promise.promise); } } let abort_callback = pipe_to .abort_callback .insert(Function::new(ctx.clone(), OnceFn::new(abort_algorithm))?); // Add abortAlgorithm to signal. AbortSignal::set_on_abort(This(signal.clone()), ctx.clone(), abort_callback.clone())?; } // In parallel but not really; see #905, using reader and writer, read all chunks from source and write them to dest. // Due to the locking provided by the reader and writer, the exact manner in which this happens is not observable to author code, and so there is flexibility in how this is done. // The following constraints apply regardless of the exact algorithm used: // Errors must be propagated forward PipeTo::is_or_becomes_errored( ctx.clone(), source_stored_error, source_closed_promise.clone(), { let pipe_to = pipe_to.clone(); move |ctx, stored_error| { if !prevent_abort { pipe_to.shutdown_with_action( ctx, { let pipe_to = pipe_to.clone(); let stored_error = stored_error.clone(); move |ctx| { let dest_objects = WritableStreamObjects::from_class( pipe_to.dest_objects.clone(), ); let (promise, _) = WritableStream::writable_stream_abort( ctx, dest_objects, Some(stored_error), )?; Ok(promise) } }, Some(stored_error), ) } else { pipe_to.shutdown(ctx, Some(stored_error)) } } }, )?; // Errors must be propagated backward PipeTo::is_or_becomes_errored(ctx.clone(), dest_stored_error, dest_closed_promise, { let pipe_to = pipe_to.clone(); move |ctx, stored_error| { if !prevent_cancel { pipe_to.shutdown_with_action( ctx, { let pipe_to = pipe_to.clone(); let stored_error = stored_error.clone(); move |ctx| { let source_objects = ReadableStreamObjects::from_class( pipe_to.source_objects.clone(), ); let (promise, _) = ReadableStream::readable_stream_cancel( ctx, source_objects, stored_error, )?; Ok(promise) } }, Some(stored_error), ) } else { pipe_to.shutdown(ctx, Some(stored_error)) } } })?; // Closing must be propagated forward PipeTo::is_or_becomes_closed(ctx.clone(), source_closed, source_closed_promise, { let pipe_to = pipe_to.clone(); move |ctx| { if !prevent_close { pipe_to.shutdown_with_action( ctx, { let pipe_to = pipe_to.clone(); move |ctx| { let dest_objects = WritableStreamObjects::from_class(pipe_to.dest_objects); WritableStreamDefaultWriter::writable_stream_default_writer_close_with_error_propagation(ctx, dest_objects) } }, None, ) } else { pipe_to.shutdown(ctx, None) } } })?; // Closing must be propagated backward if dest_closing { let dest_closed: Value<'js> = constructor_type_error.call(( "the destination writable stream closed before all data could be piped to it", ))?; if !prevent_cancel { pipe_to.shutdown_with_action( ctx.clone(), { let pipe_to = pipe_to.clone(); let dest_closed = dest_closed.clone(); move |ctx| { let source_objects = ReadableStreamObjects::from_class(pipe_to.source_objects.clone()); let (promise, _) = ReadableStream::readable_stream_cancel( ctx, source_objects, dest_closed, )?; Ok(promise) } }, Some(dest_closed), )?; } else { pipe_to.shutdown(ctx.clone(), Some(dest_closed))?; } } let result_promise = pipe_to.promise.promise.clone(); let pipe_loop_promise = pipe_to.pipe_loop(ctx)?; pipe_loop_promise.set_is_handled()?; Ok(result_promise) } } #[derive(Clone)] struct PipeTo<'js> { source_objects: ReadableStreamClassObjects< 'js, ReadableStreamControllerOwned<'js>, ReadableStreamDefaultReaderOwned<'js>, >, dest_objects: WritableStreamClassObjects<'js, WritableStreamDefaultWriterOwned<'js>>, current_write: Rc>>, shutting_down: Rc, signal: Option>>, abort_callback: Option>, promise: ResolveablePromise<'js>, promise_primordials: PromisePrimordials<'js>, } impl<'js> PipeTo<'js> { // Using reader and writer, read all chunks from this and write them to dest // - Backpressure must be enforced // - Shutdown must stop all activity fn pipe_loop(self, ctx: Ctx<'js>) -> Result> { let loop_promise = ResolveablePromise::new(&ctx)?; self.next(ctx, false, loop_promise.clone())?; Ok(loop_promise) } fn next(&self, ctx: Ctx<'js>, done: bool, loop_promise: ResolveablePromise<'js>) -> Result<()> { if done { loop_promise.resolve_undefined()? } else { let pipe_step_promise = self.pipe_step(ctx.clone())?; upon_promise(ctx, pipe_step_promise, { { let pipe_to = self.clone(); move |ctx, result| match result { Ok(done) => pipe_to.next(ctx, done, loop_promise), Err(err) => loop_promise.reject(err), } } })?; } Ok(()) } fn pipe_step(&self, ctx: Ctx<'js>) -> Result> { if self.shutting_down.load(Ordering::Acquire) { return promise_resolved_with( &ctx, &self.promise_primordials, Ok(Value::new_bool(ctx.clone(), true)), ); } let writer_ready = self .dest_objects .writer .borrow() .ready_promise .promise .clone(); upon_promise_fulfilment(ctx, writer_ready, { let current_write = self.current_write.clone(); let source_objects = self.source_objects.clone(); let dest_objects = self.dest_objects.clone(); move |ctx: Ctx<'js>, ()| -> Result> { let read_promise = ResolveablePromise::new(&ctx)?; struct ReadRequest<'js> { dest_objects: WritableStreamClassObjects<'js, WritableStreamDefaultWriterOwned<'js>>, current_write: Rc>>, read_promise: ResolveablePromise<'js>, } impl<'js> Trace<'js> for ReadRequest<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { self.current_write.as_ref().borrow().trace(tracer); self.read_promise.trace(tracer); } } impl<'js> ReadableStreamReadRequest<'js> for ReadRequest<'js> { fn chunk_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, chunk: Value<'js>, ) -> Result> { let ctx = chunk.ctx().clone(); // calling write can trigger user code; ensure we don't hold locks let objects = objects.into_inner(); let dest_objects = WritableStreamObjects::from_class(self.dest_objects.clone()); let write_promise = WritableStreamDefaultWriter::writable_stream_default_writer_write( ctx.clone(), dest_objects, chunk, )?; let write_promise: Promise<'js> = write_promise.catch()?.call(( This(write_promise.clone()), Function::new(ctx.clone(), || {}), ))?; self.current_write.replace(write_promise); self.read_promise .resolve(Value::new_bool(ctx.clone(), false))?; Ok(ReadableStreamObjects::from_class(objects)) } fn close_steps( &self, ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js>, ) -> Result> { self.read_promise .resolve(Value::new_bool(ctx.clone(), true))?; Ok(objects) } fn error_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, reason: Value<'js>, ) -> Result> { self.read_promise.reject(reason)?; Ok(objects) } } let objects = ReadableStreamObjects::from_class(source_objects); let promise = read_promise.promise.clone(); ReadableStreamDefaultReader::readable_stream_default_reader_read( &ctx, objects, ReadRequest { current_write, read_promise, dest_objects, }, )?; Ok(promise) } }) } fn is_or_becomes_errored( ctx: Ctx<'js>, stored_error: Option>, promise: Promise<'js>, action: impl FnOnce(Ctx<'js>, Value<'js>) -> Result<()> + 'js, ) -> Result<()> { if let Some(stored_error) = stored_error { action(ctx, stored_error) } else { promise.catch()?.call(( This(promise.clone()), Function::new(ctx.clone(), OnceFn::new(action)), )) } } fn is_or_becomes_closed( ctx: Ctx<'js>, already_closed: bool, promise: Promise<'js>, action: impl FnOnce(Ctx<'js>) -> Result<()> + 'js, ) -> Result<()> { if already_closed { action(ctx)?; } else { upon_promise_fulfilment(ctx, promise, |ctx, ()| action(ctx))?; } Ok(()) } fn shutdown_with_action( &self, ctx: Ctx<'js>, action: impl FnOnce(Ctx<'js>) -> Result> + 'js, original_error: Option>, ) -> Result<()> { if self.shutting_down.swap(true, Ordering::AcqRel) { // already shutting down return Ok(()); } let do_the_rest = { let pipe_to = self.clone(); move |ctx: Ctx<'js>| -> Result<()> { let action_promise = action(ctx.clone())?; upon_promise(ctx, action_promise, move |ctx, result| match result { Ok(()) => pipe_to.finalize(ctx, original_error), Err(new_error) => pipe_to.finalize(ctx, Some(new_error)), })?; Ok(()) } }; let writable = { let dest_stream = OwnedBorrow::from_class(self.dest_objects.stream.clone()); matches!(dest_stream.state, WritableStreamState::Writable) && !dest_stream.writable_stream_close_queued_or_in_flight() }; if writable { let wait_promise = Self::wait_for_writes_to_finish(ctx.clone(), self.current_write.clone())?; upon_promise_fulfilment(ctx, wait_promise, |ctx: Ctx<'js>, ()| do_the_rest(ctx))?; } else { do_the_rest(ctx)? } Ok(()) } fn shutdown(&self, ctx: Ctx<'js>, error: Option>) -> Result<()> { if self.shutting_down.swap(true, Ordering::AcqRel) { // already shutting down return Ok(()); } let writable = { let dest_stream = OwnedBorrow::from_class(self.dest_objects.stream.clone()); matches!(dest_stream.state, WritableStreamState::Writable) && !dest_stream.writable_stream_close_queued_or_in_flight() }; if writable { let wait_promise = Self::wait_for_writes_to_finish(ctx.clone(), self.current_write.clone())?; let pipe_to = self.clone(); upon_promise_fulfilment(ctx, wait_promise, move |ctx, ()| { pipe_to.finalize(ctx, error) })?; } else { self.finalize(ctx, error)?; } Ok(()) } fn wait_for_writes_to_finish( ctx: Ctx<'js>, current_write: Rc>>, ) -> Result> { let old_current_write: Promise<'js> = current_write.as_ref().borrow().clone(); upon_promise_fulfilment( ctx, old_current_write.clone(), move |ctx: Ctx<'js>, ()| -> Result>> { if !old_current_write.eq(¤t_write.as_ref().borrow()) { Ok(Undefined(Some(Self::wait_for_writes_to_finish( ctx, current_write, )?))) } else { Ok(Undefined(None)) } }, ) } fn finalize(&self, ctx: Ctx<'js>, error: Option>) -> Result<()> { let source_objects = ReadableStreamObjects::from_class(self.source_objects.clone()); let dest_objects = WritableStreamObjects::from_class(self.dest_objects.clone()); WritableStreamDefaultWriter::writable_stream_default_writer_release(dest_objects)?; ReadableStreamDefaultReader::readable_stream_default_reader_release(source_objects)?; if let (Some(signal), Some(abort_callback)) = (&self.signal, &self.abort_callback) { AbortSignal::remove_on_abort( This(signal.clone()), ctx.clone(), abort_callback.clone(), )?; } if let Some(error) = error { self.promise.reject(error) } else { self.promise.resolve_undefined() } } } #[derive(Default)] pub struct StreamPipeOptions<'js> { pub prevent_close: bool, pub prevent_abort: bool, pub prevent_cancel: bool, pub signal: Option>>, } impl<'js> FromJs<'js> for StreamPipeOptions<'js> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let get_bool = |key| { Result::Ok( obj.get_value_or_undefined::<_, Coerced>(key)? .map(|b| b.0) .unwrap_or(false), ) // missing is treated as false }; let prevent_abort = get_bool("preventAbort")?; let prevent_close = get_bool("preventClose")?; let prevent_cancel = get_bool("preventCancel")?; let signal = match obj.get_value_or_undefined::<_, Value<'js>>("signal")? { Some(signal) => Some( Class::::from_js(ctx, signal) .or_throw_type(ctx, "Invalid signal argument")?, ), None => None, }; Ok(Self { prevent_close, prevent_abort, prevent_cancel, signal, }) } } ================================================ FILE: modules/llrt_stream_web/src/readable/stream/source.rs ================================================ use rquickjs::{Function, Object, Result}; use crate::{readable::stream::ReadableStreamType, utils::ValueOrUndefined}; #[derive(Default)] pub(crate) struct UnderlyingSource<'js> { // callback UnderlyingSourceStartCallback = any (ReadableStreamController controller); pub(crate) start: Option>, // callback UnderlyingSourcePullCallback = Promise (ReadableStreamController controller); pub(crate) pull: Option>, // callback UnderlyingSourceCancelCallback = Promise (optional any reason); pub(crate) cancel: Option>, pub(super) r#type: Option, // [EnforceRange] unsigned long long autoAllocateChunkSize; pub(crate) auto_allocate_chunk_size: Option, } impl<'js> UnderlyingSource<'js> { pub(super) fn from_object(obj: Object<'js>) -> Result { let start = obj.get_value_or_undefined::<_, _>("start")?; let pull = obj.get_value_or_undefined::<_, _>("pull")?; let cancel = obj.get_value_or_undefined::<_, _>("cancel")?; let r#type = obj.get_value_or_undefined::<_, _>("type")?; let auto_allocate_chunk_size = obj.get_value_or_undefined::<_, _>("autoAllocateChunkSize")?; Ok(Self { start, pull, cancel, r#type, auto_allocate_chunk_size, }) } } ================================================ FILE: modules/llrt_stream_web/src/readable/stream/tee.rs ================================================ use std::{ cell::{OnceCell, RefCell}, rc::Rc, sync::atomic::{AtomicBool, Ordering}, }; use llrt_utils::clone::structured_clone; use rquickjs::{ class::{OwnedBorrowMut, Trace}, function::Constructor, prelude::{List, OnceFn, Opt}, ArrayBuffer, Class, Ctx, Error, Function, IntoJs, Promise, Result, Value, }; use crate::{ readable::{ byob_reader::{ReadableStreamBYOBReader, ReadableStreamReadIntoRequest, ViewBytes}, byte_controller::{ReadableByteStreamController, ReadableByteStreamControllerOwned}, controller::{ReadableStreamController, ReadableStreamControllerClass}, default_controller::{ ReadableStreamDefaultController, ReadableStreamDefaultControllerOwned, }, default_reader::{ ReadableStreamDefaultReader, ReadableStreamDefaultReaderOwned, ReadableStreamReadRequest, }, objects::{ReadableByteStreamObjects, ReadableStreamDefaultControllerObjects}, objects::{ ReadableStreamBYOBObjects, ReadableStreamClassObjects, ReadableStreamDefaultReaderObjects, ReadableStreamObjects, }, reader::{ ReadableStreamReader, ReadableStreamReaderClass, ReadableStreamReaderOwned, UndefinedReader, }, stream::{ algorithms::{CancelAlgorithm, PullAlgorithm, StartAlgorithm}, ReadableStream, ReadableStreamClass, }, }, utils::promise::{upon_promise, ResolveablePromise}, }; type ReadableStreamPair<'js> = (ReadableStreamClass<'js>, ReadableStreamClass<'js>); impl<'js> ReadableStream<'js> { pub(super) fn readable_stream_tee>( ctx: Ctx<'js>, objects: ReadableStreamObjects<'js, C, UndefinedReader>, clone_for_branch_2: bool, ) -> Result> { let (streams, _) = objects.with_controller( ctx, |ctx, objects| { // Return ? ReadableStreamDefaultTee(stream, cloneForBranch2). let (streams, objects) = Self::readable_stream_default_tee(ctx, objects, clone_for_branch_2)?; Ok((streams, objects.clear_reader())) }, |ctx, objects| { // If stream.[[controller]] implements ReadableByteStreamController, return ? ReadableByteStreamTee(stream). Self::readable_byte_stream_tee(ctx, objects) }, )?; Ok(streams) } fn readable_stream_default_tee( ctx: Ctx<'js>, mut objects: ReadableStreamDefaultControllerObjects<'js, UndefinedReader>, clone_for_branch_2: bool, ) -> Result<( ReadableStreamPair<'js>, ReadableStreamDefaultControllerObjects<'js, ReadableStreamDefaultReaderOwned<'js>>, )> { // Let reader be ? AcquireReadableStreamDefaultReader(stream). let (stream, reader) = ReadableStreamReaderClass::acquire_readable_stream_default_reader( ctx.clone(), objects.stream, )?; objects.stream = stream; // Let reading be false. let reading = Rc::new(AtomicBool::new(false)); // Let readAgain be false. let read_again = Rc::new(AtomicBool::new(false)); // Let canceled1 be false. // Let canceled2 be false. // Let reason1 be undefined. let reason_1 = Rc::new(OnceCell::new()); // Let reason2 be undefined. let reason_2 = Rc::new(OnceCell::new()); // Let branch1 be undefined. let branch_1: Rc< OnceCell< ReadableStreamClassObjects< 'js, ReadableStreamDefaultControllerOwned<'js>, UndefinedReader, >, >, > = Rc::new(OnceCell::new()); // Let branch2 be undefined. let branch_2: Rc< OnceCell< ReadableStreamClassObjects< 'js, ReadableStreamDefaultControllerOwned<'js>, UndefinedReader, >, >, > = Rc::new(OnceCell::new()); // Let cancelPromise be a new promise. let cancel_promise = ResolveablePromise::new(&ctx)?; // Let startAlgorithm be an algorithm that returns undefined. let start_algorithm = StartAlgorithm::ReturnUndefined; let objects_class = objects.into_inner().set_reader(reader); let pull_algorithm = PullAlgorithm::from_fn({ let objects_class = objects_class.clone(); let reason_1 = reason_1.clone(); let reason_2 = reason_2.clone(); let branch_1 = branch_1.clone(); let branch_2 = branch_2.clone(); let cancel_promise = cancel_promise.clone(); move |ctx: Ctx<'js>, _| { let objects = ReadableStreamObjects::from_class(objects_class.clone()); Self::readable_stream_default_pull_algorithm( ctx, objects, clone_for_branch_2, reading.clone(), read_again.clone(), reason_1.clone(), reason_2.clone(), branch_1.clone(), branch_2.clone(), cancel_promise.clone(), ) } }); let cancel_algorithm_1 = CancelAlgorithm::from_fn({ let objects_class = objects_class.clone(); let reason_1 = reason_1.clone(); let reason_2 = reason_2.clone(); let cancel_promise = cancel_promise.clone(); move |reason: Value<'js>| { let objects = ReadableStreamObjects::from_class(objects_class.clone()); Self::readable_stream_cancel_1_algorithm( reason.ctx().clone(), objects, reason_1, reason_2, cancel_promise, reason, ) } }); let cancel_algorithm_2 = CancelAlgorithm::from_fn({ let objects_class = objects_class.clone(); let reason_1 = reason_1.clone(); let reason_2 = reason_2.clone(); let cancel_promise = cancel_promise.clone(); move |reason: Value<'js>| { let objects = ReadableStreamObjects::from_class(objects_class.clone()); Self::readable_stream_cancel_2_algorithm( reason.ctx().clone(), objects, reason_1, reason_2, cancel_promise, reason, ) } }); // Set branch1 to ! CreateReadableStream(startAlgorithm, pullAlgorithm, cancel1Algorithm). let branch_1 = { let objects = Self::create_readable_stream( ctx.clone(), start_algorithm.clone(), pull_algorithm.clone(), cancel_algorithm_1, None, None, )?; _ = branch_1.set(objects.clone()); objects }; // Set branch2 to ! CreateReadableStream(startAlgorithm, pullAlgorithm, cancel2Algorithm). let branch_2 = { let objects = Self::create_readable_stream( ctx.clone(), start_algorithm, pull_algorithm, cancel_algorithm_2, None, None, )?; _ = branch_2.set(objects.clone()); objects }; upon_promise( ctx.clone(), objects_class .reader .borrow() .generic .closed_promise .promise .clone(), { let branch_1 = branch_1.clone(); let branch_2 = branch_2.clone(); move |_, result| match result { Ok(()) => Ok(()), // Upon rejection of reader.[[closedPromise]] with reason r, Err(reason) => { // Perform ! ReadableStreamDefaultControllerError(branch1.[[controller]], r). let objects_1 = ReadableStreamObjects::from_class_no_reader(branch_1).refresh_reader(); ReadableStreamDefaultController::readable_stream_default_controller_error( objects_1, reason.clone(), )?; // Perform ! ReadableStreamDefaultControllerError(branch2.[[controller]], r). let objects_2 = ReadableStreamObjects::from_class_no_reader(branch_2).refresh_reader(); ReadableStreamDefaultController::readable_stream_default_controller_error( objects_2, reason, )?; // If canceled1 is false or canceled2 is false, resolve cancelPromise with undefined. if reason_1.get().is_none() || reason_2.get().is_none() { cancel_promise.resolve_undefined()?; } Ok(()) }, } }, )?; Ok(( (branch_1.stream, branch_2.stream), ReadableStreamObjects::from_class(objects_class), )) } // Let pullAlgorithm be the following steps: #[allow(clippy::too_many_arguments)] fn readable_stream_default_pull_algorithm( ctx: Ctx<'js>, mut objects: ReadableStreamDefaultControllerObjects< 'js, ReadableStreamDefaultReaderOwned<'js>, >, clone_for_branch_2: bool, reading: Rc, read_again: Rc, reason_1: Rc>>, reason_2: Rc>>, branch_1: Rc< OnceCell< ReadableStreamClassObjects< 'js, ReadableStreamDefaultControllerOwned<'js>, UndefinedReader, >, >, >, branch_2: Rc< OnceCell< ReadableStreamClassObjects< 'js, ReadableStreamDefaultControllerOwned<'js>, UndefinedReader, >, >, >, cancel_promise: ResolveablePromise<'js>, ) -> Result> { // If reading is true, if reading.load(Ordering::Acquire) { // Set readAgain to true. read_again.store(true, Ordering::Release); // Return a promise resolved with undefined. return Ok(objects .stream .promise_primordials .promise_resolved_with_undefined .clone()); } // Set reading to true. reading.store(true, Ordering::Release); #[derive(Clone)] struct ReadRequest<'js> { clone_for_branch_2: bool, reading: Rc, read_again: Rc, reason_1: Rc>>, reason_2: Rc>>, branch_1: Rc< OnceCell< ReadableStreamClassObjects< 'js, ReadableStreamDefaultControllerOwned<'js>, UndefinedReader, >, >, >, branch_2: Rc< OnceCell< ReadableStreamClassObjects< 'js, ReadableStreamDefaultControllerOwned<'js>, UndefinedReader, >, >, >, cancel_promise: ResolveablePromise<'js>, } impl<'js> Trace<'js> for ReadRequest<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { if let Some(r) = self.reason_1.get() { r.trace(tracer) } if let Some(r) = self.reason_2.get() { r.trace(tracer) } if let Some(b) = self.branch_1.get() { b.trace(tracer) } if let Some(b) = self.branch_2.get() { b.trace(tracer) } self.cancel_promise.trace(tracer); } } // Let readRequest be a read request with the following items: impl<'js> ReadableStreamReadRequest<'js> for ReadRequest<'js> { fn chunk_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, chunk: Value<'js>, ) -> Result> { let ctx = chunk.ctx().clone(); let this = self.clone(); objects .with_assert_default_controller( |objects| { let objects_class = objects.into_inner(); // Queue a microtask to perform the following steps: let f = { let ctx = ctx.clone(); let objects_class = objects_class.clone(); move || -> Result<()> { // Set readAgain to false. this.read_again.store(false, Ordering::Release); // Let chunk1 and chunk2 be chunk. let chunk_1 = chunk.clone(); let chunk_2 = chunk.clone(); // If canceled2 is false and cloneForBranch2 is true, let chunk_2 = if this.reason_2.get().is_none() && this.clone_for_branch_2 { // Let cloneResult be StructuredClone(chunk2). let clone_result: Result> = structured_clone(&ctx, chunk_2, Opt(None)); match clone_result { // If cloneResult is an abrupt completion, Err(Error::Exception) => { let clone_result = ctx.catch(); let objects_1 = ReadableStreamObjects::from_class( this.branch_1.get().cloned().expect( "canceled1 set without branch1 being initialised", ), ).refresh_reader(); // Perform ! ReadableStreamDefaultControllerError(branch1.[[controller]], cloneResult.[[Value]]). ReadableStreamDefaultController::readable_stream_default_controller_error( objects_1, clone_result.clone(), )?; let objects_2 = ReadableStreamObjects::from_class( this.branch_2.get().cloned().clone().expect( "canceled2 set without branch2 being initialised", ), ).refresh_reader(); // Perform ! ReadableStreamDefaultControllerError(branch2.[[controller]], cloneResult.[[Value]]). ReadableStreamDefaultController::readable_stream_default_controller_error( objects_2, clone_result.clone(), )?; // Resolve cancelPromise with ! ReadableStreamCancel(stream, cloneResult.[[Value]]). let (promise, _) = ReadableStream::readable_stream_cancel( ctx, ReadableStreamObjects::from_class(objects_class), clone_result, )?; this.cancel_promise.resolve(promise)?; // Return. return Ok(()); }, Ok(clone_result) => { // Otherwise, set chunk2 to cloneResult.[[Value]]. clone_result }, Err(err) => return Err(err), } } else { chunk_2 }; // If canceled1 is false, perform ! ReadableStreamDefaultControllerEnqueue(branch1.[[controller]], chunk1). if this.reason_1.get().is_none() { let objects_1 = ReadableStreamObjects::from_class( this.branch_1 .get() .cloned() .expect("canceled1 set without branch1 being initialised"), ).refresh_reader(); ReadableStreamDefaultController::readable_stream_default_controller_enqueue(ctx.clone(), objects_1, chunk_1)?; } // If canceled2 is false, perform ! ReadableStreamDefaultControllerEnqueue(branch2.[[controller]], chunk2). if this.reason_2.get().is_none() { let objects_2 = ReadableStreamObjects::from_class( this.branch_2 .get() .cloned() .expect("canceled2 set without branch2 being initialised"), ).refresh_reader(); ReadableStreamDefaultController::readable_stream_default_controller_enqueue(ctx.clone(), objects_2, chunk_2)?; } // Set reading to false. this.reading.store(false, Ordering::Release); // If readAgain is true, perform pullAlgorithm. if this.read_again.load(Ordering::Acquire) { let objects = ReadableStreamObjects::from_class(objects_class); ReadableStream::readable_stream_default_pull_algorithm( ctx.clone(), objects, this.clone_for_branch_2, this.reading.clone(), this.read_again.clone(), this.reason_1.clone(), this.reason_2.clone(), this.branch_1.clone(), this.branch_2.clone(), this.cancel_promise.clone(), )?; } Ok(()) } }; let () = Function::new(ctx, OnceFn::new(f))?.defer(())?; Ok(ReadableStreamObjects::from_class(objects_class)) }, ) } fn close_steps( &self, ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js>, ) -> Result> { // Set reading to false. self.reading.store(false, Ordering::Release); // If canceled1 is false, perform ! ReadableStreamDefaultControllerClose(branch1.[[controller]]). if self.reason_1.get().is_none() { let objects = ReadableStreamObjects::from_class( self.branch_1 .get() .expect("close called without branch1 being initialised") .clone(), ) .refresh_reader(); ReadableStreamDefaultController::readable_stream_default_controller_close( ctx.clone(), objects, )?; } // If canceled2 is false, perform ! ReadableStreamDefaultControllerClose(branch2.[[controller]]). if self.reason_2.get().is_none() { let objects = ReadableStreamObjects::from_class( self.branch_2 .get() .expect("close called without branch2 being initialised") .clone(), ) .refresh_reader(); ReadableStreamDefaultController::readable_stream_default_controller_close( ctx.clone(), objects, )?; } // If canceled1 is false or canceled2 is false, resolve cancelPromise with undefined. if self.reason_1.get().is_none() || self.reason_2.get().is_none() { self.cancel_promise.resolve_undefined()? } Ok(objects) } fn error_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, _: Value<'js>, ) -> Result> { // Set reading to false. self.reading.store(false, Ordering::Release); Ok(objects) } } // Perform ! ReadableStreamDefaultReaderRead(reader, readRequest). objects = ReadableStreamDefaultReader::readable_stream_default_reader_read( &ctx, objects, ReadRequest { clone_for_branch_2, reading, read_again, reason_1, reason_2, branch_1, branch_2, cancel_promise, }, )?; // Return a promise resolved with undefined. Ok(objects .stream .promise_primordials .promise_resolved_with_undefined .clone()) } // Let cancel1Algorithm be the following steps, taking a reason argument: fn readable_stream_cancel_1_algorithm( ctx: Ctx<'js>, objects: ReadableStreamObjects< 'js, impl ReadableStreamController<'js>, impl ReadableStreamReader<'js>, >, reason_1: Rc>>, reason_2: Rc>>, cancel_promise: ResolveablePromise<'js>, reason: Value<'js>, ) -> Result> { // Set canceled1 to true. // Set reason1 to reason. reason_1 .set(reason.clone()) .expect("First tee stream already has a cancel reason"); // If canceled2 is true, if let Some(reason_2) = reason_2.get().cloned() { // Let compositeReason be ! CreateArrayFromList(« reason1, reason2 »). let composite_reason = List((reason, reason_2)); // Let cancelResult be ! ReadableStreamCancel(stream, compositeReason). let (cancel_result, _) = ReadableStream::readable_stream_cancel( ctx.clone(), objects, composite_reason.into_js(&ctx)?, )?; // Resolve cancelPromise with cancelResult. cancel_promise.resolve(cancel_result)?; } // Return cancelPromise. Ok(cancel_promise.promise) } // Let cancel2Algorithm be the following steps, taking a reason argument: #[allow(clippy::too_many_arguments)] fn readable_stream_cancel_2_algorithm( ctx: Ctx<'js>, objects: ReadableStreamObjects< 'js, impl ReadableStreamController<'js>, impl ReadableStreamReader<'js>, >, reason_1: Rc>>, reason_2: Rc>>, cancel_promise: ResolveablePromise<'js>, reason: Value<'js>, ) -> Result> { // Set canceled2 to true. // Set reason2 to reason. reason_2 .set(reason.clone()) .expect("Second tee stream already has a cancel reason"); // If canceled1 is true, if let Some(reason_1) = reason_1.get().cloned() { // Let compositeReason be ! CreateArrayFromList(« reason1, reason2 »). let composite_reason = List((reason_1, reason)); // Let cancelResult be ! ReadableStreamCancel(stream, compositeReason). let (cancel_result, _) = ReadableStream::readable_stream_cancel( ctx.clone(), objects, composite_reason.into_js(&ctx)?, )?; // Resolve cancelPromise with cancelResult. let () = cancel_promise.resolve(cancel_result)?; } // Return cancelPromise. Ok(cancel_promise.promise) } fn readable_byte_stream_tee( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, UndefinedReader>, ) -> Result<( ReadableStreamPair<'js>, ReadableByteStreamObjects<'js, UndefinedReader>, )> { // Let reader be ? AcquireReadableStreamDefaultReader(stream). let (stream, reader) = ReadableStreamReaderClass::acquire_readable_stream_default_reader( ctx.clone(), objects.stream, )?; objects.stream = stream; let reader: Rc>> = Rc::new(RefCell::new(reader.into())); // Let reading be false. let reading = Rc::new(AtomicBool::new(false)); // Let readAgainForBranch1 be false. let read_again_for_branch_1 = Rc::new(AtomicBool::new(false)); // Let readAgainForBranch2 be false. let read_again_for_branch_2 = Rc::new(AtomicBool::new(false)); // Let canceled1 be false. // Let canceled2 be false. // Let reason1 be undefined. let reason_1 = Rc::new(OnceCell::new()); // Let reason2 be undefined. let reason_2 = Rc::new(OnceCell::new()); // Let branch1 be undefined. let branch_1: Rc>> = Rc::new(OnceCell::new()); // Let branch2 be undefined. let branch_2: Rc>> = Rc::new(OnceCell::new()); // Let cancelPromise be a new promise. let cancel_promise = ResolveablePromise::new(&ctx)?; let objects_class = objects.into_inner(); // Let pull1Algorithm be the following steps: let pull_1_algorithm = PullAlgorithm::from_fn({ let objects_class = objects_class.clone(); let reader = reader.clone(); let reading = reading.clone(); let read_again_for_branch_1 = read_again_for_branch_1.clone(); let read_again_for_branch_2 = read_again_for_branch_2.clone(); let reason_1 = reason_1.clone(); let reason_2 = reason_2.clone(); let branch_1 = branch_1.clone(); let branch_2 = branch_2.clone(); let cancel_promise = cancel_promise.clone(); move |ctx, branch_1_controller| { let objects = ReadableStreamObjects::from_class(objects_class.clone()); let branch_1_controller = OwnedBorrowMut::from_class(match branch_1_controller { ReadableStreamControllerClass::ReadableStreamByteController(c) => c, _ => panic!( "ReadableByteStream tee pull1 algorithm called without branch1 having a byte controller" ), }); let branch_2 = OwnedBorrowMut::from_class(branch_2.get().cloned().expect("ReadableByteStream tee pull1 algorithm called without branch2 being initialised")); let branch_2_controller = match branch_2.controller { ReadableStreamControllerClass::ReadableStreamByteController(ref c) => { OwnedBorrowMut::from_class(c.clone()) }, _ => { panic!("ReadableByteStream tee pull1 algorithm called without branch2 having a byte controller") }, }; Self::readable_byte_stream_pull_1_algorithm( ctx, objects, reader.clone(), reading.clone(), read_again_for_branch_1.clone(), read_again_for_branch_2.clone(), reason_1.clone(), reason_2.clone(), ReadableStreamObjects::new_byte(OwnedBorrowMut::from_class(branch_1.get().cloned().expect("ReadableByteStream tee pull1 algorithm called without branch1 being initialised")), branch_1_controller) , ReadableStreamObjects::new_byte(branch_2, branch_2_controller), cancel_promise.clone(), ) } }); // Let pull2Algorithm be the following steps: let pull_2_algorithm = PullAlgorithm::from_fn({ let objects_class = objects_class.clone(); let reader = reader.clone(); let reading = reading.clone(); let read_again_for_branch_1 = read_again_for_branch_1.clone(); let read_again_for_branch_2 = read_again_for_branch_2.clone(); let reason_1 = reason_1.clone(); let reason_2 = reason_2.clone(); let branch_1 = branch_1.clone(); let branch_2 = branch_2.clone(); let cancel_promise = cancel_promise.clone(); move |ctx, branch_2_controller| { let objects = ReadableStreamObjects::from_class(objects_class.clone()); let branch_2 = OwnedBorrowMut::from_class(branch_2.get().cloned().expect("ReadableByteStream tee pull2 algorithm called without branch2 being initialised")); let branch_2_controller = match branch_2_controller { ReadableStreamControllerClass::ReadableStreamByteController(ref c) => { OwnedBorrowMut::from_class(c.clone()) }, _ => { panic!("ReadableByteStream tee pull2 algorithm called without branch2 having a byte controller") }, }; let branch_1 = OwnedBorrowMut::from_class(branch_1.get().cloned().expect("ReadableByteStream tee pull2 algorithm called without branch1 being initialised")); let branch_1_controller = match branch_1.controller { ReadableStreamControllerClass::ReadableStreamByteController(ref c) => { OwnedBorrowMut::from_class(c.clone()) }, _ => { panic!("ReadableByteStream tee pull2 algorithm called without branch1 having a byte controller") }, }; Self::readable_byte_stream_pull_2_algorithm( ctx, objects, reader.clone(), reading.clone(), read_again_for_branch_1.clone(), read_again_for_branch_2.clone(), reason_1.clone(), reason_2.clone(), ReadableStreamObjects::new_byte(branch_1, branch_1_controller), ReadableStreamObjects::new_byte(branch_2, branch_2_controller), cancel_promise.clone(), ) } }); let cancel_algorithm_1 = CancelAlgorithm::from_fn({ let objects_class = objects_class.clone(); let reader = reader.clone(); let reason_1 = reason_1.clone(); let reason_2 = reason_2.clone(); let cancel_promise = cancel_promise.clone(); move |reason: Value<'js>| { let reader = ReadableStreamReaderOwned::from_class(reader.borrow().clone()); let objects = ReadableStreamObjects::from_class(objects_class).set_reader(reader); Self::readable_stream_cancel_1_algorithm( reason.ctx().clone(), objects, reason_1, reason_2, cancel_promise, reason, ) } }); let cancel_algorithm_2 = CancelAlgorithm::from_fn({ let objects_class = objects_class.clone(); let reader = reader.clone(); let reason_1 = reason_1.clone(); let reason_2 = reason_2.clone(); let cancel_promise = cancel_promise.clone(); move |reason: Value<'js>| { let reader = ReadableStreamReaderOwned::from_class(reader.borrow().clone()); let objects = ReadableStreamObjects::from_class(objects_class).set_reader(reader); Self::readable_stream_cancel_2_algorithm( reason.ctx().clone(), objects, reason_1, reason_2, cancel_promise, reason, ) } }); // Let startAlgorithm be an algorithm that returns undefined. let start_algorithm = StartAlgorithm::ReturnUndefined; // Set branch1 to ! CreateReadableByteStream(startAlgorithm, pull1Algorithm, cancel1Algorithm). let objects_1 = { let (s, c) = Self::create_readable_byte_stream( ctx.clone(), start_algorithm.clone(), pull_1_algorithm.clone(), cancel_algorithm_1, )?; _ = branch_1.set(s.clone()); ReadableStreamClassObjects { stream: s, controller: c, reader: UndefinedReader, } }; // Set branch2 to ! CreateReadableByteStream(startAlgorithm, pull2Algorithm, cancel2Algorithm). let objects_2 = { let (s, c) = Self::create_readable_byte_stream( ctx.clone(), start_algorithm, pull_2_algorithm, cancel_algorithm_2, )?; _ = branch_2.set(s.clone()); ReadableStreamClassObjects { stream: s, controller: c, reader: UndefinedReader, } }; // Perform forwardReaderError, given reader. let this_reader = reader.borrow().clone(); Self::readable_byte_stream_forward_reader_error( ctx, reader, objects_1.clone(), objects_2.clone(), reason_1, reason_2, this_reader, cancel_promise, )?; // Return « branch1, branch2 ». Ok(( (objects_1.stream, objects_2.stream), ReadableStreamObjects::from_class(objects_class), )) } // Let forwardReaderError be the following steps, taking a thisReader argument: #[allow(clippy::too_many_arguments)] fn readable_byte_stream_forward_reader_error( ctx: Ctx<'js>, reader: Rc>>, objects_1: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, objects_2: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, reason_1: Rc>>, reason_2: Rc>>, this_reader: ReadableStreamReaderClass<'js>, cancel_promise: ResolveablePromise<'js>, ) -> Result<()> { // Upon rejection of thisReader.[[closedPromise]] with reason r, upon_promise( ctx, this_reader.closed_promise(), move |_, result| match result { Err(r) => { // If thisReader is not reader, return. if !reader.borrow().eq(&this_reader) { return Ok(()); } let objects_1 = ReadableStreamObjects::from_class_no_reader(objects_1).refresh_reader(); // Perform ! ReadableByteStreamControllerError(branch1.[[controller]], r). ReadableByteStreamController::readable_byte_stream_controller_error( objects_1, r.clone(), )?; let objects_2 = ReadableStreamObjects::from_class_no_reader(objects_2).refresh_reader(); // Perform ! ReadableByteStreamControllerError(branch2.[[controller]], r). ReadableByteStreamController::readable_byte_stream_controller_error( objects_2, r.clone(), )?; // If canceled1 is false or canceled2 is false, resolve cancelPromise with undefined. if reason_1.get().is_none() || reason_2.get().is_none() { cancel_promise.resolve_undefined()?; } Ok(()) }, Ok(()) => Ok(()), }, )?; Ok(()) } // Let pullWithDefaultReader be the following steps: #[allow(clippy::too_many_arguments)] fn readable_byte_stream_pull_with_default_reader( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, UndefinedReader>, reader: Rc>>, reading: Rc, read_again_for_branch_1: Rc, read_again_for_branch_2: Rc, reason_1: Rc>>, reason_2: Rc>>, objects_1: ReadableByteStreamObjects<'js, UndefinedReader>, objects_2: ReadableByteStreamObjects<'js, UndefinedReader>, cancel_promise: ResolveablePromise<'js>, ) -> Result> { let objects_class_1 = objects_1.into_inner(); let objects_class_2 = objects_2.into_inner(); // If reader implements ReadableStreamBYOBReader, let current_reader = reader.borrow().clone(); let current_reader = match current_reader { ReadableStreamReaderClass::ReadableStreamBYOBReader(r) => { let byob_reader = OwnedBorrowMut::from_class(r.clone()); // Perform ! ReadableStreamBYOBReaderRelease(reader). objects = ReadableStreamBYOBReader::readable_stream_byob_reader_release( objects.set_reader(byob_reader), )? .clear_reader(); // Set reader to ! AcquireReadableStreamDefaultReader(stream). let (s, new_reader) = ReadableStreamReaderClass::acquire_readable_stream_default_reader( ctx.clone(), objects.stream, )?; objects.stream = s; reader.replace(new_reader.clone().into()); // Perform forwardReaderError, given reader. Self::readable_byte_stream_forward_reader_error( ctx.clone(), reader.clone(), objects_class_1.clone(), objects_class_2.clone(), reason_1.clone(), reason_2.clone(), new_reader.clone().into(), cancel_promise.clone(), )?; new_reader }, ReadableStreamReaderClass::ReadableStreamDefaultReader(r) => r, }; // Let readRequest be a read request with the following items: #[derive(Clone)] struct ReadRequest<'js> { reader: Rc>>, reading: Rc, read_again_for_branch_1: Rc, read_again_for_branch_2: Rc, reason_1: Rc>>, reason_2: Rc>>, objects_class_1: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, objects_class_2: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, cancel_promise: ResolveablePromise<'js>, } impl<'js> Trace<'js> for ReadRequest<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { if let Ok(r) = self.reader.try_borrow() { r.trace(tracer) } if let Some(r) = self.reason_1.get() { r.trace(tracer) } if let Some(r) = self.reason_2.get() { r.trace(tracer) } self.objects_class_1.trace(tracer); self.objects_class_2.trace(tracer); self.cancel_promise.trace(tracer); } } impl<'js> ReadableStreamReadRequest<'js> for ReadRequest<'js> { fn chunk_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, chunk: Value<'js>, ) -> Result> { let ctx = chunk.ctx().clone(); let this = self.clone(); objects.with_assert_byte_controller(|objects| { let constructor_uint8array = objects.controller.array_constructor_primordials.constructor_uint8array.clone(); let function_array_buffer_is_view = objects.controller.function_array_buffer_is_view.clone(); let chunk = ViewBytes::from_value(&ctx, &function_array_buffer_is_view, Some(&chunk))?; let objects_class = objects.into_inner(); // Queue a microtask to perform the following steps: let f = { let ctx = ctx.clone(); let objects_class = objects_class.clone(); move || -> Result<()> { // Set readAgainForBranch1 to false. this.read_again_for_branch_1.store(false, Ordering::Release); // Set readAgainForBranch2 to false. this.read_again_for_branch_2.store(false, Ordering::Release); // Let chunk1 and chunk2 be chunk. let chunk_1 = chunk.clone(); let mut chunk_2 = chunk.clone(); // If canceled1 is false and canceled2 is false, if this.reason_1.get().is_none() && this.reason_2.get().is_none() { // Let cloneResult be CloneAsUint8Array(chunk). match clone_as_uint8_array(ctx.clone(), &constructor_uint8array, &function_array_buffer_is_view, chunk) { // If cloneResult is an abrupt completion, Err(Error::Exception) => { let err = ctx.catch(); let objects_1 = ReadableStreamObjects::from_class(this.objects_class_1); // Perform ! ReadableByteStreamControllerError(branch1.[[controller]], cloneResult.[[Value]]). ReadableByteStreamController::readable_byte_stream_controller_error( objects_1, err.clone(), )?; let objects_2 = ReadableStreamObjects::from_class(this.objects_class_2); // Perform ! ReadableByteStreamControllerError(branch2.[[controller]], cloneResult.[[Value]]). ReadableByteStreamController::readable_byte_stream_controller_error( objects_2, err.clone(), )?; // Resolve cancelPromise with ! ReadableStreamCancel(stream, cloneResult.[[Value]]). let (promise, _) = ReadableStream::readable_stream_cancel( ctx, ReadableStreamObjects::from_class(objects_class), err.clone(), )?; this.cancel_promise.resolve(promise)?; // Return. return Ok(()); }, // Otherwise, set chunk2 to cloneResult.[[Value]]. Ok(clone_result) => chunk_2 = clone_result, Err(err) => return Err(err), }; } // If canceled1 is false, perform ! ReadableByteStreamControllerEnqueue(branch1.[[controller]], chunk1). if this.reason_1.get().is_none() { let objects_1 = ReadableStreamObjects::from_class_no_reader( this.objects_class_1.clone(), ).refresh_reader(); ReadableByteStreamController::readable_byte_stream_controller_enqueue( &ctx, objects_1, chunk_1, )?; } // If canceled2 is false, perform ! ReadableByteStreamControllerEnqueue(branch2.[[controller]], chunk2). if this.reason_2.get().is_none() { let objects_2 = ReadableStreamObjects::from_class_no_reader( this.objects_class_2.clone(), ).refresh_reader(); ReadableByteStreamController::readable_byte_stream_controller_enqueue( &ctx, objects_2, chunk_2, )?; } // Set reading to false. this.reading.store(false, Ordering::Release); let objects_1 = ReadableStreamObjects::from_class(this.objects_class_1); let objects_2 = ReadableStreamObjects::from_class(this.objects_class_2); let objects = ReadableStreamObjects::from_class_no_reader(objects_class); // If readAgainForBranch1 is true, perform pull1Algorithm. if this.read_again_for_branch_1.load(Ordering::Acquire) { ReadableStream::readable_byte_stream_pull_1_algorithm( ctx.clone(), objects, this.reader, this.reading, this.read_again_for_branch_1, this.read_again_for_branch_2, this.reason_1, this.reason_2, objects_1, objects_2, this.cancel_promise, )?; } else if this.read_again_for_branch_2.load(Ordering::Acquire) { // Otherwise, if readAgainForBranch2 is true, perform pull2Algorithm. ReadableStream::readable_byte_stream_pull_2_algorithm( ctx.clone(), objects, this.reader, this.reading, this.read_again_for_branch_1, this.read_again_for_branch_2, this.reason_1, this.reason_2, objects_1, objects_2, this.cancel_promise, )?; } Ok(()) } }; let () = Function::new(ctx, OnceFn::new(f))?.defer(())?; let objects = ReadableStreamObjects::from_class(objects_class); Ok(objects) }) } fn close_steps( &self, ctx: &Ctx<'js>, objects: ReadableStreamDefaultReaderObjects<'js>, ) -> Result> { // Set reading to false. self.reading.store(false, Ordering::Release); let mut objects_1 = ReadableStreamObjects::from_class_no_reader(self.objects_class_1.clone()) .refresh_reader(); let mut objects_2 = ReadableStreamObjects::from_class_no_reader(self.objects_class_2.clone()) .refresh_reader(); // If canceled1 is false, perform ! ReadableByteStreamControllerClose(branch1.[[controller]]). if self.reason_1.get().is_none() { objects_1 = ReadableByteStreamController::readable_byte_stream_controller_close( ctx.clone(), objects_1, )?; } // If canceled2 is false, perform ! ReadableByteStreamControllerClose(branch2.[[controller]]). if self.reason_2.get().is_none() { objects_2 = ReadableByteStreamController::readable_byte_stream_controller_close( ctx.clone(), objects_2, )?; } // If branch1.[[controller]].[[pendingPullIntos]] is not empty, perform ! ReadableByteStreamControllerRespond(branch1.[[controller]], 0). if !objects_1.controller.pending_pull_intos.is_empty() { ReadableByteStreamController::readable_byte_stream_controller_respond( ctx.clone(), objects_1, 0, )? } // If branch2.[[controller]].[[pendingPullIntos]] is not empty, perform ! ReadableByteStreamControllerRespond(branch2.[[controller]], 0). if !objects_2.controller.pending_pull_intos.is_empty() { ReadableByteStreamController::readable_byte_stream_controller_respond( ctx.clone(), objects_2, 0, )? } // If canceled1 is false or canceled2 is false, resolve cancelPromise with undefined. if self.reason_1.get().is_none() || self.reason_2.get().is_none() { self.cancel_promise.resolve_undefined()? } Ok(objects) } fn error_steps( &self, objects: ReadableStreamDefaultReaderObjects<'js>, _: Value<'js>, ) -> Result> { // Set reading to false. self.reading.store(false, Ordering::Release); Ok(objects) } } // Perform ! ReadableStreamDefaultReaderRead(reader, readRequest). Ok( ReadableStreamDefaultReader::readable_stream_default_reader_read( &ctx, objects.set_reader(OwnedBorrowMut::from_class(current_reader)), ReadRequest { reader, reading, read_again_for_branch_1, read_again_for_branch_2, reason_1, reason_2, objects_class_1, objects_class_2, cancel_promise, }, )? .clear_reader(), ) } #[allow(clippy::too_many_arguments)] fn readable_byte_stream_pull_with_byob_reader( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, UndefinedReader>, reader: Rc>>, reading: Rc, read_again_for_branch_1: Rc, read_again_for_branch_2: Rc, reason_1: Rc>>, reason_2: Rc>>, objects_1: ReadableByteStreamObjects<'js, UndefinedReader>, objects_2: ReadableByteStreamObjects<'js, UndefinedReader>, cancel_promise: ResolveablePromise<'js>, view: ViewBytes<'js>, for_branch_2: bool, ) -> Result> { let objects_1 = objects_1.into_inner(); let objects_2 = objects_2.into_inner(); // If reader implements ReadableStreamDefaultReader, let current_reader = reader.borrow().clone(); let current_reader = match current_reader { ReadableStreamReaderClass::ReadableStreamDefaultReader(r) => { let default_reader = OwnedBorrowMut::from_class(r.clone()); // Perform ! ReadableStreamDefaultReaderRelease(reader). objects = ReadableStreamDefaultReader::readable_stream_default_reader_release( objects.set_reader(default_reader), )? .clear_reader(); // Set reader to ! AcquireReadableStreamBYOBReader(stream). let (s, new_reader) = ReadableStreamReaderClass::acquire_readable_stream_byob_reader( ctx.clone(), objects.stream, )?; objects.stream = s; reader.replace(new_reader.clone().into()); // Perform forwardReaderError, given reader. Self::readable_byte_stream_forward_reader_error( ctx.clone(), reader.clone(), objects_1.clone(), objects_2.clone(), reason_1.clone(), reason_2.clone(), new_reader.clone().into(), cancel_promise.clone(), )?; new_reader }, ReadableStreamReaderClass::ReadableStreamBYOBReader(r) => r.clone(), }; // Let byobBranch be branch2 if forBranch2 is true, and branch1 otherwise. // Let otherBranch be branch2 if forBranch2 is false, and branch1 otherwise. let (byob_objects, other_objects) = if for_branch_2 { (objects_2.clone(), objects_1.clone()) } else { (objects_1.clone(), objects_2.clone()) }; // Let readIntoRequest be a read-into request with the following items: #[derive(Clone)] struct ReadIntoRequest<'js> { reader: Rc>>, reading: Rc, read_again_for_branch_1: Rc, read_again_for_branch_2: Rc, reason_1: Rc>>, reason_2: Rc>>, objects_1: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, objects_2: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, byob_objects: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, other_objects: ReadableStreamClassObjects< 'js, ReadableByteStreamControllerOwned<'js>, UndefinedReader, >, cancel_promise: ResolveablePromise<'js>, for_branch_2: bool, } impl<'js> Trace<'js> for ReadIntoRequest<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { if let Ok(r) = self.reader.try_borrow() { r.trace(tracer) } if let Some(r) = self.reason_1.get() { r.trace(tracer) } if let Some(r) = self.reason_2.get() { r.trace(tracer) } self.objects_1.trace(tracer); self.objects_2.trace(tracer); self.byob_objects.trace(tracer); self.other_objects.trace(tracer); self.cancel_promise.trace(tracer); } } impl<'js> ReadableStreamReadIntoRequest<'js> for ReadIntoRequest<'js> { fn chunk_steps( &self, objects: ReadableStreamBYOBObjects<'js>, chunk: Value<'js>, ) -> Result> { let ctx = chunk.ctx().clone(); let constructor_uint8array = objects .controller .array_constructor_primordials .constructor_uint8array .clone(); let function_array_buffer_is_view = objects.controller.function_array_buffer_is_view.clone(); let chunk = ViewBytes::from_value(&ctx, &function_array_buffer_is_view, Some(&chunk))?; let objects_class = objects.into_inner(); // Queue a microtask to perform the following steps: let f = { let ctx = ctx.clone(); let objects_class = objects_class.clone(); let this = self.clone(); move || -> Result<()> { // Set readAgainForBranch1 to false. this.read_again_for_branch_1.store(false, Ordering::Release); // Set readAgainForBranch2 to false. this.read_again_for_branch_2.store(false, Ordering::Release); // Let byobCanceled be canceled2 if forBranch2 is true, and canceled1 otherwise. // Let otherCanceled be canceled2 if forBranch2 is false, and canceled1 otherwise. let (byob_canceled, other_canceled) = if this.for_branch_2 { (this.reason_2.get().is_some(), this.reason_1.get().is_some()) } else { (this.reason_1.get().is_some(), this.reason_2.get().is_some()) }; // If otherCanceled is false, if !other_canceled { // Let cloneResult be CloneAsUint8Array(chunk). match clone_as_uint8_array( ctx.clone(), &constructor_uint8array, &function_array_buffer_is_view, chunk.clone(), ) { // If cloneResult is an abrupt completion, Err(Error::Exception) => { let err = ctx.catch(); let byob_objects = ReadableStreamObjects::from_class_no_reader( this.byob_objects.clone(), ) .refresh_reader(); // Perform ! ReadableByteStreamControllerError(byobBranch.[[controller]], cloneResult.[[Value]]). ReadableByteStreamController::readable_byte_stream_controller_error( byob_objects, err.clone(), )?; let other_objects = ReadableStreamObjects::from_class_no_reader( this.other_objects.clone(), ) .refresh_reader(); // Perform ! ReadableByteStreamControllerError(otherBranch.[[controller]], cloneResult.[[Value]]). ReadableByteStreamController::readable_byte_stream_controller_error( other_objects, err.clone(), )?; // Resolve cancelPromise with ! ReadableStreamCancel(stream, cloneResult.[[Value]]). let (promise, _) = ReadableStream::readable_stream_cancel( ctx, ReadableStreamObjects::from_class(objects_class), err.clone(), )?; this.cancel_promise.resolve(promise)?; // Return. return Ok(()); }, // Otherwise, let clonedChunk be cloneResult.[[Value]]. Ok(cloned_chunk) => { // If byobCanceled is false, perform ! ReadableByteStreamControllerRespondWithNewView(byobBranch.[[controller]], chunk). if !byob_canceled { let byob_objects = ReadableStreamObjects::from_class_no_reader( this.byob_objects.clone(), ) .refresh_reader(); ReadableByteStreamController::readable_byte_stream_controller_respond_with_new_view(ctx.clone(), byob_objects, chunk)?; } let other_objects = ReadableStreamObjects::from_class_no_reader( this.other_objects.clone(), ) .refresh_reader(); // Perform ! ReadableByteStreamControllerEnqueue(otherBranch.[[controller]], clonedChunk). ReadableByteStreamController::readable_byte_stream_controller_enqueue(&ctx, other_objects, cloned_chunk)?; }, Err(err) => return Err(err), }; } else if !byob_canceled { let byob_objects = ReadableStreamObjects::from_class_no_reader( this.byob_objects.clone(), ) .refresh_reader(); // Otherwise, if byobCanceled is false, perform ! ReadableByteStreamControllerRespondWithNewView(byobBranch.[[controller]], chunk). ReadableByteStreamController::readable_byte_stream_controller_respond_with_new_view(ctx.clone(), byob_objects, chunk)?; } let objects_1 = ReadableStreamObjects::from_class(this.objects_1.clone()); let objects_2 = ReadableStreamObjects::from_class(this.objects_2.clone()); // Set reading to false. this.reading.store(false, Ordering::Release); // If readAgainForBranch1 is true, perform pull1Algorithm. if this.read_again_for_branch_1.load(Ordering::Acquire) { ReadableStream::readable_byte_stream_pull_1_algorithm( ctx.clone(), ReadableStreamObjects::from_class(objects_class).clear_reader(), this.reader.clone(), this.reading.clone(), this.read_again_for_branch_1.clone(), this.read_again_for_branch_2.clone(), this.reason_1.clone(), this.reason_2.clone(), objects_1, objects_2, this.cancel_promise.clone(), )?; } else if this.read_again_for_branch_2.load(Ordering::Acquire) { // Otherwise, if readAgainForBranch2 is true, perform pull2Algorithm. ReadableStream::readable_byte_stream_pull_2_algorithm( ctx.clone(), ReadableStreamObjects::from_class(objects_class).clear_reader(), this.reader.clone(), this.reading.clone(), this.read_again_for_branch_1.clone(), this.read_again_for_branch_2.clone(), this.reason_1.clone(), this.reason_2.clone(), objects_1, objects_2, this.cancel_promise.clone(), )?; } Ok(()) } }; let () = Function::new(ctx, OnceFn::new(f))?.defer(())?; let objects = ReadableStreamObjects::from_class(objects_class); Ok(objects) } fn close_steps( &self, objects: ReadableStreamBYOBObjects<'js>, chunk: Value<'js>, ) -> Result> { let ctx = chunk.ctx().clone(); // Set reading to false. self.reading.store(false, Ordering::Release); // Let byobCanceled be canceled2 if forBranch2 is true, and canceled1 otherwise. // Let otherCanceled be canceled2 if forBranch2 is false, and canceled1 otherwise. let (byob_canceled, other_canceled) = if self.for_branch_2 { (self.reason_2.get().is_some(), self.reason_1.get().is_some()) } else { (self.reason_1.get().is_some(), self.reason_2.get().is_some()) }; // If byobCanceled is false, perform ! ReadableByteStreamControllerClose(byobBranch.[[controller]]). if !byob_canceled { let byob_objects = ReadableStreamObjects::from_class_no_reader(self.byob_objects.clone()) .refresh_reader(); ReadableByteStreamController::readable_byte_stream_controller_close( ctx.clone(), byob_objects, )?; } // If otherCanceled is false, perform ! ReadableByteStreamControllerClose(otherBranch.[[controller]]). if !other_canceled { let other_objects = ReadableStreamObjects::from_class_no_reader(self.other_objects.clone()) .refresh_reader(); ReadableByteStreamController::readable_byte_stream_controller_close( ctx.clone(), other_objects, )?; } // If chunk is not undefined, if !chunk.is_undefined() { let chunk = ViewBytes::from_value( &ctx, &objects.controller.function_array_buffer_is_view, Some(&chunk), )?; // If byobCanceled is false, perform ! ReadableByteStreamControllerRespondWithNewView(byobBranch.[[controller]], chunk). if !byob_canceled { let byob_objects = ReadableStreamObjects::from_class_no_reader(self.byob_objects.clone()) .refresh_reader(); ReadableByteStreamController::readable_byte_stream_controller_respond_with_new_view(ctx.clone(), byob_objects, chunk)?; } let other_objects = ReadableStreamObjects::from_class_no_reader(self.other_objects.clone()) .refresh_reader(); // If otherCanceled is false and otherBranch.[[controller]].[[pendingPullIntos]] is not empty, perform ! ReadableByteStreamControllerRespond(otherBranch.[[controller]], 0). if !other_canceled && !other_objects.controller.pending_pull_intos.is_empty() { ReadableByteStreamController::readable_byte_stream_controller_respond( ctx.clone(), other_objects, 0, )?; } } // If byobCanceled is false or otherCanceled is false, resolve cancelPromise with undefined. if !byob_canceled || !other_canceled { self.cancel_promise.resolve_undefined()? } Ok(objects) } fn error_steps( &self, objects: ReadableStreamBYOBObjects<'js>, _: Value<'js>, ) -> Result> { // Set reading to false. self.reading.store(false, Ordering::Release); Ok(objects) } } // Perform ! ReadableStreamBYOBReaderRead(reader, view, 1, readIntoRequest). Ok(ReadableStreamBYOBReader::readable_stream_byob_reader_read( &ctx, objects.set_reader(OwnedBorrowMut::from_class(current_reader)), view, 1, ReadIntoRequest { reader, reading, read_again_for_branch_1, read_again_for_branch_2, reason_1, reason_2, objects_1, objects_2, byob_objects, other_objects, cancel_promise, for_branch_2, }, )? .clear_reader()) } // Let pull1Algorithm be the following steps: #[allow(clippy::too_many_arguments)] fn readable_byte_stream_pull_1_algorithm( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, UndefinedReader>, reader: Rc>>, reading: Rc, read_again_for_branch_1: Rc, read_again_for_branch_2: Rc, reason_1: Rc>>, reason_2: Rc>>, mut objects_1: ReadableByteStreamObjects<'js, UndefinedReader>, objects_2: ReadableByteStreamObjects<'js, UndefinedReader>, cancel_promise: ResolveablePromise<'js>, ) -> Result> { // If reading is true, if reading.swap(true, Ordering::AcqRel) { // Set readAgainForBranch1 to true. read_again_for_branch_1.store(true, Ordering::Release); // Return a promise resolved with undefined. return Ok(objects .stream .promise_primordials .promise_resolved_with_undefined .clone()); } // Set reading to true. // Let byobRequest be ! ReadableByteStreamControllerGetBYOBRequest(branch1.[[controller]]). let (byob_request, branch_1_controller) = ReadableByteStreamController::readable_byte_stream_controller_get_byob_request( ctx.clone(), objects_1.controller, )?; objects_1.controller = branch_1_controller; // If byobRequest is null, perform pullWithDefaultReader. objects = match byob_request.0 { None => Self::readable_byte_stream_pull_with_default_reader( ctx.clone(), objects, reader.clone(), reading.clone(), read_again_for_branch_1, read_again_for_branch_2, reason_1, reason_2, objects_1, objects_2, cancel_promise.clone(), )?, // Otherwise, perform pullWithBYOBReader, given byobRequest.[[view]] and false. Some(byob_request) => { let view = byob_request.borrow().view.clone().expect( "ReadableByteStream tee pull1Algorithm called with invalidated byobRequest", ); Self::readable_byte_stream_pull_with_byob_reader( ctx.clone(), objects, reader.clone(), reading.clone(), read_again_for_branch_1, read_again_for_branch_2, reason_1, reason_2, objects_1, objects_2, cancel_promise.clone(), view, false, )? }, }; // Return a promise resolved with undefined. Ok(objects .stream .promise_primordials .promise_resolved_with_undefined .clone()) } // Let pull2Algorithm be the following steps: #[allow(clippy::too_many_arguments)] fn readable_byte_stream_pull_2_algorithm( ctx: Ctx<'js>, mut objects: ReadableByteStreamObjects<'js, UndefinedReader>, reader: Rc>>, reading: Rc, read_again_for_branch_1: Rc, read_again_for_branch_2: Rc, reason_1: Rc>>, reason_2: Rc>>, objects_1: ReadableByteStreamObjects<'js, UndefinedReader>, mut objects_2: ReadableByteStreamObjects<'js, UndefinedReader>, cancel_promise: ResolveablePromise<'js>, ) -> Result> { // If reading is true, if reading.swap(true, Ordering::AcqRel) { // Set readAgainForBranch2 to true. read_again_for_branch_2.store(true, Ordering::Release); // Return a promise resolved with undefined. return Ok(objects .stream .promise_primordials .promise_resolved_with_undefined .clone()); } // Set reading to true. // Let byobRequest be ! ReadableByteStreamControllerGetBYOBRequest(branch2.[[controller]]). let (byob_request, branch_2_controller) = ReadableByteStreamController::readable_byte_stream_controller_get_byob_request( ctx.clone(), objects_2.controller, )?; objects_2.controller = branch_2_controller; // If byobRequest is null, perform pullWithDefaultReader. objects = match byob_request.0 { None => Self::readable_byte_stream_pull_with_default_reader( ctx.clone(), objects, reader.clone(), reading.clone(), read_again_for_branch_1, read_again_for_branch_2, reason_1, reason_2, objects_1, objects_2, cancel_promise, )?, // Otherwise, perform pullWithBYOBReader, given byobRequest.[[view]] and true. Some(byob_request) => Self::readable_byte_stream_pull_with_byob_reader( ctx.clone(), objects, reader.clone(), reading.clone(), read_again_for_branch_1, read_again_for_branch_2, reason_1, reason_2, objects_1, objects_2, cancel_promise, byob_request.borrow().view.clone().expect( "ReadableByteStream tee pull2Algorithm called with invalidated byobRequest", ), true, )?, }; // Return a promise resolved with undefined. Ok(objects .stream .promise_primordials .promise_resolved_with_undefined .clone()) } } fn clone_as_uint8_array<'js>( ctx: Ctx<'js>, constructor_uint8array: &Constructor<'js>, function_array_buffer_is_view: &Function<'js>, chunk: ViewBytes<'js>, ) -> Result> { let (buffer, byte_length, byte_offset) = chunk.get_array_buffer()?; // Let buffer be ? CloneArrayBuffer(O.[[ViewedArrayBuffer]], O.[[ByteOffset]], O.[[ByteLength]], %ArrayBuffer%). let buffer = ArrayBuffer::new_copy( ctx.clone(), &buffer .as_bytes() .expect("CloneAsUInt8Array called on detached buffer") [byte_offset..byte_offset + byte_length], )?; // Let array be ! Construct(%Uint8Array%, « buffer »). // Return array. ViewBytes::from_value( &ctx, function_array_buffer_is_view, Some(&constructor_uint8array.construct((buffer,))?), ) } ================================================ FILE: modules/llrt_stream_web/src/readable_writable_pair.rs ================================================ use rquickjs::{Ctx, Error, FromJs, Result, Value}; use crate::{readable::ReadableStreamClass, writable::WritableStreamClass}; /// An object containing a pair of linked streams, one readable and one writable /// https://streams.spec.whatwg.org/#dictdef-readablewritablepair pub struct ReadableWritablePair<'js> { pub readable: ReadableStreamClass<'js>, pub writable: WritableStreamClass<'js>, } impl<'js> FromJs<'js> for ReadableWritablePair<'js> { fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result { let ty_name = value.type_name(); let obj = value .as_object() .ok_or(Error::new_from_js(ty_name, "Object"))?; let readable = obj.get::<_, ReadableStreamClass<'js>>("readable")?; let writable = obj.get::<_, WritableStreamClass<'js>>("writable")?; Ok(Self { readable, writable }) } } ================================================ FILE: modules/llrt_stream_web/src/utils/mod.rs ================================================ use llrt_utils::option::Undefined; use rquickjs::{ class::{JsClass, OwnedBorrowMut}, Class, Ctx, FromJs, IntoAtom, Object, Result, Value, }; pub mod promise; pub mod queue; // the trait used elsewhere in this repo accepts null values as 'None', which causes many web platform tests to fail as they // like to check that undefined is accepted and null isn't. pub trait ValueOrUndefined<'js> { fn get_value_or_undefined + Clone, V: FromJs<'js>>( &self, k: K, ) -> Result>; } impl<'js> ValueOrUndefined<'js> for Object<'js> { fn get_value_or_undefined + Clone, V: FromJs<'js> + Sized>( &self, k: K, ) -> Result> { let value = self.get::>(k)?; Ok(Undefined::from_js(self.ctx(), value)?.0) } } impl<'js> ValueOrUndefined<'js> for Value<'js> { fn get_value_or_undefined + Clone, V: FromJs<'js>>( &self, k: K, ) -> Result> { if let Some(obj) = self.as_object() { return obj.get_value_or_undefined(k); } Ok(None) } } pub trait UnwrapOrUndefined<'js> { fn unwrap_or_undefined(self, ctx: &Ctx<'js>) -> Value<'js>; } impl<'js> UnwrapOrUndefined<'js> for Option> { fn unwrap_or_undefined(self, ctx: &Ctx<'js>) -> Value<'js> { self.unwrap_or_else(|| Value::new_undefined(ctx.clone())) } } pub fn class_from_owned_borrow_mut<'js, T: JsClass<'js>>( borrow: OwnedBorrowMut<'js, T>, ) -> (Class<'js, T>, OwnedBorrowMut<'js, T>) { let class = borrow.into_inner(); let borrow = OwnedBorrowMut::from_class(class.clone()); (class, borrow) } ================================================ FILE: modules/llrt_stream_web/src/utils/promise.rs ================================================ use std::{cell::Cell, rc::Rc}; use llrt_utils::primordials::Primordial; use rquickjs::{ atom::PredefinedAtom, class::{Trace, Tracer}, function::Constructor, prelude::{IntoArg, OnceFn, This}, promise::PromiseState, Ctx, Error, FromJs, Function, IntoJs, JsLifetime, Promise, Result, Value, }; pub fn promise_rejected_with<'js>( primordials: &PromisePrimordials<'js>, value: Value<'js>, ) -> Result> { primordials .promise_reject .call((This(primordials.promise_constructor.clone()), value)) } pub fn promise_rejected_catch<'js>( ctx: &Ctx<'js>, promise_primordials: &PromisePrimordials<'js>, ) -> Result> { promise_rejected_with(promise_primordials, ctx.catch()) } pub fn promise_rejected_with_constructor<'js, T: From>( constructor: &Constructor<'js>, promise_primordials: &PromisePrimordials<'js>, msg: &str, ) -> std::result::Result, T> { let e: Value = constructor.call((msg,))?; Ok(promise_rejected_with(promise_primordials, e)?) } pub fn promise_resolved_with<'js>( ctx: &Ctx<'js>, primordials: &PromisePrimordials<'js>, value: Result>, ) -> Result> { match value { Ok(value) => primordials .promise_resolve .call((This(primordials.promise_constructor.clone()), value)), Err(Error::Exception) => primordials .promise_reject .call((This(primordials.promise_constructor.clone()), ctx.catch())), Err(err) => Err(err), } } #[derive(JsLifetime, Clone)] pub struct PromisePrimordials<'js> { pub promise_constructor: Constructor<'js>, pub promise_resolve: Function<'js>, pub promise_reject: Function<'js>, pub promise_all: Function<'js>, pub promise_resolved_with_undefined: Promise<'js>, } impl<'js> Trace<'js> for PromisePrimordials<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.promise_constructor.trace(tracer); self.promise_resolve.trace(tracer); self.promise_reject.trace(tracer); self.promise_all.trace(tracer); self.promise_resolved_with_undefined.trace(tracer); } } impl<'js> Primordial<'js> for PromisePrimordials<'js> { fn new(ctx: &Ctx<'js>) -> Result where Self: Sized, { let promise_constructor: Constructor<'js> = ctx.globals().get(PredefinedAtom::Promise)?; let promise_resolve: Function<'js> = promise_constructor.get("resolve")?; let promise_reject: Function<'js> = promise_constructor.get("reject")?; let promise_all: Function<'js> = promise_constructor.get("all")?; let promise_resolved_with_undefined = promise_resolve.call(( This(promise_constructor.clone()), Value::new_undefined(ctx.clone()), ))?; Ok(Self { promise_constructor, promise_resolve, promise_reject, promise_all, promise_resolved_with_undefined, }) } } // https://webidl.spec.whatwg.org/#dfn-perform-steps-once-promise-is-settled pub fn upon_promise<'js, Input: FromJs<'js> + 'js, Output: IntoJs<'js> + 'js>( ctx: Ctx<'js>, promise: Promise<'js>, then: impl FnOnce(Ctx<'js>, std::result::Result>) -> Result + 'js, ) -> Result> { let then = Rc::new(Cell::new(Some(then))); let then2 = then.clone(); promise.then()?.call(( This(promise.clone()), Function::new( ctx.clone(), OnceFn::new(move |ctx, input| { then.take() .expect("Promise.then should only call either resolve or reject")( ctx, Ok(input), ) }), ), Function::new( ctx, OnceFn::new(move |ctx, e: Value<'js>| { then2 .take() .expect("Promise.then should only call either resolve or reject")( ctx, Err(e) ) }), ), )) } pub fn upon_promise_fulfilment<'js, Input: FromJs<'js> + 'js, Output: IntoJs<'js> + 'js>( ctx: Ctx<'js>, promise: Promise<'js>, then: impl FnOnce(Ctx<'js>, Input) -> Result + 'js, ) -> Result> { promise.then()?.call(( This(promise.clone()), Function::new(ctx.clone(), OnceFn::new(then)), )) } #[derive(Debug, JsLifetime, Clone)] pub struct ResolveablePromise<'js> { pub promise: Promise<'js>, resolve: Option>, reject: Option>, } impl<'js> ResolveablePromise<'js> { pub fn new(ctx: &Ctx<'js>) -> Result { let (promise, resolve, reject) = Promise::new(ctx)?; Ok(Self { promise, resolve: Some(resolve), reject: Some(reject), }) } pub fn resolved_with_undefined(primordials: &PromisePrimordials<'js>) -> Self { Self { promise: primordials.promise_resolved_with_undefined.clone(), resolve: None, reject: None, } } pub fn rejected_with(primordials: &PromisePrimordials<'js>, error: Value<'js>) -> Result { Ok(Self { promise: promise_rejected_with(primordials, error)?, resolve: None, reject: None, }) } pub fn rejected_with_constructor( primordials: &PromisePrimordials<'js>, constructor: &Constructor<'js>, msg: &str, ) -> Result { Ok(Self { promise: promise_rejected_with_constructor::( constructor, primordials, msg, )?, resolve: None, reject: None, }) } pub fn resolve(&self, value: impl IntoArg<'js>) -> Result<()> { if let Some(resolve) = &self.resolve { let () = resolve.call((value,))?; } Ok(()) } pub fn resolve_undefined(&self) -> Result<()> { if let Some(resolve) = &self.resolve { let () = resolve.call((rquickjs::Undefined,))?; } Ok(()) } pub fn reject(&self, value: impl IntoArg<'js>) -> Result<()> { if let Some(reject) = &self.reject { let () = reject.call((value,))?; } Ok(()) } pub fn reject_with_constructor(&self, constructor: &Constructor<'js>, msg: &str) -> Result<()> { if let Some(reject) = &self.reject { let e: Value = constructor.call((msg,))?; let () = reject.call((e,))?; } Ok(()) } pub fn is_pending(&self) -> bool { self.promise.state() == PromiseState::Pending } pub fn set_is_handled(&self) -> Result<()> { self.promise.catch()?.call(( This(self.promise.clone()), Function::new(self.promise.ctx().clone(), || {}), )) } } impl<'js> Trace<'js> for ResolveablePromise<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.promise.trace(tracer); self.resolve.trace(tracer); self.reject.trace(tracer); } } pub fn with_promise_result<'js>( ctx: &Ctx<'js>, f: impl FnOnce() -> Result>, ) -> Result> { match f() { Ok(value) => Ok(value), Err(Error::Exception) => promise_rejected_catch(ctx, &*PromisePrimordials::get(ctx)?), Err(err) => Err(err), } } ================================================ FILE: modules/llrt_stream_web/src/utils/queue.rs ================================================ use std::collections::VecDeque; use rquickjs::{class::Trace, Ctx, Exception, JsLifetime, Result, Value}; use crate::queuing_strategy::SizeValue; /// QueueWithSize is present in readable and writable streams and abstracts away certain queue operations /// https://streams.spec.whatwg.org/#queue-with-sizes #[derive(JsLifetime, Trace)] pub struct QueueWithSizes<'js> { pub queue: VecDeque>, pub queue_total_size: f64, } impl<'js> QueueWithSizes<'js> { pub fn new() -> Self { Self { queue: VecDeque::new(), queue_total_size: 0.0, } } pub fn enqueue_value_with_size( &mut self, ctx: &Ctx<'js>, value: Value<'js>, size: SizeValue<'js>, ) -> Result<()> { let size = match is_non_negative_number(size) { None => { // If ! IsNonNegativeNumber(size) is false, throw a RangeError exception. return Err(Exception::throw_range( ctx, "Size must be a finite, non-NaN, non-negative number.", )); }, Some(size) => size, }; // If size is +∞, throw a RangeError exception. if size.is_infinite() { return Err(Exception::throw_range( ctx, "Size must be a finite, non-NaN, non-negative number.", )); }; // Append a new value-with-size with value value and size size to container.[[queue]]. self.queue.push_back(ValueWithSize { value, size }); // Set container.[[queueTotalSize]] to container.[[queueTotalSize]] + size. self.queue_total_size += size; Ok(()) } pub fn dequeue_value(&mut self) -> Value<'js> { // Let valueWithSize be container.[[queue]][0]. // Remove valueWithSize from container.[[queue]]. let value_with_size = self .queue .pop_front() .expect("DequeueValue called with empty queue"); // Set container.[[queueTotalSize]] to container.[[queueTotalSize]] − valueWithSize’s size. self.queue_total_size -= value_with_size.size; // If container.[[queueTotalSize]] < 0, set container.[[queueTotalSize]] to 0. (This can occur due to rounding errors.) if self.queue_total_size < 0.0 { self.queue_total_size = 0.0 } value_with_size.value } pub fn reset_queue(&mut self) { // Set container.[[queue]] to a new empty list. self.queue.clear(); // Set container.[[queueTotalSize]] to 0. self.queue_total_size = 0.0; } } #[derive(JsLifetime, Trace, Clone)] pub struct ValueWithSize<'js> { pub value: Value<'js>, size: f64, } fn is_non_negative_number(value: SizeValue<'_>) -> Option { // If Type(v) is not Number, return false. let number = value.as_number()?; // If v is NaN, return false. if number.is_nan() { return None; } // If v < 0, return false. if number < 0.0 { return None; } // Return true. Some(number) } ================================================ FILE: modules/llrt_stream_web/src/writable/default_controller.rs ================================================ use llrt_abort::{AbortController, AbortSignal}; use llrt_utils::{ object::CreateSymbol, option::{Null, Undefined}, primordials::Primordial, }; use rquickjs::{ class::{JsClass, OwnedBorrowMut, Trace}, function::Constructor, methods, prelude::{Opt, This}, Class, Ctx, Error, Exception, Function, JsLifetime, Object, Promise, Result, Symbol, Value, }; use crate::{ queuing_strategy::{SizeAlgorithm, SizeValue}, utils::{ class_from_owned_borrow_mut, promise::{promise_resolved_with, upon_promise, PromisePrimordials}, queue::QueueWithSizes, UnwrapOrUndefined, }, writable::{ default_writer::WritableStreamDefaultWriterOwned, objects::{WritableStreamClassObjects, WritableStreamObjects}, stream::{ sink::UnderlyingSink, WritableStream, WritableStreamClass, WritableStreamOwned, WritableStreamState, }, writer::{UndefinedWriter, WritableStreamWriter}, }, }; #[rquickjs::class] #[derive(JsLifetime, Trace)] pub(crate) struct WritableStreamDefaultController<'js> { abort_algorithm: Option>, close_algorithm: Option>, container: QueueWithSizes<'js>, pub(super) started: bool, strategy_hwm: f64, strategy_size_algorithm: Option>, pub(super) abort_controller: Class<'js, AbortController<'js>>, pub(super) stream: WritableStreamClass<'js>, write_algorithm: Option>, primordials: WritableStreamDefaultControllerPrimordials<'js>, } pub(crate) type WritableStreamDefaultControllerClass<'js> = Class<'js, WritableStreamDefaultController<'js>>; pub(crate) type WritableStreamDefaultControllerOwned<'js> = OwnedBorrowMut<'js, WritableStreamDefaultController<'js>>; impl<'js> WritableStreamDefaultController<'js> { pub(super) fn set_up_writable_stream_default_controller_from_underlying_sink( ctx: Ctx<'js>, stream: WritableStreamOwned<'js>, underlying_sink: Null>>, underlying_sink_dict: UnderlyingSink<'js>, high_water_mark: f64, size_algorithm: SizeAlgorithm<'js>, ) -> Result<()> { let (start_algorithm, write_algorithm, close_algorithm, abort_algorithm) = ( // If underlyingSinkDict["start"] exists, then set startAlgorithm to an algorithm which returns the result of invoking underlyingSinkDict["start"] with argument list // « controller », exception behavior "rethrow", and callback this value underlyingSink. underlying_sink_dict .start .map(|f| StartAlgorithm::Function { f, underlying_sink: underlying_sink.clone(), }) .unwrap_or(StartAlgorithm::ReturnUndefined), // If underlyingSinkDict["write"] exists, then set writeAlgorithm to an algorithm which takes an argument chunk and returns the result of invoking underlyingSinkDict["write"] with argument list // « chunk, controller » and callback this value underlyingSink. underlying_sink_dict .write .map(|f| WriteAlgorithm::Function { f, underlying_sink: underlying_sink.clone(), }) .unwrap_or(WriteAlgorithm::ReturnPromiseUndefined), // If underlyingSinkDict["close"] exists, then set closeAlgorithm to an algorithm which returns the result of invoking underlyingSinkDict["close"] with argument list // «» and callback this value underlyingSink. underlying_sink_dict .close .map(|f| CloseAlgorithm::Function { f, underlying_sink: underlying_sink.clone(), }) .unwrap_or(CloseAlgorithm::ReturnPromiseUndefined), // If underlyingSinkDict["abort"] exists, then set abortAlgorithm to an algorithm which takes an argument reason and returns the result of invoking underlyingSinkDict["abort"] with argument list // « reason » and callback this value underlyingSink. underlying_sink_dict .abort .map(|f| AbortAlgorithm::Function { f, underlying_sink: underlying_sink.clone(), }) .unwrap_or(AbortAlgorithm::ReturnPromiseUndefined), ); // Perform ? SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm). Self::set_up_writable_stream_default_controller( ctx, stream, start_algorithm, write_algorithm, close_algorithm, abort_algorithm, high_water_mark, size_algorithm, ) } #[allow(clippy::too_many_arguments)] fn set_up_writable_stream_default_controller( ctx: Ctx<'js>, stream: WritableStreamOwned<'js>, start_algorithm: StartAlgorithm<'js>, write_algorithm: WriteAlgorithm<'js>, close_algorithm: CloseAlgorithm<'js>, abort_algorithm: AbortAlgorithm<'js>, high_water_mark: f64, size_algorithm: SizeAlgorithm<'js>, ) -> Result<()> { // TODO: needed? let (stream_class, mut stream) = class_from_owned_borrow_mut(stream); let controller = Self { // Set controller.[[stream]] to stream. stream: stream_class, // Perform ! ResetQueue(controller). container: QueueWithSizes::new(), // Set controller.[[abortController]] to a new AbortController. abort_controller: Class::instance(ctx.clone(), AbortController::new(ctx.clone())?)?, // Set controller.[[started]] to false. started: false, // Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm. strategy_size_algorithm: Some(size_algorithm), // Set controller.[[strategyHWM]] to highWaterMark. strategy_hwm: high_water_mark, // Set controller.[[writeAlgorithm]] to writeAlgorithm. write_algorithm: Some(write_algorithm), // Set controller.[[closeAlgorithm]] to closeAlgorithm. close_algorithm: Some(close_algorithm), // Set controller.[[abortAlgorithm]] to abortAlgorithm. abort_algorithm: Some(abort_algorithm), primordials: WritableStreamDefaultControllerPrimordials::get(&ctx)?.clone(), }; let controller_class = Class::instance(ctx.clone(), controller)?; // Set stream.[[controller]] to controller. stream.controller = Some(controller_class.clone()); let objects = WritableStreamObjects::from_stream(stream); // Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). let backpressure = objects .controller .writable_stream_default_controller_get_backpressure(); // Perform ! WritableStreamUpdateBackpressure(stream, backpressure). let objects = WritableStream::writable_stream_update_backpressure( ctx.clone(), objects, backpressure, )?; let promise_primordials = objects.stream.promise_primordials.clone(); // Let startResult be the result of performing startAlgorithm. (This may throw an exception.) let (start_result, objects_class) = Self::start_algorithm(ctx.clone(), objects, start_algorithm)?; // Let startPromise be a promise resolved with startResult. let start_promise = promise_resolved_with(&ctx, &promise_primordials, Ok(start_result))?; let _ = upon_promise::, _>(ctx.clone(), start_promise, { move |ctx, result| { let mut objects = WritableStreamObjects::from_class_no_writer(objects_class).refresh_writer(); match result { // Upon fulfillment of startPromise, Ok(_) => { // Set controller.[[started]] to true. objects.controller.started = true; // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). Self::writable_stream_default_controller_advance_queue_if_needed( ctx, objects, )?; }, // Upon rejection of startPromise with reason r, Err(r) => { // Set controller.[[started]] to true. objects.controller.started = true; // Perform ! WritableStreamDealWithRejection(stream, r). WritableStream::writable_stream_deal_with_rejection(ctx, objects, r)?; }, } Ok(()) } })?; Ok(()) } pub(super) fn writable_stream_default_controller_close>( ctx: Ctx<'js>, mut objects: WritableStreamObjects<'js, W>, ) -> Result> { let close_sentinel = objects .controller .primordials .close_sentinel .as_value() .clone(); // Perform ! EnqueueValueWithSize(controller, close sentinel, 0). objects.controller.container.enqueue_value_with_size( &ctx, close_sentinel, SizeValue::Native(0.0), )?; // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). objects = Self::writable_stream_default_controller_advance_queue_if_needed(ctx, objects)?; Ok(objects) } pub(super) fn writable_stream_default_controller_get_desired_size(&self) -> f64 { self.strategy_hwm - self.container.queue_total_size } pub fn writable_stream_default_controller_get_backpressure(&self) -> bool { // Let desiredSize be ! WritableStreamDefaultControllerGetDesiredSize(controller). let desired_size = self.writable_stream_default_controller_get_desired_size(); // Return true if desiredSize ≤ 0, or false otherwise. desired_size <= 0.0 } pub(super) fn writable_stream_default_controller_get_chunk_size( ctx: Ctx<'js>, mut objects: WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>, chunk: Value<'js>, ) -> Result<( SizeValue<'js>, WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>, )> { let (return_value, objects_class) = Self::strategy_size_algorithm(ctx.clone(), objects, chunk); // Let returnValue be the result of performing controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting the result as a completion record. match return_value { Ok(chunk_size) => { objects = WritableStreamObjects::from_class(objects_class); Ok((chunk_size, objects)) }, // If returnValue is an abrupt completion, Err(Error::Exception) => { let reason = ctx.catch(); objects = WritableStreamObjects::from_class(objects_class); // Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, returnValue.[[Value]]). objects = Self::writable_stream_default_controller_error_if_needed( ctx.clone(), objects, reason, )?; // Return 1. Ok((SizeValue::Native(1.0), objects)) }, Err(err) => Err(err), } } fn writable_stream_default_controller_error_if_needed( ctx: Ctx<'js>, objects: WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>, error: Value<'js>, ) -> Result>> { // If controller.[[stream]].[[state]] is "writable", perform ! WritableStreamDefaultControllerError(controller, error). if let WritableStreamState::Writable = objects.stream.state { Self::writable_stream_default_controller_error(ctx, objects, error) } else { Ok(objects) } } fn writable_stream_default_controller_error>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: WritableStreamObjects<'js, W>, reason: Value<'js>, ) -> Result> { // Perform ! WritableStreamDefaultControllerClearAlgorithms(controller). objects .controller .writable_stream_default_controller_clear_algorithms(); // Perform ! WritableStreamStartErroring(stream, error). objects = WritableStream::writable_stream_start_erroring(ctx, objects, reason)?; Ok(objects) } fn writable_stream_default_controller_clear_algorithms(&mut self) { // Set controller.[[writeAlgorithm]] to undefined. self.write_algorithm = None; // Set controller.[[closeAlgorithm]] to undefined. self.close_algorithm = None; // Set controller.[[abortAlgorithm]] to undefined. self.abort_algorithm = None; // Set controller.[[strategySizeAlgorithm]] to undefined. self.strategy_size_algorithm = None; } pub(super) fn writable_stream_default_controller_write( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>, chunk: Value<'js>, chunk_size: SizeValue<'js>, ) -> Result>> { // Let enqueueResult be EnqueueValueWithSize(controller, chunk, chunkSize). let enqueue_result = objects .controller .container .enqueue_value_with_size(&ctx, chunk, chunk_size); match enqueue_result { // If enqueueResult is an abrupt completion, Err(Error::Exception) => { let reason = ctx.catch(); // Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueResult.[[Value]]). objects = Self::writable_stream_default_controller_error_if_needed(ctx, objects, reason)?; return Ok(objects); }, Err(err) => return Err(err), Ok(()) => {}, } // If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[state]] is "writable", if !objects.stream.writable_stream_close_queued_or_in_flight() && matches!(objects.stream.state, WritableStreamState::Writable) { // Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). let backpressure = objects .controller .writable_stream_default_controller_get_backpressure(); // Perform ! WritableStreamUpdateBackpressure(stream, backpressure). objects = WritableStream::writable_stream_update_backpressure( ctx.clone(), objects, backpressure, )?; } // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). let objects = Self::writable_stream_default_controller_advance_queue_if_needed(ctx, objects)?; Ok(objects) } fn writable_stream_default_controller_advance_queue_if_needed>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. objects: WritableStreamObjects<'js, W>, ) -> Result> { // If controller.[[started]] is false, return. // If stream.[[inFlightWriteRequest]] is not undefined, return. if !objects.controller.started || objects.stream.in_flight_write_request.is_some() { return Ok(objects); } // Let state be stream.[[state]]. // If state is "erroring", if let WritableStreamState::Erroring(ref stored_error) = objects.stream.state { let stored_error = stored_error.clone(); // Perform ! WritableStreamFinishErroring(stream). // Return. return WritableStream::writable_stream_finish_erroring(ctx, objects, stored_error); } let value = match objects.controller.container.queue.front() { // If controller.[[queue]] is empty, return. None => { return Ok(objects); }, // Let value be ! PeekQueueValue(controller). Some(value) => value.clone(), }; if value.value.as_symbol() == Some(&objects.controller.primordials.close_sentinel) { // If value is the close sentinel, perform ! WritableStreamDefaultControllerProcessClose(controller). Self::writable_stream_default_controller_process_close(ctx, objects) } else { // Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(controller, value). Self::writable_stream_default_controller_process_write(ctx, objects, value.value) } } fn writable_stream_default_controller_process_close>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: WritableStreamObjects<'js, W>, ) -> Result> { // Perform ! WritableStreamMarkCloseRequestInFlight(stream). objects .stream .writable_stream_mark_close_request_in_flight(); // Perform ! DequeueValue(controller). objects.controller.container.dequeue_value(); // Assert: controller.[[queue]] is empty. // Let sinkClosePromise be the result of performing controller.[[closeAlgorithm]]. let (sink_close_promise, objects_class) = Self::close_algorithm(&ctx, objects)?; objects = WritableStreamObjects::from_class(objects_class.clone()); // Perform ! WritableStreamDefaultControllerClearAlgorithms(controller). objects .controller .writable_stream_default_controller_clear_algorithms(); upon_promise::, ()>(ctx, sink_close_promise, |ctx, result| { let objects = WritableStreamObjects::from_class(objects_class); match result { // Upon fulfillment of sinkClosePromise, Ok(_) => { // Perform ! WritableStreamFinishInFlightClose(stream). WritableStream::writable_stream_finish_in_flight_close(objects)?; }, // Upon rejection of sinkClosePromise with reason reason, Err(reason) => { // Perform ! WritableStreamFinishInFlightCloseWithError(stream, reason). WritableStream::writable_stream_finish_in_flight_close_with_error( ctx, objects, reason, )?; }, } Ok(()) })?; Ok(objects) } fn writable_stream_default_controller_process_write>( ctx: Ctx<'js>, // Let stream be controller.[[stream]]. mut objects: WritableStreamObjects<'js, W>, chunk: Value<'js>, ) -> Result> { // Perform ! WritableStreamMarkFirstWriteRequestInFlight(stream). objects .stream .writable_stream_mark_first_write_request_in_flight(); // Let sinkWritePromise be the result of performing controller.[[writeAlgorithm]], passing in chunk. let (sink_write_promise, objects_class) = Self::write_algorithm(&ctx, objects, chunk)?; // Upon fulfillment of sinkWritePromise, upon_promise::, ()>(ctx, sink_write_promise, { let objects_class = objects_class.clone(); |ctx, result| { let mut objects = WritableStreamObjects::from_class(objects_class).refresh_writer(); match result { Ok(_) => { // Upon fulfillment of sinkWritePromise, // Perform ! WritableStreamFinishInFlightWrite(stream). objects.stream.writable_stream_finish_in_flight_write()?; // Let state be stream.[[state]]. let state = &objects.stream.state; // Perform ! DequeueValue(controller). objects.controller.container.dequeue_value(); // If ! WritableStreamCloseQueuedOrInFlight(stream) is false and state is "writable", if !objects.stream.writable_stream_close_queued_or_in_flight() && matches!(state, WritableStreamState::Writable) { // Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). let backpressure = objects .controller .writable_stream_default_controller_get_backpressure(); // Perform ! WritableStreamUpdateBackpressure(stream, backpressure). objects = WritableStream::writable_stream_update_backpressure( ctx.clone(), objects, backpressure, )?; } // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). WritableStreamDefaultController::writable_stream_default_controller_advance_queue_if_needed(ctx, objects)?; }, Err(reason) => { // Upon rejection of sinkWritePromise with reason, if let WritableStreamState::Writable = objects.stream.state { // If stream.[[state]] is "writable", perform ! WritableStreamDefaultControllerClearAlgorithms(controller). objects .controller .writable_stream_default_controller_clear_algorithms(); } // Perform ! WritableStreamFinishInFlightWriteWithError(stream, reason). WritableStream::writable_stream_finish_in_flight_write_with_error( ctx, objects, reason, )?; }, } Ok(()) } })?; Ok(WritableStreamObjects::from_class(objects_class)) } pub(super) fn error_steps(&mut self) { // Perform ! ResetQueue(this). self.reset_queue() } fn reset_queue(&mut self) { // Set container.[[queue]] to a new empty list. self.container.queue.clear(); // Set container.[[queueTotalSize]] to 0. self.container.queue_total_size = 0.0; } pub(super) fn abort_steps>( ctx: &Ctx<'js>, mut objects: WritableStreamObjects<'js, W>, reason: Value<'js>, ) -> Result<(Promise<'js>, WritableStreamObjects<'js, W>)> { // Let result be the result of performing this.[[abortAlgorithm]], passing reason. let (result, objects_class) = Self::abort_algorithm(ctx, objects, reason)?; objects = WritableStreamObjects::from_class(objects_class); // Perform ! WritableStreamDefaultControllerClearAlgorithms(this). objects .controller .writable_stream_default_controller_clear_algorithms(); // Return result. Ok((result, objects)) } fn strategy_size_algorithm( ctx: Ctx<'js>, objects: WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>, chunk: Value<'js>, ) -> ( Result>, WritableStreamClassObjects<'js, WritableStreamDefaultWriterOwned<'js>>, ) { let strategy_size_algorithm = objects .controller .strategy_size_algorithm .clone() .unwrap_or(SizeAlgorithm::AlwaysOne); let objects_class = objects.into_inner(); (strategy_size_algorithm.call(ctx, chunk), objects_class) } fn start_algorithm( ctx: Ctx<'js>, objects: WritableStreamObjects<'js, UndefinedWriter>, start_algorithm: StartAlgorithm<'js>, ) -> Result<(Value<'js>, WritableStreamClassObjects<'js, UndefinedWriter>)> { let objects_class = objects.into_inner(); Ok(( start_algorithm.call(ctx, objects_class.controller.clone())?, objects_class, )) } fn write_algorithm>( ctx: &Ctx<'js>, objects: WritableStreamObjects<'js, W>, chunk: Value<'js>, ) -> Result<(Promise<'js>, WritableStreamClassObjects<'js, W>)> { let write_algorithm = objects.controller.write_algorithm.clone().expect( "write algorithm used after WritableStreamDefaultControllerClearAlgorithms", ); let promise_primordials = objects.stream.promise_primordials.clone(); let objects_class = objects.into_inner(); Ok(( write_algorithm.call( ctx, &promise_primordials, objects_class.controller.clone().clone(), chunk, )?, objects_class, )) } fn close_algorithm>( ctx: &Ctx<'js>, objects: WritableStreamObjects<'js, W>, ) -> Result<(Promise<'js>, WritableStreamClassObjects<'js, W>)> { let close_algorithm = objects.controller.close_algorithm.clone().expect( "close algorithm used after WritableStreamDefaultControllerClearAlgorithms", ); let promise_primordials = objects.stream.promise_primordials.clone(); let objects_class = objects.into_inner(); Ok(( close_algorithm.call(ctx, &promise_primordials)?, objects_class, )) } fn abort_algorithm>( ctx: &Ctx<'js>, objects: WritableStreamObjects<'js, W>, reason: Value<'js>, ) -> Result<(Promise<'js>, WritableStreamClassObjects<'js, W>)> { let abort_algorithm = objects.controller.abort_algorithm.clone().expect( "abort algorithm used after WritableStreamDefaultControllerClearAlgorithms", ); let promise_primordials = objects.stream.promise_primordials.clone(); let objects_class = objects.into_inner(); Ok(( abort_algorithm.call(ctx, &promise_primordials, reason)?, objects_class, )) } } #[methods(rename_all = "camelCase")] impl<'js> WritableStreamDefaultController<'js> { // this is required by web platform tests #[qjs(get)] pub fn constructor(ctx: Ctx<'js>) -> Result>> { ::constructor(&ctx) } #[qjs(constructor)] fn new(ctx: Ctx<'js>) -> Result> { Err(Exception::throw_type(&ctx, "Illegal constructor")) } // readonly attribute AbortSignal signal; #[qjs(get)] fn signal(&self) -> Class<'js, AbortSignal<'js>> { // Return this.[[abortController]]'s signal. self.abort_controller.borrow().signal() } // undefined error(optional any e); fn error( ctx: Ctx<'js>, controller: This>, e: Opt>, ) -> Result<()> { let objects = WritableStreamObjects::from_controller(controller.0); // Let state be this.[[stream]].[[state]]. // If state is not "writable", return. if !matches!(objects.stream.state, WritableStreamState::Writable) { return Ok(()); } // Perform ! WritableStreamDefaultControllerError(this, e). Self::writable_stream_default_controller_error( ctx.clone(), objects.refresh_writer(), e.0.unwrap_or_undefined(&ctx), )?; Ok(()) } } #[derive(Clone)] enum StartAlgorithm<'js> { ReturnUndefined, Function { f: Function<'js>, underlying_sink: Null>>, }, } impl<'js> StartAlgorithm<'js> { fn call( &self, ctx: Ctx<'js>, controller: WritableStreamDefaultControllerClass<'js>, ) -> Result> { match self { StartAlgorithm::ReturnUndefined => Ok(Value::new_undefined(ctx.clone())), StartAlgorithm::Function { f, underlying_sink } => { f.call::<_, Value>((This(underlying_sink.clone()), controller)) }, } } } #[derive(JsLifetime, Trace, Clone)] enum WriteAlgorithm<'js> { ReturnPromiseUndefined, Function { f: Function<'js>, underlying_sink: Null>>, }, } impl<'js> WriteAlgorithm<'js> { fn call( &self, ctx: &Ctx<'js>, promise_primordials: &PromisePrimordials<'js>, controller: WritableStreamDefaultControllerClass<'js>, chunk: Value<'js>, ) -> Result> { match self { WriteAlgorithm::ReturnPromiseUndefined => { Ok(promise_primordials.promise_resolved_with_undefined.clone()) }, WriteAlgorithm::Function { f, underlying_sink } => promise_resolved_with( ctx, promise_primordials, f.call::<_, Value>((This(underlying_sink.clone()), chunk, controller)), ), } } } #[derive(JsLifetime, Trace, Clone)] enum CloseAlgorithm<'js> { ReturnPromiseUndefined, Function { f: Function<'js>, underlying_sink: Null>>, }, } impl<'js> CloseAlgorithm<'js> { fn call( &self, ctx: &Ctx<'js>, promise_primordials: &PromisePrimordials<'js>, ) -> Result> { match self { CloseAlgorithm::ReturnPromiseUndefined => { Ok(promise_primordials.promise_resolved_with_undefined.clone()) }, CloseAlgorithm::Function { f, underlying_sink } => promise_resolved_with( ctx, promise_primordials, f.call::<_, Value>((This(underlying_sink.clone()),)), ), } } } #[derive(JsLifetime, Trace, Clone)] enum AbortAlgorithm<'js> { ReturnPromiseUndefined, Function { f: Function<'js>, underlying_sink: Null>>, }, } impl<'js> AbortAlgorithm<'js> { fn call( &self, ctx: &Ctx<'js>, promise_primordials: &PromisePrimordials<'js>, reason: Value<'js>, ) -> Result> { match self { AbortAlgorithm::ReturnPromiseUndefined => { Ok(promise_primordials.promise_resolved_with_undefined.clone()) }, AbortAlgorithm::Function { f, underlying_sink } => promise_resolved_with( ctx, promise_primordials, f.call::<_, Value>((This(underlying_sink.clone()), reason)), ), } } } #[derive(Trace, Clone, JsLifetime)] pub(crate) struct WritableStreamDefaultControllerPrimordials<'js> { close_sentinel: Symbol<'js>, } impl<'js> Primordial<'js> for WritableStreamDefaultControllerPrimordials<'js> { fn new(ctx: &Ctx<'js>) -> Result where Self: Sized, { Ok(Self { close_sentinel: Symbol::for_description(ctx, "close sentinel")?, }) } } ================================================ FILE: modules/llrt_stream_web/src/writable/default_writer.rs ================================================ use llrt_utils::option::Null; use rquickjs::{ class::{JsClass, OwnedBorrow, OwnedBorrowMut, Trace}, function::Constructor, prelude::{Opt, This}, Class, Ctx, Exception, JsLifetime, Promise, Result, Value, }; use crate::{ utils::{ promise::{ promise_rejected_with, promise_rejected_with_constructor, PromisePrimordials, ResolveablePromise, }, UnwrapOrUndefined, }, writable::{ default_controller::WritableStreamDefaultController, objects::WritableStreamObjects, stream::{WritableStream, WritableStreamOwned, WritableStreamState}, writer::WritableStreamWriter, }, }; #[rquickjs::class] #[derive(JsLifetime)] pub(crate) struct WritableStreamDefaultWriter<'js> { pub(crate) ready_promise: ResolveablePromise<'js>, pub(crate) closed_promise: ResolveablePromise<'js>, pub(super) stream: Option>>, constructor_type_error: Constructor<'js>, promise_primordials: PromisePrimordials<'js>, } impl<'js> Trace<'js> for WritableStreamDefaultWriter<'js> { fn trace<'a>(&self, tracer: rquickjs::class::Tracer<'a, 'js>) { self.ready_promise.trace(tracer); self.closed_promise.trace(tracer); self.stream.trace(tracer); self.constructor_type_error.trace(tracer); self.promise_primordials.trace(tracer); } } pub(crate) type WritableStreamDefaultWriterClass<'js> = Class<'js, WritableStreamDefaultWriter<'js>>; pub(crate) type WritableStreamDefaultWriterOwned<'js> = OwnedBorrowMut<'js, WritableStreamDefaultWriter<'js>>; #[rquickjs::methods(rename_all = "camelCase")] impl<'js> WritableStreamDefaultWriter<'js> { // this is required by web platform tests #[qjs(get)] pub fn constructor(ctx: Ctx<'js>) -> Result>> { ::constructor(&ctx) } #[qjs(constructor)] fn new(ctx: Ctx<'js>, stream: WritableStreamOwned<'js>) -> Result> { // Perform ? SetUpWritableStreamDefaultWriter(this, stream). let (_, writer) = Self::set_up_writable_stream_default_writer(&ctx, stream)?; Ok(writer) } #[qjs(get)] fn closed(writer: This>) -> Promise<'js> { // Return this.[[closedPromise]]. writer.0.closed_promise.promise.clone() } #[qjs(get)] fn desired_size(ctx: Ctx<'js>, writer: This>) -> Result> { match writer.0.stream { // If this.[[stream]] is undefined, throw a TypeError exception. None => Err(Exception::throw_type( &ctx, "Cannot desiredSize a stream using a released writer", )), Some(ref stream) => { // Return ! WritableStreamDefaultWriterGetDesiredSize(this). Self::writable_stream_default_writer_get_desired_size(&OwnedBorrowMut::from_class( stream.clone(), )) }, } } #[qjs(get)] fn ready(writer: This>) -> Promise<'js> { // Return this.[[readyPromise]]. writer.0.ready_promise.promise.clone() } fn abort( ctx: Ctx<'js>, writer: This>, reason: Opt>, ) -> Result> { // If this.[[stream]] is undefined, throw a TypeError exception. if writer.0.stream.is_none() { promise_rejected_with_constructor( &writer.constructor_type_error, &writer.promise_primordials, "Cannot abort a stream using a released writer", ) } else { let objects = WritableStreamObjects::from_writer(writer.0); // Return ! WritableStreamDefaultWriterAbort(this, reason). Self::writable_stream_default_writer_abort(ctx.clone(), objects, reason.0) } } fn close(ctx: Ctx<'js>, writer: This>) -> Result> { // If this.[[stream]] is undefined, throw a TypeError exception. if writer.0.stream.is_none() { promise_rejected_with_constructor( &writer.constructor_type_error, &writer.promise_primordials, "Cannot close a stream using a released writer", ) } else { let objects = WritableStreamObjects::from_writer(writer.0); // If ! WritableStreamCloseQueuedOrInFlight(stream) is true, return a promise rejected with a TypeError exception. if objects.stream.writable_stream_close_queued_or_in_flight() { return promise_rejected_with_constructor( &objects.writer.constructor_type_error, &objects.writer.promise_primordials, "Cannot close an already-closing", ); } // Return ! WritableStreamDefaultWriterClose(this). Self::writable_stream_default_writer_close(ctx, objects) } } fn release_lock(writer: This>) -> Result<()> { // If stream is undefined, return. if writer.0.stream.is_none() { Ok(()) } else { let objects = WritableStreamObjects::from_writer(writer.0); // Perform ! WritableStreamDefaultWriterRelease(this). Self::writable_stream_default_writer_release(objects) } } fn write( ctx: Ctx<'js>, writer: This>, chunk: Opt>, ) -> Result> { // If this.[[stream]] is undefined, throw a TypeError exception. if writer.0.stream.is_none() { promise_rejected_with_constructor( &writer.constructor_type_error, &writer.promise_primordials, "Cannot write a stream using a released writer", ) } else { let objects = WritableStreamObjects::from_writer(writer.0); // Return ! WritableStreamDefaultWriterWrite(this, chunk). Self::writable_stream_default_writer_write( ctx.clone(), objects, chunk.0.unwrap_or_undefined(&ctx), ) } } } impl<'js> WritableStreamDefaultWriter<'js> { pub(crate) fn acquire_writable_stream_default_writer( ctx: &Ctx<'js>, stream: WritableStreamOwned<'js>, ) -> Result<(WritableStreamOwned<'js>, Class<'js, Self>)> { Self::set_up_writable_stream_default_writer(ctx, stream) } pub(super) fn set_up_writable_stream_default_writer( ctx: &Ctx<'js>, mut stream: WritableStreamOwned<'js>, ) -> Result<(WritableStreamOwned<'js>, Class<'js, Self>)> { // If ! IsWritableStreamLocked(stream) is true, throw a TypeError exception. if stream.is_writable_stream_locked() { return Err(Exception::throw_type( ctx, "This stream has already been locked for exclusive writing by another writer", )); } let promise_primordials = stream.promise_primordials.clone(); let constructor_type_error = stream.constructor_type_error.clone(); let stream_class = stream.into_inner(); stream = OwnedBorrowMut::from_class(stream_class.clone()); let (ready_promise, closed_promise) = match stream.state { WritableStreamState::Writable => { let ready_promise = if !stream.writable_stream_close_queued_or_in_flight() && stream.backpressure { // If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[backpressure]] is true, set writer.[[readyPromise]] to a new promise. ResolveablePromise::new(ctx)? } else { // Otherwise, set writer.[[readyPromise]] to a promise resolved with undefined. ResolveablePromise::resolved_with_undefined(&stream.promise_primordials) }; // Set writer.[[closedPromise]] to a new promise. (ready_promise, ResolveablePromise::new(ctx)?) }, WritableStreamState::Erroring(ref stored_error) => { let ready_promise = ResolveablePromise::rejected_with( &stream.promise_primordials, stored_error.clone(), )?; ready_promise.set_is_handled()?; // Set writer.[[closedPromise]] to a new promise. (ready_promise, ResolveablePromise::new(ctx)?) }, WritableStreamState::Closed => { let promise = ResolveablePromise::resolved_with_undefined(&stream.promise_primordials); // Set writer.[[readyPromise]] to a promise resolved with undefined. // Set writer.[[closedPromise]] to a promise resolved with undefined. (promise.clone(), promise) }, // Let storedError be stream.[[storedError]]. WritableStreamState::Errored(ref stored_error) => { let promise = ResolveablePromise::rejected_with( &stream.promise_primordials, stored_error.clone(), )?; promise.set_is_handled()?; // Set writer.[[readyPromise]] to a promise rejected with storedError. // Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. // Set writer.[[closedPromise]] to a promise rejected with storedError. // Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. (promise.clone(), promise) }, }; let writer = Self { ready_promise, closed_promise, // Set writer.[[stream]] to stream. stream: Some(stream_class), promise_primordials, constructor_type_error, }; let writer = Class::instance(ctx.clone(), writer)?; stream.writer = Some(writer.clone()); Ok((stream, writer)) } pub(super) fn writable_stream_default_writer_ensure_ready_promise_rejected( &mut self, promise_primordials: &PromisePrimordials<'js>, error: Value<'js>, ) -> Result<()> { if self.ready_promise.is_pending() { // If writer.[[readyPromise]].[[PromiseState]] is "pending", reject writer.[[readyPromise]] with error. self.ready_promise.reject(error)?; } else { // Otherwise, set writer.[[readyPromise]] to a promise rejected with error. self.ready_promise = ResolveablePromise::rejected_with(promise_primordials, error)?; } // Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. self.ready_promise.set_is_handled()?; Ok(()) } pub(super) fn writable_stream_default_writer_ensure_closed_promise_rejected( &mut self, promise_primordials: &PromisePrimordials<'js>, error: Value<'js>, ) -> Result<()> { if self.closed_promise.is_pending() { // If writer.[[closedPromise]].[[PromiseState]] is "pending", reject writer.[[closedPromise]] with error. self.closed_promise.reject(error)?; } else { // Otherwise, set writer.[[closedPromise]] to a promise rejected with error. self.closed_promise = ResolveablePromise::rejected_with(promise_primordials, error)?; } // Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. self.closed_promise.set_is_handled()?; Ok(()) } pub(super) fn writable_stream_default_writer_get_desired_size( // Let stream be writer.[[stream]]. stream: &WritableStream<'js>, ) -> Result> { // Let state be stream.[[state]]. // If state is "errored" or "erroring", return null. if matches!( stream.state, WritableStreamState::Errored(_) | WritableStreamState::Erroring(_) ) { return Ok(Null(None)); } // If state is "closed", return 0. if matches!(stream.state, WritableStreamState::Closed) { return Ok(Null(Some(0.0))); } // Return ! WritableStreamDefaultControllerGetDesiredSize(stream.[[controller]]). let controller = OwnedBorrow::from_class( stream .controller .clone() .expect("Stream in state writable must have a controller"), ); Ok(Null(Some( controller.writable_stream_default_controller_get_desired_size(), ))) } fn writable_stream_default_writer_abort( ctx: Ctx<'js>, objects: WritableStreamObjects<'js, OwnedBorrowMut<'js, Self>>, reason: Option>, ) -> Result> { // Return ! WritableStreamAbort(stream, reason). let (promise, _) = WritableStream::writable_stream_abort(ctx, objects, reason)?; Ok(promise) } fn writable_stream_default_writer_close( ctx: Ctx<'js>, objects: WritableStreamObjects<'js, OwnedBorrowMut<'js, Self>>, ) -> Result> { // Return ! WritableStreamClose(stream). let (promise, _) = WritableStream::writable_stream_close(ctx, objects)?; Ok(promise) } pub(crate) fn writable_stream_default_writer_close_with_error_propagation( ctx: Ctx<'js>, // Let stream be writer.[[stream]]. objects: WritableStreamObjects<'js, OwnedBorrowMut<'js, Self>>, ) -> Result> { // Let state be stream.[[state]]. // If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state is "closed", return a promise resolved with undefined. if objects.stream.writable_stream_close_queued_or_in_flight() || matches!(objects.stream.state, WritableStreamState::Closed) { return Ok(objects .stream .promise_primordials .promise_resolved_with_undefined .clone()); } // If state is "errored", return a promise rejected with stream.[[storedError]]. if let WritableStreamState::Errored(ref stored_error) = objects.stream.state { return promise_rejected_with( &objects.stream.promise_primordials, stored_error.clone(), ); } // Return ! WritableStreamDefaultWriterClose(writer). Self::writable_stream_default_writer_close(ctx, objects) } pub(crate) fn writable_stream_default_writer_release( mut objects: WritableStreamObjects<'js, OwnedBorrowMut<'js, Self>>, ) -> Result<()> { // Let releasedError be a new TypeError. let released_error: Value = objects.stream.constructor_type_error.call(( "Writer was released and can no longer be used to monitor the stream's closedness", ))?; // Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedError). objects .writer .writable_stream_default_writer_ensure_ready_promise_rejected( &objects.stream.promise_primordials, released_error.clone(), )?; // Perform ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer, releasedError). objects .writer .writable_stream_default_writer_ensure_closed_promise_rejected( &objects.stream.promise_primordials, released_error, )?; // Set stream.[[writer]] to undefined. objects.stream.writer = None; // Set writer.[[stream]] to undefined. objects.writer.stream = None; Ok(()) } pub(crate) fn writable_stream_default_writer_write( ctx: Ctx<'js>, objects: WritableStreamObjects<'js, OwnedBorrowMut<'js, Self>>, chunk: Value<'js>, ) -> Result> { // Let chunkSize be ! WritableStreamDefaultControllerGetChunkSize(controller, chunk). let (chunk_size, mut objects) = WritableStreamDefaultController::writable_stream_default_controller_get_chunk_size( ctx.clone(), objects, chunk.clone(), )?; let stream_class = objects.stream.into_inner(); objects.stream = OwnedBorrowMut::from_class(stream_class.clone()); // If stream is not equal to writer.[[stream]], return a promise rejected with a TypeError exception. if objects.writer.stream != Some(stream_class) { return promise_rejected_with_constructor( &objects.stream.constructor_type_error, &objects.stream.promise_primordials, "Cannot write to a stream using a released writer", ); } // Let state be stream.[[state]]. // If state is "errored", return a promise rejected with stream.[[storedError]]. if let WritableStreamState::Errored(ref stored_error) = objects.stream.state { return promise_rejected_with( &objects.stream.promise_primordials, stored_error.clone(), ); } // If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state is "closed", return a promise rejected with a TypeError exception indicating that the stream is closing or closed. if objects.stream.writable_stream_close_queued_or_in_flight() || matches!(objects.stream.state, WritableStreamState::Closed) { return promise_rejected_with_constructor( &objects.stream.constructor_type_error, &objects.stream.promise_primordials, "The stream is closing or closed and cannot be written to", ); } // If state is "erroring", return a promise rejected with stream.[[storedError]]. if let WritableStreamState::Erroring(ref stored_error) = objects.stream.state { return promise_rejected_with( &objects.stream.promise_primordials, stored_error.clone(), ); } // Let promise be ! WritableStreamAddWriteRequest(stream). let promise = objects.stream.writable_stream_add_write_request(&ctx); // Perform ! WritableStreamDefaultControllerWrite(controller, chunk, chunkSize). WritableStreamDefaultController::writable_stream_default_controller_write( ctx, objects, chunk, chunk_size, )?; // Return promise. promise } } impl<'js> WritableStreamWriter<'js> for WritableStreamDefaultWriterOwned<'js> { type Class = WritableStreamDefaultWriterClass<'js>; fn with_writer( self, ctx: C, default: impl FnOnce( C, WritableStreamDefaultWriterOwned<'js>, ) -> Result<(C, WritableStreamDefaultWriterOwned<'js>)>, _: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { default(ctx, self) } fn into_inner(self) -> Self::Class { self.into_inner() } fn from_class(class: Self::Class) -> Self { OwnedBorrowMut::from_class(class) } } ================================================ FILE: modules/llrt_stream_web/src/writable/mod.rs ================================================ mod default_controller; mod default_writer; mod objects; mod stream; mod writer; pub(crate) use default_controller::{ WritableStreamDefaultController, WritableStreamDefaultControllerPrimordials, }; pub(crate) use default_writer::{WritableStreamDefaultWriter, WritableStreamDefaultWriterOwned}; pub(crate) use objects::{WritableStreamClassObjects, WritableStreamObjects}; pub(crate) use stream::{ WritableStream, WritableStreamClass, WritableStreamOwned, WritableStreamState, }; ================================================ FILE: modules/llrt_stream_web/src/writable/objects.rs ================================================ use rquickjs::{class::OwnedBorrowMut, Class, Result}; use crate::writable::{ default_controller::{ WritableStreamDefaultControllerClass, WritableStreamDefaultControllerOwned, }, default_writer::WritableStreamDefaultWriterOwned, stream::{WritableStream, WritableStreamOwned}, writer::{UndefinedWriter, WritableStreamWriter}, }; pub(crate) struct WritableStreamObjects<'js, W> { pub(crate) stream: WritableStreamOwned<'js>, pub(crate) controller: WritableStreamDefaultControllerOwned<'js>, pub(crate) writer: W, } pub(crate) struct WritableStreamClassObjects<'js, W: WritableStreamWriter<'js>> { pub(crate) stream: Class<'js, WritableStream<'js>>, pub(crate) controller: WritableStreamDefaultControllerClass<'js>, pub(crate) writer: W::Class, } impl<'js, W: WritableStreamWriter<'js>> Clone for WritableStreamClassObjects<'js, W> { fn clone(&self) -> Self { Self { stream: self.stream.clone(), controller: self.controller.clone(), writer: self.writer.clone(), } } } impl<'js, W: WritableStreamWriter<'js>> WritableStreamObjects<'js, W> { pub(super) fn into_inner(self) -> WritableStreamClassObjects<'js, W> { WritableStreamClassObjects { stream: self.stream.into_inner(), controller: self.controller.into_inner(), writer: self.writer.into_inner(), } } pub(crate) fn from_class(objects_class: WritableStreamClassObjects<'js, W>) -> Self { Self { stream: OwnedBorrowMut::from_class(objects_class.stream), controller: OwnedBorrowMut::from_class(objects_class.controller), writer: W::from_class(objects_class.writer), } } pub(super) fn from_class_no_writer( objects_class: WritableStreamClassObjects<'js, W>, ) -> WritableStreamObjects<'js, UndefinedWriter> { WritableStreamObjects { stream: OwnedBorrowMut::from_class(objects_class.stream), controller: OwnedBorrowMut::from_class(objects_class.controller), writer: UndefinedWriter, } } pub(super) fn with_writer( mut self, default: impl FnOnce( WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>, ) -> Result< WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>, >, none: impl FnOnce( WritableStreamObjects<'js, UndefinedWriter>, ) -> Result>, ) -> Result { ((self.stream, self.controller), self.writer) = self.writer.with_writer( (self.stream, self.controller), |(stream, controller), writer| { let objects = default(WritableStreamObjects { stream, controller, writer, })?; Ok(((objects.stream, objects.controller), objects.writer)) }, |(stream, controller)| { let objects = none(WritableStreamObjects { stream, controller, writer: UndefinedWriter, })?; Ok((objects.stream, objects.controller)) }, )?; Ok(self) } } impl<'js, W: WritableStreamWriter<'js>> WritableStreamObjects<'js, W> { pub(super) fn refresh_writer( mut self, ) -> WritableStreamObjects<'js, Option>> { drop(self.writer); let writer = self.stream.writer_mut(); WritableStreamObjects { stream: self.stream, controller: self.controller, writer, } } } impl<'js> WritableStreamObjects<'js, UndefinedWriter> { pub(super) fn from_stream(stream: WritableStreamOwned<'js>) -> Self { let controller = OwnedBorrowMut::from_class( stream .controller .clone() .expect("WritableStream must have controller"), ); WritableStreamObjects { stream, controller, writer: UndefinedWriter, } } pub(super) fn from_controller(controller: WritableStreamDefaultControllerOwned<'js>) -> Self { let stream = OwnedBorrowMut::from_class(controller.stream.clone()); WritableStreamObjects { stream, controller, writer: UndefinedWriter, } } } impl<'js> WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>> { pub(super) fn from_writer(writer: WritableStreamDefaultWriterOwned<'js>) -> Self { let stream = OwnedBorrowMut::from_class( writer .stream .clone() .expect("WritableStreamDefaultWriter must have a stream"), ); let controller = OwnedBorrowMut::from_class( stream .controller .clone() .expect("WritableStreamDefaultWriter stream must have a controller"), ); WritableStreamObjects { stream, controller, writer, } } } ================================================ FILE: modules/llrt_stream_web/src/writable/stream/mod.rs ================================================ use std::collections::VecDeque; use llrt_abort::AbortController; use llrt_utils::{ option::{Null, Undefined}, primordials::{BasePrimordials, Primordial}, }; use rquickjs::{ class::{OwnedBorrowMut, Trace, Tracer}, function::Constructor, prelude::{Opt, This}, Class, Ctx, Exception, JsLifetime, Object, Promise, Result, Value, }; use crate::{ queuing_strategy::QueuingStrategy, utils::{ promise::{ promise_rejected_with_constructor, upon_promise, PromisePrimordials, ResolveablePromise, }, UnwrapOrUndefined, }, writable::{ default_controller::{ WritableStreamDefaultController, WritableStreamDefaultControllerClass, }, default_writer::{ WritableStreamDefaultWriter, WritableStreamDefaultWriterClass, WritableStreamDefaultWriterOwned, }, objects::WritableStreamObjects, writer::WritableStreamWriter, }, }; use sink::UnderlyingSink; pub(super) mod sink; #[rquickjs::class] #[derive(JsLifetime)] pub struct WritableStream<'js> { pub(super) backpressure: bool, close_request: Option>, pub(crate) controller: Option>, pub in_flight_write_request: Option>, in_flight_close_request: Option>, pending_abort_request: Option>, pub(crate) state: WritableStreamState<'js>, pub(crate) writer: Option>, write_requests: VecDeque>, pub(super) constructor_type_error: Constructor<'js>, pub(crate) promise_primordials: PromisePrimordials<'js>, } impl<'js> Trace<'js> for WritableStream<'js> { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) { self.close_request.trace(tracer); self.controller.trace(tracer); self.in_flight_write_request.trace(tracer); self.in_flight_close_request.trace(tracer); self.pending_abort_request.trace(tracer); self.state.trace(tracer); self.writer.trace(tracer); self.write_requests.trace(tracer); self.constructor_type_error.trace(tracer); self.promise_primordials.trace(tracer); } } pub(crate) type WritableStreamClass<'js> = Class<'js, WritableStream<'js>>; pub(crate) type WritableStreamOwned<'js> = OwnedBorrowMut<'js, WritableStream<'js>>; #[rquickjs::methods(rename_all = "camelCase")] impl<'js> WritableStream<'js> { // constructor(optional object underlyingSink, optional QueuingStrategy strategy = {}); #[qjs(constructor)] fn new( ctx: Ctx<'js>, underlying_sink: Opt>>, queuing_strategy: Opt>>, ) -> Result> { // If underlyingSink is missing, set it to null. let underlying_sink = Null(underlying_sink.0); // Let underlyingSinkDict be underlyingSink, converted to an IDL value of type UnderlyingSink. let underlying_sink_dict = match underlying_sink { Null(None) | Null(Some(Undefined(None))) => UnderlyingSink::default(), Null(Some(Undefined(Some(ref obj)))) => UnderlyingSink::from_object(obj.clone())?, }; // If underlyingSinkDict["type"] exists, throw a RangeError exception. if underlying_sink_dict.r#type.is_some() { return Err(Exception::throw_range(&ctx, "Invalid type is specified")); } // Perform ! InitializeWritableStream(this). let stream_class = Class::instance( ctx.clone(), Self { // Set stream.[[state]] to "writable". state: WritableStreamState::Writable, // Set stream.[[storedError]], stream.[[writer]], stream.[[controller]], stream.[[inFlightWriteRequest]], stream.[[closeRequest]], stream.[[inFlightCloseRequest]], and stream.[[pendingAbortRequest]] to undefined. writer: None, controller: None, in_flight_write_request: None, close_request: None, in_flight_close_request: None, pending_abort_request: None, // Set stream.[[writeRequests]] to a new empty list. write_requests: VecDeque::new(), // Set stream.[[backpressure]] to false. backpressure: false, constructor_type_error: BasePrimordials::get(&ctx)?.constructor_type_error.clone(), promise_primordials: PromisePrimordials::get(&ctx)?.clone(), }, )?; let stream = OwnedBorrowMut::from_class(stream_class.clone()); let queuing_strategy = queuing_strategy.0.and_then(|qs| qs.0); // Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy). let size_algorithm = QueuingStrategy::extract_size_algorithm(queuing_strategy.as_ref()); // Let highWaterMark be ? ExtractHighWaterMark(strategy, 1). let high_water_mark = QueuingStrategy::extract_high_water_mark(&ctx, queuing_strategy, 1.0)?; // Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm). WritableStreamDefaultController::set_up_writable_stream_default_controller_from_underlying_sink(ctx, stream, underlying_sink, underlying_sink_dict, high_water_mark, size_algorithm)?; Ok(stream_class) } // readonly attribute boolean locked; #[qjs(get)] fn locked(&self) -> bool { // Return ! IsWritableStreamLocked(this). self.is_writable_stream_locked() } fn abort( ctx: Ctx<'js>, stream: This>, reason: Opt>, ) -> Result> { if stream.is_writable_stream_locked() { // If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception. return promise_rejected_with_constructor( &stream.constructor_type_error, &stream.promise_primordials, "Cannot abort a stream that already has a writer", ); } let objects = WritableStreamObjects::from_stream(stream.0); // Return ! WritableStreamAbort(this, reason). let (promise, _) = Self::writable_stream_abort(ctx.clone(), objects, reason.0)?; Ok(promise) } fn close(ctx: Ctx<'js>, stream: This>) -> Result> { if stream.is_writable_stream_locked() { // If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception. return promise_rejected_with_constructor( &stream.constructor_type_error, &stream.promise_primordials, "Cannot close a stream that already has a writer", ); } if Self::writable_stream_close_queued_or_in_flight(&stream.0) { // If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a promise rejected with a TypeError exception. return promise_rejected_with_constructor( &stream.constructor_type_error, &stream.promise_primordials, "Cannot close an already-closing stream", ); } let objects = WritableStreamObjects::from_stream(stream.0); // Return ! WritableStreamClose(this). let (promise, _) = Self::writable_stream_close(ctx.clone(), objects)?; Ok(promise) } fn get_writer( ctx: Ctx<'js>, stream: This>, ) -> Result> { // Return ? AcquireWritableStreamDefaultWriter(this). let (_, writer) = WritableStreamDefaultWriter::acquire_writable_stream_default_writer(&ctx, stream.0)?; Ok(writer) } } impl<'js> WritableStream<'js> { pub(crate) fn is_writable_stream_locked(&self) -> bool { if self.writer.is_none() { // If stream.[[writer]] is undefined, return false. false } else { // Return true. true } } pub(crate) fn writable_stream_abort>( ctx: Ctx<'js>, mut objects: WritableStreamObjects<'js, W>, mut reason: Option>, ) -> Result<(Promise<'js>, WritableStreamObjects<'js, W>)> { // If stream.[[state]] is "closed" or "errored", return a promise resolved with undefined. if matches!( objects.stream.state, WritableStreamState::Closed | WritableStreamState::Errored(_) ) { return Ok(( objects .stream .promise_primordials .promise_resolved_with_undefined .clone(), objects, )); } // Signal abort on stream.[[controller]].[[abortController]] with reason. { // this executes user code, so we should ensure we hold no locks let abort_controller = objects.controller.abort_controller.clone(); let objects_class = objects.into_inner(); AbortController::abort(ctx.clone(), This(abort_controller), Opt(reason.clone()))?; objects = WritableStreamObjects::from_class(objects_class); } // Let state be stream.[[state]]. // If state is "closed" or "errored", return a promise resolved with undefined. if matches!( objects.stream.state, WritableStreamState::Closed | WritableStreamState::Errored(_) ) { return Ok(( objects .stream .promise_primordials .promise_resolved_with_undefined .clone(), objects, )); } // If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]]'s promise. match objects.stream.pending_abort_request { None => {}, Some(ref pending_abort_request) => { return Ok((pending_abort_request.promise.promise.clone(), objects)) }, } let was_already_erroring = match objects.stream.state { // If state is "erroring", // Set wasAlreadyErroring to true. // Set reason to undefined. WritableStreamState::Erroring(_) => { reason = None; true }, // Let wasAlreadyErroring be false. _ => false, }; // Let promise be a new promise. let promise = ResolveablePromise::new(&ctx)?; let reason = reason.unwrap_or_undefined(&ctx); // Set stream.[[pendingAbortRequest]] to a new pending abort request whose promise is promise, reason is reason, and was already erroring is wasAlreadyErroring. objects.stream.pending_abort_request = Some(PendingAbortRequest { promise: promise.clone(), reason: reason.clone(), was_already_erroring, }); // If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason). if !was_already_erroring { objects = Self::writable_stream_start_erroring(ctx, objects, reason)?; } Ok((promise.promise.clone(), objects)) } pub(super) fn writable_stream_close>( ctx: Ctx<'js>, mut objects: WritableStreamObjects<'js, W>, ) -> Result<(Promise<'js>, WritableStreamObjects<'js, W>)> { // Let state be stream.[[state]]. // If state is "closed" or "errored", return a promise rejected with a TypeError exception. if matches!( objects.stream.state, WritableStreamState::Closed | WritableStreamState::Errored(_) ) { return Ok(( promise_rejected_with_constructor::( &objects.stream.constructor_type_error, &objects.stream.promise_primordials, "The stream is not in the writable state and cannot be closed", )?, objects, )); } // Let promise be a new promise. let promise = ResolveablePromise::new(&ctx)?; // Set stream.[[closeRequest]] to promise. objects.stream.close_request = Some(promise.clone()); // Let writer be stream.[[writer]]. // If writer is not undefined, and stream.[[backpressure]] is true, and state is "writable", resolve writer.[[readyPromise]] with undefined. objects = objects.with_writer( |objects| { if objects.stream.backpressure && matches!(objects.stream.state, WritableStreamState::Writable) { let () = objects.writer.ready_promise.resolve_undefined()?; } Ok(objects) }, Ok, )?; // Perform ! WritableStreamDefaultControllerClose(stream.[[controller]]). objects = WritableStreamDefaultController::writable_stream_default_controller_close( ctx, objects, )?; // Return promise. Ok((promise.promise.clone(), objects)) } pub(super) fn writable_stream_start_erroring>( ctx: Ctx<'js>, // Let controller be stream.[[controller]]. // Let writer be stream.[[writer]]. mut objects: WritableStreamObjects<'js, W>, reason: Value<'js>, ) -> Result> { // Set stream.[[state]] to "erroring". // Set stream.[[storedError]] to reason. objects.stream.state = WritableStreamState::Erroring(reason.clone()); // If writer is not undefined, perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason). objects = objects.with_writer( |mut objects| { objects .writer .writable_stream_default_writer_ensure_ready_promise_rejected( &objects.stream.promise_primordials, reason.clone(), )?; Ok(objects) }, Ok, )?; // If ! WritableStreamHasOperationMarkedInFlight(stream) is false and controller.[[started]] is true, perform ! WritableStreamFinishErroring(stream). if !objects .stream .writable_stream_has_operation_marked_in_flight() && objects.controller.started { objects = Self::writable_stream_finish_erroring(ctx, objects, reason)?; } Ok(objects) } pub(super) fn writable_stream_finish_erroring>( ctx: Ctx<'js>, mut objects: WritableStreamObjects<'js, W>, // Let storedError be stream.[[storedError]]. stored_error: Value<'js>, ) -> Result> { // Set stream.[[state]] to "errored". objects.stream.state = WritableStreamState::Errored(stored_error.clone()); // Perform ! stream.[[controller]].[[ErrorSteps]](). objects.controller.error_steps(); // For each writeRequest of stream.[[writeRequests]]: for write_request in &mut objects.stream.write_requests { let () = write_request.reject(stored_error.clone())?; } // Set stream.[[writeRequests]] to an empty list. objects.stream.write_requests.clear(); // Let abortRequest be stream.[[pendingAbortRequest]]. // Set stream.[[pendingAbortRequest]] to undefined. let abort_request = if let Some(pending_abort_request) = objects.stream.pending_abort_request.take() { pending_abort_request } else { // If stream.[[pendingAbortRequest]] is undefined, // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). objects = WritableStream::writable_stream_reject_close_and_closed_promise_if_needed(objects)?; // Return. return Ok(objects); }; // If abortRequest’s was already erroring is true, if abort_request.was_already_erroring { // Reject abortRequest’s promise with storedError. let () = abort_request.promise.reject(stored_error.clone())?; // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). objects = WritableStream::writable_stream_reject_close_and_closed_promise_if_needed(objects)?; // Return. return Ok(objects); } // Let promise be ! stream.[[controller]].[[AbortSteps]](abortRequest’s reason). let (promise, objects) = WritableStreamDefaultController::abort_steps(&ctx, objects, abort_request.reason)?; let objects_class = objects.into_inner(); // Upon fulfillment of promise, let _ = upon_promise::, _>(ctx.clone(), promise, { let objects_class = objects_class.clone(); move |_, result| { let objects = WritableStreamObjects::from_class_no_writer(objects_class).refresh_writer(); match result { // Upon fulfillment of promise, Ok(_) => { // Resolve abortRequest’s promise with undefined. let () = abort_request.promise.resolve_undefined()?; // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). WritableStream::writable_stream_reject_close_and_closed_promise_if_needed( objects, )?; Ok(()) }, // Upon rejection of promise with reason reason, Err(reason) => { // Reject abortRequest’s promise with reason. let () = abort_request.promise.reject(reason)?; // Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). WritableStream::writable_stream_reject_close_and_closed_promise_if_needed( objects, )?; Ok(()) }, } } })?; Ok(WritableStreamObjects::from_class(objects_class)) } fn writable_stream_reject_close_and_closed_promise_if_needed>( // Let writer be stream.[[writer]]. mut objects: WritableStreamObjects<'js, W>, ) -> Result> { // If stream.[[closeRequest]] is not undefined, if let Some(ref close_request) = objects.stream.close_request { // Reject stream.[[closeRequest]] with stream.[[storedError]]. let () = close_request.reject(objects.stream.stored_error())?; // Set stream.[[closeRequest]] to undefined. objects.stream.close_request = None; } // If writer is not undefined, objects.with_writer( |objects| { // Reject writer.[[closedPromise]] with stream.[[storedError]]. let () = objects .writer .closed_promise .reject(objects.stream.stored_error())?; // Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. objects.writer.closed_promise.set_is_handled()?; Ok(objects) }, Ok, ) } pub(super) fn writable_stream_mark_first_write_request_in_flight(&mut self) { // Let writeRequest be stream.[[writeRequests]][0]. // Remove writeRequest from stream.[[writeRequests]]. let write_request = self.write_requests.pop_front().expect("writable_stream_mark_first_write_request_in_flight must be called with non-empty write requests"); // Set stream.[[inFlightWriteRequest]] to writeRequest. self.in_flight_write_request = Some(write_request); } pub(super) fn writable_stream_mark_close_request_in_flight(&mut self) { // Set stream.[[inFlightCloseRequest]] to stream.[[closeRequest]]. // Set stream.[[closeRequest]] to undefined. self.in_flight_close_request = Some(self.close_request.take().expect( "writable_stream_mark_close_request_in_flight called without close request", )) } fn writable_stream_has_operation_marked_in_flight(&self) -> bool { if self.in_flight_write_request.is_none() && self.in_flight_close_request.is_none() { // If stream.[[inFlightWriteRequest]] is undefined and stream.[[inFlightCloseRequest]] is undefined, return false. false } else { // Return true. true } } pub(crate) fn writable_stream_close_queued_or_in_flight(&self) -> bool { if self.close_request.is_none() && self.in_flight_close_request.is_none() { // If stream.[[closeRequest]] is undefined and stream.[[inFlightCloseRequest]] is undefined, return false. false } else { // Return true. true } } pub(super) fn writable_stream_add_write_request( &mut self, ctx: &Ctx<'js>, ) -> Result> { // Let promise be a new promise. let promise = ResolveablePromise::new(ctx)?; // Append promise to stream.[[writeRequests]]. self.write_requests.push_back(promise.clone()); Ok(promise.promise.clone()) } pub(super) fn writable_stream_finish_in_flight_write_with_error< W: WritableStreamWriter<'js>, >( ctx: Ctx<'js>, mut objects: WritableStreamObjects<'js, W>, error: Value<'js>, ) -> Result<()> { // Reject stream.[[inFlightWriteRequest]] with error. // Set stream.[[inFlightWriteRequest]] to undefined. objects.stream.in_flight_write_request.take().expect("writable_stream_finish_in_flight_write_with_error called without in flight write request").reject(error.clone())?; // Perform ! WritableStreamDealWithRejection(stream, error). Self::writable_stream_deal_with_rejection(ctx, objects, error)?; Ok(()) } pub(super) fn writable_stream_finish_in_flight_close_with_error< W: WritableStreamWriter<'js>, >( ctx: Ctx<'js>, mut objects: WritableStreamObjects<'js, W>, error: Value<'js>, ) -> Result<()> { // Reject stream.[[inFlightCloseRequest]] with error. // Set stream.[[inFlightCloseRequest]] to undefined. objects.stream.in_flight_close_request.take().expect("writable_stream_finish_in_flight_close_with_error called without in flight close request").reject(error.clone())?; // Assert: stream.[[state]] is "writable" or "erroring". // If stream.[[pendingAbortRequest]] is not undefined, if let Some(pending_abort_request) = objects.stream.pending_abort_request.take() { // Reject stream.[[pendingAbortRequest]]'s promise with error. // Set stream.[[pendingAbortRequest]] to undefined. pending_abort_request.promise.reject(error.clone())? } // Perform ! WritableStreamDealWithRejection(stream, error). Self::writable_stream_deal_with_rejection(ctx, objects, error)?; Ok(()) } pub(super) fn writable_stream_deal_with_rejection>( ctx: Ctx<'js>, objects: WritableStreamObjects<'js, W>, error: Value<'js>, ) -> Result> { // Let state be stream.[[state]]. match &objects.stream.state { // If state is "writable", WritableStreamState::Writable => { // Perform ! WritableStreamStartErroring(stream, error). Self::writable_stream_start_erroring(ctx, objects, error) }, WritableStreamState::Erroring(ref stored_error) => { let stored_error = stored_error.clone(); // Perform ! WritableStreamFinishErroring(stream). Self::writable_stream_finish_erroring(ctx, objects, stored_error) }, other => panic!("WritableStreamDealWithRejection must be called in state 'writable' or 'erroring', found {other:?}"), } } pub(super) fn writable_stream_finish_in_flight_write(&mut self) -> Result<()> { // Resolve stream.[[inFlightWriteRequest]] with undefined. // Set stream.[[inFlightWriteRequest]] to undefined. self.in_flight_write_request .take() .expect("writable_stream_finish_in_flight_write called without in flight write request") .resolve_undefined() } pub(super) fn writable_stream_finish_in_flight_close>( // Let writer be stream.[[writer]]. mut objects: WritableStreamObjects<'js, W>, ) -> Result> { // Assert: stream.[[inFlightCloseRequest]] is not undefined. // Resolve stream.[[inFlightCloseRequest]] with undefined. // Set stream.[[inFlightCloseRequest]] to undefined. objects .stream .in_flight_close_request .take() .expect("writable_stream_finish_in_flight_close called without in flight close request") .resolve_undefined()?; // Let state be stream.[[state]]. // If state is "erroring", if let WritableStreamState::Erroring(_) = objects.stream.state { // Set stream.[[storedError]] to undefined. // (implicitly covered by change to Closed below) // If stream.[[pendingAbortRequest]] is not undefined, if let Some(pending_abort_request) = objects.stream.pending_abort_request.take() { // Resolve stream.[[pendingAbortRequest]]'s promise with undefined. // Set stream.[[pendingAbortRequest]] to undefined. pending_abort_request.promise.resolve_undefined()?; } } // Set stream.[[state]] to "closed". objects.stream.state = WritableStreamState::Closed; // If writer is not undefined, resolve writer.[[closedPromise]] with undefined. objects.with_writer( |objects| { objects.writer.closed_promise.resolve_undefined()?; Ok(objects) }, Ok, ) } pub(super) fn writable_stream_update_backpressure>( ctx: Ctx<'js>, // Let writer be stream.[[writer]]. mut objects: WritableStreamObjects<'js, W>, backpressure: bool, ) -> Result> { // If writer is not undefined and backpressure is not stream.[[backpressure]], objects = objects.with_writer( |mut objects| { if backpressure != objects.stream.backpressure { if backpressure { // If backpressure is true, set writer.[[readyPromise]] to a new promise. objects.writer.ready_promise = ResolveablePromise::new(&ctx)?; } else { // Otherwise, // Resolve writer.[[readyPromise]] with undefined. objects.writer.ready_promise.resolve_undefined()? } } Ok(objects) }, Ok, )?; // Set stream.[[backpressure]] to backpressure. objects.stream.backpressure = backpressure; Ok(objects) } pub(super) fn writer_mut(&mut self) -> Option> { self.writer.clone().map(OwnedBorrowMut::from_class) } pub(crate) fn stored_error(&self) -> Option> { match self.state { WritableStreamState::Erroring(ref stored_error) | WritableStreamState::Errored(ref stored_error) => Some(stored_error.clone()), _ => None, } } } #[derive(Debug, Trace, Clone, JsLifetime)] pub(crate) enum WritableStreamState<'js> { Writable, Closed, Erroring(Value<'js>), Errored(Value<'js>), } #[derive(JsLifetime, Trace)] struct PendingAbortRequest<'js> { promise: ResolveablePromise<'js>, reason: Value<'js>, was_already_erroring: bool, } ================================================ FILE: modules/llrt_stream_web/src/writable/stream/sink.rs ================================================ use rquickjs::{Function, Object, Result, Value}; use crate::utils::ValueOrUndefined; #[derive(Default)] pub struct UnderlyingSink<'js> { // callback UnderlyingSinkStartCallback = any (WritableStreamDefaultController controller); pub start: Option>, // callback UnderlyingSinkWriteCallback = Promise (any chunk, WritableStreamDefaultController controller); pub write: Option>, // callback UnderlyingSinkCloseCallback = Promise (); pub close: Option>, // callback UnderlyingSinkAbortCallback = Promise (optional any reason); pub abort: Option>, pub r#type: Option>, } impl<'js> UnderlyingSink<'js> { pub fn from_object(obj: Object<'js>) -> Result { let start = obj.get_value_or_undefined::<_, _>("start")?; let write = obj.get_value_or_undefined::<_, _>("write")?; let close = obj.get_value_or_undefined::<_, _>("close")?; let abort = obj.get_value_or_undefined::<_, _>("abort")?; let r#type = obj.get_value_or_undefined::<_, _>("type")?; Ok(Self { start, write, close, abort, r#type, }) } } ================================================ FILE: modules/llrt_stream_web/src/writable/writer.rs ================================================ use rquickjs::{class::Trace, Result}; use crate::writable::default_writer::WritableStreamDefaultWriterOwned; pub(crate) trait WritableStreamWriter<'js>: Sized + 'js { type Class: Clone + Trace<'js>; fn with_writer( self, ctx: C, default: impl FnOnce( C, WritableStreamDefaultWriterOwned<'js>, ) -> Result<(C, WritableStreamDefaultWriterOwned<'js>)>, none: impl FnOnce(C) -> Result, ) -> Result<(C, Self)>; fn into_inner(self) -> Self::Class; fn from_class(class: Self::Class) -> Self; } #[derive(Clone, Trace)] pub(super) struct UndefinedWriter; impl<'js> WritableStreamWriter<'js> for UndefinedWriter { type Class = UndefinedWriter; fn with_writer( self, ctx: C, _: impl FnOnce( C, WritableStreamDefaultWriterOwned<'js>, ) -> Result<(C, WritableStreamDefaultWriterOwned<'js>)>, none: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { Ok((none(ctx)?, self)) } fn into_inner(self) -> Self::Class { self } fn from_class(class: Self::Class) -> Self { class } } impl<'js, T: WritableStreamWriter<'js>> WritableStreamWriter<'js> for Option { type Class = Option<>::Class>; fn with_writer( self, mut ctx: C, default: impl FnOnce( C, WritableStreamDefaultWriterOwned<'js>, ) -> Result<(C, WritableStreamDefaultWriterOwned<'js>)>, none: impl FnOnce(C) -> Result, ) -> Result<(C, Self)> { match self { Some(mut writer) => { (ctx, writer) = writer.with_writer(ctx, default, none)?; Ok((ctx, Some(writer))) }, None => Ok((none(ctx)?, None)), } } fn into_inner(self) -> Self::Class { self.map(WritableStreamWriter::into_inner) } fn from_class(class: Self::Class) -> Self { class.map(WritableStreamWriter::from_class) } } ================================================ FILE: modules/llrt_string_decoder/Cargo.toml ================================================ [package] name = "llrt_string_decoder" description = "LLRT Module string_decoder" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_string_decoder" path = "src/lib.rs" [dependencies] llrt_buffer = { version = "0.8.1-beta", path = "../llrt_buffer" } llrt_encoding = { version = "0.8.1-beta", path = "../../libs/llrt_encoding" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", default-features = false } [dev-dependencies] llrt_test = { path = "../../libs/llrt_test" } tokio = { version = "1", features = ["test-util"], default-features = false } ================================================ FILE: modules/llrt_string_decoder/src/lib.rs ================================================ use llrt_utils::module::{export_default, ModuleInfo}; use rquickjs::{ module::{Declarations, Exports, ModuleDef}, Class, Ctx, Result, }; use self::string_decoder::StringDecoder; mod string_decoder; pub struct StringDecoderModule; impl ModuleDef for StringDecoderModule { fn declare(declare: &Declarations) -> Result<()> { declare.declare(stringify!(StringDecoder))?; declare.declare("default")?; Ok(()) } fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> { export_default(ctx, exports, |default| { Class::::define(default)?; Ok(()) })?; Ok(()) } } impl From for ModuleInfo { fn from(val: StringDecoderModule) -> Self { ModuleInfo { name: "string_decoder", module: val, } } } #[cfg(test)] mod tests { use llrt_test::{call_test, test_async_with, ModuleEvaluator}; use super::*; #[tokio::test] async fn test_utf_8() { test_async_with(|ctx| { Box::pin(async move { llrt_buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "string_decoder") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { StringDecoder } from 'string_decoder'; export async function test() { const decoder = new StringDecoder('utf-8'); const cent = Buffer.from([0xC2, 0xA2]); return decoder.write(cent); } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "¢"); }) }) .await; } #[tokio::test] async fn test_utf8_byte_by_byte() { test_async_with(|ctx| { Box::pin(async move { llrt_buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "string_decoder") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { StringDecoder } from 'string_decoder'; export async function test() { const decoder = new StringDecoder('utf8'); const data = Buffer.from("☃💩"); let res = ""; for (let i = 0; i < data.length; i++) { res += decoder.write(data.slice(i, i + 1)); } res += decoder.end(); return res; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "☃💩"); }) }) .await; } #[tokio::test] async fn test_base64() { test_async_with(|ctx| { Box::pin(async move { llrt_buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "string_decoder") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { StringDecoder } from 'string_decoder'; export async function test() { const decoder = new StringDecoder('base64'); let res = ""; res += decoder.write(Buffer.of(0x61)); res += decoder.end(); res += decoder.write(Buffer.of()); res += decoder.end(); return res; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "YQ=="); }) }) .await; } #[tokio::test] async fn test_utf16le() { test_async_with(|ctx| { Box::pin(async move { llrt_buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "string_decoder") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { StringDecoder } from 'string_decoder'; export async function test() { const decoder = new StringDecoder('utf16le'); let res = ""; res += decoder.write(Buffer.of(0x61, 0x00)); res += decoder.end(); res += decoder.write(Buffer.of()); res += decoder.end(); return res; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "a"); }) }) .await; } #[tokio::test] async fn test_utf16le_invalid_high_surrogate() { test_async_with(|ctx| { Box::pin(async move { llrt_buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "string_decoder") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { StringDecoder } from 'string_decoder'; export async function test() { const decoder = new StringDecoder('utf16le'); let res = ""; res += decoder.write(Buffer.of(0x3d, 0xd8)); res += decoder.write(Buffer.of(0x61, 0x00)); res += decoder.end(); return res; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "\u{FFFD}a"); }) }) .await; } #[tokio::test] async fn test_utf16le_byte_by_byte() { test_async_with(|ctx| { Box::pin(async move { llrt_buffer::init(&ctx).unwrap(); ModuleEvaluator::eval_rust::(ctx.clone(), "string_decoder") .await .unwrap(); let module = ModuleEvaluator::eval_js( ctx.clone(), "test", r#" import { StringDecoder } from 'string_decoder'; export async function test() { const decoder = new StringDecoder('utf16le'); let res = ""; res += decoder.write(Buffer.of(0x3d, 0xd8, 0x4d)); res += decoder.write(Buffer.of(0xdc)); res += decoder.end(); res += decoder.write(Buffer.of(0x3d, 0xd8)); res += decoder.write(Buffer.of(0x4d)); res += decoder.write(Buffer.of(0xdc)); res += decoder.end(); res += decoder.write(Buffer.of(0x3d)); res += decoder.write(Buffer.of(0xd8)); res += decoder.write(Buffer.of(0x4d)); res += decoder.write(Buffer.of(0xdc)); res += decoder.end(); res += decoder.write(Buffer.of(0x3d)); res += decoder.write(Buffer.of(0xd8, 0x4d)); res += decoder.write(Buffer.of(0xdc)); res += decoder.end(); res += decoder.write(Buffer.of(0x3d)); res += decoder.write(Buffer.of(0xd8)); res += decoder.write(Buffer.of(0x4d, 0xdc)); res += decoder.end(); res += decoder.write(Buffer.of(0x3d)); res += decoder.write(Buffer.of(0xd8, 0x4d, 0Xdc)); res += decoder.end(); return res; } "#, ) .await .unwrap(); let result = call_test::(&ctx, &module, ()).await; assert_eq!(result, "👍👍👍👍👍👍"); }) }) .await; } } ================================================ FILE: modules/llrt_string_decoder/src/string_decoder.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_buffer::ArrayBufferView; use llrt_encoding::Encoder; use llrt_utils::result::ResultExt; use rquickjs::{function::Opt, CString, Ctx, Exception, Result}; #[derive(rquickjs::class::Trace, rquickjs::JsLifetime)] #[rquickjs::class] pub struct StringDecoder { #[qjs(skip_trace)] encoder: Encoder, buffer: Vec, buffered_bytes: usize, missing_bytes: usize, } impl StringDecoder { fn make_string(&self, ctx: &Ctx<'_>, data: &[u8]) -> Result { self.encoder .encode_to_string(data, true) .map_err(|_| Exception::throw_internal(ctx, "Encoding error")) } /// Try to decode the given buffer and store the incomplete bytes. /// The logic was adapted from the [Node implementation]. /// /// [Node implementation]: https://github.com/nodejs/node/blob/ba06c5c509956dc413f91b755c1c93798bb700d4/src/string_decoder.cc#L66 fn decode_data(&mut self, ctx: &Ctx<'_>, mut data: &[u8]) -> Result { let mut result = String::new(); if matches!( self.encoder, Encoder::Utf8 | Encoder::Utf16le | Encoder::Base64 ) { // See if we want bytes to finish a character from the previous // chunk; if so, copy the new bytes to the missing bytes buffer // and create a string from it that is to be prepended to the main body. if self.missing_bytes > 0 { if matches!(self.encoder, Encoder::Utf8) { // For UTF-8, we need special alignment treatment: // If an incomplete character is found at a chunk boundary, we use // its remainder and try to decode it. let mut i = 0; while i < data.len() && i < self.missing_bytes { if (data[i] & 0xC0) != 0x80 { // This byte is not a continuation byte even though it should have // been one. We stop decoding of the incomplete character at this // point (but still use the rest of the incomplete bytes from this // chunk) and assume that the new, unexpected byte starts a new one. self.missing_bytes = 0; self.buffer.extend_from_slice(&data[..i]); self.buffered_bytes += i; data = &data[i..]; break; } i += 1; } } else if matches!(self.encoder, Encoder::Utf16le) { // For UTF-16le, we need special alignment treatment: // If we have a high surrogate we need to extend the missing bytes // to 3 to get the low surrogate. let mut i = 0; while i < data.len() && i < self.missing_bytes { if (data[i] & 0xFC) == 0xD8 { self.missing_bytes = 3; break; } i += 1; } } let found_bytes = std::cmp::min(data.len(), self.missing_bytes); self.buffer.extend_from_slice(&data[..found_bytes]); data = &data[found_bytes..]; self.missing_bytes -= found_bytes; self.buffered_bytes += found_bytes; if self.missing_bytes == 0 { // We have enough bytes to decode the buffered character result = self.make_string(ctx, &self.buffer)?; self.buffer.clear(); self.buffered_bytes = 0; } } // It could be that trying to finish the previous chunk already // consumed all data that we received in this chunk. if data.is_empty() { return Ok(result); } else { // If not, that means is no character left to finish at this point. // See whether there is a character that we may have to cut off and // finish when receiving the next chunk. if matches!(self.encoder, Encoder::Utf8) && (data[data.len() - 1] & 0x80) != 0 { let mut i = data.len() - 1; loop { self.buffered_bytes += 1; if (data[i] & 0xC0) == 0x80 { // This byte does not start a character (a "trailing" byte). if self.buffered_bytes >= 4 || i == 0 { // We either have more then 4 trailing bytes (which means // the current character would not be inside the range for // valid Unicode, and in particular cannot be represented // through JavaScript's UTF-16-based approach to strings), or the // current buffer does not contain the start of an UTF-8 character // at all. Either way, this is invalid UTF8 and we can just // let the engine's decoder handle it. self.buffer.clear(); self.buffered_bytes = 0; break; } } else { // Found the first byte of a UTF-8 character. By looking at the // upper bits we can tell how long the character *should* be. if (data[i] & 0xE0) == 0xC0 { self.missing_bytes = 2; } else if (data[i] & 0xF0) == 0xE0 { self.missing_bytes = 3; } else if (data[i] & 0xF8) == 0xF0 { self.missing_bytes = 4; } else { // This lead byte would indicate a character outside of the // representable range. self.buffered_bytes = 0; break; } if self.buffered_bytes >= self.missing_bytes { // Received more or exactly as many trailing bytes than the lead // character would indicate. In the "==" case, we have valid // data and don't need to slice anything off; // in the ">" case, this is invalid UTF-8 anyway. self.missing_bytes = 0; self.buffered_bytes = 0; } self.missing_bytes -= self.buffered_bytes; break; } i -= 1; } } else if matches!(self.encoder, Encoder::Utf16le) { // WARN: For UTF-16LE we deviate from the specification when an invalid // high surrogate is found. The spec says we should keep it as is, but // there no way to encode in UTF-8 (required to interface with quickjs). // For now, we will replace it with a replacement character. // See https://github.com/quickjs-ng/quickjs/issues/992 if (data.len() % 2) == 1 { // We got half a codepoint, and need the second byte of it. // But we need to avoid rendering high surrogates before we // have the full character. if data.len() >= 3 && (data[data.len() - 2] & 0xFC) == 0xD8 { self.buffered_bytes = 3; self.missing_bytes = 1; } else { self.buffered_bytes = 1; self.missing_bytes = 1; } } else if (data[data.len() - 1] & 0xFC) == 0xD8 { // Half a split UTF-16 character. self.buffered_bytes = 2; self.missing_bytes = 2; } } else if matches!(self.encoder, Encoder::Base64) { self.buffered_bytes = data.len() % 3; if self.buffered_bytes > 0 { self.missing_bytes = 3 - self.buffered_bytes; } } if self.buffered_bytes > 0 { // Copy the requested number of buffered bytes from the end of the // input into the incomplete character buffer. self.buffer .extend_from_slice(&data[data.len() - self.buffered_bytes..]); data = &data[..data.len() - self.buffered_bytes]; } if !data.is_empty() { result.push_str(&self.make_string(ctx, data)?); } } Ok(result) } else { // For ASCII, HEX, and LATIN1, we can decode everything directly self.make_string(ctx, data) } } fn flush(&mut self, ctx: &Ctx<'_>) -> Result { if matches!(self.encoder, Encoder::Utf16le) && self.buffered_bytes % 2 == 1 { // Ignore a single trailing byte, like the JS decoder does. self.missing_bytes -= 1; self.buffered_bytes -= 1; } if self.buffered_bytes == 0 { return Ok(String::new()); } let res = self.make_string(ctx, &self.buffer); self.missing_bytes = 0; self.buffered_bytes = 0; self.buffer.clear(); res } } #[rquickjs::methods(rename_all = "camelCase")] impl StringDecoder { #[qjs(constructor)] pub fn new(ctx: Ctx<'_>, encoding: Opt>) -> Result { let encoding = encoding.0.as_ref().map(|e| e.as_str()).unwrap_or("utf-8"); let encoder = Encoder::from_str(encoding).map_err(|_| { let msg = ["Unknown encoding: ", encoding].concat(); Exception::throw_type(&ctx, &msg) })?; Ok(Self { encoder, buffer: Vec::new(), buffered_bytes: 0, missing_bytes: 0, }) } #[qjs(get)] pub fn encoding(&self) -> &str { self.encoder.as_label() } pub fn end(&mut self, ctx: Ctx<'_>, buffer: Opt>) -> Result { let output = if let Some(data) = buffer.0.as_ref().and_then(|b| b.as_bytes()) { Some(self.decode_data(&ctx, data)?) } else { None }; let flush = self.flush(&ctx)?; Ok(output .map(|mut o| { o.push_str(&flush); o }) .unwrap_or(flush)) } pub fn write(&mut self, ctx: Ctx<'_>, buffer: ArrayBufferView<'_>) -> Result { let data = buffer .as_bytes() .or_throw_msg(&ctx, "Buffer has already been used")?; self.decode_data(&ctx, data) } } ================================================ FILE: modules/llrt_temporal/Cargo.toml ================================================ [package] name = "llrt_temporal" description = "LLRT Module temporal" version = "0.8.1-beta" edition = "2021" license = "Apache-2.0" repository = "https://github.com/awslabs/llrt" readme = "README.md" [lib] name = "llrt_temporal" path = "src/lib.rs" [dependencies] jiff = { version = "0.2" } llrt_utils = { version = "0.8.1-beta", path = "../../libs/llrt_utils", default-features = false } rquickjs = { version = "0.11", features = ["macro"], default-features = false } ================================================ FILE: modules/llrt_temporal/src/duration.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{cmp::Ordering, str::FromStr}; use jiff::{Span, SpanCompare}; use llrt_utils::result::ResultExt; use rquickjs::{ atom::PredefinedAtom, class::Trace, prelude::{Opt, Rest}, Class, Ctx, Exception, JsLifetime, Object, Result, Value, }; use crate::utils::date::fill_duration_from_iter as fill_date_from_iter; use crate::utils::round::span::SpanRoundOption; use crate::utils::span::SpanExt; use crate::utils::time::fill_duration_from_iter as fill_time_from_iter; use crate::utils::total::span::SpanTotalOption; #[derive(Clone, JsLifetime, Trace)] #[rquickjs::class] pub(crate) struct Duration { #[qjs(skip_trace)] inner: Span, } #[rquickjs::methods(rename_all = "camelCase")] impl Duration { #[qjs(constructor)] fn new<'js>(ctx: Ctx<'js>, args: Rest>) -> Result { let obj = Self::fill_object(&ctx, &args)?; Self::from_object(&ctx, &obj) } #[qjs(static)] fn compare(ctx: Ctx<'_>, duration1: Self, duration2: Self, opt: Opt>) -> Result { let sc = Self::into_span_compare(&duration2.inner, &opt); match duration1.inner.compare(sc).or_throw_range(&ctx, "")? { Ordering::Less => Ok(-1), Ordering::Equal => Ok(0), Ordering::Greater => Ok(1), } } #[qjs(static)] fn from(ctx: Ctx<'_>, info: Value<'_>) -> Result { Self::from_value(&ctx, &info) } fn abs(&self) -> Self { let span = self.inner.abs(); Self { inner: span } } fn add(&self, ctx: Ctx<'_>, other: Value<'_>) -> Result { let duration = Self::from_value(&ctx, &other)?; let span = duration.into_inner(); let span = self.inner.checked_add(span).or_throw_range(&ctx, "")?; Ok(Self { inner: span }) } fn negated(&self) -> Self { let span = self.inner.negate(); Self { inner: span } } fn round(&self, ctx: Ctx<'_>, options: Value<'_>) -> Result { let round = SpanRoundOption::from_value(&ctx, &options)?; let round = round.into_inner(); let dt = self.inner.round(round).or_throw_range(&ctx, "")?; Ok(Self { inner: dt }) } fn subtract(&self, ctx: Ctx<'_>, other: Value<'_>) -> Result { let duration = Self::from_value(&ctx, &other)?; let span = duration.into_inner(); let span = self.inner.checked_sub(span).or_throw_range(&ctx, "")?; Ok(Self { inner: span }) } #[allow(clippy::inherent_to_string)] #[qjs(rename = PredefinedAtom::ToJSON)] fn to_json(&self) -> String { self.inner.to_string() } #[allow(clippy::inherent_to_string)] #[qjs(rename = PredefinedAtom::ToString)] fn to_string(&self) -> String { self.inner.to_string() } fn total(&self, ctx: Ctx<'_>, options: Value<'_>) -> Result { let total = SpanTotalOption::from_value(&ctx, &options)?; let total = total.into_inner(); let num = self.inner.total(total).or_throw_range(&ctx, "")?; Ok(num) } fn value_of(&self, ctx: Ctx<'_>) -> Result<()> { Err(Exception::throw_type( &ctx, "can't convert Duration to primitive type", )) } fn with(&self, ctx: Ctx<'_>, info: Value<'_>) -> Result { let span = self.inner.span_with(&ctx, &info)?; Ok(Self { inner: span }) } #[qjs(get)] fn blank(&self) -> bool { self.inner.signum() == 0 } #[qjs(get)] fn days(&self) -> i32 { self.inner.get_days() } #[qjs(get)] fn hours(&self) -> i32 { self.inner.get_hours() } #[qjs(get)] fn microseconds(&self) -> i64 { self.inner.get_microseconds() } #[qjs(get)] fn milliseconds(&self) -> i64 { self.inner.get_milliseconds() } #[qjs(get)] fn minutes(&self) -> i64 { self.inner.get_minutes() } #[qjs(get)] fn months(&self) -> i32 { self.inner.get_months() } #[qjs(get)] fn nanoseconds(&self) -> i64 { self.inner.get_nanoseconds() } #[qjs(get)] fn seconds(&self) -> i64 { self.inner.get_seconds() } #[qjs(get)] fn sign(&self) -> i8 { self.inner.signum() } #[qjs(get)] fn weeks(&self) -> i32 { self.inner.get_weeks() } #[qjs(get)] fn years(&self) -> i16 { self.inner.get_years() } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] fn to_string_tag(&self) -> &'static str { "Temporal.Duration" } } impl Duration { fn fill_object<'js>(ctx: &Ctx<'js>, args: &Rest>) -> Result> { let obj = Object::new(ctx.clone())?; let mut iter = args.0.iter().cloned(); fill_date_from_iter(&obj, &mut iter)?; fill_time_from_iter(&obj, &mut iter)?; Ok(obj) } fn from_object(ctx: &Ctx<'_>, obj: &Object<'_>) -> Result { let span = Span::from_object(ctx, obj)?; Ok(Self { inner: span }) } pub(crate) fn from_value(ctx: &Ctx<'_>, value: &Value<'_>) -> Result { if let Some(obj) = value.as_object() { if let Some(cls) = Class::::from_object(obj) { return Ok(cls.borrow().clone()); } return Self::from_object(ctx, obj); } let str = value .as_string() .and_then(|s| s.to_string().ok()) .or_throw_type(ctx, "Cannot convert value to string")?; let span = Span::from_str(&str).or_throw_range(ctx, "")?; Ok(Self { inner: span }) } pub(crate) fn into_inner(self) -> Span { self.inner } fn into_span_compare<'a>(span: &Span, value: &Opt>) -> SpanCompare<'a> { Span::into_span_compare(span, value) } pub(crate) fn new_object(span: Span) -> Self { Self { inner: span } } } ================================================ FILE: modules/llrt_temporal/src/instant.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use std::{cmp::Ordering, str::FromStr}; use jiff::{Timestamp, Zoned}; use llrt_utils::result::ResultExt; use rquickjs::{ atom::PredefinedAtom, class::Trace, prelude::Opt, BigInt, Class, Ctx, Exception, JsLifetime, Result, Value, }; use crate::duration::Duration; use crate::utils::round::timestamp::TimestampRoundOption; use crate::zoned_date_time::ZonedDateTime; use super::extract_bigint_or_number; #[derive(Clone, JsLifetime, Trace)] #[rquickjs::class] pub(crate) struct Instant { #[qjs(skip_trace)] inner: Timestamp, } #[rquickjs::methods(rename_all = "camelCase")] impl Instant { #[qjs(constructor)] fn new(ctx: Ctx<'_>, nanos: Value<'_>) -> Result { Self::from_epoch_nanoseconds(ctx, nanos) } #[qjs(static)] fn compare(instant1: Self, instant2: Self) -> i8 { match instant1.inner.cmp(&instant2.inner) { Ordering::Less => -1, Ordering::Equal => 0, Ordering::Greater => 1, } } #[qjs(static)] fn from(ctx: Ctx<'_>, info: Value<'_>) -> Result { if let Some(obj) = info.as_object() { if let Some(cls) = Class::::from_object(obj) { return Ok(cls.borrow().clone()); } } let str = info .as_string() .and_then(|s| s.to_string().ok()) .or_throw_type(&ctx, "Cannot convert value to string")?; Self::from_str(&ctx, &str) } #[qjs(static)] fn from_epoch_milliseconds(ctx: Ctx<'_>, ms: f64) -> Result { let ns = (ms * 1_000_000.0) as i128; Self::from_nanosecond(&ctx, ns) } #[qjs(static)] fn from_epoch_nanoseconds(ctx: Ctx<'_>, ns: Value<'_>) -> Result { let ns = extract_bigint_or_number(&ctx, &ns)?; Self::from_nanosecond(&ctx, ns) } fn add(&self, ctx: Ctx<'_>, duration: Value<'_>) -> Result { let duration = Duration::from_value(&ctx, &duration)?; let span = duration.into_inner(); let ts = self.inner.checked_add(span).or_throw_range(&ctx, "")?; Ok(Self { inner: ts }) } fn equals(&self, other: Self) -> bool { self.inner == other.inner } fn round(&self, ctx: Ctx<'_>, options: Value<'_>) -> Result { let round = TimestampRoundOption::from_value(&ctx, &options)?; let round = round.into_inner(); let ts = self.inner.round(round).or_throw_range(&ctx, "")?; Ok(Self { inner: ts }) } fn since(&self, other: Self) -> Duration { Duration::new_object(self.inner - other.inner) } fn subtract(&self, ctx: Ctx<'_>, duration: Value<'_>) -> Result { let duration = Duration::from_value(&ctx, &duration)?; let span = duration.into_inner(); let ts = self.inner.checked_sub(span).or_throw_range(&ctx, "")?; Ok(Self { inner: ts }) } #[allow(clippy::inherent_to_string)] #[qjs(rename = PredefinedAtom::ToJSON)] fn to_json(&self) -> String { self.inner.to_string() } #[allow(clippy::inherent_to_string)] #[qjs(rename = PredefinedAtom::ToString)] fn to_string(&self) -> String { self.inner.to_string() } #[qjs(rename = "toZonedDateTimeISO")] fn to_zoned_dt_iso(&self, ctx: Ctx<'_>, timezone: Opt) -> Result { let tz = timezone.as_deref().unwrap_or("UTC"); ZonedDateTime::from_ts_tz(&ctx, &self.inner, tz) } fn until(&self, other: Self) -> Duration { Duration::new_object(other.inner - self.inner) } fn value_of(&self, ctx: Ctx<'_>) -> Result<()> { Err(Exception::throw_type( &ctx, "can't convert Instant to primitive type", )) } #[qjs(get)] fn epoch_milliseconds(&self) -> i64 { self.inner.as_millisecond() } #[qjs(get)] fn epoch_nanoseconds<'js>(&self, ctx: Ctx<'js>) -> Result> { let ns = self.inner.as_nanosecond(); let ns = ns.try_into().or_throw_range(&ctx, "")?; BigInt::from_i64(ctx, ns) } #[qjs(get, rename = PredefinedAtom::SymbolToStringTag)] fn to_string_tag(&self) -> &'static str { "Temporal.Instant" } } impl Instant { fn from_nanosecond(ctx: &Ctx<'_>, ns: i128) -> Result { let ts = Timestamp::from_nanosecond(ns).or_throw_range(ctx, "")?; Ok(Self { inner: ts }) } fn from_str(ctx: &Ctx<'_>, str: &str) -> Result { let ts = Timestamp::from_str(str).or_throw_range(ctx, "")?; Ok(Self { inner: ts }) } pub(crate) fn from_zoned(zoned: &Zoned) -> Self { let ts = zoned.timestamp(); Self { inner: ts } } pub(crate) fn now() -> Self { let ts = Timestamp::now(); Self { inner: ts } } } ================================================ FILE: modules/llrt_temporal/src/lib.rs ================================================ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 mod duration; mod instant; mod now; mod plain_date; mod plain_date_time; mod plain_time; mod utils; mod zoned_date_time; use std::str::FromStr; use jiff::civil::Time; use llrt_utils::result::ResultExt; use rquickjs::{Class, Ctx, Exception, Object, Result, Value}; use crate::duration::Duration; use crate::instant::Instant; use crate::plain_date::PlainDate; use crate::plain_date_time::PlainDateTime; use crate::plain_time::PlainTime; use crate::zoned_date_time::ZonedDateTime; pub fn init(ctx: &Ctx<'_>) -> Result<()> { let temporal = Object::new(ctx.clone())?; Class::::define(&temporal)?; Class::::define(&temporal)?; Class::::define(&temporal)?; Class::::define(&temporal)?; Class::::define(&temporal)?; Class::::define(&temporal)?; temporal.set("Now", now::define_object(ctx)?)?; ctx.globals().set("Temporal", temporal)?; Ok(()) } pub(crate) fn extract_bigint_or_number(ctx: &Ctx<'_>, value: &Value<'_>) -> Result { if let Some(num) = value.as_number() { if !num.is_finite() { return Err(Exception::throw_message(ctx, "Invalid value")); } Ok(num as i128) } else if let Some(bigint) = value.as_big_int() { match bigint.clone().to_i64() { Ok(v) => Ok(v as i128), Err(_) => Err(Exception::throw_message(ctx, "BigInt value out of range")), } } else { Err(Exception::throw_message(ctx, "Expected number or BigInt")) } } pub(crate) fn extract_time(ctx: &Ctx<'_>, val: &Option>) -> Result