diff --git a/Cargo.lock b/Cargo.lock index d4d4b9c..754b89b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -92,30 +92,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -123,9 +123,6 @@ name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" -dependencies = [ - "backtrace", -] [[package]] name = "arrayref" @@ -186,16 +183,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", -] - -[[package]] -name = "atomic-polyfill" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" -dependencies = [ - "critical-section", + "syn 2.0.41", ] [[package]] @@ -204,7 +192,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb8867f378f33f78a811a8eb9bf108ad99430d7aad43315dd9319c827ef6247" dependencies = [ - "http", + "http 0.2.11", "log", "url", "wildmatch", @@ -397,9 +385,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.10" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", "clap_derive", @@ -407,9 +395,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", @@ -426,7 +414,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -462,9 +450,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" @@ -532,17 +520,11 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" -[[package]] -name = "critical-section" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" - [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] @@ -627,7 +609,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -719,16 +701,17 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -748,7 +731,7 @@ checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "unicode-xid", ] @@ -772,7 +755,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -893,7 +876,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -927,6 +910,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1038,7 +1027,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1184,7 +1173,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap", "slab", "tokio", @@ -1192,15 +1181,6 @@ 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.14.3" @@ -1211,20 +1191,6 @@ dependencies = [ "allocator-api2", ] -[[package]] -name = "heapless" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin 0.9.8", - "stable_deref_trait", -] - [[package]] name = "heck" version = "0.4.1" @@ -1295,14 +1261,48 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -1320,28 +1320,47 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.11", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", "want", ] +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -1349,13 +1368,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.28", "rustls", "tokio", "tokio-rustls", ] +[[package]] +name = "hyper-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.1.0", + "pin-project-lite", + "socket2", + "tokio", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -1408,8 +1445,8 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http", - "hyper", + "http 0.2.11", + "hyper 0.14.28", "log", "rand", "tokio", @@ -1473,7 +1510,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.5", + "socket2", "widestring", "windows-sys 0.48.0", "winreg", @@ -1487,9 +1524,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iroh-base" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e6c2c1f49c416d9a7370de52ea73ddf64edd9c19015ef2afe69765698a8600" +checksum = "e1b42ef43639a86a49132998f066810b702798818993e34565dd4eea835f626e" dependencies = [ "anyhow", "bao-tree", @@ -1504,9 +1541,9 @@ dependencies = [ [[package]] name = "iroh-blake3" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9446c3ff33feef14809b17e90178caa73bf184ed5054337485a0b002bc7197e" +checksum = "6eb52cd11b3de4407f29579ebcd10fd746b0bd8ab758a2afac69baf88e96bede" dependencies = [ "arrayref", "arrayvec", @@ -1517,9 +1554,9 @@ dependencies = [ [[package]] name = "iroh-bytes" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78802dde7687f32a3ffcc978f8528bb5a62e14818db828a2039a23de66708656" +checksum = "38213865ec542f82fc4d8a9748b5cdae92da1707c134428e9f60b1cd46ccbe39" dependencies = [ "anyhow", "bao-tree", @@ -1566,23 +1603,30 @@ dependencies = [ [[package]] name = "iroh-metrics" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e0ba8090deb98386000a5c3e93d4912e260edf26a30d0ef64831752a69709c" +checksum = "92a3271df85ec2a18a358d36723039eeacb464d8764531c8fd9f822dac39410d" dependencies = [ + "anyhow", "erased_set", - "hyper", + "http-body-util", + "hyper 1.1.0", + "hyper-util", "once_cell", "prometheus-client", + "reqwest", + "serde", "struct_iterable", + "time", + "tokio", "tracing", ] [[package]] name = "iroh-net" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a50d291a5f5df5b7ebcdebcb183cde2f56f9d28892218493a321dcc7f30f3b" +checksum = "0477847a7fe225f71fbdff7b0baccea8fc9c9b33e7755f4b28e19f075bd64499" dependencies = [ "aead", "anyhow", @@ -1601,8 +1645,10 @@ dependencies = [ "governor", "hex", "hostname", - "http", - "hyper", + "http 1.0.0", + "http-body-util", + "hyper 1.1.0", + "hyper-util", "igd", "iroh-base", "iroh-metrics", @@ -1621,7 +1667,7 @@ dependencies = [ "rand_core", "rcgen", "reqwest", - "ring 0.17.6", + "ring 0.17.7", "rtnetlink", "rustls", "rustls-webpki", @@ -1629,8 +1675,9 @@ dependencies = [ "serde_bytes", "serdect", "smallvec", - "socket2 0.5.5", + "socket2", "ssh-key", + "strum", "stun-rs", "surge-ping", "thiserror", @@ -1643,6 +1690,7 @@ dependencies = [ "trust-dns-resolver", "ttl_cache", "url", + "watchable", "webpki-roots", "windows 0.51.1", "wmi", @@ -1652,9 +1700,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" @@ -1676,9 +1724,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libm" @@ -1725,9 +1773,9 @@ dependencies = [ [[package]] name = "mach2" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] @@ -1788,9 +1836,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi", @@ -2029,7 +2077,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2058,9 +2106,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -2169,9 +2217,9 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" dependencies = [ "base64", "serde", @@ -2223,7 +2271,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2254,7 +2302,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2374,7 +2422,6 @@ dependencies = [ "cobs", "const_format", "embedded-io", - "heapless", "postcard-derive", "serde", ] @@ -2447,10 +2494,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" dependencies = [ + "toml_datetime", "toml_edit", ] @@ -2515,7 +2563,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2583,7 +2631,7 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.5", + "socket2", "tracing", "windows-sys 0.48.0", ] @@ -2664,7 +2712,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" dependencies = [ - "pem 3.0.2", + "pem 3.0.3", "ring 0.16.20", "time", "yasna", @@ -2681,22 +2729,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280" +checksum = "53313ec9f12686aeeffb43462c3ac77aa25f590a5f630eb2cde0de59417b29c7" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" +checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2762,9 +2810,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64", "bytes", @@ -2772,9 +2820,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-rustls", "ipnet", "js-sys", @@ -2837,9 +2885,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", "getrandom", @@ -2851,9 +2899,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest", @@ -2933,12 +2981,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.9" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.6", + "ring 0.17.7", "rustls-webpki", "sct", ] @@ -2970,15 +3018,21 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "salsa20" @@ -3019,7 +3073,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.6", + "ring 0.17.7", "untrusted 0.9.0", ] @@ -3062,9 +3116,9 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" [[package]] name = "semver" @@ -3078,10 +3132,8 @@ version = "0.1.1" dependencies = [ "anyhow", "base32", - "bytes", "clap", "console", - "data-encoding", "duct", "flume", "futures", @@ -3092,10 +3144,7 @@ dependencies = [ "iroh-net", "nix 0.27.1", "num_cpus", - "postcard", - "quinn", "rand", - "serde", "serde_json", "tempfile", "tokio", @@ -3140,7 +3189,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3249,16 +3298,6 @@ dependencies = [ "serde", ] -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.5" @@ -3336,12 +3375,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strsim" version = "0.10.0" @@ -3368,7 +3401,7 @@ dependencies = [ "proc-macro2", "quote", "struct_iterable_internal", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3377,6 +3410,28 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9426b2a0c03e6cc2ea8dbc0168dbbf943f88755e409fb91bcb8f6a268305f4a" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.41", +] + [[package]] name = "stun-rs" version = "0.1.5" @@ -3415,7 +3470,7 @@ dependencies = [ "parking_lot", "pnet_packet", "rand", - "socket2 0.5.5", + "socket2", "thiserror", "tokio", "tracing", @@ -3434,9 +3489,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -3502,22 +3557,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3532,9 +3587,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", @@ -3552,9 +3607,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -3576,9 +3631,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -3588,7 +3643,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -3601,7 +3656,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3658,15 +3713,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap", "toml_datetime", @@ -3699,7 +3754,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3799,9 +3854,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttl_cache" @@ -3835,9 +3890,9 @@ checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -3964,7 +4019,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -3998,7 +4053,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4009,6 +4064,18 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "watchable" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff90d0baafb3c0abbeebec1a8a305b4211c356de1d953a0dd77aab006baa8a62" +dependencies = [ + "event-listener", + "futures-util", + "parking_lot", + "thiserror", +] + [[package]] name = "web-sys" version = "0.3.66" @@ -4339,9 +4406,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" dependencies = [ "memchr", ] @@ -4413,22 +4480,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.30" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.30" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8084388..82c9d38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,27 +9,21 @@ license = "Apache-2.0/MIT" repository = "https://github.com/n0-computer/dumb-pipe" description = "A cli tool to send directories over the network, with NAT hole punching" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0.75" base32 = "0.4.0" -bytes = "1.5.0" clap = { version = "4.4.10", features = ["derive"] } console = "0.15.7" -data-encoding = "2.5.0" flume = "0.11.0" futures = "0.3.29" hex = "0.4.3" indicatif = "0.17.7" -iroh-bytes = "0.11.0" +iroh-bytes = "0.12" iroh-io = "0.3.0" -iroh-net = "0.11.0" +iroh-net = "0.12" num_cpus = "1.16.0" -postcard = "1.0.8" -quinn = "0.10.2" rand = "0.8.5" -serde = "1.0.193" tokio = { version = "1.34.0", features = ["full"] } tokio-util = "0.7.10" tracing = "0.1.40" diff --git a/src/collection.rs b/src/collection.rs deleted file mode 100644 index f637453..0000000 --- a/src/collection.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! Should live in iroh-bytes under a feature flag -use std::collections::BTreeMap; - -use anyhow::Context; -use bao_tree::blake3; -use bytes::Bytes; -use iroh_bytes::get::fsm::EndBlobNext; -use iroh_bytes::get::Stats; -use iroh_bytes::hashseq::HashSeq; -use iroh_bytes::store::{bao_tree, MapEntry}; -use iroh_bytes::util::TempTag; -use iroh_bytes::{BlobFormat, Hash}; -use iroh_io::AsyncSliceReaderExt; -use serde::{Deserialize, Serialize}; - -/// A collection of blobs -/// -/// Note that the format is subject to change. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] -pub struct Collection { - /// Links to the blobs in this collection - blobs: Vec<(String, Hash)>, -} - -impl std::ops::Index for Collection { - type Output = (String, Hash); - - fn index(&self, index: usize) -> &Self::Output { - &self.blobs[index] - } -} - -impl Extend<(K, V)> for Collection -where - K: Into, - V: Into, -{ - fn extend>(&mut self, iter: T) { - self.blobs - .extend(iter.into_iter().map(|(k, v)| (k.into(), v.into()))); - } -} - -impl FromIterator<(K, V)> for Collection -where - K: Into, - V: Into, -{ - fn from_iter>(iter: T) -> Self { - let mut res = Self::default(); - res.extend(iter); - res - } -} - -impl IntoIterator for Collection { - type Item = (String, Hash); - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.blobs.into_iter() - } -} - -/// Metadata for a collection -/// -/// This is the wire format for the metadata blob. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -struct CollectionMeta { - header: [u8; 13], // Must contain "CollectionV0." - names: Vec, -} - -#[allow(dead_code)] -impl Collection { - /// The header for the collection format. - /// - /// This is the start of the metadata blob. - pub const HEADER: &'static [u8; 13] = b"CollectionV0."; - - /// Convert the collection to an iterator of blobs, with the last being the - /// root blob. - /// - /// To persist the collection, write all the blobs to storage, and use the - /// hash of the last blob as the collection hash. - pub fn to_blobs(&self) -> impl Iterator { - let meta = CollectionMeta { - header: *Self::HEADER, - names: self.names(), - }; - let meta_bytes = postcard::to_stdvec(&meta).unwrap(); - let meta_bytes_hash = blake3::hash(&meta_bytes).into(); - let links = std::iter::once(meta_bytes_hash) - .chain(self.links()) - .collect::(); - let links_bytes = links.into_inner(); - [meta_bytes.into(), links_bytes].into_iter() - } - - /// Read the collection from a get fsm. - /// - /// Returns the fsm at the start of the first child blob (if any), - /// the links array, and the collection. - pub async fn read_fsm( - fsm_at_start_root: iroh_bytes::get::fsm::AtStartRoot, - ) -> anyhow::Result<(iroh_bytes::get::fsm::EndBlobNext, HashSeq, Collection)> { - let (next, links) = { - let curr = fsm_at_start_root.next(); - let (curr, data) = curr.concatenate_into_vec().await?; - let links = HashSeq::new(data.into()).context("links could not be parsed")?; - (curr.next(), links) - }; - let EndBlobNext::MoreChildren(at_meta) = next else { - anyhow::bail!("expected meta"); - }; - let (next, collection) = { - let mut children = links.clone(); - let meta_link = children.pop_front().context("meta link not found")?; - let curr = at_meta.next(meta_link); - let (curr, names) = curr.concatenate_into_vec().await?; - let names = postcard::from_bytes::(&names)?; - anyhow::ensure!( - names.header == *Self::HEADER, - "expected header {:?}, got {:?}", - Self::HEADER, - names.header - ); - let collection = Collection::from_parts(children, names); - (curr.next(), collection) - }; - Ok((next, links, collection)) - } - - /// Read the collection and all it's children from a get fsm. - /// - /// Returns the collection, a map from blob offsets to bytes, and the stats. - pub async fn read_fsm_all( - fsm_at_start_root: iroh_bytes::get::fsm::AtStartRoot, - ) -> anyhow::Result<(Collection, BTreeMap, Stats)> { - let (next, links, collection) = Self::read_fsm(fsm_at_start_root).await?; - let mut res = BTreeMap::new(); - let mut curr = next; - let end = loop { - match curr { - EndBlobNext::MoreChildren(more) => { - let child_offset = more.child_offset(); - let Some(hash) = links.get(usize::try_from(child_offset)?) else { - break more.finish(); - }; - let header = more.next(hash); - let (next, blob) = header.concatenate_into_vec().await?; - res.insert(child_offset - 1, blob.into()); - curr = next.next(); - } - EndBlobNext::Closing(closing) => break closing, - } - }; - let stats = end.next().await?; - Ok((collection, res, stats)) - } - - /// Load a collection from a store given a root hash - /// - /// This assumes that both the links and the metadata of the collection is stored in the store. - /// It does not require that all child blobs are stored in the store. - pub async fn load(db: &D, root: &Hash) -> anyhow::Result - where - D: iroh_bytes::store::Map, - { - let links_entry = db.get(root).context("links not found")?; - anyhow::ensure!(links_entry.is_complete(), "links not complete"); - let links_bytes = links_entry.data_reader().await?.read_to_end().await?; - let mut links = HashSeq::try_from(links_bytes)?; - let meta_hash = links.pop_front().context("meta hash not found")?; - let meta_entry = db.get(&meta_hash).context("meta not found")?; - anyhow::ensure!(links_entry.is_complete(), "links not complete"); - let meta_bytes = meta_entry.data_reader().await?.read_to_end().await?; - let meta: CollectionMeta = postcard::from_bytes(&meta_bytes)?; - anyhow::ensure!( - meta.names.len() == links.len(), - "names and links length mismatch" - ); - Ok(Self::from_parts(links, meta)) - } - - /// Store a collection in a store. returns the root hash of the collection - /// as a TempTag. - pub async fn store(self, db: &D) -> anyhow::Result - where - D: iroh_bytes::store::Store, - { - let (links, meta) = self.into_parts(); - let meta_bytes = postcard::to_stdvec(&meta)?; - let meta_tag = db.import_bytes(meta_bytes.into(), BlobFormat::Raw).await?; - let links_bytes = std::iter::once(*meta_tag.hash()) - .chain(links) - .collect::(); - let links_tag = db - .import_bytes(links_bytes.into(), BlobFormat::HashSeq) - .await?; - Ok(links_tag) - } - - /// Split a collection into a sequence of links and metadata - fn into_parts(self) -> (Vec, CollectionMeta) { - let mut names = Vec::with_capacity(self.blobs.len()); - let mut links = Vec::with_capacity(self.blobs.len()); - for (name, hash) in self.blobs { - names.push(name); - links.push(hash); - } - let meta = CollectionMeta { - header: *Self::HEADER, - names, - }; - (links, meta) - } - - /// Create a new collection from a list of hashes and metadata - fn from_parts(links: impl IntoIterator, meta: CollectionMeta) -> Self { - meta.names.into_iter().zip(links).collect() - } - - /// Get the links to the blobs in this collection - fn links(&self) -> impl Iterator + '_ { - self.blobs.iter().map(|(_name, hash)| *hash) - } - - /// Get the names of the blobs in this collection - fn names(&self) -> Vec { - self.blobs.iter().map(|(name, _)| name.clone()).collect() - } - - /// Iterate over the blobs in this collection - pub fn iter(&self) -> impl Iterator { - self.blobs.iter() - } - - /// Get the number of blobs in this collection - pub fn len(&self) -> usize { - self.blobs.len() - } - - /// Check if this collection is empty - pub fn is_empty(&self) -> bool { - self.blobs.is_empty() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bao_tree::blake3; - - #[test] - fn roundtrip_blob() { - let b = ( - "test".to_string(), - blake3::Hash::from_hex( - "3aa61c409fd7717c9d9c639202af2fae470c0ef669be7ba2caea5779cb534e9d", - ) - .unwrap() - .into(), - ); - - let mut buf = bytes::BytesMut::zeroed(1024); - postcard::to_slice(&b, &mut buf).unwrap(); - let deserialize_b: (String, Hash) = postcard::from_bytes(&buf).unwrap(); - assert_eq!(b, deserialize_b); - } - - #[test] - fn roundtrip_collection_meta() { - let expected = CollectionMeta { - header: *Collection::HEADER, - names: vec!["test".to_string(), "a".to_string(), "b".to_string()], - }; - let mut buf = bytes::BytesMut::zeroed(1024); - postcard::to_slice(&expected, &mut buf).unwrap(); - let actual: CollectionMeta = postcard::from_bytes(&buf).unwrap(); - assert_eq!(expected, actual); - } -} diff --git a/src/get.rs b/src/get.rs deleted file mode 100644 index 5782492..0000000 --- a/src/get.rs +++ /dev/null @@ -1,433 +0,0 @@ -//! Functions to get blobs from peers -//! -//! Copied over from iroh, it should live in iroh-bytes -use std::io; - -use anyhow::Context; -use bao_tree::io::fsm::OutboardMut; -use bao_tree::{ByteNum, ChunkRanges}; -use iroh_bytes::hashseq::parse_hash_seq; -use iroh_bytes::store::bao_tree; -use iroh_bytes::store::range_collections::range_set::RangeSetRange; -use iroh_bytes::{ - get::{ - self, - fsm::{AtBlobHeader, AtEndBlob, ConnectedNext, EndBlobNext}, - Stats, - }, - protocol::{GetRequest, RangeSpecSeq}, - provider::DownloadProgress, - store::{MapEntry, PartialMap, PartialMapEntry, Store as BaoStore}, - util::progress::{IdGenerator, ProgressSender}, - BlobFormat, Hash, HashAndFormat, IROH_BLOCK_SIZE, -}; -use iroh_io::AsyncSliceReader; -use tracing::trace; - -use crate::progress::ProgressSliceWriter2; - -/// Get a blob or collection -pub async fn get( - db: &D, - conn: quinn::Connection, - hash_and_format: &HashAndFormat, - sender: impl ProgressSender + IdGenerator, -) -> anyhow::Result { - let HashAndFormat { hash, format } = hash_and_format; - let res = match format { - BlobFormat::Raw => get_blob(db, conn, hash, sender).await, - BlobFormat::HashSeq => get_hash_seq(db, conn, hash, sender).await, - }; - if let Err(e) = res.as_ref() { - tracing::error!("get failed: {}", e); - } - res -} - -/// Get a blob that was requested completely. -/// -/// We need to create our own files and handle the case where an outboard -/// is not needed. -pub async fn get_blob( - db: &D, - conn: quinn::Connection, - hash: &Hash, - progress: impl ProgressSender + IdGenerator, -) -> anyhow::Result { - let end = if let Some(entry) = db.get_partial(hash) { - trace!("got partial data for {}", hash); - let required_ranges = get_missing_ranges_blob::(&entry) - .await - .ok() - .unwrap_or_else(ChunkRanges::all); - let request = GetRequest::new(*hash, RangeSpecSeq::from_ranges([required_ranges])); - // full request - let request = get::fsm::start(conn, request); - // create a new bidi stream - let connected = request.next().await?; - // next step. we have requested a single hash, so this must be StartRoot - let ConnectedNext::StartRoot(start) = connected.next().await? else { - anyhow::bail!("expected StartRoot"); - }; - // move to the header - let header = start.next(); - // do the ceremony of getting the blob and adding it to the database - - get_blob_inner_partial(db, header, entry, progress).await? - } else { - // full request - let request = get::fsm::start(conn, GetRequest::single(*hash)); - // create a new bidi stream - let connected = request.next().await?; - // next step. we have requested a single hash, so this must be StartRoot - let ConnectedNext::StartRoot(start) = connected.next().await? else { - anyhow::bail!("expected StartRoot"); - }; - // move to the header - let header = start.next(); - // do the ceremony of getting the blob and adding it to the database - get_blob_inner(db, header, progress).await? - }; - - // we have requested a single hash, so we must be at closing - let EndBlobNext::Closing(end) = end.next() else { - anyhow::bail!("expected Closing"); - }; - // this closes the bidi stream. Do something with the stats? - let stats = end.next().await?; - anyhow::Ok(stats) -} - -pub(crate) async fn get_missing_ranges_blob( - entry: &D::PartialEntry, -) -> anyhow::Result { - use tracing::trace as log; - // compute the valid range from just looking at the data file - let mut data_reader = entry.data_reader().await?; - let data_size = data_reader.len().await?; - let valid_from_data = ChunkRanges::from(..ByteNum(data_size).full_chunks()); - // compute the valid range from just looking at the outboard file - let mut outboard = entry.outboard().await?; - let valid_from_outboard = bao_tree::io::fsm::valid_ranges(&mut outboard).await?; - let valid: ChunkRanges = valid_from_data.intersection(&valid_from_outboard); - let total_valid: u64 = valid - .iter() - .map(|x| match x { - RangeSetRange::Range(x) => x.end.to_bytes().0 - x.start.to_bytes().0, - RangeSetRange::RangeFrom(_) => 0, - }) - .sum(); - log!("valid_from_data: {:?}", valid_from_data); - log!("valid_from_outboard: {:?}", valid_from_data); - log!("total_valid: {}", total_valid); - let invalid = ChunkRanges::all().difference(&valid); - Ok(invalid) -} - -/// Get a blob that was requested completely. -/// -/// We need to create our own files and handle the case where an outboard -/// is not needed. -async fn get_blob_inner( - db: &D, - at_header: AtBlobHeader, - sender: impl ProgressSender + IdGenerator, -) -> anyhow::Result { - use iroh_io::AsyncSliceWriter; - // read the size - let (at_content, size) = at_header.next().await?; - let hash = at_content.hash(); - let child_offset = at_content.offset(); - // create the temp file pair - let entry = db.get_or_create_partial(hash, size)?; - // open the data file in any case - let df = entry.data_writer().await?; - let mut of: Option = if needs_outboard(size) { - Some(entry.outboard_mut().await?) - } else { - None - }; - // allocate a new id for progress reports for this transfer - let id = sender.new_id(); - sender - .send(DownloadProgress::Found { - id, - hash, - size, - child: child_offset, - }) - .await?; - let sender2 = sender.clone(); - let on_write = move |offset: u64, _length: usize| { - // if try send fails it means that the receiver has been dropped. - // in that case we want to abort the write_all_with_outboard. - sender2 - .try_send(DownloadProgress::Progress { id, offset }) - .map_err(|e| { - tracing::info!("aborting download of {}", hash); - e - })?; - Ok(()) - }; - let mut pw = ProgressSliceWriter2::new(df, on_write); - // use the convenience method to write all to the two vfs objects - let end = at_content - .write_all_with_outboard(of.as_mut(), &mut pw) - .await?; - // sync the data file - pw.sync().await?; - // sync the outboard file, if we wrote one - if let Some(mut of) = of { - of.sync().await?; - } - db.insert_complete(entry).await?; - // notify that we are done - sender.send(DownloadProgress::Done { id }).await?; - Ok(end) -} - -fn needs_outboard(size: u64) -> bool { - size > (IROH_BLOCK_SIZE.bytes() as u64) -} - -/// Get a blob that was requested partially. -/// -/// We get passed the data and outboard ids. Partial downloads are only done -/// for large blobs where the outboard is present. -async fn get_blob_inner_partial( - db: &D, - at_header: AtBlobHeader, - entry: D::PartialEntry, - sender: impl ProgressSender + IdGenerator, -) -> anyhow::Result { - // TODO: the data we get is validated at this point, but we need to check - // that it actually contains the requested ranges. Or DO WE? - use iroh_io::AsyncSliceWriter; - - // read the size - let (at_content, size) = at_header.next().await?; - // open the data file in any case - let df = entry.data_writer().await?; - let mut of = if needs_outboard(size) { - Some(entry.outboard_mut().await?) - } else { - None - }; - // allocate a new id for progress reports for this transfer - let id = sender.new_id(); - let hash = at_content.hash(); - let child_offset = at_content.offset(); - sender - .send(DownloadProgress::Found { - id, - hash, - size, - child: child_offset, - }) - .await?; - let sender2 = sender.clone(); - let on_write = move |offset: u64, _length: usize| { - // if try send fails it means that the receiver has been dropped. - // in that case we want to abort the write_all_with_outboard. - sender2 - .try_send(DownloadProgress::Progress { id, offset }) - .map_err(|e| { - tracing::info!("aborting download of {}", hash); - e - })?; - Ok(()) - }; - let mut pw = ProgressSliceWriter2::new(df, on_write); - // use the convenience method to write all to the two vfs objects - let at_end = at_content - .write_all_with_outboard(of.as_mut(), &mut pw) - .await?; - // sync the data file - pw.sync().await?; - // sync the outboard file - if let Some(mut of) = of { - of.sync().await?; - } - // actually store the data. it is up to the db to decide if it wants to - // rename the files or not. - db.insert_complete(entry).await?; - // notify that we are done - sender.send(DownloadProgress::Done { id }).await?; - Ok(at_end) -} - -/// Given a sequence of hashes, figure out what is missing -pub(crate) async fn get_missing_ranges_hash_seq( - db: &D, - hash_seq: &[Hash], -) -> io::Result>> { - let items = hash_seq.iter().map(|hash| async move { - io::Result::Ok(if let Some(entry) = db.get_partial(hash) { - // first look for partial - trace!("got partial data for {}", hash,); - let missing_chunks = get_missing_ranges_blob::(&entry) - .await - .ok() - .unwrap_or_else(ChunkRanges::all); - BlobInfo::Partial { - entry, - missing_chunks, - } - } else if db.get(hash).is_some() { - // then look for complete - BlobInfo::Complete - } else { - BlobInfo::Missing - }) - }); - let mut res = Vec::with_capacity(hash_seq.len()); - // todo: parallelize maybe? - for item in items { - res.push(item.await?); - } - Ok(res) -} - -/// Get a sequence of hashes -pub async fn get_hash_seq( - db: &D, - conn: quinn::Connection, - root_hash: &Hash, - sender: impl ProgressSender + IdGenerator, -) -> anyhow::Result { - use tracing::info as log; - let finishing = if let Some(entry) = db.get(root_hash) { - log!("already got collection - doing partial download"); - // got the collection - let reader = entry.data_reader().await?; - let (mut hash_seq, children) = parse_hash_seq(reader).await?; - sender - .send(DownloadProgress::FoundHashSeq { - hash: *root_hash, - children, - }) - .await?; - let mut children: Vec = vec![]; - while let Some(hash) = hash_seq.next().await? { - children.push(hash); - } - let missing_info = get_missing_ranges_hash_seq(db, &children).await?; - if missing_info.iter().all(|x| matches!(x, BlobInfo::Complete)) { - log!("nothing to do"); - return Ok(Stats::default()); - } - let missing_iter = std::iter::once(ChunkRanges::empty()) - .chain(missing_info.iter().map(|x| x.missing_chunks())) - .collect::>(); - log!("requesting chunks {:?}", missing_iter); - let request = GetRequest::new(*root_hash, RangeSpecSeq::from_ranges(missing_iter)); - let request = get::fsm::start(conn, request); - // create a new bidi stream - let connected = request.next().await?; - log!("connected"); - // we have not requested the root, so this must be StartChild - let ConnectedNext::StartChild(start) = connected.next().await? else { - anyhow::bail!("expected StartChild"); - }; - let mut next = EndBlobNext::MoreChildren(start); - // read all the children - loop { - let start = match next { - EndBlobNext::MoreChildren(start) => start, - EndBlobNext::Closing(finish) => break finish, - }; - let child_offset = - usize::try_from(start.child_offset()).context("child offset too large")?; - let (child_hash, info) = - match (children.get(child_offset), missing_info.get(child_offset)) { - (Some(blob), Some(info)) => (*blob, info), - _ => break start.finish(), - }; - tracing::info!( - "requesting child {} {:?}", - child_hash, - info.missing_chunks() - ); - let header = start.next(child_hash); - let end_blob = match info { - BlobInfo::Missing => get_blob_inner(db, header, sender.clone()).await?, - BlobInfo::Partial { entry, .. } => { - get_blob_inner_partial(db, header, entry.clone(), sender.clone()).await? - } - BlobInfo::Complete => anyhow::bail!("got data we have not requested"), - }; - next = end_blob.next(); - } - } else { - tracing::info!("don't have collection - doing full download"); - // don't have the collection, so probably got nothing - let request = get::fsm::start(conn, GetRequest::all(*root_hash)); - // create a new bidi stream - let connected = request.next().await?; - // next step. we have requested a single hash, so this must be StartRoot - let ConnectedNext::StartRoot(start) = connected.next().await? else { - anyhow::bail!("expected StartRoot"); - }; - // move to the header - let header = start.next(); - // read the blob and add it to the database - let end_root = get_blob_inner(db, header, sender.clone()).await?; - // read the collection fully for now - let entry = db.get(root_hash).context("just downloaded")?; - let reader = entry.data_reader().await?; - let (mut collection, count) = parse_hash_seq(reader).await?; - sender - .send(DownloadProgress::FoundHashSeq { - hash: *root_hash, - children: count, - }) - .await?; - let mut children = vec![]; - while let Some(hash) = collection.next().await? { - children.push(hash); - } - let mut next = end_root.next(); - // read all the children - loop { - let start = match next { - EndBlobNext::MoreChildren(start) => start, - EndBlobNext::Closing(finish) => break finish, - }; - let child_offset = - usize::try_from(start.child_offset()).context("child offset too large")?; - let child_hash = match children.get(child_offset) { - Some(blob) => *blob, - None => break start.finish(), - }; - let header = start.next(child_hash); - let end_blob = get_blob_inner(db, header, sender.clone()).await?; - next = end_blob.next(); - } - }; - // this closes the bidi stream. Do something with the stats? - let stats = finishing.next().await?; - anyhow::Ok(stats) -} - -#[derive(Debug, Clone)] -pub(crate) enum BlobInfo { - // we have the blob completely - Complete, - // we have the blob partially - Partial { - entry: D::PartialEntry, - missing_chunks: ChunkRanges, - }, - // we don't have the blob at all - Missing, -} - -impl BlobInfo { - pub fn missing_chunks(&self) -> ChunkRanges { - match self { - BlobInfo::Complete => ChunkRanges::empty(), - BlobInfo::Partial { missing_chunks, .. } => missing_chunks.clone(), - BlobInfo::Missing => ChunkRanges::all(), - } - } -} diff --git a/src/iroh_bytes_util.rs b/src/iroh_bytes_util.rs deleted file mode 100644 index 10b08ab..0000000 --- a/src/iroh_bytes_util.rs +++ /dev/null @@ -1,62 +0,0 @@ -/// Utilities for iroh_bytes, should live in iroh-bytes -use std::sync::Arc; - -use bytes::Bytes; -use iroh_bytes::get::fsm::EndBlobNext; -use iroh_bytes::hashseq::HashSeq; -use iroh_bytes::protocol::RangeSpecSeq; -use iroh_bytes::store::bao_tree::{ChunkNum, ChunkRanges}; -use iroh_bytes::{Hash, HashAndFormat}; - -pub async fn get_hash_seq_and_sizes( - connection: &quinn::Connection, - hash: &Hash, - max_size: u64, -) -> anyhow::Result<(HashSeq, Arc<[u64]>)> { - let content = HashAndFormat::hash_seq(*hash); - tracing::debug!("Getting hash seq and children sizes of {}", content); - let request = iroh_bytes::protocol::GetRequest::new( - *hash, - RangeSpecSeq::from_ranges_infinite([ - ChunkRanges::all(), - ChunkRanges::from(ChunkNum(u64::MAX)..), - ]), - ); - let at_start = iroh_bytes::get::fsm::start(connection.clone(), request); - let at_connected = at_start.next().await?; - let iroh_bytes::get::fsm::ConnectedNext::StartRoot(start) = at_connected.next().await? else { - unreachable!("query includes root"); - }; - let at_start_root = start.next(); - let (at_blob_content, size) = at_start_root.next().await?; - // check the size to avoid parsing a maliciously large hash seq - if size > max_size { - anyhow::bail!("size too large"); - } - let (mut curr, hash_seq) = at_blob_content.concatenate_into_vec().await?; - let hash_seq = HashSeq::try_from(Bytes::from(hash_seq))?; - let mut sizes = Vec::with_capacity(hash_seq.len()); - let closing = loop { - match curr.next() { - EndBlobNext::MoreChildren(more) => { - let hash = match hash_seq.get(sizes.len()) { - Some(hash) => hash, - None => break more.finish(), - }; - let at_header = more.next(hash); - let (at_content, size) = at_header.next().await?; - let next = at_content.drain().await?; - sizes.push(size); - curr = next; - } - EndBlobNext::Closing(closing) => break closing, - } - }; - let _stats = closing.next().await?; - tracing::debug!( - "Got hash seq and children sizes of {}: {:?}", - content, - sizes - ); - Ok((hash_seq, sizes.into())) -} diff --git a/src/main.rs b/src/main.rs index 1d60ceb..0dbb462 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,15 +7,13 @@ use indicatif::{ HumanBytes, HumanDuration, MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle, }; use iroh_bytes::{ - get::fsm::DecodeError, - provider::{ - self, handle_connection, DownloadProgress, EventSender, RequestAuthorizationHandler, - }, + format::collection::Collection, + get::{db::DownloadProgress, fsm::DecodeError, request::get_hash_seq_and_sizes}, + provider::{self, handle_connection, EventSender}, store::{ExportMode, ImportMode, ImportProgress}, BlobFormat, Hash, HashAndFormat, TempTag, }; -use iroh_bytes_util::get_hash_seq_and_sizes; -use iroh_net::{key::SecretKey, MagicEndpoint}; +use iroh_net::{key::SecretKey, ticket::BlobTicket, MagicEndpoint}; use rand::Rng; use std::{ collections::BTreeMap, @@ -25,15 +23,8 @@ use std::{ sync::Arc, time::Duration, }; +use tokio_util::task::LocalPoolHandle; use walkdir::WalkDir; -mod sendme_ticket; -use sendme_ticket::Ticket; - -use crate::collection::Collection; -mod collection; -mod get; -mod iroh_bytes_util; -mod progress; /// Send a file or directory between two machines, using blake3 verified streaming. /// /// For all subcommands, you can specify a secret key using the IROH_SECRET @@ -121,7 +112,7 @@ pub struct ProvideArgs { #[derive(Parser, Debug)] pub struct GetArgs { /// The ticket to use to connect to the provider. - pub ticket: sendme_ticket::Ticket, + pub ticket: BlobTicket, #[clap(flatten)] pub common: CommonArgs, @@ -141,19 +132,6 @@ fn get_or_create_secret() -> anyhow::Result { } } -#[derive(Debug)] -struct NoAuth; - -impl RequestAuthorizationHandler for NoAuth { - fn authorize( - &self, - _token: Option, - _request: &iroh_bytes::protocol::Request, - ) -> futures::future::BoxFuture<'static, anyhow::Result<()>> { - future::ok(()).boxed() - } -} - fn validate_path_component(component: &str) -> anyhow::Result<()> { anyhow::ensure!( !component.contains('/'), @@ -460,15 +438,7 @@ async fn provide(args: ProvideArgs) -> anyhow::Result<()> { anyhow::Ok(()) }); std::fs::create_dir_all(&iroh_data_dir)?; - let rt = iroh_bytes::util::runtime::Handle::from_current(1)?; - let db = iroh_bytes::store::flat::Store::load( - iroh_data_dir.clone(), - iroh_data_dir.clone(), - iroh_data_dir.clone(), - &rt, - ) - .await?; - let auth = Arc::new(NoAuth); + let db = iroh_bytes::store::flat::Store::load(&iroh_data_dir).await?; let path = args.path; let (temp_tag, size, collection) = import(path.clone(), db.clone()).await?; let hash = *temp_tag.hash(); @@ -480,7 +450,7 @@ async fn provide(args: ProvideArgs) -> anyhow::Result<()> { } // make a ticket let addr = endpoint.my_addr().await?; - let ticket = Ticket::new(addr, hash, BlobFormat::HashSeq)?; + let ticket = BlobTicket::new(addr, hash, BlobFormat::HashSeq)?; let entry_type = if path.is_file() { "file" } else { "directory" }; println!( "imported {} {}, {}, hash {}", @@ -497,6 +467,7 @@ async fn provide(args: ProvideArgs) -> anyhow::Result<()> { println!("to get this data, use"); println!("sendme get {}", ticket); let ps = ProvideStatus::new(); + let rt = LocalPoolHandle::new(1); loop { let Some(connecting) = endpoint.accept().await else { tracing::info!("no more incoming connections, exiting"); @@ -505,8 +476,7 @@ async fn provide(args: ProvideArgs) -> anyhow::Result<()> { let db = db.clone(); let rt = rt.clone(); let ps = ps.clone(); - let auth = auth.clone(); - tokio::spawn(handle_connection(connecting, db, ps.new_client(), auth, rt)); + tokio::spawn(handle_connection(connecting, db, ps.new_client(), rt)); } drop(temp_tag); std::fs::remove_dir_all(iroh_data_dir)?; @@ -621,22 +591,15 @@ async fn get(args: GetArgs) -> anyhow::Result<()> { .await?; let dir_name = format!(".sendme-get-{}", ticket.hash().to_hex()); let iroh_data_dir = std::env::current_dir()?.join(dir_name); - let rt = iroh_bytes::util::runtime::Handle::from_current(1)?; - let db = iroh_bytes::store::flat::Store::load( - iroh_data_dir.clone(), - iroh_data_dir.clone(), - iroh_data_dir.clone(), - &rt, - ) - .await?; + let db = iroh_bytes::store::flat::Store::load(&iroh_data_dir).await?; let mp = MultiProgress::new(); let connect_progress = mp.add(ProgressBar::hidden()); connect_progress.set_draw_target(ProgressDrawTarget::stderr()); connect_progress.set_style(ProgressStyle::default_spinner()); connect_progress.set_message(format!("connecting to {}", addr.node_id)); - let connection = endpoint.connect(addr, &iroh_bytes::protocol::ALPN).await?; + let connection = endpoint.connect(addr, iroh_bytes::protocol::ALPN).await?; let hash_and_format = HashAndFormat { - hash: *ticket.hash(), + hash: ticket.hash(), format: ticket.format(), }; connect_progress.finish_and_clear(); @@ -651,7 +614,7 @@ async fn get(args: GetArgs) -> anyhow::Result<()> { let payload_size = sizes.iter().skip(1).sum::(); eprintln!( "getting collection {} {} files, {}", - print_hash(ticket.hash(), args.common.format), + print_hash(&ticket.hash(), args.common.format), total_files, HumanBytes(payload_size) ); @@ -664,7 +627,7 @@ async fn get(args: GetArgs) -> anyhow::Result<()> { ); } let _task = tokio::spawn(show_download_progress(recv.into_stream(), total_size)); - let _stats = get::get(&db, connection, &hash_and_format, progress) + let _stats = iroh_bytes::get::db::get_to_db(&db, connection, &hash_and_format, progress) .await .map_err(show_get_error)?; let collection = Collection::load(&db, &hash_and_format.hash).await?; diff --git a/src/progress.rs b/src/progress.rs deleted file mode 100644 index 2f42f49..0000000 --- a/src/progress.rs +++ /dev/null @@ -1,54 +0,0 @@ -/// Progress utils, should live in iroh-common? -use std::io; - -use bytes::Bytes; -use futures::{future::LocalBoxFuture, FutureExt}; -use iroh_io::AsyncSliceWriter; - -/// A slice writer that adds a synchronous progress callback -#[derive(Debug)] -pub struct ProgressSliceWriter2(W, F); - -#[allow(dead_code)] -impl io::Result<()> + 'static> - ProgressSliceWriter2 -{ - /// Create a new `ProgressSliceWriter` from an inner writer and a progress callback - pub fn new(inner: W, on_write: F) -> Self { - Self(inner, on_write) - } - - /// Return the inner writer - pub fn into_inner(self) -> W { - self.0 - } -} - -impl io::Result<()> + 'static> AsyncSliceWriter - for ProgressSliceWriter2 -{ - type WriteBytesAtFuture<'a> = LocalBoxFuture<'a, io::Result<()>>; - fn write_bytes_at(&mut self, offset: u64, data: Bytes) -> Self::WriteBytesAtFuture<'_> { - // todo: get rid of the boxing - async move { - (self.1)(offset, data.len())?; - self.0.write_bytes_at(offset, data).await - } - .boxed_local() - } - - type WriteAtFuture<'a> = W::WriteAtFuture<'a>; - fn write_at<'a>(&'a mut self, offset: u64, bytes: &'a [u8]) -> Self::WriteAtFuture<'a> { - self.0.write_at(offset, bytes) - } - - type SyncFuture<'a> = W::SyncFuture<'a>; - fn sync(&mut self) -> Self::SyncFuture<'_> { - self.0.sync() - } - - type SetLenFuture<'a> = W::SetLenFuture<'a>; - fn set_len(&mut self, size: u64) -> Self::SetLenFuture<'_> { - self.0.set_len(size) - } -} diff --git a/src/sendme_ticket.rs b/src/sendme_ticket.rs deleted file mode 100644 index 750bd1f..0000000 --- a/src/sendme_ticket.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Tickets for blobs. -//! -//! Copied over from iroh. It should live in iroh-bytes -use std::{fmt::Display, str::FromStr}; - -use anyhow::{ensure, Context, Result}; -use iroh_bytes::{BlobFormat, Hash}; -use iroh_net::NodeAddr; -use serde::{Deserialize, Serialize}; - -/// 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)] -pub struct Ticket { - /// The provider to get a file from. - node: NodeAddr, - /// The format of the blob. - format: BlobFormat, - /// The hash to retrieve. - hash: Hash, -} - -impl Display for Ticket { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.serialize()) - } -} - -impl FromStr for Ticket { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - Self::deserialize(s) - } -} - -#[allow(dead_code)] -impl Ticket { - /// Creates a new ticket. - pub fn new(node: NodeAddr, hash: Hash, format: BlobFormat) -> Result { - ensure!(!node.info.is_empty(), "addressing info cannot be empty"); - 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 Ticket { node, hash, format } = self; - (node, hash, format) - } - - /// Serialize to postcard bytes. - fn to_bytes(&self) -> Vec { - postcard::to_stdvec(&self).expect("postcard::to_stdvec is infallible") - } - - /// Verify this ticket. - fn verify(&self) -> anyhow::Result<()> { - // do we need this? a ticket with just a node id still might be useful - // given some sort of discovery mechanism. - anyhow::ensure!(!self.node.info.is_empty(), "no node info"); - Ok(()) - } - - /// Deserialize from postcard bytes. - fn from_bytes(bytes: &[u8]) -> anyhow::Result { - let ticket: Self = postcard::from_bytes(bytes)?; - ticket.verify().context("invalid ticket")?; - Ok(ticket) - } - - /// Serialize to string. - fn serialize(&self) -> String { - let mut out = "blob".to_string(); - data_encoding::BASE32_NOPAD.encode_append(&self.to_bytes(), &mut out); - out.make_ascii_lowercase(); - out - } - - /// Deserialize from a string. - fn deserialize(str: &str) -> anyhow::Result { - let Some(base32) = str.strip_prefix("blob") else { - anyhow::bail!("invalid prefix"); - }; - let bytes = data_encoding::BASE32_NOPAD - .decode(base32.to_ascii_uppercase().as_bytes()) - .context("invalid base32")?; - Self::from_bytes(&bytes) - } -} - -impl Serialize for Ticket { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) - } else { - let Ticket { node, format, hash } = self; - (node, format, hash).serialize(serializer) - } - } -} - -impl<'de> Deserialize<'de> for Ticket { - 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 bao_tree::blake3; - use iroh_bytes::store::bao_tree; - use iroh_net::key::SecretKey; - - use super::*; - - fn make_ticket() -> Ticket { - let hash = blake3::hash(b"hi there"); - let hash = Hash::from(hash); - let peer = SecretKey::generate().public(); - let addr = SocketAddr::from_str("127.0.0.1:1234").unwrap(); - let derp_url = None; - Ticket { - hash, - node: NodeAddr::from_parts(peer, derp_url, vec![addr]), - format: BlobFormat::HashSeq, - } - } - - #[test] - fn test_ticket_postcard() { - let ticket = make_ticket(); - let bytes = postcard::to_stdvec(&ticket).unwrap(); - let ticket2: Ticket = 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: Ticket = serde_json::from_str(&json).unwrap(); - assert_eq!(ticket2, ticket); - } - - #[test] - fn test_ticket_base32_roundtrip() { - let ticket = make_ticket(); - let base32 = ticket.to_string(); - println!("Ticket: {base32}"); - println!("{} bytes", base32.len()); - - let ticket2: Ticket = base32.parse().unwrap(); - assert_eq!(ticket2, ticket); - } -} diff --git a/tests/cli.rs b/tests/cli.rs index 3aed4e5..0919047 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -3,8 +3,8 @@ use std::{ path::{Path, PathBuf}, str::FromStr, }; -#[path = "../src/sendme_ticket.rs"] -mod sendme_ticket; + +use iroh_net::ticket::BlobTicket; // binary path fn sendme_bin() -> &'static str { @@ -65,7 +65,7 @@ fn provide_get_file() { let output = read_ascii_lines(4, &mut provide).unwrap(); let output = String::from_utf8(output).unwrap(); let ticket = output.split_ascii_whitespace().last().unwrap(); - let ticket = sendme_ticket::Ticket::from_str(ticket).unwrap(); + let ticket = BlobTicket::from_str(ticket).unwrap(); let get = duct::cmd(sendme_bin(), ["get", &ticket.to_string()]) .dir(tgt_dir.path()) .env_remove("RUST_LOG") // disable tracing @@ -117,7 +117,7 @@ fn provide_get_dir() { let output = read_ascii_lines(4, &mut provide).unwrap(); let output = String::from_utf8(output).unwrap(); let ticket = output.split_ascii_whitespace().last().unwrap(); - let ticket = sendme_ticket::Ticket::from_str(ticket).unwrap(); + let ticket = BlobTicket::from_str(ticket).unwrap(); let get = duct::cmd(sendme_bin(), ["get", &ticket.to_string()]) .dir(tgt_dir.path()) .env_remove("RUST_LOG") // disable tracing