native/iroh-js vendors n0-computer/iroh-ffi v1.1.0's napi-rs crate (commit 5e45109) at module granularity: endpoint/key/net/path/relay/ ticket/watch; services dropped. All edits carry '// lofi-node:' markers; UPSTREAM.md records provenance and the tag-bump-by-diff procedure. lofi_ext.rs is the one extension: Buffer-based u32-BE length-prefixed framing (writeFrame/readFrame/maxFrame) — upstream Array<number> stream I/O measured 7.1 MiB/s loopback at gate 0. TS layer rides the same IrohNode/IrohConn contract: lazy bi-stream per conn (acceptBi only resolves on first bytes — a silent dialer must not stall the accept loop), send chained in call order, accept() → null on endpoint close (acceptor loop exits cleanly; the db_accept gap is gone). Tickets are upstream EndpointTicket strings now. require() needs .node — the loader stages an mtime-checked shim next to the cargo artifact. Gate-0 findings recorded in UPSTREAM.md: watch APIs abort the process under Deno (tokio-context panic) — never call them; poll instead. Full regression suite green: codec units, tunnel ws echo, pair, and convergence fixtures (single node, hosted daemon path, paired over iroh). db-iroh-ffi is no longer a dependency.
- commit
- 98487260818f473261cef1928dd2132bf4d0583e
- parent
- 9fc652251c80dbb05053dd9a9639d94dc49afed8
- author
- Dami <dami.etoile@gmail.com>
| M | .gitignore | 2 | + |
| M | README.md | 41 | +- |
| M | cli.ts | 4 | +- |
| M | mod.ts | 4 | +- |
| A | native/ | 4194 | ++++++++++++++++++++++++++++++ |
| A | native/ | 30 | + |
| A | native/ | 201 | + |
| A | native/ | 25 | + |
| A | native/ | 52 | + |
| A | native/ | 3 | + |
| A | native/ | 456 | +++ |
| A | native/ | 988 | +++++++ |
| A | native/ | 173 | + |
| A | native/ | 61 | + |
| A | native/ | 62 | + |
| A | native/ | 106 | + |
| A | native/ | 167 | + |
| A | native/ | 194 | + |
| A | native/ | 46 | + |
| A | native/ | 111 | + |
| M | src/ | 46 | +- |
| M | src/ | 192 | +- |
| A | src/ | 155 | + |
| D | src/ | 84 | - |
| M | src/ | 28 | +- |
| M | src/ | 22 | +- |
| M | src/ | 43 | +- |
| M | src/ | 15 | +- |
| M | tests/ | 22 | +- |
| M | tests/ | 21 | +- |
@@ -2,3 +2,5 @@ node_modules/
22 lofi-node-data/33 data/44 *.log5+native/iroh-js/target/6+native/iroh-js/*.node@@ -8,18 +8,22 @@ Jazz's WebSocket protocol — `JAZZ_SERVER_URL` points at a node and lofi's
88 the daemon: two nodes pair by ticket and replicate over a WS-over-iroh tunnel,99 dialed by key, no static IPs.1010 11-**Status: private prove-out.** Consumes the sibling `../db-iroh-ffi` checkout12-(prebuilt or `target/release`) or `LOFI_NODE_IROH=<path>`; publishing waits on13-reconciling db-iroh-ffi to a game-agnostic surface (see below).11+**Status: private prove-out.** The native layer is a trimmed vendor of12+upstream [n0-computer/iroh-ffi](https://github.com/n0-computer/iroh-ffi)'s13+`iroh-js` napi crate, built in-repo (`cargo build --release` in14+`native/iroh-js/`; see `native/iroh-js/UPSTREAM.md` for provenance and the15+tag-bump procedure). Override with `LOFI_NODE_IROH=<path>`.1416 1517 ## Layout1618 1719 | Piece | Where |1820 |---|---|1921 | One-constructor library | `src/node.ts` (`createSyncNode`) |20-| WS-over-iroh tunnel | `src/tunnel.ts` (HELLO/TEXT/BIN/CLOSE frames, 1 conn : 1 ws) |21-| FFI binding (connections surface only) | `src/native/iroh.ts`, `src/iroh/node.ts` |22-| Pairing ticket codec (`LFN1.…`) | `src/ticket.ts` |22+| WS+HTTP-over-iroh tunnel | `src/tunnel.ts` (HELLO/TEXT/BIN/CLOSE frames, 1 conn : 1 ws or 1 http request) |23+| Vendored iroh-js napi crate | `native/iroh-js/` (upstream + `lofi_ext.rs` Buffer framing) |24+| Addon loader + typed surface | `src/native/addon.ts`, `src/native/loader.ts` |25+| Adapter (IrohNode/IrohConn) | `src/iroh/node.ts` |26+| Ticket shape check (tickets are upstream `EndpointTicket` strings) | `src/ticket.ts` |2327 | Jazz wrapper (pinned `2.0.0-alpha.53`) | `src/jazz.ts` |2428 | CLI: init / start / pair / status | `cli.ts` |2529 | In-process test mesh (no iroh) | `testing/mod.ts` |@@ -47,11 +51,12 @@ node.ticket(); // -> share with the peer
4751 4852 ## Design decisions4953 50-- **Nonblocking FFI, not doorbearer's pump model.** Parking calls (`db_accept`,51- `db_conn_recv_msg`, `db_connect`, node open/close) are declared52- `nonblocking: true` and park FFI-pool threads, not the isolate. Safe because53- db-iroh-ffi's `send_msg` posts to a writer-task channel (never parks) on54- locks disjoint from the recv path, and registry/poison state is per-handle.54+- **napi async, not a flat C ABI.** The vendored iroh-js crate exposes real55+ Promises; `acceptNext()` resolves `null` on `endpoint.close()`, so the56+ accept loop exits cleanly. The one extension (`lofi_ext.rs`) adds57+ Buffer-based length-prefixed framing — upstream's `Array<number>` stream58+ I/O measured 7.1 MiB/s at gate 0. Never call the upstream watch APIs: at59+ v1.1.0 they panic outside a tokio context and abort the process.5560 - **The tunnel terminates WS on both ends**, so the dialer forwards the upgrade5661 path/subprotocol in a HELLO frame and the acceptor replays them against its5762 local JazzServer.@@ -62,15 +67,13 @@ node.ticket(); // -> share with the peer
6267 consuming lofi app pins (`2.0.0-alpha.53`). Wire compat across alphas is not6368 guaranteed; bump in lockstep.6469 65-## Native layer roadmap70+## Native layer6671 67-db-iroh-ffi is the prove-out binding, not the destination. The plan68-([docs/port-iroh-js.md](docs/port-iroh-js.md)) replaces it with a trimmed69-vendor of upstream **n0-computer/iroh-ffi's `iroh-js`** (napi-rs) built in70-this repo: connections surface only, one Buffer-framing extension module,71-`UPSTREAM.md` provenance, upstream tag tracking. That dissolves the old72-db-iroh-ffi reconciliation list (accept cancellation comes free from73-`acceptNext() → null` on close; tickets become upstream `EndpointTicket`s).72+Executed per [docs/port-iroh-js.md](docs/port-iroh-js.md): `native/iroh-js/`73+vendors upstream at v1.1.0 (endpoint/key/net/path/relay/ticket/watch modules;74+services dropped), plus `lofi_ext.rs`. db-iroh-ffi is no longer a dependency.75+Remaining from the plan: cross-compiled `prebuilt/<triple>/` artifacts, and76+surfacing `conn.stats()/paths()` into `SyncNodeStatus.mesh`.7477 7578 ## Tests7679 @@ -3,7 +3,7 @@
33 44 import { initConfig, loadConfig, saveConfig } from "./src/config.ts";55 import { createSyncNode } from "./src/node.ts";6-import { decodeTicket } from "./src/ticket.ts";6+import { looksLikeTicket } from "./src/ticket.ts";77 88 const USAGE = `lofi-node — self-hostable Jazz sync node with iroh transport99 @@ -105,7 +105,7 @@ async function cmdStart(args: Args) {
105105 async function cmdPair(args: Args) {106106 const ticket = args.positional[0];107107 if (!ticket) fail("usage: lofi-node pair <ticket>");108- if (!decodeTicket(ticket)) fail("malformed ticket (expected LFN1.…)");108+ if (!looksLikeTicket(ticket)) fail("that does not look like a pairing ticket");109109 const config = await requireConfig(args.dir);110110 config.upstream = { peer: ticket };111111 await saveConfig(args.dir, config);@@ -5,5 +5,5 @@ export { createSyncNode } from "./src/node.ts";
55 export type { MeshStatus, SyncNode, SyncNodeOptions, SyncNodeStatus } from "./src/node.ts";66 export { initConfig, loadConfig, saveConfig } from "./src/config.ts";77 export type { NodeConfig, UpstreamConfig } from "./src/config.ts";8-export { decodeTicket, encodeTicket } from "./src/ticket.ts";9-export { IrohFfiError, IrohPoisonedError, MeshUnavailableError } from "./src/errors.ts";8+export { looksLikeTicket } from "./src/ticket.ts";9+export { MeshUnavailableError } from "./src/errors.ts";@@ -0,0 +1,4194 @@
1+# This file is automatically @generated by Cargo.2+# It is not intended for manual editing.3+version = 44+5+[[package]]6+name = "aead"7+version = "0.5.2"8+source = "registry+https://github.com/rust-lang/crates.io-index"9+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"10+dependencies = [11+ "crypto-common 0.1.7",12+ "generic-array",13+]14+15+[[package]]16+name = "aes"17+version = "0.8.4"18+source = "registry+https://github.com/rust-lang/crates.io-index"19+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"20+dependencies = [21+ "cfg-if",22+ "cipher",23+ "cpufeatures 0.2.17",24+]25+26+[[package]]27+name = "aes-gcm"28+version = "0.10.3"29+source = "registry+https://github.com/rust-lang/crates.io-index"30+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"31+dependencies = [32+ "aead",33+ "aes",34+ "cipher",35+ "ctr",36+ "ghash",37+ "subtle",38+]39+40+[[package]]41+name = "aho-corasick"42+version = "1.1.4"43+source = "registry+https://github.com/rust-lang/crates.io-index"44+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"45+dependencies = [46+ "memchr",47+]48+49+[[package]]50+name = "allocator-api2"51+version = "0.2.21"52+source = "registry+https://github.com/rust-lang/crates.io-index"53+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"54+55+[[package]]56+name = "android_system_properties"57+version = "0.1.5"58+source = "registry+https://github.com/rust-lang/crates.io-index"59+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"60+dependencies = [61+ "libc",62+]63+64+[[package]]65+name = "anyhow"66+version = "1.0.103"67+source = "registry+https://github.com/rust-lang/crates.io-index"68+checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"69+70+[[package]]71+name = "arc-swap"72+version = "1.9.2"73+source = "registry+https://github.com/rust-lang/crates.io-index"74+checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"75+dependencies = [76+ "rustversion",77+]78+79+[[package]]80+name = "arrayref"81+version = "0.3.9"82+source = "registry+https://github.com/rust-lang/crates.io-index"83+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"84+85+[[package]]86+name = "arrayvec"87+version = "0.7.8"88+source = "registry+https://github.com/rust-lang/crates.io-index"89+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"90+91+[[package]]92+name = "async-trait"93+version = "0.1.89"94+source = "registry+https://github.com/rust-lang/crates.io-index"95+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"96+dependencies = [97+ "proc-macro2",98+ "quote",99+ "syn",100+]101+102+[[package]]103+name = "async_io_stream"104+version = "0.3.3"105+source = "registry+https://github.com/rust-lang/crates.io-index"106+checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c"107+dependencies = [108+ "futures",109+ "pharos",110+ "rustc_version",111+]112+113+[[package]]114+name = "atomic-polyfill"115+version = "1.0.3"116+source = "registry+https://github.com/rust-lang/crates.io-index"117+checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"118+dependencies = [119+ "critical-section",120+]121+122+[[package]]123+name = "atomic-waker"124+version = "1.1.2"125+source = "registry+https://github.com/rust-lang/crates.io-index"126+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"127+128+[[package]]129+name = "attohttpc"130+version = "0.30.1"131+source = "registry+https://github.com/rust-lang/crates.io-index"132+checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9"133+dependencies = [134+ "base64",135+ "http",136+ "log",137+ "url",138+]139+140+[[package]]141+name = "autocfg"142+version = "1.5.1"143+source = "registry+https://github.com/rust-lang/crates.io-index"144+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"145+146+[[package]]147+name = "backon"148+version = "1.6.0"149+source = "registry+https://github.com/rust-lang/crates.io-index"150+checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"151+dependencies = [152+ "fastrand",153+ "gloo-timers",154+ "tokio",155+]156+157+[[package]]158+name = "base16ct"159+version = "1.0.0"160+source = "registry+https://github.com/rust-lang/crates.io-index"161+checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"162+163+[[package]]164+name = "base64"165+version = "0.22.1"166+source = "registry+https://github.com/rust-lang/crates.io-index"167+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"168+169+[[package]]170+name = "base64ct"171+version = "1.8.3"172+source = "registry+https://github.com/rust-lang/crates.io-index"173+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"174+175+[[package]]176+name = "bitflags"177+version = "2.13.1"178+source = "registry+https://github.com/rust-lang/crates.io-index"179+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"180+181+[[package]]182+name = "blake3"183+version = "1.8.5"184+source = "registry+https://github.com/rust-lang/crates.io-index"185+checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"186+dependencies = [187+ "arrayref",188+ "arrayvec",189+ "cc",190+ "cfg-if",191+ "constant_time_eq",192+ "cpufeatures 0.3.0",193+]194+195+[[package]]196+name = "block-buffer"197+version = "0.12.1"198+source = "registry+https://github.com/rust-lang/crates.io-index"199+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"200+dependencies = [201+ "hybrid-array",202+]203+204+[[package]]205+name = "block2"206+version = "0.6.2"207+source = "registry+https://github.com/rust-lang/crates.io-index"208+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"209+dependencies = [210+ "objc2",211+]212+213+[[package]]214+name = "bumpalo"215+version = "3.20.3"216+source = "registry+https://github.com/rust-lang/crates.io-index"217+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"218+219+[[package]]220+name = "byteorder"221+version = "1.5.0"222+source = "registry+https://github.com/rust-lang/crates.io-index"223+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"224+225+[[package]]226+name = "bytes"227+version = "1.12.1"228+source = "registry+https://github.com/rust-lang/crates.io-index"229+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"230+231+[[package]]232+name = "cc"233+version = "1.2.67"234+source = "registry+https://github.com/rust-lang/crates.io-index"235+checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"236+dependencies = [237+ "find-msvc-tools",238+ "shlex",239+]240+241+[[package]]242+name = "cesu8"243+version = "1.1.0"244+source = "registry+https://github.com/rust-lang/crates.io-index"245+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"246+247+[[package]]248+name = "cfg-if"249+version = "1.0.4"250+source = "registry+https://github.com/rust-lang/crates.io-index"251+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"252+253+[[package]]254+name = "cfg_aliases"255+version = "0.2.2"256+source = "registry+https://github.com/rust-lang/crates.io-index"257+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"258+259+[[package]]260+name = "chacha20"261+version = "0.10.1"262+source = "registry+https://github.com/rust-lang/crates.io-index"263+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"264+dependencies = [265+ "cfg-if",266+ "cpufeatures 0.3.0",267+ "rand_core",268+]269+270+[[package]]271+name = "chrono"272+version = "0.4.45"273+source = "registry+https://github.com/rust-lang/crates.io-index"274+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"275+dependencies = [276+ "iana-time-zone",277+ "num-traits",278+ "serde",279+ "windows-link",280+]281+282+[[package]]283+name = "cipher"284+version = "0.4.4"285+source = "registry+https://github.com/rust-lang/crates.io-index"286+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"287+dependencies = [288+ "crypto-common 0.1.7",289+ "inout",290+]291+292+[[package]]293+name = "cmov"294+version = "0.5.4"295+source = "registry+https://github.com/rust-lang/crates.io-index"296+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"297+298+[[package]]299+name = "cobs"300+version = "0.3.0"301+source = "registry+https://github.com/rust-lang/crates.io-index"302+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"303+dependencies = [304+ "thiserror 2.0.18",305+]306+307+[[package]]308+name = "combine"309+version = "4.6.7"310+source = "registry+https://github.com/rust-lang/crates.io-index"311+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"312+dependencies = [313+ "bytes",314+ "memchr",315+]316+317+[[package]]318+name = "const-oid"319+version = "0.10.2"320+source = "registry+https://github.com/rust-lang/crates.io-index"321+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"322+323+[[package]]324+name = "constant_time_eq"325+version = "0.4.2"326+source = "registry+https://github.com/rust-lang/crates.io-index"327+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"328+329+[[package]]330+name = "convert_case"331+version = "0.10.0"332+source = "registry+https://github.com/rust-lang/crates.io-index"333+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"334+dependencies = [335+ "unicode-segmentation",336+]337+338+[[package]]339+name = "convert_case"340+version = "0.11.0"341+source = "registry+https://github.com/rust-lang/crates.io-index"342+checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49"343+dependencies = [344+ "unicode-segmentation",345+]346+347+[[package]]348+name = "cordyceps"349+version = "0.3.4"350+source = "registry+https://github.com/rust-lang/crates.io-index"351+checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a"352+dependencies = [353+ "loom",354+ "tracing",355+]356+357+[[package]]358+name = "core-foundation"359+version = "0.9.4"360+source = "registry+https://github.com/rust-lang/crates.io-index"361+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"362+dependencies = [363+ "core-foundation-sys",364+ "libc",365+]366+367+[[package]]368+name = "core-foundation"369+version = "0.10.1"370+source = "registry+https://github.com/rust-lang/crates.io-index"371+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"372+dependencies = [373+ "core-foundation-sys",374+ "libc",375+]376+377+[[package]]378+name = "core-foundation-sys"379+version = "0.8.7"380+source = "registry+https://github.com/rust-lang/crates.io-index"381+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"382+383+[[package]]384+name = "cpufeatures"385+version = "0.2.17"386+source = "registry+https://github.com/rust-lang/crates.io-index"387+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"388+dependencies = [389+ "libc",390+]391+392+[[package]]393+name = "cpufeatures"394+version = "0.3.0"395+source = "registry+https://github.com/rust-lang/crates.io-index"396+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"397+dependencies = [398+ "libc",399+]400+401+[[package]]402+name = "critical-section"403+version = "1.2.0"404+source = "registry+https://github.com/rust-lang/crates.io-index"405+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"406+407+[[package]]408+name = "crossbeam-channel"409+version = "0.5.16"410+source = "registry+https://github.com/rust-lang/crates.io-index"411+checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"412+dependencies = [413+ "crossbeam-utils",414+]415+416+[[package]]417+name = "crossbeam-epoch"418+version = "0.9.20"419+source = "registry+https://github.com/rust-lang/crates.io-index"420+checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"421+dependencies = [422+ "crossbeam-utils",423+]424+425+[[package]]426+name = "crossbeam-utils"427+version = "0.8.22"428+source = "registry+https://github.com/rust-lang/crates.io-index"429+checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"430+431+[[package]]432+name = "crypto-common"433+version = "0.1.7"434+source = "registry+https://github.com/rust-lang/crates.io-index"435+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"436+dependencies = [437+ "generic-array",438+ "typenum",439+]440+441+[[package]]442+name = "crypto-common"443+version = "0.2.2"444+source = "registry+https://github.com/rust-lang/crates.io-index"445+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"446+dependencies = [447+ "hybrid-array",448+]449+450+[[package]]451+name = "ctor"452+version = "1.0.9"453+source = "registry+https://github.com/rust-lang/crates.io-index"454+checksum = "a394189d59f9befacce833f337f7b1eca5e9a91221bcdd4d28e0114d96e597b3"455+456+[[package]]457+name = "ctr"458+version = "0.9.2"459+source = "registry+https://github.com/rust-lang/crates.io-index"460+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"461+dependencies = [462+ "cipher",463+]464+465+[[package]]466+name = "ctutils"467+version = "0.4.2"468+source = "registry+https://github.com/rust-lang/crates.io-index"469+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"470+dependencies = [471+ "cmov",472+]473+474+[[package]]475+name = "curve25519-dalek"476+version = "5.0.0-rc.0"477+source = "registry+https://github.com/rust-lang/crates.io-index"478+checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf"479+dependencies = [480+ "cfg-if",481+ "cpufeatures 0.3.0",482+ "curve25519-dalek-derive",483+ "digest",484+ "fiat-crypto",485+ "rand_core",486+ "rustc_version",487+ "serde",488+ "subtle",489+ "zeroize",490+]491+492+[[package]]493+name = "curve25519-dalek-derive"494+version = "0.1.1"495+source = "registry+https://github.com/rust-lang/crates.io-index"496+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"497+dependencies = [498+ "proc-macro2",499+ "quote",500+ "syn",501+]502+503+[[package]]504+name = "darling"505+version = "0.20.11"506+source = "registry+https://github.com/rust-lang/crates.io-index"507+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"508+dependencies = [509+ "darling_core",510+ "darling_macro",511+]512+513+[[package]]514+name = "darling_core"515+version = "0.20.11"516+source = "registry+https://github.com/rust-lang/crates.io-index"517+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"518+dependencies = [519+ "fnv",520+ "ident_case",521+ "proc-macro2",522+ "quote",523+ "strsim",524+ "syn",525+]526+527+[[package]]528+name = "darling_macro"529+version = "0.20.11"530+source = "registry+https://github.com/rust-lang/crates.io-index"531+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"532+dependencies = [533+ "darling_core",534+ "quote",535+ "syn",536+]537+538+[[package]]539+name = "data-encoding"540+version = "2.11.0"541+source = "registry+https://github.com/rust-lang/crates.io-index"542+checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"543+544+[[package]]545+name = "data-encoding-macro"546+version = "0.1.20"547+source = "registry+https://github.com/rust-lang/crates.io-index"548+checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c"549+dependencies = [550+ "data-encoding",551+ "data-encoding-macro-internal",552+]553+554+[[package]]555+name = "data-encoding-macro-internal"556+version = "0.1.18"557+source = "registry+https://github.com/rust-lang/crates.io-index"558+checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"559+dependencies = [560+ "data-encoding",561+ "syn",562+]563+564+[[package]]565+name = "der"566+version = "0.8.1"567+source = "registry+https://github.com/rust-lang/crates.io-index"568+checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"569+dependencies = [570+ "const-oid",571+ "pem-rfc7468",572+ "zeroize",573+]574+575+[[package]]576+name = "deranged"577+version = "0.5.8"578+source = "registry+https://github.com/rust-lang/crates.io-index"579+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"580+581+[[package]]582+name = "derive_builder"583+version = "0.20.2"584+source = "registry+https://github.com/rust-lang/crates.io-index"585+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"586+dependencies = [587+ "derive_builder_macro",588+]589+590+[[package]]591+name = "derive_builder_core"592+version = "0.20.2"593+source = "registry+https://github.com/rust-lang/crates.io-index"594+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"595+dependencies = [596+ "darling",597+ "proc-macro2",598+ "quote",599+ "syn",600+]601+602+[[package]]603+name = "derive_builder_macro"604+version = "0.20.2"605+source = "registry+https://github.com/rust-lang/crates.io-index"606+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"607+dependencies = [608+ "derive_builder_core",609+ "syn",610+]611+612+[[package]]613+name = "derive_more"614+version = "2.1.1"615+source = "registry+https://github.com/rust-lang/crates.io-index"616+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"617+dependencies = [618+ "derive_more-impl",619+]620+621+[[package]]622+name = "derive_more-impl"623+version = "2.1.1"624+source = "registry+https://github.com/rust-lang/crates.io-index"625+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"626+dependencies = [627+ "convert_case 0.10.0",628+ "proc-macro2",629+ "quote",630+ "rustc_version",631+ "syn",632+ "unicode-xid",633+]634+635+[[package]]636+name = "diatomic-waker"637+version = "0.2.3"638+source = "registry+https://github.com/rust-lang/crates.io-index"639+checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c"640+641+[[package]]642+name = "digest"643+version = "0.11.3"644+source = "registry+https://github.com/rust-lang/crates.io-index"645+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"646+dependencies = [647+ "block-buffer",648+ "crypto-common 0.2.2",649+]650+651+[[package]]652+name = "dispatch2"653+version = "0.3.1"654+source = "registry+https://github.com/rust-lang/crates.io-index"655+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"656+dependencies = [657+ "bitflags",658+ "block2",659+ "libc",660+ "objc2",661+]662+663+[[package]]664+name = "displaydoc"665+version = "0.2.6"666+source = "registry+https://github.com/rust-lang/crates.io-index"667+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"668+dependencies = [669+ "proc-macro2",670+ "quote",671+ "syn",672+]673+674+[[package]]675+name = "dlopen2"676+version = "0.8.2"677+source = "registry+https://github.com/rust-lang/crates.io-index"678+checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4"679+dependencies = [680+ "libc",681+ "once_cell",682+ "winapi",683+]684+685+[[package]]686+name = "ed25519"687+version = "3.0.0"688+source = "registry+https://github.com/rust-lang/crates.io-index"689+checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"690+dependencies = [691+ "pkcs8",692+ "serdect",693+ "signature",694+]695+696+[[package]]697+name = "ed25519-dalek"698+version = "3.0.0-rc.0"699+source = "registry+https://github.com/rust-lang/crates.io-index"700+checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60"701+dependencies = [702+ "curve25519-dalek",703+ "ed25519",704+ "rand_core",705+ "serde",706+ "sha2",707+ "signature",708+ "subtle",709+ "zeroize",710+]711+712+[[package]]713+name = "either"714+version = "1.16.0"715+source = "registry+https://github.com/rust-lang/crates.io-index"716+checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"717+718+[[package]]719+name = "embedded-io"720+version = "0.4.0"721+source = "registry+https://github.com/rust-lang/crates.io-index"722+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"723+724+[[package]]725+name = "embedded-io"726+version = "0.6.1"727+source = "registry+https://github.com/rust-lang/crates.io-index"728+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"729+730+[[package]]731+name = "enum-assoc"732+version = "1.3.0"733+source = "registry+https://github.com/rust-lang/crates.io-index"734+checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7"735+dependencies = [736+ "proc-macro2",737+ "quote",738+ "syn",739+]740+741+[[package]]742+name = "equivalent"743+version = "1.0.2"744+source = "registry+https://github.com/rust-lang/crates.io-index"745+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"746+747+[[package]]748+name = "errno"749+version = "0.3.14"750+source = "registry+https://github.com/rust-lang/crates.io-index"751+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"752+dependencies = [753+ "libc",754+ "windows-sys 0.61.2",755+]756+757+[[package]]758+name = "fastrand"759+version = "2.4.1"760+source = "registry+https://github.com/rust-lang/crates.io-index"761+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"762+763+[[package]]764+name = "fiat-crypto"765+version = "0.3.0"766+source = "registry+https://github.com/rust-lang/crates.io-index"767+checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"768+769+[[package]]770+name = "find-msvc-tools"771+version = "0.1.9"772+source = "registry+https://github.com/rust-lang/crates.io-index"773+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"774+775+[[package]]776+name = "fnv"777+version = "1.0.7"778+source = "registry+https://github.com/rust-lang/crates.io-index"779+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"780+781+[[package]]782+name = "foldhash"783+version = "0.2.0"784+source = "registry+https://github.com/rust-lang/crates.io-index"785+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"786+787+[[package]]788+name = "form_urlencoded"789+version = "1.2.2"790+source = "registry+https://github.com/rust-lang/crates.io-index"791+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"792+dependencies = [793+ "percent-encoding",794+]795+796+[[package]]797+name = "futures"798+version = "0.3.32"799+source = "registry+https://github.com/rust-lang/crates.io-index"800+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"801+dependencies = [802+ "futures-channel",803+ "futures-core",804+ "futures-executor",805+ "futures-io",806+ "futures-sink",807+ "futures-task",808+ "futures-util",809+]810+811+[[package]]812+name = "futures-buffered"813+version = "0.2.13"814+source = "registry+https://github.com/rust-lang/crates.io-index"815+checksum = "4421cb78ee172b6b06080093479d3c50f058e7c81b7d577bbb8d118d551d4cd5"816+dependencies = [817+ "cordyceps",818+ "diatomic-waker",819+ "futures-core",820+ "pin-project-lite",821+ "spin 0.10.1",822+]823+824+[[package]]825+name = "futures-channel"826+version = "0.3.32"827+source = "registry+https://github.com/rust-lang/crates.io-index"828+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"829+dependencies = [830+ "futures-core",831+ "futures-sink",832+]833+834+[[package]]835+name = "futures-core"836+version = "0.3.32"837+source = "registry+https://github.com/rust-lang/crates.io-index"838+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"839+840+[[package]]841+name = "futures-executor"842+version = "0.3.32"843+source = "registry+https://github.com/rust-lang/crates.io-index"844+checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"845+dependencies = [846+ "futures-core",847+ "futures-task",848+ "futures-util",849+]850+851+[[package]]852+name = "futures-io"853+version = "0.3.32"854+source = "registry+https://github.com/rust-lang/crates.io-index"855+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"856+857+[[package]]858+name = "futures-lite"859+version = "2.6.1"860+source = "registry+https://github.com/rust-lang/crates.io-index"861+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"862+dependencies = [863+ "fastrand",864+ "futures-core",865+ "futures-io",866+ "parking",867+ "pin-project-lite",868+]869+870+[[package]]871+name = "futures-macro"872+version = "0.3.32"873+source = "registry+https://github.com/rust-lang/crates.io-index"874+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"875+dependencies = [876+ "proc-macro2",877+ "quote",878+ "syn",879+]880+881+[[package]]882+name = "futures-sink"883+version = "0.3.32"884+source = "registry+https://github.com/rust-lang/crates.io-index"885+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"886+887+[[package]]888+name = "futures-task"889+version = "0.3.32"890+source = "registry+https://github.com/rust-lang/crates.io-index"891+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"892+893+[[package]]894+name = "futures-util"895+version = "0.3.32"896+source = "registry+https://github.com/rust-lang/crates.io-index"897+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"898+dependencies = [899+ "futures-channel",900+ "futures-core",901+ "futures-io",902+ "futures-macro",903+ "futures-sink",904+ "futures-task",905+ "memchr",906+ "pin-project-lite",907+ "slab",908+]909+910+[[package]]911+name = "generator"912+version = "0.8.9"913+source = "registry+https://github.com/rust-lang/crates.io-index"914+checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae"915+dependencies = [916+ "cc",917+ "cfg-if",918+ "libc",919+ "log",920+ "rustversion",921+ "windows-link",922+ "windows-result",923+]924+925+[[package]]926+name = "generic-array"927+version = "0.14.7"928+source = "registry+https://github.com/rust-lang/crates.io-index"929+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"930+dependencies = [931+ "typenum",932+ "version_check",933+]934+935+[[package]]936+name = "getrandom"937+version = "0.2.17"938+source = "registry+https://github.com/rust-lang/crates.io-index"939+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"940+dependencies = [941+ "cfg-if",942+ "js-sys",943+ "libc",944+ "wasi",945+ "wasm-bindgen",946+]947+948+[[package]]949+name = "getrandom"950+version = "0.4.3"951+source = "registry+https://github.com/rust-lang/crates.io-index"952+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"953+dependencies = [954+ "cfg-if",955+ "js-sys",956+ "libc",957+ "r-efi",958+ "rand_core",959+ "wasm-bindgen",960+]961+962+[[package]]963+name = "ghash"964+version = "0.5.1"965+source = "registry+https://github.com/rust-lang/crates.io-index"966+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"967+dependencies = [968+ "opaque-debug",969+ "polyval",970+]971+972+[[package]]973+name = "gloo-timers"974+version = "0.3.0"975+source = "registry+https://github.com/rust-lang/crates.io-index"976+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"977+dependencies = [978+ "futures-channel",979+ "futures-core",980+ "js-sys",981+ "wasm-bindgen",982+]983+984+[[package]]985+name = "h2"986+version = "0.4.15"987+source = "registry+https://github.com/rust-lang/crates.io-index"988+checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"989+dependencies = [990+ "atomic-waker",991+ "bytes",992+ "fnv",993+ "futures-core",994+ "futures-sink",995+ "http",996+ "indexmap",997+ "slab",998+ "tokio",999+ "tokio-util",1000+ "tracing",1001+]1002+1003+[[package]]1004+name = "hash32"1005+version = "0.2.1"1006+source = "registry+https://github.com/rust-lang/crates.io-index"1007+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"1008+dependencies = [1009+ "byteorder",1010+]1011+1012+[[package]]1013+name = "hashbrown"1014+version = "0.17.1"1015+source = "registry+https://github.com/rust-lang/crates.io-index"1016+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"1017+dependencies = [1018+ "allocator-api2",1019+ "equivalent",1020+ "foldhash",1021+]1022+1023+[[package]]1024+name = "heapless"1025+version = "0.7.17"1026+source = "registry+https://github.com/rust-lang/crates.io-index"1027+checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"1028+dependencies = [1029+ "atomic-polyfill",1030+ "hash32",1031+ "rustc_version",1032+ "serde",1033+ "spin 0.9.9",1034+ "stable_deref_trait",1035+]1036+1037+[[package]]1038+name = "heck"1039+version = "0.5.0"1040+source = "registry+https://github.com/rust-lang/crates.io-index"1041+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"1042+1043+[[package]]1044+name = "hickory-net"1045+version = "0.26.1"1046+source = "registry+https://github.com/rust-lang/crates.io-index"1047+checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183"1048+dependencies = [1049+ "async-trait",1050+ "bytes",1051+ "cfg-if",1052+ "data-encoding",1053+ "futures-channel",1054+ "futures-io",1055+ "futures-util",1056+ "h2",1057+ "hickory-proto",1058+ "http",1059+ "idna",1060+ "ipnet",1061+ "jni 0.22.4",1062+ "rand",1063+ "rustls",1064+ "thiserror 2.0.18",1065+ "tinyvec",1066+ "tokio",1067+ "tokio-rustls",1068+ "tracing",1069+ "url",1070+]1071+1072+[[package]]1073+name = "hickory-proto"1074+version = "0.26.1"1075+source = "registry+https://github.com/rust-lang/crates.io-index"1076+checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643"1077+dependencies = [1078+ "data-encoding",1079+ "idna",1080+ "ipnet",1081+ "jni 0.22.4",1082+ "once_cell",1083+ "prefix-trie",1084+ "rand",1085+ "ring",1086+ "thiserror 2.0.18",1087+ "tinyvec",1088+ "tracing",1089+ "url",1090+]1091+1092+[[package]]1093+name = "hickory-resolver"1094+version = "0.26.1"1095+source = "registry+https://github.com/rust-lang/crates.io-index"1096+checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c"1097+dependencies = [1098+ "cfg-if",1099+ "futures-util",1100+ "hickory-net",1101+ "hickory-proto",1102+ "ipconfig",1103+ "ipnet",1104+ "jni 0.22.4",1105+ "moka",1106+ "ndk-context",1107+ "once_cell",1108+ "parking_lot",1109+ "rand",1110+ "resolv-conf",1111+ "rustls",1112+ "smallvec",1113+ "system-configuration",1114+ "thiserror 2.0.18",1115+ "tokio",1116+ "tokio-rustls",1117+ "tracing",1118+]1119+1120+[[package]]1121+name = "http"1122+version = "1.4.2"1123+source = "registry+https://github.com/rust-lang/crates.io-index"1124+checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"1125+dependencies = [1126+ "bytes",1127+ "itoa",1128+]1129+1130+[[package]]1131+name = "http-body"1132+version = "1.1.0"1133+source = "registry+https://github.com/rust-lang/crates.io-index"1134+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"1135+dependencies = [1136+ "bytes",1137+ "http",1138+]1139+1140+[[package]]1141+name = "http-body-util"1142+version = "0.1.4"1143+source = "registry+https://github.com/rust-lang/crates.io-index"1144+checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"1145+dependencies = [1146+ "bytes",1147+ "futures-core",1148+ "http",1149+ "http-body",1150+ "pin-project-lite",1151+]1152+1153+[[package]]1154+name = "httparse"1155+version = "1.10.1"1156+source = "registry+https://github.com/rust-lang/crates.io-index"1157+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"1158+1159+[[package]]1160+name = "httpdate"1161+version = "1.0.3"1162+source = "registry+https://github.com/rust-lang/crates.io-index"1163+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"1164+1165+[[package]]1166+name = "hybrid-array"1167+version = "0.4.13"1168+source = "registry+https://github.com/rust-lang/crates.io-index"1169+checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"1170+dependencies = [1171+ "typenum",1172+]1173+1174+[[package]]1175+name = "hyper"1176+version = "1.10.1"1177+source = "registry+https://github.com/rust-lang/crates.io-index"1178+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"1179+dependencies = [1180+ "atomic-waker",1181+ "bytes",1182+ "futures-channel",1183+ "futures-core",1184+ "h2",1185+ "http",1186+ "http-body",1187+ "httparse",1188+ "httpdate",1189+ "itoa",1190+ "pin-project-lite",1191+ "smallvec",1192+ "tokio",1193+ "want",1194+]1195+1196+[[package]]1197+name = "hyper-rustls"1198+version = "0.27.9"1199+source = "registry+https://github.com/rust-lang/crates.io-index"1200+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"1201+dependencies = [1202+ "http",1203+ "hyper",1204+ "hyper-util",1205+ "rustls",1206+ "tokio",1207+ "tokio-rustls",1208+ "tower-service",1209+]1210+1211+[[package]]1212+name = "hyper-util"1213+version = "0.1.20"1214+source = "registry+https://github.com/rust-lang/crates.io-index"1215+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"1216+dependencies = [1217+ "base64",1218+ "bytes",1219+ "futures-channel",1220+ "futures-util",1221+ "http",1222+ "http-body",1223+ "hyper",1224+ "ipnet",1225+ "libc",1226+ "percent-encoding",1227+ "pin-project-lite",1228+ "socket2",1229+ "tokio",1230+ "tower-service",1231+ "tracing",1232+]1233+1234+[[package]]1235+name = "iana-time-zone"1236+version = "0.1.65"1237+source = "registry+https://github.com/rust-lang/crates.io-index"1238+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"1239+dependencies = [1240+ "android_system_properties",1241+ "core-foundation-sys",1242+ "iana-time-zone-haiku",1243+ "js-sys",1244+ "log",1245+ "wasm-bindgen",1246+ "windows-core",1247+]1248+1249+[[package]]1250+name = "iana-time-zone-haiku"1251+version = "0.1.2"1252+source = "registry+https://github.com/rust-lang/crates.io-index"1253+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"1254+dependencies = [1255+ "cc",1256+]1257+1258+[[package]]1259+name = "icu_collections"1260+version = "2.2.0"1261+source = "registry+https://github.com/rust-lang/crates.io-index"1262+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"1263+dependencies = [1264+ "displaydoc",1265+ "potential_utf",1266+ "utf8_iter",1267+ "yoke",1268+ "zerofrom",1269+ "zerovec",1270+]1271+1272+[[package]]1273+name = "icu_locale_core"1274+version = "2.2.0"1275+source = "registry+https://github.com/rust-lang/crates.io-index"1276+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"1277+dependencies = [1278+ "displaydoc",1279+ "litemap",1280+ "tinystr",1281+ "writeable",1282+ "zerovec",1283+]1284+1285+[[package]]1286+name = "icu_normalizer"1287+version = "2.2.0"1288+source = "registry+https://github.com/rust-lang/crates.io-index"1289+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"1290+dependencies = [1291+ "icu_collections",1292+ "icu_normalizer_data",1293+ "icu_properties",1294+ "icu_provider",1295+ "smallvec",1296+ "zerovec",1297+]1298+1299+[[package]]1300+name = "icu_normalizer_data"1301+version = "2.2.0"1302+source = "registry+https://github.com/rust-lang/crates.io-index"1303+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"1304+1305+[[package]]1306+name = "icu_properties"1307+version = "2.2.0"1308+source = "registry+https://github.com/rust-lang/crates.io-index"1309+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"1310+dependencies = [1311+ "icu_collections",1312+ "icu_locale_core",1313+ "icu_properties_data",1314+ "icu_provider",1315+ "zerotrie",1316+ "zerovec",1317+]1318+1319+[[package]]1320+name = "icu_properties_data"1321+version = "2.2.0"1322+source = "registry+https://github.com/rust-lang/crates.io-index"1323+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"1324+1325+[[package]]1326+name = "icu_provider"1327+version = "2.2.0"1328+source = "registry+https://github.com/rust-lang/crates.io-index"1329+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"1330+dependencies = [1331+ "displaydoc",1332+ "icu_locale_core",1333+ "writeable",1334+ "yoke",1335+ "zerofrom",1336+ "zerotrie",1337+ "zerovec",1338+]1339+1340+[[package]]1341+name = "ident_case"1342+version = "1.0.1"1343+source = "registry+https://github.com/rust-lang/crates.io-index"1344+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"1345+1346+[[package]]1347+name = "identity-hash"1348+version = "0.1.0"1349+source = "registry+https://github.com/rust-lang/crates.io-index"1350+checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da"1351+1352+[[package]]1353+name = "idna"1354+version = "1.1.0"1355+source = "registry+https://github.com/rust-lang/crates.io-index"1356+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"1357+dependencies = [1358+ "idna_adapter",1359+ "smallvec",1360+ "utf8_iter",1361+]1362+1363+[[package]]1364+name = "idna_adapter"1365+version = "1.2.2"1366+source = "registry+https://github.com/rust-lang/crates.io-index"1367+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"1368+dependencies = [1369+ "icu_normalizer",1370+ "icu_properties",1371+]1372+1373+[[package]]1374+name = "igd-next"1375+version = "0.17.1"1376+source = "registry+https://github.com/rust-lang/crates.io-index"1377+checksum = "de7238d487a9aff61f81b5ab41c0a841532a115a398b5fa92a2fadd0885e2581"1378+dependencies = [1379+ "attohttpc",1380+ "bytes",1381+ "futures",1382+ "http",1383+ "http-body-util",1384+ "hyper",1385+ "hyper-util",1386+ "log",1387+ "rand",1388+ "tokio",1389+ "url",1390+ "xmltree",1391+]1392+1393+[[package]]1394+name = "indexmap"1395+version = "2.14.0"1396+source = "registry+https://github.com/rust-lang/crates.io-index"1397+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"1398+dependencies = [1399+ "equivalent",1400+ "hashbrown",1401+]1402+1403+[[package]]1404+name = "inout"1405+version = "0.1.4"1406+source = "registry+https://github.com/rust-lang/crates.io-index"1407+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"1408+dependencies = [1409+ "generic-array",1410+]1411+1412+[[package]]1413+name = "ipconfig"1414+version = "0.3.4"1415+source = "registry+https://github.com/rust-lang/crates.io-index"1416+checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"1417+dependencies = [1418+ "socket2",1419+ "widestring",1420+ "windows-registry",1421+ "windows-result",1422+ "windows-sys 0.61.2",1423+]1424+1425+[[package]]1426+name = "ipnet"1427+version = "2.12.0"1428+source = "registry+https://github.com/rust-lang/crates.io-index"1429+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"1430+dependencies = [1431+ "serde",1432+]1433+1434+[[package]]1435+name = "iroh"1436+version = "1.0.2"1437+source = "registry+https://github.com/rust-lang/crates.io-index"1438+checksum = "5fca9b4b462c343ff88fc0af4096c186f939b602a0bc08723536ef2c31c93971"1439+dependencies = [1440+ "backon",1441+ "blake3",1442+ "bytes",1443+ "cfg_aliases",1444+ "ctutils",1445+ "data-encoding",1446+ "derive_more",1447+ "ed25519-dalek",1448+ "futures-util",1449+ "getrandom 0.4.3",1450+ "hickory-resolver",1451+ "http",1452+ "ipnet",1453+ "iroh-base",1454+ "iroh-dns",1455+ "iroh-metrics",1456+ "iroh-relay",1457+ "n0-error",1458+ "n0-future",1459+ "n0-watcher",1460+ "netwatch",1461+ "noq",1462+ "noq-proto",1463+ "noq-udp",1464+ "papaya",1465+ "pin-project",1466+ "portable-atomic",1467+ "portmapper",1468+ "rand",1469+ "reqwest",1470+ "rustc-hash",1471+ "rustls",1472+ "rustls-pki-types",1473+ "serde",1474+ "smallvec",1475+ "strum",1476+ "time",1477+ "tokio",1478+ "tokio-stream",1479+ "tokio-util",1480+ "tracing",1481+ "url",1482+ "wasm-bindgen-futures",1483+]1484+1485+[[package]]1486+name = "iroh-base"1487+version = "1.0.2"1488+source = "registry+https://github.com/rust-lang/crates.io-index"1489+checksum = "830a582cd54410dc1aa71d4786a82c3297d7b0165accd8b6dbbb3b240b48140d"1490+dependencies = [1491+ "curve25519-dalek",1492+ "data-encoding",1493+ "data-encoding-macro",1494+ "derive_more",1495+ "ed25519-dalek",1496+ "getrandom 0.4.3",1497+ "n0-error",1498+ "rand",1499+ "serde",1500+ "url",1501+ "zeroize",1502+]1503+1504+[[package]]1505+name = "iroh-dns"1506+version = "1.0.2"1507+source = "registry+https://github.com/rust-lang/crates.io-index"1508+checksum = "516e4eedc38e33ab69a6bd325520332dc3d67b25454e2d590ebb84a25240dd9a"1509+dependencies = [1510+ "arc-swap",1511+ "cfg_aliases",1512+ "derive_more",1513+ "hickory-resolver",1514+ "iroh-base",1515+ "n0-error",1516+ "n0-future",1517+ "ndk-context",1518+ "portable-atomic",1519+ "rand",1520+ "rustls",1521+ "simple-dns",1522+ "strum",1523+ "tokio",1524+ "tracing",1525+ "url",1526+]1527+1528+[[package]]1529+name = "iroh-metrics"1530+version = "1.0.1"1531+source = "registry+https://github.com/rust-lang/crates.io-index"1532+checksum = "291065721ad7c477b972e581bbc528df031dc8eb5e39fe1ff3300ae5dfb157ef"1533+dependencies = [1534+ "iroh-metrics-derive",1535+ "itoa",1536+ "n0-error",1537+ "portable-atomic",1538+ "ryu",1539+ "serde",1540+ "tracing",1541+]1542+1543+[[package]]1544+name = "iroh-metrics-derive"1545+version = "1.0.1"1546+source = "registry+https://github.com/rust-lang/crates.io-index"1547+checksum = "1ae5f0c4405d1fbc9fb16ff422ca40620e93dc36c30ecaba0c2aee3992b7bd48"1548+dependencies = [1549+ "heck",1550+ "proc-macro2",1551+ "quote",1552+ "syn",1553+]1554+1555+[[package]]1556+name = "iroh-relay"1557+version = "1.0.2"1558+source = "registry+https://github.com/rust-lang/crates.io-index"1559+checksum = "8149bb6a57126225a07d6928846d82dcedfd24ea0f863ef7b2eb475e1d726354"1560+dependencies = [1561+ "blake3",1562+ "bytes",1563+ "cfg_aliases",1564+ "data-encoding",1565+ "derive_more",1566+ "getrandom 0.4.3",1567+ "hickory-resolver",1568+ "http",1569+ "http-body-util",1570+ "hyper",1571+ "hyper-util",1572+ "iroh-base",1573+ "iroh-dns",1574+ "iroh-metrics",1575+ "lru",1576+ "n0-error",1577+ "n0-future",1578+ "noq",1579+ "noq-proto",1580+ "num_enum",1581+ "pin-project",1582+ "postcard",1583+ "rand",1584+ "reqwest",1585+ "rustls",1586+ "rustls-pki-types",1587+ "serde",1588+ "serde_bytes",1589+ "strum",1590+ "tokio",1591+ "tokio-rustls",1592+ "tokio-util",1593+ "tokio-websockets",1594+ "tracing",1595+ "url",1596+ "vergen-gitcl",1597+ "webpki-roots",1598+ "ws_stream_wasm",1599+]1600+1601+[[package]]1602+name = "iroh-tickets"1603+version = "1.0.0"1604+source = "registry+https://github.com/rust-lang/crates.io-index"1605+checksum = "da53233419ca36bf521ed45683b7748366f9b233032891eefc2d70567a84ac54"1606+dependencies = [1607+ "data-encoding",1608+ "derive_more",1609+ "iroh-base",1610+ "n0-error",1611+ "postcard",1612+ "serde",1613+]1614+1615+[[package]]1616+name = "itoa"1617+version = "1.0.18"1618+source = "registry+https://github.com/rust-lang/crates.io-index"1619+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"1620+1621+[[package]]1622+name = "jni"1623+version = "0.21.1"1624+source = "registry+https://github.com/rust-lang/crates.io-index"1625+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"1626+dependencies = [1627+ "cesu8",1628+ "cfg-if",1629+ "combine",1630+ "jni-sys 0.3.1",1631+ "log",1632+ "thiserror 1.0.69",1633+ "walkdir",1634+ "windows-sys 0.45.0",1635+]1636+1637+[[package]]1638+name = "jni"1639+version = "0.22.4"1640+source = "registry+https://github.com/rust-lang/crates.io-index"1641+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"1642+dependencies = [1643+ "cfg-if",1644+ "combine",1645+ "jni-macros",1646+ "jni-sys 0.4.1",1647+ "log",1648+ "simd_cesu8",1649+ "thiserror 2.0.18",1650+ "walkdir",1651+ "windows-link",1652+]1653+1654+[[package]]1655+name = "jni-macros"1656+version = "0.22.4"1657+source = "registry+https://github.com/rust-lang/crates.io-index"1658+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"1659+dependencies = [1660+ "proc-macro2",1661+ "quote",1662+ "rustc_version",1663+ "simd_cesu8",1664+ "syn",1665+]1666+1667+[[package]]1668+name = "jni-sys"1669+version = "0.3.1"1670+source = "registry+https://github.com/rust-lang/crates.io-index"1671+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"1672+dependencies = [1673+ "jni-sys 0.4.1",1674+]1675+1676+[[package]]1677+name = "jni-sys"1678+version = "0.4.1"1679+source = "registry+https://github.com/rust-lang/crates.io-index"1680+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"1681+dependencies = [1682+ "jni-sys-macros",1683+]1684+1685+[[package]]1686+name = "jni-sys-macros"1687+version = "0.4.1"1688+source = "registry+https://github.com/rust-lang/crates.io-index"1689+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"1690+dependencies = [1691+ "quote",1692+ "syn",1693+]1694+1695+[[package]]1696+name = "js-sys"1697+version = "0.3.103"1698+source = "registry+https://github.com/rust-lang/crates.io-index"1699+checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"1700+dependencies = [1701+ "cfg-if",1702+ "futures-util",1703+ "wasm-bindgen",1704+]1705+1706+[[package]]1707+name = "lazy_static"1708+version = "1.5.0"1709+source = "registry+https://github.com/rust-lang/crates.io-index"1710+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"1711+1712+[[package]]1713+name = "libc"1714+version = "0.2.186"1715+source = "registry+https://github.com/rust-lang/crates.io-index"1716+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"1717+1718+[[package]]1719+name = "libloading"1720+version = "0.9.0"1721+source = "registry+https://github.com/rust-lang/crates.io-index"1722+checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"1723+dependencies = [1724+ "cfg-if",1725+ "windows-link",1726+]1727+1728+[[package]]1729+name = "litemap"1730+version = "0.8.2"1731+source = "registry+https://github.com/rust-lang/crates.io-index"1732+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"1733+1734+[[package]]1735+name = "lock_api"1736+version = "0.4.14"1737+source = "registry+https://github.com/rust-lang/crates.io-index"1738+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"1739+dependencies = [1740+ "scopeguard",1741+]1742+1743+[[package]]1744+name = "log"1745+version = "0.4.33"1746+source = "registry+https://github.com/rust-lang/crates.io-index"1747+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"1748+1749+[[package]]1750+name = "loom"1751+version = "0.7.2"1752+source = "registry+https://github.com/rust-lang/crates.io-index"1753+checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"1754+dependencies = [1755+ "cfg-if",1756+ "generator",1757+ "scoped-tls",1758+ "tracing",1759+ "tracing-subscriber",1760+]1761+1762+[[package]]1763+name = "lru"1764+version = "0.18.1"1765+source = "registry+https://github.com/rust-lang/crates.io-index"1766+checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"1767+dependencies = [1768+ "hashbrown",1769+]1770+1771+[[package]]1772+name = "lru-slab"1773+version = "0.1.2"1774+source = "registry+https://github.com/rust-lang/crates.io-index"1775+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"1776+1777+[[package]]1778+name = "mac-addr"1779+version = "0.3.0"1780+source = "registry+https://github.com/rust-lang/crates.io-index"1781+checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f"1782+1783+[[package]]1784+name = "matchers"1785+version = "0.2.0"1786+source = "registry+https://github.com/rust-lang/crates.io-index"1787+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"1788+dependencies = [1789+ "regex-automata",1790+]1791+1792+[[package]]1793+name = "memchr"1794+version = "2.8.3"1795+source = "registry+https://github.com/rust-lang/crates.io-index"1796+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"1797+1798+[[package]]1799+name = "mio"1800+version = "1.2.2"1801+source = "registry+https://github.com/rust-lang/crates.io-index"1802+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"1803+dependencies = [1804+ "libc",1805+ "wasi",1806+ "windows-sys 0.61.2",1807+]1808+1809+[[package]]1810+name = "moka"1811+version = "0.12.15"1812+source = "registry+https://github.com/rust-lang/crates.io-index"1813+checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046"1814+dependencies = [1815+ "crossbeam-channel",1816+ "crossbeam-epoch",1817+ "crossbeam-utils",1818+ "equivalent",1819+ "parking_lot",1820+ "portable-atomic",1821+ "smallvec",1822+ "tagptr",1823+ "uuid",1824+]1825+1826+[[package]]1827+name = "n0-error"1828+version = "1.0.0"1829+source = "registry+https://github.com/rust-lang/crates.io-index"1830+checksum = "c37e81176a83a77d2514528b91bdafc70ef88aab428f0e1b91aebb8d99888895"1831+dependencies = [1832+ "n0-error-macros",1833+ "spez",1834+]1835+1836+[[package]]1837+name = "n0-error-macros"1838+version = "1.0.0"1839+source = "registry+https://github.com/rust-lang/crates.io-index"1840+checksum = "e2acd8b070213b0299282f884b4beba4e7b52d624fdcd504a3ad3665390c11e1"1841+dependencies = [1842+ "proc-macro2",1843+ "quote",1844+ "syn",1845+]1846+1847+[[package]]1848+name = "n0-future"1849+version = "0.3.2"1850+source = "registry+https://github.com/rust-lang/crates.io-index"1851+checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe"1852+dependencies = [1853+ "cfg_aliases",1854+ "derive_more",1855+ "futures-buffered",1856+ "futures-lite",1857+ "futures-util",1858+ "js-sys",1859+ "pin-project",1860+ "send_wrapper",1861+ "tokio",1862+ "tokio-util",1863+ "wasm-bindgen",1864+ "wasm-bindgen-futures",1865+ "web-time",1866+]1867+1868+[[package]]1869+name = "n0-watcher"1870+version = "1.0.0"1871+source = "registry+https://github.com/rust-lang/crates.io-index"1872+checksum = "bbc618745ad0b7414b149d0517ad8b5573b2fb4d4e2717add3d2446ce1fdd826"1873+dependencies = [1874+ "derive_more",1875+ "n0-error",1876+ "n0-future",1877+]1878+1879+[[package]]1880+name = "napi"1881+version = "3.10.5"1882+source = "registry+https://github.com/rust-lang/crates.io-index"1883+checksum = "6826e5ddc15589b2d68c8ad5321c18e85d40488e93e32962f362e572669bccf6"1884+dependencies = [1885+ "anyhow",1886+ "bitflags",1887+ "ctor",1888+ "futures",1889+ "napi-build",1890+ "napi-sys",1891+ "nohash-hasher",1892+ "rustc-hash",1893+ "tokio",1894+]1895+1896+[[package]]1897+name = "napi-build"1898+version = "2.3.2"1899+source = "registry+https://github.com/rust-lang/crates.io-index"1900+checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1"1901+1902+[[package]]1903+name = "napi-derive"1904+version = "3.5.10"1905+source = "registry+https://github.com/rust-lang/crates.io-index"1906+checksum = "b0fe526e81c105d3640516fcde83909dd1afe757c0d7a15af58830b5bc0fb9a1"1907+dependencies = [1908+ "convert_case 0.11.0",1909+ "ctor",1910+ "napi-derive-backend",1911+ "proc-macro2",1912+ "quote",1913+ "syn",1914+]1915+1916+[[package]]1917+name = "napi-derive-backend"1918+version = "5.1.2"1919+source = "registry+https://github.com/rust-lang/crates.io-index"1920+checksum = "514281397bcddd9ea9a876c7a21a57bff2374237a000ca9a64ea0211ec1993e2"1921+dependencies = [1922+ "convert_case 0.11.0",1923+ "proc-macro2",1924+ "quote",1925+ "semver",1926+ "syn",1927+]1928+1929+[[package]]1930+name = "napi-sys"1931+version = "3.2.3"1932+source = "registry+https://github.com/rust-lang/crates.io-index"1933+checksum = "73e43cf2eb0bd1bf95a43c07c076ebd2da5d1e015a71c3d201faeffffcc0ecac"1934+dependencies = [1935+ "libloading",1936+]1937+1938+[[package]]1939+name = "ndk-context"1940+version = "0.1.1"1941+source = "registry+https://github.com/rust-lang/crates.io-index"1942+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"1943+1944+[[package]]1945+name = "netdev"1946+version = "0.45.0"1947+source = "registry+https://github.com/rust-lang/crates.io-index"1948+checksum = "569dfbdd2efd771b24ec9bb57f956e04d4fbfc72f62b2f11961723f9b3f4b020"1949+dependencies = [1950+ "block2",1951+ "dispatch2",1952+ "dlopen2",1953+ "ipnet",1954+ "jni 0.21.1",1955+ "libc",1956+ "mac-addr",1957+ "ndk-context",1958+ "netlink-packet-core",1959+ "netlink-packet-route",1960+ "netlink-sys",1961+ "objc2",1962+ "objc2-core-foundation",1963+ "objc2-core-wlan",1964+ "objc2-foundation",1965+ "objc2-system-configuration",1966+ "once_cell",1967+ "plist",1968+ "windows-sys 0.61.2",1969+]1970+1971+[[package]]1972+name = "netlink-packet-core"1973+version = "0.8.1"1974+source = "registry+https://github.com/rust-lang/crates.io-index"1975+checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"1976+dependencies = [1977+ "paste",1978+]1979+1980+[[package]]1981+name = "netlink-packet-route"1982+version = "0.31.0"1983+source = "registry+https://github.com/rust-lang/crates.io-index"1984+checksum = "e2288fcb784eb3defd5fb16f4c4160d5f477de192eac730f43e1d11c24d9a007"1985+dependencies = [1986+ "bitflags",1987+ "libc",1988+ "log",1989+ "netlink-packet-core",1990+]1991+1992+[[package]]1993+name = "netlink-proto"1994+version = "0.12.0"1995+source = "registry+https://github.com/rust-lang/crates.io-index"1996+checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128"1997+dependencies = [1998+ "bytes",1999+ "futures",2000+ "log",2001+ "netlink-packet-core",2002+ "netlink-sys",2003+ "thiserror 2.0.18",2004+]2005+2006+[[package]]2007+name = "netlink-sys"2008+version = "0.8.8"2009+source = "registry+https://github.com/rust-lang/crates.io-index"2010+checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae"2011+dependencies = [2012+ "bytes",2013+ "futures-util",2014+ "libc",2015+ "log",2016+ "tokio",2017+]2018+2019+[[package]]2020+name = "netwatch"2021+version = "0.19.1"2022+source = "registry+https://github.com/rust-lang/crates.io-index"2023+checksum = "4d9cbe01741347ef750d743d6690603f5eed8341e679fb51c8e629337aa11976"2024+dependencies = [2025+ "atomic-waker",2026+ "bytes",2027+ "cfg_aliases",2028+ "derive_more",2029+ "ipnet",2030+ "js-sys",2031+ "libc",2032+ "n0-error",2033+ "n0-future",2034+ "n0-watcher",2035+ "netdev",2036+ "netlink-packet-core",2037+ "netlink-packet-route",2038+ "netlink-proto",2039+ "netlink-sys",2040+ "noq-udp",2041+ "objc2-core-foundation",2042+ "objc2-system-configuration",2043+ "pin-project-lite",2044+ "serde",2045+ "socket2",2046+ "time",2047+ "tokio",2048+ "tokio-util",2049+ "tracing",2050+ "web-sys",2051+ "windows",2052+ "windows-result",2053+ "wmi",2054+]2055+2056+[[package]]2057+name = "nohash-hasher"2058+version = "0.2.0"2059+source = "registry+https://github.com/rust-lang/crates.io-index"2060+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"2061+2062+[[package]]2063+name = "noq"2064+version = "1.0.1"2065+source = "registry+https://github.com/rust-lang/crates.io-index"2066+checksum = "4bf95190af1bd4a00a10e8255ca0c8ddd9e9a9f5e79151d7a7eb6d56aff5dc89"2067+dependencies = [2068+ "bytes",2069+ "cfg_aliases",2070+ "derive_more",2071+ "noq-proto",2072+ "noq-udp",2073+ "pin-project-lite",2074+ "rustc-hash",2075+ "rustls",2076+ "socket2",2077+ "thiserror 2.0.18",2078+ "tokio",2079+ "tokio-stream",2080+ "tracing",2081+ "web-time",2082+]2083+2084+[[package]]2085+name = "noq-proto"2086+version = "1.0.1"2087+source = "registry+https://github.com/rust-lang/crates.io-index"2088+checksum = "aa6c890013591e709a3e45dd53501351b7e27e7ff3c7e9fc3dce43e300e7e9d3"2089+dependencies = [2090+ "aes-gcm",2091+ "bytes",2092+ "derive_more",2093+ "enum-assoc",2094+ "getrandom 0.4.3",2095+ "identity-hash",2096+ "lru-slab",2097+ "rand",2098+ "rand_pcg",2099+ "ring",2100+ "rustc-hash",2101+ "rustls",2102+ "rustls-pki-types",2103+ "slab",2104+ "sorted-index-buffer",2105+ "thiserror 2.0.18",2106+ "tinyvec",2107+ "tracing",2108+ "web-time",2109+]2110+2111+[[package]]2112+name = "noq-udp"2113+version = "1.0.1"2114+source = "registry+https://github.com/rust-lang/crates.io-index"2115+checksum = "3137a52df66c20090a889828d1c655f21f52294cba64e5c4fbb04fc83eee7c8e"2116+dependencies = [2117+ "cfg_aliases",2118+ "libc",2119+ "socket2",2120+ "tracing",2121+ "windows-sys 0.61.2",2122+]2123+2124+[[package]]2125+name = "nu-ansi-term"2126+version = "0.50.3"2127+source = "registry+https://github.com/rust-lang/crates.io-index"2128+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"2129+dependencies = [2130+ "windows-sys 0.61.2",2131+]2132+2133+[[package]]2134+name = "num-conv"2135+version = "0.2.2"2136+source = "registry+https://github.com/rust-lang/crates.io-index"2137+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"2138+2139+[[package]]2140+name = "num-traits"2141+version = "0.2.19"2142+source = "registry+https://github.com/rust-lang/crates.io-index"2143+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"2144+dependencies = [2145+ "autocfg",2146+]2147+2148+[[package]]2149+name = "num_enum"2150+version = "0.7.6"2151+source = "registry+https://github.com/rust-lang/crates.io-index"2152+checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"2153+dependencies = [2154+ "num_enum_derive",2155+ "rustversion",2156+]2157+2158+[[package]]2159+name = "num_enum_derive"2160+version = "0.7.6"2161+source = "registry+https://github.com/rust-lang/crates.io-index"2162+checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"2163+dependencies = [2164+ "proc-macro-crate",2165+ "proc-macro2",2166+ "quote",2167+ "syn",2168+]2169+2170+[[package]]2171+name = "num_threads"2172+version = "0.1.7"2173+source = "registry+https://github.com/rust-lang/crates.io-index"2174+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"2175+dependencies = [2176+ "libc",2177+]2178+2179+[[package]]2180+name = "number0_iroh"2181+version = "1.1.0"2182+dependencies = [2183+ "anyhow",2184+ "iroh",2185+ "iroh-base",2186+ "iroh-metrics",2187+ "iroh-relay",2188+ "iroh-tickets",2189+ "n0-future",2190+ "napi",2191+ "napi-build",2192+ "napi-derive",2193+ "tokio",2194+ "tracing",2195+ "tracing-subscriber",2196+ "url",2197+]2198+2199+[[package]]2200+name = "objc2"2201+version = "0.6.4"2202+source = "registry+https://github.com/rust-lang/crates.io-index"2203+checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"2204+dependencies = [2205+ "objc2-encode",2206+]2207+2208+[[package]]2209+name = "objc2-core-foundation"2210+version = "0.3.2"2211+source = "registry+https://github.com/rust-lang/crates.io-index"2212+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"2213+dependencies = [2214+ "bitflags",2215+ "block2",2216+ "dispatch2",2217+ "libc",2218+ "objc2",2219+]2220+2221+[[package]]2222+name = "objc2-core-wlan"2223+version = "0.3.2"2224+source = "registry+https://github.com/rust-lang/crates.io-index"2225+checksum = "c71e34919aba0d701380d911702455038a8a3587467fe0141d6a71501e7ffe48"2226+dependencies = [2227+ "bitflags",2228+ "objc2",2229+ "objc2-core-foundation",2230+ "objc2-foundation",2231+ "objc2-security",2232+ "objc2-security-foundation",2233+]2234+2235+[[package]]2236+name = "objc2-encode"2237+version = "4.1.0"2238+source = "registry+https://github.com/rust-lang/crates.io-index"2239+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"2240+2241+[[package]]2242+name = "objc2-foundation"2243+version = "0.3.2"2244+source = "registry+https://github.com/rust-lang/crates.io-index"2245+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"2246+dependencies = [2247+ "bitflags",2248+ "block2",2249+ "libc",2250+ "objc2",2251+ "objc2-core-foundation",2252+]2253+2254+[[package]]2255+name = "objc2-security"2256+version = "0.3.2"2257+source = "registry+https://github.com/rust-lang/crates.io-index"2258+checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a"2259+dependencies = [2260+ "bitflags",2261+ "objc2",2262+ "objc2-core-foundation",2263+]2264+2265+[[package]]2266+name = "objc2-security-foundation"2267+version = "0.3.2"2268+source = "registry+https://github.com/rust-lang/crates.io-index"2269+checksum = "ef76382e9cedd18123099f17638715cc3d81dba3637d4c0d39ab69df2ef345a5"2270+dependencies = [2271+ "objc2",2272+ "objc2-foundation",2273+]2274+2275+[[package]]2276+name = "objc2-system-configuration"2277+version = "0.3.2"2278+source = "registry+https://github.com/rust-lang/crates.io-index"2279+checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"2280+dependencies = [2281+ "bitflags",2282+ "dispatch2",2283+ "libc",2284+ "objc2",2285+ "objc2-core-foundation",2286+ "objc2-security",2287+]2288+2289+[[package]]2290+name = "once_cell"2291+version = "1.21.4"2292+source = "registry+https://github.com/rust-lang/crates.io-index"2293+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"2294+dependencies = [2295+ "critical-section",2296+ "portable-atomic",2297+]2298+2299+[[package]]2300+name = "opaque-debug"2301+version = "0.3.1"2302+source = "registry+https://github.com/rust-lang/crates.io-index"2303+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"2304+2305+[[package]]2306+name = "openssl-probe"2307+version = "0.2.1"2308+source = "registry+https://github.com/rust-lang/crates.io-index"2309+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"2310+2311+[[package]]2312+name = "papaya"2313+version = "0.2.4"2314+source = "registry+https://github.com/rust-lang/crates.io-index"2315+checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7"2316+dependencies = [2317+ "equivalent",2318+ "seize",2319+]2320+2321+[[package]]2322+name = "parking"2323+version = "2.2.1"2324+source = "registry+https://github.com/rust-lang/crates.io-index"2325+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"2326+2327+[[package]]2328+name = "parking_lot"2329+version = "0.12.5"2330+source = "registry+https://github.com/rust-lang/crates.io-index"2331+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"2332+dependencies = [2333+ "lock_api",2334+ "parking_lot_core",2335+]2336+2337+[[package]]2338+name = "parking_lot_core"2339+version = "0.9.12"2340+source = "registry+https://github.com/rust-lang/crates.io-index"2341+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"2342+dependencies = [2343+ "cfg-if",2344+ "libc",2345+ "redox_syscall",2346+ "smallvec",2347+ "windows-link",2348+]2349+2350+[[package]]2351+name = "paste"2352+version = "1.0.15"2353+source = "registry+https://github.com/rust-lang/crates.io-index"2354+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"2355+2356+[[package]]2357+name = "pem-rfc7468"2358+version = "1.0.0"2359+source = "registry+https://github.com/rust-lang/crates.io-index"2360+checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"2361+dependencies = [2362+ "base64ct",2363+]2364+2365+[[package]]2366+name = "percent-encoding"2367+version = "2.3.2"2368+source = "registry+https://github.com/rust-lang/crates.io-index"2369+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"2370+2371+[[package]]2372+name = "pharos"2373+version = "0.5.3"2374+source = "registry+https://github.com/rust-lang/crates.io-index"2375+checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414"2376+dependencies = [2377+ "futures",2378+ "rustc_version",2379+]2380+2381+[[package]]2382+name = "pin-project"2383+version = "1.1.13"2384+source = "registry+https://github.com/rust-lang/crates.io-index"2385+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"2386+dependencies = [2387+ "pin-project-internal",2388+]2389+2390+[[package]]2391+name = "pin-project-internal"2392+version = "1.1.13"2393+source = "registry+https://github.com/rust-lang/crates.io-index"2394+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"2395+dependencies = [2396+ "proc-macro2",2397+ "quote",2398+ "syn",2399+]2400+2401+[[package]]2402+name = "pin-project-lite"2403+version = "0.2.17"2404+source = "registry+https://github.com/rust-lang/crates.io-index"2405+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"2406+2407+[[package]]2408+name = "pkcs8"2409+version = "0.11.0"2410+source = "registry+https://github.com/rust-lang/crates.io-index"2411+checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"2412+dependencies = [2413+ "der",2414+ "spki",2415+]2416+2417+[[package]]2418+name = "plist"2419+version = "1.10.0"2420+source = "registry+https://github.com/rust-lang/crates.io-index"2421+checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"2422+dependencies = [2423+ "base64",2424+ "indexmap",2425+ "quick-xml",2426+ "serde",2427+ "time",2428+]2429+2430+[[package]]2431+name = "polyval"2432+version = "0.6.2"2433+source = "registry+https://github.com/rust-lang/crates.io-index"2434+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"2435+dependencies = [2436+ "cfg-if",2437+ "cpufeatures 0.2.17",2438+ "opaque-debug",2439+ "universal-hash",2440+]2441+2442+[[package]]2443+name = "portable-atomic"2444+version = "1.14.0"2445+source = "registry+https://github.com/rust-lang/crates.io-index"2446+checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"2447+dependencies = [2448+ "serde",2449+]2450+2451+[[package]]2452+name = "portmapper"2453+version = "0.19.1"2454+source = "registry+https://github.com/rust-lang/crates.io-index"2455+checksum = "eb3713e4977408279158444a18c1a01ac9bf2e7eaf1fbfd1a19ac9cd18d90721"2456+dependencies = [2457+ "base64",2458+ "bytes",2459+ "derive_more",2460+ "hyper-util",2461+ "igd-next",2462+ "iroh-metrics",2463+ "libc",2464+ "n0-error",2465+ "n0-future",2466+ "netwatch",2467+ "num_enum",2468+ "rand",2469+ "serde",2470+ "smallvec",2471+ "socket2",2472+ "time",2473+ "tokio",2474+ "tokio-util",2475+ "tower-layer",2476+ "tracing",2477+ "url",2478+]2479+2480+[[package]]2481+name = "postcard"2482+version = "1.1.3"2483+source = "registry+https://github.com/rust-lang/crates.io-index"2484+checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"2485+dependencies = [2486+ "cobs",2487+ "embedded-io 0.4.0",2488+ "embedded-io 0.6.1",2489+ "heapless",2490+ "postcard-derive",2491+ "serde",2492+]2493+2494+[[package]]2495+name = "postcard-derive"2496+version = "0.2.2"2497+source = "registry+https://github.com/rust-lang/crates.io-index"2498+checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb"2499+dependencies = [2500+ "proc-macro2",2501+ "quote",2502+ "syn",2503+]2504+2505+[[package]]2506+name = "potential_utf"2507+version = "0.1.5"2508+source = "registry+https://github.com/rust-lang/crates.io-index"2509+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"2510+dependencies = [2511+ "zerovec",2512+]2513+2514+[[package]]2515+name = "powerfmt"2516+version = "0.2.0"2517+source = "registry+https://github.com/rust-lang/crates.io-index"2518+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"2519+2520+[[package]]2521+name = "prefix-trie"2522+version = "0.8.4"2523+source = "registry+https://github.com/rust-lang/crates.io-index"2524+checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7"2525+dependencies = [2526+ "either",2527+ "ipnet",2528+ "num-traits",2529+]2530+2531+[[package]]2532+name = "proc-macro-crate"2533+version = "3.5.0"2534+source = "registry+https://github.com/rust-lang/crates.io-index"2535+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"2536+dependencies = [2537+ "toml_edit",2538+]2539+2540+[[package]]2541+name = "proc-macro2"2542+version = "1.0.106"2543+source = "registry+https://github.com/rust-lang/crates.io-index"2544+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"2545+dependencies = [2546+ "unicode-ident",2547+]2548+2549+[[package]]2550+name = "quick-xml"2551+version = "0.41.0"2552+source = "registry+https://github.com/rust-lang/crates.io-index"2553+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"2554+dependencies = [2555+ "memchr",2556+]2557+2558+[[package]]2559+name = "quote"2560+version = "1.0.46"2561+source = "registry+https://github.com/rust-lang/crates.io-index"2562+checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"2563+dependencies = [2564+ "proc-macro2",2565+]2566+2567+[[package]]2568+name = "r-efi"2569+version = "6.0.0"2570+source = "registry+https://github.com/rust-lang/crates.io-index"2571+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"2572+2573+[[package]]2574+name = "rand"2575+version = "0.10.2"2576+source = "registry+https://github.com/rust-lang/crates.io-index"2577+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"2578+dependencies = [2579+ "chacha20",2580+ "getrandom 0.4.3",2581+ "rand_core",2582+]2583+2584+[[package]]2585+name = "rand_core"2586+version = "0.10.1"2587+source = "registry+https://github.com/rust-lang/crates.io-index"2588+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"2589+2590+[[package]]2591+name = "rand_pcg"2592+version = "0.10.2"2593+source = "registry+https://github.com/rust-lang/crates.io-index"2594+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"2595+dependencies = [2596+ "rand_core",2597+]2598+2599+[[package]]2600+name = "redox_syscall"2601+version = "0.5.18"2602+source = "registry+https://github.com/rust-lang/crates.io-index"2603+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"2604+dependencies = [2605+ "bitflags",2606+]2607+2608+[[package]]2609+name = "regex-automata"2610+version = "0.4.16"2611+source = "registry+https://github.com/rust-lang/crates.io-index"2612+checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"2613+dependencies = [2614+ "aho-corasick",2615+ "memchr",2616+ "regex-syntax",2617+]2618+2619+[[package]]2620+name = "regex-syntax"2621+version = "0.8.11"2622+source = "registry+https://github.com/rust-lang/crates.io-index"2623+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"2624+2625+[[package]]2626+name = "reqwest"2627+version = "0.13.4"2628+source = "registry+https://github.com/rust-lang/crates.io-index"2629+checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"2630+dependencies = [2631+ "base64",2632+ "bytes",2633+ "futures-core",2634+ "futures-util",2635+ "http",2636+ "http-body",2637+ "http-body-util",2638+ "hyper",2639+ "hyper-rustls",2640+ "hyper-util",2641+ "js-sys",2642+ "log",2643+ "percent-encoding",2644+ "pin-project-lite",2645+ "rustls",2646+ "rustls-pki-types",2647+ "rustls-platform-verifier",2648+ "sync_wrapper",2649+ "tokio",2650+ "tokio-rustls",2651+ "tokio-util",2652+ "tower",2653+ "tower-http",2654+ "tower-service",2655+ "url",2656+ "wasm-bindgen",2657+ "wasm-bindgen-futures",2658+ "wasm-streams",2659+ "web-sys",2660+]2661+2662+[[package]]2663+name = "resolv-conf"2664+version = "0.7.6"2665+source = "registry+https://github.com/rust-lang/crates.io-index"2666+checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"2667+2668+[[package]]2669+name = "ring"2670+version = "0.17.14"2671+source = "registry+https://github.com/rust-lang/crates.io-index"2672+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"2673+dependencies = [2674+ "cc",2675+ "cfg-if",2676+ "getrandom 0.2.17",2677+ "libc",2678+ "untrusted",2679+ "windows-sys 0.52.0",2680+]2681+2682+[[package]]2683+name = "rustc-hash"2684+version = "2.1.3"2685+source = "registry+https://github.com/rust-lang/crates.io-index"2686+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"2687+2688+[[package]]2689+name = "rustc_version"2690+version = "0.4.1"2691+source = "registry+https://github.com/rust-lang/crates.io-index"2692+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"2693+dependencies = [2694+ "semver",2695+]2696+2697+[[package]]2698+name = "rustls"2699+version = "0.23.42"2700+source = "registry+https://github.com/rust-lang/crates.io-index"2701+checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"2702+dependencies = [2703+ "log",2704+ "once_cell",2705+ "ring",2706+ "rustls-pki-types",2707+ "rustls-webpki",2708+ "subtle",2709+ "zeroize",2710+]2711+2712+[[package]]2713+name = "rustls-native-certs"2714+version = "0.8.4"2715+source = "registry+https://github.com/rust-lang/crates.io-index"2716+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"2717+dependencies = [2718+ "openssl-probe",2719+ "rustls-pki-types",2720+ "schannel",2721+ "security-framework",2722+]2723+2724+[[package]]2725+name = "rustls-pki-types"2726+version = "1.15.0"2727+source = "registry+https://github.com/rust-lang/crates.io-index"2728+checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"2729+dependencies = [2730+ "web-time",2731+ "zeroize",2732+]2733+2734+[[package]]2735+name = "rustls-platform-verifier"2736+version = "0.7.0"2737+source = "registry+https://github.com/rust-lang/crates.io-index"2738+checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"2739+dependencies = [2740+ "core-foundation 0.10.1",2741+ "core-foundation-sys",2742+ "jni 0.22.4",2743+ "log",2744+ "once_cell",2745+ "rustls",2746+ "rustls-native-certs",2747+ "rustls-platform-verifier-android",2748+ "rustls-webpki",2749+ "security-framework",2750+ "security-framework-sys",2751+ "webpki-root-certs",2752+ "windows-sys 0.61.2",2753+]2754+2755+[[package]]2756+name = "rustls-platform-verifier-android"2757+version = "0.1.1"2758+source = "registry+https://github.com/rust-lang/crates.io-index"2759+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"2760+2761+[[package]]2762+name = "rustls-webpki"2763+version = "0.103.13"2764+source = "registry+https://github.com/rust-lang/crates.io-index"2765+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"2766+dependencies = [2767+ "ring",2768+ "rustls-pki-types",2769+ "untrusted",2770+]2771+2772+[[package]]2773+name = "rustversion"2774+version = "1.0.23"2775+source = "registry+https://github.com/rust-lang/crates.io-index"2776+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"2777+2778+[[package]]2779+name = "ryu"2780+version = "1.0.23"2781+source = "registry+https://github.com/rust-lang/crates.io-index"2782+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"2783+2784+[[package]]2785+name = "same-file"2786+version = "1.0.6"2787+source = "registry+https://github.com/rust-lang/crates.io-index"2788+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"2789+dependencies = [2790+ "winapi-util",2791+]2792+2793+[[package]]2794+name = "schannel"2795+version = "0.1.29"2796+source = "registry+https://github.com/rust-lang/crates.io-index"2797+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"2798+dependencies = [2799+ "windows-sys 0.61.2",2800+]2801+2802+[[package]]2803+name = "scoped-tls"2804+version = "1.0.1"2805+source = "registry+https://github.com/rust-lang/crates.io-index"2806+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"2807+2808+[[package]]2809+name = "scopeguard"2810+version = "1.2.0"2811+source = "registry+https://github.com/rust-lang/crates.io-index"2812+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"2813+2814+[[package]]2815+name = "security-framework"2816+version = "3.7.0"2817+source = "registry+https://github.com/rust-lang/crates.io-index"2818+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"2819+dependencies = [2820+ "bitflags",2821+ "core-foundation 0.10.1",2822+ "core-foundation-sys",2823+ "libc",2824+ "security-framework-sys",2825+]2826+2827+[[package]]2828+name = "security-framework-sys"2829+version = "2.17.0"2830+source = "registry+https://github.com/rust-lang/crates.io-index"2831+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"2832+dependencies = [2833+ "core-foundation-sys",2834+ "libc",2835+]2836+2837+[[package]]2838+name = "seize"2839+version = "0.5.1"2840+source = "registry+https://github.com/rust-lang/crates.io-index"2841+checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521"2842+dependencies = [2843+ "libc",2844+ "windows-sys 0.61.2",2845+]2846+2847+[[package]]2848+name = "semver"2849+version = "1.0.28"2850+source = "registry+https://github.com/rust-lang/crates.io-index"2851+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"2852+2853+[[package]]2854+name = "send_wrapper"2855+version = "0.6.0"2856+source = "registry+https://github.com/rust-lang/crates.io-index"2857+checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"2858+2859+[[package]]2860+name = "serde"2861+version = "1.0.228"2862+source = "registry+https://github.com/rust-lang/crates.io-index"2863+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"2864+dependencies = [2865+ "serde_core",2866+ "serde_derive",2867+]2868+2869+[[package]]2870+name = "serde_bytes"2871+version = "0.11.19"2872+source = "registry+https://github.com/rust-lang/crates.io-index"2873+checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"2874+dependencies = [2875+ "serde",2876+ "serde_core",2877+]2878+2879+[[package]]2880+name = "serde_core"2881+version = "1.0.228"2882+source = "registry+https://github.com/rust-lang/crates.io-index"2883+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"2884+dependencies = [2885+ "serde_derive",2886+]2887+2888+[[package]]2889+name = "serde_derive"2890+version = "1.0.228"2891+source = "registry+https://github.com/rust-lang/crates.io-index"2892+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"2893+dependencies = [2894+ "proc-macro2",2895+ "quote",2896+ "syn",2897+]2898+2899+[[package]]2900+name = "serdect"2901+version = "0.4.3"2902+source = "registry+https://github.com/rust-lang/crates.io-index"2903+checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"2904+dependencies = [2905+ "base16ct",2906+ "serde",2907+]2908+2909+[[package]]2910+name = "sha1_smol"2911+version = "1.0.1"2912+source = "registry+https://github.com/rust-lang/crates.io-index"2913+checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"2914+2915+[[package]]2916+name = "sha2"2917+version = "0.11.0"2918+source = "registry+https://github.com/rust-lang/crates.io-index"2919+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"2920+dependencies = [2921+ "cfg-if",2922+ "cpufeatures 0.3.0",2923+ "digest",2924+]2925+2926+[[package]]2927+name = "sharded-slab"2928+version = "0.1.7"2929+source = "registry+https://github.com/rust-lang/crates.io-index"2930+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"2931+dependencies = [2932+ "lazy_static",2933+]2934+2935+[[package]]2936+name = "shlex"2937+version = "2.0.1"2938+source = "registry+https://github.com/rust-lang/crates.io-index"2939+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"2940+2941+[[package]]2942+name = "signal-hook-registry"2943+version = "1.4.8"2944+source = "registry+https://github.com/rust-lang/crates.io-index"2945+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"2946+dependencies = [2947+ "errno",2948+ "libc",2949+]2950+2951+[[package]]2952+name = "signature"2953+version = "3.0.0"2954+source = "registry+https://github.com/rust-lang/crates.io-index"2955+checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"2956+2957+[[package]]2958+name = "simd_cesu8"2959+version = "1.2.0"2960+source = "registry+https://github.com/rust-lang/crates.io-index"2961+checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"2962+dependencies = [2963+ "rustc_version",2964+ "simdutf8",2965+]2966+2967+[[package]]2968+name = "simdutf8"2969+version = "0.1.5"2970+source = "registry+https://github.com/rust-lang/crates.io-index"2971+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"2972+2973+[[package]]2974+name = "simple-dns"2975+version = "0.11.3"2976+source = "registry+https://github.com/rust-lang/crates.io-index"2977+checksum = "7a75cbde1bf934313596a004973e462f9a82caa814dcf1a5f507bdf51597eeb4"2978+dependencies = [2979+ "bitflags",2980+]2981+2982+[[package]]2983+name = "slab"2984+version = "0.4.12"2985+source = "registry+https://github.com/rust-lang/crates.io-index"2986+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"2987+2988+[[package]]2989+name = "smallvec"2990+version = "1.15.2"2991+source = "registry+https://github.com/rust-lang/crates.io-index"2992+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"2993+2994+[[package]]2995+name = "socket2"2996+version = "0.6.5"2997+source = "registry+https://github.com/rust-lang/crates.io-index"2998+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"2999+dependencies = [3000+ "libc",diff truncated
@@ -0,0 +1,30 @@
1+[package]2+edition = "2024"3+name = "number0_iroh"4+version = "1.1.0"5+license = "MIT OR Apache-2.0"6+authors = ["n0 team"]7+repository = "https://github.com/n0-computer/iroh-ffi"8+rust-version = "1.91"9+10+[lib]11+crate-type = ["cdylib"]12+13+[dependencies]14+napi = { version = "3", default-features = false, features = ["napi8", "error_anyhow", "async"] }15+napi-derive = "3"16+anyhow = "1.0.69"17+iroh = "1.0.0"18+iroh-base = "1.0.0"19+iroh-metrics = "1.0.0"20+iroh-relay = { version = "1.0.0", default-features = false }21+# lofi-node: iroh-services dependency dropped (services module not vendored)22+iroh-tickets = "1.0.0"23+n0-future = "0.3"24+tokio = "1.25.0"25+url = "2.4"26+tracing = "0.1.40"27+tracing-subscriber = { version = "0.3.17" }28+29+[build-dependencies]30+napi-build = "2.2"@@ -0,0 +1,201 @@
1+ Apache License2+ Version 2.0, January 20043+ http://www.apache.org/licenses/4+5+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION6+7+1. Definitions.8+9+ "License" shall mean the terms and conditions for use, reproduction,10+ and distribution as defined by Sections 1 through 9 of this document.11+12+ "Licensor" shall mean the copyright owner or entity authorized by13+ the copyright owner that is granting the License.14+15+ "Legal Entity" shall mean the union of the acting entity and all16+ other entities that control, are controlled by, or are under common17+ control with that entity. For the purposes of this definition,18+ "control" means (i) the power, direct or indirect, to cause the19+ direction or management of such entity, whether by contract or20+ otherwise, or (ii) ownership of fifty percent (50%) or more of the21+ outstanding shares, or (iii) beneficial ownership of such entity.22+23+ "You" (or "Your") shall mean an individual or Legal Entity24+ exercising permissions granted by this License.25+26+ "Source" form shall mean the preferred form for making modifications,27+ including but not limited to software source code, documentation28+ source, and configuration files.29+30+ "Object" form shall mean any form resulting from mechanical31+ transformation or translation of a Source form, including but32+ not limited to compiled object code, generated documentation,33+ and conversions to other media types.34+35+ "Work" shall mean the work of authorship, whether in Source or36+ Object form, made available under the License, as indicated by a37+ copyright notice that is included in or attached to the work38+ (an example is provided in the Appendix below).39+40+ "Derivative Works" shall mean any work, whether in Source or Object41+ form, that is based on (or derived from) the Work and for which the42+ editorial revisions, annotations, elaborations, or other modifications43+ represent, as a whole, an original work of authorship. For the purposes44+ of this License, Derivative Works shall not include works that remain45+ separable from, or merely link (or bind by name) to the interfaces of,46+ the Work and Derivative Works thereof.47+48+ "Contribution" shall mean any work of authorship, including49+ the original version of the Work and any modifications or additions50+ to that Work or Derivative Works thereof, that is intentionally51+ submitted to Licensor for inclusion in the Work by the copyright owner52+ or by an individual or Legal Entity authorized to submit on behalf of53+ the copyright owner. For the purposes of this definition, "submitted"54+ means any form of electronic, verbal, or written communication sent55+ to the Licensor or its representatives, including but not limited to56+ communication on electronic mailing lists, source code control systems,57+ and issue tracking systems that are managed by, or on behalf of, the58+ Licensor for the purpose of discussing and improving the Work, but59+ excluding communication that is conspicuously marked or otherwise60+ designated in writing by the copyright owner as "Not a Contribution."61+62+ "Contributor" shall mean Licensor and any individual or Legal Entity63+ on behalf of whom a Contribution has been received by Licensor and64+ subsequently incorporated within the Work.65+66+2. Grant of Copyright License. Subject to the terms and conditions of67+ this License, each Contributor hereby grants to You a perpetual,68+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable69+ copyright license to reproduce, prepare Derivative Works of,70+ publicly display, publicly perform, sublicense, and distribute the71+ Work and such Derivative Works in Source or Object form.72+73+3. Grant of Patent License. Subject to the terms and conditions of74+ this License, each Contributor hereby grants to You a perpetual,75+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable76+ (except as stated in this section) patent license to make, have made,77+ use, offer to sell, sell, import, and otherwise transfer the Work,78+ where such license applies only to those patent claims licensable79+ by such Contributor that are necessarily infringed by their80+ Contribution(s) alone or by combination of their Contribution(s)81+ with the Work to which such Contribution(s) was submitted. If You82+ institute patent litigation against any entity (including a83+ cross-claim or counterclaim in a lawsuit) alleging that the Work84+ or a Contribution incorporated within the Work constitutes direct85+ or contributory patent infringement, then any patent licenses86+ granted to You under this License for that Work shall terminate87+ as of the date such litigation is filed.88+89+4. Redistribution. You may reproduce and distribute copies of the90+ Work or Derivative Works thereof in any medium, with or without91+ modifications, and in Source or Object form, provided that You92+ meet the following conditions:93+94+ (a) You must give any other recipients of the Work or95+ Derivative Works a copy of this License; and96+97+ (b) You must cause any modified files to carry prominent notices98+ stating that You changed the files; and99+100+ (c) You must retain, in the Source form of any Derivative Works101+ that You distribute, all copyright, patent, trademark, and102+ attribution notices from the Source form of the Work,103+ excluding those notices that do not pertain to any part of104+ the Derivative Works; and105+106+ (d) If the Work includes a "NOTICE" text file as part of its107+ distribution, then any Derivative Works that You distribute must108+ include a readable copy of the attribution notices contained109+ within such NOTICE file, excluding those notices that do not110+ pertain to any part of the Derivative Works, in at least one111+ of the following places: within a NOTICE text file distributed112+ as part of the Derivative Works; within the Source form or113+ documentation, if provided along with the Derivative Works; or,114+ within a display generated by the Derivative Works, if and115+ wherever such third-party notices normally appear. The contents116+ of the NOTICE file are for informational purposes only and117+ do not modify the License. You may add Your own attribution118+ notices within Derivative Works that You distribute, alongside119+ or as an addendum to the NOTICE text from the Work, provided120+ that such additional attribution notices cannot be construed121+ as modifying the License.122+123+ You may add Your own copyright statement to Your modifications and124+ may provide additional or different license terms and conditions125+ for use, reproduction, or distribution of Your modifications, or126+ for any such Derivative Works as a whole, provided Your use,127+ reproduction, and distribution of the Work otherwise complies with128+ the conditions stated in this License.129+130+5. Submission of Contributions. Unless You explicitly state otherwise,131+ any Contribution intentionally submitted for inclusion in the Work132+ by You to the Licensor shall be under the terms and conditions of133+ this License, without any additional terms or conditions.134+ Notwithstanding the above, nothing herein shall supersede or modify135+ the terms of any separate license agreement you may have executed136+ with Licensor regarding such Contributions.137+138+6. Trademarks. This License does not grant permission to use the trade139+ names, trademarks, service marks, or product names of the Licensor,140+ except as required for reasonable and customary use in describing the141+ origin of the Work and reproducing the content of the NOTICE file.142+143+7. Disclaimer of Warranty. Unless required by applicable law or144+ agreed to in writing, Licensor provides the Work (and each145+ Contributor provides its Contributions) on an "AS IS" BASIS,146+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or147+ implied, including, without limitation, any warranties or conditions148+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A149+ PARTICULAR PURPOSE. You are solely responsible for determining the150+ appropriateness of using or redistributing the Work and assume any151+ risks associated with Your exercise of permissions under this License.152+153+8. Limitation of Liability. In no event and under no legal theory,154+ whether in tort (including negligence), contract, or otherwise,155+ unless required by applicable law (such as deliberate and grossly156+ negligent acts) or agreed to in writing, shall any Contributor be157+ liable to You for damages, including any direct, indirect, special,158+ incidental, or consequential damages of any character arising as a159+ result of this License or out of the use or inability to use the160+ Work (including but not limited to damages for loss of goodwill,161+ work stoppage, computer failure or malfunction, or any and all162+ other commercial damages or losses), even if such Contributor163+ has been advised of the possibility of such damages.164+165+9. Accepting Warranty or Additional Liability. While redistributing166+ the Work or Derivative Works thereof, You may choose to offer,167+ and charge a fee for, acceptance of support, warranty, indemnity,168+ or other liability obligations and/or rights consistent with this169+ License. However, in accepting such obligations, You may act only170+ on Your own behalf and on Your sole responsibility, not on behalf171+ of any other Contributor, and only if You agree to indemnify,172+ defend, and hold each Contributor harmless for any liability173+ incurred by, or claims asserted against, such Contributor by reason174+ of your accepting any such warranty or additional liability.175+176+END OF TERMS AND CONDITIONS177+178+APPENDIX: How to apply the Apache License to your work.179+180+ To apply the Apache License to your work, attach the following181+ boilerplate notice, with the fields enclosed by brackets "[]"182+ replaced with your own identifying information. (Don't include183+ the brackets!) The text should be enclosed in the appropriate184+ comment syntax for the file format. We also recommend that a185+ file or class name and description of purpose be included on the186+ same "printed page" as the copyright notice for easier187+ identification within third-party archives.188+189+Copyright [2025] [N0, INC]190+191+Licensed under the Apache License, Version 2.0 (the "License");192+you may not use this file except in compliance with the License.193+You may obtain a copy of the License at194+195+ http://www.apache.org/licenses/LICENSE-2.0196+197+Unless required by applicable law or agreed to in writing, software198+distributed under the License is distributed on an "AS IS" BASIS,199+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.200+See the License for the specific language governing permissions and201+limitations under the License.@@ -0,0 +1,25 @@
1+Copyright 2025 N0, INC.2+3+Permission is hereby granted, free of charge, to any4+person obtaining a copy of this software and associated5+documentation files (the "Software"), to deal in the6+Software without restriction, including without7+limitation the rights to use, copy, modify, merge,8+publish, distribute, sublicense, and/or sell copies of9+the Software, and to permit persons to whom the Software10+is furnished to do so, subject to the following11+conditions:12+13+The above copyright notice and this permission notice14+shall be included in all copies or substantial portions15+of the Software.16+17+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF18+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED19+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A20+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT21+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY22+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION23+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR24+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER25+DEALINGS IN THE SOFTWARE.@@ -0,0 +1,52 @@
1+# Provenance: vendored from n0-computer/iroh-ffi (iroh-js)2+3+- **Upstream**: https://github.com/n0-computer/iroh-ffi — `iroh-js/` crate4+- **Tag**: `v1.1.0`5+- **Commit**: `5e451092dba0c1a09ee83ff6e5be37b1152a5c58`6+- **License**: MIT OR Apache-2.0 (both vendored alongside)7+8+## Vendored files (byte-identical to upstream unless a `// lofi-node:` marker says otherwise)9+10+| File | Status |11+|---|---|12+| `src/endpoint.rs` | 2 marked edits: `pub(crate)` on the SendStream/RecvStream tuple fields (needed by `lofi_ext`) |13+| `src/key.rs` | identical |14+| `src/net.rs` | identical |15+| `src/path.rs` | identical |16+| `src/relay.rs` | identical |17+| `src/ticket.rs` | identical |18+| `src/watch.rs` | identical (but see Known issues) |19+| `src/lib.rs` | marked edits: services module/re-export dropped; `lofi_ext` module registered |20+| `Cargo.toml` | marked edit: `iroh-services` dependency dropped |21+| `build.rs` | identical |22+| `index.d.ts` | upstream-generated, vendored as reference; services classes listed there are NOT exported by this build |23+24+## Dropped (not vendored)25+26+- `src/services.rs` + `iroh-services` dependency — lofi-node does not use iroh services.27+- npm packaging (`npm/`, `package.json`, yarn) — lofi-node embeds its own artifacts.28+- Android glue, test/, tsconfig/typedoc.29+30+## Not upstream31+32+- `src/lofi_ext.rs` — Buffer-based length-prefixed framing (`writeFrame`,33+ `readFrame`, `maxFrame`). Justified at gate 0: upstream `Array<number>`34+ stream I/O measured 7.1 MiB/s loopback round-trip. Deletes if upstream35+ grows Buffer stream I/O.36+37+## Known issues (upstream, observed under Deno at v1.1.0)38+39+- **watch APIs abort the process**: `watchAddr`/`watchPaths`/… call40+ `tokio::spawn` from the calling JS thread (`watch.rs:35`, "no reactor41+ running") and the panic cannot unwind through napi → hard abort. The TS42+ layer must never call watch APIs; poll instead. Candidate upstream report.43+44+## Update procedure (tag bump)45+46+1. `git clone --depth 1 --branch <newtag> https://github.com/n0-computer/iroh-ffi`47+2. `diff -r <clone>/iroh-js/src native/iroh-js/src` — every diff hunk must be48+ either upstream's change (take it) or a `// lofi-node:` marker (keep it).49+3. Re-check: does upstream now have Buffer stream I/O (delete `lofi_ext`)?50+ Did the watch tokio-context bug get fixed (unblock watch APIs)?51+4. Update tag/commit here; `cargo build --release`; run the full Deno fixture52+ suite (`deno task test`) — it is the regression gate.@@ -0,0 +1,456 @@
1+/* auto-generated by NAPI-RS */2+/* eslint-disable */3+/** A server-side handshake in progress. */4+export declare class Accepting {5+ connect(): Promise<Connection>6+ alpn(): Promise<Array<number>>7+}8+9+/** A bidirectional QUIC stream pair. */10+export declare class BiStream {11+ get send(): SendStream12+ get recv(): RecvStream13+}14+15+/** A client-side handshake in progress. */16+export declare class Connecting {17+ connect(): Promise<Connection>18+ alpn(): Promise<Array<number>>19+ remoteId(): Promise<EndpointId>20+}21+22+/** An active QUIC connection. */23+export declare class Connection {24+ alpn(): Array<number>25+ remoteId(): EndpointId26+ side(): Side27+ openUni(): Promise<SendStream>28+ acceptUni(): Promise<RecvStream>29+ openBi(): Promise<BiStream>30+ acceptBi(): Promise<BiStream>31+ readDatagram(): Promise<Array<number>>32+ closed(): Promise<string>33+ closeReason(): string | null34+ close(errorCode: bigint, reason: Array<number>): void35+ sendDatagram(data: Array<number>): void36+ sendDatagramWait(data: Array<number>): Promise<void>37+ maxDatagramSize(): number | null38+ datagramSendBufferSpace(): number39+ stableId(): number40+ rtt(): number | null41+ stats(): ConnectionStats42+ paths(): Array<PathSnapshot>43+ watchPaths(callback: (paths: Array<PathSnapshot>) => void): WatchHandle44+ watchPathEvents(callback: (event: PathEvent) => void): WatchHandle45+ setMaxConcurrentUniStreams(count: bigint): void46+ setReceiveWindow(count: bigint): void47+ setMaxConcurrentBiStreams(count: bigint): void48+}49+50+/** An iroh endpoint. */51+export declare class Endpoint {52+ /** Create an endpoint builder (starts empty — apply a preset). */53+ static builder(): EndpointBuilder54+ /**55+ * Bind a new endpoint. Applies the n0 preset, then the given options.56+ * For a custom preset use [`Endpoint::builder`].57+ */58+ static bind(options?: EndpointOptions | undefined | null, relayMode?: RelayMode | undefined | null): Promise<Endpoint>59+ /** This endpoint's id. */60+ id(): EndpointId61+ /** The [`EndpointAddr`] for this endpoint. */62+ addr(): EndpointAddr63+ /** Look up cached information about a remote endpoint, if any. */64+ remoteAddr(id: EndpointId): Promise<EndpointAddr | null>65+ /** Current statistics for this endpoint. */66+ stats(): Record<string, CounterStats>67+ /** The secret key backing this endpoint's identity. */68+ secretKey(): SecretKey69+ /** Replace the set of advertised ALPNs. */70+ setAlpns(alpns: Array<Array<number>>): void71+ /** Add an external (manually-known) socket address. */72+ addExternalAddr(addr: string): Promise<void>73+ /** Remove a previously-added external address. */74+ removeExternalAddr(addr: string): Promise<boolean>75+ /** The local socket addresses this endpoint is bound to. */76+ boundSockets(): Array<string>77+ /** Resolves once the endpoint has a usable home relay. */78+ online(): Promise<void>79+ /** Insert (or replace) a relay configuration at runtime. */80+ insertRelay(config: RelayConfig): Promise<void>81+ /** Remove a relay configuration at runtime. */82+ removeRelay(url: string): Promise<boolean>83+ /** Connect to a remote endpoint via the given ALPN. */84+ connect(addr: EndpointAddr, alpn: Array<number>): Promise<Connection>85+ /** Begin a connection attempt, returning the in-progress handle. */86+ connectPending(addr: EndpointAddr, alpn: Array<number>): Promise<Connecting>87+ /** Pull the next incoming connection attempt. */88+ acceptNext(): Promise<Incoming | null>89+ /** Watch for changes to this endpoint's address. */90+ watchAddr(callback: (addr: EndpointAddr) => void): WatchHandle91+ /** Watch for changes to the connected relays. */92+ watchHomeRelay(callback: (relayUrls: Array<string>) => void): WatchHandle93+ /** Watch for network-stack changes. */94+ watchNetworkChange(callback: () => void): WatchHandle95+ /** Close the endpoint. */96+ close(): Promise<void>97+ /** True if the endpoint is closed. */98+ isClosed(): boolean99+}100+101+/**102+ * An endpoint's id together with the network-level addresses where it can be103+ * reached. Mirrors the uniffi `EndpointAddr` object surface.104+ */105+export declare class EndpointAddr {106+ /** Create a new [`EndpointAddr`]. */107+ constructor(id: EndpointId, relayUrl?: string | undefined | null, addresses?: Array<string> | undefined | null)108+ /** The endpoint id. */109+ id(): EndpointId110+ /** The direct (ip:port) addresses of this peer. */111+ directAddresses(): Array<string>112+ /** The home relay URL for this peer, if known. */113+ relayUrl(): string | null114+ /** Returns true if both [`EndpointAddr`]s have the same values. */115+ equal(other: EndpointAddr): boolean116+ /** Clean string representation. */117+ toString(): string118+}119+120+/**121+ * A mutable handle to an endpoint builder.122+ *123+ * Mirrors the uniffi `EndpointBuilder`. A "preset" in JS is simply any124+ * function `(builder: EndpointBuilder) => void` — call one of the built-in125+ * [`presetN0`] / [`presetMinimal`] / [`presetN0DisableRelay`] helpers (which126+ * install the crypto provider) and then layer on your own configuration.127+ *128+ * ```js129+ * const b = Endpoint.builder()130+ * presetMinimal(b)131+ * b.alpns([alpn])132+ * const ep = await b.bind()133+ * ```134+ */135+export declare class EndpointBuilder {136+ /** Replay the n0 production preset (relays + discovery + crypto provider). */137+ applyN0(): void138+ /** Replay the minimal preset (crypto provider only, no external deps). */139+ applyMinimal(): void140+ /** Replay the n0 preset with relays disabled. */141+ applyN0DisableRelay(): void142+ /** Set the endpoint secret key (32 bytes). */143+ secretKey(bytes: Array<number>): void144+ /** Set the advertised ALPNs. */145+ alpns(alpns: Array<Array<number>>): void146+ /** Set the relay mode. */147+ relayMode(mode: RelayMode): void148+ /** Set the address the endpoint binds to (`host:port`). */149+ bindAddr(addr: string): void150+ /** Bind the endpoint. */151+ bind(): Promise<Endpoint>152+}153+154+/** An endpoint's identifier, a 32-byte ed25519 public key. */155+export declare class EndpointId {156+ /** Returns true if both [`EndpointId`]s are equal. */157+ equals(other: EndpointId): boolean158+ /** Get the underlying 32 bytes. */159+ toBytes(): Array<number>160+ /** Parse an [`EndpointId`] from its base32 representation. */161+ static fromString(s: string): EndpointId162+ /** Construct an [`EndpointId`] from raw bytes (32 bytes). */163+ static fromBytes(bytes: Array<number>): EndpointId164+ /** Short base32 prefix. */165+ fmtShort(): string166+ /** Base32 string form. */167+ toString(): string168+ /** Verify a signature on `message` against this endpoint's key. */169+ verify(message: Array<number>, signature: Signature): void170+}171+172+/** A token containing information for establishing a connection to an endpoint. */173+export declare class EndpointTicket {174+ /** Wrap an [`EndpointAddr`] into a ticket. */175+ static fromAddr(addr: EndpointAddr): EndpointTicket176+ /** Parse a ticket from its base32 string form. */177+ static fromString(s: string): EndpointTicket178+ /** The [`EndpointAddr`] embedded in this ticket. */179+ endpointAddr(): EndpointAddr180+ /** Base32 string form. */181+ toString(): string182+}183+184+/** An incoming connection that has not yet begun its server-side handshake. */185+export declare class Incoming {186+ accept(): Promise<Accepting>187+ refuse(): Promise<void>188+ retry(): Promise<void>189+ ignore(): Promise<void>190+ localAddr(): Promise<IncomingLocalAddr>191+ remoteAddr(): Promise<IncomingAddr>192+ remoteAddrValidated(): Promise<boolean>193+}194+195+/** The incoming half of a QUIC stream. */196+export declare class RecvStream {197+ read(sizeLimit: number): Promise<Array<number>>198+ readExact(size: number): Promise<Array<number>>199+ readToEnd(sizeLimit: number): Promise<Array<number>>200+ id(): Promise<string>201+ bytesRead(): Promise<number>202+ stop(errorCode: bigint): Promise<void>203+ receivedReset(): Promise<number | null>204+}205+206+/** A collection of relay servers an endpoint should consider. */207+export declare class RelayMap {208+ /** Create an empty relay map. */209+ static empty(): RelayMap210+ /** Build a relay map from a list of relay URLs. */211+ static fromUrls(urls: Array<string>): RelayMap212+ /** Insert a relay (replacing any prior entry for the same URL). */213+ insert(config: RelayConfig): void214+ /** Remove the entry for the given relay URL. Returns true if removed. */215+ remove(url: string): boolean216+ /** Check whether the given relay URL is in the map. */217+ contains(url: string): boolean218+ /** Look up the configuration for the given relay URL. */219+ get(url: string): RelayConfig | null220+ /** All relay URLs currently in the map. */221+ urls(): Array<string>222+ /** Number of relays in the map. */223+ len(): number224+ /** True if the map has no relays. */225+ isEmpty(): boolean226+ /** Clean string representation. */227+ toString(): string228+}229+230+/** Configuration for which relay servers an endpoint uses. */231+export declare class RelayMode {232+ /** No relays. */233+ static disabled(): RelayMode234+ /** Use the n0 production relay map. */235+ static defaultMode(): RelayMode236+ /** Use the n0 staging relay map. */237+ static staging(): RelayMode238+ /** Use a custom relay map. */239+ static custom(map: RelayMap): RelayMode240+ /** Build a custom relay mode from a list of relay URLs. */241+ static customFromUrls(urls: Array<string>): RelayMode242+ /** The relay map this mode resolves to. */243+ relayMap(): RelayMap244+ /** Clean string representation. */245+ toString(): string246+}247+248+/** The secret key half of an endpoint identity. */249+export declare class SecretKey {250+ /** Generate a new random secret key. */251+ static generate(): SecretKey252+ /** Construct from raw bytes (32 bytes). */253+ static fromBytes(bytes: Array<number>): SecretKey254+ /** Raw 32-byte form. */255+ toBytes(): Array<number>256+ /** The public [`EndpointId`] derived from this secret key. */257+ public(): EndpointId258+ /** Sign a message, producing an ed25519 signature. */259+ sign(message: Array<number>): Signature260+}261+262+/** The outgoing half of a QUIC stream. */263+export declare class SendStream {264+ write(buf: Array<number>): Promise<number>265+ writeAll(buf: Array<number>): Promise<void>266+ finish(): Promise<void>267+ reset(errorCode: bigint): Promise<void>268+ setPriority(p: number): Promise<void>269+ priority(): Promise<number>270+ stopped(): Promise<number | null>271+ id(): Promise<string>272+}273+274+/** Client for services.iroh.computer. */275+export declare class ServicesClient {276+ /** Build a new client bound to the given endpoint. */277+ static create(endpoint: Endpoint, options: ServicesOptions): Promise<ServicesClient>278+ /** Read the current endpoint name from the local client. */279+ name(): Promise<string | null>280+ /** Set the endpoint name cloud-side. */281+ setName(name: string): Promise<void>282+ /** Ping the remote service. */283+ ping(): Promise<void>284+ /** Force a metrics flush. */285+ pushMetrics(): Promise<void>286+ /** Run a network-diagnostics report, optionally submitting it. */287+ submitNetworkDiagnostics(send: boolean): Promise<DiagnosticsSummary>288+}289+290+/** An ed25519 signature over a message. */291+export declare class Signature {292+ /** Construct from raw bytes (64 bytes). */293+ static fromBytes(bytes: Array<number>): Signature294+ /** Raw 64-byte form. */295+ toBytes(): Array<number>296+ /** Lowercase hex representation. */297+ toString(): string298+}299+300+/** Handle to a running watcher task. Call `stop()` (or drop) to unregister. */301+export declare class WatchHandle {302+ /** Stop the watcher, aborting the background task. */303+ stop(): Promise<void>304+}305+306+/** Flat snapshot of headline connection statistics. */307+export interface ConnectionStats {308+ udpTxDatagrams: number309+ udpTxBytes: number310+ udpRxDatagrams: number311+ udpRxBytes: number312+ lostPackets: number313+ lostBytes: number314+}315+316+/** A snapshot value for a single endpoint metric. */317+export interface CounterStats {318+ value: number319+ description: string320+}321+322+/** Flattened summary of an `iroh_services` diagnostics report. */323+export interface DiagnosticsSummary {324+ endpointId: string325+ directAddrs: Array<string>326+ irohVersion: string327+ irohServicesVersion: string328+ hasNetReport: boolean329+ upnp?: boolean330+ pcp?: boolean331+ natPmp?: boolean332+}333+334+/**335+ * Options passed to [`Endpoint::bind`].336+ *337+ * `bind` applies the n0 preset by default. For a custom preset use338+ * [`Endpoint::builder`] + the `EndpointBuilder` surface.339+ */340+export interface EndpointOptions {341+ bindAddr?: string342+ secretKey?: Array<number>343+ alpns?: Array<Array<number>>344+}345+346+/** Where an incoming connection came from. */347+export interface IncomingAddr {348+ /** One of "ip" | "relay" | "custom". */349+ kind: string350+ /** `ip:port` for ip, relay URL for relay. */351+ addr?: string352+ /** Remote endpoint id (relay only). */353+ endpointId?: string354+ /** Debug description (custom only). */355+ description?: string356+}357+358+/** The local address that received an incoming connection. */359+export interface IncomingLocalAddr {360+ /** One of "ip" | "relay" | "custom". */361+ kind: string362+ addr?: string363+ description?: string364+}365+366+/** The logging level. See the rust (log crate)[https://docs.rs/log] for more information. */367+export declare const enum LogLevel {368+ Trace = 'Trace',369+ Debug = 'Debug',370+ Info = 'Info',371+ Warn = 'Warn',372+ Error = 'Error',373+ Off = 'Off'374+}375+376+/** A path event with its associated data. */377+export interface PathEvent {378+ kind: PathEventKind379+ id?: string380+ remoteAddr?: string381+ localAddr?: string382+ lastStats?: PathStatsRecord383+ missed?: number384+}385+386+/** An event from `Connection::watchPathEvents`. */387+export declare const enum PathEventKind {388+ Opened = 'Opened',389+ Closed = 'Closed',390+ Selected = 'Selected',391+ Lagged = 'Lagged'392+}393+394+/** A flat snapshot of an open path's state. */395+export interface PathSnapshot {396+ id: string397+ isSelected: boolean398+ remoteAddr: string399+ isIp: boolean400+ isRelay: boolean401+ rttMs: number402+ stats: PathStatsRecord403+}404+405+/** Flattened headline numbers from `noq::PathStats`. */406+export interface PathStatsRecord {407+ rttMs: number408+ udpTxDatagrams: number409+ udpTxBytes: number410+ udpRxDatagrams: number411+ udpRxBytes: number412+ cwnd: number413+ congestionEvents: number414+ lostPackets: number415+ lostBytes: number416+ currentMtu: number417+}418+419+/** The minimal preset (no external dependencies; good for tests / offline). */420+export declare function presetMinimal(builder: EndpointBuilder): void421+422+/** The n0 production preset (relays + discovery). */423+export declare function presetN0(builder: EndpointBuilder): void424+425+/** The n0 preset with relays disabled. */426+export declare function presetN0DisableRelay(builder: EndpointBuilder): void427+428+/** Config for a single relay server. */429+export interface RelayConfig {430+ url: string431+ quicPort?: number432+ authToken?: string433+}434+435+/** Build options for [`ServicesClient`]. */436+export interface ServicesOptions {437+ /** Encoded API secret string (`services1...`). */438+ apiSecret?: string439+ /** If true, read the API secret from `IROH_SERVICES_API_SECRET`. */440+ apiSecretFromEnv?: boolean441+ /** Unencrypted PEM-encoded OpenSSH ed25519 private key. */442+ sshKeyPem?: string443+ /** Optional endpoint name to register cloud-side. */444+ name?: string445+ /** Metrics push interval (ms). `0` disables interval pushes. */446+ metricsIntervalMs?: number447+}448+449+/** Set the logging level. */450+export declare function setLogLevel(level: LogLevel): void451+452+/** Which side of a connection we are. */453+export declare const enum Side {454+ Client = 'Client',455+ Server = 'Server'456+}@@ -0,0 +1,988 @@
1+use std::{str::FromStr, sync::Arc};2+3+use iroh::endpoint::{self, presets, presets::Preset as _};4+use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeFunction};5+use napi_derive::napi;6+use tokio::sync::Mutex;7+8+use crate::{EndpointAddr, RelayMode, SecretKey, WatchHandle, path, watch};9+10+/// A mutable handle to an endpoint builder.11+///12+/// Mirrors the uniffi `EndpointBuilder`. A "preset" in JS is simply any13+/// function `(builder: EndpointBuilder) => void` — call one of the built-in14+/// [`presetN0`] / [`presetMinimal`] / [`presetN0DisableRelay`] helpers (which15+/// install the crypto provider) and then layer on your own configuration.16+///17+/// ```js18+/// const b = Endpoint.builder()19+/// presetMinimal(b)20+/// b.alpns([alpn])21+/// const ep = await b.bind()22+/// ```23+24+pub struct EndpointBuilder {25+ inner: std::sync::Mutex<Option<iroh::endpoint::Builder>>,26+}27+28+impl EndpointBuilder {29+ fn new(builder: iroh::endpoint::Builder) -> Self {30+ Self {31+ inner: std::sync::Mutex::new(Some(builder)),32+ }33+ }34+35+ fn map<F>(&self, f: F)36+ where37+ F: FnOnce(iroh::endpoint::Builder) -> iroh::endpoint::Builder,38+ {39+ let mut guard = self.inner.lock().unwrap();40+ let b = guard.take().expect("EndpointBuilder consumed");41+ *guard = Some(f(b));42+ }43+}44+45+46+impl EndpointBuilder {47+ /// Replay the n0 production preset (relays + discovery + crypto provider).48+ 49+ pub fn apply_n0(&self) {50+ self.map(|b| presets::N0.apply(b));51+ }52+53+ /// Replay the minimal preset (crypto provider only, no external deps).54+ 55+ pub fn apply_minimal(&self) {56+ self.map(|b| presets::Minimal.apply(b));57+ }58+59+ /// Replay the n0 preset with relays disabled.60+ 61+ pub fn apply_n0_disable_relay(&self) {62+ self.map(|b| presets::N0DisableRelay.apply(b));63+ }64+65+ /// Set the endpoint secret key (32 bytes).66+ 67+ pub fn secret_key(&self, bytes: Vec<u8>) -> Result<()> {68+ let key: [u8; 32] = bytes69+ .try_into()70+ .map_err(|_| anyhow::anyhow!("secret_key must be 32 bytes"))?;71+ self.map(|b| b.secret_key(iroh::SecretKey::from_bytes(&key)));72+ Ok(())73+ }74+75+ /// Set the advertised ALPNs.76+ 77+ pub fn alpns(&self, alpns: Vec<Vec<u8>>) {78+ self.map(|b| b.alpns(alpns));79+ }80+81+ /// Set the relay mode.82+ 83+ pub fn relay_mode(&self, mode: &RelayMode) {84+ let mode = mode.0.clone();85+ self.map(|b| b.relay_mode(mode));86+ }87+88+ /// Set the address the endpoint binds to (`host:port`).89+ 90+ pub fn bind_addr(&self, addr: String) -> Result<()> {91+ let socket = std::net::SocketAddr::from_str(&addr).map_err(anyhow::Error::from)?;92+ let mut guard = self.inner.lock().unwrap();93+ let b = guard.take().expect("EndpointBuilder consumed");94+ *guard = Some(b.bind_addr(socket).map_err(anyhow::Error::from)?);95+ Ok(())96+ }97+98+ /// Bind the endpoint.99+ 100+ pub async fn bind(&self) -> Result<Endpoint> {101+ let builder = self102+ .inner103+ .lock()104+ .unwrap()105+ .take()106+ .ok_or_else(|| anyhow::anyhow!("EndpointBuilder already consumed"))?;107+ let endpoint = builder.bind().await.map_err(|e| anyhow::anyhow!("{e:?}"))?;108+ Ok(Endpoint { inner: endpoint })109+ }110+}111+112+/// The n0 production preset (relays + discovery).113+114+pub fn preset_n0(builder: &EndpointBuilder) {115+ builder.apply_n0();116+}117+118+/// The minimal preset (no external dependencies; good for tests / offline).119+120+pub fn preset_minimal(builder: &EndpointBuilder) {121+ builder.apply_minimal();122+}123+124+/// The n0 preset with relays disabled.125+126+pub fn preset_n0_disable_relay(builder: &EndpointBuilder) {127+ builder.apply_n0_disable_relay();128+}129+130+/// Which side of a connection we are.131+132+133+pub enum Side {134+ Client,135+ Server,136+}137+138+impl From<iroh::endpoint::Side> for Side {139+ fn from(s: iroh::endpoint::Side) -> Self {140+ match s {141+ iroh::endpoint::Side::Client => Side::Client,142+ iroh::endpoint::Side::Server => Side::Server,143+ }144+ }145+}146+147+/// Where an incoming connection came from.148+149+150+pub struct IncomingAddr {151+ /// One of "ip" | "relay" | "custom".152+ pub kind: String,153+ /// `ip:port` for ip, relay URL for relay.154+ pub addr: Option<String>,155+ /// Remote endpoint id (relay only).156+ pub endpoint_id: Option<String>,157+ /// Debug description (custom only).158+ pub description: Option<String>,159+}160+161+impl From<iroh::endpoint::IncomingAddr> for IncomingAddr {162+ fn from(addr: iroh::endpoint::IncomingAddr) -> Self {163+ match addr {164+ iroh::endpoint::IncomingAddr::Ip(socket) => IncomingAddr {165+ kind: "ip".into(),166+ addr: Some(socket.to_string()),167+ endpoint_id: None,168+ description: None,169+ },170+ iroh::endpoint::IncomingAddr::Relay { url, endpoint_id } => IncomingAddr {171+ kind: "relay".into(),172+ addr: Some(url.to_string()),173+ endpoint_id: Some(endpoint_id.to_string()),174+ description: None,175+ },176+ iroh::endpoint::IncomingAddr::Custom(c) => IncomingAddr {177+ kind: "custom".into(),178+ addr: None,179+ endpoint_id: None,180+ description: Some(format!("{c:?}")),181+ },182+ _ => IncomingAddr {183+ kind: "custom".into(),184+ addr: None,185+ endpoint_id: None,186+ description: Some("unknown".into()),187+ },188+ }189+ }190+}191+192+/// The local address that received an incoming connection.193+194+195+pub struct IncomingLocalAddr {196+ /// One of "ip" | "relay" | "custom".197+ pub kind: String,198+ pub addr: Option<String>,199+ pub description: Option<String>,200+}201+202+impl From<iroh::endpoint::LocalTransportAddr> for IncomingLocalAddr {203+ fn from(value: iroh::endpoint::LocalTransportAddr) -> Self {204+ match value {205+ iroh::endpoint::LocalTransportAddr::Ip(ip) => IncomingLocalAddr {206+ kind: "ip".into(),207+ addr: ip.map(|i| i.to_string()),208+ description: None,209+ },210+ iroh::endpoint::LocalTransportAddr::Relay(url) => IncomingLocalAddr {211+ kind: "relay".into(),212+ addr: Some(url.to_string()),213+ description: None,214+ },215+ iroh::endpoint::LocalTransportAddr::Custom(c) => IncomingLocalAddr {216+ kind: "custom".into(),217+ addr: None,218+ description: c.map(|c| format!("{c:?}")),219+ },220+ _ => IncomingLocalAddr {221+ kind: "custom".into(),222+ addr: None,223+ description: None,224+ },225+ }226+ }227+}228+229+/// A snapshot value for a single endpoint metric.230+231+232+pub struct CounterStats {233+ pub value: u32,234+ pub description: String,235+}236+237+/// Flat snapshot of headline connection statistics.238+239+240+pub struct ConnectionStats {241+ pub udp_tx_datagrams: i64,242+ pub udp_tx_bytes: i64,243+ pub udp_rx_datagrams: i64,244+ pub udp_rx_bytes: i64,245+ pub lost_packets: i64,246+ pub lost_bytes: i64,247+}248+249+/// Options passed to [`Endpoint::bind`].250+///251+/// `bind` applies the n0 preset by default. For a custom preset use252+/// [`Endpoint::builder`] + the `EndpointBuilder` surface.253+254+255+pub struct EndpointOptions {256+ pub bind_addr: Option<String>,257+ pub secret_key: Option<Vec<u8>>,258+ pub alpns: Option<Vec<Vec<u8>>>,259+}260+261+/// An iroh endpoint.262+263+pub struct Endpoint {264+ inner: endpoint::Endpoint,265+}266+267+impl Endpoint {268+ pub(crate) fn raw(&self) -> &endpoint::Endpoint {269+ &self.inner270+ }271+}272+273+274+impl Endpoint {275+ /// Create an endpoint builder (starts empty — apply a preset).276+ 277+ pub fn builder() -> EndpointBuilder {278+ EndpointBuilder::new(iroh::endpoint::Builder::empty())279+ }280+281+ /// Bind a new endpoint. Applies the n0 preset, then the given options.282+ /// For a custom preset use [`Endpoint::builder`].283+ 284+ pub async fn bind(285+ options: Option<EndpointOptions>,286+ relay_mode: Option<&RelayMode>,287+ ) -> Result<Self> {288+ let options = options.unwrap_or_default();289+ let wrapper = EndpointBuilder::new(iroh::endpoint::Builder::empty());290+ wrapper.apply_n0();291+292+ if let Some(secret_key) = options.secret_key {293+ wrapper.secret_key(secret_key)?;294+ }295+ if let Some(alpns) = options.alpns {296+ wrapper.alpns(alpns);297+ }298+ if let Some(relay_mode) = relay_mode {299+ wrapper.relay_mode(relay_mode);300+ }301+ if let Some(addr) = options.bind_addr {302+ wrapper.bind_addr(addr)?;303+ }304+305+ wrapper.bind().await306+ }307+308+ /// This endpoint's id.309+ 310+ pub fn id(&self) -> crate::EndpointId {311+ self.inner.id().into()312+ }313+314+ /// The [`EndpointAddr`] for this endpoint.315+ 316+ pub fn addr(&self) -> EndpointAddr {317+ self.inner.addr().into()318+ }319+320+ /// Look up cached information about a remote endpoint, if any.321+ 322+ pub async fn remote_addr(&self, id: &crate::EndpointId) -> Result<Option<EndpointAddr>> {323+ let id: iroh::EndpointId = id.into();324+ let info = match self.inner.remote_info(id).await {325+ Some(i) => i,326+ None => return Ok(None),327+ };328+ let id = info.id();329+ let addrs = info.into_addrs().map(|a| a.into_addr());330+ Ok(Some(iroh::EndpointAddr::from_parts(id, addrs).into()))331+ }332+333+ /// Current statistics for this endpoint.334+ 335+ pub fn stats(&self) -> std::collections::HashMap<String, CounterStats> {336+ use iroh_metrics::{MetricValue, MetricsGroupSet};337+ self.inner338+ .metrics()339+ .iter()340+ .map(|(group, item)| {341+ let name = format!("{}:{}", group, item.name());342+ let value = match item.value() {343+ MetricValue::Counter(v) => u32::try_from(v).unwrap_or(u32::MAX),344+ MetricValue::Gauge(v) => u32::try_from(v.max(0)).unwrap_or(u32::MAX),345+ _ => 0,346+ };347+ (348+ name,349+ CounterStats {350+ value,351+ description: item.help().to_string(),352+ },353+ )354+ })355+ .collect()356+ }357+358+ /// The secret key backing this endpoint's identity.359+ 360+ pub fn secret_key(&self) -> SecretKey {361+ self.inner.secret_key().clone().into()362+ }363+364+ /// Replace the set of advertised ALPNs.365+ 366+ pub fn set_alpns(&self, alpns: Vec<Vec<u8>>) {367+ self.inner.set_alpns(alpns);368+ }369+370+ /// Add an external (manually-known) socket address.371+ 372+ pub async fn add_external_addr(&self, addr: String) -> Result<()> {373+ let socket = std::net::SocketAddr::from_str(&addr).map_err(anyhow::Error::from)?;374+ self.inner.add_external_addr(socket).await;375+ Ok(())376+ }377+378+ /// Remove a previously-added external address.379+ 380+ pub async fn remove_external_addr(&self, addr: String) -> Result<bool> {381+ let socket = std::net::SocketAddr::from_str(&addr).map_err(anyhow::Error::from)?;382+ Ok(self.inner.remove_external_addr(&socket).await)383+ }384+385+ /// The local socket addresses this endpoint is bound to.386+ 387+ pub fn bound_sockets(&self) -> Vec<String> {388+ self.inner389+ .bound_sockets()390+ .into_iter()391+ .map(|a| a.to_string())392+ .collect()393+ }394+395+ /// Resolves once the endpoint has a usable home relay.396+ 397+ pub async fn online(&self) {398+ self.inner.online().await;399+ }400+401+ /// Insert (or replace) a relay configuration at runtime.402+ 403+ pub async fn insert_relay(&self, config: crate::RelayConfig) -> Result<()> {404+ let config: iroh::RelayConfig = config.try_into()?;405+ let url = config.url.clone();406+ self.inner.insert_relay(url, Arc::new(config)).await;407+ Ok(())408+ }409+410+ /// Remove a relay configuration at runtime.411+ 412+ pub async fn remove_relay(&self, url: String) -> Result<bool> {413+ let url = iroh::RelayUrl::from_str(&url).map_err(anyhow::Error::from)?;414+ Ok(self.inner.remove_relay(&url).await.is_some())415+ }416+417+ /// Connect to a remote endpoint via the given ALPN.418+ 419+ pub async fn connect(&self, addr: &EndpointAddr, alpn: Vec<u8>) -> Result<Connection> {420+ let addr: iroh::EndpointAddr = addr.try_into()?;421+ let conn = self422+ .inner423+ .connect(addr, &alpn)424+ .await425+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;426+ Ok(Connection(Arc::new(conn)))427+ }428+429+ /// Begin a connection attempt, returning the in-progress handle.430+ 431+ pub async fn connect_pending(&self, addr: &EndpointAddr, alpn: Vec<u8>) -> Result<Connecting> {432+ let addr: iroh::EndpointAddr = addr.try_into()?;433+ let connecting = self434+ .inner435+ .connect_with_opts(addr, &alpn, iroh::endpoint::ConnectOptions::default())436+ .await437+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;438+ Ok(Connecting(Mutex::new(Some(connecting))))439+ }440+441+ /// Pull the next incoming connection attempt.442+ 443+ pub async fn accept_next(&self) -> Option<Incoming> {444+ let inner = self.inner.accept().await?;445+ Some(Incoming(Mutex::new(Some(inner))))446+ }447+448+ /// Watch for changes to this endpoint's address.449+ 450+ pub fn watch_addr(&self, callback: ThreadsafeFunction<EndpointAddr>) -> WatchHandle {451+ watch::spawn_watch_addr(self.inner.clone(), callback)452+ }453+454+ /// Watch for changes to the connected relays.455+ 456+ pub fn watch_home_relay(&self, callback: ThreadsafeFunction<Vec<String>>) -> WatchHandle {457+ watch::spawn_home_relay_watch(self.inner.clone(), callback)458+ }459+460+ /// Watch for network-stack changes.461+ 462+ pub fn watch_network_change(&self, callback: ThreadsafeFunction<()>) -> WatchHandle {463+ watch::spawn_network_change_watch(self.inner.clone(), callback)464+ }465+466+ /// Close the endpoint.467+ 468+ pub async fn close(&self) {469+ self.inner.close().await;470+ }471+472+ /// True if the endpoint is closed.473+ 474+ pub fn is_closed(&self) -> bool {475+ self.inner.is_closed()476+ }477+}478+479+/// An incoming connection that has not yet begun its server-side handshake.480+481+pub struct Incoming(Mutex<Option<iroh::endpoint::Incoming>>);482+483+484+impl Incoming {485+ 486+ pub async fn accept(&self) -> Result<Accepting> {487+ let inner = self488+ .0489+ .lock()490+ .await491+ .take()492+ .ok_or_else(|| anyhow::anyhow!("Incoming already consumed"))?;493+ let accepting = inner.accept().map_err(|e| anyhow::anyhow!("{e:?}"))?;494+ Ok(Accepting(Mutex::new(Some(accepting))))495+ }496+497+ 498+ pub async fn refuse(&self) -> Result<()> {499+ let inner = self500+ .0501+ .lock()502+ .await503+ .take()504+ .ok_or_else(|| anyhow::anyhow!("Incoming already consumed"))?;505+ inner.refuse();506+ Ok(())507+ }508+509+ 510+ pub async fn retry(&self) -> Result<()> {511+ let inner = self512+ .0513+ .lock()514+ .await515+ .take()516+ .ok_or_else(|| anyhow::anyhow!("Incoming already consumed"))?;517+ inner518+ .retry()519+ .map_err(|e| anyhow::anyhow!("retry failed: {e:?}").into())520+ }521+522+ 523+ pub async fn ignore(&self) -> Result<()> {524+ let inner = self525+ .0526+ .lock()527+ .await528+ .take()529+ .ok_or_else(|| anyhow::anyhow!("Incoming already consumed"))?;530+ inner.ignore();531+ Ok(())532+ }533+534+ 535+ pub async fn local_addr(&self) -> Result<IncomingLocalAddr> {536+ let guard = self.0.lock().await;537+ let inner = guard538+ .as_ref()539+ .ok_or_else(|| anyhow::anyhow!("Incoming already consumed"))?;540+ Ok(inner.local_addr().into())541+ }542+543+ 544+ pub async fn remote_addr(&self) -> Result<IncomingAddr> {545+ let guard = self.0.lock().await;546+ let inner = guard547+ .as_ref()548+ .ok_or_else(|| anyhow::anyhow!("Incoming already consumed"))?;549+ Ok(inner.remote_addr().into())550+ }551+552+ 553+ pub async fn remote_addr_validated(&self) -> Result<bool> {554+ let guard = self.0.lock().await;555+ let inner = guard556+ .as_ref()557+ .ok_or_else(|| anyhow::anyhow!("Incoming already consumed"))?;558+ Ok(inner.remote_addr_validated())559+ }560+}561+562+/// A server-side handshake in progress.563+564+pub struct Accepting(Mutex<Option<iroh::endpoint::Accepting>>);565+566+567+impl Accepting {568+ 569+ pub async fn connect(&self) -> Result<Connection> {570+ let inner = self571+ .0572+ .lock()573+ .await574+ .take()575+ .ok_or_else(|| anyhow::anyhow!("Accepting already consumed"))?;576+ let conn = inner.await.map_err(|e| anyhow::anyhow!("{e:?}"))?;577+ Ok(Connection(Arc::new(conn)))578+ }579+580+ 581+ pub async fn alpn(&self) -> Result<Vec<u8>> {582+ let mut guard = self.0.lock().await;583+ let inner = guard584+ .as_mut()585+ .ok_or_else(|| anyhow::anyhow!("Accepting already consumed"))?;586+ inner587+ .alpn()588+ .await589+ .map_err(|e| anyhow::anyhow!("{e:?}").into())590+ }591+}592+593+/// A client-side handshake in progress.594+595+pub struct Connecting(Mutex<Option<iroh::endpoint::Connecting>>);596+597+598+impl Connecting {599+ 600+ pub async fn connect(&self) -> Result<Connection> {601+ let inner = self602+ .0603+ .lock()604+ .await605+ .take()606+ .ok_or_else(|| anyhow::anyhow!("Connecting already consumed"))?;607+ let conn = inner.await.map_err(|e| anyhow::anyhow!("{e:?}"))?;608+ Ok(Connection(Arc::new(conn)))609+ }610+611+ 612+ pub async fn alpn(&self) -> Result<Vec<u8>> {613+ let mut guard = self.0.lock().await;614+ let inner = guard615+ .as_mut()616+ .ok_or_else(|| anyhow::anyhow!("Connecting already consumed"))?;617+ inner618+ .alpn()619+ .await620+ .map_err(|e| anyhow::anyhow!("{e:?}").into())621+ }622+623+ 624+ pub async fn remote_id(&self) -> Result<crate::EndpointId> {625+ let guard = self.0.lock().await;626+ let inner = guard627+ .as_ref()628+ .ok_or_else(|| anyhow::anyhow!("Connecting already consumed"))?;629+ Ok(inner.remote_id().into())630+ }631+}632+633+/// An active QUIC connection.634+635+pub struct Connection(Arc<endpoint::Connection>);636+637+638+impl Connection {639+ 640+ pub fn alpn(&self) -> Vec<u8> {641+ self.0.alpn().to_vec()642+ }643+644+ 645+ pub fn remote_id(&self) -> crate::EndpointId {646+ self.0.remote_id().into()647+ }648+649+ 650+ pub fn side(&self) -> Side {651+ self.0.side().into()652+ }653+654+ 655+ pub async fn open_uni(&self) -> Result<SendStream> {656+ let s = self657+ .0658+ .open_uni()659+ .await660+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;661+ Ok(SendStream::new(s))662+ }663+664+ 665+ pub async fn accept_uni(&self) -> Result<RecvStream> {666+ let r = self667+ .0668+ .accept_uni()669+ .await670+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;671+ Ok(RecvStream::new(r))672+ }673+674+ 675+ pub async fn open_bi(&self) -> Result<BiStream> {676+ let (s, r) = self677+ .0678+ .open_bi()679+ .await680+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;681+ Ok(BiStream {682+ send: SendStream::new(s),683+ recv: RecvStream::new(r),684+ })685+ }686+687+ 688+ pub async fn accept_bi(&self) -> Result<BiStream> {689+ let (s, r) = self690+ .0691+ .accept_bi()692+ .await693+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;694+ Ok(BiStream {695+ send: SendStream::new(s),696+ recv: RecvStream::new(r),697+ })698+ }699+700+ 701+ pub async fn read_datagram(&self) -> Result<Vec<u8>> {702+ let res = self703+ .0704+ .read_datagram()705+ .await706+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;707+ Ok(res.to_vec())708+ }709+710+ 711+ pub async fn closed(&self) -> String {712+ self.0.closed().await.to_string()713+ }714+715+ 716+ pub fn close_reason(&self) -> Option<String> {717+ self.0.close_reason().map(|s| s.to_string())718+ }719+720+ 721+ pub fn close(&self, error_code: BigInt, reason: Vec<u8>) -> Result<()> {722+ let code =723+ endpoint::VarInt::from_u64(error_code.get_u64().1).map_err(anyhow::Error::from)?;724+ self.0.close(code, &reason);725+ Ok(())726+ }727+728+ 729+ pub fn send_datagram(&self, data: Vec<u8>) -> Result<()> {730+ self.0731+ .send_datagram(data.into())732+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;733+ Ok(())734+ }735+736+ 737+ pub async fn send_datagram_wait(&self, data: Vec<u8>) -> Result<()> {738+ self.0739+ .send_datagram_wait(data.into())740+ .await741+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;742+ Ok(())743+ }744+745+ 746+ pub fn max_datagram_size(&self) -> Option<i64> {747+ self.0.max_datagram_size().map(|s| s as i64)748+ }749+750+ 751+ pub fn datagram_send_buffer_space(&self) -> i64 {752+ self.0.datagram_send_buffer_space() as i64753+ }754+755+ 756+ pub fn stable_id(&self) -> u32 {757+ self.0.stable_id() as _758+ }759+760+ 761+ pub fn rtt(&self) -> Option<i64> {762+ self.0763+ .paths()764+ .iter()765+ .find(|p| p.is_selected())766+ .map(|p| p.rtt().as_millis() as i64)767+ }768+769+ 770+ pub fn stats(&self) -> ConnectionStats {771+ let s = self.0.stats();772+ ConnectionStats {773+ udp_tx_datagrams: s.udp_tx.datagrams as i64,774+ udp_tx_bytes: s.udp_tx.bytes as i64,775+ udp_rx_datagrams: s.udp_rx.datagrams as i64,776+ udp_rx_bytes: s.udp_rx.bytes as i64,777+ lost_packets: s.lost_packets as i64,778+ lost_bytes: s.lost_bytes as i64,779+ }780+ }781+782+ 783+ pub fn paths(&self) -> Vec<path::PathSnapshot> {784+ path::snapshot_paths(&self.0)785+ }786+787+ 788+ pub fn watch_paths(789+ &self,790+ callback: ThreadsafeFunction<Vec<path::PathSnapshot>>,791+ ) -> WatchHandle {792+ watch::spawn_paths_watch((*self.0).clone(), callback)793+ }794+795+ 796+ pub fn watch_path_events(&self, callback: ThreadsafeFunction<path::PathEvent>) -> WatchHandle {797+ watch::spawn_path_events_watch((*self.0).clone(), callback)798+ }799+800+ 801+ pub fn set_max_concurrent_uni_streams(&self, count: BigInt) -> Result<()> {802+ let n = endpoint::VarInt::from_u64(count.get_u64().1).map_err(anyhow::Error::from)?;803+ self.0.set_max_concurrent_uni_streams(n);804+ Ok(())805+ }806+807+ 808+ pub fn set_receive_window(&self, count: BigInt) -> Result<()> {809+ let n = endpoint::VarInt::from_u64(count.get_u64().1).map_err(anyhow::Error::from)?;810+ self.0.set_receive_window(n);811+ Ok(())812+ }813+814+ 815+ pub fn set_max_concurrent_bi_streams(&self, count: BigInt) -> Result<()> {816+ let n = endpoint::VarInt::from_u64(count.get_u64().1).map_err(anyhow::Error::from)?;817+ self.0.set_max_concurrent_bi_streams(n);818+ Ok(())819+ }820+}821+822+/// A bidirectional QUIC stream pair.823+824+pub struct BiStream {825+ send: SendStream,826+ recv: RecvStream,827+}828+829+830+impl BiStream {831+ 832+ pub fn send(&self) -> SendStream {833+ self.send.clone()834+ }835+836+ 837+ pub fn recv(&self) -> RecvStream {838+ self.recv.clone()839+ }840+}841+842+/// The outgoing half of a QUIC stream.843+844+845+pub struct SendStream(pub(crate) Arc<Mutex<endpoint::SendStream>>); // lofi-node: pub(crate) for lofi_ext framing846+847+impl SendStream {848+ fn new(s: endpoint::SendStream) -> Self {849+ SendStream(Arc::new(Mutex::new(s)))850+ }851+}852+853+854+impl SendStream {855+ 856+ pub async fn write(&self, buf: Vec<u8>) -> Result<i64> {857+ let mut s = self.0.lock().await;858+ let n = s.write(&buf).await.map_err(|e| anyhow::anyhow!("{e:?}"))?;859+ Ok(n as i64)860+ }861+862+ 863+ pub async fn write_all(&self, buf: Vec<u8>) -> Result<()> {864+ let mut s = self.0.lock().await;865+ s.write_all(&buf)866+ .await867+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;868+ Ok(())869+ }870+871+ 872+ pub async fn finish(&self) -> Result<()> {873+ let mut s = self.0.lock().await;874+ s.finish().map_err(|e| anyhow::anyhow!("{e:?}"))?;875+ Ok(())876+ }877+878+ 879+ pub async fn reset(&self, error_code: BigInt) -> Result<()> {880+ let code =881+ endpoint::VarInt::from_u64(error_code.get_u64().1).map_err(anyhow::Error::from)?;882+ let mut s = self.0.lock().await;883+ s.reset(code).map_err(|e| anyhow::anyhow!("{e:?}"))?;884+ Ok(())885+ }886+887+ 888+ pub async fn set_priority(&self, p: i32) -> Result<()> {889+ let s = self.0.lock().await;890+ s.set_priority(p).map_err(|e| anyhow::anyhow!("{e:?}"))?;891+ Ok(())892+ }893+894+ 895+ pub async fn priority(&self) -> Result<i32> {896+ let s = self.0.lock().await;897+ s.priority().map_err(|e| anyhow::anyhow!("{e:?}").into())898+ }899+900+ 901+ pub async fn stopped(&self) -> Result<Option<i64>> {902+ let s = self.0.lock().await;903+ let res = s.stopped().await.map_err(|e| anyhow::anyhow!("{e:?}"))?;904+ Ok(res.map(|r| r.into_inner() as i64))905+ }906+907+ 908+ pub async fn id(&self) -> String {909+ self.0.lock().await.id().to_string()910+ }911+}912+913+/// The incoming half of a QUIC stream.914+915+916+pub struct RecvStream(pub(crate) Arc<Mutex<endpoint::RecvStream>>); // lofi-node: pub(crate) for lofi_ext framing917+918+impl RecvStream {919+ fn new(s: endpoint::RecvStream) -> Self {920+ RecvStream(Arc::new(Mutex::new(s)))921+ }922+}923+924+925+impl RecvStream {926+ 927+ pub async fn read(&self, size_limit: u32) -> Result<Vec<u8>> {928+ let mut buf = vec![0u8; size_limit as usize];929+ let mut r = self.0.lock().await;930+ let res = r931+ .read(&mut buf)932+ .await933+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;934+ let len = res.unwrap_or(0);935+ buf.truncate(len);936+ Ok(buf)937+ }938+939+ 940+ pub async fn read_exact(&self, size: u32) -> Result<Vec<u8>> {941+ let mut buf = vec![0u8; size as usize];942+ let mut r = self.0.lock().await;943+ r.read_exact(&mut buf)944+ .await945+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;946+ Ok(buf)947+ }948+949+ 950+ pub async fn read_to_end(&self, size_limit: u32) -> Result<Vec<u8>> {951+ let mut r = self.0.lock().await;952+ let res = r953+ .read_to_end(size_limit as usize)954+ .await955+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;956+ Ok(res)957+ }958+959+ 960+ pub async fn id(&self) -> String {961+ self.0.lock().await.id().to_string()962+ }963+964+ 965+ pub async fn bytes_read(&self) -> Result<i64> {966+ let r = self.0.lock().await;967+ Ok(r.bytes_read().map_err(|e| anyhow::anyhow!("{e:?}"))? as i64)968+ }969+970+ 971+ pub async fn stop(&self, error_code: BigInt) -> Result<()> {972+ let code =973+ endpoint::VarInt::from_u64(error_code.get_u64().1).map_err(anyhow::Error::from)?;974+ let mut r = self.0.lock().await;975+ r.stop(code).map_err(|e| anyhow::anyhow!("{e:?}"))?;976+ Ok(())977+ }978+979+ 980+ pub async fn received_reset(&self) -> Result<Option<i64>> {981+ let mut r = self.0.lock().await;982+ let code = r983+ .received_reset()984+ .await985+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;986+ Ok(code.map(|c| c.into_inner() as i64))987+ }988+}@@ -0,0 +1,173 @@
1+use std::str::FromStr;2+3+use napi::bindgen_prelude::*;4+use napi_derive::napi;5+6+/// An endpoint's identifier, a 32-byte ed25519 public key.7+8+9+pub struct EndpointId {10+ key: [u8; 32],11+}12+13+impl From<iroh::EndpointId> for EndpointId {14+ fn from(key: iroh::EndpointId) -> Self {15+ EndpointId {16+ key: *key.as_bytes(),17+ }18+ }19+}20+21+impl From<&EndpointId> for iroh::EndpointId {22+ fn from(key: &EndpointId) -> Self {23+ iroh::EndpointId::from_bytes(&key.key).expect("EndpointId bytes are always valid")24+ }25+}26+27+impl EndpointId {28+ pub(crate) fn raw_bytes(&self) -> [u8; 32] {29+ self.key30+ }31+32+ pub(crate) fn from_raw_bytes(key: [u8; 32]) -> Self {33+ EndpointId { key }34+ }35+}36+37+38+impl EndpointId {39+ /// Returns true if both [`EndpointId`]s are equal.40+ 41+ pub fn equals(&self, other: &EndpointId) -> bool {42+ *self == *other43+ }44+45+ /// Get the underlying 32 bytes.46+ 47+ pub fn to_bytes(&self) -> Vec<u8> {48+ self.key.to_vec()49+ }50+51+ /// Parse an [`EndpointId`] from its base32 representation.52+ 53+ pub fn from_string(s: String) -> Result<Self> {54+ let key = iroh::EndpointId::from_str(&s).map_err(anyhow::Error::from)?;55+ Ok(key.into())56+ }57+58+ /// Construct an [`EndpointId`] from raw bytes (32 bytes).59+ 60+ pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {61+ let bytes: [u8; 32] = bytes62+ .try_into()63+ .map_err(|_| anyhow::anyhow!("EndpointId requires exactly 32 bytes"))?;64+ let key = iroh::EndpointId::from_bytes(&bytes).map_err(anyhow::Error::from)?;65+ Ok(key.into())66+ }67+68+ /// Short base32 prefix.69+ 70+ pub fn fmt_short(&self) -> String {71+ iroh::EndpointId::from(self).fmt_short().to_string()72+ }73+74+ /// Base32 string form.75+ 76+ pub fn to_string(&self) -> String {77+ iroh::EndpointId::from(self).to_string()78+ }79+80+ /// Verify a signature on `message` against this endpoint's key.81+ 82+ pub fn verify(&self, message: Vec<u8>, signature: &Signature) -> Result<()> {83+ iroh::EndpointId::from(self)84+ .verify(&message, &signature.0)85+ .map_err(|e| anyhow::anyhow!("signature verification failed: {e:?}").into())86+ }87+}88+89+impl PartialEq for EndpointId {90+ fn eq(&self, other: &EndpointId) -> bool {91+ self.key == other.key92+ }93+}94+95+/// The secret key half of an endpoint identity.96+97+98+pub struct SecretKey(pub(crate) iroh::SecretKey);99+100+impl From<iroh::SecretKey> for SecretKey {101+ fn from(key: iroh::SecretKey) -> Self {102+ SecretKey(key)103+ }104+}105+106+107+impl SecretKey {108+ /// Generate a new random secret key.109+ 110+ pub fn generate() -> Self {111+ SecretKey(iroh::SecretKey::generate())112+ }113+114+ /// Construct from raw bytes (32 bytes).115+ 116+ pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {117+ let bytes: [u8; 32] = bytes118+ .try_into()119+ .map_err(|_| anyhow::anyhow!("SecretKey requires exactly 32 bytes"))?;120+ Ok(SecretKey(iroh::SecretKey::from_bytes(&bytes)))121+ }122+123+ /// Raw 32-byte form.124+ 125+ pub fn to_bytes(&self) -> Vec<u8> {126+ self.0.to_bytes().to_vec()127+ }128+129+ /// The public [`EndpointId`] derived from this secret key.130+ 131+ pub fn public(&self) -> EndpointId {132+ self.0.public().into()133+ }134+135+ /// Sign a message, producing an ed25519 signature.136+ 137+ pub fn sign(&self, message: Vec<u8>) -> Signature {138+ Signature(self.0.sign(&message))139+ }140+}141+142+/// An ed25519 signature over a message.143+144+145+pub struct Signature(pub(crate) iroh_base::Signature);146+147+148+impl Signature {149+ /// Construct from raw bytes (64 bytes).150+ 151+ pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {152+ let bytes: [u8; 64] = bytes153+ .try_into()154+ .map_err(|_| anyhow::anyhow!("Signature requires exactly 64 bytes"))?;155+ Ok(Signature(iroh_base::Signature::from_bytes(&bytes)))156+ }157+158+ /// Raw 64-byte form.159+ 160+ pub fn to_bytes(&self) -> Vec<u8> {161+ self.0.to_bytes().to_vec()162+ }163+164+ /// Lowercase hex representation.165+ 166+ pub fn to_string(&self) -> String {167+ self.0168+ .to_bytes()169+ .iter()170+ .map(|b| format!("{b:02x}"))171+ .collect()172+ }173+}@@ -0,0 +1,61 @@
1+use napi_derive::napi;2+use tracing_subscriber::filter::LevelFilter;3+4+mod endpoint;5+mod key;6+mod net;7+mod path;8+mod relay;9+// lofi-node: services module dropped (iroh-services unused by lofi-node)10+mod ticket;11+mod watch;12+13+pub use endpoint::*;14+pub use key::*;15+pub use net::*;16+pub use path::*;17+pub use relay::*;18+// lofi-node: services re-export dropped19+mod lofi_ext; // lofi-node: extension module (Buffer framed I/O)20+pub use lofi_ext::*; // lofi-node: extension re-export21+pub use ticket::*;22+pub use watch::*;23+24+/// The logging level. See the rust (log crate)[https://docs.rs/log] for more information.25+26+27+pub enum LogLevel {28+ Trace,29+ Debug,30+ Info,31+ Warn,32+ Error,33+ Off,34+}35+36+impl From<LogLevel> for LevelFilter {37+ fn from(level: LogLevel) -> LevelFilter {38+ match level {39+ LogLevel::Trace => LevelFilter::TRACE,40+ LogLevel::Debug => LevelFilter::DEBUG,41+ LogLevel::Info => LevelFilter::INFO,42+ LogLevel::Warn => LevelFilter::WARN,43+ LogLevel::Error => LevelFilter::ERROR,44+ LogLevel::Off => LevelFilter::OFF,45+ }46+ }47+}48+49+/// Set the logging level.50+51+pub fn set_log_level(level: LogLevel) {52+ use tracing_subscriber::{fmt, prelude::*, reload};53+ let filter: LevelFilter = level.into();54+ let (filter, _) = reload::Layer::new(filter);55+ let mut layer = fmt::Layer::default();56+ layer.set_ansi(false);57+ tracing_subscriber::registry()58+ .with(filter)59+ .with(layer)60+ .init();61+}@@ -0,0 +1,62 @@
1+//! lofi-node extension module — NOT upstream code (everything else in src/ is2+//! vendored from n0-computer/iroh-ffi iroh-js; see UPSTREAM.md).3+//!4+//! Buffer-based length-prefixed framing over QUIC streams. Upstream stream I/O5+//! moves bytes as `Vec<u8>` ⇄ JS `Array<number>` (measured 7.1 MiB/s loopback6+//! round-trip at gate 0) and needs two awaited hops per length-prefixed7+//! message. These free functions use napi `Buffer` (zero-copy at the boundary)8+//! and one await per frame: u32-BE length prefix + payload.9+//!10+//! Deletes if upstream grows Buffer-based stream I/O.11+12+use napi::bindgen_prelude::*;13+use napi_derive::napi;14+15+use crate::endpoint::{RecvStream, SendStream};16+17+/// Hard frame cap: sized for Jazz sync payloads, not inherited from anywhere.18+pub const MAX_FRAME: u32 = 16 * 1024 * 1024;19+20+/// Returns the frame cap so the TS side never hardcodes it.21+22+pub fn max_frame() -> u32 {23+ MAX_FRAME24+}25+26+/// Write one framed message: u32-BE length prefix, then the payload.27+28+pub async fn write_frame(stream: &SendStream, data: Buffer) -> Result<()> {29+ let bytes: &[u8] = &data;30+ if bytes.len() > MAX_FRAME as usize {31+ return Err(anyhow::anyhow!("frame of {} bytes exceeds MAX_FRAME ({MAX_FRAME})", bytes.len()).into());32+ }33+ let mut s = stream.0.lock().await;34+ s.write_all(&(bytes.len() as u32).to_be_bytes())35+ .await36+ .map_err(|e| anyhow::anyhow!("{e:?}"))?;37+ s.write_all(bytes).await.map_err(|e| anyhow::anyhow!("{e:?}"))?;38+ Ok(())39+}40+41+/// Read one framed message. `None` when the stream ends (finish/reset/close)42+/// at a frame boundary — the TS side's end-of-stream contract. A stream that43+/// dies MID-frame is an error, not `None`.44+45+pub async fn read_frame(stream: &RecvStream, max: Option<u32>) -> Result<Option<Buffer>> {46+ let max = max.unwrap_or(MAX_FRAME).min(MAX_FRAME);47+ let mut r = stream.0.lock().await;48+ let mut len_buf = [0u8; 4];49+ if r.read_exact(&mut len_buf).await.is_err() {50+ // Ended before a new frame started: clean end-of-stream.51+ return Ok(None);52+ }53+ let len = u32::from_be_bytes(len_buf);54+ if len > max {55+ return Err(anyhow::anyhow!("incoming frame of {len} bytes exceeds cap ({max})").into());56+ }57+ let mut buf = vec![0u8; len as usize];58+ r.read_exact(&mut buf)59+ .await60+ .map_err(|e| anyhow::anyhow!("stream ended mid-frame: {e:?}"))?;61+ Ok(Some(buf.into()))62+}@@ -0,0 +1,106 @@
1+use std::{collections::BTreeSet, net::SocketAddr, str::FromStr};2+3+use iroh_base::{RelayUrl, TransportAddr};4+use napi_derive::napi;5+6+use crate::EndpointId;7+8+/// An endpoint's id together with the network-level addresses where it can be9+/// reached. Mirrors the uniffi `EndpointAddr` object surface.10+11+12+pub struct EndpointAddr {13+ pub(crate) id: [u8; 32],14+ pub(crate) relay_url: Option<String>,15+ pub(crate) addresses: Vec<String>,16+}17+18+19+impl EndpointAddr {20+ /// Create a new [`EndpointAddr`].21+ 22+ pub fn new(id: &EndpointId, relay_url: Option<String>, addresses: Option<Vec<String>>) -> Self {23+ Self {24+ id: id.raw_bytes(),25+ relay_url,26+ addresses: addresses.unwrap_or_default(),27+ }28+ }29+30+ /// The endpoint id.31+ 32+ pub fn id(&self) -> EndpointId {33+ EndpointId::from_raw_bytes(self.id)34+ }35+36+ /// The direct (ip:port) addresses of this peer.37+ 38+ pub fn direct_addresses(&self) -> Vec<String> {39+ self.addresses.clone()40+ }41+42+ /// The home relay URL for this peer, if known.43+ 44+ pub fn relay_url(&self) -> Option<String> {45+ self.relay_url.clone()46+ }47+48+ /// Returns true if both [`EndpointAddr`]s have the same values.49+ 50+ pub fn equal(&self, other: &EndpointAddr) -> bool {51+ self == other52+ }53+54+ /// Clean string representation.55+ 56+ pub fn to_string(&self) -> String {57+ let id = iroh::EndpointId::from_bytes(&self.id)58+ .map(|i| i.to_string())59+ .unwrap_or_else(|_| "<invalid>".to_string());60+ let mut s = id;61+ if let Some(relay) = &self.relay_url {62+ s.push_str(&format!(" relay={relay}"));63+ }64+ if !self.addresses.is_empty() {65+ s.push_str(&format!(" addrs=[{}]", self.addresses.join(", ")));66+ }67+ s68+ }69+}70+71+impl TryFrom<&EndpointAddr> for iroh::EndpointAddr {72+ type Error = anyhow::Error;73+74+ fn try_from(value: &EndpointAddr) -> anyhow::Result<Self> {75+ let id = iroh::EndpointId::from_bytes(&value.id)?;76+ let mut addrs: BTreeSet<TransportAddr> = BTreeSet::new();77+ for addr in &value.addresses {78+ let socket = SocketAddr::from_str(addr)?;79+ addrs.insert(TransportAddr::Ip(socket));80+ }81+ if let Some(relay) = &value.relay_url {82+ let url = RelayUrl::from_str(relay)?;83+ addrs.insert(TransportAddr::Relay(url));84+ }85+ Ok(iroh::EndpointAddr { id, addrs })86+ }87+}88+89+impl From<iroh::EndpointAddr> for EndpointAddr {90+ fn from(value: iroh::EndpointAddr) -> Self {91+ let mut relay_url = None;92+ let mut addresses = Vec::new();93+ for addr in &value.addrs {94+ match addr {95+ TransportAddr::Relay(url) => relay_url = Some(url.to_string()),96+ TransportAddr::Ip(socket) => addresses.push(socket.to_string()),97+ _ => {}98+ }99+ }100+ EndpointAddr {101+ id: *value.id.as_bytes(),102+ relay_url,103+ addresses,104+ }105+ }106+}@@ -0,0 +1,167 @@
1+use iroh::endpoint::LocalTransportAddr;2+use iroh_base::TransportAddr;3+use napi_derive::napi;4+5+/// Flattened headline numbers from `noq::PathStats`.6+7+8+pub struct PathStatsRecord {9+ pub rtt_ms: i64,10+ pub udp_tx_datagrams: i64,11+ pub udp_tx_bytes: i64,12+ pub udp_rx_datagrams: i64,13+ pub udp_rx_bytes: i64,14+ pub cwnd: i64,15+ pub congestion_events: i64,16+ pub lost_packets: i64,17+ pub lost_bytes: i64,18+ pub current_mtu: u32,19+}20+21+impl From<iroh::endpoint::PathStats> for PathStatsRecord {22+ fn from(s: iroh::endpoint::PathStats) -> Self {23+ Self {24+ rtt_ms: s.rtt.as_millis() as i64,25+ udp_tx_datagrams: s.udp_tx.datagrams as i64,26+ udp_tx_bytes: s.udp_tx.bytes as i64,27+ udp_rx_datagrams: s.udp_rx.datagrams as i64,28+ udp_rx_bytes: s.udp_rx.bytes as i64,29+ cwnd: s.cwnd as i64,30+ congestion_events: s.congestion_events as i64,31+ lost_packets: s.lost_packets as i64,32+ lost_bytes: s.lost_bytes as i64,33+ current_mtu: s.current_mtu as u32,34+ }35+ }36+}37+38+/// A flat snapshot of an open path's state.39+40+41+pub struct PathSnapshot {42+ pub id: String,43+ pub is_selected: bool,44+ pub remote_addr: String,45+ pub is_ip: bool,46+ pub is_relay: bool,47+ pub rtt_ms: i64,48+ pub stats: PathStatsRecord,49+}50+51+pub(crate) fn transport_addr_to_string(addr: &TransportAddr) -> String {52+ match addr {53+ TransportAddr::Ip(socket) => socket.to_string(),54+ TransportAddr::Relay(url) => url.to_string(),55+ _ => "unknown".to_string(),56+ }57+}58+59+pub(crate) fn local_transport_addr_to_string(addr: &LocalTransportAddr) -> String {60+ match addr {61+ LocalTransportAddr::Ip(Some(ip)) => ip.to_string(),62+ LocalTransportAddr::Ip(None) => "unknown".to_string(),63+ LocalTransportAddr::Relay(url) => url.to_string(),64+ LocalTransportAddr::Custom(Some(c)) => format!("{c:?}"),65+ _ => "unknown".to_string(),66+ }67+}68+69+/// An event from `Connection::watchPathEvents`.70+71+72+pub enum PathEventKind {73+ Opened,74+ Closed,75+ Selected,76+ Lagged,77+}78+79+/// A path event with its associated data.80+81+82+pub struct PathEvent {83+ pub kind: PathEventKind,84+ pub id: Option<String>,85+ pub remote_addr: Option<String>,86+ pub local_addr: Option<String>,87+ pub last_stats: Option<PathStatsRecord>,88+ pub missed: Option<i64>,89+}90+91+impl From<iroh::endpoint::PathEvent> for PathEvent {92+ fn from(e: iroh::endpoint::PathEvent) -> Self {93+ match e {94+ iroh::endpoint::PathEvent::Opened {95+ id,96+ remote_addr,97+ local_addr,98+ ..99+ } => PathEvent {100+ kind: PathEventKind::Opened,101+ id: Some(id.to_string()),102+ remote_addr: Some(transport_addr_to_string(&remote_addr)),103+ local_addr: Some(local_transport_addr_to_string(&local_addr)),104+ last_stats: None,105+ missed: None,106+ },107+ iroh::endpoint::PathEvent::Closed {108+ id,109+ remote_addr,110+ local_addr,111+ last_stats,112+ ..113+ } => PathEvent {114+ kind: PathEventKind::Closed,115+ id: Some(id.to_string()),116+ remote_addr: Some(transport_addr_to_string(&remote_addr)),117+ local_addr: Some(local_transport_addr_to_string(&local_addr)),118+ last_stats: Some((*last_stats).into()),119+ missed: None,120+ },121+ iroh::endpoint::PathEvent::Selected {122+ id,123+ remote_addr,124+ local_addr,125+ ..126+ } => PathEvent {127+ kind: PathEventKind::Selected,128+ id: Some(id.to_string()),129+ remote_addr: Some(transport_addr_to_string(&remote_addr)),130+ local_addr: Some(local_transport_addr_to_string(&local_addr)),131+ last_stats: None,132+ missed: None,133+ },134+ iroh::endpoint::PathEvent::Lagged { missed, .. } => PathEvent {135+ kind: PathEventKind::Lagged,136+ id: None,137+ remote_addr: None,138+ local_addr: None,139+ last_stats: None,140+ missed: Some(missed as i64),141+ },142+ _ => PathEvent {143+ kind: PathEventKind::Lagged,144+ id: None,145+ remote_addr: None,146+ local_addr: None,147+ last_stats: None,148+ missed: Some(0),149+ },150+ }151+ }152+}153+154+pub(crate) fn snapshot_paths(conn: &iroh::endpoint::Connection) -> Vec<PathSnapshot> {155+ conn.paths()156+ .iter()157+ .map(|p| PathSnapshot {158+ id: p.id().to_string(),159+ is_selected: p.is_selected(),160+ remote_addr: transport_addr_to_string(p.remote_addr()),161+ is_ip: p.is_ip(),162+ is_relay: p.is_relay(),163+ rtt_ms: p.rtt().as_millis() as i64,164+ stats: p.stats().into(),165+ })166+ .collect()167+}@@ -0,0 +1,194 @@
1+use std::{str::FromStr, sync::Arc};2+3+use napi::bindgen_prelude::*;4+use napi_derive::napi;5+6+/// Config for a single relay server.7+8+9+pub struct RelayConfig {10+ pub url: String,11+ pub quic_port: Option<u16>,12+ pub auth_token: Option<String>,13+}14+15+impl TryFrom<RelayConfig> for iroh::RelayConfig {16+ type Error = anyhow::Error;17+18+ fn try_from(value: RelayConfig) -> anyhow::Result<Self> {19+ let url = iroh::RelayUrl::from_str(&value.url)?;20+ let quic = value.quic_port.map(iroh_relay::RelayQuicConfig::new);21+ let mut config = iroh::RelayConfig::new(url, quic);22+ if let Some(token) = value.auth_token {23+ config = config.with_auth_token(token);24+ }25+ Ok(config)26+ }27+}28+29+impl From<&iroh::RelayConfig> for RelayConfig {30+ fn from(value: &iroh::RelayConfig) -> Self {31+ Self {32+ url: value.url.to_string(),33+ quic_port: value.quic.as_ref().map(|q| q.port),34+ auth_token: value.auth_token.clone(),35+ }36+ }37+}38+39+/// A collection of relay servers an endpoint should consider.40+41+42+pub struct RelayMap(pub(crate) iroh::RelayMap);43+44+impl From<iroh::RelayMap> for RelayMap {45+ fn from(map: iroh::RelayMap) -> Self {46+ Self(map)47+ }48+}49+50+51+impl RelayMap {52+ /// Create an empty relay map.53+ 54+ pub fn empty() -> Self {55+ Self(iroh::RelayMap::empty())56+ }57+58+ /// Build a relay map from a list of relay URLs.59+ 60+ pub fn from_urls(urls: Vec<String>) -> Result<Self> {61+ let map = iroh::RelayMap::try_from_iter(urls.iter().map(|s| s.as_str()))62+ .map_err(anyhow::Error::from)?;63+ Ok(Self(map))64+ }65+66+ /// Insert a relay (replacing any prior entry for the same URL).67+ 68+ pub fn insert(&self, config: RelayConfig) -> Result<()> {69+ let config: iroh::RelayConfig = config.try_into()?;70+ let url = config.url.clone();71+ self.0.insert(url, Arc::new(config));72+ Ok(())73+ }74+75+ /// Remove the entry for the given relay URL. Returns true if removed.76+ 77+ pub fn remove(&self, url: String) -> Result<bool> {78+ let url = iroh::RelayUrl::from_str(&url).map_err(anyhow::Error::from)?;79+ Ok(self.0.remove(&url).is_some())80+ }81+82+ /// Check whether the given relay URL is in the map.83+ 84+ pub fn contains(&self, url: String) -> Result<bool> {85+ let url = iroh::RelayUrl::from_str(&url).map_err(anyhow::Error::from)?;86+ Ok(self.0.contains(&url))87+ }88+89+ /// Look up the configuration for the given relay URL.90+ 91+ pub fn get(&self, url: String) -> Result<Option<RelayConfig>> {92+ let url = iroh::RelayUrl::from_str(&url).map_err(anyhow::Error::from)?;93+ Ok(self.0.get(&url).map(|c| (c.as_ref()).into()))94+ }95+96+ /// All relay URLs currently in the map.97+ 98+ pub fn urls(&self) -> Vec<String> {99+ self.0100+ .urls::<Vec<_>>()101+ .into_iter()102+ .map(|u| u.to_string())103+ .collect()104+ }105+106+ /// Number of relays in the map.107+ 108+ pub fn len(&self) -> u32 {109+ self.0.len() as _110+ }111+112+ /// True if the map has no relays.113+ 114+ pub fn is_empty(&self) -> bool {115+ self.0.is_empty()116+ }117+118+ /// Clean string representation.119+ 120+ pub fn to_string(&self) -> String {121+ let urls: Vec<String> = self122+ .0123+ .urls::<Vec<_>>()124+ .into_iter()125+ .map(|u| u.to_string())126+ .collect();127+ format!("RelayMap([{}])", urls.join(", "))128+ }129+}130+131+/// Configuration for which relay servers an endpoint uses.132+133+134+pub struct RelayMode(pub(crate) iroh::RelayMode);135+136+impl From<iroh::RelayMode> for RelayMode {137+ fn from(mode: iroh::RelayMode) -> Self {138+ Self(mode)139+ }140+}141+142+143+impl RelayMode {144+ /// No relays.145+ 146+ pub fn disabled() -> Self {147+ Self(iroh::RelayMode::Disabled)148+ }149+150+ /// Use the n0 production relay map.151+ 152+ pub fn default_mode() -> Self {153+ Self(iroh::RelayMode::Default)154+ }155+156+ /// Use the n0 staging relay map.157+ 158+ pub fn staging() -> Self {159+ Self(iroh::RelayMode::Staging)160+ }161+162+ /// Use a custom relay map.163+ 164+ pub fn custom(map: &RelayMap) -> Self {165+ Self(iroh::RelayMode::Custom(map.0.clone()))166+ }167+168+ /// Build a custom relay mode from a list of relay URLs.169+ 170+ pub fn custom_from_urls(urls: Vec<String>) -> Result<Self> {171+ let urls: Vec<iroh::RelayUrl> = urls172+ .into_iter()173+ .map(|s| iroh::RelayUrl::from_str(&s).map_err(anyhow::Error::from))174+ .collect::<anyhow::Result<Vec<_>>>()?;175+ Ok(Self(iroh::RelayMode::custom(urls)))176+ }177+178+ /// The relay map this mode resolves to.179+ 180+ pub fn relay_map(&self) -> RelayMap {181+ self.0.relay_map().into()182+ }183+184+ /// Clean string representation.185+ 186+ pub fn to_string(&self) -> String {187+ match &self.0 {188+ iroh::RelayMode::Disabled => "disabled".to_string(),189+ iroh::RelayMode::Default => "default".to_string(),190+ iroh::RelayMode::Staging => "staging".to_string(),191+ iroh::RelayMode::Custom(map) => format!("custom({} relays)", map.len()),192+ }193+ }194+}@@ -0,0 +1,46 @@
1+use iroh_tickets::Ticket as _;2+use napi::bindgen_prelude::*;3+use napi_derive::napi;4+5+use crate::EndpointAddr;6+7+/// A token containing information for establishing a connection to an endpoint.8+9+10+pub struct EndpointTicket(iroh_tickets::endpoint::EndpointTicket);11+12+impl From<iroh_tickets::endpoint::EndpointTicket> for EndpointTicket {13+ fn from(t: iroh_tickets::endpoint::EndpointTicket) -> Self {14+ EndpointTicket(t)15+ }16+}17+18+19+impl EndpointTicket {20+ /// Wrap an [`EndpointAddr`] into a ticket.21+ 22+ pub fn from_addr(addr: &EndpointAddr) -> Result<Self> {23+ let inner: iroh::EndpointAddr = addr.try_into()?;24+ Ok(iroh_tickets::endpoint::EndpointTicket::new(inner).into())25+ }26+27+ /// Parse a ticket from its base32 string form.28+ 29+ pub fn from_string(s: String) -> Result<Self> {30+ let ticket = iroh_tickets::endpoint::EndpointTicket::decode_string(&s)31+ .map_err(anyhow::Error::from)?;32+ Ok(EndpointTicket(ticket))33+ }34+35+ /// The [`EndpointAddr`] embedded in this ticket.36+ 37+ pub fn endpoint_addr(&self) -> EndpointAddr {38+ self.0.endpoint_addr().clone().into()39+ }40+41+ /// Base32 string form.42+ 43+ pub fn to_string(&self) -> String {44+ self.0.to_string()45+ }46+}@@ -0,0 +1,111 @@
1+use n0_future::{StreamExt, task::AbortOnDropHandle};2+use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode};3+use napi_derive::napi;4+use tokio::sync::Mutex;5+6+use crate::{EndpointAddr, PathEvent, PathSnapshot};7+8+/// Handle to a running watcher task. Call `stop()` (or drop) to unregister.9+10+pub struct WatchHandle {11+ task: Mutex<Option<AbortOnDropHandle<()>>>,12+}13+14+impl WatchHandle {15+ pub(crate) fn new(task: AbortOnDropHandle<()>) -> Self {16+ Self {17+ task: Mutex::new(Some(task)),18+ }19+ }20+}21+22+23+impl WatchHandle {24+ /// Stop the watcher, aborting the background task.25+ 26+ pub async fn stop(&self) {27+ self.task.lock().await.take();28+ }29+}30+31+pub(crate) fn spawn_watch_addr(32+ endpoint: iroh::Endpoint,33+ cb: ThreadsafeFunction<EndpointAddr>,34+) -> WatchHandle {35+ let task = n0_future::task::spawn(async move {36+ use iroh::Watcher;37+ let mut stream = endpoint.watch_addr().stream();38+ while let Some(addr) = stream.next().await {39+ let mapped: EndpointAddr = addr.into();40+ cb.call(Ok(mapped), ThreadsafeFunctionCallMode::NonBlocking);41+ }42+ });43+ WatchHandle::new(AbortOnDropHandle::new(task))44+}45+46+pub(crate) fn spawn_home_relay_watch(47+ endpoint: iroh::Endpoint,48+ cb: ThreadsafeFunction<Vec<String>>,49+) -> WatchHandle {50+ let task = n0_future::task::spawn(async move {51+ use iroh::Watcher;52+ let mut stream = endpoint.home_relay_status().stream();53+ while let Some(statuses) = stream.next().await {54+ let urls: Vec<String> = statuses.into_iter().map(|s| s.url().to_string()).collect();55+ cb.call(Ok(urls), ThreadsafeFunctionCallMode::NonBlocking);56+ }57+ });58+ WatchHandle::new(AbortOnDropHandle::new(task))59+}60+61+pub(crate) fn spawn_network_change_watch(62+ endpoint: iroh::Endpoint,63+ cb: ThreadsafeFunction<()>,64+) -> WatchHandle {65+ let task = n0_future::task::spawn(async move {66+ loop {67+ endpoint.network_change().await;68+ cb.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking);69+ }70+ });71+ WatchHandle::new(AbortOnDropHandle::new(task))72+}73+74+pub(crate) fn spawn_paths_watch(75+ conn: iroh::endpoint::Connection,76+ cb: ThreadsafeFunction<Vec<PathSnapshot>>,77+) -> WatchHandle {78+ let task = n0_future::task::spawn(async move {79+ let mut stream = conn.paths_stream();80+ while let Some(snapshot) = stream.next().await {81+ let mapped: Vec<PathSnapshot> = snapshot82+ .iter()83+ .map(|p| PathSnapshot {84+ id: p.id().to_string(),85+ is_selected: p.is_selected(),86+ remote_addr: crate::path::transport_addr_to_string(p.remote_addr()),87+ is_ip: p.is_ip(),88+ is_relay: p.is_relay(),89+ rtt_ms: p.rtt().as_millis() as i64,90+ stats: p.stats().into(),91+ })92+ .collect();93+ cb.call(Ok(mapped), ThreadsafeFunctionCallMode::NonBlocking);94+ }95+ });96+ WatchHandle::new(AbortOnDropHandle::new(task))97+}98+99+pub(crate) fn spawn_path_events_watch(100+ conn: iroh::endpoint::Connection,101+ cb: ThreadsafeFunction<PathEvent>,102+) -> WatchHandle {103+ let task = n0_future::task::spawn(async move {104+ let mut stream = conn.path_events();105+ while let Some(event) = stream.next().await {106+ let mapped: PathEvent = event.into();107+ cb.call(Ok(mapped), ThreadsafeFunctionCallMode::NonBlocking);108+ }109+ });110+ WatchHandle::new(AbortOnDropHandle::new(task))111+}@@ -1,47 +1,5 @@
1-// Status-code contract for the db-iroh-ffi flat C ABI (src/lib.rs consts).2-3-export const DB_OK = 0;4-5-const CODE_NAMES: Record<number, string> = {6- [-1]: "UNIMPLEMENTED",7- [-2]: "BAD_HANDLE",8- [-3]: "BAD_ARG",9- [-4]: "IO",10- [-5]: "NOT_FOUND",11- [-6]: "CRYPTO",12- [-7]: "PANIC",13-};14-15-export const DB_ERR_BAD_HANDLE = -2;16-export const DB_ERR_IO = -4;17-export const DB_ERR_PANIC = -7;18-19-export class IrohFfiError extends Error {20- readonly code: number;21- constructor(code: number, context: string) {22- super(`iroh ffi ${context}: ${CODE_NAMES[code] ?? "UNKNOWN"} (${code})`);23- this.name = "IrohFfiError";24- this.code = code;25- }26-}27-28-/** DB_ERR_PANIC: the ONE handle that panicked is poisoned and must be closed29- * and reopened; other handles and the process are unaffected. */30-export class IrohPoisonedError extends IrohFfiError {31- constructor(context: string) {32- super(DB_ERR_PANIC, context);33- this.name = "IrohPoisonedError";34- }35-}36-37-export function check(code: number, context: string): void {38- if (code === DB_OK) return;39- if (code === DB_ERR_PANIC) throw new IrohPoisonedError(context);40- throw new IrohFfiError(code, context);41-}42-43-/** The iroh mesh layer could not be brought up (missing dylib, unsupported44- * platform, ABI mismatch). The Jazz server itself still works without it. */1+/** The iroh mesh layer could not be brought up (missing addon, unsupported2+ * platform, wrong build). The Jazz server itself still works without it. */453 export class MeshUnavailableError extends Error {464 constructor(reason: string) {475 super(`iroh mesh unavailable: ${reason}`);@@ -1,120 +1,150 @@
1-// Typed wrappers over the connections surface: one class per C handle,2-// idempotent close, poison contract surfaced as IrohPoisonedError.3-4-import {5- check,6- DB_OK,7- type IrohLib,8- newDbBufStruct,9- readDbBuf,10-} from "../native/iroh.ts";11-import { DB_ERR_BAD_HANDLE, DB_ERR_IO, IrohPoisonedError } from "../errors.ts";1+// Typed adapter over the vendored iroh-js addon, preserving the contract the2+// tunnel and fixtures were built against: IrohNode open/ticket/connect/accept3+// /close, IrohConn sendMsg/recvMsg/close (recvMsg → null at end of stream).4+//5+// One bi-stream per connection carries the framed messages (lofi_ext6+// writeFrame/readFrame). The bi-stream is LAZY: QUIC acceptBi only resolves7+// when the dialer's first bytes arrive, so accept() must return at the8+// connection level or one silent dialer stalls the accept loop; first9+// sendMsg/recvMsg resolves the stream instead. accept() resolves null when10+// the endpoint closes — the acceptor loop's clean exit (proven at gate 0).11+12+import type { BiStream, Connection, Endpoint, IrohAddon } from "../native/addon.ts";13+14+const ALPN = [...new TextEncoder().encode("lofi-node/0")];1215 1316 export class IrohConn {14- #lib: IrohLib;15- #handle: bigint;17+ #addon: IrohAddon;18+ #conn: Connection;19+ #biFactory: () => Promise<BiStream>;20+ #bi: Promise<BiStream> | null = null;21+ #sendChain: Promise<void> = Promise.resolve();22+ #sendFailed = false;1623 #closed = false;1724 18- constructor(lib: IrohLib, handle: bigint) {19- this.#lib = lib;20- this.#handle = handle;25+ constructor(addon: IrohAddon, conn: Connection, biFactory: () => Promise<BiStream>) {26+ this.#addon = addon;27+ this.#conn = conn;28+ this.#biFactory = biFactory;2129 }2230 23- /** Post one framed message. Never parks: frames feed the crate's writer task. */31+ #stream(): Promise<BiStream> {32+ return (this.#bi ??= this.#biFactory());33+ }34+35+ /** Post one framed message. Returns void (contract): frames are chained in36+ * call order; a write failure marks the conn dead and closes it, which the37+ * peer and our recv loop observe as end-of-stream. */2438 sendMsg(bytes: Uint8Array): void {25- check(26- this.#lib.symbols.db_conn_send_msg(this.#handle, bytes, BigInt(bytes.length)),27- "conn_send_msg",28- );39+ if (this.#closed || this.#sendFailed) throw new Error("conn is closed");40+ this.#sendChain = this.#sendChain41+ .then(async () => {42+ const bi = await this.#stream();43+ await this.#addon.writeFrame(bi.send, bytes);44+ })45+ .catch(() => {46+ this.#sendFailed = true;47+ this.close().catch(() => {});48+ });2949 }3050 31- /** Await the next framed message; null once the connection is closed/ended.32- * Parks an FFI-pool thread, not the isolate. */51+ /** Await the next framed message; null once the stream ends. */3352 async recvMsg(): Promise<Uint8Array | null> {34- const out = newDbBufStruct();35- const code = await this.#lib.symbols.db_conn_recv_msg(this.#handle, out);36- if (code === DB_OK) return readDbBuf(this.#lib, out);37- if (code === DB_ERR_IO || code === DB_ERR_BAD_HANDLE) return null;38- check(code, "conn_recv_msg");39- return null;53+ if (this.#closed) return null;54+ try {55+ const bi = await this.#stream();56+ const frame = await this.#addon.readFrame(bi.recv, null);57+ return frame === null ? null : new Uint8Array(frame);58+ } catch {59+ // Mid-frame death or closed conn: end-of-stream for the caller.60+ return null;61+ }4062 }4163 42- /** Idempotent. Wakes a parked recvMsg on this conn (per-handle registry). */64+ stats(): { rtt: number | null; paths: number } {65+ try {66+ return { rtt: this.#conn.rtt(), paths: this.#conn.paths().length };67+ } catch {68+ return { rtt: null, paths: 0 };69+ }70+ }71+72+ /** Idempotent. Flushes queued frames (bounded) before closing. */4373 async close(): Promise<void> {4474 if (this.#closed) return;4575 this.#closed = true;46- const code = await this.#lib.symbols.db_conn_close(this.#handle);47- if (code !== DB_OK && code !== DB_ERR_BAD_HANDLE) check(code, "conn_close");76+ await Promise.race([this.#sendChain, new Promise((r) => setTimeout(r, 3000))]);77+ if (this.#bi !== null) {78+ try {79+ const bi = await this.#bi;80+ await bi.send.finish();81+ } catch {82+ // stream never opened or already finished/reset83+ }84+ }85+ try {86+ this.#conn.close(0n, []);87+ } catch {88+ // already closed89+ }4890 }4991 }5092 5193 export class IrohNode {52- #lib: IrohLib;53- #handle: bigint;94+ #addon: IrohAddon;95+ #endpoint: Endpoint;5496 #closed = false;5597 56- private constructor(lib: IrohLib, handle: bigint) {57- this.#lib = lib;58- this.#handle = handle;98+ private constructor(addon: IrohAddon, endpoint: Endpoint) {99+ this.#addon = addon;100+ this.#endpoint = endpoint;59101 }60102 61- static async open(lib: IrohLib, secretKey: Uint8Array): Promise<IrohNode> {103+ static async open(addon: IrohAddon, secretKey: Uint8Array): Promise<IrohNode> {62104 if (secretKey.length !== 32) throw new Error("iroh secret key must be 32 bytes");63- const out = new BigUint64Array(1);64- const outBytes = new Uint8Array(out.buffer);65- check(await lib.symbols.db_node_open(secretKey, outBytes), "node_open");66- return new IrohNode(lib, out[0]);67- }68-69- id(): Uint8Array {70- const id = new Uint8Array(32);71- check(this.#lib.symbols.db_node_id(this.#handle, id), "node_id");72- return id;105+ const builder = addon.Endpoint.builder();106+ builder.applyN0();107+ builder.secretKey([...secretKey]);108+ builder.alpns([ALPN]);109+ return new IrohNode(addon, await builder.bind());73110 }74111 75- /** Dialable postcard-encoded EndpointAddr (what tickets carry). */76- async addr(): Promise<Uint8Array> {77- const out = newDbBufStruct();78- check(await this.#lib.symbols.db_node_addr(this.#handle, out), "node_addr");79- return readDbBuf(this.#lib, out);112+ idString(): string {113+ return this.#endpoint.id().toString();80114 }81115 82- /** Seed a peer address so connect can dial without discovery. */83- addAddr(addrBytes: Uint8Array): void {84- check(85- this.#lib.symbols.db_add_addr(this.#handle, addrBytes, BigInt(addrBytes.length)),86- "add_addr",87- );116+ /** Pairing ticket (upstream EndpointTicket string). Waits briefly for the117+ * endpoint to learn dialable addresses (direct or relay). */118+ async ticket(): Promise<string> {119+ for (let i = 0; i < 100; i++) {120+ const addr = this.#endpoint.addr();121+ if (addr.directAddresses().length > 0 || addr.relayUrl() !== null) {122+ return this.#addon.EndpointTicket.fromAddr(addr).toString();123+ }124+ await new Promise((r) => setTimeout(r, 100));125+ }126+ throw new Error("endpoint has no dialable addresses after 10s");88127 }89128 90- async connect(addrBytes: Uint8Array): Promise<IrohConn> {91- const out = new BigUint64Array(1);92- check(93- await this.#lib.symbols.db_connect(94- this.#handle,95- addrBytes,96- BigInt(addrBytes.length),97- new Uint8Array(out.buffer),98- ),99- "connect",100- );101- return new IrohConn(this.#lib, out[0]);129+ /** Dial a peer by its ticket. */130+ async connect(ticket: string): Promise<IrohConn> {131+ const addr = this.#addon.EndpointTicket.fromString(ticket).endpointAddr();132+ const conn = await this.#endpoint.connect(addr, ALPN);133+ return new IrohConn(this.#addon, conn, () => conn.openBi());102134 }103135 104- /** Await the next inbound connection. KNOWN GAP: not woken by close() —105- * callers must treat a daemon-lifetime accept loop as process-scoped. */106- async accept(): Promise<IrohConn> {107- const out = new BigUint64Array(1);108- check(await this.#lib.symbols.db_accept(this.#handle, new Uint8Array(out.buffer)), "accept");109- return new IrohConn(this.#lib, out[0]);136+ /** Await the next inbound connection; null when the endpoint closes (the137+ * acceptor loop's clean exit). */138+ async accept(): Promise<IrohConn | null> {139+ const incoming = await this.#endpoint.acceptNext();140+ if (incoming === null) return null;141+ const conn = await (await incoming.accept()).connect();142+ return new IrohConn(this.#addon, conn, () => conn.acceptBi());110143 }111144 112145 async close(): Promise<void> {113146 if (this.#closed) return;114147 this.#closed = true;115- const code = await this.#lib.symbols.db_node_close(this.#handle);116- if (code !== DB_OK && code !== DB_ERR_BAD_HANDLE) check(code, "node_close");148+ await this.#endpoint.close();117149 }118150 }119-120-export { IrohPoisonedError };Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.