From e6d21c60273e028ce20801c3287895cc7777c43a Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 4 Jun 2026 12:05:09 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20card=20standalone=20=E2=80=94=20primiti?= =?UTF-8?q?va=20de=20identidad=20soberana=20(hoja,=20autocontenida)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tarjeta de Presentación canónica: identidad arje + flujos tipados brahman, content-addressed. Hoja pura sin deps internas — la base sobre la que se montan red (chasqui/minga) y escritorio. cargo check pasa (3 crates). Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 3 + Cargo.lock | 3933 +++++++++++++++++ Cargo.toml | 432 ++ LICENSE | 21 + README.md | 18 + shared/card/README.md | 36 + shared/card/card-core/Cargo.toml | 19 + shared/card/card-core/src/lib.rs | 1290 ++++++ shared/card/card-net/Cargo.toml | 24 + shared/card/card-net/src/key.rs | 167 + shared/card/card-net/src/lib.rs | 518 +++ shared/card/card-wit/Cargo.toml | 21 + shared/card/card-wit/README.md | 34 + .../card-wit/examples/brahman-wit-info.rs | 45 + shared/card/card-wit/src/lib.rs | 178 + 15 files changed, 6739 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 shared/card/README.md create mode 100644 shared/card/card-core/Cargo.toml create mode 100644 shared/card/card-core/src/lib.rs create mode 100644 shared/card/card-net/Cargo.toml create mode 100644 shared/card/card-net/src/key.rs create mode 100644 shared/card/card-net/src/lib.rs create mode 100644 shared/card/card-wit/Cargo.toml create mode 100644 shared/card/card-wit/README.md create mode 100644 shared/card/card-wit/examples/brahman-wit-info.rs create mode 100644 shared/card/card-wit/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7141ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +*.pdb diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f99711c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3933 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "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 = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[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 2.0.18", + "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", + "synstructure", +] + +[[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", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64", + "http", + "log", + "url", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[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 = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "card-core" +version = "0.1.0" +dependencies = [ + "postcard", + "serde", + "serde_json", + "thiserror 2.0.18", + "toml", + "ulid", +] + +[[package]] +name = "card-net" +version = "0.1.0" +dependencies = [ + "blake3", + "futures", + "libp2p", + "libp2p-allow-block-list", + "libp2p-stream", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "card-wit" +version = "0.1.0" +dependencies = [ + "anyhow", + "card-core", + "thiserror 2.0.18", + "wit-parser 0.230.0", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +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 = "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-encoding-macro" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" +dependencies = [ + "data-encoding", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[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 = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[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 = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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 = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-bounded" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" +dependencies = [ + "futures-timer", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[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", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[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 = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.4", + "ring", + "socket2 0.5.10", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.4", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +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 = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2 0.6.4", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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 = "if-addrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "if-watch" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows", +] + +[[package]] +name = "igd-next" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.4", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.4", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libp2p" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce71348bf5838e46449ae240631117b487073d5f347c06d434caddcb91dceb5a" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.17", + "libp2p-allow-block-list", + "libp2p-autonat", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dcutr", + "libp2p-dns", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-quic", + "libp2p-relay", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-upnp", + "libp2p-yamux", + "multiaddr", + "pin-project", + "rw-stream-sink", + "thiserror 2.0.18", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16ccf824ee859ca83df301e1c0205270206223fd4b1f2e512a693e1912a8f4a" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-autonat" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fab5e25c49a7d48dac83d95d8f3bac0a290d8a5df717012f6e34ce9886396c0b" +dependencies = [ + "async-trait", + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-request-response", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.6", + "rand_core 0.6.4", + "thiserror 2.0.18", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18b8b607cf3bfa2f8c57db9c7d8569a315d5cc0a282e6bfd5ebfc0a9840b2a0" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-core" +version = "0.43.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot", + "pin-project", + "quick-protobuf", + "rand 0.8.6", + "rw-stream-sink", + "thiserror 2.0.18", + "tracing", + "unsigned-varint 0.8.0", + "web-time", +] + +[[package]] +name = "libp2p-dcutr" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4107305e12158af3e66960b6181789c547394c9c9a8696f721521602bfc73a" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "hashlink", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "thiserror 2.0.18", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-dns" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b770c1c8476736ca98c578cba4b505104ff8e842c2876b528925f9766379f9a" +dependencies = [ + "async-trait", + "futures", + "hickory-resolver", + "libp2p-core", + "libp2p-identity", + "parking_lot", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-identify" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab792a8b68fdef443a62155b01970c81c3aadab5e659621b063ef252a8e65e8" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "libp2p-identity" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9525f3831544f7ae497bde79adf114ef127b0fbbb97edbbf692a80408636421c" +dependencies = [ + "bs58", + "ed25519-dalek", + "hkdf", + "multihash", + "prost", + "rand 0.8.6", + "sha2", + "thiserror 2.0.18", + "tracing", + "zeroize", +] + +[[package]] +name = "libp2p-kad" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d3fd632a5872ec804d37e7413ceea20588f69d027a0fa3c46f82574f4dee60" +dependencies = [ + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.6", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tracing", + "uint", + "web-time", +] + +[[package]] +name = "libp2p-mdns" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66872d0f1ffcded2788683f76931be1c52e27f343edb93bc6d0bcd8887be443" +dependencies = [ + "futures", + "hickory-proto", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.6", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-metrics" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805a555148522cb3414493a5153451910cb1a146c53ffbf4385708349baf62b7" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-dcutr", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-relay", + "libp2p-swarm", + "pin-project", + "prometheus-client", + "web-time", +] + +[[package]] +name = "libp2p-noise" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc73eacbe6462a0eb92a6527cac6e63f02026e5407f8831bde8293f19217bfbf" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "libp2p-identity", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.6", + "snow", + "static_assertions", + "thiserror 2.0.18", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-quic" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc448b2de9f4745784e3751fe8bc6c473d01b8317edd5ababcb0dec803d843f" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "quinn", + "rand 0.8.6", + "ring", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-relay" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9b0392ed623243ad298326b9f806d51191829ac7585cc825c54c6c67b04d9" +dependencies = [ + "asynchronous-codec", + "bytes", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.6", + "static_assertions", + "thiserror 2.0.18", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-request-response" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9f1cca83488b90102abac7b67d5c36fc65bc02ed47620228af7ed002e6a1478" +dependencies = [ + "async-trait", + "futures", + "futures-bounded", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.6", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-stream" +version = "0.4.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6bd8025c80205ec2810cfb28b02f362ab48a01bee32c50ab5f12761e033464" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.6", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce88c6c4bf746c8482480345ea3edfd08301f49e026889d1cbccfa1808a9ed9e" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "hashlink", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "multistream-select", + "rand 0.8.6", + "smallvec", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c" +dependencies = [ + "heck", + "quote", + "syn", +] + +[[package]] +name = "libp2p-tcp" +version = "0.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb6585b9309699f58704ec9ab0bb102eca7a3777170fa91a8678d73ca9cafa93" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "socket2 0.6.4", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-tls" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring", + "rustls", + "rustls-webpki", + "thiserror 2.0.18", + "x509-parser", + "yasna", +] + +[[package]] +name = "libp2p-upnp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4757e65fe69399c1a243bbb90ec1ae5a2114b907467bf09f3575e899815bb8d3" +dependencies = [ + "futures", + "futures-timer", + "igd-next", + "libp2p-core", + "libp2p-swarm", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-yamux" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" +dependencies = [ + "either", + "futures", + "libp2p-core", + "thiserror 2.0.18", + "tracing", + "yamux 0.12.1", + "yamux 0.13.10", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.8.0", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447" +dependencies = [ + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-route" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" +dependencies = [ + "bitflags", + "libc", + "log", + "netlink-packet-core", +] + +[[package]] +name = "netlink-proto" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.18", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[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 = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +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.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +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 = "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" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[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 = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[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.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf41c1a7c32ed72abe5082fb19505b969095c12da9f5732a4bc9878757fd087c" +dependencies = [ + "dtoa", + "itoa", + "parking_lot", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror 1.0.69", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.4", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.4", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[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.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[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", + "windows-sys 0.52.0", +] + +[[package]] +name = "rtnetlink" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5" +dependencies = [ + "futures-channel", + "futures-util", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core 0.6.4", + "ring", + "rustc_version", + "sha2", + "subtle", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[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 = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.4", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[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 = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[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", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "ulid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" +dependencies = [ + "rand 0.9.4", + "serde", + "web-time", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[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", +] + +[[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.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +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 = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasmparser" +version = "0.230.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808198a69b5a0535583370a51d459baa14261dfab04800c4864ee9e1a14346ed" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[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 = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[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-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[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", +] + +[[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", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[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-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[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 = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.230.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679fde5556495f98079a8e6b9ef8c887f731addaffa3d48194075c1dd5cd611b" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.230.0", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "yamux" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.8.6", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.9.4", + "static_assertions", + "web-time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9462437 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,432 @@ +# Cargo.toml raíz STANDALONE de card — front-door sobre Llimphi. +# Solo el código de card; Llimphi y lo fundacional por git-dep del monorepo gioser.git. +[workspace] +resolver = "2" +members = [ + "shared/card/card-core", + "shared/card/card-net", + "shared/card/card-wit", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +rust-version = "1.80" +license = "MIT" +authors = ["Sergio "] +publish = false +repository = "https://gitea.gioser.net/sergio/card" + +[workspace.dependencies] + +# === Registro de apps / menú global === +app-bus = { git = "https://gitea.gioser.net/sergio/gioser.git" } +# === Serialización === +serde = { version = "1", features = ["derive"] } +serde_json = "1" +lsp-types = "0.97" +serde-big-array = "0.5" +postcard = { version = "1", features = ["use-std"] } +toml = "0.8" +ron = "0.8" +bincode = "1" +base64 = "0.22" + +# === Errores === +thiserror = "2" # bump uniforme; arje (era 1) puede requerir ajustes menores +anyhow = "1" + +# === Async === +tokio = { version = "1", features = ["full"] } +tokio-util = { version = "0.7", features = ["compat"] } +async-trait = "0.1" +futures = "0.3" + +# === Observabilidad === +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } + +# === Linux primitives (arje) === +nix = { version = "0.29", features = ["signal", "process", "sched", "mount", "fs", "socket", "net", "user"] } +libc = "0.2" + +# === IDs / Hash / Crypto === +ulid = { version = "1", features = ["serde"] } +uuid = { version = "1", features = ["v4", "rng-getrandom"] } +sha2 = "0.10" +blake3 = "1.5" +ed25519-dalek = "2" +aes-gcm = "0.10" +chacha20poly1305 = "0.10" +argon2 = "0.5" +rand = "0.8" + +# === WASM (arje) === +# wasmi 1.0: unifica la versión con renaser (su kernel ya corre 1.0), para +# que el ABI WASM del host sea idéntico en Linux y en bare-metal. +wasmi = "1.0" +wat = "1" + +# === Storage / DB === +sled = "0.34" +rusqlite = { version = "0.31", features = ["bundled", "blob"] } + +# === Ingesta de documentos (iniy-ingest: PDF / EPUB) === +pdf-extract = "0.7" +epub = "2.1" + +# === Bulk import Wikipedia (iniy-wiki dump) === +bzip2 = "0.4" + +# === Compresión (minga multi-bundle) === +zstd = "0.13" + +# === HTTP server (iniy-server) === +axum = "0.7" +tower = "0.5" + +# === ANN sobre embeddings (iniy nli --ann) === +instant-distance = "0.6" + +# === P2P (minga) === +libp2p = { version = "0.56", features = ["tokio", "tcp", "noise", "yamux", "macros", "kad", "identify", "relay", "dcutr", "autonat", "mdns"] } +libp2p-stream = "=0.4.0-alpha" +libp2p-allow-block-list = "0.6" + +# === SSH (ssh, sandokan RemoteEngine, matilda) === +russh = "0.54" + +# === Math determinista cross-platform (dominium) === +libm = "0.2" + +# === SMF (takiy-midi) === +# midly: parser/emitter SMF tipo 0/1, no_std-friendly, sin allocs en hot path. +midly = "0.5" + +# === Code parsing (minga) === +arboard = "3" +ropey = "1.6" +tree-sitter = "0.24" +tree-sitter-rust = "0.23" +tree-sitter-python = "0.23" +tree-sitter-typescript = "0.23" +tree-sitter-javascript = "0.23" +tree-sitter-go = "0.23" + +# === FS notify === +notify = "6.1" + +# === Grafos (iniy, nakui-core ya lo usa directo en 0.6) === +petgraph = "0.6" + +# === Image decoding (nahual-image-viewer-llimphi) === +# default-features = false: nos quedamos con PNG + JPEG + WebP (lossless). +# tullpu-render exporta a las tres; AVIF/TIFF/… los habilitamos si una app +# los pide específicamente. +image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] } + +# === FUSE (minga-vfs) === +# default-features = false: prescinde de pkg-config/libfuse-dev en build. +# El montaje pasa a ser Rust puro (vía el helper `fusermount3` en runtime). +fuser = { version = "0.15", default-features = false } + +# === CLI / auth (minga) === +clap = { version = "4", features = ["derive"] } +rpassword = "7" + +# === PAM (auth-core) === +pam = "0.8" + +# === D-Bus (arje compat) === +zbus = { version = "4", default-features = false, features = ["tokio"] } + +# === Tests === +tempfile = "3" + +# === Llimphi (motor gráfico soberano) === +# wgpu sobre Vulkan/Metal/DX12, winit para ventana en dev Linux. +# raw-window-handle 0.6 alinea winit 0.30 con wgpu 24. +# vello 0.5 = rasterizador vectorial sobre wgpu 24. +# taffy 0.9 = motor Flexbox/Grid puro Rust (ya pulled por transitivos, lo alineamos). +# parley 0.2 = shaping/layout de texto compatible con peniko 0.4 (que vello 0.5 expone). +wgpu = "24" +winit = "0.30" +raw-window-handle = "0.6" +pollster = "0.4" +vello = "0.5" +taffy = "0.9" +# parley = shaping completo (bidi, ligatures, fallback CJK/emoji vía fontique, line break). +parley = "0.4" +# Bucle Elm (input→update→view→layout→raster→present). Lo consumen las apps. +llimphi-ui = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Paleta semántica compartida por las apps y los widgets. +llimphi-theme = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Tweens y helpers de animación sobre el bucle Elm. +llimphi-motion = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Iconos vectoriales (BezPath en grid 24×24) compartidos por todas las apps. +llimphi-icons = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Widgets reusables sobre llimphi-ui — uno por crate. +llimphi-widget-app-header = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-banner = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-button = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-card = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-clipboard = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-context-menu = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-edit-menu = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-menubar = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-list = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-grid = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-slider = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-scroll = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-splitter = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-stat-card = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-tabs = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-command-palette = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-diff-viewer = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-fif = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-file-picker = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-bookmarks = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-mini-map = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-shuma-term = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-module-symbol-outline = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-plugin-host = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-theme-switcher = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-text-area = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-text-editor-core = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-text-editor = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-text-editor-lsp = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-text-input = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-tiled = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-nodegraph = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-tree = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-navigator = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Sello vectorial wawa (rombo + W implícita + Merkle Core). +llimphi-widget-wawa-mark = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Widgets de elegancia transversal (tooltip, spinner, progress, toast, +# modal, empty, status-bar, shortcuts-help, splash). +llimphi-widget-tooltip = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-spinner = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-progress = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-toast = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-modal = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-empty = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-status-bar = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-shortcuts-help = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-timeline = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-splash = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Controles de formulario y signaling (switch, segmented, breadcrumb, +# badge, avatar, skeleton, field). +llimphi-widget-switch = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-segmented = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-dock-rail = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-breadcrumb = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-badge = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-avatar = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-skeleton = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-field = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Firma visual transversal (gradient sutil + hairline accent). +llimphi-widget-panel = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-widget-panes = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +llimphi-workspace = { git = "https://gitea.gioser.net/sergio/llimphi.git" } +# Abstracción Selector — host (paths) + wawa (khipus). +llimphi-module-selector = { git = "https://gitea.gioser.net/sergio/llimphi.git" } + +# === Filesystem helpers === +directories = "5" + +# === Diff line-based (llimphi-module-diff-viewer) === +# `similar` es la crate de facto: implementa Myers + Patience + LCS, +# expone `TextDiff` con ChangeTag por línea (Equal/Insert/Delete), +# zero deps fuera de std. La 2.x es estable hace años. +similar = "2" + +# === Fuzzy matching (shuma-history) === +# nucleo-matcher = mismo matcher que helix-editor: rápido, Unicode-correct, +# bonus por prefijos, ranking estable. La versión 0.3 expone el API simple +# que necesitamos (Matcher + Pattern + score). +nucleo-matcher = "0.3" + +# === Transporte autenticado (shuma-link) === +# snow = framework Noise pure-rust. Lo usamos en modo Noise_XK (cliente +# conoce la pubkey del servidor, server descubre la del cliente y la +# valida contra una allowlist). ChaCha20-Poly1305 + X25519 + BLAKE2s. +# La versión 0.9 viene pinneada por libp2p, así nos alineamos. +snow = "0.9" +hex = "0.4" + +# === PTY + emulador de terminal (shuma-exec, módulos REPL) === +# portable-pty aloja un PTY cross-platform; lo usamos para los +# comandos TUI tipo vim/htop/less que necesitan un terminal de verdad. +# vt100 parsea la secuencia de bytes que el PTY emite (ANSI + cursor +# movement + erase + screen state) y mantiene un buffer de pantalla +# renderizable como grid. +portable-pty = "0.9" +vt100 = "0.16" + +# === WASM web (gioser) === +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = "0.3" +glam = "0.30" + +# === Markdown (pluma) === +pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] } + +# === Archivos comprimidos (nahual archive viewer) === +# Sólo listamos el directorio central (nombres/tamaños); no descomprimimos, +# por eso default-features=false alcanza para ZIP. Para tar.gz sí +# descomprimimos en streaming con flate2 (ya declarado arriba), saltando +# los datos de cada entrada — sólo leemos headers. +zip = { version = "2.4", default-features = false } +tar = { version = "0.4", default-features = false } + +# === Fuentes (nahual font viewer) === +# Parseo de TTF/OTF/TTC y extracción de contornos de glifo a paths. +ttf-parser = "0.25" + +# ============================================================ +# Intra-workspace deps de nahual (referenciadas por workspace = true) +# ============================================================ +nahual-text-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-image-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-thumb-core = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-gallery-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-video-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-card-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-audio-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-tree-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-hex-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-table-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-markdown-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-archive-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-font-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-map-viewer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-geo-core = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-viewer-core = { git = "https://gitea.gioser.net/sergio/gioser.git" } +nahual-file-explorer-llimphi = { git = "https://gitea.gioser.net/sergio/gioser.git" } + +# ============================================================ +# Intra-workspace deps de pineal (módulo de gráficos) +# ============================================================ +pineal-core = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-render = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-cartesian = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-stream = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-mesh = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-financial = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-polar = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-heatmap = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-treemap = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-flow = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-phosphor = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-export = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-hexbin = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-contour = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal-bars = { git = "https://gitea.gioser.net/sergio/gioser.git" } +pineal = { git = "https://gitea.gioser.net/sergio/gioser.git" } + +# ============================================================ +# Intra-workspace deps de iniy (laboratorio semántico de creencias) +# ============================================================ +iniy-core = { git = "https://gitea.gioser.net/sergio/gioser.git" } +iniy-ingest = { git = "https://gitea.gioser.net/sergio/gioser.git" } +iniy-extract = { git = "https://gitea.gioser.net/sergio/gioser.git" } +iniy-nli = { git = "https://gitea.gioser.net/sergio/gioser.git" } +iniy-nli-llm = { git = "https://gitea.gioser.net/sergio/gioser.git" } +iniy-graph = { git = "https://gitea.gioser.net/sergio/gioser.git" } +iniy-store = { git = "https://gitea.gioser.net/sergio/gioser.git" } + +# === auto: declarados por crates internos faltantes === +cosmos-coords = { git = "https://gitea.gioser.net/sergio/gioser.git" } +cosmos-core = { git = "https://gitea.gioser.net/sergio/gioser.git" } +cosmos-ephemeris = { git = "https://gitea.gioser.net/sergio/gioser.git" } +cosmos-time = { git = "https://gitea.gioser.net/sergio/gioser.git" } +cosmos-wcs = { git = "https://gitea.gioser.net/sergio/gioser.git" } + +# === auto: externas de eternal === +celestial-eop-data = { version = "0.1"} +approx = "0.5" +byteorder = "1.5" +cc = "1.0" +chrono = "0.4" +crc32fast = "1.4" +criterion = "0.5" +csv = "1.4" +flate2 = "1.0" +glob = "0.3" +indicatif = "0.18" +lz4_flex = "0.11" +memmap2 = "0.9" +mockito = "1.0" +ndarray = "0.15" +num-traits = "0.2" +once_cell = "1.19" +parking_lot = "0.12" +png = "0.18" +proptest = "1.4" +quick-xml = "0.31" +rayon = "1.8" +regex = "1.11" +reqwest = "0.12" +tiff = "0.11" +wide = "0.7" +wiremock = "0.6" + +# === i18n (rimay-localize) === +fluent-bundle = "0.15" +unic-langid = { version = "0.9", features = ["macros"] } +sys-locale = "0.3" + +# === Servo (puriy-engine) === +# Crates publicados de Servo embebibles individualmente. html5ever/markup5ever +# ya entran via ammonia→surrealdb→nakui, así que alineamos versión para no +# duplicar el árbol. markup5ever_rcdom es el DOM Rc-based simple (suficiente +# para Fase 2: parsear y renderizar, sin scripting). cssparser es el tokenizer +# CSS de Stylo, sirve para inline styles. ureq = HTTP síncrono minimalista, +# evita pull de tokio en el engine. +html5ever = "0.39" +markup5ever = "0.39" +markup5ever_rcdom = "0.39" +cssparser = "0.35" +url = "2" +ureq = { version = "2", default-features = false, features = ["tls"] } + +# === takiy-synth (SoundFont MIDI) === +# rustysynth = sintetizador SF2 puro Rust, MIT. Reemplaza el oscilador +# feo de takiy-synth por muestras reales (FluidR3, GeneralUser GS, etc). +rustysynth = "1.3" + +# === takiy-playback (audio device output) === +# cpal = backend de audio cross-platform (ALSA/PulseAudio/Pipewire en +# Linux, WASAPI en Windows, CoreAudio en macOS). Lo usamos sólo para +# abrir el device default y empujar muestras f32 — nada de mezclado +# ni efectos en el callback. +cpal = "0.15" + +# === media-source-wav (decoder PCM en disco) === +# hound = lector/escritor WAV puro-Rust, sin deps nativas. Soporta PCM +# entero (8/16/24/32) y float (32). Suficiente para abrir samples y +# stems de prueba sin meter ffmpeg/symphonia. +hound = "3.5" + +# === media-source-{mp3,flac,vorbis} (decoders vía symphonia) === +# symphonia es una colección de decoders puro-Rust mantenida. `mp3` cubre +# media-source-mp3; `flac` (decoder + demuxer FLAC nativo) cubre +# media-source-flac (lossless); `vorbis` + `ogg` (codec + demuxer Ogg) +# cubren media-source-vorbis (lossy clásico, libre de patentes). Sin aac: +# ese tier patentado entra por shared/foreign-av. +symphonia = { version = "0.5", default-features = false, features = ["mp3", "flac", "vorbis", "ogg"] } + +# === media-source-opus (decoder Opus NATIVO puro-Rust) === +# Opus es el formato de audio nativo de gioser (par del video AV1). ogg +# demuxea las páginas Ogg; opus-wave es un port puro-Rust de libopus +# (SILK+CELT, sin C ni FFI) — par del rav1d del lado video. +ogg = "0.9" +opus-wave = "3" + +# === media-source-webm (demux nativo Matroska/WebM) === +# matroska-demuxer es un demuxer puro-Rust de MKV/WebM (EBML). Saca los +# paquetes de los tracks V_AV1 y A_OPUS para alimentar a media-source-av1 +# y media-source-opus — un .webm AV1+Opus se reproduce 100% nativo. +matroska-demuxer = "0.7" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ede9631 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Sergio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4149fb7 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# card + +> The canonical **presentation card** — sovereign identity + typed flows, in Rust. + +`card` is the suite's identity primitive: a content-addressed *Tarjeta de Presentación* that binds an `arje` identity to typed `brahman` flows. It is the leaf that the networking and desktop layers build on — peers, channels and capabilities are all expressed in terms of cards. + +## Crates + +- **`card-core`** — the canonical card type (identity + typed flows), content-addressed. +- **`card-wit`** — WIT-typed flow descriptions (component-model interfaces). + +## How dependencies work + +A true leaf: `card` has **no internal dependencies** — only crates.io. It compiles standalone and is git-depended by the rest of the suite (networking via `chasqui`/`minga`, the desktop, etc.). + +## License + +MIT. diff --git a/shared/card/README.md b/shared/card/README.md new file mode 100644 index 0000000..d06e224 --- /dev/null +++ b/shared/card/README.md @@ -0,0 +1,36 @@ +# card — identidad agnóstica de transporte + +Contrato de identidad y membresía **independiente del transporte**: claves +Ed25519, `EspinaId` (hash de la clave pública), firmas y handshake de membresía +de una "espina" (red privada). El mismo contrato lo implementan dos transportes +distintos: `card-net` (libp2p) y `wawa-akasha` (protocolo propio de wawa). + +## Subcrates + +- **`card-core`** — el contrato agnóstico real: par Ed25519 → `EspinaId`, + `Card` firmada, `handshake` de membresía (un miembro presenta su tarjeta + firmada; el anfitrión la verifica contra la raíz de confianza de la espina). + Sólo bytes firmados — no sabe de libp2p ni Akasha. +- **`card-net`** — espina dorsal P2P sobre **libp2p**: discovery (mDNS + + Kademlia DHT), gossipsub, NAT traversal (Circuit Relay v2 + DCUtR + AutoNAT). + Implementa el contrato de `card-core` sobre libp2p. Lo consume `khipu`. +- **`card-wit`** — **[DORMIDO]** binding WIT/wasm del contrato. Se reactiva + cuando `card` cruce a apps WASM; hoy el contrato real es `card-core`. + +## Estado (2026-05-31) + +### Hecho +- `card-core`: identidad Ed25519 + `EspinaId` + handshake de membresía (contrato + agnóstico declarado como la fuente de verdad). +- `card-net`: discovery (mDNS+DHT), gossipsub y NAT traversal completo + (Relay v2 + DCUtR + AutoNAT); discovery de personas por `DhtKey::Persona`. + +### Pendiente +- `card-wit`: dormido — binding WASM pendiente de reactivación. +- Espejo del contrato sobre `wawa-akasha` (transporte wawa) aún por cablear. +- Endurecer revocación / rotación de membresía de espina. + +## Lugar en el repo + +`shared/card` — contrato de identidad. `card-net` lo lleva a libp2p (khipu); +`agora` cubre firma/confianza de más alto nivel. diff --git a/shared/card/card-core/Cargo.toml b/shared/card/card-core/Cargo.toml new file mode 100644 index 0000000..b012cde --- /dev/null +++ b/shared/card/card-core/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "card-core" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +publish.workspace = true +description = "Brahman — Tarjeta de Presentación canónica (identidad arje + flujos tipados brahman)." + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +toml = { workspace = true } +thiserror = { workspace = true } +ulid = { workspace = true } + +[dev-dependencies] +postcard = { workspace = true } diff --git a/shared/card/card-core/src/lib.rs b/shared/card/card-core/src/lib.rs new file mode 100644 index 0000000..1861d8d --- /dev/null +++ b/shared/card/card-core/src/lib.rs @@ -0,0 +1,1290 @@ +//! `brahman-card` — Tarjeta de Presentación canónica de Brahman. +//! +//! Híbrida del `EntityCard` de arje (identidad ULID, capacidades tipadas, +//! `Payload`/`SomaSpec`/`Supervision`/`genesis` recursivo) con flujos tipados, +//! permisos enumerados explícitos y nivel de confianza derivado del modelo +//! que veníamos diseñando en `core_protocol`. Una sola tarjeta sirve a: +//! +//! - **El Init** (encarnación): `payload` + `soma` + `supervision` + `genesis`. +//! - **El Admin** (matching): `provides`/`requires` + `flow` + `permissions`. +//! - **El runtime** (sandbox): `permissions` enumerados → seccomp / namespaces. +//! +//! Forward-compat: cualquier campo desconocido se preserva en `extensions` +//! (raíz) o en `extra` (sub-secciones). +//! +//! Formatos soportados: JSON (canónico, compatible con arje) y TOML +//! (humano-legible). Auto-detección por extensión. + +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms)] + +use std::collections::{BTreeMap, BTreeSet, HashSet}; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; +use thiserror::Error; +use ulid::Ulid; + +// Re-export para que los consumidores no necesiten depender de `ulid` +// directamente. +pub use ::ulid; + +/// Versión del esquema de la Card. +pub const CARD_SCHEMA_VERSION: u16 = 1; + +/// Versión del protocolo Brahman. +pub const PROTOCOL_VERSION: &str = "0.1.0"; + +/// Errores de parseo o validación de la Card. +#[derive(Debug, Error)] +pub enum CardError { + #[error("schema version mismatch: got {got}, expected {expected}")] + SchemaMismatch { got: u16, expected: u16 }, + #[error("label vacío")] + EmptyLabel, + #[error("label demasiado largo: {0} bytes (máx 256)")] + LabelTooLong(usize), + #[error("capacidad presente en provides Y requires: {0:?}")] + SelfDependency(Capability), + #[error("payload Native/Legacy con exec vacío")] + EmptyExec, + #[error("payload Wasm con sha256 sentinela (todo ceros)")] + SentinelWasmHash, + #[error("rlimit inválido: {0}")] + InvalidRlimit(&'static str), + #[error("cgroup weight fuera de [1,10000]: {0}")] + InvalidCgroupWeight(&'static str), + #[error("flujo {section}: nombre duplicado '{name}'")] + DuplicateFlowName { + section: &'static str, + name: String, + }, + #[error("JSON inválido: {0}")] + Json(#[from] serde_json::Error), + #[error("TOML inválido: {0}")] + Toml(#[from] toml::de::Error), + #[error("E/S leyendo card: {0}")] + Io(#[from] std::io::Error), + #[error("format desconocido (extensiones esperadas: .json, .toml)")] + UnknownFormat, +} + +// ===================================================================== +// Card raíz +// ===================================================================== + +/// Tarjeta de Presentación de un módulo Brahman. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Card { + /// Versión del esquema. Cambiar = romper compatibilidad del fractal. + pub schema_version: u16, + + /// Identidad opaca, única en el grafo del fractal. + pub id: Ulid, + + /// Ancestro del que esta Card desciende (genealogía). + #[serde(default)] + pub lineage: Option, + + /// Nombre humano-legible. Único por convención, no por validación. + pub label: String, + + /// Capacidades del sistema que esta Card ofrece a otros. + #[serde(default)] + pub provides: BTreeSet, + + /// Capacidades que necesita resolver el Init antes de encarnarla. + #[serde(default)] + pub requires: BTreeSet, + + /// Permisos sandbox declarativos (más alto nivel que `Capability`). + /// El Admin los compila a seccomp/namespaces/cgroups concretos. + #[serde(default)] + pub permissions: Permissions, + + /// Spec runtime Linux (namespaces, cgroups, rlimits, cpu_affinity). + #[serde(default)] + pub soma: SomaSpec, + + /// Qué encarnar: WASM, ELF nativo, virtual, o legacy con shims. + pub payload: Payload, + + /// Política de supervisión (restart con backoff, oneshot, delegada). + pub supervision: Supervision, + + /// Modelo de ejecución (eje ortogonal a `supervision`). + #[serde(default)] + pub lifecycle: Lifecycle, + + /// Prioridad de scheduling. + #[serde(default)] + pub priority: Priority, + + /// Contratos de flujo de datos: qué consume, qué produce. + #[serde(default)] + pub flow: Flows, + + /// Si la entidad expone un socket Unix de servicio (data plane, + /// distinto al socket del Init), declara aquí su path. Los + /// consumidores que reciban un `MatchEvent` con este Card como + /// productor pueden conectar directo al socket sin discovery + /// adicional. + #[serde(default)] + pub service_socket: Option, + + /// Referencias a otras Cards: "soy procesado por X", "poseo Y", + /// etc. Forma el grafo de relaciones del fractal. Cada Card las + /// declara unilateralmente; los consumidores pueden cruzarlas para + /// reconstruir vínculos bidireccionales. + #[serde(default)] + pub references: Vec, + + /// Naturaleza de la entidad detrás de la Card. Por defecto `Ente` + /// para mantener compatibilidad con Cards existentes. + #[serde(default)] + pub kind: CardKind, + + /// Faceta de datos cuando `kind != Ente`. `None` para entes + /// runtime; `Some(...)` para Mónadas, índices, etc. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data: Option, + + /// Hijas a instanciar inmediatamente al encarnar esta Card. + #[serde(default)] + pub genesis: Vec, + + /// Biases per-contexto. La key es el nombre del contexto (p. ej. + /// `"test"`, `"prod"`, `"foreground"`). Cuando el broker está + /// configurado bajo ese contexto, el bias se aplica. Sin contexto + /// activo o sin entrada matching, este campo no afecta el ranking. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub priority_contexts: BTreeMap, + + /// Campos JSON/TOML desconocidos preservados durante I/O de archivos + /// (forward-compat). **No se transmiten por wire (postcard)** — la + /// proyección a [`WireCard`] los descarta porque `serde_json::Value` + /// no es postcard-friendly. Sirven para anotaciones locales que + /// sobreviven leer/escribir Cards en disco. + #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")] + pub extensions: BTreeMap, +} + +impl Default for Card { + /// Default determinista pensado para el patrón `..Default::default()` + /// en struct-literals donde el caller sobreescribe `id` y `label`. + /// + /// **Trap conocida**: `id` queda en `Ulid::nil()`. Si construís una + /// Card "viva" para registrar en el broker, NUNCA dejes el `id` + /// derivado de `Default` — todas las Cards default-construidas + /// colisionarían bajo el mismo `00000000000000000000000000`. Para + /// Cards frescas usá [`Card::new`], que asigna `Ulid::new()`. + /// `Ulid::nil()` queda reservado para patterns de búsqueda y + /// sentinel values en serialización. + fn default() -> Self { + Self { + schema_version: CARD_SCHEMA_VERSION, + id: Ulid::nil(), + lineage: None, + label: String::new(), + provides: BTreeSet::new(), + requires: BTreeSet::new(), + permissions: Permissions::default(), + soma: SomaSpec::default(), + payload: Payload::Virtual, + supervision: Supervision::OneShot, + lifecycle: Lifecycle::default(), + priority: Priority::default(), + flow: Flows::default(), + genesis: Vec::new(), + service_socket: None, + references: Vec::new(), + kind: CardKind::default(), + data: None, + priority_contexts: BTreeMap::new(), + extensions: BTreeMap::new(), + } + } +} + +// ===================================================================== +// Capacidades — heredadas de arje, tipadas, no strings +// ===================================================================== + +/// Capacidad del sistema. Identificadores tipados, no strings libres. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum Capability { + /// Provee un punto de montaje root para Cards hijas. + FilesystemRoot, + /// Acceso a una familia netlink del kernel. + KernelNetlink(NetlinkFamily), + /// Endpoint del bus interno — equivalente tipado de un nombre D-Bus. + Endpoint { + interface: InterfaceId, + version: u16, + }, + /// Reemplazo del shim de systemd-logind. Solo el ente compat lo provee. + LegacyLogind, + /// Acceso crudo a una clase de dispositivo. Capacidad escalada. + Device { class: DeviceClass }, + /// Permiso de instanciar Cards hijas. Por defecto solo PID 1 lo tiene. + Spawn, + /// Acceso a logging estructurado del fractal. + Journal, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum NetlinkFamily { + Uevent, + Route, + Generic, + Audit, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum DeviceClass { + Block, + Tty, + Input, + Drm, + Net, + Hidraw, +} + +/// Identificador de interfaz del bus interno (UUID, no string libre). +/// Para extender el protocolo, se genera un UUID nuevo y se versiona. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct InterfaceId(pub [u8; 16]); + +// ===================================================================== +// Permisos sandbox — más alto nivel que Capability +// ===================================================================== + +/// Permisos declarativos. El Admin los traduce a seccomp/namespaces. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Permissions { + #[serde(default)] + pub networking: NetworkingPolicy, + #[serde(default)] + pub filesystem: FsPolicy, + #[serde(default)] + pub ipc: IpcPolicy, + /// Capacidad de spawnear sub-procesos. Implica `TrustLevel::System`. + #[serde(default)] + pub processes: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum NetworkingPolicy { + #[default] + None, + Loopback, + Outbound, + Full, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum FsPolicy { + #[default] + None, + ReadOnly, + ReadWrite, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct IpcPolicy { + /// Protocolos IPC permitidos (p. ej. `"wit-v1"`, `"shm-v1"`). + #[serde(default)] + pub allow: Vec, +} + +// ===================================================================== +// SomaSpec — runtime Linux (heredado de arje sin cambios) +// ===================================================================== + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SomaSpec { + pub namespaces: NamespaceSet, + pub rlimits: ResourceLimits, + pub cgroup: CgroupSpec, + pub cpu_affinity: Option>, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct NamespaceSet { + pub mount: bool, + pub pid: bool, + pub net: bool, + pub uts: bool, + pub ipc: bool, + pub user: bool, + pub cgroup: bool, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ResourceLimits { + pub mem_bytes: Option, + pub nproc: Option, + pub nofile: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CgroupSpec { + #[serde(default)] + pub path: String, + pub cpu_weight: Option, + pub io_weight: Option, +} + +// ===================================================================== +// Payload — qué encarnar (heredado de arje) +// ===================================================================== + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Payload { + Wasm { + module_sha256: [u8; 32], + entry: String, + }, + Native { + exec: String, + argv: Vec, + envp: Vec<(String, String)>, + }, + /// Sin proceso. Nodo lógico del grafo (agregadores, mediators). + Virtual, + /// Wrapper de daemon legacy. `fakes` activa shims D-Bus / sd_notify. + Legacy { + exec: String, + argv: Vec, + fakes: BTreeSet, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum LegacyFacade { + SystemdLogind, + SystemdHostnamed, + SystemdNotify, +} + +// ===================================================================== +// Supervisión (heredada de arje) +// ===================================================================== + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Supervision { + Restart { + #[serde(with = "duration_millis")] + initial: Duration, + #[serde(with = "duration_millis")] + max: Duration, + }, + OneShot, + Delegate, +} + +mod duration_millis { + use serde::{Deserialize, Deserializer, Serializer}; + use std::time::Duration; + + pub fn serialize(d: &Duration, s: S) -> Result { + s.serialize_u64(d.as_millis() as u64) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { + let ms = u64::deserialize(d)?; + Ok(Duration::from_millis(ms)) + } +} + +// ===================================================================== +// Lifecycle / Priority (del modelo brahman) +// ===================================================================== + +/// Modelo de ejecución (rol). Ortogonal a `Supervision` (política de restart). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Lifecycle { + /// Servicio de larga duración. + #[default] + Daemon, + /// Una sola ejecución; sale al terminar su tarea. + Oneshot, + /// Componente UI gestionado por el motor de widgets. + Widget, +} + +/// Tipo de relación entre dos Cards. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum RelationshipKind { + /// Esta Card administra/posee al target (Ente sobre Mónada). + Owns, + /// Esta Card es administrada/poseída por el target (Mónada bajo Ente). + OwnedBy, + /// Esta Card procesa al target (Ente que consume Mónada). + Processes, + /// Esta Card es procesada por el target (Mónada siendo consumida). + ProcessedBy, + /// Relación lateral genérica. + Sibling, +} + +/// Referencia desde una Card a otra. Forma el grafo de relaciones del +/// fractal: "el Engine X posee la Mónada Y", "el Worker A procesa la +/// Tarea B", etc. +/// +/// Es responsabilidad del que declara mantener `target_id` apuntando a +/// una Card que existe (o existió) en el ecosistema. El `target_label` +/// es redundante con el lookup en runtime, pero se incluye para que la +/// UI pueda renderear sin resolver. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CardReference { + pub kind: RelationshipKind, + pub target_id: Ulid, + /// Label humano del target en el momento de declararse la + /// referencia (cache; el target real puede haber cambiado de label). + #[serde(default)] + pub target_label: String, +} + +/// Naturaleza de la entidad detrás de la Card. +/// +/// La función de presentarse es la misma para todos: tener identidad, +/// resumen, capacidades, y poder ser encontrada por otros. Pero NO todas +/// las entidades son procesos — algunas son agrupaciones de datos +/// (Mónadas de Nouser, índices, streams). +/// +/// El kind permite a consumidores (UI, broker, observadores) discriminar +/// sólo cuando importa, pero todos hablan el mismo protocolo de Card. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CardKind { + /// Entidad runtime con `payload`/`soma`/`supervision` activos + /// (proceso, módulo, daemon). + #[default] + Ente, + /// Agrupación de datos sin proceso detrás (Mónadas Nouser, índices, + /// resultados cacheados). `payload` típicamente `Virtual`. + Data, +} + +/// Faceta de datos: campos relevantes cuando `Card.kind != Ente`. +/// +/// Optimizada para el wire — incluye sólo metadatos de presentación, NO +/// listas pesadas (los miembros, embeddings completos, etc. se consultan +/// al daemon dueño bajo demanda). El "presentation_hint" es un string +/// libre que la UI mapea a su lente (p. ej. `"code"` → editor de código). +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct DataFacet { + /// Resumen humano (1-2 oraciones). Generado por el daemon dueño. + #[serde(default)] + pub summary: String, + /// Tokens dominantes / palabras clave (5-10 típicamente). + #[serde(default)] + pub keywords: Vec, + /// Centroide vectorial. Vacío si no hay embeddings calculados. + #[serde(default)] + pub centroid: Vec, + /// Cantidad de elementos contenidos (archivos, registros, ...). + #[serde(default)] + pub member_count: u32, + /// Métrica de dispersión interna [0, 1] (típicamente entropía). + #[serde(default)] + pub dispersion: f32, + /// Hint de presentación. Strings libres como `"code"`, `"gallery"`, + /// `"markdown"`, `"database"`, `"grid"`, `"tree"`. La UI los mapea. + #[serde(default)] + pub presentation_hint: String, +} + +/// Prioridad de scheduling. Orden: `Low < Normal < High < Critical` — +/// usable como tiebreaker en el broker (mayor priority gana). +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Priority { + Low, + #[default] + Normal, + High, + Critical, +} + +/// Override per-contexto sobre los matches del broker. +/// +/// La Card declara biases bajo `priority_contexts.` que se +/// activan cuando el broker corre bajo ese contexto. Aplicación según rol: +/// +/// - **Como consumidor**: `pin_to` sobreescribe el `pin_to` estático del +/// `Flow.pin_to` durante la búsqueda de productores. +/// - **Como productor**: `priority_offset` se suma a la priority base +/// (saturando en `[Low, Critical]`) para el ranking de candidatos. +/// +/// Casos de uso típicos: test↔prod (mock vs real), foreground↔background +/// (latencia vs costo), trust gates (sólo productores con cierto nivel). +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct ContextBias { + /// Override del `pin_to` estático cuando el broker está en este + /// contexto y la Card actúa como consumidor. + /// + /// **No se usa `skip_serializing_if` aquí**: postcard requiere + /// layout fijo. La verbosidad extra en JSON (campos null/cero + /// emitidos) es el costo aceptado para compatibilidad de wire. + #[serde(default)] + pub pin_to: Option, + + /// Modifica la priority efectiva del Card como productor. + /// `+1` lo eleva, `-1` lo baja. El resultado se clampa al rango de + /// `Priority` ([Low, Critical]). + #[serde(default)] + pub priority_offset: i8, +} + +// ===================================================================== +// Flujos tipados (del modelo brahman) +// ===================================================================== + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Flows { + #[serde(default)] + pub input: Vec, + #[serde(default)] + pub output: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Flow { + /// Nombre único dentro de su dirección. + pub name: String, + /// Tipo de los datos que viajan por el flujo. + #[serde(rename = "type")] + pub ty: TypeRef, + /// Sugerencia de productor/consumidor concreto. El broker la respeta + /// como pista; cae en matching por tipo si no es resoluble. + #[serde(default)] + pub pin_to: Option, +} + +/// Referencia a un tipo, discriminada para distinguir primitivas de tipos WIT. +/// +/// **Wire format (JSON / TOML / postcard):** externally-tagged. Ejemplo JSON: +/// ```json +/// { "primitive": { "name": "string" } } +/// { "wit": { "package": "brahman:dht", "name": "entity-result" } } +/// ``` +/// Se eligió externally-tagged por compatibilidad con postcard, que no +/// soporta `#[serde(tag = "...")]` (internally-tagged) en formatos no +/// self-describing. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TypeRef { + /// Tipo primitivo del runtime. + Primitive { name: String }, + /// Tipo declarado en un paquete WIT. + Wit { + package: String, + #[serde(default)] + interface: Option, + name: String, + }, +} + +// ===================================================================== +// API: parseo y validación +// ===================================================================== + +impl Card { + /// Construye una Card "viva" lista para registrarse en el broker: + /// `id = Ulid::new()` (único), `label` provisto, todo lo demás en + /// los defaults seguros (Payload::Virtual, Supervision::OneShot, + /// CardKind::Ente, etc.). + /// + /// Diseñada para usarse en struct-literals con override parcial, + /// igual que `Default` pero sin la trap de `Ulid::nil()`: + /// + /// ```ignore + /// let card = Card { + /// kind: CardKind::Data, + /// payload: Payload::Embedded(serde_json::json!({"foo": 1})), + /// ..Card::new("mi-modulo.algo") + /// }; + /// ``` + /// + /// Para Cards de búsqueda/sentinel donde `nil` es semánticamente + /// significativo, usá `Card::default()` directamente. + pub fn new(label: impl Into) -> Self { + Self { + id: Ulid::new(), + label: label.into(), + ..Self::default() + } + } + + /// Deserializa una Card desde JSON y valida. + pub fn from_json(src: &str) -> Result { + let c: Self = serde_json::from_str(src)?; + c.validate()?; + Ok(c) + } + + /// Deserializa una Card desde TOML y valida. + pub fn from_toml(src: &str) -> Result { + let c: Self = toml::from_str(src)?; + c.validate()?; + Ok(c) + } + + /// Carga una Card desde disco. Auto-detecta format por extensión + /// (`.json` o `.toml`). + pub fn from_path(path: impl AsRef) -> Result { + let p = path.as_ref(); + let src = std::fs::read_to_string(p)?; + match p.extension().and_then(|e| e.to_str()) { + Some("json") => Self::from_json(&src), + Some("toml") => Self::from_toml(&src), + _ => Err(CardError::UnknownFormat), + } + } + + /// Re-serializa la Card a JSON con indentación. + pub fn to_json_pretty(&self) -> Result { + Ok(serde_json::to_string_pretty(self)?) + } + + /// Validación semántica exhaustiva, recursiva sobre `genesis`. + pub fn validate(&self) -> Result<(), CardError> { + if self.schema_version != CARD_SCHEMA_VERSION { + return Err(CardError::SchemaMismatch { + got: self.schema_version, + expected: CARD_SCHEMA_VERSION, + }); + } + if self.label.is_empty() { + return Err(CardError::EmptyLabel); + } + if self.label.len() > 256 { + return Err(CardError::LabelTooLong(self.label.len())); + } + for cap in &self.requires { + if self.provides.contains(cap) { + return Err(CardError::SelfDependency(cap.clone())); + } + } + validate_payload(&self.payload)?; + validate_rlimits(&self.soma.rlimits)?; + validate_cgroup(&self.soma.cgroup)?; + check_unique_flow_names(&self.flow.input, "flow.input")?; + check_unique_flow_names(&self.flow.output, "flow.output")?; + for child in &self.genesis { + child.validate()?; + } + Ok(()) + } +} + +fn validate_payload(p: &Payload) -> Result<(), CardError> { + match p { + Payload::Native { exec, .. } | Payload::Legacy { exec, .. } => { + if exec.trim().is_empty() { + return Err(CardError::EmptyExec); + } + } + Payload::Wasm { module_sha256, .. } => { + if module_sha256.iter().all(|&b| b == 0) { + return Err(CardError::SentinelWasmHash); + } + } + Payload::Virtual => {} + } + Ok(()) +} + +fn validate_rlimits(rl: &ResourceLimits) -> Result<(), CardError> { + if let Some(m) = rl.mem_bytes { + if m == 0 { + return Err(CardError::InvalidRlimit("mem_bytes=0")); + } + if m > 1u64 << 40 { + return Err(CardError::InvalidRlimit("mem_bytes>1TiB")); + } + } + if let Some(n) = rl.nproc { + if n == 0 || n > 65535 { + return Err(CardError::InvalidRlimit("nproc fuera de [1,65535]")); + } + } + if let Some(n) = rl.nofile { + if n == 0 || n > 1_048_576 { + return Err(CardError::InvalidRlimit("nofile fuera de [1,1M]")); + } + } + Ok(()) +} + +fn validate_cgroup(cg: &CgroupSpec) -> Result<(), CardError> { + if let Some(w) = cg.cpu_weight { + if !(1..=10000).contains(&w) { + return Err(CardError::InvalidCgroupWeight("cpu_weight")); + } + } + if let Some(w) = cg.io_weight { + if !(1..=10000).contains(&w) { + return Err(CardError::InvalidCgroupWeight("io_weight")); + } + } + Ok(()) +} + +fn check_unique_flow_names(flows: &[Flow], section: &'static str) -> Result<(), CardError> { + let mut seen = HashSet::new(); + for f in flows { + if !seen.insert(f.name.as_str()) { + return Err(CardError::DuplicateFlowName { + section, + name: f.name.clone(), + }); + } + } + Ok(()) +} + +// ===================================================================== +// Trust derivado +// ===================================================================== + +/// Nivel de confianza derivado de los permisos. **No es un campo declarado** — +/// se calcula. Una sola fuente de verdad: lo que el Admin concede. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum TrustLevel { + /// Sin permisos — sandbox total. + Untrusted, + /// Permisos menores (loopback, FS read-only, IPC). + Sandboxed, + /// Permisos amplios (red saliente o FS read-write). + Privileged, + /// Capacidad de spawnear procesos. + System, +} + +impl TrustLevel { + /// Política de derivación: + /// - `processes = true` ⇒ `System`. + /// - FS `read-write` o networking `outbound`/`full` ⇒ `Privileged`. + /// - FS `read-only`, networking `loopback`, o cualquier IPC ⇒ `Sandboxed`. + /// - Sin permisos ⇒ `Untrusted`. + pub fn derive(p: &Permissions) -> Self { + if p.processes { + return Self::System; + } + if matches!(p.filesystem, FsPolicy::ReadWrite) + || matches!( + p.networking, + NetworkingPolicy::Outbound | NetworkingPolicy::Full + ) + { + return Self::Privileged; + } + if matches!(p.filesystem, FsPolicy::ReadOnly) + || matches!(p.networking, NetworkingPolicy::Loopback) + || !p.ipc.allow.is_empty() + { + return Self::Sandboxed; + } + Self::Untrusted + } +} + +// ===================================================================== +// Identidad runtime (Card + WIT extraído + trust) +// ===================================================================== + +/// Resumen de la interfaz WIT extraída del componente WASM/WIT. +/// Vacío para módulos agnósticos (sin contrato WIT). +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct WitInterface { + pub package: String, + pub world: String, + pub exports: Vec, + pub imports: Vec, +} + +/// Card resuelta a runtime: schema + WIT opcional + trust derivado. +/// Es lo que el Admin indexa. +#[derive(Debug, Clone)] +pub struct ResolvedCard { + pub card: Card, + /// `Some` si el módulo es consciente (expone WIT), `None` si es agnóstico. + pub wit: Option, + pub trust: TrustLevel, +} + +impl ResolvedCard { + /// Construye una Card resuelta sin información WIT. + pub fn from_agnostic(card: Card) -> Self { + let trust = TrustLevel::derive(&card.permissions); + Self { + card, + wit: None, + trust, + } + } + + /// Construye una Card resuelta con interfaz WIT extraída. + pub fn from_conscious(card: Card, wit: WitInterface) -> Self { + let trust = TrustLevel::derive(&card.permissions); + Self { + card, + wit: Some(wit), + trust, + } + } +} + +// ===================================================================== +// WireCard — proyección postcard-friendly de Card +// ===================================================================== + +/// Forma de wire de [`Card`]: idéntica al schema rico **sin** el campo +/// `extensions` (incompatible con postcard porque `serde_json::Value` +/// usa secuencias/maps de longitud dinámica). +/// +/// Conversión: +/// - `WireCard::from(card)` descarta `extensions` y proyecta `genesis` +/// recursivamente. +/// - `Card::from(wire)` recupera todos los campos; `extensions` queda +/// vacío (la información de extensions no cruza el wire). +/// +/// Esta separación implementa el contrato: +/// - **JSON/TOML**: `Card` directa, con extensiones preservadas. +/// - **Wire (postcard)**: `WireCard`, sin extensiones. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WireCard { + pub schema_version: u16, + pub id: Ulid, + #[serde(default)] + pub lineage: Option, + pub label: String, + #[serde(default)] + pub provides: BTreeSet, + #[serde(default)] + pub requires: BTreeSet, + #[serde(default)] + pub permissions: Permissions, + #[serde(default)] + pub soma: SomaSpec, + pub payload: Payload, + pub supervision: Supervision, + #[serde(default)] + pub lifecycle: Lifecycle, + #[serde(default)] + pub priority: Priority, + #[serde(default)] + pub flow: Flows, + #[serde(default)] + pub genesis: Vec, + #[serde(default)] + pub service_socket: Option, + #[serde(default)] + pub references: Vec, + #[serde(default)] + pub kind: CardKind, + #[serde(default)] + pub data: Option, + #[serde(default)] + pub priority_contexts: BTreeMap, +} + +impl From for WireCard { + fn from(c: Card) -> Self { + Self { + schema_version: c.schema_version, + id: c.id, + lineage: c.lineage, + label: c.label, + provides: c.provides, + requires: c.requires, + permissions: c.permissions, + soma: c.soma, + payload: c.payload, + supervision: c.supervision, + lifecycle: c.lifecycle, + priority: c.priority, + flow: c.flow, + genesis: c.genesis.into_iter().map(WireCard::from).collect(), + service_socket: c.service_socket, + references: c.references, + kind: c.kind, + data: c.data, + priority_contexts: c.priority_contexts, + } + } +} + +impl From for Card { + fn from(w: WireCard) -> Self { + Self { + schema_version: w.schema_version, + id: w.id, + lineage: w.lineage, + label: w.label, + provides: w.provides, + requires: w.requires, + permissions: w.permissions, + soma: w.soma, + payload: w.payload, + supervision: w.supervision, + lifecycle: w.lifecycle, + priority: w.priority, + flow: w.flow, + genesis: w.genesis.into_iter().map(Card::from).collect(), + service_socket: w.service_socket, + references: w.references, + kind: w.kind, + data: w.data, + priority_contexts: w.priority_contexts, + extensions: BTreeMap::new(), + } + } +} + +// ===================================================================== +// Tests +// ===================================================================== + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_card_json() -> &'static str { + r#"{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS01", + "lineage": null, + "label": "brahman.semantic_dht", + "provides": ["Spawn", "Journal"], + "requires": [], + "permissions": { + "networking": "loopback", + "filesystem": "read-only", + "ipc": { "allow": ["wit-v1"] }, + "processes": false + }, + "soma": { + "namespaces": { + "mount": false, "pid": false, "net": false, + "uts": false, "ipc": false, "user": false, "cgroup": false + }, + "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null }, + "cgroup": { "path": "ente.slice/dht", "cpu_weight": null, "io_weight": null }, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": { "Restart": { "initial": 100, "max": 30000 } }, + "lifecycle": "daemon", + "priority": "high", + "flow": { + "input": [ + { "name": "search-query", "type": { "primitive": { "name": "string" } } } + ], + "output": [ + { "name": "dht-results", + "type": { "wit": { "package": "brahman:dht", "name": "entity-result" } } } + ] + }, + "genesis": [] + }"# + } + + #[test] + fn parses_full_json() { + let c = Card::from_json(sample_card_json()).unwrap(); + assert_eq!(c.label, "brahman.semantic_dht"); + assert_eq!(c.lifecycle, Lifecycle::Daemon); + assert_eq!(c.priority, Priority::High); + assert_eq!(c.permissions.filesystem, FsPolicy::ReadOnly); + assert_eq!(c.permissions.networking, NetworkingPolicy::Loopback); + assert_eq!(c.permissions.ipc.allow, vec!["wit-v1".to_string()]); + assert_eq!(c.flow.input.len(), 1); + assert_eq!(c.flow.output.len(), 1); + match &c.flow.output[0].ty { + TypeRef::Wit { package, name, .. } => { + assert_eq!(package, "brahman:dht"); + assert_eq!(name, "entity-result"); + } + _ => panic!("expected Wit"), + } + } + + #[test] + fn json_roundtrip_preserves_shape() { + let c1 = Card::from_json(sample_card_json()).unwrap(); + let s = c1.to_json_pretty().unwrap(); + let c2 = Card::from_json(&s).unwrap(); + assert_eq!(c1.label, c2.label); + assert_eq!(c1.flow.input.len(), c2.flow.input.len()); + } + + #[test] + fn trust_derivation() { + let mut p = Permissions::default(); + assert_eq!(TrustLevel::derive(&p), TrustLevel::Untrusted); + p.filesystem = FsPolicy::ReadOnly; + assert_eq!(TrustLevel::derive(&p), TrustLevel::Sandboxed); + p.networking = NetworkingPolicy::Outbound; + assert_eq!(TrustLevel::derive(&p), TrustLevel::Privileged); + p.processes = true; + assert_eq!(TrustLevel::derive(&p), TrustLevel::System); + } + + #[test] + fn duplicate_flow_names_rejected() { + let mut c: Card = serde_json::from_str(sample_card_json()).unwrap(); + c.flow.input.push(c.flow.input[0].clone()); + assert!(matches!( + c.validate(), + Err(CardError::DuplicateFlowName { .. }) + )); + } + + #[test] + fn self_dependency_rejected() { + let mut c: Card = serde_json::from_str(sample_card_json()).unwrap(); + c.requires.insert(Capability::Spawn); + assert!(matches!(c.validate(), Err(CardError::SelfDependency(_)))); + } + + #[test] + fn invalid_genesis_propagates() { + let parent_src = r#"{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS01", + "label": "parent", + "provides": [], "requires": [], + "soma": { + "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false}, + "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null}, + "cgroup": {"path":"x","cpu_weight":null,"io_weight":null}, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": "OneShot", + "genesis": [{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS02", + "label": "", + "provides": [], "requires": [], + "soma": { + "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false}, + "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null}, + "cgroup": {"path":"x","cpu_weight":null,"io_weight":null}, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": "OneShot", + "genesis": [] + }] + }"#; + assert!(matches!( + Card::from_json(parent_src), + Err(CardError::EmptyLabel) + )); + } + + #[test] + fn presentation_card_carries_derived_trust() { + let c = Card::from_json(sample_card_json()).unwrap(); + let resolved = ResolvedCard::from_agnostic(c); + assert_eq!(resolved.trust, TrustLevel::Sandboxed); + assert!(resolved.wit.is_none()); + } + + #[test] + fn arje_seed_format_compatible() { + // Reproduce el format canónico de arje (sin lifecycle/priority/flow, + // que son aditivos brahman). Debe parsear con defaults. + let src = r#"{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS01", + "lineage": null, + "label": "vps-min", + "provides": ["Spawn", "Journal"], + "requires": [], + "soma": { + "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false}, + "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null}, + "cgroup": {"path":"ente.slice/zero","cpu_weight":null,"io_weight":null}, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": "OneShot", + "genesis": [] + }"#; + let c = Card::from_json(src).unwrap(); + assert_eq!(c.lifecycle, Lifecycle::Daemon); // default + assert_eq!(c.priority, Priority::Normal); // default + assert_eq!(c.flow.input.len(), 0); + } + + #[test] + fn extensions_preserved_in_json_roundtrip() { + let src = r#"{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS01", + "label": "x", + "soma": { + "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false}, + "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null}, + "cgroup": {"path":"x","cpu_weight":null,"io_weight":null}, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": "OneShot", + "author": "sergio", + "tags": ["draft", "experimental"] + }"#; + let c = Card::from_json(src).unwrap(); + assert_eq!(c.extensions.get("author").and_then(|v| v.as_str()), Some("sergio")); + assert!(c.extensions.contains_key("tags")); + + // Roundtrip JSON: extensions deben re-emitirse. + let s = c.to_json_pretty().unwrap(); + let c2 = Card::from_json(&s).unwrap(); + assert_eq!(c2.extensions.get("author"), c.extensions.get("author")); + } + + #[test] + fn wire_card_roundtrip_strips_extensions() { + let src = r#"{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS01", + "label": "x", + "soma": { + "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false}, + "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null}, + "cgroup": {"path":"x","cpu_weight":null,"io_weight":null}, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": "OneShot", + "author": "sergio" + }"#; + let c = Card::from_json(src).unwrap(); + assert!(c.extensions.contains_key("author")); + + // Card → WireCard descarta extensions. + let wire: WireCard = c.into(); + assert_eq!(wire.label, "x"); + + // WireCard → Card → extensiones quedan vacías (se perdieron). + let c_back: Card = wire.into(); + assert_eq!(c_back.label, "x"); + assert!(c_back.extensions.is_empty(), "extensions sobreviven al wire"); + } + + #[test] + fn wirecard_postcard_with_priority_contexts() { + // Repro del bug que rompía chasqui-nous-mock: ContextBias con + // skip_serializing_if causaba que postcard leyera bytes + // equivocados. Sin esos atributos, el roundtrip es estable. + let src = r#"{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS01", + "label": "x", + "soma": { + "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false}, + "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null}, + "cgroup": {"path":"x","cpu_weight":null,"io_weight":null}, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": "OneShot" + }"#; + let mut c = Card::from_json(src).unwrap(); + c.priority_contexts.insert( + "test".into(), + ContextBias { + pin_to: None, + priority_offset: 1, + }, + ); + c.priority_contexts.insert( + "prod".into(), + ContextBias { + pin_to: Some("real-nous".into()), + priority_offset: 2, + }, + ); + + let wire: WireCard = c.into(); + let bytes = postcard::to_allocvec(&wire).expect("postcard encode"); + let decoded: WireCard = postcard::from_bytes(&bytes).expect("postcard decode"); + + assert_eq!(decoded.priority_contexts.len(), 2); + let test_bias = decoded + .priority_contexts + .get("test") + .expect("test context"); + assert_eq!(test_bias.priority_offset, 1); + assert!(test_bias.pin_to.is_none()); + let prod_bias = decoded + .priority_contexts + .get("prod") + .expect("prod context"); + assert_eq!(prod_bias.pin_to.as_deref(), Some("real-nous")); + assert_eq!(prod_bias.priority_offset, 2); + } + + #[test] + fn wire_card_postcard_friendly() { + // Validación: WireCard puede ser postcard-encoded sin error. + // Si Card tuviera extensions populadas, el encode rompería con + // "length of a sequence must be known". WireCard las descarta. + let src = r#"{ + "schema_version": 1, + "id": "01HQAR53D4M2NBV8KZTYXFGS01", + "label": "x", + "soma": { + "namespaces": {"mount":false,"pid":false,"net":false,"uts":false,"ipc":false,"user":false,"cgroup":false}, + "rlimits": {"mem_bytes":null,"nproc":null,"nofile":null}, + "cgroup": {"path":"x","cpu_weight":null,"io_weight":null}, + "cpu_affinity": null + }, + "payload": "Virtual", + "supervision": "OneShot", + "author": "sergio" + }"#; + let c = Card::from_json(src).unwrap(); + let wire: WireCard = c.into(); + let bytes = postcard::to_allocvec(&wire).expect("WireCard debe encodear"); + let decoded: WireCard = postcard::from_bytes(&bytes).expect("WireCard debe decodear"); + assert_eq!(decoded.label, "x"); + } + + #[test] + fn new_assigns_real_ulid_and_label() { + let c = Card::new("chasqui.engine"); + assert_eq!(c.label, "chasqui.engine"); + assert_ne!(c.id, Ulid::nil(), "Card::new no debe dejar id en nil"); + } + + #[test] + fn new_yields_distinct_ids_per_call() { + let a = Card::new("x"); + let b = Card::new("x"); + assert_ne!(a.id, b.id); + } + + #[test] + fn default_keeps_nil_id_for_struct_update_pattern() { + // Mantener este invariante explícito: Default::default() es + // determinista y devuelve nil. Cualquier cambio aquí rompería + // el patrón `..Default::default()` en patterns de búsqueda. + let d = Card::default(); + assert_eq!(d.id, Ulid::nil()); + assert!(d.label.is_empty()); + } +} diff --git a/shared/card/card-net/Cargo.toml b/shared/card/card-net/Cargo.toml new file mode 100644 index 0000000..36b63af --- /dev/null +++ b/shared/card/card-net/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "card-net" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +publish.workspace = true +description = "Brahman — capa de transporte P2P compartida (libp2p TCP+Noise+Yamux+Kad+Identify+Stream). Cualquier protocolo (handshake brahman, sync minga, futuros) puede registrar su StreamProtocol y abrir/aceptar streams sobre la malla común." + +[dependencies] +futures = { workspace = true } +libp2p = { workspace = true } +libp2p-stream = { workspace = true } +libp2p-allow-block-list = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +blake3 = { workspace = true } +serde = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true } +tokio-util = { workspace = true } diff --git a/shared/card/card-net/src/key.rs b/shared/card/card-net/src/key.rs new file mode 100644 index 0000000..ba3d402 --- /dev/null +++ b/shared/card/card-net/src/key.rs @@ -0,0 +1,167 @@ +//! Claves namespaced del DHT compartido — la **primitiva unificadora** de +//! Brahman (Capa 1). +//! +//! El ecosistema brahman corre UN solo Kademlia (en este crate, `card-net`). +//! Para que distintos dominios — código indexado (minga), Cards +//! (card-discovery), Personas (ágora), servicios — coexistan sin colisión, +//! cada clave lleva un byte de `kind` como prefijo. La representación en wire +//! es de longitud fija: `[kind_tag] ++ blake3(id)` = 33 bytes. +//! +//! Vive en `card-net` (y no en un dominio concreto) precisamente porque es el +//! namespace COMÚN: minga, agora y card-discovery la comparten. `minga-dht` +//! la re-exporta por compatibilidad histórica. + +use serde::{Deserialize, Serialize}; + +/// Tipo de registro — el namespace de una clave en el DHT. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum RecordKind { + /// Bloque de código indexado (minga). + Code, + /// `Card` brahman (módulo, ente, etc.). + Card, + /// `Persona` de ágora (identidad humana federada). + Persona, + /// Endpoint de servicio. + Service, + /// Dominio definido por el consumidor. + Custom(u8), +} + +impl RecordKind { + /// Byte de etiqueta. `Custom(n)` ocupa `0x80 | n` (top bit) para no + /// chocar nunca con los kinds estándar (`0x00..`). + pub fn tag(&self) -> u8 { + match self { + RecordKind::Code => 0x01, + RecordKind::Card => 0x02, + RecordKind::Persona => 0x03, + RecordKind::Service => 0x04, + RecordKind::Custom(n) => 0x80 | (n & 0x7f), + } + } +} + +/// Longitud fija de la clave en wire: 1 byte de kind + 32 de hash. +pub const DHT_KEY_LEN: usize = 33; + +/// Clave de DHT namespaced. Dos formas de construcción: +/// +/// - Con un `id` legible (`new`, `code`, `card`, `persona`) — el wire +/// hashea el id con blake3. Útil cuando el consumidor publica +/// identidades simbólicas (nombres de módulos, slugs de personas). +/// - Con `for_hash` — el wire usa los 32 bytes del hash directamente. +/// Útil cuando el id YA es un blake3 (como en minga, que indexa +/// contenido por su α-hash, o ágora, cuyo `IdentityId` ya es +/// `blake3(pubkey)`) — evita una segunda pasada de blake3. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DhtKey { + kind: RecordKind, + /// Representación canónica de los 32 bytes que forman la "id" en wire. + /// Si se construyó por nombre simbólico, son `blake3(id_string)`. + /// Si se construyó por hash directo, son el hash tal cual. + body: [u8; 32], + /// `id` legible — para `Display`. `None` si se construyó por hash. + label: Option, +} + +impl DhtKey { + pub fn new(kind: RecordKind, id: impl Into) -> Self { + let id = id.into(); + let body = *blake3::hash(id.as_bytes()).as_bytes(); + Self { + kind, + body, + label: Some(id), + } + } + + /// Clave para un bloque de código (id legible). + pub fn code(id: impl Into) -> Self { + Self::new(RecordKind::Code, id) + } + + /// Clave para una Card. + pub fn card(id: impl Into) -> Self { + Self::new(RecordKind::Card, id) + } + + /// Clave para una Persona. + pub fn persona(id: impl Into) -> Self { + Self::new(RecordKind::Persona, id) + } + + /// Clave a partir de un hash ya computado (32 bytes). El wire usa + /// esos bytes directamente, **sin re-hashear**. + pub fn for_hash(kind: RecordKind, hash: [u8; 32]) -> Self { + Self { + kind, + body: hash, + label: None, + } + } + + pub fn kind(&self) -> RecordKind { + self.kind + } + + pub fn id(&self) -> Option<&str> { + self.label.as_deref() + } + + /// Representación en wire: `[kind_tag] ++ body`, 33 bytes. + pub fn to_bytes(&self) -> Vec { + let mut out = Vec::with_capacity(DHT_KEY_LEN); + out.push(self.kind.tag()); + out.extend_from_slice(&self.body); + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn wire_key_has_fixed_length() { + assert_eq!(DhtKey::card("modulo-x").to_bytes().len(), DHT_KEY_LEN); + assert_eq!(DhtKey::code("fn-hash").to_bytes().len(), DHT_KEY_LEN); + } + + #[test] + fn same_id_different_kind_does_not_collide() { + let a = DhtKey::card("foo").to_bytes(); + let b = DhtKey::code("foo").to_bytes(); + let c = DhtKey::persona("foo").to_bytes(); + assert_ne!(a, b); + assert_ne!(b, c); + assert_ne!(a, c); + // El hash del id es el mismo; sólo difiere el byte de kind. + assert_eq!(a[1..], b[1..]); + assert_ne!(a[0], b[0]); + } + + #[test] + fn same_kind_and_id_is_stable() { + assert_eq!(DhtKey::card("x").to_bytes(), DhtKey::card("x").to_bytes()); + } + + #[test] + fn for_hash_no_rehashea() { + // for_hash debe usar los bytes tal cual: body = hash, no blake3(hash). + let h = [7u8; 32]; + let k = DhtKey::for_hash(RecordKind::Persona, h); + let wire = k.to_bytes(); + assert_eq!(wire[0], RecordKind::Persona.tag()); + assert_eq!(&wire[1..], &h); + } + + #[test] + fn custom_kind_never_collides_with_standard() { + for std in [RecordKind::Code, RecordKind::Card, RecordKind::Persona, RecordKind::Service] { + for n in 0..=127u8 { + assert_ne!(std.tag(), RecordKind::Custom(n).tag()); + } + } + } +} diff --git a/shared/card/card-net/src/lib.rs b/shared/card/card-net/src/lib.rs new file mode 100644 index 0000000..c10f29c --- /dev/null +++ b/shared/card/card-net/src/lib.rs @@ -0,0 +1,518 @@ +//! `brahman-net` — capa P2P compartida de la red Brahman. +//! +//! Provee un nodo libp2p genérico que cualquier protocolo de la +//! familia (handshake brahman remoto, sync minga, futuros) puede +//! reusar. La idea: una sola malla, múltiples sub-protocolos +//! multiplexados por `StreamProtocol`. +//! +//! ## Stack +//! +//! - **TCP + Noise + Yamux**: transporte autenticado y multiplexado. +//! - **`stream::Behaviour`**: streams bidireccionales por +//! `StreamProtocol`. Cada protocolo (`/brahman/handshake/1.0.0`, +//! `/minga/sync/1.0.0`, …) se registra independientemente vía el +//! `stream::Control` que `BrahmanNet` expone. +//! - **`kad::Behaviour`**: Kademlia DHT en modo Server +//! para discovery (peers cercanos + content providers). +//! - **`identify::Behaviour`**: cada peer anuncia sus listen-addrs +//! reales; las inyectamos automáticamente al routing table de Kad. +//! +//! ## Modelo +//! +//! El swarm corre en una task tokio dedicada. La interfaz pública son: +//! 1. **Comandos** (canal mpsc): `dial`, `listen`, `add_dht_peer`, +//! `find_closest_peers`, `start_providing`, `find_providers`. +//! 2. **`stream::Control`** (acceso directo): para abrir/aceptar +//! streams de un protocolo concreto. Cada protocolo se ocupa de +//! su propia lógica sobre el stream resultante. +//! +//! La separación entre comandos y control permite que la lógica de +//! red (DHT, dial, listen) y la lógica de protocolos (handshake/sync) +//! evolucionen independientes — el protocolo no necesita conocer al +//! swarm, sólo pide streams. +//! +//! ## Identidad +//! +//! Por defecto se genera una keypair Ed25519 efímera. Para identidad +//! persistente (la misma `peer_id` across reboots), pasar la keypair +//! con [`BrahmanNet::with_keypair`]. Esa misma keypair puede ser la +//! base para firmas de Cards (cuando se implemente trust remoto). + +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms)] + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +use futures::StreamExt; +use libp2p::{ + autonat, dcutr, identify, identity, kad, mdns, noise, relay, + swarm::{behaviour::toggle::Toggle, NetworkBehaviour, SwarmEvent}, + tcp, yamux, Swarm, SwarmBuilder, +}; +use libp2p_allow_block_list::{self as allow_block_list, BlockedPeers}; +use libp2p_stream as stream; +use tokio::sync::{mpsc, oneshot, Mutex}; + +pub mod key; +pub use key::{DhtKey, RecordKind, DHT_KEY_LEN}; + +pub use libp2p::{ + identity::{Keypair, PublicKey}, + multiaddr::Protocol, + Multiaddr, PeerId, Stream, StreamProtocol, +}; +pub use libp2p_stream::OpenStreamError; + +const IDENTIFY_PROTOCOL: &str = "/brahman-net/0.1.0"; +const IDLE_CONNECTION_TIMEOUT: Duration = Duration::from_secs(60); + +#[derive(NetworkBehaviour)] +struct BrahmanBehaviour { + /// Block-list a nivel de swarm: peers en este behaviour son + /// rechazados ANTES del handshake Noise. Más eficiente que + /// rechazar al nivel del handshake brahman (ahorra round-trip + /// TCP+Noise por intento denegado). Sincronizado con la + /// `PeerPolicy.deny` vía `block_peer`/`unblock_peer` exposed + /// en `BrahmanNet`. + block_list: allow_block_list::Behaviour, + stream: stream::Behaviour, + kad: kad::Behaviour, + identify: identify::Behaviour, + /// Relay server (Circuit Relay v2): un nodo alcanzable presta su + /// conexión para que dos pares detrás de NAT se contacten. Pasivo — + /// sólo actúa ante reservas/solicitudes de circuito. + relay: relay::Behaviour, + /// Relay client: permite reservar un circuito en un relay y ser + /// alcanzable vía `…/p2p//p2p-circuit/p2p/`. + relay_client: relay::client::Behaviour, + /// DCUtR: tras conectar por relay, intenta promover a conexión + /// directa (hole-punching) coordinando por el circuito. + dcutr: dcutr::Behaviour, + /// AutoNAT: confirma qué direcciones externas son realmente alcanzables + /// pidiéndole a otros peers que nos disquen de vuelta. Sustituye el + /// "confiar a ciegas en el observed_addr de identify" por direcciones + /// verificadas — sólo esas se anuncian (y entran en reservas de relay). + autonat: autonat::Behaviour, + /// mDNS: descubrimiento de pares en la MISMA LAN sin bootstrap ni IP + /// conocida (multicast 224.0.0.251). Los pares descubiertos se inyectan + /// a la routing table de Kad (igual que las listen-addrs de identify), así + /// el DHT y los protocolos de stream funcionan zero-config en LAN. + /// `Toggle`: si el socket multicast no se puede abrir (sandbox, red sin + /// multicast), queda deshabilitado y el nodo sigue andando igual. + mdns: Toggle, +} + +#[derive(Debug, thiserror::Error)] +pub enum NodeError { + #[error("transport build failed: {0}")] + Build(String), +} + +#[derive(Debug)] +enum Command { + Dial(Multiaddr), + Listen(Multiaddr), + AddDhtPeer(PeerId, Multiaddr), + FindClosestPeers(PeerId, oneshot::Sender>), + StartProviding(Vec), + StopProviding(Vec), + GetProviders(Vec, oneshot::Sender>), + BlockPeer(PeerId), + UnblockPeer(PeerId), +} + +/// Peer descubierto vía DHT: identidad + direcciones conocidas. +#[derive(Debug, Clone)] +pub struct DiscoveredPeer { + pub peer_id: PeerId, + pub addrs: Vec, +} + +/// Nodo Brahman en la malla P2P. Maneja el swarm libp2p y expone +/// API uniforme para listen/dial/DHT/streams. +pub struct BrahmanNet { + /// Identidad libp2p de este nodo. Estable mientras viva la + /// keypair (efímera por default; persistente si pasaste una + /// vía [`with_keypair`]). + pub peer_id: PeerId, + /// Keypair compartida (Arc para compartir con consumers que + /// necesitan firmar mensajes con la misma identidad — p. ej. + /// `card_handshake::network::connect_libp2p` que firma el + /// Hello). NO se expone públicamente; usar [`Self::keypair`]. + keypair: Arc, + cmd_tx: mpsc::UnboundedSender, + listen_rx: Mutex>, + /// Control para abrir y aceptar streams. Cada protocolo + /// (handshake brahman, sync minga, etc.) llama + /// `control.accept(StreamProtocol::new("/foo/1.0.0"))` para + /// recibir streams entrantes, o `control.open_stream(peer, proto)` + /// para abrirlos. Multiplexado y demultiplexado lo hace libp2p. + pub control: stream::Control, +} + +impl BrahmanNet { + /// Crea un nodo con keypair Ed25519 generada al vuelo (peer_id + /// efímero — cambia en cada arranque). + pub fn new() -> Result { + Self::with_keypair(identity::Keypair::generate_ed25519()) + } + + /// Crea un nodo con una keypair libp2p específica. Usá esto para + /// `peer_id` estable (por ejemplo si tu identidad se persiste a + /// disco, o si la derivás de la identidad criptográfica del + /// módulo). + /// + /// Sólo Ed25519 se soporta — la `keypair` se duplica internamente + /// vía clone del `ed25519::Keypair` para que tanto el swarm + /// (Noise auth) como el caller (firma de Cards) compartan la + /// misma identidad sin la fricción de que `identity::Keypair` no + /// implemente `Clone`. + pub fn with_keypair(keypair: identity::Keypair) -> Result { + let ed_kp = keypair + .try_into_ed25519() + .map_err(|_| NodeError::Build("brahman-net sólo soporta keypairs Ed25519".into()))?; + let kp_for_swarm = identity::Keypair::from(ed_kp.clone()); + let kp_for_storage = Arc::new(identity::Keypair::from(ed_kp)); + let peer_id = kp_for_swarm.public().to_peer_id(); + + let mut swarm: Swarm = SwarmBuilder::with_existing_identity(kp_for_swarm) + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + ) + .map_err(|e| NodeError::Build(format!("{e}")))? + // Inyecta el transporte de relay-client: habilita marcar y + // escuchar direcciones `…/p2p-circuit`. Provee el + // `relay_client` behaviour al closure de abajo. + .with_relay_client(noise::Config::new, yamux::Config::default) + .map_err(|e| NodeError::Build(format!("{e}")))? + .with_behaviour(|key, relay_client| { + let local = key.public().to_peer_id(); + let mut kad = + kad::Behaviour::new(local, kad::store::MemoryStore::new(local)); + // Modo Server: respondemos a queries del DHT. Auto + // requiere detectar reachability; para entornos + // controlados (localhost, redes privadas) Server es + // lo correcto. + kad.set_mode(Some(kad::Mode::Server)); + let identify = identify::Behaviour::new( + identify::Config::new(IDENTIFY_PROTOCOL.to_string(), key.public()) + .with_agent_version(format!("brahman-net/{}", env!("CARGO_PKG_VERSION"))), + ); + // mDNS resiliente: si el socket multicast no abre, seguimos sin + // descubrimiento LAN en vez de fallar el nodo entero. + let depurar = std::env::var("BRAHMAN_DEBUG").is_ok(); + let mdns = match mdns::tokio::Behaviour::new(mdns::Config::default(), local) { + Ok(b) => { + if depurar { + eprintln!("brahman: mDNS ACTIVO (descubrimiento LAN)"); + } + Toggle::from(Some(b)) + } + Err(e) => { + if depurar { + eprintln!("brahman: mDNS DESACTIVADO ({e})"); + } + Toggle::from(None) + } + }; + BrahmanBehaviour { + block_list: allow_block_list::Behaviour::default(), + stream: stream::Behaviour::new(), + kad, + identify, + relay: relay::Behaviour::new(local, Default::default()), + relay_client, + dcutr: dcutr::Behaviour::new(local), + mdns, + autonat: autonat::Behaviour::new( + local, + autonat::Config { + // Confirmamos también IPs privadas/loopback: la + // malla Brahman vive tanto en LAN como en WAN, no + // sólo en IPs globales (default `true` las + // ignoraría y nada se confirmaría en LAN). + only_global_ips: false, + // Sondeo pronto tras arrancar (default ~15 s es + // demasiado para el flujo de reservas de relay). + boot_delay: Duration::from_secs(2), + retry_interval: Duration::from_secs(5), + ..Default::default() + }, + ), + } + }) + .map_err(|e| NodeError::Build(format!("{e}")))? + .with_swarm_config(|c| c.with_idle_connection_timeout(IDLE_CONNECTION_TIMEOUT)) + .build(); + + let control = swarm.behaviour().stream.new_control(); + + let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::(); + let (listen_tx, listen_rx) = mpsc::unbounded_channel::(); + + tokio::spawn(async move { + let mut pending_finds: HashMap< + kad::QueryId, + oneshot::Sender>, + > = HashMap::new(); + let mut pending_providers: HashMap< + kad::QueryId, + (Vec, oneshot::Sender>), + > = HashMap::new(); + + loop { + tokio::select! { + Some(cmd) = cmd_rx.recv() => { + match cmd { + Command::Dial(addr) => { + let _ = swarm.dial(addr); + } + Command::Listen(addr) => { + let _ = swarm.listen_on(addr); + } + Command::AddDhtPeer(peer, addr) => { + swarm.behaviour_mut().kad.add_address(&peer, addr); + } + Command::FindClosestPeers(target, tx) => { + let qid = swarm.behaviour_mut().kad.get_closest_peers(target); + pending_finds.insert(qid, tx); + } + Command::StartProviding(key) => { + // Best-effort: si falla (sin peers cercanos para + // replicar), seguirá viviendo en el local store + // y se servirá vía get_providers de quien tenga + // conexión con nosotros. + let _ = swarm.behaviour_mut().kad.start_providing(key.into()); + } + Command::StopProviding(key) => { + // Quitamos el record local del provider store. + // Los peers cercanos eventualmente expiran su + // copia replicada por TTL natural (~24h en + // libp2p kad default); para retiro inmediato + // habría que enviar un republish con sentinel, + // pero kad no expone esa primitiva. Aceptable + // para el caso "el provider local desapareció": + // queries que pasen por nosotros dejan de + // listarnos al instante. + swarm.behaviour_mut().kad.stop_providing(&key.into()); + } + Command::GetProviders(key, tx) => { + let qid = swarm.behaviour_mut().kad.get_providers(key.into()); + pending_providers.insert(qid, (Vec::new(), tx)); + } + Command::BlockPeer(peer) => { + swarm.behaviour_mut().block_list.block_peer(peer); + } + Command::UnblockPeer(peer) => { + swarm.behaviour_mut().block_list.unblock_peer(peer); + } + } + } + event = swarm.select_next_some() => { + match event { + SwarmEvent::NewListenAddr { address, .. } => { + let _ = listen_tx.send(address); + } + // Identify nos dice las listen-addrs reales del + // peer. Las inyectamos a Kad para poblar el + // routing table sin necesidad de add_dht_peer + // manual — la propagación pasa a ser automática. + SwarmEvent::Behaviour(BrahmanBehaviourEvent::Identify( + identify::Event::Received { peer_id, info, .. } + )) => { + // El observed_addr que nos reporta identify se + // emite como CANDIDATO a externa; AutoNAT lo + // prueba pidiendo dial-backs y, si es + // alcanzable, dispara StatusChanged(Public) — + // ahí recién lo confirmamos (abajo). Las + // listen-addrs del peer pueblan la routing + // table de Kad. + for addr in info.listen_addrs { + swarm.behaviour_mut().kad.add_address(&peer_id, addr); + } + } + // mDNS descubrió pares en la LAN: inyectamos sus + // direcciones a Kad (igual que identify). Con eso el + // DHT y los streams funcionan sin bootstrap manual. + SwarmEvent::Behaviour(BrahmanBehaviourEvent::Mdns( + mdns::Event::Discovered(list) + )) => { + for (peer_id, addr) in list { + if std::env::var("BRAHMAN_DEBUG").is_ok() { + eprintln!("brahman: mDNS descubrió {peer_id} en {addr}"); + } + swarm.behaviour_mut().kad.add_address(&peer_id, addr); + } + } + // AutoNAT confirmó (vía dial-back de otros peers) + // que somos alcanzables en `addr`: recién entonces + // la anunciamos como externa — la usa el relay + // server en las reservas y la ven los pares. + SwarmEvent::Behaviour(BrahmanBehaviourEvent::Autonat( + autonat::Event::StatusChanged { new: autonat::NatStatus::Public(addr), .. } + )) => { + swarm.add_external_address(addr); + } + SwarmEvent::Behaviour(BrahmanBehaviourEvent::Kad( + kad::Event::OutboundQueryProgressed { id, result, step, .. } + )) => { + match result { + kad::QueryResult::GetClosestPeers(Ok(ok)) if step.last => { + if let Some(tx) = pending_finds.remove(&id) { + let infos = ok.peers.into_iter() + .map(|p| DiscoveredPeer { + peer_id: p.peer_id, + addrs: p.addrs, + }) + .collect(); + let _ = tx.send(infos); + } + } + kad::QueryResult::GetClosestPeers(Err(_)) if step.last => { + if let Some(tx) = pending_finds.remove(&id) { + let _ = tx.send(Vec::new()); + } + } + kad::QueryResult::GetProviders(Ok(ok)) => { + if let Some((collected, _)) = + pending_providers.get_mut(&id) + { + if let kad::GetProvidersOk::FoundProviders { + providers, .. + } = ok + { + for p in providers { + if !collected.contains(&p) { + collected.push(p); + } + } + } + } + if step.last { + if let Some((providers, tx)) = + pending_providers.remove(&id) + { + let _ = tx.send(providers); + } + } + } + kad::QueryResult::GetProviders(Err(_)) if step.last => { + if let Some((providers, tx)) = + pending_providers.remove(&id) + { + let _ = tx.send(providers); + } + } + _ => {} + } + } + _ => {} + } + } + } + } + }); + + Ok(Self { + peer_id, + keypair: kp_for_storage, + cmd_tx, + listen_rx: Mutex::new(listen_rx), + control, + }) + } + + /// Acceso a la keypair de identidad del nodo. Usar para firmar + /// payloads que viajan asociados al `peer_id` (handshake brahman + /// firmado, futuros sub-protocolos con autenticación). El `Arc` + /// permite compartir sin copia — la keypair libp2p no es `Clone`. + pub fn keypair(&self) -> Arc { + self.keypair.clone() + } + + /// Bloquea conexiones desde/hacia `peer` a nivel del swarm. + /// Conexiones existentes se cierran y nuevos intentos son + /// rechazados ANTES del Noise handshake — más eficiente que + /// rechazar al nivel del handshake brahman (ahorra round-trip + /// TCP+Noise por intento). Idempotente. + pub fn block_peer(&self, peer: PeerId) { + let _ = self.cmd_tx.send(Command::BlockPeer(peer)); + } + + /// Quita a `peer` de la block-list del swarm. Conexiones futuras + /// son aceptadas con normalidad. Idempotente. + pub fn unblock_peer(&self, peer: PeerId) { + let _ = self.cmd_tx.send(Command::UnblockPeer(peer)); + } + + /// Empieza a escuchar en `addr`. Bloquea hasta que el listener + /// publique su dirección real (Multiaddr resuelta — útil cuando + /// pediste `/ip4/0.0.0.0/tcp/0` y querés saber qué puerto te tocó). + pub async fn listen(&self, addr: Multiaddr) -> Multiaddr { + self.cmd_tx + .send(Command::Listen(addr)) + .expect("swarm task alive"); + let mut rx = self.listen_rx.lock().await; + rx.recv().await.expect("listen address arrives") + } + + /// Inicia conexión con un peer en `addr`. No-op si ya hay + /// conexión. Best-effort — fallos se loggean al swarm pero no se + /// propagan al caller (consistente con libp2p). + pub fn dial(&self, addr: Multiaddr) { + let _ = self.cmd_tx.send(Command::Dial(addr)); + } + + /// Añade un peer al routing table de Kademlia. Punto de entrada + /// para bootstrap: tras esto, el nodo puede dirigir queries DHT + /// a través de este peer. + pub fn add_dht_peer(&self, peer: PeerId, addr: Multiaddr) { + let _ = self.cmd_tx.send(Command::AddDhtPeer(peer, addr)); + } + + /// Consulta el DHT por los peers más cercanos al `target` PeerId. + /// Devuelve la lista resuelta (vacía si la query falla o si no + /// hay peers conocidos). Bloquea hasta que la query completa. + pub async fn find_closest_peers(&self, target: PeerId) -> Vec { + let (tx, rx) = oneshot::channel(); + let _ = self + .cmd_tx + .send(Command::FindClosestPeers(target, tx)); + rx.await.unwrap_or_default() + } + + /// Anuncia en el DHT que este peer tiene el contenido identificado + /// por `key`. Otros peers pueden luego descubrirlo vía + /// [`find_providers`](Self::find_providers). Best-effort: si la + /// replicación falla inicialmente, el record vive en el store + /// local hasta que llegue conexión. + pub fn start_providing(&self, key: &[u8]) { + let _ = self.cmd_tx.send(Command::StartProviding(key.to_vec())); + } + + /// Retira el anuncio previo de [`start_providing`] para `key`. + /// El record local se borra al instante (queries que lleguen a + /// nosotros dejan de listarnos). Los records replicados en peers + /// remotos viven hasta su TTL — kad no expone primitiva para + /// retracción inmediata cross-peer. Aceptable: simétrico al + /// caso "el provider apareció" (también propagación eventual). + pub fn stop_providing(&self, key: &[u8]) { + let _ = self.cmd_tx.send(Command::StopProviding(key.to_vec())); + } + + /// Consulta el DHT por peers que han anunciado proveer `key`. + /// Devuelve la lista de `PeerId`s que se reportan como providers. + /// Lista vacía si nadie anuncia. + pub async fn find_providers(&self, key: &[u8]) -> Vec { + let (tx, rx) = oneshot::channel(); + let _ = self + .cmd_tx + .send(Command::GetProviders(key.to_vec(), tx)); + rx.await.unwrap_or_default() + } +} diff --git a/shared/card/card-wit/Cargo.toml b/shared/card/card-wit/Cargo.toml new file mode 100644 index 0000000..a92e65f --- /dev/null +++ b/shared/card/card-wit/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "card-wit" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +publish.workspace = true +description = "Brahman — extractor opcional: parsea contratos WIT y devuelve `WitInterface` listo para acoplar a una `Card`." + +[dependencies] +card-core = { path = "../card-core" } +wit-parser = "0.230" +thiserror = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } + +[[example]] +name = "brahman-wit-info" +path = "examples/brahman-wit-info.rs" diff --git a/shared/card/card-wit/README.md b/shared/card/card-wit/README.md new file mode 100644 index 0000000..0568111 --- /dev/null +++ b/shared/card/card-wit/README.md @@ -0,0 +1,34 @@ +# brahman-card-wit + +> **DORMIDO (2026-05-30).** Capa 3 de Brahman. Ver `/BRAHMAN.md`. + +Parser opcional de contratos WIT (`.wit` texto → `Vec`, uno por `world`), +sobre `wit-parser` (sin `wasm-tools`/`wit-component`). + +## Estado: relegado, no borrado + +La visión original de Brahman incluía **módulos agnósticos descritos por interfaz WIT** (eventualmente WASM). +Esa capa **nunca se ejecutó**: + +- No existe ningún archivo `.wit` en el workspace. +- Ningún crate de producción depende de este crate — sólo `examples/brahman-wit-info.rs` y la + **dev-dependency** de `card-sidecar`. + +Se conserva (funciona, 210 LOC, reversible) por si algún día aparecen `.wit` reales. **No asumir que está +en ninguna ruta de build.** + +## El contrato agnóstico real y vigente + +``` +shared/card (formato Card) + card-handshake (handshake nativo Rust) + DhtKey (namespacing en la DHT) +``` + +El tipo de metadata `WitInterface` vive en **`card-core`** (no aquí) y **sí** lo usa el broker +(`chasqui-broker`) para matching estructural — es metadata opcional viva. Lo único dormido es *este parser* +de archivos `.wit` inexistentes. + +## Si se decide revivir WIT + +Requeriría: (1) que el Init lea `/wit/protocol.wit` en el descubrimiento y construya +`ResolvedCard::from_conscious(card, wit)`; (2) que algún módulo de producción publique un `.wit`. +Hasta entonces, este crate es tooling latente (`cargo run -p card-wit --example brahman-wit-info -- `). diff --git a/shared/card/card-wit/examples/brahman-wit-info.rs b/shared/card/card-wit/examples/brahman-wit-info.rs new file mode 100644 index 0000000..ef58524 --- /dev/null +++ b/shared/card/card-wit/examples/brahman-wit-info.rs @@ -0,0 +1,45 @@ +//! `brahman-wit-info` — inspecciona un archivo WIT y lista sus worlds. +//! +//! Uso: +//! ```sh +//! cargo run -p brahman-card-wit --example brahman-wit-info -- shared_wit/protocol.wit +//! ``` + +use std::process::ExitCode; + +fn main() -> ExitCode { + let path = match std::env::args().nth(1) { + Some(p) => p, + None => { + eprintln!("uso: brahman-wit-info "); + return ExitCode::from(2); + } + }; + + let worlds = match card_wit::parse_wit_file(&path) { + Ok(w) => w, + Err(e) => { + eprintln!("error parseando {path}: {e}"); + return ExitCode::from(1); + } + }; + + if worlds.is_empty() { + println!("(ningún world declarado)"); + return ExitCode::SUCCESS; + } + + println!("{} world(s):", worlds.len()); + for w in &worlds { + println!(); + println!(" package: {}", w.package); + println!(" world: {}", w.world); + if !w.imports.is_empty() { + println!(" imports: {}", w.imports.join(", ")); + } + if !w.exports.is_empty() { + println!(" exports: {}", w.exports.join(", ")); + } + } + ExitCode::SUCCESS +} diff --git a/shared/card/card-wit/src/lib.rs b/shared/card/card-wit/src/lib.rs new file mode 100644 index 0000000..6d37e29 --- /dev/null +++ b/shared/card/card-wit/src/lib.rs @@ -0,0 +1,178 @@ +//! `brahman-card-wit` — extractor de contratos WIT. +//! +//! **DORMIDO (2026-05-30).** No existe ningún archivo `.wit` en el workspace +//! y ningún crate de producción depende de éste (sólo `examples/` y la +//! dev-dependency de `card-sidecar`). Es la "Capa 3" de Brahman (ver `/BRAHMAN.md`): +//! la idea de módulos agnósticos descritos por interfaz WIT nunca se ejecutó. +//! El **contrato agnóstico real y vigente** es `shared/card` (formato `Card`) + +//! el handshake nativo Rust de `card-handshake` + el namespacing de `DhtKey`. +//! Se conserva como herramienta opcional por si algún día aparecen `.wit` +//! reales; no asumir que está en ninguna ruta de build. +//! +//! Nota: el tipo de metadata [`WitInterface`] vive en `card-core` y SÍ lo usa +//! el broker para matching estructural — eso es metadata opcional viva. Lo +//! dormido es este *parser* de archivos `.wit` inexistentes. +//! +//! Crate **opcional** (no es dep de `brahman-card`). Parsea texto WIT +//! mediante [`wit-parser`] y devuelve una lista de [`WitInterface`] +//! (uno por `world`) lista para acoplarse a una [`card_core::Card`] +//! cuando se construye una [`card_core::ResolvedCard`]. +//! +//! Casos de uso (hipotéticos hasta que existan `.wit`): +//! +//! - El Init lee `/wit/protocol.wit` durante el descubrimiento +//! y lo combina con la Card del módulo para obtener una +//! `ResolvedCard::from_conscious(card, wit)`. +//! - Tooling (`brahman-wit-info`) inspecciona un `.wit` y muestra +//! sus mundos, exports e imports. +//! +//! No depende de `wasm-tools`/`wit-component` — sólo del parser texto. + +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms)] + +use std::path::{Path, PathBuf}; + +use card_core::WitInterface; +use thiserror::Error; +use wit_parser::{Resolve, WorldKey}; + +#[derive(Debug, Error)] +pub enum WitError { + #[error("parse: {0}")] + Parse(String), + #[error("E/S: {0}")] + Io(#[from] std::io::Error), +} + +/// Parsea WIT desde una string. Devuelve un `WitInterface` por cada +/// `world` declarado. +pub fn parse_wit(source: &str) -> Result, WitError> { + parse_with_path(source, Path::new("inline.wit")) +} + +/// Parsea WIT desde un archivo. Útil para `module/wit/protocol.wit`. +pub fn parse_wit_file(path: impl AsRef) -> Result, WitError> { + let p = path.as_ref(); + let source = std::fs::read_to_string(p)?; + parse_with_path(&source, p) +} + +fn parse_with_path(source: &str, path: &Path) -> Result, WitError> { + let mut resolve = Resolve::new(); + let path_buf: PathBuf = path.to_path_buf(); + resolve + .push_str(&path_buf, source) + .map_err(|e| WitError::Parse(e.to_string()))?; + + let mut out = Vec::new(); + for (_pkg_id, pkg) in resolve.packages.iter() { + let pkg_name = pkg.name.to_string(); + for (_name, &world_id) in &pkg.worlds { + let world = &resolve.worlds[world_id]; + let exports = collect_keys(world.exports.iter().map(|(k, _)| k), &resolve); + let imports = collect_keys(world.imports.iter().map(|(k, _)| k), &resolve); + out.push(WitInterface { + package: pkg_name.clone(), + world: world.name.clone(), + exports, + imports, + }); + } + } + Ok(out) +} + +fn collect_keys<'a, I>(keys: I, resolve: &Resolve) -> Vec +where + I: Iterator, +{ + keys.map(|k| match k { + WorldKey::Name(n) => n.clone(), + WorldKey::Interface(id) => resolve.interfaces[*id] + .name + .clone() + .unwrap_or_else(|| format!("", id.index())), + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &str = r#" +package brahman:test@0.1.0; + +interface handshake { + hello: func() -> result<_, string>; +} + +interface lifecycle { + report: func(); +} + +world module { + import handshake; + import lifecycle; + export run: func() -> result<_, string>; +} +"#; + + #[test] + fn parses_inline_wit() { + let worlds = parse_wit(SAMPLE).unwrap(); + assert_eq!(worlds.len(), 1, "esperaba un único world"); + let w = &worlds[0]; + assert!(w.package.starts_with("brahman:test")); + assert_eq!(w.world, "module"); + assert!( + w.imports.iter().any(|i| i == "handshake"), + "imports={:?}", + w.imports + ); + assert!( + w.imports.iter().any(|i| i == "lifecycle"), + "imports={:?}", + w.imports + ); + assert!( + w.exports.iter().any(|e| e == "run"), + "exports={:?}", + w.exports + ); + } + + #[test] + fn parses_shared_protocol() { + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../shared_wit/protocol.wit"); + let worlds = parse_wit_file(path).unwrap(); + assert!( + worlds.iter().any(|w| w.world == "module"), + "no encontró world 'module' en {:?}", + worlds.iter().map(|w| &w.world).collect::>() + ); + assert!( + worlds.iter().any(|w| w.world == "admin-host"), + "no encontró world 'admin-host'" + ); + } + + #[test] + fn parse_error_on_garbage() { + let bad = "this is not wit at all { } } ;;;;"; + assert!(matches!(parse_wit(bad), Err(WitError::Parse(_)))); + } + + #[test] + fn empty_world_handled() { + let src = r#" +package brahman:empty@0.1.0; +world hollow {} +"#; + let worlds = parse_wit(src).unwrap(); + assert_eq!(worlds.len(), 1); + assert!(worlds[0].exports.is_empty()); + assert!(worlds[0].imports.is_empty()); + } +}