diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index db74fda80c..019bf96391 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -66,6 +66,14 @@ jobs: recipe: bench-ffi artifact: bench-ffi artifact-path: target/bench/ffi + # Keeps the BoltFFI and UniFFI slice building and running for the + # browser generator comparison, which reuses it. It skips the size + # step, whose moq-ffi and moq-wasm builds double the time and are + # already built elsewhere. + - name: Generator comparison + recipe: bench-ffi-gen build bench + artifact: bench-ffi-gen + artifact-path: target/ffi-gen/results - name: Audio grouping relay benchmark recipe: bench-audio - name: Relay shard sweep diff --git a/bench/ffi-gen/Cargo.lock b/bench/ffi-gen/Cargo.lock new file mode 100644 index 0000000000..c058556a08 --- /dev/null +++ b/bench/ffi-gen/Cargo.lock @@ -0,0 +1,3828 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_log-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "askama" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6024d73179f43f15ccd2b881bfea6fee7f3a46ec53f33b52210dea749ebebaa4" +dependencies = [ + "askama_macros", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071ee5ebf2138e3ad180e0aacf6940c2cab5e6d8333741d9925c7bee2b153f39" +dependencies = [ + "askama_parser", + "basic-toml", + "glob", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn 3.0.6", +] + +[[package]] +name = "askama_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643e1c7cbb6aec1d920332fe51a7c0d8219e273dcb8602db03f5263e4d16487b" +dependencies = [ + "askama_derive", +] + +[[package]] +name = "askama_parser" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5ae75772275d268b03ab8bdccdd12117b6169ee23256942b34e46c9f476583" +dependencies = [ + "rustc-hash", + "serde", + "serde_derive", + "unicode-ident", + "winnow", +] + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure 0.13.2", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "boltffi" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bb38f198fcecec3a0845a01198df637bbbe21f927b642e73a54881df665c20" +dependencies = [ + "boltffi_core", + "boltffi_macros", +] + +[[package]] +name = "boltffi_ast" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e5bf26f14830edc8673d05a1e8d0bb44b6a533e6d01d99e411e02577ed6d5f" +dependencies = [ + "serde", +] + +[[package]] +name = "boltffi_binding" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d39b74b304ca107ea1b1ef6d2d8d7c4b69a9bad7bf206cc047dd87174d29776" +dependencies = [ + "boltffi_ast", + "serde", + "serde_json", +] + +[[package]] +name = "boltffi_core" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6ee6290de9493be873985f34baa4b75ecdede5a46be85eee28c8e058449288" +dependencies = [ + "boltffi_macros", + "url", + "uuid", +] + +[[package]] +name = "boltffi_macros" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ae23a49821e96421a1973dfdf14e298b2626fad1da355d5644dbfa8bf71bcc" +dependencies = [ + "boltffi_ast", + "boltffi_binding", + "boltffi_scan", + "proc-macro-crate", + "proc-macro2", + "quote", + "rustc_lexer", + "serde", + "syn 2.0.119", + "toml", +] + +[[package]] +name = "boltffi_scan" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c351cd73c32e7038e1abe3e08770618506de340fa9e5396cabe7dbce684730d" +dependencies = [ + "boltffi_ast", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "camino" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_affinity" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a034b3a7b624016c6e13f5df875747cc25f884156aad2abd12b6c46797971342" +dependencies = [ + "libc", + "num_cpus", + "winapi", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.6", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common 0.2.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "dns-lookup" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263c0ccfc6b3b6a93e959edad4f985779e76c0b36dbb2e13e3d27c690974adf1" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "enum-assoc" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0590c4a94da3372e83493b956755a6e2266830b6e4e3b101afe66e3f39477b91" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastbloom" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" +dependencies = [ + "foldhash", + "libm", + "portable-atomic", + "siphasher", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ade96dc9003043bce7c035c85a9df5a858bfb2039c5a2e6fdf00f324f6c551" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "goblin" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "identity-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" +dependencies = [ + "bitflags 2.13.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "kio" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3033155c4fffcd86959a9ebdfbcadbed7ec4bfcc2fb545469778f791adf704eb" +dependencies = [ + "loom", + "smallvec", +] + +[[package]] +name = "kio" +version = "0.6.2" +source = "git+https://github.com/moq-dev/moq?rev=b4eab0db18aa6846dc9a486283aa3207d4577a58#b4eab0db18aa6846dc9a486283aa3207d4577a58" +dependencies = [ + "loom", + "smallvec", +] + +[[package]] +name = "kqueue" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "pin-utils", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moq-ffi-gen" +version = "0.0.0" +dependencies = [ + "boltffi", + "getrandom 0.4.3", + "kio 0.6.2", + "libc", + "moq-ffi-gen-uniffi-shim", + "moq-net", + "moq-tokio", + "tokio", + "uniffi", + "url", + "web-async", +] + +[[package]] +name = "moq-ffi-gen-uniffi-shim" +version = "0.0.0" + +[[package]] +name = "moq-net" +version = "0.3.10" +source = "git+https://github.com/moq-dev/moq?rev=b4eab0db18aa6846dc9a486283aa3207d4577a58#b4eab0db18aa6846dc9a486283aa3207d4577a58" +dependencies = [ + "arrayvec", + "bytes", + "futures", + "kio 0.6.2", + "moq-pattern", + "num_enum", + "rand", + "serde", + "slab", + "thiserror", + "tracing", + "uuid", + "web-async", +] + +[[package]] +name = "moq-noq" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d9d1334f556a15e60f6d406020fc3a40409335ae8a2ad472f6bf074a01acfa" +dependencies = [ + "bytes", + "cfg_aliases", + "derive_more", + "moq-noq-proto", + "moq-noq-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "web-time", +] + +[[package]] +name = "moq-noq-proto" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac444c6c1ed18d45536cd939be7f0fd664996e79c0519119b13d6ae9d3d5e2" +dependencies = [ + "aes-gcm", + "aws-lc-rs", + "bytes", + "derive_more", + "enum-assoc", + "fastbloom", + "getrandom 0.4.3", + "identity-hash", + "lru-slab", + "rand", + "rand_pcg", + "rustc-hash", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "slab", + "sorted-index-buffer", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "moq-noq-udp" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e978cded333ca8a6d50b96a7a4388ccc8d98db51d89df7dbee598e9b849ee79" +dependencies = [ + "cfg_aliases", + "libc", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "moq-pattern" +version = "0.1.2" +source = "git+https://github.com/moq-dev/moq?rev=b4eab0db18aa6846dc9a486283aa3207d4577a58#b4eab0db18aa6846dc9a486283aa3207d4577a58" +dependencies = [ + "serde", +] + +[[package]] +name = "moq-sock" +version = "0.2.0" +source = "git+https://github.com/moq-dev/moq?rev=b4eab0db18aa6846dc9a486283aa3207d4577a58#b4eab0db18aa6846dc9a486283aa3207d4577a58" +dependencies = [ + "cfg_aliases", + "core_affinity", + "libc", + "rand", + "socket2", + "thiserror", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "moq-tokio" +version = "0.19.23" +source = "git+https://github.com/moq-dev/moq?rev=b4eab0db18aa6846dc9a486283aa3207d4577a58#b4eab0db18aa6846dc9a486283aa3207d4577a58" +dependencies = [ + "bytes", + "dns-lookup", + "futures", + "hex", + "humantime", + "jni", + "libc", + "moq-net", + "moq-sock", + "notify", + "qmux", + "rand", + "rcgen", + "reqwest", + "rustls", + "rustls-platform-verifier", + "rustls-webpki", + "serde", + "serde_with", + "thiserror", + "time", + "tokio", + "toml", + "tracing", + "tracing-android", + "tracing-subscriber", + "url", + "usage-rs", + "web-transport-moq", + "web-transport-trait", + "webpki-roots", + "x509-parser", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.13.2", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qmux" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542edbed299cac6d624d3964c423c2b2a94b19ffcde4eef5fde09b74c64799e4" +dependencies = [ + "bytes", + "futures", + "libc", + "rustls", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-tungstenite", + "tracing", + "web-transport-proto", + "web-transport-trait", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rcgen" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8" +dependencies = [ + "aws-lc-rs", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_lexer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86aae0c77166108c01305ee1a36a1e77289d7dc6ca0a3cd91ff4992de2d16a5" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" +dependencies = [ + "base64 0.23.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "sfv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890ef0e5532daf21fdaa3dbaf991417187f3a87ab69231fe69be942495746480" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.2", + "ref-cast", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" + +[[package]] +name = "smawk" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sorted-index-buffer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textwrap" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" +dependencies = [ + "smawk", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap 2.14.2", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap 2.14.2", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12612be8f868a09c0ceae7113ff26afe79d81a24473a393cb9120ece162e86c0" +dependencies = [ + "android_log-sys", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uniffi" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76407f5f396a2c949a069eff4a9fca9ce462410eb872bffef4c2b7b89804a77a" +dependencies = [ + "anyhow", + "cargo_metadata", + "uniffi_bindgen", + "uniffi_core", + "uniffi_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_bindgen" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df3ced8f0eda3de99f6d50820e8628f443da8e4f447f477d1403ceba29dacbd" +dependencies = [ + "anyhow", + "askama", + "camino", + "cargo_metadata", + "fs-err", + "glob", + "goblin", + "heck", + "indexmap 2.14.2", + "once_cell", + "serde", + "tempfile", + "textwrap", + "toml", + "uniffi_internal_macros", + "uniffi_meta", + "uniffi_pipeline", + "uniffi_udl", +] + +[[package]] +name = "uniffi_core" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7530ae8efeaaa488622865966763fe0294832779fff80508c36d69c6a874a6a" +dependencies = [ + "anyhow", + "bytes", + "once_cell", + "static_assertions", +] + +[[package]] +name = "uniffi_internal_macros" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4bad017164375d99450d70495014810d15a84ba9da6ed14b9628b1424223ba" +dependencies = [ + "anyhow", + "indexmap 2.14.2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "uniffi_macros" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b855a58da153739ce6e863f6e296edc3716a093768c4d3d957f17f5e4317c60" +dependencies = [ + "camino", + "fs-err", + "once_cell", + "proc-macro2", + "quote", + "serde", + "syn 2.0.119", + "toml", + "uniffi_meta", +] + +[[package]] +name = "uniffi_meta" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af0c88664a9cb9559856f5a250283c9708923b303170e2e5c7db8f4ad65e9459" +dependencies = [ + "anyhow", + "siphasher", + "uniffi_internal_macros", + "uniffi_pipeline", +] + +[[package]] +name = "uniffi_pipeline" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f5c044b4a3dca0b1226c92ddaeea626f337470e22180dd182160c87a06b0b" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.2", + "tempfile", + "uniffi_internal_macros", +] + +[[package]] +name = "uniffi_udl" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55dfd9c778d6b39cb5acd541d9ff63431974035d2bad83c897b766a6ee3152ac" +dependencies = [ + "anyhow", + "textwrap", + "uniffi_meta", + "weedle2", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "usage-argv" +version = "6.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a2fcdc2d909c0858fcf190d6135dd9fb303da3497f1b9fea8592bb414d74c" + +[[package]] +name = "usage-config" +version = "6.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf47002e31d27e4cad2326006ff50b88e726b6e8601f3a47cb2c2a3f4eb1357" + +[[package]] +name = "usage-derive" +version = "6.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba1fef6ebefcad2733a1120567baf665b67b85382539de4b6187dd85b5eab12" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "usage-rs" +version = "6.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ba4af226b5e8186231f5fc83dc913522fab3b5accc7fc3811e940794d522407" +dependencies = [ + "usage-argv", + "usage-config", + "usage-derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "wasm-bindgen", +] + +[[package]] +name = "web-async" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba1426dcf56093a94d9415ba4c016e45c1323679501e7309f1919ab32f08206" +dependencies = [ + "tokio", + "tracing", + "wasm-bindgen-futures", + "wasmtimer", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-transport-moq" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc946e718371f88a79d849c352b6bdd95e0a36139e9b76818c1a101b45ed97a" +dependencies = [ + "bytes", + "futures", + "http", + "kio 0.5.9", + "moq-noq", + "rustls", + "rustls-native-certs", + "thiserror", + "tokio", + "tracing", + "url", + "web-transport-proto", + "web-transport-trait", +] + +[[package]] +name = "web-transport-proto" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a81bcc79bf48ad0b819bfa51ec28524ee7c356056d43c316727d6b505dcc71e" +dependencies = [ + "bytes", + "http", + "sfv", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "web-transport-trait" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1e801f997864ab0074afb1c7d3262df46d5d9ad9b78274c47694544db2dba7" +dependencies = [ + "bytes", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weedle2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e" +dependencies = [ + "nom", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "aws-lc-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yasna" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +dependencies = [ + "bit-vec", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure 0.14.0", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure 0.14.0", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/bench/ffi-gen/Cargo.toml b/bench/ffi-gen/Cargo.toml new file mode 100644 index 0000000000..b73378ec8e --- /dev/null +++ b/bench/ffi-gen/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "moq-ffi-gen" +description = "One moq-ffi slice bound by UniFFI or BoltFFI, to compare the generators" +version = "0.0.0" +edition = "2024" +license = "MIT OR Apache-2.0" +publish = false + +# Its own workspace and lockfile, so BoltFFI never enters the main lockfile or `just check`. +[workspace] +members = ["uniffi-shim"] + +[lib] +name = "moq_ffi_gen" +crate-type = ["cdylib", "staticlib", "rlib"] + +[features] +default = ["client"] +# Pick exactly one generator per build. +uniffi = ["dep:uniffi", "dep:moq-ffi-gen-uniffi-shim"] +boltffi = ["dep:boltffi"] +# `MoqClient` and `MoqSession`, native only: the browser dials through +# web-transport-wasm, which the slice leaves out. A feature rather than a target `cfg`, +# because BoltFFI's macros never see the target, so `cfg(target_arch)` reads as unset. +client = ["dep:moq-tokio", "dep:url"] + +[dependencies] +boltffi = { version = "=0.31.1", optional = true } +# Not a path dependency: BoltFFI 0.31.1 scans every local (path) dependency whose +# source has an item-level `#[error]`, which thiserror structs trip, and then fails on +# moq-tokio's `#[path]` module (see README.md). Bump `rev` to re-measure. +kio = { git = "https://github.com/moq-dev/moq", rev = "b4eab0db18aa6846dc9a486283aa3207d4577a58" } +moq-net = { git = "https://github.com/moq-dev/moq", rev = "b4eab0db18aa6846dc9a486283aa3207d4577a58" } +moq-ffi-gen-uniffi-shim = { path = "uniffi-shim", optional = true } +uniffi = { version = "0.32", optional = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +moq-tokio = { git = "https://github.com/moq-dev/moq", rev = "b4eab0db18aa6846dc9a486283aa3207d4577a58", optional = true } +tokio = { version = "1.48", features = ["rt-multi-thread"] } +url = { version = "2", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.4", features = ["wasm_js"] } +# As moq-ffi: drops the `Send` bounds moq-net's wasm futures and handles cannot meet. +uniffi = { version = "0.32", optional = true, features = ["wasm-unstable-single-threaded"] } +web-async = "0.1.5" + +[dev-dependencies] +libc = "0.2" + +[[example]] +name = "bench" +required-features = ["uniffi"] + +# The root workspace's release profile, which every language package ships. +# UniFFI reads the symbol table, so only debuginfo is stripped, as for moq-ffi. +[profile.release] +lto = "fat" +codegen-units = 1 +strip = "debuginfo" + +# The root's `wasm-release`, which moq-wasm ships. +[profile.wasm-release] +inherits = "release" +opt-level = "z" diff --git a/bench/ffi-gen/README.md b/bench/ffi-gen/README.md new file mode 100644 index 0000000000..419e4280a5 --- /dev/null +++ b/bench/ffi-gen/README.md @@ -0,0 +1,236 @@ +# BoltFFI vs UniFFI + +A spike comparing [BoltFFI](https://github.com/boltffi/boltffi) 0.31.1 with +UniFFI 0.32 as the base for moq's [binding generator fork](/quest/m2/uniffi-fork.md). +The same slice of moq-ffi is bound by each generator and measured from Python and +Kotlin, with a Rust floor. Swift is generated but not run (see [Not measured](#not-measured)). + +## The slice + +`src/` binds moq-net directly: an origin, a broadcast, one track, groups, `write_frame` +and `read_frame` with a `MoqFrame { payload, timestamp_us }` record, an async +`recv_group` and `read_frame` loop, and a native-only QUIC client (`client` feature) +with `MoqClient::connect` and the async constructor `MoqSession::dial`. Three call +probes (`noop`, `noop_async`, `yield_once`) isolate the boundary. + +The source carries BoltFFI's markers (`#[export]`, `#[data]`, `#[error]`) literally, +because BoltFFI finds them by scanning source text and does not see them inside +`cfg_attr`. The UniFFI build imports same-named attribute macros from `uniffi-shim/`, +which expand to UniFFI's. One `--features uniffi` or `--features boltffi` build picks +the generator. + +The crate is its own Cargo workspace, so BoltFFI never enters the root lockfile or +`just check`. It depends on moq-net by git revision rather than path: BoltFFI scans +local path dependencies and fails on moq-tokio (see the gaps below). + +## Reproduce + +From the Nix dev shell (cargo, uv, npm, gradle, a JDK, wasm-bindgen): + +```bash +just bench-ffi-gen # build, bench, size +just bench-ffi-gen build bench # or some steps: build | bench | size +``` + +`build` installs `boltffi_cli` 0.31.1, `tsc`, and `@boltffi/runtime` under +`target/ffi-gen`, then builds the slice with each generator, its Python, Kotlin, and +Swift bindings, both `.wasm` builds, and BoltFFI's web package. `bench` runs +`MOQ_BENCH_ROUNDS` rounds (default 3), alternating which generator goes first, and +writes `target/ffi-gen/results/report.md`. Each case gets 3 warmup steps and a +`MOQ_BENCH_SECONDS` budget (default 1). Throughput mirrors `just bench-ffi`: +`clamp(8 MiB / size, 1, 64)` frames per group, every byte checked, and CPU from +`getrusage`. `size` builds moq-ffi and moq-wasm for reference, then prints the size +tables. + +The nightly runs `build bench` and keeps `target/ffi-gen/results` as an artifact. + +## Results + +Measured 2026-10-16 on an Intel Core Ultra 7 270K Plus (24 threads), Linux, at moq +`b4eab0db1`. The host was shared (load average 8 to 29), so cells are medians of 3 +rounds with the min-max range. Rust 1.98.1, CPython 3.14.7, OpenJDK 17 with Kotlin +2.4.20 and kotlinx-coroutines 1.11.0, JNA 5.19.1. Gain is how many times better BoltFFI +is than UniFFI. + +### Calls (ns per call) + +| call | Rust | Python UniFFI | Python BoltFFI | gain | Kotlin UniFFI | Kotlin BoltFFI | gain | +|---|---|---|---|---|---|---|---| +| `noop()` | 0.2 | 834 | 21.6 | 39x | 3,200 | 5.6 | 569x | +| `group.sequence()` | 0.4 | 1,920 | 63 | 30x | 7,440 | 5.8 | 1,290x | +| `track.info()`, a record return | 12 | 4,890 | 1,120 | 4.4x | 10,400 | 61 | 171x | +| `write_frame`, a 100 B record argument | 78 | 12,000 | 667 | 18x | 12,800 | 134 | 95x | +| `await noop_async()` | 0.3 | 7,190 | 5,330 | 1.3x | 8,320 | 342 | 24x | +| `await yield_once()`, one wakeup | 44 | 12,600 | 10,000 | 1.3x | 6,510 | 587 | 11x | + +In Python, both generators sit at 5 to 13 us per await, which points at asyncio +rather than the boundary. + +### Throughput (MB/s, write plus async read) + +| frame | Rust | Python UniFFI | Python BoltFFI | gain | Kotlin UniFFI | Kotlin BoltFFI | gain | +|---|---|---|---|---|---|---|---| +| 100 B | 473 | 3.8 | 12.1 | 3.2x | 2.9 | 105 | 36x | +| 1 KiB | 3,340 | 36 | 117 | 3.3x | 36 | 954 | 27x | +| 16 KiB | 10,400 | 323 | 1,410 | 4.4x | 286 | 2,730 | 9.6x | +| 64 KiB | 8,930 | 531 | 2,840 | 5.4x | 448 | 2,760 | 6.2x | +| 1 MiB | 6,120 | 605 | 3,220 | 5.3x | 562 | 1,560 | 2.8x | + +### CPU (ms per MB) + +| frame | Rust | Python UniFFI | Python BoltFFI | gain | Kotlin UniFFI | Kotlin BoltFFI | gain | +|---|---|---|---|---|---|---|---| +| 100 B | 2.1 | 261 | 83 | 3.2x | 1,290 | 15 | 85x | +| 1 KiB | 0.30 | 28 | 8.6 | 3.3x | 76 | 1.2 | 61x | +| 16 KiB | 0.10 | 3.1 | 0.71 | 4.4x | 10 | 0.39 | 27x | +| 64 KiB | 0.11 | 1.9 | 0.35 | 5.4x | 3.8 | 0.37 | 10x | +| 1 MiB | 0.16 | 1.7 | 0.31 | 5.3x | 2.3 | 0.66 | 3.4x | + +At 100 B, Kotlin through UniFFI spends about 130 us of CPU per frame written and read, +so 1,000 audio frames a second cost 13% of a core. BoltFFI spends 1.5 us. + +### Size + +Native libraries are `release` (fat LTO, `strip = "debuginfo"`, as moq-ffi ships). +"Stripped" is `strip --strip-all` on a copy. The slice includes moq-tokio's QUIC client. + +| native library | as built | stripped | +|---|---|---| +| slice, UniFFI | 17,571 KiB | 14,553 KiB | +| slice, BoltFFI | 17,472 KiB | 14,482 KiB | +| BoltFFI's Python extension (`_native`), extra | 240 KiB | 64 KiB | +| BoltFFI's Kotlin JNI glue, extra | 52 KiB | 38 KiB | +| moq-ffi, default features | 34,100 KiB | 29,396 KiB | +| moq-ffi, no default features | 30,625 KiB | 26,416 KiB | + +| generated bindings | files | raw | gzip | +|---|---|---|---| +| slice, Python: UniFFI / BoltFFI | 1 / 2 | 85 / 36 KiB | 12 / 7 KiB | +| slice, Kotlin: UniFFI / BoltFFI | 1 / 1 | 152 / 57 KiB | 17 / 9 KiB | +| slice, Swift: UniFFI / BoltFFI | 3 / 2 | 101 / 51 KiB | 14 / 8 KiB | +| moq-ffi, UniFFI: Python / Kotlin / Swift | 1 / 1 / 3 | 653 / 914 / 684 KiB | 66 / 73 / 72 KiB | + +Browser builds use opt-level z after wasm-bindgen and no wasm-opt, as moq-wasm ships. +moq-wasm's profile strips symbols while the others keep a `name` section, so the +stripped columns drop it everywhere. moq-ffi's toggle changes nothing on wasm32, where +`audio` and `video` are already compiled out. + +| browser module | .wasm as built | .wasm stripped | stripped, gzip | JS glue | JS glue, gzip | +|---|---|---|---|---|---| +| slice, UniFFI | 1,245 KiB | 515 KiB | 174 KiB | not generated | not generated | +| slice, BoltFFI | 1,148 KiB | 456 KiB | 155 KiB | 126 KiB | 25 KiB | +| moq-ffi, UniFFI | 6,218 KiB | 4,403 KiB | 1,575 KiB | not generated | not generated | +| moq-wasm, wasm-bindgen | 2,116 KiB | 2,115 KiB | 691 KiB | 38 KiB | 8 KiB | + +BoltFFI's JS glue is its generated web module plus `@boltffi/runtime`, unminified. + +## Not measured + +- **Swift at runtime.** The dev shell has no Linux Swift toolchain. The bindings are + generated and sized above. A Swift runner would run on the macOS job in + `.github/workflows/platform.yml` (`macos-latest`, beside `just swift check`). The + harness has no Swift runner yet. +- **UniFFI's JS glue.** ubrn, UniFFI's JS generator, does not support 0.32 yet, so only + its `.wasm` is sized. Browser per-call cost is + [#5478](https://github.com/moq-dev/moq/pull/5478)'s job. +- **The full moq-ffi through BoltFFI.** BoltFFI cannot bind moq-ffi without the API + changes listed below, so its numbers come from the slice alone. +- Go, Dart, C++, and C, which BoltFFI lacks or has only in part. Startup and memory. + +## Where the gap comes from + +The async model is the same: both generators poll a Rust future and resume it from a +continuation. The difference is in each backend's calling convention. + +- **Python**: UniFFI calls through `ctypes` and serializes records into a + `RustBuffer`. BoltFFI generates a CPython C extension, so a call is a C function + call. +- **Kotlin**: UniFFI calls through JNA, which builds per-call structures by reflection + ([Kotlin calls](/quest/m2/kt-ffi-calls.md)). BoltFFI generates JNI glue and passes + buffers as direct `ByteBuffer`s. BoltFFI's 6 ns no-op call is about the JNI floor. +- **Bytes**: UniFFI copies `Vec` one byte at a time + ([Payload bytes](/quest/m2/ffi-bytes.md)). BoltFFI copies in bulk, which is why its + advantage grows with frame size in Python. + +None of these needs BoltFFI's core. Each is a backend change that a UniFFI fork can +make, and BoltFFI's numbers are the targets to hit. + +## Gaps found in BoltFFI 0.31.1 + +Each was hit while binding the slice, about 300 lines. + +| gap | workaround in the slice | cost to close in a fork | +|---|---|---| +| The scanner does not see markers inside `cfg_attr` | literal markers plus `uniffi-shim/` | S: expand `cfg_attr` in the scanner | +| The scanner ignores target `cfg`, so `cfg(target_arch = "wasm32")` items are bound everywhere | a `client` cargo feature | M: evaluate target cfgs while scanning, which moq-ffi's wasm gating needs | +| It scans any local path dependency with an item-level `#[error]`, so thiserror crates count as BoltFFI crates | git dependencies | S | +| It resolves `#[path]` from the wrong directory and fails on moq-tokio | git dependencies | S | +| `#[error]` collides with thiserror's attribute | a hand-written `Display` | S, or rename the marker | +| `Arc` is rejected in signatures | classes by value, arguments by `&T` | M: moq-ffi returns `Arc` everywhere | +| The Python backend cannot return `Option`, and by default it skips the whole class silently | `recv_group` returns `Ended` as an error; `--deny-skipped` | S to M | +| Classes must be `Send + Sync` on wasm, and `single_threaded` cannot be target-conditional | `unsafe impl Send/Sync` on wasm32 | M | +| A panic inside an async export aborts the process ("panic in a function that cannot unwind"); UniFFI raises `InternalError` | fixed the panic | S: `catch_unwind` in the generated poll | +| Its macros emit the FFI only under its CLI's build (`--cfg boltffi_binding_expansion`), so a plain `cargo build` exports nothing, and the CLI accepts only `debug` and `release` profiles | `CARGO_PROFILE_RELEASE_OPT_LEVEL=z` | S | +| `pack wasm` needs `wasm-opt`, `tsc`, and `@types/node` on hand | installed under `target/ffi-gen` | S | + +Async constructors work: `MoqSession::dial` binds in Python, Kotlin, Swift, and TS, and +its error path ran from Python under both generators, although +[boltffi#95](https://github.com/boltffi/boltffi/issues/95) is still open. TS types it as +`Promise`. + +## UniFFI costs found + +- An error field named `message` clashes with `Throwable.message` in Kotlin, so the + slice uses `reason`. +- Two `#[cfg]`-gated variants of one method inside an exported `impl` both emit + UniFFI's scaffolding, so its symbols are defined twice. +- `Option` needs `Option>`. +- The known ones: three external generator forks that break on each minor release, the + per-byte `Vec` copy, bare-name type keys + ([mozilla/uniffi-rs#3026](https://github.com/mozilla/uniffi-rs/issues/3026)), and the + call overhead above. + +## Carrying cost + +### moq's eight targets + +| target | from UniFFI | from BoltFFI | +|---|---|---| +| Python | in core | in core | +| Kotlin | in core | in core | +| Swift | in core | in core | +| Go | `kixelated/uniffi-bindgen-go`: import | none: write a backend, L | +| Dart | `kixelated/uniffi-dart`: import | unfinished: finish it, M to L | +| C++ | `kixelated/uniffi-bindgen-cpp`: import | none: write a backend, L | +| C | `uniffi-bindgen-cpp`'s C backend: import | sync only: add async, M | +| browser TS | ubrn, once it supports 0.32 | in core, with its own loader and runtime | + +BoltFFI's existing backends run 6k (C) to 18k (Python) lines each, so each missing +backend is a similar rewrite rather than a port: UniFFI's generators are template-based +and do not translate to BoltFFI's IR. + +### Planned fork work + +| work | from UniFFI | from BoltFFI | +|---|---|---| +| Bulk byte copies | M: [Payload bytes](/quest/m2/ffi-bytes.md) | done | +| Call overhead | M: JNI or JNA direct mapping for Kotlin ([Kotlin calls](/quest/m2/kt-ffi-calls.md)); L: a C-extension Python backend, not yet planned | done | +| Zero-copy byte returns | M: [Zero-copy byte returns](/quest/m2/ffi-bytes-return.md) | M: bytes cross its wire buffer, with no borrowed return | +| Namespaced types | M: [Namespaced external types](/quest/m2/uniffi-namespaces.md) | M: same-name types collide (RFC #700, open) | +| Poll-native async | M: [Poll-native async](/quest/m2/ffi-poll.md) | M: the same poll-plus-continuation model, with per-method poll exports | +| Binding moq-ffi as written | none | M: drop `Arc` returns, rework wasm gating, and fix the gaps above | + +Either way the fork owns a large codebase. BoltFFI's is about 134k lines, written mostly +by one maintainer, and is at 0.31 with breaking minor releases. + +## Recommendation + +Fork **UniFFI**. BoltFFI is much faster, with 30 to 1,300 times cheaper sync calls and +3 to 36 times higher throughput, and slightly smaller. But it lacks Go and C++, has only +part of Dart and C, and broke in eleven ways on a 300-line slice before moq-ffi's real +surface. Its speed comes from backend calling conventions (a CPython extension, JNI, and +bulk copies), not from its core. A UniFFI fork can adopt each one backend at a time, +using these numbers as targets, while keeping all eight targets working throughout. + +Pick BoltFFI instead if call overhead outweighs everything else and writing the Go and +C++ backends is acceptable. The browser half is decided separately in #5478. diff --git a/bench/ffi-gen/boltffi.toml b/bench/ffi-gen/boltffi.toml new file mode 100644 index 0000000000..171ecd8460 --- /dev/null +++ b/bench/ffi-gen/boltffi.toml @@ -0,0 +1,46 @@ +# BoltFFI's build of the slice; `run.sh` drives it. Generated output stays under target/. +[package] +name = "moq_ffi_gen" +crate = "moq_ffi_gen" + +[cargo] +global_args = ["--features=boltffi"] + +[targets.python] +enabled = true +output = "../../target/ffi-gen/boltffi/python" + +[targets.android] +enabled = true +output = "../../target/ffi-gen/boltffi/android" + +[targets.android.kotlin] +package = "dev.moq.gen" +output = "../../target/ffi-gen/boltffi/kotlin" + +[targets.apple] +enabled = true +output = "../../target/ffi-gen/boltffi/apple" + +[targets.apple.swift] +output = "../../target/ffi-gen/boltffi/swift" + +[targets.wasm] +enabled = true +output = "../../target/ffi-gen/boltffi/wasm" +# Only `debug` or `release`: moq-wasm's size-tuned `wasm-release` is not selectable. +profile = "release" +# No `client`: the browser dials through web-transport-wasm, outside the slice. +cargo_args = ["--no-default-features"] + +# moq-wasm ships without wasm-opt, and the dev shell has none. +[targets.wasm.optimize] +enabled = false + +[targets.wasm.typescript] +output = "../../target/ffi-gen/boltffi/typescript" + +[targets.wasm.npm] +output = "../../target/ffi-gen/boltffi/npm" +package_name = "moq-ffi-gen" +targets = ["web"] diff --git a/bench/ffi-gen/examples/bench.rs b/bench/ffi-gen/examples/bench.rs new file mode 100644 index 0000000000..d5734a371d --- /dev/null +++ b/bench/ffi-gen/examples/bench.rs @@ -0,0 +1,177 @@ +//! The Rust floor: py/bench.py's rounds and calls on the slice's API, with no boundary. +//! +//! Built with the `uniffi` feature only because the slice needs one generator's markers +//! to compile; nothing here crosses the FFI. + +use std::hint::black_box; +use std::time::{Duration, Instant}; + +use moq_ffi_gen::*; + +const WARMUP: u64 = 3; +const CACHE_BYTES: u64 = 64 << 20; +const SIZES: [usize; 5] = [100, 1024, 16 * 1024, 64 * 1024, 1024 * 1024]; + +fn budget() -> Duration { + let seconds = std::env::var("MOQ_BENCH_SECONDS").map_or(1.0, |s| s.parse().expect("MOQ_BENCH_SECONDS")); + Duration::from_secs_f64(seconds) +} + +fn cpu() -> Duration { + let mut usage = std::mem::MaybeUninit::::uninit(); + // SAFETY: getrusage fills the struct it is handed and returns non-zero only for a bad `who`. + let usage = unsafe { + assert_eq!(libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()), 0); + usage.assume_init() + }; + let time = |t: libc::timeval| Duration::new(t.tv_sec as u64, t.tv_usec as u32 * 1000); + time(usage.ru_utime) + time(usage.ru_stime) +} + +fn frames(size: usize) -> u64 { + ((8 << 20) / size).clamp(1, 64) as u64 +} + +struct Report(Vec); + +impl Report { + fn push(&mut self, name: String, unit: &str, value: f64) { + self.0 + .push(format!(r#"{{"name": "{name}", "unit": "{unit}", "value": {value}}}"#)); + } + + async fn throughput(&mut self, size: usize) { + let origin = MoqOriginProducer::new(CACHE_BYTES); + let track = origin + .create_broadcast("bench".into()) + .unwrap() + .publish_track("0".into()) + .unwrap(); + let consumer = std::sync::Arc::new(track.consume().unwrap()); + let payload: Vec = (0..size).map(|i| (i % 251) as u8).collect(); + let count = frames(size); + + let step = async |n: u64| { + let reader = { + let (consumer, payload) = (consumer.clone(), payload.clone()); + tokio::spawn(async move { + let group = consumer.recv_group().await.unwrap(); + for f in 0..count { + let frame = group.read_frame().await.unwrap().expect("group ended"); + assert!(frame.payload == payload, "corrupt frame {f} of round {n}"); + assert_eq!(frame.timestamp_us, Some(n * count + f)); + } + }) + }; + let group = track.append_group().unwrap(); + for f in 0..count { + let frame = MoqFrame { + payload: payload.clone(), + timestamp_us: Some(n * count + f), + }; + group.write_frame(frame).unwrap(); + } + group.finish().unwrap(); + reader.await.unwrap(); + }; + + for n in 0..WARMUP { + step(n).await; + } + let (start, cpu_start, budget) = (Instant::now(), cpu(), budget()); + let mut n = WARMUP; + while start.elapsed() < budget { + step(n).await; + n += 1; + } + let (wall, used) = (start.elapsed(), cpu() - cpu_start); + let mb = ((n - WARMUP) * count * size as u64) as f64 / 1e6; + self.push(format!("throughput/{size}"), "MB/s", mb / wall.as_secs_f64()); + self.push(format!("cpu/{size}"), "ms/MB", used.as_secs_f64() * 1e3 / mb); + } + + fn call(&mut self, name: &str, mut call: impl FnMut()) { + let (start, budget) = (Instant::now(), budget()); + let mut calls = 0u64; + while start.elapsed() < budget { + for _ in 0..1000 { + call(); + } + calls += 1000; + } + self.push( + format!("call/{name}"), + "ns", + start.elapsed().as_nanos() as f64 / calls as f64, + ); + } + + async fn call_async>(&mut self, name: &str, mut call: impl FnMut() -> F) { + let (start, budget) = (Instant::now(), budget()); + let mut calls = 0u64; + while start.elapsed() < budget { + for _ in 0..1000 { + call().await; + } + calls += 1000; + } + self.push( + format!("call/{name}"), + "ns", + start.elapsed().as_nanos() as f64 / calls as f64, + ); + } +} + +async fn run() { + let mut report = Report(Vec::new()); + for size in SIZES { + report.throughput(size).await; + } + + let origin = MoqOriginProducer::new(CACHE_BYTES); + let track = origin + .create_broadcast("calls".into()) + .unwrap() + .publish_track("calls".into()) + .unwrap(); + let mut group = track.append_group().unwrap(); + let consumer = track.consume().unwrap(); + report.call("noop", || { + noop(); + black_box(()); + }); + report.call("sequence", || { + black_box(black_box(&group).sequence()); + }); + report.call("info", || { + black_box(black_box(&consumer).info()); + }); + let mut writes = 0u64; + report.call("write_frame/100", || { + writes += 1; + if writes.is_multiple_of(1000) { + group.finish().unwrap(); + group = track.append_group().unwrap(); + } + let frame = MoqFrame { + payload: vec![0; 100], + timestamp_us: Some(0), + }; + group.write_frame(frame).unwrap(); + }); + report.call_async("noop_async", noop_async).await; + report.call_async("yield_once", yield_once).await; + + println!( + "{{\"runner\": \"rust\", \"results\": [\n {}\n ]}}", + report.0.join(",\n ") + ); +} + +fn main() { + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(run()); +} diff --git a/bench/ffi-gen/kt/boltffi/src/Gen.kt b/bench/ffi-gen/kt/boltffi/src/Gen.kt new file mode 100644 index 0000000000..25ddacf0d6 --- /dev/null +++ b/bench/ffi-gen/kt/boltffi/src/Gen.kt @@ -0,0 +1,12 @@ +package bench + +// The runner's names for BoltFFI's bindings. +typealias MoqBroadcastProducer = dev.moq.gen.MoqBroadcastProducer +typealias MoqFrame = dev.moq.gen.MoqFrame +typealias MoqOriginProducer = dev.moq.gen.MoqOriginProducer + +fun noop() = dev.moq.gen.noop() + +suspend fun noopAsync() = dev.moq.gen.noopAsync() + +suspend fun yieldOnce() = dev.moq.gen.yieldOnce() diff --git a/bench/ffi-gen/kt/build.gradle.kts b/bench/ffi-gen/kt/build.gradle.kts new file mode 100644 index 0000000000..9172eaf0bf --- /dev/null +++ b/bench/ffi-gen/kt/build.gradle.kts @@ -0,0 +1,41 @@ +// Same Kotlin and coroutine versions as kt/, so the runner matches what the wrapper ships. +plugins { + kotlin("jvm") version "2.4.20" apply false +} + +// Build output lives with the rest of the harness's, under the repo's target/. +allprojects { + layout.buildDirectory.set(rootProject.file("../../../target/ffi-gen/kt/$name")) +} + +subprojects { + apply(plugin = "org.jetbrains.kotlin.jvm") + apply(plugin = "application") + + val bindings = System.getenv("MOQ_GEN_BINDINGS") ?: "missing-MOQ_GEN_BINDINGS" + val lib = System.getenv("MOQ_GEN_LIB") ?: "missing-MOQ_GEN_LIB" + + extensions.configure { + jvmToolchain(17) + sourceSets.getByName("main").kotlin.srcDirs("../common", "src", bindings) + } + + dependencies { + "implementation"("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0") + } + + extensions.configure { + mainClass.set("bench.BenchKt") + applicationDefaultJvmArgs = listOf( + "-Djava.library.path=$lib", + "-Djna.library.path=$lib", + "-Dbench.generator=${project.name}", + ) + } +} + +project(":uniffi") { + dependencies { + "implementation"("net.java.dev.jna:jna:5.19.1") + } +} diff --git a/bench/ffi-gen/kt/common/Bench.kt b/bench/ffi-gen/kt/common/Bench.kt new file mode 100644 index 0000000000..b0afba4d2a --- /dev/null +++ b/bench/ffi-gen/kt/common/Bench.kt @@ -0,0 +1,130 @@ +package bench + +import java.io.File +import java.lang.management.ManagementFactory +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout + +// Kotlin runner for the generator comparison (see ../../README.md). The same rounds as +// py/bench.py, written against the names each generator's Gen.kt aliases. + +private const val WARMUP = 3 +private const val CACHE_BYTES: ULong = 67_108_864uL +private const val ROUND_TIMEOUT_MS = 30_000L +private val SIZES = listOf(100, 1024, 16 * 1024, 64 * 1024, 1024 * 1024) +private val budgetNs = ((System.getenv("MOQ_BENCH_SECONDS")?.toDouble() ?: 1.0) * 1e9).toLong() + +private fun cpuNs(): Long = + (ManagementFactory.getOperatingSystemMXBean() as com.sun.management.OperatingSystemMXBean).processCpuTime + +/** Frames per group, holding a group under moq-net's 32 MB group cache. */ +private fun frames(size: Int) = ((8 shl 20) / size).coerceIn(1, 64) + +private class Result(val name: String, val unit: String, val value: Double) + +private suspend fun throughput(size: Int, results: MutableList) = coroutineScope { + val origin = MoqOriginProducer(CACHE_BYTES) + val broadcast = origin.createBroadcast("bench") + val track = broadcast.publishTrack("0") + val consumer = track.consume() + // A non-uniform payload, so a zero-filled or truncated copy fails the check. + val payload = ByteArray(size) { (it % 251).toByte() } + val count = frames(size) + + suspend fun step(n: Long) { + val reader = async { + consumer.recvGroup().use { group -> + repeat(count) { f -> + val frame = checkNotNull(group.readFrame()) { "group ended" } + check(frame.payload.contentEquals(payload)) { "corrupt frame $f of round $n" } + check(frame.timestampUs == (n * count + f).toULong()) + } + } + } + track.appendGroup().use { group -> + repeat(count) { f -> + group.writeFrame(MoqFrame(payload, (n * count + f).toULong())) + } + group.finish() + } + withTimeout(ROUND_TIMEOUT_MS) { reader.await() } + } + + repeat(WARMUP) { step(it.toLong()) } + val (start, cpuStart) = System.nanoTime() to cpuNs() + var n = WARMUP.toLong() + while (System.nanoTime() - start < budgetNs) { + step(n) + n++ + } + val (wallNs, cpuUsedNs) = (System.nanoTime() - start) to (cpuNs() - cpuStart) + val mb = (n - WARMUP) * count.toLong() * size / 1e6 + results += Result("throughput/$size", "MB/s", mb / (wallNs / 1e9)) + results += Result("cpu/$size", "ms/MB", cpuUsedNs / 1e6 / mb) + listOf(consumer, track, broadcast, origin).forEach { it.close() } +} + +/** Time [fn] twice and keep the second, so the JIT has compiled the call path. */ +private inline fun call(name: String, results: MutableList, fn: () -> Unit) { + var perCall = 0.0 + repeat(2) { + val start = System.nanoTime() + var calls = 0L + while (System.nanoTime() - start < budgetNs) { + repeat(1000) { fn() } + calls += 1000 + } + perCall = (System.nanoTime() - start).toDouble() / calls + } + results += Result("call/$name", "ns", perCall) +} + +/** A finished track surfaces as the slice's `Ended` error on both generators. */ +private suspend fun ended() { + MoqBroadcastProducer().use { broadcast -> + val track = broadcast.publishTrack("ended") + val consumer = track.consume() + track.finish() + val err = runCatching { consumer.recvGroup() }.exceptionOrNull() + check(err != null && err::class.simpleName == "Ended") { "expected Ended, got $err" } + } +} + +fun main(args: Array) { + val results = mutableListOf() + runBlocking { + ended() + SIZES.forEach { throughput(it, results) } + } + + val origin = MoqOriginProducer(CACHE_BYTES) + val track = origin.createBroadcast("calls").publishTrack("calls") + var group = track.appendGroup() + val consumer = track.consume() + call("noop", results) { noop() } + call("sequence", results) { group.sequence() } + call("info", results) { consumer.info() } + + // A fresh group every 1000 frames, so the origin's cache can evict the old ones. + val frame = MoqFrame(ByteArray(100), 0uL) + var writes = 0L + call("write_frame/100", results) { + if (++writes % 1000 == 0L) { + group.finish() + group.close() + group = track.appendGroup() + } + group.writeFrame(frame) + } + runBlocking { + call("noop_async", results) { noopAsync() } + call("yield_once", results) { yieldOnce() } + } + + val json = results.joinToString(",\n ", "[\n ", "\n ]") { + """{"name": "${it.name}", "unit": "${it.unit}", "value": ${it.value}}""" + } + File(args[0]).writeText("""{"runner": "kotlin-${System.getProperty("bench.generator")}", "results": $json}""") +} diff --git a/bench/ffi-gen/kt/settings.gradle.kts b/bench/ffi-gen/kt/settings.gradle.kts new file mode 100644 index 0000000000..d99b633b46 --- /dev/null +++ b/bench/ffi-gen/kt/settings.gradle.kts @@ -0,0 +1,17 @@ +// The Kotlin runner, built once per generator. `run.sh` points each build at that +// generator's bindings (MOQ_GEN_BINDINGS) and native library (MOQ_GEN_LIB). +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + } +} + +rootProject.name = "ffi-gen" +include(":uniffi", ":boltffi") diff --git a/bench/ffi-gen/kt/uniffi/src/Gen.kt b/bench/ffi-gen/kt/uniffi/src/Gen.kt new file mode 100644 index 0000000000..65d2083965 --- /dev/null +++ b/bench/ffi-gen/kt/uniffi/src/Gen.kt @@ -0,0 +1,12 @@ +package bench + +// The runner's names for UniFFI's bindings. +typealias MoqBroadcastProducer = uniffi.moq_ffi_gen.MoqBroadcastProducer +typealias MoqFrame = uniffi.moq_ffi_gen.MoqFrame +typealias MoqOriginProducer = uniffi.moq_ffi_gen.MoqOriginProducer + +fun noop() = uniffi.moq_ffi_gen.noop() + +suspend fun noopAsync() = uniffi.moq_ffi_gen.noopAsync() + +suspend fun yieldOnce() = uniffi.moq_ffi_gen.yieldOnce() diff --git a/bench/ffi-gen/py/bench.py b/bench/ffi-gen/py/bench.py new file mode 100644 index 0000000000..c82e309573 --- /dev/null +++ b/bench/ffi-gen/py/bench.py @@ -0,0 +1,137 @@ +"""Python runner for the generator comparison (see ../README.md). + +Runs against whichever generator's `moq_ffi_gen` module is on the path; both bind the +same Rust and expose the same Python names. Prints a JSON report to stdout. + +The throughput rounds mirror bench/ffi.py's: one publisher writes a group from this +task while one subscriber reads it on another, and every frame is checked byte for byte. +""" + +import asyncio +import json +import os +import resource +import sys +import time +from typing import Awaitable, Callable + +import moq_ffi_gen as gen + +WARMUP = 3 +CACHE_BYTES = 64 << 20 +BUDGET = float(os.environ.get("MOQ_BENCH_SECONDS", "1")) +ROUND_TIMEOUT = 30 +SIZES = (100, 1024, 16 * 1024, 64 * 1024, 1024 * 1024) + + +def cpu() -> float: + usage = resource.getrusage(resource.RUSAGE_SELF) + return usage.ru_utime + usage.ru_stime + + +def frames(size: int) -> int: + """Frames per group, holding a group under moq-net's 32 MB group cache.""" + return min(max((8 << 20) // size, 1), 64) + + +async def throughput(size: int, results: list[dict]) -> None: + origin = gen.MoqOriginProducer(CACHE_BYTES) + track = origin.create_broadcast("bench").publish_track("0") + consumer = track.consume() + # A non-uniform payload, so a zero-filled or truncated copy fails the check. + payload = bytes(i % 251 for i in range(size)) + count = frames(size) + + async def read(n: int) -> None: + group = await consumer.recv_group() + for f in range(count): + frame = await group.read_frame() + assert frame is not None, "group ended" + assert frame.payload == payload, f"corrupt frame {f} of round {n}" + assert frame.timestamp_us == n * count + f + + async def step(n: int) -> None: + reader = asyncio.create_task(read(n)) + group = track.append_group() + for f in range(count): + group.write_frame(gen.MoqFrame(payload=payload, timestamp_us=n * count + f)) + group.finish() + await asyncio.wait_for(reader, ROUND_TIMEOUT) + + for n in range(WARMUP): + await step(n) + start, cpu_start, n = time.perf_counter(), cpu(), WARMUP + while time.perf_counter() - start < BUDGET: + await step(n) + n += 1 + wall, used = time.perf_counter() - start, cpu() - cpu_start + mb = (n - WARMUP) * count * size / 1e6 + results.append({"name": f"throughput/{size}", "unit": "MB/s", "value": mb / wall}) + results.append({"name": f"cpu/{size}", "unit": "ms/MB", "value": used * 1e3 / mb}) + + +def call(name: str, fn: Callable[[], object], results: list[dict]) -> None: + start, calls = time.perf_counter(), 0 + while time.perf_counter() - start < BUDGET: + for _ in range(1000): + fn() + calls += 1000 + results.append({"name": f"call/{name}", "unit": "ns", "value": (time.perf_counter() - start) * 1e9 / calls}) + + +async def call_async(name: str, fn: Callable[[], Awaitable[object]], results: list[dict]) -> None: + start, calls = time.perf_counter(), 0 + while time.perf_counter() - start < BUDGET: + for _ in range(1000): + await fn() + calls += 1000 + results.append({"name": f"call/{name}", "unit": "ns", "value": (time.perf_counter() - start) * 1e9 / calls}) + + +async def ended() -> None: + """A finished track surfaces as the slice's `Ended` error on both generators.""" + track = gen.MoqBroadcastProducer().publish_track("ended") + consumer = track.consume() + track.finish() + try: + await consumer.recv_group() + except Exception as err: # UniFFI raises MoqError.Ended, BoltFFI MoqErrorException. + assert "Ended" in repr(err) or "Ended" in repr(getattr(err, "error", None)), repr(err) + return + raise AssertionError("recv_group on a finished track returned") + + +async def main() -> None: + results: list[dict] = [] + await ended() + for size in SIZES: + await throughput(size, results) + + origin = gen.MoqOriginProducer(CACHE_BYTES) + track = origin.create_broadcast("calls").publish_track("calls") + group = track.append_group() + consumer = track.consume() + call("noop", gen.noop, results) + call("sequence", group.sequence, results) + call("info", consumer.info, results) + + # A fresh group every 1000 frames, so the origin's cache can evict the old ones. + frame = gen.MoqFrame(payload=bytes(100), timestamp_us=0) + writes = [group, 0] + + def write() -> None: + writes[1] += 1 + if writes[1] % 1000 == 0: + writes[0].finish() + writes[0] = track.append_group() + writes[0].write_frame(frame) + + call("write_frame/100", write, results) + await call_async("noop_async", gen.noop_async, results) + await call_async("yield_once", gen.yield_once, results) + + json.dump({"runner": f"python-{sys.argv[1]}", "results": results}, sys.stdout, indent=2) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/bench/ffi-gen/report.py b/bench/ffi-gen/report.py new file mode 100644 index 0000000000..15983b57f8 --- /dev/null +++ b/bench/ffi-gen/report.py @@ -0,0 +1,68 @@ +"""Combine the runners' rounds into one Markdown table (see README.md). + +Each cell is the median across rounds, with the spread (min-max) beside it when there +are several. The last column per language is UniFFI's value over BoltFFI's, oriented so +that above 1 means BoltFFI is better: time and CPU divide UniFFI by BoltFFI, and +throughput divides BoltFFI by UniFFI. +""" + +import json +import statistics +import sys +from collections import defaultdict +from pathlib import Path + +RUNNERS = ["rust", "python-uniffi", "python-boltffi", "kotlin-uniffi", "kotlin-boltffi"] + + +def load(directory: Path) -> dict[str, dict[str, list[float]]]: + values: dict[str, dict[str, list[float]]] = defaultdict(lambda: defaultdict(list)) + for path in sorted(directory.glob("*.json")): + report = json.loads(path.read_text()) + for result in report["results"]: + values[report["runner"]][result["name"]].append(result["value"]) + return values + + +def cell(samples: list[float]) -> str: + if not samples: + return "-" + median = statistics.median(samples) + text = f"{median:.3g}" + if len(samples) > 1: + text += f" ({min(samples):.3g}-{max(samples):.3g})" + return text + + +def advantage(name: str, uniffi: list[float], boltffi: list[float]) -> str: + if not uniffi or not boltffi: + return "-" + u, b = statistics.median(uniffi), statistics.median(boltffi) + ratio = b / u if name.startswith("throughput/") else u / b + return f"{ratio:.3g}x" + + +def main() -> None: + directory = Path(sys.argv[1]) + values = load(directory) + names = list(dict.fromkeys(name for runner in RUNNERS for name in values.get(runner, {}))) + units = {} + for path in directory.glob("*.json"): + for result in json.loads(path.read_text())["results"]: + units[result["name"]] = result["unit"] + + header = ["case", "unit", *RUNNERS, "python gain", "kotlin gain"] + lines = ["| " + " | ".join(header) + " |", "|" + "---|" * len(header)] + for name in names: + row = [name, units[name]] + row += [cell(values.get(runner, {}).get(name, [])) for runner in RUNNERS] + for lang in ("python", "kotlin"): + row.append(advantage(name, values[f"{lang}-uniffi"][name], values[f"{lang}-boltffi"][name])) + lines.append("| " + " | ".join(row) + " |") + table = "\n".join(lines) + (directory / "report.md").write_text(table + "\n") + print(table) + + +if __name__ == "__main__": + main() diff --git a/bench/ffi-gen/run.sh b/bench/ffi-gen/run.sh new file mode 100755 index 0000000000..9a997ad8c8 --- /dev/null +++ b/bench/ffi-gen/run.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Build the slice with each generator, generate its bindings, then run the benchmarks +# and the size table. See README.md. +# +# bench/ffi-gen/run.sh [build|bench|size|all]... (default: all) +# +# MOQ_BENCH_SECONDS sets each case's time budget (default 1). MOQ_BENCH_ROUNDS sets how +# many times each runner repeats, alternating which generator goes first (default 3). +set -euo pipefail + +dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +root=$(cd "$dir/../.." && pwd) +out="$root/target/ffi-gen" +boltffi_version=0.31.1 +typescript_version=7.0.2 +types_node_version=26.6.4 + +host=$(rustc -vV | sed -n 's/^host: //p') +case "$host" in + *-apple-*) dylib=dylib os=darwin origin=@loader_path ;; + *-linux-*) dylib=so os=linux origin="\$ORIGIN" ;; + *) echo "ffi-gen: unsupported host $host" >&2 && exit 1 ;; +esac + +# The release library each language package loads. +lib() { + echo "$out/cargo/$1/release/libmoq_ffi_gen.$dylib" +} + +boltffi() { + (cd "$dir" && CARGO_TARGET_DIR="$out/cargo/boltffi" PATH="$out/node_modules/.bin:$PATH" \ + "$out/tools/bin/boltffi" "$@") +} + +build_tools() { + [[ -x "$out/tools/bin/boltffi" ]] || + cargo install --locked --quiet --root "$out/tools" "boltffi_cli@$boltffi_version" + # BoltFFI's wasm packaging transpiles its TypeScript with `tsc`, which resolves these + # from an ancestor of its output: the repo's versions, and the runtime its glue imports. + [[ -d "$out/node_modules/@boltffi/runtime" ]] || + npm install --silent --no-save --prefix "$out" "typescript@$typescript_version" "@types/node@$types_node_version" \ + "@boltffi/runtime@$boltffi_version" + [[ -x "$out/venv/bin/python" ]] || uv venv --quiet --seed --python "$(command -v python3)" "$out/venv" + uv pip install --quiet --python "$out/venv/bin/python" setuptools +} + +# moq's own UniFFI bindgen: the generator fork the language packages ship from. +bindgen() { + cargo run --locked --quiet --release --package uniffi-bindgen --features cli --manifest-path "$root/Cargo.toml" -- "$@" +} + +build_uniffi() { + cargo build --locked --release --features uniffi --manifest-path "$dir/Cargo.toml" --target-dir "$out/cargo/uniffi" + rm -rf "$out/uniffi" + for lang in python kotlin swift; do + bindgen generate --library "$(lib uniffi)" --language "$lang" --out-dir "$out/uniffi/$lang" --no-format + done + cp "$(lib uniffi)" "$out/uniffi/python/" + # The browser half: ubrn, UniFFI's JS generator, does not support 0.32 yet (#5478), + # so only the `.wasm` is measured, after wasm-bindgen strips it as it would ship. + cargo build --locked --profile wasm-release --target wasm32-unknown-unknown --no-default-features \ + --features uniffi --manifest-path "$dir/Cargo.toml" --target-dir "$out/cargo/uniffi" + wasm-bindgen --target web --out-dir "$out/uniffi/wasm" \ + "$out/cargo/uniffi/wasm32-unknown-unknown/wasm-release/moq_ffi_gen.wasm" +} + +build_boltffi() { + cargo build --locked --release --features boltffi --manifest-path "$dir/Cargo.toml" --target-dir "$out/cargo/boltffi" + rm -rf "$out/boltffi" + # --deny-skipped: fail rather than silently drop a declaration a backend cannot bind. + for target in python kotlin swift; do + boltffi generate "$target" --deny-skipped + done + # Python is a CPython extension module, compiled against the release library. + boltffi pack python --release --deny-skipped --python "$out/venv/bin/python" + uv pip install --quiet --python "$out/venv/bin/python" --reinstall --no-deps \ + --target "$out/boltffi/python-site" "$out"/boltffi/python/wheelhouse/*.whl + # Kotlin on a desktop JVM loads JNI glue, linked against the release library. + local java_home="${JAVA_HOME:-$(dirname "$(dirname "$(readlink -f "$(command -v java)")")")}" + cc -shared -fPIC -O2 -o "$(dirname "$(lib boltffi)")/libmoq_ffi_gen_jni.$dylib" \ + -I"$out/boltffi/kotlin/jni" -I"$java_home/include" -I"$java_home/include/$os" \ + "$out/boltffi/kotlin/jni/jni_glue.c" -L"$(dirname "$(lib boltffi)")" -lmoq_ffi_gen -Wl,-rpath,"$origin" + # The browser package: `.wasm`, loader, and TypeScript glue. BoltFFI's macros emit the + # FFI only under its CLI's build, which accepts no custom profile, so `release` is + # overridden to match `wasm-release`. + CARGO_PROFILE_RELEASE_OPT_LEVEL=z boltffi pack wasm --release --deny-skipped +} + +# moq-ffi and moq-wasm themselves, for the size table's reference rows. +build_reference() { + cargo build --locked --release --package moq-ffi --manifest-path "$root/Cargo.toml" --target-dir "$out/cargo/moq-ffi" + cp "$out/cargo/moq-ffi/release/libmoq_ffi.$dylib" "$out/cargo/moq-ffi/libmoq_ffi-default.$dylib" + rm -rf "$out/moq-ffi" + for lang in python kotlin swift; do + bindgen generate --library "$out/cargo/moq-ffi/libmoq_ffi-default.$dylib" --language "$lang" \ + --out-dir "$out/moq-ffi/$lang" --no-format + done + cargo build --locked --release --package moq-ffi --no-default-features --manifest-path "$root/Cargo.toml" \ + --target-dir "$out/cargo/moq-ffi" + cp "$out/cargo/moq-ffi/release/libmoq_ffi.$dylib" "$out/cargo/moq-ffi/libmoq_ffi-no-default.$dylib" + cargo build --locked --profile wasm-release --target wasm32-unknown-unknown --package moq-ffi \ + --manifest-path "$root/Cargo.toml" --target-dir "$out/cargo/moq-ffi" + wasm-bindgen --target web --out-dir "$out/moq-ffi/wasm" \ + "$out/cargo/moq-ffi/wasm32-unknown-unknown/wasm-release/moq_ffi.wasm" + cargo build --locked --profile wasm-release --target wasm32-unknown-unknown --package moq-wasm \ + --manifest-path "$root/Cargo.toml" --target-dir "$out/cargo/moq-wasm" + rm -rf "$out/moq-wasm" + wasm-bindgen --target web --out-name moq --out-dir "$out/moq-wasm" \ + "$out/cargo/moq-wasm/wasm32-unknown-unknown/wasm-release/moq_wasm.wasm" +} + +# The same rounds in Rust with no boundary: the floor both generators add to. +bench_rust() { + cargo run --locked --quiet --release --example bench --features uniffi --manifest-path "$dir/Cargo.toml" \ + --target-dir "$out/cargo/rust" >"$1" +} + +bench_python() { + local path + case "$1" in + uniffi) path="$out/uniffi/python" ;; + boltffi) path="$out/boltffi/python-site" ;; + esac + PYTHONPATH="$path" "$out/venv/bin/python" "$dir/py/bench.py" "$1" >"$2" +} + +bench_kotlin() { + MOQ_GEN_LIB="$(dirname "$(lib "$1")")" MOQ_GEN_BINDINGS="$out/$1/kotlin" \ + gradle --quiet --project-cache-dir "$out/gradle" -p "$dir/kt" ":$1:run" --args="$2" +} + +bench() { + rm -rf "$out/results" + mkdir -p "$out/results" + local rounds=${MOQ_BENCH_ROUNDS:-3} order + for ((round = 0; round < rounds; round++)); do + # Alternate which generator goes first, so drift on a shared host hits both. + order=(uniffi boltffi) + ((round % 2)) && order=(boltffi uniffi) + echo "ffi-gen: round $round rust" >&2 + bench_rust "$out/results/rust-$round.json" + for gen in "${order[@]}"; do + echo "ffi-gen: round $round python $gen" >&2 + bench_python "$gen" "$out/results/python-$gen-$round.json" + done + for gen in "${order[@]}"; do + echo "ffi-gen: round $round kotlin $gen" >&2 + bench_kotlin "$gen" "$out/results/kotlin-$gen-$round.json" + done + done + python3 "$dir/report.py" "$out/results" +} + +step() { + case "$1" in + build) + build_tools + build_uniffi + build_boltffi + ;; + bench) bench ;; + # Only the size table needs moq-ffi and moq-wasm, which take longer to build than + # the slice, so the nightly's `build bench` skips them. + size) + build_reference + python3 "$dir/size.py" "$out" + ;; + all) + step build + step bench + step size + ;; + *) + echo "usage: $0 [build|bench|size|all]..." >&2 + exit 1 + ;; + esac +} + +for arg in "${@:-all}"; do + step "$arg" +done diff --git a/bench/ffi-gen/size.py b/bench/ffi-gen/size.py new file mode 100644 index 0000000000..ed6e0a2b8f --- /dev/null +++ b/bench/ffi-gen/size.py @@ -0,0 +1,144 @@ +"""Print the size table from `run.sh build`'s outputs (see README.md). + +Bytes as built, stripped, and gzip -9. Stripping is `strip --strip-all` on a copy of a +native library, and dropping the `name` section of a `.wasm`, as the root `release` +profile's `strip = "symbols"` ships moq-wasm. +Generated bindings count every file a package ships for that language; JS glue is the +unminified ES modules a web build loads. Every `.wasm` is at opt-level z after +wasm-bindgen, without wasm-opt, as moq-wasm ships. +""" + +import gzip +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path + +out = Path(sys.argv[1]) + + +def files(*patterns: str) -> list[Path]: + found = [path for pattern in patterns for path in sorted(out.glob(pattern)) if path.is_file()] + if not found: + raise SystemExit(f"size: nothing matches {patterns} under {out}; run `run.sh build` first") + return found + + +def raw(paths: list[Path]) -> int: + return sum(path.stat().st_size for path in paths) + + +def gz(paths: list[Path]) -> int: + return sum(len(gzip.compress(path.read_bytes(), 9)) for path in paths) + + +def stripped(paths: list[Path]) -> int: + total = 0 + with tempfile.TemporaryDirectory() as tmp: + for path in paths: + copy = Path(tmp) / path.name + shutil.copy(path, copy) + subprocess.run(["strip", "--strip-all", str(copy)], check=True) + total += copy.stat().st_size + return total + + +def leb128(data: bytes, i: int) -> tuple[int, int]: + value = shift = 0 + while True: + byte = data[i] + i += 1 + value |= (byte & 0x7F) << shift + shift += 7 + if byte < 0x80: + return value, i + + +def unnamed(path: Path) -> bytes: + """The module without its `name` custom section, as `strip = "symbols"` ships it.""" + data = path.read_bytes() + kept, i = [data[:8]], 8 + while i < len(data): + start = i + section = data[i] + size, i = leb128(data, i + 1) + body = data[i : i + size] + i += size + if section == 0: + length, j = leb128(body, 0) + if body[j : j + length] == b"name": + continue + kept.append(data[start:i]) + return b"".join(kept) + + +def kib(n: int) -> str: + return f"{n / 1024:,.0f} KiB" + + +def table(title: str, header: list[str], rows: list[list[str]]) -> None: + print(f"\n### {title}\n") + print("| " + " | ".join(header) + " |") + print("|" + "---|" * len(header)) + for row in rows: + print("| " + " | ".join(row) + " |") + + +native = [ + ("slice, UniFFI", files("cargo/uniffi/release/libmoq_ffi_gen.so", "cargo/uniffi/release/libmoq_ffi_gen.dylib")), + ("slice, BoltFFI", files("cargo/boltffi/release/libmoq_ffi_gen.so", "cargo/boltffi/release/libmoq_ffi_gen.dylib")), + ("BoltFFI Python extension (`_native`)", files("boltffi/python-site/moq_ffi_gen/_native*")), + ("BoltFFI Kotlin JNI glue", files("cargo/boltffi/release/libmoq_ffi_gen_jni.*")), + ("moq-ffi, default features", files("cargo/moq-ffi/libmoq_ffi-default.*")), + ("moq-ffi, no default features", files("cargo/moq-ffi/libmoq_ffi-no-default.*")), +] +table( + "Native libraries", + ["library", "as built", "stripped"], + [[name, kib(raw(p)), kib(stripped(p))] for name, p in native], +) + +bindings = [ + ("slice, UniFFI", "Python", files("uniffi/python/*.py")), + ("slice, BoltFFI", "Python", files("boltffi/python-site/moq_ffi_gen/__init__.py*")), + ("slice, UniFFI", "Kotlin", files("uniffi/kotlin/**/*.kt")), + ("slice, BoltFFI", "Kotlin", files("boltffi/kotlin/**/*.kt")), + ("slice, UniFFI", "Swift", files("uniffi/swift/*")), + ("slice, BoltFFI", "Swift", files("boltffi/swift/*")), + ("moq-ffi, UniFFI", "Python", files("moq-ffi/python/*.py")), + ("moq-ffi, UniFFI", "Kotlin", files("moq-ffi/kotlin/**/*.kt")), + ("moq-ffi, UniFFI", "Swift", files("moq-ffi/swift/*")), +] +table( + "Generated bindings (source)", + ["bindings", "language", "files", "raw", "gzip"], + [[name, lang, str(len(p)), kib(raw(p)), kib(gz(p))] for name, lang, p in bindings], +) + +runtime = files("node_modules/@boltffi/runtime/dist/*.js") +boltffi_glue = files( + "boltffi/npm/web.js", + "boltffi/npm/moq_ffi_gen.js", + "boltffi/npm/moq_ffi_gen_bg.js", + "boltffi/npm/moq_ffi_gen_imports.js", +) +wasm = [ + ("slice, UniFFI", files("uniffi/wasm/*.wasm"), None), + ("slice, BoltFFI", files("boltffi/npm/*.wasm"), boltffi_glue + runtime), + ("moq-ffi, UniFFI", files("moq-ffi/wasm/*.wasm"), None), + ("moq-wasm, wasm-bindgen", files("moq-wasm/*.wasm"), files("moq-wasm/moq.js")), +] + + +def wasm_row(name: str, module: list[Path], js: list[Path] | None) -> list[str]: + bare = [unnamed(path) for path in module] + row = [name, kib(raw(module)), kib(sum(map(len, bare))), kib(sum(len(gzip.compress(b, 9)) for b in bare))] + return row + ([kib(raw(js)), kib(gz(js))] if js else ["not generated"] * 2) + + +table( + "Browser", + ["module", ".wasm as built", ".wasm stripped", ".wasm stripped, gzip", "JS glue", "JS glue, gzip"], + [wasm_row(name, w, js) for name, w, js in wasm], +) diff --git a/bench/ffi-gen/src/client.rs b/bench/ffi-gen/src/client.rs new file mode 100644 index 0000000000..70d4231bed --- /dev/null +++ b/bench/ffi-gen/src/client.rs @@ -0,0 +1,75 @@ +//! Connecting to a relay: native only, like moq-ffi's QUIC client. The browser connects +//! through web-transport-wasm, which the slice leaves out. + +#[cfg(feature = "boltffi")] +use boltffi::export; +#[cfg(feature = "uniffi")] +use moq_ffi_gen_uniffi_shim::export; + +use crate::{MoqError, MoqOriginProducer, runtime}; + +/// Run `fut` on the worker runtime and await its result from the host's poll. +async fn on_runtime(fut: impl std::future::Future + Send + 'static) -> T { + runtime().spawn(fut).await.expect("runtime task panicked") +} + +/// Dials relays, publishing and consuming through one origin. +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqClient { + client: moq_tokio::Client, +} + +#[export] +impl MoqClient { + /// A client that dials once, with no reconnect. + #[cfg_attr(feature = "uniffi", uniffi::constructor)] + pub fn new() -> Result { + let mut connect = moq_tokio::connect::Config::default(); + connect.once = Some(true); + let _guard = runtime().enter(); + let client = connect + .init(moq_tokio::quic::Config::default()) + .map_err(|err| MoqError::Net { + reason: err.to_string(), + })?; + Ok(Self { client }) + } + + /// Connect to `url`, publishing and consuming through `origin`. Takes `&self`, which + /// BoltFFI binds. + pub async fn connect(&self, url: String, origin: &MoqOriginProducer) -> Result { + let url = url::Url::parse(&url).map_err(|err| MoqError::Net { + reason: err.to_string(), + })?; + let client = self + .client + .clone() + .with_publisher(origin.inner.consume()) + .with_subscriber(origin.inner.clone()); + // `connect` spawns its driver, so it runs on the runtime too. + let connection = on_runtime(async move { client.connect(url).established().await }) + .await + .map_err(|err| MoqError::Net { + reason: err.to_string(), + })?; + Ok(MoqSession { + _connection: connection, + }) + } +} + +/// An established connection, closed on drop. +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqSession { + _connection: moq_tokio::Connection, +} + +#[export] +impl MoqSession { + /// Dial `url` with a default client: an async constructor (boltffi/boltffi#95). + #[cfg_attr(feature = "uniffi", uniffi::constructor)] + pub async fn dial(url: String, origin: &MoqOriginProducer) -> Result { + let client = MoqClient::new()?; + client.connect(url, origin).await + } +} diff --git a/bench/ffi-gen/src/lib.rs b/bench/ffi-gen/src/lib.rs new file mode 100644 index 0000000000..b38bdc7d5d --- /dev/null +++ b/bench/ffi-gen/src/lib.rs @@ -0,0 +1,356 @@ +//! A slice of moq-ffi bound by either UniFFI or BoltFFI, so the generators compare on +//! the same Rust (see `README.md`). +//! +//! The `uniffi` or `boltffi` feature picks one generator per build. Exports carry +//! BoltFFI's markers literally, because BoltFFI scans source text and cannot see a marker +//! inside `cfg_attr`; the UniFFI build imports same-named shims that expand to UniFFI's +//! attributes. Only UniFFI's extra attributes (`Object`, `constructor`) use `cfg_attr`. +//! The shapes follow moq-ffi's, so a call costs what the same moq-ffi call does, minus +//! moq-ffi's extra bookkeeping. + +#[cfg(all(feature = "uniffi", feature = "boltffi"))] +compile_error!("enable one generator: `uniffi` or `boltffi`"); + +#[cfg(feature = "uniffi")] +uniffi::setup_scaffolding!(); + +#[cfg(feature = "boltffi")] +use boltffi::{data, error, export}; +#[cfg(feature = "uniffi")] +use moq_ffi_gen_uniffi_shim::{data, error, export}; + +use std::sync::Mutex; + +#[cfg(feature = "client")] +mod client; +#[cfg(feature = "client")] +pub use client::*; + +// No thiserror: its derive claims BoltFFI's bare `#[error]` marker as its own attribute. +// `reason`, not `message`: UniFFI's Kotlin exception would clash with `Throwable.message`. +/// An error crossing the boundary. +#[derive(Debug)] +#[error] +pub enum MoqError { + /// The handle was already closed. + Closed, + /// The track ended: no more groups. + Ended, + /// moq-net refused the operation. + Net { reason: String }, +} + +impl std::fmt::Display for MoqError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Closed => write!(f, "closed"), + Self::Ended => write!(f, "ended"), + Self::Net { reason } => write!(f, "{reason}"), + } + } +} + +impl std::error::Error for MoqError {} + +impl From for MoqError { + fn from(err: moq_net::Error) -> Self { + Self::Net { + reason: err.to_string(), + } + } +} + +/// A frame's payload and presentation timestamp. +#[derive(Clone)] +#[data] +pub struct MoqFrame { + pub payload: Vec, + pub timestamp_us: Option, +} + +/// A track's delivery metadata, the record the call benchmark returns. +#[derive(Clone)] +#[data] +pub struct MoqTrackInfo { + pub name: String, + pub priority: u8, + pub timescale: Option, +} + +/// Does nothing: the floor of a sync call. +#[export] +pub fn noop() {} + +/// Resolves at once: the floor of an async call. +#[export] +pub async fn noop_async() {} + +/// Returns pending once and wakes itself, so the host pays one continuation and re-poll. +#[export] +pub async fn yield_once() { + let mut yielded = false; + std::future::poll_fn(|cx| { + if yielded { + return std::task::Poll::Ready(()); + } + yielded = true; + cx.waker().wake_by_ref(); + std::task::Poll::Pending + }) + .await +} + +/// Publishes broadcasts under a cache budget, so a long benchmark keeps memory flat. +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqOriginProducer { + inner: moq_net::origin::Producer, +} + +#[export] +impl MoqOriginProducer { + /// An origin whose cached groups total at most `cache_capacity_bytes`. + #[cfg_attr(feature = "uniffi", uniffi::constructor)] + pub fn new(cache_capacity_bytes: u64) -> Self { + let mut config = moq_net::origin::Config::default(); + let cache = moq_net::cache::Config::default() + .with_capacity(cache_capacity_bytes) + .with_expiry(config.pool.expiry()); + config.pool = moq_net::cache::Pool::new(cache); + let (inner, driver) = moq_net::origin::Producer::new(config); + spawn(async move { + moq_net::time::run(driver).await; + }); + Self { inner } + } + + /// Create a broadcast at `path`. + pub fn create_broadcast(&self, path: String) -> Result { + Ok(MoqBroadcastProducer::from_inner( + self.inner.create_broadcast(path.as_str())?, + )) + } +} + +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqBroadcastProducer { + inner: moq_net::broadcast::Producer, +} + +impl MoqBroadcastProducer { + fn from_inner(inner: moq_net::broadcast::Producer) -> Self { + Self { inner } + } +} + +impl Default for MoqBroadcastProducer { + fn default() -> Self { + Self::new() + } +} + +#[export] +impl MoqBroadcastProducer { + /// A standalone broadcast, outside any origin. + #[cfg_attr(feature = "uniffi", uniffi::constructor)] + pub fn new() -> Self { + Self::from_inner(moq_net::broadcast::Info::new().produce()) + } + + /// Publish a track timed in microseconds. + pub fn publish_track(&self, name: String) -> Result { + let info = moq_net::track::Info::default().with_timescale(moq_net::Timescale::MICRO); + let track = self.inner.create_track(name, Some(info))?; + Ok(MoqTrackProducer { + inner: Mutex::new(Some(track)), + }) + } +} + +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqTrackProducer { + inner: Mutex>, +} + +#[export] +impl MoqTrackProducer { + /// Subscribe to this track locally. + pub fn consume(&self) -> Result { + let guard = self.inner.lock().unwrap(); + let track = guard.as_ref().ok_or(MoqError::Closed)?; + Ok(MoqTrackConsumer { + inner: Mutex::new(track.subscribe(None)), + }) + } + + /// Start the next group. + pub fn append_group(&self) -> Result { + let mut guard = self.inner.lock().unwrap(); + let track = guard.as_mut().ok_or(MoqError::Closed)?; + let group = track.append_group()?; + Ok(MoqGroupProducer { + sequence: group.sequence, + inner: Mutex::new(Some(group)), + }) + } + + /// End the track. + pub fn finish(&self) -> Result<(), MoqError> { + let mut guard = self.inner.lock().unwrap(); + guard.as_mut().ok_or(MoqError::Closed)?.finish()?; + Ok(()) + } +} + +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqGroupProducer { + sequence: u64, + inner: Mutex>, +} + +#[export] +impl MoqGroupProducer { + /// This group's sequence number: the plain sync getter the call benchmark times. + pub fn sequence(&self) -> u64 { + self.sequence + } + + /// Read this group back locally. + pub fn consume(&self) -> Result { + let guard = self.inner.lock().unwrap(); + let group = guard.as_ref().ok_or(MoqError::Closed)?; + Ok(MoqGroupConsumer::new(group.consume())) + } + + /// Append `frame`, copying its payload into Rust as moq-ffi does. + pub fn write_frame(&self, frame: MoqFrame) -> Result<(), MoqError> { + let timestamp = frame + .timestamp_us + .map(moq_net::Timestamp::from_micros) + .transpose() + .map_err(|err| MoqError::Net { + reason: err.to_string(), + })?; + let mut guard = self.inner.lock().unwrap(); + let group = guard.as_mut().ok_or(MoqError::Closed)?; + group.write_frame(timestamp, frame.payload)?; + Ok(()) + } + + /// Close the group; readers see its end. + pub fn finish(&self) -> Result<(), MoqError> { + let mut guard = self.inner.lock().unwrap(); + guard.as_mut().ok_or(MoqError::Closed)?.finish()?; + Ok(()) + } +} + +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqTrackConsumer { + inner: Mutex, +} + +#[export] +impl MoqTrackConsumer { + /// The track's metadata: the record the call benchmark returns. + pub fn info(&self) -> MoqTrackInfo { + let inner = self.inner.lock().unwrap(); + let info = inner.info(); + MoqTrackInfo { + name: inner.name().to_string(), + priority: info.priority, + timescale: info.timescale.map(|t| t.as_u64()), + } + } + + /// The next group in arrival order; [`MoqError::Ended`] once the track ends. + /// + /// moq-ffi returns `None` at the end instead, but neither generator takes that + /// shape as-is: UniFFI lowers an optional object only as `Option>`, which + /// BoltFFI cannot represent, and BoltFFI's Python backend skips any class with a + /// method returning `Option` for a class `T`. + pub async fn recv_group(&self) -> Result { + let group = kio::wait(|waiter| self.inner.lock().unwrap().poll_recv_group(waiter)).await?; + group.map(MoqGroupConsumer::new).ok_or(MoqError::Ended) + } +} + +#[cfg_attr(feature = "uniffi", derive(uniffi::Object))] +pub struct MoqGroupConsumer { + sequence: u64, + inner: Mutex, +} + +impl MoqGroupConsumer { + fn new(group: moq_net::group::Consumer) -> Self { + Self { + sequence: group.sequence, + inner: Mutex::new(group), + } + } +} + +#[export] +impl MoqGroupConsumer { + /// This group's sequence number. + pub fn sequence(&self) -> u64 { + self.sequence + } + + /// The next frame, copied out of Rust as moq-ffi does, or `None` once the group ends. + pub async fn read_frame(&self) -> Result, MoqError> { + let frame = kio::wait(|waiter| self.inner.lock().unwrap().poll_read_frame(waiter)).await?; + Ok(frame.map(|frame| MoqFrame { + payload: frame.payload.to_vec(), + timestamp_us: frame.timestamp.map(|t| t.as_micros() as u64), + })) + } +} + +/// The worker runtime that drives timers and I/O beside host-polled calls. +#[cfg(not(target_arch = "wasm32"))] +fn runtime() -> &'static tokio::runtime::Runtime { + static RUNTIME: std::sync::OnceLock = std::sync::OnceLock::new(); + RUNTIME.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .thread_name("moq-ffi-gen") + .enable_all() + .build() + .unwrap() + }) +} + +#[cfg(not(target_arch = "wasm32"))] +fn spawn(fut: impl std::future::Future + Send + 'static) { + runtime().spawn(fut); +} + +// BoltFFI requires every class to be `Send + Sync`, but moq-net's handles are not on +// wasm32. Its `#[export(single_threaded)]` opt-out cannot depend on the target, since +// BoltFFI ignores markers inside `cfg_attr`, so assert it here instead: wasm32 without +// threads never moves a handle across threads. UniFFI's `wasm-unstable-single-threaded` +// feature drops the same bounds. +#[cfg(all(feature = "boltffi", target_arch = "wasm32"))] +mod single_threaded { + use super::*; + + macro_rules! assume_single_threaded { + ($($ty:ty),*) => {$( + unsafe impl Send for $ty {} + unsafe impl Sync for $ty {} + )*}; + } + + assume_single_threaded!( + MoqOriginProducer, + MoqBroadcastProducer, + MoqTrackProducer, + MoqGroupProducer, + MoqTrackConsumer, + MoqGroupConsumer + ); +} + +#[cfg(target_arch = "wasm32")] +fn spawn(fut: impl std::future::Future + 'static) { + web_async::spawn(fut); +} diff --git a/bench/ffi-gen/uniffi-shim/Cargo.toml b/bench/ffi-gen/uniffi-shim/Cargo.toml new file mode 100644 index 0000000000..3295d24d0c --- /dev/null +++ b/bench/ffi-gen/uniffi-shim/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "moq-ffi-gen-uniffi-shim" +description = "BoltFFI's marker attributes, spelled as UniFFI's, for the UniFFI build of moq-ffi-gen" +version = "0.0.0" +edition = "2024" +license = "MIT OR Apache-2.0" +publish = false + +[lib] +proc-macro = true diff --git a/bench/ffi-gen/uniffi-shim/src/lib.rs b/bench/ffi-gen/uniffi-shim/src/lib.rs new file mode 100644 index 0000000000..915a568b25 --- /dev/null +++ b/bench/ffi-gen/uniffi-shim/src/lib.rs @@ -0,0 +1,31 @@ +//! BoltFFI's `#[export]`, `#[data]`, and `#[error]`, expanding to UniFFI's attributes. +//! +//! BoltFFI finds its markers by scanning source text, and only literal attributes count: +//! `#[cfg_attr(feature = "boltffi", data)]` is invisible to it. So the slice spells every +//! marker literally and imports these instead of BoltFFI's in the UniFFI build. + +use proc_macro::TokenStream; + +fn prefix(attr: &str, item: TokenStream) -> TokenStream { + let mut out: TokenStream = attr.parse().unwrap(); + out.extend(item); + out +} + +/// `#[uniffi::export]`. +#[proc_macro_attribute] +pub fn export(_args: TokenStream, item: TokenStream) -> TokenStream { + prefix("#[::uniffi::export]", item) +} + +/// `#[derive(uniffi::Record)]`. +#[proc_macro_attribute] +pub fn data(_args: TokenStream, item: TokenStream) -> TokenStream { + prefix("#[derive(::uniffi::Record)]", item) +} + +/// `#[derive(uniffi::Error)]`. +#[proc_macro_attribute] +pub fn error(_args: TokenStream, item: TokenStream) -> TokenStream { + prefix("#[derive(::uniffi::Error)]", item) +} diff --git a/justfile b/justfile index bc4b8aa04f..d66e4ac351 100644 --- a/justfile +++ b/justfile @@ -50,6 +50,10 @@ bench $BASE="": bench-ffi: bench/ffi.sh +# Compare BoltFFI with UniFFI on one moq-ffi slice. STEPS: build, bench, size, or all. +bench-ffi-gen *STEPS: + bench/ffi-gen/run.sh {{ STEPS }} + # Measure audio group sizes across relay publisher/subscriber and fanout shapes. bench-audio: bench/run.sh --audio diff --git a/quest/m2/README.md b/quest/m2/README.md index 3cf0e15c42..9d0f08f92a 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -28,7 +28,6 @@ ranked just below deterministic TS legs, which it requires. - [Epoch anchor](/quest/m2/epoch-anchor.md) - same-epoch importers each derive the catalog clock and timestamp era from the epoch and stay live, so they agree across a 33-bit wrap, a forward clock break, and a restart - [Raw TS cuts on the epoch timeline](/quest/m2/ts-passthrough-epoch-cuts.md) - passthrough fallback cuts fall on the epoch's grid and the M2ts section derives from the epoch and content, so instances started apart publish identical groups and catalogs - [Same-epoch importers](/quest/m2/hop-aligned-import.md) - importers sharing one `--epoch` and fed one stream publish identical groups and timestamps, so failover between a redundant pair survives -- [BoltFFI or UniFFI](/quest/m2/boltffi.md) - BoltFFI vs UniFFI on a moq-ffi slice in Python, Kotlin, and Swift decides which one the moq-dev fork starts from - [Hard-fork the binding generator](/quest/m2/uniffi-fork.md) - one moq-dev repo holds the chosen generator's core and every language backend, released under one tag and one pin - [Benchmark trend site](/quest/m2/bench-site.md) - a GitHub Pages site charts the binding, Rust, and JS nightly benchmarks over time - [Link the benchmark trends](/quest/m2/bench-site-doc.md) - the docs point language choosers at the trend site diff --git a/quest/m2/boltffi.md b/quest/m2/boltffi.md deleted file mode 100644 index 75df7ef7d1..0000000000 --- a/quest/m2/boltffi.md +++ /dev/null @@ -1,68 +0,0 @@ -# [M] Spike: BoltFFI or UniFFI as the fork's base - -## Goal - -A reproducible report compares BoltFFI with UniFFI on the same slice of -moq-ffi, in Python, Kotlin, and Swift, for call overhead, payload throughput, -async cost, and output size. It also estimates what each base costs to -carry. The maintainer picks which one [the hard fork](/quest/m2/uniffi-fork.md) -starts from. This quest does not adopt or fork either one. - -## Plan - -Decided with the maintainer on 2026-10-13: - -- **Why now**: moq is hard-forking its binding generator. It is not clear - whether UniFFI or BoltFFI is the better starting point, so this spike runs - in parallel with other work and nothing waits on its harness. -- **Candidates**: [BoltFFI](https://github.com/boltffi/boltffi) against - UniFFI 0.32. BoltFFI has UniFFI's proc-macro style and the same - poll-plus-continuation async model. It ships Swift, Kotlin, Java, C#, - Python, and wasm/TS (its own loader; since v0.31 it also runs - wasm-bindgen for crates that use web-sys). Its README claims 1000x faster - no-op calls than UniFFI, but those are vendor-run benchmarks. Diplomat was - rejected: no async, no Swift or Go, no data enums, and its JS breaks on - crates using wasm-bindgen. -- **BoltFFI's known gaps**: no Go or C++, Dart is unfinished, C is - sync-only, and async constructors and static async methods are dropped - (boltffi/boltffi#95). `MoqClient::connect` takes `&self` and works, but - async constructors such as the media catalog consumer's `subscribe` hit - it. One maintainer wrote about 80% - of the code. Same-name types in one target probably collide (RFC #700). - Put each gap in the report with what closing it in a fork would cost. -- **UniFFI's known costs**: three generator forks broken by every minor - release, `Vec` copied one byte at a time, and bare-name type keys - (mozilla/uniffi-rs#3026). -- **Slice**: connect, publish and subscribe one track, `write_frame` and - `read_frame`, and one async read loop. Bind them with both generators side - by side via `cfg_attr` in a spike crate, not in moq-ffi. The spike crate - carries its own benchmarks, so it does not wait on the - [binding benchmark](/bench/README.md). It also builds for wasm32: - the browser comparison in #5478 reuses this slice for per-call numbers, - so tell that quest if the crate's location or shape changes. -- **Measure**: no-op and record call overhead, payload throughput across - frame sizes from 100 B to 1 MB, and the cost of each async await. - Re-measure UniFFI rather than trusting BoltFFI's numbers for it. - `just bench-ffi` gives UniFFI's numbers through the real moq-ffi. A known - UniFFI cost to weigh: Kotlin sync calls measured 11-22 us against 1.3 us - from Python on 2026-10-15, possibly from JNA's per-call structures - ([Kotlin calls](/quest/m2/kt-ffi-calls.md)); async wakeups may add a JVM - thread attach each, a separate suspect. -- **Size table**: native library, generated bindings, and wasm plus JS glue - (gzip), for both generators. Measure the native library with moq-ffi's - default features on and off; the toggle changes nothing on wasm32, where - `audio` and `video` are already compiled out. Put moq-wasm in the table for reference. -- **Carry cost**: for each base, estimate the work to reach moq's eight - targets (Python, Go, Swift, Kotlin, Dart, C++, C, browser TS), porting - the existing Go, Dart, and C++/C generators where needed. Also estimate - how each base's internals suit the planned fork work: bulk byte copies, - zero-copy returns, namespaced types, and poll-native async. -- **Browser half**: BoltFFI also joins the browser generator choice in - [#5478](https://github.com/moq-dev/moq/pull/5478) as a third candidate on - the baseline harness. Neither blocks the other. - -Public API: none. Wire: none. - -## Related - -- [Rust-backed @moq/net](/quest/m2/rs2ts/README.md) - the browser binding choice BoltFFI also competes in diff --git a/quest/m2/kt-ffi-calls.md b/quest/m2/kt-ffi-calls.md index 839c83a70d..0ec76b1fb5 100644 --- a/quest/m2/kt-ffi-calls.md +++ b/quest/m2/kt-ffi-calls.md @@ -26,8 +26,10 @@ Decided with the maintainer on 2026-10-15: patching generated code. Both costs are one quest: same root, same generator, same proof. Prefer a shape that keeps the wrapper's API unchanged. -- If [the BoltFFI spike](/quest/m2/boltffi.md) picks BoltFFI, re-plan this - against its Kotlin backend, or delete it if that backend already avoids JNA. +- [The BoltFFI spike](/bench/ffi-gen/README.md) measured the target: BoltFFI's + JNI backend makes a no-op call in 6 ns and a 100 B `write_frame` in 134 ns, + against UniFFI's 3.2 and 12.8 us. If the maintainer picks BoltFFI, delete + this quest. Public API: none expected. Wire: none. diff --git a/quest/m2/rs2ts/generator.md b/quest/m2/rs2ts/generator.md index 52d1b1377d..d0834584b9 100644 --- a/quest/m2/rs2ts/generator.md +++ b/quest/m2/rs2ts/generator.md @@ -40,9 +40,12 @@ Decided with the maintainer on 2026-10-13: (decided in [#5498](https://github.com/moq-dev/moq/pull/5498), whose BoltFFI spike covers the native half). Neither quest blocks the other. BoltFFI has its own macros, so it cannot bind `moq-ffi` as-is. Reuse - that spike's slice crate, which binds both generators via `cfg_attr`, + that spike's slice crate, [`bench/ffi-gen`](/bench/ffi-gen/README.md), and measure UniFFI on the same slice so the per-call numbers compare - like for like. Full `moq-ffi` through UniFFI still provides the size and + like for like. It carries BoltFFI's markers literally, with a shim crate + mapping them to UniFFI's, because BoltFFI's scanner does not see + `cfg_attr`. Its `run.sh build` already produces both `.wasm` files and + BoltFFI's web package; ubrn has no 0.32 support, so no UniFFI JS. Full `moq-ffi` through UniFFI still provides the size and coverage numbers. The slice is a feasibility filter for BoltFFI, not selection evidence: if BoltFFI passes and is favored, port the harness workload to it and rerun the parity, size, startup, and memory cases @@ -50,11 +53,10 @@ Decided with the maintainer on 2026-10-13: `.d.ts` files for web, node, and bundlers. Async uses poll plus a continuation, like UniFFI. Crates using `web-sys` trapped at runtime until [boltffi#943](https://github.com/boltffi/boltffi/pull/943) - (2026-09-28, released in v0.31.0). Async constructors and static async - methods are dropped - ([boltffi#95](https://github.com/boltffi/boltffi/issues/95), open). - `MoqClient::connect` takes `&self` and works, but async constructors - such as the media catalog consumer's `subscribe` hit it. TypeScript streams are experimental + (2026-09-28, released in v0.31.0). Async constructors bind in v0.31.1, + though [boltffi#95](https://github.com/boltffi/boltffi/issues/95) is + still open; TS types the slice's `MoqSession.dial` as + `Promise`. TypeScript streams are experimental ([boltffi#40](https://github.com/boltffi/boltffi/issues/40)). Roughly one maintainer. diff --git a/quest/m2/uniffi-fork.md b/quest/m2/uniffi-fork.md index bdf8ddb201..ef4f79f706 100644 --- a/quest/m2/uniffi-fork.md +++ b/quest/m2/uniffi-fork.md @@ -14,9 +14,10 @@ are unchanged by the move itself. Decided with the maintainer on 2026-10-13, after a review of how moq uses UniFFI: -- **Base**: [the BoltFFI spike](/quest/m2/boltffi.md) decides whether the - fork starts from UniFFI or BoltFFI. The plan below assumes UniFFI. If - BoltFFI is chosen, re-plan this quest against it (the repo name, the +- **Base**: the maintainer picks UniFFI or BoltFFI from + [the BoltFFI spike](/bench/ffi-gen/README.md), which recommends UniFFI. + The plan below assumes UniFFI. If BoltFFI is chosen, re-plan this quest + against it (the repo name, the crate names, porting the Go, Dart, and C++/C generators to BoltFFI) along with the quests that name UniFFI internals: [Payload bytes](/quest/m2/ffi-bytes.md), @@ -66,10 +67,6 @@ UniFFI: Public API: none at the move; moq-ffi's dependency names change. Wire: none. -## Required - -- [BoltFFI or UniFFI](/quest/m2/boltffi.md) - picks the base this fork starts from - ## Related - [Payload bytes](/quest/m2/ffi-bytes.md) - the first patch the fork carries