├── .dockerignore ├── .github └── workflows │ ├── build.yaml │ └── release.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md └── src ├── add_device.rs ├── devices.rs ├── heartbeat.rs ├── main.rs ├── mdns.rs ├── passthrough.rs └── raw_packet.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | harvest.sh 3 | output 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | buildx: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Set up QEMU 13 | uses: docker/setup-qemu-action@v1 14 | - name: Set up Docker Buildx 15 | id: buildx 16 | uses: docker/setup-buildx-action@v1 17 | - name: Log in to the Container registry 18 | uses: docker/login-action@v1 19 | with: 20 | registry: ghcr.io 21 | username: ${{ github.actor }} 22 | password: ${{ secrets.TOKEN }} 23 | - name: Build and push 24 | uses: docker/build-push-action@v2 25 | with: 26 | context: . 27 | push: true 28 | platforms: linux/amd64,linux/arm64,linux/arm/v7 29 | tags: ${{ secrets.TAGS }} 30 | cache-from: type=gha 31 | cache-to: type=gha,mode=max 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v[0-9]+.* 10 | 11 | jobs: 12 | create-release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: taiki-e/create-gh-release-action@v1 17 | with: 18 | # (required) GitHub token for creating GitHub Releases. 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | upload-assets: 22 | needs: create-release 23 | strategy: 24 | matrix: 25 | include: 26 | - target: x86_64-unknown-linux-gnu 27 | os: ubuntu-latest 28 | - target: x86_64-pc-windows-msvc 29 | os: windows-latest 30 | - target: x86_64-apple-darwin 31 | os: macos-13 32 | - target: aarch64-apple-darwin 33 | os: macos-latest 34 | runs-on: ${{ matrix.os }} 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: taiki-e/upload-rust-binary-action@v1 38 | with: 39 | # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload. 40 | # Note that glob pattern is not supported yet. 41 | bin: netmuxd 42 | # (optional) Target triple, default is host triple. 43 | # This is optional but it is recommended that this always be set to 44 | # clarify which target you are building for if macOS is included in 45 | # the matrix because GitHub Actions changed the default architecture 46 | # of macos-latest since macos-14. 47 | target: ${{ matrix.target }} 48 | # (optional) On which platform to distribute the `.tar.gz` file. 49 | # [default value: unix] 50 | # [possible values: all, unix, windows, none] 51 | tar: unix 52 | # (optional) On which platform to distribute the `.zip` file. 53 | # [default value: windows] 54 | # [possible values: all, unix, windows, none] 55 | zip: windows 56 | # (required) GitHub token for uploading assets to GitHub Releases. 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | run.sh 3 | .DS_Store 4 | output 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "async-attributes" 81 | version = "1.1.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" 84 | dependencies = [ 85 | "quote", 86 | "syn 1.0.109", 87 | ] 88 | 89 | [[package]] 90 | name = "async-channel" 91 | version = "1.9.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 94 | dependencies = [ 95 | "concurrent-queue", 96 | "event-listener 2.5.3", 97 | "futures-core", 98 | ] 99 | 100 | [[package]] 101 | name = "async-channel" 102 | version = "2.3.1" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 105 | dependencies = [ 106 | "concurrent-queue", 107 | "event-listener-strategy", 108 | "futures-core", 109 | "pin-project-lite", 110 | ] 111 | 112 | [[package]] 113 | name = "async-executor" 114 | version = "1.13.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" 117 | dependencies = [ 118 | "async-task", 119 | "concurrent-queue", 120 | "fastrand", 121 | "futures-lite", 122 | "slab", 123 | ] 124 | 125 | [[package]] 126 | name = "async-global-executor" 127 | version = "2.4.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" 130 | dependencies = [ 131 | "async-channel 2.3.1", 132 | "async-executor", 133 | "async-io", 134 | "async-lock", 135 | "blocking", 136 | "futures-lite", 137 | "once_cell", 138 | ] 139 | 140 | [[package]] 141 | name = "async-io" 142 | version = "2.4.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" 145 | dependencies = [ 146 | "async-lock", 147 | "cfg-if 1.0.0", 148 | "concurrent-queue", 149 | "futures-io", 150 | "futures-lite", 151 | "parking", 152 | "polling", 153 | "rustix", 154 | "slab", 155 | "tracing", 156 | "windows-sys 0.59.0", 157 | ] 158 | 159 | [[package]] 160 | name = "async-lock" 161 | version = "3.4.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 164 | dependencies = [ 165 | "event-listener 5.4.0", 166 | "event-listener-strategy", 167 | "pin-project-lite", 168 | ] 169 | 170 | [[package]] 171 | name = "async-process" 172 | version = "2.3.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" 175 | dependencies = [ 176 | "async-channel 2.3.1", 177 | "async-io", 178 | "async-lock", 179 | "async-signal", 180 | "async-task", 181 | "blocking", 182 | "cfg-if 1.0.0", 183 | "event-listener 5.4.0", 184 | "futures-lite", 185 | "rustix", 186 | "tracing", 187 | ] 188 | 189 | [[package]] 190 | name = "async-signal" 191 | version = "0.2.10" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" 194 | dependencies = [ 195 | "async-io", 196 | "async-lock", 197 | "atomic-waker", 198 | "cfg-if 1.0.0", 199 | "futures-core", 200 | "futures-io", 201 | "rustix", 202 | "signal-hook-registry", 203 | "slab", 204 | "windows-sys 0.59.0", 205 | ] 206 | 207 | [[package]] 208 | name = "async-std" 209 | version = "1.13.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" 212 | dependencies = [ 213 | "async-attributes", 214 | "async-channel 1.9.0", 215 | "async-global-executor", 216 | "async-io", 217 | "async-lock", 218 | "async-process", 219 | "crossbeam-utils", 220 | "futures-channel", 221 | "futures-core", 222 | "futures-io", 223 | "futures-lite", 224 | "gloo-timers", 225 | "kv-log-macro", 226 | "log", 227 | "memchr", 228 | "once_cell", 229 | "pin-project-lite", 230 | "pin-utils", 231 | "slab", 232 | "wasm-bindgen-futures", 233 | ] 234 | 235 | [[package]] 236 | name = "async-stream" 237 | version = "0.2.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" 240 | dependencies = [ 241 | "async-stream-impl", 242 | "futures-core", 243 | ] 244 | 245 | [[package]] 246 | name = "async-stream-impl" 247 | version = "0.2.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" 250 | dependencies = [ 251 | "proc-macro2", 252 | "quote", 253 | "syn 1.0.109", 254 | ] 255 | 256 | [[package]] 257 | name = "async-task" 258 | version = "4.7.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 261 | 262 | [[package]] 263 | name = "atomic-waker" 264 | version = "1.1.2" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 267 | 268 | [[package]] 269 | name = "autocfg" 270 | version = "1.4.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 273 | 274 | [[package]] 275 | name = "avahi-sys" 276 | version = "0.10.1" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "70e00c83b3887835fd326daae1b2c4e7a435405033331a876ae1dd03f77b8274" 279 | dependencies = [ 280 | "bindgen 0.69.5", 281 | "libc", 282 | ] 283 | 284 | [[package]] 285 | name = "aws-lc-rs" 286 | version = "1.13.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" 289 | dependencies = [ 290 | "aws-lc-sys", 291 | "zeroize", 292 | ] 293 | 294 | [[package]] 295 | name = "aws-lc-sys" 296 | version = "0.28.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" 299 | dependencies = [ 300 | "bindgen 0.69.5", 301 | "cc", 302 | "cmake", 303 | "dunce", 304 | "fs_extra", 305 | ] 306 | 307 | [[package]] 308 | name = "backtrace" 309 | version = "0.3.74" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 312 | dependencies = [ 313 | "addr2line", 314 | "cfg-if 1.0.0", 315 | "libc", 316 | "miniz_oxide", 317 | "object", 318 | "rustc-demangle", 319 | "windows-targets", 320 | ] 321 | 322 | [[package]] 323 | name = "base64" 324 | version = "0.22.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 327 | 328 | [[package]] 329 | name = "bindgen" 330 | version = "0.68.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" 333 | dependencies = [ 334 | "bitflags", 335 | "cexpr", 336 | "clang-sys", 337 | "lazy_static", 338 | "lazycell", 339 | "log", 340 | "peeking_take_while", 341 | "prettyplease", 342 | "proc-macro2", 343 | "quote", 344 | "regex", 345 | "rustc-hash", 346 | "shlex", 347 | "syn 2.0.96", 348 | "which", 349 | ] 350 | 351 | [[package]] 352 | name = "bindgen" 353 | version = "0.69.5" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 356 | dependencies = [ 357 | "bitflags", 358 | "cexpr", 359 | "clang-sys", 360 | "itertools", 361 | "lazy_static", 362 | "lazycell", 363 | "log", 364 | "prettyplease", 365 | "proc-macro2", 366 | "quote", 367 | "regex", 368 | "rustc-hash", 369 | "shlex", 370 | "syn 2.0.96", 371 | "which", 372 | ] 373 | 374 | [[package]] 375 | name = "bitflags" 376 | version = "2.7.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" 379 | 380 | [[package]] 381 | name = "blocking" 382 | version = "1.6.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" 385 | dependencies = [ 386 | "async-channel 2.3.1", 387 | "async-task", 388 | "futures-io", 389 | "futures-lite", 390 | "piper", 391 | ] 392 | 393 | [[package]] 394 | name = "bonjour-sys" 395 | version = "0.1.1" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "749c457f0105b15077fedc900f9c3c032c98e12af5cd107477db657dfc82142b" 398 | dependencies = [ 399 | "bindgen 0.68.1", 400 | "libc", 401 | ] 402 | 403 | [[package]] 404 | name = "bumpalo" 405 | version = "3.16.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 408 | 409 | [[package]] 410 | name = "byteorder" 411 | version = "1.5.0" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 414 | 415 | [[package]] 416 | name = "bytes" 417 | version = "1.9.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" 420 | 421 | [[package]] 422 | name = "cc" 423 | version = "1.2.9" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" 426 | dependencies = [ 427 | "jobserver", 428 | "libc", 429 | "shlex", 430 | ] 431 | 432 | [[package]] 433 | name = "cexpr" 434 | version = "0.6.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 437 | dependencies = [ 438 | "nom", 439 | ] 440 | 441 | [[package]] 442 | name = "cfg-if" 443 | version = "0.1.10" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 446 | 447 | [[package]] 448 | name = "cfg-if" 449 | version = "1.0.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 452 | 453 | [[package]] 454 | name = "clang-sys" 455 | version = "1.8.1" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 458 | dependencies = [ 459 | "glob", 460 | "libc", 461 | "libloading", 462 | ] 463 | 464 | [[package]] 465 | name = "cmake" 466 | version = "0.1.54" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 469 | dependencies = [ 470 | "cc", 471 | ] 472 | 473 | [[package]] 474 | name = "colorchoice" 475 | version = "1.0.3" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 478 | 479 | [[package]] 480 | name = "colored" 481 | version = "2.2.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" 484 | dependencies = [ 485 | "lazy_static", 486 | "windows-sys 0.59.0", 487 | ] 488 | 489 | [[package]] 490 | name = "concurrent-queue" 491 | version = "2.5.0" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 494 | dependencies = [ 495 | "crossbeam-utils", 496 | ] 497 | 498 | [[package]] 499 | name = "crossbeam-utils" 500 | version = "0.8.21" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 503 | 504 | [[package]] 505 | name = "darling" 506 | version = "0.10.2" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" 509 | dependencies = [ 510 | "darling_core", 511 | "darling_macro", 512 | ] 513 | 514 | [[package]] 515 | name = "darling_core" 516 | version = "0.10.2" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" 519 | dependencies = [ 520 | "fnv", 521 | "ident_case", 522 | "proc-macro2", 523 | "quote", 524 | "strsim", 525 | "syn 1.0.109", 526 | ] 527 | 528 | [[package]] 529 | name = "darling_macro" 530 | version = "0.10.2" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" 533 | dependencies = [ 534 | "darling_core", 535 | "quote", 536 | "syn 1.0.109", 537 | ] 538 | 539 | [[package]] 540 | name = "deranged" 541 | version = "0.3.11" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 544 | dependencies = [ 545 | "powerfmt", 546 | ] 547 | 548 | [[package]] 549 | name = "derive-getters" 550 | version = "0.2.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "0122f262bf9c9a367829da84f808d9fb128c10ef283bbe7b0922a77cf07b2747" 553 | dependencies = [ 554 | "proc-macro2", 555 | "quote", 556 | "syn 1.0.109", 557 | ] 558 | 559 | [[package]] 560 | name = "derive-new" 561 | version = "0.5.9" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" 564 | dependencies = [ 565 | "proc-macro2", 566 | "quote", 567 | "syn 1.0.109", 568 | ] 569 | 570 | [[package]] 571 | name = "derive_builder" 572 | version = "0.9.0" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" 575 | dependencies = [ 576 | "darling", 577 | "derive_builder_core", 578 | "proc-macro2", 579 | "quote", 580 | "syn 1.0.109", 581 | ] 582 | 583 | [[package]] 584 | name = "derive_builder_core" 585 | version = "0.9.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" 588 | dependencies = [ 589 | "darling", 590 | "proc-macro2", 591 | "quote", 592 | "syn 1.0.109", 593 | ] 594 | 595 | [[package]] 596 | name = "dns-parser" 597 | version = "0.8.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" 600 | dependencies = [ 601 | "byteorder", 602 | "quick-error", 603 | ] 604 | 605 | [[package]] 606 | name = "dunce" 607 | version = "1.0.5" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 610 | 611 | [[package]] 612 | name = "either" 613 | version = "1.13.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 616 | 617 | [[package]] 618 | name = "env_filter" 619 | version = "0.1.3" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 622 | dependencies = [ 623 | "log", 624 | "regex", 625 | ] 626 | 627 | [[package]] 628 | name = "env_logger" 629 | version = "0.11.6" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 632 | dependencies = [ 633 | "anstream", 634 | "anstyle", 635 | "env_filter", 636 | "humantime", 637 | "log", 638 | ] 639 | 640 | [[package]] 641 | name = "equivalent" 642 | version = "1.0.1" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 645 | 646 | [[package]] 647 | name = "err-derive" 648 | version = "0.2.4" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4" 651 | dependencies = [ 652 | "proc-macro-error", 653 | "proc-macro2", 654 | "quote", 655 | "rustversion", 656 | "syn 1.0.109", 657 | "synstructure", 658 | ] 659 | 660 | [[package]] 661 | name = "errno" 662 | version = "0.3.10" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 665 | dependencies = [ 666 | "libc", 667 | "windows-sys 0.59.0", 668 | ] 669 | 670 | [[package]] 671 | name = "event-listener" 672 | version = "2.5.3" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 675 | 676 | [[package]] 677 | name = "event-listener" 678 | version = "5.4.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 681 | dependencies = [ 682 | "concurrent-queue", 683 | "parking", 684 | "pin-project-lite", 685 | ] 686 | 687 | [[package]] 688 | name = "event-listener-strategy" 689 | version = "0.5.3" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" 692 | dependencies = [ 693 | "event-listener 5.4.0", 694 | "pin-project-lite", 695 | ] 696 | 697 | [[package]] 698 | name = "fastrand" 699 | version = "2.3.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 702 | 703 | [[package]] 704 | name = "fnv" 705 | version = "1.0.7" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 708 | 709 | [[package]] 710 | name = "fs_extra" 711 | version = "1.3.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 714 | 715 | [[package]] 716 | name = "futures-channel" 717 | version = "0.3.31" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 720 | dependencies = [ 721 | "futures-core", 722 | ] 723 | 724 | [[package]] 725 | name = "futures-core" 726 | version = "0.3.31" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 729 | 730 | [[package]] 731 | name = "futures-io" 732 | version = "0.3.31" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 735 | 736 | [[package]] 737 | name = "futures-lite" 738 | version = "2.6.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" 741 | dependencies = [ 742 | "fastrand", 743 | "futures-core", 744 | "futures-io", 745 | "parking", 746 | "pin-project-lite", 747 | ] 748 | 749 | [[package]] 750 | name = "futures-macro" 751 | version = "0.3.31" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 754 | dependencies = [ 755 | "proc-macro2", 756 | "quote", 757 | "syn 2.0.96", 758 | ] 759 | 760 | [[package]] 761 | name = "futures-task" 762 | version = "0.3.31" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 765 | 766 | [[package]] 767 | name = "futures-util" 768 | version = "0.3.31" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 771 | dependencies = [ 772 | "futures-core", 773 | "futures-macro", 774 | "futures-task", 775 | "pin-project-lite", 776 | "pin-utils", 777 | "slab", 778 | ] 779 | 780 | [[package]] 781 | name = "getrandom" 782 | version = "0.2.15" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 785 | dependencies = [ 786 | "cfg-if 1.0.0", 787 | "libc", 788 | "wasi", 789 | ] 790 | 791 | [[package]] 792 | name = "gimli" 793 | version = "0.31.1" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 796 | 797 | [[package]] 798 | name = "glob" 799 | version = "0.3.2" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 802 | 803 | [[package]] 804 | name = "gloo-timers" 805 | version = "0.3.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 808 | dependencies = [ 809 | "futures-channel", 810 | "futures-core", 811 | "js-sys", 812 | "wasm-bindgen", 813 | ] 814 | 815 | [[package]] 816 | name = "hashbrown" 817 | version = "0.15.2" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 820 | 821 | [[package]] 822 | name = "hermit-abi" 823 | version = "0.4.0" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 826 | 827 | [[package]] 828 | name = "home" 829 | version = "0.5.11" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 832 | dependencies = [ 833 | "windows-sys 0.59.0", 834 | ] 835 | 836 | [[package]] 837 | name = "humantime" 838 | version = "2.1.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 841 | 842 | [[package]] 843 | name = "ident_case" 844 | version = "1.0.1" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 847 | 848 | [[package]] 849 | name = "idevice" 850 | version = "0.1.29" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "c598466c8dfdd2c6cb03c99f9077236e3141161d88d76d9249529421aef65ac8" 853 | dependencies = [ 854 | "env_logger", 855 | "log", 856 | "plist", 857 | "rustls", 858 | "serde", 859 | "thiserror", 860 | "tokio", 861 | "tokio-rustls", 862 | ] 863 | 864 | [[package]] 865 | name = "indexmap" 866 | version = "2.7.0" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 869 | dependencies = [ 870 | "equivalent", 871 | "hashbrown", 872 | ] 873 | 874 | [[package]] 875 | name = "is_terminal_polyfill" 876 | version = "1.70.1" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 879 | 880 | [[package]] 881 | name = "itertools" 882 | version = "0.12.1" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 885 | dependencies = [ 886 | "either", 887 | ] 888 | 889 | [[package]] 890 | name = "itoa" 891 | version = "1.0.14" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 894 | 895 | [[package]] 896 | name = "jobserver" 897 | version = "0.1.32" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 900 | dependencies = [ 901 | "libc", 902 | ] 903 | 904 | [[package]] 905 | name = "js-sys" 906 | version = "0.3.77" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 909 | dependencies = [ 910 | "once_cell", 911 | "wasm-bindgen", 912 | ] 913 | 914 | [[package]] 915 | name = "kv-log-macro" 916 | version = "1.0.7" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 919 | dependencies = [ 920 | "log", 921 | ] 922 | 923 | [[package]] 924 | name = "lazy_static" 925 | version = "1.5.0" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 928 | 929 | [[package]] 930 | name = "lazycell" 931 | version = "1.3.0" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 934 | 935 | [[package]] 936 | name = "libc" 937 | version = "0.2.169" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 940 | 941 | [[package]] 942 | name = "libloading" 943 | version = "0.8.6" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 946 | dependencies = [ 947 | "cfg-if 1.0.0", 948 | "windows-targets", 949 | ] 950 | 951 | [[package]] 952 | name = "libusb1-sys" 953 | version = "0.7.0" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" 956 | dependencies = [ 957 | "cc", 958 | "libc", 959 | "pkg-config", 960 | "vcpkg", 961 | ] 962 | 963 | [[package]] 964 | name = "linux-raw-sys" 965 | version = "0.4.15" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 968 | 969 | [[package]] 970 | name = "lock_api" 971 | version = "0.4.12" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 974 | dependencies = [ 975 | "autocfg", 976 | "scopeguard", 977 | ] 978 | 979 | [[package]] 980 | name = "log" 981 | version = "0.4.22" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 984 | dependencies = [ 985 | "value-bag", 986 | ] 987 | 988 | [[package]] 989 | name = "mdns" 990 | version = "3.0.0" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "c769962ac75a6ea437f0922b27834bcccd4c013d591383a16ae5731e3ef0f3f3" 993 | dependencies = [ 994 | "async-std", 995 | "async-stream", 996 | "dns-parser", 997 | "err-derive", 998 | "futures-core", 999 | "futures-util", 1000 | "log", 1001 | "net2", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "memchr" 1006 | version = "2.7.4" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1009 | 1010 | [[package]] 1011 | name = "minimal-lexical" 1012 | version = "0.2.1" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1015 | 1016 | [[package]] 1017 | name = "miniz_oxide" 1018 | version = "0.8.2" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" 1021 | dependencies = [ 1022 | "adler2", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "mio" 1027 | version = "1.0.3" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1030 | dependencies = [ 1031 | "libc", 1032 | "wasi", 1033 | "windows-sys 0.52.0", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "net2" 1038 | version = "0.2.39" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" 1041 | dependencies = [ 1042 | "cfg-if 0.1.10", 1043 | "libc", 1044 | "winapi", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "netmuxd" 1049 | version = "0.2.1" 1050 | dependencies = [ 1051 | "colored", 1052 | "env_logger", 1053 | "futures-util", 1054 | "idevice", 1055 | "libusb1-sys", 1056 | "log", 1057 | "mdns", 1058 | "plist", 1059 | "rusb", 1060 | "tokio", 1061 | "uuid", 1062 | "zeroconf", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "nom" 1067 | version = "7.1.3" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1070 | dependencies = [ 1071 | "memchr", 1072 | "minimal-lexical", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "num-conv" 1077 | version = "0.1.0" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1080 | 1081 | [[package]] 1082 | name = "object" 1083 | version = "0.36.7" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1086 | dependencies = [ 1087 | "memchr", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "once_cell" 1092 | version = "1.20.2" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1095 | 1096 | [[package]] 1097 | name = "parking" 1098 | version = "2.2.1" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1101 | 1102 | [[package]] 1103 | name = "parking_lot" 1104 | version = "0.12.3" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1107 | dependencies = [ 1108 | "lock_api", 1109 | "parking_lot_core", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "parking_lot_core" 1114 | version = "0.9.10" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1117 | dependencies = [ 1118 | "cfg-if 1.0.0", 1119 | "libc", 1120 | "redox_syscall", 1121 | "smallvec", 1122 | "windows-targets", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "peeking_take_while" 1127 | version = "0.1.2" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 1130 | 1131 | [[package]] 1132 | name = "pin-project-lite" 1133 | version = "0.2.16" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1136 | 1137 | [[package]] 1138 | name = "pin-utils" 1139 | version = "0.1.0" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1142 | 1143 | [[package]] 1144 | name = "piper" 1145 | version = "0.2.4" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 1148 | dependencies = [ 1149 | "atomic-waker", 1150 | "fastrand", 1151 | "futures-io", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "pkg-config" 1156 | version = "0.3.31" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1159 | 1160 | [[package]] 1161 | name = "plist" 1162 | version = "1.7.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" 1165 | dependencies = [ 1166 | "base64", 1167 | "indexmap", 1168 | "quick-xml", 1169 | "serde", 1170 | "time", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "polling" 1175 | version = "3.7.4" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" 1178 | dependencies = [ 1179 | "cfg-if 1.0.0", 1180 | "concurrent-queue", 1181 | "hermit-abi", 1182 | "pin-project-lite", 1183 | "rustix", 1184 | "tracing", 1185 | "windows-sys 0.59.0", 1186 | ] 1187 | 1188 | [[package]] 1189 | name = "powerfmt" 1190 | version = "0.2.0" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1193 | 1194 | [[package]] 1195 | name = "prettyplease" 1196 | version = "0.2.29" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" 1199 | dependencies = [ 1200 | "proc-macro2", 1201 | "syn 2.0.96", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "proc-macro-error" 1206 | version = "1.0.4" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1209 | dependencies = [ 1210 | "proc-macro-error-attr", 1211 | "proc-macro2", 1212 | "quote", 1213 | "syn 1.0.109", 1214 | "version_check", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "proc-macro-error-attr" 1219 | version = "1.0.4" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1222 | dependencies = [ 1223 | "proc-macro2", 1224 | "quote", 1225 | "version_check", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "proc-macro2" 1230 | version = "1.0.93" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 1233 | dependencies = [ 1234 | "unicode-ident", 1235 | ] 1236 | 1237 | [[package]] 1238 | name = "quick-error" 1239 | version = "1.2.3" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 1242 | 1243 | [[package]] 1244 | name = "quick-xml" 1245 | version = "0.32.0" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" 1248 | dependencies = [ 1249 | "memchr", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "quote" 1254 | version = "1.0.38" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1257 | dependencies = [ 1258 | "proc-macro2", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "redox_syscall" 1263 | version = "0.5.8" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1266 | dependencies = [ 1267 | "bitflags", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "regex" 1272 | version = "1.11.1" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1275 | dependencies = [ 1276 | "aho-corasick", 1277 | "memchr", 1278 | "regex-automata", 1279 | "regex-syntax", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "regex-automata" 1284 | version = "0.4.9" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1287 | dependencies = [ 1288 | "aho-corasick", 1289 | "memchr", 1290 | "regex-syntax", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "regex-syntax" 1295 | version = "0.8.5" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1298 | 1299 | [[package]] 1300 | name = "ring" 1301 | version = "0.17.14" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1304 | dependencies = [ 1305 | "cc", 1306 | "cfg-if 1.0.0", 1307 | "getrandom", 1308 | "libc", 1309 | "untrusted", 1310 | "windows-sys 0.52.0", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "rusb" 1315 | version = "0.9.4" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" 1318 | dependencies = [ 1319 | "libc", 1320 | "libusb1-sys", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "rustc-demangle" 1325 | version = "0.1.24" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1328 | 1329 | [[package]] 1330 | name = "rustc-hash" 1331 | version = "1.1.0" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1334 | 1335 | [[package]] 1336 | name = "rustix" 1337 | version = "0.38.43" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" 1340 | dependencies = [ 1341 | "bitflags", 1342 | "errno", 1343 | "libc", 1344 | "linux-raw-sys", 1345 | "windows-sys 0.59.0", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "rustls" 1350 | version = "0.23.26" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" 1353 | dependencies = [ 1354 | "aws-lc-rs", 1355 | "log", 1356 | "once_cell", 1357 | "rustls-pki-types", 1358 | "rustls-webpki", 1359 | "subtle", 1360 | "zeroize", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "rustls-pki-types" 1365 | version = "1.11.0" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1368 | 1369 | [[package]] 1370 | name = "rustls-webpki" 1371 | version = "0.103.1" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" 1374 | dependencies = [ 1375 | "aws-lc-rs", 1376 | "ring", 1377 | "rustls-pki-types", 1378 | "untrusted", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "rustversion" 1383 | version = "1.0.19" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1386 | 1387 | [[package]] 1388 | name = "scopeguard" 1389 | version = "1.2.0" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1392 | 1393 | [[package]] 1394 | name = "serde" 1395 | version = "1.0.217" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1398 | dependencies = [ 1399 | "serde_derive", 1400 | ] 1401 | 1402 | [[package]] 1403 | name = "serde_derive" 1404 | version = "1.0.217" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1407 | dependencies = [ 1408 | "proc-macro2", 1409 | "quote", 1410 | "syn 2.0.96", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "shlex" 1415 | version = "1.3.0" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1418 | 1419 | [[package]] 1420 | name = "signal-hook-registry" 1421 | version = "1.4.2" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1424 | dependencies = [ 1425 | "libc", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "slab" 1430 | version = "0.4.9" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1433 | dependencies = [ 1434 | "autocfg", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "smallvec" 1439 | version = "1.13.2" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1442 | 1443 | [[package]] 1444 | name = "socket2" 1445 | version = "0.5.8" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1448 | dependencies = [ 1449 | "libc", 1450 | "windows-sys 0.52.0", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "strsim" 1455 | version = "0.9.3" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 1458 | 1459 | [[package]] 1460 | name = "subtle" 1461 | version = "2.6.1" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1464 | 1465 | [[package]] 1466 | name = "syn" 1467 | version = "1.0.109" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1470 | dependencies = [ 1471 | "proc-macro2", 1472 | "quote", 1473 | "unicode-ident", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "syn" 1478 | version = "2.0.96" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 1481 | dependencies = [ 1482 | "proc-macro2", 1483 | "quote", 1484 | "unicode-ident", 1485 | ] 1486 | 1487 | [[package]] 1488 | name = "synstructure" 1489 | version = "0.12.6" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 1492 | dependencies = [ 1493 | "proc-macro2", 1494 | "quote", 1495 | "syn 1.0.109", 1496 | "unicode-xid", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "thiserror" 1501 | version = "2.0.11" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" 1504 | dependencies = [ 1505 | "thiserror-impl", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "thiserror-impl" 1510 | version = "2.0.11" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" 1513 | dependencies = [ 1514 | "proc-macro2", 1515 | "quote", 1516 | "syn 2.0.96", 1517 | ] 1518 | 1519 | [[package]] 1520 | name = "time" 1521 | version = "0.3.37" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" 1524 | dependencies = [ 1525 | "deranged", 1526 | "itoa", 1527 | "num-conv", 1528 | "powerfmt", 1529 | "serde", 1530 | "time-core", 1531 | "time-macros", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "time-core" 1536 | version = "0.1.2" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1539 | 1540 | [[package]] 1541 | name = "time-macros" 1542 | version = "0.2.19" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" 1545 | dependencies = [ 1546 | "num-conv", 1547 | "time-core", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "tokio" 1552 | version = "1.43.0" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 1555 | dependencies = [ 1556 | "backtrace", 1557 | "bytes", 1558 | "libc", 1559 | "mio", 1560 | "parking_lot", 1561 | "pin-project-lite", 1562 | "signal-hook-registry", 1563 | "socket2", 1564 | "tokio-macros", 1565 | "windows-sys 0.52.0", 1566 | ] 1567 | 1568 | [[package]] 1569 | name = "tokio-macros" 1570 | version = "2.5.0" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1573 | dependencies = [ 1574 | "proc-macro2", 1575 | "quote", 1576 | "syn 2.0.96", 1577 | ] 1578 | 1579 | [[package]] 1580 | name = "tokio-rustls" 1581 | version = "0.26.2" 1582 | source = "registry+https://github.com/rust-lang/crates.io-index" 1583 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 1584 | dependencies = [ 1585 | "rustls", 1586 | "tokio", 1587 | ] 1588 | 1589 | [[package]] 1590 | name = "tracing" 1591 | version = "0.1.41" 1592 | source = "registry+https://github.com/rust-lang/crates.io-index" 1593 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1594 | dependencies = [ 1595 | "pin-project-lite", 1596 | "tracing-core", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "tracing-core" 1601 | version = "0.1.33" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1604 | 1605 | [[package]] 1606 | name = "unicode-ident" 1607 | version = "1.0.14" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 1610 | 1611 | [[package]] 1612 | name = "unicode-xid" 1613 | version = "0.2.6" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 1616 | 1617 | [[package]] 1618 | name = "untrusted" 1619 | version = "0.9.0" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1622 | 1623 | [[package]] 1624 | name = "utf8parse" 1625 | version = "0.2.2" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1628 | 1629 | [[package]] 1630 | name = "uuid" 1631 | version = "1.11.1" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" 1634 | dependencies = [ 1635 | "getrandom", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "value-bag" 1640 | version = "1.10.0" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" 1643 | 1644 | [[package]] 1645 | name = "vcpkg" 1646 | version = "0.2.15" 1647 | source = "registry+https://github.com/rust-lang/crates.io-index" 1648 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1649 | 1650 | [[package]] 1651 | name = "version_check" 1652 | version = "0.9.5" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1655 | 1656 | [[package]] 1657 | name = "wasi" 1658 | version = "0.11.0+wasi-snapshot-preview1" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1661 | 1662 | [[package]] 1663 | name = "wasm-bindgen" 1664 | version = "0.2.100" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1667 | dependencies = [ 1668 | "cfg-if 1.0.0", 1669 | "once_cell", 1670 | "rustversion", 1671 | "wasm-bindgen-macro", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "wasm-bindgen-backend" 1676 | version = "0.2.100" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1679 | dependencies = [ 1680 | "bumpalo", 1681 | "log", 1682 | "proc-macro2", 1683 | "quote", 1684 | "syn 2.0.96", 1685 | "wasm-bindgen-shared", 1686 | ] 1687 | 1688 | [[package]] 1689 | name = "wasm-bindgen-futures" 1690 | version = "0.4.50" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 1693 | dependencies = [ 1694 | "cfg-if 1.0.0", 1695 | "js-sys", 1696 | "once_cell", 1697 | "wasm-bindgen", 1698 | "web-sys", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "wasm-bindgen-macro" 1703 | version = "0.2.100" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1706 | dependencies = [ 1707 | "quote", 1708 | "wasm-bindgen-macro-support", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "wasm-bindgen-macro-support" 1713 | version = "0.2.100" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1716 | dependencies = [ 1717 | "proc-macro2", 1718 | "quote", 1719 | "syn 2.0.96", 1720 | "wasm-bindgen-backend", 1721 | "wasm-bindgen-shared", 1722 | ] 1723 | 1724 | [[package]] 1725 | name = "wasm-bindgen-shared" 1726 | version = "0.2.100" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1729 | dependencies = [ 1730 | "unicode-ident", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "web-sys" 1735 | version = "0.3.77" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 1738 | dependencies = [ 1739 | "js-sys", 1740 | "wasm-bindgen", 1741 | ] 1742 | 1743 | [[package]] 1744 | name = "which" 1745 | version = "4.4.2" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1748 | dependencies = [ 1749 | "either", 1750 | "home", 1751 | "once_cell", 1752 | "rustix", 1753 | ] 1754 | 1755 | [[package]] 1756 | name = "winapi" 1757 | version = "0.3.9" 1758 | source = "registry+https://github.com/rust-lang/crates.io-index" 1759 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1760 | dependencies = [ 1761 | "winapi-i686-pc-windows-gnu", 1762 | "winapi-x86_64-pc-windows-gnu", 1763 | ] 1764 | 1765 | [[package]] 1766 | name = "winapi-i686-pc-windows-gnu" 1767 | version = "0.4.0" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1770 | 1771 | [[package]] 1772 | name = "winapi-x86_64-pc-windows-gnu" 1773 | version = "0.4.0" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1776 | 1777 | [[package]] 1778 | name = "windows-sys" 1779 | version = "0.52.0" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1782 | dependencies = [ 1783 | "windows-targets", 1784 | ] 1785 | 1786 | [[package]] 1787 | name = "windows-sys" 1788 | version = "0.59.0" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1791 | dependencies = [ 1792 | "windows-targets", 1793 | ] 1794 | 1795 | [[package]] 1796 | name = "windows-targets" 1797 | version = "0.52.6" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1800 | dependencies = [ 1801 | "windows_aarch64_gnullvm", 1802 | "windows_aarch64_msvc", 1803 | "windows_i686_gnu", 1804 | "windows_i686_gnullvm", 1805 | "windows_i686_msvc", 1806 | "windows_x86_64_gnu", 1807 | "windows_x86_64_gnullvm", 1808 | "windows_x86_64_msvc", 1809 | ] 1810 | 1811 | [[package]] 1812 | name = "windows_aarch64_gnullvm" 1813 | version = "0.52.6" 1814 | source = "registry+https://github.com/rust-lang/crates.io-index" 1815 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1816 | 1817 | [[package]] 1818 | name = "windows_aarch64_msvc" 1819 | version = "0.52.6" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1822 | 1823 | [[package]] 1824 | name = "windows_i686_gnu" 1825 | version = "0.52.6" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1828 | 1829 | [[package]] 1830 | name = "windows_i686_gnullvm" 1831 | version = "0.52.6" 1832 | source = "registry+https://github.com/rust-lang/crates.io-index" 1833 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1834 | 1835 | [[package]] 1836 | name = "windows_i686_msvc" 1837 | version = "0.52.6" 1838 | source = "registry+https://github.com/rust-lang/crates.io-index" 1839 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1840 | 1841 | [[package]] 1842 | name = "windows_x86_64_gnu" 1843 | version = "0.52.6" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1846 | 1847 | [[package]] 1848 | name = "windows_x86_64_gnullvm" 1849 | version = "0.52.6" 1850 | source = "registry+https://github.com/rust-lang/crates.io-index" 1851 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1852 | 1853 | [[package]] 1854 | name = "windows_x86_64_msvc" 1855 | version = "0.52.6" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1858 | 1859 | [[package]] 1860 | name = "zeroconf" 1861 | version = "0.10.5" 1862 | source = "git+https://github.com/zeyugao/zeroconf-rs#860b030064308d4318e2c6936886674d955c6472" 1863 | dependencies = [ 1864 | "avahi-sys", 1865 | "bonjour-sys", 1866 | "derive-getters", 1867 | "derive-new", 1868 | "derive_builder", 1869 | "libc", 1870 | "log", 1871 | "serde", 1872 | "zeroconf-macros", 1873 | ] 1874 | 1875 | [[package]] 1876 | name = "zeroconf-macros" 1877 | version = "0.1.2" 1878 | source = "git+https://github.com/zeyugao/zeroconf-rs#860b030064308d4318e2c6936886674d955c6472" 1879 | dependencies = [ 1880 | "quote", 1881 | "syn 1.0.109", 1882 | ] 1883 | 1884 | [[package]] 1885 | name = "zeroize" 1886 | version = "1.8.1" 1887 | source = "registry+https://github.com/rust-lang/crates.io-index" 1888 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1889 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netmuxd" 3 | version = "0.2.1" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | default-run = "netmuxd" 9 | 10 | [[bin]] 11 | name = "passthrough" 12 | path = "src/passthrough.rs" 13 | 14 | [[bin]] 15 | name = "add_device" 16 | path = "src/add_device.rs" 17 | 18 | [dependencies] 19 | tokio = { version = "1.17.0", features = ["full"] } 20 | futures-util = { version = "0.3.21" } 21 | 22 | zeroconf = { git = "https://github.com/zeyugao/zeroconf-rs", optional = true } 23 | mdns = "3.0.0" 24 | 25 | idevice = { version = "0.1.29", features = ["usbmuxd", "heartbeat"] } 26 | plist = "1.7" 27 | 28 | log = { version = "0.4.16" } 29 | env_logger = { version = "0.11" } 30 | colored = { version = "2.0.0" } 31 | uuid = { version = "1.11", features = ["v4"] } 32 | 33 | rusb = { version = "0.9.1", optional = true } 34 | libusb1-sys = { version = "0.7", optional = true } 35 | 36 | [features] 37 | usb = ["libusb1-sys", "rusb"] 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic-20220427 as builder 2 | 3 | WORKDIR /work 4 | 5 | RUN apt-get update \ 6 | && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 7 | build-essential \ 8 | pkg-config \ 9 | checkinstall \ 10 | git \ 11 | autoconf \ 12 | automake \ 13 | libtool-bin \ 14 | libavahi-glib-dev libavahi-client-dev \ 15 | libusb-1.0-0-dev \ 16 | libssl-dev \ 17 | udev \ 18 | libplist++-dev libtool autoconf automake \ 19 | python3 python3-dev \ 20 | curl usbmuxd \ 21 | wget lsb-release wget software-properties-common \ 22 | clang-10 23 | 24 | RUN for i in /etc/ssl/certs/*.pem; do HASH=$(openssl x509 -hash -noout -in $i); ln -s $(basename $i) /etc/ssl/certs/$HASH.0 || true; done 25 | 26 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 27 | 28 | ENV CARGO_NET_GIT_FETCH_WITH_CLI=true 29 | 30 | RUN . "$HOME/.cargo/env" && cargo install cargo-chef 31 | 32 | RUN git clone https://github.com/zeyugao/zeroconf-rs.git \ 33 | && git clone https://github.com/jkcoxson/mdns.git 34 | 35 | RUN cd zeroconf-rs && git checkout 860b030064308d4318e2c6936886674d955c6472 && cd .. \ 36 | && cd mdns && git checkout 961ab21b5e01143dc3a7f0ba5f654285634e5569 && cd .. 37 | 38 | RUN mkdir netmuxd 39 | COPY Cargo.toml netmuxd 40 | COPY Cargo.lock netmuxd 41 | RUN . "$HOME/.cargo/env" \ 42 | && cd netmuxd \ 43 | && cargo chef prepare --recipe-path recipe.json \ 44 | && cargo chef cook --release --recipe-path recipe.json \ 45 | && cargo chef cook --release --recipe-path recipe.json --features "zeroconf" 46 | 47 | COPY . netmuxd 48 | 49 | RUN mkdir -p /output/ \ 50 | && cd netmuxd \ 51 | && . "$HOME/.cargo/env" \ 52 | && cargo build --release --features "zeroconf" --bin netmuxd \ 53 | && cp target/release/netmuxd /output/netmuxd-zeroconf \ 54 | && cargo build --release \ 55 | && cp target/release/netmuxd /output/netmuxd-mdns 56 | 57 | FROM ubuntu:20.04 58 | RUN apt-get update \ 59 | && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 60 | libavahi-client-dev 61 | 62 | COPY --from=builder /output/ /usr/local/bin/ 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | ␌ 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | ␌ 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | ␌ 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | ␌ 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | ␌ 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | ␌ 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | ␌ 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | ␌ 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | ␌ 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netmuxd 2 | 3 | A replacement/addition to usbmuxd which is a reimplementation of Apple's usbmuxd on MacOS 4 | 5 | # Building 6 | 7 | Run ``cargo build --release`` to generate binaries. They will be generated at ``target/release/netmuxd`` 8 | 9 | # Usage 10 | You need to pair your device beforehand using another muxer like [usbmuxd](https://github.com/libimobiledevice/usbmuxd). 11 | For example, start usbmuxd, plug in your device and enter the passcode that pops up, stop usbmuxd, start netmuxd. 12 | 13 | Run with root, options can be listed with ``--help`` 14 | 15 | # Extension Mode 16 | To use this project in extension with another muxer like usbmuxd, you can pass ``--disable-unix`` and ``--host 127.0.0.1``. 17 | Then before you run a program that uses a muxer set the environment variable ``USBMUXD_SOCKET_ADDRESS=127.0.0.1:27015``. 18 | 19 | ## AltStore 20 | A common usecase for netmuxd is in use with [AltStore-Linux](https://github.com/NyaMisty/AltStore-Linux). 21 | The best way to set this up for that use case is as follows: 22 | 1. Install usbmuxd for your distribution 23 | 2. Download netmuxd from the releases and place it somewhere permanent 24 | 3. Install ``screen`` and open run a new screen like so ``screen -S netmuxd`` 25 | 4. Run netmuxd like ``./netmuxd --disable-unix --host 127.0.0.1``, then press control a+d to escape the screen 26 | 5. Start a new screen for AltServer like ``screen -S altserver`` 27 | 6. Set the environment variable like ``export USBMUXD_SOCKET_ADDRESS=127.0.0.1:27015`` 28 | 7. Run AltServer ``./AltServer-x86_64`` 29 | 30 | ## License 31 | 32 | This code is licensed under the LGPL 2.1 license. You may use netmuxd's 33 | code how you will, but binaries must be distributed under and with that license. 34 | -------------------------------------------------------------------------------- /src/add_device.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | // Stand-alone binary to add devices to netmuxd 3 | 4 | use std::io::{Read, Write}; 5 | 6 | mod raw_packet; 7 | 8 | const SERVICE_NAME: &str = "apple-mobdev2"; 9 | const SERVICE_PROTOCOL: &str = "tcp"; 10 | 11 | trait ReadWrite: Read + Write + std::fmt::Debug {} 12 | impl ReadWrite for T {} 13 | 14 | fn main() { 15 | // Read the command line arguments 16 | let args = std::env::args().collect::>(); 17 | if args.len() < 3 { 18 | println!("Usage: add_device "); 19 | return; 20 | } 21 | let udid = &args[1]; 22 | let ip = &args[2]; 23 | let mut request = plist::Dictionary::new(); 24 | request.insert("MessageType".into(), "AddDevice".into()); 25 | request.insert("ConnectionType".into(), "Network".into()); 26 | request.insert( 27 | "ServiceName".into(), 28 | format!("_{}._{}.local", SERVICE_NAME, SERVICE_PROTOCOL).into(), 29 | ); 30 | request.insert("IPAddress".into(), ip.to_string().into()); 31 | request.insert("DeviceID".into(), udid.as_str().into()); 32 | 33 | let request = raw_packet::RawPacket::new(request, 69, 69, 69); 34 | let request: Vec = request.into(); 35 | 36 | // Connect to the socket 37 | let socket_address = 38 | std::env::var("USBMUXD_SOCKET_ADDRESS").unwrap_or("/var/run/usbmuxd".to_string()); 39 | 40 | let mut stream: Box = if socket_address.starts_with('/') { 41 | Box::new( 42 | std::os::unix::net::UnixStream::connect(socket_address) 43 | .expect("Unable to connect to unix socket"), 44 | ) 45 | } else { 46 | Box::new( 47 | std::net::TcpStream::connect(socket_address).expect("Unable to connect to TCP socket"), 48 | ) 49 | }; 50 | stream.write_all(&request).unwrap(); 51 | 52 | let mut buf = Vec::new(); 53 | let size = stream.read_to_end(&mut buf).unwrap(); 54 | 55 | let buffer = &mut buf[0..size].to_vec(); 56 | if size == 16 { 57 | let packet_size = &buffer[0..4]; 58 | let packet_size = u32::from_le_bytes(packet_size.try_into().unwrap()); 59 | // Pull the rest of the packet 60 | let mut packet = vec![0; packet_size as usize]; 61 | let _ = stream.read(&mut packet).unwrap(); 62 | // Append the packet to the buffer 63 | buffer.append(&mut packet); 64 | } 65 | 66 | let parsed: raw_packet::RawPacket = buffer.try_into().unwrap(); 67 | match parsed.plist.get("Result") { 68 | Some(plist::Value::Integer(r)) => { 69 | if r.as_unsigned().unwrap() == 1 { 70 | println!("Success"); 71 | } else { 72 | println!("Failure"); 73 | } 74 | } 75 | _ => { 76 | println!("Failure"); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/devices.rs: -------------------------------------------------------------------------------- 1 | // jkcoxson 2 | 3 | use std::{collections::HashMap, io::Read, net::IpAddr, path::PathBuf}; 4 | 5 | use log::{debug, info, trace, warn}; 6 | use tokio::{io::AsyncReadExt, sync::oneshot::Sender}; 7 | 8 | pub struct SharedDevices { 9 | pub devices: HashMap, 10 | pub last_index: u64, 11 | pub last_interface_index: u64, 12 | plist_storage: String, 13 | known_mac_addresses: HashMap, 14 | paired_udids: Vec, 15 | } 16 | 17 | pub struct MuxerDevice { 18 | // Universal types 19 | pub connection_type: String, 20 | pub device_id: u64, 21 | pub interface_index: u64, 22 | pub serial_number: String, 23 | 24 | // Network types 25 | pub network_address: Option, 26 | pub heartbeat_handle: Option>, 27 | pub service_name: Option, 28 | 29 | // USB types 30 | pub connection_speed: Option, 31 | pub location_id: Option, 32 | pub product_id: Option, 33 | } 34 | 35 | impl SharedDevices { 36 | pub async fn new(plist_storage: Option) -> Self { 37 | let plist_storage = if let Some(plist_storage) = plist_storage { 38 | info!("Plist storage specified, ensure the environment is aware"); 39 | plist_storage 40 | } else { 41 | info!("Using system default plist storage"); 42 | match std::env::consts::OS { 43 | "macos" => "/var/db/lockdown", 44 | "linux" => "/var/lib/lockdown", 45 | "windows" => "C:/ProgramData/Apple/Lockdown", 46 | _ => panic!("Unsupported OS, specify a path"), 47 | } 48 | .to_string() 49 | }; 50 | 51 | // Make sure the directory exists 52 | if tokio::fs::read_dir(&plist_storage).await.is_err() { 53 | // Create the directory 54 | tokio::fs::create_dir(&plist_storage) 55 | .await 56 | .expect("Unable to create plist storage folder"); 57 | info!("Created plist storage!"); 58 | } else { 59 | trace!("Plist storage exists"); 60 | } 61 | 62 | Self { 63 | devices: HashMap::new(), 64 | last_index: 0, 65 | last_interface_index: 0, 66 | plist_storage, 67 | known_mac_addresses: HashMap::new(), 68 | paired_udids: Vec::new(), 69 | } 70 | } 71 | pub async fn add_network_device( 72 | &mut self, 73 | udid: String, 74 | network_address: IpAddr, 75 | service_name: String, 76 | connection_type: String, 77 | heartbeat_handle: Option>, 78 | ) -> Result<(), Box> { 79 | if self.devices.contains_key(&udid) { 80 | trace!("Device has already been added, skipping"); 81 | return Ok(()); 82 | } 83 | self.last_index += 1; 84 | self.last_interface_index += 1; 85 | 86 | let dev = MuxerDevice { 87 | connection_type, 88 | device_id: self.last_index, 89 | service_name: Some(service_name), 90 | interface_index: self.last_interface_index, 91 | network_address: Some(network_address), 92 | serial_number: udid.clone(), 93 | heartbeat_handle, 94 | connection_speed: None, 95 | location_id: None, 96 | product_id: None, 97 | }; 98 | info!("Adding device: {:?}", udid); 99 | self.devices.insert(udid, dev); 100 | Ok(()) 101 | } 102 | 103 | pub fn get_device_by_id(&self, id: u64) -> Option<&MuxerDevice> { 104 | self.devices.values().find(|x| x.device_id == id) 105 | } 106 | 107 | pub fn remove_device(&mut self, udid: &String) { 108 | if !self.devices.contains_key(udid) { 109 | warn!("Device isn't in the muxer, skipping"); 110 | return; 111 | } 112 | info!("Removing device: {:?}", udid); 113 | match self.devices.remove(udid) { 114 | Some(d) => match d.heartbeat_handle { 115 | Some(h) => { 116 | if let Err(e) = h.send(()) { 117 | warn!("Couldn't send kill signal to heartbeat thread: {e:?}"); 118 | } 119 | } 120 | None => { 121 | warn!("Heartbeat handle option has none, can't kill"); 122 | } 123 | }, 124 | None => { 125 | warn!("No heartbeat handle found for device"); 126 | } 127 | }; 128 | } 129 | pub async fn get_pairing_record(&self, udid: &String) -> Result, std::io::Error> { 130 | let path = PathBuf::from(self.plist_storage.clone()).join(format!("{}.plist", udid)); 131 | info!("Attempting to read pairing file: {path:?}"); 132 | if !path.exists() { 133 | warn!("No pairing record found for device: {:?}", udid); 134 | return Err(std::io::Error::new( 135 | std::io::ErrorKind::NotFound, 136 | "No pairing record for the device", 137 | )); 138 | } 139 | // Read the file 140 | info!("Reading pairing record for device: {:?}", udid); 141 | let mut file = tokio::fs::File::open(path).await?; 142 | let mut contents = Vec::new(); 143 | file.read_to_end(&mut contents).await?; 144 | Ok(contents) 145 | } 146 | pub async fn get_buid(&self) -> Result { 147 | let path = PathBuf::from(self.plist_storage.clone()).join("SystemConfiguration.plist"); 148 | if !path.exists() { 149 | info!("No SystemConfiguration.plist found, generating BUID"); 150 | warn!("The SystemConfiguration.plist generated by netmuxd is incomplete for other muxers. Delete if using usbmuxd or another muxer."); 151 | let mut new_plist = plist::Dictionary::new(); 152 | let new_udid = uuid::Uuid::new_v4(); 153 | new_plist.insert("SystemBUID".into(), new_udid.to_string().into()); 154 | let f = std::fs::File::create_new(&path)?; 155 | plist::to_writer_xml(f, &new_plist).unwrap(); 156 | } 157 | // Read the file to a string 158 | debug!("Reading SystemConfiguration.plist"); 159 | let mut file = std::fs::File::open(path)?; 160 | let mut contents = Vec::new(); 161 | file.read_to_end(&mut contents)?; 162 | 163 | // Parse the string into a plist 164 | debug!("Parsing SystemConfiguration.plist"); 165 | let plist = match plist::from_bytes::(&contents) { 166 | Ok(p) => p, 167 | Err(e) => { 168 | log::error!("Failed to parse plist: {e:?}"); 169 | return Err(std::io::Error::new( 170 | std::io::ErrorKind::InvalidInput, 171 | "unable to parse plist", 172 | )); 173 | } 174 | }; 175 | match plist.get("SystemBUID") { 176 | Some(plist::Value::String(b)) => Ok(b.to_owned()), 177 | _ => Err(std::io::Error::new( 178 | std::io::ErrorKind::InvalidInput, 179 | "Plist did not contain SystemBUID", 180 | )), 181 | } 182 | } 183 | 184 | pub async fn update_cache(&mut self) { 185 | // Iterate through all files in the plist storage, loading them into memory 186 | trace!("Updating plist cache"); 187 | let path = PathBuf::from(self.plist_storage.clone()); 188 | for entry in std::fs::read_dir(path).expect("Plist storage is unreadable!!") { 189 | let entry = match entry { 190 | Ok(e) => e, 191 | Err(e) => { 192 | warn!("Unable to read entry in plist storage: {e:?}"); 193 | continue; 194 | } 195 | }; 196 | let path = entry.path(); 197 | trace!("Attempting to read {:?}", path); 198 | if path.is_file() { 199 | let mut file = match tokio::fs::File::open(&path).await { 200 | Ok(f) => f, 201 | Err(e) => { 202 | warn!("Unable to read plist storage entry to memory: {e:?}"); 203 | continue; 204 | } 205 | }; 206 | let mut contents = Vec::new(); 207 | let plist: plist::Dictionary = match file.read_to_end(&mut contents).await { 208 | Ok(_) => match plist::from_bytes(&contents) { 209 | Ok(p) => p, 210 | Err(e) => { 211 | warn!("Unable to parse entry file to plist: {e:?}"); 212 | continue; 213 | } 214 | }, 215 | Err(e) => { 216 | trace!("Could not read plist to memory: {e:?}"); 217 | continue; 218 | } 219 | }; 220 | let mac_addr = match plist.get("WiFiMACAddress") { 221 | Some(plist::Value::String(m)) => m, 222 | _ => { 223 | warn!("Could not get string value of WiFiMACAddress"); 224 | continue; 225 | } 226 | }; 227 | let udid = match plist.get("UDID") { 228 | Some(plist::Value::String(u)) => Some(u), 229 | _ => { 230 | warn!("Plist did not contain UDID"); 231 | None 232 | } 233 | }; 234 | 235 | let udid = if let Some(udid) = udid { 236 | udid.to_owned() 237 | } else { 238 | // Use the file name as the UDID 239 | // This won't be reached because the SystemConfiguration doesn't have a WiFiMACAddress 240 | // This is just used as a last resort, but might not be correct so we'll pass a warning 241 | warn!("Using the file name as the UDID"); 242 | match path.file_name() { 243 | Some(f) => match f.to_str() { 244 | Some(f) => f.split('.').collect::>()[0].to_string(), 245 | None => { 246 | warn!("Failed to get entry file name string"); 247 | continue; 248 | } 249 | }, 250 | None => { 251 | trace!("File had no name"); 252 | continue; 253 | } 254 | } 255 | }; 256 | 257 | let stem = match path.file_stem() { 258 | Some(s) => s, 259 | None => { 260 | warn!("Failed to get file stem for entry"); 261 | continue; 262 | } 263 | }; 264 | 265 | self.known_mac_addresses 266 | .insert(mac_addr.to_owned(), stem.to_string_lossy().to_string()); 267 | if self.paired_udids.contains(&udid) { 268 | trace!("Cache already contained this UDID"); 269 | continue; 270 | } 271 | trace!("Adding {} to plist cache", udid); 272 | self.paired_udids.push(udid); 273 | } 274 | } 275 | } 276 | 277 | pub async fn get_udid_from_mac(&mut self, mac: String) -> Result { 278 | info!("Getting UDID for MAC: {:?}", mac); 279 | if let Some(udid) = self.known_mac_addresses.get(&mac) { 280 | info!("Found UDID: {:?}", udid); 281 | return Ok(udid.to_string()); 282 | } else { 283 | trace!("No UDID found for {:?} in cache, re-caching...", mac); 284 | } 285 | self.update_cache().await; 286 | 287 | if let Some(udid) = self.known_mac_addresses.get(&mac) { 288 | info!("Found UDID: {:?}", udid); 289 | return Ok(udid.to_string()); 290 | } 291 | trace!("No UDID found after a re-cache"); 292 | Err(()) 293 | } 294 | } 295 | 296 | impl From<&MuxerDevice> for plist::Dictionary { 297 | fn from(device: &MuxerDevice) -> Self { 298 | let mut p = plist::Dictionary::new(); 299 | p.insert( 300 | "ConnectionType".into(), 301 | device.connection_type.clone().into(), 302 | ); 303 | p.insert("DeviceID".into(), device.device_id.into()); 304 | if device.connection_type == "Network" { 305 | p.insert( 306 | "EscapedFullServiceName".into(), 307 | device 308 | .service_name 309 | .clone() 310 | .expect("Network device, but no service name") 311 | .into(), 312 | ); 313 | } 314 | p.insert("InterfaceIndex".into(), device.interface_index.into()); 315 | 316 | // Reassemble the network address back into bytes 317 | if device.connection_type == "Network" { 318 | let mut data = [0u8; 152]; 319 | match device 320 | .network_address 321 | .expect("Network device, but no address") 322 | { 323 | IpAddr::V4(ip_addr) => { 324 | data[0] = 0x02; 325 | data[1] = 0x00; 326 | data[2] = 0x00; 327 | data[3] = 0x00; 328 | let mut i = 4; 329 | for byte in ip_addr.octets() { 330 | data[i] = byte; 331 | i += 1; 332 | } 333 | } 334 | IpAddr::V6(ip_addr) => { 335 | data[0] = 0x1E; 336 | data[1] = 0x00; 337 | data[2] = 0x00; 338 | data[3] = 0x00; 339 | data[4] = 0x00; 340 | data[5] = 0x00; 341 | data[6] = 0x00; 342 | data[7] = 0x00; 343 | let mut i = 8; 344 | for byte in ip_addr.octets() { 345 | data[i] = byte; 346 | i += 1; 347 | } 348 | } 349 | } 350 | // Start from the back and fill with zeros 351 | let mut i = data.len() - 2; 352 | while i > 0 { 353 | if data[i] != 0 { 354 | break; 355 | } 356 | data[i] = 0; 357 | i -= 1; 358 | } 359 | p.insert("NetworkAddress".into(), plist::Value::Data(data.to_vec())); 360 | } 361 | 362 | p.insert("SerialNumber".into(), device.serial_number.clone().into()); 363 | p 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/heartbeat.rs: -------------------------------------------------------------------------------- 1 | // jkcoxson 2 | 3 | use idevice::{heartbeat::HeartbeatClient, lockdown::LockdownClient, Idevice}; 4 | use log::{info, warn}; 5 | use std::{ 6 | net::{IpAddr, SocketAddr}, 7 | sync::Arc, 8 | }; 9 | use tokio::sync::oneshot::{error::TryRecvError, Sender}; 10 | 11 | use crate::devices::SharedDevices; 12 | 13 | pub async fn heartbeat( 14 | ip_addr: IpAddr, 15 | udid: String, 16 | pairing_file: idevice::pairing_file::PairingFile, 17 | data: Arc>, 18 | ) -> Result, Box> { 19 | let (tx, mut rx) = tokio::sync::oneshot::channel(); 20 | 21 | let socket = SocketAddr::new(ip_addr, LockdownClient::LOCKDOWND_PORT); 22 | 23 | let socket = tokio::net::TcpStream::connect(socket).await?; 24 | let socket = Box::new(socket); 25 | let idevice = Idevice::new(socket, "netmuxd"); 26 | 27 | let mut lockdown_client = LockdownClient { idevice }; 28 | lockdown_client.start_session(&pairing_file).await?; 29 | 30 | let (port, _) = lockdown_client 31 | .start_service("com.apple.mobile.heartbeat") 32 | .await?; 33 | 34 | let socket = SocketAddr::new(ip_addr, port); 35 | let socket = tokio::net::TcpStream::connect(socket).await?; 36 | let socket = Box::new(socket); 37 | let mut idevice = Idevice::new(socket, "heartbeat_client"); 38 | 39 | idevice.start_session(&pairing_file).await?; 40 | 41 | tokio::spawn(async move { 42 | let mut interval = 10; 43 | let mut heartbeat_client = HeartbeatClient { idevice }; 44 | loop { 45 | match heartbeat_client.get_marco(interval + 5).await { 46 | Ok(i) => { 47 | interval = i; 48 | } 49 | Err(e) => { 50 | info!("Heartbeat recv failed: {:?}", e); 51 | tokio::spawn(async move { 52 | remove_from_data(data, udid).await; 53 | }); 54 | break; 55 | } 56 | } 57 | match rx.try_recv() { 58 | Ok(_) => { 59 | info!("Heartbeat instructed to die") 60 | } 61 | Err(TryRecvError::Closed) => { 62 | warn!("Heartbeat killer closed"); 63 | break; 64 | } 65 | _ => {} 66 | } 67 | if let Err(e) = heartbeat_client.send_polo().await { 68 | info!("Heartbeat send failed: {:?}", e); 69 | tokio::spawn(async move { 70 | remove_from_data(data, udid).await; 71 | }); 72 | return; 73 | } 74 | } 75 | }); 76 | Ok(tx) 77 | } 78 | 79 | pub async fn remove_from_data(data: Arc>, udid: String) { 80 | println!("Removing {}", udid); 81 | let mut data = data.lock().await; 82 | data.remove_device(&udid); 83 | } 84 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // jkcoxson 2 | 3 | use std::sync::Arc; 4 | #[cfg(unix)] 5 | use std::{fs, os::unix::prelude::PermissionsExt}; 6 | 7 | use crate::raw_packet::RawPacket; 8 | use devices::SharedDevices; 9 | use idevice::pairing_file::PairingFile; 10 | use log::{debug, error, info, trace, warn}; 11 | use tokio::{ 12 | io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, 13 | sync::Mutex, 14 | }; 15 | 16 | mod devices; 17 | mod heartbeat; 18 | mod mdns; 19 | mod raw_packet; 20 | 21 | #[tokio::main] 22 | async fn main() { 23 | println!("Starting netmuxd"); 24 | 25 | env_logger::init(); 26 | info!("Logger initialized"); 27 | 28 | let mut port = 27015; 29 | #[cfg(unix)] 30 | let mut host = None; 31 | #[cfg(windows)] 32 | let mut host = Some("localhost".to_string()); 33 | let mut plist_storage = None; 34 | let mut use_heartbeat = true; 35 | 36 | #[cfg(unix)] 37 | let mut use_unix = true; 38 | 39 | let mut use_mdns = true; 40 | 41 | // Loop through args 42 | let mut i = 0; 43 | while i < std::env::args().len() { 44 | match std::env::args().nth(i).unwrap().as_str() { 45 | "-p" | "--port" => { 46 | port = std::env::args() 47 | .nth(i + 1) 48 | .expect("port flag passed without number") 49 | .parse::() 50 | .expect("port isn't a number"); 51 | i += 2; 52 | } 53 | "--host" => { 54 | host = Some( 55 | std::env::args() 56 | .nth(i + 1) 57 | .expect("host flag passed without host") 58 | .to_string(), 59 | ); 60 | i += 2; 61 | } 62 | "--plist-storage" => { 63 | plist_storage = Some( 64 | std::env::args() 65 | .nth(i + 1) 66 | .expect("flag passed without value"), 67 | ); 68 | i += 1; 69 | } 70 | #[cfg(unix)] 71 | "--disable-unix" => { 72 | use_unix = false; 73 | i += 1; 74 | } 75 | "--disable-mdns" => { 76 | use_mdns = false; 77 | i += 1; 78 | } 79 | "--disable-heartbeat" => { 80 | use_heartbeat = false; 81 | i += 1; 82 | } 83 | "-h" | "--help" => { 84 | println!("netmuxd - a network multiplexer"); 85 | println!("Usage:"); 86 | println!(" netmuxd [options]"); 87 | println!("Options:"); 88 | println!(" -p, --port "); 89 | println!(" --host "); 90 | println!(" --plist-storage "); 91 | println!(" --disable-heartbeat"); 92 | #[cfg(unix)] 93 | println!(" --disable-unix"); 94 | println!(" --disable-mdns"); 95 | #[cfg(feature = "usb")] 96 | println!(" --enable-usb (unusable for now)"); 97 | println!(" -h, --help"); 98 | println!(" --about"); 99 | println!("\n\nSet RUST_LOG to info, debug, warn, error, or trace to see more logs. Default is error."); 100 | std::process::exit(0); 101 | } 102 | "--about" => { 103 | println!("netmuxd - a network multiplexer"); 104 | println!("Copyright (c) 2020 Jackson Coxson"); 105 | println!("Licensed under the MIT License"); 106 | } 107 | _ => { 108 | i += 1; 109 | } 110 | } 111 | } 112 | info!("Collected arguments, proceeding"); 113 | 114 | let data = Arc::new(Mutex::new(devices::SharedDevices::new(plist_storage).await)); 115 | info!("Created new central data"); 116 | let data_clone = data.clone(); 117 | 118 | if let Some(host) = host.clone() { 119 | let tcp_data = data.clone(); 120 | tokio::spawn(async move { 121 | let data = tcp_data; 122 | // Create TcpListener 123 | let listener = tokio::net::TcpListener::bind(format!("{}:{}", host, port)) 124 | .await 125 | .expect("Unable to bind to TCP listener"); 126 | 127 | println!("Listening on {}:{}", host, port); 128 | #[cfg(unix)] 129 | println!("WARNING: Running in host mode will not work unless you are running a daemon in unix mode as well"); 130 | loop { 131 | let (socket, _) = match listener.accept().await { 132 | Ok(s) => s, 133 | Err(_) => { 134 | warn!("Error accepting connection"); 135 | continue; 136 | } 137 | }; 138 | 139 | handle_stream(socket, data.clone(), use_heartbeat).await; 140 | } 141 | }); 142 | } 143 | 144 | #[cfg(unix)] 145 | if use_unix { 146 | tokio::spawn(async move { 147 | // Delete old Unix socket 148 | info!("Deleting old Unix socket"); 149 | std::fs::remove_file("/var/run/usbmuxd").unwrap_or_default(); 150 | // Create UnixListener 151 | info!("Binding to new Unix socket"); 152 | let listener = tokio::net::UnixListener::bind("/var/run/usbmuxd") 153 | .expect("Unable to bind to unix socket"); 154 | // Change the permission of the socket 155 | info!("Changing permissions of socket"); 156 | fs::set_permissions("/var/run/usbmuxd", fs::Permissions::from_mode(0o666)) 157 | .expect("Unable to set socket file permissions"); 158 | 159 | println!("Listening on /var/run/usbmuxd"); 160 | 161 | loop { 162 | let (socket, _) = match listener.accept().await { 163 | Ok(s) => s, 164 | Err(_) => { 165 | warn!("Error accepting connection"); 166 | continue; 167 | } 168 | }; 169 | 170 | handle_stream(socket, data.clone(), use_heartbeat).await; 171 | } 172 | }); 173 | } 174 | 175 | if use_mdns { 176 | let local = tokio::task::LocalSet::new(); 177 | local.spawn_local(async move { 178 | mdns::discover(data_clone, use_heartbeat).await; 179 | error!("mDNS discovery stopped, how the heck did you break this"); 180 | }); 181 | local.await; 182 | error!("mDNS discovery stopped"); 183 | std::process::exit(1); 184 | } else { 185 | loop { 186 | tokio::time::sleep(std::time::Duration::from_secs(10)).await; 187 | } 188 | } 189 | } 190 | 191 | enum Directions { 192 | None, 193 | Listen, 194 | } 195 | 196 | async fn handle_stream( 197 | mut socket: impl AsyncRead + AsyncWrite + Unpin + Send + 'static, 198 | data: Arc>, 199 | use_heartbeat: bool, 200 | ) { 201 | tokio::spawn(async move { 202 | let mut current_directions = Directions::None; 203 | 204 | loop { 205 | // Wait for a message from the client 206 | let mut buf = [0; 1024]; 207 | trace!("Waiting for data from client..."); 208 | let size = match socket.read(&mut buf).await { 209 | Ok(s) => s, 210 | Err(_) => { 211 | return; 212 | } 213 | }; 214 | trace!("Recv'd {size} bytes"); 215 | if size == 0 { 216 | debug!("Received size is zero, closing connection"); 217 | return; 218 | } 219 | 220 | let buffer = &mut buf[0..size].to_vec(); 221 | if size == 16 { 222 | info!("Only read the header, pulling more bytes"); 223 | // Get the number of bytes to pull 224 | let packet_size = &buffer[0..4]; 225 | let packet_size = match packet_size.try_into() { 226 | Ok(p) => p, 227 | Err(e) => { 228 | warn!("Failed to read packet size: {e:?}"); 229 | return; 230 | } 231 | }; 232 | let packet_size = u32::from_le_bytes(packet_size); 233 | info!("Packet size: {}", packet_size); 234 | // Pull the rest of the packet 235 | let mut packet = vec![0; packet_size as usize]; 236 | let size = match socket.read(&mut packet).await { 237 | Ok(s) => s, 238 | Err(_) => { 239 | return; 240 | } 241 | }; 242 | if size == 0 { 243 | info!("Size was zero"); 244 | return; 245 | } 246 | // Append the packet to the buffer 247 | buffer.append(&mut packet); 248 | } 249 | 250 | let parsed: raw_packet::RawPacket = match buffer.try_into() { 251 | Ok(p) => p, 252 | Err(_) => { 253 | warn!("Could not parse packet"); 254 | return; 255 | } 256 | }; 257 | trace!("Recv'd plist: {parsed:#?}"); 258 | 259 | match current_directions { 260 | Directions::None => { 261 | // Handle the packet 262 | let packet_type = match parsed.plist.get("MessageType") { 263 | Some(plist::Value::String(p)) => p, 264 | _ => { 265 | warn!("Packet didn't contain MessageType"); 266 | return; 267 | } 268 | }; 269 | 270 | trace!("usbmuxd client sent {packet_type}"); 271 | 272 | match packet_type.as_str() { 273 | ////////////////////////////// 274 | // netmuxd specific packets // 275 | ////////////////////////////// 276 | "AddDevice" => { 277 | let connection_type = match parsed.plist.get("ConnectionType") { 278 | Some(plist::Value::String(c)) => c, 279 | _ => { 280 | warn!("Packet didn't contain ConnectionType"); 281 | return; 282 | } 283 | }; 284 | let service_name = match parsed.plist.get("ServiceName") { 285 | Some(plist::Value::String(s)) => s, 286 | _ => { 287 | warn!("Packet didn't contain ServiceName"); 288 | return; 289 | } 290 | }; 291 | 292 | let ip_address = match parsed.plist.get("IPAddress") { 293 | Some(plist::Value::String(ip)) => ip, 294 | _ => { 295 | warn!("Packet didn't contain IPAddress"); 296 | return; 297 | } 298 | }; 299 | 300 | let ip_address = match ip_address.parse() { 301 | Ok(i) => i, 302 | Err(_) => { 303 | warn!("Bad IP requested: {ip_address}"); 304 | return; 305 | } 306 | }; 307 | 308 | let udid = match parsed.plist.get("DeviceID") { 309 | Some(plist::Value::String(u)) => u, 310 | _ => { 311 | warn!("Packet didn't contain DeviceID"); 312 | return; 313 | } 314 | }; 315 | 316 | let heartbeat_handle = if use_heartbeat { 317 | let pairing_file = 318 | match data.lock().await.get_pairing_record(udid).await { 319 | Ok(p) => match PairingFile::from_bytes(&p) { 320 | Ok(p) => p, 321 | Err(e) => { 322 | log::error!("Failed to parse pair record: {e:?}"); 323 | return; 324 | } 325 | }, 326 | Err(e) => { 327 | log::error!( 328 | "Failed to get pairing file for device: {e:?}" 329 | ); 330 | return; 331 | } 332 | }; 333 | let heartbeat_handle = match heartbeat::heartbeat( 334 | ip_address, 335 | udid.clone(), 336 | pairing_file, 337 | data.clone(), 338 | ) 339 | .await 340 | { 341 | Ok(h) => h, 342 | Err(e) => { 343 | warn!("Failed to start heartbeat: {e:?}"); 344 | return; 345 | } 346 | }; 347 | Some(heartbeat_handle) 348 | } else { 349 | None 350 | }; 351 | 352 | let mut central_data = data.lock().await; 353 | let res = match central_data 354 | .add_network_device( 355 | udid.to_owned(), 356 | ip_address, 357 | service_name.to_owned(), 358 | connection_type.to_owned(), 359 | heartbeat_handle, 360 | ) 361 | .await 362 | { 363 | Ok(_) => 1, 364 | Err(e) => { 365 | error!("Failed to add requested device to muxer: {e:?}"); 366 | 0 367 | } 368 | }; 369 | std::mem::drop(central_data); 370 | 371 | let mut p = plist::Dictionary::new(); 372 | p.insert("Result".into(), res.into()); 373 | let res: Vec = RawPacket::new(p, 1, 8, parsed.tag).into(); 374 | if let Err(e) = socket.write_all(&res).await { 375 | warn!("Failed to send back success message: {e:?}"); 376 | } 377 | 378 | // No more further communication for this packet 379 | return; 380 | } 381 | "RemoveDevice" => { 382 | let udid = match parsed.plist.get("DeviceID") { 383 | Some(plist::Value::String(u)) => u, 384 | _ => { 385 | warn!("Packet didn't contain DeviceID"); 386 | return; 387 | } 388 | }; 389 | 390 | let mut central_data = data.lock().await; 391 | central_data.remove_device(udid); 392 | return; 393 | } 394 | ////////////////////////////// 395 | // usbmuxd protocol packets // 396 | ////////////////////////////// 397 | "ListDevices" => { 398 | let data = data.lock().await; 399 | let mut device_list = Vec::new(); 400 | for i in &data.devices { 401 | let mut to_push = plist::Dictionary::new(); 402 | to_push.insert("DeviceID".into(), i.1.device_id.into()); 403 | to_push.insert("MessageType".into(), "Attached".into()); 404 | to_push.insert( 405 | "Properties".into(), 406 | plist::Value::Dictionary(i.1.into()), 407 | ); 408 | 409 | device_list.push(plist::Value::Dictionary(to_push)); 410 | } 411 | std::mem::drop(data); 412 | let mut upper = plist::Dictionary::new(); 413 | upper.insert("DeviceList".into(), plist::Value::Array(device_list)); 414 | let res = RawPacket::new(upper, 1, 8, parsed.tag); 415 | let res: Vec = res.into(); 416 | if let Err(e) = socket.write_all(&res).await { 417 | warn!("Failed to send response to client: {e:}"); 418 | return; 419 | } 420 | 421 | continue; 422 | } 423 | "Listen" => { 424 | // The full functionality of this is not implemented. We will just maintain the connection. 425 | current_directions = Directions::Listen; 426 | } 427 | "ReadPairRecord" => { 428 | let lock = data.lock().await; 429 | let pair_file = match lock 430 | .get_pairing_record(match parsed.plist.get("PairRecordID") { 431 | Some(plist::Value::String(p)) => p, 432 | _ => { 433 | warn!("Request did not contain PairRecordID"); 434 | return; 435 | } 436 | }) 437 | .await 438 | { 439 | Ok(pair_file) => pair_file, 440 | Err(_) => { 441 | // Unimplemented 442 | return; 443 | } 444 | }; 445 | std::mem::drop(lock); 446 | 447 | let mut p = plist::Dictionary::new(); 448 | p.insert("PairRecordData".into(), plist::Value::Data(pair_file)); 449 | 450 | let res = RawPacket::new(p, 1, 8, parsed.tag); 451 | let res: Vec = res.into(); 452 | if let Err(e) = socket.write_all(&res).await { 453 | warn!("Failed to send response to client: {e:?}"); 454 | return; 455 | } 456 | 457 | continue; 458 | } 459 | "ReadBUID" => { 460 | let lock = data.lock().await; 461 | let buid = match lock.get_buid().await { 462 | Ok(b) => b, 463 | Err(e) => { 464 | log::error!("Failed to get buid: {e:?}"); 465 | return; 466 | } 467 | }; 468 | std::mem::drop(lock); 469 | 470 | let mut p = plist::Dictionary::new(); 471 | p.insert("BUID".into(), buid.into()); 472 | 473 | let res = RawPacket::new(p, 1, 8, parsed.tag); 474 | let res: Vec = res.into(); 475 | if let Err(e) = socket.write_all(&res).await { 476 | warn!("Failed to send response to client: {e:?}"); 477 | return; 478 | } 479 | 480 | continue; 481 | } 482 | "Connect" => { 483 | let connection_port = match parsed.plist.get("PortNumber") { 484 | Some(plist::Value::Integer(p)) => match p.as_unsigned() { 485 | Some(p) => p, 486 | None => { 487 | warn!("PortNumber is not unsigned!"); 488 | return; 489 | } 490 | }, 491 | _ => { 492 | warn!("Packet didn't contain PortNumber"); 493 | return; 494 | } 495 | }; 496 | 497 | let device_id = match parsed.plist.get("DeviceID") { 498 | Some(plist::Value::Integer(d)) => match d.as_unsigned() { 499 | Some(d) => d, 500 | None => { 501 | warn!("DeviceID is not unsigned!"); 502 | return; 503 | } 504 | }, 505 | _ => { 506 | warn!("Packet didn't contain DeviceID"); 507 | return; 508 | } 509 | }; 510 | 511 | let connection_port = connection_port as u16; 512 | let connection_port = connection_port.to_be(); 513 | 514 | info!("Client is establishing connection to port {connection_port}"); 515 | let central_data = data.lock().await; 516 | if let Some(device) = central_data.get_device_by_id(device_id) { 517 | let network_address = device.network_address; 518 | let device_id = device.device_id; 519 | std::mem::drop(central_data); 520 | 521 | info!("Connecting to device {}", device_id); 522 | 523 | match network_address { 524 | Some(ip) => { 525 | match tokio::net::TcpStream::connect((ip, connection_port)) 526 | .await 527 | { 528 | Ok(mut stream) => { 529 | let mut p = plist::Dictionary::new(); 530 | p.insert("MessageType".into(), "Result".into()); 531 | p.insert("Number".into(), 0.into()); 532 | 533 | let res = RawPacket::new(p, 1, 8, parsed.tag); 534 | let res: Vec = res.into(); 535 | if let Err(e) = socket.write_all(&res).await { 536 | warn!( 537 | "Failed to send response to client: {e:?}" 538 | ); 539 | return; 540 | } 541 | 542 | if let Err(e) = tokio::io::copy_bidirectional( 543 | &mut stream, 544 | &mut socket, 545 | ) 546 | .await 547 | { 548 | info!("Bidirectional stream stopped: {e:?}"); 549 | } 550 | continue; 551 | } 552 | Err(e) => { 553 | error!("Unable to connect to device {device_id} port {connection_port}: {e:?}"); 554 | let mut p = plist::Dictionary::new(); 555 | p.insert("MessageType".into(), "Result".into()); 556 | p.insert("Number".into(), 1.into()); 557 | 558 | let res = RawPacket::new(p, 1, 8, parsed.tag); 559 | let res: Vec = res.into(); 560 | if let Err(e) = socket.write_all(&res).await { 561 | warn!( 562 | "Failed to send response to client: {e:?}" 563 | ); 564 | } 565 | 566 | continue; 567 | } 568 | } 569 | } 570 | None => { 571 | unimplemented!() 572 | } 573 | } 574 | } else { 575 | std::mem::drop(central_data); 576 | let mut p = plist::Dictionary::new(); 577 | p.insert("MessageType".into(), "Result".into()); 578 | p.insert("Number".into(), 1.into()); 579 | 580 | let res = RawPacket::new(p, 1, 8, parsed.tag); 581 | let res: Vec = res.into(); 582 | if let Err(e) = socket.write_all(&res).await { 583 | warn!("Failed to send response to client: {e:?}"); 584 | } 585 | 586 | continue; 587 | } 588 | } 589 | _ => { 590 | warn!("Unknown packet type"); 591 | } 592 | } 593 | } 594 | Directions::Listen => { 595 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 596 | } 597 | } 598 | } 599 | }); 600 | } 601 | -------------------------------------------------------------------------------- /src/mdns.rs: -------------------------------------------------------------------------------- 1 | // jkcoxson 2 | 3 | use crate::{devices::SharedDevices, heartbeat}; 4 | use idevice::pairing_file::PairingFile; 5 | use log::{info, warn}; 6 | use std::net::IpAddr; 7 | use std::sync::Arc; 8 | use tokio::sync::Mutex; 9 | 10 | #[cfg(not(feature = "zeroconf"))] 11 | use { 12 | futures_util::{pin_mut, stream::StreamExt}, 13 | mdns::{Record, RecordKind}, 14 | std::time::Duration, 15 | }; 16 | 17 | #[cfg(feature = "zeroconf")] 18 | use { 19 | zeroconf::prelude::*, 20 | zeroconf::{MdnsBrowser, ServiceType}, 21 | }; 22 | 23 | const SERVICE_NAME: &str = "apple-mobdev2"; 24 | const SERVICE_PROTOCOL: &str = "tcp"; 25 | 26 | #[cfg(feature = "zeroconf")] 27 | pub async fn discover(data: Arc>, use_heartbeat: bool) { 28 | let service_name = format!("_{}._{}.local", SERVICE_NAME, SERVICE_PROTOCOL); 29 | println!("Starting mDNS discovery for {} with zeroconf", service_name); 30 | 31 | let mut browser = MdnsBrowser::new( 32 | ServiceType::new(SERVICE_NAME, SERVICE_PROTOCOL).expect("Unable to start mDNS browse"), 33 | ); 34 | loop { 35 | let result = browser.browse_async().await; 36 | 37 | if let Ok(service) = result { 38 | info!("Service discovered: {:?}", service); 39 | let name = service.name(); 40 | if !name.contains("@") { 41 | continue; 42 | } 43 | let addr = match service.address() { 44 | addr if addr.contains(":") => IpAddr::V6(match addr.parse() { 45 | Ok(i) => i, 46 | Err(e) => { 47 | log::error!("Unable to parse IPv6 address: {e:?}"); 48 | continue; 49 | } 50 | }), 51 | addr => IpAddr::V4(match addr.parse() { 52 | Ok(i) => i, 53 | Err(e) => { 54 | log::error!("Unable to parse IPv4 address: {e:?}"); 55 | continue; 56 | } 57 | }), 58 | }; 59 | 60 | let mac_addr = name.split("@").collect::>()[0]; 61 | let mut lock = data.lock().await; 62 | if let Ok(udid) = lock.get_udid_from_mac(mac_addr.to_string()).await { 63 | if lock.devices.contains_key(&udid) { 64 | info!("Device has already been added to muxer, skipping"); 65 | continue; 66 | } 67 | println!("Adding device {}", udid); 68 | 69 | let heartbeat_handle = if use_heartbeat { 70 | let pairing_file = match data.lock().await.get_pairing_record(&udid).await { 71 | Ok(p) => match PairingFile::from_bytes(&p) { 72 | Ok(p) => p, 73 | Err(e) => { 74 | log::error!("Failed to parse pair record: {e:?}"); 75 | continue; 76 | } 77 | }, 78 | Err(e) => { 79 | log::error!("Failed to get pairing file for device: {e:?}"); 80 | continue; 81 | } 82 | }; 83 | let heartbeat_handle = 84 | match heartbeat::heartbeat(addr, udid.clone(), pairing_file, data.clone()) 85 | .await 86 | { 87 | Ok(h) => h, 88 | Err(e) => { 89 | warn!("Failed to start heartbeat: {e:?}"); 90 | return; 91 | } 92 | }; 93 | Some(heartbeat_handle) 94 | } else { 95 | None 96 | }; 97 | 98 | if let Err(e) = lock 99 | .add_network_device( 100 | udid.clone(), 101 | addr, 102 | service_name.clone(), 103 | "Network".to_string(), 104 | heartbeat_handle, 105 | ) 106 | .await 107 | { 108 | warn!("Failed to add {udid} to muxer: {e:?}"); 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | #[cfg(not(feature = "zeroconf"))] 116 | pub async fn discover(data: Arc>, use_heartbeat: bool) { 117 | use log::warn; 118 | 119 | let service_name = format!("_{}._{}.local", SERVICE_NAME, SERVICE_PROTOCOL); 120 | println!("Starting mDNS discovery for {} with mdns", service_name); 121 | 122 | let stream = mdns::discover::all(&service_name, Duration::from_secs(5)) 123 | .expect("Unable to start mDNS discover stream") 124 | .listen(); 125 | pin_mut!(stream); 126 | 127 | while let Some(Ok(response)) = stream.next().await { 128 | let addr = response.records().filter_map(self::to_ip_addr).next(); 129 | 130 | if let Some(mut addr) = addr { 131 | let mut mac_addr = None; 132 | for i in response.records() { 133 | if let RecordKind::A(addr4) = i.kind { 134 | addr = std::net::IpAddr::V4(addr4) 135 | } 136 | if i.name.contains(&service_name) && i.name.contains('@') { 137 | mac_addr = Some(i.name.split('@').collect::>()[0]); 138 | } 139 | } 140 | 141 | // Look through paired devices for mac address 142 | let mac_addr = match mac_addr { 143 | Some(m) => m, 144 | None => { 145 | warn!("Unable to get mac address for mDNS record"); 146 | continue; 147 | } 148 | }; 149 | 150 | let mut lock = data.lock().await; 151 | if let Ok(udid) = lock.get_udid_from_mac(mac_addr.to_string()).await { 152 | if lock.devices.contains_key(&udid) { 153 | info!("Device has already been added to muxer, skipping"); 154 | continue; 155 | } 156 | println!("Adding device {}", udid); 157 | let heartbeat_handle = if use_heartbeat { 158 | let pairing_file = match data.lock().await.get_pairing_record(&udid).await { 159 | Ok(p) => match PairingFile::from_bytes(&p) { 160 | Ok(p) => p, 161 | Err(e) => { 162 | log::error!("Failed to parse pair record: {e:?}"); 163 | continue; 164 | } 165 | }, 166 | Err(e) => { 167 | log::error!("Failed to get pairing file for device: {e:?}"); 168 | continue; 169 | } 170 | }; 171 | let heartbeat_handle = 172 | match heartbeat::heartbeat(addr, udid.clone(), pairing_file, data.clone()) 173 | .await 174 | { 175 | Ok(h) => h, 176 | Err(e) => { 177 | warn!("Failed to start heartbeat: {e:?}"); 178 | return; 179 | } 180 | }; 181 | Some(heartbeat_handle) 182 | } else { 183 | None 184 | }; 185 | 186 | if let Err(e) = lock 187 | .add_network_device( 188 | udid.clone(), 189 | addr, 190 | service_name.clone(), 191 | "Network".to_string(), 192 | heartbeat_handle, 193 | ) 194 | .await 195 | { 196 | warn!("Failed to add {udid} to muxer: {e:?}"); 197 | } 198 | } 199 | } 200 | } 201 | } 202 | 203 | #[cfg(not(feature = "zeroconf"))] 204 | fn to_ip_addr(record: &Record) -> Option { 205 | match record.kind { 206 | RecordKind::A(addr) => Some(addr.into()), 207 | RecordKind::AAAA(addr) => Some(addr.into()), 208 | _ => None, 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/passthrough.rs: -------------------------------------------------------------------------------- 1 | // jkcoxson 2 | // Passes packets from a a TCP stream to the unix socket for analysis. 3 | 4 | use colored::Colorize; 5 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 6 | 7 | mod raw_packet; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | let mut port = 27015; 12 | let mut host = "127.0.0.1".to_string(); 13 | 14 | // Loop through args 15 | let mut i = 0; 16 | while i < std::env::args().len() { 17 | match std::env::args().nth(i).unwrap().as_str() { 18 | "-p" | "--port" => { 19 | port = std::env::args().nth(i + 1).unwrap().parse::().unwrap(); 20 | i += 2; 21 | } 22 | "-h" | "--host" => { 23 | host = std::env::args().nth(i + 1).unwrap().to_string(); 24 | i += 2; 25 | } 26 | _ => { 27 | i += 1; 28 | } 29 | } 30 | } 31 | 32 | // Create TcpListener 33 | let listener = tokio::net::TcpListener::bind(format!("{}:{}", host, port)) 34 | .await 35 | .unwrap(); 36 | 37 | println!("Listening on {}:{}", host, port); 38 | 39 | loop { 40 | let (mut socket, _) = match listener.accept().await { 41 | Ok(s) => { 42 | println!("Accepted connection"); 43 | s 44 | } 45 | Err(e) => { 46 | println!("Error accepting connection: {}", e); 47 | continue; 48 | } 49 | }; 50 | 51 | // Dial the unix socket 52 | let mut unix_socket = match tokio::net::UnixStream::connect("/var/run/usbmuxd").await { 53 | Ok(s) => s, 54 | Err(e) => { 55 | println!("Error connecting to unix socket: {}", e); 56 | continue; 57 | } 58 | }; 59 | 60 | tokio::spawn(async move { 61 | loop { 62 | let mut tcp_buffer = [0; 16384]; 63 | let mut unix_buffer = [0; 16384]; 64 | 65 | tokio::select! { 66 | size = socket.read(&mut tcp_buffer) => { 67 | if size.is_err() { 68 | println!("{}", "Error reading from socket".red()); 69 | break; 70 | } 71 | let size = size.unwrap(); 72 | if size == 0 { 73 | continue; 74 | } 75 | let tcp_buffer = &tcp_buffer[..size]; 76 | println!("{}", String::from_utf8_lossy(tcp_buffer).trim().green()); 77 | unix_socket.write_all(tcp_buffer).await.unwrap(); 78 | }, 79 | size = unix_socket.read(&mut unix_buffer) => { 80 | if size.is_err() { 81 | println!("{}", "Error reading from unix socket".red()); 82 | break; 83 | } 84 | let size = size.unwrap(); 85 | if size == 0 { 86 | continue; 87 | } 88 | let unix_buffer = &unix_buffer[..size]; 89 | println!("{}", String::from_utf8_lossy(unix_buffer).trim().blue()); 90 | socket.write_all(unix_buffer).await.unwrap(); 91 | } 92 | } 93 | 94 | // let size = socket.read(&mut tcp_buffer).await; 95 | // if size.is_ok() { 96 | // let size = size.unwrap(); 97 | // if size == 0 { 98 | // println!("Client disconnected"); 99 | // break; 100 | // } else { 101 | // println!("Received {} bytes", size); 102 | // let buffer = &tcp_buffer[0..size]; 103 | // println!("{:?}", buffer); 104 | // if size > 4 { 105 | // // let parsed = raw_packet::RawPacket::from(buffer); 106 | // // println!("{}", format!("{:?}", parsed).blue()); 107 | // } 108 | // println!("{}", String::from_utf8_lossy(buffer).blue()); 109 | // unix_socket.write_all(buffer).await.unwrap(); 110 | // } 111 | // } 112 | // let size = unix_socket.read(&mut unix_buffer).await; 113 | // if size.is_ok() { 114 | // let size = size.unwrap(); 115 | // if size == 0 { 116 | // println!("Unix socket disconnected"); 117 | // break; 118 | // } else { 119 | // println!("Received {} bytes", size); 120 | // let buffer = &unix_buffer[0..size]; 121 | // println!("{:?}", buffer); 122 | // if size > 4 { 123 | // // let parsed = raw_packet::RawPacket::from(buffer); 124 | // // println!("{}", format!("{:?}", parsed).green()); 125 | // } 126 | // println!("{}", String::from_utf8_lossy(buffer).green()); 127 | // socket.write_all(buffer).await.unwrap(); 128 | // } 129 | // } 130 | } 131 | }); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/raw_packet.rs: -------------------------------------------------------------------------------- 1 | // jkcoxson 2 | 3 | use log::{debug, warn}; 4 | 5 | #[derive(Debug)] 6 | pub struct RawPacket { 7 | pub size: u32, 8 | pub version: u32, 9 | pub message: u32, 10 | pub tag: u32, 11 | pub plist: plist::Dictionary, 12 | } 13 | 14 | fn plist_to_bytes(p: &plist::Dictionary) -> Vec { 15 | let buf = Vec::new(); 16 | let mut writer = std::io::BufWriter::new(buf); 17 | plist::to_writer_xml(&mut writer, &p).unwrap(); 18 | 19 | writer.into_inner().unwrap() 20 | } 21 | 22 | impl RawPacket { 23 | pub fn new(plist: plist::Dictionary, version: u32, message: u32, tag: u32) -> RawPacket { 24 | let plist_bytes = plist_to_bytes(&plist); 25 | let size = plist_bytes.len() as u32 + 16; 26 | RawPacket { 27 | size, 28 | version, 29 | message, 30 | tag, 31 | plist, 32 | } 33 | } 34 | } 35 | 36 | impl From for Vec { 37 | fn from(raw_packet: RawPacket) -> Vec { 38 | let mut packet = vec![]; 39 | packet.extend_from_slice(&raw_packet.size.to_le_bytes()); 40 | packet.extend_from_slice(&raw_packet.version.to_le_bytes()); 41 | packet.extend_from_slice(&raw_packet.message.to_le_bytes()); 42 | packet.extend_from_slice(&raw_packet.tag.to_le_bytes()); 43 | packet.extend_from_slice(&plist_to_bytes(&raw_packet.plist)); 44 | packet 45 | } 46 | } 47 | 48 | impl TryFrom<&mut Vec> for RawPacket { 49 | type Error = (); 50 | fn try_from(packet: &mut Vec) -> Result { 51 | let packet: &[u8] = packet; 52 | packet.try_into() 53 | } 54 | } 55 | 56 | impl TryFrom<&[u8]> for RawPacket { 57 | type Error = (); 58 | fn try_from(packet: &[u8]) -> Result { 59 | debug!("Parsing raw packet: {:02X?}", packet); 60 | 61 | // Determine if we have enough data to parse 62 | if packet.len() < 16 { 63 | warn!("Not enough data to parse a raw packet header"); 64 | return Err(()); 65 | } 66 | 67 | let packet_size = &packet[0..4]; 68 | let packet_size = u32::from_le_bytes(match packet_size.try_into() { 69 | Ok(packet_size) => packet_size, 70 | Err(_) => { 71 | warn!("Failed to parse packet size"); 72 | return Err(()); 73 | } 74 | }); 75 | 76 | // Determine if we have enough data to parse 77 | if packet.len() < packet_size as usize { 78 | warn!("Not enough data to parse a raw packet body"); 79 | return Err(()); 80 | } 81 | 82 | let packet_version = &packet[4..8]; 83 | let packet_version = u32::from_le_bytes(match packet_version.try_into() { 84 | Ok(packet_version) => packet_version, 85 | Err(_) => { 86 | warn!("Failed to parse packet version"); 87 | return Err(()); 88 | } 89 | }); 90 | 91 | let message = &packet[8..12]; 92 | let message = u32::from_le_bytes(match message.try_into() { 93 | Ok(message) => message, 94 | Err(_) => { 95 | warn!("Failed to parse packet message"); 96 | return Err(()); 97 | } 98 | }); 99 | 100 | let packet_tag = &packet[12..16]; 101 | let packet_tag = u32::from_le_bytes(match packet_tag.try_into() { 102 | Ok(packet_tag) => packet_tag, 103 | Err(_) => { 104 | warn!("Failed to parse packet tag"); 105 | return Err(()); 106 | } 107 | }); 108 | 109 | let plist = &packet[16..packet_size as usize]; 110 | let plist = if let Ok(p) = plist::from_bytes(plist) { 111 | p 112 | } else { 113 | warn!("Failed to parse packet plist"); 114 | return Err(()); 115 | }; 116 | 117 | Ok(RawPacket { 118 | size: packet_size, 119 | version: packet_version, 120 | message, 121 | tag: packet_tag, 122 | plist, 123 | }) 124 | } 125 | } 126 | --------------------------------------------------------------------------------