From 007d6c8711fde5e6d4eab4572d0454d17d07bb3d Mon Sep 17 00:00:00 2001 From: kurealnum Date: Wed, 30 Apr 2025 21:26:36 -0400 Subject: [PATCH 01/23] chore: Added webrtc --- Cargo.lock | 999 ++++++++++++++++++++++++++++++++- crates/libtortillas/Cargo.toml | 1 + 2 files changed, 976 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4c552a5..2b806f5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,41 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.11" @@ -116,6 +151,51 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-scoped" version = "0.9.0" @@ -185,6 +265,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -203,13 +289,22 @@ version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -223,6 +318,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -253,6 +354,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "borsh" version = "1.5.7" @@ -278,6 +388,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -362,6 +478,15 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.16" @@ -373,6 +498,18 @@ dependencies = [ "shlex", ] +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -418,6 +555,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -564,6 +711,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -585,7 +747,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags", + "bitflags 2.9.0", "crossterm_winapi", "futures-core", "mio", @@ -605,6 +767,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -612,9 +786,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -642,6 +826,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "debug-ignore" version = "1.0.5" @@ -655,9 +845,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive-where" version = "1.2.7" @@ -689,7 +903,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -710,6 +937,20 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -741,6 +982,27 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enable-ansi-support" version = "0.2.1" @@ -797,6 +1059,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -976,6 +1248,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1005,6 +1278,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.1" @@ -1030,6 +1313,17 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "guppy" version = "0.17.17" @@ -1100,6 +1394,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -1315,6 +1627,36 @@ dependencies = [ "web-time", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "interceptor" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ab04c530fd82e414e40394cabe5f0ebfe30d119f10fe29d6e3561926af412e" +dependencies = [ + "async-trait", + "bytes", + "log", + "portable-atomic", + "rand 0.8.5", + "rtcp", + "rtp", + "thiserror 1.0.69", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1410,7 +1752,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", "redox_syscall", ] @@ -1457,6 +1799,7 @@ dependencies = [ "tokio-stream", "tracing", "tracing-test", + "webrtc", ] [[package]] @@ -1496,12 +1839,31 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "miette" version = "7.5.0" @@ -1646,7 +2008,7 @@ dependencies = [ "recursion", "regex", "regex-syntax 0.8.5", - "smol_str", + "smol_str 0.3.2", "thiserror 2.0.12", "winnow", ] @@ -1661,7 +2023,7 @@ dependencies = [ "nextest-workspace-hack", "serde", "serde_json", - "smol_str", + "smol_str 0.3.2", "target-spec", ] @@ -1706,7 +2068,7 @@ dependencies = [ "nextest-filtering", "nextest-metadata", "nextest-workspace-hack", - "nix", + "nix 0.29.0", "owo-colors 4.2.0", "pin-project-lite", "quick-junit", @@ -1721,7 +2083,7 @@ dependencies = [ "sha2", "shell-words", "smallvec", - "smol_str", + "smol_str 0.3.2", "strip-ansi-escapes", "supports-unicode", "swrite", @@ -1748,13 +2110,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3" +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", @@ -1781,21 +2156,46 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "num_enum" -version = "0.7.3" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "num_enum_derive", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", ] [[package]] @@ -1825,19 +2225,34 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", @@ -1903,6 +2318,30 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1935,6 +2374,25 @@ dependencies = [ "camino", ] +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1999,12 +2457,30 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2014,6 +2490,15 @@ dependencies = [ "zerocopy 0.8.23", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -2185,6 +2670,20 @@ dependencies = [ "getrandom 0.3.1", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "recursion" version = "0.5.2" @@ -2197,7 +2696,7 @@ version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -2294,6 +2793,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2325,12 +2834,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags", + "bitflags 2.9.0", "indexmap", "serde", "serde_derive", ] +[[package]] +name = "rtcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8306430fb118b7834bbee50e744dc34826eca1da2158657a3d6cbc70e24c2096" +dependencies = [ + "bytes", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rtp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68baca5b6cb4980678713f0d06ef3a432aa642baefcbfd0f4dd2ef9eb5ab550" +dependencies = [ + "bytes", + "memchr", + "portable-atomic", + "rand 0.8.5", + "serde", + "thiserror 1.0.69", + "webrtc-util", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2358,13 +2893,22 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2377,7 +2921,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.9.2", @@ -2454,13 +2998,39 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a526161f474ae94b966ba622379d939a8fe46c930eebbadb73e339622599d5" +dependencies = [ + "rand 0.8.5", + "substring", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", @@ -2742,6 +3312,15 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + [[package]] name = "smol_str" version = "0.3.2" @@ -2793,6 +3372,34 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "stun" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea256fb46a13f9204e9dee9982997b2c3097db175a9fddaa8350310d03c4d5a3" +dependencies = [ + "base64 0.22.1", + "crc", + "lazy_static", + "md-5", + "rand 0.8.5", + "ring", + "subtle", + "thiserror 1.0.69", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2846,13 +3453,24 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.9.0", "core-foundation", "system-configuration-sys", ] @@ -2998,6 +3616,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.9.0" @@ -3255,6 +3904,27 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "turn" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0044fdae001dd8a1e247ea6289abf12f4fcea1331a2364da512f9cd680bbd8cb" +dependencies = [ + "async-trait", + "base64 0.22.1", + "futures", + "log", + "md-5", + "portable-atomic", + "rand 0.8.5", + "ring", + "stun", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "webrtc-util", +] + [[package]] name = "typenum" version = "1.18.0" @@ -3300,6 +3970,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -3371,6 +4051,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + [[package]] name = "want" version = "0.3.1" @@ -3495,6 +4184,215 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webrtc" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30367074d9f18231d28a74fab0120856b2b665da108d71a12beab7185a36f97b" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "cfg-if", + "hex", + "interceptor", + "lazy_static", + "log", + "portable-atomic", + "rand 0.8.5", + "rcgen", + "regex", + "ring", + "rtcp", + "rtp", + "rustls", + "sdp", + "serde", + "serde_json", + "sha2", + "smol_str 0.2.2", + "stun", + "thiserror 1.0.69", + "time", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec93b991efcd01b73c5b3503fa8adba159d069abe5785c988ebe14fcf8f05d1" +dependencies = [ + "bytes", + "log", + "portable-atomic", + "thiserror 1.0.69", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c9b89fc909f9da0499283b1112cd98f72fec28e55a54a9e352525ca65cd95c" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bincode", + "byteorder", + "cbc", + "ccm", + "der-parser", + "hkdf", + "hmac", + "log", + "p256", + "p384", + "portable-atomic", + "rand 0.8.5", + "rand_core 0.6.4", + "rcgen", + "ring", + "rustls", + "sec1", + "serde", + "sha1", + "sha2", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser", +] + +[[package]] +name = "webrtc-ice" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348b28b593f7709ac98d872beb58c0009523df652c78e01b950ab9c537ff17d" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "portable-atomic", + "rand 0.8.5", + "serde", + "serde_json", + "stun", + "thiserror 1.0.69", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dfe9686c6c9c51428da4de415cb6ca2dc0591ce2b63212e23fd9cccf0e316b" +dependencies = [ + "log", + "socket2", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e153be16b8650021ad3e9e49ab6e5fa9fb7f6d1c23c213fd8bbd1a1135a4c704" +dependencies = [ + "byteorder", + "bytes", + "rand 0.8.5", + "rtp", + "thiserror 1.0.69", +] + +[[package]] +name = "webrtc-sctp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5faf3846ec4b7e64b56338d62cbafe084aa79806b0379dff5cc74a8b7a2b3063" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "portable-atomic", + "rand 0.8.5", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771db9993712a8fb3886d5be4613ebf27250ef422bd4071988bf55f1ed1a64fa" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac", + "log", + "rtcp", + "rtp", + "sha1", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1438a8fd0d69c5775afb4a71470af92242dbd04059c61895163aa3c1ef933375" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "libc", + "log", + "nix 0.26.4", + "portable-atomic", + "rand 0.8.5", + "thiserror 1.0.69", + "tokio", + "winapi", +] + [[package]] name = "win32job" version = "2.0.2" @@ -3844,7 +4742,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -3856,6 +4754,36 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + [[package]] name = "xattr" version = "1.5.0" @@ -3872,6 +4800,15 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3917,6 +4854,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zipsign-api" diff --git a/crates/libtortillas/Cargo.toml b/crates/libtortillas/Cargo.toml index f9a108c2..30cf1cac 100644 --- a/crates/libtortillas/Cargo.toml +++ b/crates/libtortillas/Cargo.toml @@ -21,6 +21,7 @@ tracing = "0.1.41" async-trait = "0.1.88" librqbit-utp = "0.2.3" bitvec = "1.0.1" +webrtc = "0.12.0" [dev-dependencies] tracing-test = "0.2.5" From 7588db72e7919815b4ec0f22da06b8e38ed11ebd Mon Sep 17 00:00:00 2001 From: kurealnum Date: Thu, 1 May 2025 12:54:51 -0400 Subject: [PATCH 02/23] init: Created webrtc.rs and updated supporting files --- crates/libtortillas/src/peers/mod.rs | 1 + crates/libtortillas/src/peers/utp.rs | 2 +- crates/libtortillas/src/peers/webrtc.rs | 99 +++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 crates/libtortillas/src/peers/webrtc.rs diff --git a/crates/libtortillas/src/peers/mod.rs b/crates/libtortillas/src/peers/mod.rs index 13e301c9..08b2ce31 100644 --- a/crates/libtortillas/src/peers/mod.rs +++ b/crates/libtortillas/src/peers/mod.rs @@ -22,6 +22,7 @@ pub mod messages; pub mod tcp; mod transport_messages; pub mod utp; +pub mod webrtc; pub type PeerKey = SocketAddr; /// Represents a BitTorrent peer with connection state and statistics diff --git a/crates/libtortillas/src/peers/utp.rs b/crates/libtortillas/src/peers/utp.rs index 47c90bb3..e1799419 100644 --- a/crates/libtortillas/src/peers/utp.rs +++ b/crates/libtortillas/src/peers/utp.rs @@ -14,7 +14,7 @@ use tokio::{ sync::Mutex, time::Instant, }; -use tracing::{debug, error, info, instrument, trace}; +use tracing::{debug, error, info, trace}; #[derive(Clone)] pub struct UtpProtocol { diff --git a/crates/libtortillas/src/peers/webrtc.rs b/crates/libtortillas/src/peers/webrtc.rs new file mode 100644 index 00000000..edd65d69 --- /dev/null +++ b/crates/libtortillas/src/peers/webrtc.rs @@ -0,0 +1,99 @@ +use std::{collections::HashMap, net::IpAddr, str::FromStr, sync::Arc}; + +use anyhow::Result; +use async_trait::async_trait; +use tokio::sync::Mutex; +use webrtc::{ + api::{ + interceptor_registry::register_default_interceptors, media_engine::MediaEngine, APIBuilder, + }, + data_channel::RTCDataChannel, + ice_transport::ice_server::RTCIceServer, + interceptor::registry::Registry, + peer_connection::{configuration::RTCConfiguration, RTCPeerConnection}, +}; + +use crate::{ + errors::PeerTransportError, + hashes::{Hash, InfoHash}, +}; + +use super::{Peer, PeerKey, TransportProtocol}; + +#[derive(Clone)] +pub struct WebRTCProtocol { + pub socket: Arc, + pub peers: HashMap>>, +} + +impl WebRTCProtocol { + pub async fn new() -> Self { + let mut m = MediaEngine::default(); + + // Register default codecs + m.register_default_codecs().unwrap(); + + // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. + // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` + // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry + // for each PeerConnection. + let mut registry = Registry::new(); + + // Use the default set of Interceptors + registry = register_default_interceptors(registry, &mut m).unwrap(); + + // Create the API object with the MediaEngine + let api = APIBuilder::new() + .with_media_engine(m) + .with_interceptor_registry(registry) + .build(); + + let config = RTCConfiguration { + ice_servers: vec![RTCIceServer { + urls: vec!["stun:stun.l.google.com:19302".to_owned()], + ..Default::default() + }], + ..Default::default() + }; + let peer_connection = Arc::new(api.new_peer_connection(config).await.unwrap()); + WebRTCProtocol { + socket: peer_connection, + peers: HashMap::new(), + } + } +} + +#[async_trait] +#[allow(unused_variables)] +impl TransportProtocol for WebRTCProtocol { + async fn connect_peer( + &mut self, + peer: &mut Peer, + id: Arc>, + info_hash: Arc, + ) -> Result { + Ok(PeerKey::new(IpAddr::from_str("192.168.1.0").unwrap(), 1234)) + } + async fn send_data(&mut self, to: PeerKey, data: Vec) -> Result<(), PeerTransportError> { + Ok(()) + } + async fn receive_data( + &mut self, + info_hash: Arc, + id: Arc>, + ) -> Result<(PeerKey, Vec), PeerTransportError> { + Ok(( + PeerKey::new(IpAddr::from_str("192.168.1.0").unwrap(), 1234), + vec![0], + )) + } + fn close_connection(&mut self, peer_key: PeerKey) -> Result<()> { + Ok(()) + } + fn is_peer_connected(&self, peer_key: PeerKey) -> bool { + true + } + async fn get_connected_peer(&self, peer_key: PeerKey) -> Option { + Some(Peer::new(IpAddr::from_str("192.168.1.0").unwrap(), 1234)) + } +} From 61a96a22776c293ccb2e8a2c004e5ce56f25f8e7 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Thu, 1 May 2025 12:56:07 -0400 Subject: [PATCH 03/23] docs: Updated README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index abd5f3b3..f306b6ee 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,18 @@ We plan to support: - uTP peer connections - TCP peer connections -- Webtorrent (WSS) peer connections +- WebTorrent (WebRTC) peer connections We currently support: - Handling Magnet URIs - Handling Torrent files +- uTP peer handshakes +- TCP peer handshakes We are currently working on (this may be a little bit out of date, feel free to ask/open an issue): -- uTP peer connections -- TCP peer connections +- Webtorrent (WebRTC) peer handshakes/initial connections ## Testing From 1274b2c305d4209f6752ad1bf99325c864fb7e4e Mon Sep 17 00:00:00 2001 From: kurealnum Date: Fri, 2 May 2025 13:36:08 -0400 Subject: [PATCH 04/23] chore: Installed deps for WSS --- Cargo.lock | 162 +++++++++++++++++++++++++++++++-- crates/libtortillas/Cargo.toml | 28 +++++- 2 files changed, 182 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b806f5f..4149f2cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,6 +241,29 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "aws-lc-rs" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -310,12 +333,15 @@ dependencies = [ "itertools 0.12.1", "lazy_static", "lazycell", + "log", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", "syn", + "which", ] [[package]] @@ -619,6 +645,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "color-eyre" version = "0.6.3" @@ -696,6 +731,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1133,6 +1178,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -1789,6 +1840,9 @@ dependencies = [ "num_enum", "rand 0.9.0", "reqwest", + "rustls", + "rustls-native-certs", + "rustls-pki-types", "serde", "serde_bencode", "serde_qs", @@ -1796,9 +1850,12 @@ dependencies = [ "sha1", "thiserror 2.0.12", "tokio", + "tokio-rustls", "tokio-stream", + "tokio-tungstenite", "tracing", "tracing-test", + "webpki-roots", "webrtc", ] @@ -1974,7 +2031,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2490,6 +2547,16 @@ dependencies = [ "zerocopy 0.8.23", ] +[[package]] +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -2930,10 +2997,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -2942,6 +3011,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -2962,10 +3043,11 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3031,7 +3113,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -3471,7 +3566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3722,6 +3817,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.13" @@ -3904,6 +4015,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.0", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + [[package]] name = "turn" version = "0.9.0" @@ -4003,6 +4133,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4393,6 +4529,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "win32job" version = "2.0.2" diff --git a/crates/libtortillas/Cargo.toml b/crates/libtortillas/Cargo.toml index 30cf1cac..47b1f9a1 100644 --- a/crates/libtortillas/Cargo.toml +++ b/crates/libtortillas/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" serde_bencode = "^0.2.4" serde = { version = "1.0", features = ["derive"] } serde_qs = "0.14.0" -sha1 = "0.10.6" +sha1 = {version = "0.10.6", optional = true} hex = "0.4.3" reqwest = "0.12.13" serde_repr = "0.1.20" @@ -22,6 +22,32 @@ async-trait = "0.1.88" librqbit-utp = "0.2.3" bitvec = "1.0.1" webrtc = "0.12.0" +tokio-tungstenite = {version = "0.26.2", features=["rustls-tls-native-roots"]} +rustls = {version = "0.23.26", optional = true} + +[features] +rustls-tls-native-roots = ["__rustls-tls", "rustls-native-certs"] +rustls-tls-webpki-roots = ["__rustls-tls", "webpki-roots"] +__rustls-tls = ["rustls", "rustls-pki-types", "tokio-rustls", "stream", "tokio-tungstenite/__rustls-tls", "handshake"] +stream = [] +handshake = ["tokio-tungstenite/handshake"] + +[dependencies.rustls-native-certs] +optional = true +version = "0.8.0" + +[dependencies.webpki-roots] +optional = true +version = "0.26.0" + +[dependencies.rustls-pki-types] +optional = true +version = "1.0" + +[dependencies.tokio-rustls] +optional = true +version = "0.26.0" +default-features = false [dev-dependencies] tracing-test = "0.2.5" From 22d850a1398dbe4a79f385bd962ae3877d00c6aa Mon Sep 17 00:00:00 2001 From: kurealnum Date: Fri, 2 May 2025 13:36:38 -0400 Subject: [PATCH 05/23] feat: Implemented basic WSS tracker struct. Not sure if this is correct --- crates/libtortillas/src/tracker/mod.rs | 3 ++- crates/libtortillas/src/tracker/wss.rs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 crates/libtortillas/src/tracker/wss.rs diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index bc2e8f9c..a8b874f8 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -3,8 +3,8 @@ use async_trait::async_trait; use http::HttpTracker; use rand::random_range; use serde::{ - Deserialize, de::{self, Visitor}, + Deserialize, }; use std::{fmt, net::SocketAddr}; use tokio::sync::mpsc; @@ -13,6 +13,7 @@ use udp::UdpTracker; use crate::{hashes::InfoHash, peers::Peer}; pub mod http; pub mod udp; +pub mod wss; #[async_trait] pub trait TrackerTrait: Clone { diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs new file mode 100644 index 00000000..963dbd8b --- /dev/null +++ b/crates/libtortillas/src/tracker/wss.rs @@ -0,0 +1,22 @@ +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; +use tracing::trace; + +use crate::errors::TrackerError; + +/// Tracker for websockets +pub struct WssTracker { + uri: String, + pub stream: WebSocketStream>, +} + +impl WssTracker { + pub async fn new(&self, uri: String) -> Result { + let (ws_stream, _) = connect_async(&uri).await.unwrap(); + trace!("Connected to WSS tracker at {}", uri); + Ok(WssTracker { + uri, + stream: ws_stream, + }) + } +} From f4ced1f3b3d06fc45819234d927040dfedcbb58a Mon Sep 17 00:00:00 2001 From: kurealnum Date: Mon, 5 May 2025 18:59:52 -0400 Subject: [PATCH 06/23] feat: Some progress made on handling Websocket trackersg --- crates/libtortillas/Cargo.toml | 17 ++--------- crates/libtortillas/src/peers/webrtc.rs | 40 ++++++++++++++++++++----- crates/libtortillas/src/tracker/wss.rs | 11 ++----- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/crates/libtortillas/Cargo.toml b/crates/libtortillas/Cargo.toml index 47b1f9a1..4d707402 100644 --- a/crates/libtortillas/Cargo.toml +++ b/crates/libtortillas/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0" serde_bencode = "^0.2.4" serde = { version = "1.0", features = ["derive"] } serde_qs = "0.14.0" -sha1 = {version = "0.10.6", optional = true} +sha1 = {version = "0.10.6" } hex = "0.4.3" reqwest = "0.12.13" serde_repr = "0.1.20" @@ -23,14 +23,8 @@ librqbit-utp = "0.2.3" bitvec = "1.0.1" webrtc = "0.12.0" tokio-tungstenite = {version = "0.26.2", features=["rustls-tls-native-roots"]} -rustls = {version = "0.23.26", optional = true} - -[features] -rustls-tls-native-roots = ["__rustls-tls", "rustls-native-certs"] -rustls-tls-webpki-roots = ["__rustls-tls", "webpki-roots"] -__rustls-tls = ["rustls", "rustls-pki-types", "tokio-rustls", "stream", "tokio-tungstenite/__rustls-tls", "handshake"] -stream = [] -handshake = ["tokio-tungstenite/handshake"] +rustls = { version = "0.23.26" } +tokio-rustls = "0.26.2" [dependencies.rustls-native-certs] optional = true @@ -44,10 +38,5 @@ version = "0.26.0" optional = true version = "1.0" -[dependencies.tokio-rustls] -optional = true -version = "0.26.0" -default-features = false - [dev-dependencies] tracing-test = "0.2.5" diff --git a/crates/libtortillas/src/peers/webrtc.rs b/crates/libtortillas/src/peers/webrtc.rs index edd65d69..d5e29469 100644 --- a/crates/libtortillas/src/peers/webrtc.rs +++ b/crates/libtortillas/src/peers/webrtc.rs @@ -1,16 +1,18 @@ -use std::{collections::HashMap, net::IpAddr, str::FromStr, sync::Arc}; - use anyhow::Result; use async_trait::async_trait; +use std::{collections::HashMap, net::IpAddr, str::FromStr, sync::Arc}; use tokio::sync::Mutex; +use tracing::error; use webrtc::{ api::{ interceptor_registry::register_default_interceptors, media_engine::MediaEngine, APIBuilder, }, - data_channel::RTCDataChannel, + data_channel::{data_channel_init::RTCDataChannelInit, RTCDataChannel}, ice_transport::ice_server::RTCIceServer, interceptor::registry::Registry, - peer_connection::{configuration::RTCConfiguration, RTCPeerConnection}, + peer_connection::{ + configuration::RTCConfiguration, offer_answer_options::RTCOfferOptions, RTCPeerConnection, + }, }; use crate::{ @@ -22,7 +24,7 @@ use super::{Peer, PeerKey, TransportProtocol}; #[derive(Clone)] pub struct WebRTCProtocol { - pub socket: Arc, + pub connection: Arc, pub peers: HashMap>>, } @@ -57,7 +59,7 @@ impl WebRTCProtocol { }; let peer_connection = Arc::new(api.new_peer_connection(config).await.unwrap()); WebRTCProtocol { - socket: peer_connection, + connection: peer_connection, peers: HashMap::new(), } } @@ -66,13 +68,37 @@ impl WebRTCProtocol { #[async_trait] #[allow(unused_variables)] impl TransportProtocol for WebRTCProtocol { + /// Some helpful information: + /// + /// async fn connect_peer( &mut self, peer: &mut Peer, id: Arc>, info_hash: Arc, ) -> Result { - Ok(PeerKey::new(IpAddr::from_str("192.168.1.0").unwrap(), 1234)) + // let options = Some(RTCDataChannelInit { + // ordered: Some(true), + // ..Default::default() + // }); + // let data_channel = self + // .connection + // .create_data_channel("data", options) + // .await + // .map_err(|e| error!("Failed to create data channel!")) + // .unwrap(); + + let offer_options = Some(RTCOfferOptions { + voice_activity_detection: false, + ice_restart: true, + }); + + let sdp_description = self.connection.create_offer(offer_options).await.unwrap(); + self + .connection + .set_local_description(sdp_description) + .await + .unwrap(); } async fn send_data(&mut self, to: PeerKey, data: Vec) -> Result<(), PeerTransportError> { Ok(()) diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 963dbd8b..fcb4df23 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -2,8 +2,6 @@ use tokio::net::TcpStream; use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::trace; -use crate::errors::TrackerError; - /// Tracker for websockets pub struct WssTracker { uri: String, @@ -11,12 +9,9 @@ pub struct WssTracker { } impl WssTracker { - pub async fn new(&self, uri: String) -> Result { - let (ws_stream, _) = connect_async(&uri).await.unwrap(); + pub async fn new(&self, uri: String) -> Self { + let (stream, _) = connect_async(&uri).await.unwrap(); trace!("Connected to WSS tracker at {}", uri); - Ok(WssTracker { - uri, - stream: ws_stream, - }) + WssTracker { uri, stream } } } From a021d158a37f1b92dacb6ae688ce2e0d0880ece2 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Tue, 6 May 2025 14:18:05 -0400 Subject: [PATCH 07/23] refactor: Reworked WssTracker. This impl should be correct --- Cargo.lock | 1 + README.md | 4 ++ crates/libtortillas/Cargo.toml | 1 + crates/libtortillas/src/tracker/http.rs | 42 +------------------- crates/libtortillas/src/tracker/mod.rs | 44 +++++++++++++++++++- crates/libtortillas/src/tracker/wss.rs | 53 ++++++++++++++++++++++--- 6 files changed, 98 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4149f2cb..151faf99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1835,6 +1835,7 @@ dependencies = [ "anyhow", "async-trait", "bitvec", + "futures-util", "hex", "librqbit-utp", "num_enum", diff --git a/README.md b/README.md index f306b6ee..139b626d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ We use [Nextest](https://nexte.st/) for testing. You may have to install Nextest Please keep in mind that as of April 6th, 2025, this library is not complete. +### A small note about WebSocket (WS) trackers + +Generally speaking, this project refers to WebSockets as WSS (Web Sockets Secured), not WS. Consequently, you'll see files/structs/etc. with names such as `wss.rs`, `WssTracker`, and so on. Keep in mind that these files/structs/etc. refer to both secure and unsecured WebSockets. + ### Handshaking with peers #### uTP diff --git a/crates/libtortillas/Cargo.toml b/crates/libtortillas/Cargo.toml index 4d707402..0f9988ae 100644 --- a/crates/libtortillas/Cargo.toml +++ b/crates/libtortillas/Cargo.toml @@ -25,6 +25,7 @@ webrtc = "0.12.0" tokio-tungstenite = {version = "0.26.2", features=["rustls-tls-native-roots"]} rustls = { version = "0.23.26" } tokio-rustls = "0.26.2" +futures-util = "0.3.31" [dependencies.rustls-native-certs] optional = true diff --git a/crates/libtortillas/src/tracker/http.rs b/crates/libtortillas/src/tracker/http.rs index 95d2d350..2687b1a8 100644 --- a/crates/libtortillas/src/tracker/http.rs +++ b/crates/libtortillas/src/tracker/http.rs @@ -1,5 +1,5 @@ /// See https://www.bittorrent.org/beps/bep_0003.html -use super::{Peer, TrackerTrait}; +use super::{Peer, TrackerRequest, TrackerTrait}; use crate::{ errors::{HttpTrackerError, TrackerError}, hashes::{Hash, InfoHash}, @@ -7,12 +7,11 @@ use crate::{ use anyhow::Result; use async_trait::async_trait; use serde::{ - Deserialize, Serialize, de::{self, Visitor}, + Deserialize, Serialize, }; use std::{ net::{Ipv4Addr, SocketAddr}, - str::FromStr, time::Duration, }; use tokio::{sync::mpsc, time::sleep}; @@ -20,49 +19,12 @@ use tokio::{sync::mpsc, time::sleep}; use tracing::{debug, error, info, instrument, trace, warn}; #[derive(Debug, Deserialize)] -#[allow(dead_code)] // REMOVE SOON pub struct TrackerResponse { pub interval: usize, #[serde(deserialize_with = "deserialize_peers")] pub peers: Vec, } -/// Event. See @ trackers -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum Event { - Started, - Completed, - Stopped, - Empty, -} - -/// Tracker request. See @ trackers -#[derive(Clone, Debug, Deserialize, Serialize)] -struct TrackerRequest { - ip: Option, - port: u16, - uploaded: u8, - downloaded: u8, - left: Option, - event: Event, - peer_tracker_addr: SocketAddr, -} - -impl TrackerRequest { - pub fn new(peer_tracker_addr: Option) -> TrackerRequest { - TrackerRequest { - ip: None, - port: 6881, - uploaded: 0, - downloaded: 0, - left: None, - event: Event::Stopped, - peer_tracker_addr: peer_tracker_addr - .unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap()), - } - } -} - /// Struct for handling tracker over HTTP /// Interval is set to `u32::MAX` by default. #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index a8b874f8..715dcc33 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -4,9 +4,13 @@ use http::HttpTracker; use rand::random_range; use serde::{ de::{self, Visitor}, - Deserialize, + Deserialize, Serialize, +}; +use std::{ + fmt, + net::{Ipv4Addr, SocketAddr}, + str::FromStr, }; -use std::{fmt, net::SocketAddr}; use tokio::sync::mpsc; use udp::UdpTracker; @@ -23,6 +27,42 @@ pub trait TrackerTrait: Clone { async fn get_peers(&mut self) -> Result>; } +/// Event. See @ trackers +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum Event { + Started, + Completed, + Stopped, + Empty, +} + +/// Tracker request. See @ trackers +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TrackerRequest { + ip: Option, + port: u16, + uploaded: u8, + downloaded: u8, + left: Option, + event: Event, + peer_tracker_addr: SocketAddr, +} + +impl TrackerRequest { + pub fn new(peer_tracker_addr: Option) -> TrackerRequest { + TrackerRequest { + ip: None, + port: 6881, + uploaded: 0, + downloaded: 0, + left: None, + event: Event::Stopped, + peer_tracker_addr: peer_tracker_addr + .unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap()), + } + } +} + /// An Announce URI from a torrent file or magnet URI. /// /// Example: diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index fcb4df23..e8a9d7e1 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -1,17 +1,60 @@ +use std::net::SocketAddr; + +use anyhow::Result; +use async_trait::async_trait; +use futures_util::stream::{SplitSink, SplitStream}; +use futures_util::StreamExt; use tokio::net::TcpStream; -use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; -use tracing::trace; +use tokio_tungstenite::connect_async; +use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; +use tracing::{debug, trace}; + +use crate::hashes::Hash; +use crate::{hashes::InfoHash, peers::Peer}; + +use super::{TrackerRequest, TrackerTrait}; /// Tracker for websockets pub struct WssTracker { uri: String, - pub stream: WebSocketStream>, + info_hash: InfoHash, + read: SplitStream>>, + write: SplitSink>, Message>, + params: TrackerRequest, + peer_id: Hash<20>, + interval: u32, } impl WssTracker { - pub async fn new(&self, uri: String) -> Self { + pub async fn new( + &self, + uri: String, + info_hash: InfoHash, + peer_tracker_addr: Option, + ) -> Self { let (stream, _) = connect_async(&uri).await.unwrap(); + let (write, read) = stream.split(); trace!("Connected to WSS tracker at {}", uri); - WssTracker { uri, stream } + + let mut peer_id_bytes = [0u8; 20]; + rand::fill(&mut peer_id_bytes); + let peer_id = Hash::new(peer_id_bytes); + debug!(peer_id = %peer_id, "Generated peer ID"); + + WssTracker { + uri, + info_hash, + read, + write, + params: TrackerRequest::new(peer_tracker_addr), + peer_id, + interval: u32::MAX, + } } } + +#[async_trait] +impl TrackerTrait for WssTracker { + async fn get_peers(&mut self) -> Result> {} +} From 9c7d3aa7ea9c44aef6286153798ea3306ad46049 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Wed, 7 May 2025 19:35:07 -0400 Subject: [PATCH 08/23] feat: Finished (but not tested) get_peers for WssTracker --- Cargo.lock | 3 +- crates/libtortillas/Cargo.toml | 3 +- crates/libtortillas/src/tracker/wss.rs | 58 ++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 151faf99..62737521 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1835,7 +1835,7 @@ dependencies = [ "anyhow", "async-trait", "bitvec", - "futures-util", + "futures", "hex", "librqbit-utp", "num_enum", @@ -1846,6 +1846,7 @@ dependencies = [ "rustls-pki-types", "serde", "serde_bencode", + "serde_json", "serde_qs", "serde_repr", "sha1", diff --git a/crates/libtortillas/Cargo.toml b/crates/libtortillas/Cargo.toml index 0f9988ae..352b2352 100644 --- a/crates/libtortillas/Cargo.toml +++ b/crates/libtortillas/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2024" [dependencies] +serde_json = "1.0" tokio = { version = "1", features = ["full"] } anyhow = "1.0" serde_bencode = "^0.2.4" @@ -25,7 +26,7 @@ webrtc = "0.12.0" tokio-tungstenite = {version = "0.26.2", features=["rustls-tls-native-roots"]} rustls = { version = "0.23.26" } tokio-rustls = "0.26.2" -futures-util = "0.3.31" +futures = "0.3.31" [dependencies.rustls-native-certs] optional = true diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index e8a9d7e1..3cca2beb 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -1,9 +1,12 @@ -use std::net::SocketAddr; +use std::net::{IpAddr, SocketAddr}; +use std::str::FromStr; use anyhow::Result; use async_trait::async_trait; -use futures_util::stream::{SplitSink, SplitStream}; -use futures_util::StreamExt; +use futures::stream::StreamExt; +use futures::stream::{SplitSink, SplitStream, TryStreamExt}; +use futures::SinkExt; +use serde_json::Value; use tokio::net::TcpStream; use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::Message; @@ -56,5 +59,52 @@ impl WssTracker { #[async_trait] impl TrackerTrait for WssTracker { - async fn get_peers(&mut self) -> Result> {} + /// It should be noted that WebSockets are supposed to communicate in JSON. (This makes our + /// lives very easy though) + async fn get_peers(&mut self) -> Result> { + trace!("Generated request parameters"); + let mut tracker_request_as_json = serde_json::to_string(&self.params).unwrap(); + + // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc"} + tracker_request_as_json.pop(); + let request = format!( + "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\"}}", + tracker_request_as_json, self.info_hash, self.peer_id + ); + + trace!("Request json generated: {}", request); + let message = Message::text(tracker_request_as_json); + + trace!("Sending message to tracker"); + self.write.send(message).await; + + trace!("Recieving message from tracker"); + + // This section of code is completely and utterly scuffed. self.read.collect() refuses to + // work, so this is what we're stuck with for now. + let mut output = "".to_string(); + while let Some(message) = self.read.next().await { + let data = message.unwrap().into_text().unwrap().to_string(); + output += &data; + } + + // Output should be a vec of peers + let res_json = serde_json::from_str(&output); + match res_json.unwrap() { + Value::Array(arr) => { + let mut res = vec![]; + for i in 0..(arr.len()) { + let ip = IpAddr::from_str(arr[i]["ip"].as_str().unwrap()).unwrap(); + + let port = arr[i]["port"].as_u64().unwrap(); + let peer = Peer::new(ip, port.try_into().unwrap()); + res.push(peer); + } + Ok(res) + } + _ => { + panic!("Something went wrong with the peer response"); + } + } + } } From 03a9ce69468af0e299ec94a49f2babc74b19df7a Mon Sep 17 00:00:00 2001 From: kurealnum Date: Thu, 8 May 2025 14:57:20 -0400 Subject: [PATCH 09/23] refactor: Refactor of logic for abstraction --- crates/libtortillas/src/peers/webrtc.rs | 3 +- crates/libtortillas/src/tracker/http.rs | 33 ++------------ crates/libtortillas/src/tracker/mod.rs | 51 ++++++++++++++++++++-- crates/libtortillas/src/tracker/udp.rs | 4 ++ crates/libtortillas/src/tracker/wss.rs | 58 ++++++++++--------------- 5 files changed, 78 insertions(+), 71 deletions(-) diff --git a/crates/libtortillas/src/peers/webrtc.rs b/crates/libtortillas/src/peers/webrtc.rs index d5e29469..194b4b9f 100644 --- a/crates/libtortillas/src/peers/webrtc.rs +++ b/crates/libtortillas/src/peers/webrtc.rs @@ -2,12 +2,11 @@ use anyhow::Result; use async_trait::async_trait; use std::{collections::HashMap, net::IpAddr, str::FromStr, sync::Arc}; use tokio::sync::Mutex; -use tracing::error; use webrtc::{ api::{ interceptor_registry::register_default_interceptors, media_engine::MediaEngine, APIBuilder, }, - data_channel::{data_channel_init::RTCDataChannelInit, RTCDataChannel}, + data_channel::RTCDataChannel, ice_transport::ice_server::RTCIceServer, interceptor::registry::Registry, peer_connection::{ diff --git a/crates/libtortillas/src/tracker/http.rs b/crates/libtortillas/src/tracker/http.rs index 2687b1a8..fccea214 100644 --- a/crates/libtortillas/src/tracker/http.rs +++ b/crates/libtortillas/src/tracker/http.rs @@ -10,11 +10,7 @@ use serde::{ de::{self, Visitor}, Deserialize, Serialize, }; -use std::{ - net::{Ipv4Addr, SocketAddr}, - time::Duration, -}; -use tokio::{sync::mpsc, time::sleep}; +use std::net::{Ipv4Addr, SocketAddr}; use tracing::{debug, error, info, instrument, trace, warn}; @@ -74,31 +70,8 @@ fn urlencode(t: &[u8; 20]) -> String { /// Fetches peers from tracker over HTTP and returns a stream of [Peers](Peer) #[async_trait] impl TrackerTrait for HttpTracker { - async fn stream_peers(&mut self) -> Result>> { - let (tx, rx) = mpsc::channel(100); - let mut tracker = self.clone(); - let tx = tx.clone(); - // no pre‑captured interval – always read the latest value - tokio::spawn(async move { - loop { - let peers = tracker.get_peers().await.unwrap(); - trace!( - "Successfully made request to get peers: {}", - peers.last().unwrap() - ); - - // stop gracefully if the receiver was dropped - if tx.send(peers).await.is_err() { - warn!("Receiver dropped – stopping peer stream"); - break; - } - - // pick up possibly updated interval (never sleep 0s) - let delay = tracker.interval.max(1); - sleep(Duration::from_secs(delay as u64)).await; - } - }); - Ok(rx) + fn get_interval(&self) -> u32 { + self.interval } #[instrument(skip(self))] diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 715dcc33..54bfccb7 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -10,9 +10,12 @@ use std::{ fmt, net::{Ipv4Addr, SocketAddr}, str::FromStr, + time::Duration, }; -use tokio::sync::mpsc; +use tokio::{sync::mpsc, time::sleep}; +use tracing::{trace, warn}; use udp::UdpTracker; +use wss::WssTracker; use crate::{hashes::InfoHash, peers::Peer}; pub mod http; @@ -20,9 +23,41 @@ pub mod udp; pub mod wss; #[async_trait] -pub trait TrackerTrait: Clone { +pub trait TrackerTrait: Clone + 'static { /// Acts as a wrapper function for get_peers. Should be spawned with tokio::spawn. - async fn stream_peers(&mut self) -> Result>>; + async fn stream_peers(&mut self) -> Result>> { + let (tx, rx) = mpsc::channel(100); + + // Not *super* cheap clone, but not awful + let mut tracker = self.clone(); + // Very cheap clone + let interval = self.get_interval().clone(); + + let tx = tx.clone(); + // no pre‑captured interval – always read the latest value + tokio::spawn(async move { + loop { + let peers = tracker.get_peers().await.unwrap(); + trace!( + "Successfully made request to get peers: {}", + peers.last().unwrap() + ); + + // stop gracefully if the receiver was dropped + if tx.send(peers).await.is_err() { + warn!("Receiver dropped – stopping peer stream"); + break; + } + + // pick up possibly updated interval (never sleep 0s) + let delay = interval.max(1); + sleep(Duration::from_secs(delay as u64)).await; + } + }); + Ok(rx) + } + + fn get_interval(&self) -> u32; async fn get_peers(&mut self) -> Result>; } @@ -98,7 +133,15 @@ impl Tracker { Ok(tracker.get_peers().await.unwrap()) } - Tracker::Websocket(_) => todo!(), + Tracker::Websocket(uri) => { + let port: u16 = random_range(1024..65535); + let mut tracker = WssTracker::new( + uri.clone(), + info_hash, + Some(SocketAddr::from(([0, 0, 0, 0], port))), + ); + Ok(tracker.get_peers().await.unwrap()) + } } } diff --git a/crates/libtortillas/src/tracker/udp.rs b/crates/libtortillas/src/tracker/udp.rs index 12f15b38..f9c17bbe 100644 --- a/crates/libtortillas/src/tracker/udp.rs +++ b/crates/libtortillas/src/tracker/udp.rs @@ -492,6 +492,10 @@ impl UdpTracker { #[async_trait] impl TrackerTrait for UdpTracker { + fn get_interval(&self) -> u32 { + self.interval + } + async fn stream_peers(&mut self) -> anyhow::Result>> { let (tx, rx) = mpsc::channel(100); diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 3cca2beb..1ee221ba 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -4,13 +4,10 @@ use std::str::FromStr; use anyhow::Result; use async_trait::async_trait; use futures::stream::StreamExt; -use futures::stream::{SplitSink, SplitStream, TryStreamExt}; use futures::SinkExt; use serde_json::Value; -use tokio::net::TcpStream; use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::Message; -use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; use tracing::{debug, trace}; use crate::hashes::Hash; @@ -19,27 +16,17 @@ use crate::{hashes::InfoHash, peers::Peer}; use super::{TrackerRequest, TrackerTrait}; /// Tracker for websockets +#[derive(Clone)] pub struct WssTracker { uri: String, info_hash: InfoHash, - read: SplitStream>>, - write: SplitSink>, Message>, params: TrackerRequest, peer_id: Hash<20>, interval: u32, } impl WssTracker { - pub async fn new( - &self, - uri: String, - info_hash: InfoHash, - peer_tracker_addr: Option, - ) -> Self { - let (stream, _) = connect_async(&uri).await.unwrap(); - let (write, read) = stream.split(); - trace!("Connected to WSS tracker at {}", uri); - + pub fn new(uri: String, info_hash: InfoHash, peer_tracker_addr: Option) -> Self { let mut peer_id_bytes = [0u8; 20]; rand::fill(&mut peer_id_bytes); let peer_id = Hash::new(peer_id_bytes); @@ -48,8 +35,6 @@ impl WssTracker { WssTracker { uri, info_hash, - read, - write, params: TrackerRequest::new(peer_tracker_addr), peer_id, interval: u32::MAX, @@ -62,6 +47,10 @@ impl TrackerTrait for WssTracker { /// It should be noted that WebSockets are supposed to communicate in JSON. (This makes our /// lives very easy though) async fn get_peers(&mut self) -> Result> { + let (stream, _) = connect_async(&self.uri).await.unwrap(); + let (mut write, mut read) = stream.split(); + trace!("Connected to WSS tracker at {}", self.uri); + trace!("Generated request parameters"); let mut tracker_request_as_json = serde_json::to_string(&self.params).unwrap(); @@ -76,35 +65,34 @@ impl TrackerTrait for WssTracker { let message = Message::text(tracker_request_as_json); trace!("Sending message to tracker"); - self.write.send(message).await; + write.send(message).await; trace!("Recieving message from tracker"); // This section of code is completely and utterly scuffed. self.read.collect() refuses to // work, so this is what we're stuck with for now. let mut output = "".to_string(); - while let Some(message) = self.read.next().await { + while let Some(message) = read.next().await { let data = message.unwrap().into_text().unwrap().to_string(); output += &data; } // Output should be a vec of peers - let res_json = serde_json::from_str(&output); - match res_json.unwrap() { - Value::Array(arr) => { - let mut res = vec![]; - for i in 0..(arr.len()) { - let ip = IpAddr::from_str(arr[i]["ip"].as_str().unwrap()).unwrap(); - - let port = arr[i]["port"].as_u64().unwrap(); - let peer = Peer::new(ip, port.try_into().unwrap()); - res.push(peer); - } - Ok(res) - } - _ => { - panic!("Something went wrong with the peer response"); - } + let res_json: Value = serde_json::from_str(&output).unwrap(); + + let arr = res_json.as_array().unwrap(); + let mut res = vec![]; + for i in 0..(arr.len()) { + let ip = IpAddr::from_str(arr[i]["ip"].as_str().unwrap()).unwrap(); + + let port = arr[i]["port"].as_u64().unwrap(); + let peer = Peer::new(ip, port.try_into().unwrap()); + res.push(peer); } + Ok(res) + } + + fn get_interval(&self) -> u32 { + self.interval } } From 7f5979011515fe7294af732a3892db15410b0321 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Fri, 9 May 2025 15:01:17 -0400 Subject: [PATCH 10/23] feat: Succesfully contacted tracker with non-secure websocket (added test) --- Cargo.lock | 1 + crates/libtortillas/Cargo.toml | 1 + crates/libtortillas/src/peers/webrtc.rs | 23 +++--- crates/libtortillas/src/tracker/mod.rs | 3 +- crates/libtortillas/src/tracker/wss.rs | 100 +++++++++++++++++++++--- 5 files changed, 103 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62737521..2beefa59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1836,6 +1836,7 @@ dependencies = [ "async-trait", "bitvec", "futures", + "futures-channel", "hex", "librqbit-utp", "num_enum", diff --git a/crates/libtortillas/Cargo.toml b/crates/libtortillas/Cargo.toml index 352b2352..9dac4140 100644 --- a/crates/libtortillas/Cargo.toml +++ b/crates/libtortillas/Cargo.toml @@ -27,6 +27,7 @@ tokio-tungstenite = {version = "0.26.2", features=["rustls-tls-native-roots"]} rustls = { version = "0.23.26" } tokio-rustls = "0.26.2" futures = "0.3.31" +futures-channel = "0.3.31" [dependencies.rustls-native-certs] optional = true diff --git a/crates/libtortillas/src/peers/webrtc.rs b/crates/libtortillas/src/peers/webrtc.rs index 194b4b9f..acfbc20f 100644 --- a/crates/libtortillas/src/peers/webrtc.rs +++ b/crates/libtortillas/src/peers/webrtc.rs @@ -87,17 +87,18 @@ impl TransportProtocol for WebRTCProtocol { // .map_err(|e| error!("Failed to create data channel!")) // .unwrap(); - let offer_options = Some(RTCOfferOptions { - voice_activity_detection: false, - ice_restart: true, - }); - - let sdp_description = self.connection.create_offer(offer_options).await.unwrap(); - self - .connection - .set_local_description(sdp_description) - .await - .unwrap(); + // let offer_options = Some(RTCOfferOptions { + // voice_activity_detection: false, + // ice_restart: true, + // }); + // + // let sdp_description = self.connection.create_offer(offer_options).await.unwrap(); + // self + // .connection + // .set_local_description(sdp_description) + // .await + // .unwrap(); + Ok(PeerKey::new(IpAddr::from_str("192.168.1.1").unwrap(), 1234)) } async fn send_data(&mut self, to: PeerKey, data: Vec) -> Result<(), PeerTransportError> { Ok(()) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 54bfccb7..a2594498 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -31,7 +31,7 @@ pub trait TrackerTrait: Clone + 'static { // Not *super* cheap clone, but not awful let mut tracker = self.clone(); // Very cheap clone - let interval = self.get_interval().clone(); + let interval = self.get_interval(); let tx = tx.clone(); // no pre‑captured interval – always read the latest value @@ -64,6 +64,7 @@ pub trait TrackerTrait: Clone + 'static { /// Event. See @ trackers #[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] pub enum Event { Started, Completed, diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 1ee221ba..339003eb 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -8,7 +8,7 @@ use futures::SinkExt; use serde_json::Value; use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::Message; -use tracing::{debug, trace}; +use tracing::{debug, error, trace}; use crate::hashes::Hash; use crate::{hashes::InfoHash, peers::Peer}; @@ -47,7 +47,12 @@ impl TrackerTrait for WssTracker { /// It should be noted that WebSockets are supposed to communicate in JSON. (This makes our /// lives very easy though) async fn get_peers(&mut self) -> Result> { - let (stream, _) = connect_async(&self.uri).await.unwrap(); + let (stream, _) = connect_async(&self.uri) + .await + .map_err(|e| { + error!("Error connecting to peer: {}", e); + }) + .unwrap(); let (mut write, mut read) = stream.split(); trace!("Connected to WSS tracker at {}", self.uri); @@ -57,35 +62,58 @@ impl TrackerTrait for WssTracker { // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc"} tracker_request_as_json.pop(); let request = format!( - "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\"}}", + "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\"}}", tracker_request_as_json, self.info_hash, self.peer_id ); trace!("Request json generated: {}", request); - let message = Message::text(tracker_request_as_json); + let message = Message::from(request); trace!("Sending message to tracker"); - write.send(message).await; + write + .send(message) + .await + .map_err(|e| { + error!("Error sending message: {e}"); + }) + .unwrap(); + write + .flush() + .await + .map_err(|e| { + error!("Error sending message: {e}"); + }) + .unwrap(); trace!("Recieving message from tracker"); // This section of code is completely and utterly scuffed. self.read.collect() refuses to // work, so this is what we're stuck with for now. - let mut output = "".to_string(); - while let Some(message) = read.next().await { - let data = message.unwrap().into_text().unwrap().to_string(); - output += &data; - } + let output = read + .next() + .await + .unwrap() + .unwrap() + .into_text() + .unwrap() + .to_string(); + + trace!("Message recieved: {}", output); // Output should be a vec of peers let res_json: Value = serde_json::from_str(&output).unwrap(); + let json = res_json.as_object().unwrap(); + if json.contains_key("failure reason") { + panic!("Error: {}", json.get("failure reason").unwrap()); + } + let arr = res_json.as_array().unwrap(); let mut res = vec![]; - for i in 0..(arr.len()) { - let ip = IpAddr::from_str(arr[i]["ip"].as_str().unwrap()).unwrap(); + for peer in arr { + let ip = IpAddr::from_str(peer["ip"].as_str().unwrap()).unwrap(); - let port = arr[i]["port"].as_u64().unwrap(); + let port = peer["port"].as_u64().unwrap(); let peer = Peer::new(ip, port.try_into().unwrap()); res.push(peer); } @@ -96,3 +124,49 @@ impl TrackerTrait for WssTracker { self.interval } } + +#[cfg(test)] +mod tests { + + use crate::tracker::TrackerTrait; + use tracing_test::traced_test; + + use crate::{ + parser::{MagnetUri, MetaInfo}, + tracker::wss::WssTracker, + }; + + // Support for WSS trackers still needs to be tested + #[tokio::test] + #[traced_test] + async fn test_get_peers_with_ws_tracker() { + let path = std::env::current_dir() + .unwrap() + .join("tests/magneturis/zenshuu.txt"); + let contents = tokio::fs::read_to_string(path).await.unwrap(); + let metainfo = MagnetUri::parse(contents).await.unwrap(); + match metainfo { + MetaInfo::MagnetUri(magnet) => { + let info_hash = magnet.info_hash(); + // From + let uri = "ws://tracker.files.fm:7072/announce".into(); + + let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None); + + // Make request + let res = WssTracker::get_peers(&mut wss_tracker) + .await + .expect("Issue when unwrapping result of get_peers"); + + // Spawn a task to re-fetch the latest list of peers at a given interval + // let mut rx = wss_tracker.stream_peers().await.unwrap(); + // + // let peers = rx.recv().await.unwrap(); + // + // let peer = &peers[0]; + // assert!(peer.ip.is_ipv4()); + } + _ => panic!("Expected Torrent"), + } + } +} From 6e237a81fc4d11eaf1dcf12dad3bdd0341759852 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Sat, 10 May 2025 09:29:34 -0400 Subject: [PATCH 11/23] chore: Added new torrent file --- .../libtortillas/tests/torrents/sintel.torrent | Bin 0 -> 20792 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 crates/libtortillas/tests/torrents/sintel.torrent diff --git a/crates/libtortillas/tests/torrents/sintel.torrent b/crates/libtortillas/tests/torrents/sintel.torrent new file mode 100644 index 0000000000000000000000000000000000000000..c3775deb9e2bba4d38805d99bf995fa1168505ca GIT binary patch literal 20792 zcmb5VQy&NVwr$(CZQHhOow9A)wmql2r+a$-uP7uX$Fp*}IXV4r zWg9x%IXeC`qILK1{3pTqU%LNW5^g5{3o^2^`IiIBe}PO5oScpRJ+!u-CMIm0{~()x zivQ|XGci>DAM*Iz=FZOc|IPS+Q?sz+WuWIYax^h;HZjIG^!Wc4!Sp{OEbMIYjsFE& zFfen_b1<;7{u>^4P7_)|6GqC+{#nAuHVr=Z0|KFAT8=(KF*w*?#6j_-556<@g z2abi|KlSYYBSS`dhW~?}qsxEwWoBhyVEJEy^qlr~PR{?-lVLJ76<72ZWCG1iVl$a0f`-ouNP+u4?C)$hx|?f$4S zDe$?j{hG-2{+Sa^SOtP`_p&(=oOTV*ue#9Sb>I$=w8U(%rt&92*Ph+ZT}^~l+A6Bv zex1UHNZ~DncYDv>LZd;gY}Vo@mz7l`MqB&*6Ef8&fmX*8?c+dO41wjc6Rs0<$cQKwRQ%TX@A(oPC-YDmwo#P!9yOtOf7m7fj=ux;K~aK$@B$M2RY> zYE?oJ{?AjJ-q~l=5cpzADri{n(B~=$Pl1R1D}@SO{`Y58D^SheB^6d3VlU@eR*o+$ zgneIXvGxeqbzs%%$aCoc{p|eRCEfV+kd`KrpAUVS&YYDe%E2!2U9bdXL@ml?(rbw8 z-i*^|gS^Y11M*}e68gN4b*N{XmjIrITqy;Ya%ITj&(ctSJh~VO()NjpEX_yQ@!C2H zRF*`t`vxA($8@>zs^aZ;>A#opZ}*DE07}2a)exuW0i-CMx}Hk;ZGOo@tq!l{Bj2yq z(oR=sBE>YIgbe`^49L(-XxHxjX2tU&QPdB}hC^}ufs z;#l-#$xP*l;OGkG!1fPs&3u`n=~S-Z)K+CsJ8@e5km!(rhokM;^~I%&gAy1+lUgv2 zlW~!%6?!6@@ow>yW^}vhqk&CINRVMGnR(7u;xBCVVtG2_fhUbAgqXdE%Hah8W-d?m zeAL)TbZHoEvB;MZNeay|sNwZEyK4}Tw1_8D^9W2ew9&DH)DdzYF~lHl#taCvSPOHV znQ|(0G<-?Gz z-MpAIBLoq?SERo@(xXk&o{~z6r>)D=zz9`6x2KM#o3F$M4wk(>{USG;v#`0O8Tor* zV9ky9DZPD5!6wj*Hx(PI>>qW?7&A?ck_$F_2q{>VL$Tqzf@hcXz*CKRJOA{w@R$`p@2Fy9u~wflPVoNk~9ZsW8%? zmd$iBCz*bXwxLEZ?T>u1>_p^Y4~Ut9$b`pX_v^?E!{=Wtko(swkh;Bmd%}w^Uk)+q z08T%JLchwW_r`>-!#8S?^XpMhg~2_OjB=d@{I!^}xyNBcRl8M(cYNLR(0Gta@+pV& zN?cy4&bC@o>@J&=G4Npkg5;bfFTV0bX~?0IfuoDc+B;}c&X3UgRvdpyp@pP3fC;+?1BZ* z)Td~OC@0*{i4v^Gb;mY8$>Vi17JG~f(^4RX*!E%f+3`_Oz+b4Bm+Mcc#qeB2!O%1- zmLgjQUKCg&0Xc2X1E8u$d!JEyW2E}Xi=A1eA@z&zWR&68%0gNRq+m#yT|IrmtEpi? zXMLVjK-dcNmD{u&a=>qH;N#;!Cq`SS_XqUOEJ7LqKD~zq!jyz@ca<&c?vP|W^Kcs> zpUrAlkWDABJD$6E%&!32cm*!Uc#bpsl|qW*W&0C%C+v+Lrj&I$vdEQuy%H8d{=`Dh zZ5_j zVE7Uo+fhvRcTQU%!3ez*WA*;Z?DuM*uw_CAYx<|r-y_I``(TrQ zNK&mxWEx7Z(A*EX-3?eh@VnMnVlK9iAYm7^V*gv^NPVJzSr~e&<~Wa3rWTn5?G1r} z;F4|_=hP>rwGNBlD&N9o)=9~s1Uz!r6n`iWz30&LMNeDCwIc(6aBGplZbFQ{W|+L| z*?r~1Ft+wbwnWO5VhxCtskeh6*`u{cw5dfcjv9|J2i-#qlR56_~IYMIdzT-jr|U8nLu)7Fz9oMIQ*}QGi8to5}bKJ(ZU5xg%AmwjLVzv1OW0l zXQ0P1*rb2UUT}fH@!~PEN6+KALP`U!pJh={sDST~>8cr7D3>6HqH0mFp#a~lgjVZ26QdR}0AKW!ePW6S&m&p`%SVXC zkvjo)eK%us04Kw7btS-a8SQX=wnq)_yPVqVVcOA>jf_dV_9~Hr9|;UyfQVAA`M4)f zDHf<_f_O5`LIFsIlqzj|A+<~}S32H?iHCA^;(hq-I+QC*m_=~Cv%plE^mIKLSR$OU zHjA(rkWY#RjPHa4v~1rONO27Gbd1yfEY*|o+yzdAA`tQFBPgcv!6~DUPtV%H%tC}M zLG-Kn5?^CSi4wK9%Y{q5KH?ijk9(0e6n%ITuc$X|le_$BR@i#?g6p5;+TfKVgk%3d@n!!ikkKWhUU3U*|`e616* zj^Qjp7S`xFO%GW!QiEa~HT$&Z!{aW*UXr&eBnnE{@*L^%=0##R=v-9A)=9??7QqU( zs40ln$$4RsBVNK6OT64L@`uq~H^#eNmkX2p$a3~pRAom3)FnC;R+0~Mx;$<<%46JK zEtw(KLDdgwoKB41k1h{AzebB3ldpBCrcpvElxt71kA*6BT@#ALrdDFAI_7S0+dUSS zLc}K3tOW7UxEYaPfI{!4zF6l-+DH0*Pg-2=R;l2emn;J|*X;yPnqsh@t-3PB-Z%i-Hqw9@b^Hd7Ju>f!R`uh`VCmmQiZu9xp8I32DSV0C*O2fI? zja#*Xg9sU@8xbqT7V36MX7ux0Eodmp$4BrKiU=btXvF7y>N#BR#i>6Zw`|VMWa?y$w@D3bP?#{r&C)lCQ0A+NUKmtf?^pR_WBO{x zW;O#AM}NdR2X!Mm%n|)!z?>l7e4df%tDtjB%mhWpq2uYN6Bz4*k>~gjNspO~d2|gf z{sID$nEE-v2pW4_&ZkKcWU>AIQ`^>tg<3a(=1`1xK}wOeKpDmzCY7 zrlowP=UFfe{52Sf&26VQgCr07*ox;~0^Wba0Hw5eCY1E8f9%LjN*!vO`rcrzG_XHt zc|wP#%gt;8duhN85(FSMhX<>2Xrb#F3t|NVxW5CyFr^!T@G3?JU4GnU0S9GwoiHm% zeexIZE8?XWeB%f1mDB~~FR?vSwa~~yz_Cq1jKLIX*ip?bPMpS+qANG^kn#=t0G8wO zOU`1{vzf*P8BK@_h}Be@GfAL={ugb9fGPGQ@I1wy`BKs#NF;gNasj%`vJab{7DTwR zPOd}31H#WCxnbwg&`UOFl^zHsrNn@i!KY9Ga9@ZWCdIiT9KrCHe7A31$HWNFO};4w3!w3msZ7?@~f zm+(}uTh$3gG$_@dvW=_pjP8&gNbuJ)2zey|S-CmZR4qKK$@kUz6Ni10Up4FJ8P-B~ z`0vEeEvnQZe|jJ8smzX<&T}X=QA{YdU86JR9a#6@)oJWVtRfd-r_}Yw(y<`DqQrjF zR!_|l7>l9>q{CZ^5N>2Vly=3}wNffo&zQw&RA+Nr9~&d(HYKt>EzgXhq@O395LDix z5}cj%O)U!Y7=2aR&22ab6d<JyN|{xDJi-@^N3@4&|0gva{RU4{DW&w~=njcE zca#x`kuz7u6^T(lInLnUkV37u%hg<0*4SZKlngbQevzP|&xlIsF2tC%=$+?n%R zh3>$0S6KTl80rylv##)}N%UKxtOta~8Off7#rVp)2;)Jc!B(fS441QfheIyffSy1R z*BR62hJIsMmRYp*dxXy3)04pd+VkG7PpX4!eE!6=!gD5c{nzeu&!s#Nz!d+eYprlrAYWaAfG|Jw6nBGp(HF&nqri97nq#G3SP>sGm|c^={>`7N z4?zY1n~rf*Wox<2ubc)~$FYZ=V0>7*$kMFd1L6WHR-XX8L_mWJp^ClqS-2{p4kxVW zf7TX!E!66E8A0HLB;qrX2i7ysaQu3$?oL4jY?g`I$P41SUWX@OjNIcF-mi_VYNu)# zEp1r>)Mx`F;4bSHL1ASiUMq4}Cit@te8gD?Z}1U1O>R8B(X`4ohJu zlKQ;}h5Q0_O09h{=3RmTI;nWSfim!FGL7m0bH#PMFcDTNLS*A{sIqXK;Y=m=>~?A5cX{S(L$svY#iYhtks!r+r1kV8C`a!go9y5fZ5oJhXphMW=Ha!>0MfA{dWyFr{$ zE?>V2RpufX>D>CD%ct=PeqzCZtK^gw_Y>Z$qk>3U7eWg(G%U|u~x@G%b3fH z&~YY)OPyTX0E%POH_EWB#cd%Ki*}n>Pb6B&;s%TEuFqQi&OxXY%+3!^@hhl2+BHe~ zdiVx3+Q^Nb^6LX*F0=PfX4L)piErzIX~HXsxv`8qbNq|vbVdjFULs1eweOiaiEWWh z_8O{y>T{c?g(_&oR}ZDpcM<8ORd9U5IW8iGk>?q0O`i9ixy77`aKV%5qz4Uq6On4} z8h~!y>S245RV_5rgL=*Yr#M5$4(`Fuyt;JVfV{Mn#`+4On6!>rlG5yx2fprm!Gefc ze61v?xLdI+eD7l%ONjAEd&&-w10A@gBa&zzQZrt+;Yzb50VMJ=k?_Vg8IJkc@4gYV zGk4k1INuZR)NM+E(fbf|j;yVFgM%g|WBwzh`#?ccn<5s5x0;B1sH*QY1S@K^fdn_) z(J<3oh!~Z2_4>afHRi)xF2)qlu0Ba-BcM0at1kd&JztAYX34y@AzqtHKolHOYn8IF z_#i^bDjPW{c6TOi#ViRyg>Z35I+mj|eMoFD!uo7G_`}(# zC<(~Wgf06O!@N-6F_uiksZ@!tOSEk`sWnja3Gv^>fMMcF{CEjkLV7=oD4_MkH3AJ6 zi3-rhS70o!rOT`r&7v}S$Ubp;7`E2*ts#mM(fci@*&rlUNI%rnG*Wj2Vi(`#l^OkFf;pfZP*qQdW zXlKJQ5)-=)IO9_eYSA3mmFgytTNs%$oAI^ZNZdPrj?^&MTb|H&HCZoz@aB0Vu*u_^ z+8N;+AlAf6V#4Ppq@QMKx6Flw2=QINI%Pa_RNRORzFV?Smv07YwIgdJ9D>YFi+L!S zh8Q@#+TD4QROPUy@UiU7rFq@mZQBGO&GK6o(!atZ0{E&omTo%E*e0IkgvG&YApJCO z!&Zd@SpAB+Rjay?@b>6K8QhPE;a;Q?meY1Ya_jn()EfK55$B)lm*Zwc9;VqiGB|rv z?XK@5{I3!!`sAu4l3WgM=BZYdb0J0!7K6MEu~kJ8QpT0hTYCLQ&-XI`D8wzoQC{xU zR+rC5E)D&~=M#{D4IC49XXem_A@sd{oqJ|u+DS>iIJ3#=xoQ5+QGBcg=L*)3S-MXcb#I zJ|&ag>&Yi>t%frEC<{%!116Fg)aVKrFbW7bC0%+{)19-(^BzC_tRvcBVn%ijVFXqj z^nwy25l*jHz1F{r3xnCXjS+odp)(kdK*RF z7qX^S+*}^m0Afj>nd4*pE$Fq>t+KGQCD3qbtV(@$d|*2lH2 z^x;;QvM4L;Ht&19lIw~WBW#n3@VA`aG)mDw{D;HzkNER)6LX}2I6!{b!+FX6H!rx| zf#xVSOiq=y3Pd;s-f|re{Bjp5qI1az63y=h3qB+%$77oBzg9;u2sGsr=cvNsBxJ>p z#np5{<=8CnUh|4no^X9=JkM9~bJMgfcB9Z5zrzNf=*N{P8gjJiarVJIX?Xh=31s+W zE`G%q<^|6It4;&9WWSBS?0WpmcWWRS6c2MDe>ds&l1P<&ABz>FJy%Z?Lp~hNaL!IA z`ctp&LqqcY7a_Jklycx|H$T|rRNmaj|6ZdpcpL@JLrctz_I%VJ5|SswcE6o4w$uPF z%_#vwGjp+G%CknHA^e+|o}Wb%s3=hxa77ZtHa*w!eyKTngH5L7ZfGVPO|Y4Z;rA83 zlEhC3V+9Wa#;)9o9Rn$oy=u=Rvsy-YCj%(}7|RgODX1p!h% zMB?z%KM#MLC4moi<##Sv+GNlHSAOD~D&Oi}YZckmP1A8|Pp@^N4J=SwQOk;T9?BW| zCnxJI4!F-ta~Y7TE@~}uMKanS35Y1}&2DDWK_Rqtt9D4-yIeW2*JBzjrsIeOIqD-B zc76^a@BtR(vvv_OK=WXm`zp$liYQe@t{Z@ove8=_5H|0#;f?N*w1;!JUa^#A*0Js6 z*(VuoS*W1u-;vef6{>B5>_{vb`8pjjl#}^xTRHA|?WeIMXXnXcMfT+F)3x9u1<^cF#g~OmLcW(f0T9J+0zpk*pIs-5DaOZuZt*yD zg<;~S(#6vRsZnUP4D6`RPj&0&l2R70C)gOh!m2K=#BSc)pcOnqJmXdC>sa3+$cm}9 z6lL!(j=tQq2Z82_xrr)Jqr%bG`j0YS$@^-O+i4JDNIf{3_hpQKSLqi{{X0a$TI?*b zQ~>|3n7YKPDlcMm=M}Nn(?&dp&-&Qv5#V>ybK_AuvoVg&`Exon3bsbUP5r*hp(=f& z{D#8$)8}2Cr`AFU*<&!(K+Fsn_irQCd;l_n#{y(>Z@z!yM2xS3sD`P=Y-OusK0*UQ zWQjsg%L+T)2Bk?j*5lTn(t15;KH^ZKQ$NlZzF1mPu>&VhXGDn`DIa>59Rm=D^i zq=w*ybXHUb1uj|LgUNWV-BfgqTPUP3E`zUpJ#U$@EyHx+iq8_$S*NG4g^dvX^7?L2 zSlHl-Xr;VWpE1{~`w;`QKjD)g?n4HA*@2^aX*jZC?A$1B-VN&^YeZmWb|R)bsjla( zb5?f@7$|R{xr}hh8nT)*xDgA*KaxT8_hR&Yw;fQ8n4LPrQV|&0hTCXmIa!R|`6JBb z%$VOKL~P^g;=Ikji95j=D{UYyKq&4Bv=c;}(V8pipHa+-foXZP^0uHcndXw#IlwHJ_ ztxcevPS90#qlxq*7zgegn46-*=E{0ww~B=8E?#@ElddZnuj>%=2+)A_ZkEg0YmWiP z-F*;iK1QCRpbabP>r?0iXn|$W_qBdO0;nqyQkwJ%&);^{*qTM8Ps!VQHwkg<{^sBn zav?i)v;Q7&Ciy>lgn`L zK%|i}!7zLsud@cpBNfQ=3Nv{4CVSGU>}^f?z8H$Ba;)4V3;ojP;(Xur{cL!%WICHTp;o|Pn9SI0eOa^C-tc9 z))f(MnrF>3_I$8KYcKuY^invGAKwIcL;dNrYBs!4BK-RaLC@1s5t4Y8HK3)ofob5} zWBS|=lR8@7#FZPleIK!}5nkKb!g1+5-_L+?++93*EaBC!C@l!Vh}8B!M?0j<*xYvs zEeGTjWy}iP3yI^4mXIHXqw@rkPYlXTcfi3ho7_uA2mmR*m^%GpIwcG@G^r;;(VE3tK&9%OsPaMKb}+$m z@(Xl~A`ImVjn}fV$k^SU02;x&7K}`{(L#@sc_x?PCRDMUz#bd|$jW(Sw-t^mjW{Z?z3#zm?p|RuJ>!r&^q!XcuH`vKCbNJ z3d3x2JZpmMyoO<;Cqh7yG6{Nm%)Nx9FojR4O{EEl}+jc>M^A*AvNq z>J*)vEF>4ho65j`AKo{$6ipYhhIve@bo-wCMJomV84U2&@kT}}fPB7Vq>t7&hghP# zJVF#ljLuOEI^|Rr*(JKGy-t#Om<0r2V_5Nukma6?_yr(pZyp@L4FW(*rYS#fd6NmIVuVzRdXAM1T#aSrghzGNO_>Ms$iZ8mz$q9dnfJ$;QINL~zsS1{fl6O_U_5Qk z_-VOV{N@=?i+{jeC*Me8Bddv)D@QG^J#9F4Y!@mle3VIKk$+to4W*2*rv=kmh9Ewo zX-y{T2pMxmX+u+f%#-+g>j~B*9xXry>v)t`pT6k}m~1^<$3ZZi#t~tlTRhZ}Jx}Np z;2`tknhjrN+83c?jelmAHPFn%rACls4);Z8Y@<4hC9PN9tL!|9tp>5UHF@-{vS zs6Fj-_;iqqOOXk;!EKX=fv$ITBYeOeF9})?Dh3;-z3WQ{Efa5GCIM~FR$dL%gWbTo zsVEv<0Z2D}Vpe~n=k~{gMm=msWO9^}ls|mS8*Y&dAn$gQE;?448&%*T9Be zsZK;BPN*l7HJd5cm+GnB(Ixgt9zwI0u9mgp?j@>{Wtcz70eASV-j2_JRJQ&~lsM#< ze_)Z$-^v2j8A5L48S=U4Q5>La7l)6muGz0i46|{Q;dpy?$sngY*+ik(FKP!GE}t)3 zCj%{lGl&!(8ZJ74%$+1|+5Pze`^LdrQ;2aCC!(`Lo$yPvPSeoEc2BOI@(i=W0xIvpOef~Qt+ zyc416SuJaAl-tish29Lg|8Y4IUQo7toi(jmhOzGq`1>X5(NoXxAzOy0Lu29oOj7hw z9PSmQ_trM8ycyr(1*ox}lOTm6-~R)(4WbjenkOon7KD%N>K-}AXx3ikIy`sIn%#8| zg{bxN*$rQtw0OFB0hPFs@&*o~K@Qi_9^lVRXoQEn+}^hhTR6s&`e~j=V)B-y`i%xe zwrMSWr@R6ClF+Pp<={-qL21!uU|!3nlO|R$4vbZ;MSjD^BlqyvX`onmj3T082&NXM zw5%G0#xA7^sR<}Hz1T8^O^`kxF=?kbHTn=Jbt-(=Z)1MxgTa#AA5+GROZ>^de0YG+9#hw?De^;;j8Ecn$jvdTw4HuY4ea*k z;2uXu?CJaNQnVHL+0kNZuKI>bZP^+A*FgHA#V9>Mvt5z&quXF(p@=;N0Sw; zK*Zs0FzF`H2f>pBNKS|Lo-vLa^)TVX#8|!6M9jO&oar?f$h7eoKsG9=6be7X1XhET zn9=SQND~l^mU|AafoGyb$L>SmS&O)9ki{{7c3v0PlEa|h`y*;kxpt1caoWHq@RSFw ztn3pa>Z-N)368-wbH{i1s{NjWaEA)SopW^y$_#6WcxJtlk^zh&w&VkwJ71(gOHFO; zHFrv`qA`VxTCM%kSXP4d1U2NMS)T@Ch7cwkb&_YXg0%I0;MRS{Rn-}3=uNNUHIn(A zBYw|hx6SDM=-k$3(g!5|=?sma$`=r!;fw~H{NOx=CX(Eio`r-}fZz!g0-b7onz)YEa!+TtW7c>vjtCqOtGapO zeZ`Xqm&ifb+`_=!szate-pI?!q{xkMg7T=OJO}Z!Eq_WtTe7Mfe`X5duS^Oo$632i z1i69wtw2=7JLv*uTv2gMp+*Q-@DTP~T{1!{Lp-j4iC&;L1DMm_70tZlB*beNKdsu{ zZ6*{1hGVpq)=d)a^X`12dn@tDSNvHD>?Zr&o|+SVj>vi4!h9n%4`Liw<*R-(%Ox_<8%1cCh;)Fy#(vH)nfxUzD4cGy^QHzRck%Fkt&xToj)!% zc4iy7K9486{$7%(!Zo>=sHp78qTG~<*-MPY_Z**g@3VI;5uNe~RXNkiAwrY8DMF+` z>TCWHrRh>!6hLED81LK+UgQQycbwP(*?vGW<0k!{p3NEd7XIzB&;p2g=n`%QR>+&96ejeV9DSR5!%p4^q(QKX69rBo}IS~57Ef>(lYcnOsN@&FY zHF9|++*dR-;x!iYn>$Xh`lNb6)m%R`Z}3SZd$acfhhmbT%<8+TT{G6e=v1kkK5(Ju z%PGmMtz2~@Wpn>THHLqYB!rB~^S_3GYhNCE7?O40=(j#7PZInr#{?$hA#*Y8I^GaP z!Zh`RlGeTYP`T=6(RcUfPWJDyMLBcKiHH+o;5XcX)a3OuLUWbF35>fNOVX31KL-*S z^U*Yl2hE%guM>^^h4L?~vn^d|*GzttIGIv$doGZDShCKUH454<1?P%T(o^AJxFtP2 zn`*r;rhjyR3;npgQ*5s!ru1%By}T?n=oLKFKbnLI7J@#tMIQRvX!!%D&wq_gp)(LjUl2Ruy{yCs2z4pwt{bK}?4%0-STPLkim3}Q)TIfH+7=^;P`MiMiH>Tf35qMB6jIN^J=Cgg)c z(=6_PF=}oXUEE?l_4dK^B{VxsgnwnpJLmN^AK$-#Y2o}w(A#L3r~XfcHNNt5TO%t4 zo+;|($;-wHc}EmbtX4G|!TcPecb2aQDyIO3_$?J8yExa~2&Fz*a^eK8Zm5uZzWW>! z&0NmaJ!nLvK}p}GtMka*sbD5`$u7wTaXVgPolgCkAm#wHL0ggO4@lHYHRex}clx&5 z!T1oI*QKZl0l!@VuHh0fQ=V(ju-q|@YuIP!Iq+Wl`I@H^Iz}l!)0P;VOwZUzAFS5O zMT2DP;8-Q-7@E^#G?~6-?9F?h75182X}mCwQs*3xc&I_f+$D61`H_m&?FlaO`FX^$ z4rVAy+aw;7TC=HU?mN9V=cHP zKllqJalJ%jNx4Nxyrv((>QaO9PwUekGJ$|}5|HLw z7;>Xruq6v0>AYq9^v6 z3gBO5UxC5(j>)YwiEhkY4>p9$N7;@WNGgVp_xP7l#Q-$S(SSuf2h4;r`GzOn3#(9= zm`M?mUqEtX@lRp%@TnSYAV+)&{Rv#cZY#yO3g}N!n{Xe=pec*x#)L2czR`%k3$QGl ze}^~Gsw|Ig{f+B)k(g<6on8#1l$%9*z(@G2=6{T}Zm_*oco3%8s(u$t8UjAQt-?Bc zm~!P-@PdLP**Df@Q8EN4l_cd@x1&Xv=+m5mvIZt)N#but_a?qz%?D0%$(s*o;%QHW zc0XrcXwsLX4AzI4jXY9T#S2j4afP1?U2#>6|JLJs6H3U@X4Fr;aT>k=H{@d!#KeSs2eBa4&qZ|35v1|kjrW~MpJ*b_b z-_7LCXJN`Zfcz<~af~%ZHCWwD{%RKC_&awVL!su$MUrA*MH9rC{)x+hiU8>V{>%h& zEZfS_LZOl;5$8~$i)(QT^@udsF6X}!t7B-4M~|Yu_k86Yv^7C4mLftYiZ!*SCCjtB zH5mXvEuKv7Q%TRn=l*nWi=|eu_ zC28x*9~!4z^Hm_+?C40M$A!>toJpk@&phc4#4_6OM-@t6hHO-&`w_8PHjukgiC(7a zO*snZj7^+pwOM*nwt`#1e~S2o!CZTQ+m5jo-7H{yd2^|37(fR`IQnL^d`Y79EU@&7X>9fH9BKORUhdJa z*o4l+BK6=U1+OFFF+sbNM3w$Q2DwJnpN7HklFxUD7+~6WF6E%BUV+=ET7H3@9RK8! z3OwSjGEy&2ycK1yMdw;T6W*S>nHsOxRazidI8C z1r?DjzFM+*BEe7G?L5_c@{!Y0IuL|J*+J+MC>U}0eJ74a*!$^_kDT2TS(;@X@H`-$I=5WLxt zytK~?`jzaS`o^GmSi-63N&zec@^`Wg_;h~yrH=fsLFJviw~zkAL#%P`K9s!F4hSN=Bpnx=$SH%Z87~zqT1?r*~9u{yqif5>69}S zjF_<-s{_&bNF=_}4czjg`Deeu@PVXk~|yZ4=_OBu0Q)!wo3le;kKy7)kHBVnMwgzn6XwT8kG3E|`)Y zDsgRrdAcP!y%xHG`d#U+p|*Yyj5J?<&GDln*rxoX$I|1XEdiiTg3Ju~?!0-VVpDi#i`n)R<_0!@oju_H{W+buXbt*9O%CPb*of9ooLm*_;+?YHoCxRH5f^p zQ;8s0-t5Wr8)^iQR3Bt5<^GeVuoY=l&?IzZs$Z&&2hMt6&Dqb$(L$^rcB3E(qth@0 zv%%PNXP@)ZF--TX(}h;$1Z6mJNyrw#7wey(MA~h_TcpvgZK{Y`oz&JME-_6l%$Q2Lf$0 z=JLDY(K{>a;;qOO!)!F*Y>HUSw8@>is1jj>^Or8VFk>(3C8BM@C+*Lr7|anY_R!K8 z&D=!V@Rc)CKVGe^640wNtPC>w+^X|h_-~K>YuWh5|%uqP;B`*Yd9(#Wf zK@dFmB4P8yn>EDaNJp|yQH^3?zsNj7BqieYSHmidL)$X_QLhxO-a~Yv;ltTLP~E?e zxGdeRFEbV~J#VZV`)2j(n*(*qW7Y7Nrb{+qdoM&)F3}_Nay;<5&X8~p1NEbil;06P z@)h-t%DZBiQeO*(v-!4gjJl7~<)64drs;&0s-NqogY*^X?OhM$5qcUPJwz;)^TB-5 zL)`OGlq(@koI|+)>&jw@a5h!J3Aqc!OV1R*gWG7JH%OaoOau-Ki(4@ENPc{T%&Sh8 z`6N?rELeXr(u2W+vW~=sz_(1ScCQLsJTPjfKP9g~Tq?E87p5Mvh&S6xhTID6Q)G_h zXxRfZq^u4#OsHP61iBc7&wv&X2!$W+SmT7RbEWLTVop&?!1G=`o)?{_z!|6@f7YH~ z$^ajC(q~98%{nQpWJ&r6URtHrgOX=VDuRa1otK>hvs>%@lT3ebKMITR+;;6KvY8bR zE8(uy3W&Jut*r{ZBGE%cJaU%MSliOU z!D-DzZp5b!1z#%5a{-4 z|H%@=HRnw*#d$vOn%Z$}Q0I*9qx+Z#HmCSRF8{3MHZS&CiA2d}5RXhg>BppEV%X3w zhAUK2GL^M)Y~|O-ju+hG+AFKErHJVi%=-7s+fF0NGN4;pcK$gfQ5N{|%AL0z*uHnR zCVoAbpn&^D0aCwHs-N*Ju9uBPX#9(xYL?{Xc*J7U%SBPVo*~L4j!O(BX1#+=YNF1y zqPIP*1A04Osq2u9G;4wqwn=;4xNf<*$e+{(`rjmt3f;h}ry z3v>Bg=+wza<|)mOkLW8EAIvcbl#~VAlY0f*_9L0fKKFNJp0vC^oQ|{1Wios+R!B70 z%B1K)HYFs^5)#R6HCn>Tg+Wwvp_7dof{|VdNK!gT8t6i=3`FdXKbfE(z#mO3S4^Ji zNnHxV#p6TfHC;W-0_9gxD$vYH_4Hi_AN(OUF&7%OQmWkr=#-?W?E~Y-F1Sh}Sa(pL z17ee`y6fDGo)#@)#%8u{0~YvMyaI2w@c;>OJ3#0gSeZ5H_gRWJ$w|y(g`l(igDr9E z`Z}~1g`sWFJDe(u=F@kgi0bycrzTt+9g}Bfuv325DB$8Gu-%0bWSKiB43X2rU{WxD z=(LRU?fWVHzqN7?fdr#l2 zJk$^~M7-W?VnZ{438?32jcGxR4Uz(9%%ZtC$SS3szj`inscS>2cR@BXglptXME==3RK_b6hX@^A6UF3b+$)1tqn#2@;%^3x;tP8H46$B9KM78FjQ8I(4(pisZL`-{G3ahK(pYb%Hz6(`6AJ@%bh;*aSQwvM zhhW2pADs`nhsxd>ql4T{3N>V2!30?ob{)maK$ydc?Y~W(p1_v57UA zEZ7$S9w=}K`)^|$%-_b$BzQuHjpq0ULUB7vbhGv@+J}hAE&xz8bu@C;^y0UHL9Pfd z4T$8|R!FssVbJ>l-ykk?udxWM`xg?17xT$u#HkN*Pq`|pj5-wy83Wvp=A4q%5r0go zGOQ~RY0}eE%D{bBJpizoOzQj2vbZU&A-<(#K|SvT=XyG^EfULeu#TRTOozFyfGLH2Z3-=Zb@A?c`fV!W%4lSJt}4ZgKdsAxxM#^{`8`?V z{iW)-pWdm--vNqi*09SvOs-KO+EG0<+EW1?VB5^l!op!HR)5QsyWteH zs#4q;L)Vb$s@v@8EV*x5^VZ+dfm23E8=lhUvB_c#j+Z%^*RleV;UOg_`aare2XV2z zx?&6PM&c(xeXW0tWy>R@1aq05A!adI&9A zdFFB|#%WUL3Fe|$L!5t|xj@@Qa`LBz$q<1oekVaZKolKLJL)_W zrxtL?qH4&L5-FPP4{Gpd#l5y|Wb)PAK1>4X2-#A7DkFm%Wq8$E2VS%I+K!6DI~RRs zbKjEqkopJo(;aY)psrClxTLse7hb0LPCkx>R@bI}G{Y)qhq_WOstol5(}LrV1J}h6 zr%o*>%HX97ri%L&O1@RN2aerTzpca=?Ta$qjlK()w#b%}4Z@5uvm@ZP_sGZ?!Ds+g zUv#pJdjWk{=FMV+mSgB@Q2^2Ud6@@?dvj`6YRr#?=X3>z3q=d%b~ulHyVk%|RzOXV ze}6pcy2Pmy7{IYq6W|UG^ZwXytI*4?_fr;MO6k-86~+!Z@$%IV1G2$6k>M%>Z>#Y! ze#Os&pP@(}8V#a!2Z7q78JNen!lD}%I=%#mPe@~qN(WFcepABWFc4@J>ZZ-)EU71F zm;jb#C>;7Y1o;so_Chl_28SKZ0I2h%H*wojnj++`z)#O*uX8)Be$Qy?QdltsRmsbI ziEowYC#(aj=s*Y4qJN)rwV4A|Mq)QtTgqRl_*(eMALAHymamV{Alm{Ri5wn{WVU3s z0ZT4+L1~=(^jA8wLeh3zQV9As+o`XqwG7`0mTUYB;dd#8g{+^c zi2trWR2Xbl;?f*0^qcpAL%fa;fe!xEy86dUYARU4_Q#5|XYW*pihg6dk8<*o@*Y_~ z(!u}0cryk46iC%ZbYb#XrI3X%4*HtA<{v2MI6qf7NGg+~fQPer+l;dEchYw>(TKKa zW}%Qa)g{DCu8cnHq34I{@q(Nkb^P}*YM&{xo0{<$Gc0D!uO0;m5I%gByTGwJM?nz^ z=IhJ`wEK!52rpd|SE2#kr{t%D+ z_uDf6alIxqlxA4O{{GM%SkM1q(7OfSo+H1FvDA(P>@G>Ay@XVaGZjV1V%lVbbsJB+ z=}uXS+aLm+`<#!HgA4ULc_%x3O|-8L4uIiVCi@gETnrHnYxoV`7}eCQ=RXf7tytO? zQ#iZ&-0Ey6?9zoOajcM_H;a4jwndqetjzSg#b!sPEr8AmQ?ywU!9x$giw7-j!;TJ) zyM6_&p5iXQAVDQ}--O*^jlPX~MISh8wv+6BpRys%3 z9UE+SRH+*Pz$Wz>r;WsMXHqM}V27DrCZ$qfBijeGaLE~0$ZnlNtqH$O5?ZU9L|e;R zT9>zpT)4ce1lo$r!JA{vW3IODLuD%dPY2r^4RY%wb9Vtjupv>!%S@vOk(B?vI#*Iu zMe^bHfBcV0`G^hd5JHuReToF&jfZ*7l%bgNlk!>deG(R;8g zf{{0gZdZAChG1t>)JCM@DP1t`#XK^tJOXDdPefEaB!_5(yBotvm80FA5lG==h=}xv zc-|@7nn3^gd>MifV*U}j4`fF9R_4$KdDJ3LIvY=KHf7kX#wZzSyXMM$LGg?SqFfFX&Q!vn9R1bbLmF$K(@ zF>CaX7pJ5+x%+N9l*Ri{SX}D;G<3U2Qf%CV7OABlm9G3>!{M_b1^+~9=aTiTGtSU)Tgq;n!2 zV$W$!a>9qlwYvA7jySY2?Jv5X>KWWUo7z8y>)jaho*w~xg8m-+!+3VR$`rW>yCuzH zPvv9aEXQU6^Q_(-{@=t9j?rcFABNoKAcOh$k2{IYby8POjLoqtiO; zayZp@#!T$V#&V|+OlWQUA({)!Xr#LG_xSJG?0HK6rq{OubdM_1N57UFzgnzS=XFU2&iU{37>rd+YS zV7jq8ftTf{z)j8>BD8PIbi`FF{=h=TD+UKRg&=;or4CF4MNOfr07!@wY%&xL)M(zj zsCmj#ub$cDPil||0?c>AV%LFrldgx4{ROR$E~H&LUiT=h&;_ysk|yx|0O4O+o?ANT5w*CxyB_F#%ng1?79uX9ShNk>(4Q&q4|H3q*NFTrfE|DzDk?%3+$R0%v(0{x!` zI{F-Wclj0Z@5OVpYrKRSG8?%?gVB)LMIw+)SK3EO6pXUIkPnvSQ6m!E;6tepM9464HxL>%)ML;aTM}O&&>2K-+lad^W@965W&f4*Og`G z892c>EXF~&7Hxr|ySWSHpg!kP$@zgGq#us2L8q1pJM+R?_32R& zk9LT5Oi&~}7IZLPd*R6f`0d34_x@~qi3lC@P5Xix&9#r3@mK_TDWoYRGWUwneGXUt z?23YJS<~Mdt|5wju*m*vA7OKf^LS*FVrTZ1Dlwt3Eo;v@FS2NQYs12~ra#u5noc&L z{l_js3HioX@k1Y7eIYD4ss6iHJW;r7Dnk`SgNQ*mc}qsDhVKPV(~!`iUeWWY%e1b} zeE%ohdodOZuv08DNcBdRJ>8AwfMZR>DU4i!Dz_9toKRxxLgM>R62*(3T*jQ~K1{&p ze4>}3{K(jAxb!Q^Iv__1IC`+6d}qBw2=45ZWhtGDYX7OpWA(5<_ed+Jc~nagK=z|d zBZgF|;OcG}BKvlrbv4I`Xaz?sVhEl1`z?_mdF%-0>_GX0QkTp0)g_C z6x};9Wd$ozaYC+t$z9fjnQ~)ND;lw-_95E~6bsonVjaH|F5jC~^s6mcFV7|#od0BM_#^b=P<0A{M|48=I5Oa6>gKm zHTZasqRQWzzNi6ZJK`T23<&G6Ce&e8p6z;i5Dix~-hT6>e*~h$h2!?!E<+j%Q3-5+ zVkpBHr2JzC?FMQ?vO>ve;j+-5ffF2gkUdpt=T!O*LMU!THXR+a4u!VM?vjP6!qVereB8d;nkj>oO08cQn^!P2 zuBLQxz6ZyJ3R}A9TV*GMldivV8{uZ!H$|GR&ZTaGtATfo}(s)}t^`b;bbK5g% z%OgcPMgS`U}UehEV={pl80J``SW%Dbec7S!$ZRQT5a z#Lo2yfh3(6%~3m!Cjruju9>m*GnO0JTki(Ru$1)1#Cg?#3SEu6p1j+w-q@ov)_;7= z=C9!7GJEVNE{tKq{KaN|a_0Y74Hf$XJ_hRQOqON%GwGW9Y$ly-O^bpLxMHd&?<><- zfu)rmt}_Dv3d&m9Hf^T}{_w7dFzD-TP%?vWN9Jsdn71xx*QH+iQd-eU201;)KmX?E z6?ti%SIwN%Gf#ZR`n(13qPPh4x&2?|J|)8edtKAPP$k07Be&f9Fc$oTz0IL$gMK<7=zGTPDrQv>x`pRnV3o+eZW*TO<=U zID~=uJveNa zt$_~xHYd3!tm-0Mim=lX&H0Z$#kZWtB3#FGmd&I6-AxRS;#R%7F?t$WF*tvH^UsUG zyGWoCwe>Fc!5T7U1cuB^QgAPsx=kb?uR)%AKu8|_K(%D6h{u^D*cNks_D*(9rZbf_ zePiAiwKQrDE}DoI(b^3C9vrcEmu=GUqv$oaW3LlV(p{jgWJP%gv z?p}}fDu!}00n%7j9l>>LENyg^$&xDVl&)&Ff**_js{bL~KM>%U;1@P$Fz9dsk}BH3 z^BgOX27PC0IW=eQ-St#E=r=WuyOX33d6CxuK@F@5^dGP-SD=I5JY5+3y~~@0JEd`8 zrsUR#&EfckcRSm|19;&W3E`PD80DQlS^PpO7ZeUtQ@5l#;Rh(}xj^C+b4l{Sce}=L zH)-mXGU_EKklMY6*1rTv%HICZj~7=6hHfh@pd34WlU7Qey`D4bm5|JH zu>ZbnOzoFbpXG%0?R(qC5Kq93iCjIo8gSR=CB%P6mkmRQJmEyCZrTJbXwqR>ueeH6 zFDHK6$YLpLo_<;3_|%mShz!IcW=yIvs(e^wI68H5Y%OeQb98JoF*;~;bZ~PzFE4jx aVsvkEa%FCGE@^KsbZ>HUWo~qHFJ)y`DD-Lo literal 0 HcmV?d00001 From 63bd72ec17968f734ee95744ce0e01c3eb59fc36 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Sat, 10 May 2025 09:30:48 -0400 Subject: [PATCH 12/23] feat: Added support for WSS (instead of only WS) trackers. Tests will fail because given trackers do not have the given info_hash --- Cargo.lock | 9 ++-- crates/libtortillas/Cargo.toml | 2 +- crates/libtortillas/src/tracker/wss.rs | 58 ++++++++++++++++++++------ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2beefa59..7291d2ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3828,11 +3828,9 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", - "rustls", - "rustls-native-certs", - "rustls-pki-types", + "native-tls", "tokio", - "tokio-rustls", + "tokio-native-tls", "tungstenite", ] @@ -4029,9 +4027,8 @@ dependencies = [ "http", "httparse", "log", + "native-tls", "rand 0.9.0", - "rustls", - "rustls-pki-types", "sha1", "thiserror 2.0.12", "utf-8", diff --git a/crates/libtortillas/Cargo.toml b/crates/libtortillas/Cargo.toml index 9dac4140..8ae3310f 100644 --- a/crates/libtortillas/Cargo.toml +++ b/crates/libtortillas/Cargo.toml @@ -23,7 +23,7 @@ async-trait = "0.1.88" librqbit-utp = "0.2.3" bitvec = "1.0.1" webrtc = "0.12.0" -tokio-tungstenite = {version = "0.26.2", features=["rustls-tls-native-roots"]} +tokio-tungstenite = {version = "0.26.2", features=["native-tls"]} rustls = { version = "0.23.26" } tokio-rustls = "0.26.2" futures = "0.3.31" diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 339003eb..dac8d663 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -5,6 +5,8 @@ use anyhow::Result; use async_trait::async_trait; use futures::stream::StreamExt; use futures::SinkExt; +use rustls::crypto::CryptoProvider; +use rustls::SupportedCipherSuite; use serde_json::Value; use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::Message; @@ -47,6 +49,7 @@ impl TrackerTrait for WssTracker { /// It should be noted that WebSockets are supposed to communicate in JSON. (This makes our /// lives very easy though) async fn get_peers(&mut self) -> Result> { + trace!("Attemping connection to WSS tracker: {}", self.uri); let (stream, _) = connect_async(&self.uri) .await .map_err(|e| { @@ -128,27 +131,58 @@ impl TrackerTrait for WssTracker { #[cfg(test)] mod tests { - use crate::tracker::TrackerTrait; + use crate::{parser::TorrentFile, tracker::TrackerTrait}; use tracing_test::traced_test; - use crate::{ - parser::{MagnetUri, MetaInfo}, - tracker::wss::WssTracker, - }; + use crate::{parser::MetaInfo, tracker::wss::WssTracker}; + + // TO TEST: + // Using stream_peers() + + #[tokio::test] + #[traced_test] + async fn test_get_peers_with_wss_tracker() { + let path = std::env::current_dir() + .unwrap() + .join("tests/torrents/big-buck-bunny.torrent"); + + let metainfo = TorrentFile::parse(path).await.unwrap(); + match metainfo { + MetaInfo::Torrent(torrent) => { + let info_hash = torrent.info.hash(); + let uri = "wss://tracker.btorrent.xyz".into(); + + let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None); + + // Make request + let res = WssTracker::get_peers(&mut wss_tracker) + .await + .expect("Issue when unwrapping result of get_peers"); + + // Spawn a task to re-fetch the latest list of peers at a given interval + // let mut rx = wss_tracker.stream_peers().await.unwrap(); + // + // let peers = rx.recv().await.unwrap(); + // + // let peer = &peers[0]; + // assert!(peer.ip.is_ipv4()); + } + _ => panic!("Expected Torrent"), + } + } - // Support for WSS trackers still needs to be tested #[tokio::test] #[traced_test] async fn test_get_peers_with_ws_tracker() { let path = std::env::current_dir() .unwrap() - .join("tests/magneturis/zenshuu.txt"); - let contents = tokio::fs::read_to_string(path).await.unwrap(); - let metainfo = MagnetUri::parse(contents).await.unwrap(); + .join("tests/torrents/big-buck-bunny.torrent"); + + let metainfo = TorrentFile::parse(path).await.unwrap(); match metainfo { - MetaInfo::MagnetUri(magnet) => { - let info_hash = magnet.info_hash(); - // From + MetaInfo::Torrent(torrent) => { + let info_hash = torrent.info.hash(); + // From https://github.com/ngosang/trackerslist/blob/master/trackers_all_ws.txt let uri = "ws://tracker.files.fm:7072/announce".into(); let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None); From d1d1b3fb32c9f7bd744d80f321c993acf8d24c89 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Sun, 11 May 2025 15:34:31 -0400 Subject: [PATCH 13/23] refactor: Refactored tests to use stream_peers, and cleaned up code. Tests still do not work because trackers do not have the given info_hash. FIXME --- crates/libtortillas/src/tracker/http.rs | 14 +------- crates/libtortillas/src/tracker/mod.rs | 13 +++++++ crates/libtortillas/src/tracker/wss.rs | 45 +++++++++---------------- 3 files changed, 30 insertions(+), 42 deletions(-) diff --git a/crates/libtortillas/src/tracker/http.rs b/crates/libtortillas/src/tracker/http.rs index fccea214..7fde0aa7 100644 --- a/crates/libtortillas/src/tracker/http.rs +++ b/crates/libtortillas/src/tracker/http.rs @@ -3,6 +3,7 @@ use super::{Peer, TrackerRequest, TrackerTrait}; use crate::{ errors::{HttpTrackerError, TrackerError}, hashes::{Hash, InfoHash}, + tracker::urlencode, }; use anyhow::Result; use async_trait::async_trait; @@ -54,19 +55,6 @@ impl HttpTracker { } } -fn urlencode(t: &[u8; 20]) -> String { - let mut encoded = String::with_capacity(3 * t.len()); - - for &byte in t { - encoded.push('%'); - - let byte = hex::encode([byte]); - encoded.push_str(&byte); - } - - encoded -} - /// Fetches peers from tracker over HTTP and returns a stream of [Peers](Peer) #[async_trait] impl TrackerTrait for HttpTracker { diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index a2594498..a5dec3fb 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -22,6 +22,19 @@ pub mod http; pub mod udp; pub mod wss; +fn urlencode(t: &[u8; 20]) -> String { + let mut encoded = String::with_capacity(3 * t.len()); + + for &byte in t { + encoded.push('%'); + + let byte = hex::encode([byte]); + encoded.push_str(&byte); + } + + encoded +} + #[async_trait] pub trait TrackerTrait: Clone + 'static { /// Acts as a wrapper function for get_peers. Should be spawned with tokio::spawn. diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index dac8d663..9fce3306 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -5,14 +5,13 @@ use anyhow::Result; use async_trait::async_trait; use futures::stream::StreamExt; use futures::SinkExt; -use rustls::crypto::CryptoProvider; -use rustls::SupportedCipherSuite; use serde_json::Value; use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::Message; use tracing::{debug, error, trace}; use crate::hashes::Hash; +use crate::tracker::urlencode; use crate::{hashes::InfoHash, peers::Peer}; use super::{TrackerRequest, TrackerTrait}; @@ -136,15 +135,12 @@ mod tests { use crate::{parser::MetaInfo, tracker::wss::WssTracker}; - // TO TEST: - // Using stream_peers() - #[tokio::test] #[traced_test] async fn test_get_peers_with_wss_tracker() { let path = std::env::current_dir() .unwrap() - .join("tests/torrents/big-buck-bunny.torrent"); + .join("tests/torrents/sintel.torrent"); let metainfo = TorrentFile::parse(path).await.unwrap(); match metainfo { @@ -154,18 +150,13 @@ mod tests { let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None); - // Make request - let res = WssTracker::get_peers(&mut wss_tracker) - .await - .expect("Issue when unwrapping result of get_peers"); - // Spawn a task to re-fetch the latest list of peers at a given interval - // let mut rx = wss_tracker.stream_peers().await.unwrap(); - // - // let peers = rx.recv().await.unwrap(); - // - // let peer = &peers[0]; - // assert!(peer.ip.is_ipv4()); + let mut rx = wss_tracker.stream_peers().await.unwrap(); + + let peers = rx.recv().await.unwrap(); + + let peer = &peers[0]; + assert!(peer.ip.is_ipv4()); } _ => panic!("Expected Torrent"), } @@ -182,23 +173,19 @@ mod tests { match metainfo { MetaInfo::Torrent(torrent) => { let info_hash = torrent.info.hash(); - // From https://github.com/ngosang/trackerslist/blob/master/trackers_all_ws.txt + // From https://github.com/ngosang/trackerslist/blob/master/trackers_all_ws.txt. May + // not be consistently present, as this repo is automatically updated/changed let uri = "ws://tracker.files.fm:7072/announce".into(); let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None); - // Make request - let res = WssTracker::get_peers(&mut wss_tracker) - .await - .expect("Issue when unwrapping result of get_peers"); - // Spawn a task to re-fetch the latest list of peers at a given interval - // let mut rx = wss_tracker.stream_peers().await.unwrap(); - // - // let peers = rx.recv().await.unwrap(); - // - // let peer = &peers[0]; - // assert!(peer.ip.is_ipv4()); + let mut rx = wss_tracker.stream_peers().await.unwrap(); + + let peers = rx.recv().await.unwrap(); + + let peer = &peers[0]; + assert!(peer.ip.is_ipv4()); } _ => panic!("Expected Torrent"), } From 3bf393be427485dd6d183c8efc291f8e1d3d22db Mon Sep 17 00:00:00 2001 From: kurealnum Date: Tue, 13 May 2025 18:02:20 -0400 Subject: [PATCH 14/23] fix: Fixed how info hashes are handled with WebSocket trackers --- crates/libtortillas/src/tracker/mod.rs | 25 ++++++++++++++++++++++++- crates/libtortillas/src/tracker/wss.rs | 9 ++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index a5dec3fb..0768c2df 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -17,11 +17,34 @@ use tracing::{trace, warn}; use udp::UdpTracker; use wss::WssTracker; -use crate::{hashes::InfoHash, peers::Peer}; +use crate::{ + hashes::{Hash, InfoHash}, + peers::Peer, +}; pub mod http; pub mod udp; pub mod wss; +// To be completely frank, I don't completely understand what we're doing here. +// Courtesy of +fn hash_to_utf8(hash: Hash<20>) -> String { + let mut arr = [0u8; 20]; + let info_hash_string = hash.to_string(); + let mut char_iter = info_hash_string.chars(); + for a in arr.iter_mut() { + if let Some(c) = char_iter.next() { + if c as u32 > 255 { + panic!("Character not in single byte range") + } + + *a = c as u8; + } else { + panic!("Info hash was not 20 bytes"); + } + } + String::from_utf8(arr.to_vec()).unwrap() +} + fn urlencode(t: &[u8; 20]) -> String { let mut encoded = String::with_capacity(3 * t.len()); diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 9fce3306..2109ca12 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -11,7 +11,7 @@ use tokio_tungstenite::tungstenite::Message; use tracing::{debug, error, trace}; use crate::hashes::Hash; -use crate::tracker::urlencode; +use crate::tracker::hash_to_utf8; use crate::{hashes::InfoHash, peers::Peer}; use super::{TrackerRequest, TrackerTrait}; @@ -49,6 +49,7 @@ impl TrackerTrait for WssTracker { /// lives very easy though) async fn get_peers(&mut self) -> Result> { trace!("Attemping connection to WSS tracker: {}", self.uri); + let (stream, _) = connect_async(&self.uri) .await .map_err(|e| { @@ -58,14 +59,16 @@ impl TrackerTrait for WssTracker { let (mut write, mut read) = stream.split(); trace!("Connected to WSS tracker at {}", self.uri); - trace!("Generated request parameters"); let mut tracker_request_as_json = serde_json::to_string(&self.params).unwrap(); + trace!("Generated request parameters"); // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc"} tracker_request_as_json.pop(); let request = format!( "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\"}}", - tracker_request_as_json, self.info_hash, self.peer_id + tracker_request_as_json, + hash_to_utf8(self.info_hash), + hash_to_utf8(self.peer_id) ); trace!("Request json generated: {}", request); From 4cda5b22173f8bab5ccacf3c56faae262b6146b3 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Wed, 14 May 2025 19:21:13 -0400 Subject: [PATCH 15/23] refactor: Moved initial connection of WebSocket into new() fn --- crates/libtortillas/src/tracker/mod.rs | 3 +- crates/libtortillas/src/tracker/wss.rs | 97 ++++++++++++++++---------- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 0768c2df..95a69717 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -176,7 +176,8 @@ impl Tracker { uri.clone(), info_hash, Some(SocketAddr::from(([0, 0, 0, 0], port))), - ); + ) + .await; Ok(tracker.get_peers().await.unwrap()) } } diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 2109ca12..55acb755 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -1,13 +1,15 @@ -use std::net::{IpAddr, SocketAddr}; -use std::str::FromStr; +use std::net::SocketAddr; +use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; -use futures::stream::StreamExt; +use futures::stream::{SplitSink, SplitStream, StreamExt}; use futures::SinkExt; use serde_json::Value; -use tokio_tungstenite::connect_async; +use tokio::net::TcpStream; +use tokio::sync::Mutex; use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, trace}; use crate::hashes::Hash; @@ -19,26 +21,45 @@ use super::{TrackerRequest, TrackerTrait}; /// Tracker for websockets #[derive(Clone)] pub struct WssTracker { - uri: String, info_hash: InfoHash, params: TrackerRequest, peer_id: Hash<20>, interval: u32, + write: Arc>, Message>>>, + read: Arc>>>>, } impl WssTracker { - pub fn new(uri: String, info_hash: InfoHash, peer_tracker_addr: Option) -> Self { + pub async fn new( + uri: String, + info_hash: InfoHash, + peer_tracker_addr: Option, + ) -> Self { let mut peer_id_bytes = [0u8; 20]; rand::fill(&mut peer_id_bytes); let peer_id = Hash::new(peer_id_bytes); debug!(peer_id = %peer_id, "Generated peer ID"); + trace!("Attemping connection to WSS tracker: {}", uri); + + let (stream, _) = connect_async(&uri) + .await + .map_err(|e| { + error!("Error connecting to peer: {}", e); + }) + .unwrap(); + let (write, read) = stream.split(); + let arc_write = Arc::new(Mutex::new(write)); + let arc_read = Arc::new(Mutex::new(read)); + trace!("Connected to WSS tracker at {}", uri); + WssTracker { - uri, info_hash, params: TrackerRequest::new(peer_tracker_addr), peer_id, interval: u32::MAX, + write: arc_write, + read: arc_read, } } } @@ -48,17 +69,6 @@ impl TrackerTrait for WssTracker { /// It should be noted that WebSockets are supposed to communicate in JSON. (This makes our /// lives very easy though) async fn get_peers(&mut self) -> Result> { - trace!("Attemping connection to WSS tracker: {}", self.uri); - - let (stream, _) = connect_async(&self.uri) - .await - .map_err(|e| { - error!("Error connecting to peer: {}", e); - }) - .unwrap(); - let (mut write, mut read) = stream.split(); - trace!("Connected to WSS tracker at {}", self.uri); - let mut tracker_request_as_json = serde_json::to_string(&self.params).unwrap(); trace!("Generated request parameters"); @@ -75,14 +85,20 @@ impl TrackerTrait for WssTracker { let message = Message::from(request); trace!("Sending message to tracker"); - write + self + .write + .lock() + .await .send(message) .await .map_err(|e| { error!("Error sending message: {e}"); }) .unwrap(); - write + self + .write + .lock() + .await .flush() .await .map_err(|e| { @@ -94,7 +110,10 @@ impl TrackerTrait for WssTracker { // This section of code is completely and utterly scuffed. self.read.collect() refuses to // work, so this is what we're stuck with for now. - let output = read + let output = self + .read + .lock() + .await .next() .await .unwrap() @@ -105,23 +124,29 @@ impl TrackerTrait for WssTracker { trace!("Message recieved: {}", output); - // Output should be a vec of peers + // Output should look something like this: + // {"complete":0,"incomplete":0,"action":"announce","interval":120,"info_hash":"myhash"} let res_json: Value = serde_json::from_str(&output).unwrap(); - let json = res_json.as_object().unwrap(); - if json.contains_key("failure reason") { - panic!("Error: {}", json.get("failure reason").unwrap()); + // Check for "failure_reason" key (response failed) + let res_json = res_json.as_object().unwrap(); + if res_json.contains_key("failure reason") { + panic!("Error: {}", res_json.get("failure reason").unwrap()); } - let arr = res_json.as_array().unwrap(); - let mut res = vec![]; - for peer in arr { - let ip = IpAddr::from_str(peer["ip"].as_str().unwrap()).unwrap(); - - let port = peer["port"].as_u64().unwrap(); - let peer = Peer::new(ip, port.try_into().unwrap()); - res.push(peer); - } + self.interval = res_json.get("interval").unwrap().as_u64().unwrap() as u32; + + // let arr = res_json.as_array().unwrap(); + // let mut res = vec![]; + // for peer in arr { + // let ip = IpAddr::from_str(peer["ip"].as_str().unwrap()).unwrap(); + // + // let port = peer["port"].as_u64().unwrap(); + // let peer = Peer::new(ip, port.try_into().unwrap()); + // res.push(peer); + // } + // Ok(res) + let res: Vec = vec![]; Ok(res) } @@ -151,7 +176,7 @@ mod tests { let info_hash = torrent.info.hash(); let uri = "wss://tracker.btorrent.xyz".into(); - let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None); + let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None).await; // Spawn a task to re-fetch the latest list of peers at a given interval let mut rx = wss_tracker.stream_peers().await.unwrap(); @@ -180,7 +205,7 @@ mod tests { // not be consistently present, as this repo is automatically updated/changed let uri = "ws://tracker.files.fm:7072/announce".into(); - let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None); + let mut wss_tracker = WssTracker::new(uri, info_hash.unwrap(), None).await; // Spawn a task to re-fetch the latest list of peers at a given interval let mut rx = wss_tracker.stream_peers().await.unwrap(); From 1b685084f7a224be930f06e7a9b67587b8966376 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Thu, 15 May 2025 21:32:59 -0400 Subject: [PATCH 16/23] feat: Began to implement SDP offer to tracker --- crates/libtortillas/src/tracker/wss.rs | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 55acb755..a4e05ec5 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -1,5 +1,6 @@ use std::net::SocketAddr; use std::sync::Arc; +use std::time::{Duration, UNIX_EPOCH}; use anyhow::Result; use async_trait::async_trait; @@ -8,6 +9,7 @@ use futures::SinkExt; use serde_json::Value; use tokio::net::TcpStream; use tokio::sync::Mutex; +use tokio::time::Instant; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, trace}; @@ -136,6 +138,49 @@ impl TrackerTrait for WssTracker { self.interval = res_json.get("interval").unwrap().as_u64().unwrap() as u32; + // SDP offer & answer + // See for more + // information + + let timestamp = UNIX_EPOCH.elapsed().unwrap().as_secs(); + let raw_sdp_offer = format!( + "{{\"offer\":\"\ + v=0\ + o=- {} {} IN IP4 127.0.0.1\ + s=-\ + \"}}", + timestamp, timestamp + ); + + trace!("Sending SDP message: {}", raw_sdp_offer); + + let sdp_offer = Message::from(raw_sdp_offer); + + self + .write + .lock() + .await + .send(sdp_offer) + .await + .map_err(|e| { + error!("Error sending message: {e}"); + }) + .unwrap(); + + let sdp_answer = self + .read + .lock() + .await + .next() + .await + .unwrap() + .unwrap() + .into_text() + .unwrap() + .to_string(); + + trace!("SDP message result: {}", sdp_answer); + // let arr = res_json.as_array().unwrap(); // let mut res = vec![]; // for peer in arr { @@ -146,6 +191,8 @@ impl TrackerTrait for WssTracker { // res.push(peer); // } // Ok(res) + + // tmp let res: Vec = vec![]; Ok(res) } From 6f2454b7723b6fed19ba9d36177338a41ca05ea1 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Fri, 16 May 2025 18:22:13 -0400 Subject: [PATCH 17/23] refactor: Corrected previously incorrect implementation of SDP offer(s) + updated docs --- README.md | 6 +- crates/libtortillas/src/tracker/wss.rs | 141 +++++++++++++++---------- 2 files changed, 91 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 139b626d..65d85990 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,14 @@ We use [Nextest](https://nexte.st/) for testing. You may have to install Nextest Please keep in mind that as of April 6th, 2025, this library is not complete. -### A small note about WebSocket (WS) trackers +### A few notes about WebSocket (WS) trackers Generally speaking, this project refers to WebSockets as WSS (Web Sockets Secured), not WS. Consequently, you'll see files/structs/etc. with names such as `wss.rs`, `WssTracker`, and so on. Keep in mind that these files/structs/etc. refer to both secure and unsecured WebSockets. +Additionally, WebSocket peers/trackers act in a very different manner in comparison to HTTPS/UDP peers/trackers. The general workflow for this process can be found in this very helpful diagram posted by [Akshat Schan on LinkedIn.](https://www.linkedin.com/pulse/edition-2-diving-deep-webrtc-webtorrent-p2p-streaming-akshat-sachan-bbtrc/) + +![Diagram of WSS peers/trackers](https://media.licdn.com/dms/image/v2/D5612AQG1-HvLrx6lAQ/article-inline_image-shrink_1000_1488/B56ZYjsOvSGUAY-/0/1744355520685?e=1752710400&v=beta&t=y9C53nrL8sl5aTjmhSdj20_8idD0PEzBMkhLiJGhvCE) + ### Handshaking with peers #### uTP diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index a4e05ec5..e12e97d1 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -1,15 +1,15 @@ use std::net::SocketAddr; use std::sync::Arc; -use std::time::{Duration, UNIX_EPOCH}; +use std::time::UNIX_EPOCH; use anyhow::Result; use async_trait::async_trait; use futures::stream::{SplitSink, SplitStream, StreamExt}; use futures::SinkExt; +use serde::{Deserialize, Serialize}; use serde_json::Value; use tokio::net::TcpStream; use tokio::sync::Mutex; -use tokio::time::Instant; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, trace}; @@ -31,6 +31,57 @@ pub struct WssTracker { read: Arc>>>>, } +/// This is primarily used for serializing offers. Try torrenting a file with +/// and check out how the offers are "shaped" in the network tab. +#[derive(Serialize, Deserialize)] +struct WssOffer { + #[serde(rename(deserialize = "type"))] + offer_type: String, + sdp: String, + offer_id: String, +} + +impl WssOffer { + pub fn new(sdp: String) -> Self { + let mut offer_id_bytes = [0u8; 20]; + rand::fill(&mut offer_id_bytes); + let offer_id = Hash::new(offer_id_bytes); + WssOffer { + offer_type: "offer".into(), + sdp, + offer_id: hash_to_utf8(offer_id), + } + } +} + +/// Again, please try torrenting using a site like and examine +/// the format that offers are sent in. We need to serialize offers in a format like this: +/// [ +/// { +/// "offer": { +/// ... +/// } +/// } +/// { +/// "offer": { +/// ... +/// } +/// } +/// ] +/// Hence, the easiest thing to do is use a wrapper. +#[derive(Serialize, Deserialize)] +struct WssOfferWrapper { + offer: WssOffer, +} + +impl WssOfferWrapper { + pub fn new(sdp: String) -> Self { + WssOfferWrapper { + offer: WssOffer::new(sdp), + } + } +} + impl WssTracker { pub async fn new( uri: String, @@ -64,23 +115,51 @@ impl WssTracker { read: arc_read, } } + + /// Prototype. Headers will be changed. + pub async fn recv_peers(&mut self) -> bool { + true + } } #[async_trait] impl TrackerTrait for WssTracker { - /// It should be noted that WebSockets are supposed to communicate in JSON. (This makes our + /// It should be noted that WebSockets are intended to communicate in JSON. (This makes our /// lives very easy though) + /// + /// This does not initially return a list of peers, so to speak. Instead, it sends an SDP offer + /// to the tracker, and the tracker forwards that SDP offer to relevant peers. Those peers then + /// return an SDP answer to the tracker, which forwards the answer to us. async fn get_peers(&mut self) -> Result> { let mut tracker_request_as_json = serde_json::to_string(&self.params).unwrap(); trace!("Generated request parameters"); - // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc"} + // Generate offers + let numwant = 5; + let mut offers = vec![]; + let timestamp = UNIX_EPOCH.elapsed()?.as_secs(); + let raw_sdp_offer = format!( + "{{\"offer\":\"\ + v=0\ + o=- {} {} IN IP4 127.0.0.1\ + s=-\ + \"}}", + timestamp, timestamp + ); + for _i in 0..numwant { + let offer = WssOffer::new(raw_sdp_offer.clone()); + offers.push(offer); + } + + // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc",numwant:5} tracker_request_as_json.pop(); let request = format!( - "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\"}}", + "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\",\"numwant\":{}, \"offer\": {} }}", tracker_request_as_json, hash_to_utf8(self.info_hash), - hash_to_utf8(self.peer_id) + hash_to_utf8(self.peer_id), + numwant, + serde_json::to_string(&offers)? ); trace!("Request json generated: {}", request); @@ -142,55 +221,7 @@ impl TrackerTrait for WssTracker { // See for more // information - let timestamp = UNIX_EPOCH.elapsed().unwrap().as_secs(); - let raw_sdp_offer = format!( - "{{\"offer\":\"\ - v=0\ - o=- {} {} IN IP4 127.0.0.1\ - s=-\ - \"}}", - timestamp, timestamp - ); - - trace!("Sending SDP message: {}", raw_sdp_offer); - - let sdp_offer = Message::from(raw_sdp_offer); - - self - .write - .lock() - .await - .send(sdp_offer) - .await - .map_err(|e| { - error!("Error sending message: {e}"); - }) - .unwrap(); - - let sdp_answer = self - .read - .lock() - .await - .next() - .await - .unwrap() - .unwrap() - .into_text() - .unwrap() - .to_string(); - - trace!("SDP message result: {}", sdp_answer); - - // let arr = res_json.as_array().unwrap(); - // let mut res = vec![]; - // for peer in arr { - // let ip = IpAddr::from_str(peer["ip"].as_str().unwrap()).unwrap(); - // - // let port = peer["port"].as_u64().unwrap(); - // let peer = Peer::new(ip, port.try_into().unwrap()); - // res.push(peer); - // } - // Ok(res) + // ??? // tmp let res: Vec = vec![]; From b63eebca77fbeee5b78a136e0c86efbde5b5db78 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Sat, 17 May 2025 14:36:46 -0400 Subject: [PATCH 18/23] feat: Fixed SDP & general formatting issues. Info hash might still not be sending correctly --- crates/libtortillas/src/tracker/wss.rs | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index e12e97d1..9a4eac26 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -35,21 +35,16 @@ pub struct WssTracker { /// and check out how the offers are "shaped" in the network tab. #[derive(Serialize, Deserialize)] struct WssOffer { - #[serde(rename(deserialize = "type"))] + #[serde(rename = "type")] offer_type: String, sdp: String, - offer_id: String, } impl WssOffer { pub fn new(sdp: String) -> Self { - let mut offer_id_bytes = [0u8; 20]; - rand::fill(&mut offer_id_bytes); - let offer_id = Hash::new(offer_id_bytes); WssOffer { offer_type: "offer".into(), sdp, - offer_id: hash_to_utf8(offer_id), } } } @@ -61,23 +56,30 @@ impl WssOffer { /// "offer": { /// ... /// } +/// "offer_id": ... /// } /// { /// "offer": { /// ... /// } +/// "offer_id": ... /// } /// ] /// Hence, the easiest thing to do is use a wrapper. #[derive(Serialize, Deserialize)] struct WssOfferWrapper { offer: WssOffer, + offer_id: String, } impl WssOfferWrapper { pub fn new(sdp: String) -> Self { + let mut offer_id_bytes = [0u8; 20]; + rand::fill(&mut offer_id_bytes); + let offer_id = Hash::new(offer_id_bytes); WssOfferWrapper { offer: WssOffer::new(sdp), + offer_id: hash_to_utf8(offer_id), } } } @@ -139,22 +141,20 @@ impl TrackerTrait for WssTracker { let mut offers = vec![]; let timestamp = UNIX_EPOCH.elapsed()?.as_secs(); let raw_sdp_offer = format!( - "{{\"offer\":\"\ - v=0\ - o=- {} {} IN IP4 127.0.0.1\ - s=-\ - \"}}", + "v=0\ + o=- {} {} IN IP4 0.0.0.0\ + s=-\"", timestamp, timestamp ); for _i in 0..numwant { - let offer = WssOffer::new(raw_sdp_offer.clone()); + let offer = WssOfferWrapper::new(raw_sdp_offer.clone()); offers.push(offer); } // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc",numwant:5} tracker_request_as_json.pop(); let request = format!( - "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\",\"numwant\":{}, \"offer\": {} }}", + "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\",\"numwant\":{}, \"offers\": {} }}", tracker_request_as_json, hash_to_utf8(self.info_hash), hash_to_utf8(self.peer_id), @@ -217,13 +217,18 @@ impl TrackerTrait for WssTracker { self.interval = res_json.get("interval").unwrap().as_u64().unwrap() as u32; - // SDP offer & answer - // See for more - // information - - // ??? + let answers = self + .read + .lock() + .await + .next() + .await + .unwrap() + .unwrap() + .into_text() + .unwrap() + .to_string(); - // tmp let res: Vec = vec![]; Ok(res) } From 5e92da46125fb17dd163b8f474f9235840fd2b1a Mon Sep 17 00:00:00 2001 From: kurealnum Date: Sat, 17 May 2025 14:40:30 -0400 Subject: [PATCH 19/23] feat: quickfix - changed event from stopped to started --- crates/libtortillas/src/tracker/wss.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 9a4eac26..455b2fc1 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -15,7 +15,7 @@ use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, trace}; use crate::hashes::Hash; -use crate::tracker::hash_to_utf8; +use crate::tracker::{hash_to_utf8, Event}; use crate::{hashes::InfoHash, peers::Peer}; use super::{TrackerRequest, TrackerTrait}; @@ -133,6 +133,7 @@ impl TrackerTrait for WssTracker { /// to the tracker, and the tracker forwards that SDP offer to relevant peers. Those peers then /// return an SDP answer to the tracker, which forwards the answer to us. async fn get_peers(&mut self) -> Result> { + self.params.event = Event::Started; let mut tracker_request_as_json = serde_json::to_string(&self.params).unwrap(); trace!("Generated request parameters"); From 2c2411bb76d8d465061a4f84beeffcc230ef98e2 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Sun, 18 May 2025 10:27:46 -0400 Subject: [PATCH 20/23] refactor: Made progress on converting info_hash to correct format. This commit also acts as a save point. --- crates/libtortillas/src/tracker/mod.rs | 18 ++++++++++++++++++ crates/libtortillas/src/tracker/wss.rs | 6 ++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 95a69717..0119046d 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -45,6 +45,24 @@ fn hash_to_utf8(hash: Hash<20>) -> String { String::from_utf8(arr.to_vec()).unwrap() } +fn encode_to_byte_string(arr: &[u8; 20]) -> String { + let mut result = String::new(); + for c in arr { + // If it's a valid character, do this. + if 32 <= *c && *c <= 196 { + result.push(*c as char); + } + // Otherwise, just escape it + else { + let mut tmp = String::new(); + tmp.push_str("\\u00"); + tmp.push_str(&c.to_string()); + result.push_str(&tmp); + } + } + result +} + fn urlencode(t: &[u8; 20]) -> String { let mut encoded = String::with_capacity(3 * t.len()); diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 455b2fc1..3d014c64 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -15,7 +15,7 @@ use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, trace}; use crate::hashes::Hash; -use crate::tracker::{hash_to_utf8, Event}; +use crate::tracker::{encode_to_byte_string, hash_to_utf8, Event}; use crate::{hashes::InfoHash, peers::Peer}; use super::{TrackerRequest, TrackerTrait}; @@ -152,12 +152,14 @@ impl TrackerTrait for WssTracker { offers.push(offer); } + trace!("{}", encode_to_byte_string(self.info_hash.as_bytes())); + // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc",numwant:5} tracker_request_as_json.pop(); let request = format!( "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\",\"numwant\":{}, \"offers\": {} }}", tracker_request_as_json, - hash_to_utf8(self.info_hash), + String::from_utf8_lossy(self.info_hash.as_bytes()), hash_to_utf8(self.peer_id), numwant, serde_json::to_string(&offers)? From 5f8ada8caab1f336d68c2af0949b28e0ab61d0d7 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Mon, 19 May 2025 18:50:06 -0400 Subject: [PATCH 21/23] save: Just a commit to save progress. --- crates/libtortillas/src/tracker/mod.rs | 5 +++-- crates/libtortillas/src/tracker/wss.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index 0119046d..facb2a6e 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -1,5 +1,6 @@ use anyhow::Result; use async_trait::async_trait; +use core::str; use http::HttpTracker; use rand::random_range; use serde::{ @@ -48,8 +49,8 @@ fn hash_to_utf8(hash: Hash<20>) -> String { fn encode_to_byte_string(arr: &[u8; 20]) -> String { let mut result = String::new(); for c in arr { - // If it's a valid character, do this. - if 32 <= *c && *c <= 196 { + // If it's a valid character (not a control character), do this. + if *c > 31 && *c < 127 { result.push(*c as char); } // Otherwise, just escape it diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 3d014c64..d0110156 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -159,7 +159,7 @@ impl TrackerTrait for WssTracker { let request = format!( "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\",\"numwant\":{}, \"offers\": {} }}", tracker_request_as_json, - String::from_utf8_lossy(self.info_hash.as_bytes()), + encode_to_byte_string(self.info_hash.as_bytes()), hash_to_utf8(self.peer_id), numwant, serde_json::to_string(&offers)? From 28ddf61253afbe503fa667432fe00fba8bdc8000 Mon Sep 17 00:00:00 2001 From: kurealnum Date: Tue, 20 May 2025 15:18:41 -0400 Subject: [PATCH 22/23] feat: Actually got WebSocket trackers working with a little help from ChatGPT --- crates/libtortillas/src/tracker/mod.rs | 65 ++++++++++++++------------ crates/libtortillas/src/tracker/wss.rs | 12 ++--- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/crates/libtortillas/src/tracker/mod.rs b/crates/libtortillas/src/tracker/mod.rs index facb2a6e..c3804e3e 100644 --- a/crates/libtortillas/src/tracker/mod.rs +++ b/crates/libtortillas/src/tracker/mod.rs @@ -1,6 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use core::str; +use hex::FromHex; use http::HttpTracker; use rand::random_range; use serde::{ @@ -26,42 +27,44 @@ pub mod http; pub mod udp; pub mod wss; -// To be completely frank, I don't completely understand what we're doing here. -// Courtesy of -fn hash_to_utf8(hash: Hash<20>) -> String { - let mut arr = [0u8; 20]; - let info_hash_string = hash.to_string(); - let mut char_iter = info_hash_string.chars(); - for a in arr.iter_mut() { - if let Some(c) = char_iter.next() { - if c as u32 > 255 { - panic!("Character not in single byte range") +// This is AI generated. But it works. +fn hash_to_byte_string(hex_str: &str) -> String { + // 1) decode hex → raw bytes + let bytes = Vec::from_hex(hex_str).expect("invalid hex input"); + + // 2) build the escaped string + let mut out = String::new(); + for &b in &bytes { + match b { + // common C-style escapes + 0x00 => out.push_str(r"\0"), + 0x07 => out.push_str(r"\a"), + 0x08 => out.push_str(r"\b"), + 0x09 => out.push_str(r"\t"), + 0x0A => out.push_str(r"\n"), + 0x0B => out.push_str(r"\v"), + 0x0C => out.push_str(r"\f"), + 0x0D => out.push_str(r"\r"), + + // any other C0 control → \u00XX + 0x01..=0x06 | 0x0E..=0x1F => { + out.push_str(&format!(r"\u{:04x}", b)); } - *a = c as u8; - } else { - panic!("Info hash was not 20 bytes"); - } - } - String::from_utf8(arr.to_vec()).unwrap() -} + // printable ASCII + 0x20..=0x7E => { + out.push(b as char); + } -fn encode_to_byte_string(arr: &[u8; 20]) -> String { - let mut result = String::new(); - for c in arr { - // If it's a valid character (not a control character), do this. - if *c > 31 && *c < 127 { - result.push(*c as char); - } - // Otherwise, just escape it - else { - let mut tmp = String::new(); - tmp.push_str("\\u00"); - tmp.push_str(&c.to_string()); - result.push_str(&tmp); + // high-bit set → ISO-8859-1 codepoint + _ => { + let ch = char::from_u32(b as u32).expect("byte → char failed"); + out.push(ch); + } } } - result + + out } fn urlencode(t: &[u8; 20]) -> String { diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index d0110156..2e2047d0 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -15,7 +15,7 @@ use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, trace}; use crate::hashes::Hash; -use crate::tracker::{encode_to_byte_string, hash_to_utf8, Event}; +use crate::tracker::{hash_to_byte_string, Event}; use crate::{hashes::InfoHash, peers::Peer}; use super::{TrackerRequest, TrackerTrait}; @@ -79,7 +79,7 @@ impl WssOfferWrapper { let offer_id = Hash::new(offer_id_bytes); WssOfferWrapper { offer: WssOffer::new(sdp), - offer_id: hash_to_utf8(offer_id), + offer_id: hash_to_byte_string(&offer_id.to_string()), } } } @@ -152,15 +152,13 @@ impl TrackerTrait for WssTracker { offers.push(offer); } - trace!("{}", encode_to_byte_string(self.info_hash.as_bytes())); - - // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc",numwant:5} + // {tracker_request_as_json,info_hash:"xyz",peer_id:"abc",action:"announce",numwant:5,offers:{...}} tracker_request_as_json.pop(); let request = format!( "{},\"info_hash\":\"{}\",\"peer_id\":\"{}\",\"action\":\"announce\",\"numwant\":{}, \"offers\": {} }}", tracker_request_as_json, - encode_to_byte_string(self.info_hash.as_bytes()), - hash_to_utf8(self.peer_id), + hash_to_byte_string(&self.info_hash.to_string()), + hash_to_byte_string(&self.peer_id.to_string()), numwant, serde_json::to_string(&offers)? ); From 17757ffbed5ff1ccab2c9be67deccfd2ab61776d Mon Sep 17 00:00:00 2001 From: kurealnum Date: Wed, 21 May 2025 15:15:23 -0400 Subject: [PATCH 23/23] refactor: Utilized a crate for SDP --- crates/libtortillas/src/tracker/wss.rs | 57 ++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/crates/libtortillas/src/tracker/wss.rs b/crates/libtortillas/src/tracker/wss.rs index 2e2047d0..006c7a61 100644 --- a/crates/libtortillas/src/tracker/wss.rs +++ b/crates/libtortillas/src/tracker/wss.rs @@ -13,6 +13,9 @@ use tokio::sync::Mutex; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; use tracing::{debug, error, trace}; +use webrtc::sdp::description::media::{MediaName, RangedPort}; +use webrtc::sdp::description::session::Origin; +use webrtc::sdp::{MediaDescription, SessionDescription}; use crate::hashes::Hash; use crate::tracker::{hash_to_byte_string, Event}; @@ -138,15 +141,55 @@ impl TrackerTrait for WssTracker { trace!("Generated request parameters"); // Generate offers - let numwant = 5; + let numwant = 1; + let mut offers = vec![]; let timestamp = UNIX_EPOCH.elapsed()?.as_secs(); - let raw_sdp_offer = format!( - "v=0\ - o=- {} {} IN IP4 0.0.0.0\ - s=-\"", - timestamp, timestamp - ); + + // SDP Offer + // FIXME: Should unicast address actually be 0.0.0.0? All of WebTorrent's offers/answers are + // 0.0.0.0 or 127.0.0.1 + // FIXME: media_name port? + let sdp_offer = SessionDescription { + version: 0, + origin: Origin { + username: "-".to_string(), + session_id: timestamp, + session_version: timestamp, + network_type: "IN".to_string(), + address_type: "IP4".to_string(), + unicast_address: "0.0.0.0".to_string(), + }, + session_name: "SDP offer from WebTorrent peer".to_string(), + session_information: None, + uri: None, + email_address: None, + phone_number: None, + connection_information: None, + bandwidth: vec![], + time_descriptions: vec![], + time_zones: vec![], + encryption_key: None, + attributes: vec![], + media_descriptions: vec![MediaDescription { + media_name: MediaName { + media: "application".to_string(), + port: RangedPort { + value: 27764, + range: None, + }, + protos: vec!["UDP".to_string(), "DTLS".to_string(), "SCTP".to_string()], + formats: vec!["webrtc-datachannel".to_string()], + }, + media_title: None, + connection_information: None, + bandwidth: vec![], + encryption_key: None, + attributes: vec![], + }], + }; + let raw_sdp_offer = sdp_offer.marshal(); + for _i in 0..numwant { let offer = WssOfferWrapper::new(raw_sdp_offer.clone()); offers.push(offer);