diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a7cfc24..17e9f77 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -430,10 +430,16 @@ multi-modal payloads (text inline, audio/photo/data via `iroh-blobs`). Receiving --example iroh_engine` → the **real Engine** (unchanged public API) syncs a clipboard A→B, E2E-decrypted, **no SSE/RTC/LAN/relay**. The seam paid off — zero engine/client changes. + - [x] **Files via `iroh-blobs`** — `send_file` adds the (sealed) bytes + to a `MemStore`, broadcasts a blob announcement over gossip; + peers fetch P2P by hash and emit `MeshEvent::File`. Replaces the + M/C/E frame protocol. Verified: a 200 KB photo syncs A→B + E2E-decrypted alongside text (`iroh_engine` example). - [ ] Remaining: rendezvous for zero-config bootstrap (today via - `TETHER_IROH_BOOTSTRAP` env stopgap); file transfer via - `iroh-blobs`; make webrtc/mdns/reqwest optional so the iroh/wasm - build is lean; A/B vs RTC+LAN; flip the feature on by default. + `TETHER_IROH_BOOTSTRAP` env stopgap); cross-compile the feature + for iOS + build the xcframework with it; make webrtc/mdns/reqwest + optional so the iroh/wasm build is lean; A/B vs RTC+LAN; flip the + feature on by default. - [ ] Spike **Automerge** as the Sync tier; re-express the clipboard as a tiny app over replicated state to validate the App API. - [ ] Durable device keypairs + authenticated handshake (Noise/`snow`, or diff --git a/core/Cargo.lock b/core/Cargo.lock index 511d291..59c1b93 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -161,7 +161,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.118", ] [[package]] @@ -185,7 +185,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "asn1-rs-derive", + "asn1-rs-derive 0.5.1", "asn1-rs-impl", "displaydoc", "nom", @@ -195,6 +195,22 @@ dependencies = [ "time", ] +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "asn1-rs-derive" version = "0.5.1" @@ -203,7 +219,19 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", "synstructure", ] @@ -215,7 +243,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -226,7 +254,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -240,6 +268,15 @@ dependencies = [ "rustc_version", ] +[[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" @@ -275,6 +312,25 @@ dependencies = [ "tokio", ] +[[package]] +name = "bao-tree" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06384416b1825e6e04fde63262fda2dc408f5b64c02d04e0d8b70ae72c17a52b" +dependencies = [ + "blake3", + "bytes", + "futures-lite", + "genawaiter", + "iroh-io", + "positioned-io", + "range-collections", + "self_cell", + "serde", + "smallvec", + "tokio", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -308,6 +364,12 @@ dependencies = [ "serde", ] +[[package]] +name = "binary-merge" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597bb81c80a54b6a4381b23faba8d7774b144c94cbd1d6fe3f1329bd776554ab" + [[package]] name = "bincode" version = "1.3.3" @@ -317,6 +379,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -405,7 +476,7 @@ checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -546,8 +617,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -593,7 +666,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -850,7 +923,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -874,7 +947,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.118", ] [[package]] @@ -885,7 +958,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -911,7 +984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn", + "syn 2.0.118", ] [[package]] @@ -942,7 +1015,21 @@ version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.2", "displaydoc", "nom", "num-bigint", @@ -974,7 +1061,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -984,7 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.118", ] [[package]] @@ -1006,7 +1093,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.118", "unicode-xid", ] @@ -1059,7 +1146,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1087,7 +1174,7 @@ dependencies = [ "cbc", "ccm", "chacha20poly1305", - "der-parser", + "der-parser 9.0.0", "hmac", "log", "p256", @@ -1095,7 +1182,7 @@ dependencies = [ "portable-atomic", "rand 0.9.4", "rand_core 0.6.4", - "rcgen", + "rcgen 0.13.2", "ring", "rkyv", "rustls", @@ -1106,7 +1193,7 @@ dependencies = [ "tokio", "webrtc-util", "x25519-dalek", - "x509-parser", + "x509-parser 0.16.0", ] [[package]] @@ -1197,7 +1284,7 @@ checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1216,6 +1303,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fastbloom" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" +dependencies = [ + "foldhash", + "libm", + "portable-atomic", + "siphasher 1.0.3", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -1392,7 +1491,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1424,6 +1523,37 @@ dependencies = [ "slab", ] +[[package]] +name = "genawaiter" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" +dependencies = [ + "futures-core", + "genawaiter-macro", + "genawaiter-proc-macro", + "proc-macro-hack", +] + +[[package]] +name = "genawaiter-macro" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" + +[[package]] +name = "genawaiter-proc-macro" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784f84eebc366e15251c4a8c3acee82a6a6f427949776ecb88377362a9621738" +dependencies = [ + "proc-macro-error", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "generator" version = "0.8.9" @@ -1560,6 +1690,15 @@ dependencies = [ "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.17.1" @@ -1571,6 +1710,20 @@ dependencies = [ "foldhash", ] +[[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 0.9.8", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -1982,6 +2135,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inplace-vec-builder" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf64c2edc8226891a71f127587a2861b132d2b942310843814d5001d99a1d307" +dependencies = [ + "smallvec", +] + [[package]] name = "interceptor" version = "0.17.1" @@ -2095,6 +2257,47 @@ dependencies = [ "zeroize", ] +[[package]] +name = "iroh-blobs" +version = "0.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be50b0e2d0a9ba65cee4e0dfb708b3704e02ad12bd4c14c6307e94245943126" +dependencies = [ + "arrayvec", + "bao-tree", + "bytes", + "cfg_aliases", + "chrono", + "constant_time_eq", + "data-encoding", + "derive_more", + "genawaiter", + "getrandom 0.4.3", + "hex", + "iroh", + "iroh-base", + "iroh-io", + "iroh-metrics", + "iroh-tickets", + "iroh-util", + "irpc", + "n0-error", + "n0-future", + "nested_enum_utils", + "noq", + "postcard", + "rand 0.10.1", + "range-collections", + "redb", + "ref-cast", + "reflink-copy", + "self_cell", + "serde", + "smallvec", + "tokio", + "tracing", +] + [[package]] name = "iroh-dns" version = "1.0.0" @@ -2146,6 +2349,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "iroh-io" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a5feb781017b983ff1b155cd1faf8174da2acafd807aa482876da2d7e6577a" +dependencies = [ + "bytes", + "futures-lite", + "pin-project", + "smallvec", + "tokio", +] + [[package]] name = "iroh-metrics" version = "1.0.1" @@ -2170,7 +2386,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2219,17 +2435,51 @@ dependencies = [ "ws_stream_wasm", ] +[[package]] +name = "iroh-tickets" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da53233419ca36bf521ed45683b7748366f9b233032891eefc2d70567a84ac54" +dependencies = [ + "data-encoding", + "derive_more", + "iroh-base", + "n0-error", + "postcard", + "serde", +] + +[[package]] +name = "iroh-util" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e41eb982f15230c55f0a70a74a514360e1f565b07861924fd0e8db172b3d00" +dependencies = [ + "derive_more", + "iroh", + "n0-error", + "n0-future", + "tokio", + "tracing", +] + [[package]] name = "irpc" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3623d6ff582b415904b29bbe6ebcb4a4f9a262ccdee05a45fdd003ef0950c386" dependencies = [ + "futures-buffered", "futures-util", "irpc-derive", "n0-error", "n0-future", + "noq", + "postcard", + "rcgen 0.14.8", + "rustls", "serde", + "smallvec", "tokio", "tokio-util", "tracing", @@ -2243,7 +2493,7 @@ checksum = "35c254013736de16472140d26904e6ac98e8f3887284dcf4af40f88c77411b56" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2301,7 +2551,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.118", ] [[package]] @@ -2329,7 +2579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2355,6 +2605,18 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -2527,7 +2789,7 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2549,7 +2811,7 @@ checksum = "e2acd8b070213b0299282f884b4beba4e7b52d624fdcd504a3ad3665390c11e1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2590,6 +2852,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "nested_enum_utils" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d5475271bdd36a4a2769eac1ef88df0f99428ea43e52dfd8b0ee5cb674695f" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "netdev" version = "0.44.0" @@ -2769,6 +3043,7 @@ dependencies = [ "bytes", "derive_more", "enum-assoc", + "fastbloom", "getrandom 0.4.3", "identity-hash", "lru-slab", @@ -2778,6 +3053,7 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "slab", "sorted-index-buffer", "thiserror 2.0.18", @@ -2861,7 +3137,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2969,7 +3245,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.2", ] [[package]] @@ -3130,7 +3415,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3245,6 +3530,16 @@ dependencies = [ "url", ] +[[package]] +name = "positioned-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ec4b80060f033312b99b6874025d9503d2af87aef2dd4c516e253fbfcdada7" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "postcard" version = "1.1.3" @@ -3254,6 +3549,7 @@ dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", + "heapless", "postcard-derive", "serde", ] @@ -3266,7 +3562,7 @@ checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3322,6 +3618,38 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "syn-mid", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -3348,7 +3676,7 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3509,6 +3837,19 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "range-collections" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "861706ea9c4aded7584c5cd1d241cec2ea7f5f50999f236c22b65409a1f1a0d0" +dependencies = [ + "binary-merge", + "inplace-vec-builder", + "ref-cast", + "serde", + "smallvec", +] + [[package]] name = "rcgen" version = "0.13.2" @@ -3519,8 +3860,31 @@ dependencies = [ "ring", "rustls-pki-types", "time", - "x509-parser", - "yasna", + "x509-parser 0.16.0", + "yasna 0.5.2", +] + +[[package]] +name = "rcgen" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", + "yasna 0.6.0", +] + +[[package]] +name = "redb" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e925444704b5f17d32bf42f5b6e2df050bceebc3dcd6e71cc73dafe8092e839" +dependencies = [ + "libc", ] [[package]] @@ -3532,6 +3896,38 @@ dependencies = [ "bitflags 2.13.0", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "reflink-copy" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9dd7ab4af0363d5ccfd2838d782a28196cf32a5cc2e4fe3c5dc83f2be588b8b" +dependencies = [ + "cfg-if", + "libc", + "rustix", + "windows", +] + [[package]] name = "regex" version = "1.12.4" @@ -3705,7 +4101,7 @@ checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3758,6 +4154,19 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.40" @@ -3892,7 +4301,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3954,6 +4363,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + [[package]] name = "semver" version = "1.0.28" @@ -4007,7 +4422,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4156,6 +4571,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.12" @@ -4167,6 +4588,9 @@ name = "smallvec" version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] [[package]] name = "smawk" @@ -4228,7 +4652,7 @@ checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4302,7 +4726,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4339,6 +4763,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.118" @@ -4350,6 +4785,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-mid" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea305d57546cc8cd04feb14b62ec84bf17f50e3f7b12560d7bfa9265f39d9ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4367,7 +4813,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4408,6 +4854,7 @@ dependencies = [ "futures-util", "getrandom 0.4.3", "iroh", + "iroh-blobs", "iroh-gossip", "mdns-sd", "reqwest 0.12.28", @@ -4454,7 +4901,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4465,7 +4912,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4560,7 +5007,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4726,7 +5173,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4870,7 +5317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" dependencies = [ "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4900,7 +5347,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.118", "toml", "uniffi_meta", ] @@ -4913,7 +5360,7 @@ checksum = "4a74ed96c26882dac1ca9b93ca23c827e284bacbd7ec23c6f0b0372f747d59e4" dependencies = [ "anyhow", "bytes", - "siphasher", + "siphasher 0.3.11", "uniffi_checksum_derive", ] @@ -5129,7 +5576,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -5222,7 +5669,7 @@ dependencies = [ "log", "portable-atomic", "rand 0.9.4", - "rcgen", + "rcgen 0.13.2", "regex", "ring", "rtcp", @@ -5474,7 +5921,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5485,7 +5932,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5837,18 +6284,36 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", "data-encoding", - "der-parser", + "der-parser 9.0.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.7.1", "ring", "rusticata-macros", "thiserror 1.0.69", "time", ] +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs 0.7.2", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "xml-rs" version = "0.8.28" @@ -5873,6 +6338,16 @@ dependencies = [ "time", ] +[[package]] +name = "yasna" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +dependencies = [ + "bit-vec", + "time", +] + [[package]] name = "yoke" version = "0.8.3" @@ -5892,7 +6367,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -5913,7 +6388,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5933,7 +6408,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -5954,7 +6429,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -5987,7 +6462,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] diff --git a/core/Cargo.toml b/core/Cargo.toml index 6fbba14..ba02fd3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -34,10 +34,11 @@ getrandom = "0.4.3" # the hand-rolled SSE/RTC/LAN stack. iroh = { version = "1", optional = true } iroh-gossip = { version = "0.101", optional = true } +iroh-blobs = { version = "0.103", optional = true } [features] default = [] -iroh-transport = ["dep:iroh", "dep:iroh-gossip"] +iroh-transport = ["dep:iroh", "dep:iroh-gossip", "dep:iroh-blobs"] [dev-dependencies] # Iroh spikes (examples/iroh_*.rs) link iroh directly regardless of the feature. diff --git a/core/examples/iroh_engine.rs b/core/examples/iroh_engine.rs index 7041ecb..c90e344 100644 --- a/core/examples/iroh_engine.rs +++ b/core/examples/iroh_engine.rs @@ -22,13 +22,18 @@ impl MessageHandler for Noop { fn on_file(&self, _n: String, _m: String, _d: Vec) {} } -struct Collector(mpsc::Sender); +struct Collector { + text: mpsc::Sender, + file: mpsc::Sender<(String, usize)>, +} impl MessageHandler for Collector { fn on_message(&self, m: Message) { - let _ = self.0.send(m.text); + let _ = self.text.send(m.text); } fn on_status(&self, _c: bool) {} - fn on_file(&self, _n: String, _m: String, _d: Vec) {} + fn on_file(&self, name: String, _mime: String, data: Vec) { + let _ = self.file.send((name, data.len())); + } } fn main() { @@ -61,28 +66,52 @@ fn main() { // Joiner (bootstraps to A via the env var its IrohTransport reads at start). let (tx, rx) = mpsc::channel(); + let (ftx, frx) = mpsc::channel(); let b = Engine::new( server.into(), room.into(), "iroh-b".into(), "ios".into(), "B".into(), account.into(), ); - b.start(Box::new(Collector(tx))); + b.start(Box::new(Collector { text: tx, file: ftx })); eprintln!("waiting for gossip topic to form…"); std::thread::sleep(Duration::from_secs(8)); + // 1) Text clipboard over gossip. let payload = "clipboard synced over iroh 🎉"; - eprintln!("A sending: {payload:?}"); + eprintln!("A sending text: {payload:?}"); a.send(payload.to_string()); - match rx.recv_timeout(Duration::from_secs(10)) { Ok(t) if t == payload => { - println!("✅ engine-over-iroh: B received {t:?} (E2E-decrypted, via gossip, no relay/SSE/RTC/LAN)"); + println!("✅ text: B received {t:?} (E2E-decrypted, via gossip)"); + } + Ok(t) => { + println!("⚠️ B got {t:?} (expected {payload:?})"); + std::process::exit(1); } - Ok(t) => println!("⚠️ B got {t:?} (expected {payload:?})"), Err(_) => { - println!("❌ B received nothing — gossip topic may not have formed"); + println!("❌ B received no text"); std::process::exit(1); } } + + // 2) A "photo"/blob over iroh-blobs (announced via gossip, fetched P2P). + let photo: Vec = (0..200_000u32).map(|i| (i % 251) as u8).collect(); + eprintln!("A sending blob: photo.bin ({} bytes)", photo.len()); + a.send_file("photo.bin".into(), "image/png".into(), photo.clone()); + match frx.recv_timeout(Duration::from_secs(15)) { + Ok((name, len)) if len == photo.len() => { + println!("✅ blob: B received {name:?} ({len} bytes, E2E-decrypted, via iroh-blobs)"); + } + Ok((name, len)) => { + println!("⚠️ B got {name:?} with {len} bytes (expected {})", photo.len()); + std::process::exit(1); + } + Err(_) => { + println!("❌ B received no blob"); + std::process::exit(1); + } + } + + println!("🎉 engine-over-iroh: text + photo synced, no SSE/RTC/LAN/relay"); a.stop(); b.stop(); } diff --git a/core/src/iroh.rs b/core/src/iroh.rs index d18c8f8..55e2d86 100644 --- a/core/src/iroh.rs +++ b/core/src/iroh.rs @@ -1,7 +1,8 @@ //! IrohTransport — the Mesh, on iroh. The end state of M4: this one transport //! subsumes the hand-rolled SSE/RTC/LAN stack. iroh handles key-addressing, //! holepunch, and relay; `iroh-gossip` carries the clipboard `Message`s and -//! presence over the account-derived topic. +//! presence; `iroh-blobs` carries files/photos/data (content-addressed, fetched +//! P2P on demand) — replacing the hand-rolled M/C/E frame protocol. //! //! It implements the same `Transport` trait and emits the same `MeshEvent`s as //! the legacy transports, so the engine event loop and every client are @@ -9,38 +10,64 @@ //! //! Bootstrap note (v1): joining the gossip topic needs ≥1 peer id. Until the //! rendezvous supernode lands (see ARCHITECTURE §M3), bootstrap ids come from -//! the `TETHER_IROH_BOOTSTRAP` env var (comma-separated). That's the one -//! stopgap; everything else is production-shaped. +//! the `TETHER_IROH_BOOTSTRAP` env var (comma-separated). use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex as StdMutex}; use futures_util::TryStreamExt; use iroh::{endpoint::presets, protocol::Router, Endpoint, EndpointId}; +use iroh_blobs::{store::mem::MemStore, BlobsProtocol, Hash}; use iroh_gossip::{ api::{Event, GossipSender}, net::{Gossip, GOSSIP_ALPN}, proto::TopicId, }; +use serde::{Deserialize, Serialize}; use tokio::runtime::Handle; use crate::{Config, EventTx, MeshEvent, Message, Transport}; +/// Blob handles needed by `send_file` / blob downloads, set once online. +#[derive(Clone)] +struct Blobs { + store: MemStore, + endpoint: Endpoint, + my_id: String, +} + pub(crate) struct IrohTransport { cfg: Config, events: EventTx, /// Set once the gossip topic is joined; used by `publish`. sender: Arc>>, + /// Blob store + endpoint, set once online; used by `send_file` and downloads. + blobs: Arc>>, /// This node's iroh EndpointId, surfaced once online (for bootstrap wiring). id: Arc>>, } impl IrohTransport { pub(crate) fn new(cfg: Config, events: EventTx, id: Arc>>) -> Self { - Self { cfg, events, sender: Arc::new(StdMutex::new(None)), id } + Self { + cfg, + events, + sender: Arc::new(StdMutex::new(None)), + blobs: Arc::new(StdMutex::new(None)), + id, + } } } +/// A blob announcement, broadcast over gossip so peers can fetch the bytes P2P. +#[derive(Serialize, Deserialize)] +struct BlobAnnounce { + hash: String, // blake3 hash of the (sealed) bytes + name: String, + mime: String, + provider: String, // the sender's iroh EndpointId to download from +} + /// account/room → 32-byte gossip topic (consistent with room_for_account: the /// room is already sha256(account)[..4], this is the full-width derivation). fn topic_for(room: &str) -> TopicId { @@ -62,6 +89,34 @@ fn bootstrap_ids() -> Vec { .collect() } +impl IrohTransport { + /// Fetch a blob announced over gossip from its provider and surface it. + async fn fetch_blob(blobs: Blobs, events: EventTx, ann: BlobAnnounce) { + let (Ok(hash), Ok(provider)) = + (ann.hash.parse::(), ann.provider.parse::()) + else { + return; + }; + let downloader = blobs.store.downloader(&blobs.endpoint); + if let Err(e) = downloader.download(hash, Some(provider)).await { + eprintln!("tethercore[iroh]: blob download failed: {e}"); + return; + } + match blobs.store.get_bytes(hash).await { + Ok(bytes) => { + eprintln!("tethercore[iroh]: fetched blob {:?} ({} bytes)", ann.name, bytes.len()); + // Bytes are still sealed; the engine's File handler decrypts. + let _ = events.send(MeshEvent::File { + name: ann.name, + mime: ann.mime, + data: bytes.to_vec(), + }); + } + Err(e) => eprintln!("tethercore[iroh]: blob read failed: {e}"), + } + } +} + impl Transport for IrohTransport { fn name(&self) -> &'static str { "iroh" @@ -87,9 +142,17 @@ impl Transport for IrohTransport { eprintln!("tethercore[iroh]: online id={my_id}"); let gossip = Gossip::builder().spawn(endpoint.clone()); + let store = MemStore::new(); + let blobs_proto = BlobsProtocol::new(&store, None); let router = Router::builder(endpoint.clone()) .accept(GOSSIP_ALPN, gossip.clone()) + .accept(iroh_blobs::ALPN, blobs_proto) .spawn(); + *this.blobs.lock().unwrap() = Some(Blobs { + store: store.clone(), + endpoint: endpoint.clone(), + my_id: my_id.clone(), + }); let topic = topic_for(&this.cfg.room); let sub = match gossip.subscribe_and_join(topic, bootstrap_ids()).await { @@ -107,10 +170,22 @@ impl Transport for IrohTransport { while running.load(Ordering::SeqCst) { match receiver.try_next().await { Ok(Some(Event::Received(msg))) => { - if let Ok(m) = serde_json::from_slice::(&msg.content) { - if m.from != this.cfg.from { - let _ = this.events.send(MeshEvent::Message(m)); + let Ok(m) = serde_json::from_slice::(&msg.content) else { + continue; + }; + if m.from == this.cfg.from { + continue; // our own broadcast + } + if m.kind == "blob" { + // File announcement → fetch the bytes P2P, then emit File. + if let Ok(ann) = serde_json::from_str::(&m.text) { + if let Some(blobs) = this.blobs.lock().unwrap().clone() { + let events = this.events.clone(); + tokio::spawn(Self::fetch_blob(blobs, events, ann)); + } } + } else { + let _ = this.events.send(MeshEvent::Message(m)); } } Ok(Some(Event::NeighborUp(id))) => { @@ -119,8 +194,8 @@ impl Transport for IrohTransport { Ok(Some(Event::NeighborDown(id))) => { let _ = this.events.send(MeshEvent::DirectDown(id.to_string())); } - Ok(Some(_)) => {} // Lagged, etc. - Ok(None) => break, // stream ended + Ok(Some(_)) => {} // Lagged, etc. + Ok(None) => break, // stream ended Err(e) => { eprintln!("tethercore[iroh]: recv error: {e}"); break; @@ -147,4 +222,43 @@ impl Transport for IrohTransport { }); } } + + /// Add the (already-sealed) bytes to the local blob store and broadcast a + /// blob announcement over gossip; peers fetch the bytes P2P by hash. + fn send_file(&self, rt: &Handle, _id: String, name: String, mime: String, data: Vec) { + let blobs = self.blobs.lock().unwrap().clone(); + let sender = self.sender.lock().unwrap().clone(); + let cfg = self.cfg.clone(); + let (Some(blobs), Some(sender)) = (blobs, sender) else { + return; + }; + rt.spawn(async move { + let tag = match blobs.store.add_slice(&data).await { + Ok(t) => t, + Err(e) => { + eprintln!("tethercore[iroh]: blob add failed: {e}"); + return; + } + }; + let ann = BlobAnnounce { + hash: tag.hash.to_string(), + name, + mime, + provider: blobs.my_id.clone(), + }; + let msg = Message { + kind: "blob".into(), + text: serde_json::to_string(&ann).unwrap_or_default(), + from: cfg.from.clone(), + to: String::new(), + role: String::new(), + source: cfg.source.clone(), + room: cfg.room.clone(), + ts: 0, + }; + if let Ok(body) = serde_json::to_vec(&msg) { + let _ = sender.broadcast(body.into()).await; + } + }); + } }