diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fa15a8b02..74f74a96e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,7 @@ env: RUST_BACKTRACE: 1 RUSTFLAGS: -Dwarnings RUSTDOCFLAGS: -Dwarnings - MSRV: "1.76" + MSRV: "1.81" SCCACHE_CACHE_SIZE: "50G" IROH_FORCE_STAGING_RELAYS: "1" @@ -204,7 +204,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-05-02 + toolchain: nightly-2024-11-30 - name: Install sccache uses: mozilla-actions/sccache-action@v0.0.6 diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index efe80a993..8aac4efca 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-05-02 + toolchain: nightly-2024-11-30 - name: Install sccache uses: mozilla-actions/sccache-action@v0.0.6 @@ -68,6 +68,6 @@ jobs: comment-id: ${{ steps.fc.outputs.comment-id }} body: | Documentation for this PR has been generated and is available at: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/${{ env.PREVIEW_PATH }}/iroh_blobs/ - + Last updated: ${{ env.TIMESTAMP }} edit-mode: replace diff --git a/Cargo.lock b/Cargo.lock index abe4aaa92..ae11cb1a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -267,7 +267,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower", "tower-layer", @@ -290,7 +290,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "shlex", ] @@ -497,9 +497,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -523,9 +523,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -533,9 +533,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -557,9 +557,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cobs" @@ -667,6 +667,24 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +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.20" @@ -1092,9 +1110,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" @@ -1241,7 +1259,7 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -1470,33 +1488,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-proto" -version = "0.24.1" +version = "0.25.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand", - "thiserror 1.0.69", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-proto" -version = "0.25.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8270a1857fb962b9914aafd46a89a187a4e63d0eb4190c327e7c7b8256a2d055" +checksum = "d063c0692ee669aa6d261988aa19ca5510f1cc40e4f211024f50c888499a35d7" dependencies = [ "async-recursion", "async-trait", @@ -1506,12 +1500,11 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.5.0", + "idna", "ipnet", "once_cell", "rand", - "thiserror 1.0.69", - "time", + "thiserror 2.0.6", "tinyvec", "tokio", "tracing", @@ -1520,21 +1513,21 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.25.0-alpha.2" +version = "0.25.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c110355b5703070d9e29c344d79818a7cde3de9c27fc35750defea6074b0ad" +checksum = "42bc352e4412fb657e795f79b4efcf2bd60b59ee5ca0187f3554194cd1107a27" dependencies = [ "cfg-if", "futures-util", - "hickory-proto 0.25.0-alpha.2", + "hickory-proto", "ipconfig", - "lru-cache", + "moka", "once_cell", "parking_lot", "rand", "resolv-conf", "smallvec", - "thiserror 1.0.69", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -1560,9 +1553,9 @@ dependencies = [ [[package]] name = "hmac-sha256" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" +checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb" [[package]] name = "hostname" @@ -1848,26 +1841,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.0.3" @@ -1981,7 +1954,7 @@ checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "iroh" version = "0.29.0" -source = "git+https://github.com/n0-computer/iroh#321d8ffd3d2bde2f1a80e78eab1ad83687484fc2" +source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0" dependencies = [ "anyhow", "axum", @@ -1999,7 +1972,6 @@ dependencies = [ "genawaiter", "governor", "hex", - "hickory-proto 0.25.0-alpha.2", "hickory-resolver", "hostname 0.4.0", "http 1.2.0", @@ -2023,7 +1995,6 @@ dependencies = [ "netwatch", "num_enum", "once_cell", - "parking_lot", "pin-project", "pkarr", "portmapper", @@ -2043,7 +2014,7 @@ dependencies = [ "stun-rs", "surge-ping", "swarm-discovery", - "thiserror 2.0.4", + "thiserror 2.0.6", "time", "tokio", "tokio-rustls", @@ -2064,7 +2035,7 @@ dependencies = [ [[package]] name = "iroh-base" version = "0.29.0" -source = "git+https://github.com/n0-computer/iroh#321d8ffd3d2bde2f1a80e78eab1ad83687484fc2" +source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0" dependencies = [ "aead", "anyhow", @@ -2074,15 +2045,13 @@ dependencies = [ "ed25519-dalek", "getrandom", "hex", - "iroh-blake3", "once_cell", "postcard", "rand", "rand_core", - "redb", "serde", "ssh-key", - "thiserror 2.0.4", + "thiserror 2.0.6", "ttl_cache", "url", "zeroize", @@ -2112,6 +2081,7 @@ dependencies = [ "chrono", "clap", "console", + "data-encoding", "derive_more", "futures-buffered", "futures-lite 2.5.0", @@ -2123,6 +2093,7 @@ dependencies = [ "indicatif", "iroh", "iroh-base", + "iroh-blake3", "iroh-io", "iroh-metrics", "iroh-quinn", @@ -2152,7 +2123,7 @@ dependencies = [ "tempfile", "testdir", "testresult", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tokio-util", "tracing", @@ -2198,7 +2169,7 @@ dependencies = [ [[package]] name = "iroh-net-report" version = "0.29.0" -source = "git+https://github.com/n0-computer/iroh#321d8ffd3d2bde2f1a80e78eab1ad83687484fc2" +source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0" dependencies = [ "anyhow", "bytes", @@ -2208,6 +2179,7 @@ dependencies = [ "hickory-resolver", "iroh-base", "iroh-metrics", + "iroh-quinn", "iroh-relay", "netwatch", "portmapper", @@ -2274,7 +2246,7 @@ dependencies = [ [[package]] name = "iroh-relay" version = "0.29.0" -source = "git+https://github.com/n0-computer/iroh#321d8ffd3d2bde2f1a80e78eab1ad83687484fc2" +source = "git+https://github.com/n0-computer/iroh#ee72f6da7caed23c24d34c611b5de222898dcbd0" dependencies = [ "anyhow", "base64", @@ -2287,7 +2259,7 @@ dependencies = [ "futures-util", "governor", "hex", - "hickory-proto 0.25.0-alpha.2", + "hickory-proto", "hickory-resolver", "hostname 0.4.0", "http 1.2.0", @@ -2301,7 +2273,6 @@ dependencies = [ "libc", "num_enum", "once_cell", - "parking_lot", "pin-project", "postcard", "rand", @@ -2316,7 +2287,7 @@ dependencies = [ "smallvec", "socket2", "stun-rs", - "thiserror 2.0.4", + "thiserror 2.0.6", "time", "tokio", "tokio-rustls", @@ -2377,9 +2348,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -2396,9 +2367,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libm" @@ -2465,15 +2436,6 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "mainline" version = "2.0.1" @@ -2559,6 +2521,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "moka" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "once_cell", + "parking_lot", + "quanta", + "rustc_version", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "triomphe", + "uuid", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -2680,9 +2662,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" dependencies = [ "bytes", "futures", @@ -2715,7 +2697,7 @@ dependencies = [ "rtnetlink 0.14.1", "serde", "socket2", - "thiserror 2.0.4", + "thiserror 2.0.6", "time", "tokio", "tokio-util", @@ -3041,20 +3023,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror 2.0.6", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -3062,9 +3044,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", @@ -3075,9 +3057,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -3243,7 +3225,7 @@ dependencies = [ "serde", "smallvec", "socket2", - "thiserror 2.0.4", + "thiserror 2.0.6", "time", "tokio", "tokio-util", @@ -3453,9 +3435,9 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ae09230350898e9a243a7a4a5fdde934edfb6b010e6a9cfb4136f79b54dbbb" +checksum = "b7a980daf521a275ae2a04fefc311c96fd0cf11ae430324d1b914d072bcc408b" dependencies = [ "anyhow", "derive_more", @@ -3475,9 +3457,9 @@ dependencies = [ [[package]] name = "quic-rpc-derive" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bb53e37914f7e7c6670dc2c834b9299110620cded49e783e78e9d8baf80128" +checksum = "81701deb4fec21c393d66ca8b6e0cb2ced1a61c5ca409bbf552e726e048c00e3" dependencies = [ "proc-macro2", "quic-rpc", @@ -3504,7 +3486,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -3523,7 +3505,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.4", + "thiserror 2.0.6", "tinyvec", "tracing", "web-time", @@ -3531,9 +3513,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" dependencies = [ "cfg_aliases", "libc", @@ -3637,18 +3619,18 @@ dependencies = [ [[package]] name = "redb" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b1de48a7cf7ba193e81e078d17ee2b786236eed1d3f7c60f8a09545efc4925" +checksum = "a7c2a94325f9c5826b17c42af11067230f503747f870117a28180e85696e21ba" dependencies = [ "libc", ] [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -3764,7 +3746,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-rustls", "tower-service", @@ -3900,22 +3882,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "log", "once_cell", @@ -4098,9 +4080,9 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "semver" @@ -4113,9 +4095,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -4150,9 +4132,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -4507,13 +4489,13 @@ dependencies = [ [[package]] name = "swarm-discovery" -version = "0.2.1" +version = "0.3.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39769914108ae68e261d85ceac7bce7095947130f79c29d4535e9b31fc702a40" +checksum = "58b529764e2f746ef954505dd7cb3c03331bd678c52a387e4a86540577f41e45" dependencies = [ "acto", "anyhow", - "hickory-proto 0.24.1", + "hickory-proto", "rand", "socket2", "tokio", @@ -4553,12 +4535,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4614,6 +4590,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tempfile" version = "3.14.0" @@ -4621,7 +4603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.2.0", + "fastrand 2.3.0", "once_cell", "rustix", "windows-sys 0.59.0", @@ -4659,11 +4641,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.6", ] [[package]] @@ -4679,9 +4661,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -4785,12 +4767,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -4814,7 +4795,7 @@ dependencies = [ "rustls", "serde", "serde_json", - "thiserror 2.0.4", + "thiserror 2.0.6", "time", "tokio", "tokio-rustls", @@ -4824,9 +4805,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -4915,14 +4896,14 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 0.1.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -5013,6 +4994,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + [[package]] name = "try-lock" version = "0.2.5" @@ -5074,12 +5061,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - [[package]] name = "unicode-ident" version = "1.0.14" @@ -5151,7 +5132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 1.0.3", + "idna", "percent-encoding", "serde", ] @@ -5180,6 +5161,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" @@ -5240,9 +5230,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -5251,13 +5241,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -5266,9 +5255,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -5279,9 +5268,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5289,9 +5278,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -5302,9 +5291,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "watchable" @@ -5320,9 +5309,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -5675,7 +5664,7 @@ dependencies = [ "futures", "log", "serde", - "thiserror 2.0.4", + "thiserror 2.0.6", "windows 0.58.0", "windows-core 0.58.0", ] diff --git a/Cargo.toml b/Cargo.toml index 56660f903..07cdd6231 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/n0-computer/iroh-blobs" keywords = ["hashing", "quic", "blake3"] # Sadly this also needs to be updated in .github/workflows/ci.yml -rust-version = "1.76" +rust-version = "1.81" [dependencies] anyhow = { version = "1" } @@ -19,9 +19,11 @@ bao-tree = { version = "0.13", features = [ "tokio_fsm", "validate", ], default-features = false } +blake3 = { version = "1.4.5", package = "iroh-blake3" } bytes = { version = "1.7", features = ["serde"] } chrono = "0.4.31" clap = { version = "4.5.20", features = ["derive"], optional = true } +data-encoding = { version = "2.3.3" } derive_more = { version = "1.0.0", features = [ "debug", "display", @@ -38,7 +40,7 @@ genawaiter = { version = "0.99.1", features = ["futures03"] } hashlink = { version = "0.9.0", optional = true } hex = "0.4.3" indicatif = { version = "0.17.8", optional = true } -iroh-base = { version = "0.29.0", features = ["redb"] } +iroh-base = { version = "0.29.0" } iroh-io = { version = "0.6.0", features = ["stats"] } iroh-metrics = { version = "0.29.0", default-features = false } iroh = "0.29.0" diff --git a/deny.toml b/deny.toml index a909fb4a7..c903f9260 100644 --- a/deny.toml +++ b/deny.toml @@ -34,7 +34,6 @@ license-files = [ ignore = [ "RUSTSEC-2024-0370", # unmaintained, no upgrade available "RUSTSEC-2024-0384", # unmaintained, no upgrade available - "RUSTSEC-2024-0421", # we store dns packets by key, and don't do anything else with them. Todo: remove ] [sources] diff --git a/examples/custom-protocol.rs b/examples/custom-protocol.rs index a15194792..31b246f70 100644 --- a/examples/custom-protocol.rs +++ b/examples/custom-protocol.rs @@ -48,8 +48,9 @@ use iroh::{ protocol::{ProtocolHandler, Router}, Endpoint, NodeId, }; -use iroh_base::hash::Hash; -use iroh_blobs::{net_protocol::Blobs, rpc::client::blobs::MemClient, util::local_pool::LocalPool}; +use iroh_blobs::{ + net_protocol::Blobs, rpc::client::blobs::MemClient, util::local_pool::LocalPool, Hash, +}; use tracing_subscriber::{prelude::*, EnvFilter}; #[derive(Debug, Parser)] diff --git a/examples/hello-world-fetch.rs b/examples/hello-world-fetch.rs index 0741a5cd9..7a6c61407 100644 --- a/examples/hello-world-fetch.rs +++ b/examples/hello-world-fetch.rs @@ -7,8 +7,9 @@ use std::{env, str::FromStr}; use anyhow::{bail, ensure, Context, Result}; use iroh::{protocol::Router, Endpoint}; -use iroh_base::ticket::BlobTicket; -use iroh_blobs::{net_protocol::Blobs, util::local_pool::LocalPool, BlobFormat}; +use iroh_blobs::{ + net_protocol::Blobs, ticket::BlobTicket, util::local_pool::LocalPool, BlobFormat, +}; use tracing_subscriber::{prelude::*, EnvFilter}; // set the RUST_LOG env var to one of {debug,info,warn} to see logging info diff --git a/examples/hello-world-provide.rs b/examples/hello-world-provide.rs index 96fa028c2..cd614dd9b 100644 --- a/examples/hello-world-provide.rs +++ b/examples/hello-world-provide.rs @@ -4,8 +4,7 @@ //! run this example from the project root: //! $ cargo run --example hello-world-provide use iroh::{protocol::Router, Endpoint}; -use iroh_base::{node_addr::AddrInfoOptions, ticket::BlobTicket}; -use iroh_blobs::{net_protocol::Blobs, util::local_pool::LocalPool}; +use iroh_blobs::{net_protocol::Blobs, ticket::BlobTicket, util::local_pool::LocalPool}; use tracing_subscriber::{prelude::*, EnvFilter}; // set the RUST_LOG env var to one of {debug,info,warn} to see logging info @@ -35,8 +34,7 @@ async fn main() -> anyhow::Result<()> { let res = blobs_client.add_bytes("Hello, world!").await?; // create a ticket - let mut addr = node.endpoint().node_addr().await?; - addr.apply_options(AddrInfoOptions::RelayAndAddresses); + let addr = node.endpoint().node_addr().await?; let ticket = BlobTicket::new(addr, res.hash, res.format)?; // print some info about the node diff --git a/examples/local-swarm-discovery.rs b/examples/local-swarm-discovery.rs index fd84a8f56..7305b7755 100644 --- a/examples/local-swarm-discovery.rs +++ b/examples/local-swarm-discovery.rs @@ -10,12 +10,11 @@ use std::path::PathBuf; use anyhow::ensure; use clap::{Parser, Subcommand}; use iroh::{ - discovery::local_swarm_discovery::LocalSwarmDiscovery, key::PublicKey, protocol::Router, - Endpoint, NodeAddr, RelayMode, + discovery::local_swarm_discovery::LocalSwarmDiscovery, protocol::Router, Endpoint, NodeAddr, + PublicKey, RelayMode, SecretKey, }; -use iroh_base::{hash::Hash, key::SecretKey}; use iroh_blobs::{ - net_protocol::Blobs, rpc::client::blobs::WrapOption, util::local_pool::LocalPool, + net_protocol::Blobs, rpc::client::blobs::WrapOption, util::local_pool::LocalPool, Hash, }; use tracing_subscriber::{prelude::*, EnvFilter}; diff --git a/examples/transfer.rs b/examples/transfer.rs index 4e73909ea..15b457f05 100644 --- a/examples/transfer.rs +++ b/examples/transfer.rs @@ -2,10 +2,10 @@ use std::{path::PathBuf, str::FromStr}; use anyhow::Result; use iroh::{protocol::Router, Endpoint}; -use iroh_base::ticket::BlobTicket; use iroh_blobs::{ net_protocol::Blobs, rpc::client::blobs::{ReadAtLen, WrapOption}, + ticket::BlobTicket, util::{local_pool::LocalPool, SetTagOption}, }; diff --git a/src/cli.rs b/src/cli.rs index 57c99697e..29a594dba 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,8 +15,7 @@ use indicatif::{ HumanBytes, HumanDuration, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle, }; -use iroh::{key::PublicKey, relay::RelayUrl, NodeAddr}; -use iroh_base::{node_addr::AddrInfoOptions, ticket::BlobTicket}; +use iroh::{NodeAddr, PublicKey, RelayUrl}; use tokio::io::AsyncWriteExt; use crate::{ @@ -27,6 +26,7 @@ use crate::{ self, BlobInfo, BlobStatus, CollectionInfo, DownloadOptions, IncompleteBlobInfo, WrapOption, }, store::{ConsistencyCheckProgress, ExportFormat, ExportMode, ReportLevel, ValidateProgress}, + ticket::BlobTicket, util::SetTagOption, BlobFormat, Hash, HashAndFormat, Tag, }; @@ -146,11 +146,6 @@ pub enum BlobCommands { Share { /// Hash of the blob to share. hash: Hash, - /// Options to configure the address information in the generated ticket. - /// - /// Use `relay-and-addresses` in networks with no internet connectivity. - #[clap(long, default_value_t = AddrInfoOptions::Id)] - addr_options: AddrInfoOptions, /// If the blob is a collection, the requester will also fetch the listed blobs. #[clap(long, default_value_t = false)] recursive: bool, @@ -203,18 +198,22 @@ impl BlobCommands { // create the node address with the appropriate overrides let node_addr = { - let NodeAddr { node_id, info } = node_addr; + let NodeAddr { + node_id, + relay_url: original_relay_url, + direct_addresses, + } = node_addr; let addresses = if override_addresses { // use only the cli supplied ones address } else { // use both the cli supplied ones and the ticket ones - address.extend(info.direct_addresses); + address.extend(direct_addresses); address }; // prefer direct arg over ticket - let relay_url = relay_url.or(info.relay_url); + let relay_url = relay_url.or(original_relay_url); NodeAddr::from_parts(node_id, relay_url, addresses) }; @@ -357,7 +356,6 @@ impl BlobCommands { } => add_with_opts(blobs, addr, path, options).await, Self::Share { hash, - addr_options, recursive, debug, } => { @@ -367,8 +365,6 @@ impl BlobCommands { BlobFormat::Raw }; let status = blobs.status(hash).await?; - let mut addr = addr; - addr.apply_options(addr_options); let ticket = BlobTicket::new(addr, hash, format)?; let (blob_status, size) = match (status, format) { diff --git a/src/downloader.rs b/src/downloader.rs index 7df0bd504..a147131ae 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -46,7 +46,6 @@ use anyhow::anyhow; use futures_lite::{future::BoxedLocal, Stream, StreamExt}; use hashlink::LinkedHashSet; use iroh::{endpoint, Endpoint, NodeAddr, NodeId}; -use iroh_base::hash::{BlobFormat, Hash, HashAndFormat}; use iroh_metrics::inc; use tokio::{ sync::{mpsc, oneshot}, @@ -60,6 +59,7 @@ use crate::{ metrics::Metrics, store::Store, util::{local_pool::LocalPoolHandle, progress::ProgressSender}, + BlobFormat, Hash, HashAndFormat, }; mod get; diff --git a/src/downloader/test.rs b/src/downloader/test.rs index a444dca4b..1fab8ff8e 100644 --- a/src/downloader/test.rs +++ b/src/downloader/test.rs @@ -6,7 +6,7 @@ use std::{ use anyhow::anyhow; use futures_util::future::FutureExt; -use iroh::key::SecretKey; +use iroh::SecretKey; use super::*; use crate::{ diff --git a/src/get/db.rs b/src/get/db.rs index 8731e01ed..783bbabc5 100644 --- a/src/get/db.rs +++ b/src/get/db.rs @@ -10,7 +10,6 @@ use genawaiter::{ GeneratorState, }; use iroh::endpoint::Connection; -use iroh_base::hash::Hash; use iroh_io::AsyncSliceReader; use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; @@ -31,7 +30,7 @@ use crate::{ Store as BaoStore, }, util::progress::{IdGenerator, ProgressSender}, - BlobFormat, HashAndFormat, + BlobFormat, Hash, HashAndFormat, }; type GetGenerator = Gen>>>>; diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 000000000..e0589ecde --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,564 @@ +//! The blake3 hash used in Iroh. + +use std::{borrow::Borrow, fmt, str::FromStr}; + +use iroh_base::base32::{self, parse_array_hex_or_base32, HexOrBase32ParseError}; +use postcard::experimental::max_size::MaxSize; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +/// Hash type used throughout. +#[derive(PartialEq, Eq, Copy, Clone, Hash)] +pub struct Hash(blake3::Hash); + +impl fmt::Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Hash").field(&DD(self.to_hex())).finish() + } +} + +struct DD(T); + +impl fmt::Debug for DD { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Hash { + /// The hash for the empty byte range (`b""`). + pub const EMPTY: Hash = Hash::from_bytes([ + 175, 19, 73, 185, 245, 249, 161, 166, 160, 64, 77, 234, 54, 220, 201, 73, 155, 203, 37, + 201, 173, 193, 18, 183, 204, 154, 147, 202, 228, 31, 50, 98, + ]); + + /// Calculate the hash of the provided bytes. + pub fn new(buf: impl AsRef<[u8]>) -> Self { + let val = blake3::hash(buf.as_ref()); + Hash(val) + } + + /// Bytes of the hash. + pub fn as_bytes(&self) -> &[u8; 32] { + self.0.as_bytes() + } + + /// Create a `Hash` from its raw bytes representation. + pub const fn from_bytes(bytes: [u8; 32]) -> Self { + Self(blake3::Hash::from_bytes(bytes)) + } + + /// Convert the hash to a hex string. + pub fn to_hex(&self) -> String { + self.0.to_hex().to_string() + } + + /// Convert to a base32 string limited to the first 10 bytes for a friendly string + /// representation of the hash. + pub fn fmt_short(&self) -> String { + base32::fmt_short(self.as_bytes()) + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl Borrow<[u8]> for Hash { + fn borrow(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl Borrow<[u8; 32]> for Hash { + fn borrow(&self) -> &[u8; 32] { + self.0.as_bytes() + } +} + +impl From for blake3::Hash { + fn from(value: Hash) -> Self { + value.0 + } +} + +impl From for Hash { + fn from(value: blake3::Hash) -> Self { + Hash(value) + } +} + +impl From<[u8; 32]> for Hash { + fn from(value: [u8; 32]) -> Self { + Hash(blake3::Hash::from(value)) + } +} + +impl From for [u8; 32] { + fn from(value: Hash) -> Self { + *value.as_bytes() + } +} + +impl From<&[u8; 32]> for Hash { + fn from(value: &[u8; 32]) -> Self { + Hash(blake3::Hash::from(*value)) + } +} + +impl PartialOrd for Hash { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.as_bytes().cmp(other.0.as_bytes())) + } +} + +impl Ord for Hash { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.as_bytes().cmp(other.0.as_bytes()) + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // result will be 52 bytes + let mut res = [b'b'; 52]; + // write the encoded bytes + data_encoding::BASE32_NOPAD.encode_mut(self.as_bytes(), &mut res); + // convert to string, this is guaranteed to succeed + let t = std::str::from_utf8_mut(res.as_mut()).unwrap(); + // hack since data_encoding doesn't have BASE32LOWER_NOPAD as a const + t.make_ascii_lowercase(); + // write the str, no allocations + f.write_str(t) + } +} + +impl FromStr for Hash { + type Err = HexOrBase32ParseError; + + fn from_str(s: &str) -> Result { + parse_array_hex_or_base32(s).map(Hash::from) + } +} + +impl Serialize for Hash { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(self.to_string().as_str()) + } else { + self.0.as_bytes().serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for Hash { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + s.parse().map_err(de::Error::custom) + } else { + let data: [u8; 32] = Deserialize::deserialize(deserializer)?; + Ok(Self(blake3::Hash::from(data))) + } + } +} + +impl MaxSize for Hash { + const POSTCARD_MAX_SIZE: usize = 32; +} + +/// A format identifier +#[derive( + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Default, + Debug, + MaxSize, + Hash, + derive_more::Display, +)] +pub enum BlobFormat { + /// Raw blob + #[default] + Raw, + /// A sequence of BLAKE3 hashes + HashSeq, +} + +impl From for u64 { + fn from(value: BlobFormat) -> Self { + match value { + BlobFormat::Raw => 0, + BlobFormat::HashSeq => 1, + } + } +} + +impl BlobFormat { + /// Is raw format + pub const fn is_raw(&self) -> bool { + matches!(self, BlobFormat::Raw) + } + + /// Is hash seq format + pub const fn is_hash_seq(&self) -> bool { + matches!(self, BlobFormat::HashSeq) + } +} + +/// A hash and format pair +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, MaxSize, Hash)] +pub struct HashAndFormat { + /// The hash + pub hash: Hash, + /// The format + pub format: BlobFormat, +} + +#[cfg(feature = "redb")] +mod redb_support { + use postcard::experimental::max_size::MaxSize; + use redb::{Key as RedbKey, Value as RedbValue}; + + use super::{Hash, HashAndFormat}; + + impl RedbValue for Hash { + type SelfType<'a> = Self; + + type AsBytes<'a> = &'a [u8; 32]; + + fn fixed_width() -> Option { + Some(32) + } + + fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> + where + Self: 'a, + { + let contents: &'a [u8; 32] = data.try_into().unwrap(); + (*contents).into() + } + + fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> + where + Self: 'a, + Self: 'b, + { + value.as_bytes() + } + + fn type_name() -> redb::TypeName { + redb::TypeName::new("iroh_blobs::Hash") + } + } + + impl RedbKey for Hash { + fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering { + data1.cmp(data2) + } + } + + impl RedbValue for HashAndFormat { + type SelfType<'a> = Self; + + type AsBytes<'a> = [u8; Self::POSTCARD_MAX_SIZE]; + + fn fixed_width() -> Option { + Some(Self::POSTCARD_MAX_SIZE) + } + + fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> + where + Self: 'a, + { + let t: &'a [u8; Self::POSTCARD_MAX_SIZE] = data.try_into().unwrap(); + postcard::from_bytes(t.as_slice()).unwrap() + } + + fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> + where + Self: 'a, + Self: 'b, + { + let mut res = [0u8; 33]; + postcard::to_slice(&value, &mut res).unwrap(); + res + } + + fn type_name() -> redb::TypeName { + redb::TypeName::new("iroh_blobs::HashAndFormat") + } + } +} + +impl HashAndFormat { + /// Create a new hash and format pair. + pub fn new(hash: Hash, format: BlobFormat) -> Self { + Self { hash, format } + } + + /// Create a new hash and format pair, using the default (raw) format. + pub fn raw(hash: Hash) -> Self { + Self { + hash, + format: BlobFormat::Raw, + } + } + + /// Create a new hash and format pair, using the collection format. + pub fn hash_seq(hash: Hash) -> Self { + Self { + hash, + format: BlobFormat::HashSeq, + } + } +} + +impl fmt::Display for HashAndFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut slice = [0u8; 65]; + hex::encode_to_slice(self.hash.as_bytes(), &mut slice[1..]).unwrap(); + match self.format { + BlobFormat::Raw => { + write!(f, "{}", std::str::from_utf8(&slice[1..]).unwrap()) + } + BlobFormat::HashSeq => { + slice[0] = b's'; + write!(f, "{}", std::str::from_utf8(&slice).unwrap()) + } + } + } +} + +impl FromStr for HashAndFormat { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let s = s.as_bytes(); + let mut hash = [0u8; 32]; + match s.len() { + 64 => { + hex::decode_to_slice(s, &mut hash)?; + Ok(Self::raw(hash.into())) + } + 65 if s[0].to_ascii_lowercase() == b's' => { + hex::decode_to_slice(&s[1..], &mut hash)?; + Ok(Self::hash_seq(hash.into())) + } + _ => anyhow::bail!("invalid hash and format"), + } + } +} + +impl Serialize for HashAndFormat { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(self.to_string().as_str()) + } else { + (self.hash, self.format).serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for HashAndFormat { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + s.parse().map_err(de::Error::custom) + } else { + let (hash, format) = <(Hash, BlobFormat)>::deserialize(deserializer)?; + Ok(Self { hash, format }) + } + } +} + +#[cfg(test)] +mod tests { + + use iroh_test::{assert_eq_hex, hexdump::parse_hexdump}; + use serde_test::{assert_tokens, Configure, Token}; + + use super::*; + + #[test] + fn test_display_parse_roundtrip() { + for i in 0..100 { + let hash: Hash = blake3::hash(&[i]).into(); + let text = hash.to_string(); + let hash1 = text.parse::().unwrap(); + assert_eq!(hash, hash1); + + let text = hash.to_hex(); + let hash1 = Hash::from_str(&text).unwrap(); + assert_eq!(hash, hash1); + } + } + + #[test] + fn test_hash() { + let data = b"hello world"; + let hash = Hash::new(data); + + let encoded = hash.to_string(); + assert_eq!(encoded.parse::().unwrap(), hash); + } + + #[test] + fn test_empty_hash() { + let hash = Hash::new(b""); + assert_eq!(hash, Hash::EMPTY); + } + + #[test] + fn hash_wire_format() { + let hash = Hash::from([0xab; 32]); + let serialized = postcard::to_stdvec(&hash).unwrap(); + let expected = parse_hexdump(r" + ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab # hash + ").unwrap(); + assert_eq_hex!(serialized, expected); + } + + #[cfg(feature = "redb")] + #[test] + fn hash_redb() { + use redb::Value as RedbValue; + let bytes: [u8; 32] = (0..32).collect::>().as_slice().try_into().unwrap(); + let hash = Hash::from(bytes); + assert_eq!(::fixed_width(), Some(32)); + assert_eq!( + ::type_name(), + redb::TypeName::new("iroh_blobs::Hash") + ); + let serialized = ::as_bytes(&hash); + assert_eq!(serialized, &bytes); + let deserialized = ::from_bytes(serialized.as_slice()); + assert_eq!(deserialized, hash); + let expected = parse_hexdump( + r" + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f + 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash + ", + ) + .unwrap(); + assert_eq_hex!(serialized, expected); + } + + #[cfg(feature = "redb")] + #[test] + fn hash_and_format_redb() { + use redb::Value as RedbValue; + let hash_bytes: [u8; 32] = (0..32).collect::>().as_slice().try_into().unwrap(); + let hash = Hash::from(hash_bytes); + let haf = HashAndFormat::raw(hash); + assert_eq!(::fixed_width(), Some(33)); + assert_eq!( + ::type_name(), + redb::TypeName::new("iroh_blobs::HashAndFormat") + ); + let serialized = ::as_bytes(&haf); + let mut bytes = [0u8; 33]; + bytes[0..32].copy_from_slice(&hash_bytes); + assert_eq!(serialized, bytes); + let deserialized = ::from_bytes(serialized.as_slice()); + assert_eq!(deserialized, haf); + let expected = parse_hexdump( + r" + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f + 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f # hash + 00 # format (raw) + ", + ) + .unwrap(); + assert_eq_hex!(serialized, expected); + } + + #[test] + fn test_hash_serde() { + let hash = Hash::new("hello"); + + // Hashes are serialized as 32 tuples + let mut tokens = Vec::new(); + tokens.push(Token::Tuple { len: 32 }); + for byte in hash.as_bytes() { + tokens.push(Token::U8(*byte)); + } + tokens.push(Token::TupleEnd); + assert_eq!(tokens.len(), 34); + + assert_tokens(&hash.compact(), &tokens); + + let tokens = vec![Token::String( + "5khrmpntq2bjexseshc6ldklwnig56gbj23yvbxjbdcwestheahq", + )]; + assert_tokens(&hash.readable(), &tokens); + } + + #[test] + fn test_hash_postcard() { + let hash = Hash::new("hello"); + let ser = postcard::to_stdvec(&hash).unwrap(); + let de = postcard::from_bytes(&ser).unwrap(); + assert_eq!(hash, de); + + assert_eq!(ser.len(), 32); + } + + #[test] + fn test_hash_json() { + let hash = Hash::new("hello"); + let ser = serde_json::to_string(&hash).unwrap(); + let de = serde_json::from_str(&ser).unwrap(); + assert_eq!(hash, de); + // 52 bytes of base32 + 2 quotes + assert_eq!(ser.len(), 54); + } + + #[test] + fn test_hash_and_format_parse() { + let hash = Hash::new("hello"); + + let expected = HashAndFormat::raw(hash); + let actual = expected.to_string().parse::().unwrap(); + assert_eq!(expected, actual); + + let expected = HashAndFormat::hash_seq(hash); + let actual = expected.to_string().parse::().unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn test_hash_and_format_postcard() { + let haf = HashAndFormat::raw(Hash::new("hello")); + let ser = postcard::to_stdvec(&haf).unwrap(); + let de = postcard::from_bytes(&ser).unwrap(); + assert_eq!(haf, de); + } + + #[test] + fn test_hash_and_format_json() { + let haf = HashAndFormat::raw(Hash::new("hello")); + let ser = serde_json::to_string(&haf).unwrap(); + let de = serde_json::from_str(&ser).unwrap(); + assert_eq!(haf, de); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9404e40c2..d6855d36f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,15 +48,19 @@ pub mod provider; #[cfg_attr(iroh_docsrs, doc(cfg(feature = "rpc")))] pub mod rpc; pub mod store; +pub mod ticket; pub mod util; +mod hash; + use bao_tree::BlockSize; -pub use iroh_base::hash::{BlobFormat, Hash, HashAndFormat}; -pub use crate::util::{Tag, TempTag}; +#[doc(inline)] +pub use crate::protocol::ALPN; +pub use crate::{ + hash::{BlobFormat, Hash, HashAndFormat}, + util::{Tag, TempTag}, +}; /// Block size used by iroh, 2^4*1024 = 16KiB pub const IROH_BLOCK_SIZE: BlockSize = BlockSize::from_chunk_log(4); - -#[doc(inline)] -pub use crate::protocol::ALPN; diff --git a/src/net_protocol.rs b/src/net_protocol.rs index 48667aa5c..ca62c146b 100644 --- a/src/net_protocol.rs +++ b/src/net_protocol.rs @@ -9,7 +9,6 @@ use anyhow::{bail, Result}; use futures_lite::future::Boxed as BoxedFuture; use futures_util::future::BoxFuture; use iroh::{endpoint::Connecting, protocol::ProtocolHandler, Endpoint, NodeAddr}; -use iroh_base::hash::{BlobFormat, Hash}; use serde::{Deserialize, Serialize}; use tracing::debug; @@ -21,6 +20,7 @@ use crate::{ local_pool::{self, LocalPoolHandle}, SetTagOption, }, + BlobFormat, Hash, }; /// A callback that blobs can ask about a set of hashes that should not be garbage collected. @@ -75,7 +75,7 @@ pub(crate) struct BlobBatches { #[derive(Debug, Default)] struct BlobBatch { /// The tags in this batch. - tags: std::collections::BTreeMap>, + tags: std::collections::BTreeMap>, } #[cfg(feature = "rpc")] @@ -94,11 +94,7 @@ impl BlobBatches { } /// Remove a tag from a batch. - pub fn remove_one( - &mut self, - batch: BatchId, - content: &iroh::hash::HashAndFormat, - ) -> Result<()> { + pub fn remove_one(&mut self, batch: BatchId, content: &crate::HashAndFormat) -> Result<()> { if let Some(batch) = self.batches.get_mut(&batch) { if let Some(tags) = batch.tags.get_mut(content) { tags.pop(); diff --git a/src/rpc.rs b/src/rpc.rs index f3b24ab82..e1739b52c 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -17,7 +17,6 @@ use futures_lite::StreamExt; use futures_util::{FutureExt, Stream}; use genawaiter::sync::{Co, Gen}; use iroh::{Endpoint, NodeAddr}; -use iroh_base::hash::{BlobFormat, HashAndFormat}; use iroh_io::AsyncSliceReader; use proto::{ blobs::{ @@ -58,7 +57,7 @@ use crate::{ progress::{AsyncChannelProgressSender, ProgressSender}, SetTagOption, }, - Tag, + BlobFormat, HashAndFormat, Tag, }; pub mod client; pub mod proto; @@ -985,7 +984,7 @@ impl Handler { let mut any_added = false; for node in nodes { node_ids.push(node.node_id); - if !node.info.is_empty() { + if !node.is_empty() { endpoint.add_node_addr_with_source(node, BLOB_DOWNLOAD_SOURCE_NAME)?; any_added = true; } diff --git a/src/rpc/client/blobs.rs b/src/rpc/client/blobs.rs index 4183ffb15..454617a84 100644 --- a/src/rpc/client/blobs.rs +++ b/src/rpc/client/blobs.rs @@ -991,15 +991,14 @@ pub struct DownloadOptions { mod tests { use std::{path::Path, time::Duration}; - use iroh::{key::SecretKey, test_utils::DnsPkarrServer, NodeId, RelayMode}; - use iroh_base::{node_addr::AddrInfoOptions, ticket::BlobTicket}; + use iroh::{test_utils::DnsPkarrServer, NodeId, RelayMode, SecretKey}; use node::Node; use rand::RngCore; use testresult::TestResult; use tokio::{io::AsyncWriteExt, sync::mpsc}; use super::*; - use crate::hashseq::HashSeq; + use crate::{hashseq::HashSeq, ticket::BlobTicket}; mod node { //! An iroh node that just has the blobs transport @@ -1799,11 +1798,10 @@ mod tests { .await? .hash; - let mut addr = node.node_addr().await?; - addr.apply_options(AddrInfoOptions::RelayAndAddresses); + let addr = node.node_addr().await?; let ticket = BlobTicket::new(addr, hash, BlobFormat::Raw)?; - println!("addrs: {:?}", ticket.node_addr().info); - assert!(!ticket.node_addr().info.direct_addresses.is_empty()); + println!("addrs: {:?}", ticket.node_addr()); + assert!(!ticket.node_addr().direct_addresses.is_empty()); Ok(()) } diff --git a/src/rpc/proto/blobs.rs b/src/rpc/proto/blobs.rs index 2788027d6..75fdad1c7 100644 --- a/src/rpc/proto/blobs.rs +++ b/src/rpc/proto/blobs.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use bytes::Bytes; -use iroh_base::hash::Hash; use nested_enum_utils::enum_conversions; use quic_rpc_derive::rpc_requests; use serde::{Deserialize, Serialize}; @@ -20,7 +19,7 @@ use crate::{ ValidateProgress, }, util::SetTagOption, - BlobFormat, HashAndFormat, Tag, + BlobFormat, Hash, HashAndFormat, Tag, }; #[allow(missing_docs)] diff --git a/src/store/bao_file.rs b/src/store/bao_file.rs index 369a3574d..c06328669 100644 --- a/src/store/bao_file.rs +++ b/src/store/bao_file.rs @@ -27,14 +27,13 @@ use bao_tree::{ }; use bytes::{Bytes, BytesMut}; use derive_more::Debug; -use iroh_base::hash::Hash; use iroh_io::AsyncSliceReader; use super::mutable_mem_storage::{MutableMemStorage, SizeInfo}; use crate::{ store::BaoBatchWriter, util::{get_limited_slice, MemOrFile, SparseMemFile}, - IROH_BLOCK_SIZE, + Hash, IROH_BLOCK_SIZE, }; /// Data files are stored in 3 files. The data file, the outboard file, diff --git a/src/store/fs.rs b/src/store/fs.rs index 2ed33366f..2b8bf95aa 100644 --- a/src/store/fs.rs +++ b/src/store/fs.rs @@ -79,7 +79,6 @@ use bao_tree::io::{ use bytes::Bytes; use futures_lite::{Stream, StreamExt}; use genawaiter::rc::{Co, Gen}; -use iroh_base::hash::{BlobFormat, Hash, HashAndFormat}; use iroh_io::AsyncSliceReader; use redb::{AccessGuard, DatabaseError, ReadableTable, StorageError}; use serde::{Deserialize, Serialize}; @@ -120,7 +119,7 @@ use crate::{ }, raw_outboard_size, MemOrFile, TagCounter, TagDrop, }, - Tag, TempTag, + BlobFormat, Hash, HashAndFormat, Tag, TempTag, }; /// Location of the data. @@ -1333,7 +1332,7 @@ impl super::Store for Store { async fn import_bytes( &self, data: bytes::Bytes, - format: iroh_base::hash::BlobFormat, + format: crate::BlobFormat, ) -> io::Result { let this = self.0.clone(); Ok(tokio::task::spawn_blocking(move || this.import_bytes_sync(data, format)).await??) diff --git a/src/store/fs/tables.rs b/src/store/fs/tables.rs index d49dd42e7..591326dc5 100644 --- a/src/store/fs/tables.rs +++ b/src/store/fs/tables.rs @@ -1,11 +1,10 @@ //! Table definitions and accessors for the redb database. use std::collections::BTreeSet; -use iroh_base::hash::{Hash, HashAndFormat}; use redb::{ReadableTable, TableDefinition, TableError}; use super::{EntryState, PathOptions}; -use crate::util::Tag; +use crate::{util::Tag, Hash, HashAndFormat}; pub(super) const BLOBS_TABLE: TableDefinition = TableDefinition::new("blobs-0"); diff --git a/src/store/mem.rs b/src/store/mem.rs index 15e037a83..89d8fffd7 100644 --- a/src/store/mem.rs +++ b/src/store/mem.rs @@ -16,7 +16,6 @@ use bao_tree::{ }; use bytes::{Bytes, BytesMut}; use futures_lite::{Stream, StreamExt}; -use iroh_base::hash::{BlobFormat, Hash, HashAndFormat}; use iroh_io::AsyncSliceReader; use super::{ @@ -31,7 +30,7 @@ use crate::{ progress::{BoxedProgressSender, IdGenerator, IgnoreProgressSender, ProgressSender}, TagCounter, TagDrop, }, - Tag, TempTag, IROH_BLOCK_SIZE, + BlobFormat, Hash, HashAndFormat, Tag, TempTag, IROH_BLOCK_SIZE, }; /// A fully featured in memory database for iroh-blobs, including support for @@ -428,17 +427,13 @@ impl ReadableStore for Store { )) } - async fn tags( - &self, - ) -> io::Result> { + async fn tags(&self) -> io::Result> { #[allow(clippy::mutable_key_type)] let tags = self.read_lock().tags.clone(); Ok(Box::new(tags.into_iter().map(Ok))) } - fn temp_tags( - &self, - ) -> Box + Send + Sync + 'static> { + fn temp_tags(&self) -> Box + Send + Sync + 'static> { let tags = self.read_lock().temp.keys(); Box::new(tags) } diff --git a/src/store/mutable_mem_storage.rs b/src/store/mutable_mem_storage.rs index 1bc4b184a..f100476fa 100644 --- a/src/store/mutable_mem_storage.rs +++ b/src/store/mutable_mem_storage.rs @@ -63,10 +63,7 @@ impl SizeInfo { impl MutableMemStorage { /// Create a new mutable mem storage from the given data - pub fn complete( - bytes: Bytes, - cb: impl Fn(u64) + Send + Sync + 'static, - ) -> (Self, iroh_base::hash::Hash) { + pub fn complete(bytes: Bytes, cb: impl Fn(u64) + Send + Sync + 'static) -> (Self, crate::Hash) { let (hash, outboard) = compute_outboard(&bytes[..], bytes.len() as u64, move |offset| { cb(offset); Ok(()) diff --git a/src/ticket.rs b/src/ticket.rs new file mode 100644 index 000000000..8d1a45a5e --- /dev/null +++ b/src/ticket.rs @@ -0,0 +1,215 @@ +//! Tickets for blobs. +use std::{collections::BTreeSet, net::SocketAddr, str::FromStr}; + +use anyhow::Result; +use iroh::{NodeAddr, NodeId, RelayUrl}; +use iroh_base::ticket::{self, Ticket}; +use serde::{Deserialize, Serialize}; + +use crate::{BlobFormat, Hash}; + +/// A token containing everything to get a file from the provider. +/// +/// It is a single item which can be easily serialized and deserialized. +#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)] +#[display("{}", Ticket::serialize(self))] +pub struct BlobTicket { + /// The provider to get a file from. + node: NodeAddr, + /// The format of the blob. + format: BlobFormat, + /// The hash to retrieve. + hash: Hash, +} + +/// Wire format for [`BlobTicket`]. +/// +/// In the future we might have multiple variants (not versions, since they +/// might be both equally valid), so this is a single variant enum to force +/// postcard to add a discriminator. +#[derive(Serialize, Deserialize)] +enum TicketWireFormat { + Variant0(Variant0BlobTicket), +} + +// Legacy +#[derive(Serialize, Deserialize)] +struct Variant0BlobTicket { + node: Variant0NodeAddr, + format: BlobFormat, + hash: Hash, +} + +#[derive(Serialize, Deserialize)] +struct Variant0NodeAddr { + node_id: NodeId, + info: Variant0AddrInfo, +} + +#[derive(Serialize, Deserialize)] +struct Variant0AddrInfo { + relay_url: Option, + direct_addresses: BTreeSet, +} + +impl Ticket for BlobTicket { + const KIND: &'static str = "blob"; + + fn to_bytes(&self) -> Vec { + let data = TicketWireFormat::Variant0(Variant0BlobTicket { + node: Variant0NodeAddr { + node_id: self.node.node_id, + info: Variant0AddrInfo { + relay_url: self.node.relay_url.clone(), + direct_addresses: self.node.direct_addresses.clone(), + }, + }, + format: self.format, + hash: self.hash, + }); + postcard::to_stdvec(&data).expect("postcard serialization failed") + } + + fn from_bytes(bytes: &[u8]) -> std::result::Result { + let res: TicketWireFormat = postcard::from_bytes(bytes).map_err(ticket::Error::Postcard)?; + let TicketWireFormat::Variant0(Variant0BlobTicket { node, format, hash }) = res; + Ok(Self { + node: NodeAddr { + node_id: node.node_id, + relay_url: node.info.relay_url, + direct_addresses: node.info.direct_addresses, + }, + format, + hash, + }) + } +} + +impl FromStr for BlobTicket { + type Err = ticket::Error; + + fn from_str(s: &str) -> Result { + Ticket::deserialize(s) + } +} + +impl BlobTicket { + /// Creates a new ticket. + pub fn new(node: NodeAddr, hash: Hash, format: BlobFormat) -> Result { + Ok(Self { hash, format, node }) + } + + /// The hash of the item this ticket can retrieve. + pub fn hash(&self) -> Hash { + self.hash + } + + /// The [`NodeAddr`] of the provider for this ticket. + pub fn node_addr(&self) -> &NodeAddr { + &self.node + } + + /// The [`BlobFormat`] for this ticket. + pub fn format(&self) -> BlobFormat { + self.format + } + + /// True if the ticket is for a collection and should retrieve all blobs in it. + pub fn recursive(&self) -> bool { + self.format.is_hash_seq() + } + + /// Get the contents of the ticket, consuming it. + pub fn into_parts(self) -> (NodeAddr, Hash, BlobFormat) { + let BlobTicket { node, hash, format } = self; + (node, hash, format) + } +} + +impl Serialize for BlobTicket { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + serializer.serialize_str(&self.to_string()) + } else { + let BlobTicket { node, format, hash } = self; + (node, format, hash).serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for BlobTicket { + fn deserialize>(deserializer: D) -> Result { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } else { + let (peer, format, hash) = Deserialize::deserialize(deserializer)?; + Self::new(peer, hash, format).map_err(serde::de::Error::custom) + } + } +} + +#[cfg(test)] +mod tests { + use std::net::SocketAddr; + + use iroh::{PublicKey, SecretKey}; + use iroh_base::base32; + use iroh_test::{assert_eq_hex, hexdump::parse_hexdump}; + + use super::*; + + fn make_ticket() -> BlobTicket { + let hash = Hash::new(b"hi there"); + let peer = SecretKey::generate().public(); + let addr = SocketAddr::from_str("127.0.0.1:1234").unwrap(); + let relay_url = None; + BlobTicket { + hash, + node: NodeAddr::from_parts(peer, relay_url, [addr]), + format: BlobFormat::HashSeq, + } + } + + #[test] + fn test_ticket_postcard() { + let ticket = make_ticket(); + let bytes = postcard::to_stdvec(&ticket).unwrap(); + let ticket2: BlobTicket = postcard::from_bytes(&bytes).unwrap(); + assert_eq!(ticket2, ticket); + } + + #[test] + fn test_ticket_json() { + let ticket = make_ticket(); + let json = serde_json::to_string(&ticket).unwrap(); + let ticket2: BlobTicket = serde_json::from_str(&json).unwrap(); + assert_eq!(ticket2, ticket); + } + + #[test] + fn test_ticket_base32() { + let hash = + Hash::from_str("0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072") + .unwrap(); + let node_id = + PublicKey::from_str("ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6") + .unwrap(); + + let ticket = BlobTicket { + node: NodeAddr::from_parts(node_id, None, []), + format: BlobFormat::Raw, + hash, + }; + let base32 = base32::parse_vec(ticket.to_string().strip_prefix("blob").unwrap()).unwrap(); + let expected = parse_hexdump(" + 00 # discriminator for variant 0 + ae58ff8833241ac82d6ff7611046ed67b5072d142c588d0063e942d9a75502b6 # node id, 32 bytes, see above + 00 # relay url + 00 # number of addresses (0) + 00 # format (raw) + 0b84d358e4c8be6c38626b2182ff575818ba6bd3f4b90464994be14cb354a072 # hash, 32 bytes, see above + ").unwrap(); + assert_eq_hex!(base32, expected); + } +} diff --git a/src/util/fs.rs b/src/util/fs.rs index f87532201..19589adda 100644 --- a/src/util/fs.rs +++ b/src/util/fs.rs @@ -126,20 +126,20 @@ pub fn relative_canonicalized_path_to_string(path: impl AsRef) -> anyhow:: canonicalized_path_to_string(path, true) } -/// Loads a [`iroh::key::SecretKey`] from the provided file, or stores a newly generated one +/// Loads a [`iroh::SecretKey`] from the provided file, or stores a newly generated one /// at the given location. #[cfg(feature = "rpc")] #[cfg_attr(iroh_docsrs, doc(cfg(feature = "rpc")))] -pub async fn load_secret_key(key_path: PathBuf) -> anyhow::Result { +pub async fn load_secret_key(key_path: PathBuf) -> anyhow::Result { + use iroh::SecretKey; use tokio::io::AsyncWriteExt; if key_path.exists() { let keystr = tokio::fs::read(key_path).await?; - let secret_key = - iroh::key::SecretKey::try_from_openssh(keystr).context("invalid keyfile")?; + let secret_key = SecretKey::try_from_openssh(keystr).context("invalid keyfile")?; Ok(secret_key) } else { - let secret_key = iroh::key::SecretKey::generate(); + let secret_key = SecretKey::generate(); let ser_key = secret_key.to_openssh()?; // Try to canonicalize if possible