diff --git a/Cargo.lock b/Cargo.lock index fc0d9a9b8e..b21feede06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,62 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] [[package]] name = "asn1-rs" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -43,15 +93,33 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", @@ -63,11 +131,63 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "der-parser" @@ -85,24 +205,30 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "err-context" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449aad22b1364e927ff3bf50f55404efd705c40065fb47f73f28704de707c89e" + [[package]] name = "ffi-support" version = "0.4.4" @@ -115,32 +241,132 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "lazy_static" @@ -150,21 +376,30 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memoffset" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] [[package]] name = "minimal-lexical" @@ -172,6 +407,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.3" @@ -194,9 +453,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -216,6 +475,19 @@ dependencies = [ "autocfg", ] +[[package]] +name = "obfuscators" +version = "0.1.0" +dependencies = [ + "base64", + "clap", + "log", + "once_cell", + "rand", + "tokio", + "udp-over-tcp", +] + [[package]] name = "oid-registry" version = "0.8.1" @@ -225,30 +497,87 @@ dependencies = [ "asn1-rs", ] +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "ring" version = "0.17.14" @@ -260,7 +589,7 @@ dependencies = [ "getrandom", "libc", "untrusted", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -272,15 +601,6 @@ dependencies = [ "nom", ] -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -303,9 +623,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signature" @@ -321,11 +641,33 @@ dependencies = [ "x509-parser", ] +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" -version = "2.0.111" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -365,40 +707,78 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", ] +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "udp-over-tcp" +version = "0.4.0" +source = "git+https://github.com/mullvad/udp-over-tcp?rev=5c6d8f44a5aa12ed9bb4ae51dd17e5e22e5ec303#5c6d8f44a5aa12ed9bb4ae51dd17e5e22e5ec303" +dependencies = [ + "err-context", + "futures", + "log", + "nix", + "tokio", +] + [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "untrusted" @@ -406,12 +786,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.52.0" @@ -421,6 +813,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -502,3 +903,23 @@ dependencies = [ "thiserror", "time", ] + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index c4a118e8cf..0572185481 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "signature", + "obfuscators", ] resolver = "2" diff --git a/android/daemon/build.gradle b/android/daemon/build.gradle index 772bdd810f..a2ae856327 100644 --- a/android/daemon/build.gradle +++ b/android/daemon/build.gradle @@ -99,4 +99,5 @@ dependencies { implementation project(path: ':common') implementation project(path: ':tunnel') + implementation project(path: ':obfuscator') } diff --git a/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt b/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt index c50ddc5cdd..0b1faeefc0 100644 --- a/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt +++ b/android/daemon/src/main/java/org/mozilla/firefox/vpn/daemon/VPNService.kt @@ -18,6 +18,7 @@ import com.wireguard.crypto.Key import org.json.JSONObject import org.mozilla.firefox.qt.common.CoreBinder import org.mozilla.firefox.qt.common.Prefs +import org.mozilla.firefox.vpn.obfuscator.Obfuscator import org.mozilla.guardian.tunnel.WireGuardGo import java.util.* @@ -30,6 +31,7 @@ class VPNService : android.net.VpnService() { private var mAlreadyInitialised = false val mConnectionHealth = ConnectionHealth(this) private var mCityname = "" + private var mObfuscator: Obfuscator? = null private val mBackgroundPingTimerMSec: Long = 3 * 60 * 60 * 1000 // 3 hours, in milliseconds private val mShortTimerBackgroundPingMSec: Long = 3 * 60 * 1000 // 3 minutes, in milliseconds @@ -223,9 +225,31 @@ class VPNService : android.net.VpnService() { } Log.sensitive(tag, json.toString()) val jServer: JSONObject = getNextServerConfig(json, useFallbackServer) - val wireguard_conf = buildWireguardConfig(jServer, json) + + // If an obfuscator is configured, start its proxy now and rewrite the + // WireGuard endpoint to its loopback port. The relay's outbound socket + // is protected below (after the tunnel is up) so its packets bypass + // the VPN. + val obfuscationMethodRaw = json.optInt("obfuscationMethod", 0) + val obfuscationMethod = Obfuscator.Method.fromValue(obfuscationMethodRaw) + Log.i(tag, "Attempt to start obfuscator '$obfuscationMethodRaw'") + val obfuscator: Obfuscator? = if (obfuscationMethod != null && obfuscationMethod != Obfuscator.Method.NoObfuscation) { + val r = Obfuscator.start( + method = obfuscationMethod, + serverIpv4 = jServer.optString("ipv4AddrIn").ifEmpty { null }, + serverIpv6 = jServer.optString("ipv6AddrIn").ifEmpty { null }, + serverPort = jServer.getInt("port"), + serverPublicKey = jServer.optString("publicKey").ifEmpty { null }, + publicKey = json.optJSONObject("device")?.optString("publicKey")?.ifEmpty { null }, + ) ?: throw Error("Failed to start obfuscator method: $obfuscationMethod") + Log.i(tag, "Obfuscator '$obfuscationMethod' listening on 127.0.0.1:${r.localPort}") + r + } else { null } + + val wireguard_conf = buildWireguardConfig(jServer, json, obfuscator) val wgConfig: String = wireguard_conf.toWgUserspaceString() if (wgConfig.isEmpty()) { + obfuscator?.stop() throw Error("WG_Userspace config is empty, can't continue") } mCityname = json.getString("city") @@ -247,6 +271,7 @@ class VPNService : android.net.VpnService() { builder.establish().use { tun -> if (tun == null) { Log.e(tag, "Activation Error: did not get a TUN handle") + obfuscator?.stop() return } // We should have everything to establish a new connection, turn down the old tunnel @@ -254,10 +279,13 @@ class VPNService : android.net.VpnService() { if (currentTunnelHandle != -1) { Log.i(tag, "Currently have a connection, close old handle") WireGuardGo.wgTurnOff(currentTunnelHandle) + mObfuscator?.stop() + mObfuscator = null } currentTunnelHandle = WireGuardGo.wgTurnOn("mvpn0", tun.detachFd(), wgConfig) } if (currentTunnelHandle < 0) { + obfuscator?.stop() throw Error("Activation Error Wireguard-Error -> $currentTunnelHandle") } else { Log.i(tag, "Updated tunnel handle to: " + currentTunnelHandle) @@ -265,6 +293,13 @@ class VPNService : android.net.VpnService() { protect(WireGuardGo.wgGetSocketV4(currentTunnelHandle)) protect(WireGuardGo.wgGetSocketV6(currentTunnelHandle)) + // Mark the obfuscator's outbound socket so its packets bypass the VPN. + obfuscator?.let { + if (it.socketV4 >= 0) protect(it.socketV4) + if (it.socketV6 >= 0) protect(it.socketV6) + mObfuscator = it + } + mConfig = json // We don't want to update connection health in several situations: // - If this is an app-caused server switch. @@ -355,6 +390,8 @@ class VPNService : android.net.VpnService() { WireGuardGo.wgTurnOff(currentTunnelHandle) currentTunnelHandle = -1 + mObfuscator?.stop() + mObfuscator = null // If the client is "dead", on a disconnect the // message won't be updated to 'you disconnected from X' // so we should get rid of it. :) @@ -418,14 +455,23 @@ class VPNService : android.net.VpnService() { * Create a Wireguard [Config] from a [json] string - The [json] will be created in * AndroidController.cpp */ - private fun buildWireguardConfig(jServer: JSONObject, obj: JSONObject): Config { + private fun buildWireguardConfig( + jServer: JSONObject, + obj: JSONObject, + obfuscator: Obfuscator? = null, + ): Config { val confBuilder = Config.Builder() val peerBuilder = Peer.Builder() - val ep = + val ep = if (obfuscator != null) { + // Send WireGuard traffic to the local obfuscator instead of the + // real server + InetEndpoint.parse("127.0.0.1:${obfuscator.localPort}") + } else { InetEndpoint.parse( jServer.getString("ipv4AddrIn") + ":" + jServer.getString("port"), ) + } peerBuilder.setEndpoint(ep) peerBuilder.setPublicKey(Key.fromBase64(jServer.getString("publicKey"))) diff --git a/android/obfuscator/build.gradle.kts b/android/obfuscator/build.gradle.kts new file mode 100644 index 0000000000..0e3f5a78a6 --- /dev/null +++ b/android/obfuscator/build.gradle.kts @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +plugins { + id("com.android.library") + id("kotlin-android") +} + +android { + namespace = "org.mozilla.firefox.vpn.obfuscator" + compileSdk = Config.compileSdkVersion + ndkVersion = Config.ndkVersion + + defaultConfig { + minSdk = Config.minSdkVersion + targetSdk = Config.targetSdkVersion + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + } + + buildFeatures { + buildConfig = false + } + + buildTypes { + debug { + isMinifyEnabled = false + } + release { + isMinifyEnabled = false + } + } +} + +dependencies { + implementation("net.java.dev.jna:jna:5.18.1@aar") +} diff --git a/android/obfuscator/src/main/AndroidManifest.xml b/android/obfuscator/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..74b7379f73 --- /dev/null +++ b/android/obfuscator/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/android/obfuscator/src/main/java/org/mozilla/firefox/vpn/obfuscator/Obfuscator.kt b/android/obfuscator/src/main/java/org/mozilla/firefox/vpn/obfuscator/Obfuscator.kt new file mode 100644 index 0000000000..06dd64fecb --- /dev/null +++ b/android/obfuscator/src/main/java/org/mozilla/firefox/vpn/obfuscator/Obfuscator.kt @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.firefox.vpn.obfuscator + +import com.sun.jna.Callback +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure + +/** + * Kotlin wrapper around the `obfuscators` Rust library. + * + * Usage from VPNService: + * val obfuscator = Obfuscator.start( + * method = Obfuscator.Method.UdpOverTcp, + * serverIpv4 = "1.2.3.4", + * serverPort = 51820, + * ... + * ) + * protect(obfuscator.socketV4) + * protect(obfuscator.socketV6) + * // rewrite WG endpoint -> 127.0.0.1:relay.localPort + * obfuscator.stop() + */ +class Obfuscator private constructor( + private val handle: Pointer, + val localPort: Int, + val socketV4: Int, + val socketV6: Int, +) { + @Volatile + private var stopped = false + + fun stop() { + synchronized(this) { + if (stopped) return + stopped = true + LIB.obfuscator_stop(handle) + } + } + + /** Mirrors `ObfuscationMethod` in obfuscators/src/obfuscator.rs. */ + enum class Method(val value: Int) { + NoObfuscation(0), + Lwo(1), + Masque(2), + UdpOverTcp(3), + Shadowsocks(4), + ; + + companion object { + fun fromValue(v: Int): Method? = values().firstOrNull { it.value == v } + } + } + + companion object { + /** Mirrors `ObfuscatorConfig` in obfuscators/src/obfuscator.rs. */ + @Structure.FieldOrder( + "obfuscation_method", + "server_ipv4_addr_in", + "server_ipv6_addr_in", + "server_port", + "listen_port", + "server_public_key", + "public_key", + ) + open class Config : Structure() { + @JvmField var obfuscation_method: Int = 0 + @JvmField var server_ipv4_addr_in: String? = null + @JvmField var server_ipv6_addr_in: String? = null + @JvmField var server_port: Short = 0 + @JvmField var listen_port: Short = 0 + @JvmField var server_public_key: String? = null + @JvmField var public_key: String? = null + + class ByReference : Config(), Structure.ByReference + } + + interface LogCallback : Callback { + fun invoke(level: Int, message: Pointer) + } + + fun interface LogHandler { + fun onLog(level: Int, message: String) + } + + private interface Lib : Library { + fun obfuscator_start(cfg: Config.ByReference): Pointer? + fun obfuscator_local_port(handle: Pointer): Short + fun obfuscator_socket_v4(handle: Pointer): Int + fun obfuscator_socket_v6(handle: Pointer): Int + fun obfuscator_stop(handle: Pointer) + fun obfuscators_set_log_handler(handler: LogCallback) + } + + private val LIB: Lib by lazy { + Native.load("obfuscators", Lib::class.java) + } + + // Avoid log callback being collected by GC + @Volatile + private var logCallback: LogCallback? = null + + // Install the log handler + fun setLogHandler(handler: LogHandler) { + val cb = object : LogCallback { + override fun invoke(level: Int, message: Pointer) { + handler.onLog(level, message.getString(0)) + } + } + logCallback = cb + LIB.obfuscators_set_log_handler(cb) + } + + // Run the obfuscator, returns null on failure + fun start( + method: Method, + serverIpv4: String?, + serverIpv6: String?, + serverPort: Int, + serverPublicKey: String? = null, + publicKey: String? = null, + ): Obfuscator? { + val cfg = Config.ByReference().apply { + this.obfuscation_method = method.value + this.server_ipv4_addr_in = serverIpv4 + this.server_ipv6_addr_in = serverIpv6 + this.server_port = serverPort.toShort() + this.server_public_key = serverPublicKey + this.public_key = publicKey + } + val handle = LIB.obfuscator_start(cfg) ?: return null + val localPort = LIB.obfuscator_local_port(handle).toInt() and 0xFFFF + val socketV4 = LIB.obfuscator_socket_v4(handle) + val socketV6 = LIB.obfuscator_socket_v6(handle) + return Obfuscator(handle, localPort, socketV4, socketV6) + } + } +} diff --git a/android/settings.gradle b/android/settings.gradle index ee1c3f15b5..9500b68f1d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -12,6 +12,7 @@ buildCache { include ':tunnel' include ':daemon' +include ':obfuscator' include ':ClientCommon' include ':vpnClient' include ':common' diff --git a/env-android.yml b/env-android.yml index df8a86ffbf..d768c43402 100644 --- a/env-android.yml +++ b/env-android.yml @@ -5,12 +5,12 @@ dependencies: - ccache=4.10.1 - python=3.12 - pip=25.0 - - rust=1.85 + - rust=1.91 - go=1.24.5 - - rust-std-armv7-linux-androideabi=1.85 - - rust-std-x86_64-linux-android=1.85 - - rust-std-i686-linux-android=1.85 - - rust-std-aarch64-linux-android=1.85 + - rust-std-armv7-linux-androideabi=1.91 + - rust-std-x86_64-linux-android=1.91 + - rust-std-i686-linux-android=1.91 + - rust-std-aarch64-linux-android=1.91 - cmake=4.2.1 - ninja=1.11.0 - conda-forge::openjdk=17 diff --git a/env-apple.yml b/env-apple.yml index 8d891acbbe..f2856dd378 100644 --- a/env-apple.yml +++ b/env-apple.yml @@ -11,11 +11,11 @@ dependencies: - python=3.12 - nodejs=20.* - pip=25.0 - - rust=1.85 - - rust-std-aarch64-apple-darwin=1.85 - - rust-std-x86_64-apple-darwin=1.85 - - rust-std-aarch64-apple-ios=1.85 - - rust-std-x86_64-apple-ios=1.85 + - rust=1.91 + - rust-std-aarch64-apple-darwin=1.91 + - rust-std-x86_64-apple-darwin=1.91 + - rust-std-aarch64-apple-ios=1.91 + - rust-std-x86_64-apple-ios=1.91 - go=1.23 - compiler-rt=20.1.8 - compiler-rt_osx-64 diff --git a/env-wasm.yml b/env-wasm.yml index d620354dd3..1e94cb30e7 100644 --- a/env-wasm.yml +++ b/env-wasm.yml @@ -5,8 +5,8 @@ dependencies: - python=3.12 - nodejs=20.* - pip=25.0 - - rust=1.85 - - rust-std-wasm32-unknown-emscripten=1.85 + - rust=1.91 + - rust-std-wasm32-unknown-emscripten=1.91 - cmake=3.26.3 - ninja=1.11.0 - pip: diff --git a/env-windows.yml b/env-windows.yml index ddceb8e168..a042b3b718 100644 --- a/env-windows.yml +++ b/env-windows.yml @@ -11,9 +11,9 @@ dependencies: - python=3.12 - nodejs=20.* - pip=25.0 - - rust=1.85.0 - - rust-std-aarch64-pc-windows-msvc=1.85.0 - - rust-std-x86_64-pc-windows-msvc=1.85.0 + - rust=1.91.0 + - rust-std-aarch64-pc-windows-msvc=1.91.0 + - rust-std-x86_64-pc-windows-msvc=1.91.0 - compiler-rt=20.1.8 - compiler-rt_win-64 - cmake=4.2.1 diff --git a/linux/debian/control b/linux/debian/control index 6c4f138ce2..ac8105f966 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 13), gcc:native (>=4:8.0.0~), g++:native (>=4:8.0.0~), golang:native (>=2:1.18~), - cargo:native (>=1.85) | cargo-web:native (>=1.85) | cargo-1.85:native, + cargo:native (>=1.91) | cargo-web:native (>=1.91) | cargo-1.91:native, python3-yaml:native, python3-jinja2:native, python3-click:native, diff --git a/linux/debian/mozillavpn.install b/linux/debian/mozillavpn.install index f45631bc69..91900cdff8 100644 --- a/linux/debian/mozillavpn.install +++ b/linux/debian/mozillavpn.install @@ -4,6 +4,7 @@ etc/opt/chrome/native-messaging-hosts/mozillavpn.json ${env:SYSTEMD_UNIT_PATH}/mozillavpn.service ${env:SYSTEMD_UNIT_PATH}/socksproxy.service usr/bin/mozillavpn +usr/bin/mozillavpn-obfuscator usr/bin/socksproxy lib/mozilla/native-messaging-hosts/mozillavpn.json usr/share/applications/org.mozilla.vpn.desktop diff --git a/linux/flatpak/flatpak-vpn-crates.json b/linux/flatpak/flatpak-vpn-crates.json index 1d38433ea8..774454d006 100644 --- a/linux/flatpak/flatpak-vpn-crates.json +++ b/linux/flatpak/flatpak-vpn-crates.json @@ -1,68 +1,9 @@ [ { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/adler2/adler2-2.0.1.crate", - "sha256": "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa", - "dest": "cargo/vendor/adler2-2.0.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa\", \"files\": {}}", - "dest": "cargo/vendor/adler2-2.0.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/aho-corasick/aho-corasick-1.1.4.crate", - "sha256": "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301", - "dest": "cargo/vendor/aho-corasick-1.1.4" - }, - { - "type": "inline", - "contents": "{\"package\": \"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301\", \"files\": {}}", - "dest": "cargo/vendor/aho-corasick-1.1.4", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/android_log-sys/android_log-sys-0.2.0.crate", - "sha256": "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e", - "dest": "cargo/vendor/android_log-sys-0.2.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e\", \"files\": {}}", - "dest": "cargo/vendor/android_log-sys-0.2.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/android_logger/android_logger-0.12.0.crate", - "sha256": "037f3e1da32ddba7770530e69258b742c15ad67bdf90e5f6b35f4b6db9a60eb7", - "dest": "cargo/vendor/android_logger-0.12.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"037f3e1da32ddba7770530e69258b742c15ad67bdf90e5f6b35f4b6db9a60eb7\", \"files\": {}}", - "dest": "cargo/vendor/android_logger-0.12.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/android_system_properties/android_system_properties-0.1.5.crate", - "sha256": "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311", - "dest": "cargo/vendor/android_system_properties-0.1.5" - }, - { - "type": "inline", - "contents": "{\"package\": \"819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311\", \"files\": {}}", - "dest": "cargo/vendor/android_system_properties-0.1.5", - "dest-filename": ".cargo-checksum.json" + "type": "git", + "url": "https://github.com/mullvad/udp-over-tcp", + "commit": "5c6d8f44a5aa12ed9bb4ae51dd17e5e22e5ec303", + "dest": "flatpak-cargo/git/udp-over-tcp-5c6d8f4" }, { "type": "archive", @@ -80,14 +21,14 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/anstyle/anstyle-1.0.13.crate", - "sha256": "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78", - "dest": "cargo/vendor/anstyle-1.0.13" + "url": "https://static.crates.io/crates/anstyle/anstyle-1.0.14.crate", + "sha256": "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000", + "dest": "cargo/vendor/anstyle-1.0.14" }, { "type": "inline", - "contents": "{\"package\": \"5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78\", \"files\": {}}", - "dest": "cargo/vendor/anstyle-1.0.13", + "contents": "{\"package\": \"940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000\", \"files\": {}}", + "dest": "cargo/vendor/anstyle-1.0.14", "dest-filename": ".cargo-checksum.json" }, { @@ -132,2107 +73,1085 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/anyhow/anyhow-1.0.100.crate", - "sha256": "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61", - "dest": "cargo/vendor/anyhow-1.0.100" - }, - { - "type": "inline", - "contents": "{\"package\": \"a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61\", \"files\": {}}", - "dest": "cargo/vendor/anyhow-1.0.100", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/arrayref/arrayref-0.3.9.crate", - "sha256": "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb", - "dest": "cargo/vendor/arrayref-0.3.9" - }, - { - "type": "inline", - "contents": "{\"package\": \"76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb\", \"files\": {}}", - "dest": "cargo/vendor/arrayref-0.3.9", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/askama/askama-0.13.1.crate", - "sha256": "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7", - "dest": "cargo/vendor/askama-0.13.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7\", \"files\": {}}", - "dest": "cargo/vendor/askama-0.13.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/askama_derive/askama_derive-0.13.1.crate", - "sha256": "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac", - "dest": "cargo/vendor/askama_derive-0.13.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac\", \"files\": {}}", - "dest": "cargo/vendor/askama_derive-0.13.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/askama_parser/askama_parser-0.13.0.crate", - "sha256": "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f", - "dest": "cargo/vendor/askama_parser-0.13.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f\", \"files\": {}}", - "dest": "cargo/vendor/askama_parser-0.13.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/asn1-rs/asn1-rs-0.7.1.crate", - "sha256": "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60", - "dest": "cargo/vendor/asn1-rs-0.7.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60\", \"files\": {}}", - "dest": "cargo/vendor/asn1-rs-0.7.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/asn1-rs-derive/asn1-rs-derive-0.6.0.crate", - "sha256": "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c", - "dest": "cargo/vendor/asn1-rs-derive-0.6.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c\", \"files\": {}}", - "dest": "cargo/vendor/asn1-rs-derive-0.6.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/asn1-rs-impl/asn1-rs-impl-0.2.0.crate", - "sha256": "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7", - "dest": "cargo/vendor/asn1-rs-impl-0.2.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7\", \"files\": {}}", - "dest": "cargo/vendor/asn1-rs-impl-0.2.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/autocfg/autocfg-1.5.0.crate", - "sha256": "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8", - "dest": "cargo/vendor/autocfg-1.5.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\", \"files\": {}}", - "dest": "cargo/vendor/autocfg-1.5.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/basic-toml/basic-toml-0.1.10.crate", - "sha256": "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a", - "dest": "cargo/vendor/basic-toml-0.1.10" - }, - { - "type": "inline", - "contents": "{\"package\": \"ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a\", \"files\": {}}", - "dest": "cargo/vendor/basic-toml-0.1.10", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/bincode/bincode-1.3.3.crate", - "sha256": "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad", - "dest": "cargo/vendor/bincode-1.3.3" + "url": "https://static.crates.io/crates/asn1-rs/asn1-rs-0.7.2.crate", + "sha256": "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8", + "dest": "cargo/vendor/asn1-rs-0.7.2" }, { "type": "inline", - "contents": "{\"package\": \"b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad\", \"files\": {}}", - "dest": "cargo/vendor/bincode-1.3.3", + "contents": "{\"package\": \"b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8\", \"files\": {}}", + "dest": "cargo/vendor/asn1-rs-0.7.2", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/bitflags/bitflags-2.10.0.crate", - "sha256": "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3", - "dest": "cargo/vendor/bitflags-2.10.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3\", \"files\": {}}", - "dest": "cargo/vendor/bitflags-2.10.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/bumpalo/bumpalo-3.19.1.crate", - "sha256": "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510", - "dest": "cargo/vendor/bumpalo-3.19.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510\", \"files\": {}}", - "dest": "cargo/vendor/bumpalo-3.19.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/bytes/bytes-1.11.0.crate", - "sha256": "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3", - "dest": "cargo/vendor/bytes-1.11.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3\", \"files\": {}}", - "dest": "cargo/vendor/bytes-1.11.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/camino/camino-1.2.2.crate", - "sha256": "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48", - "dest": "cargo/vendor/camino-1.2.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48\", \"files\": {}}", - "dest": "cargo/vendor/camino-1.2.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/cbindgen/cbindgen-0.29.2.crate", - "sha256": "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799", - "dest": "cargo/vendor/cbindgen-0.29.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799\", \"files\": {}}", - "dest": "cargo/vendor/cbindgen-0.29.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/cc/cc-1.2.49.crate", - "sha256": "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215", - "dest": "cargo/vendor/cc-1.2.49" - }, - { - "type": "inline", - "contents": "{\"package\": \"90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215\", \"files\": {}}", - "dest": "cargo/vendor/cc-1.2.49", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/cfg-if/cfg-if-1.0.4.crate", - "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801", - "dest": "cargo/vendor/cfg-if-1.0.4" - }, - { - "type": "inline", - "contents": "{\"package\": \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\", \"files\": {}}", - "dest": "cargo/vendor/cfg-if-1.0.4", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/chrono/chrono-0.4.42.crate", - "sha256": "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2", - "dest": "cargo/vendor/chrono-0.4.42" - }, - { - "type": "inline", - "contents": "{\"package\": \"145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2\", \"files\": {}}", - "dest": "cargo/vendor/chrono-0.4.42", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap/clap-4.5.53.crate", - "sha256": "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8", - "dest": "cargo/vendor/clap-4.5.53" - }, - { - "type": "inline", - "contents": "{\"package\": \"c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8\", \"files\": {}}", - "dest": "cargo/vendor/clap-4.5.53", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap_builder/clap_builder-4.5.53.crate", - "sha256": "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00", - "dest": "cargo/vendor/clap_builder-4.5.53" - }, - { - "type": "inline", - "contents": "{\"package\": \"d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00\", \"files\": {}}", - "dest": "cargo/vendor/clap_builder-4.5.53", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/clap_lex/clap_lex-0.7.6.crate", - "sha256": "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d", - "dest": "cargo/vendor/clap_lex-0.7.6" - }, - { - "type": "inline", - "contents": "{\"package\": \"a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d\", \"files\": {}}", - "dest": "cargo/vendor/clap_lex-0.7.6", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/colorchoice/colorchoice-1.0.4.crate", - "sha256": "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75", - "dest": "cargo/vendor/colorchoice-1.0.4" - }, - { - "type": "inline", - "contents": "{\"package\": \"b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75\", \"files\": {}}", - "dest": "cargo/vendor/colorchoice-1.0.4", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/core-foundation-sys/core-foundation-sys-0.8.7.crate", - "sha256": "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b", - "dest": "cargo/vendor/core-foundation-sys-0.8.7" - }, - { - "type": "inline", - "contents": "{\"package\": \"773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b\", \"files\": {}}", - "dest": "cargo/vendor/core-foundation-sys-0.8.7", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/crc32fast/crc32fast-1.5.0.crate", - "sha256": "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511", - "dest": "cargo/vendor/crc32fast-1.5.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511\", \"files\": {}}", - "dest": "cargo/vendor/crc32fast-1.5.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/crossbeam-channel/crossbeam-channel-0.5.15.crate", - "sha256": "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2", - "dest": "cargo/vendor/crossbeam-channel-0.5.15" - }, - { - "type": "inline", - "contents": "{\"package\": \"82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2\", \"files\": {}}", - "dest": "cargo/vendor/crossbeam-channel-0.5.15", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/crossbeam-utils/crossbeam-utils-0.8.21.crate", - "sha256": "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28", - "dest": "cargo/vendor/crossbeam-utils-0.8.21" - }, - { - "type": "inline", - "contents": "{\"package\": \"d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28\", \"files\": {}}", - "dest": "cargo/vendor/crossbeam-utils-0.8.21", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ctor/ctor-0.2.9.crate", - "sha256": "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501", - "dest": "cargo/vendor/ctor-0.2.9" - }, - { - "type": "inline", - "contents": "{\"package\": \"32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501\", \"files\": {}}", - "dest": "cargo/vendor/ctor-0.2.9", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/dashmap/dashmap-4.0.2.crate", - "sha256": "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c", - "dest": "cargo/vendor/dashmap-4.0.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c\", \"files\": {}}", - "dest": "cargo/vendor/dashmap-4.0.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/data-encoding/data-encoding-2.10.0.crate", - "sha256": "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea", - "dest": "cargo/vendor/data-encoding-2.10.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea\", \"files\": {}}", - "dest": "cargo/vendor/data-encoding-2.10.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/der-parser/der-parser-10.0.0.crate", - "sha256": "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6", - "dest": "cargo/vendor/der-parser-10.0.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6\", \"files\": {}}", - "dest": "cargo/vendor/der-parser-10.0.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/deranged/deranged-0.5.5.crate", - "sha256": "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587", - "dest": "cargo/vendor/deranged-0.5.5" - }, - { - "type": "inline", - "contents": "{\"package\": \"ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587\", \"files\": {}}", - "dest": "cargo/vendor/deranged-0.5.5", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/displaydoc/displaydoc-0.2.5.crate", - "sha256": "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0", - "dest": "cargo/vendor/displaydoc-0.2.5" - }, - { - "type": "inline", - "contents": "{\"package\": \"97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0\", \"files\": {}}", - "dest": "cargo/vendor/displaydoc-0.2.5", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/either/either-1.15.0.crate", - "sha256": "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719", - "dest": "cargo/vendor/either-1.15.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\", \"files\": {}}", - "dest": "cargo/vendor/either-1.15.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/env_logger/env_logger-0.10.2.crate", - "sha256": "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580", - "dest": "cargo/vendor/env_logger-0.10.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580\", \"files\": {}}", - "dest": "cargo/vendor/env_logger-0.10.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/equivalent/equivalent-1.0.2.crate", - "sha256": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f", - "dest": "cargo/vendor/equivalent-1.0.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\", \"files\": {}}", - "dest": "cargo/vendor/equivalent-1.0.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/errno/errno-0.3.14.crate", - "sha256": "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb", - "dest": "cargo/vendor/errno-0.3.14" - }, - { - "type": "inline", - "contents": "{\"package\": \"39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb\", \"files\": {}}", - "dest": "cargo/vendor/errno-0.3.14", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/fastrand/fastrand-2.3.0.crate", - "sha256": "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be", - "dest": "cargo/vendor/fastrand-2.3.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be\", \"files\": {}}", - "dest": "cargo/vendor/fastrand-2.3.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ffi-support/ffi-support-0.4.4.crate", - "sha256": "27838c6815cfe9de2d3aeb145ffd19e565f577414b33f3bdbf42fe040e9e0ff6", - "dest": "cargo/vendor/ffi-support-0.4.4" - }, - { - "type": "inline", - "contents": "{\"package\": \"27838c6815cfe9de2d3aeb145ffd19e565f577414b33f3bdbf42fe040e9e0ff6\", \"files\": {}}", - "dest": "cargo/vendor/ffi-support-0.4.4", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.5.crate", - "sha256": "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844", - "dest": "cargo/vendor/find-msvc-tools-0.1.5" - }, - { - "type": "inline", - "contents": "{\"package\": \"3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844\", \"files\": {}}", - "dest": "cargo/vendor/find-msvc-tools-0.1.5", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/flate2/flate2-1.1.5.crate", - "sha256": "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb", - "dest": "cargo/vendor/flate2-1.1.5" - }, - { - "type": "inline", - "contents": "{\"package\": \"bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb\", \"files\": {}}", - "dest": "cargo/vendor/flate2-1.1.5", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/form_urlencoded/form_urlencoded-1.2.2.crate", - "sha256": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf", - "dest": "cargo/vendor/form_urlencoded-1.2.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf\", \"files\": {}}", - "dest": "cargo/vendor/form_urlencoded-1.2.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/fs-err/fs-err-2.11.0.crate", - "sha256": "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41", - "dest": "cargo/vendor/fs-err-2.11.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41\", \"files\": {}}", - "dest": "cargo/vendor/fs-err-2.11.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/getrandom/getrandom-0.2.16.crate", - "sha256": "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592", - "dest": "cargo/vendor/getrandom-0.2.16" - }, - { - "type": "inline", - "contents": "{\"package\": \"335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592\", \"files\": {}}", - "dest": "cargo/vendor/getrandom-0.2.16", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/getrandom/getrandom-0.3.4.crate", - "sha256": "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd", - "dest": "cargo/vendor/getrandom-0.3.4" - }, - { - "type": "inline", - "contents": "{\"package\": \"899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd\", \"files\": {}}", - "dest": "cargo/vendor/getrandom-0.3.4", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/glob/glob-0.3.3.crate", - "sha256": "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280", - "dest": "cargo/vendor/glob-0.3.3" - }, - { - "type": "inline", - "contents": "{\"package\": \"0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280\", \"files\": {}}", - "dest": "cargo/vendor/glob-0.3.3", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/goblin/goblin-0.8.2.crate", - "sha256": "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47", - "dest": "cargo/vendor/goblin-0.8.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47\", \"files\": {}}", - "dest": "cargo/vendor/goblin-0.8.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/hashbrown/hashbrown-0.16.1.crate", - "sha256": "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100", - "dest": "cargo/vendor/hashbrown-0.16.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100\", \"files\": {}}", - "dest": "cargo/vendor/hashbrown-0.16.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/heck/heck-0.5.0.crate", - "sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea", - "dest": "cargo/vendor/heck-0.5.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\", \"files\": {}}", - "dest": "cargo/vendor/heck-0.5.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/hermit-abi/hermit-abi-0.5.2.crate", - "sha256": "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c", - "dest": "cargo/vendor/hermit-abi-0.5.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c\", \"files\": {}}", - "dest": "cargo/vendor/hermit-abi-0.5.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/hex/hex-0.4.3.crate", - "sha256": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70", - "dest": "cargo/vendor/hex-0.4.3" - }, - { - "type": "inline", - "contents": "{\"package\": \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\", \"files\": {}}", - "dest": "cargo/vendor/hex-0.4.3", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/humantime/humantime-2.3.0.crate", - "sha256": "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424", - "dest": "cargo/vendor/humantime-2.3.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424\", \"files\": {}}", - "dest": "cargo/vendor/humantime-2.3.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/iana-time-zone/iana-time-zone-0.1.64.crate", - "sha256": "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb", - "dest": "cargo/vendor/iana-time-zone-0.1.64" - }, - { - "type": "inline", - "contents": "{\"package\": \"33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb\", \"files\": {}}", - "dest": "cargo/vendor/iana-time-zone-0.1.64", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/iana-time-zone-haiku/iana-time-zone-haiku-0.1.2.crate", - "sha256": "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f", - "dest": "cargo/vendor/iana-time-zone-haiku-0.1.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f\", \"files\": {}}", - "dest": "cargo/vendor/iana-time-zone-haiku-0.1.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/id-arena/id-arena-2.2.1.crate", - "sha256": "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005", - "dest": "cargo/vendor/id-arena-2.2.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005\", \"files\": {}}", - "dest": "cargo/vendor/id-arena-2.2.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/idna/idna-0.4.0.crate", - "sha256": "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c", - "dest": "cargo/vendor/idna-0.4.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c\", \"files\": {}}", - "dest": "cargo/vendor/idna-0.4.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/indexmap/indexmap-2.12.1.crate", - "sha256": "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2", - "dest": "cargo/vendor/indexmap-2.12.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2\", \"files\": {}}", - "dest": "cargo/vendor/indexmap-2.12.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/inherent/inherent-1.0.13.crate", - "sha256": "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7", - "dest": "cargo/vendor/inherent-1.0.13" - }, - { - "type": "inline", - "contents": "{\"package\": \"c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7\", \"files\": {}}", - "dest": "cargo/vendor/inherent-1.0.13", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/iri-string/iri-string-0.5.6.crate", - "sha256": "bf071934ee7ee97e52fa1868a9540a7885eab75926bd70794030304a9797cea1", - "dest": "cargo/vendor/iri-string-0.5.6" - }, - { - "type": "inline", - "contents": "{\"package\": \"bf071934ee7ee97e52fa1868a9540a7885eab75926bd70794030304a9797cea1\", \"files\": {}}", - "dest": "cargo/vendor/iri-string-0.5.6", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/is_terminal_polyfill/is_terminal_polyfill-1.70.2.crate", - "sha256": "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695", - "dest": "cargo/vendor/is_terminal_polyfill-1.70.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\", \"files\": {}}", - "dest": "cargo/vendor/is_terminal_polyfill-1.70.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/iso8601/iso8601-0.4.2.crate", - "sha256": "e5b94fbeb759754d87e1daea745bc8efd3037cd16980331fe1d1524c9a79ce96", - "dest": "cargo/vendor/iso8601-0.4.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"e5b94fbeb759754d87e1daea745bc8efd3037cd16980331fe1d1524c9a79ce96\", \"files\": {}}", - "dest": "cargo/vendor/iso8601-0.4.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/itertools/itertools-0.10.5.crate", - "sha256": "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473", - "dest": "cargo/vendor/itertools-0.10.5" - }, - { - "type": "inline", - "contents": "{\"package\": \"b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473\", \"files\": {}}", - "dest": "cargo/vendor/itertools-0.10.5", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/itoa/itoa-1.0.15.crate", - "sha256": "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c", - "dest": "cargo/vendor/itoa-1.0.15" - }, - { - "type": "inline", - "contents": "{\"package\": \"4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c\", \"files\": {}}", - "dest": "cargo/vendor/itoa-1.0.15", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/js-sys/js-sys-0.3.83.crate", - "sha256": "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8", - "dest": "cargo/vendor/js-sys-0.3.83" - }, - { - "type": "inline", - "contents": "{\"package\": \"464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8\", \"files\": {}}", - "dest": "cargo/vendor/js-sys-0.3.83", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/json-pointer/json-pointer-0.3.4.crate", - "sha256": "5fe841b94e719a482213cee19dd04927cf412f26d8dc84c5a446c081e49c2997", - "dest": "cargo/vendor/json-pointer-0.3.4" - }, - { - "type": "inline", - "contents": "{\"package\": \"5fe841b94e719a482213cee19dd04927cf412f26d8dc84c5a446c081e49c2997\", \"files\": {}}", - "dest": "cargo/vendor/json-pointer-0.3.4", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/jsonschema-valid/jsonschema-valid-0.5.2.crate", - "sha256": "998c0b6acd4e20747af58157c9d55878970f546088e17c9870f4b41bc8a032a3", - "dest": "cargo/vendor/jsonschema-valid-0.5.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"998c0b6acd4e20747af58157c9d55878970f546088e17c9870f4b41bc8a032a3\", \"files\": {}}", - "dest": "cargo/vendor/jsonschema-valid-0.5.2", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/lazy_static/lazy_static-1.5.0.crate", - "sha256": "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe", - "dest": "cargo/vendor/lazy_static-1.5.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe\", \"files\": {}}", - "dest": "cargo/vendor/lazy_static-1.5.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/libc/libc-0.2.178.crate", - "sha256": "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091", - "dest": "cargo/vendor/libc-0.2.178" - }, - { - "type": "inline", - "contents": "{\"package\": \"37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091\", \"files\": {}}", - "dest": "cargo/vendor/libc-0.2.178", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/linux-raw-sys/linux-raw-sys-0.11.0.crate", - "sha256": "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039", - "dest": "cargo/vendor/linux-raw-sys-0.11.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039\", \"files\": {}}", - "dest": "cargo/vendor/linux-raw-sys-0.11.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/log/log-0.4.29.crate", - "sha256": "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897", - "dest": "cargo/vendor/log-0.4.29" - }, - { - "type": "inline", - "contents": "{\"package\": \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\", \"files\": {}}", - "dest": "cargo/vendor/log-0.4.29", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/malloc_size_of_derive/malloc_size_of_derive-0.1.3.crate", - "sha256": "f44db74bde26fdf427af23f1d146c211aed857c59e3be750cf2617f6b0b05c94", - "dest": "cargo/vendor/malloc_size_of_derive-0.1.3" - }, - { - "type": "inline", - "contents": "{\"package\": \"f44db74bde26fdf427af23f1d146c211aed857c59e3be750cf2617f6b0b05c94\", \"files\": {}}", - "dest": "cargo/vendor/malloc_size_of_derive-0.1.3", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/memchr/memchr-2.7.6.crate", - "sha256": "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273", - "dest": "cargo/vendor/memchr-2.7.6" - }, - { - "type": "inline", - "contents": "{\"package\": \"f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273\", \"files\": {}}", - "dest": "cargo/vendor/memchr-2.7.6", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/minimal-lexical/minimal-lexical-0.2.1.crate", - "sha256": "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a", - "dest": "cargo/vendor/minimal-lexical-0.2.1" - }, - { - "type": "inline", - "contents": "{\"package\": \"68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a\", \"files\": {}}", - "dest": "cargo/vendor/minimal-lexical-0.2.1", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/miniz_oxide/miniz_oxide-0.8.9.crate", - "sha256": "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316", - "dest": "cargo/vendor/miniz_oxide-0.8.9" - }, - { - "type": "inline", - "contents": "{\"package\": \"1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316\", \"files\": {}}", - "dest": "cargo/vendor/miniz_oxide-0.8.9", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/nom/nom-7.1.3.crate", - "sha256": "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a", - "dest": "cargo/vendor/nom-7.1.3" - }, - { - "type": "inline", - "contents": "{\"package\": \"d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a\", \"files\": {}}", - "dest": "cargo/vendor/nom-7.1.3", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/num-bigint/num-bigint-0.4.6.crate", - "sha256": "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9", - "dest": "cargo/vendor/num-bigint-0.4.6" - }, - { - "type": "inline", - "contents": "{\"package\": \"a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9\", \"files\": {}}", - "dest": "cargo/vendor/num-bigint-0.4.6", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/num-conv/num-conv-0.1.0.crate", - "sha256": "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9", - "dest": "cargo/vendor/num-conv-0.1.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9\", \"files\": {}}", - "dest": "cargo/vendor/num-conv-0.1.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/num-integer/num-integer-0.1.46.crate", - "sha256": "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f", - "dest": "cargo/vendor/num-integer-0.1.46" - }, - { - "type": "inline", - "contents": "{\"package\": \"7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f\", \"files\": {}}", - "dest": "cargo/vendor/num-integer-0.1.46", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/num-traits/num-traits-0.2.19.crate", - "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841", - "dest": "cargo/vendor/num-traits-0.2.19" - }, - { - "type": "inline", - "contents": "{\"package\": \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\", \"files\": {}}", - "dest": "cargo/vendor/num-traits-0.2.19", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/num_cpus/num_cpus-1.17.0.crate", - "sha256": "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b", - "dest": "cargo/vendor/num_cpus-1.17.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b\", \"files\": {}}", - "dest": "cargo/vendor/num_cpus-1.17.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/oid-registry/oid-registry-0.8.1.crate", - "sha256": "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7", - "dest": "cargo/vendor/oid-registry-0.8.1" + "url": "https://static.crates.io/crates/asn1-rs-derive/asn1-rs-derive-0.6.0.crate", + "sha256": "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c", + "dest": "cargo/vendor/asn1-rs-derive-0.6.0" }, { "type": "inline", - "contents": "{\"package\": \"12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7\", \"files\": {}}", - "dest": "cargo/vendor/oid-registry-0.8.1", + "contents": "{\"package\": \"3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c\", \"files\": {}}", + "dest": "cargo/vendor/asn1-rs-derive-0.6.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/once_cell/once_cell-1.21.4.crate", - "sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50", - "dest": "cargo/vendor/once_cell-1.21.4" + "url": "https://static.crates.io/crates/asn1-rs-impl/asn1-rs-impl-0.2.0.crate", + "sha256": "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7", + "dest": "cargo/vendor/asn1-rs-impl-0.2.0" }, { "type": "inline", - "contents": "{\"package\": \"9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50\", \"files\": {}}", - "dest": "cargo/vendor/once_cell-1.21.4", + "contents": "{\"package\": \"7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7\", \"files\": {}}", + "dest": "cargo/vendor/asn1-rs-impl-0.2.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/once_cell_polyfill/once_cell_polyfill-1.70.2.crate", - "sha256": "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe", - "dest": "cargo/vendor/once_cell_polyfill-1.70.2" + "url": "https://static.crates.io/crates/autocfg/autocfg-1.5.1.crate", + "sha256": "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53", + "dest": "cargo/vendor/autocfg-1.5.1" }, { "type": "inline", - "contents": "{\"package\": \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\", \"files\": {}}", - "dest": "cargo/vendor/once_cell_polyfill-1.70.2", + "contents": "{\"package\": \"f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53\", \"files\": {}}", + "dest": "cargo/vendor/autocfg-1.5.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ordered-float/ordered-float-3.9.2.crate", - "sha256": "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc", - "dest": "cargo/vendor/ordered-float-3.9.2" + "url": "https://static.crates.io/crates/base64/base64-0.22.1.crate", + "sha256": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6", + "dest": "cargo/vendor/base64-0.22.1" }, { "type": "inline", - "contents": "{\"package\": \"f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc\", \"files\": {}}", - "dest": "cargo/vendor/ordered-float-3.9.2", + "contents": "{\"package\": \"72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6\", \"files\": {}}", + "dest": "cargo/vendor/base64-0.22.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/oslog/oslog-0.1.0.crate", - "sha256": "8343ce955f18e7e68c0207dd0ea776ec453035685395ababd2ea651c569728b3", - "dest": "cargo/vendor/oslog-0.1.0" + "url": "https://static.crates.io/crates/bitflags/bitflags-2.11.1.crate", + "sha256": "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3", + "dest": "cargo/vendor/bitflags-2.11.1" }, { "type": "inline", - "contents": "{\"package\": \"8343ce955f18e7e68c0207dd0ea776ec453035685395ababd2ea651c569728b3\", \"files\": {}}", - "dest": "cargo/vendor/oslog-0.1.0", + "contents": "{\"package\": \"c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3\", \"files\": {}}", + "dest": "cargo/vendor/bitflags-2.11.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/percent-encoding/percent-encoding-2.3.2.crate", - "sha256": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220", - "dest": "cargo/vendor/percent-encoding-2.3.2" + "url": "https://static.crates.io/crates/bytes/bytes-1.11.1.crate", + "sha256": "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33", + "dest": "cargo/vendor/bytes-1.11.1" }, { "type": "inline", - "contents": "{\"package\": \"9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220\", \"files\": {}}", - "dest": "cargo/vendor/percent-encoding-2.3.2", + "contents": "{\"package\": \"1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33\", \"files\": {}}", + "dest": "cargo/vendor/bytes-1.11.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/plain/plain-0.2.3.crate", - "sha256": "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6", - "dest": "cargo/vendor/plain-0.2.3" + "url": "https://static.crates.io/crates/cc/cc-1.2.63.crate", + "sha256": "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f", + "dest": "cargo/vendor/cc-1.2.63" }, { "type": "inline", - "contents": "{\"package\": \"b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6\", \"files\": {}}", - "dest": "cargo/vendor/plain-0.2.3", + "contents": "{\"package\": \"556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f\", \"files\": {}}", + "dest": "cargo/vendor/cc-1.2.63", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/powerfmt/powerfmt-0.2.0.crate", - "sha256": "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391", - "dest": "cargo/vendor/powerfmt-0.2.0" + "url": "https://static.crates.io/crates/cfg-if/cfg-if-1.0.4.crate", + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801", + "dest": "cargo/vendor/cfg-if-1.0.4" }, { "type": "inline", - "contents": "{\"package\": \"439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391\", \"files\": {}}", - "dest": "cargo/vendor/powerfmt-0.2.0", + "contents": "{\"package\": \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\", \"files\": {}}", + "dest": "cargo/vendor/cfg-if-1.0.4", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.103.crate", - "sha256": "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8", - "dest": "cargo/vendor/proc-macro2-1.0.103" + "url": "https://static.crates.io/crates/cfg_aliases/cfg_aliases-0.2.1.crate", + "sha256": "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724", + "dest": "cargo/vendor/cfg_aliases-0.2.1" }, { "type": "inline", - "contents": "{\"package\": \"5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8\", \"files\": {}}", - "dest": "cargo/vendor/proc-macro2-1.0.103", + "contents": "{\"package\": \"613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724\", \"files\": {}}", + "dest": "cargo/vendor/cfg_aliases-0.2.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/quote/quote-1.0.42.crate", - "sha256": "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f", - "dest": "cargo/vendor/quote-1.0.42" + "url": "https://static.crates.io/crates/clap/clap-4.5.20.crate", + "sha256": "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8", + "dest": "cargo/vendor/clap-4.5.20" }, { "type": "inline", - "contents": "{\"package\": \"a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f\", \"files\": {}}", - "dest": "cargo/vendor/quote-1.0.42", + "contents": "{\"package\": \"b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8\", \"files\": {}}", + "dest": "cargo/vendor/clap-4.5.20", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/r-efi/r-efi-5.3.0.crate", - "sha256": "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f", - "dest": "cargo/vendor/r-efi-5.3.0" + "url": "https://static.crates.io/crates/clap_builder/clap_builder-4.5.20.crate", + "sha256": "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54", + "dest": "cargo/vendor/clap_builder-4.5.20" }, { "type": "inline", - "contents": "{\"package\": \"69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f\", \"files\": {}}", - "dest": "cargo/vendor/r-efi-5.3.0", + "contents": "{\"package\": \"19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54\", \"files\": {}}", + "dest": "cargo/vendor/clap_builder-4.5.20", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/regex/regex-1.12.2.crate", - "sha256": "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4", - "dest": "cargo/vendor/regex-1.12.2" + "url": "https://static.crates.io/crates/clap_derive/clap_derive-4.5.18.crate", + "sha256": "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab", + "dest": "cargo/vendor/clap_derive-4.5.18" }, { "type": "inline", - "contents": "{\"package\": \"843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4\", \"files\": {}}", - "dest": "cargo/vendor/regex-1.12.2", + "contents": "{\"package\": \"4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab\", \"files\": {}}", + "dest": "cargo/vendor/clap_derive-4.5.18", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/regex-automata/regex-automata-0.4.13.crate", - "sha256": "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c", - "dest": "cargo/vendor/regex-automata-0.4.13" + "url": "https://static.crates.io/crates/clap_lex/clap_lex-0.7.7.crate", + "sha256": "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32", + "dest": "cargo/vendor/clap_lex-0.7.7" }, { "type": "inline", - "contents": "{\"package\": \"5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c\", \"files\": {}}", - "dest": "cargo/vendor/regex-automata-0.4.13", + "contents": "{\"package\": \"c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32\", \"files\": {}}", + "dest": "cargo/vendor/clap_lex-0.7.7", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/regex-syntax/regex-syntax-0.8.8.crate", - "sha256": "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58", - "dest": "cargo/vendor/regex-syntax-0.8.8" + "url": "https://static.crates.io/crates/colorchoice/colorchoice-1.0.5.crate", + "sha256": "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570", + "dest": "cargo/vendor/colorchoice-1.0.5" }, { "type": "inline", - "contents": "{\"package\": \"7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58\", \"files\": {}}", - "dest": "cargo/vendor/regex-syntax-0.8.8", + "contents": "{\"package\": \"1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570\", \"files\": {}}", + "dest": "cargo/vendor/colorchoice-1.0.5", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/ring/ring-0.17.14.crate", - "sha256": "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7", - "dest": "cargo/vendor/ring-0.17.14" + "url": "https://static.crates.io/crates/data-encoding/data-encoding-2.11.0.crate", + "sha256": "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8", + "dest": "cargo/vendor/data-encoding-2.11.0" }, { "type": "inline", - "contents": "{\"package\": \"a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7\", \"files\": {}}", - "dest": "cargo/vendor/ring-0.17.14", + "contents": "{\"package\": \"a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8\", \"files\": {}}", + "dest": "cargo/vendor/data-encoding-2.11.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/rkv/rkv-0.20.0.crate", - "sha256": "0f67a9dbc634fcd36a2d1d800ca818065dcf71a1d907dc35130c2d1552c6e1dc", - "dest": "cargo/vendor/rkv-0.20.0" + "url": "https://static.crates.io/crates/der-parser/der-parser-10.0.0.crate", + "sha256": "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6", + "dest": "cargo/vendor/der-parser-10.0.0" }, { "type": "inline", - "contents": "{\"package\": \"0f67a9dbc634fcd36a2d1d800ca818065dcf71a1d907dc35130c2d1552c6e1dc\", \"files\": {}}", - "dest": "cargo/vendor/rkv-0.20.0", + "contents": "{\"package\": \"07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6\", \"files\": {}}", + "dest": "cargo/vendor/der-parser-10.0.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/rustc-hash/rustc-hash-2.1.1.crate", - "sha256": "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d", - "dest": "cargo/vendor/rustc-hash-2.1.1" + "url": "https://static.crates.io/crates/deranged/deranged-0.5.8.crate", + "sha256": "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c", + "dest": "cargo/vendor/deranged-0.5.8" }, { "type": "inline", - "contents": "{\"package\": \"357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d\", \"files\": {}}", - "dest": "cargo/vendor/rustc-hash-2.1.1", + "contents": "{\"package\": \"7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c\", \"files\": {}}", + "dest": "cargo/vendor/deranged-0.5.8", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/rusticata-macros/rusticata-macros-4.1.0.crate", - "sha256": "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632", - "dest": "cargo/vendor/rusticata-macros-4.1.0" + "url": "https://static.crates.io/crates/displaydoc/displaydoc-0.2.6.crate", + "sha256": "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f", + "dest": "cargo/vendor/displaydoc-0.2.6" }, { "type": "inline", - "contents": "{\"package\": \"faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632\", \"files\": {}}", - "dest": "cargo/vendor/rusticata-macros-4.1.0", + "contents": "{\"package\": \"1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f\", \"files\": {}}", + "dest": "cargo/vendor/displaydoc-0.2.6", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/rustix/rustix-1.1.2.crate", - "sha256": "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e", - "dest": "cargo/vendor/rustix-1.1.2" + "url": "https://static.crates.io/crates/err-context/err-context-0.1.0.crate", + "sha256": "449aad22b1364e927ff3bf50f55404efd705c40065fb47f73f28704de707c89e", + "dest": "cargo/vendor/err-context-0.1.0" }, { "type": "inline", - "contents": "{\"package\": \"cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e\", \"files\": {}}", - "dest": "cargo/vendor/rustix-1.1.2", + "contents": "{\"package\": \"449aad22b1364e927ff3bf50f55404efd705c40065fb47f73f28704de707c89e\", \"files\": {}}", + "dest": "cargo/vendor/err-context-0.1.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/rustversion/rustversion-1.0.22.crate", - "sha256": "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d", - "dest": "cargo/vendor/rustversion-1.0.22" + "url": "https://static.crates.io/crates/ffi-support/ffi-support-0.4.4.crate", + "sha256": "27838c6815cfe9de2d3aeb145ffd19e565f577414b33f3bdbf42fe040e9e0ff6", + "dest": "cargo/vendor/ffi-support-0.4.4" }, { "type": "inline", - "contents": "{\"package\": \"b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d\", \"files\": {}}", - "dest": "cargo/vendor/rustversion-1.0.22", + "contents": "{\"package\": \"27838c6815cfe9de2d3aeb145ffd19e565f577414b33f3bdbf42fe040e9e0ff6\", \"files\": {}}", + "dest": "cargo/vendor/ffi-support-0.4.4", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/scroll/scroll-0.12.0.crate", - "sha256": "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6", - "dest": "cargo/vendor/scroll-0.12.0" + "url": "https://static.crates.io/crates/find-msvc-tools/find-msvc-tools-0.1.9.crate", + "sha256": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582", + "dest": "cargo/vendor/find-msvc-tools-0.1.9" }, { "type": "inline", - "contents": "{\"package\": \"6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6\", \"files\": {}}", - "dest": "cargo/vendor/scroll-0.12.0", + "contents": "{\"package\": \"5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582\", \"files\": {}}", + "dest": "cargo/vendor/find-msvc-tools-0.1.9", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/scroll_derive/scroll_derive-0.12.1.crate", - "sha256": "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d", - "dest": "cargo/vendor/scroll_derive-0.12.1" + "url": "https://static.crates.io/crates/futures/futures-0.3.32.crate", + "sha256": "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d", + "dest": "cargo/vendor/futures-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d\", \"files\": {}}", - "dest": "cargo/vendor/scroll_derive-0.12.1", + "contents": "{\"package\": \"8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d\", \"files\": {}}", + "dest": "cargo/vendor/futures-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/serde/serde-1.0.228.crate", - "sha256": "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e", - "dest": "cargo/vendor/serde-1.0.228" + "url": "https://static.crates.io/crates/futures-channel/futures-channel-0.3.32.crate", + "sha256": "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d", + "dest": "cargo/vendor/futures-channel-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\", \"files\": {}}", - "dest": "cargo/vendor/serde-1.0.228", + "contents": "{\"package\": \"07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d\", \"files\": {}}", + "dest": "cargo/vendor/futures-channel-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/serde_core/serde_core-1.0.228.crate", - "sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad", - "dest": "cargo/vendor/serde_core-1.0.228" + "url": "https://static.crates.io/crates/futures-core/futures-core-0.3.32.crate", + "sha256": "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d", + "dest": "cargo/vendor/futures-core-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\", \"files\": {}}", - "dest": "cargo/vendor/serde_core-1.0.228", + "contents": "{\"package\": \"7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d\", \"files\": {}}", + "dest": "cargo/vendor/futures-core-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/serde_derive/serde_derive-1.0.228.crate", - "sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79", - "dest": "cargo/vendor/serde_derive-1.0.228" + "url": "https://static.crates.io/crates/futures-executor/futures-executor-0.3.32.crate", + "sha256": "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d", + "dest": "cargo/vendor/futures-executor-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\", \"files\": {}}", - "dest": "cargo/vendor/serde_derive-1.0.228", + "contents": "{\"package\": \"baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d\", \"files\": {}}", + "dest": "cargo/vendor/futures-executor-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/serde_json/serde_json-1.0.149.crate", - "sha256": "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86", - "dest": "cargo/vendor/serde_json-1.0.149" + "url": "https://static.crates.io/crates/futures-io/futures-io-0.3.32.crate", + "sha256": "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718", + "dest": "cargo/vendor/futures-io-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86\", \"files\": {}}", - "dest": "cargo/vendor/serde_json-1.0.149", + "contents": "{\"package\": \"cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718\", \"files\": {}}", + "dest": "cargo/vendor/futures-io-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/serde_spanned/serde_spanned-1.0.4.crate", - "sha256": "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776", - "dest": "cargo/vendor/serde_spanned-1.0.4" + "url": "https://static.crates.io/crates/futures-macro/futures-macro-0.3.32.crate", + "sha256": "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b", + "dest": "cargo/vendor/futures-macro-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776\", \"files\": {}}", - "dest": "cargo/vendor/serde_spanned-1.0.4", + "contents": "{\"package\": \"e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b\", \"files\": {}}", + "dest": "cargo/vendor/futures-macro-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/shlex/shlex-1.3.0.crate", - "sha256": "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64", - "dest": "cargo/vendor/shlex-1.3.0" + "url": "https://static.crates.io/crates/futures-sink/futures-sink-0.3.32.crate", + "sha256": "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893", + "dest": "cargo/vendor/futures-sink-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64\", \"files\": {}}", - "dest": "cargo/vendor/shlex-1.3.0", + "contents": "{\"package\": \"c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893\", \"files\": {}}", + "dest": "cargo/vendor/futures-sink-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/simd-adler32/simd-adler32-0.3.8.crate", - "sha256": "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2", - "dest": "cargo/vendor/simd-adler32-0.3.8" + "url": "https://static.crates.io/crates/futures-task/futures-task-0.3.32.crate", + "sha256": "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393", + "dest": "cargo/vendor/futures-task-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2\", \"files\": {}}", - "dest": "cargo/vendor/simd-adler32-0.3.8", + "contents": "{\"package\": \"037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393\", \"files\": {}}", + "dest": "cargo/vendor/futures-task-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/siphasher/siphasher-0.3.11.crate", - "sha256": "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d", - "dest": "cargo/vendor/siphasher-0.3.11" + "url": "https://static.crates.io/crates/futures-util/futures-util-0.3.32.crate", + "sha256": "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6", + "dest": "cargo/vendor/futures-util-0.3.32" }, { "type": "inline", - "contents": "{\"package\": \"38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d\", \"files\": {}}", - "dest": "cargo/vendor/siphasher-0.3.11", + "contents": "{\"package\": \"389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6\", \"files\": {}}", + "dest": "cargo/vendor/futures-util-0.3.32", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/smawk/smawk-0.3.2.crate", - "sha256": "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c", - "dest": "cargo/vendor/smawk-0.3.2" + "url": "https://static.crates.io/crates/getrandom/getrandom-0.2.17.crate", + "sha256": "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0", + "dest": "cargo/vendor/getrandom-0.2.17" }, { "type": "inline", - "contents": "{\"package\": \"b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c\", \"files\": {}}", - "dest": "cargo/vendor/smawk-0.3.2", + "contents": "{\"package\": \"ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0\", \"files\": {}}", + "dest": "cargo/vendor/getrandom-0.2.17", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/static_assertions/static_assertions-1.1.0.crate", - "sha256": "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f", - "dest": "cargo/vendor/static_assertions-1.1.0" + "url": "https://static.crates.io/crates/heck/heck-0.5.0.crate", + "sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea", + "dest": "cargo/vendor/heck-0.5.0" }, { "type": "inline", - "contents": "{\"package\": \"a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f\", \"files\": {}}", - "dest": "cargo/vendor/static_assertions-1.1.0", + "contents": "{\"package\": \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\", \"files\": {}}", + "dest": "cargo/vendor/heck-0.5.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/strsim/strsim-0.11.1.crate", - "sha256": "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f", - "dest": "cargo/vendor/strsim-0.11.1" + "url": "https://static.crates.io/crates/hex/hex-0.4.3.crate", + "sha256": "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70", + "dest": "cargo/vendor/hex-0.4.3" }, { "type": "inline", - "contents": "{\"package\": \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\", \"files\": {}}", - "dest": "cargo/vendor/strsim-0.11.1", + "contents": "{\"package\": \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\", \"files\": {}}", + "dest": "cargo/vendor/hex-0.4.3", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/syn/syn-2.0.111.crate", - "sha256": "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87", - "dest": "cargo/vendor/syn-2.0.111" + "url": "https://static.crates.io/crates/is_terminal_polyfill/is_terminal_polyfill-1.70.2.crate", + "sha256": "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695", + "dest": "cargo/vendor/is_terminal_polyfill-1.70.2" }, { "type": "inline", - "contents": "{\"package\": \"390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87\", \"files\": {}}", - "dest": "cargo/vendor/syn-2.0.111", + "contents": "{\"package\": \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\", \"files\": {}}", + "dest": "cargo/vendor/is_terminal_polyfill-1.70.2", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/synstructure/synstructure-0.13.2.crate", - "sha256": "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2", - "dest": "cargo/vendor/synstructure-0.13.2" + "url": "https://static.crates.io/crates/itoa/itoa-1.0.18.crate", + "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682", + "dest": "cargo/vendor/itoa-1.0.18" }, { "type": "inline", - "contents": "{\"package\": \"728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2\", \"files\": {}}", - "dest": "cargo/vendor/synstructure-0.13.2", + "contents": "{\"package\": \"8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682\", \"files\": {}}", + "dest": "cargo/vendor/itoa-1.0.18", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/tempfile/tempfile-3.23.0.crate", - "sha256": "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16", - "dest": "cargo/vendor/tempfile-3.23.0" + "url": "https://static.crates.io/crates/lazy_static/lazy_static-1.5.0.crate", + "sha256": "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe", + "dest": "cargo/vendor/lazy_static-1.5.0" }, { "type": "inline", - "contents": "{\"package\": \"2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16\", \"files\": {}}", - "dest": "cargo/vendor/tempfile-3.23.0", + "contents": "{\"package\": \"bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe\", \"files\": {}}", + "dest": "cargo/vendor/lazy_static-1.5.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/textwrap/textwrap-0.16.2.crate", - "sha256": "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057", - "dest": "cargo/vendor/textwrap-0.16.2" + "url": "https://static.crates.io/crates/libc/libc-0.2.186.crate", + "sha256": "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66", + "dest": "cargo/vendor/libc-0.2.186" }, { "type": "inline", - "contents": "{\"package\": \"c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057\", \"files\": {}}", - "dest": "cargo/vendor/textwrap-0.16.2", + "contents": "{\"package\": \"68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66\", \"files\": {}}", + "dest": "cargo/vendor/libc-0.2.186", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/thiserror/thiserror-2.0.18.crate", - "sha256": "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4", - "dest": "cargo/vendor/thiserror-2.0.18" + "url": "https://static.crates.io/crates/log/log-0.4.30.crate", + "sha256": "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5", + "dest": "cargo/vendor/log-0.4.30" }, { "type": "inline", - "contents": "{\"package\": \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\", \"files\": {}}", - "dest": "cargo/vendor/thiserror-2.0.18", + "contents": "{\"package\": \"616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5\", \"files\": {}}", + "dest": "cargo/vendor/log-0.4.30", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/thiserror-impl/thiserror-impl-2.0.18.crate", - "sha256": "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5", - "dest": "cargo/vendor/thiserror-impl-2.0.18" + "url": "https://static.crates.io/crates/memchr/memchr-2.8.1.crate", + "sha256": "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8", + "dest": "cargo/vendor/memchr-2.8.1" }, { "type": "inline", - "contents": "{\"package\": \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\", \"files\": {}}", - "dest": "cargo/vendor/thiserror-impl-2.0.18", + "contents": "{\"package\": \"6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8\", \"files\": {}}", + "dest": "cargo/vendor/memchr-2.8.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/time/time-0.3.44.crate", - "sha256": "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d", - "dest": "cargo/vendor/time-0.3.44" + "url": "https://static.crates.io/crates/memoffset/memoffset-0.9.1.crate", + "sha256": "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a", + "dest": "cargo/vendor/memoffset-0.9.1" }, { "type": "inline", - "contents": "{\"package\": \"91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d\", \"files\": {}}", - "dest": "cargo/vendor/time-0.3.44", + "contents": "{\"package\": \"488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a\", \"files\": {}}", + "dest": "cargo/vendor/memoffset-0.9.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/time-core/time-core-0.1.6.crate", - "sha256": "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b", - "dest": "cargo/vendor/time-core-0.1.6" + "url": "https://static.crates.io/crates/minimal-lexical/minimal-lexical-0.2.1.crate", + "sha256": "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a", + "dest": "cargo/vendor/minimal-lexical-0.2.1" }, { "type": "inline", - "contents": "{\"package\": \"40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b\", \"files\": {}}", - "dest": "cargo/vendor/time-core-0.1.6", + "contents": "{\"package\": \"68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a\", \"files\": {}}", + "dest": "cargo/vendor/minimal-lexical-0.2.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/time-macros/time-macros-0.2.24.crate", - "sha256": "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3", - "dest": "cargo/vendor/time-macros-0.2.24" + "url": "https://static.crates.io/crates/mio/mio-1.2.1.crate", + "sha256": "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda", + "dest": "cargo/vendor/mio-1.2.1" }, { "type": "inline", - "contents": "{\"package\": \"30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3\", \"files\": {}}", - "dest": "cargo/vendor/time-macros-0.2.24", + "contents": "{\"package\": \"02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda\", \"files\": {}}", + "dest": "cargo/vendor/mio-1.2.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/tinyvec/tinyvec-1.10.0.crate", - "sha256": "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa", - "dest": "cargo/vendor/tinyvec-1.10.0" + "url": "https://static.crates.io/crates/nix/nix-0.31.3.crate", + "sha256": "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d", + "dest": "cargo/vendor/nix-0.31.3" }, { "type": "inline", - "contents": "{\"package\": \"bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa\", \"files\": {}}", - "dest": "cargo/vendor/tinyvec-1.10.0", + "contents": "{\"package\": \"cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d\", \"files\": {}}", + "dest": "cargo/vendor/nix-0.31.3", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/tinyvec_macros/tinyvec_macros-0.1.1.crate", - "sha256": "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20", - "dest": "cargo/vendor/tinyvec_macros-0.1.1" + "url": "https://static.crates.io/crates/nom/nom-7.1.3.crate", + "sha256": "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a", + "dest": "cargo/vendor/nom-7.1.3" }, { "type": "inline", - "contents": "{\"package\": \"1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20\", \"files\": {}}", - "dest": "cargo/vendor/tinyvec_macros-0.1.1", + "contents": "{\"package\": \"d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a\", \"files\": {}}", + "dest": "cargo/vendor/nom-7.1.3", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/toml/toml-0.5.11.crate", - "sha256": "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234", - "dest": "cargo/vendor/toml-0.5.11" + "url": "https://static.crates.io/crates/num-bigint/num-bigint-0.4.6.crate", + "sha256": "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9", + "dest": "cargo/vendor/num-bigint-0.4.6" }, { "type": "inline", - "contents": "{\"package\": \"f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234\", \"files\": {}}", - "dest": "cargo/vendor/toml-0.5.11", + "contents": "{\"package\": \"a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9\", \"files\": {}}", + "dest": "cargo/vendor/num-bigint-0.4.6", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/toml/toml-0.9.9+spec-1.0.0.crate", - "sha256": "eb5238e643fc34a1d5d7e753e1532a91912d74b63b92b3ea51fde8d1b7bc79dd", - "dest": "cargo/vendor/toml-0.9.9+spec-1.0.0" + "url": "https://static.crates.io/crates/num-conv/num-conv-0.2.2.crate", + "sha256": "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441", + "dest": "cargo/vendor/num-conv-0.2.2" }, { "type": "inline", - "contents": "{\"package\": \"eb5238e643fc34a1d5d7e753e1532a91912d74b63b92b3ea51fde8d1b7bc79dd\", \"files\": {}}", - "dest": "cargo/vendor/toml-0.9.9+spec-1.0.0", + "contents": "{\"package\": \"521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441\", \"files\": {}}", + "dest": "cargo/vendor/num-conv-0.2.2", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/toml_datetime/toml_datetime-0.7.4+spec-1.0.0.crate", - "sha256": "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6", - "dest": "cargo/vendor/toml_datetime-0.7.4+spec-1.0.0" + "url": "https://static.crates.io/crates/num-integer/num-integer-0.1.46.crate", + "sha256": "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f", + "dest": "cargo/vendor/num-integer-0.1.46" }, { "type": "inline", - "contents": "{\"package\": \"fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6\", \"files\": {}}", - "dest": "cargo/vendor/toml_datetime-0.7.4+spec-1.0.0", + "contents": "{\"package\": \"7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f\", \"files\": {}}", + "dest": "cargo/vendor/num-integer-0.1.46", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/toml_parser/toml_parser-1.0.5+spec-1.0.0.crate", - "sha256": "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c", - "dest": "cargo/vendor/toml_parser-1.0.5+spec-1.0.0" + "url": "https://static.crates.io/crates/num-traits/num-traits-0.2.19.crate", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841", + "dest": "cargo/vendor/num-traits-0.2.19" }, { "type": "inline", - "contents": "{\"package\": \"4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c\", \"files\": {}}", - "dest": "cargo/vendor/toml_parser-1.0.5+spec-1.0.0", + "contents": "{\"package\": \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\", \"files\": {}}", + "dest": "cargo/vendor/num-traits-0.2.19", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/toml_writer/toml_writer-1.0.5+spec-1.0.0.crate", - "sha256": "a9cd6190959dce0994aa8970cd32ab116d1851ead27e866039acaf2524ce44fa", - "dest": "cargo/vendor/toml_writer-1.0.5+spec-1.0.0" + "url": "https://static.crates.io/crates/oid-registry/oid-registry-0.8.1.crate", + "sha256": "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7", + "dest": "cargo/vendor/oid-registry-0.8.1" }, { "type": "inline", - "contents": "{\"package\": \"a9cd6190959dce0994aa8970cd32ab116d1851ead27e866039acaf2524ce44fa\", \"files\": {}}", - "dest": "cargo/vendor/toml_writer-1.0.5+spec-1.0.0", + "contents": "{\"package\": \"12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7\", \"files\": {}}", + "dest": "cargo/vendor/oid-registry-0.8.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/unicode-bidi/unicode-bidi-0.3.18.crate", - "sha256": "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5", - "dest": "cargo/vendor/unicode-bidi-0.3.18" + "url": "https://static.crates.io/crates/once_cell/once_cell-1.21.4.crate", + "sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50", + "dest": "cargo/vendor/once_cell-1.21.4" }, { "type": "inline", - "contents": "{\"package\": \"5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5\", \"files\": {}}", - "dest": "cargo/vendor/unicode-bidi-0.3.18", + "contents": "{\"package\": \"9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50\", \"files\": {}}", + "dest": "cargo/vendor/once_cell-1.21.4", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/unicode-ident/unicode-ident-1.0.22.crate", - "sha256": "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5", - "dest": "cargo/vendor/unicode-ident-1.0.22" + "url": "https://static.crates.io/crates/once_cell_polyfill/once_cell_polyfill-1.70.2.crate", + "sha256": "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe", + "dest": "cargo/vendor/once_cell_polyfill-1.70.2" }, { "type": "inline", - "contents": "{\"package\": \"9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5\", \"files\": {}}", - "dest": "cargo/vendor/unicode-ident-1.0.22", + "contents": "{\"package\": \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\", \"files\": {}}", + "dest": "cargo/vendor/once_cell_polyfill-1.70.2", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/unicode-normalization/unicode-normalization-0.1.25.crate", - "sha256": "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8", - "dest": "cargo/vendor/unicode-normalization-0.1.25" + "url": "https://static.crates.io/crates/pin-project-lite/pin-project-lite-0.2.17.crate", + "sha256": "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd", + "dest": "cargo/vendor/pin-project-lite-0.2.17" }, { "type": "inline", - "contents": "{\"package\": \"5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8\", \"files\": {}}", - "dest": "cargo/vendor/unicode-normalization-0.1.25", + "contents": "{\"package\": \"a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd\", \"files\": {}}", + "dest": "cargo/vendor/pin-project-lite-0.2.17", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi/uniffi-0.29.5.crate", - "sha256": "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f", - "dest": "cargo/vendor/uniffi-0.29.5" + "url": "https://static.crates.io/crates/powerfmt/powerfmt-0.2.0.crate", + "sha256": "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391", + "dest": "cargo/vendor/powerfmt-0.2.0" }, { "type": "inline", - "contents": "{\"package\": \"3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f\", \"files\": {}}", - "dest": "cargo/vendor/uniffi-0.29.5", + "contents": "{\"package\": \"439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391\", \"files\": {}}", + "dest": "cargo/vendor/powerfmt-0.2.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_bindgen/uniffi_bindgen-0.29.5.crate", - "sha256": "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e", - "dest": "cargo/vendor/uniffi_bindgen-0.29.5" + "url": "https://static.crates.io/crates/ppv-lite86/ppv-lite86-0.2.21.crate", + "sha256": "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9", + "dest": "cargo/vendor/ppv-lite86-0.2.21" }, { "type": "inline", - "contents": "{\"package\": \"a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_bindgen-0.29.5", + "contents": "{\"package\": \"85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9\", \"files\": {}}", + "dest": "cargo/vendor/ppv-lite86-0.2.21", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_build/uniffi_build-0.29.5.crate", - "sha256": "025a05cba02ee22b6624ac3d257e59c7395319ea8fe1aae33a7cdb4e2a3016cc", - "dest": "cargo/vendor/uniffi_build-0.29.5" + "url": "https://static.crates.io/crates/proc-macro2/proc-macro2-1.0.106.crate", + "sha256": "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934", + "dest": "cargo/vendor/proc-macro2-1.0.106" }, { "type": "inline", - "contents": "{\"package\": \"025a05cba02ee22b6624ac3d257e59c7395319ea8fe1aae33a7cdb4e2a3016cc\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_build-0.29.5", + "contents": "{\"package\": \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\", \"files\": {}}", + "dest": "cargo/vendor/proc-macro2-1.0.106", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_core/uniffi_core-0.29.5.crate", - "sha256": "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98", - "dest": "cargo/vendor/uniffi_core-0.29.5" + "url": "https://static.crates.io/crates/quote/quote-1.0.45.crate", + "sha256": "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924", + "dest": "cargo/vendor/quote-1.0.45" }, { "type": "inline", - "contents": "{\"package\": \"f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_core-0.29.5", + "contents": "{\"package\": \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\", \"files\": {}}", + "dest": "cargo/vendor/quote-1.0.45", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_internal_macros/uniffi_internal_macros-0.29.5.crate", - "sha256": "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933", - "dest": "cargo/vendor/uniffi_internal_macros-0.29.5" + "url": "https://static.crates.io/crates/rand/rand-0.8.6.crate", + "sha256": "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a", + "dest": "cargo/vendor/rand-0.8.6" }, { "type": "inline", - "contents": "{\"package\": \"09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_internal_macros-0.29.5", + "contents": "{\"package\": \"5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a\", \"files\": {}}", + "dest": "cargo/vendor/rand-0.8.6", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_macros/uniffi_macros-0.29.5.crate", - "sha256": "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152", - "dest": "cargo/vendor/uniffi_macros-0.29.5" + "url": "https://static.crates.io/crates/rand_chacha/rand_chacha-0.3.1.crate", + "sha256": "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88", + "dest": "cargo/vendor/rand_chacha-0.3.1" }, { "type": "inline", - "contents": "{\"package\": \"5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_macros-0.29.5", + "contents": "{\"package\": \"e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88\", \"files\": {}}", + "dest": "cargo/vendor/rand_chacha-0.3.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_meta/uniffi_meta-0.29.5.crate", - "sha256": "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff", - "dest": "cargo/vendor/uniffi_meta-0.29.5" + "url": "https://static.crates.io/crates/rand_core/rand_core-0.6.4.crate", + "sha256": "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c", + "dest": "cargo/vendor/rand_core-0.6.4" }, { "type": "inline", - "contents": "{\"package\": \"beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_meta-0.29.5", + "contents": "{\"package\": \"ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c\", \"files\": {}}", + "dest": "cargo/vendor/rand_core-0.6.4", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_pipeline/uniffi_pipeline-0.29.5.crate", - "sha256": "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a", - "dest": "cargo/vendor/uniffi_pipeline-0.29.5" + "url": "https://static.crates.io/crates/ring/ring-0.17.14.crate", + "sha256": "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7", + "dest": "cargo/vendor/ring-0.17.14" }, { "type": "inline", - "contents": "{\"package\": \"dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_pipeline-0.29.5", + "contents": "{\"package\": \"a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7\", \"files\": {}}", + "dest": "cargo/vendor/ring-0.17.14", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uniffi_udl/uniffi_udl-0.29.5.crate", - "sha256": "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716", - "dest": "cargo/vendor/uniffi_udl-0.29.5" + "url": "https://static.crates.io/crates/rusticata-macros/rusticata-macros-4.1.0.crate", + "sha256": "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632", + "dest": "cargo/vendor/rusticata-macros-4.1.0" }, { "type": "inline", - "contents": "{\"package\": \"4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716\", \"files\": {}}", - "dest": "cargo/vendor/uniffi_udl-0.29.5", + "contents": "{\"package\": \"faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632\", \"files\": {}}", + "dest": "cargo/vendor/rusticata-macros-4.1.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/untrusted/untrusted-0.9.0.crate", - "sha256": "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1", - "dest": "cargo/vendor/untrusted-0.9.0" + "url": "https://static.crates.io/crates/serde_core/serde_core-1.0.228.crate", + "sha256": "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad", + "dest": "cargo/vendor/serde_core-1.0.228" }, { "type": "inline", - "contents": "{\"package\": \"8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1\", \"files\": {}}", - "dest": "cargo/vendor/untrusted-0.9.0", + "contents": "{\"package\": \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\", \"files\": {}}", + "dest": "cargo/vendor/serde_core-1.0.228", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/url/url-2.4.1.crate", - "sha256": "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5", - "dest": "cargo/vendor/url-2.4.1" + "url": "https://static.crates.io/crates/serde_derive/serde_derive-1.0.228.crate", + "sha256": "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79", + "dest": "cargo/vendor/serde_derive-1.0.228" }, { "type": "inline", - "contents": "{\"package\": \"143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5\", \"files\": {}}", - "dest": "cargo/vendor/url-2.4.1", + "contents": "{\"package\": \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\", \"files\": {}}", + "dest": "cargo/vendor/serde_derive-1.0.228", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/utf8parse/utf8parse-0.2.2.crate", - "sha256": "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821", - "dest": "cargo/vendor/utf8parse-0.2.2" + "url": "https://static.crates.io/crates/shlex/shlex-2.0.1.crate", + "sha256": "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba", + "dest": "cargo/vendor/shlex-2.0.1" }, { "type": "inline", - "contents": "{\"package\": \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\", \"files\": {}}", - "dest": "cargo/vendor/utf8parse-0.2.2", + "contents": "{\"package\": \"f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba\", \"files\": {}}", + "dest": "cargo/vendor/shlex-2.0.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/uuid/uuid-1.19.0.crate", - "sha256": "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a", - "dest": "cargo/vendor/uuid-1.19.0" + "url": "https://static.crates.io/crates/slab/slab-0.4.12.crate", + "sha256": "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5", + "dest": "cargo/vendor/slab-0.4.12" }, { "type": "inline", - "contents": "{\"package\": \"e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a\", \"files\": {}}", - "dest": "cargo/vendor/uuid-1.19.0", + "contents": "{\"package\": \"0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5\", \"files\": {}}", + "dest": "cargo/vendor/slab-0.4.12", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wasi/wasi-0.11.1+wasi-snapshot-preview1.crate", - "sha256": "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b", - "dest": "cargo/vendor/wasi-0.11.1+wasi-snapshot-preview1" + "url": "https://static.crates.io/crates/socket2/socket2-0.6.4.crate", + "sha256": "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51", + "dest": "cargo/vendor/socket2-0.6.4" }, { "type": "inline", - "contents": "{\"package\": \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\", \"files\": {}}", - "dest": "cargo/vendor/wasi-0.11.1+wasi-snapshot-preview1", + "contents": "{\"package\": \"52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51\", \"files\": {}}", + "dest": "cargo/vendor/socket2-0.6.4", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wasip2/wasip2-1.0.1+wasi-0.2.4.crate", - "sha256": "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7", - "dest": "cargo/vendor/wasip2-1.0.1+wasi-0.2.4" + "url": "https://static.crates.io/crates/strsim/strsim-0.11.1.crate", + "sha256": "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f", + "dest": "cargo/vendor/strsim-0.11.1" }, { "type": "inline", - "contents": "{\"package\": \"0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7\", \"files\": {}}", - "dest": "cargo/vendor/wasip2-1.0.1+wasi-0.2.4", + "contents": "{\"package\": \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\", \"files\": {}}", + "dest": "cargo/vendor/strsim-0.11.1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wasm-bindgen/wasm-bindgen-0.2.106.crate", - "sha256": "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd", - "dest": "cargo/vendor/wasm-bindgen-0.2.106" + "url": "https://static.crates.io/crates/syn/syn-2.0.117.crate", + "sha256": "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99", + "dest": "cargo/vendor/syn-2.0.117" }, { "type": "inline", - "contents": "{\"package\": \"0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd\", \"files\": {}}", - "dest": "cargo/vendor/wasm-bindgen-0.2.106", + "contents": "{\"package\": \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\", \"files\": {}}", + "dest": "cargo/vendor/syn-2.0.117", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wasm-bindgen-macro/wasm-bindgen-macro-0.2.106.crate", - "sha256": "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3", - "dest": "cargo/vendor/wasm-bindgen-macro-0.2.106" + "url": "https://static.crates.io/crates/synstructure/synstructure-0.13.2.crate", + "sha256": "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2", + "dest": "cargo/vendor/synstructure-0.13.2" }, { "type": "inline", - "contents": "{\"package\": \"48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3\", \"files\": {}}", - "dest": "cargo/vendor/wasm-bindgen-macro-0.2.106", + "contents": "{\"package\": \"728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2\", \"files\": {}}", + "dest": "cargo/vendor/synstructure-0.13.2", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wasm-bindgen-macro-support/wasm-bindgen-macro-support-0.2.106.crate", - "sha256": "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40", - "dest": "cargo/vendor/wasm-bindgen-macro-support-0.2.106" + "url": "https://static.crates.io/crates/thiserror/thiserror-2.0.18.crate", + "sha256": "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4", + "dest": "cargo/vendor/thiserror-2.0.18" }, { "type": "inline", - "contents": "{\"package\": \"cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40\", \"files\": {}}", - "dest": "cargo/vendor/wasm-bindgen-macro-support-0.2.106", + "contents": "{\"package\": \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\", \"files\": {}}", + "dest": "cargo/vendor/thiserror-2.0.18", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wasm-bindgen-shared/wasm-bindgen-shared-0.2.106.crate", - "sha256": "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4", - "dest": "cargo/vendor/wasm-bindgen-shared-0.2.106" + "url": "https://static.crates.io/crates/thiserror-impl/thiserror-impl-2.0.18.crate", + "sha256": "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5", + "dest": "cargo/vendor/thiserror-impl-2.0.18" }, { "type": "inline", - "contents": "{\"package\": \"cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4\", \"files\": {}}", - "dest": "cargo/vendor/wasm-bindgen-shared-0.2.106", + "contents": "{\"package\": \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\", \"files\": {}}", + "dest": "cargo/vendor/thiserror-impl-2.0.18", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/weedle2/weedle2-5.0.0.crate", - "sha256": "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e", - "dest": "cargo/vendor/weedle2-5.0.0" + "url": "https://static.crates.io/crates/time/time-0.3.47.crate", + "sha256": "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c", + "dest": "cargo/vendor/time-0.3.47" }, { "type": "inline", - "contents": "{\"package\": \"998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e\", \"files\": {}}", - "dest": "cargo/vendor/weedle2-5.0.0", + "contents": "{\"package\": \"743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c\", \"files\": {}}", + "dest": "cargo/vendor/time-0.3.47", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/whatsys/whatsys-0.3.2.crate", - "sha256": "192bcd2925a9791ba474bc673938f8c59b8978b3f304ef2c50672156bacf943b", - "dest": "cargo/vendor/whatsys-0.3.2" + "url": "https://static.crates.io/crates/time-core/time-core-0.1.8.crate", + "sha256": "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca", + "dest": "cargo/vendor/time-core-0.1.8" }, { "type": "inline", - "contents": "{\"package\": \"192bcd2925a9791ba474bc673938f8c59b8978b3f304ef2c50672156bacf943b\", \"files\": {}}", - "dest": "cargo/vendor/whatsys-0.3.2", + "contents": "{\"package\": \"7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca\", \"files\": {}}", + "dest": "cargo/vendor/time-core-0.1.8", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/winapi/winapi-0.3.9.crate", - "sha256": "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419", - "dest": "cargo/vendor/winapi-0.3.9" + "url": "https://static.crates.io/crates/time-macros/time-macros-0.2.27.crate", + "sha256": "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215", + "dest": "cargo/vendor/time-macros-0.2.27" }, { "type": "inline", - "contents": "{\"package\": \"5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419\", \"files\": {}}", - "dest": "cargo/vendor/winapi-0.3.9", + "contents": "{\"package\": \"2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215\", \"files\": {}}", + "dest": "cargo/vendor/time-macros-0.2.27", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/winapi-i686-pc-windows-gnu/winapi-i686-pc-windows-gnu-0.4.0.crate", - "sha256": "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6", - "dest": "cargo/vendor/winapi-i686-pc-windows-gnu-0.4.0" + "url": "https://static.crates.io/crates/tokio/tokio-1.52.3.crate", + "sha256": "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe", + "dest": "cargo/vendor/tokio-1.52.3" }, { "type": "inline", - "contents": "{\"package\": \"ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6\", \"files\": {}}", - "dest": "cargo/vendor/winapi-i686-pc-windows-gnu-0.4.0", + "contents": "{\"package\": \"8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe\", \"files\": {}}", + "dest": "cargo/vendor/tokio-1.52.3", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/winapi-x86_64-pc-windows-gnu/winapi-x86_64-pc-windows-gnu-0.4.0.crate", - "sha256": "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f", - "dest": "cargo/vendor/winapi-x86_64-pc-windows-gnu-0.4.0" + "url": "https://static.crates.io/crates/tokio-macros/tokio-macros-2.7.0.crate", + "sha256": "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496", + "dest": "cargo/vendor/tokio-macros-2.7.0" }, { "type": "inline", - "contents": "{\"package\": \"712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f\", \"files\": {}}", - "dest": "cargo/vendor/winapi-x86_64-pc-windows-gnu-0.4.0", + "contents": "{\"package\": \"385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496\", \"files\": {}}", + "dest": "cargo/vendor/tokio-macros-2.7.0", "dest-filename": ".cargo-checksum.json" }, { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-core/windows-core-0.62.2.crate", - "sha256": "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb", - "dest": "cargo/vendor/windows-core-0.62.2" + "type": "shell", + "commands": [ + "cp -r --reflink=auto \"flatpak-cargo/git/udp-over-tcp-5c6d8f4/.\" \"cargo/vendor/udp-over-tcp\"" + ] }, { "type": "inline", - "contents": "{\"package\": \"b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb\", \"files\": {}}", - "dest": "cargo/vendor/windows-core-0.62.2", + "contents": "[package]\nname = \"udp-over-tcp\"\nversion = \"0.4.0\"\nauthors = [\"Mullvad VPN\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"Tunnel UDP traffic inside a TCP stream. Each datagram is prefixed with a 16 bit unsigned integer containing the length\"\nrepository = \"https://github.com/mullvad/udp-over-tcp\"\nedition = \"2024\"\nrust-version = \"1.87.0\"\npublish = false\n\n[[bin]]\nname = \"tcp2udp\"\nrequired-features = [\"clap\"]\n\n[[bin]]\nname = \"udp2tcp\"\nrequired-features = [\"clap\"]\n\n[profile.release]\nopt-level = 3\nlto = true\ncodegen-units = 1\n\n[features]\nstatsd = [\"cadence\"]\n\n[dependencies]\nerr-context = \"0.1.0\"\nlog = \"0.4.11\"\nfutures = \"0.3.31\"\n\n[dependencies.tokio]\nversion = \"1.0\"\nfeatures = [\"rt-multi-thread\", \"macros\", \"net\", \"time\", \"io-util\"]\n\n[dependencies.clap]\nversion = \"4.0\"\nfeatures = [\"derive\"]\noptional = true\n\n[dependencies.env_logger]\nversion = \"0.11.3\"\noptional = true\n\n[dependencies.cadence]\nversion = \"1.0.0\"\noptional = true\n\n[target.\"cfg(target_os = \\\"linux\\\")\".dependencies.nix]\nversion = \"0.31.2\"\nfeatures = [\"socket\"]\n", + "dest": "cargo/vendor/udp-over-tcp", + "dest-filename": "Cargo.toml" + }, + { + "type": "inline", + "contents": "{\"package\": null, \"files\": {}}", + "dest": "cargo/vendor/udp-over-tcp", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-implement/windows-implement-0.60.2.crate", - "sha256": "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf", - "dest": "cargo/vendor/windows-implement-0.60.2" + "url": "https://static.crates.io/crates/unicode-ident/unicode-ident-1.0.24.crate", + "sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75", + "dest": "cargo/vendor/unicode-ident-1.0.24" }, { "type": "inline", - "contents": "{\"package\": \"053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf\", \"files\": {}}", - "dest": "cargo/vendor/windows-implement-0.60.2", + "contents": "{\"package\": \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\", \"files\": {}}", + "dest": "cargo/vendor/unicode-ident-1.0.24", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-interface/windows-interface-0.59.3.crate", - "sha256": "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358", - "dest": "cargo/vendor/windows-interface-0.59.3" + "url": "https://static.crates.io/crates/untrusted/untrusted-0.9.0.crate", + "sha256": "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1", + "dest": "cargo/vendor/untrusted-0.9.0" }, { "type": "inline", - "contents": "{\"package\": \"3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358\", \"files\": {}}", - "dest": "cargo/vendor/windows-interface-0.59.3", + "contents": "{\"package\": \"8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1\", \"files\": {}}", + "dest": "cargo/vendor/untrusted-0.9.0", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-link/windows-link-0.2.1.crate", - "sha256": "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5", - "dest": "cargo/vendor/windows-link-0.2.1" + "url": "https://static.crates.io/crates/utf8parse/utf8parse-0.2.2.crate", + "sha256": "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821", + "dest": "cargo/vendor/utf8parse-0.2.2" }, { "type": "inline", - "contents": "{\"package\": \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\", \"files\": {}}", - "dest": "cargo/vendor/windows-link-0.2.1", + "contents": "{\"package\": \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\", \"files\": {}}", + "dest": "cargo/vendor/utf8parse-0.2.2", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-result/windows-result-0.4.1.crate", - "sha256": "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5", - "dest": "cargo/vendor/windows-result-0.4.1" + "url": "https://static.crates.io/crates/wasi/wasi-0.11.1+wasi-snapshot-preview1.crate", + "sha256": "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b", + "dest": "cargo/vendor/wasi-0.11.1+wasi-snapshot-preview1" }, { "type": "inline", - "contents": "{\"package\": \"7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5\", \"files\": {}}", - "dest": "cargo/vendor/windows-result-0.4.1", + "contents": "{\"package\": \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\", \"files\": {}}", + "dest": "cargo/vendor/wasi-0.11.1+wasi-snapshot-preview1", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/windows-strings/windows-strings-0.5.1.crate", - "sha256": "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091", - "dest": "cargo/vendor/windows-strings-0.5.1" + "url": "https://static.crates.io/crates/windows-link/windows-link-0.2.1.crate", + "sha256": "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5", + "dest": "cargo/vendor/windows-link-0.2.1" }, { "type": "inline", - "contents": "{\"package\": \"7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091\", \"files\": {}}", - "dest": "cargo/vendor/windows-strings-0.5.1", + "contents": "{\"package\": \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\", \"files\": {}}", + "dest": "cargo/vendor/windows-link-0.2.1", "dest-filename": ".cargo-checksum.json" }, { @@ -2378,45 +1297,6 @@ "dest": "cargo/vendor/windows_x86_64_msvc-0.52.6", "dest-filename": ".cargo-checksum.json" }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/winnow/winnow-0.7.14.crate", - "sha256": "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829", - "dest": "cargo/vendor/winnow-0.7.14" - }, - { - "type": "inline", - "contents": "{\"package\": \"5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829\", \"files\": {}}", - "dest": "cargo/vendor/winnow-0.7.14", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wit-bindgen/wit-bindgen-0.46.0.crate", - "sha256": "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59", - "dest": "cargo/vendor/wit-bindgen-0.46.0" - }, - { - "type": "inline", - "contents": "{\"package\": \"f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59\", \"files\": {}}", - "dest": "cargo/vendor/wit-bindgen-0.46.0", - "dest-filename": ".cargo-checksum.json" - }, - { - "type": "archive", - "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/wr_malloc_size_of/wr_malloc_size_of-0.2.2.crate", - "sha256": "482308b684f11723b200a32808094bb460b5ac4840903ccbcb78ad92a6354a1f", - "dest": "cargo/vendor/wr_malloc_size_of-0.2.2" - }, - { - "type": "inline", - "contents": "{\"package\": \"482308b684f11723b200a32808094bb460b5ac4840903ccbcb78ad92a6354a1f\", \"files\": {}}", - "dest": "cargo/vendor/wr_malloc_size_of-0.2.2", - "dest-filename": ".cargo-checksum.json" - }, { "type": "archive", "archive-type": "tar-gzip", @@ -2433,32 +1313,32 @@ { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zeitstempel/zeitstempel-0.2.0.crate", - "sha256": "f523a0d9326c4f3242ad3a9d306baa7fe4572fd532cc891cabecfb714c786c1e", - "dest": "cargo/vendor/zeitstempel-0.2.0" + "url": "https://static.crates.io/crates/zerocopy/zerocopy-0.8.50.crate", + "sha256": "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1", + "dest": "cargo/vendor/zerocopy-0.8.50" }, { "type": "inline", - "contents": "{\"package\": \"f523a0d9326c4f3242ad3a9d306baa7fe4572fd532cc891cabecfb714c786c1e\", \"files\": {}}", - "dest": "cargo/vendor/zeitstempel-0.2.0", + "contents": "{\"package\": \"3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1\", \"files\": {}}", + "dest": "cargo/vendor/zerocopy-0.8.50", "dest-filename": ".cargo-checksum.json" }, { "type": "archive", "archive-type": "tar-gzip", - "url": "https://static.crates.io/crates/zmij/zmij-1.0.19.crate", - "sha256": "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445", - "dest": "cargo/vendor/zmij-1.0.19" + "url": "https://static.crates.io/crates/zerocopy-derive/zerocopy-derive-0.8.50.crate", + "sha256": "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639", + "dest": "cargo/vendor/zerocopy-derive-0.8.50" }, { "type": "inline", - "contents": "{\"package\": \"3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445\", \"files\": {}}", - "dest": "cargo/vendor/zmij-1.0.19", + "contents": "{\"package\": \"0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639\", \"files\": {}}", + "dest": "cargo/vendor/zerocopy-derive-0.8.50", "dest-filename": ".cargo-checksum.json" }, { "type": "inline", - "contents": "[source.vendored-sources]\ndirectory = \"cargo/vendor\"\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n", + "contents": "[source.vendored-sources]\ndirectory = \"cargo/vendor\"\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n\n[source.\"https://github.com/mullvad/udp-over-tcp\"]\ngit = \"https://github.com/mullvad/udp-over-tcp\"\nreplace-with = \"vendored-sources\"\nrev = \"5c6d8f44a5aa12ed9bb4ae51dd17e5e22e5ec303\"\n", "dest": "cargo", "dest-filename": "config" } diff --git a/linux/mozillavpn.spec b/linux/mozillavpn.spec index 82ae89c05b..1240867446 100644 --- a/linux/mozillavpn.spec +++ b/linux/mozillavpn.spec @@ -69,6 +69,7 @@ install %{_srcdir}/LICENSE.md %{buildroot}/%{_licensedir}/%{name}/ %{_unitdir}/mozillavpn.service %{_unitdir}/socksproxy.service %{_bindir}/mozillavpn +%{_bindir}/mozillavpn-obfuscator %{_bindir}/socksproxy %{_prefix}/lib/mozilla/native-messaging-hosts/mozillavpn.json %{_datadir}/applications/org.mozilla.vpn.desktop diff --git a/obfuscators/Cargo.toml b/obfuscators/Cargo.toml new file mode 100644 index 0000000000..74bdcf01d8 --- /dev/null +++ b/obfuscators/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "obfuscators" +version = "0.1.0" +edition = "2021" + +[lib] +name = "obfuscators" +path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] + +[[bin]] +name = "mozillavpn-obfuscator" +path = "src/main.rs" + +[dependencies] +rand = "0.8" +log = "0.4" +base64 = "0.22" +once_cell = "1.21.4" +tokio = { version = "1", features = ["rt", "macros", "time", "net"] } +udp-over-tcp = { git = "https://github.com/mullvad/udp-over-tcp", rev = "5c6d8f44a5aa12ed9bb4ae51dd17e5e22e5ec303" } +clap = { version = "=4.5.20", features = ["derive"] } diff --git a/obfuscators/src/factory.rs b/obfuscators/src/factory.rs new file mode 100644 index 0000000000..352448201a --- /dev/null +++ b/obfuscators/src/factory.rs @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; + +use crate::obfuscator::{Config, Obfuscator, ObfuscationMethod}; + +use crate::udp_over_tcp::UdpOverTcpObfuscator; + +pub fn create_obfuscator(cfg: &Config) -> io::Result> { + log::info!("Creating obfuscator for config: {cfg:#?}"); + match cfg.method { + ObfuscationMethod::UdpOverTcp => Ok(Box::new(UdpOverTcpObfuscator::new(cfg)?)), + + // No obfuscation should raise an error as the daemon should skip start the obfuscator in this case + m => Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("obfuscation method not implemented: {m:?}"), + )), + } +} diff --git a/obfuscators/src/lib.rs b/obfuscators/src/lib.rs new file mode 100644 index 0000000000..baed8a0618 --- /dev/null +++ b/obfuscators/src/lib.rs @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::os::raw::c_char; +use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use logger::Logger; + +pub mod factory; +mod logger; +#[allow(dead_code)] +mod obfuscator; +mod udp_over_tcp; + +pub use obfuscator::{Config, ObfuscationMethod, Obfuscator, ObfuscatorConfig}; + +/// Opaque handle held by the JNA caller. +/// Owns the runner thread and the shutdown flag dropping it stops the obfuscator. +pub struct ObfuscatorHandle { + shutdown: Arc, + thread: Option>, + local_port: u16, + socket_v4: i32, + socket_v6: i32, +} + +impl Drop for ObfuscatorHandle { + fn drop(&mut self) { + self.shutdown.store(true, Ordering::Release); + if let Some(t) = self.thread.take() { + let _ = t.join(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn obfuscator_start( + cfg: *const ObfuscatorConfig, +) -> *mut ObfuscatorHandle { + let Some(cfg) = Config::from_c(cfg) else { + return ptr::null_mut(); + }; + + let Ok(mut obf) = factory::create_obfuscator(&cfg) else { + return ptr::null_mut(); + }; + + let local_port = obf.local_port(); + let socket_v4 = obf.socket_v4(); + let socket_v6 = obf.socket_v6(); + + let shutdown = Arc::new(AtomicBool::new(false)); + let shutdown_run = Arc::clone(&shutdown); + let Ok(thread) = thread::Builder::new() + .name("obf-runner".into()) + .spawn(move || obf.run(shutdown_run)) + else { + return ptr::null_mut(); + }; + + Box::into_raw(Box::new(ObfuscatorHandle { + shutdown, + thread: Some(thread), + local_port, + socket_v4, + socket_v6, + })) +} + +#[no_mangle] +pub unsafe extern "C" fn obfuscator_local_port( + handle: *const ObfuscatorHandle, +) -> u16 { + if handle.is_null() { + return 0; + } + (*handle).local_port +} + +#[no_mangle] +pub unsafe extern "C" fn obfuscator_socket_v4( + handle: *const ObfuscatorHandle, +) -> i32 { + if handle.is_null() { + return -1; + } + (*handle).socket_v4 +} + + +#[no_mangle] +pub unsafe extern "C" fn obfuscator_socket_v6( + handle: *const ObfuscatorHandle, +) -> i32 { + if handle.is_null() { + return -1; + } + (*handle).socket_v6 +} + +#[no_mangle] +pub unsafe extern "C" fn obfuscator_stop(handle: *mut ObfuscatorHandle) { + if handle.is_null() { + return; + } + drop(Box::from_raw(handle)); +} + +#[no_mangle] +pub extern "C" fn obfuscators_set_log_handler(message_handler: extern "C" fn(i32, *mut c_char)){ + Logger::init(message_handler); +} diff --git a/obfuscators/src/logger.rs b/obfuscators/src/logger.rs new file mode 100644 index 0000000000..041c22fc2b --- /dev/null +++ b/obfuscators/src/logger.rs @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::os::raw::c_char; +use std::ffi::CString; + +use log::{Level, LevelFilter, Log, Metadata, Record}; +use once_cell::sync::OnceCell; + +// Logger implementation to integrate the Rust logs with src/loghandler.cpp. +// +// This message handler should be initialized alogside the Qt message handler. +pub struct Logger { + message_handler: extern "C" fn(i32, *mut c_char) +} + +static LOGGER: OnceCell = OnceCell::new(); + +impl Logger { + fn new(f: extern "C" fn(i32, *mut c_char)) -> Logger { + Logger { + message_handler: f + } + } + + pub fn init(f: extern "C" fn(i32, *mut c_char)) { + let logger = LOGGER.get_or_init(|| Logger::new(f)); + if log::set_logger(logger).is_ok() { + log::set_max_level(LevelFilter::Info); + } + } +} + +impl Log for Logger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + // The numbers here are dictated by the order of the enum on + // src/loglevel.h + let log_level: i32 = match record.level() { + Level::Error => 4, + Level::Warn => 3, + Level::Info => 2, + Level::Debug => 1, + Level::Trace => 0, + }; + + if let Ok(message) = CString::new(record.args().to_string()) { + (self.message_handler)(log_level, message.as_ptr() as *mut c_char); + } + } + + fn flush(&self) {} +} \ No newline at end of file diff --git a/obfuscators/src/main.rs b/obfuscators/src/main.rs new file mode 100644 index 0000000000..f83a7ea2c4 --- /dev/null +++ b/obfuscators/src/main.rs @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::ffi::{c_char, CStr}; +use std::net::IpAddr; +use std::process::ExitCode; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use clap::{Parser, Subcommand}; + +use obfuscators::factory::create_obfuscator; +use obfuscators::{Config, ObfuscationMethod}; + +#[derive(Parser)] +#[command( + name = "mozillavpn-obfuscator", + about = "Run a Mozilla VPN obfuscator as a standalone local proxy", + version +)] +struct Cli { + #[command(subcommand)] + obfuscator: Obfuscator, +} + +#[derive(Subcommand)] +enum Obfuscator { + /// Tunnel WireGuard UDP through a TCP stream. + UdpOverTcp { + /// Server IPv4 or IPv6 address. + #[arg(short, long)] + server: IpAddr, + /// Server TCP port. + #[arg(short, long, value_parser = parse_nonzero_port)] + port: u16, + /// Local UDP port to listen on (127.0.0.1). Defaults to OS-assigned. + #[arg(short, long, value_parser = parse_nonzero_port)] + listen_port: Option, + /// Fwmark + #[arg(long)] + fwmark: Option, + }, +} + +fn parse_nonzero_port(s: &str) -> Result { + let port: u16 = s.parse().map_err(|_| format!("invalid port '{s}'"))?; + if port == 0 { + return Err("port cannot be 0".into()); + } + Ok(port) +} + +fn main() -> ExitCode { + let cli = Cli::parse(); + obfuscators::obfuscators_set_log_handler(log_to_stderr); + + let cfg = match cli.obfuscator { + Obfuscator::UdpOverTcp { server, port, listen_port, fwmark } => { + let (server_ipv4, server_ipv6) = match server { + IpAddr::V4(v4) => (Some(v4), None), + IpAddr::V6(v6) => (None, Some(v6)), + }; + Config { + method: ObfuscationMethod::UdpOverTcp, + public_key: None, + server_ipv4, + server_ipv6, + server_port: port, + listen_port: listen_port.unwrap_or(0), + server_public_key: None, + fwmark, + } + } + }; + + let mut obf = match create_obfuscator(&cfg) { + Ok(o) => o, + Err(e) => { + eprintln!("error: failed to start obfuscator: {e}"); + return ExitCode::FAILURE; + } + }; + + println!("listening on 127.0.0.1:{}", obf.local_port()); + obf.run(Arc::new(AtomicBool::new(false))); + ExitCode::SUCCESS +} + +extern "C" fn log_to_stderr(level: i32, msg: *mut c_char) { + if msg.is_null() { + return; + } + let tag = match level { + 0 => "TRACE", + 1 => "DEBUG", + 2 => "INFO", + 3 => "WARN", + 4 => "ERROR", + _ => "LOG", + }; + let s = unsafe { CStr::from_ptr(msg) }.to_string_lossy(); + eprintln!("[{tag}] {s}"); +} diff --git a/obfuscators/src/obfuscator.rs b/obfuscators/src/obfuscator.rs new file mode 100644 index 0000000000..9157c3b36a --- /dev/null +++ b/obfuscators/src/obfuscator.rs @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use std::ffi::{c_char, CStr}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::str::FromStr; + +use base64::engine::general_purpose::STANDARD as BASE64; +use base64::Engine; + +pub const WG_KEY_LEN: usize = 32; + + +/// An obfuscator is a self-contained proxy. Implementations bind their own +/// sockets in their constructor, expose the bound port + outbound socket FDs. +pub trait Obfuscator: Send { + /// Local UDP port the obfuscator is listening on (always on 127.0.0.1). + /// Callers rewrite the WireGuard peer endpoint to this port. + fn local_port(&self) -> u16; + + /// Outbound socket FD for IPv4 traffic, or -1 if not used. + /// The platform layer marks/protects this. + fn socket_v4(&self) -> i32 { + -1 + } + + /// Outbound socket FD for IPv6 traffic, or -1 if not used. + fn socket_v6(&self) -> i32 { + -1 + } + + fn run(&mut self, shutdown: Arc); +} + +// Obfuscation methods enum, remember to keep in sync with the ones in settingsholder.h and models/server.h +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] + +pub enum ObfuscationMethod { + NoObfuscation = 0, + Lwo = 1, + Masque = 2, + UdpOverTcp = 3, + Shadowsocks = 4, +} + +impl ObfuscationMethod { + pub fn from_u32(v: u32) -> Option { + match v { + 0 => Some(Self::NoObfuscation), + 1 => Some(Self::Lwo), + 2 => Some(Self::Masque), + 3 => Some(Self::UdpOverTcp), + 4 => Some(Self::Shadowsocks), + _ => None, + } + } +} + +/// C-ABI view: what the JNA caller actually fills in and passes across the FFI. +#[repr(C)] +pub struct ObfuscatorConfig { + pub obfuscation_method: u32, + pub server_ipv4_addr_in: *const c_char, + pub server_ipv6_addr_in: *const c_char, + pub server_port: u16, + /// Local UDP port the obfuscator should listen on (127.0.0.1). + /// 0 = let the OS pick a free port. + pub listen_port: u16, + // Wireguard keys required by LWO + pub server_public_key: *const c_char, + pub public_key: *const c_char, + #[cfg(target_os = "linux")] + pub fwmark: u32, +} + +/// Safe Rust mirror used inside the crate. +#[derive(Debug, Clone)] +pub struct Config { + pub method: ObfuscationMethod, + pub public_key: Option<[u8; WG_KEY_LEN]>, + pub server_ipv4: Option, + pub server_ipv6: Option, + pub server_port: u16, + pub listen_port: u16, + pub server_public_key: Option<[u8; WG_KEY_LEN]>, + #[cfg(target_os = "linux")] + pub fwmark: Option, +} + +impl Config { + /// Convert a C struct from C++ into a safe Rust value. + /// Returns None in case of error: obfuscation method is unknown, the port is zero, or both addresses are missing / unparseable. + pub unsafe fn from_c(cfg: *const ObfuscatorConfig) -> Option { + if cfg.is_null() { + return None; + } + let cfg = &*cfg; + let method = ObfuscationMethod::from_u32(cfg.obfuscation_method)?; + if cfg.server_port == 0 { + return None; + } + let server_ipv4 = parse_addr::(cfg.server_ipv4_addr_in); + let server_ipv6 = parse_addr::(cfg.server_ipv6_addr_in); + if server_ipv4.is_none() && server_ipv6.is_none() { + return None; + } + let public_key = parse_wg_key(cfg.public_key); + let server_public_key = parse_wg_key(cfg.server_public_key); + + Some(Self { + method, + public_key, + server_ipv4, + server_ipv6, + server_port: cfg.server_port, + listen_port: cfg.listen_port, + server_public_key, + #[cfg(target_os = "linux")] + fwmark: if cfg.fwmark == 0 { None } else { Some(cfg.fwmark) }, + }) + } + + pub fn server_addr(&self) -> SocketAddr { + let ip: IpAddr = match (self.server_ipv4, self.server_ipv6) { + (Some(v4), _) => v4.into(), + (None, Some(v6)) => v6.into(), + (None, None) => unreachable!("from_c rejects configs with no address"), + }; + SocketAddr::new(ip, self.server_port) + } +} + +unsafe fn parse_addr(p: *const c_char) -> Option { + if p.is_null() { + return None; + } + let s = CStr::from_ptr(p).to_str().ok()?; + if s.is_empty() { + return None; + } + T::from_str(s).ok() +} + +unsafe fn parse_wg_key(p: *const c_char) -> Option<[u8; WG_KEY_LEN]> { + if p.is_null() { + return None; + } + let s = CStr::from_ptr(p).to_str().ok()?; + if s.is_empty() { + return None; + } + let decoded = BASE64.decode(s).ok()?; + decoded.try_into().ok() +} diff --git a/obfuscators/src/udp_over_tcp.rs b/obfuscators/src/udp_over_tcp.rs new file mode 100644 index 0000000000..62adc0d619 --- /dev/null +++ b/obfuscators/src/udp_over_tcp.rs @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::net::SocketAddr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use tokio::runtime::Runtime; +use udp_over_tcp::{TcpOptions, Udp2Tcp}; + +use crate::obfuscator::{Obfuscator, Config}; + +const SHUTDOWN_POLL: Duration = Duration::from_millis(200); + +/// Tunnels UDP traffic through a TCP stream using mullvad/udp-over-tcp. +/// The TCP socket is what callers must mark/protect. + +pub struct UdpOverTcpObfuscator { + runtime: Option, + inner: Option, + local_port: u16, + socket_v4: i32, + socket_v6: i32, +} + +impl UdpOverTcpObfuscator { + pub fn new(cfg: &Config) -> io::Result { + let server = cfg.server_addr(); + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + + let listen_addr: SocketAddr = ([127, 0, 0, 1], cfg.listen_port).into(); + let mut tcp_options = TcpOptions::default(); + tcp_options.nodelay = true; + #[cfg(target_os = "linux")] + { + tcp_options.fwmark = cfg.fwmark; + } + let inner = runtime + .block_on(Udp2Tcp::new(listen_addr, server, tcp_options)) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + + let local_port = inner.local_udp_addr()?.port(); + let (socket_v4, socket_v6) = tcp_fds(&inner, server); + + Ok(Self { + runtime: Some(runtime), + inner: Some(inner), + local_port, + socket_v4, + socket_v6, + }) + } +} + +impl Obfuscator for UdpOverTcpObfuscator { + fn local_port(&self) -> u16 { + self.local_port + } + fn socket_v4(&self) -> i32 { + self.socket_v4 + } + fn socket_v6(&self) -> i32 { + self.socket_v6 + } + + fn run(&mut self, shutdown: Arc) { + let Some(runtime) = self.runtime.take() else { + return; + }; + let Some(inner) = self.inner.take() else { + return; + }; + + runtime.block_on(async move { + let run_fut = inner.run(); + tokio::pin!(run_fut); + loop { + tokio::select! { + res = &mut run_fut => { + let _ = res; + return; + } + _ = tokio::time::sleep(SHUTDOWN_POLL) => { + if shutdown.load(Ordering::Acquire) { + return; + } + } + } + } + }); + } +} + +#[cfg(unix)] +fn tcp_fds(inner: &Udp2Tcp, server: SocketAddr) -> (i32, i32) { + let fd = inner.remote_tcp_fd(); + match server { + SocketAddr::V4(_) => (fd, -1), + SocketAddr::V6(_) => (-1, fd), + } +} + +#[cfg(not(unix))] +fn tcp_fds(_inner: &Udp2Tcp, _server: SocketAddr) -> (i32, i32) { + (-1, -1) +} \ No newline at end of file diff --git a/scripts/cmake/rustlang.cmake b/scripts/cmake/rustlang.cmake index 1cfb4d90f8..31ff6e547e 100644 --- a/scripts/cmake/rustlang.cmake +++ b/scripts/cmake/rustlang.cmake @@ -155,6 +155,117 @@ if(NOT HAS_CARGO_POOL) set_property(GLOBAL APPEND PROPERTY JOB_POOLS cargo=1) endif() +## Append the appropriate SDKROOT entry to an env list for Apple targets. +## Updates the variable named by OUT_VAR in the caller's scope. +function(__rust_append_apple_sdkroot OUT_VAR ARCH) + set(_env "${${OUT_VAR}}") + if((ARCH STREQUAL "aarch64-apple-ios-sim") OR (ARCH STREQUAL "x86_64-apple-ios")) + execute_process(OUTPUT_VARIABLE IOS_SDK_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND xcrun --sdk ${CMAKE_OSX_SYSROOT} --show-sdk-version) + execute_process(OUTPUT_VARIABLE IOS_SIMULATOR_SDKROOT OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND xcrun --sdk iphonesimulator${IOS_SDK_VERSION} --show-sdk-path) + list(APPEND _env SDKROOT=${IOS_SIMULATOR_SDKROOT}) + elseif(APPLE AND CMAKE_OSX_SYSROOT) + if(IS_DIRECTORY ${CMAKE_OSX_SYSROOT}) + list(APPEND _env "SDKROOT=${CMAKE_OSX_SYSROOT}") + else() + execute_process(OUTPUT_VARIABLE _sdkroot OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND xcrun --sdk ${CMAKE_OSX_SYSROOT} --show-sdk-path) + list(APPEND _env "SDKROOT=${_sdkroot}") + endif() + endif() + set(${OUT_VAR} "${_env}" PARENT_SCOPE) +endfunction() + +## Guess rust target architecture(s) from the CMake configuration. +function(__rust_guess_target_arch OUT_VAR) + set(_arch "") + if((CMAKE_SYSTEM_NAME STREQUAL "Darwin") AND CMAKE_OSX_ARCHITECTURES) + # Special case for MacOS universal binaries. + foreach(OSXARCH ${CMAKE_OSX_ARCHITECTURES}) + string(REPLACE "arm64" "aarch64" OSXARCH ${OSXARCH}) + list(APPEND _arch "${OSXARCH}-apple-darwin") + endforeach() + elseif(NOT CMAKE_CROSSCOMPILING) + set(_arch ${RUSTC_HOST_ARCH}) + elseif(CMAKE_C_COMPILER_TARGET) + # If set, the C compiler triple makes a reasonable guess. + set(_arch ${CMAKE_C_COMPILER_TARGET}) + else() + message(FATAL_ERROR "Unable to determine rust target architecture when cross compiling.") + endif() + set(${OUT_VAR} "${_arch}" PARENT_SCOPE) +endfunction() + +## Emit add_custom_command rules that lipo per-arch artifacts into +## ${BINARY_DIR}/unified/{debug,release}/${FILENAME}. +function(__rust_lipo_unified) + cmake_parse_arguments(LIPO + "" + "BINARY_DIR;FILENAME" + "RELEASE_INPUTS;DEBUG_INPUTS" + ${ARGN}) + foreach(PROFILE debug release) + if(PROFILE STREQUAL "release") + set(_inputs ${LIPO_RELEASE_INPUTS}) + else() + set(_inputs ${LIPO_DEBUG_INPUTS}) + endif() + add_custom_command( + OUTPUT ${LIPO_BINARY_DIR}/unified/${PROFILE}/${LIPO_FILENAME} + DEPENDS ${_inputs} + COMMAND ${CMAKE_COMMAND} -E make_directory ${LIPO_BINARY_DIR}/unified/${PROFILE} + COMMAND ${LIPO_BUILD_TOOL} -create -output ${LIPO_BINARY_DIR}/unified/${PROFILE}/${LIPO_FILENAME} + ${_inputs} + ) + endforeach() +endfunction() + +## Emit add_custom_command rules to invoke cargo for one (ARCH, PROFILE) pair. +## CARGO_ARGS supplies the subcommand args that differ between libraries +## (--lib) and binaries (--bin NAME); everything else is shared. +function(__rust_add_cargo_build_command) + cmake_parse_arguments(RUST_BUILD + "" + "ARCH;BINARY_DIR;PACKAGE_DIR;OUTPUT_FILE;DEPFILE;PROFILE" + "CARGO_ENV;CARGO_ARGS" + ${ARGN}) + + set(_outdir ${RUST_BUILD_BINARY_DIR}/${RUST_BUILD_ARCH}/${RUST_BUILD_PROFILE}) + set(_args ${RUST_BUILD_CARGO_ARGS} --target ${RUST_BUILD_ARCH} --target-dir ${RUST_BUILD_BINARY_DIR}) + if(RUST_BUILD_PROFILE STREQUAL "release") + list(APPEND _args --release) + endif() + + if((CMAKE_GENERATOR MATCHES "Ninja") OR (CMAKE_GENERATOR MATCHES "Makefiles") OR XCODE) + ## If the generator supports it, we can improve build times by setting + ## a DEPFILE to let CMake know when the artifact needs rebuilding. + cmake_policy(PUSH) + cmake_policy(SET CMP0116 NEW) + add_custom_command( + OUTPUT ${_outdir}/${RUST_BUILD_OUTPUT_FILE} + DEPFILE ${_outdir}/${RUST_BUILD_DEPFILE} + JOB_POOL cargo + WORKING_DIRECTORY ${RUST_BUILD_PACKAGE_DIR} + COMMAND ${CMAKE_COMMAND} -E env ${RUST_BUILD_CARGO_ENV} + ${CARGO_BUILD_TOOL} build ${_args} + ) + cmake_policy(POP) + else() + ## For all other generators, set a non-existent output file to force + ## the command to be invoked on every build. This relies on cargo to + ## rebuild if necessary. + add_custom_command( + OUTPUT + ${_outdir}/${RUST_BUILD_OUTPUT_FILE} + ${_outdir}/.noexist + WORKING_DIRECTORY ${RUST_BUILD_PACKAGE_DIR} + COMMAND ${CMAKE_COMMAND} -E env ${RUST_BUILD_CARGO_ENV} + ${CARGO_BUILD_TOOL} build ${_args} + ) + endif() +endfunction() + ### Helper function to get the rust library filename with extension. # # Sets the variable "RUST_LIBRARY_FILENAME" with the value. @@ -170,35 +281,30 @@ function(get_rust_library_filename SHARED CRATE_NAME) endif() endfunction() -### Helper function to build Rust static libraries. +### Helper to build a Rust artifact (library or binary) for one architecture. # # Accepts the following arguments: -# ARCH: Rust target architecture to build with --target ${ARCH} +# KIND: "library" or "binary". Required. +# ARCH: Rust target architecture to build with --target ${ARCH}. Required. # BINARY_DIR: Binary directory to output build artifacts to. -# PACKAGE_DIR: Soruce directory where Cargo.toml can be found. -# LIBRARY_FILE: Filename of the expected library to be built. -# CARGO_ENV: Environment variables to pass to cargo -# SHARED: Whether or not we are building a shared library. Defaults to "false". -# -# This function generates commands necessary to build static archives -# in ${BINARY_DIR}/${ARCH}/debug/ and ${BINARY_DIR}/${ARCH}/release/ -# and it is up to the caller of this function to link the artifacts -# into their targets as necessary. -# -# This function is intended to be used internally by add_rust_library, -# you should consider using that instead. +# PACKAGE_DIR: Source directory where Cargo.toml can be found. +# CRATE_NAME: Name of the crate to build. Required. +# BIN_NAME: For KIND=binary, the name of the [[bin]] target. Defaults to CRATE_NAME. +# CARGO_ENV: Environment variables to pass to cargo. +# SHARED: For KIND=library, whether to build a shared library. Required for libraries. # -function(build_rust_archives) +# Generates the add_custom_command rules to produce the artifact in +# ${BINARY_DIR}/${ARCH}/{debug,release}/. Intended for internal use only; +# callers should prefer add_rust_library / add_rust_binary. +function(__rust_build_cargo_target) cmake_parse_arguments(RUST_BUILD "" - "ARCH;BINARY_DIR;PACKAGE_DIR;CRATE_NAME" + "KIND;ARCH;BINARY_DIR;PACKAGE_DIR;CRATE_NAME;BIN_NAME" "CARGO_ENV;SHARED" ${ARGN}) - list(APPEND RUST_BUILD_CARGO_ENV CARGO_HOME=${CMAKE_BINARY_DIR}/cargo_home) - - if(NOT DEFINED RUST_BUILD_SHARED) - message(FATAL_ERROR "Mandatory argument SHARED was not found") + if(NOT RUST_BUILD_KIND) + message(FATAL_ERROR "Mandatory argument KIND was not found") endif() if(NOT RUST_BUILD_CRATE_NAME) message(FATAL_ERROR "Mandatory argument CRATE_NAME was not found") @@ -213,86 +319,43 @@ function(build_rust_archives) set(RUST_BUILD_PACKAGE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) endif() - ## Some files that we will be building. - file(MAKE_DIRECTORY ${RUST_BUILD_BINARY_DIR}) - get_rust_library_filename(${RUST_BUILD_SHARED} ${RUST_BUILD_CRATE_NAME}) + list(APPEND RUST_BUILD_CARGO_ENV CARGO_HOME=${CMAKE_BINARY_DIR}/cargo_home) - ## For iOS simulator targets, find the SDKROOT of the simulator matching the - ## iOS platform SDK. - if((RUST_BUILD_ARCH STREQUAL "aarch64-apple-ios-sim") OR (RUST_BUILD_ARCH STREQUAL "x86_64-apple-ios")) - execute_process(OUTPUT_VARIABLE IOS_SDK_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND xcrun --sdk ${CMAKE_OSX_SYSROOT} --show-sdk-version) - execute_process(OUTPUT_VARIABLE IOS_SIMULATOR_SDKROOT OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND xcrun --sdk iphonesimulator${IOS_SDK_VERSION} --show-sdk-path) - list(APPEND RUST_BUILD_CARGO_ENV SDKROOT=${IOS_SIMULATOR_SDKROOT}) - elseif(APPLE AND CMAKE_OSX_SYSROOT) - if (IS_DIRECTORY ${CMAKE_OSX_SYSROOT}) - list(APPEND RUST_BUILD_CARGO_ENV "SDKROOT=${CMAKE_OSX_SYSROOT}") - else() - execute_process(OUTPUT_VARIABLE RUST_BUILD_SDKROOT OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND xcrun --sdk ${CMAKE_OSX_SYSROOT} --show-sdk-path) - list(APPEND RUST_BUILD_CARGO_ENV "SDKROOT=${RUST_BUILD_SDKROOT}") + if(RUST_BUILD_KIND STREQUAL "library") + if(NOT DEFINED RUST_BUILD_SHARED) + message(FATAL_ERROR "Mandatory argument SHARED was not found") endif() - endif() - - if((CMAKE_GENERATOR MATCHES "Ninja") OR (CMAKE_GENERATOR MATCHES "Makefiles") OR XCODE) - ## If the generator supports it, we can improve build times by setting - # a DEPFILE to let CMake know when the library needs building and when - # we can skip it. - set(RUST_BUILD_DEPENDENCY_FILE - ${CMAKE_STATIC_LIBRARY_PREFIX}${RUST_BUILD_CRATE_NAME}.d - ) - cmake_policy(PUSH) - cmake_policy(SET CMP0116 NEW) - - ## Outputs for the release build - add_custom_command( - OUTPUT ${RUST_BUILD_BINARY_DIR}/${ARCH}/release/${RUST_LIBRARY_FILENAME} - DEPFILE ${RUST_BUILD_BINARY_DIR}/${ARCH}/release/${RUST_BUILD_DEPENDENCY_FILE} - JOB_POOL cargo - WORKING_DIRECTORY ${RUST_BUILD_PACKAGE_DIR} - COMMAND ${CMAKE_COMMAND} -E env ${RUST_BUILD_CARGO_ENV} - ${CARGO_BUILD_TOOL} build --lib --release --target ${ARCH} --target-dir ${RUST_BUILD_BINARY_DIR} - ) - - ## Outputs for the debug build - add_custom_command( - OUTPUT ${RUST_BUILD_BINARY_DIR}/${ARCH}/debug/${RUST_LIBRARY_FILENAME} - DEPFILE ${RUST_BUILD_BINARY_DIR}/${ARCH}/debug/${RUST_BUILD_DEPENDENCY_FILE} - JOB_POOL cargo - WORKING_DIRECTORY ${RUST_BUILD_PACKAGE_DIR} - COMMAND ${CMAKE_COMMAND} -E env ${RUST_BUILD_CARGO_ENV} - ${CARGO_BUILD_TOOL} build --lib --target ${ARCH} --target-dir ${RUST_BUILD_BINARY_DIR} - ) - - ## Reset our policy changes - cmake_policy(POP) + get_rust_library_filename(${RUST_BUILD_SHARED} ${RUST_BUILD_CRATE_NAME}) + set(_output ${RUST_LIBRARY_FILENAME}) + set(_depfile ${CMAKE_STATIC_LIBRARY_PREFIX}${RUST_BUILD_CRATE_NAME}.d) + set(_cargo_args --lib) + elseif(RUST_BUILD_KIND STREQUAL "binary") + if(NOT RUST_BUILD_BIN_NAME) + set(RUST_BUILD_BIN_NAME ${RUST_BUILD_CRATE_NAME}) + endif() + get_rust_binary_filename(${RUST_BUILD_BIN_NAME}) + set(_output ${RUST_BINARY_FILENAME}) + set(_depfile ${RUST_BUILD_BIN_NAME}.d) + set(_cargo_args --bin ${RUST_BUILD_BIN_NAME}) else() - ## For all other generators, set a non-existent output file to force - # the command to be invoked on every build. This ensures that the - # library stays up todate with the sources, and relies on cargo to - # rebuild if necessary. - - ## Outputs for the release build - add_custom_command( - OUTPUT - ${RUST_BUILD_BINARY_DIR}/${ARCH}/release/${RUST_LIBRARY_FILENAME} - ${RUST_BUILD_BINARY_DIR}/${ARCH}/release/.noexist - WORKING_DIRECTORY ${RUST_BUILD_PACKAGE_DIR} - COMMAND ${CMAKE_COMMAND} -E env ${RUST_BUILD_CARGO_ENV} - ${CARGO_BUILD_TOOL} build --lib --release --target ${ARCH} --target-dir ${RUST_BUILD_BINARY_DIR} - ) + message(FATAL_ERROR "Invalid KIND \"${RUST_BUILD_KIND}\": must be 'library' or 'binary'") + endif() - ## Outputs for the debug build - add_custom_command( - OUTPUT - ${RUST_BUILD_BINARY_DIR}/${ARCH}/debug/${RUST_LIBRARY_FILENAME} - ${RUST_BUILD_BINARY_DIR}/${ARCH}/debug/.noexist - WORKING_DIRECTORY ${RUST_BUILD_PACKAGE_DIR} - COMMAND ${CMAKE_COMMAND} -E env ${RUST_BUILD_CARGO_ENV} - ${CARGO_BUILD_TOOL} build --lib --target ${ARCH} --target-dir ${RUST_BUILD_BINARY_DIR} + file(MAKE_DIRECTORY ${RUST_BUILD_BINARY_DIR}) + __rust_append_apple_sdkroot(RUST_BUILD_CARGO_ENV ${RUST_BUILD_ARCH}) + + foreach(PROFILE debug release) + __rust_add_cargo_build_command( + ARCH ${RUST_BUILD_ARCH} + BINARY_DIR ${RUST_BUILD_BINARY_DIR} + PACKAGE_DIR ${RUST_BUILD_PACKAGE_DIR} + OUTPUT_FILE ${_output} + DEPFILE ${_depfile} + PROFILE ${PROFILE} + CARGO_ENV ${RUST_BUILD_CARGO_ENV} + CARGO_ARGS ${_cargo_args} ) - endif() + endforeach() endfunction() ### Helper function to create a linkable target from a Rust package. @@ -350,30 +413,16 @@ function(add_rust_library TARGET_NAME) set(RUST_TARGET_PACKAGE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) endif() - # Guess the target architecture if not set. if(NOT RUST_TARGET_ARCH) - if((CMAKE_SYSTEM_NAME STREQUAL "Darwin") AND CMAKE_OSX_ARCHITECTURES) - # Special case for MacOS universal binaries. - foreach(OSXARCH ${CMAKE_OSX_ARCHITECTURES}) - string(REPLACE "arm64" "aarch64" OSXARCH ${OSXARCH}) - list(APPEND RUST_TARGET_ARCH "${OSXARCH}-apple-darwin") - endforeach() - elseif(NOT CMAKE_CROSSCOMPILING) - set(RUST_TARGET_ARCH ${RUSTC_HOST_ARCH}) - elseif(CMAKE_C_COMPILER_TARGET) - # If set, the C compiler triple makes a reasonable guess. - set(RUST_TARGET_ARCH ${CMAKE_C_COMPILER_TARGET}) - else() - # TODO: We could write something here for Android and IOS maybe - message(FATAL_ERROR "Unable to determine rust target architecture when cross compiling.") - endif() + __rust_guess_target_arch(RUST_TARGET_ARCH) endif() get_rust_library_filename(${RUST_TARGET_SHARED} ${RUST_TARGET_CRATE_NAME}) ## Build the rust library file(s) foreach(ARCH ${RUST_TARGET_ARCH}) - build_rust_archives( + __rust_build_cargo_target( + KIND library ARCH ${ARCH} BINARY_DIR ${RUST_TARGET_BINARY_DIR} PACKAGE_DIR ${RUST_TARGET_PACKAGE_DIR} @@ -428,19 +477,11 @@ function(add_rust_library TARGET_NAME) IMPORTED_LOCATION_DEBUG ${RUST_TARGET_BINARY_DIR}/unified/debug/${FW_NAME}.framework/${FW_NAME} ) else() - add_custom_command( - OUTPUT ${RUST_TARGET_BINARY_DIR}/unified/release/${RUST_LIBRARY_FILENAME} - DEPENDS ${RUST_TARGET_RELEASE_LIBS} - COMMAND ${CMAKE_COMMAND} -E make_directory ${RUST_TARGET_BINARY_DIR}/unified/release - COMMAND ${LIPO_BUILD_TOOL} -create -output ${RUST_TARGET_BINARY_DIR}/unified/release/${RUST_LIBRARY_FILENAME} - ${RUST_TARGET_RELEASE_LIBS} - ) - add_custom_command( - OUTPUT ${RUST_TARGET_BINARY_DIR}/unified/debug/${RUST_LIBRARY_FILENAME} - DEPENDS ${RUST_TARGET_DEBUG_LIBS} - COMMAND ${CMAKE_COMMAND} -E make_directory ${RUST_TARGET_BINARY_DIR}/unified/debug - COMMAND ${LIPO_BUILD_TOOL} -create -output ${RUST_TARGET_BINARY_DIR}/unified/debug/${RUST_LIBRARY_FILENAME} - ${RUST_TARGET_DEBUG_LIBS} + __rust_lipo_unified( + BINARY_DIR ${RUST_TARGET_BINARY_DIR} + FILENAME ${RUST_LIBRARY_FILENAME} + RELEASE_INPUTS ${RUST_TARGET_RELEASE_LIBS} + DEBUG_INPUTS ${RUST_TARGET_DEBUG_LIBS} ) add_custom_target(${TARGET_NAME}_builder @@ -480,3 +521,109 @@ function(add_rust_library TARGET_NAME) add_dependencies(${TARGET_NAME} ${TARGET_NAME}_builder) set_property(TARGET ${TARGET_NAME} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS}) endfunction() + +### Helper function to get the rust binary filename with platform extension. +# +# Sets the variable "RUST_BINARY_FILENAME" with the value. +function(get_rust_binary_filename BIN_NAME) + set(RUST_BINARY_FILENAME + ${BIN_NAME}${CMAKE_EXECUTABLE_SUFFIX} + PARENT_SCOPE) +endfunction() + +### Helper function to create an IMPORTED executable target from a Rust crate. +# +# This function takes one mandatory argument: TARGET_NAME which sets the +# name of the CMake target to produce. +# +# Accepts the following optional arguments: +# ARCH: Rust target architecture(s) to build with --target ${ARCH} +# BINARY_DIR: Binary directory to output build artifacts to. +# PACKAGE_DIR: Source directory where Cargo.toml can be found. +# CRATE_NAME: Name of the crate (Cargo package) to build. Required. +# BIN_NAME: Name of the [[bin]] target inside the crate. Defaults to CRATE_NAME. +# CARGO_ENV: Environment variables to pass to cargo. +# DEPENDS: Additional files on which the binary depends. +# +# Produces a custom target named ${TARGET_NAME} that builds the binary and +# sets ${TARGET_NAME}_EXECUTABLE in the caller's scope to the path of the built +# binary (usable in install()). +# On macOS multiple architectures are combined into a universal binary using +# lipo, mirroring add_rust_library. iOS targets are not supported and should +# not call this function. +function(add_rust_binary TARGET_NAME) + cmake_parse_arguments(RUST_TARGET + "" + "BINARY_DIR;PACKAGE_DIR;CRATE_NAME;BIN_NAME" + "ARCH;CARGO_ENV;DEPENDS" + ${ARGN}) + + if(IOS) + message(FATAL_ERROR "add_rust_binary is not supported on iOS; use add_rust_library instead.") + endif() + + if(NOT RUST_TARGET_CRATE_NAME) + message(FATAL_ERROR "Mandatory argument CRATE_NAME was not found") + endif() + if(NOT RUST_TARGET_BIN_NAME) + set(RUST_TARGET_BIN_NAME ${RUST_TARGET_CRATE_NAME}) + endif() + if(NOT RUST_TARGET_BINARY_DIR) + set(RUST_TARGET_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) + endif() + if(NOT RUST_TARGET_PACKAGE_DIR) + set(RUST_TARGET_PACKAGE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + if(NOT RUST_TARGET_ARCH) + __rust_guess_target_arch(RUST_TARGET_ARCH) + endif() + + get_rust_binary_filename(${RUST_TARGET_BIN_NAME}) + + ## Build the rust binary for each requested architecture. + foreach(ARCH ${RUST_TARGET_ARCH}) + __rust_build_cargo_target( + KIND binary + ARCH ${ARCH} + BINARY_DIR ${RUST_TARGET_BINARY_DIR} + PACKAGE_DIR ${RUST_TARGET_PACKAGE_DIR} + CRATE_NAME ${RUST_TARGET_CRATE_NAME} + BIN_NAME ${RUST_TARGET_BIN_NAME} + CARGO_ENV ${RUST_TARGET_CARGO_ENV} + ) + + if(RUST_TARGET_DEPENDS) + add_custom_command(APPEND + OUTPUT ${RUST_TARGET_BINARY_DIR}/${ARCH}/release/${RUST_BINARY_FILENAME} + DEPENDS ${RUST_TARGET_DEPENDS} + ) + add_custom_command(APPEND + OUTPUT ${RUST_TARGET_BINARY_DIR}/${ARCH}/debug/${RUST_BINARY_FILENAME} + DEPENDS ${RUST_TARGET_DEPENDS} + ) + endif() + + list(APPEND RUST_TARGET_RELEASE_BINS ${RUST_TARGET_BINARY_DIR}/${ARCH}/release/${RUST_BINARY_FILENAME}) + list(APPEND RUST_TARGET_DEBUG_BINS ${RUST_TARGET_BINARY_DIR}/${ARCH}/debug/${RUST_BINARY_FILENAME}) + endforeach() + + if((CMAKE_SYSTEM_NAME STREQUAL "Darwin")) + ## Combine all architectures into a single universal binary. + __rust_lipo_unified( + BINARY_DIR ${RUST_TARGET_BINARY_DIR} + FILENAME ${RUST_BINARY_FILENAME} + RELEASE_INPUTS ${RUST_TARGET_RELEASE_BINS} + DEBUG_INPUTS ${RUST_TARGET_DEBUG_BINS} + ) + set(_executable ${RUST_TARGET_BINARY_DIR}/unified/$,debug,release>/${RUST_BINARY_FILENAME}) + else() + ## For non-Darwin platforms, only build the first architecture. + list(GET RUST_TARGET_ARCH 0 RUST_FIRST_ARCH) + set(_executable ${RUST_TARGET_BINARY_DIR}/${RUST_FIRST_ARCH}/$,debug,release>/${RUST_BINARY_FILENAME}) + endif() + + add_custom_target(${TARGET_NAME} DEPENDS ${_executable}) + set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tools") + set(${TARGET_NAME}_EXECUTABLE ${_executable} PARENT_SCOPE) +endfunction() diff --git a/src/cmake/android.cmake b/src/cmake/android.cmake index c81d02739e..b2725976a3 100644 --- a/src/cmake/android.cmake +++ b/src/cmake/android.cmake @@ -10,7 +10,38 @@ if(QT_KNOWN_POLICY_QTP0002) qt_policy(SET QTP0002 OLD) endif() +## Build the obfuscators shared library. +## This is used by Android via JNA. +include(${CMAKE_SOURCE_DIR}/scripts/cmake/rustlang.cmake) + +if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64") + set(OBFUSCATORS_RUST_ARCH "aarch64-linux-android") +elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "armv7-a") + set(OBFUSCATORS_RUST_ARCH "armv7-linux-androideabi") +elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i686") + set(OBFUSCATORS_RUST_ARCH "i686-linux-android") +elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64") + set(OBFUSCATORS_RUST_ARCH "x86_64-linux-android") +else() + message(FATAL_ERROR "Unsupported Android architecture for obfuscators: ${CMAKE_SYSTEM_PROCESSOR}") +endif() + +set(OBFUSCATORS_CARGO_ENV + CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}/obfuscators + "RUSTFLAGS=-Ctarget-feature=-crt-static -C link-arg=-Wl,-z,max-page-size=16384 -C link-arg=-Wl,-z,common-page-size=16384" +) + +add_rust_library(obfuscators + ARCH ${OBFUSCATORS_RUST_ARCH} + PACKAGE_DIR ${CMAKE_SOURCE_DIR}/obfuscators + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/obfuscators + CRATE_NAME obfuscators + CARGO_ENV ${OBFUSCATORS_CARGO_ENV} + SHARED 1 +) + target_link_libraries(mozillavpn PRIVATE + obfuscators Qt6::Test Qt6::Xml) @@ -40,6 +71,7 @@ target_sources(mozillavpn PRIVATE ) get_property(OPENSSL_LIBS_DIR GLOBAL PROPERTY OPENSSL_LIBS) +get_property(OBFUSCATORS_LIB_LOCATION TARGET obfuscators PROPERTY LOCATION_${CMAKE_BUILD_TYPE}) # This property flags the build system to copy these # shared libraries into the expected Android shared library folder. @@ -64,6 +96,7 @@ set_property(TARGET mozillavpn PROPERTY QT_ANDROID_EXTRA_LIBS ${OPENSSL_LIBS_DIR}/libssl.so ${OPENSSL_LIBS_DIR}/libcrypto_1_1.so ${OPENSSL_LIBS_DIR}/libssl_1_1.so + ${OBFUSCATORS_LIB_LOCATION} APPEND) diff --git a/src/cmake/ios.cmake b/src/cmake/ios.cmake index 1b1e26ebea..27ad032309 100644 --- a/src/cmake/ios.cmake +++ b/src/cmake/ios.cmake @@ -104,6 +104,8 @@ target_sources(mozillavpn PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/macospingsender.h ${CMAKE_CURRENT_SOURCE_DIR}/tasks/purchase/taskpurchase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tasks/purchase/taskpurchase.h + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/dummyobfuscator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/dummyobfuscator.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosauthenticationlistener.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosauthenticationlistener.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosiaphandler.swift diff --git a/src/cmake/linux.cmake b/src/cmake/linux.cmake index eba217b7dc..483b44644c 100644 --- a/src/cmake/linux.cmake +++ b/src/cmake/linux.cmake @@ -106,6 +106,19 @@ if(NOT BUILD_FLATPAK) include(${CMAKE_SOURCE_DIR}/scripts/cmake/golang.cmake) add_go_library(netfilter ${CMAKE_SOURCE_DIR}/linux/netfilter/netfilter.go) target_link_libraries(mozillavpn PRIVATE netfilter) + + # Add the obfuscator binary + add_rust_binary(mozillavpn-obfuscator + PACKAGE_DIR ${CMAKE_SOURCE_DIR}/obfuscators + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/obfuscators_target + CRATE_NAME obfuscators + BIN_NAME mozillavpn-obfuscator + CARGO_ENV CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}/obfuscators_target + ) + add_dependencies(mozillavpn mozillavpn-obfuscator) + + # Install the obfuscator binary + install(PROGRAMS ${mozillavpn-obfuscator_EXECUTABLE} DESTINATION bin) else() # Linux source files for sandboxed builds target_compile_definitions(mozillavpn PRIVATE MZ_FLATPAK) diff --git a/src/cmake/macos-daemon.cmake b/src/cmake/macos-daemon.cmake index 680a4a3328..42daa88cb8 100644 --- a/src/cmake/macos-daemon.cmake +++ b/src/cmake/macos-daemon.cmake @@ -37,6 +37,9 @@ target_sources(daemon PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/daemon/daemonerrors.h ${CMAKE_CURRENT_SOURCE_DIR}/daemon/dnsutils.h ${CMAKE_CURRENT_SOURCE_DIR}/daemon/iputils.h + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/obfuscator.h + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/qprocessobfuscator.h + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/qprocessobfuscator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/daemon/wireguardutils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/daemon/dnsutilsmacos.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/daemon/dnsutilsmacos.h diff --git a/src/cmake/sources.cmake b/src/cmake/sources.cmake index 93c20237ff..3effb05f0f 100644 --- a/src/cmake/sources.cmake +++ b/src/cmake/sources.cmake @@ -70,6 +70,7 @@ target_sources(mozillavpn-sources INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/controller_p.h ${CMAKE_CURRENT_SOURCE_DIR}/daemon/daemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/daemon/daemon.h + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/obfuscator.h ${CMAKE_CURRENT_SOURCE_DIR}/daemon/daemonlocalserverconnection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/daemon/daemonlocalserverconnection.h ${CMAKE_CURRENT_SOURCE_DIR}/daemon/dnsutils.h @@ -194,11 +195,19 @@ if(NOT QT_FEATURE_zstd) set_property(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/resources/public_keys/public_keys.qrc PROPERTY AUTORCC_OPTIONS "--no-zstd") endif() +if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten" AND + NOT ${CMAKE_SYSTEM_NAME} STREQUAL "iOS") + target_sources(mozillavpn-sources INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/qprocessobfuscator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/qprocessobfuscator.h + ) +endif() + # Sources for desktop platforms. if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - target_sources(mozillavpn-sources INTERFACE + target_sources(mozillavpn-sources INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/systemtraynotificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/systemtraynotificationhandler.h ) diff --git a/src/cmake/wasm.cmake b/src/cmake/wasm.cmake index c071c57020..32855a9eb0 100644 --- a/src/cmake/wasm.cmake +++ b/src/cmake/wasm.cmake @@ -15,6 +15,8 @@ target_sources(mozillavpn PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/wasm/wasmwindowcontroller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/wasm/wasmiaphandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/wasm/wasmiaphandler.h + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/dummyobfuscator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/daemon/obfuscator/dummyobfuscator.h ${CMAKE_CURRENT_SOURCE_DIR}/systemtraynotificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/systemtraynotificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/tasks/purchase/taskpurchase.cpp diff --git a/src/controller.cpp b/src/controller.cpp index 724394d462..b6d95f8694 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -376,6 +376,9 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, } SettingsHolder* settingsHolder = SettingsHolder::instance(); QList returnList; + Server::ObfuscationMethod obfuscationMethod = + static_cast( + settingsHolder->obfuscationMethod()); auto allowedIPList = m_initiator == ExtensionUser ? getExtensionProxyAddressRanges(exitServer) @@ -383,6 +386,7 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, // Prepare the exit server's connection data. InterfaceConfig exitConfig; exitConfig.m_privateKey = vpn->keys()->privateKey(); + exitConfig.m_publicKey = vpn->keys()->publicKey(); exitConfig.m_deviceIpv4Address = device->ipv4Address(); exitConfig.m_deviceIpv6Address = device->ipv6Address(); exitConfig.m_serverIpv4Gateway = exitServer.ipv4Gateway(); @@ -394,6 +398,7 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, exitConfig.m_allowedIPAddressRanges = allowedIPList; exitConfig.m_dnsServer = DNSHelper::getDNS(exitServer.ipv4Gateway()); exitConfig.m_exitCity = exitServer.cityName(); + exitConfig.m_obfuscationMethod = Server::ObfuscationMethod::NoObfuscation; logger.debug() << "DNS Set" << exitConfig.m_dnsServer; if (m_impl->splitTunnelSupported()) { @@ -407,12 +412,19 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, if (!Feature::multiHop.supported || !m_serverData.multihop()) { logger.info() << "Activating single hop"; exitConfig.m_hopType = InterfaceConfig::SingleHop; + exitConfig.m_obfuscationMethod = obfuscationMethod; // If requested, force the use of port 53/DNS. if (dnsPort == ForceDNSPort) { logger.info() << "Forcing port 53"; exitConfig.m_serverPort = 53; } + + // If UDP over TCP is enabled choose a dedicated port + if (settingsHolder->obfuscationMethod() == + Server::ObfuscationMethod::UdpOverTcp) { + exitConfig.m_serverPort = exitServer.chooseTcpPort(); + } } // For controllers that support multiple hops, create a queue of connections. // The entry server should start first, followed by the exit server. @@ -435,6 +447,7 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, InterfaceConfig entryConfig; entryConfig.m_privateKey = vpn->keys()->privateKey(); + entryConfig.m_publicKey = vpn->keys()->publicKey(); entryConfig.m_deviceIpv4Address = device->ipv4Address(); entryConfig.m_deviceIpv6Address = device->ipv6Address(); entryConfig.m_serverPublicKey = entryServer.publicKey(); @@ -442,6 +455,7 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, entryConfig.m_serverIpv6AddrIn = entryServer.ipv6AddrIn(); entryConfig.m_serverPort = entryServer.choosePort(); entryConfig.m_hopType = InterfaceConfig::MultiHopEntry; + entryConfig.m_obfuscationMethod = obfuscationMethod; entryConfig.m_allowedIPAddressRanges.append( IPAddress(exitServer.ipv4AddrIn())); if (!exitServer.ipv6AddrIn().isEmpty()) { @@ -455,6 +469,11 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, logger.info() << "Forcing port 53"; entryConfig.m_serverPort = 53; } + // If UDP over TCP is enabled choose a dedicated port + if (settingsHolder->obfuscationMethod() == + Server::ObfuscationMethod::UdpOverTcp) { + entryConfig.m_serverPort = exitServer.chooseTcpPort(); + } returnList.append(entryConfig); } @@ -484,6 +503,12 @@ auto Controller::setupConfigs(DNSPortPolicy dnsPort, exitConfig.m_serverIpv4AddrIn = entryServer.ipv4AddrIn(); exitConfig.m_serverIpv6AddrIn = entryServer.ipv6AddrIn(); exitConfig.m_entryCity = entryServer.cityName(); + // We cannot emulate multihop and use UDP over TCP at the same time, LWO + // will work + exitConfig.m_obfuscationMethod = + obfuscationMethod == Server::ObfuscationMethod::LWO + ? Server::ObfuscationMethod::LWO + : Server::ObfuscationMethod::NoObfuscation; } returnList.append(exitConfig); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index fcc5c60756..f30ea285b3 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -18,8 +18,15 @@ #include "leakdetector.h" #include "logger.h" #include "loghandler.h" +#include "obfuscator/obfuscator.h" #include "wireguardutils.h" +#if defined(MZ_WASM) || defined(MZ_IOS) +# include "obfuscator/dummyobfuscator.h" +#else +# include "obfuscator/qprocessobfuscator.h" +#endif + constexpr const char* JSON_ALLOWEDIPADDRESSRANGES = "allowedIPAddressRanges"; constexpr int HANDSHAKE_POLL_MSEC = 250; @@ -137,8 +144,27 @@ bool Daemon::activate(const InterfaceConfig& config) { } } + // If an obfuscator is configured, start the relay and rewrite the peer + // endpoint so WireGuard talks to it instead of the real server. + // For multi-hop configure obfuscator only on the entry node. + std::unique_ptr obfuscator; + InterfaceConfig peerConfig = config; + if (config.m_obfuscationMethod != Server::ObfuscationMethod::NoObfuscation && + config.m_hopType != InterfaceConfig::MultiHopExit) { + obfuscator = createObfuscator(config); + if (!obfuscator->start()) { + logger.error() << "Failed to start obfuscator" + << config.m_obfuscationMethod; + return false; + } + peerConfig.m_serverIpv4AddrIn = "127.0.0.1"; + peerConfig.m_serverIpv6AddrIn = "::1"; + peerConfig.m_serverPort = obfuscator->localPort(); + m_obfuscator = std::move(obfuscator); + } + // Add the peer to this interface. - if (!wgutils()->updatePeer(config)) { + if (!wgutils()->updatePeer(peerConfig)) { logger.error() << "Peer creation failed."; return false; } @@ -224,6 +250,7 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { } GETVALUE("privateKey", config.m_privateKey, String); + GETVALUE("publicKey", config.m_publicKey, String); GETVALUE("serverPublicKey", config.m_serverPublicKey, String); GETVALUE("serverPort", config.m_serverPort, Double); @@ -332,6 +359,27 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) { return false; } + + if (!obj.contains("obfuscationMethod")) { + config.m_obfuscationMethod = Server::ObfuscationMethod::NoObfuscation; + } else { + QJsonValue value = obj.value("obfuscationMethod"); + if (!value.isString()) { + logger.error() << "obfuscationMethod is not a string"; + return false; + } + + bool okay; + QMetaEnum meta = QMetaEnum::fromType(); + config.m_obfuscationMethod = Server::ObfuscationMethod( + meta.keyToValue(value.toString().toUtf8().constData(), &okay)); + if (!okay) { + logger.error() << "obfuscationMethod" << value.toString() + << "is not valid"; + return false; + } + } + return true; } @@ -369,6 +417,9 @@ bool Daemon::deactivate(bool emitSignals) { } m_connections.clear(); + // Delete the obfuscator + m_obfuscator.reset(); + // Delete the interface return wgutils()->deleteInterface(); } @@ -386,6 +437,15 @@ QString Daemon::logs() { void Daemon::cleanLogs() { LogHandler::instance()->cleanupLogs(); } +std::unique_ptr Daemon::createObfuscator( + const InterfaceConfig& config) { +#if defined(MZ_WASM) || defined(MZ_IOS) + return std::make_unique(config); +#else + return std::make_unique(config); +#endif +} + bool Daemon::supportServerSwitching(const InterfaceConfig& config) const { if (!m_connections.contains(config.m_hopType)) { return false; @@ -409,8 +469,25 @@ bool Daemon::switchServer(const InterfaceConfig& config) { const InterfaceConfig& lastConfig = m_connections.value(config.m_hopType).m_config; + // Stand up a new obfuscator for the new endpoint (entry hop only) + std::unique_ptr obfuscator; + InterfaceConfig peerConfig = config; + if (config.m_obfuscationMethod != Server::ObfuscationMethod::NoObfuscation && + config.m_hopType != InterfaceConfig::MultiHopExit) { + obfuscator = createObfuscator(config); + if (!obfuscator->start()) { + logger.error() << "Failed to start obfuscator on switch" + << config.m_obfuscationMethod; + return false; + } + peerConfig.m_serverIpv4AddrIn = "127.0.0.1"; + peerConfig.m_serverIpv6AddrIn = QString(); + peerConfig.m_serverPort = obfuscator->localPort(); + m_obfuscator = std::move(obfuscator); + } + // Activate the new peer and its routes. - if (!wgutils()->updatePeer(config)) { + if (!wgutils()->updatePeer(peerConfig)) { logger.error() << "Server switch failed to update the peer wireguard config"; return false; diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 365cf457fb..26cc2a6324 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -7,13 +7,16 @@ #include #include +#include #include "daemon/daemonerrors.h" #include "daemonerrors.h" #include "interfaceconfig.h" +#include "obfuscator/obfuscator.h" class DnsUtils; class IPUtils; +class Obfuscator; class WireguardUtils; class Daemon : public QObject { @@ -56,6 +59,7 @@ class Daemon : public QObject { private: bool maybeUpdateResolvers(const InterfaceConfig& config); + std::unique_ptr createObfuscator(const InterfaceConfig& config); protected: virtual bool run(Op op, const InterfaceConfig& config) { @@ -84,6 +88,7 @@ class Daemon : public QObject { }; QMap m_connections; QTimer m_handshakeTimer; + std::unique_ptr m_obfuscator; }; #endif // DAEMON_H diff --git a/src/daemon/obfuscator/dummyobfuscator.cpp b/src/daemon/obfuscator/dummyobfuscator.cpp new file mode 100644 index 0000000000..31f08dd9a1 --- /dev/null +++ b/src/daemon/obfuscator/dummyobfuscator.cpp @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "dummyobfuscator.h" + +#include "leakdetector.h" +#include "logger.h" + +namespace { +Logger logger("DummyObfuscator"); +} + +DummyObfuscator::DummyObfuscator(const InterfaceConfig& config) { + MZ_COUNT_CTOR(DummyObfuscator); + Q_UNUSED(config); +} + +bool DummyObfuscator::start() { + logger.warning() << "Obfuscation is not supported on this platform"; + return false; +} + +DummyObfuscator::~DummyObfuscator() { MZ_COUNT_DTOR(DummyObfuscator); } diff --git a/src/daemon/obfuscator/dummyobfuscator.h b/src/daemon/obfuscator/dummyobfuscator.h new file mode 100644 index 0000000000..ba9f46b3dd --- /dev/null +++ b/src/daemon/obfuscator/dummyobfuscator.h @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DUMMYOBFUSCATOR_H +#define DUMMYOBFUSCATOR_H + +#include + +#include "../utils/interfaceconfig.h" +#include "obfuscator.h" + +// No-op obfuscator used on WASM +class DummyObfuscator final : public Obfuscator { + public: + explicit DummyObfuscator(const InterfaceConfig& config); + ~DummyObfuscator() override; + + bool start() override; + quint16 localPort() const override { return m_localPort; } + + private: + quint16 m_localPort = 0; +}; + +#endif // DUMMYOBFUSCATOR_H diff --git a/src/daemon/obfuscator/obfuscator.h b/src/daemon/obfuscator/obfuscator.h new file mode 100644 index 0000000000..44ee3eb895 --- /dev/null +++ b/src/daemon/obfuscator/obfuscator.h @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef OBFUSCATOR_H +#define OBFUSCATOR_H + +#include + +// Generic obfuscator interface. +class Obfuscator { + public: + virtual ~Obfuscator() = default; + + Obfuscator(const Obfuscator&) = delete; + Obfuscator& operator=(const Obfuscator&) = delete; + + virtual bool start() = 0; + + virtual quint16 localPort() const = 0; + + protected: + Obfuscator() = default; +}; + +#endif // OBFUSCATOR_H diff --git a/src/daemon/obfuscator/qprocessobfuscator.cpp b/src/daemon/obfuscator/qprocessobfuscator.cpp new file mode 100644 index 0000000000..1dd04cc1e6 --- /dev/null +++ b/src/daemon/obfuscator/qprocessobfuscator.cpp @@ -0,0 +1,130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "qprocessobfuscator.h" + +#include +#include +#include +#include +#include + +#include "leakdetector.h" +#include "logger.h" + +constexpr int OBFUSCATOR_PROC_TIMEOUT_MS = 5000; +#ifdef MZ_LINUX +constexpr uint32_t WG_FIREWALL_MARK = 0xca6c; +#endif + +namespace { +Logger logger("QProcessObfuscator"); +} + +QProcessObfuscator::QProcessObfuscator(const InterfaceConfig& config) { + MZ_COUNT_CTOR(QProcessObfuscator); + + const QStringList args = buildArgs(config); + if (args.isEmpty()) { + logger.error() << "Unsupported obfuscation method" + << config.m_obfuscationMethod; + return; + } + + const QString binary = binaryName(); + m_process.setProgram(binary); + m_process.setArguments(args); + // Merge stderr into stdout so we can read the "listening on" announce line + m_process.setProcessChannelMode(QProcess::MergedChannels); +} + +bool QProcessObfuscator::start() { + logger.debug() << "Starting obfuscator"; + m_process.start(); + if (!m_process.waitForStarted(OBFUSCATOR_PROC_TIMEOUT_MS)) { + logger.error() << "Failed to start obfuscator process" + << m_process.errorString(); + return false; + } + + // Block until the helper either announces its port or exits + while (m_process.state() == QProcess::Running) { + if (!m_process.waitForReadyRead(OBFUSCATOR_PROC_TIMEOUT_MS)) { + logger.error() << "Obfuscator did not announce a listening port"; + m_process.kill(); + m_process.waitForFinished(OBFUSCATOR_PROC_TIMEOUT_MS); + return false; + } + while (m_process.canReadLine()) { + const QByteArray line = m_process.readLine().trimmed(); + logger.debug() << "obf:" << QString::fromUtf8(line); + const quint16 port = parseListeningPort(line); + if (port != 0) { + m_localPort = port; + return true; + } + } + } + + logger.error() << "Obfuscator exited before announcing a port" + << m_process.exitCode(); + return false; +} + +quint16 QProcessObfuscator::parseListeningPort(const QByteArray& line) const { + static const QByteArray prefix = "listening on 127.0.0.1:"; + const int idx = line.indexOf(prefix); + if (idx < 0) { + return 0; + } + bool ok = false; + const quint16 port = line.mid(idx + prefix.size()).toUShort(&ok); + return ok ? port : 0; +} + +QStringList QProcessObfuscator::buildArgs(const InterfaceConfig& config) { + QStringList args; + switch (config.m_obfuscationMethod) { + case Server::ObfuscationMethod::UdpOverTcp: + args << QStringLiteral("udp-over-tcp"); + break; + default: + return {}; + } + + const QString server = !config.m_serverIpv4AddrIn.isEmpty() + ? config.m_serverIpv4AddrIn + : config.m_serverIpv6AddrIn; + args << QStringLiteral("--server") << server; + args << QStringLiteral("--port") << QString::number(config.m_serverPort); +#ifdef MZ_LINUX + args << QStringLiteral("--fwmark") << QString::number(WG_FIREWALL_MARK); +#endif + // The obfuscator needs to bind to the same interface as the WireGuard peer + return args; +} + +QString QProcessObfuscator::binaryName() const { +#if defined(MZ_WINDOWS) + return QStringLiteral("mozillavpn-obfuscator.exe"); +#elif defined(MZ_LINUX) + return QStringLiteral("mozillavpn-obfuscator"); +#else + logger.error() << "Obfuscation is not supported on this platform"; + return QString(); +#endif +} + +QProcessObfuscator::~QProcessObfuscator() { + MZ_COUNT_DTOR(QProcessObfuscator); + if (m_process.state() == QProcess::NotRunning) { + return; + } + logger.debug() << "Stopping obfuscator"; + m_process.terminate(); + if (!m_process.waitForFinished(OBFUSCATOR_PROC_TIMEOUT_MS)) { + m_process.kill(); + m_process.waitForFinished(OBFUSCATOR_PROC_TIMEOUT_MS); + } +} diff --git a/src/daemon/obfuscator/qprocessobfuscator.h b/src/daemon/obfuscator/qprocessobfuscator.h new file mode 100644 index 0000000000..3f633cb450 --- /dev/null +++ b/src/daemon/obfuscator/qprocessobfuscator.h @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef QPROCESSOBFUSCATOR_H +#define QPROCESSOBFUSCATOR_H + +#include +#include + +#include "../utils/interfaceconfig.h" +#include "obfuscator.h" + +class QProcessObfuscator final : public Obfuscator { + public: + explicit QProcessObfuscator(const InterfaceConfig& config); + ~QProcessObfuscator() override; + + bool start() override; + quint16 localPort() const override { return m_localPort; } + + private: + bool isRunning() const { return m_process.state() != QProcess::NotRunning; } + quint16 parseListeningPort(const QByteArray& line) const; + QStringList buildArgs(const InterfaceConfig& config); + QString binaryName() const; + + QProcess m_process; + quint16 m_localPort = 0; +}; + +#endif // QPROCESSOBFUSCATOR_H diff --git a/src/feature/featuremodel.h b/src/feature/featuremodel.h index 791ed6b490..c465c105ab 100644 --- a/src/feature/featuremodel.h +++ b/src/feature/featuremodel.h @@ -34,6 +34,10 @@ inline const AnyFeature s_exposedFeatures[] = { ref(multiHop), ref(networkExtension), ref(notificationControl), + ref(obfuscationLwo), + ref(obfuscationMasque), + ref(obfuscationShadowsocks), + ref(obfuscationUdpOverTcp), ref(recommendedServers), ref(replacerAddon), ref(serverUnavailableNotification), diff --git a/src/feature/features.h b/src/feature/features.h index b67fabb362..936f09f5a0 100644 --- a/src/feature/features.h +++ b/src/feature/features.h @@ -263,6 +263,30 @@ inline constexpr ConstantFeature freeTrial = { .supported = false, }; +inline constexpr ConstantFeature obfuscationLwo = { + .id = "obfuscationLwo", + .name = "LWO obfuscation", + .supported = false, +}; + +inline constexpr ConstantFeature obfuscationMasque = { + .id = "obfuscationMasque", + .name = "MASQUE obfuscation", + .supported = false, +}; + +inline constexpr ConstantFeature obfuscationShadowsocks = { + .id = "obfuscationShadowsocks", + .name = "SHADOWSOCKS obfuscation", + .supported = false, +}; + +inline constexpr ConstantFeature obfuscationUdpOverTcp = { + .id = "obfuscationUdpOverTcp", + .name = "UDP over TCP", + .supported = Platform::linux_ || Platform::android, +}; + inline const OverridableFeature replacerAddon = { .id = "replacerAddon", .name = "Replacer Addon", diff --git a/src/platforms/android/androidcontroller.cpp b/src/platforms/android/androidcontroller.cpp index a2ab1b0cdf..dc68e18a22 100644 --- a/src/platforms/android/androidcontroller.cpp +++ b/src/platforms/android/androidcontroller.cpp @@ -204,6 +204,7 @@ void AndroidController::activate(const InterfaceConfig& config, args["allowedIPs"] = jAllowedIPs; args["excludedApps"] = excludedApps; args["dns"] = config.m_dnsServer; + args["obfuscationMethod"] = (int)config.m_obfuscationMethod; // Build the "canned" Notification messages // They will be used in case this config will be re-enabled diff --git a/src/platforms/linux/daemon/dbusservice.cpp b/src/platforms/linux/daemon/dbusservice.cpp index f07dd4be60..f2c595bff0 100644 --- a/src/platforms/linux/daemon/dbusservice.cpp +++ b/src/platforms/linux/daemon/dbusservice.cpp @@ -293,3 +293,15 @@ bool DBusService::isCallerAuthorized(const QString& actionId) { logger.warning() << "Polkit authorization denied"; return false; } + +void DBusService::markObfuscatorSockets(int v4, int v6) { + constexpr uint32_t WG_FIREWALL_MARK = 0xca6c; + for (int fd : {v4, v6}) { + if (fd < 0) continue; + if (::setsockopt(fd, SOL_SOCKET, SO_MARK, &WG_FIREWALL_MARK, + sizeof(WG_FIREWALL_MARK)) < 0) { + logger.warning() << "Failed to mark obfuscator socket:" + << strerror(errno); + } + } +} diff --git a/src/platforms/linux/daemon/dbusservice.h b/src/platforms/linux/daemon/dbusservice.h index a2a022ca76..17506b3e97 100644 --- a/src/platforms/linux/daemon/dbusservice.h +++ b/src/platforms/linux/daemon/dbusservice.h @@ -43,6 +43,7 @@ class DBusService final : public Daemon, protected QDBusContext { WireguardUtils* wgutils() const override { return m_wgutils; } IPUtils* iputils() override; DnsUtils* dnsutils() override; + void markObfuscatorSockets(int v4, int v6); private: bool removeInterfaceIfExists(); diff --git a/src/settingsholder.h b/src/settingsholder.h index 7aa0d12f3d..e9d75476fb 100644 --- a/src/settingsholder.h +++ b/src/settingsholder.h @@ -47,6 +47,17 @@ class SettingsHolder final : public QObject { }; Q_ENUM(DNSProviderFlags) + // Obfuscation methods enum, remember to keep in sync with the one in server.h + // and in the obfuscators crate + enum ObfuscationMethod { + NoObfuscation, + LWO, + Masque, + UdpOverTcp, + Shadowsocks, + }; + Q_ENUM(ObfuscationMethod) + #define SETTING(type, toType, getter, setter, remover, has, ...) \ bool has() const { return m_##getter->isSet(); } \ type getter() const { return m_##getter->get().toType(); } \ diff --git a/src/settingslist.h b/src/settingslist.h index 3033f500e2..d06f3e3538 100644 --- a/src/settingslist.h +++ b/src/settingslist.h @@ -315,6 +315,16 @@ SETTING_STRINGLIST(missingApps, // getter false // sensitive (do not log) ) +SETTING_INT(obfuscationMethod, // getter + setObfuscationMethod, // setter + removeObfuscationMethod, // remover + hasObfuscationMethod, // has + "obfuscationMethod", // key + SettingsHolder::ObfuscationMethod::NoObfuscation, // default value + false, // remove when reset + false // sensitive (do not log) +) + SETTING_BOOL(onboardingCompleted, // getter setOnboardingCompleted, // setter removeOnboardingCompleted, // remover diff --git a/src/translations/strings.yaml b/src/translations/strings.yaml index 1f7c4f3bc1..08c5f46199 100644 --- a/src/translations/strings.yaml +++ b/src/translations/strings.yaml @@ -329,6 +329,18 @@ systray: settings: privacySettings: Privacy features + antiCensorshipSettings: Anti-Censorship features + antiCensorshipSettingsWarning: These options may cause connection stability issues. + antiCensorshipPort53Title: Connect using port 53 + antiCensorshipPort53Body: This may help on networks that block ports or UDP traffic. + antiCensorshipMasqueTitle: MASQUE + antiCensorshipMasqueBody: This may help on networks that block VPN protocols by disguising VPN traffic as regular HTTPS traffic. + antiCensorshipLwoTitle: Lightweight obfuscation + antiCensorshipLwoBody: The fastest obfuscation method. May help on networks that detect or block WireGuard traffic without adding significant overhead. + antiCensorshipShadowsocksTitle: SHADOWSOCKS + antiCensorshipShadowsocksBody: A reliable censorship circumvention protocol that may help bypass restrictive or heavily filtered networks. + antiCensorshipUdpOverTcpTitle: UDP over TCP + antiCensorshipUdpOverTcpBody: This may help on networks that block UDP traffic by tunneling it over TCP. appExclusionTitle: Excluded apps appearance: value: Appearance @@ -880,6 +892,18 @@ devices: comment: Title for the menu bar when viewing the devices or device limit pages helpSheets: + antiCensorshipTitle: + value: Anti-Censorship features + comment: Title label for the Anti-Censorship features help sheet + antiCensorshipHeader: + value: What can Anti-Censorship features do for me? + comment: Header label for the Anti-Censorship features help sheet + antiCensorshipBody1: + value: Anti-Censorship features can help when your provider or network blocks VPN or UDP traffic. + comment: Body label for the Anti-Censorship features help sheet + antiCensorshipBody2: + value: These features may slow down your connection, so turn them off when you no longer need them. + comment: Body label for the Anti-Censorship features help sheet dnsTitle: value: Custom DNS settings comment: Title label for the custom dns help sheet @@ -1084,6 +1108,9 @@ controller: regeneratingKey: value: Preparing your connection comment: Connection subtitle label for the main screen when the app is regenerating WireGuard keys + antiCensorshipOn: + value: Anti-Censorship is on + comment: Label for the main screen when Anti-Censorship features are enabled iosAppIntentsMain: turnOnAction: diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 275580e367..7258280fa6 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -105,6 +105,8 @@ qt_add_qml_module(mozillavpn-ui screens/settings/ViewSettingsMenu.qml screens/settings/privacy/ViewPrivacy.qml screens/settings/privacy/PrivacyFeaturesList.qml + screens/settings/antiCensorship/ViewAntiCensorship.qml + screens/settings/antiCensorship/AntiCensorshipFeaturesList.qml screens/subscriptionNeeded/ViewSubscriptionNeeded.qml sharedViews/ViewErrorFullScreen.qml sharedViews/ViewPermissionRequiredOSX.qml diff --git a/src/ui/screens/home/controller/ControllerView.qml b/src/ui/screens/home/controller/ControllerView.qml index c1dc08091c..4e03424377 100644 --- a/src/ui/screens/home/controller/ControllerView.qml +++ b/src/ui/screens/home/controller/ControllerView.qml @@ -634,6 +634,20 @@ Item { implicitHeight: childrenRect.height } + MZInterLabel { + id: obfuscationMethodDescription + objectName: "obfuscationMethodDescription" + anchors.horizontalCenter: parent.horizontalCenter + opacity: 0.8 + color: MZTheme.colors.fontColorInverted + lineHeight: MZTheme.theme.controllerInterLineHeight + visible: VPNController.state === VPNController.StateOn && + MZSettings.obfuscationMethod !== MZSettings.NoObfuscation + Accessible.ignored: ipInfoPanel.isOpen || !visible + + text: MZI18n.ControllerAntiCensorshipOn + } + } VPNToggle { diff --git a/src/ui/screens/settings/ViewSettingsMenu.qml b/src/ui/screens/settings/ViewSettingsMenu.qml index 0caf1f0d1f..fc61b1f2f0 100644 --- a/src/ui/screens/settings/ViewSettingsMenu.qml +++ b/src/ui/screens/settings/ViewSettingsMenu.qml @@ -74,6 +74,17 @@ MZViewBase { } } + MZSettingsItem { + objectName: "antiCensorshipSettings" + settingTitle: MZI18n.SettingsAntiCensorshipSettings + imageLeftSrc: MZAssetLookup.getImageSource("EyeHidden") + imageRightSrc: MZAssetLookup.getImageSource("Chevron") + imageRightMirror: MZLocalizer.isRightToLeft + onClicked: { + stackview.push("qrc:/qt/qml/Mozilla/VPN/screens/settings/antiCensorship/ViewAntiCensorship.qml") + } + } + MZSettingsItem { objectName: "appExclusionSettings" settingTitle: MZI18n.SettingsAppExclusionTitle diff --git a/src/ui/screens/settings/antiCensorship/AntiCensorshipFeaturesList.qml b/src/ui/screens/settings/antiCensorship/AntiCensorshipFeaturesList.qml new file mode 100644 index 0000000000..e795e117d8 --- /dev/null +++ b/src/ui/screens/settings/antiCensorship/AntiCensorshipFeaturesList.qml @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import Mozilla.Shared 1.0 +import components 0.1 + +ColumnLayout { + id: root + + property int dividerSpacing: MZTheme.theme.toggleRowDividerSpacing + + + spacing: dividerSpacing + MZToggleRow { + objectName: "alwaysUsePort53" + + labelText: MZI18n.SettingsAntiCensorshipPort53Title + subLabelText: MZI18n.SettingsAntiCensorshipPort53Body + checked: MZFeatureList.get("alwaysPort53").isSupported + dividerTopMargin: dividerSpacing + onClicked: { + MZFeatureList.toggle("alwaysPort53"); + } + } + + Repeater { + id: repeater + + model: [ + { + objectName: "obfuscationUdpOverTcp", + settingValue: MZSettings.UdpOverTcp, + settingTitle: MZI18n.SettingsAntiCensorshipUdpOverTcpTitle, + settingDescription: MZI18n.SettingsAntiCensorshipUdpOverTcpBody, + }, { + objectName: "obfuscationLwo", + settingValue: MZSettings.LWO, + settingTitle: MZI18n.SettingsAntiCensorshipLwoTitle, + settingDescription: MZI18n.SettingsAntiCensorshipLwoBody, + }, { + objectName: "obfuscationMasque", + settingValue: MZSettings.Masque, + settingTitle: MZI18n.SettingsAntiCensorshipMasqueTitle, + settingDescription: MZI18n.SettingsAntiCensorshipMasqueBody, + },{ + objectName: "obfuscationShadowsocks", + settingValue: MZSettings.Shadowsocks, + settingTitle: MZI18n.SettingsAntiCensorshipShadowsocksTitle, + settingDescription: MZI18n.SettingsAntiCensorshipShadowsocksBody, + } + ]; + + delegate: MZToggleRow { + objectName: modelData.objectName + + labelText: modelData.settingTitle + subLabelText: modelData.settingDescription + checked: MZSettings.obfuscationMethod == modelData.settingValue + dividerTopMargin: dividerSpacing + visible: MZFeatureList.get(modelData.objectName).isSupported + showDivider: !isOnboarding || index + 1 !== repeater.model.length + + onClicked: { + if(MZSettings.obfuscationMethod == modelData.settingValue) { + MZSettings.obfuscationMethod = MZSettings.NoObfuscation; + } else { + MZSettings.obfuscationMethod = modelData.settingValue; + // Maybe automatically disable alwaysPort53 when enabling an obfuscation method + // that requires a specific port. Right now the port it's overridden in the controller. + } + } + } + } +} diff --git a/src/ui/screens/settings/antiCensorship/ViewAntiCensorship.qml b/src/ui/screens/settings/antiCensorship/ViewAntiCensorship.qml new file mode 100644 index 0000000000..41c81f68dd --- /dev/null +++ b/src/ui/screens/settings/antiCensorship/ViewAntiCensorship.qml @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import Mozilla.Shared 1.0 +import Mozilla.VPN 1.0 +import components 0.1 +import components.forms 0.1 +import compat 0.1 +import "qrc:/nebula/utils/MZAssetLookup.js" as MZAssetLookup + +MZViewBase { + id: root + objectName: "antiCensorshipSettingsView" + + property Component rightMenuButton: Component { + Loader { + active: true + sourceComponent: MZIconButton { + objectName: "antiCensorshipHelpButton" + + onClicked: { + helpSheet.open() + } + + accessibleName: MZI18n.GetHelpLinkText + + Image { + anchors.centerIn: parent + + source: MZAssetLookup.getImageSource("Question") + fillMode: Image.PreserveAspectFit + } + } + } + } + + _menuTitle: MZI18n.SettingsAntiCensorshipSettings + + _viewContentData: ColumnLayout { + spacing: MZTheme.theme.windowMargin * 1.5 + Layout.preferredWidth: parent.width + + MZInformationCard { + objectName: "antiCensorshipSettingsViewInformationCard" + cardType: MZInformationCard.CardType.Warning + Layout.preferredWidth: Math.min(window.width - MZTheme.theme.windowMargin * 2, MZTheme.theme.navBarMaxWidth) + Layout.alignment: Qt.AlignHCenter + + _infoContent: ColumnLayout { + id: textBlocks + spacing: 0 + + MZTextBlock { + Layout.fillWidth: true + width: undefined + text: MZI18n.SettingsAntiCensorshipSettingsWarning + verticalAlignment: Text.AlignVCenter + } + Loader { + active: !VPNController.silentServerSwitchingSupported && VPNController.state !== VPNController.StateOff + Layout.fillWidth: true + visible: active + sourceComponent: MZTextBlock { + width: parent.width + text: MZI18n.SettingsDnsSettingsDisconnectWarning + verticalAlignment: Text.AlignVCenter + } + } + } + } + + AntiCensorshipFeaturesList { + id: antiCensorshipFeaturesList + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.fillWidth: true + } + } + + MZHelpSheet { + id: helpSheet + objectName: "antiCensorshipHelpSheet" + + title: MZI18n.HelpSheetsAntiCensorshipTitle + + model: [ + {type: MZHelpSheet.BlockType.Title, text: MZI18n.HelpSheetsAntiCensorshipHeader}, + {type: MZHelpSheet.BlockType.Text, text: MZI18n.HelpSheetsAntiCensorshipBody1, margin: MZTheme.theme.helpSheetTitleBodySpacing}, + {type: MZHelpSheet.BlockType.Text, text: MZI18n.HelpSheetsAntiCensorshipBody2, margin: MZTheme.theme.helpSheetBodySpacing} + ] + } +} diff --git a/src/utils/interfaceconfig.cpp b/src/utils/interfaceconfig.cpp index e4bf155bd1..209b67bb14 100644 --- a/src/utils/interfaceconfig.cpp +++ b/src/utils/interfaceconfig.cpp @@ -16,6 +16,7 @@ QJsonObject InterfaceConfig::toJson() const { json.insert("hopType", QJsonValue(metaEnum.valueToKey(m_hopType))); json.insert("privateKey", QJsonValue(m_privateKey)); + json.insert("publicKey", QJsonValue(m_publicKey)); json.insert("deviceIpv4Address", QJsonValue(m_deviceIpv4Address)); json.insert("deviceIpv6Address", QJsonValue(m_deviceIpv6Address)); json.insert("serverPublicKey", QJsonValue(m_serverPublicKey)); @@ -46,6 +47,11 @@ QJsonObject InterfaceConfig::toJson() const { } json.insert("vpnDisabledApps", disabledApps); + QMetaEnum obfuscationMetaEnum = + QMetaEnum::fromType(); + json.insert("obfuscationMethod", + QJsonValue(obfuscationMetaEnum.valueToKey(m_obfuscationMethod))); + return json; } diff --git a/src/utils/interfaceconfig.h b/src/utils/interfaceconfig.h index 6a7d53a76d..a4e38c235d 100644 --- a/src/utils/interfaceconfig.h +++ b/src/utils/interfaceconfig.h @@ -10,6 +10,7 @@ #include #include "ipaddress.h" +#include "models/server.h" class QJsonObject; @@ -24,6 +25,7 @@ class InterfaceConfig { HopType m_hopType; QString m_privateKey; + QString m_publicKey; QString m_deviceIpv4Address; QString m_deviceIpv6Address; QString m_serverIpv4Gateway; @@ -37,6 +39,7 @@ class InterfaceConfig { int m_serverPort = 0; QList m_allowedIPAddressRanges; QStringList m_vpnDisabledApps; + Server::ObfuscationMethod m_obfuscationMethod; QJsonObject toJson() const; QString toWgConf( diff --git a/src/utils/models/server.cpp b/src/utils/models/server.cpp index 7a90b5e73f..1d620e64fa 100644 --- a/src/utils/models/server.cpp +++ b/src/utils/models/server.cpp @@ -148,6 +148,26 @@ bool Server::forcePort(uint32_t port) { return true; } +bool Server::supportObfuscationMethod(const ObfuscationMethod method) const { + switch (method) { + case NoObfuscation: + [[fallthrough]]; + case UdpOverTcp: + return true; + case LWO: + [[fallthrough]]; + case Masque: + [[fallthrough]]; + case Shadowsocks: + // Shadowsocks currently falls under false as it looks like it's not + // supported by the server itself but we should communicate to the server + // through a relay and this requires changes in the guardian api + [[fallthrough]]; + default: + return false; + } +} + // static const Server& Server::weightChooser(const QList& servers) { static const Server emptyServer; @@ -177,14 +197,16 @@ const Server& Server::weightChooser(const QList& servers) { return emptyServer; } -uint32_t Server::choosePort() const { - if (m_portRanges.isEmpty()) { +uint32_t Server::choosePort(bool tcp) const { + QList> portRanges = + tcp ? m_udpOverTcpPorts : m_portRanges; + if (portRanges.isEmpty()) { return 0; } // Count the total number of potential ports. quint32 length = 0; - for (const QPair& range : m_portRanges) { + for (const QPair& range : portRanges) { Q_ASSERT(range.first <= range.second); length += range.second - range.first + 1; } @@ -195,7 +217,7 @@ uint32_t Server::choosePort() const { quint32 r = QRandomGenerator::global()->generate() % length; quint32 port = 0; - for (const QPair& range : m_portRanges) { + for (const QPair& range : portRanges) { if (r <= (range.second - range.first)) { port = r + range.first; break; diff --git a/src/utils/models/server.h b/src/utils/models/server.h index 04e8faffe0..0e0c8959c6 100644 --- a/src/utils/models/server.h +++ b/src/utils/models/server.h @@ -6,12 +6,15 @@ #define SERVER_H #include +#include #include #include class QJsonObject; -class Server final { +class Server final : public QObject { + Q_OBJECT + public: Server(); Server(const QString& countryCode, const QString& cityName); @@ -19,6 +22,17 @@ class Server final { Server& operator=(const Server& other); ~Server(); + // Obfuscation methods enum, remember to keep in sync with the one in + // settingsholder.h and in the obfuscators crate + enum ObfuscationMethod { + NoObfuscation, + LWO, + Masque, + UdpOverTcp, + Shadowsocks + }; + Q_ENUM(ObfuscationMethod) + [[nodiscard]] bool fromJson(const QJsonObject& obj); bool fromMultihop(const Server& exit, const Server& entry); @@ -26,6 +40,8 @@ class Server final { bool initialized() const { return !m_hostname.isEmpty(); } + bool supportObfuscationMethod(const ObfuscationMethod method) const; + const QString& hostname() const { return m_hostname; } const QString& ipv4AddrIn() const { return m_ipv4AddrIn; } @@ -42,7 +58,9 @@ class Server final { uint32_t weight() const { return m_weight; } - uint32_t choosePort() const; + uint32_t choosePort(bool tcp = false) const; + + uint32_t chooseTcpPort() const { return choosePort(true); } uint32_t multihopPort() const { return m_multihopPort; } @@ -68,6 +86,11 @@ class Server final { QString m_ipv6AddrIn; QString m_ipv6Gateway; QList> m_portRanges; + // Mullvad ports for UDP over TCP obfuscation, hardcoded as specified + // Use QPair so we can reuse choosePort logic, maybe expose these ports in + // guardian APIs in the future + QList> m_udpOverTcpPorts{ + {80, 80}, {443, 443}, {5001, 5001}}; QString m_publicKey; QString m_socksName; uint32_t m_weight = 0; diff --git a/taskcluster/docker/base/Dockerfile b/taskcluster/docker/base/Dockerfile index 11eab150b3..6dc09c9f42 100644 --- a/taskcluster/docker/base/Dockerfile +++ b/taskcluster/docker/base/Dockerfile @@ -40,13 +40,15 @@ RUN apt-get update -qq \ git \ wget \ zip \ - cargo \ + cargo-1.91 \ curl \ rpm \ libglib2.0-0 \ ccache \ && apt-get clean +RUN ln -s /usr/bin/cargo-1.91 /usr/bin/cargo + RUN pip install --upgrade pip RUN pip install taskcluster diff --git a/taskcluster/docker/linux-dpkg-build/Dockerfile b/taskcluster/docker/linux-dpkg-build/Dockerfile index 8784e60ade..bcbba0740e 100644 --- a/taskcluster/docker/linux-dpkg-build/Dockerfile +++ b/taskcluster/docker/linux-dpkg-build/Dockerfile @@ -50,7 +50,7 @@ RUN /root/setup-worker.sh WORKDIR /builds/worker/ # Grant the worker user the ability to install packages without login -RUN echo "worker ALL=(ALL) SETENV:NOPASSWD:/usr/bin/apt,/usr/bin/apt-get,/usr/bin/dpkg,/usr/bin/mk-build-deps" > /etc/sudoers.d/worker-packages +RUN echo "worker ALL=(ALL) SETENV:NOPASSWD:/usr/bin/apt,/usr/bin/apt-get,/usr/bin/dpkg,/usr/bin/mk-build-deps,/usr/bin/ln" > /etc/sudoers.d/worker-packages #---------------------------------------------------------------------------------------------------------------------- #-- Task Setup -------------------------------------------------------------------------------------------------------- diff --git a/taskcluster/docker/linux-qt6-build/Dockerfile b/taskcluster/docker/linux-qt6-build/Dockerfile index f12661388e..f4d7c0d47d 100644 --- a/taskcluster/docker/linux-qt6-build/Dockerfile +++ b/taskcluster/docker/linux-qt6-build/Dockerfile @@ -29,6 +29,7 @@ RUN apt-get -y install build-essential \ debhelper \ devscripts \ equivs \ + gawk \ libclang-16-dev \ libgl1-mesa-dev \ locales \ @@ -89,21 +90,23 @@ RUN apt -y install libwayland-dev:${DEB_HOST_ARCH} \ # Also pull in packages from build-depends as early as possible # %include linux/debian/control ADD topsrcdir/linux/debian/control /root/mozillavpn-dpkg-control -RUN sed -rie '/\s+(qt6-|qml6-|libqt6|qmake)/d' /root/mozillavpn-dpkg-control && \ - mk-build-deps -i -r --tool="apt-get --no-install-recommends --yes" --host-arch $DEB_HOST_ARCH /root/mozillavpn-dpkg-control ENV RUSTUP_HOME=/usr/local/rustup \ CARGO_HOME=/usr/local/cargo +# Install rust toolchain using rustup, cargo version is parsed from control file RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --default-toolchain $(rustc --version | awk '{print $2}') --profile minimal --no-modify-path + sh -s -- -y --default-toolchain $(gawk 'match($0, /cargo-([0-9.]+):native/, a) { print a[1] }' /root/mozillavpn-dpkg-control) --profile minimal --no-modify-path ENV PATH="/usr/local/cargo/bin:${PATH}" RUN if [ "$DEB_HOST_ARCH" != "amd64" ]; then \ - rustup target add --toolchain $(rustc --version | awk '{print $2}') ${DEB_HOST_GNU_CPU}-unknown-linux-gnu; \ + rustup target add ${DEB_HOST_GNU_CPU}-unknown-linux-gnu; \ fi +RUN sed -rie '/\s+(qt6-|qml6-|libqt6|qmake|cargo)/d' /root/mozillavpn-dpkg-control && \ + mk-build-deps -i -r --tool="apt-get --no-install-recommends --yes" --host-arch $DEB_HOST_ARCH /root/mozillavpn-dpkg-control + #---------------------------------------------------------------------------------------------------------------------- #-- Worker User ------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------- @@ -115,7 +118,7 @@ RUN /root/setup-worker.sh WORKDIR /builds/worker/ # Grant the worker user the ability to install packages without login -RUN echo "worker ALL=(ALL) SETENV:NOPASSWD:/usr/bin/apt,/usr/bin/apt-get,/usr/bin/dpkg,/usr/bin/mk-build-deps" > /etc/sudoers.d/worker-packages +RUN echo "worker ALL=(ALL) SETENV:NOPASSWD:/usr/bin/apt,/usr/bin/apt-get,/usr/bin/dpkg,/usr/bin/mk-build-deps,/usr/bin/ln" > /etc/sudoers.d/worker-packages #---------------------------------------------------------------------------------------------------------------------- #-- Task Setup -------------------------------------------------------------------------------------------------------- diff --git a/taskcluster/scripts/build/linux_build_dpkg.sh b/taskcluster/scripts/build/linux_build_dpkg.sh index a67f238fc4..cd89f1d559 100755 --- a/taskcluster/scripts/build/linux_build_dpkg.sh +++ b/taskcluster/scripts/build/linux_build_dpkg.sh @@ -102,8 +102,9 @@ dch -c "$(pwd)/mozillavpn-source/debian/changelog" \ -D "${DIST}" --force-distribution "Release for ${DIST}" # For static Qt or cross builds, strip out the Qt build and runtime dependencies. +# Also strip out the rust toolchain as it's installed in the docker image if [[ "$STATICQT" == "Y" ]]; then - sed -rie '/\s+(qt6-|qml6-|libqt6|qmake)/d' "$(pwd)/mozillavpn-source/debian/control" + sed -rie '/\s+(qt6-|qml6-|libqt6|qmake|cargo)/d' "$(pwd)/mozillavpn-source/debian/control" fi # Set up cross-compilation environment @@ -132,6 +133,17 @@ fi sudo mk-build-deps --tool 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' \ --install --remove $MK_BUILD_DEPS_ARGS "$(pwd)/mozillavpn-source/debian/control" +# /usr/bin/cargo and /usr/bin/rustc may be installed as cargo- and rustc- +# Find the latest versions of these binaries as installed by the build dependencies and link them to /usr/bin. + +if ! command -v cargo >/dev/null 2>&1; then + CARGO_BIN=$(find -L /usr/bin -maxdepth 1 -type f -name 'cargo-*' | sort -V | tail -n1) + RUSTC_BIN=$(find -L /usr/bin -maxdepth 1 -type f -name 'rustc-*' | sort -V | tail -n1) + + [ -n "$CARGO_BIN" ] && sudo ln -sf "$CARGO_BIN" /usr/bin/cargo + [ -n "$RUSTC_BIN" ] && sudo ln -sf "$RUSTC_BIN" /usr/bin/rustc +fi + (cd mozillavpn-source/ && dpkg-buildpackage $DPKG_PACKAGE_BUILD_ARGS) # Compress all build artifacts into a tarball