Distributed Erlang over iroh P2P + TLS, with a bot powers SDK
0

Configure Feed

Select the types of activity you want to include in your feed.

Initial erl_iroh: Erlang dist over iroh + TLS bot SDK

P2P distribution via iroh (QUIC/TLS), classic TCP/TLS carriers, and a
capability-gated bot SDK. Flake apps: nix run .#demo (two-bot handshake).

author
nandi
date (Jul 26, 2026, 11:33 PM -0700) commit 85da286a
+6873
+4
.gitignore
··· 1 + /target 2 + /result 3 + /result-* 4 + .direnv
+4436
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "adler32" 7 + version = "1.2.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 10 + 11 + [[package]] 12 + name = "aead" 13 + version = "0.5.2" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 16 + dependencies = [ 17 + "crypto-common 0.1.7", 18 + "generic-array", 19 + ] 20 + 21 + [[package]] 22 + name = "aes" 23 + version = "0.8.4" 24 + source = "registry+https://github.com/rust-lang/crates.io-index" 25 + checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 26 + dependencies = [ 27 + "cfg-if", 28 + "cipher", 29 + "cpufeatures 0.2.17", 30 + ] 31 + 32 + [[package]] 33 + name = "aes-gcm" 34 + version = "0.10.3" 35 + source = "registry+https://github.com/rust-lang/crates.io-index" 36 + checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" 37 + dependencies = [ 38 + "aead", 39 + "aes", 40 + "cipher", 41 + "ctr", 42 + "ghash", 43 + "subtle", 44 + ] 45 + 46 + [[package]] 47 + name = "aho-corasick" 48 + version = "1.1.4" 49 + source = "registry+https://github.com/rust-lang/crates.io-index" 50 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 51 + dependencies = [ 52 + "memchr", 53 + ] 54 + 55 + [[package]] 56 + name = "allocator-api2" 57 + version = "0.2.21" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 60 + 61 + [[package]] 62 + name = "android_system_properties" 63 + version = "0.1.5" 64 + source = "registry+https://github.com/rust-lang/crates.io-index" 65 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 66 + dependencies = [ 67 + "libc", 68 + ] 69 + 70 + [[package]] 71 + name = "anstream" 72 + version = "1.0.0" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" 75 + dependencies = [ 76 + "anstyle", 77 + "anstyle-parse", 78 + "anstyle-query", 79 + "anstyle-wincon", 80 + "colorchoice", 81 + "is_terminal_polyfill", 82 + "utf8parse", 83 + ] 84 + 85 + [[package]] 86 + name = "anstyle" 87 + version = "1.0.14" 88 + source = "registry+https://github.com/rust-lang/crates.io-index" 89 + checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" 90 + 91 + [[package]] 92 + name = "anstyle-parse" 93 + version = "1.0.0" 94 + source = "registry+https://github.com/rust-lang/crates.io-index" 95 + checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" 96 + dependencies = [ 97 + "utf8parse", 98 + ] 99 + 100 + [[package]] 101 + name = "anstyle-query" 102 + version = "1.1.5" 103 + source = "registry+https://github.com/rust-lang/crates.io-index" 104 + checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" 105 + dependencies = [ 106 + "windows-sys 0.61.2", 107 + ] 108 + 109 + [[package]] 110 + name = "anstyle-wincon" 111 + version = "3.0.11" 112 + source = "registry+https://github.com/rust-lang/crates.io-index" 113 + checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" 114 + dependencies = [ 115 + "anstyle", 116 + "once_cell_polyfill", 117 + "windows-sys 0.61.2", 118 + ] 119 + 120 + [[package]] 121 + name = "anyhow" 122 + version = "1.0.104" 123 + source = "registry+https://github.com/rust-lang/crates.io-index" 124 + checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" 125 + 126 + [[package]] 127 + name = "arc-swap" 128 + version = "1.9.2" 129 + source = "registry+https://github.com/rust-lang/crates.io-index" 130 + checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" 131 + dependencies = [ 132 + "rustversion", 133 + ] 134 + 135 + [[package]] 136 + name = "arrayref" 137 + version = "0.3.9" 138 + source = "registry+https://github.com/rust-lang/crates.io-index" 139 + checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" 140 + 141 + [[package]] 142 + name = "arrayvec" 143 + version = "0.7.8" 144 + source = "registry+https://github.com/rust-lang/crates.io-index" 145 + checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" 146 + 147 + [[package]] 148 + name = "async-dup" 149 + version = "1.2.4" 150 + source = "registry+https://github.com/rust-lang/crates.io-index" 151 + checksum = "7c2886ab563af5038f79ec016dd7b87947ed138b794e8dd64992962c9cca0411" 152 + dependencies = [ 153 + "async-lock", 154 + "futures-io", 155 + ] 156 + 157 + [[package]] 158 + name = "async-lock" 159 + version = "3.4.2" 160 + source = "registry+https://github.com/rust-lang/crates.io-index" 161 + checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" 162 + dependencies = [ 163 + "event-listener", 164 + "event-listener-strategy", 165 + "pin-project-lite", 166 + ] 167 + 168 + [[package]] 169 + name = "async-trait" 170 + version = "0.1.91" 171 + source = "registry+https://github.com/rust-lang/crates.io-index" 172 + checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" 173 + dependencies = [ 174 + "proc-macro2", 175 + "quote", 176 + "syn 3.0.3", 177 + ] 178 + 179 + [[package]] 180 + name = "async_io_stream" 181 + version = "0.3.3" 182 + source = "registry+https://github.com/rust-lang/crates.io-index" 183 + checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" 184 + dependencies = [ 185 + "futures", 186 + "pharos", 187 + "rustc_version", 188 + ] 189 + 190 + [[package]] 191 + name = "atomic-waker" 192 + version = "1.1.2" 193 + source = "registry+https://github.com/rust-lang/crates.io-index" 194 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 195 + 196 + [[package]] 197 + name = "attohttpc" 198 + version = "0.30.1" 199 + source = "registry+https://github.com/rust-lang/crates.io-index" 200 + checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" 201 + dependencies = [ 202 + "base64", 203 + "http", 204 + "log", 205 + "url", 206 + ] 207 + 208 + [[package]] 209 + name = "autocfg" 210 + version = "1.5.1" 211 + source = "registry+https://github.com/rust-lang/crates.io-index" 212 + checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" 213 + 214 + [[package]] 215 + name = "aws-lc-rs" 216 + version = "1.17.3" 217 + source = "registry+https://github.com/rust-lang/crates.io-index" 218 + checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" 219 + dependencies = [ 220 + "aws-lc-sys", 221 + "zeroize", 222 + ] 223 + 224 + [[package]] 225 + name = "aws-lc-sys" 226 + version = "0.43.0" 227 + source = "registry+https://github.com/rust-lang/crates.io-index" 228 + checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" 229 + dependencies = [ 230 + "cc", 231 + "cmake", 232 + "dunce", 233 + "fs_extra", 234 + "pkg-config", 235 + ] 236 + 237 + [[package]] 238 + name = "backon" 239 + version = "1.6.0" 240 + source = "registry+https://github.com/rust-lang/crates.io-index" 241 + checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" 242 + dependencies = [ 243 + "fastrand", 244 + "gloo-timers", 245 + "tokio", 246 + ] 247 + 248 + [[package]] 249 + name = "base16ct" 250 + version = "1.0.0" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" 253 + 254 + [[package]] 255 + name = "base64" 256 + version = "0.22.1" 257 + source = "registry+https://github.com/rust-lang/crates.io-index" 258 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 259 + 260 + [[package]] 261 + name = "base64ct" 262 + version = "1.8.3" 263 + source = "registry+https://github.com/rust-lang/crates.io-index" 264 + checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" 265 + 266 + [[package]] 267 + name = "bitflags" 268 + version = "2.13.1" 269 + source = "registry+https://github.com/rust-lang/crates.io-index" 270 + checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" 271 + 272 + [[package]] 273 + name = "blake3" 274 + version = "1.8.5" 275 + source = "registry+https://github.com/rust-lang/crates.io-index" 276 + checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" 277 + dependencies = [ 278 + "arrayref", 279 + "arrayvec", 280 + "cc", 281 + "cfg-if", 282 + "constant_time_eq", 283 + "cpufeatures 0.3.0", 284 + ] 285 + 286 + [[package]] 287 + name = "block-buffer" 288 + version = "0.12.1" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" 291 + dependencies = [ 292 + "hybrid-array", 293 + ] 294 + 295 + [[package]] 296 + name = "block2" 297 + version = "0.6.2" 298 + source = "registry+https://github.com/rust-lang/crates.io-index" 299 + checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" 300 + dependencies = [ 301 + "objc2", 302 + ] 303 + 304 + [[package]] 305 + name = "bumpalo" 306 + version = "3.20.3" 307 + source = "registry+https://github.com/rust-lang/crates.io-index" 308 + checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" 309 + 310 + [[package]] 311 + name = "bytes" 312 + version = "1.12.1" 313 + source = "registry+https://github.com/rust-lang/crates.io-index" 314 + checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" 315 + 316 + [[package]] 317 + name = "cc" 318 + version = "1.4.0" 319 + source = "registry+https://github.com/rust-lang/crates.io-index" 320 + checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" 321 + dependencies = [ 322 + "find-msvc-tools", 323 + "jobserver", 324 + "libc", 325 + "shlex", 326 + ] 327 + 328 + [[package]] 329 + name = "cesu8" 330 + version = "1.1.0" 331 + source = "registry+https://github.com/rust-lang/crates.io-index" 332 + checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 333 + 334 + [[package]] 335 + name = "cfg-if" 336 + version = "1.0.4" 337 + source = "registry+https://github.com/rust-lang/crates.io-index" 338 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 339 + 340 + [[package]] 341 + name = "cfg_aliases" 342 + version = "0.2.2" 343 + source = "registry+https://github.com/rust-lang/crates.io-index" 344 + checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" 345 + 346 + [[package]] 347 + name = "chacha20" 348 + version = "0.10.1" 349 + source = "registry+https://github.com/rust-lang/crates.io-index" 350 + checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" 351 + dependencies = [ 352 + "cfg-if", 353 + "cpufeatures 0.3.0", 354 + "rand_core 0.10.1", 355 + ] 356 + 357 + [[package]] 358 + name = "chrono" 359 + version = "0.4.45" 360 + source = "registry+https://github.com/rust-lang/crates.io-index" 361 + checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" 362 + dependencies = [ 363 + "iana-time-zone", 364 + "num-traits", 365 + "serde", 366 + "windows-link", 367 + ] 368 + 369 + [[package]] 370 + name = "cipher" 371 + version = "0.4.4" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 374 + dependencies = [ 375 + "crypto-common 0.1.7", 376 + "inout", 377 + ] 378 + 379 + [[package]] 380 + name = "clap" 381 + version = "4.6.4" 382 + source = "registry+https://github.com/rust-lang/crates.io-index" 383 + checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" 384 + dependencies = [ 385 + "clap_builder", 386 + "clap_derive", 387 + ] 388 + 389 + [[package]] 390 + name = "clap_builder" 391 + version = "4.6.2" 392 + source = "registry+https://github.com/rust-lang/crates.io-index" 393 + checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" 394 + dependencies = [ 395 + "anstream", 396 + "anstyle", 397 + "clap_lex", 398 + "strsim", 399 + ] 400 + 401 + [[package]] 402 + name = "clap_derive" 403 + version = "4.6.4" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" 406 + dependencies = [ 407 + "heck", 408 + "proc-macro2", 409 + "quote", 410 + "syn 3.0.3", 411 + ] 412 + 413 + [[package]] 414 + name = "clap_lex" 415 + version = "1.1.0" 416 + source = "registry+https://github.com/rust-lang/crates.io-index" 417 + checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" 418 + 419 + [[package]] 420 + name = "cmake" 421 + version = "0.1.58" 422 + source = "registry+https://github.com/rust-lang/crates.io-index" 423 + checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" 424 + dependencies = [ 425 + "cc", 426 + ] 427 + 428 + [[package]] 429 + name = "cmov" 430 + version = "0.5.4" 431 + source = "registry+https://github.com/rust-lang/crates.io-index" 432 + checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" 433 + 434 + [[package]] 435 + name = "cobs" 436 + version = "0.3.0" 437 + source = "registry+https://github.com/rust-lang/crates.io-index" 438 + checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" 439 + dependencies = [ 440 + "thiserror 2.0.19", 441 + ] 442 + 443 + [[package]] 444 + name = "colorchoice" 445 + version = "1.0.5" 446 + source = "registry+https://github.com/rust-lang/crates.io-index" 447 + checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" 448 + 449 + [[package]] 450 + name = "combine" 451 + version = "4.6.7" 452 + source = "registry+https://github.com/rust-lang/crates.io-index" 453 + checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 454 + dependencies = [ 455 + "bytes", 456 + "memchr", 457 + ] 458 + 459 + [[package]] 460 + name = "concurrent-queue" 461 + version = "2.5.0" 462 + source = "registry+https://github.com/rust-lang/crates.io-index" 463 + checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 464 + dependencies = [ 465 + "crossbeam-utils", 466 + ] 467 + 468 + [[package]] 469 + name = "const-oid" 470 + version = "0.10.2" 471 + source = "registry+https://github.com/rust-lang/crates.io-index" 472 + checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" 473 + 474 + [[package]] 475 + name = "constant_time_eq" 476 + version = "0.4.2" 477 + source = "registry+https://github.com/rust-lang/crates.io-index" 478 + checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" 479 + 480 + [[package]] 481 + name = "convert_case" 482 + version = "0.10.0" 483 + source = "registry+https://github.com/rust-lang/crates.io-index" 484 + checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" 485 + dependencies = [ 486 + "unicode-segmentation", 487 + ] 488 + 489 + [[package]] 490 + name = "cordyceps" 491 + version = "0.3.4" 492 + source = "registry+https://github.com/rust-lang/crates.io-index" 493 + checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" 494 + dependencies = [ 495 + "loom", 496 + "tracing", 497 + ] 498 + 499 + [[package]] 500 + name = "core-foundation" 501 + version = "0.9.4" 502 + source = "registry+https://github.com/rust-lang/crates.io-index" 503 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 504 + dependencies = [ 505 + "core-foundation-sys", 506 + "libc", 507 + ] 508 + 509 + [[package]] 510 + name = "core-foundation" 511 + version = "0.10.1" 512 + source = "registry+https://github.com/rust-lang/crates.io-index" 513 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 514 + dependencies = [ 515 + "core-foundation-sys", 516 + "libc", 517 + ] 518 + 519 + [[package]] 520 + name = "core-foundation-sys" 521 + version = "0.8.7" 522 + source = "registry+https://github.com/rust-lang/crates.io-index" 523 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 524 + 525 + [[package]] 526 + name = "cpufeatures" 527 + version = "0.2.17" 528 + source = "registry+https://github.com/rust-lang/crates.io-index" 529 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 530 + dependencies = [ 531 + "libc", 532 + ] 533 + 534 + [[package]] 535 + name = "cpufeatures" 536 + version = "0.3.0" 537 + source = "registry+https://github.com/rust-lang/crates.io-index" 538 + checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" 539 + dependencies = [ 540 + "libc", 541 + ] 542 + 543 + [[package]] 544 + name = "crc32fast" 545 + version = "1.5.0" 546 + source = "registry+https://github.com/rust-lang/crates.io-index" 547 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 548 + dependencies = [ 549 + "cfg-if", 550 + ] 551 + 552 + [[package]] 553 + name = "critical-section" 554 + version = "1.2.0" 555 + source = "registry+https://github.com/rust-lang/crates.io-index" 556 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 557 + 558 + [[package]] 559 + name = "crossbeam-channel" 560 + version = "0.5.16" 561 + source = "registry+https://github.com/rust-lang/crates.io-index" 562 + checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" 563 + dependencies = [ 564 + "crossbeam-utils", 565 + ] 566 + 567 + [[package]] 568 + name = "crossbeam-epoch" 569 + version = "0.9.20" 570 + source = "registry+https://github.com/rust-lang/crates.io-index" 571 + checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" 572 + dependencies = [ 573 + "crossbeam-utils", 574 + ] 575 + 576 + [[package]] 577 + name = "crossbeam-utils" 578 + version = "0.8.22" 579 + source = "registry+https://github.com/rust-lang/crates.io-index" 580 + checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" 581 + 582 + [[package]] 583 + name = "crypto-common" 584 + version = "0.1.7" 585 + source = "registry+https://github.com/rust-lang/crates.io-index" 586 + checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 587 + dependencies = [ 588 + "generic-array", 589 + "typenum", 590 + ] 591 + 592 + [[package]] 593 + name = "crypto-common" 594 + version = "0.2.2" 595 + source = "registry+https://github.com/rust-lang/crates.io-index" 596 + checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" 597 + dependencies = [ 598 + "hybrid-array", 599 + "rand_core 0.10.1", 600 + ] 601 + 602 + [[package]] 603 + name = "ctr" 604 + version = "0.9.2" 605 + source = "registry+https://github.com/rust-lang/crates.io-index" 606 + checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 607 + dependencies = [ 608 + "cipher", 609 + ] 610 + 611 + [[package]] 612 + name = "ctutils" 613 + version = "0.4.2" 614 + source = "registry+https://github.com/rust-lang/crates.io-index" 615 + checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" 616 + dependencies = [ 617 + "cmov", 618 + ] 619 + 620 + [[package]] 621 + name = "curve25519-dalek" 622 + version = "5.0.0" 623 + source = "registry+https://github.com/rust-lang/crates.io-index" 624 + checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" 625 + dependencies = [ 626 + "cfg-if", 627 + "cpufeatures 0.3.0", 628 + "curve25519-dalek-derive", 629 + "digest", 630 + "fiat-crypto", 631 + "rand_core 0.10.1", 632 + "rustc_version", 633 + "serde", 634 + "subtle", 635 + "zeroize", 636 + ] 637 + 638 + [[package]] 639 + name = "curve25519-dalek-derive" 640 + version = "0.1.1" 641 + source = "registry+https://github.com/rust-lang/crates.io-index" 642 + checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" 643 + dependencies = [ 644 + "proc-macro2", 645 + "quote", 646 + "syn 2.0.119", 647 + ] 648 + 649 + [[package]] 650 + name = "dary_heap" 651 + version = "0.3.9" 652 + source = "registry+https://github.com/rust-lang/crates.io-index" 653 + checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe" 654 + 655 + [[package]] 656 + name = "data-encoding" 657 + version = "2.11.0" 658 + source = "registry+https://github.com/rust-lang/crates.io-index" 659 + checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" 660 + 661 + [[package]] 662 + name = "data-encoding-macro" 663 + version = "0.1.20" 664 + source = "registry+https://github.com/rust-lang/crates.io-index" 665 + checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" 666 + dependencies = [ 667 + "data-encoding", 668 + "data-encoding-macro-internal", 669 + ] 670 + 671 + [[package]] 672 + name = "data-encoding-macro-internal" 673 + version = "0.1.18" 674 + source = "registry+https://github.com/rust-lang/crates.io-index" 675 + checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" 676 + dependencies = [ 677 + "data-encoding", 678 + "syn 2.0.119", 679 + ] 680 + 681 + [[package]] 682 + name = "der" 683 + version = "0.8.1" 684 + source = "registry+https://github.com/rust-lang/crates.io-index" 685 + checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" 686 + dependencies = [ 687 + "const-oid", 688 + "pem-rfc7468", 689 + "zeroize", 690 + ] 691 + 692 + [[package]] 693 + name = "deranged" 694 + version = "0.5.8" 695 + source = "registry+https://github.com/rust-lang/crates.io-index" 696 + checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" 697 + 698 + [[package]] 699 + name = "derive_more" 700 + version = "2.1.1" 701 + source = "registry+https://github.com/rust-lang/crates.io-index" 702 + checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" 703 + dependencies = [ 704 + "derive_more-impl", 705 + ] 706 + 707 + [[package]] 708 + name = "derive_more-impl" 709 + version = "2.1.1" 710 + source = "registry+https://github.com/rust-lang/crates.io-index" 711 + checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" 712 + dependencies = [ 713 + "convert_case", 714 + "proc-macro2", 715 + "quote", 716 + "rustc_version", 717 + "syn 2.0.119", 718 + "unicode-xid", 719 + ] 720 + 721 + [[package]] 722 + name = "diatomic-waker" 723 + version = "0.2.3" 724 + source = "registry+https://github.com/rust-lang/crates.io-index" 725 + checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" 726 + 727 + [[package]] 728 + name = "digest" 729 + version = "0.11.3" 730 + source = "registry+https://github.com/rust-lang/crates.io-index" 731 + checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" 732 + dependencies = [ 733 + "block-buffer", 734 + "crypto-common 0.2.2", 735 + ] 736 + 737 + [[package]] 738 + name = "dispatch2" 739 + version = "0.3.1" 740 + source = "registry+https://github.com/rust-lang/crates.io-index" 741 + checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" 742 + dependencies = [ 743 + "bitflags", 744 + "block2", 745 + "libc", 746 + "objc2", 747 + ] 748 + 749 + [[package]] 750 + name = "displaydoc" 751 + version = "0.2.6" 752 + source = "registry+https://github.com/rust-lang/crates.io-index" 753 + checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" 754 + dependencies = [ 755 + "proc-macro2", 756 + "quote", 757 + "syn 2.0.119", 758 + ] 759 + 760 + [[package]] 761 + name = "dlopen2" 762 + version = "0.8.2" 763 + source = "registry+https://github.com/rust-lang/crates.io-index" 764 + checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" 765 + dependencies = [ 766 + "libc", 767 + "once_cell", 768 + "winapi", 769 + ] 770 + 771 + [[package]] 772 + name = "dunce" 773 + version = "1.0.5" 774 + source = "registry+https://github.com/rust-lang/crates.io-index" 775 + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 776 + 777 + [[package]] 778 + name = "ed25519" 779 + version = "3.0.0" 780 + source = "registry+https://github.com/rust-lang/crates.io-index" 781 + checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" 782 + dependencies = [ 783 + "pkcs8", 784 + "serdect", 785 + "signature", 786 + ] 787 + 788 + [[package]] 789 + name = "ed25519-dalek" 790 + version = "3.0.0" 791 + source = "registry+https://github.com/rust-lang/crates.io-index" 792 + checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" 793 + dependencies = [ 794 + "curve25519-dalek", 795 + "ed25519", 796 + "rand_core 0.10.1", 797 + "serde", 798 + "sha2", 799 + "signature", 800 + "subtle", 801 + "zeroize", 802 + ] 803 + 804 + [[package]] 805 + name = "eetf" 806 + version = "0.11.0" 807 + source = "registry+https://github.com/rust-lang/crates.io-index" 808 + checksum = "b59d5a438de6099b4fd27f69d61a67c3b7758e3950fa6b1cef7d679d1e74a7d0" 809 + dependencies = [ 810 + "libflate", 811 + "num-bigint", 812 + "num-traits", 813 + ] 814 + 815 + [[package]] 816 + name = "either" 817 + version = "1.17.0" 818 + source = "registry+https://github.com/rust-lang/crates.io-index" 819 + checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" 820 + 821 + [[package]] 822 + name = "embedded-io" 823 + version = "0.4.0" 824 + source = "registry+https://github.com/rust-lang/crates.io-index" 825 + checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" 826 + 827 + [[package]] 828 + name = "embedded-io" 829 + version = "0.6.1" 830 + source = "registry+https://github.com/rust-lang/crates.io-index" 831 + checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 832 + 833 + [[package]] 834 + name = "enum-assoc" 835 + version = "1.3.0" 836 + source = "registry+https://github.com/rust-lang/crates.io-index" 837 + checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7" 838 + dependencies = [ 839 + "proc-macro2", 840 + "quote", 841 + "syn 2.0.119", 842 + ] 843 + 844 + [[package]] 845 + name = "equivalent" 846 + version = "1.0.2" 847 + source = "registry+https://github.com/rust-lang/crates.io-index" 848 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 849 + 850 + [[package]] 851 + name = "erl_dist" 852 + version = "0.8.0" 853 + source = "registry+https://github.com/rust-lang/crates.io-index" 854 + checksum = "61525240382fc8a099fe85ff7ed1aa1a806addf265fe1254867ab3a689d14015" 855 + dependencies = [ 856 + "eetf", 857 + "futures", 858 + "md5", 859 + "rand 0.10.2", 860 + ] 861 + 862 + [[package]] 863 + name = "erl_iroh" 864 + version = "0.1.0" 865 + dependencies = [ 866 + "anyhow", 867 + "async-dup", 868 + "bitflags", 869 + "bytes", 870 + "clap", 871 + "eetf", 872 + "erl_dist", 873 + "futures", 874 + "iroh", 875 + "rand 0.8.7", 876 + "rcgen", 877 + "rustls", 878 + "rustls-pemfile", 879 + "rustls-pki-types", 880 + "tempfile", 881 + "thiserror 2.0.19", 882 + "tokio", 883 + "tokio-rustls", 884 + "tokio-util", 885 + "tracing", 886 + "tracing-subscriber", 887 + "webpki-roots 0.26.11", 888 + ] 889 + 890 + [[package]] 891 + name = "errno" 892 + version = "0.3.14" 893 + source = "registry+https://github.com/rust-lang/crates.io-index" 894 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 895 + dependencies = [ 896 + "libc", 897 + "windows-sys 0.61.2", 898 + ] 899 + 900 + [[package]] 901 + name = "event-listener" 902 + version = "5.4.1" 903 + source = "registry+https://github.com/rust-lang/crates.io-index" 904 + checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" 905 + dependencies = [ 906 + "concurrent-queue", 907 + "parking", 908 + "pin-project-lite", 909 + ] 910 + 911 + [[package]] 912 + name = "event-listener-strategy" 913 + version = "0.5.4" 914 + source = "registry+https://github.com/rust-lang/crates.io-index" 915 + checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 916 + dependencies = [ 917 + "event-listener", 918 + "pin-project-lite", 919 + ] 920 + 921 + [[package]] 922 + name = "fastrand" 923 + version = "2.5.0" 924 + source = "registry+https://github.com/rust-lang/crates.io-index" 925 + checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" 926 + 927 + [[package]] 928 + name = "fiat-crypto" 929 + version = "0.3.0" 930 + source = "registry+https://github.com/rust-lang/crates.io-index" 931 + checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" 932 + 933 + [[package]] 934 + name = "find-msvc-tools" 935 + version = "0.1.9" 936 + source = "registry+https://github.com/rust-lang/crates.io-index" 937 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" 938 + 939 + [[package]] 940 + name = "fnv" 941 + version = "1.0.7" 942 + source = "registry+https://github.com/rust-lang/crates.io-index" 943 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 944 + 945 + [[package]] 946 + name = "foldhash" 947 + version = "0.2.0" 948 + source = "registry+https://github.com/rust-lang/crates.io-index" 949 + checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" 950 + 951 + [[package]] 952 + name = "form_urlencoded" 953 + version = "1.2.2" 954 + source = "registry+https://github.com/rust-lang/crates.io-index" 955 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 956 + dependencies = [ 957 + "percent-encoding", 958 + ] 959 + 960 + [[package]] 961 + name = "fs_extra" 962 + version = "1.3.0" 963 + source = "registry+https://github.com/rust-lang/crates.io-index" 964 + checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 965 + 966 + [[package]] 967 + name = "futures" 968 + version = "0.3.33" 969 + source = "registry+https://github.com/rust-lang/crates.io-index" 970 + checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" 971 + dependencies = [ 972 + "futures-channel", 973 + "futures-core", 974 + "futures-executor", 975 + "futures-io", 976 + "futures-sink", 977 + "futures-task", 978 + "futures-util", 979 + ] 980 + 981 + [[package]] 982 + name = "futures-buffered" 983 + version = "0.2.13" 984 + source = "registry+https://github.com/rust-lang/crates.io-index" 985 + checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5" 986 + dependencies = [ 987 + "cordyceps", 988 + "diatomic-waker", 989 + "futures-core", 990 + "pin-project-lite", 991 + "spin", 992 + ] 993 + 994 + [[package]] 995 + name = "futures-channel" 996 + version = "0.3.33" 997 + source = "registry+https://github.com/rust-lang/crates.io-index" 998 + checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" 999 + dependencies = [ 1000 + "futures-core", 1001 + "futures-sink", 1002 + ] 1003 + 1004 + [[package]] 1005 + name = "futures-core" 1006 + version = "0.3.33" 1007 + source = "registry+https://github.com/rust-lang/crates.io-index" 1008 + checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" 1009 + 1010 + [[package]] 1011 + name = "futures-executor" 1012 + version = "0.3.33" 1013 + source = "registry+https://github.com/rust-lang/crates.io-index" 1014 + checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" 1015 + dependencies = [ 1016 + "futures-core", 1017 + "futures-task", 1018 + "futures-util", 1019 + ] 1020 + 1021 + [[package]] 1022 + name = "futures-io" 1023 + version = "0.3.33" 1024 + source = "registry+https://github.com/rust-lang/crates.io-index" 1025 + checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" 1026 + 1027 + [[package]] 1028 + name = "futures-lite" 1029 + version = "2.6.1" 1030 + source = "registry+https://github.com/rust-lang/crates.io-index" 1031 + checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" 1032 + dependencies = [ 1033 + "fastrand", 1034 + "futures-core", 1035 + "futures-io", 1036 + "parking", 1037 + "pin-project-lite", 1038 + ] 1039 + 1040 + [[package]] 1041 + name = "futures-macro" 1042 + version = "0.3.33" 1043 + source = "registry+https://github.com/rust-lang/crates.io-index" 1044 + checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" 1045 + dependencies = [ 1046 + "proc-macro2", 1047 + "quote", 1048 + "syn 2.0.119", 1049 + ] 1050 + 1051 + [[package]] 1052 + name = "futures-sink" 1053 + version = "0.3.33" 1054 + source = "registry+https://github.com/rust-lang/crates.io-index" 1055 + checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" 1056 + 1057 + [[package]] 1058 + name = "futures-task" 1059 + version = "0.3.33" 1060 + source = "registry+https://github.com/rust-lang/crates.io-index" 1061 + checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" 1062 + 1063 + [[package]] 1064 + name = "futures-util" 1065 + version = "0.3.33" 1066 + source = "registry+https://github.com/rust-lang/crates.io-index" 1067 + checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" 1068 + dependencies = [ 1069 + "futures-channel", 1070 + "futures-core", 1071 + "futures-io", 1072 + "futures-macro", 1073 + "futures-sink", 1074 + "futures-task", 1075 + "memchr", 1076 + "pin-project-lite", 1077 + "slab", 1078 + ] 1079 + 1080 + [[package]] 1081 + name = "generator" 1082 + version = "0.8.9" 1083 + source = "registry+https://github.com/rust-lang/crates.io-index" 1084 + checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" 1085 + dependencies = [ 1086 + "cc", 1087 + "cfg-if", 1088 + "libc", 1089 + "log", 1090 + "rustversion", 1091 + "windows-link", 1092 + "windows-result", 1093 + ] 1094 + 1095 + [[package]] 1096 + name = "generic-array" 1097 + version = "0.14.7" 1098 + source = "registry+https://github.com/rust-lang/crates.io-index" 1099 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 1100 + dependencies = [ 1101 + "typenum", 1102 + "version_check", 1103 + ] 1104 + 1105 + [[package]] 1106 + name = "getrandom" 1107 + version = "0.2.17" 1108 + source = "registry+https://github.com/rust-lang/crates.io-index" 1109 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 1110 + dependencies = [ 1111 + "cfg-if", 1112 + "js-sys", 1113 + "libc", 1114 + "wasi", 1115 + "wasm-bindgen", 1116 + ] 1117 + 1118 + [[package]] 1119 + name = "getrandom" 1120 + version = "0.4.3" 1121 + source = "registry+https://github.com/rust-lang/crates.io-index" 1122 + checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" 1123 + dependencies = [ 1124 + "cfg-if", 1125 + "js-sys", 1126 + "libc", 1127 + "r-efi", 1128 + "rand_core 0.10.1", 1129 + "wasm-bindgen", 1130 + ] 1131 + 1132 + [[package]] 1133 + name = "ghash" 1134 + version = "0.5.1" 1135 + source = "registry+https://github.com/rust-lang/crates.io-index" 1136 + checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" 1137 + dependencies = [ 1138 + "opaque-debug", 1139 + "polyval", 1140 + ] 1141 + 1142 + [[package]] 1143 + name = "gloo-timers" 1144 + version = "0.3.0" 1145 + source = "registry+https://github.com/rust-lang/crates.io-index" 1146 + checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 1147 + dependencies = [ 1148 + "futures-channel", 1149 + "futures-core", 1150 + "js-sys", 1151 + "wasm-bindgen", 1152 + ] 1153 + 1154 + [[package]] 1155 + name = "h2" 1156 + version = "0.4.15" 1157 + source = "registry+https://github.com/rust-lang/crates.io-index" 1158 + checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" 1159 + dependencies = [ 1160 + "atomic-waker", 1161 + "bytes", 1162 + "fnv", 1163 + "futures-core", 1164 + "futures-sink", 1165 + "http", 1166 + "indexmap", 1167 + "slab", 1168 + "tokio", 1169 + "tokio-util", 1170 + "tracing", 1171 + ] 1172 + 1173 + [[package]] 1174 + name = "hashbrown" 1175 + version = "0.16.1" 1176 + source = "registry+https://github.com/rust-lang/crates.io-index" 1177 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 1178 + dependencies = [ 1179 + "allocator-api2", 1180 + "equivalent", 1181 + "foldhash", 1182 + ] 1183 + 1184 + [[package]] 1185 + name = "hashbrown" 1186 + version = "0.17.1" 1187 + source = "registry+https://github.com/rust-lang/crates.io-index" 1188 + checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" 1189 + dependencies = [ 1190 + "allocator-api2", 1191 + "equivalent", 1192 + "foldhash", 1193 + ] 1194 + 1195 + [[package]] 1196 + name = "heck" 1197 + version = "0.5.0" 1198 + source = "registry+https://github.com/rust-lang/crates.io-index" 1199 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1200 + 1201 + [[package]] 1202 + name = "hickory-net" 1203 + version = "0.26.1" 1204 + source = "registry+https://github.com/rust-lang/crates.io-index" 1205 + checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" 1206 + dependencies = [ 1207 + "async-trait", 1208 + "bytes", 1209 + "cfg-if", 1210 + "data-encoding", 1211 + "futures-channel", 1212 + "futures-io", 1213 + "futures-util", 1214 + "h2", 1215 + "hickory-proto", 1216 + "http", 1217 + "idna", 1218 + "ipnet", 1219 + "jni 0.22.4", 1220 + "rand 0.10.2", 1221 + "rustls", 1222 + "thiserror 2.0.19", 1223 + "tinyvec", 1224 + "tokio", 1225 + "tokio-rustls", 1226 + "tracing", 1227 + "url", 1228 + ] 1229 + 1230 + [[package]] 1231 + name = "hickory-proto" 1232 + version = "0.26.1" 1233 + source = "registry+https://github.com/rust-lang/crates.io-index" 1234 + checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" 1235 + dependencies = [ 1236 + "data-encoding", 1237 + "idna", 1238 + "ipnet", 1239 + "jni 0.22.4", 1240 + "once_cell", 1241 + "prefix-trie", 1242 + "rand 0.10.2", 1243 + "ring", 1244 + "thiserror 2.0.19", 1245 + "tinyvec", 1246 + "tracing", 1247 + "url", 1248 + ] 1249 + 1250 + [[package]] 1251 + name = "hickory-resolver" 1252 + version = "0.26.1" 1253 + source = "registry+https://github.com/rust-lang/crates.io-index" 1254 + checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" 1255 + dependencies = [ 1256 + "cfg-if", 1257 + "futures-util", 1258 + "hickory-net", 1259 + "hickory-proto", 1260 + "ipconfig", 1261 + "ipnet", 1262 + "jni 0.22.4", 1263 + "moka", 1264 + "ndk-context", 1265 + "once_cell", 1266 + "parking_lot", 1267 + "rand 0.10.2", 1268 + "resolv-conf", 1269 + "rustls", 1270 + "smallvec", 1271 + "system-configuration", 1272 + "thiserror 2.0.19", 1273 + "tokio", 1274 + "tokio-rustls", 1275 + "tracing", 1276 + ] 1277 + 1278 + [[package]] 1279 + name = "http" 1280 + version = "1.4.2" 1281 + source = "registry+https://github.com/rust-lang/crates.io-index" 1282 + checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" 1283 + dependencies = [ 1284 + "bytes", 1285 + "itoa", 1286 + ] 1287 + 1288 + [[package]] 1289 + name = "http-body" 1290 + version = "1.1.0" 1291 + source = "registry+https://github.com/rust-lang/crates.io-index" 1292 + checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" 1293 + dependencies = [ 1294 + "bytes", 1295 + "http", 1296 + ] 1297 + 1298 + [[package]] 1299 + name = "http-body-util" 1300 + version = "0.1.4" 1301 + source = "registry+https://github.com/rust-lang/crates.io-index" 1302 + checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" 1303 + dependencies = [ 1304 + "bytes", 1305 + "futures-core", 1306 + "http", 1307 + "http-body", 1308 + "pin-project-lite", 1309 + ] 1310 + 1311 + [[package]] 1312 + name = "httparse" 1313 + version = "1.10.1" 1314 + source = "registry+https://github.com/rust-lang/crates.io-index" 1315 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1316 + 1317 + [[package]] 1318 + name = "httpdate" 1319 + version = "1.0.3" 1320 + source = "registry+https://github.com/rust-lang/crates.io-index" 1321 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1322 + 1323 + [[package]] 1324 + name = "hybrid-array" 1325 + version = "0.4.13" 1326 + source = "registry+https://github.com/rust-lang/crates.io-index" 1327 + checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" 1328 + dependencies = [ 1329 + "typenum", 1330 + ] 1331 + 1332 + [[package]] 1333 + name = "hyper" 1334 + version = "1.11.0" 1335 + source = "registry+https://github.com/rust-lang/crates.io-index" 1336 + checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" 1337 + dependencies = [ 1338 + "atomic-waker", 1339 + "bytes", 1340 + "futures-channel", 1341 + "futures-core", 1342 + "h2", 1343 + "http", 1344 + "http-body", 1345 + "httparse", 1346 + "httpdate", 1347 + "itoa", 1348 + "pin-project-lite", 1349 + "smallvec", 1350 + "tokio", 1351 + "want", 1352 + ] 1353 + 1354 + [[package]] 1355 + name = "hyper-rustls" 1356 + version = "0.27.9" 1357 + source = "registry+https://github.com/rust-lang/crates.io-index" 1358 + checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" 1359 + dependencies = [ 1360 + "http", 1361 + "hyper", 1362 + "hyper-util", 1363 + "rustls", 1364 + "tokio", 1365 + "tokio-rustls", 1366 + "tower-service", 1367 + ] 1368 + 1369 + [[package]] 1370 + name = "hyper-util" 1371 + version = "0.1.20" 1372 + source = "registry+https://github.com/rust-lang/crates.io-index" 1373 + checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 1374 + dependencies = [ 1375 + "base64", 1376 + "bytes", 1377 + "futures-channel", 1378 + "futures-util", 1379 + "http", 1380 + "http-body", 1381 + "hyper", 1382 + "ipnet", 1383 + "libc", 1384 + "percent-encoding", 1385 + "pin-project-lite", 1386 + "socket2", 1387 + "tokio", 1388 + "tower-service", 1389 + "tracing", 1390 + ] 1391 + 1392 + [[package]] 1393 + name = "iana-time-zone" 1394 + version = "0.1.65" 1395 + source = "registry+https://github.com/rust-lang/crates.io-index" 1396 + checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" 1397 + dependencies = [ 1398 + "android_system_properties", 1399 + "core-foundation-sys", 1400 + "iana-time-zone-haiku", 1401 + "js-sys", 1402 + "log", 1403 + "wasm-bindgen", 1404 + "windows-core", 1405 + ] 1406 + 1407 + [[package]] 1408 + name = "iana-time-zone-haiku" 1409 + version = "0.1.2" 1410 + source = "registry+https://github.com/rust-lang/crates.io-index" 1411 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1412 + dependencies = [ 1413 + "cc", 1414 + ] 1415 + 1416 + [[package]] 1417 + name = "icu_collections" 1418 + version = "2.2.0" 1419 + source = "registry+https://github.com/rust-lang/crates.io-index" 1420 + checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" 1421 + dependencies = [ 1422 + "displaydoc", 1423 + "potential_utf", 1424 + "utf8_iter", 1425 + "yoke", 1426 + "zerofrom", 1427 + "zerovec", 1428 + ] 1429 + 1430 + [[package]] 1431 + name = "icu_locale_core" 1432 + version = "2.2.0" 1433 + source = "registry+https://github.com/rust-lang/crates.io-index" 1434 + checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" 1435 + dependencies = [ 1436 + "displaydoc", 1437 + "litemap", 1438 + "tinystr", 1439 + "writeable", 1440 + "zerovec", 1441 + ] 1442 + 1443 + [[package]] 1444 + name = "icu_normalizer" 1445 + version = "2.2.0" 1446 + source = "registry+https://github.com/rust-lang/crates.io-index" 1447 + checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" 1448 + dependencies = [ 1449 + "icu_collections", 1450 + "icu_normalizer_data", 1451 + "icu_properties", 1452 + "icu_provider", 1453 + "smallvec", 1454 + "zerovec", 1455 + ] 1456 + 1457 + [[package]] 1458 + name = "icu_normalizer_data" 1459 + version = "2.2.0" 1460 + source = "registry+https://github.com/rust-lang/crates.io-index" 1461 + checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" 1462 + 1463 + [[package]] 1464 + name = "icu_properties" 1465 + version = "2.2.0" 1466 + source = "registry+https://github.com/rust-lang/crates.io-index" 1467 + checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" 1468 + dependencies = [ 1469 + "icu_collections", 1470 + "icu_locale_core", 1471 + "icu_properties_data", 1472 + "icu_provider", 1473 + "zerotrie", 1474 + "zerovec", 1475 + ] 1476 + 1477 + [[package]] 1478 + name = "icu_properties_data" 1479 + version = "2.2.0" 1480 + source = "registry+https://github.com/rust-lang/crates.io-index" 1481 + checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" 1482 + 1483 + [[package]] 1484 + name = "icu_provider" 1485 + version = "2.2.0" 1486 + source = "registry+https://github.com/rust-lang/crates.io-index" 1487 + checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" 1488 + dependencies = [ 1489 + "displaydoc", 1490 + "icu_locale_core", 1491 + "writeable", 1492 + "yoke", 1493 + "zerofrom", 1494 + "zerotrie", 1495 + "zerovec", 1496 + ] 1497 + 1498 + [[package]] 1499 + name = "identity-hash" 1500 + version = "0.1.0" 1501 + source = "registry+https://github.com/rust-lang/crates.io-index" 1502 + checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" 1503 + 1504 + [[package]] 1505 + name = "idna" 1506 + version = "1.1.0" 1507 + source = "registry+https://github.com/rust-lang/crates.io-index" 1508 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 1509 + dependencies = [ 1510 + "idna_adapter", 1511 + "smallvec", 1512 + "utf8_iter", 1513 + ] 1514 + 1515 + [[package]] 1516 + name = "idna_adapter" 1517 + version = "1.2.2" 1518 + source = "registry+https://github.com/rust-lang/crates.io-index" 1519 + checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" 1520 + dependencies = [ 1521 + "icu_normalizer", 1522 + "icu_properties", 1523 + ] 1524 + 1525 + [[package]] 1526 + name = "igd-next" 1527 + version = "0.17.1" 1528 + source = "registry+https://github.com/rust-lang/crates.io-index" 1529 + checksum = "de7238d487a9aff61f81b5ab41c0a841532a115a398b5fa92a2fadd0885e2581" 1530 + dependencies = [ 1531 + "attohttpc", 1532 + "bytes", 1533 + "futures", 1534 + "http", 1535 + "http-body-util", 1536 + "hyper", 1537 + "hyper-util", 1538 + "log", 1539 + "rand 0.10.2", 1540 + "tokio", 1541 + "url", 1542 + "xmltree", 1543 + ] 1544 + 1545 + [[package]] 1546 + name = "indexmap" 1547 + version = "2.14.0" 1548 + source = "registry+https://github.com/rust-lang/crates.io-index" 1549 + checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" 1550 + dependencies = [ 1551 + "equivalent", 1552 + "hashbrown 0.17.1", 1553 + ] 1554 + 1555 + [[package]] 1556 + name = "inout" 1557 + version = "0.1.4" 1558 + source = "registry+https://github.com/rust-lang/crates.io-index" 1559 + checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 1560 + dependencies = [ 1561 + "generic-array", 1562 + ] 1563 + 1564 + [[package]] 1565 + name = "ipconfig" 1566 + version = "0.3.4" 1567 + source = "registry+https://github.com/rust-lang/crates.io-index" 1568 + checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" 1569 + dependencies = [ 1570 + "socket2", 1571 + "widestring", 1572 + "windows-registry", 1573 + "windows-result", 1574 + "windows-sys 0.61.2", 1575 + ] 1576 + 1577 + [[package]] 1578 + name = "ipnet" 1579 + version = "2.12.0" 1580 + source = "registry+https://github.com/rust-lang/crates.io-index" 1581 + checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" 1582 + dependencies = [ 1583 + "serde", 1584 + ] 1585 + 1586 + [[package]] 1587 + name = "iroh" 1588 + version = "1.0.3" 1589 + source = "registry+https://github.com/rust-lang/crates.io-index" 1590 + checksum = "460de6bc52163b41b1646931f2897e5ab986f0966ade444467fec25024751a72" 1591 + dependencies = [ 1592 + "backon", 1593 + "blake3", 1594 + "bytes", 1595 + "cfg_aliases", 1596 + "ctutils", 1597 + "data-encoding", 1598 + "derive_more", 1599 + "ed25519-dalek", 1600 + "futures-util", 1601 + "getrandom 0.4.3", 1602 + "hickory-resolver", 1603 + "http", 1604 + "ipnet", 1605 + "iroh-base", 1606 + "iroh-dns", 1607 + "iroh-metrics", 1608 + "iroh-relay", 1609 + "n0-error", 1610 + "n0-future", 1611 + "n0-watcher", 1612 + "netwatch", 1613 + "noq", 1614 + "noq-proto", 1615 + "noq-udp", 1616 + "papaya", 1617 + "pin-project", 1618 + "portable-atomic", 1619 + "portmapper", 1620 + "rand 0.10.2", 1621 + "reqwest", 1622 + "rustc-hash", 1623 + "rustls", 1624 + "rustls-pki-types", 1625 + "serde", 1626 + "smallvec", 1627 + "strum", 1628 + "time", 1629 + "tokio", 1630 + "tokio-stream", 1631 + "tokio-util", 1632 + "tracing", 1633 + "url", 1634 + "wasm-bindgen-futures", 1635 + ] 1636 + 1637 + [[package]] 1638 + name = "iroh-base" 1639 + version = "1.0.3" 1640 + source = "registry+https://github.com/rust-lang/crates.io-index" 1641 + checksum = "6be73e16ee21c923aca9b3121aaa0db936f7c7ecc156ff47b8dac944c68d59a8" 1642 + dependencies = [ 1643 + "curve25519-dalek", 1644 + "data-encoding", 1645 + "data-encoding-macro", 1646 + "derive_more", 1647 + "ed25519-dalek", 1648 + "getrandom 0.4.3", 1649 + "n0-error", 1650 + "rand 0.10.2", 1651 + "serde", 1652 + "url", 1653 + "zeroize", 1654 + ] 1655 + 1656 + [[package]] 1657 + name = "iroh-dns" 1658 + version = "1.0.3" 1659 + source = "registry+https://github.com/rust-lang/crates.io-index" 1660 + checksum = "46f6a9b39d18e6345f5c151afd299f2488e2cb5c520fe41b107b6bd3dc4c3349" 1661 + dependencies = [ 1662 + "arc-swap", 1663 + "cfg_aliases", 1664 + "derive_more", 1665 + "hickory-resolver", 1666 + "iroh-base", 1667 + "n0-error", 1668 + "n0-future", 1669 + "ndk-context", 1670 + "portable-atomic", 1671 + "rand 0.10.2", 1672 + "rustls", 1673 + "simple-dns", 1674 + "strum", 1675 + "tokio", 1676 + "tracing", 1677 + "url", 1678 + ] 1679 + 1680 + [[package]] 1681 + name = "iroh-metrics" 1682 + version = "1.0.1" 1683 + source = "registry+https://github.com/rust-lang/crates.io-index" 1684 + checksum = "291065721ad7c477b972e581bbc528df031dc8eb5e39fe1ff3300ae5dfb157ef" 1685 + dependencies = [ 1686 + "iroh-metrics-derive", 1687 + "itoa", 1688 + "n0-error", 1689 + "portable-atomic", 1690 + "ryu", 1691 + "serde", 1692 + "tracing", 1693 + ] 1694 + 1695 + [[package]] 1696 + name = "iroh-metrics-derive" 1697 + version = "1.0.1" 1698 + source = "registry+https://github.com/rust-lang/crates.io-index" 1699 + checksum = "1ae5f0c4405d1fbc9fb16ff422ca40620e93dc36c30ecaba0c2aee3992b7bd48" 1700 + dependencies = [ 1701 + "heck", 1702 + "proc-macro2", 1703 + "quote", 1704 + "syn 2.0.119", 1705 + ] 1706 + 1707 + [[package]] 1708 + name = "iroh-relay" 1709 + version = "1.0.3" 1710 + source = "registry+https://github.com/rust-lang/crates.io-index" 1711 + checksum = "24bd586cf927f7b700f56ec3639b53cb5fa901ce284784051ff71092bfbf8193" 1712 + dependencies = [ 1713 + "blake3", 1714 + "bytes", 1715 + "cfg_aliases", 1716 + "data-encoding", 1717 + "derive_more", 1718 + "getrandom 0.4.3", 1719 + "hickory-resolver", 1720 + "http", 1721 + "http-body-util", 1722 + "hyper", 1723 + "hyper-util", 1724 + "iroh-base", 1725 + "iroh-dns", 1726 + "iroh-metrics", 1727 + "lru", 1728 + "n0-error", 1729 + "n0-future", 1730 + "noq", 1731 + "noq-proto", 1732 + "num_enum", 1733 + "pin-project", 1734 + "postcard", 1735 + "rand 0.10.2", 1736 + "reqwest", 1737 + "rustls", 1738 + "rustls-pki-types", 1739 + "serde", 1740 + "serde_bytes", 1741 + "strum", 1742 + "tokio", 1743 + "tokio-rustls", 1744 + "tokio-util", 1745 + "tokio-websockets", 1746 + "tracing", 1747 + "url", 1748 + "webpki-roots 1.0.9", 1749 + "ws_stream_wasm", 1750 + ] 1751 + 1752 + [[package]] 1753 + name = "is_terminal_polyfill" 1754 + version = "1.70.2" 1755 + source = "registry+https://github.com/rust-lang/crates.io-index" 1756 + checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 1757 + 1758 + [[package]] 1759 + name = "itoa" 1760 + version = "1.0.18" 1761 + source = "registry+https://github.com/rust-lang/crates.io-index" 1762 + checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" 1763 + 1764 + [[package]] 1765 + name = "jni" 1766 + version = "0.21.1" 1767 + source = "registry+https://github.com/rust-lang/crates.io-index" 1768 + checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 1769 + dependencies = [ 1770 + "cesu8", 1771 + "cfg-if", 1772 + "combine", 1773 + "jni-sys 0.3.1", 1774 + "log", 1775 + "thiserror 1.0.69", 1776 + "walkdir", 1777 + "windows-sys 0.45.0", 1778 + ] 1779 + 1780 + [[package]] 1781 + name = "jni" 1782 + version = "0.22.4" 1783 + source = "registry+https://github.com/rust-lang/crates.io-index" 1784 + checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" 1785 + dependencies = [ 1786 + "cfg-if", 1787 + "combine", 1788 + "jni-macros", 1789 + "jni-sys 0.4.1", 1790 + "log", 1791 + "simd_cesu8", 1792 + "thiserror 2.0.19", 1793 + "walkdir", 1794 + "windows-link", 1795 + ] 1796 + 1797 + [[package]] 1798 + name = "jni-macros" 1799 + version = "0.22.4" 1800 + source = "registry+https://github.com/rust-lang/crates.io-index" 1801 + checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" 1802 + dependencies = [ 1803 + "proc-macro2", 1804 + "quote", 1805 + "rustc_version", 1806 + "simd_cesu8", 1807 + "syn 2.0.119", 1808 + ] 1809 + 1810 + [[package]] 1811 + name = "jni-sys" 1812 + version = "0.3.1" 1813 + source = "registry+https://github.com/rust-lang/crates.io-index" 1814 + checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" 1815 + dependencies = [ 1816 + "jni-sys 0.4.1", 1817 + ] 1818 + 1819 + [[package]] 1820 + name = "jni-sys" 1821 + version = "0.4.1" 1822 + source = "registry+https://github.com/rust-lang/crates.io-index" 1823 + checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" 1824 + dependencies = [ 1825 + "jni-sys-macros", 1826 + ] 1827 + 1828 + [[package]] 1829 + name = "jni-sys-macros" 1830 + version = "0.4.1" 1831 + source = "registry+https://github.com/rust-lang/crates.io-index" 1832 + checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" 1833 + dependencies = [ 1834 + "quote", 1835 + "syn 2.0.119", 1836 + ] 1837 + 1838 + [[package]] 1839 + name = "jobserver" 1840 + version = "0.1.35" 1841 + source = "registry+https://github.com/rust-lang/crates.io-index" 1842 + checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" 1843 + dependencies = [ 1844 + "getrandom 0.4.3", 1845 + "libc", 1846 + ] 1847 + 1848 + [[package]] 1849 + name = "js-sys" 1850 + version = "0.3.103" 1851 + source = "registry+https://github.com/rust-lang/crates.io-index" 1852 + checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" 1853 + dependencies = [ 1854 + "cfg-if", 1855 + "futures-util", 1856 + "wasm-bindgen", 1857 + ] 1858 + 1859 + [[package]] 1860 + name = "lazy_static" 1861 + version = "1.5.0" 1862 + source = "registry+https://github.com/rust-lang/crates.io-index" 1863 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1864 + 1865 + [[package]] 1866 + name = "libc" 1867 + version = "0.2.189" 1868 + source = "registry+https://github.com/rust-lang/crates.io-index" 1869 + checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" 1870 + 1871 + [[package]] 1872 + name = "libflate" 1873 + version = "2.3.1" 1874 + source = "registry+https://github.com/rust-lang/crates.io-index" 1875 + checksum = "a4da9b700e758e57152a1fd1c52cbdc5727c1aa6d8743dc1acda917398f1d76c" 1876 + dependencies = [ 1877 + "adler32", 1878 + "crc32fast", 1879 + "dary_heap", 1880 + "libflate_lz77", 1881 + "no_std_io2", 1882 + ] 1883 + 1884 + [[package]] 1885 + name = "libflate_lz77" 1886 + version = "2.3.0" 1887 + source = "registry+https://github.com/rust-lang/crates.io-index" 1888 + checksum = "ff7a10e427698aef6eef269482776debfef63384d30f13aad39a1a95e0e098fd" 1889 + dependencies = [ 1890 + "hashbrown 0.16.1", 1891 + "no_std_io2", 1892 + "rle-decode-fast", 1893 + ] 1894 + 1895 + [[package]] 1896 + name = "linux-raw-sys" 1897 + version = "0.12.1" 1898 + source = "registry+https://github.com/rust-lang/crates.io-index" 1899 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 1900 + 1901 + [[package]] 1902 + name = "litemap" 1903 + version = "0.8.2" 1904 + source = "registry+https://github.com/rust-lang/crates.io-index" 1905 + checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" 1906 + 1907 + [[package]] 1908 + name = "lock_api" 1909 + version = "0.4.14" 1910 + source = "registry+https://github.com/rust-lang/crates.io-index" 1911 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1912 + dependencies = [ 1913 + "scopeguard", 1914 + ] 1915 + 1916 + [[package]] 1917 + name = "log" 1918 + version = "0.4.33" 1919 + source = "registry+https://github.com/rust-lang/crates.io-index" 1920 + checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" 1921 + 1922 + [[package]] 1923 + name = "loom" 1924 + version = "0.7.2" 1925 + source = "registry+https://github.com/rust-lang/crates.io-index" 1926 + checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 1927 + dependencies = [ 1928 + "cfg-if", 1929 + "generator", 1930 + "scoped-tls", 1931 + "tracing", 1932 + "tracing-subscriber", 1933 + ] 1934 + 1935 + [[package]] 1936 + name = "lru" 1937 + version = "0.18.1" 1938 + source = "registry+https://github.com/rust-lang/crates.io-index" 1939 + checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" 1940 + dependencies = [ 1941 + "hashbrown 0.17.1", 1942 + ] 1943 + 1944 + [[package]] 1945 + name = "lru-slab" 1946 + version = "0.1.2" 1947 + source = "registry+https://github.com/rust-lang/crates.io-index" 1948 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 1949 + 1950 + [[package]] 1951 + name = "mac-addr" 1952 + version = "0.3.0" 1953 + source = "registry+https://github.com/rust-lang/crates.io-index" 1954 + checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" 1955 + 1956 + [[package]] 1957 + name = "matchers" 1958 + version = "0.2.0" 1959 + source = "registry+https://github.com/rust-lang/crates.io-index" 1960 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 1961 + dependencies = [ 1962 + "regex-automata", 1963 + ] 1964 + 1965 + [[package]] 1966 + name = "md5" 1967 + version = "0.8.1" 1968 + source = "registry+https://github.com/rust-lang/crates.io-index" 1969 + checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" 1970 + 1971 + [[package]] 1972 + name = "memchr" 1973 + version = "2.8.3" 1974 + source = "registry+https://github.com/rust-lang/crates.io-index" 1975 + checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" 1976 + 1977 + [[package]] 1978 + name = "mio" 1979 + version = "1.2.2" 1980 + source = "registry+https://github.com/rust-lang/crates.io-index" 1981 + checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" 1982 + dependencies = [ 1983 + "libc", 1984 + "wasi", 1985 + "windows-sys 0.61.2", 1986 + ] 1987 + 1988 + [[package]] 1989 + name = "moka" 1990 + version = "0.12.15" 1991 + source = "registry+https://github.com/rust-lang/crates.io-index" 1992 + checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" 1993 + dependencies = [ 1994 + "crossbeam-channel", 1995 + "crossbeam-epoch", 1996 + "crossbeam-utils", 1997 + "equivalent", 1998 + "parking_lot", 1999 + "portable-atomic", 2000 + "smallvec", 2001 + "tagptr", 2002 + "uuid", 2003 + ] 2004 + 2005 + [[package]] 2006 + name = "n0-error" 2007 + version = "1.0.0" 2008 + source = "registry+https://github.com/rust-lang/crates.io-index" 2009 + checksum = "c37e81176a83a77d2514528b91bdafc70ef88aab428f0e1b91aebb8d99888895" 2010 + dependencies = [ 2011 + "n0-error-macros", 2012 + "spez", 2013 + ] 2014 + 2015 + [[package]] 2016 + name = "n0-error-macros" 2017 + version = "1.0.0" 2018 + source = "registry+https://github.com/rust-lang/crates.io-index" 2019 + checksum = "e2acd8b070213b0299282f884b4beba4e7b52d624fdcd504a3ad3665390c11e1" 2020 + dependencies = [ 2021 + "proc-macro2", 2022 + "quote", 2023 + "syn 2.0.119", 2024 + ] 2025 + 2026 + [[package]] 2027 + name = "n0-future" 2028 + version = "0.3.2" 2029 + source = "registry+https://github.com/rust-lang/crates.io-index" 2030 + checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe" 2031 + dependencies = [ 2032 + "cfg_aliases", 2033 + "derive_more", 2034 + "futures-buffered", 2035 + "futures-lite", 2036 + "futures-util", 2037 + "js-sys", 2038 + "pin-project", 2039 + "send_wrapper", 2040 + "tokio", 2041 + "tokio-util", 2042 + "wasm-bindgen", 2043 + "wasm-bindgen-futures", 2044 + "web-time", 2045 + ] 2046 + 2047 + [[package]] 2048 + name = "n0-watcher" 2049 + version = "1.0.0" 2050 + source = "registry+https://github.com/rust-lang/crates.io-index" 2051 + checksum = "bbc618745ad0b7414b149d0517ad8b5573b2fb4d4e2717add3d2446ce1fdd826" 2052 + dependencies = [ 2053 + "derive_more", 2054 + "n0-error", 2055 + "n0-future", 2056 + ] 2057 + 2058 + [[package]] 2059 + name = "ndk-context" 2060 + version = "0.1.1" 2061 + source = "registry+https://github.com/rust-lang/crates.io-index" 2062 + checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 2063 + 2064 + [[package]] 2065 + name = "netdev" 2066 + version = "0.45.0" 2067 + source = "registry+https://github.com/rust-lang/crates.io-index" 2068 + checksum = "569dfbdd2efd771b24ec9bb57f956e04d4fbfc72f62b2f11961723f9b3f4b020" 2069 + dependencies = [ 2070 + "block2", 2071 + "dispatch2", 2072 + "dlopen2", 2073 + "ipnet", 2074 + "jni 0.21.1", 2075 + "libc", 2076 + "mac-addr", 2077 + "ndk-context", 2078 + "netlink-packet-core", 2079 + "netlink-packet-route", 2080 + "netlink-sys", 2081 + "objc2", 2082 + "objc2-core-foundation", 2083 + "objc2-core-wlan", 2084 + "objc2-foundation", 2085 + "objc2-system-configuration", 2086 + "once_cell", 2087 + "plist", 2088 + "windows-sys 0.61.2", 2089 + ] 2090 + 2091 + [[package]] 2092 + name = "netlink-packet-core" 2093 + version = "0.8.1" 2094 + source = "registry+https://github.com/rust-lang/crates.io-index" 2095 + checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" 2096 + dependencies = [ 2097 + "paste", 2098 + ] 2099 + 2100 + [[package]] 2101 + name = "netlink-packet-route" 2102 + version = "0.31.0" 2103 + source = "registry+https://github.com/rust-lang/crates.io-index" 2104 + checksum = "e2288fcb784eb3defd5fb16f4c4160d5f477de192eac730f43e1d11c24d9a007" 2105 + dependencies = [ 2106 + "bitflags", 2107 + "libc", 2108 + "log", 2109 + "netlink-packet-core", 2110 + ] 2111 + 2112 + [[package]] 2113 + name = "netlink-proto" 2114 + version = "0.12.1" 2115 + source = "registry+https://github.com/rust-lang/crates.io-index" 2116 + checksum = "e6f7398dddf5f152d2a91a2921a134c6097056e292c0d4b9906007855e7cece6" 2117 + dependencies = [ 2118 + "bytes", 2119 + "futures-channel", 2120 + "futures-util", 2121 + "log", 2122 + "netlink-packet-core", 2123 + "netlink-sys", 2124 + "thiserror 2.0.19", 2125 + ] 2126 + 2127 + [[package]] 2128 + name = "netlink-sys" 2129 + version = "0.8.8" 2130 + source = "registry+https://github.com/rust-lang/crates.io-index" 2131 + checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" 2132 + dependencies = [ 2133 + "bytes", 2134 + "futures-util", 2135 + "libc", 2136 + "log", 2137 + "tokio", 2138 + ] 2139 + 2140 + [[package]] 2141 + name = "netwatch" 2142 + version = "0.19.1" 2143 + source = "registry+https://github.com/rust-lang/crates.io-index" 2144 + checksum = "4d9cbe01741347ef750d743d6690603f5eed8341e679fb51c8e629337aa11976" 2145 + dependencies = [ 2146 + "atomic-waker", 2147 + "bytes", 2148 + "cfg_aliases", 2149 + "derive_more", 2150 + "ipnet", 2151 + "js-sys", 2152 + "libc", 2153 + "n0-error", 2154 + "n0-future", 2155 + "n0-watcher", 2156 + "netdev", 2157 + "netlink-packet-core", 2158 + "netlink-packet-route", 2159 + "netlink-proto", 2160 + "netlink-sys", 2161 + "noq-udp", 2162 + "objc2-core-foundation", 2163 + "objc2-system-configuration", 2164 + "pin-project-lite", 2165 + "serde", 2166 + "socket2", 2167 + "time", 2168 + "tokio", 2169 + "tokio-util", 2170 + "tracing", 2171 + "web-sys", 2172 + "windows", 2173 + "windows-result", 2174 + "wmi", 2175 + ] 2176 + 2177 + [[package]] 2178 + name = "no_std_io2" 2179 + version = "0.9.4" 2180 + source = "registry+https://github.com/rust-lang/crates.io-index" 2181 + checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" 2182 + dependencies = [ 2183 + "memchr", 2184 + ] 2185 + 2186 + [[package]] 2187 + name = "noq" 2188 + version = "1.1.0" 2189 + source = "registry+https://github.com/rust-lang/crates.io-index" 2190 + checksum = "e11803df44ac03a30988d61585ea50885d5428e42da944fe1e498799da7886a2" 2191 + dependencies = [ 2192 + "bytes", 2193 + "cfg_aliases", 2194 + "derive_more", 2195 + "noq-proto", 2196 + "noq-udp", 2197 + "pin-project-lite", 2198 + "rustc-hash", 2199 + "rustls", 2200 + "socket2", 2201 + "thiserror 2.0.19", 2202 + "tokio", 2203 + "tokio-stream", 2204 + "tracing", 2205 + "web-time", 2206 + ] 2207 + 2208 + [[package]] 2209 + name = "noq-proto" 2210 + version = "1.1.0" 2211 + source = "registry+https://github.com/rust-lang/crates.io-index" 2212 + checksum = "334c3c9833f7b2c573cceb9896ddc7aaeb58c8807cbb63211b24d1fe88bf866e" 2213 + dependencies = [ 2214 + "aes-gcm", 2215 + "bytes", 2216 + "derive_more", 2217 + "enum-assoc", 2218 + "getrandom 0.4.3", 2219 + "identity-hash", 2220 + "lru-slab", 2221 + "rand 0.10.2", 2222 + "rand_pcg", 2223 + "ring", 2224 + "rustc-hash", 2225 + "rustls", 2226 + "rustls-pki-types", 2227 + "slab", 2228 + "sorted-index-buffer", 2229 + "thiserror 2.0.19", 2230 + "tinyvec", 2231 + "tracing", 2232 + "web-time", 2233 + ] 2234 + 2235 + [[package]] 2236 + name = "noq-udp" 2237 + version = "1.1.0" 2238 + source = "registry+https://github.com/rust-lang/crates.io-index" 2239 + checksum = "bde7a5d5102f1cff03d482240f0ed20551661f63663620f4b26112ed751165e9" 2240 + dependencies = [ 2241 + "cfg_aliases", 2242 + "libc", 2243 + "socket2", 2244 + "tracing", 2245 + "windows-sys 0.61.2", 2246 + ] 2247 + 2248 + [[package]] 2249 + name = "nu-ansi-term" 2250 + version = "0.50.3" 2251 + source = "registry+https://github.com/rust-lang/crates.io-index" 2252 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 2253 + dependencies = [ 2254 + "windows-sys 0.61.2", 2255 + ] 2256 + 2257 + [[package]] 2258 + name = "num-bigint" 2259 + version = "0.4.8" 2260 + source = "registry+https://github.com/rust-lang/crates.io-index" 2261 + checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" 2262 + dependencies = [ 2263 + "num-integer", 2264 + "num-traits", 2265 + ] 2266 + 2267 + [[package]] 2268 + name = "num-conv" 2269 + version = "0.2.2" 2270 + source = "registry+https://github.com/rust-lang/crates.io-index" 2271 + checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" 2272 + 2273 + [[package]] 2274 + name = "num-integer" 2275 + version = "0.1.46" 2276 + source = "registry+https://github.com/rust-lang/crates.io-index" 2277 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 2278 + dependencies = [ 2279 + "num-traits", 2280 + ] 2281 + 2282 + [[package]] 2283 + name = "num-traits" 2284 + version = "0.2.19" 2285 + source = "registry+https://github.com/rust-lang/crates.io-index" 2286 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 2287 + dependencies = [ 2288 + "autocfg", 2289 + ] 2290 + 2291 + [[package]] 2292 + name = "num_enum" 2293 + version = "0.7.6" 2294 + source = "registry+https://github.com/rust-lang/crates.io-index" 2295 + checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" 2296 + dependencies = [ 2297 + "num_enum_derive", 2298 + "rustversion", 2299 + ] 2300 + 2301 + [[package]] 2302 + name = "num_enum_derive" 2303 + version = "0.7.6" 2304 + source = "registry+https://github.com/rust-lang/crates.io-index" 2305 + checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" 2306 + dependencies = [ 2307 + "proc-macro-crate", 2308 + "proc-macro2", 2309 + "quote", 2310 + "syn 2.0.119", 2311 + ] 2312 + 2313 + [[package]] 2314 + name = "objc2" 2315 + version = "0.6.4" 2316 + source = "registry+https://github.com/rust-lang/crates.io-index" 2317 + checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" 2318 + dependencies = [ 2319 + "objc2-encode", 2320 + ] 2321 + 2322 + [[package]] 2323 + name = "objc2-core-foundation" 2324 + version = "0.3.2" 2325 + source = "registry+https://github.com/rust-lang/crates.io-index" 2326 + checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" 2327 + dependencies = [ 2328 + "bitflags", 2329 + "block2", 2330 + "dispatch2", 2331 + "libc", 2332 + "objc2", 2333 + ] 2334 + 2335 + [[package]] 2336 + name = "objc2-core-wlan" 2337 + version = "0.3.2" 2338 + source = "registry+https://github.com/rust-lang/crates.io-index" 2339 + checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48" 2340 + dependencies = [ 2341 + "bitflags", 2342 + "objc2", 2343 + "objc2-core-foundation", 2344 + "objc2-foundation", 2345 + "objc2-security", 2346 + "objc2-security-foundation", 2347 + ] 2348 + 2349 + [[package]] 2350 + name = "objc2-encode" 2351 + version = "4.1.0" 2352 + source = "registry+https://github.com/rust-lang/crates.io-index" 2353 + checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 2354 + 2355 + [[package]] 2356 + name = "objc2-foundation" 2357 + version = "0.3.2" 2358 + source = "registry+https://github.com/rust-lang/crates.io-index" 2359 + checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" 2360 + dependencies = [ 2361 + "bitflags", 2362 + "block2", 2363 + "libc", 2364 + "objc2", 2365 + "objc2-core-foundation", 2366 + ] 2367 + 2368 + [[package]] 2369 + name = "objc2-security" 2370 + version = "0.3.2" 2371 + source = "registry+https://github.com/rust-lang/crates.io-index" 2372 + checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" 2373 + dependencies = [ 2374 + "bitflags", 2375 + "objc2", 2376 + "objc2-core-foundation", 2377 + ] 2378 + 2379 + [[package]] 2380 + name = "objc2-security-foundation" 2381 + version = "0.3.2" 2382 + source = "registry+https://github.com/rust-lang/crates.io-index" 2383 + checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5" 2384 + dependencies = [ 2385 + "objc2", 2386 + "objc2-foundation", 2387 + ] 2388 + 2389 + [[package]] 2390 + name = "objc2-system-configuration" 2391 + version = "0.3.2" 2392 + source = "registry+https://github.com/rust-lang/crates.io-index" 2393 + checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" 2394 + dependencies = [ 2395 + "bitflags", 2396 + "dispatch2", 2397 + "libc", 2398 + "objc2", 2399 + "objc2-core-foundation", 2400 + "objc2-security", 2401 + ] 2402 + 2403 + [[package]] 2404 + name = "once_cell" 2405 + version = "1.21.4" 2406 + source = "registry+https://github.com/rust-lang/crates.io-index" 2407 + checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" 2408 + dependencies = [ 2409 + "critical-section", 2410 + "portable-atomic", 2411 + ] 2412 + 2413 + [[package]] 2414 + name = "once_cell_polyfill" 2415 + version = "1.70.2" 2416 + source = "registry+https://github.com/rust-lang/crates.io-index" 2417 + checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 2418 + 2419 + [[package]] 2420 + name = "opaque-debug" 2421 + version = "0.3.1" 2422 + source = "registry+https://github.com/rust-lang/crates.io-index" 2423 + checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 2424 + 2425 + [[package]] 2426 + name = "openssl-probe" 2427 + version = "0.2.1" 2428 + source = "registry+https://github.com/rust-lang/crates.io-index" 2429 + checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 2430 + 2431 + [[package]] 2432 + name = "papaya" 2433 + version = "0.2.4" 2434 + source = "registry+https://github.com/rust-lang/crates.io-index" 2435 + checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" 2436 + dependencies = [ 2437 + "equivalent", 2438 + "seize", 2439 + ] 2440 + 2441 + [[package]] 2442 + name = "parking" 2443 + version = "2.2.1" 2444 + source = "registry+https://github.com/rust-lang/crates.io-index" 2445 + checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 2446 + 2447 + [[package]] 2448 + name = "parking_lot" 2449 + version = "0.12.5" 2450 + source = "registry+https://github.com/rust-lang/crates.io-index" 2451 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 2452 + dependencies = [ 2453 + "lock_api", 2454 + "parking_lot_core", 2455 + ] 2456 + 2457 + [[package]] 2458 + name = "parking_lot_core" 2459 + version = "0.9.12" 2460 + source = "registry+https://github.com/rust-lang/crates.io-index" 2461 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 2462 + dependencies = [ 2463 + "cfg-if", 2464 + "libc", 2465 + "redox_syscall", 2466 + "smallvec", 2467 + "windows-link", 2468 + ] 2469 + 2470 + [[package]] 2471 + name = "paste" 2472 + version = "1.0.15" 2473 + source = "registry+https://github.com/rust-lang/crates.io-index" 2474 + checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 2475 + 2476 + [[package]] 2477 + name = "pem" 2478 + version = "3.0.6" 2479 + source = "registry+https://github.com/rust-lang/crates.io-index" 2480 + checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" 2481 + dependencies = [ 2482 + "base64", 2483 + "serde_core", 2484 + ] 2485 + 2486 + [[package]] 2487 + name = "pem-rfc7468" 2488 + version = "1.0.0" 2489 + source = "registry+https://github.com/rust-lang/crates.io-index" 2490 + checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" 2491 + dependencies = [ 2492 + "base64ct", 2493 + ] 2494 + 2495 + [[package]] 2496 + name = "percent-encoding" 2497 + version = "2.3.2" 2498 + source = "registry+https://github.com/rust-lang/crates.io-index" 2499 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 2500 + 2501 + [[package]] 2502 + name = "pharos" 2503 + version = "0.5.3" 2504 + source = "registry+https://github.com/rust-lang/crates.io-index" 2505 + checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" 2506 + dependencies = [ 2507 + "futures", 2508 + "rustc_version", 2509 + ] 2510 + 2511 + [[package]] 2512 + name = "pin-project" 2513 + version = "1.1.13" 2514 + source = "registry+https://github.com/rust-lang/crates.io-index" 2515 + checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" 2516 + dependencies = [ 2517 + "pin-project-internal", 2518 + ] 2519 + 2520 + [[package]] 2521 + name = "pin-project-internal" 2522 + version = "1.1.13" 2523 + source = "registry+https://github.com/rust-lang/crates.io-index" 2524 + checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" 2525 + dependencies = [ 2526 + "proc-macro2", 2527 + "quote", 2528 + "syn 2.0.119", 2529 + ] 2530 + 2531 + [[package]] 2532 + name = "pin-project-lite" 2533 + version = "0.2.17" 2534 + source = "registry+https://github.com/rust-lang/crates.io-index" 2535 + checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 2536 + 2537 + [[package]] 2538 + name = "pkcs8" 2539 + version = "0.11.0" 2540 + source = "registry+https://github.com/rust-lang/crates.io-index" 2541 + checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" 2542 + dependencies = [ 2543 + "der", 2544 + "spki", 2545 + ] 2546 + 2547 + [[package]] 2548 + name = "pkg-config" 2549 + version = "0.3.33" 2550 + source = "registry+https://github.com/rust-lang/crates.io-index" 2551 + checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" 2552 + 2553 + [[package]] 2554 + name = "plist" 2555 + version = "1.10.0" 2556 + source = "registry+https://github.com/rust-lang/crates.io-index" 2557 + checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" 2558 + dependencies = [ 2559 + "base64", 2560 + "indexmap", 2561 + "quick-xml", 2562 + "serde", 2563 + "time", 2564 + ] 2565 + 2566 + [[package]] 2567 + name = "polyval" 2568 + version = "0.6.2" 2569 + source = "registry+https://github.com/rust-lang/crates.io-index" 2570 + checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" 2571 + dependencies = [ 2572 + "cfg-if", 2573 + "cpufeatures 0.2.17", 2574 + "opaque-debug", 2575 + "universal-hash", 2576 + ] 2577 + 2578 + [[package]] 2579 + name = "portable-atomic" 2580 + version = "1.14.0" 2581 + source = "registry+https://github.com/rust-lang/crates.io-index" 2582 + checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" 2583 + dependencies = [ 2584 + "serde", 2585 + ] 2586 + 2587 + [[package]] 2588 + name = "portmapper" 2589 + version = "0.19.1" 2590 + source = "registry+https://github.com/rust-lang/crates.io-index" 2591 + checksum = "eb3713e4977408279158444a18c1a01ac9bf2e7eaf1fbfd1a19ac9cd18d90721" 2592 + dependencies = [ 2593 + "base64", 2594 + "bytes", 2595 + "derive_more", 2596 + "hyper-util", 2597 + "igd-next", 2598 + "iroh-metrics", 2599 + "libc", 2600 + "n0-error", 2601 + "n0-future", 2602 + "netwatch", 2603 + "num_enum", 2604 + "rand 0.10.2", 2605 + "serde", 2606 + "smallvec", 2607 + "socket2", 2608 + "time", 2609 + "tokio", 2610 + "tokio-util", 2611 + "tower-layer", 2612 + "tracing", 2613 + "url", 2614 + ] 2615 + 2616 + [[package]] 2617 + name = "postcard" 2618 + version = "1.1.3" 2619 + source = "registry+https://github.com/rust-lang/crates.io-index" 2620 + checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" 2621 + dependencies = [ 2622 + "cobs", 2623 + "embedded-io 0.4.0", 2624 + "embedded-io 0.6.1", 2625 + "postcard-derive", 2626 + "serde", 2627 + ] 2628 + 2629 + [[package]] 2630 + name = "postcard-derive" 2631 + version = "0.2.2" 2632 + source = "registry+https://github.com/rust-lang/crates.io-index" 2633 + checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" 2634 + dependencies = [ 2635 + "proc-macro2", 2636 + "quote", 2637 + "syn 2.0.119", 2638 + ] 2639 + 2640 + [[package]] 2641 + name = "potential_utf" 2642 + version = "0.1.5" 2643 + source = "registry+https://github.com/rust-lang/crates.io-index" 2644 + checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" 2645 + dependencies = [ 2646 + "zerovec", 2647 + ] 2648 + 2649 + [[package]] 2650 + name = "powerfmt" 2651 + version = "0.2.0" 2652 + source = "registry+https://github.com/rust-lang/crates.io-index" 2653 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2654 + 2655 + [[package]] 2656 + name = "ppv-lite86" 2657 + version = "0.2.21" 2658 + source = "registry+https://github.com/rust-lang/crates.io-index" 2659 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 2660 + dependencies = [ 2661 + "zerocopy", 2662 + ] 2663 + 2664 + [[package]] 2665 + name = "prefix-trie" 2666 + version = "0.8.4" 2667 + source = "registry+https://github.com/rust-lang/crates.io-index" 2668 + checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" 2669 + dependencies = [ 2670 + "either", 2671 + "ipnet", 2672 + "num-traits", 2673 + ] 2674 + 2675 + [[package]] 2676 + name = "proc-macro-crate" 2677 + version = "3.5.0" 2678 + source = "registry+https://github.com/rust-lang/crates.io-index" 2679 + checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" 2680 + dependencies = [ 2681 + "toml_edit", 2682 + ] 2683 + 2684 + [[package]] 2685 + name = "proc-macro2" 2686 + version = "1.0.107" 2687 + source = "registry+https://github.com/rust-lang/crates.io-index" 2688 + checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" 2689 + dependencies = [ 2690 + "unicode-ident", 2691 + ] 2692 + 2693 + [[package]] 2694 + name = "quick-xml" 2695 + version = "0.41.0" 2696 + source = "registry+https://github.com/rust-lang/crates.io-index" 2697 + checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" 2698 + dependencies = [ 2699 + "memchr", 2700 + ] 2701 + 2702 + [[package]] 2703 + name = "quote" 2704 + version = "1.0.47" 2705 + source = "registry+https://github.com/rust-lang/crates.io-index" 2706 + checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" 2707 + dependencies = [ 2708 + "proc-macro2", 2709 + ] 2710 + 2711 + [[package]] 2712 + name = "r-efi" 2713 + version = "6.0.0" 2714 + source = "registry+https://github.com/rust-lang/crates.io-index" 2715 + checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" 2716 + 2717 + [[package]] 2718 + name = "rand" 2719 + version = "0.8.7" 2720 + source = "registry+https://github.com/rust-lang/crates.io-index" 2721 + checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" 2722 + dependencies = [ 2723 + "libc", 2724 + "rand_chacha", 2725 + "rand_core 0.6.4", 2726 + ] 2727 + 2728 + [[package]] 2729 + name = "rand" 2730 + version = "0.10.2" 2731 + source = "registry+https://github.com/rust-lang/crates.io-index" 2732 + checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" 2733 + dependencies = [ 2734 + "chacha20", 2735 + "getrandom 0.4.3", 2736 + "rand_core 0.10.1", 2737 + ] 2738 + 2739 + [[package]] 2740 + name = "rand_chacha" 2741 + version = "0.3.1" 2742 + source = "registry+https://github.com/rust-lang/crates.io-index" 2743 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 2744 + dependencies = [ 2745 + "ppv-lite86", 2746 + "rand_core 0.6.4", 2747 + ] 2748 + 2749 + [[package]] 2750 + name = "rand_core" 2751 + version = "0.6.4" 2752 + source = "registry+https://github.com/rust-lang/crates.io-index" 2753 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 2754 + dependencies = [ 2755 + "getrandom 0.2.17", 2756 + ] 2757 + 2758 + [[package]] 2759 + name = "rand_core" 2760 + version = "0.10.1" 2761 + source = "registry+https://github.com/rust-lang/crates.io-index" 2762 + checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" 2763 + 2764 + [[package]] 2765 + name = "rand_pcg" 2766 + version = "0.10.2" 2767 + source = "registry+https://github.com/rust-lang/crates.io-index" 2768 + checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" 2769 + dependencies = [ 2770 + "rand_core 0.10.1", 2771 + ] 2772 + 2773 + [[package]] 2774 + name = "rcgen" 2775 + version = "0.13.2" 2776 + source = "registry+https://github.com/rust-lang/crates.io-index" 2777 + checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" 2778 + dependencies = [ 2779 + "pem", 2780 + "ring", 2781 + "rustls-pki-types", 2782 + "time", 2783 + "yasna", 2784 + ] 2785 + 2786 + [[package]] 2787 + name = "redox_syscall" 2788 + version = "0.5.18" 2789 + source = "registry+https://github.com/rust-lang/crates.io-index" 2790 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 2791 + dependencies = [ 2792 + "bitflags", 2793 + ] 2794 + 2795 + [[package]] 2796 + name = "regex-automata" 2797 + version = "0.4.16" 2798 + source = "registry+https://github.com/rust-lang/crates.io-index" 2799 + checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" 2800 + dependencies = [ 2801 + "aho-corasick", 2802 + "memchr", 2803 + "regex-syntax", 2804 + ] 2805 + 2806 + [[package]] 2807 + name = "regex-syntax" 2808 + version = "0.8.11" 2809 + source = "registry+https://github.com/rust-lang/crates.io-index" 2810 + checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" 2811 + 2812 + [[package]] 2813 + name = "reqwest" 2814 + version = "0.13.4" 2815 + source = "registry+https://github.com/rust-lang/crates.io-index" 2816 + checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" 2817 + dependencies = [ 2818 + "base64", 2819 + "bytes", 2820 + "futures-core", 2821 + "futures-util", 2822 + "http", 2823 + "http-body", 2824 + "http-body-util", 2825 + "hyper", 2826 + "hyper-rustls", 2827 + "hyper-util", 2828 + "js-sys", 2829 + "log", 2830 + "percent-encoding", 2831 + "pin-project-lite", 2832 + "rustls", 2833 + "rustls-pki-types", 2834 + "rustls-platform-verifier", 2835 + "sync_wrapper", 2836 + "tokio", 2837 + "tokio-rustls", 2838 + "tokio-util", 2839 + "tower", 2840 + "tower-http", 2841 + "tower-service", 2842 + "url", 2843 + "wasm-bindgen", 2844 + "wasm-bindgen-futures", 2845 + "wasm-streams", 2846 + "web-sys", 2847 + ] 2848 + 2849 + [[package]] 2850 + name = "resolv-conf" 2851 + version = "0.7.6" 2852 + source = "registry+https://github.com/rust-lang/crates.io-index" 2853 + checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" 2854 + 2855 + [[package]] 2856 + name = "ring" 2857 + version = "0.17.14" 2858 + source = "registry+https://github.com/rust-lang/crates.io-index" 2859 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 2860 + dependencies = [ 2861 + "cc", 2862 + "cfg-if", 2863 + "getrandom 0.2.17", 2864 + "libc", 2865 + "untrusted", 2866 + "windows-sys 0.52.0", 2867 + ] 2868 + 2869 + [[package]] 2870 + name = "rle-decode-fast" 2871 + version = "1.0.3" 2872 + source = "registry+https://github.com/rust-lang/crates.io-index" 2873 + checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" 2874 + 2875 + [[package]] 2876 + name = "rustc-hash" 2877 + version = "2.1.3" 2878 + source = "registry+https://github.com/rust-lang/crates.io-index" 2879 + checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" 2880 + 2881 + [[package]] 2882 + name = "rustc_version" 2883 + version = "0.4.1" 2884 + source = "registry+https://github.com/rust-lang/crates.io-index" 2885 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 2886 + dependencies = [ 2887 + "semver", 2888 + ] 2889 + 2890 + [[package]] 2891 + name = "rustix" 2892 + version = "1.1.4" 2893 + source = "registry+https://github.com/rust-lang/crates.io-index" 2894 + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 2895 + dependencies = [ 2896 + "bitflags", 2897 + "errno", 2898 + "libc", 2899 + "linux-raw-sys", 2900 + "windows-sys 0.61.2", 2901 + ] 2902 + 2903 + [[package]] 2904 + name = "rustls" 2905 + version = "0.23.42" 2906 + source = "registry+https://github.com/rust-lang/crates.io-index" 2907 + checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" 2908 + dependencies = [ 2909 + "aws-lc-rs", 2910 + "log", 2911 + "once_cell", 2912 + "ring", 2913 + "rustls-pki-types", 2914 + "rustls-webpki", 2915 + "subtle", 2916 + "zeroize", 2917 + ] 2918 + 2919 + [[package]] 2920 + name = "rustls-native-certs" 2921 + version = "0.8.4" 2922 + source = "registry+https://github.com/rust-lang/crates.io-index" 2923 + checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" 2924 + dependencies = [ 2925 + "openssl-probe", 2926 + "rustls-pki-types", 2927 + "schannel", 2928 + "security-framework", 2929 + ] 2930 + 2931 + [[package]] 2932 + name = "rustls-pemfile" 2933 + version = "2.2.0" 2934 + source = "registry+https://github.com/rust-lang/crates.io-index" 2935 + checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 2936 + dependencies = [ 2937 + "rustls-pki-types", 2938 + ] 2939 + 2940 + [[package]] 2941 + name = "rustls-pki-types" 2942 + version = "1.15.1" 2943 + source = "registry+https://github.com/rust-lang/crates.io-index" 2944 + checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" 2945 + dependencies = [ 2946 + "web-time", 2947 + "zeroize", 2948 + ] 2949 + 2950 + [[package]] 2951 + name = "rustls-platform-verifier" 2952 + version = "0.7.0" 2953 + source = "registry+https://github.com/rust-lang/crates.io-index" 2954 + checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" 2955 + dependencies = [ 2956 + "core-foundation 0.10.1", 2957 + "core-foundation-sys", 2958 + "jni 0.22.4", 2959 + "log", 2960 + "once_cell", 2961 + "rustls", 2962 + "rustls-native-certs", 2963 + "rustls-platform-verifier-android", 2964 + "rustls-webpki", 2965 + "security-framework", 2966 + "security-framework-sys", 2967 + "webpki-root-certs", 2968 + "windows-sys 0.61.2", 2969 + ] 2970 + 2971 + [[package]] 2972 + name = "rustls-platform-verifier-android" 2973 + version = "0.1.1" 2974 + source = "registry+https://github.com/rust-lang/crates.io-index" 2975 + checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" 2976 + 2977 + [[package]] 2978 + name = "rustls-webpki" 2979 + version = "0.103.13" 2980 + source = "registry+https://github.com/rust-lang/crates.io-index" 2981 + checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" 2982 + dependencies = [ 2983 + "aws-lc-rs", 2984 + "ring", 2985 + "rustls-pki-types", 2986 + "untrusted", 2987 + ] 2988 + 2989 + [[package]] 2990 + name = "rustversion" 2991 + version = "1.0.23" 2992 + source = "registry+https://github.com/rust-lang/crates.io-index" 2993 + checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" 2994 + 2995 + [[package]] 2996 + name = "ryu" 2997 + version = "1.0.23" 2998 + source = "registry+https://github.com/rust-lang/crates.io-index" 2999 + checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 3000 + 3001 + [[package]] 3002 + name = "same-file" 3003 + version = "1.0.6" 3004 + source = "registry+https://github.com/rust-lang/crates.io-index" 3005 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 3006 + dependencies = [ 3007 + "winapi-util", 3008 + ] 3009 + 3010 + [[package]] 3011 + name = "schannel" 3012 + version = "0.1.29" 3013 + source = "registry+https://github.com/rust-lang/crates.io-index" 3014 + checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" 3015 + dependencies = [ 3016 + "windows-sys 0.61.2", 3017 + ] 3018 + 3019 + [[package]] 3020 + name = "scoped-tls" 3021 + version = "1.0.1" 3022 + source = "registry+https://github.com/rust-lang/crates.io-index" 3023 + checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 3024 + 3025 + [[package]] 3026 + name = "scopeguard" 3027 + version = "1.2.0" 3028 + source = "registry+https://github.com/rust-lang/crates.io-index" 3029 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 3030 + 3031 + [[package]] 3032 + name = "security-framework" 3033 + version = "3.7.0" 3034 + source = "registry+https://github.com/rust-lang/crates.io-index" 3035 + checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" 3036 + dependencies = [ 3037 + "bitflags", 3038 + "core-foundation 0.10.1", 3039 + "core-foundation-sys", 3040 + "libc", 3041 + "security-framework-sys", 3042 + ] 3043 + 3044 + [[package]] 3045 + name = "security-framework-sys" 3046 + version = "2.17.0" 3047 + source = "registry+https://github.com/rust-lang/crates.io-index" 3048 + checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" 3049 + dependencies = [ 3050 + "core-foundation-sys", 3051 + "libc", 3052 + ] 3053 + 3054 + [[package]] 3055 + name = "seize" 3056 + version = "0.5.1" 3057 + source = "registry+https://github.com/rust-lang/crates.io-index" 3058 + checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" 3059 + dependencies = [ 3060 + "libc", 3061 + "windows-sys 0.61.2", 3062 + ] 3063 + 3064 + [[package]] 3065 + name = "semver" 3066 + version = "1.0.28" 3067 + source = "registry+https://github.com/rust-lang/crates.io-index" 3068 + checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" 3069 + 3070 + [[package]] 3071 + name = "send_wrapper" 3072 + version = "0.6.0" 3073 + source = "registry+https://github.com/rust-lang/crates.io-index" 3074 + checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" 3075 + 3076 + [[package]] 3077 + name = "serde" 3078 + version = "1.0.229" 3079 + source = "registry+https://github.com/rust-lang/crates.io-index" 3080 + checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" 3081 + dependencies = [ 3082 + "serde_core", 3083 + "serde_derive", 3084 + ] 3085 + 3086 + [[package]] 3087 + name = "serde_bytes" 3088 + version = "0.11.19" 3089 + source = "registry+https://github.com/rust-lang/crates.io-index" 3090 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 3091 + dependencies = [ 3092 + "serde", 3093 + "serde_core", 3094 + ] 3095 + 3096 + [[package]] 3097 + name = "serde_core" 3098 + version = "1.0.229" 3099 + source = "registry+https://github.com/rust-lang/crates.io-index" 3100 + checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" 3101 + dependencies = [ 3102 + "serde_derive", 3103 + ] 3104 + 3105 + [[package]] 3106 + name = "serde_derive" 3107 + version = "1.0.229" 3108 + source = "registry+https://github.com/rust-lang/crates.io-index" 3109 + checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" 3110 + dependencies = [ 3111 + "proc-macro2", 3112 + "quote", 3113 + "syn 3.0.3", 3114 + ] 3115 + 3116 + [[package]] 3117 + name = "serdect" 3118 + version = "0.4.3" 3119 + source = "registry+https://github.com/rust-lang/crates.io-index" 3120 + checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" 3121 + dependencies = [ 3122 + "base16ct", 3123 + "serde", 3124 + ] 3125 + 3126 + [[package]] 3127 + name = "sha1_smol" 3128 + version = "1.0.1" 3129 + source = "registry+https://github.com/rust-lang/crates.io-index" 3130 + checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" 3131 + 3132 + [[package]] 3133 + name = "sha2" 3134 + version = "0.11.0" 3135 + source = "registry+https://github.com/rust-lang/crates.io-index" 3136 + checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" 3137 + dependencies = [ 3138 + "cfg-if", 3139 + "cpufeatures 0.3.0", 3140 + "digest", 3141 + ] 3142 + 3143 + [[package]] 3144 + name = "sharded-slab" 3145 + version = "0.1.7" 3146 + source = "registry+https://github.com/rust-lang/crates.io-index" 3147 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 3148 + dependencies = [ 3149 + "lazy_static", 3150 + ] 3151 + 3152 + [[package]] 3153 + name = "shlex" 3154 + version = "2.0.1" 3155 + source = "registry+https://github.com/rust-lang/crates.io-index" 3156 + checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" 3157 + 3158 + [[package]] 3159 + name = "signal-hook-registry" 3160 + version = "1.4.8" 3161 + source = "registry+https://github.com/rust-lang/crates.io-index" 3162 + checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 3163 + dependencies = [ 3164 + "errno", 3165 + "libc", 3166 + ] 3167 + 3168 + [[package]] 3169 + name = "signature" 3170 + version = "3.0.0" 3171 + source = "registry+https://github.com/rust-lang/crates.io-index" 3172 + checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" 3173 + dependencies = [ 3174 + "rand_core 0.10.1", 3175 + ] 3176 + 3177 + [[package]] 3178 + name = "simd_cesu8" 3179 + version = "1.2.0" 3180 + source = "registry+https://github.com/rust-lang/crates.io-index" 3181 + checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" 3182 + dependencies = [ 3183 + "rustc_version", 3184 + "simdutf8", 3185 + ] 3186 + 3187 + [[package]] 3188 + name = "simdutf8" 3189 + version = "0.1.5" 3190 + source = "registry+https://github.com/rust-lang/crates.io-index" 3191 + checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 3192 + 3193 + [[package]] 3194 + name = "simple-dns" 3195 + version = "0.11.3" 3196 + source = "registry+https://github.com/rust-lang/crates.io-index" 3197 + checksum = "7a75cbde1bf934313596a004973e462f9a82caa814dcf1a5f507bdf51597eeb4" 3198 + dependencies = [ 3199 + "bitflags", 3200 + ] 3201 + 3202 + [[package]] 3203 + name = "slab" 3204 + version = "0.4.12" 3205 + source = "registry+https://github.com/rust-lang/crates.io-index" 3206 + checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" 3207 + 3208 + [[package]] 3209 + name = "smallvec" 3210 + version = "1.15.2" 3211 + source = "registry+https://github.com/rust-lang/crates.io-index" 3212 + checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" 3213 + 3214 + [[package]] 3215 + name = "socket2" 3216 + version = "0.6.5" 3217 + source = "registry+https://github.com/rust-lang/crates.io-index" 3218 + checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" 3219 + dependencies = [ 3220 + "libc", 3221 + "windows-sys 0.61.2", 3222 + ] 3223 + 3224 + [[package]] 3225 + name = "sorted-index-buffer" 3226 + version = "0.2.1" 3227 + source = "registry+https://github.com/rust-lang/crates.io-index" 3228 + checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" 3229 + 3230 + [[package]] 3231 + name = "spez" 3232 + version = "0.1.2" 3233 + source = "registry+https://github.com/rust-lang/crates.io-index" 3234 + checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" 3235 + dependencies = [ 3236 + "proc-macro2", 3237 + "quote", 3238 + "syn 2.0.119", 3239 + ] 3240 + 3241 + [[package]] 3242 + name = "spin" 3243 + version = "0.10.1" 3244 + source = "registry+https://github.com/rust-lang/crates.io-index" 3245 + checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" 3246 + 3247 + [[package]] 3248 + name = "spki" 3249 + version = "0.8.0" 3250 + source = "registry+https://github.com/rust-lang/crates.io-index" 3251 + checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" 3252 + dependencies = [ 3253 + "base64ct", 3254 + "der", 3255 + ] 3256 + 3257 + [[package]] 3258 + name = "stable_deref_trait" 3259 + version = "1.2.1" 3260 + source = "registry+https://github.com/rust-lang/crates.io-index" 3261 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 3262 + 3263 + [[package]] 3264 + name = "strsim" 3265 + version = "0.11.1" 3266 + source = "registry+https://github.com/rust-lang/crates.io-index" 3267 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 3268 + 3269 + [[package]] 3270 + name = "strum" 3271 + version = "0.28.0" 3272 + source = "registry+https://github.com/rust-lang/crates.io-index" 3273 + checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" 3274 + dependencies = [ 3275 + "strum_macros", 3276 + ] 3277 + 3278 + [[package]] 3279 + name = "strum_macros" 3280 + version = "0.28.0" 3281 + source = "registry+https://github.com/rust-lang/crates.io-index" 3282 + checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" 3283 + dependencies = [ 3284 + "heck", 3285 + "proc-macro2", 3286 + "quote", 3287 + "syn 2.0.119", 3288 + ] 3289 + 3290 + [[package]] 3291 + name = "subtle" 3292 + version = "2.6.1" 3293 + source = "registry+https://github.com/rust-lang/crates.io-index" 3294 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 3295 + 3296 + [[package]] 3297 + name = "syn" 3298 + version = "2.0.119" 3299 + source = "registry+https://github.com/rust-lang/crates.io-index" 3300 + checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" 3301 + dependencies = [ 3302 + "proc-macro2", 3303 + "quote", 3304 + "unicode-ident", 3305 + ] 3306 + 3307 + [[package]] 3308 + name = "syn" 3309 + version = "3.0.3" 3310 + source = "registry+https://github.com/rust-lang/crates.io-index" 3311 + checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" 3312 + dependencies = [ 3313 + "proc-macro2", 3314 + "quote", 3315 + "unicode-ident", 3316 + ] 3317 + 3318 + [[package]] 3319 + name = "sync_wrapper" 3320 + version = "1.0.2" 3321 + source = "registry+https://github.com/rust-lang/crates.io-index" 3322 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3323 + dependencies = [ 3324 + "futures-core", 3325 + ] 3326 + 3327 + [[package]] 3328 + name = "synstructure" 3329 + version = "0.13.2" 3330 + source = "registry+https://github.com/rust-lang/crates.io-index" 3331 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 3332 + dependencies = [ 3333 + "proc-macro2", 3334 + "quote", 3335 + "syn 2.0.119", 3336 + ] 3337 + 3338 + [[package]] 3339 + name = "system-configuration" 3340 + version = "0.7.0" 3341 + source = "registry+https://github.com/rust-lang/crates.io-index" 3342 + checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" 3343 + dependencies = [ 3344 + "bitflags", 3345 + "core-foundation 0.9.4", 3346 + "system-configuration-sys", 3347 + ] 3348 + 3349 + [[package]] 3350 + name = "system-configuration-sys" 3351 + version = "0.6.0" 3352 + source = "registry+https://github.com/rust-lang/crates.io-index" 3353 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 3354 + dependencies = [ 3355 + "core-foundation-sys", 3356 + "libc", 3357 + ] 3358 + 3359 + [[package]] 3360 + name = "tagptr" 3361 + version = "0.2.0" 3362 + source = "registry+https://github.com/rust-lang/crates.io-index" 3363 + checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 3364 + 3365 + [[package]] 3366 + name = "tempfile" 3367 + version = "3.27.0" 3368 + source = "registry+https://github.com/rust-lang/crates.io-index" 3369 + checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" 3370 + dependencies = [ 3371 + "fastrand", 3372 + "getrandom 0.4.3", 3373 + "once_cell", 3374 + "rustix", 3375 + "windows-sys 0.61.2", 3376 + ] 3377 + 3378 + [[package]] 3379 + name = "thiserror" 3380 + version = "1.0.69" 3381 + source = "registry+https://github.com/rust-lang/crates.io-index" 3382 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 3383 + dependencies = [ 3384 + "thiserror-impl 1.0.69", 3385 + ] 3386 + 3387 + [[package]] 3388 + name = "thiserror" 3389 + version = "2.0.19" 3390 + source = "registry+https://github.com/rust-lang/crates.io-index" 3391 + checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" 3392 + dependencies = [ 3393 + "thiserror-impl 2.0.19", 3394 + ] 3395 + 3396 + [[package]] 3397 + name = "thiserror-impl" 3398 + version = "1.0.69" 3399 + source = "registry+https://github.com/rust-lang/crates.io-index" 3400 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 3401 + dependencies = [ 3402 + "proc-macro2", 3403 + "quote", 3404 + "syn 2.0.119", 3405 + ] 3406 + 3407 + [[package]] 3408 + name = "thiserror-impl" 3409 + version = "2.0.19" 3410 + source = "registry+https://github.com/rust-lang/crates.io-index" 3411 + checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" 3412 + dependencies = [ 3413 + "proc-macro2", 3414 + "quote", 3415 + "syn 3.0.3", 3416 + ] 3417 + 3418 + [[package]] 3419 + name = "thread_local" 3420 + version = "1.1.10" 3421 + source = "registry+https://github.com/rust-lang/crates.io-index" 3422 + checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" 3423 + dependencies = [ 3424 + "cfg-if", 3425 + ] 3426 + 3427 + [[package]] 3428 + name = "time" 3429 + version = "0.3.54" 3430 + source = "registry+https://github.com/rust-lang/crates.io-index" 3431 + checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" 3432 + dependencies = [ 3433 + "deranged", 3434 + "js-sys", 3435 + "num-conv", 3436 + "powerfmt", 3437 + "serde_core", 3438 + "time-core", 3439 + "time-macros", 3440 + ] 3441 + 3442 + [[package]] 3443 + name = "time-core" 3444 + version = "0.1.9" 3445 + source = "registry+https://github.com/rust-lang/crates.io-index" 3446 + checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" 3447 + 3448 + [[package]] 3449 + name = "time-macros" 3450 + version = "0.2.32" 3451 + source = "registry+https://github.com/rust-lang/crates.io-index" 3452 + checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" 3453 + dependencies = [ 3454 + "num-conv", 3455 + "time-core", 3456 + ] 3457 + 3458 + [[package]] 3459 + name = "tinystr" 3460 + version = "0.8.3" 3461 + source = "registry+https://github.com/rust-lang/crates.io-index" 3462 + checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" 3463 + dependencies = [ 3464 + "displaydoc", 3465 + "zerovec", 3466 + ] 3467 + 3468 + [[package]] 3469 + name = "tinyvec" 3470 + version = "1.12.0" 3471 + source = "registry+https://github.com/rust-lang/crates.io-index" 3472 + checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" 3473 + dependencies = [ 3474 + "tinyvec_macros", 3475 + ] 3476 + 3477 + [[package]] 3478 + name = "tinyvec_macros" 3479 + version = "0.1.1" 3480 + source = "registry+https://github.com/rust-lang/crates.io-index" 3481 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 3482 + 3483 + [[package]] 3484 + name = "tokio" 3485 + version = "1.53.1" 3486 + source = "registry+https://github.com/rust-lang/crates.io-index" 3487 + checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" 3488 + dependencies = [ 3489 + "bytes", 3490 + "libc", 3491 + "mio", 3492 + "pin-project-lite", 3493 + "signal-hook-registry", 3494 + "socket2", 3495 + "tokio-macros", 3496 + "windows-sys 0.61.2", 3497 + ] 3498 + 3499 + [[package]] 3500 + name = "tokio-macros" 3501 + version = "2.7.1" 3502 + source = "registry+https://github.com/rust-lang/crates.io-index" 3503 + checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" 3504 + dependencies = [ 3505 + "proc-macro2", 3506 + "quote", 3507 + "syn 2.0.119", 3508 + ] 3509 + 3510 + [[package]] 3511 + name = "tokio-rustls" 3512 + version = "0.26.4" 3513 + source = "registry+https://github.com/rust-lang/crates.io-index" 3514 + checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 3515 + dependencies = [ 3516 + "rustls", 3517 + "tokio", 3518 + ] 3519 + 3520 + [[package]] 3521 + name = "tokio-stream" 3522 + version = "0.1.19" 3523 + source = "registry+https://github.com/rust-lang/crates.io-index" 3524 + checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" 3525 + dependencies = [ 3526 + "futures-core", 3527 + "pin-project-lite", 3528 + "tokio", 3529 + "tokio-util", 3530 + ] 3531 + 3532 + [[package]] 3533 + name = "tokio-util" 3534 + version = "0.7.19" 3535 + source = "registry+https://github.com/rust-lang/crates.io-index" 3536 + checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" 3537 + dependencies = [ 3538 + "bytes", 3539 + "futures-core", 3540 + "futures-io", 3541 + "futures-sink", 3542 + "futures-util", 3543 + "libc", 3544 + "pin-project-lite", 3545 + "tokio", 3546 + ] 3547 + 3548 + [[package]] 3549 + name = "tokio-websockets" 3550 + version = "0.13.3" 3551 + source = "registry+https://github.com/rust-lang/crates.io-index" 3552 + checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" 3553 + dependencies = [ 3554 + "base64", 3555 + "bytes", 3556 + "futures-core", 3557 + "futures-sink", 3558 + "getrandom 0.4.3", 3559 + "http", 3560 + "httparse", 3561 + "rand 0.10.2", 3562 + "ring", 3563 + "rustls-pki-types", 3564 + "sha1_smol", 3565 + "simdutf8", 3566 + "tokio", 3567 + "tokio-rustls", 3568 + "tokio-util", 3569 + ] 3570 + 3571 + [[package]] 3572 + name = "toml_datetime" 3573 + version = "1.1.1+spec-1.1.0" 3574 + source = "registry+https://github.com/rust-lang/crates.io-index" 3575 + checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" 3576 + dependencies = [ 3577 + "serde_core", 3578 + ] 3579 + 3580 + [[package]] 3581 + name = "toml_edit" 3582 + version = "0.25.13+spec-1.1.0" 3583 + source = "registry+https://github.com/rust-lang/crates.io-index" 3584 + checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" 3585 + dependencies = [ 3586 + "indexmap", 3587 + "toml_datetime", 3588 + "toml_parser", 3589 + "winnow", 3590 + ] 3591 + 3592 + [[package]] 3593 + name = "toml_parser" 3594 + version = "1.1.2+spec-1.1.0" 3595 + source = "registry+https://github.com/rust-lang/crates.io-index" 3596 + checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" 3597 + dependencies = [ 3598 + "winnow", 3599 + ] 3600 + 3601 + [[package]] 3602 + name = "tower" 3603 + version = "0.5.3" 3604 + source = "registry+https://github.com/rust-lang/crates.io-index" 3605 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 3606 + dependencies = [ 3607 + "futures-core", 3608 + "futures-util", 3609 + "pin-project-lite", 3610 + "sync_wrapper", 3611 + "tokio", 3612 + "tower-layer", 3613 + "tower-service", 3614 + ] 3615 + 3616 + [[package]] 3617 + name = "tower-http" 3618 + version = "0.6.11" 3619 + source = "registry+https://github.com/rust-lang/crates.io-index" 3620 + checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" 3621 + dependencies = [ 3622 + "bitflags", 3623 + "bytes", 3624 + "futures-util", 3625 + "http", 3626 + "http-body", 3627 + "pin-project-lite", 3628 + "tower", 3629 + "tower-layer", 3630 + "tower-service", 3631 + "url", 3632 + ] 3633 + 3634 + [[package]] 3635 + name = "tower-layer" 3636 + version = "0.3.3" 3637 + source = "registry+https://github.com/rust-lang/crates.io-index" 3638 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 3639 + 3640 + [[package]] 3641 + name = "tower-service" 3642 + version = "0.3.3" 3643 + source = "registry+https://github.com/rust-lang/crates.io-index" 3644 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 3645 + 3646 + [[package]] 3647 + name = "tracing" 3648 + version = "0.1.44" 3649 + source = "registry+https://github.com/rust-lang/crates.io-index" 3650 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 3651 + dependencies = [ 3652 + "log", 3653 + "pin-project-lite", 3654 + "tracing-attributes", 3655 + "tracing-core", 3656 + ] 3657 + 3658 + [[package]] 3659 + name = "tracing-attributes" 3660 + version = "0.1.31" 3661 + source = "registry+https://github.com/rust-lang/crates.io-index" 3662 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 3663 + dependencies = [ 3664 + "proc-macro2", 3665 + "quote", 3666 + "syn 2.0.119", 3667 + ] 3668 + 3669 + [[package]] 3670 + name = "tracing-core" 3671 + version = "0.1.36" 3672 + source = "registry+https://github.com/rust-lang/crates.io-index" 3673 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 3674 + dependencies = [ 3675 + "once_cell", 3676 + "valuable", 3677 + ] 3678 + 3679 + [[package]] 3680 + name = "tracing-log" 3681 + version = "0.2.0" 3682 + source = "registry+https://github.com/rust-lang/crates.io-index" 3683 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 3684 + dependencies = [ 3685 + "log", 3686 + "once_cell", 3687 + "tracing-core", 3688 + ] 3689 + 3690 + [[package]] 3691 + name = "tracing-subscriber" 3692 + version = "0.3.23" 3693 + source = "registry+https://github.com/rust-lang/crates.io-index" 3694 + checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" 3695 + dependencies = [ 3696 + "matchers", 3697 + "nu-ansi-term", 3698 + "once_cell", 3699 + "regex-automata", 3700 + "sharded-slab", 3701 + "smallvec", 3702 + "thread_local", 3703 + "tracing", 3704 + "tracing-core", 3705 + "tracing-log", 3706 + ] 3707 + 3708 + [[package]] 3709 + name = "try-lock" 3710 + version = "0.2.5" 3711 + source = "registry+https://github.com/rust-lang/crates.io-index" 3712 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3713 + 3714 + [[package]] 3715 + name = "typenum" 3716 + version = "1.20.1" 3717 + source = "registry+https://github.com/rust-lang/crates.io-index" 3718 + checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" 3719 + 3720 + [[package]] 3721 + name = "unicode-ident" 3722 + version = "1.0.24" 3723 + source = "registry+https://github.com/rust-lang/crates.io-index" 3724 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 3725 + 3726 + [[package]] 3727 + name = "unicode-segmentation" 3728 + version = "1.13.3" 3729 + source = "registry+https://github.com/rust-lang/crates.io-index" 3730 + checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" 3731 + 3732 + [[package]] 3733 + name = "unicode-xid" 3734 + version = "0.2.6" 3735 + source = "registry+https://github.com/rust-lang/crates.io-index" 3736 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 3737 + 3738 + [[package]] 3739 + name = "universal-hash" 3740 + version = "0.5.1" 3741 + source = "registry+https://github.com/rust-lang/crates.io-index" 3742 + checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 3743 + dependencies = [ 3744 + "crypto-common 0.1.7", 3745 + "subtle", 3746 + ] 3747 + 3748 + [[package]] 3749 + name = "untrusted" 3750 + version = "0.9.0" 3751 + source = "registry+https://github.com/rust-lang/crates.io-index" 3752 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 3753 + 3754 + [[package]] 3755 + name = "url" 3756 + version = "2.5.8" 3757 + source = "registry+https://github.com/rust-lang/crates.io-index" 3758 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 3759 + dependencies = [ 3760 + "form_urlencoded", 3761 + "idna", 3762 + "percent-encoding", 3763 + "serde", 3764 + "serde_derive", 3765 + ] 3766 + 3767 + [[package]] 3768 + name = "utf8_iter" 3769 + version = "1.0.4" 3770 + source = "registry+https://github.com/rust-lang/crates.io-index" 3771 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 3772 + 3773 + [[package]] 3774 + name = "utf8parse" 3775 + version = "0.2.2" 3776 + source = "registry+https://github.com/rust-lang/crates.io-index" 3777 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 3778 + 3779 + [[package]] 3780 + name = "uuid" 3781 + version = "1.24.0" 3782 + source = "registry+https://github.com/rust-lang/crates.io-index" 3783 + checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" 3784 + dependencies = [ 3785 + "getrandom 0.4.3", 3786 + "js-sys", 3787 + "wasm-bindgen", 3788 + ] 3789 + 3790 + [[package]] 3791 + name = "valuable" 3792 + version = "0.1.1" 3793 + source = "registry+https://github.com/rust-lang/crates.io-index" 3794 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 3795 + 3796 + [[package]] 3797 + name = "version_check" 3798 + version = "0.9.5" 3799 + source = "registry+https://github.com/rust-lang/crates.io-index" 3800 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 3801 + 3802 + [[package]] 3803 + name = "walkdir" 3804 + version = "2.5.0" 3805 + source = "registry+https://github.com/rust-lang/crates.io-index" 3806 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 3807 + dependencies = [ 3808 + "same-file", 3809 + "winapi-util", 3810 + ] 3811 + 3812 + [[package]] 3813 + name = "want" 3814 + version = "0.3.1" 3815 + source = "registry+https://github.com/rust-lang/crates.io-index" 3816 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 3817 + dependencies = [ 3818 + "try-lock", 3819 + ] 3820 + 3821 + [[package]] 3822 + name = "wasi" 3823 + version = "0.11.1+wasi-snapshot-preview1" 3824 + source = "registry+https://github.com/rust-lang/crates.io-index" 3825 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 3826 + 3827 + [[package]] 3828 + name = "wasm-bindgen" 3829 + version = "0.2.126" 3830 + source = "registry+https://github.com/rust-lang/crates.io-index" 3831 + checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" 3832 + dependencies = [ 3833 + "cfg-if", 3834 + "once_cell", 3835 + "rustversion", 3836 + "wasm-bindgen-macro", 3837 + "wasm-bindgen-shared", 3838 + ] 3839 + 3840 + [[package]] 3841 + name = "wasm-bindgen-futures" 3842 + version = "0.4.76" 3843 + source = "registry+https://github.com/rust-lang/crates.io-index" 3844 + checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" 3845 + dependencies = [ 3846 + "js-sys", 3847 + "wasm-bindgen", 3848 + ] 3849 + 3850 + [[package]] 3851 + name = "wasm-bindgen-macro" 3852 + version = "0.2.126" 3853 + source = "registry+https://github.com/rust-lang/crates.io-index" 3854 + checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" 3855 + dependencies = [ 3856 + "quote", 3857 + "wasm-bindgen-macro-support", 3858 + ] 3859 + 3860 + [[package]] 3861 + name = "wasm-bindgen-macro-support" 3862 + version = "0.2.126" 3863 + source = "registry+https://github.com/rust-lang/crates.io-index" 3864 + checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" 3865 + dependencies = [ 3866 + "bumpalo", 3867 + "proc-macro2", 3868 + "quote", 3869 + "syn 2.0.119", 3870 + "wasm-bindgen-shared", 3871 + ] 3872 + 3873 + [[package]] 3874 + name = "wasm-bindgen-shared" 3875 + version = "0.2.126" 3876 + source = "registry+https://github.com/rust-lang/crates.io-index" 3877 + checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" 3878 + dependencies = [ 3879 + "unicode-ident", 3880 + ] 3881 + 3882 + [[package]] 3883 + name = "wasm-streams" 3884 + version = "0.5.0" 3885 + source = "registry+https://github.com/rust-lang/crates.io-index" 3886 + checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" 3887 + dependencies = [ 3888 + "futures-util", 3889 + "js-sys", 3890 + "wasm-bindgen", 3891 + "wasm-bindgen-futures", 3892 + "web-sys", 3893 + ] 3894 + 3895 + [[package]] 3896 + name = "web-sys" 3897 + version = "0.3.103" 3898 + source = "registry+https://github.com/rust-lang/crates.io-index" 3899 + checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" 3900 + dependencies = [ 3901 + "js-sys", 3902 + "wasm-bindgen", 3903 + ] 3904 + 3905 + [[package]] 3906 + name = "web-time" 3907 + version = "1.1.0" 3908 + source = "registry+https://github.com/rust-lang/crates.io-index" 3909 + checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 3910 + dependencies = [ 3911 + "js-sys", 3912 + "wasm-bindgen", 3913 + ] 3914 + 3915 + [[package]] 3916 + name = "webpki-root-certs" 3917 + version = "1.0.9" 3918 + source = "registry+https://github.com/rust-lang/crates.io-index" 3919 + checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" 3920 + dependencies = [ 3921 + "rustls-pki-types", 3922 + ] 3923 + 3924 + [[package]] 3925 + name = "webpki-roots" 3926 + version = "0.26.11" 3927 + source = "registry+https://github.com/rust-lang/crates.io-index" 3928 + checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" 3929 + dependencies = [ 3930 + "webpki-roots 1.0.9", 3931 + ] 3932 + 3933 + [[package]] 3934 + name = "webpki-roots" 3935 + version = "1.0.9" 3936 + source = "registry+https://github.com/rust-lang/crates.io-index" 3937 + checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" 3938 + dependencies = [ 3939 + "rustls-pki-types", 3940 + ] 3941 + 3942 + [[package]] 3943 + name = "widestring" 3944 + version = "1.2.1" 3945 + source = "registry+https://github.com/rust-lang/crates.io-index" 3946 + checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 3947 + 3948 + [[package]] 3949 + name = "winapi" 3950 + version = "0.3.9" 3951 + source = "registry+https://github.com/rust-lang/crates.io-index" 3952 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 3953 + dependencies = [ 3954 + "winapi-i686-pc-windows-gnu", 3955 + "winapi-x86_64-pc-windows-gnu", 3956 + ] 3957 + 3958 + [[package]] 3959 + name = "winapi-i686-pc-windows-gnu" 3960 + version = "0.4.0" 3961 + source = "registry+https://github.com/rust-lang/crates.io-index" 3962 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 3963 + 3964 + [[package]] 3965 + name = "winapi-util" 3966 + version = "0.1.11" 3967 + source = "registry+https://github.com/rust-lang/crates.io-index" 3968 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 3969 + dependencies = [ 3970 + "windows-sys 0.61.2", 3971 + ] 3972 + 3973 + [[package]] 3974 + name = "winapi-x86_64-pc-windows-gnu" 3975 + version = "0.4.0" 3976 + source = "registry+https://github.com/rust-lang/crates.io-index" 3977 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 3978 + 3979 + [[package]] 3980 + name = "windows" 3981 + version = "0.62.2" 3982 + source = "registry+https://github.com/rust-lang/crates.io-index" 3983 + checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" 3984 + dependencies = [ 3985 + "windows-collections", 3986 + "windows-core", 3987 + "windows-future", 3988 + "windows-numerics", 3989 + ] 3990 + 3991 + [[package]] 3992 + name = "windows-collections" 3993 + version = "0.3.2" 3994 + source = "registry+https://github.com/rust-lang/crates.io-index" 3995 + checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" 3996 + dependencies = [ 3997 + "windows-core", 3998 + ] 3999 + 4000 + [[package]] 4001 + name = "windows-core" 4002 + version = "0.62.2" 4003 + source = "registry+https://github.com/rust-lang/crates.io-index" 4004 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 4005 + dependencies = [ 4006 + "windows-implement", 4007 + "windows-interface", 4008 + "windows-link", 4009 + "windows-result", 4010 + "windows-strings", 4011 + ] 4012 + 4013 + [[package]] 4014 + name = "windows-future" 4015 + version = "0.3.2" 4016 + source = "registry+https://github.com/rust-lang/crates.io-index" 4017 + checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" 4018 + dependencies = [ 4019 + "windows-core", 4020 + "windows-link", 4021 + "windows-threading", 4022 + ] 4023 + 4024 + [[package]] 4025 + name = "windows-implement" 4026 + version = "0.60.2" 4027 + source = "registry+https://github.com/rust-lang/crates.io-index" 4028 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 4029 + dependencies = [ 4030 + "proc-macro2", 4031 + "quote", 4032 + "syn 2.0.119", 4033 + ] 4034 + 4035 + [[package]] 4036 + name = "windows-interface" 4037 + version = "0.59.3" 4038 + source = "registry+https://github.com/rust-lang/crates.io-index" 4039 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 4040 + dependencies = [ 4041 + "proc-macro2", 4042 + "quote", 4043 + "syn 2.0.119", 4044 + ] 4045 + 4046 + [[package]] 4047 + name = "windows-link" 4048 + version = "0.2.1" 4049 + source = "registry+https://github.com/rust-lang/crates.io-index" 4050 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4051 + 4052 + [[package]] 4053 + name = "windows-numerics" 4054 + version = "0.3.1" 4055 + source = "registry+https://github.com/rust-lang/crates.io-index" 4056 + checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" 4057 + dependencies = [ 4058 + "windows-core", 4059 + "windows-link", 4060 + ] 4061 + 4062 + [[package]] 4063 + name = "windows-registry" 4064 + version = "0.6.1" 4065 + source = "registry+https://github.com/rust-lang/crates.io-index" 4066 + checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 4067 + dependencies = [ 4068 + "windows-link", 4069 + "windows-result", 4070 + "windows-strings", 4071 + ] 4072 + 4073 + [[package]] 4074 + name = "windows-result" 4075 + version = "0.4.1" 4076 + source = "registry+https://github.com/rust-lang/crates.io-index" 4077 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 4078 + dependencies = [ 4079 + "windows-link", 4080 + ] 4081 + 4082 + [[package]] 4083 + name = "windows-strings" 4084 + version = "0.5.1" 4085 + source = "registry+https://github.com/rust-lang/crates.io-index" 4086 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 4087 + dependencies = [ 4088 + "windows-link", 4089 + ] 4090 + 4091 + [[package]] 4092 + name = "windows-sys" 4093 + version = "0.45.0" 4094 + source = "registry+https://github.com/rust-lang/crates.io-index" 4095 + checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 4096 + dependencies = [ 4097 + "windows-targets 0.42.2", 4098 + ] 4099 + 4100 + [[package]] 4101 + name = "windows-sys" 4102 + version = "0.52.0" 4103 + source = "registry+https://github.com/rust-lang/crates.io-index" 4104 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 4105 + dependencies = [ 4106 + "windows-targets 0.52.6", 4107 + ] 4108 + 4109 + [[package]] 4110 + name = "windows-sys" 4111 + version = "0.61.2" 4112 + source = "registry+https://github.com/rust-lang/crates.io-index" 4113 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 4114 + dependencies = [ 4115 + "windows-link", 4116 + ] 4117 + 4118 + [[package]] 4119 + name = "windows-targets" 4120 + version = "0.42.2" 4121 + source = "registry+https://github.com/rust-lang/crates.io-index" 4122 + checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 4123 + dependencies = [ 4124 + "windows_aarch64_gnullvm 0.42.2", 4125 + "windows_aarch64_msvc 0.42.2", 4126 + "windows_i686_gnu 0.42.2", 4127 + "windows_i686_msvc 0.42.2", 4128 + "windows_x86_64_gnu 0.42.2", 4129 + "windows_x86_64_gnullvm 0.42.2", 4130 + "windows_x86_64_msvc 0.42.2", 4131 + ] 4132 + 4133 + [[package]] 4134 + name = "windows-targets" 4135 + version = "0.52.6" 4136 + source = "registry+https://github.com/rust-lang/crates.io-index" 4137 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 4138 + dependencies = [ 4139 + "windows_aarch64_gnullvm 0.52.6", 4140 + "windows_aarch64_msvc 0.52.6", 4141 + "windows_i686_gnu 0.52.6", 4142 + "windows_i686_gnullvm", 4143 + "windows_i686_msvc 0.52.6", 4144 + "windows_x86_64_gnu 0.52.6", 4145 + "windows_x86_64_gnullvm 0.52.6", 4146 + "windows_x86_64_msvc 0.52.6", 4147 + ] 4148 + 4149 + [[package]] 4150 + name = "windows-threading" 4151 + version = "0.2.1" 4152 + source = "registry+https://github.com/rust-lang/crates.io-index" 4153 + checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" 4154 + dependencies = [ 4155 + "windows-link", 4156 + ] 4157 + 4158 + [[package]] 4159 + name = "windows_aarch64_gnullvm" 4160 + version = "0.42.2" 4161 + source = "registry+https://github.com/rust-lang/crates.io-index" 4162 + checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 4163 + 4164 + [[package]] 4165 + name = "windows_aarch64_gnullvm" 4166 + version = "0.52.6" 4167 + source = "registry+https://github.com/rust-lang/crates.io-index" 4168 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 4169 + 4170 + [[package]] 4171 + name = "windows_aarch64_msvc" 4172 + version = "0.42.2" 4173 + source = "registry+https://github.com/rust-lang/crates.io-index" 4174 + checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 4175 + 4176 + [[package]] 4177 + name = "windows_aarch64_msvc" 4178 + version = "0.52.6" 4179 + source = "registry+https://github.com/rust-lang/crates.io-index" 4180 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 4181 + 4182 + [[package]] 4183 + name = "windows_i686_gnu" 4184 + version = "0.42.2" 4185 + source = "registry+https://github.com/rust-lang/crates.io-index" 4186 + checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 4187 + 4188 + [[package]] 4189 + name = "windows_i686_gnu" 4190 + version = "0.52.6" 4191 + source = "registry+https://github.com/rust-lang/crates.io-index" 4192 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 4193 + 4194 + [[package]] 4195 + name = "windows_i686_gnullvm" 4196 + version = "0.52.6" 4197 + source = "registry+https://github.com/rust-lang/crates.io-index" 4198 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 4199 + 4200 + [[package]] 4201 + name = "windows_i686_msvc" 4202 + version = "0.42.2" 4203 + source = "registry+https://github.com/rust-lang/crates.io-index" 4204 + checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 4205 + 4206 + [[package]] 4207 + name = "windows_i686_msvc" 4208 + version = "0.52.6" 4209 + source = "registry+https://github.com/rust-lang/crates.io-index" 4210 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 4211 + 4212 + [[package]] 4213 + name = "windows_x86_64_gnu" 4214 + version = "0.42.2" 4215 + source = "registry+https://github.com/rust-lang/crates.io-index" 4216 + checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 4217 + 4218 + [[package]] 4219 + name = "windows_x86_64_gnu" 4220 + version = "0.52.6" 4221 + source = "registry+https://github.com/rust-lang/crates.io-index" 4222 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 4223 + 4224 + [[package]] 4225 + name = "windows_x86_64_gnullvm" 4226 + version = "0.42.2" 4227 + source = "registry+https://github.com/rust-lang/crates.io-index" 4228 + checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 4229 + 4230 + [[package]] 4231 + name = "windows_x86_64_gnullvm" 4232 + version = "0.52.6" 4233 + source = "registry+https://github.com/rust-lang/crates.io-index" 4234 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 4235 + 4236 + [[package]] 4237 + name = "windows_x86_64_msvc" 4238 + version = "0.42.2" 4239 + source = "registry+https://github.com/rust-lang/crates.io-index" 4240 + checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 4241 + 4242 + [[package]] 4243 + name = "windows_x86_64_msvc" 4244 + version = "0.52.6" 4245 + source = "registry+https://github.com/rust-lang/crates.io-index" 4246 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 4247 + 4248 + [[package]] 4249 + name = "winnow" 4250 + version = "1.0.4" 4251 + source = "registry+https://github.com/rust-lang/crates.io-index" 4252 + checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" 4253 + dependencies = [ 4254 + "memchr", 4255 + ] 4256 + 4257 + [[package]] 4258 + name = "wmi" 4259 + version = "0.18.4" 4260 + source = "registry+https://github.com/rust-lang/crates.io-index" 4261 + checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" 4262 + dependencies = [ 4263 + "chrono", 4264 + "futures", 4265 + "log", 4266 + "serde", 4267 + "thiserror 2.0.19", 4268 + "windows", 4269 + "windows-core", 4270 + ] 4271 + 4272 + [[package]] 4273 + name = "writeable" 4274 + version = "0.6.3" 4275 + source = "registry+https://github.com/rust-lang/crates.io-index" 4276 + checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" 4277 + 4278 + [[package]] 4279 + name = "ws_stream_wasm" 4280 + version = "0.7.5" 4281 + source = "registry+https://github.com/rust-lang/crates.io-index" 4282 + checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" 4283 + dependencies = [ 4284 + "async_io_stream", 4285 + "futures", 4286 + "js-sys", 4287 + "log", 4288 + "pharos", 4289 + "rustc_version", 4290 + "send_wrapper", 4291 + "thiserror 2.0.19", 4292 + "wasm-bindgen", 4293 + "wasm-bindgen-futures", 4294 + "web-sys", 4295 + ] 4296 + 4297 + [[package]] 4298 + name = "xml-rs" 4299 + version = "0.8.28" 4300 + source = "registry+https://github.com/rust-lang/crates.io-index" 4301 + checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" 4302 + 4303 + [[package]] 4304 + name = "xmltree" 4305 + version = "0.10.3" 4306 + source = "registry+https://github.com/rust-lang/crates.io-index" 4307 + checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" 4308 + dependencies = [ 4309 + "xml-rs", 4310 + ] 4311 + 4312 + [[package]] 4313 + name = "yasna" 4314 + version = "0.5.2" 4315 + source = "registry+https://github.com/rust-lang/crates.io-index" 4316 + checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" 4317 + dependencies = [ 4318 + "time", 4319 + ] 4320 + 4321 + [[package]] 4322 + name = "yoke" 4323 + version = "0.8.3" 4324 + source = "registry+https://github.com/rust-lang/crates.io-index" 4325 + checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" 4326 + dependencies = [ 4327 + "stable_deref_trait", 4328 + "yoke-derive", 4329 + "zerofrom", 4330 + ] 4331 + 4332 + [[package]] 4333 + name = "yoke-derive" 4334 + version = "0.8.2" 4335 + source = "registry+https://github.com/rust-lang/crates.io-index" 4336 + checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" 4337 + dependencies = [ 4338 + "proc-macro2", 4339 + "quote", 4340 + "syn 2.0.119", 4341 + "synstructure", 4342 + ] 4343 + 4344 + [[package]] 4345 + name = "zerocopy" 4346 + version = "0.8.55" 4347 + source = "registry+https://github.com/rust-lang/crates.io-index" 4348 + checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" 4349 + dependencies = [ 4350 + "zerocopy-derive", 4351 + ] 4352 + 4353 + [[package]] 4354 + name = "zerocopy-derive" 4355 + version = "0.8.55" 4356 + source = "registry+https://github.com/rust-lang/crates.io-index" 4357 + checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" 4358 + dependencies = [ 4359 + "proc-macro2", 4360 + "quote", 4361 + "syn 2.0.119", 4362 + ] 4363 + 4364 + [[package]] 4365 + name = "zerofrom" 4366 + version = "0.1.8" 4367 + source = "registry+https://github.com/rust-lang/crates.io-index" 4368 + checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" 4369 + dependencies = [ 4370 + "zerofrom-derive", 4371 + ] 4372 + 4373 + [[package]] 4374 + name = "zerofrom-derive" 4375 + version = "0.1.7" 4376 + source = "registry+https://github.com/rust-lang/crates.io-index" 4377 + checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" 4378 + dependencies = [ 4379 + "proc-macro2", 4380 + "quote", 4381 + "syn 2.0.119", 4382 + "synstructure", 4383 + ] 4384 + 4385 + [[package]] 4386 + name = "zeroize" 4387 + version = "1.9.0" 4388 + source = "registry+https://github.com/rust-lang/crates.io-index" 4389 + checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" 4390 + dependencies = [ 4391 + "zeroize_derive", 4392 + ] 4393 + 4394 + [[package]] 4395 + name = "zeroize_derive" 4396 + version = "1.5.0" 4397 + source = "registry+https://github.com/rust-lang/crates.io-index" 4398 + checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" 4399 + dependencies = [ 4400 + "proc-macro2", 4401 + "quote", 4402 + "syn 2.0.119", 4403 + ] 4404 + 4405 + [[package]] 4406 + name = "zerotrie" 4407 + version = "0.2.4" 4408 + source = "registry+https://github.com/rust-lang/crates.io-index" 4409 + checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" 4410 + dependencies = [ 4411 + "displaydoc", 4412 + "yoke", 4413 + "zerofrom", 4414 + ] 4415 + 4416 + [[package]] 4417 + name = "zerovec" 4418 + version = "0.11.6" 4419 + source = "registry+https://github.com/rust-lang/crates.io-index" 4420 + checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" 4421 + dependencies = [ 4422 + "yoke", 4423 + "zerofrom", 4424 + "zerovec-derive", 4425 + ] 4426 + 4427 + [[package]] 4428 + name = "zerovec-derive" 4429 + version = "0.11.3" 4430 + source = "registry+https://github.com/rust-lang/crates.io-index" 4431 + checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" 4432 + dependencies = [ 4433 + "proc-macro2", 4434 + "quote", 4435 + "syn 2.0.119", 4436 + ]
+58
Cargo.toml
··· 1 + [package] 2 + name = "erl_iroh" 3 + version = "0.1.0" 4 + edition = "2021" 5 + description = "Distributed Erlang protocol over iroh P2P + TLS, with a bot powers SDK" 6 + license = "MIT OR Apache-2.0" 7 + readme = "README.md" 8 + keywords = ["erlang", "iroh", "p2p", "distribution", "beam"] 9 + categories = ["network-programming", "asynchronous"] 10 + 11 + [dependencies] 12 + # Erlang distribution + ETF 13 + erl_dist = "0.8" 14 + eetf = "0.11" 15 + 16 + # P2P transport (QUIC + TLS + holepunch) 17 + iroh = "1.0" 18 + 19 + # Async runtime 20 + tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "time", "sync", "io-util", "signal"] } 21 + tokio-util = { version = "0.7", features = ["compat"] } 22 + futures = "0.3" 23 + async-dup = "1.2" 24 + 25 + # TLS for classic TCP path (security pedants) 26 + tokio-rustls = "0.26" 27 + rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } 28 + rustls-pki-types = "1" 29 + rustls-pemfile = "2" 30 + webpki-roots = "0.26" 31 + 32 + # Errors / utils 33 + thiserror = "2" 34 + anyhow = "1" 35 + bytes = "1" 36 + tracing = "0.1" 37 + rand = "0.8" 38 + bitflags = "2" 39 + 40 + # CLI demos (also used as flake apps) 41 + tracing-subscriber = { version = "0.3", features = ["env-filter"] } 42 + clap = { version = "4", features = ["derive"] } 43 + 44 + [dev-dependencies] 45 + tempfile = "3" 46 + rcgen = "0.13" 47 + 48 + [[bin]] 49 + name = "erl-iroh-demo" 50 + path = "examples/two_bots.rs" 51 + 52 + [[bin]] 53 + name = "erl-iroh-bot" 54 + path = "examples/bot.rs" 55 + 56 + [[bin]] 57 + name = "erl-iroh-peer" 58 + path = "examples/peer.rs"
+147
README.md
··· 1 + # erl_iroh 2 + 3 + **Distributed Erlang over [iroh](https://iroh.computer) P2P**, with optional classic **TCP + TLS**, and a small **bot powers SDK**. 4 + 5 + Bots import the crate, get an iroh endpoint (public-key dialing, holepunch, QUIC), speak the real Erlang distribution handshake (`erl_dist`), and unlock capability-gated operations: RPC, send, introspect, cluster, eval, trace. 6 + 7 + ```text 8 + Bot SDK (powers: rpc | send | introspect | cluster | listen | eval | trace) 9 + 10 + Session ── erl_dist handshake + message channel + net ticks + erpc 11 + 12 + Transport 13 + ├─ iroh public-key P2P (QUIC, TLS 1.3 built-in, relays + holepunch) 14 + ├─ tcp classic BEAM / EPMD 15 + └─ tls TCP + rustls (mTLS welcome, security pedants) 16 + ``` 17 + 18 + ## Why 19 + 20 + - **NAT-friendly mesh of BEAM-adjacent bots** without babysitting IPs — dial `EndpointId`s. 21 + - **Wire-compatible distribution** so a bot can also talk to real OTP nodes (TCP/TLS + cookie). 22 + - **Powers** keep each bot least-privileged: a scout does not need `TRACE` just to `RPC`. 23 + 24 + ## Quick start 25 + 26 + ### Nix (flake app) 27 + 28 + ```bash 29 + # in-process two-bot iroh handshake demo 30 + nix run git+https://tangled.org/nandi.uk/erl-iroh 31 + # or from a checkout: 32 + nix run .#demo 33 + 34 + # long-running listener / dialer 35 + nix run .#bot -- --name alpha 36 + nix run .#peer -- --name beta --peer <endpoint-id> 37 + ``` 38 + 39 + ### Cargo 40 + 41 + ```bash 42 + # two bots in one process (handshake over local iroh) 43 + cargo run --bin erl-iroh-demo 44 + # same as: cargo run --example two_bots 45 + 46 + # or two terminals 47 + cargo run --bin erl-iroh-bot -- --name alpha 48 + cargo run --bin erl-iroh-peer -- --name beta --peer <endpoint-id-from-alpha> 49 + ``` 50 + 51 + ### As a library 52 + 53 + ```rust 54 + use erl_iroh::bot::Bot; 55 + use erl_iroh::powers::Powers; 56 + use erl_iroh::term; 57 + use erl_iroh::TlsConfig; 58 + 59 + #[tokio::main] 60 + async fn main() -> erl_iroh::Result<()> { 61 + let bot = Bot::builder("scout") 62 + .cookie("super-secret-cookie") 63 + .powers(Powers::worker()) // rpc|send|introspect|cluster 64 + .tls(TlsConfig::client_webpki()?) // optional classic path 65 + .build() 66 + .await?; 67 + 68 + bot.listen_iroh().await?; 69 + println!("dial me: {}", bot.endpoint_id()); 70 + 71 + // P2P 72 + // bot.connect_iroh("…endpoint id…").await?; 73 + 74 + // Real BEAM via EPMD + TCP 75 + // bot.connect_tcp("app@localhost", None).await?; 76 + 77 + // Real BEAM via TLS 78 + // bot.connect_tls("app@host", "host", 4370, "host", None).await?; 79 + 80 + // Powers 81 + // let procs = bot.processes("app@localhost").await?; 82 + // bot.send("app@localhost", "logger", term::atom("hi").into()).await?; 83 + // let v = bot.eval("app@localhost", "1 + 1").await?; 84 + 85 + Ok(()) 86 + } 87 + ``` 88 + 89 + ## Powers 90 + 91 + | Power | Gates | 92 + |--------------|--------------------------------------------| 93 + | `RPC` | `rpc`, `ping` | 94 + | `SEND` | `send` to registered names | 95 + | `INTROSPECT` | `processes`, `applications`, `ets_tables`, … | 96 + | `CLUSTER` | `connect_iroh`, `connect_tcp`, `connect_tls` | 97 + | `LISTEN` | `listen_iroh`, `accept_tls` | 98 + | `EVAL` | `eval` (scan/parse/eval over RPC) | 99 + | `TRACE` | reserved for dbg/recon-style tracing | 100 + 101 + Presets: `Powers::all()`, `Powers::worker()`, `Powers::observer()`. 102 + 103 + ## Security model 104 + 105 + 1. **iroh path** — every connection is QUIC with TLS 1.3; peers authenticate by ed25519 `EndpointId`. The Erlang **cookie** still gates the dist handshake. 106 + 2. **TLS path** — rustls (ring) over TCP; optional mutual TLS via `TlsConfig::server(..., Some(client_ca))` and `TlsConfig::client_with_ca(...)`. 107 + 3. **TCP path** — plain sockets for local/dev; cookie is the only auth. Prefer TLS or iroh off-localhost. 108 + 4. **Powers** — application-level capability checks before sensitive ops. 109 + 110 + Dev-only: `TlsConfig::client_insecure()` skips cert verification (self-signed labs). 111 + 112 + ## Protocol notes 113 + 114 + - ALPN: `erl-iroh/1` 115 + - Handshake: OTP 23+ (`HANDSHAKE_23`, protocol v6), including `NAME_ME` dynamic names 116 + - RPC: `SPAWN_REQUEST` → `erpc:execute_call/4` (same approach as `erl_rpc`) 117 + - Ticks every 15s to keep links alive 118 + 119 + Real OTP nodes do not speak iroh natively. To bridge: 120 + 121 + - run a bot with `LISTEN` + `connect_tcp`/`connect_tls` as a gateway, or 122 + - put a TLS terminator / TCP proxy in front of `erl` distribution ports. 123 + 124 + ## Layout 125 + 126 + ``` 127 + src/ 128 + lib.rs crate root, ALPN 129 + bot.rs Bot + BotBuilder SDK 130 + powers.rs capability bitflags 131 + session.rs handshake, channel, ticks, RPC 132 + stream.rs clonable futures-io duplex (iroh/tls/tcp → erl_dist) 133 + transport/ 134 + iroh.rs Endpoint bind / dial / accept 135 + tcp.rs TCP + EPMD PORT_PLEASE2 136 + tls.rs rustls client/server 137 + term.rs ETF helpers 138 + error.rs 139 + examples/ 140 + bot.rs listen forever 141 + peer.rs dial + send 142 + two_bots.rs in-process mesh smoke test 143 + ``` 144 + 145 + ## License 146 + 147 + MIT OR Apache-2.0
+48
examples/bot.rs
··· 1 + //! Minimal bot that listens on iroh and prints its endpoint id. 2 + //! 3 + //! ```bash 4 + //! cargo run --example bot -- --name scout --cookie secret 5 + //! ``` 6 + 7 + use clap::Parser; 8 + use erl_iroh::bot::Bot; 9 + use erl_iroh::powers::Powers; 10 + use tracing_subscriber::EnvFilter; 11 + 12 + #[derive(Parser, Debug)] 13 + #[command(name = "bot", about = "erl-iroh bot that listens for peers")] 14 + struct Args { 15 + /// Short node name (becomes name@iroh). 16 + #[arg(long, default_value = "scout")] 17 + name: String, 18 + 19 + /// Erlang distribution cookie. 20 + #[arg(long, default_value = "erl-iroh-cookie")] 21 + cookie: String, 22 + } 23 + 24 + #[tokio::main] 25 + async fn main() -> erl_iroh::Result<()> { 26 + tracing_subscriber::fmt() 27 + .with_env_filter(EnvFilter::from_default_env().add_directive("erl_iroh=info".parse().unwrap())) 28 + .init(); 29 + 30 + let args = Args::parse(); 31 + 32 + let bot = Bot::builder(args.name) 33 + .cookie(args.cookie) 34 + .powers(Powers::all()) 35 + .build() 36 + .await?; 37 + 38 + bot.listen_iroh().await?; 39 + 40 + println!("node: {}", bot.node_name()); 41 + println!("endpoint id: {}", bot.endpoint_id()); 42 + println!("powers: {}", bot.powers()); 43 + println!("waiting for peers (Ctrl-C to quit)…"); 44 + 45 + tokio::signal::ctrl_c().await?; 46 + println!("bye"); 47 + Ok(()) 48 + }
+69
examples/peer.rs
··· 1 + //! Peer bot that dials another bot over iroh and runs a few power demos. 2 + //! 3 + //! ```bash 4 + //! # terminal 1 5 + //! cargo run --example bot -- --name alpha 6 + //! # copy the endpoint id printed 7 + //! 8 + //! # terminal 2 9 + //! cargo run --example peer -- --name beta --peer <endpoint-id> 10 + //! ``` 11 + 12 + use clap::Parser; 13 + use erl_iroh::bot::Bot; 14 + use erl_iroh::powers::Powers; 15 + use erl_iroh::term; 16 + use tracing_subscriber::EnvFilter; 17 + 18 + #[derive(Parser, Debug)] 19 + #[command(name = "peer", about = "dial an erl-iroh bot and exercise powers")] 20 + struct Args { 21 + #[arg(long, default_value = "beta")] 22 + name: String, 23 + 24 + #[arg(long, default_value = "erl-iroh-cookie")] 25 + cookie: String, 26 + 27 + /// Remote iroh endpoint id. 28 + #[arg(long)] 29 + peer: String, 30 + } 31 + 32 + #[tokio::main] 33 + async fn main() -> erl_iroh::Result<()> { 34 + tracing_subscriber::fmt() 35 + .with_env_filter(EnvFilter::from_default_env().add_directive("erl_iroh=info".parse().unwrap())) 36 + .init(); 37 + 38 + let args = Args::parse(); 39 + 40 + let bot = Bot::builder(args.name) 41 + .cookie(args.cookie) 42 + .powers(Powers::all()) 43 + .build() 44 + .await?; 45 + 46 + println!("local endpoint: {}", bot.endpoint_id()); 47 + println!("dialing {} …", args.peer); 48 + 49 + let handle = bot.connect_iroh(&args.peer).await?; 50 + println!("connected to erlang node {}", handle.peer_name()); 51 + 52 + // Both sides are Rust "fake" nodes — RPC to erpc only works against real BEAM. 53 + // Still demonstrate SEND power with a registered-name message. 54 + bot.send( 55 + &handle.peer_name().to_string(), 56 + "net_kernel", 57 + term::atom("hello_from_peer").into(), 58 + ) 59 + .await 60 + .map_err(|e| { 61 + eprintln!("send (best-effort): {e}"); 62 + e 63 + }) 64 + .ok(); 65 + 66 + println!("peers: {:?}", bot.peers().await); 67 + println!("done"); 68 + Ok(()) 69 + }
+61
examples/two_bots.rs
··· 1 + //! Spin up two bots in-process, connect over iroh, exchange a message. 2 + //! 3 + //! ```bash 4 + //! cargo run --example two_bots 5 + //! ``` 6 + 7 + use erl_iroh::bot::Bot; 8 + use erl_iroh::powers::Powers; 9 + use erl_iroh::term; 10 + use tracing_subscriber::EnvFilter; 11 + 12 + #[tokio::main] 13 + async fn main() -> erl_iroh::Result<()> { 14 + tracing_subscriber::fmt() 15 + .with_env_filter(EnvFilter::from_default_env().add_directive("erl_iroh=info".parse().unwrap())) 16 + .init(); 17 + 18 + let cookie = "two-bots-cookie"; 19 + 20 + let alpha = Bot::builder("alpha") 21 + .cookie(cookie) 22 + .powers(Powers::all()) 23 + .build() 24 + .await?; 25 + alpha.listen_iroh().await?; 26 + 27 + // Give the accept loop a moment to arm. 28 + tokio::time::sleep(std::time::Duration::from_millis(200)).await; 29 + 30 + let beta = Bot::builder("beta") 31 + .cookie(cookie) 32 + .powers(Powers::all()) 33 + .build() 34 + .await?; 35 + 36 + println!("alpha: {}", alpha.endpoint_id()); 37 + println!("beta: {}", beta.endpoint_id()); 38 + 39 + let handle = beta.connect_iroh(&alpha.endpoint_id_str()).await?; 40 + println!( 41 + "beta → alpha handshake ok (peer node name: {})", 42 + handle.peer_name() 43 + ); 44 + 45 + // Fire a reg_send; alpha has no registered process, but the wire path works. 46 + let _ = beta 47 + .send( 48 + handle.peer_name(), 49 + "echo", 50 + term::tuple2(term::atom("hi"), term::atom("from_beta")), 51 + ) 52 + .await; 53 + 54 + println!("alpha peers: {:?}", alpha.peers().await); 55 + println!("beta peers: {:?}", beta.peers().await); 56 + println!("success"); 57 + 58 + beta.shutdown().await; 59 + alpha.shutdown().await; 60 + Ok(()) 61 + }
+107
flake.nix
··· 1 + { 2 + description = "erl_iroh — distributed Erlang over iroh P2P + TLS, with a bot powers SDK"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + flake-utils.url = "github:numtide/flake-utils"; 7 + }; 8 + 9 + outputs = 10 + { 11 + self, 12 + nixpkgs, 13 + flake-utils, 14 + }: 15 + flake-utils.lib.eachDefaultSystem ( 16 + system: 17 + let 18 + pkgs = import nixpkgs { inherit system; }; 19 + inherit (pkgs) lib; 20 + 21 + erl-iroh = pkgs.rustPlatform.buildRustPackage { 22 + pname = "erl-iroh"; 23 + version = "0.1.0"; 24 + src = lib.cleanSourceWith { 25 + src = ./.; 26 + filter = 27 + path: type: 28 + let 29 + base = baseNameOf path; 30 + in 31 + !( 32 + lib.hasInfix "/target" path 33 + || lib.hasInfix "/.git" path 34 + || base == "target" 35 + || base == "result" 36 + || base == ".git" 37 + ); 38 + }; 39 + 40 + cargoLock.lockFile = ./Cargo.lock; 41 + 42 + nativeBuildInputs = [ 43 + pkgs.pkg-config 44 + pkgs.cmake 45 + pkgs.perl 46 + ]; 47 + 48 + # aws-lc-sys / ring need a C toolchain; no OpenSSL linkage required. 49 + doCheck = true; 50 + # Integration-style unit tests are pure; keep checks on. 51 + checkFlags = [ ]; 52 + 53 + meta = { 54 + description = "Distributed Erlang over iroh P2P + TLS, bot powers SDK"; 55 + homepage = "https://tangled.org/nandi.uk/erl-iroh"; 56 + license = with lib.licenses; [ 57 + mit 58 + asl20 59 + ]; 60 + mainProgram = "erl-iroh-demo"; 61 + }; 62 + }; 63 + in 64 + { 65 + packages.default = erl-iroh; 66 + packages.erl-iroh = erl-iroh; 67 + 68 + # Default app: in-process two-bot iroh handshake demo 69 + apps.default = { 70 + type = "app"; 71 + program = "${erl-iroh}/bin/erl-iroh-demo"; 72 + }; 73 + apps.demo = { 74 + type = "app"; 75 + program = "${erl-iroh}/bin/erl-iroh-demo"; 76 + }; 77 + apps.bot = { 78 + type = "app"; 79 + program = "${erl-iroh}/bin/erl-iroh-bot"; 80 + }; 81 + apps.peer = { 82 + type = "app"; 83 + program = "${erl-iroh}/bin/erl-iroh-peer"; 84 + }; 85 + 86 + devShells.default = pkgs.mkShell { 87 + packages = [ 88 + pkgs.rustc 89 + pkgs.cargo 90 + pkgs.rustfmt 91 + pkgs.clippy 92 + pkgs.pkg-config 93 + pkgs.cmake 94 + pkgs.perl 95 + pkgs.rust-analyzer 96 + ]; 97 + shellHook = '' 98 + echo "erl-iroh dev shell" 99 + echo " cargo run --bin erl-iroh-demo" 100 + echo " nix run .#demo" 101 + ''; 102 + }; 103 + 104 + formatter = pkgs.nixfmt-rfc-style; 105 + } 106 + ); 107 + }
+438
src/bot.rs
··· 1 + //! Bot SDK — import this, get powers. 2 + //! 3 + //! A [`Bot`] is a named distribution endpoint with an iroh identity, optional 4 + //! TLS config, and a set of [`Powers`] that gate high-level operations. 5 + 6 + use crate::error::{Error, Result}; 7 + use crate::powers::Powers; 8 + use crate::session::{PeerId, Session, SessionHandle, SessionRegistry}; 9 + use crate::term; 10 + use crate::transport::iroh::IrohTransport; 11 + use crate::transport::tcp::TcpTransport; 12 + use crate::transport::tls::{TlsConfig, TlsTransport}; 13 + use erl_dist::term::{Atom, List, Term}; 14 + use iroh::{EndpointId, SecretKey}; 15 + use std::sync::Arc; 16 + use tokio::net::TcpListener; 17 + use tokio::sync::Mutex; 18 + use tracing::{info, warn}; 19 + 20 + /// A distribution-capable bot with gated powers. 21 + pub struct Bot { 22 + name: String, 23 + cookie: String, 24 + powers: Powers, 25 + iroh: IrohTransport, 26 + sessions: SessionRegistry, 27 + tls: Option<TlsConfig>, 28 + accept_tasks: Arc<Mutex<Vec<tokio::task::JoinHandle<()>>>>, 29 + } 30 + 31 + /// Builder for [`Bot`]. 32 + pub struct BotBuilder { 33 + name: String, 34 + cookie: Option<String>, 35 + powers: Powers, 36 + secret: Option<SecretKey>, 37 + tls: Option<TlsConfig>, 38 + host: String, 39 + } 40 + 41 + impl Bot { 42 + /// Start building a bot named `name` (short name; host defaults to `iroh`). 43 + /// 44 + /// Full node name becomes `{name}@{host}` (host overridable via builder). 45 + pub fn builder(name: impl Into<String>) -> BotBuilder { 46 + BotBuilder { 47 + name: name.into(), 48 + cookie: None, 49 + powers: Powers::default(), 50 + secret: None, 51 + tls: None, 52 + host: "iroh".into(), 53 + } 54 + } 55 + 56 + /// Full Erlang-style node name (`scout@iroh`). 57 + pub fn node_name(&self) -> &str { 58 + &self.name 59 + } 60 + 61 + /// Granted powers. 62 + pub fn powers(&self) -> Powers { 63 + self.powers 64 + } 65 + 66 + /// This bot's iroh [`EndpointId`] (share to let peers dial you). 67 + pub fn endpoint_id(&self) -> EndpointId { 68 + self.iroh.endpoint_id() 69 + } 70 + 71 + /// iroh endpoint id as a string. 72 + pub fn endpoint_id_str(&self) -> String { 73 + self.iroh.endpoint_id().to_string() 74 + } 75 + 76 + /// Underlying iroh transport. 77 + pub fn iroh(&self) -> &IrohTransport { 78 + &self.iroh 79 + } 80 + 81 + // ── cluster powers ────────────────────────────────────────────────── 82 + 83 + /// Dial another bot / node over iroh by endpoint id (base32 string). 84 + pub async fn connect_iroh(&self, peer_endpoint_id: &str) -> Result<SessionHandle> { 85 + self.powers.require(Powers::CLUSTER)?; 86 + let stream = self.iroh.connect(peer_endpoint_id).await?; 87 + let peer_id = PeerId::Endpoint(peer_endpoint_id.to_string()); 88 + let session = Session::connect(stream, &self.name, &self.cookie, peer_id).await?; 89 + let handle = session.handle(); 90 + info!(peer = %handle.peer_name(), "connected over iroh"); 91 + self.sessions.insert(&session).await; 92 + // Keep session alive by leaking into accept_tasks list via spawn of empty future... 93 + // Session's JoinHandle is inside Session; we store the Session so the runner lives. 94 + self.keep(session).await; 95 + Ok(handle) 96 + } 97 + 98 + /// Dial by full iroh [`iroh::EndpointAddr`]. 99 + pub async fn connect_iroh_addr(&self, addr: iroh::EndpointAddr) -> Result<SessionHandle> { 100 + self.powers.require(Powers::CLUSTER)?; 101 + let id = addr.id.to_string(); 102 + let stream = self.iroh.connect_addr(addr).await?; 103 + let peer_id = PeerId::Endpoint(id); 104 + let session = Session::connect(stream, &self.name, &self.cookie, peer_id).await?; 105 + let handle = session.handle(); 106 + self.sessions.insert(&session).await; 107 + self.keep(session).await; 108 + Ok(handle) 109 + } 110 + 111 + /// Dial a classic BEAM node via EPMD + TCP (no TLS). 112 + pub async fn connect_tcp(&self, node: &str, cookie: Option<&str>) -> Result<SessionHandle> { 113 + self.powers.require(Powers::CLUSTER)?; 114 + let stream = TcpTransport::connect_node(node).await?; 115 + let cookie = cookie.unwrap_or(&self.cookie); 116 + let peer_id = PeerId::Node(node.to_string()); 117 + let session = Session::connect(stream, &self.name, cookie, peer_id).await?; 118 + let handle = session.handle(); 119 + info!(peer = %handle.peer_name(), "connected over tcp"); 120 + self.sessions.insert(&session).await; 121 + self.keep(session).await; 122 + Ok(handle) 123 + } 124 + 125 + /// Dial `host:port` with TLS, then run the Erlang dist handshake. 126 + /// 127 + /// `node` is the Erlang node name used in the handshake (`app@host`). 128 + /// `server_name` is the TLS SNI / certificate name. 129 + pub async fn connect_tls( 130 + &self, 131 + node: &str, 132 + host: &str, 133 + port: u16, 134 + server_name: &str, 135 + cookie: Option<&str>, 136 + ) -> Result<SessionHandle> { 137 + self.powers.require(Powers::CLUSTER)?; 138 + let tls = self 139 + .tls 140 + .as_ref() 141 + .ok_or_else(|| Error::tls("no TlsConfig configured on bot"))?; 142 + let stream = TlsTransport::connect(host, port, server_name, tls).await?; 143 + let cookie = cookie.unwrap_or(&self.cookie); 144 + let peer_id = PeerId::Node(node.to_string()); 145 + let session = Session::connect(stream, &self.name, cookie, peer_id).await?; 146 + let handle = session.handle(); 147 + info!(peer = %handle.peer_name(), "connected over tls"); 148 + self.sessions.insert(&session).await; 149 + self.keep(session).await; 150 + Ok(handle) 151 + } 152 + 153 + /// Start accepting inbound iroh distribution connections in the background. 154 + pub async fn listen_iroh(&self) -> Result<()> { 155 + self.powers.require(Powers::LISTEN)?; 156 + let iroh = self.iroh.clone(); 157 + let name = self.name.clone(); 158 + let cookie = self.cookie.clone(); 159 + let sessions = self.sessions.clone(); 160 + let keep = self.accept_tasks.clone(); 161 + 162 + let task = tokio::spawn(async move { 163 + loop { 164 + match iroh.accept().await { 165 + Ok((stream, remote)) => { 166 + let peer_id = PeerId::Endpoint(remote.to_string()); 167 + match Session::accept(stream, &name, &cookie, peer_id).await { 168 + Ok(session) => { 169 + info!(peer = %session.peer_node().name, remote = %remote, "accepted iroh peer"); 170 + sessions.insert(&session).await; 171 + // Park the session so its runner stays alive. 172 + let parked = Arc::new(session); 173 + let k = keep.clone(); 174 + k.lock().await.push(tokio::spawn(async move { 175 + // Hold Arc until cancelled. 176 + std::future::pending::<()>().await; 177 + drop(parked); 178 + })); 179 + } 180 + Err(e) => warn!(error = %e, "accept handshake failed"), 181 + } 182 + } 183 + Err(e) => { 184 + warn!(error = %e, "iroh accept error"); 185 + break; 186 + } 187 + } 188 + } 189 + }); 190 + self.accept_tasks.lock().await.push(task); 191 + info!(id = %self.endpoint_id(), "listening for iroh peers"); 192 + Ok(()) 193 + } 194 + 195 + /// Accept one TLS distribution connection on `listener`. 196 + pub async fn accept_tls(&self, listener: &TcpListener) -> Result<SessionHandle> { 197 + self.powers.require(Powers::LISTEN)?; 198 + let tls = self 199 + .tls 200 + .as_ref() 201 + .ok_or_else(|| Error::tls("no TlsConfig configured on bot"))?; 202 + let stream = TlsTransport::accept(listener, tls).await?; 203 + let peer_id = PeerId::Node("tls-peer".into()); 204 + let session = Session::accept(stream, &self.name, &self.cookie, peer_id).await?; 205 + let handle = session.handle(); 206 + self.sessions.insert(&session).await; 207 + self.keep(session).await; 208 + Ok(handle) 209 + } 210 + 211 + /// List connected peer keys. 212 + pub async fn peers(&self) -> Vec<String> { 213 + self.sessions.list().await 214 + } 215 + 216 + /// Get a session handle by peer name or endpoint id key. 217 + pub async fn session(&self, peer: &str) -> Result<SessionHandle> { 218 + self.sessions 219 + .get(peer) 220 + .await 221 + .ok_or_else(|| Error::NotConnected(peer.to_string())) 222 + } 223 + 224 + // ── rpc / send powers ─────────────────────────────────────────────── 225 + 226 + /// RPC to a connected peer: `module:function(args)`. 227 + pub async fn rpc( 228 + &self, 229 + peer: &str, 230 + module: &str, 231 + function: &str, 232 + args: List, 233 + ) -> Result<Term> { 234 + self.powers.require(Powers::RPC)?; 235 + let s = self.session(peer).await?; 236 + s.rpc(Atom::from(module), Atom::from(function), args).await 237 + } 238 + 239 + /// Convenience: `erlang:node/0` on peer. 240 + pub async fn remote_node_name(&self, peer: &str) -> Result<Term> { 241 + self.rpc(peer, "erlang", "node", term::nil()).await 242 + } 243 + 244 + /// Send a term to a registered process on a peer. 245 + pub async fn send(&self, peer: &str, to: &str, msg: Term) -> Result<()> { 246 + self.powers.require(Powers::SEND)?; 247 + let s = self.session(peer).await?; 248 + s.send(Atom::from(to), msg).await 249 + } 250 + 251 + // ── introspect powers (thin RPC wrappers) ─────────────────────────── 252 + 253 + /// List processes on a peer (`erlang:processes/0`). 254 + pub async fn processes(&self, peer: &str) -> Result<Term> { 255 + self.powers.require(Powers::INTROSPECT)?; 256 + self.rpc(peer, "erlang", "processes", term::nil()).await 257 + } 258 + 259 + /// Which applications are running (`application:which_applications/0`). 260 + pub async fn applications(&self, peer: &str) -> Result<Term> { 261 + self.powers.require(Powers::INTROSPECT)?; 262 + self.rpc(peer, "application", "which_applications", term::nil()) 263 + .await 264 + } 265 + 266 + /// Process info for a pid term. 267 + pub async fn process_info(&self, peer: &str, pid: Term) -> Result<Term> { 268 + self.powers.require(Powers::INTROSPECT)?; 269 + self.rpc(peer, "erlang", "process_info", term::list([pid])) 270 + .await 271 + } 272 + 273 + /// `ets:all/0`. 274 + pub async fn ets_tables(&self, peer: &str) -> Result<Term> { 275 + self.powers.require(Powers::INTROSPECT)?; 276 + self.rpc(peer, "ets", "all", term::nil()).await 277 + } 278 + 279 + /// Ping a peer via `net_adm:ping/1`. Requires the peer to be a real BEAM node. 280 + pub async fn ping(&self, via: &str, target_node: &str) -> Result<Term> { 281 + self.powers.require(Powers::RPC)?; 282 + self.rpc( 283 + via, 284 + "net_adm", 285 + "ping", 286 + term::list([term::atom(target_node).into()]), 287 + ) 288 + .await 289 + } 290 + 291 + // ── eval power ────────────────────────────────────────────────────── 292 + 293 + /// Evaluate an Erlang expression string via `erl_eval` / `erl_parse`. 294 + /// 295 + /// Uses: `erl_eval:exprs(element(2, erl_parse:parse_exprs(element(2, erl_scan:string(S)))), [])` 296 + /// simplified through a small helper MFA when available; falls back to 297 + /// `rpc:call` style via `erl_eval:expr`. 298 + pub async fn eval(&self, peer: &str, expr: &str) -> Result<Term> { 299 + self.powers.require(Powers::EVAL)?; 300 + // {ok, Tokens, _} = erl_scan:string(Expr ++ "."). 301 + // {ok, Parsed} = erl_parse:parse_exprs(Tokens). 302 + // {value, Value, _} = erl_eval:exprs(Parsed, []). 303 + // We ship a multi-step via erpc by calling a one-liner through `erlang:apply`. 304 + // Simpler approach: use `erl_eval:eval_str` is not std — compose: 305 + let code = format!("{expr}."); 306 + let scan = self 307 + .rpc( 308 + peer, 309 + "erl_scan", 310 + "string", 311 + term::list([term::string(&code)]), 312 + ) 313 + .await?; 314 + let tokens = extract_ok_tuple(&scan, 1)?; 315 + let parsed = self 316 + .rpc(peer, "erl_parse", "parse_exprs", term::list([tokens])) 317 + .await?; 318 + let forms = extract_ok_tuple(&parsed, 1)?; 319 + let value = self 320 + .rpc( 321 + peer, 322 + "erl_eval", 323 + "exprs", 324 + term::list([forms, term::nil().into()]), 325 + ) 326 + .await?; 327 + // {value, V, Bindings} 328 + if let Term::Tuple(t) = &value { 329 + if t.elements.len() >= 2 { 330 + if term::as_atom(&t.elements[0]) == Some("value") { 331 + return Ok(t.elements[1].clone()); 332 + } 333 + } 334 + } 335 + Ok(value) 336 + } 337 + 338 + /// Gracefully shut down accept loops and the iroh endpoint. 339 + pub async fn shutdown(self) { 340 + let mut tasks = self.accept_tasks.lock().await; 341 + for t in tasks.drain(..) { 342 + t.abort(); 343 + } 344 + drop(tasks); 345 + self.iroh.close().await; 346 + } 347 + 348 + // ── internals ─────────────────────────────────────────────────────── 349 + 350 + async fn keep(&self, session: Session) { 351 + let parked = Arc::new(session); 352 + self.accept_tasks.lock().await.push(tokio::spawn(async move { 353 + std::future::pending::<()>().await; 354 + drop(parked); 355 + })); 356 + } 357 + } 358 + 359 + impl BotBuilder { 360 + /// Erlang cookie shared with peers. 361 + pub fn cookie(mut self, cookie: impl Into<String>) -> Self { 362 + self.cookie = Some(cookie.into()); 363 + self 364 + } 365 + 366 + /// Grant powers. 367 + pub fn powers(mut self, powers: Powers) -> Self { 368 + self.powers = powers; 369 + self 370 + } 371 + 372 + /// Stable iroh identity. 373 + pub fn secret_key(mut self, key: SecretKey) -> Self { 374 + self.secret = Some(key); 375 + self 376 + } 377 + 378 + /// TLS config for classic TCP+TLS paths. 379 + pub fn tls(mut self, tls: TlsConfig) -> Self { 380 + self.tls = Some(tls); 381 + self 382 + } 383 + 384 + /// Host part of the node name (default `iroh` → `name@iroh`). 385 + pub fn host(mut self, host: impl Into<String>) -> Self { 386 + self.host = host.into(); 387 + self 388 + } 389 + 390 + /// Build and bind the iroh endpoint. 391 + pub async fn build(self) -> Result<Bot> { 392 + let cookie = self 393 + .cookie 394 + .ok_or_else(|| Error::InvalidName("cookie is required".into()))?; 395 + let full_name = if self.name.contains('@') { 396 + self.name 397 + } else { 398 + format!("{}@{}", self.name, self.host) 399 + }; 400 + 401 + let iroh = if let Some(secret) = self.secret { 402 + IrohTransport::bind_with_key(secret).await? 403 + } else { 404 + IrohTransport::bind().await? 405 + }; 406 + 407 + info!( 408 + node = %full_name, 409 + endpoint = %iroh.endpoint_id(), 410 + powers = %self.powers, 411 + "bot ready" 412 + ); 413 + 414 + Ok(Bot { 415 + name: full_name, 416 + cookie, 417 + powers: self.powers, 418 + iroh, 419 + sessions: SessionRegistry::default(), 420 + tls: self.tls, 421 + accept_tasks: Arc::new(Mutex::new(Vec::new())), 422 + }) 423 + } 424 + } 425 + 426 + fn extract_ok_tuple(term: &Term, idx: usize) -> Result<Term> { 427 + if let Term::Tuple(t) = term { 428 + if let Some(Term::Atom(tag)) = t.elements.first() { 429 + if tag.name == "ok" && t.elements.len() > idx { 430 + return Ok(t.elements[idx].clone()); 431 + } 432 + if tag.name == "error" { 433 + return Err(Error::rpc(format!("{term}"))); 434 + } 435 + } 436 + } 437 + Err(Error::rpc(format!("expected {{ok, …}}, got {term}"))) 438 + }
+101
src/error.rs
··· 1 + //! Error types for erl_iroh. 2 + 3 + use thiserror::Error; 4 + 5 + /// Convenient result alias. 6 + pub type Result<T> = std::result::Result<T, Error>; 7 + 8 + /// Top-level error type. 9 + #[derive(Debug, Error)] 10 + pub enum Error { 11 + /// I/O failure on a transport stream. 12 + #[error("io error: {0}")] 13 + Io(#[from] std::io::Error), 14 + 15 + /// Erlang distribution handshake failed. 16 + #[error("handshake error: {0}")] 17 + Handshake(String), 18 + 19 + /// Distribution message send failed. 20 + #[error("send error: {0}")] 21 + Send(String), 22 + 23 + /// Distribution message receive failed. 24 + #[error("recv error: {0}")] 25 + Recv(String), 26 + 27 + /// RPC call failed or returned an error term. 28 + #[error("rpc error: {0}")] 29 + Rpc(String), 30 + 31 + /// Missing capability (bot power) for the requested operation. 32 + #[error("power denied: need {needed}, have {have}")] 33 + PowerDenied { 34 + /// Required power name. 35 + needed: &'static str, 36 + /// Powers currently granted. 37 + have: String, 38 + }, 39 + 40 + /// Peer / node not found among connected sessions. 41 + #[error("node not connected: {0}")] 42 + NotConnected(String), 43 + 44 + /// Invalid node name or endpoint id. 45 + #[error("invalid name: {0}")] 46 + InvalidName(String), 47 + 48 + /// iroh endpoint / connection error. 49 + #[error("iroh error: {0}")] 50 + Iroh(String), 51 + 52 + /// TLS configuration or handshake error. 53 + #[error("tls error: {0}")] 54 + Tls(String), 55 + 56 + /// EPMD lookup failed. 57 + #[error("epmd error: {0}")] 58 + Epmd(String), 59 + 60 + /// Session background task stopped. 61 + #[error("session terminated")] 62 + Terminated, 63 + 64 + /// Operation timed out. 65 + #[error("timeout")] 66 + Timeout, 67 + 68 + /// Catch-all for other failures. 69 + #[error(transparent)] 70 + Other(#[from] anyhow::Error), 71 + } 72 + 73 + impl Error { 74 + pub(crate) fn handshake(e: impl ToString) -> Self { 75 + Self::Handshake(e.to_string()) 76 + } 77 + 78 + pub(crate) fn send(e: impl ToString) -> Self { 79 + Self::Send(e.to_string()) 80 + } 81 + 82 + pub(crate) fn recv(e: impl ToString) -> Self { 83 + Self::Recv(e.to_string()) 84 + } 85 + 86 + pub(crate) fn iroh(e: impl ToString) -> Self { 87 + Self::Iroh(e.to_string()) 88 + } 89 + 90 + pub(crate) fn tls(e: impl ToString) -> Self { 91 + Self::Tls(e.to_string()) 92 + } 93 + 94 + pub(crate) fn epmd(e: impl ToString) -> Self { 95 + Self::Epmd(e.to_string()) 96 + } 97 + 98 + pub(crate) fn rpc(e: impl ToString) -> Self { 99 + Self::Rpc(e.to_string()) 100 + } 101 + }
+73
src/lib.rs
··· 1 + //! # erl_iroh 2 + //! 3 + //! Distributed Erlang over [iroh](https://iroh.computer) P2P (QUIC + TLS) and 4 + //! optional classic TCP/TLS, with a small **bot powers** SDK. 5 + //! 6 + //! ## Architecture 7 + //! 8 + //! ```text 9 + //! Bot SDK (powers: rpc, send, introspect, cluster, …) 10 + //! │ 11 + //! Session ── erl_dist handshake + message channel + net ticks 12 + //! │ 13 + //! Transport 14 + //! ├─ iroh (public-key dialing, holepunch, built-in TLS) 15 + //! ├─ tcp (plain, for local / EPMD peers) 16 + //! └─ tls (TCP + rustls mTLS for the security pedants) 17 + //! ``` 18 + //! 19 + //! ## Quick start (bot) 20 + //! 21 + //! ```no_run 22 + //! use erl_iroh::{Bot, Powers}; 23 + //! 24 + //! # async fn demo() -> erl_iroh::Result<()> { 25 + //! let bot = Bot::builder("scout") 26 + //! .cookie("super-secret-cookie") 27 + //! .powers(Powers::all()) // bitflags: every defined power 28 + //! .build() 29 + //! .await?; 30 + //! 31 + //! println!("iroh endpoint id: {}", bot.endpoint_id()); 32 + //! 33 + //! // Dial another bot by iroh EndpointId (base32) 34 + //! // bot.connect_iroh("…endpoint id…").await?; 35 + //! 36 + //! // Or classic BEAM over TLS 37 + //! // bot.connect_tls("app@host", "cookie", tls_config).await?; 38 + //! # Ok(()) 39 + //! # } 40 + //! ``` 41 + 42 + #![warn(missing_docs)] 43 + 44 + pub mod bot; 45 + pub mod error; 46 + pub mod powers; 47 + pub mod session; 48 + pub mod stream; 49 + pub mod term; 50 + pub mod transport; 51 + 52 + pub use bot::{Bot, BotBuilder}; 53 + pub use error::{Error, Result}; 54 + pub use powers::Powers; 55 + pub use session::Session; 56 + pub use transport::tls::TlsConfig; 57 + 58 + /// ALPN identifier for Erlang distribution over iroh. 59 + /// 60 + /// Both peers must advertise this ALPN for the connection to be accepted. 61 + pub const ALPN: &[u8] = b"erl-iroh/1"; 62 + 63 + /// Re-exports of useful `erl_dist` / ETF types. 64 + pub mod dist { 65 + pub use erl_dist::handshake::{ClientSideHandshake, HandshakeStatus, ServerSideHandshake}; 66 + pub use erl_dist::message::{self, Message}; 67 + pub use erl_dist::node::{Creation, LocalNode, NodeName, PeerNode}; 68 + pub use erl_dist::term::{self, Atom, FixInteger, List, Pid, Reference, Term}; 69 + pub use erl_dist::{ 70 + DistributionFlags, HIGHEST_DISTRIBUTION_PROTOCOL_VERSION, 71 + LOWEST_DISTRIBUTION_PROTOCOL_VERSION, 72 + }; 73 + }
+114
src/powers.rs
··· 1 + //! Capability flags ("powers") that bots can be granted. 2 + //! 3 + //! Powers gate high-level SDK operations so a bot only gets the tools it needs. 4 + 5 + use bitflags::bitflags; 6 + 7 + bitflags! { 8 + /// Set of powers a [`crate::Bot`] may exercise. 9 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 10 + pub struct Powers: u32 { 11 + /// Call remote MFA via `erpc` / `rex` (`spawn_request`). 12 + const RPC = 0b0000_0001; 13 + /// Send messages to registered names / pids. 14 + const SEND = 0b0000_0010; 15 + /// Introspect processes, applications, ETS (via RPC helpers). 16 + const INTROSPECT = 0b0000_0100; 17 + /// Connect / disconnect peers (iroh, tcp, tls). 18 + const CLUSTER = 0b0000_1000; 19 + /// Accept inbound distribution connections. 20 + const LISTEN = 0b0001_0000; 21 + /// Evaluate expressions (`erl_eval` style RPC). 22 + const EVAL = 0b0010_0000; 23 + /// Start / stop function tracing on peers. 24 + const TRACE = 0b0100_0000; 25 + } 26 + } 27 + 28 + impl Powers { 29 + /// Safe default for a worker bot: rpc + send + introspect + cluster. 30 + pub fn worker() -> Self { 31 + Self::RPC | Self::SEND | Self::INTROSPECT | Self::CLUSTER 32 + } 33 + 34 + /// Read-only observer: introspect + cluster. 35 + pub fn observer() -> Self { 36 + Self::INTROSPECT | Self::CLUSTER 37 + } 38 + 39 + /// Human-readable list of set flags. 40 + pub fn describe(self) -> String { 41 + let mut names = Vec::new(); 42 + if self.contains(Self::RPC) { 43 + names.push("rpc"); 44 + } 45 + if self.contains(Self::SEND) { 46 + names.push("send"); 47 + } 48 + if self.contains(Self::INTROSPECT) { 49 + names.push("introspect"); 50 + } 51 + if self.contains(Self::CLUSTER) { 52 + names.push("cluster"); 53 + } 54 + if self.contains(Self::LISTEN) { 55 + names.push("listen"); 56 + } 57 + if self.contains(Self::EVAL) { 58 + names.push("eval"); 59 + } 60 + if self.contains(Self::TRACE) { 61 + names.push("trace"); 62 + } 63 + if names.is_empty() { 64 + "none".into() 65 + } else { 66 + names.join("|") 67 + } 68 + } 69 + 70 + /// Return `Ok(())` if `needed` is granted, else [`crate::Error::PowerDenied`]. 71 + pub fn require(self, needed: Powers) -> crate::Result<()> { 72 + if self.contains(needed) { 73 + Ok(()) 74 + } else { 75 + Err(crate::Error::PowerDenied { 76 + needed: power_name(needed), 77 + have: self.describe(), 78 + }) 79 + } 80 + } 81 + } 82 + 83 + impl Default for Powers { 84 + fn default() -> Self { 85 + Self::worker() 86 + } 87 + } 88 + 89 + impl std::fmt::Display for Powers { 90 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 91 + write!(f, "{}", self.describe()) 92 + } 93 + } 94 + 95 + fn power_name(p: Powers) -> &'static str { 96 + // Match single-flag values used at call sites. 97 + if p == Powers::RPC { 98 + "rpc" 99 + } else if p == Powers::SEND { 100 + "send" 101 + } else if p == Powers::INTROSPECT { 102 + "introspect" 103 + } else if p == Powers::CLUSTER { 104 + "cluster" 105 + } else if p == Powers::LISTEN { 106 + "listen" 107 + } else if p == Powers::EVAL { 108 + "eval" 109 + } else if p == Powers::TRACE { 110 + "trace" 111 + } else { 112 + "unknown" 113 + } 114 + }
+504
src/session.rs
··· 1 + //! Post-handshake distribution session: message channel, ticks, RPC. 2 + 3 + use crate::error::{Error, Result}; 4 + use crate::stream::DistStream; 5 + use crate::term; 6 + use erl_dist::handshake::{ClientSideHandshake, HandshakeStatus, ServerSideHandshake}; 7 + use erl_dist::message::{self, Message, Receiver, Sender}; 8 + use erl_dist::node::{Creation, LocalNode, NodeName, PeerNode}; 9 + use erl_dist::term::{Atom, FixInteger, List, Mfa, Pid, PidOrAtom, Reference, Term}; 10 + use erl_dist::{DistributionFlags, HIGHEST_DISTRIBUTION_PROTOCOL_VERSION}; 11 + use futures::channel::{mpsc, oneshot}; 12 + use futures::{FutureExt, StreamExt}; 13 + use std::collections::HashMap; 14 + use std::sync::Arc; 15 + use std::time::Duration; 16 + use tokio::sync::RwLock; 17 + use tracing::{debug, warn}; 18 + 19 + const TICK_INTERVAL: Duration = Duration::from_secs(15); 20 + const DEFAULT_RPC_TIMEOUT: Duration = Duration::from_secs(30); 21 + 22 + /// How we identify a connected peer. 23 + #[derive(Debug, Clone, PartialEq, Eq, Hash)] 24 + pub enum PeerId { 25 + /// Erlang node name (`foo@host`). 26 + Node(String), 27 + /// iroh endpoint id (base32). 28 + Endpoint(String), 29 + } 30 + 31 + impl std::fmt::Display for PeerId { 32 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 + match self { 34 + Self::Node(n) => write!(f, "{n}"), 35 + Self::Endpoint(e) => write!(f, "iroh:{e}"), 36 + } 37 + } 38 + } 39 + 40 + /// A live distribution link to one peer. 41 + pub struct Session { 42 + local: LocalNode, 43 + peer: PeerNode, 44 + peer_id: PeerId, 45 + handle: SessionHandle, 46 + _runner: tokio::task::JoinHandle<()>, 47 + } 48 + 49 + /// Cloneable handle used to talk to a session's background task. 50 + #[derive(Clone)] 51 + pub struct SessionHandle { 52 + req_tx: mpsc::Sender<SessionReq>, 53 + local_name: String, 54 + peer_name: String, 55 + } 56 + 57 + enum SessionReq { 58 + Rpc { 59 + module: Atom, 60 + function: Atom, 61 + args: List, 62 + reply: oneshot::Sender<Result<Term>>, 63 + }, 64 + Send { 65 + to: Atom, 66 + msg: Term, 67 + reply: oneshot::Sender<Result<()>>, 68 + }, 69 + Raw { 70 + msg: Message, 71 + reply: oneshot::Sender<Result<()>>, 72 + }, 73 + } 74 + 75 + impl Session { 76 + /// Client-side: handshake as a connecting node over an established stream. 77 + pub async fn connect( 78 + stream: DistStream, 79 + local_name: &str, 80 + cookie: &str, 81 + peer_id: PeerId, 82 + ) -> Result<Self> { 83 + let local_node_name: NodeName = local_name 84 + .parse() 85 + .map_err(|e: erl_dist::node::NodeNameError| Error::InvalidName(e.to_string()))?; 86 + 87 + let mut local = LocalNode::new(local_node_name, Creation::random()); 88 + local.flags |= DistributionFlags::NAME_ME; 89 + local.flags |= DistributionFlags::SPAWN; 90 + local.flags |= DistributionFlags::DIST_MONITOR; 91 + local.flags |= DistributionFlags::DIST_MONITOR_NAME; 92 + local.flags |= DistributionFlags::EXPORT_PTR_TAG; 93 + local.flags |= DistributionFlags::BIT_BINARIES; 94 + local.flags |= DistributionFlags::NEW_FLOATS; 95 + local.flags |= DistributionFlags::FUN_TAGS; 96 + local.flags |= DistributionFlags::NEW_FUN_TAGS; 97 + local.flags |= DistributionFlags::EXTENDED_PIDS_PORTS; 98 + local.flags |= DistributionFlags::UTF8_ATOMS; 99 + local.flags |= DistributionFlags::MAP_TAGS; 100 + local.flags |= DistributionFlags::BIG_CREATION; 101 + local.flags |= DistributionFlags::HANDSHAKE_23; 102 + local.flags |= DistributionFlags::UNLINK_ID; 103 + local.flags |= DistributionFlags::V4_NC; 104 + 105 + let mut handshake = ClientSideHandshake::new(stream, local.clone(), cookie); 106 + let status = handshake 107 + .execute_send_name(HIGHEST_DISTRIBUTION_PROTOCOL_VERSION) 108 + .await 109 + .map_err(Error::handshake)?; 110 + 111 + match status { 112 + HandshakeStatus::Ok | HandshakeStatus::OkSimultaneous => {} 113 + HandshakeStatus::Named { name, creation } => { 114 + local.name = NodeName::new(&name, local.name.host()) 115 + .map_err(|e| Error::InvalidName(e.to_string()))?; 116 + local.creation = creation; 117 + } 118 + HandshakeStatus::Alive => { 119 + // continue 120 + } 121 + other => { 122 + return Err(Error::handshake(format!("unexpected status: {other:?}"))); 123 + } 124 + } 125 + 126 + let (connection, peer) = handshake 127 + .execute_rest(true) 128 + .await 129 + .map_err(Error::handshake)?; 130 + 131 + debug!(peer = %peer.name, "handshake complete (client)"); 132 + Self::from_connection(connection, local, peer, peer_id) 133 + } 134 + 135 + /// Server-side: accept a handshake on an inbound stream. 136 + pub async fn accept( 137 + stream: DistStream, 138 + local_name: &str, 139 + cookie: &str, 140 + peer_id: PeerId, 141 + ) -> Result<Self> { 142 + let local_node_name: NodeName = local_name 143 + .parse() 144 + .map_err(|e: erl_dist::node::NodeNameError| Error::InvalidName(e.to_string()))?; 145 + 146 + let mut local = LocalNode::new(local_node_name, Creation::random()); 147 + local.flags |= DistributionFlags::SPAWN; 148 + local.flags |= DistributionFlags::DIST_MONITOR; 149 + local.flags |= DistributionFlags::DIST_MONITOR_NAME; 150 + local.flags |= DistributionFlags::HANDSHAKE_23; 151 + local.flags |= DistributionFlags::UNLINK_ID; 152 + local.flags |= DistributionFlags::V4_NC; 153 + local.flags |= DistributionFlags::UTF8_ATOMS; 154 + local.flags |= DistributionFlags::MAP_TAGS; 155 + local.flags |= DistributionFlags::BIG_CREATION; 156 + 157 + let mut handshake = ServerSideHandshake::new(stream, local.clone(), cookie); 158 + let peer_name = handshake 159 + .execute_recv_name() 160 + .await 161 + .map_err(Error::handshake)?; 162 + 163 + let status = if peer_name.is_none() { 164 + // Peer asked for a dynamic name — hand one out. 165 + use erl_dist::node::Creation; 166 + HandshakeStatus::Named { 167 + name: format!("dyn-{}", rand::random::<u32>()), 168 + creation: Creation::random(), 169 + } 170 + } else { 171 + HandshakeStatus::Ok 172 + }; 173 + 174 + let (connection, peer) = handshake 175 + .execute_rest(status) 176 + .await 177 + .map_err(Error::handshake)?; 178 + 179 + debug!(peer = %peer.name, "handshake complete (server)"); 180 + Self::from_connection(connection, local, peer, peer_id) 181 + } 182 + 183 + fn from_connection( 184 + connection: DistStream, 185 + local: LocalNode, 186 + peer: PeerNode, 187 + peer_id: PeerId, 188 + ) -> Result<Self> { 189 + let flags = local.flags & peer.flags; 190 + let (msg_tx, msg_rx) = message::channel(connection, flags); 191 + let (req_tx, req_rx) = mpsc::channel(256); 192 + 193 + let handle = SessionHandle { 194 + req_tx, 195 + local_name: local.name.to_string(), 196 + peer_name: peer.name.to_string(), 197 + }; 198 + 199 + let runner_state = Runner { 200 + msg_tx, 201 + msg_rx: Some(msg_rx), 202 + req_rx: Some(req_rx), 203 + local: local.clone(), 204 + ongoing: HashMap::new(), 205 + }; 206 + 207 + let _runner = tokio::spawn(async move { 208 + if let Err(e) = runner_state.run().await { 209 + warn!(error = %e, "session runner stopped"); 210 + } 211 + }); 212 + 213 + Ok(Self { 214 + local, 215 + peer, 216 + peer_id, 217 + handle, 218 + _runner, 219 + }) 220 + } 221 + 222 + /// Cloneable handle for RPC / send. 223 + pub fn handle(&self) -> SessionHandle { 224 + self.handle.clone() 225 + } 226 + 227 + /// Local node info after handshake (may have been renamed via NAME_ME). 228 + pub fn local_node(&self) -> &LocalNode { 229 + &self.local 230 + } 231 + 232 + /// Peer node info. 233 + pub fn peer_node(&self) -> &PeerNode { 234 + &self.peer 235 + } 236 + 237 + /// Peer identifier used by the bot registry. 238 + pub fn peer_id(&self) -> &PeerId { 239 + &self.peer_id 240 + } 241 + } 242 + 243 + impl SessionHandle { 244 + /// Peer Erlang node name. 245 + pub fn peer_name(&self) -> &str { 246 + &self.peer_name 247 + } 248 + 249 + /// Local Erlang node name. 250 + pub fn local_name(&self) -> &str { 251 + &self.local_name 252 + } 253 + 254 + /// Remote procedure call: `module:function(args...)`. 255 + pub async fn rpc( 256 + &self, 257 + module: impl Into<Atom>, 258 + function: impl Into<Atom>, 259 + args: List, 260 + ) -> Result<Term> { 261 + self.rpc_timeout(module, function, args, DEFAULT_RPC_TIMEOUT) 262 + .await 263 + } 264 + 265 + /// RPC with an explicit timeout. 266 + pub async fn rpc_timeout( 267 + &self, 268 + module: impl Into<Atom>, 269 + function: impl Into<Atom>, 270 + args: List, 271 + timeout: Duration, 272 + ) -> Result<Term> { 273 + let (reply, rx) = oneshot::channel(); 274 + self.req_tx 275 + .clone() 276 + .try_send(SessionReq::Rpc { 277 + module: module.into(), 278 + function: function.into(), 279 + args, 280 + reply, 281 + }) 282 + .map_err(|_| Error::Terminated)?; 283 + 284 + match tokio::time::timeout(timeout, rx).await { 285 + Ok(Ok(r)) => r, 286 + Ok(Err(_)) => Err(Error::Terminated), 287 + Err(_) => Err(Error::Timeout), 288 + } 289 + } 290 + 291 + /// Send a message to a registered process name on the peer. 292 + pub async fn send(&self, to: impl Into<Atom>, msg: Term) -> Result<()> { 293 + let (reply, rx) = oneshot::channel(); 294 + self.req_tx 295 + .clone() 296 + .try_send(SessionReq::Send { 297 + to: to.into(), 298 + msg, 299 + reply, 300 + }) 301 + .map_err(|_| Error::Terminated)?; 302 + rx.await.map_err(|_| Error::Terminated)? 303 + } 304 + 305 + /// Send a raw distribution message. 306 + pub async fn send_raw(&self, msg: Message) -> Result<()> { 307 + let (reply, rx) = oneshot::channel(); 308 + self.req_tx 309 + .clone() 310 + .try_send(SessionReq::Raw { msg, reply }) 311 + .map_err(|_| Error::Terminated)?; 312 + rx.await.map_err(|_| Error::Terminated)? 313 + } 314 + } 315 + 316 + struct Runner { 317 + msg_tx: Sender<DistStream>, 318 + msg_rx: Option<Receiver<DistStream>>, 319 + req_rx: Option<mpsc::Receiver<SessionReq>>, 320 + local: LocalNode, 321 + ongoing: HashMap<Reference, oneshot::Sender<Result<Term>>>, 322 + } 323 + 324 + impl Runner { 325 + async fn run(mut self) -> Result<()> { 326 + let mut req_rx = self.req_rx.take().expect("req_rx").into_future(); 327 + let msg_rx = self.msg_rx.take().expect("msg_rx"); 328 + let mut msg_fut = msg_rx.recv_owned().boxed(); 329 + let mut tick = tokio::time::interval(TICK_INTERVAL); 330 + tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); 331 + 332 + loop { 333 + tokio::select! { 334 + _ = tick.tick() => { 335 + if let Err(e) = self.msg_tx.send(Message::Tick).await { 336 + return Err(Error::send(e)); 337 + } 338 + } 339 + result = &mut msg_fut => { 340 + let (msg, next_rx) = result.map_err(Error::recv)?; 341 + self.handle_msg(msg).await?; 342 + msg_fut = next_rx.recv_owned().boxed(); 343 + } 344 + (req, rest) = &mut req_rx => { 345 + match req { 346 + Some(r) => { 347 + self.handle_req(r).await?; 348 + req_rx = rest.into_future(); 349 + } 350 + None => { 351 + debug!("session request channel closed"); 352 + break; 353 + } 354 + } 355 + } 356 + } 357 + } 358 + Ok(()) 359 + } 360 + 361 + async fn handle_req(&mut self, req: SessionReq) -> Result<()> { 362 + match req { 363 + SessionReq::Rpc { 364 + module, 365 + function, 366 + args, 367 + reply, 368 + } => { 369 + let req_id = self.make_ref(); 370 + let spawn_request = Message::spawn_request( 371 + req_id.clone(), 372 + self.pid(), 373 + self.pid(), 374 + Mfa { 375 + module: "erpc".into(), 376 + function: "execute_call".into(), 377 + arity: FixInteger::from(4), 378 + }, 379 + List::from(vec![Atom::from("monitor").into()]), 380 + List::from(vec![ 381 + self.make_ref().into(), 382 + module.into(), 383 + function.into(), 384 + args.into(), 385 + ]), 386 + ); 387 + if let Err(e) = self.msg_tx.send(spawn_request).await { 388 + let _ = reply.send(Err(Error::send(e))); 389 + } else { 390 + self.ongoing.insert(req_id, reply); 391 + } 392 + } 393 + SessionReq::Send { to, msg, reply } => { 394 + let m = Message::reg_send(self.pid(), to, msg); 395 + let r = self.msg_tx.send(m).await.map_err(Error::send); 396 + let _ = reply.send(r); 397 + } 398 + SessionReq::Raw { msg, reply } => { 399 + let r = self.msg_tx.send(msg).await.map_err(Error::send); 400 + let _ = reply.send(r); 401 + } 402 + } 403 + Ok(()) 404 + } 405 + 406 + async fn handle_msg(&mut self, msg: Message) -> Result<()> { 407 + match msg { 408 + Message::Tick => Ok(()), 409 + Message::SpawnReply(msg) => { 410 + if let PidOrAtom::Atom(reason) = msg.result { 411 + // Find and fail the matching request if still pending. 412 + // The monitor exit will also fire; if spawn failed, clean up. 413 + if let Some((_, reply)) = self 414 + .ongoing 415 + .iter() 416 + .find(|(k, _)| k.id == msg.req_id.id) 417 + .map(|(k, _)| k.clone()) 418 + .and_then(|k| self.ongoing.remove(&k).map(|r| (k, r))) 419 + { 420 + let _ = reply.send(Err(Error::rpc(format!( 421 + "spawn_request failed: {}", 422 + reason.name 423 + )))); 424 + } 425 + } 426 + Ok(()) 427 + } 428 + Message::MonitorPExit(msg) => { 429 + if let Some(reply) = self.ongoing.remove(&msg.reference) { 430 + let _ = reply.send(decode_erpc_result(msg.reason)); 431 + } 432 + Ok(()) 433 + } 434 + other => { 435 + debug!(?other, "ignored distribution message"); 436 + Ok(()) 437 + } 438 + } 439 + } 440 + 441 + fn node(&self) -> Atom { 442 + Atom::from(self.local.name.to_string()) 443 + } 444 + 445 + fn pid(&self) -> Pid { 446 + Pid::new(self.node(), 0, 0, self.local.creation.get()) 447 + } 448 + 449 + fn make_ref(&self) -> Reference { 450 + term::make_ref(&self.local.name.to_string(), self.local.creation.get()) 451 + } 452 + } 453 + 454 + /// Decode `{Ref, return, Value}` / error shapes from `erpc:execute_call`. 455 + fn decode_erpc_result(reason: Term) -> Result<Term> { 456 + // Success shape: {CallerRef, return, Value} OR just the exit reason from monitor 457 + // erpc:execute_call exits with {Ref, return, Result} on success. 458 + if let Term::Tuple(tup) = &reason { 459 + if tup.elements.len() == 3 { 460 + if let Term::Atom(kind) = &tup.elements[1] { 461 + if kind.name == "return" { 462 + return Ok(tup.elements[2].clone()); 463 + } 464 + if kind.name == "throw" || kind.name == "error" || kind.name == "exit" { 465 + return Err(Error::rpc(format!("{reason}"))); 466 + } 467 + } 468 + } 469 + } 470 + // Some paths just return the value as the exit reason directly. 471 + Ok(reason) 472 + } 473 + 474 + /// Registry of active sessions, shared across the bot. 475 + #[derive(Clone, Default)] 476 + pub struct SessionRegistry { 477 + inner: Arc<RwLock<HashMap<String, SessionHandle>>>, 478 + } 479 + 480 + impl SessionRegistry { 481 + /// Insert a session under its peer name and peer_id display key. 482 + pub async fn insert(&self, session: &Session) { 483 + let mut g = self.inner.write().await; 484 + let handle = session.handle(); 485 + g.insert(session.peer_node().name.to_string(), handle.clone()); 486 + g.insert(session.peer_id().to_string(), handle); 487 + } 488 + 489 + /// Remove by any known key. 490 + pub async fn remove(&self, key: &str) { 491 + let mut g = self.inner.write().await; 492 + g.remove(key); 493 + } 494 + 495 + /// Lookup handle. 496 + pub async fn get(&self, key: &str) -> Option<SessionHandle> { 497 + self.inner.read().await.get(key).cloned() 498 + } 499 + 500 + /// All known keys. 501 + pub async fn list(&self) -> Vec<String> { 502 + self.inner.read().await.keys().cloned().collect() 503 + } 504 + }
+152
src/stream.rs
··· 1 + //! Clonable duplex byte streams that implement `futures_io` traits. 2 + //! 3 + //! `erl_dist` requires `AsyncRead + AsyncWrite + Unpin + Clone` on the carrier 4 + //! (smol's `TcpStream` is clonable; iroh bi-streams and tokio-rustls streams are 5 + //! not). We wrap any split read/write half in a shared duplex. 6 + //! 7 + //! Layout: `async_dup::Arc<async_dup::Mutex<Combined>>` — Arc is `Clone`, and 8 + //! both Arc and Mutex forward futures-io traits when the inner type does. 9 + 10 + use async_dup::{Arc as DupArc, Mutex as DupMutex}; 11 + use futures::io::{AsyncRead, AsyncWrite}; 12 + use std::io; 13 + use std::pin::Pin; 14 + use std::task::{Context, Poll}; 15 + use tokio::io::{AsyncRead as TokioRead, AsyncWrite as TokioWrite, ReadBuf}; 16 + 17 + /// A clonable futures-io duplex built from independent tokio read/write halves. 18 + #[derive(Clone)] 19 + pub struct DistStream { 20 + inner: DupArc<DupMutex<Combined>>, 21 + } 22 + 23 + /// Type-erased tokio read half. 24 + type DynRead = Box<dyn TokioRead + Unpin + Send>; 25 + /// Type-erased tokio write half. 26 + type DynWrite = Box<dyn TokioWrite + Unpin + Send>; 27 + 28 + struct Combined { 29 + read: DynRead, 30 + write: DynWrite, 31 + } 32 + 33 + impl DistStream { 34 + /// Wrap independent tokio async read/write halves. 35 + pub fn from_split<R, W>(read: R, write: W) -> Self 36 + where 37 + R: TokioRead + Unpin + Send + 'static, 38 + W: TokioWrite + Unpin + Send + 'static, 39 + { 40 + Self { 41 + inner: DupArc::new(DupMutex::new(Combined { 42 + read: Box::new(read), 43 + write: Box::new(write), 44 + })), 45 + } 46 + } 47 + 48 + /// Wrap a single tokio type that is both `AsyncRead` and `AsyncWrite`. 49 + pub fn from_rw<T>(stream: T) -> Self 50 + where 51 + T: TokioRead + TokioWrite + Unpin + Send + 'static, 52 + { 53 + let (read, write) = tokio::io::split(stream); 54 + Self::from_split(read, write) 55 + } 56 + } 57 + 58 + impl TokioRead for Combined { 59 + fn poll_read( 60 + mut self: Pin<&mut Self>, 61 + cx: &mut Context<'_>, 62 + buf: &mut ReadBuf<'_>, 63 + ) -> Poll<io::Result<()>> { 64 + Pin::new(&mut self.read).poll_read(cx, buf) 65 + } 66 + } 67 + 68 + impl TokioWrite for Combined { 69 + fn poll_write( 70 + mut self: Pin<&mut Self>, 71 + cx: &mut Context<'_>, 72 + buf: &[u8], 73 + ) -> Poll<io::Result<usize>> { 74 + Pin::new(&mut self.write).poll_write(cx, buf) 75 + } 76 + 77 + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { 78 + Pin::new(&mut self.write).poll_flush(cx) 79 + } 80 + 81 + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { 82 + Pin::new(&mut self.write).poll_shutdown(cx) 83 + } 84 + } 85 + 86 + // Bridge tokio traits → futures_io for erl_dist. 87 + impl AsyncRead for Combined { 88 + fn poll_read( 89 + self: Pin<&mut Self>, 90 + cx: &mut Context<'_>, 91 + buf: &mut [u8], 92 + ) -> Poll<io::Result<usize>> { 93 + let mut read_buf = ReadBuf::new(buf); 94 + match TokioRead::poll_read(self, cx, &mut read_buf) { 95 + Poll::Ready(Ok(())) => Poll::Ready(Ok(read_buf.filled().len())), 96 + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), 97 + Poll::Pending => Poll::Pending, 98 + } 99 + } 100 + } 101 + 102 + impl AsyncWrite for Combined { 103 + fn poll_write( 104 + self: Pin<&mut Self>, 105 + cx: &mut Context<'_>, 106 + buf: &[u8], 107 + ) -> Poll<io::Result<usize>> { 108 + TokioWrite::poll_write(self, cx, buf) 109 + } 110 + 111 + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { 112 + TokioWrite::poll_flush(self, cx) 113 + } 114 + 115 + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { 116 + TokioWrite::poll_shutdown(self, cx) 117 + } 118 + } 119 + 120 + impl AsyncRead for DistStream { 121 + fn poll_read( 122 + mut self: Pin<&mut Self>, 123 + cx: &mut Context<'_>, 124 + buf: &mut [u8], 125 + ) -> Poll<io::Result<usize>> { 126 + Pin::new(&mut self.inner).poll_read(cx, buf) 127 + } 128 + } 129 + 130 + impl AsyncWrite for DistStream { 131 + fn poll_write( 132 + mut self: Pin<&mut Self>, 133 + cx: &mut Context<'_>, 134 + buf: &[u8], 135 + ) -> Poll<io::Result<usize>> { 136 + Pin::new(&mut self.inner).poll_write(cx, buf) 137 + } 138 + 139 + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { 140 + Pin::new(&mut self.inner).poll_flush(cx) 141 + } 142 + 143 + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { 144 + Pin::new(&mut self.inner).poll_close(cx) 145 + } 146 + } 147 + 148 + impl std::fmt::Debug for DistStream { 149 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 150 + f.debug_struct("DistStream").finish_non_exhaustive() 151 + } 152 + }
+90
src/term.rs
··· 1 + //! Small helpers for building and reading Erlang terms. 2 + 3 + use erl_dist::term::{Atom, FixInteger, List, Pid, Reference, Term}; 4 + 5 + /// Atom from a string. 6 + pub fn atom(name: impl AsRef<str>) -> Atom { 7 + Atom::from(name.as_ref()) 8 + } 9 + 10 + /// Empty list `[]`. 11 + pub fn nil() -> List { 12 + List::nil() 13 + } 14 + 15 + /// Proper list from a sequence of terms. 16 + pub fn list(items: impl IntoIterator<Item = Term>) -> List { 17 + List::from(items.into_iter().collect::<Vec<_>>()) 18 + } 19 + 20 + /// Small integer. 21 + pub fn int(n: i32) -> FixInteger { 22 + FixInteger::from(n) 23 + } 24 + 25 + /// 2-tuple `{a, b}`. 26 + pub fn tuple2(a: impl Into<Term>, b: impl Into<Term>) -> Term { 27 + Term::from(eetf::Tuple::from(vec![a.into(), b.into()])) 28 + } 29 + 30 + /// 3-tuple `{a, b, c}`. 31 + pub fn tuple3(a: impl Into<Term>, b: impl Into<Term>, c: impl Into<Term>) -> Term { 32 + Term::from(eetf::Tuple::from(vec![a.into(), b.into(), c.into()])) 33 + } 34 + 35 + /// 4-tuple. 36 + pub fn tuple4( 37 + a: impl Into<Term>, 38 + b: impl Into<Term>, 39 + c: impl Into<Term>, 40 + d: impl Into<Term>, 41 + ) -> Term { 42 + Term::from(eetf::Tuple::from(vec![a.into(), b.into(), c.into(), d.into()])) 43 + } 44 + 45 + /// N-tuple from a vec of terms. 46 + pub fn tuple(elements: Vec<Term>) -> Term { 47 + Term::from(eetf::Tuple::from(elements)) 48 + } 49 + 50 + /// Binary from bytes. 51 + pub fn binary(data: impl AsRef<[u8]>) -> Term { 52 + Term::from(eetf::Binary::from(data.as_ref().to_vec())) 53 + } 54 + 55 + /// String as a char list (Erlang string). 56 + pub fn string(s: impl AsRef<str>) -> Term { 57 + Term::from(eetf::List::from( 58 + s.as_ref() 59 + .chars() 60 + .map(|c| Term::from(FixInteger::from(c as i32))) 61 + .collect::<Vec<_>>(), 62 + )) 63 + } 64 + 65 + /// Local pid for this node (serial 0 — the "bot process"). 66 + pub fn local_pid(node: &str, creation: u32) -> Pid { 67 + Pid::new(node.to_string(), 0, 0, creation) 68 + } 69 + 70 + /// Fresh distribution reference. 71 + pub fn make_ref(node: &str, creation: u32) -> Reference { 72 + Reference { 73 + node: Atom::from(node), 74 + id: vec![rand::random(), rand::random(), rand::random()], 75 + creation, 76 + } 77 + } 78 + 79 + /// Try to extract an atom name. 80 + pub fn as_atom(term: &Term) -> Option<&str> { 81 + match term { 82 + Term::Atom(a) => Some(a.name.as_str()), 83 + _ => None, 84 + } 85 + } 86 + 87 + /// Pretty-print a term (debug-ish, good enough for logs). 88 + pub fn format_term(term: &Term) -> String { 89 + format!("{term}") 90 + }
+105
src/transport/iroh.rs
··· 1 + //! iroh P2P transport for Erlang distribution. 2 + //! 3 + //! Connections are QUIC with TLS 1.3 identity (ed25519 endpoint keys). The 4 + //! Erlang cookie handshake still runs on top for BEAM-compatible auth. 5 + 6 + use crate::error::{Error, Result}; 7 + use crate::stream::DistStream; 8 + use crate::ALPN; 9 + use iroh::endpoint::presets; 10 + use iroh::{Endpoint, EndpointAddr, EndpointId, SecretKey}; 11 + use std::str::FromStr; 12 + use std::sync::Arc; 13 + use tracing::{debug, info}; 14 + 15 + /// Manages an iroh [`Endpoint`] that speaks the `erl-iroh/1` ALPN. 16 + #[derive(Clone, Debug)] 17 + pub struct IrohTransport { 18 + endpoint: Endpoint, 19 + } 20 + 21 + impl IrohTransport { 22 + /// Bind a new endpoint with a random secret key (default N0 relays). 23 + pub async fn bind() -> Result<Self> { 24 + Self::bind_with_key(SecretKey::generate()).await 25 + } 26 + 27 + /// Bind with an explicit secret key (stable identity across restarts). 28 + pub async fn bind_with_key(secret: SecretKey) -> Result<Self> { 29 + let endpoint = Endpoint::builder(presets::N0) 30 + .secret_key(secret) 31 + .alpns(vec![ALPN.to_vec()]) 32 + .bind() 33 + .await 34 + .map_err(|e| Error::iroh(e))?; 35 + info!(id = %endpoint.id(), "iroh endpoint bound"); 36 + Ok(Self { endpoint }) 37 + } 38 + 39 + /// This endpoint's public id (share this so peers can dial you). 40 + pub fn endpoint_id(&self) -> EndpointId { 41 + self.endpoint.id() 42 + } 43 + 44 + /// Full addressing info (id + relay / direct addrs). 45 + pub fn endpoint_addr(&self) -> EndpointAddr { 46 + self.endpoint.addr() 47 + } 48 + 49 + /// Underlying iroh endpoint (for advanced use). 50 + pub fn endpoint(&self) -> &Endpoint { 51 + &self.endpoint 52 + } 53 + 54 + /// Dial a peer by endpoint id string (base32) and open a dist stream. 55 + pub async fn connect(&self, peer: &str) -> Result<DistStream> { 56 + let id = EndpointId::from_str(peer).map_err(|e| Error::InvalidName(e.to_string()))?; 57 + self.connect_id(id).await 58 + } 59 + 60 + /// Dial a peer by [`EndpointId`]. 61 + pub async fn connect_id(&self, id: EndpointId) -> Result<DistStream> { 62 + debug!(%id, "connecting over iroh"); 63 + let conn = self 64 + .endpoint 65 + .connect(id, ALPN) 66 + .await 67 + .map_err(|e| Error::iroh(e))?; 68 + let (send, recv) = conn.open_bi().await.map_err(|e| Error::iroh(e))?; 69 + Ok(DistStream::from_split(recv, send)) 70 + } 71 + 72 + /// Dial with a full [`EndpointAddr`] (id + relay/direct hints). 73 + pub async fn connect_addr(&self, addr: EndpointAddr) -> Result<DistStream> { 74 + debug!(id = %addr.id, "connecting over iroh (with addr hints)"); 75 + let conn = self 76 + .endpoint 77 + .connect(addr, ALPN) 78 + .await 79 + .map_err(|e| Error::iroh(e))?; 80 + let (send, recv) = conn.open_bi().await.map_err(|e| Error::iroh(e))?; 81 + Ok(DistStream::from_split(recv, send)) 82 + } 83 + 84 + /// Accept one inbound connection and return the dist stream + remote id. 85 + pub async fn accept(&self) -> Result<(DistStream, EndpointId)> { 86 + let incoming = self 87 + .endpoint 88 + .accept() 89 + .await 90 + .ok_or_else(|| Error::iroh("endpoint closed"))?; 91 + let conn = incoming.await.map_err(|e| Error::iroh(e))?; 92 + let remote = conn.remote_id(); 93 + let (send, recv) = conn.accept_bi().await.map_err(|e| Error::iroh(e))?; 94 + debug!(%remote, "accepted iroh connection"); 95 + Ok((DistStream::from_split(recv, send), remote)) 96 + } 97 + 98 + /// Gracefully close the endpoint. 99 + pub async fn close(self) { 100 + self.endpoint.close().await; 101 + } 102 + } 103 + 104 + /// Shared handle used by accept loops. 105 + pub type SharedIroh = Arc<IrohTransport>;
+13
src/transport/mod.rs
··· 1 + //! Transport carriers for the Erlang distribution handshake. 2 + //! 3 + //! - [`iroh`] — P2P QUIC dialed by public key (TLS built-in) 4 + //! - [`tcp`] — classic TCP (optionally after EPMD lookup) 5 + //! - [`tls`] — TCP wrapped in rustls for the security pedants 6 + 7 + pub mod iroh; 8 + pub mod tcp; 9 + pub mod tls; 10 + 11 + pub use iroh::IrohTransport; 12 + pub use tcp::TcpTransport; 13 + pub use tls::{TlsConfig, TlsTransport};
+74
src/transport/tcp.rs
··· 1 + //! Classic TCP transport (+ optional EPMD lookup). 2 + 3 + use crate::error::{Error, Result}; 4 + use crate::stream::DistStream; 5 + use erl_dist::epmd::DEFAULT_EPMD_PORT; 6 + use erl_dist::node::NodeName; 7 + use std::net::SocketAddr; 8 + use tokio::io::{AsyncReadExt, AsyncWriteExt}; 9 + use tokio::net::TcpStream; 10 + use tracing::debug; 11 + 12 + /// TCP carrier helpers. 13 + pub struct TcpTransport; 14 + 15 + impl TcpTransport { 16 + /// Connect to `host:port` and wrap as a [`DistStream`]. 17 + pub async fn connect(host: &str, port: u16) -> Result<DistStream> { 18 + debug!(%host, port, "tcp connect"); 19 + let stream = TcpStream::connect((host, port)).await?; 20 + stream.set_nodelay(true)?; 21 + Ok(DistStream::from_rw(stream)) 22 + } 23 + 24 + /// Connect to a socket address. 25 + pub async fn connect_addr(addr: SocketAddr) -> Result<DistStream> { 26 + debug!(%addr, "tcp connect"); 27 + let stream = TcpStream::connect(addr).await?; 28 + stream.set_nodelay(true)?; 29 + Ok(DistStream::from_rw(stream)) 30 + } 31 + 32 + /// Resolve a BEAM node name via EPMD, then TCP-connect to its port. 33 + /// 34 + /// `node` is a full name like `foo@localhost`. 35 + pub async fn connect_node(node: &str) -> Result<DistStream> { 36 + let name: NodeName = node 37 + .parse() 38 + .map_err(|e: erl_dist::node::NodeNameError| Error::InvalidName(e.to_string()))?; 39 + let port = epmd_lookup(name.host(), name.name()).await?; 40 + Self::connect(name.host(), port).await 41 + } 42 + 43 + /// Look up a short node name on a host's EPMD. 44 + pub async fn epmd_port(host: &str, short_name: &str) -> Result<u16> { 45 + epmd_lookup(host, short_name).await 46 + } 47 + } 48 + 49 + async fn epmd_lookup(host: &str, short_name: &str) -> Result<u16> { 50 + // Minimal EPMD PORT_PLEASE2 over tokio TCP. 51 + let mut stream = TcpStream::connect((host, DEFAULT_EPMD_PORT)).await?; 52 + // PORT_PLEASE2_REQ: 122, then name bytes. Length-prefixed packet (2-byte BE). 53 + let mut body = Vec::with_capacity(1 + short_name.len()); 54 + body.push(122u8); // PORT_PLEASE2_REQ 55 + body.extend_from_slice(short_name.as_bytes()); 56 + let mut packet = Vec::with_capacity(2 + body.len()); 57 + packet.extend_from_slice(&(body.len() as u16).to_be_bytes()); 58 + packet.extend_from_slice(&body); 59 + stream.write_all(&packet).await?; 60 + 61 + // Response is NOT length-prefixed: result(1) [+ port(2) + …] or result≠0 (not found) 62 + let mut result = [0u8; 1]; 63 + stream.read_exact(&mut result).await?; 64 + if result[0] != 0 { 65 + return Err(Error::epmd(format!( 66 + "node `{short_name}` not registered on EPMD at {host}" 67 + ))); 68 + } 69 + let mut port_buf = [0u8; 2]; 70 + stream.read_exact(&mut port_buf).await?; 71 + let port = u16::from_be_bytes(port_buf); 72 + debug!(%host, %short_name, port, "epmd lookup ok"); 73 + Ok(port) 74 + }
+261
src/transport/tls.rs
··· 1 + //! TCP + rustls transport for classic BEAM peers (and pedants). 2 + //! 3 + //! Layering: 4 + //! ```text 5 + //! erl_dist handshake / messages 6 + //! │ 7 + //! rustls TLS 1.2/1.3 (optional mutual auth) 8 + //! │ 9 + //! TCP 10 + //! ``` 11 + //! 12 + //! iroh already encrypts with TLS under QUIC; this module is for talking to 13 + //! (or proxying) traditional TCP listeners that require TLS. 14 + 15 + use crate::error::{Error, Result}; 16 + use crate::stream::DistStream; 17 + use rustls::ClientConfig as RustlsClientConfig; 18 + use rustls::ServerConfig as RustlsServerConfig; 19 + use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName}; 20 + use rustls::RootCertStore; 21 + use std::fs::File; 22 + use std::io::BufReader; 23 + use std::path::Path; 24 + use std::sync::Arc; 25 + use tokio::net::{TcpListener, TcpStream}; 26 + use tokio_rustls::{TlsAcceptor, TlsConnector}; 27 + use tracing::debug; 28 + 29 + /// TLS material for client and/or server roles. 30 + #[derive(Clone)] 31 + pub struct TlsConfig { 32 + /// Client connector (required to dial). 33 + pub client: Option<Arc<RustlsClientConfig>>, 34 + /// Server acceptor (required to accept). 35 + pub server: Option<Arc<RustlsServerConfig>>, 36 + } 37 + 38 + impl std::fmt::Debug for TlsConfig { 39 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 + f.debug_struct("TlsConfig") 41 + .field("client", &self.client.is_some()) 42 + .field("server", &self.server.is_some()) 43 + .finish() 44 + } 45 + } 46 + 47 + impl TlsConfig { 48 + /// Build a client config that trusts the webpki root store (public CAs). 49 + pub fn client_webpki() -> Result<Self> { 50 + let mut roots = RootCertStore::empty(); 51 + roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); 52 + let config = RustlsClientConfig::builder() 53 + .with_root_certificates(roots) 54 + .with_no_client_auth(); 55 + Ok(Self { 56 + client: Some(Arc::new(config)), 57 + server: None, 58 + }) 59 + } 60 + 61 + /// Client that trusts a custom CA PEM file (and optionally presents a client cert). 62 + pub fn client_with_ca( 63 + ca_pem: impl AsRef<Path>, 64 + client_cert_pem: Option<&Path>, 65 + client_key_pem: Option<&Path>, 66 + ) -> Result<Self> { 67 + let mut roots = RootCertStore::empty(); 68 + let ca_file = File::open(ca_pem.as_ref()).map_err(|e| Error::tls(e))?; 69 + let mut reader = BufReader::new(ca_file); 70 + for cert in rustls_pemfile::certs(&mut reader) { 71 + let cert = cert.map_err(|e| Error::tls(e))?; 72 + roots 73 + .add(cert) 74 + .map_err(|e| Error::tls(format!("add ca: {e}")))?; 75 + } 76 + 77 + let builder = RustlsClientConfig::builder().with_root_certificates(roots); 78 + let config = match (client_cert_pem, client_key_pem) { 79 + (Some(cert_path), Some(key_path)) => { 80 + let certs = load_certs(cert_path)?; 81 + let key = load_key(key_path)?; 82 + builder 83 + .with_client_auth_cert(certs, key) 84 + .map_err(|e| Error::tls(e))? 85 + } 86 + _ => builder.with_no_client_auth(), 87 + }; 88 + 89 + Ok(Self { 90 + client: Some(Arc::new(config)), 91 + server: None, 92 + }) 93 + } 94 + 95 + /// Server config from cert + key PEM files. Optionally require client certs. 96 + pub fn server( 97 + cert_pem: impl AsRef<Path>, 98 + key_pem: impl AsRef<Path>, 99 + client_ca_pem: Option<&Path>, 100 + ) -> Result<Self> { 101 + let certs = load_certs(cert_pem.as_ref())?; 102 + let key = load_key(key_pem.as_ref())?; 103 + 104 + let config = if let Some(ca) = client_ca_pem { 105 + let mut roots = RootCertStore::empty(); 106 + let ca_file = File::open(ca).map_err(|e| Error::tls(e))?; 107 + let mut reader = BufReader::new(ca_file); 108 + for cert in rustls_pemfile::certs(&mut reader) { 109 + let cert = cert.map_err(|e| Error::tls(e))?; 110 + roots 111 + .add(cert) 112 + .map_err(|e| Error::tls(format!("add client ca: {e}")))?; 113 + } 114 + let verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(roots)) 115 + .build() 116 + .map_err(|e| Error::tls(e))?; 117 + RustlsServerConfig::builder() 118 + .with_client_cert_verifier(verifier) 119 + .with_single_cert(certs, key) 120 + .map_err(|e| Error::tls(e))? 121 + } else { 122 + RustlsServerConfig::builder() 123 + .with_no_client_auth() 124 + .with_single_cert(certs, key) 125 + .map_err(|e| Error::tls(e))? 126 + }; 127 + 128 + Ok(Self { 129 + client: None, 130 + server: Some(Arc::new(config)), 131 + }) 132 + } 133 + 134 + /// Combine client + server configs. 135 + pub fn merge(mut self, other: TlsConfig) -> Self { 136 + if other.client.is_some() { 137 + self.client = other.client; 138 + } 139 + if other.server.is_some() { 140 + self.server = other.server; 141 + } 142 + self 143 + } 144 + 145 + /// Dangerous: client that skips server cert verification (dev / self-signed). 146 + pub fn client_insecure() -> Result<Self> { 147 + use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; 148 + use rustls::{DigitallySignedStruct, Error as TlsError, SignatureScheme}; 149 + use rustls_pki_types::{ServerName as PkiServerName, UnixTime}; 150 + 151 + #[derive(Debug)] 152 + struct NoVerify; 153 + impl ServerCertVerifier for NoVerify { 154 + fn verify_server_cert( 155 + &self, 156 + _end_entity: &CertificateDer<'_>, 157 + _intermediates: &[CertificateDer<'_>], 158 + _server_name: &PkiServerName<'_>, 159 + _ocsp_response: &[u8], 160 + _now: UnixTime, 161 + ) -> std::result::Result<ServerCertVerified, TlsError> { 162 + Ok(ServerCertVerified::assertion()) 163 + } 164 + fn verify_tls12_signature( 165 + &self, 166 + _message: &[u8], 167 + _cert: &CertificateDer<'_>, 168 + _dss: &DigitallySignedStruct, 169 + ) -> std::result::Result<HandshakeSignatureValid, TlsError> { 170 + Ok(HandshakeSignatureValid::assertion()) 171 + } 172 + fn verify_tls13_signature( 173 + &self, 174 + _message: &[u8], 175 + _cert: &CertificateDer<'_>, 176 + _dss: &DigitallySignedStruct, 177 + ) -> std::result::Result<HandshakeSignatureValid, TlsError> { 178 + Ok(HandshakeSignatureValid::assertion()) 179 + } 180 + fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { 181 + rustls::crypto::ring::default_provider() 182 + .signature_verification_algorithms 183 + .supported_schemes() 184 + } 185 + } 186 + 187 + let config = RustlsClientConfig::builder() 188 + .dangerous() 189 + .with_custom_certificate_verifier(Arc::new(NoVerify)) 190 + .with_no_client_auth(); 191 + Ok(Self { 192 + client: Some(Arc::new(config)), 193 + server: None, 194 + }) 195 + } 196 + } 197 + 198 + /// TLS dial/accept helpers. 199 + pub struct TlsTransport; 200 + 201 + impl TlsTransport { 202 + /// Dial `host:port` over TLS. `server_name` is the SNI / cert name. 203 + pub async fn connect( 204 + host: &str, 205 + port: u16, 206 + server_name: &str, 207 + tls: &TlsConfig, 208 + ) -> Result<DistStream> { 209 + let client = tls 210 + .client 211 + .as_ref() 212 + .ok_or_else(|| Error::tls("TlsConfig has no client config"))?; 213 + debug!(%host, port, %server_name, "tls connect"); 214 + let tcp = TcpStream::connect((host, port)).await?; 215 + tcp.set_nodelay(true)?; 216 + let connector = TlsConnector::from(Arc::clone(client)); 217 + let name = ServerName::try_from(server_name.to_string()) 218 + .map_err(|e| Error::tls(format!("invalid server name: {e}")))?; 219 + let tls_stream = connector 220 + .connect(name, tcp) 221 + .await 222 + .map_err(|e| Error::tls(e))?; 223 + Ok(DistStream::from_rw(tls_stream)) 224 + } 225 + 226 + /// Accept one TLS connection on an already-bound [`TcpListener`]. 227 + pub async fn accept(listener: &TcpListener, tls: &TlsConfig) -> Result<DistStream> { 228 + let server = tls 229 + .server 230 + .as_ref() 231 + .ok_or_else(|| Error::tls("TlsConfig has no server config"))?; 232 + let (tcp, peer) = listener.accept().await?; 233 + tcp.set_nodelay(true)?; 234 + debug!(%peer, "tls accept"); 235 + let acceptor = TlsAcceptor::from(Arc::clone(server)); 236 + let tls_stream = acceptor.accept(tcp).await.map_err(|e| Error::tls(e))?; 237 + Ok(DistStream::from_rw(tls_stream)) 238 + } 239 + } 240 + 241 + fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> { 242 + let file = File::open(path).map_err(|e| Error::tls(e))?; 243 + let mut reader = BufReader::new(file); 244 + rustls_pemfile::certs(&mut reader) 245 + .collect::<std::result::Result<Vec<_>, _>>() 246 + .map_err(|e| Error::tls(e)) 247 + } 248 + 249 + fn load_key(path: &Path) -> Result<PrivateKeyDer<'static>> { 250 + let file = File::open(path).map_err(|e| Error::tls(e))?; 251 + let mut reader = BufReader::new(file); 252 + loop { 253 + match rustls_pemfile::read_one(&mut reader).map_err(|e| Error::tls(e))? { 254 + Some(rustls_pemfile::Item::Pkcs8Key(k)) => return Ok(PrivateKeyDer::Pkcs8(k)), 255 + Some(rustls_pemfile::Item::Pkcs1Key(k)) => return Ok(PrivateKeyDer::Pkcs1(k)), 256 + Some(rustls_pemfile::Item::Sec1Key(k)) => return Ok(PrivateKeyDer::Sec1(k)), 257 + None => return Err(Error::tls(format!("no private key in {}", path.display()))), 258 + _ => continue, 259 + } 260 + } 261 + }
+18
tests/powers.rs
··· 1 + use erl_iroh::Powers; 2 + 3 + #[test] 4 + fn presets() { 5 + assert!(Powers::worker().contains(Powers::RPC)); 6 + assert!(Powers::worker().contains(Powers::SEND)); 7 + assert!(!Powers::observer().contains(Powers::RPC)); 8 + assert!(Powers::all().contains(Powers::TRACE)); 9 + assert_eq!(Powers::worker().describe(), "rpc|send|introspect|cluster"); 10 + } 11 + 12 + #[test] 13 + fn require_denies() { 14 + let p = Powers::observer(); 15 + let err = p.require(Powers::RPC).unwrap_err(); 16 + let msg = err.to_string(); 17 + assert!(msg.contains("power denied")); 18 + }