diff --git a/components/mycelium/.github/dependabot.yml b/components/mycelium/.github/dependabot.yml
new file mode 100644
index 0000000..d23b411
--- /dev/null
+++ b/components/mycelium/.github/dependabot.yml
@@ -0,0 +1,31 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: "cargo" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
+ groups:
+ mycelium:
+ patterns:
+ - "*"
+ - package-ecosystem: "cargo" # See documentation for possible values
+ directory: "/myceliumd" # Location of package manifests
+ schedule:
+ interval: "weekly"
+ groups:
+ myceliumd:
+ patterns:
+ - "*"
+ - package-ecosystem: "cargo" # See documentation for possible values
+ directory: "/myceliumd-private" # Location of package manifests
+ schedule:
+ interval: "weekly"
+ groups:
+ myceliumd-private:
+ patterns:
+ - "*"
diff --git a/components/mycelium/.github/workflows/ci.yaml b/components/mycelium/.github/workflows/ci.yaml
new file mode 100644
index 0000000..95cf8ed
--- /dev/null
+++ b/components/mycelium/.github/workflows/ci.yaml
@@ -0,0 +1,133 @@
+name: ci
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ check_fmt:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: dtolnay/rust-toolchain@nightly
+ with:
+ components: rustfmt
+ - uses: clechasseur/rs-fmt-check@v2
+
+ clippy:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set windows VCPKG_ROOT env variable
+ run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
+ if: runner.os == 'Windows'
+ - name: Install windows openssl
+ run: vcpkg install openssl:x64-windows-static-md
+ if: runner.os == 'Windows'
+ - uses: actions/checkout@v4
+ - name: Run Clippy
+ run: cargo clippy --all-features -- -Dwarnings
+
+ check_library:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest,windows-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set windows VCPKG_ROOT env variable
+ run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
+ if: runner.os == 'Windows'
+ - name: Install windows openssl
+ run: vcpkg install openssl:x64-windows-static-md
+ if: runner.os == 'Windows'
+ - uses: actions/checkout@v4
+ - name: Build
+ run: cargo build -p mycelium --all-features --verbose
+ - name: Run tests
+ run: cargo test -p mycelium --all-features --verbose
+
+ check_ios_library:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: install ios target
+ run: rustup target add aarch64-apple-ios
+ - name: Cache cargo
+ uses: actions/cache@v3
+ with:
+ path: ~/.cargo/registry
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ - name: Build
+ run: cargo build --target aarch64-apple-ios
+ working-directory: mobile
+
+ check_android_library:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: install android target
+ run: rustup target add aarch64-linux-android
+ - name: Setup Java
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'adopt'
+ java-version: '17'
+ - name: Set up Android NDK
+ uses: android-actions/setup-android@v3
+ - name: Accept Android Licenses
+ run: yes | sdkmanager --licenses || true
+ - name: Cache cargo
+ uses: actions/cache@v3
+ with:
+ path: ~/.cargo/registry
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ - name: install cargo NDK
+ run: cargo install cargo-ndk
+ - name: Build
+ run: cargo ndk -t arm64-v8a build
+ working-directory: mobile
+
+ check_binaries:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ binary: [myceliumd, myceliumd-private]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Set windows VCPKG_ROOT env variable
+ run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
+ if: runner.os == 'Windows'
+ - name: Install windows openssl
+ run: vcpkg install openssl:x64-windows-static-md
+ if: runner.os == 'Windows'
+ - uses: actions/checkout@v4
+ - name: Change directory to binary
+ run: cd ${{ matrix.binary }}
+ - name: Build
+ run: cargo build --verbose
+ - name: Run tests
+ run: cargo test --verbose
+ - name: Run Clippy
+ run: cargo clippy --all-features -- -Dwarnings
+
+ check_flake:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ permissions:
+ id-token: "write"
+ contents: "read"
+ steps:
+ - uses: actions/checkout@v4
+ - uses: DeterminateSystems/nix-installer-action@main
+ - uses: DeterminateSystems/flake-checker-action@main
+ - name: Run `nix build`
+ run: nix build .
diff --git a/components/mycelium/.github/workflows/release.yaml b/components/mycelium/.github/workflows/release.yaml
new file mode 100644
index 0000000..40597bc
--- /dev/null
+++ b/components/mycelium/.github/workflows/release.yaml
@@ -0,0 +1,121 @@
+name: Release
+
+permissions:
+ contents: write
+
+on:
+ push:
+ tags:
+ - v[0-9]+.*
+
+jobs:
+ create-release:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: taiki-e/create-gh-release-action@v1
+ with:
+ changelog: CHANGELOG.md
+ # (required) GitHub token for creating GitHub Releases.
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ upload-assets-mycelium:
+ needs: create-release
+ strategy:
+ matrix:
+ include:
+ - target: aarch64-apple-darwin
+ os: macos-latest
+ - target: x86_64-unknown-linux-musl
+ os: ubuntu-latest
+ - target: x86_64-apple-darwin
+ os: macos-latest
+ - target: aarch64-unknown-linux-musl
+ os: ubuntu-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: taiki-e/upload-rust-binary-action@v1
+ with:
+ # Name of the compiled binary, also name of the non-extension part of the produced file
+ bin: mycelium
+ # --target flag value, default is host
+ target: ${{ matrix.target }}
+ # Name of the archive when uploaded
+ archive: $bin-$target
+ # (required) GitHub token for uploading assets to GitHub Releases.
+ token: ${{ secrets.GITHUB_TOKEN }}
+ # Specify manifest since we are in a subdirectory
+ manifest-path: myceliumd/Cargo.toml
+
+ # TODO: Figure out the correct matrix setup to have this in a single action
+ upload-assets-myceliumd-private:
+ needs: create-release
+ strategy:
+ matrix:
+ include:
+ - target: aarch64-apple-darwin
+ os: macos-latest
+ - target: x86_64-unknown-linux-musl
+ os: ubuntu-latest
+ - target: x86_64-apple-darwin
+ os: macos-latest
+ - target: aarch64-unknown-linux-musl
+ os: ubuntu-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: taiki-e/upload-rust-binary-action@v1
+ with:
+ # Name of the compiled binary, also name of the non-extension part of the produced file
+ bin: mycelium-private
+ # Set the vendored-openssl flag for provided release builds
+ features: vendored-openssl
+ # --target flag value, default is host
+ target: ${{ matrix.target }}
+ # Name of the archive when uploaded
+ archive: $bin-$target
+ # (required) GitHub token for uploading assets to GitHub Releases.
+ token: ${{ secrets.GITHUB_TOKEN }}
+ # Specify manifest since we are in a subdirectory
+ manifest-path: myceliumd-private/Cargo.toml
+
+ build-msi:
+ needs: create-release
+ runs-on: windows-latest
+ steps:
+
+ - name: Checkout repo
+ uses: actions/checkout@v4
+
+ - name: Create .exe file
+ shell: bash
+ run: cd myceliumd && RUSTFLAGS="-C target-feature=+crt-static" cargo build --release && cd ..
+
+ - name: Setup .NET Core SDK
+ uses: actions/setup-dotnet@v4.0.0
+
+ - name: Install WiX Toolset
+ run: dotnet tool install --global wix
+
+ - name: Add WixToolset.UI.wixext extension
+ run: wix extension add WixToolset.UI.wixext
+
+ - name: Download Wintun zip file
+ run: curl -o wintun.zip https://www.wintun.net/builds/wintun-0.14.1.zip
+
+ - name: Unzip Wintun
+ run: unzip wintun.zip
+
+ - name: Move .dll file to myceliumd directory
+ run: move wintun\bin\amd64\wintun.dll myceliumd
+
+ - name: Build MSI package
+ run: wix build -loc installers\windows\wix\mycelium.en-us.wxl installers\windows\wix\mycelium.wxs -ext WixToolset.UI.wixext -arch x64 -dcl high -out mycelium_installer.msi
+
+ - name: Upload MSI artifact
+ uses: alexellis/upload-assets@0.4.0
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ asset_paths: '["mycelium_installer.msi"]'
diff --git a/components/mycelium/.gitignore b/components/mycelium/.gitignore
new file mode 100644
index 0000000..f828e85
--- /dev/null
+++ b/components/mycelium/.gitignore
@@ -0,0 +1,21 @@
+/target
+nodeconfig.toml
+keys.txt
+priv_key.bin
+
+# Profile output
+*.profraw
+*.profdata
+profile.json
+
+# vscode settings, keep these locally
+.vscode
+# visual studio project stuff
+.vs
+
+# wintun.dll, windows tun driver in repo root for windows development
+wintun.dll
+
+result/
+
+.idea
diff --git a/components/mycelium/CHANGELOG.md b/components/mycelium/CHANGELOG.md
new file mode 100644
index 0000000..ba50296
--- /dev/null
+++ b/components/mycelium/CHANGELOG.md
@@ -0,0 +1,614 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+- New log format option `plain`, this option is the same as logfmt, but with colors
+ always disabled.
+
+## [0.6.1] - 2025-05-14
+
+### Added
+
+- When a route is used which is about to expire, we now send a route request to
+ try and refresh its duration before it expires.
+- We now track when a peer was fist discovered and when we last connected to it.
+ This info is displayed in the CLI when listing peers.
+- We now maintain a cache of recently sent route requests, so we can avoid spamming
+ peers with duplicate requests.
+
+### Changed
+
+- Only keep a record of retracted routes for 6 seconds instead of 60. We'll track
+ how this affects the route propagation before removing this altogether.
+
+### Fixed
+
+- Fixed an unsoundness issue in the routing table clone implementation.
+- Clear dead peer buffer once peers have been removed from the routing table.
+- Properly reply with an address unreachable ICMP when pinging an IP in the local
+ subnet which does not exist.
+- Verify a packet has sufficient TTL to be routed before injecting it, and reply
+ with a TTL exceeded otherwise. This fixes an issue where packets with a TTL of
+ 1 and 0 originating locally would not result in a proper ICMP reply. This happens
+ for instance when using `traceroute`.
+- Check the local seqno request cache before sending a seqno request to a peer,
+ to avoid spamming in certain occasions.
+- Don't accept packet for a destination if we only have fallback routes for said
+ destination.
+
+## [0.6.0] - 2025-04-25
+
+This is a breaking change, check the main README file for update info.
+
+### Added
+
+- Json-rpc based API, see the docs for more info.
+- Message forwarding to unix sockets if configured.
+- Config file support to handle messages, if this is enabled.
+
+### Changed
+
+- Routing has been reworked. We no longer advertise selected subnets (which aren't
+ our own). Now if a subnet is needed, we perform a route request for that subnet,
+ memorizing state and responses. The current imlementation expires routes every 5
+ minutes but does not yet refresh active routes before they expire.
+- Before we process a seqno request for a subnet, check the seqno cache to see if
+ we recently forwarded an entry for it.
+- Discard Update TLV's if there are too many in the queue already. This binds memory
+ usage but can cause nodes with a lot of load to not pick up routes immediately.
+
+## [0.5.7] - 2024-11-31
+
+### Fixed
+
+- Properly set the interface ipv6 MTU on windows.
+
+## [0.5.6] - 2024-10-03
+
+### Fixed
+
+- Fix a panic in the route cleanup task when a peer dies who is the last stored
+ announcer of a subnet.
+
+## [0.5.5] - 2024-09-27
+
+### Added
+
+- Mycelium-ui, a standalone GUI which exposes (part of) the mycelium API. This
+ does **not** have a bundled mycelium node, so that needs to be run separately.
+
+### Changed
+
+- Default TUN name on Linux and Windows is now `mycelium`. On MacOS it is now `utun0`.
+- TUN interface name validation on MacOS. If the user supplies an invalid or already
+ taken interface name, an available interface name will be automatically assigned.
+
+### Fixed
+
+- Release flow to create the Windows installer now properly extracts wintun
+- Guard against a race condition when a route is deleted which could rarely
+ trigger a panic and subsequent memory leak.
+
+## [0.5.4] - 2024-08-20
+
+### Added
+
+- Quic protocol can now be disabled using the `--disable-quic` flag
+- Mycelium can now be started with a configuration file using `--config-file`.
+ If no configuration file is supplied, Mycelium will look in a default location
+ based on the OS. For more information see [README](/README.md#configuration)
+- Windows installer for Mycelium. The `.msi` file can be downloaded from the release
+ assets.
+- Added flag to specify how many update workers should be started, which governs
+ the amount of parallelism used for processing updates.
+- Send a seqno request if we receive an unfeasible update for a subnet with no
+ routes, or if there is no selected route for the subnet.
+- New public peers in US, India, and Singapore.
+
+### Changed
+
+- Increased the starting metric of a peer from 50 to 1000.
+- Reworked the internals of the routing table, which should reduce memory consumption.
+ Additionally, it is now possible to apply updates in parallel
+- Periodically reduce the allocated size of the seqno cache to avoid wasting some
+ memory which is not currently used by the cache but still allocated.
+- Demote seqno cache warnings about duplicate seqno requests go debug lvl, as it
+ is valid to send duplicate requests if sufficient time passed.
+- Skip route selection after an unfeasible update to a fallback route, as the (now
+ unfeasible) route won't be selected anyway.
+- No longer refresh route timer after an unfeasbile update. This allows routes
+ which have become unfeasible to gracefully be removed from the routing table
+ over time.
+- Expired routes which aren't selected are now immediately removed from the routing
+ table.
+- Changed how updates are sent to be more performant.
+- A triggered update is no longer sent just because a route sequence number got
+ increased. We do still send the update to peer in the seqno request cache.
+- Reduced log level when a route changes next-hop to debug from info.
+
+### Fixed
+
+- When running `mycelium` with a command, a keyfile was loaded (or created, if not
+ yet present). This was not necessary in that context.
+- Limit the amount of time allowed for inbound quic connections to be set up, and
+ process multiple of them in parallel. This fixes a DOS vector against the quic
+ listener.
+- We now update the source table even if we don't send an update because we are
+ sure the receiver won't select us as a next-hop anyway.
+
+## [0.5.3] - 2024-06-07
+
+### Added
+
+- On Linux and macOS, a more descriptive error is printed when setting up the tun
+ device fails because a device with the same name already exists.
+- Seqno request cache, to avoid spamming peers with duplicate seqno requests and
+ to make sure seqno's are forwarded to different peers.
+- Added myceliumd-private binary, which contains private network functionality.
+- Added API endpoint to retrieve the public key associated with an IP.
+- The CLI can now be used to list, remove or add peers (see `mycelium peers --help`)
+- The CLI can now be used to list selected and fallback routes (see
+ `mycelium routes --help`)
+
+### Changed
+
+- We now send seqno requests to all peers who advertised a subnet if the selected
+ route to it is lost as a result of the next-hop dying, or and update coming in
+ which causes no routes to be feasible anymore.
+- Switched from the log to the tracing ecosystem.
+- Only do the periodic route announcement every 5 minutes instead of every minute.
+- Mycelium binary is no longer part of the workspace, and no longer contains private
+ network functionality.
+- If a packet received from a peer can't be forwarded to the router, terminate the
+ connection to the peer.
+
+### Fixed
+
+- Manually implement Hash for Subnet, previously we could potentially have multiple
+ distinct entries in the source table for the same source key.
+
+## [0.5.2] - 2024-05-03
+
+### Added
+
+- New CI workflow to build and test the mycelium library separately from the full
+ provided binary build.
+
+### Changed
+
+- Disabled the protobuf feature on prometheus, this removes protobuf related code
+ and significantly reduces the release binary size.
+- Changed log level when sending a protocol message to a peer which is no longer
+ alive from error to trace in most instances.
+- Improved performance of sending protocol messages to peers by queueing up multiple
+ packets at once (if multiple are ready).
+- Before trying to send an update we now check if it makes sense to do so.
+- If a peer died, fallback routes using it are no longer retained with an infinite
+ metric but removed immediately.
+- No longer run route selection for subnets if a peer died and the route is not
+ selected.
+- If routes are removed, shrink the capacity of the route list in the route table
+ if it is larger than required.
+- Check if the originator of a TLV is still available before processing said TLV.
+- The router now uses a dedicated task per TLV type to handle received TLV's from
+ peers.
+- Statically linking openssl is now a feature flag when building yourself.
+
+### Fixed
+
+- If a peer died, unselect the selected routes which have it as next-hop if there
+ is no other feasible route.
+- Properly unselect a route if a retraction update comes in and there is no other
+ feasible route.
+- If the router bumps it's seqno it now properly announces the local route to it's
+ peers instead of the selected routes
+- Seqno bump requests for advertised local routes now properly bump the router
+ seqno.
+
+## [0.5.1] - 2024-04-19
+
+### Added
+
+- The repo is now a workspace, and pure library code is separated out. This is mainly
+ done to make it easier to develop implementations on different platforms.
+- Link local discovery will now send discovery beacons on every interface the process
+ listens on for remote beacons.
+- Experimental private network support. See [the private network docs](/docs/private_network.md)
+ for more info.
+- You can now optionally expose Prometheus compatible metrics about the system by
+ setting the --metrics-api-address flag.
+- On Linux, you can now set an optional firewall mark by setting the --firewall-mark
+ flag.
+- Added a nix flake to the repo.
+
+### Changed
+
+- We no longer create an outbound connection to a link local discovered IP if that
+ IP is already known (usually as inbound address) with potentially a different
+ port.
+
+## [0.5.0] - 2024-04-04
+
+### Changed
+
+- Connection identifier is now included in the error log if we can't forward a
+ seqno request.
+- Garbage collection time for source entries has been increased from 5 to 30 minutes
+ for now.
+- The router implementation has been changed to use regular locks instead of an
+ always readable concurrency primitive for all but the actual routing table. This
+ should reduce the memory consumption a bit.
+- Public key and shared secret for a destination are now saved on the router, instead
+ of maintaining a separate mapping for them. This slightly reduces memory consumption
+ of the router, and ensures stale data is properly cleaned up when all routes to
+ a subnet are removed.
+- Hello packets now set the interval in which the next Hello will be sent properly
+ in centiseconds.
+- IHU packets now set the interval properly in centiseconds.
+- IHU packets now set an RX cost. For now this is the link cost, in the future
+ this will be set properly.
+- Route expiration time is now calculated from the interval received in updates.
+- Ip address derivation from public keys now uses the blake3 hash algorithm.
+
+### Fixed
+
+- Don't try to forward seqno requests to a peer if we know its connection is dead.
+
+## [0.4.5] - 2024-03-26
+
+### Changed
+
+- Size of data packets is limited to 65535 bytes.
+- Update interval is now expressed as centiseconds, in accordance with the babel
+ RFC.
+- Update filters now allow retractions for a route from any router-id.
+
+### Fixed
+
+- The feasibility distance of an existing source key is no longer incorrectly updated
+ when the metric increases.
+- Source key garbage collection timers are properly reset on update even if the
+ source key itself is not updated.
+- Nodes now properly reply to route requests for a static route.
+- A retraction is now sent as reply to a route request if the route is not known.
+
+## [0.4.4] - 2024-03-22
+
+### Changed
+
+- The amount of bytes read and written to a peer are now no longer reset after
+ a reconnect (for outgoing connection).
+- Renamed `connectionTxBytes` and `connectionRxBytes` on the peer stats struct
+ to `txBytes` and `rxBytes` to better express that they are no longer tied to
+ a single connection to the peer.
+
+### Fixed
+
+- When joining a link local multicast group on an interface returns a
+ `Address already in use` error, the error is now ignored and the interface is
+ considered to be joined.
+- When sending an update to a peer, the source table is now correctly updated before
+ the update is sent, instead of doing a batched source table update afterward.
+
+## [0.4.3] - 2024-03-15
+
+### Added
+
+- Feature flag for message subsystem. It is enabled by default, but a user can
+ make a custom build with `--default-features-false` which completely leaves out
+ the message related code, should he desire this and have no need for it.
+- Link local discovery now periodically checks for new IPv6 enabled interfaces
+ and also joins the discovery multicast group on them.
+- Trace logs are removed from release binaries at compile time, slightly reducing
+ binary size.
+- New `--silent` flag which disables all logging except error logs.
+
+### Changed
+
+- Update GitHub CI action to use latest version of the checkout action.
+- Update GitHub CI action to stop using deprecated actions-rs actions.
+- Failing to join the link local discovery multicast group now logs as warning
+ instead of error.
+- Failing to join any IPv6 multicast group for link local peer discovery will no
+ longer disable local peer discovery entirely.
+
+### Fixed
+
+- Add proper validation when receiving an OOB ICMP packet.
+
+## [0.4.2] - 2024-02-28
+
+### Fixed
+
+- Make sure the HTTP API doesn't shut down immediately after startup.
+
+## [0.4.1] - 2024-02-27
+
+### Added
+
+- Admin API
+ - Ability to see current peers and related info
+ - Ability to add a new peer
+ - Ability to remove an existing peer
+ - List current selected routes
+ - List current fallback routes
+ - General node info (for now just the node subnet)
+
+### Changed
+
+- The tokio_unstable config flag is no longer used when building.
+- The key file is now created without read permissions for the group/world.
+
+### Removed
+
+- .cargo/config.toml aarch64-linux target specific entries. Cross compilation for
+ these platforms can use `cross` or entries in the global .cargo/config.toml of
+ the developer instead.
+- Sending SIGUSR1 to the process on unix based systems no longer dumps internal
+ state, this can be accessed with the admin API instead.
+
+## [0.4.0] - 2024-02-22
+
+### Added
+
+- Support for windows tunnels. While this works, there are no windows
+ packages yet, so this is still a "developer experience".
+- Validation on source IP when sending packets over TUN interface.
+
+### Changed
+
+- Overlay network is now hosted in 400::/7 instead of 200::/7.
+- Key file is no longer created if it does not exist when the
+ inspect command is run.
+- Packets with destination outside the global subnet now return
+ a proper ICMP instead of being silently dropped.
+
+### Fixed
+
+- Log the endpoint when a Quic connection can't be established.
+
+## [0.3.2] - 2024-01-31
+
+### Added
+
+- If the router notices a Peer is dead, the connection is now forcibly terminated.
+- Example Systemd file.
+
+### Changed
+
+- The multicast discovery group is now joined from all available
+ interfaces. This should increase the resilience of local peer
+ discovery.
+- Setup of the node is now done completely in the library.
+- Route selection now accounts for the link cost of peers when
+ considering if it should switch to the new route.
+- Hop count of data packets is now decremented on the first
+ hop as well. As a result the sending node will show up in
+ traceroute results.
+
+### Fixed
+
+- Inbound peers now replace existing peers in the peer manager. This should fix
+ an issue where Quic could leave zombie connections.
+
+## [0.3.1] - 2024-01-23
+
+### Added
+
+- You can now check the version of the current binary with the --version flag.
+- Bandwidth usage is now tracked per peer.
+
+### Changed
+
+- Prefix decoding is now more resilient to bad prefix lengths.
+- The `-k/--key-file` flag is now global, allowing you to specify it for (inspect)
+ sub commands.
+- Log the actual endpoint when we can't connect to it
+
+## [0.3.0] - 2024-01-17
+
+### Added
+
+- Nodes can now explicitly request selected route(s) from connected peers by
+ sending a Route Request Tlv.
+- The router can now inform a Peer that the connection is seemingly
+ dead. This should improve the reconnect speed on connection types
+ which can't tell themselves if they died.
+
+### Changed
+
+- Locally discovered peers are now forgotten if we fail to connect to them 3
+ times.
+- Duration between periodic events has been increased, this should
+ reduce bandwidth when idle to maintain the system.
+- Address encoding in update packets is now in-line with address
+ encoding as described by the babel RFC.
+
+### Fixed
+
+- TLV bodies of unknown type are now properly skipped. Previously, the
+ calculation of the body size was off by one, causing the connection to
+ the peer to always die. Now, these packets should be properly ignored.
+- We are a bit more active now and no longer sleep for a second when we
+ need to remove an expired route entry.
+
+## [0.2.3] - 2024-01-04
+
+### Added
+
+- Added automatic release builds for aarch64-linux.
+
+### Changed
+
+- Reduce the Quic keep-alive timeout.
+
+## [0.2.2] - 2024-01-03
+
+### Changed
+
+- Changed default multicast peer discovery port to 9650.
+
+## [0.2.1] - 2024-01-03
+
+### Added
+
+- Experimental Quic transport. The same socket is used for sending and
+ receiving. This transport is experimental and breaking changes are
+ possible which won't be covered by semver guarantees for now.
+
+## [0.2.0] - 2023-12-29
+
+### Added
+
+- Link local peer discovery over IPv6. The system will automatically detect peers on the same LAN and try to connect to them.
+ This makes sure peers on the same network don't needlessly use bandwidth on external "hop" peers.
+- Data packets now carry a Hop Limit field as part of the header. Every node decrements this value, and if it is decremented
+ to zero, the packet is discarded
+- Intermediate nodes can now send ICMP packets back to the source in
+ reply to a dropped packet. This is useful if a hop does not have route
+ to forward a packet, or the hop count for a packet reaches 0.
+- Local node now returns an ICMP Destination unreachable - no route if a
+ packet is sent on the TUN interface and there is no key for the remote
+ address (so the user data can't be encrypted).
+- Peers connected over IPv4 now incur a higher processing cost, causing
+ IPv6 connections to be preferred for forwarding data.
+- Peer addresses now include a protocol specifier, so multiple underlay
+ connections can be specified in the future.
+
+### Changed
+
+- The peer manager now tracks sufficient info of each connected peer to
+ avoid going through the router every time to see if it needs to
+ reconnect to said peers.
+- We don't send the receiver nodes IP address in IHU packets anymore, as the packet is sent over a unicast link.
+ This is valid per the babel rfc.
+- Setting the peers on the CLI now requires specifying the protocol to use.
+ For now only TCP is supported.
+- Change `--port` flag to `--tcp-listen-port` to more accurately reflect
+ what it controls, as well as the fact that it is only for TCP.
+
+### Removed
+
+- Handshake when connecting to a new peer has been removed.
+
+## [0.1.3] - 2023-11-22
+
+### Added
+
+- Add info log when next hop of a peer changes.
+- Add windows builds to CI.
+
+### Changed
+
+- When printing the connected peers, print the underlay IP instead of the overlay IP.
+- The link cost of a peer is now the smoothed average. This makes sure a single short latency spike doesn't disrupt routing.
+- On Linux, set the TUN ip as /7 and avoid setting a /64 route. This brings it in line with OSX.
+- When selecting the best route for a subnet, consider the currently
+ installed route and only switch if it is significantly better, or
+ directly connected.
+- Increase the static link cost component of a peer. This will increase
+ the value of a hop in the metric of a route, in turn increasing the
+ impact of multiple hops on route selection. The route selection will
+ thus be more inclined to find a path with fewer hops toward a
+ destination. Ultimately, if multiple paths to a destination exist with
+ roughly the same latency, they one with fewer hops should be
+ preferred, since this avoids putting unnecessary pressure on multiple
+ nodes in the network.
+- IHU packets now include the underlay IP instead of the overlay IP.
+- When a new peer connects the underlay IP is logged instead of the
+ overlay IP.
+
+### Fixed
+
+- Ignore retraction updates if the route table has an existing but retracted route already. This fixes
+ an issue where retracted routes would not be flushed from the routing table.
+
+### Removed
+
+- All uses of the exchanged overlay IP in the peer handshake are fully
+ removed. Handshake is still performed to stay backwards compatible
+ until the next breaking release.
+
+## [0.1.2] - 2023-11-21
+
+### Changed
+
+- Allow routes with infinite metric for a subnet to be selected. They will only be selected if no feasible route
+ with finite metric exists. They are also still ignored when looking up a route to a subnet.
+
+### Fixed
+
+- Don't trigger an update when a route retraction comes in for a subnet where the route is already retracted.
+ This fixes a potential storm of retraction requests in the network.
+
+## [0.1.1] - 2023-11-21
+
+### Added
+
+- CHANGELOG.md file to keep track of notable additions, changes, fixes, deprecations, and removals.
+- A peer can now detect if it is no longer useable in most cases, allowing it to notify the router if it died. This
+ allows near instant retraction of routes if a connection dies, decreasing the amount of time needed to find a
+ suitable alternative route.
+- Add CHANGELOG.md entries when creating a release.
+- When sending SIGUSR1 to the process, the routing table dump will now include a list of the public key derived IP
+ for every currently known subnet.
+- You can now set the name of the TUN interface on the command line with the --tun-name flag.
+- Added support for TUN devices on OSX.
+
+### Changed
+
+- When a peer is found to be dead, routes which use it as next-hop now have their metric set to infinity.
+ If the route is selected, route selection for the subnet is run again and if needed a triggered update is sent.
+ This will allow downstream peers to receive a timely update informing them of a potentially retracted route,
+ instead of having to wait for route expiration.
+- Account for the link with the peer of a route when performing route selection. This was not the case previously,
+ and could theoretically lead to a case where a route was selected with a non-optimal path, because the lower metric
+ was offset by a high link cost of the peer.
+
+### Fixed
+
+- Remove trailing 'e' character from release archive names
+
+## [0.1.0] - 2023-11-15
+
+### Added
+
+- Initial working setup with end-to-end encryption of traffic.
+- Ability to specify peers to connect to
+- Message subsystem, including API to use it.
+- CLI options to send and receive messages with the API.
+
+[0.1.1]: https://github.com/threefoldtech/mycelium/compare/v0.1.0...v0.1.1
+[0.1.2]: https://github.com/threefoldtech/mycelium/compare/v0.1.1...v0.1.2
+[0.1.3]: https://github.com/threefoldtech/mycelium/compare/v0.1.2...v0.1.3
+[0.2.0]: https://github.com/threefoldtech/mycelium/compare/v0.1.3...v0.2.0
+[0.2.1]: https://github.com/threefoldtech/mycelium/compare/v0.2.0...v0.2.1
+[0.2.2]: https://github.com/threefoldtech/mycelium/compare/v0.2.1...v0.2.2
+[0.2.3]: https://github.com/threefoldtech/mycelium/compare/v0.2.2...v0.2.3
+[0.3.0]: https://github.com/threefoldtech/mycelium/compare/v0.2.3...v0.3.0
+[0.3.1]: https://github.com/threefoldtech/mycelium/compare/v0.3.0...v0.3.1
+[0.3.2]: https://github.com/threefoldtech/mycelium/compare/v0.3.1...v0.3.2
+[0.4.0]: https://github.com/threefoldtech/mycelium/compare/v0.3.2...v0.4.0
+[0.4.1]: https://github.com/threefoldtech/mycelium/compare/v0.4.0...v0.4.1
+[0.4.2]: https://github.com/threefoldtech/mycelium/compare/v0.4.1...v0.4.2
+[0.4.3]: https://github.com/threefoldtech/mycelium/compare/v0.4.2...v0.4.3
+[0.4.4]: https://github.com/threefoldtech/mycelium/compare/v0.4.3...v0.4.4
+[0.4.5]: https://github.com/threefoldtech/mycelium/compare/v0.4.4...v0.4.5
+[0.5.0]: https://github.com/threefoldtech/mycelium/compare/v0.4.5...v0.5.0
+[0.5.1]: https://github.com/threefoldtech/mycelium/compare/v0.5.0...v0.5.1
+[0.5.2]: https://github.com/threefoldtech/mycelium/compare/v0.5.1...v0.5.2
+[0.5.3]: https://github.com/threefoldtech/mycelium/compare/v0.5.2...v0.5.3
+[0.5.4]: https://github.com/threefoldtech/mycelium/compare/v0.5.3...v0.5.4
+[0.5.5]: https://github.com/threefoldtech/mycelium/compare/v0.5.4...v0.5.5
+[0.5.6]: https://github.com/threefoldtech/mycelium/compare/v0.5.5...v0.5.6
+[0.5.7]: https://github.com/threefoldtech/mycelium/compare/v0.5.6...v0.5.7
+[0.6.0]: https://github.com/threefoldtech/mycelium/compare/v0.5.7...v0.6.0
+[0.6.1]: https://github.com/threefoldtech/mycelium/compare/v0.6.0...v0.6.1
+[unreleased]: https://github.com/threefoldtech/mycelium/compare/v0.6.1...HEAD
diff --git a/components/mycelium/CODEOWNERS b/components/mycelium/CODEOWNERS
new file mode 100644
index 0000000..9658d49
--- /dev/null
+++ b/components/mycelium/CODEOWNERS
@@ -0,0 +1,2 @@
+# global code owners
+* @leesmet
diff --git a/components/mycelium/Cargo.lock b/components/mycelium/Cargo.lock
new file mode 100644
index 0000000..89ecb66
--- /dev/null
+++ b/components/mycelium/Cargo.lock
@@ -0,0 +1,3967 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom 0.2.15",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.2.15",
+ "once_cell",
+ "version_check",
+ "zerocopy 0.7.35",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "async-trait"
+version = "0.1.88"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "axum"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
+dependencies = [
+ "axum-core",
+ "bytes",
+ "form_urlencoded",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-extra"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
+dependencies = [
+ "axum",
+ "axum-core",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
+dependencies = [
+ "bincode_derive",
+ "serde",
+ "unty",
+]
+
+[[package]]
+name = "bincode_derive"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
+dependencies = [
+ "virtue",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+
+[[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
+[[package]]
+name = "blake3"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "borsh"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc"
+dependencies = [
+ "borsh-derive",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
+[[package]]
+name = "byte-unit"
+version = "5.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174"
+dependencies = [
+ "rust_decimal",
+ "serde",
+ "utf8-width",
+]
+
+[[package]]
+name = "bytecheck"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
+dependencies = [
+ "bytecheck_derive",
+ "ptr_meta",
+ "simdutf8",
+]
+
+[[package]]
+name = "bytecheck_derive"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "c2rust-bitfields"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b43c3f07ab0ef604fa6f595aa46ec2f8a22172c975e186f6f5bf9829a3b72c41"
+dependencies = [
+ "c2rust-bitfields-derive",
+]
+
+[[package]]
+name = "c2rust-bitfields-derive"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3cbc102e2597c9744c8bd8c15915d554300601c91a079430d309816b0912545"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cdn-meta"
+version = "0.1.0"
+source = "git+https://github.com/threefoldtech/mycelium-cdn-registry#2fac04710b60502a5165f1b1d90671730346e977"
+dependencies = [
+ "aes-gcm",
+ "bincode",
+ "serde",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "csv"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "dashmap"
+version = "6.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core 0.9.10",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "dlopen2"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa"
+dependencies = [
+ "libc",
+ "once_cell",
+ "winapi",
+]
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "etherparse"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff83a5facf1a7cbfef93cfb48d6d4fb6a1f42d8ac2341a96b3255acb4d4f860"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "faster-hex"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73"
+dependencies = [
+ "heapless",
+ "serde",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generator"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustversion",
+ "windows 0.48.0",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.13.3+wasi-0.2.2",
+ "wasm-bindgen",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "h2"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash 0.7.8",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+
+[[package]]
+name = "heapless"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
+dependencies = [
+ "hash32",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2 0.5.10",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.15.2",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-uring"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "ioctl-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c"
+
+[[package]]
+name = "ip_network_table-deps-treebitmap"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d"
+
+[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "iri-string"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jsonrpsee"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16"
+dependencies = [
+ "jsonrpsee-core",
+ "jsonrpsee-proc-macros",
+ "jsonrpsee-server",
+ "jsonrpsee-types",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "jsonrpsee-core"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "jsonrpsee-types",
+ "parking_lot 0.12.3",
+ "pin-project",
+ "rand 0.9.1",
+ "rustc-hash",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.12",
+ "tokio",
+ "tower",
+ "tracing",
+]
+
+[[package]]
+name = "jsonrpsee-proc-macros"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fa4f5daed39f982a1bb9d15449a28347490ad42b212f8eaa2a2a344a0dce9e9"
+dependencies = [
+ "heck",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "jsonrpsee-server"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38b0bcf407ac68d241f90e2d46041e6a06988f97fe1721fb80b91c42584fae6"
+dependencies = [
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
+ "pin-project",
+ "route-recognizer",
+ "serde",
+ "serde_json",
+ "soketto",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tower",
+ "tracing",
+]
+
+[[package]]
+name = "jsonrpsee-types"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca"
+dependencies = [
+ "http",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.12",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "left-right"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cabfddf3ad712b726484562039aa6fc2014bc1b5c088bb211b208052cf0439e6"
+dependencies = [
+ "loom",
+ "slab",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+
+[[package]]
+name = "libloading"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
+dependencies = [
+ "cfg-if",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "libredox"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
+dependencies = [
+ "bitflags 2.9.0",
+ "libc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "litemap"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+
+[[package]]
+name = "loom"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
+dependencies = [
+ "cfg-if",
+ "generator",
+ "scoped-tls",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "lru"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
+dependencies = [
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "matchit"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+dependencies = [
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "mycelium"
+version = "0.6.1"
+dependencies = [
+ "aes-gcm",
+ "ahash 0.8.11",
+ "arc-swap",
+ "axum",
+ "axum-extra",
+ "blake3",
+ "bytes",
+ "cdn-meta",
+ "dashmap",
+ "etherparse",
+ "faster-hex",
+ "futures",
+ "ip_network_table-deps-treebitmap",
+ "ipnet",
+ "left-right",
+ "libc",
+ "netdev",
+ "nix 0.29.0",
+ "nix 0.30.1",
+ "openssl",
+ "quinn",
+ "rand 0.9.1",
+ "rcgen",
+ "redis",
+ "reed-solomon-erasure",
+ "reqwest",
+ "rtnetlink",
+ "rustls",
+ "serde",
+ "tokio",
+ "tokio-openssl",
+ "tokio-stream",
+ "tokio-tun",
+ "tokio-util",
+ "tracing",
+ "tracing-logfmt",
+ "tracing-subscriber",
+ "tun",
+ "wintun 0.5.1",
+ "x25519-dalek",
+]
+
+[[package]]
+name = "mycelium-api"
+version = "0.6.1"
+dependencies = [
+ "async-trait",
+ "axum",
+ "base64",
+ "jsonrpsee",
+ "mycelium",
+ "mycelium-metrics",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "mycelium-cli"
+version = "0.6.1"
+dependencies = [
+ "base64",
+ "byte-unit",
+ "mycelium",
+ "mycelium-api",
+ "prettytable-rs",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tracing",
+ "urlencoding",
+]
+
+[[package]]
+name = "mycelium-metrics"
+version = "0.6.1"
+dependencies = [
+ "axum",
+ "mycelium",
+ "prometheus",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "netdev"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862209dce034f82a44c95ce2b5183730d616f2a68746b9c1959aa2572e77c0a1"
+dependencies = [
+ "dlopen2",
+ "ipnet",
+ "libc",
+ "netlink-packet-core",
+ "netlink-packet-route 0.22.0",
+ "netlink-sys",
+ "once_cell",
+ "system-configuration",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "netlink-packet-core"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4"
+dependencies = [
+ "anyhow",
+ "bitflags 2.9.0",
+ "byteorder",
+ "libc",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56d83370a96813d7c977f8b63054f1162df6e5784f1c598d689236564fb5a6f2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.9.0",
+ "byteorder",
+ "libc",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-utils"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "paste",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "netlink-proto"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60"
+dependencies = [
+ "bytes",
+ "futures",
+ "log",
+ "netlink-packet-core",
+ "netlink-sys",
+ "thiserror 2.0.12",
+]
+
+[[package]]
+name = "netlink-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23"
+dependencies = [
+ "bytes",
+ "futures",
+ "libc",
+ "log",
+ "tokio",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-src"
+version = "300.4.2+3.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.10",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.10",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pem"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
+dependencies = [
+ "base64",
+ "serde",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy 0.8.23",
+]
+
+[[package]]
+name = "prettytable-rs"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
+dependencies = [
+ "csv",
+ "encode_unicode",
+ "is-terminal",
+ "lazy_static",
+ "term",
+ "unicode-width",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "procfs"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
+dependencies = [
+ "bitflags 2.9.0",
+ "hex",
+ "procfs-core",
+ "rustix 0.38.44",
+]
+
+[[package]]
+name = "procfs-core"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
+dependencies = [
+ "bitflags 2.9.0",
+ "hex",
+]
+
+[[package]]
+name = "prometheus"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a"
+dependencies = [
+ "cfg-if",
+ "fnv",
+ "lazy_static",
+ "libc",
+ "memchr",
+ "parking_lot 0.12.3",
+ "procfs",
+ "thiserror 2.0.12",
+]
+
+[[package]]
+name = "ptr_meta"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
+dependencies = [
+ "ptr_meta_derive",
+]
+
+[[package]]
+name = "ptr_meta_derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2 0.5.10",
+ "thiserror 2.0.12",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
+dependencies = [
+ "bytes",
+ "getrandom 0.3.1",
+ "lru-slab",
+ "rand 0.9.1",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.12",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2 0.5.10",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radium"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.15",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.1",
+]
+
+[[package]]
+name = "rcgen"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49bc8ffa8a832eb1d7c8000337f8b0d2f4f2f5ec3cf4ddc26f125e3ad2451824"
+dependencies = [
+ "pem",
+ "ring",
+ "rustls-pki-types",
+ "time",
+ "yasna",
+]
+
+[[package]]
+name = "redis"
+version = "0.32.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1f66bf4cac9733a23bcdf1e0e01effbaaad208567beba68be8f67e5f4af3ee1"
+dependencies = [
+ "bytes",
+ "cfg-if",
+ "combine",
+ "futures-util",
+ "itoa",
+ "num-bigint",
+ "percent-encoding",
+ "pin-project-lite",
+ "ryu",
+ "sha1_smol",
+ "socket2 0.6.0",
+ "tokio",
+ "tokio-util",
+ "url",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
+dependencies = [
+ "bitflags 2.9.0",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
+dependencies = [
+ "getrandom 0.2.15",
+ "libredox",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "reed-solomon-erasure"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7263373d500d4d4f505d43a2a662d475a894aa94503a1ee28e9188b5f3960d4f"
+dependencies = [
+ "libm",
+ "lru",
+ "parking_lot 0.11.2",
+ "smallvec",
+ "spin",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.9",
+ "regex-syntax 0.8.5",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.5",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "rend"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
+dependencies = [
+ "bytecheck",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.12.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.15",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rkyv"
+version = "0.7.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
+dependencies = [
+ "bitvec",
+ "bytecheck",
+ "bytes",
+ "hashbrown 0.12.3",
+ "ptr_meta",
+ "rend",
+ "rkyv_derive",
+ "seahash",
+ "tinyvec",
+ "uuid",
+]
+
+[[package]]
+name = "rkyv_derive"
+version = "0.7.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "route-recognizer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
+
+[[package]]
+name = "rtnetlink"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbe0a03f6b9b483c67d4b328fc5d66c8db0b6aa274e0fa2def71b5e442a69acf"
+dependencies = [
+ "futures",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-route 0.24.0",
+ "netlink-packet-utils",
+ "netlink-proto",
+ "netlink-sys",
+ "nix 0.29.0",
+ "thiserror 1.0.69",
+ "tokio",
+]
+
+[[package]]
+name = "rust_decimal"
+version = "1.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555"
+dependencies = [
+ "arrayvec",
+ "borsh",
+ "bytes",
+ "num-traits",
+ "rand 0.8.5",
+ "rkyv",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags 2.9.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.15",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags 2.9.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.9.4",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.9.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha1_smol"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
+
+[[package]]
+name = "socket2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "soketto"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures",
+ "http",
+ "httparse",
+ "log",
+ "rand 0.8.5",
+ "sha1",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+dependencies = [
+ "bitflags 2.9.0",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[package]]
+name = "tempfile"
+version = "3.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.1",
+ "once_cell",
+ "rustix 1.0.7",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.3.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
+
+[[package]]
+name = "time-macros"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.46.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "io-uring",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "slab",
+ "socket2 0.5.10",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-openssl"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd"
+dependencies = [
+ "openssl",
+ "openssl-sys",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "tokio-tun"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be25a316a2d10d0ddd46de9ddd73cdb4262678e1e52f4a32a31421f1e2b816b4"
+dependencies = [
+ "libc",
+ "nix 0.29.0",
+ "thiserror 2.0.12",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+
+[[package]]
+name = "toml_edit"
+version = "0.22.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+dependencies = [
+ "bitflags 2.9.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-logfmt"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1f47d22deb79c3f59fcf2a1f00f60cbdc05462bf17d1cd356c1fefa3f444bd"
+dependencies = [
+ "nu-ansi-term 0.50.1",
+ "time",
+ "tracing",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
+dependencies = [
+ "matchers",
+ "nu-ansi-term 0.46.0",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tun"
+version = "0.6.1"
+source = "git+https://github.com/LeeSmet/rust-tun#ae4c222e7a7aba5752cb08d2dbaf67bbc669c26a"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "futures-core",
+ "ioctl-sys",
+ "libc",
+ "log",
+ "thiserror 1.0.69",
+ "tokio",
+ "tokio-util",
+ "wintun 0.3.2",
+]
+
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "unty"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8-width"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "virtue"
+version = "0.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasi"
+version = "0.13.3+wasi-0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-registry"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wintun"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29b83b0eca06dd125dbcd48a45327c708a6da8aada3d95a3f06db0ce4b17e0d4"
+dependencies = [
+ "c2rust-bitfields",
+ "libloading",
+ "log",
+ "thiserror 1.0.69",
+ "windows 0.51.1",
+]
+
+[[package]]
+name = "wintun"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da99be64b5aa3de869c16977994314d0759a698d9a73ab0a5b1d52e2282033ae"
+dependencies = [
+ "c2rust-bitfields",
+ "libloading",
+ "log",
+ "thiserror 1.0.69",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+dependencies = [
+ "bitflags 2.9.0",
+]
+
+[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
+
+[[package]]
+name = "x25519-dalek"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
+dependencies = [
+ "curve25519-dalek",
+ "rand_core 0.6.4",
+ "serde",
+ "zeroize",
+]
+
+[[package]]
+name = "yasna"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
+dependencies = [
+ "time",
+]
+
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "zerocopy-derive 0.7.35",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
+dependencies = [
+ "zerocopy-derive 0.8.23",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
diff --git a/components/mycelium/Cargo.toml b/components/mycelium/Cargo.toml
new file mode 100644
index 0000000..77523cf
--- /dev/null
+++ b/components/mycelium/Cargo.toml
@@ -0,0 +1,9 @@
+[workspace]
+members = ["mycelium", "mycelium-metrics", "mycelium-api", "mycelium-cli"]
+exclude = ["myceliumd", "myceliumd-private", "mycelium-ui", "mobile"]
+resolver = "2"
+
+
+[profile.release]
+lto = "fat"
+codegen-units = 1
diff --git a/components/mycelium/LICENSE b/components/mycelium/LICENSE
new file mode 100644
index 0000000..b6418e9
--- /dev/null
+++ b/components/mycelium/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright TF Tech NV
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/components/mycelium/README.md b/components/mycelium/README.md
new file mode 100644
index 0000000..57f5966
--- /dev/null
+++ b/components/mycelium/README.md
@@ -0,0 +1,229 @@
+# Mycelium
+
+Mycelium is an IPv6 overlay network written in Rust. Each node that joins the overlay
+network will receive an overlay network IP in the 400::/7 range.
+
+## Features
+
+- Mycelium, is locality aware, it will look for the shortest path between nodes
+- All traffic between the nodes is end-2-end encrypted
+- Traffic can be routed over nodes of friends, location aware
+- If a physical link goes down Mycelium will automatically reroute your traffic
+- The IP address is IPV6 and linked to private key
+- A simple reliable messagebus is implemented on top of Mycelium
+- Mycelium has multiple ways how to communicate quic, tcp, ... and we are working on holepunching for Quick which means P2P traffic without middlemen for NATted networks e.g. most homes
+- Scalability is very important for us, we tried many overlay networks before and got stuck on all of them, we are trying to design a network which scales to a planetary level
+- You can run mycelium without TUN and only use it as reliable message bus.
+
+> We are looking for lots of testers to push the system
+
+> [see here for docs](https://github.com/threefoldtech/mycelium/tree/master/docs)
+
+## Running
+
+> Currently, Linux, macOS and Windows are supported.
+
+### Linux and macOS
+
+Get an useable binary, either by downloading [an artifact from a release](https://github.com/threefoldtech/mycelium/releases),
+or by [checking out and building the code yourself](#developing).
+
+### Windows
+
+Download the [mycelium_installer.msi](https://github.com/threefoldtech/mycelium/releases/latest/download/mycelium_installer.msi) and run the installer.
+
+### Run Mycelium
+
+Once you have an useable binary, simply start it. If you want to connect to other
+nodes, you can specify their listening address as part of the command (combined
+with the protocol they are listening on, usually TCP). Check the next section if
+you want to connect to hosted public nodes.
+
+```sh
+mycelium --peers tcp://188.40.132.242:9651 quic://185.69.166.8:9651
+
+#other example with other tun interface if utun3 (the default) would already be used
+#also here we use sudo e.g. on OSX
+sudo mycelium --peers tcp://188.40.132.242:9651 quic://185.69.166.8:9651 --tun-name utun9
+
+```
+
+By default, the node will listen on port `9651`, though this can be overwritten
+with the `-p` flag.
+
+To check your own info
+
+```bash
+mycelium inspect --json
+{
+ "publicKey": "abd16194646defe7ad2318a0f0a69eb2e3fe939c3b0b51cf0bb88bb8028ecd1d",
+ "address": "5c4:c176:bf44:b2ab:5e7e:f6a:b7e2:11ca"
+}
+# test that network works, ping to anyone in the network
+ping6 54b:83ab:6cb5:7b38:44ae:cd14:53f3:a907
+```
+
+The node uses a `x25519` key pair from which its identity is derived. The private key of this key pair
+is saved in a local file (32 bytes in binary format). You can specify the path to this file with the
+`-k` flag. By default, the file is saved in the current working directory as `priv_key.bin`.
+
+### Running without TUN interface
+
+It is possible to run the system without creating a TUN interface, by starting with the `--no-tun` flag.
+Obviously, this means that your node won't be able to send or receive L3 traffic. There is no interface
+to send packets on, and consequently no interface to send received packets out of. From the point of
+other nodes, your node will simply drop all incoming L3 traffic destined for it. The node **will still
+route traffic** as normal. It takes part in routing, exchanges route info, and forwards packets not
+intended for itself.
+
+The node also still allows access to the [message subsystem](#message-system).
+
+## Configuration
+
+Mycelium can be started with an **optional** configuration file using the `--config-file`
+option, which offers the same capabilities as the command line arguments.
+
+If no configuration file is specified with `--config-file`, Mycelium will search for one
+in a default location based on the operating system:
+
+- Linux: $HOME/.config/mycelium.toml
+- Windows: %APPDATA%/ThreeFold Tech/Mycelium/mycelium.toml
+- Mac OS: $HOME/Library/Application Support/ThreeFold Tech/Mycelium/mycelium.toml
+
+Command line arguments will override any settings found in the configuration file.
+
+## Hosted public nodes v0.6.x
+
+A couple of public nodes are provided, which can be freely connected to. This allows
+anyone to join the global network. These are hosted in multiple geographic regions,
+on both IPv4 and IPv6, and supporting both the Tcp and Quic protocols. The nodes
+are the following:
+
+| Node ID | Region | IPv4 | IPv6 | Tcp port | Quic port | Mycelium IP |
+| ------- | ------- | -------------- | --------------------------------- | -------- | --------- | -------------------------------------- |
+| 01 | DE | 188.40.132.242 | 2a01:4f8:221:1e0b::2 | 9651 | 9651 | 54b:83ab:6cb5:7b38:44ae:cd14:53f3:a907 |
+| 02 | DE | 136.243.47.186 | 2a01:4f8:212:fa6::2 | 9651 | 9651 | 40a:152c:b85b:9646:5b71:d03a:eb27:2462 |
+| 03 | BE | 185.69.166.7 | 2a02:1802:5e:0:ec4:7aff:fe51:e80d | 9651 | 9651 | 597:a4ef:806:b09:6650:cbbf:1b68:cc94 |
+| 04 | BE | 185.69.166.8 | 2a02:1802:5e:0:ec4:7aff:fe51:e36b | 9651 | 9651 | 549:8bce:fa45:e001:cbf8:f2e2:2da6:a67c |
+| 05 | FI | 65.21.231.58 | 2a01:4f9:6a:1dc5::2 | 9651 | 9651 | 410:2778:53bf:6f41:af28:1b60:d7c0:707a |
+| 06 | FI | 65.109.18.113 | 2a01:4f9:5a:1042::2 | 9651 | 9651 | 488:74ac:8a31:277b:9683:c8e:e14f:79a7 |
+| 07 | US-EAST | 209.159.146.190 | 2604:a00:50:17b:9e6b:ff:fe1f:e054 | 9651 | 9651 | 4ab:a385:5a4e:ef8f:92e0:1605:7cb6:24b2 |
+| 08 | US-WEST | 5.78.122.16 | 2a01:4ff:1f0:8859::1 | 9651 | 9651 | 4de:b695:3859:8234:d04c:5de6:8097:c27c |
+| 09 | SG | 5.223.43.251 | 2a01:4ff:2f0:3621::1 | 9651 | 9651 | 5eb:c711:f9ab:eb24:ff26:e392:a115:1c0e |
+| 10 | IND | 142.93.217.194 | 2400:6180:100:d0::841:2001 | 9651 | 9651 | 445:465:fe81:1e2b:5420:a029:6b0:9f61 |
+
+These nodes are all interconnected, so 2 peers who each connect to a different node
+(or set of disjoint nodes) will still be able to reach each other. For optimal performance,
+it is recommended to connect to all of the above at once however. An example connection
+string could be:
+
+`--peers tcp://188.40.132.242:9651 "quic://[2a01:4f8:212:fa6::2]:9651" tcp://185.69.166.7:9651 "quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651" tcp://65.21.231.58:9651 "quic://[2a01:4f9:5a:1042::2]:9651" "tcp://[2604:a00:50:17b:9e6b:ff:fe1f:e054]:9651" quic://5.78.122.16:9651 "tcp://[2a01:4ff:2f0:3621::1]:9651" quic://142.93.217.194:9651`
+
+It is up to the user to decide which peers he wants to use, over which protocol.
+Note that quotation may or may not be required, depending on which shell is being
+used. IPv6 addresses should of course only be used if your ISP provides you with
+IPv6 connectivity.
+
+### Private network
+
+Mycelium supports running a private network, in which you must know the network name
+and a PSK (pre shared key) to connect to nodes in the network. For more info, check
+out [the relevant docs](/docs/private_network.md).
+
+## API
+
+The node starts an HTTP API, which by default listens on `localhost:8989`. A different
+listening address can be specified on the CLI when starting the system through the
+`--api-addr` flag. The API allows access to [send and receive messages](#message-system),
+and will later be expanded to allow admin functionality on the system. Note that
+message are sent using the identity of the node, and a future admin API can be
+used to change the system behavior. As such, care should be taken that this API
+is not accessible to unauthorized users.
+
+## Message system
+
+A message system is provided which allows users to send a message, which is essentially just "some data"
+to a remote. Since the system is end-to-end encrypted, a receiver of a message is sure of the authenticity
+and confidentiality of the content. The system does not interpret the data in any way and handles it
+as an opaque block of bytes. Messages are sent with a deadline. This means the system continuously
+tries to send (part of) the message, until it either succeeds, or the deadline expires. This happens
+similar to the way TCP handles data. Messages are transmitted in chunks, which are embedded in the
+same data stream used by L3 packets. As such, intermediate nodes can't distinguish between regular L3
+and message data.
+The primary way to interact with the message system is through [the API](#api). The message API is
+documented in [an OpenAPI spec in the docs folder](docs/api.yaml). For some more info about how to
+use the message system, see [the message docs](/docs/message.md).
+
+Messages can be categorized by topics, which can be configured with whitelisted subnets and socket forwarding paths.
+For detailed information on how to configure topics, see the [Topic Configuration Guide](/docs/topic_configuration.md).
+use the message system, see [the message docs](/docs/message.md).
+
+## Inspecting node keys
+
+Using the `inspect` subcommand, you can view the address associated with a public key. If no public key is provided, the node will show
+its own public key. In either case, the derived address is also printed. You can specify the path to the private key with the `-k` flag.
+If the file does not exist, a new private key will be generated. The optional `--json` flag can be used to print the information in json
+format.
+
+```sh
+mycelium inspect a47c1d6f2a15b2c670d3a88fbe0aeb301ced12f7bcb4c8e3aa877b20f8559c02
+Public key: a47c1d6f2a15b2c670d3a88fbe0aeb301ced12f7bcb4c8e3aa877b20f8559c02
+Address: 47f:b2c5:a944:4dad:9cb1:da4:8bf7:7e65
+```
+
+```sh
+mycelium inspect --json
+{
+ "publicKey": "955bf6bea5e1150fd8e270c12e5b2fc08f08f7c5f3799d10550096cc137d671b",
+ "address": "54f:b680:ba6e:7ced:355f:346f:d97b:eecb"
+}
+```
+
+## Developing
+
+This project is built in Rust, and you must have a rust compiler to build the code
+yourself. Please refer to [the official rust documentation](https://www.rust-lang.org/)
+for information on how to install `rustc` and `cargo`. This project is a workspace,
+however the binaries (`myceliumd` and `myceliumd-private`) are explicitly _not_
+part of this workspace. The reason for this is the way the cargo resolver unifies
+features. Making both binaries part of the workspace would make the library build
+for the regular binary include the code for the private network, and since that
+is internal code it won't be removed at link time.
+
+First make sure you have cloned the repo
+
+```sh
+git clone https://github.com/threefoldtech/mycelium.git
+cd mycelium
+```
+
+If you only want to build the library, you can do so from the root of the repo
+
+```sh
+cargo build
+```
+
+If you instead want to build a binary, that must be done from the appropriate subdirectory
+
+```sh
+cd myceliumd
+cargo build
+```
+
+Refer to the README files in those directories for more info.
+
+In case a release build is required, the `--release` flag can be added to the cargo
+command (`cargo build --release`).
+
+## Cross compilation
+
+For cross compilation, it is advised to use the [`cross`](https://github.com/cross-rs/cross)
+project. Alternatively, the standard way of cross compiling in rust can be used
+(by specifying the `--target` flag in the `cargo build` command). This might require
+setting some environment variables or local cargo config. On top of this, you should
+also provide the `vendored-openssl` feature flag to build and statically link a copy
+of openssl.
+
+## Remarks
+
+- The overlay network uses some of the core principles of the Babel routing protocol ().
diff --git a/components/mycelium/config_example.toml b/components/mycelium/config_example.toml
new file mode 100644
index 0000000..4e79779
--- /dev/null
+++ b/components/mycelium/config_example.toml
@@ -0,0 +1,22 @@
+# REMOVE/ADD COMMENT TO ENABLE/DISABLE LINE
+
+peers = [
+ "tcp://188.40.132.242:9651",
+ "quic://[2a01:4f8:212:fa6::2]:9651",
+ "quic://185.69.166.7:9651",
+ "tcp://[2a02:1802:5e:0:8c9e:7dff:fec9:f0d2]:9651",
+ "quic://65.21.231.58:9651",
+ "tcp://[2a01:4f9:5a:1042::2]:9651",
+]
+api_addr = "127.0.0.1:8989"
+tcp_listen_port = 9651
+quic_listen_port = 9651
+tun_name = "mycelium"
+disable_peer_discovery = false
+no_tun = false
+#metrics_api_address = 0.0.0.0:9999
+#firewall_mark = 30
+
+## Options below only apply when myceliumd-private is used
+#network_name = "private network name"
+#network_key_file = "path_to_key_file"
diff --git a/components/mycelium/default.nix b/components/mycelium/default.nix
new file mode 100644
index 0000000..2b62571
--- /dev/null
+++ b/components/mycelium/default.nix
@@ -0,0 +1,14 @@
+(
+ import
+ (
+ let
+ lock = builtins.fromJSON (builtins.readFile ./flake.lock);
+ in
+ fetchTarball {
+ url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
+ sha256 = lock.nodes.flake-compat.locked.narHash;
+ }
+ )
+ {src = ./.;}
+)
+.defaultNix
diff --git a/components/mycelium/docs/api.yaml b/components/mycelium/docs/api.yaml
new file mode 100644
index 0000000..d1005ed
--- /dev/null
+++ b/components/mycelium/docs/api.yaml
@@ -0,0 +1,990 @@
+openapi: 3.0.2
+info:
+ version: "1.0.0"
+
+ title: Mycelium management
+ contact:
+ url: "https://github.com/threefoldtech/mycelium"
+ license:
+ name: Apache 2.0
+ url: "https://github.com/threefoldtech/mycelium/blob/master/LICENSE"
+
+ description: |
+ This is the specification of the **mycelium** management API. It is used to perform admin tasks on the system, and
+ to perform administrative duties.
+
+externalDocs:
+ description: For full documentation, check out the mycelium github repo.
+ url: "https://github.com/threefoldtech/mycelium"
+
+tags:
+ - name: Admin
+ description: Administrative operations
+ - name: Peer
+ description: Operations related to peer management
+ - name: Route
+ description: Operations related to network routes
+ - name: Message
+ description: Operations on the embedded message subsystem
+ - name: Topic
+ description: Operations related to message topic configuration
+
+servers:
+ - url: "http://localhost:8989"
+
+paths:
+ "/api/v1/admin":
+ get:
+ tags:
+ - Admin
+ summary: Get general info about the node
+ description: |
+ Get general info about the node, which is not related to other more specific functionality
+ operationId: getInfo
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Info"
+
+ "/api/v1/admin/peers":
+ get:
+ tags:
+ - Admin
+ - Peer
+ summary: List known peers
+ description: |
+ List all peers known in the system, and info about their connection.
+ This includes the endpoint, how we know about the peer, the connection state, and if the connection is alive the amount
+ of bytes we've sent to and received from the peer.
+ operationId: getPeers
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/PeerStats"
+ post:
+ tags:
+ - Admin
+ - Peer
+ summary: Add a new peer
+ description: |
+ Add a new peer identified by the provided endpoint.
+ The peer is added to the list of known peers. It will eventually be connected
+ to by the standard connection loop of the peer manager. This means that a peer
+ which can't be connected to will stay in the system, as it might be reachable
+ later on.
+ operationId: addPeer
+ responses:
+ "204":
+ description: Peer added
+ "400":
+ description: Malformed endpoint
+ content:
+ text/plain:
+ schema:
+ type: string
+ description: Details about why the endpoint is not valid
+ "409":
+ description: Peer already exists
+ content:
+ text/plain:
+ schema:
+ type: string
+ description: message saying we already know this peer
+
+ "/api/v1/admin/peers/{endpoint}":
+ delete:
+ tags:
+ - Admin
+ - Peer
+ summary: Remove an existing peer
+ description: |
+ Remove an existing peer identified by the provided endpoint.
+ The peer is removed from the list of known peers. If a connection to it
+ is currently active, it will be closed.
+ operationId: deletePeer
+ responses:
+ "204":
+ description: Peer removed
+ "400":
+ description: Malformed endpoint
+ content:
+ text/plain:
+ schema:
+ type: string
+ description: Details about why the endpoint is not valid
+ "404":
+ description: Peer doesn't exist
+ content:
+ text/plain:
+ schema:
+ type: string
+ description: message saying we don't know this peer
+
+ "/api/v1/admin/routes/selected":
+ get:
+ tags:
+ - Admin
+ - Route
+ summary: List all selected routes
+ description: |
+ List all selected routes in the system, and their next hop identifier, metric and sequence number.
+ It is possible for a route to be selected and have an infinite metric. This route will however not forward packets.
+ operationId: getSelectedRoutes
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Route"
+
+ "/api/v1/admin/routes/fallback":
+ get:
+ tags:
+ - Admin
+ - Route
+ summary: List all active fallback routes
+ description: |
+ List all fallback routes in the system, and their next hop identifier, metric and sequence number.
+ These routes are available to be selected in case the selected route for a destination suddenly fails, or gets retracted.
+ operationId: getFallbackRoutes
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Route"
+
+ "/api/v1/admin/routes/queried":
+ get:
+ tags:
+ - Admin
+ - Route
+ summary: List all currently queried subnets
+ description: |
+ List all currently queried subnets in the system, and the amount of seconds until the query expires.
+ These subnets are actively being probed in the network. If no route to them is discovered before the query expires,
+ they will be marked as not reachable temporarily.
+ operationId: getQueriedSubnets
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/QueriedSubnet"
+
+ "/api/v1/admin/routes/no_route":
+ get:
+ tags:
+ - Admin
+ - Route
+ summary: List all subnets which are explicitly marked as no route
+ description: |
+ List all subnets in the system which are marked no route, and the amount of seconds until the query expires.
+ These subnets have recently been probed in the network, and no route for them was discovered in time. No more
+ route requests will be send for these subnets until the entry expires.
+ operationId: getNoRouteEntries
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/NoRouteSubnet"
+
+ "/api/v1/messages":
+ get:
+ tags:
+ - Message
+ summary: Get a message from the inbound message queue
+ description: |
+ Get a message from the inbound message queue. By default, the message is removed from the queue and won't be shown again.
+ If the peek query parameter is set to true, the message will be peeked, and the next call to this endpoint will show the same message.
+ This method returns immediately by default: a message is returned if one is ready, and if there isn't nothing is returned. If the timeout
+ query parameter is set, this call won't return for the given amount of seconds, unless a message is received
+ operationId: popMessage
+ parameters:
+ - in: query
+ name: peek
+ required: false
+ schema:
+ type: boolean
+ description: Whether to peek the message or not. If this is true, the message won't be removed from the inbound queue when it is read
+ example: true
+ - in: query
+ name: timeout
+ required: false
+ schema:
+ type: integer
+ format: int64
+ minimum: 0
+ description: |
+ Amount of seconds to wait for a message to arrive if one is not available. Setting this to 0 is valid and will return
+ a message if present, or return immediately if there isn't
+ example: 60
+ - in: query
+ name: topic
+ required: false
+ schema:
+ type: string
+ format: byte
+ minLength: 0
+ maxLength: 340
+ description: |
+ Optional filter for loading messages. If set, the system checks if the message has the given string at the start. This way
+ a topic can be encoded.
+ example: example.topic
+ responses:
+ "200":
+ description: Message retrieved
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InboundMessage"
+ "204":
+ description: No message ready
+ post:
+ tags:
+ - Message
+ summary: Submit a new message to the system.
+ description: |
+ Push a new message to the systems outbound message queue. The system will continuously attempt to send the message until
+ it is either fully transmitted, or the send deadline is expired.
+ operationId: pushMessage
+ parameters:
+ - in: query
+ name: reply_timeout
+ required: false
+ schema:
+ type: integer
+ format: int64
+ minimum: 0
+ description: |
+ Amount of seconds to wait for a reply to this message to come in. If not set, the system won't wait for a reply and return
+ the ID of the message, which can be used later. If set, the system will wait for at most the given amount of seconds for a reply
+ to come in. If a reply arrives, it is returned to the client. If not, the message ID is returned for later use.
+ example: 120
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PushMessageBody"
+ responses:
+ "200":
+ description: We received a reply within the specified timeout
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/InboundMessage"
+
+ "201":
+ description: Message pushed successfully, and not waiting for a reply
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PushMessageResponseId"
+ "408":
+ description: The system timed out waiting for a reply to the message
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PushMessageResponseId"
+
+ "/api/v1/messages/reply/{id}":
+ post:
+ tags:
+ - Message
+ summary: Reply to a message with the given ID
+ description: |
+ Submits a reply message to the system, where ID is an id of a previously received message. If the sender is waiting
+ for a reply, it will bypass the queue of open messages.
+ operationId: pushMessageReply
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ type: string
+ format: hex
+ minLength: 16
+ maxLength: 16
+ example: abcdef0123456789
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PushMessageBody"
+ responses:
+ "204":
+ description: successfully submitted the reply
+
+ "/api/v1/messages/status/{id}":
+ get:
+ tags:
+ - Message
+ summary: Get the status of an outbound message
+ description: |
+ Get information about the current state of an outbound message. This can be used to check the transmission
+ state, size and destination of the message.
+ operationId: getMessageInfo
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ type: string
+ format: hex
+ minLength: 16
+ maxLength: 16
+ example: abcdef0123456789
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MessageStatusResponse"
+ "404":
+ description: Message not found
+
+ "/api/v1/messages/topics/default":
+ get:
+ tags:
+ - Message
+ - Topic
+ summary: Get the default topic action
+ description: |
+ Get the default action for topics that are not explicitly configured (accept or reject).
+ operationId: getDefaultTopicAction
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/DefaultTopicActionResponse"
+ put:
+ tags:
+ - Message
+ - Topic
+ summary: Set the default topic action
+ description: |
+ Set the default action for topics that are not explicitly configured (accept or reject).
+ operationId: setDefaultTopicAction
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/DefaultTopicActionRequest"
+ responses:
+ "204":
+ description: Default topic action set successfully
+
+ "/api/v1/messages/topics":
+ get:
+ tags:
+ - Message
+ - Topic
+ summary: Get all configured topics
+ description: |
+ Get all topics that are explicitly configured in the system.
+ operationId: getTopics
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ format: byte
+ description: Base64 encoded topic identifier
+ post:
+ tags:
+ - Message
+ - Topic
+ summary: Add a new topic
+ description: |
+ Add a new topic to the system's whitelist.
+ operationId: addTopic
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: string
+ format: byte
+ description: The topic to add
+ responses:
+ "201":
+ description: Topic added successfully
+
+ "/api/v1/messages/topics/{topic}":
+ delete:
+ tags:
+ - Message
+ - Topic
+ summary: Remove a topic
+ description: |
+ Remove a topic from the system's whitelist.
+ operationId: removeTopic
+ parameters:
+ - in: path
+ name: topic
+ required: true
+ schema:
+ type: string
+ format: byte
+ description: The topic to remove (base64 encoded)
+ responses:
+ "204":
+ description: Topic removed successfully
+ "400":
+ description: Invalid topic format
+
+ "/api/v1/messages/topics/{topic}/sources":
+ get:
+ tags:
+ - Message
+ - Topic
+ summary: Get sources for a topic
+ description: |
+ Get all sources (subnets) that are allowed to send messages for a specific topic.
+ operationId: getTopicSources
+ parameters:
+ - in: path
+ name: topic
+ required: true
+ schema:
+ type: string
+ format: byte
+ description: The topic to get sources for (base64 encoded)
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ description: Subnet in CIDR notation
+ "400":
+ description: Invalid topic format
+ post:
+ tags:
+ - Message
+ - Topic
+ summary: Add a source to a topic
+ description: |
+ Add a source (subnet) that is allowed to send messages for a specific topic.
+ operationId: addTopicSource
+ parameters:
+ - in: path
+ name: topic
+ required: true
+ schema:
+ type: string
+ format: byte
+ description: The topic to add a source to (base64 encoded)
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TopicSourceRequest"
+ responses:
+ "204":
+ description: Source added successfully
+ "400":
+ description: Invalid topic format or subnet
+
+ "/api/v1/messages/topics/{topic}/sources/{subnet}":
+ delete:
+ tags:
+ - Message
+ - Topic
+ summary: Remove a source from a topic
+ description: |
+ Remove a source (subnet) that is allowed to send messages for a specific topic.
+ operationId: removeTopicSource
+ parameters:
+ - in: path
+ name: topic
+ required: true
+ schema:
+ type: string
+ format: byte
+ description: The topic to remove a source from (base64 encoded)
+ - in: path
+ name: subnet
+ required: true
+ schema:
+ type: string
+ description: The subnet to remove as a source
+ responses:
+ "204":
+ description: Source removed successfully
+ "400":
+ description: Invalid topic format or subnet
+
+ "/api/v1/messages/topics/{topic}/forward":
+ get:
+ tags:
+ - Message
+ - Topic
+ summary: Get the forward socket for a topic
+ description: |
+ Get the socket path where messages for a specific topic are forwarded to.
+ operationId: getTopicForwardSocket
+ parameters:
+ - in: path
+ name: topic
+ required: true
+ schema:
+ type: string
+ format: byte
+ description: The topic to get the forward socket for (base64 encoded)
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ type: string
+ nullable: true
+ description: The socket path where messages are forwarded to
+ "400":
+ description: Invalid topic format
+ put:
+ tags:
+ - Message
+ - Topic
+ summary: Set the forward socket for a topic
+ description: |
+ Set the socket path where messages for a specific topic should be forwarded to.
+ operationId: setTopicForwardSocket
+ parameters:
+ - in: path
+ name: topic
+ required: true
+ schema:
+ type: string
+ format: byte
+ description: The topic to set the forward socket for (base64 encoded)
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TopicForwardSocketRequest"
+ responses:
+ "204":
+ description: Forward socket set successfully
+ "400":
+ description: Invalid topic format
+ delete:
+ tags:
+ - Message
+ - Topic
+ summary: Remove the forward socket for a topic
+ description: |
+ Remove the socket path where messages for a specific topic are forwarded to.
+ operationId: removeTopicForwardSocket
+ parameters:
+ - in: path
+ name: topic
+ required: true
+ schema:
+ type: string
+ format: byte
+ description: The topic to remove the forward socket for (base64 encoded)
+ responses:
+ "204":
+ description: Forward socket removed successfully
+ "400":
+ description: Invalid topic format
+ "/api/v1/pubkey/{mycelium_ip}":
+ get:
+ summary: Get the pubkey from node ip
+ description: |
+ Get the node's public key from it's IP address.
+ operationId: getPublicKeyFromIp
+ parameters:
+ - in: path
+ name: mycelium_ip
+ required: true
+ schema:
+ type: string
+ format: ipv6
+ example: 5fd:7636:b80:9ad0::1
+ responses:
+ "200":
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/PublicKeyResponse"
+ "404":
+ description: Public key not found
+
+components:
+ schemas:
+ Info:
+ description: General information about a node
+ type: object
+ properties:
+ nodeSubnet:
+ description: The subnet owned by the node and advertised to peers
+ type: string
+ example: 54f:b680:ba6e:7ced::/64
+ nodePubkey:
+ description: The public key of the node
+ type: string
+ format: hex
+ minLength: 64
+ maxLength: 64
+ example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf
+
+ Endpoint:
+ description: Identification to connect to a peer
+ type: object
+ properties:
+ proto:
+ description: Protocol used
+ type: string
+ enum:
+ - "tcp"
+ - "quic"
+ example: tcp
+ socketAddr:
+ description: The socket address used
+ type: string
+ example: 192.0.2.6:9651
+
+ PeerStats:
+ description: Info about a peer
+ type: object
+ properties:
+ endpoint:
+ $ref: "#/components/schemas/Endpoint"
+ type:
+ description: How we know about this peer
+ type: string
+ enum:
+ - "static"
+ - "inbound"
+ - "linkLocalDiscovery"
+ example: static
+ connectionState:
+ description: The current state of the connection to the peer
+ type: string
+ enum:
+ - "alive"
+ - "connecting"
+ - "dead"
+ example: alive
+ txBytes:
+ description: The amount of bytes transmitted to this peer
+ type: integer
+ format: int64
+ minimum: 0
+ example: 464531564
+ rxBytes:
+ description: The amount of bytes received from this peer
+ type: integer
+ format: int64
+ minimum: 0
+ example: 64645089
+
+ Route:
+ description: Information about a route
+ type: object
+ properties:
+ subnet:
+ description: The overlay subnet for which this is the route
+ type: string
+ example: 469:1348:ab0c:a1d8::/64
+ nextHop:
+ description: A way to identify the next hop of the route, where forwarded packets will be sent
+ type: string
+ example: TCP 203.0.113.2:60128 <-> 198.51.100.27:9651
+ metric:
+ description: The metric of the route, an estimation of how long the packet will take to arrive at its final destination
+ oneOf:
+ - description: A finite metric value
+ type: integer
+ format: int32
+ minimum: 0
+ maximum: 65534
+ example: 13
+ - description: An infinite (unreachable) metric. This is always `infinite`
+ type: string
+ example: infinite
+ seqno:
+ description: the sequence number advertised with this route by the source
+ type: integer
+ format: int32
+ minimum: 0
+ maximum: 65535
+ example: 1
+
+ QueriedSubnet:
+ description: Information about a subnet currently being queried
+ type: object
+ properties:
+ subnet:
+ description: The overlay subnet which we are currently querying
+ type: string
+ example: 503:5478:df06:d79a::/64
+ expiration:
+ description: The amount of seconds until the query expires
+ type: string
+ example: "37"
+
+ NoRouteSubnet:
+ description: Information about a subnet which is marked as no route
+ type: object
+ properties:
+ subnet:
+ description: The overlay subnet which is marked
+ type: string
+ example: 503:5478:df06:d79a::/64
+ expiration:
+ description: The amount of seconds until the entry expires
+ type: string
+ example: "37"
+
+ InboundMessage:
+ description: A message received by the system
+ type: object
+ properties:
+ id:
+ description: Id of the message, hex encoded
+ type: string
+ format: hex
+ minLength: 16
+ maxLength: 16
+ example: 0123456789abcdef
+ srcIp:
+ description: Sender overlay IP address
+ type: string
+ format: ipv6
+ example: 449:abcd:0123:defa::1
+ srcPk:
+ description: Sender public key, hex encoded
+ type: string
+ format: hex
+ minLength: 64
+ maxLength: 64
+ example: fedbca9876543210fedbca9876543210fedbca9876543210fedbca9876543210
+ dstIp:
+ description: Receiver overlay IP address
+ type: string
+ format: ipv6
+ example: 34f:b680:ba6e:7ced:355f:346f:d97b:eecb
+ dstPk:
+ description: Receiver public key, hex encoded. This is the public key of the system
+ type: string
+ format: hex
+ minLength: 64
+ maxLength: 64
+ example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf
+ topic:
+ description: An optional message topic
+ type: string
+ format: byte
+ minLength: 0
+ maxLength: 340
+ example: hpV+
+ payload:
+ description: The message payload, encoded in standard alphabet base64
+ type: string
+ format: byte
+ example: xuV+
+
+ PushMessageBody:
+ description: A message to send to a given receiver
+ type: object
+ properties:
+ dst:
+ $ref: "#/components/schemas/MessageDestination"
+ topic:
+ description: An optional message topic
+ type: string
+ format: byte
+ minLength: 0
+ maxLength: 340
+ example: hpV+
+ payload:
+ description: The message to send, base64 encoded
+ type: string
+ format: byte
+ example: xuV+
+
+ MessageDestination:
+ oneOf:
+ - description: An IP in the subnet of the receiver node
+ type: object
+ properties:
+ ip:
+ description: The target IP of the message
+ format: ipv6
+ example: 449:abcd:0123:defa::1
+ - description: The hex encoded public key of the receiver node
+ type: object
+ properties:
+ pk:
+ description: The hex encoded public key of the target node
+ type: string
+ minLength: 64
+ maxLength: 64
+ example: bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32
+
+ PushMessageResponseId:
+ description: The ID generated for a message after pushing it to the system
+ type: object
+ properties:
+ id:
+ description: Id of the message, hex encoded
+ type: string
+ format: hex
+ minLength: 16
+ maxLength: 16
+ example: 0123456789abcdef
+
+ MessageStatusResponse:
+ description: Information about an outbound message
+ type: object
+ properties:
+ dst:
+ description: IP address of the receiving node
+ type: string
+ format: ipv6
+ example: 449:abcd:0123:defa::1
+ state:
+ $ref: "#/components/schemas/TransmissionState"
+ created:
+ description: Unix timestamp of when this message was created
+ type: integer
+ format: int64
+ example: 1649512789
+ deadline:
+ description: Unix timestamp of when this message will expire. If the message is not received before this, the system will give up
+ type: integer
+ format: int64
+ example: 1649513089
+ msgLen:
+ description: Length of the message in bytes
+ type: integer
+ minimum: 0
+ example: 27
+
+ TransmissionState:
+ description: The state of an outbound message in it's lifetime
+ oneOf:
+ - type: string
+ enum: ["pending", "received", "read", "aborted"]
+ example: "received"
+ - type: object
+ properties:
+ sending:
+ type: object
+ properties:
+ pending:
+ type: integer
+ minimum: 0
+ example: 5
+ sent:
+ type: integer
+ minimum: 0
+ example: 17
+ acked:
+ type: integer
+ minimum: 0
+ example: 3
+ example: "received"
+
+ PublicKeyResponse:
+ description: Public key requested based on a node's IP
+ type: object
+ properties:
+ NodePubKey:
+ type: string
+ format: hex
+ minLength: 64
+ maxLength: 64
+ example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf
+
+ DefaultTopicActionResponse:
+ description: Response for the default topic action
+ type: object
+ properties:
+ accept:
+ description: Whether unconfigured topics are accepted by default
+ type: boolean
+ example: true
+
+ DefaultTopicActionRequest:
+ description: Request to set the default topic action
+ type: object
+ properties:
+ accept:
+ description: Whether to accept unconfigured topics by default
+ type: boolean
+ example: true
+
+ TopicInfo:
+ description: Information about a configured topic
+ type: object
+ properties:
+ topic:
+ description: The topic identifier (base64 encoded)
+ type: string
+ format: byte
+ example: "ZXhhbXBsZS50b3BpYw=="
+ sources:
+ description: List of subnets that are allowed to send messages for this topic
+ type: array
+ items:
+ type: string
+ example: "503:5478:df06:d79a::/64"
+ forward_socket:
+ description: Optional socket path where messages for this topic are forwarded to
+ type: string
+ nullable: true
+ example: "/var/run/mycelium/topic_socket"
+
+ TopicSourceRequest:
+ description: Request to add a source to a topic whitelist
+ type: object
+ properties:
+ subnet:
+ description: The subnet to add as a source in CIDR notation
+ type: string
+ example: "503:5478:df06:d79a::/64"
+
+ TopicForwardSocketRequest:
+ description: Request to set a forward socket for a topic
+ type: object
+ properties:
+ socket_path:
+ description: The socket path where messages should be forwarded to
+ type: string
+ example: "/var/run/mycelium/topic_socket"
diff --git a/components/mycelium/docs/data_packet.md b/components/mycelium/docs/data_packet.md
new file mode 100644
index 0000000..c648e1c
--- /dev/null
+++ b/components/mycelium/docs/data_packet.md
@@ -0,0 +1,54 @@
+# Data packet
+
+A `data packet` contains user specified data. This can be any data, as long as the sender and receiver
+both understand what it is, without further help. Intermediate hops, which route the data have sufficient
+information with the header to know where to forward the packet. In practice, the data will be encrypted
+to avoid eavesdropping by intermediate hops.
+
+## Packet header
+
+The packet header has a fixed size of 36 bytes, with the following layout:
+
+```
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Reserved | Length | Hop Limit |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| |
++ +
+| |
++ Source IP +
+| |
++ +
+| |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| |
++ +
+| |
++ Destination IP +
+| |
++ +
+| |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+```
+
+The first 8 bits are reserved and must be set to 0.
+
+The next 16 bits are used to specify the length of the body. It is expected that
+the actual length of a packet does not exceed 65K right now, and overhead related
+to encryption should be handled by the client before sending the packet.
+
+The next byte is the hop-limit. Every node decrements this value by 1 before sending
+the packet. If a node decrements this value to 0, the packet is discarded.
+
+The next 16 bytes contain the sender IP address.
+
+The final 16 bytes contain the destination IP address.
+
+## Body
+
+Following the header is a variable length body. The protocol does not have any requirements for the
+body, and the only requirement imposed is that the body is as long as specified in the header length
+field. It is technically legal according to the protocol to transmit a data packet without a body,
+i.e. a body length of 0. This is useless however, as there will not be any data to interpret.
diff --git a/components/mycelium/docs/message.md b/components/mycelium/docs/message.md
new file mode 100644
index 0000000..44c00ca
--- /dev/null
+++ b/components/mycelium/docs/message.md
@@ -0,0 +1,161 @@
+# Message subsystem
+
+The message subsystem can be used to send arbitrary length messages to receivers. A receiver is any
+other node in the network. It can be identified both by its public key, or an IP address in its announced
+range. The message subsystem can be interacted with both via the HTTP API, which is
+[documented here](./api.yaml), or via the `mycelium` binary. By default, the messages do not interpret
+the data in any way. When using the binary, the message is slightly modified to include an optional
+topic at the start of the message. Note that in the HTTP API, all messages are encoded in base64. This
+might make it difficult to consume these messages without additional tooling.
+
+Messages can be categorized by topics, which can be configured with whitelisted subnets and socket forwarding paths.
+For detailed information on how to configure topics, see the [Topic Configuration Guide](./topic_configuration.md).
+
+## JSON-RPC API Examples
+
+These examples assume you have at least 2 nodes running, and that they are both part of the same network.
+
+Send a message on node1, waiting up to 2 minutes for a possible reply:
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "pushMessage",
+ "params": [
+ {
+ "dst": {"pk": "bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32"},
+ "payload": "xuV+"
+ },
+ 120
+ ],
+ "id": 1
+}
+```
+
+Using curl:
+
+```bash
+curl -X POST http://localhost:8990/rpc \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "method": "pushMessage",
+ "params": [
+ {
+ "dst": {"pk": "bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32"},
+ "payload": "xuV+"
+ },
+ 120
+ ],
+ "id": 1
+ }'
+```
+
+Listen for a message on node2. Note that messages received while nothing is listening are added to
+a queue for later consumption. Wait for up to 1 minute.
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "popMessage",
+ "params": [false, 60, null],
+ "id": 1
+}
+```
+
+Using curl:
+
+```bash
+curl -X POST http://localhost:8990/rpc \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "method": "popMessage",
+ "params": [false, 60, null],
+ "id": 1
+ }'
+```
+
+The system will (immediately) receive our previously sent message:
+
+```json
+{"id":"e47b25063912f4a9","srcIp":"34f:b680:ba6e:7ced:355f:346f:d97b:eecb","srcPk":"955bf6bea5e1150fd8e270c12e5b2fc08f08f7c5f3799d10550096cc137d671b","dstIp":"2e4:9ace:9252:630:beee:e405:74c0:d876","dstPk":"bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32","payload":"xuV+"}
+```
+
+To send a reply, we can post a message on the reply path, with the received message `id` (still on
+node2):
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "pushMessageReply",
+ "params": [
+ "e47b25063912f4a9",
+ {
+ "dst": {"pk":"955bf6bea5e1150fd8e270c12e5b2fc08f08f7c5f3799d10550096cc137d671b"},
+ "payload": "xuC+"
+ }
+ ],
+ "id": 1
+}
+```
+
+Using curl:
+
+```bash
+curl -X POST http://localhost:8990/rpc \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "method": "pushMessageReply",
+ "params": [
+ "e47b25063912f4a9",
+ {
+ "dst": {"pk":"955bf6bea5e1150fd8e270c12e5b2fc08f08f7c5f3799d10550096cc137d671b"},
+ "payload": "xuC+"
+ }
+ ],
+ "id": 1
+ }'
+```
+
+If you did this fast enough, the initial sender (node1) will now receive the reply.
+
+## Mycelium binary examples
+
+As explained above, while using the binary the message is slightly modified to insert the optional
+topic. As such, when using the binary to send messages, it is suggested to make sure the receiver is
+also using the binary to listen for messages. The options discussed here are not covering all possibilities,
+use the `--help` flag (`mycelium message send --help` and `mycelium message receive --help`) for a
+full overview.
+
+Once again, send a message. This time using a topic (example.topic). Note that there are no constraints
+on what a valid topic is, other than that it is valid UTF-8, and at most 255 bytes in size. The `--wait`
+flag can be used to indicate that we are waiting for a reply. If it is set, we can also use an additional
+`--timeout` flag to govern exactly how long (in seconds) to wait for. The default is to wait forever.
+
+```bash
+mycelium message send 2e4:9ace:9252:630:beee:e405:74c0:d876 'this is a message' -t example.topic --wait
+```
+
+On the second node, listen for messages with this topic. If a different topic is used, the previous
+message won't be received. If no topic is set, all messages are received. An optional timeout flag
+can be specified, which indicates how long to wait for. Absence of this flag will cause the binary
+to wait forever.
+
+```bash
+mycelium message receive -t example.topic
+```
+
+Again, if the previous command was executed a message will be received immediately:
+
+```json
+{"id":"4a6c956e8d36381f","topic":"example.topic","srcIp":"34f:b680:ba6e:7ced:355f:346f:d97b:eecb","srcPk":"955bf6bea5e1150fd8e270c12e5b2fc08f08f7c5f3799d10550096cc137d671b","dstIp":"2e4:9ace:9252:630:beee:e405:74c0:d876","dstPk":"bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32","payload":"this is a message"}
+```
+
+And once again, we can use the ID from this message to reply to the original sender, who might be waiting
+for this reply (notice we used the hex encoded public key to identify the receiver here, rather than an IP):
+
+```bash
+mycelium message send 955bf6bea5e1150fd8e270c12e5b2fc08f08f7c5f3799d10550096cc137d671b "this is a reply" --reply-to 4a6c956e8d36381f
+```
diff --git a/components/mycelium/docs/openrpc.json b/components/mycelium/docs/openrpc.json
new file mode 100644
index 0000000..762512b
--- /dev/null
+++ b/components/mycelium/docs/openrpc.json
@@ -0,0 +1,1190 @@
+{
+ "openrpc": "1.2.6",
+ "info": {
+ "version": "1.0.0",
+ "title": "Mycelium JSON-RPC API",
+ "description": "This is the specification of the Mycelium JSON-RPC API. It is used to perform admin tasks on the system, and to perform administrative duties.",
+ "contact": {
+ "url": "https://github.com/threefoldtech/mycelium"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "https://github.com/threefoldtech/mycelium/blob/master/LICENSE"
+ }
+ },
+ "servers": [
+ {
+ "url": "http://localhost:8990",
+ "name": "Mycelium JSON-RPC API"
+ }
+ ],
+ "methods": [
+ {
+ "name": "getInfo",
+ "summary": "Get general info about the node",
+ "description": "Get general info about the node, which is not related to other more specific functionality",
+ "tags": [
+ {
+ "name": "Admin"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "info",
+ "description": "General information about the node",
+ "schema": {
+ "$ref": "#/components/schemas/Info"
+ }
+ }
+ },
+ {
+ "name": "getPeers",
+ "summary": "List known peers",
+ "description": "List all peers known in the system, and info about their connection. This includes the endpoint, how we know about the peer, the connection state, and if the connection is alive the amount of bytes we've sent to and received from the peer.",
+ "tags": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Peer"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "peers",
+ "description": "List of peers",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PeerStats"
+ }
+ }
+ }
+ },
+ {
+ "name": "addPeer",
+ "summary": "Add a new peer",
+ "description": "Add a new peer identified by the provided endpoint. The peer is added to the list of known peers. It will eventually be connected to by the standard connection loop of the peer manager. This means that a peer which can't be connected to will stay in the system, as it might be reachable later on.",
+ "tags": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Peer"
+ }
+ ],
+ "params": [
+ {
+ "name": "endpoint",
+ "description": "The endpoint of the peer to add",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the peer was added successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ "errors": [
+ {
+ "code": 400,
+ "message": "Malformed endpoint"
+ },
+ {
+ "code": 409,
+ "message": "Peer already exists"
+ }
+ ]
+ },
+ {
+ "name": "deletePeer",
+ "summary": "Remove an existing peer",
+ "description": "Remove an existing peer identified by the provided endpoint. The peer is removed from the list of known peers. If a connection to it is currently active, it will be closed.",
+ "tags": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Peer"
+ }
+ ],
+ "params": [
+ {
+ "name": "endpoint",
+ "description": "The endpoint of the peer to remove",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the peer was removed successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ "errors": [
+ {
+ "code": 400,
+ "message": "Malformed endpoint"
+ },
+ {
+ "code": 404,
+ "message": "Peer doesn't exist"
+ }
+ ]
+ },
+ {
+ "name": "getSelectedRoutes",
+ "summary": "List all selected routes",
+ "description": "List all selected routes in the system, and their next hop identifier, metric and sequence number. It is possible for a route to be selected and have an infinite metric. This route will however not forward packets.",
+ "tags": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Route"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "routes",
+ "description": "List of selected routes",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Route"
+ }
+ }
+ }
+ },
+ {
+ "name": "getFallbackRoutes",
+ "summary": "List all active fallback routes",
+ "description": "List all fallback routes in the system, and their next hop identifier, metric and sequence number. These routes are available to be selected in case the selected route for a destination suddenly fails, or gets retracted.",
+ "tags": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Route"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "routes",
+ "description": "List of fallback routes",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Route"
+ }
+ }
+ }
+ },
+ {
+ "name": "getQueriedSubnets",
+ "summary": "List all currently queried subnets",
+ "description": "List all currently queried subnets in the system, and the amount of seconds until the query expires. These subnets are actively being probed in the network. If no route to them is discovered before the query expires, they will be marked as not reachable temporarily.",
+ "tags": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Route"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "subnets",
+ "description": "List of queried subnets",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/QueriedSubnet"
+ }
+ }
+ }
+ },
+ {
+ "name": "getNoRouteEntries",
+ "summary": "List all subnets which are explicitly marked as no route",
+ "description": "List all subnets in the system which are marked no route, and the amount of seconds until the query expires. These subnets have recently been probed in the network, and no route for them was discovered in time. No more route requests will be send for these subnets until the entry expires.",
+ "tags": [
+ {
+ "name": "Admin"
+ },
+ {
+ "name": "Route"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "subnets",
+ "description": "List of no route subnets",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NoRouteSubnet"
+ }
+ }
+ }
+ },
+ {
+ "name": "popMessage",
+ "summary": "Get a message from the inbound message queue",
+ "description": "Get a message from the inbound message queue. By default, the message is removed from the queue and won't be shown again. If the peek parameter is set to true, the message will be peeked, and the next call to this method will show the same message. This method returns immediately by default: a message is returned if one is ready, and if there isn't nothing is returned. If the timeout parameter is set, this call won't return for the given amount of seconds, unless a message is received.",
+ "tags": [
+ {
+ "name": "Message"
+ }
+ ],
+ "params": [
+ {
+ "name": "peek",
+ "description": "Whether to peek the message or not. If this is true, the message won't be removed from the inbound queue when it is read",
+ "required": false,
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "timeout",
+ "description": "Amount of seconds to wait for a message to arrive if one is not available. Setting this to 0 is valid and will return a message if present, or return immediately if there isn't",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "minimum": 0
+ }
+ },
+ {
+ "name": "topic",
+ "description": "Optional filter for loading messages. If set, the system checks if the message has the given string at the start. This way a topic can be encoded.",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "format": "byte",
+ "minLength": 0,
+ "maxLength": 340
+ }
+ }
+ ],
+ "result": {
+ "name": "message",
+ "description": "The retrieved message",
+ "schema": {
+ "$ref": "#/components/schemas/InboundMessage"
+ }
+ },
+ "errors": [
+ {
+ "code": 204,
+ "message": "No message ready"
+ }
+ ]
+ },
+ {
+ "name": "pushMessage",
+ "summary": "Submit a new message to the system",
+ "description": "Push a new message to the systems outbound message queue. The system will continuously attempt to send the message until it is either fully transmitted, or the send deadline is expired.",
+ "tags": [
+ {
+ "name": "Message"
+ }
+ ],
+ "params": [
+ {
+ "name": "message",
+ "description": "The message to send",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/PushMessageBody"
+ }
+ },
+ {
+ "name": "reply_timeout",
+ "description": "Amount of seconds to wait for a reply to this message to come in. If not set, the system won't wait for a reply and return the ID of the message, which can be used later. If set, the system will wait for at most the given amount of seconds for a reply to come in. If a reply arrives, it is returned to the client. If not, the message ID is returned for later use.",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "int64",
+ "minimum": 0
+ }
+ }
+ ],
+ "result": {
+ "name": "response",
+ "description": "The response to the message push",
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/InboundMessage"
+ },
+ {
+ "$ref": "#/components/schemas/PushMessageResponseId"
+ }
+ ]
+ }
+ },
+ "errors": [
+ {
+ "code": 408,
+ "message": "The system timed out waiting for a reply to the message"
+ }
+ ]
+ },
+ {
+ "name": "pushMessageReply",
+ "summary": "Reply to a message with the given ID",
+ "description": "Submits a reply message to the system, where ID is an id of a previously received message. If the sender is waiting for a reply, it will bypass the queue of open messages.",
+ "tags": [
+ {
+ "name": "Message"
+ }
+ ],
+ "params": [
+ {
+ "name": "id",
+ "description": "The ID of the message to reply to",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "hex",
+ "minLength": 16,
+ "maxLength": 16
+ }
+ },
+ {
+ "name": "message",
+ "description": "The reply message",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/PushMessageBody"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the reply was submitted successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "getMessageInfo",
+ "summary": "Get the status of an outbound message",
+ "description": "Get information about the current state of an outbound message. This can be used to check the transmission state, size and destination of the message.",
+ "tags": [
+ {
+ "name": "Message"
+ }
+ ],
+ "params": [
+ {
+ "name": "id",
+ "description": "The ID of the message to get info for",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "hex",
+ "minLength": 16,
+ "maxLength": 16
+ }
+ }
+ ],
+ "result": {
+ "name": "info",
+ "description": "Information about the message",
+ "schema": {
+ "$ref": "#/components/schemas/MessageStatusResponse"
+ }
+ },
+ "errors": [
+ {
+ "code": 404,
+ "message": "Message not found"
+ }
+ ]
+ },
+ {
+ "name": "getPublicKeyFromIp",
+ "summary": "Get the pubkey from node ip",
+ "description": "Get the node's public key from it's IP address.",
+ "tags": [
+ {
+ "name": "Admin"
+ }
+ ],
+ "params": [
+ {
+ "name": "mycelium_ip",
+ "description": "The IP address to get the public key for",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "ipv6"
+ }
+ }
+ ],
+ "result": {
+ "name": "pubkey",
+ "description": "The public key of the node",
+ "schema": {
+ "$ref": "#/components/schemas/PublicKeyResponse"
+ }
+ },
+ "errors": [
+ {
+ "code": 404,
+ "message": "Public key not found"
+ }
+ ]
+ },
+ {
+ "name": "getDefaultTopicAction",
+ "summary": "Get the default topic action",
+ "description": "Get the default action for topics that are not explicitly configured (accept or reject).",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "accept",
+ "description": "Whether unconfigured topics are accepted by default",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "setDefaultTopicAction",
+ "summary": "Set the default topic action",
+ "description": "Set the default action for topics that are not explicitly configured (accept or reject).",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "accept",
+ "description": "Whether to accept unconfigured topics by default",
+ "required": true,
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the default topic action was set successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "getTopics",
+ "summary": "Get all configured topics",
+ "description": "Get all topics that are explicitly configured in the system.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [],
+ "result": {
+ "name": "topics",
+ "description": "List of configured topics",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "byte",
+ "description": "Base64 encoded topic identifier"
+ }
+ }
+ }
+ },
+ {
+ "name": "addTopic",
+ "summary": "Add a new topic",
+ "description": "Add a new topic to the system's whitelist.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to add",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the topic was added successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "removeTopic",
+ "summary": "Remove a topic",
+ "description": "Remove a topic from the system's whitelist.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to remove",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the topic was removed successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "getTopicSources",
+ "summary": "Get sources for a topic",
+ "description": "Get all sources (subnets) that are allowed to send messages for a specific topic.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to get sources for",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "sources",
+ "description": "List of sources (subnets) for the topic",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ {
+ "name": "addTopicSource",
+ "summary": "Add a source to a topic",
+ "description": "Add a source (subnet) that is allowed to send messages for a specific topic.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to add a source to",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "subnet",
+ "description": "The subnet to add as a source",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the source was added successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "removeTopicSource",
+ "summary": "Remove a source from a topic",
+ "description": "Remove a source (subnet) that is allowed to send messages for a specific topic.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to remove a source from",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "subnet",
+ "description": "The subnet to remove as a source",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the source was removed successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "getTopicForwardSocket",
+ "summary": "Get the forward socket for a topic",
+ "description": "Get the socket path where messages for a specific topic are forwarded to.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to get the forward socket for",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "socket_path",
+ "description": "The socket path where messages are forwarded to",
+ "schema": {
+ "type": ["string", "null"]
+ }
+ }
+ },
+ {
+ "name": "setTopicForwardSocket",
+ "summary": "Set the forward socket for a topic",
+ "description": "Set the socket path where messages for a specific topic should be forwarded to.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to set the forward socket for",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "socket_path",
+ "description": "The socket path where messages should be forwarded to",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the forward socket was set successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "name": "removeTopicForwardSocket",
+ "summary": "Remove the forward socket for a topic",
+ "description": "Remove the socket path where messages for a specific topic are forwarded to.",
+ "tags": [
+ {
+ "name": "Message"
+ },
+ {
+ "name": "Topic"
+ }
+ ],
+ "params": [
+ {
+ "name": "topic",
+ "description": "The topic to remove the forward socket for",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "result": {
+ "name": "success",
+ "description": "Whether the forward socket was removed successfully",
+ "schema": {
+ "type": "boolean"
+ }
+ }
+ }
+ ],
+ "components": {
+ "schemas": {
+ "Info": {
+ "description": "General information about a node",
+ "type": "object",
+ "properties": {
+ "nodeSubnet": {
+ "description": "The subnet owned by the node and advertised to peers",
+ "type": "string",
+ "example": "54f:b680:ba6e:7ced::/64"
+ },
+ "nodePubkey": {
+ "description": "The public key of the node",
+ "type": "string",
+ "format": "hex",
+ "minLength": 64,
+ "maxLength": 64,
+ "example": "02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf"
+ }
+ }
+ },
+ "Endpoint": {
+ "description": "Identification to connect to a peer",
+ "type": "object",
+ "properties": {
+ "proto": {
+ "description": "Protocol used",
+ "type": "string",
+ "enum": [
+ "tcp",
+ "quic"
+ ],
+ "example": "tcp"
+ },
+ "socketAddr": {
+ "description": "The socket address used",
+ "type": "string",
+ "example": "192.0.2.6:9651"
+ }
+ }
+ },
+ "TopicInfo": {
+ "description": "Information about a configured topic",
+ "type": "object",
+ "properties": {
+ "topic": {
+ "description": "The topic identifier",
+ "type": "string",
+ "format": "byte",
+ "example": "example.topic"
+ },
+ "sources": {
+ "description": "List of subnets that are allowed to send messages for this topic",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "503:5478:df06:d79a::/64"
+ }
+ },
+ "forward_socket": {
+ "description": "Optional socket path where messages for this topic are forwarded to",
+ "type": ["string", "null"],
+ "example": "/var/run/mycelium/topic_socket"
+ }
+ }
+ },
+ "PeerStats": {
+ "description": "Info about a peer",
+ "type": "object",
+ "properties": {
+ "endpoint": {
+ "$ref": "#/components/schemas/Endpoint"
+ },
+ "type": {
+ "description": "How we know about this peer",
+ "type": "string",
+ "enum": [
+ "static",
+ "inbound",
+ "linkLocalDiscovery"
+ ],
+ "example": "static"
+ },
+ "connectionState": {
+ "description": "The current state of the connection to the peer",
+ "type": "string",
+ "enum": [
+ "alive",
+ "connecting",
+ "dead"
+ ],
+ "example": "alive"
+ },
+ "txBytes": {
+ "description": "The amount of bytes transmitted to this peer",
+ "type": "integer",
+ "format": "int64",
+ "minimum": 0,
+ "example": 464531564
+ },
+ "rxBytes": {
+ "description": "The amount of bytes received from this peer",
+ "type": "integer",
+ "format": "int64",
+ "minimum": 0,
+ "example": 64645089
+ }
+ }
+ },
+ "Route": {
+ "description": "Information about a route",
+ "type": "object",
+ "properties": {
+ "subnet": {
+ "description": "The overlay subnet for which this is the route",
+ "type": "string",
+ "example": "469:1348:ab0c:a1d8::/64"
+ },
+ "nextHop": {
+ "description": "A way to identify the next hop of the route, where forwarded packets will be sent",
+ "type": "string",
+ "example": "TCP 203.0.113.2:60128 <-> 198.51.100.27:9651"
+ },
+ "metric": {
+ "description": "The metric of the route, an estimation of how long the packet will take to arrive at its final destination",
+ "oneOf": [
+ {
+ "description": "A finite metric value",
+ "type": "integer",
+ "format": "int32",
+ "minimum": 0,
+ "maximum": 65534,
+ "example": 13
+ },
+ {
+ "description": "An infinite (unreachable) metric. This is always `infinite`",
+ "type": "string",
+ "example": "infinite"
+ }
+ ]
+ },
+ "seqno": {
+ "description": "the sequence number advertised with this route by the source",
+ "type": "integer",
+ "format": "int32",
+ "minimum": 0,
+ "maximum": 65535,
+ "example": 1
+ }
+ }
+ },
+ "QueriedSubnet": {
+ "description": "Information about a subnet currently being queried",
+ "type": "object",
+ "properties": {
+ "subnet": {
+ "description": "The overlay subnet which we are currently querying",
+ "type": "string",
+ "example": "503:5478:df06:d79a::/64"
+ },
+ "expiration": {
+ "description": "The amount of seconds until the query expires",
+ "type": "string",
+ "example": "37"
+ }
+ }
+ },
+ "NoRouteSubnet": {
+ "description": "Information about a subnet which is marked as no route",
+ "type": "object",
+ "properties": {
+ "subnet": {
+ "description": "The overlay subnet which is marked",
+ "type": "string",
+ "example": "503:5478:df06:d79a::/64"
+ },
+ "expiration": {
+ "description": "The amount of seconds until the entry expires",
+ "type": "string",
+ "example": "37"
+ }
+ }
+ },
+ "InboundMessage": {
+ "description": "A message received by the system",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "Id of the message, hex encoded",
+ "type": "string",
+ "format": "hex",
+ "minLength": 16,
+ "maxLength": 16,
+ "example": "0123456789abcdef"
+ },
+ "srcIp": {
+ "description": "Sender overlay IP address",
+ "type": "string",
+ "format": "ipv6",
+ "example": "449:abcd:0123:defa::1"
+ },
+ "srcPk": {
+ "description": "Sender public key, hex encoded",
+ "type": "string",
+ "format": "hex",
+ "minLength": 64,
+ "maxLength": 64,
+ "example": "fedbca9876543210fedbca9876543210fedbca9876543210fedbca9876543210"
+ },
+ "dstIp": {
+ "description": "Receiver overlay IP address",
+ "type": "string",
+ "format": "ipv6",
+ "example": "34f:b680:ba6e:7ced:355f:346f:d97b:eecb"
+ },
+ "dstPk": {
+ "description": "Receiver public key, hex encoded. This is the public key of the system",
+ "type": "string",
+ "format": "hex",
+ "minLength": 64,
+ "maxLength": 64,
+ "example": "02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf"
+ },
+ "topic": {
+ "description": "An optional message topic",
+ "type": "string",
+ "format": "byte",
+ "minLength": 0,
+ "maxLength": 340,
+ "example": "hpV+"
+ },
+ "payload": {
+ "description": "The message payload, encoded in standard alphabet base64",
+ "type": "string",
+ "format": "byte",
+ "example": "xuV+"
+ }
+ }
+ },
+ "PushMessageBody": {
+ "description": "A message to send to a given receiver",
+ "type": "object",
+ "properties": {
+ "dst": {
+ "$ref": "#/components/schemas/MessageDestination"
+ },
+ "topic": {
+ "description": "An optional message topic",
+ "type": "string",
+ "format": "byte",
+ "minLength": 0,
+ "maxLength": 340,
+ "example": "hpV+"
+ },
+ "payload": {
+ "description": "The message to send, base64 encoded",
+ "type": "string",
+ "format": "byte",
+ "example": "xuV+"
+ }
+ }
+ },
+ "MessageDestination": {
+ "oneOf": [
+ {
+ "description": "An IP in the subnet of the receiver node",
+ "type": "object",
+ "properties": {
+ "ip": {
+ "description": "The target IP of the message",
+ "type": "string",
+ "format": "ipv6",
+ "example": "449:abcd:0123:defa::1"
+ }
+ }
+ },
+ {
+ "description": "The hex encoded public key of the receiver node",
+ "type": "object",
+ "properties": {
+ "pk": {
+ "description": "The hex encoded public key of the target node",
+ "type": "string",
+ "minLength": 64,
+ "maxLength": 64,
+ "example": "bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32"
+ }
+ }
+ }
+ ]
+ },
+ "PushMessageResponseId": {
+ "description": "The ID generated for a message after pushing it to the system",
+ "type": "object",
+ "properties": {
+ "id": {
+ "description": "Id of the message, hex encoded",
+ "type": "string",
+ "format": "hex",
+ "minLength": 16,
+ "maxLength": 16,
+ "example": "0123456789abcdef"
+ }
+ }
+ },
+ "MessageStatusResponse": {
+ "description": "Information about an outbound message",
+ "type": "object",
+ "properties": {
+ "dst": {
+ "description": "IP address of the receiving node",
+ "type": "string",
+ "format": "ipv6",
+ "example": "449:abcd:0123:defa::1"
+ },
+ "state": {
+ "$ref": "#/components/schemas/TransmissionState"
+ },
+ "created": {
+ "description": "Unix timestamp of when this message was created",
+ "type": "integer",
+ "format": "int64",
+ "example": 1649512789
+ },
+ "deadline": {
+ "description": "Unix timestamp of when this message will expire. If the message is not received before this, the system will give up",
+ "type": "integer",
+ "format": "int64",
+ "example": 1649513089
+ },
+ "msgLen": {
+ "description": "Length of the message in bytes",
+ "type": "integer",
+ "minimum": 0,
+ "example": 27
+ }
+ }
+ },
+ "TransmissionState": {
+ "description": "The state of an outbound message in it's lifetime",
+ "oneOf": [
+ {
+ "type": "string",
+ "enum": [
+ "pending",
+ "received",
+ "read",
+ "aborted"
+ ],
+ "example": "received"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "sending": {
+ "type": "object",
+ "properties": {
+ "pending": {
+ "type": "integer",
+ "minimum": 0,
+ "example": 5
+ },
+ "sent": {
+ "type": "integer",
+ "minimum": 0,
+ "example": 17
+ },
+ "acked": {
+ "type": "integer",
+ "minimum": 0,
+ "example": 3
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ "PublicKeyResponse": {
+ "description": "Public key requested based on a node's IP",
+ "type": "object",
+ "properties": {
+ "NodePubKey": {
+ "type": "string",
+ "format": "hex",
+ "minLength": 64,
+ "maxLength": 64,
+ "example": "02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/components/mycelium/docs/packet.md b/components/mycelium/docs/packet.md
new file mode 100644
index 0000000..50d5987
--- /dev/null
+++ b/components/mycelium/docs/packet.md
@@ -0,0 +1,22 @@
+# Packet
+
+A `Packet` is the largest communication object between established `peers`. All communication is done
+via these `packets`. The `packet` itself consists of a fixed size header, and a variable size body.
+The body contains a more specific type of data.
+
+## Packet header
+
+The packet header has a fixed size of 4 bytes, with the following layout:
+
+```
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Version | Type | Reserved |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+```
+
+The first byte is used to indicate the version of the protocol. Currently, only version 1 is supported
+(0x01). The next byte is used to indicate the type of the body. `0x00` indicates a data packet, while
+`0x01` indicates a control packet. The remaining 16 bits are currently reserved, and should be set to
+all 0.
diff --git a/components/mycelium/docs/private_network.md b/components/mycelium/docs/private_network.md
new file mode 100644
index 0000000..5f44a54
--- /dev/null
+++ b/components/mycelium/docs/private_network.md
@@ -0,0 +1,35 @@
+# Private network
+
+> Private network functionality is currently in an experimental stage
+
+While traffic is end-to-end encrypted in mycelium, any node in the network learns
+every available connected subnet (and can derive the associated default address
+in that subnet). As a result, running a mycelium node adds what is effectively
+a public interface to your computer, so everyone can send traffic to it. On top
+of this, the routing table consumes memory in relation to the amount of nodes in
+the network. To remedy this, people can opt to run a "private network". By configuring
+a pre shared key (and network name), only nodes which know the key associated to
+the name can connect to your network.
+
+## Implementation
+
+Private networks are implemented entirely in the connection layer (no specific
+protocol logic is implemented to support this). This relies on the pre shared key
+functionality of TLS 1.3. As such, you need both a `network name` (an `identity`),
+and the `PSK` itself. Next to the limitations in the protocol, we currently further
+limit the network name and PSK as follows:
+
+- Network name must be a UTF-8 encoded string of 2 to 64 bytes.
+- PSK must be exactly 32 bytes.
+
+Not all cipher suites supported in TLS1.3 are supported. At present, _at least_
+`TLS_AES_128_GCM_SHA256` and `TLS_CHACHA20_POLY1305_SHA256` are supported.
+
+## Enable private network
+
+In order to use the private network implementation of `mycelium`, a separate `mycelium-private`
+binary is available. Private network functionality can be enabled by setting both
+the `network-name` and `network-key-file` flags on the command line. All nodes who
+wish to join the network must use the same values for both flags.
+
+> ⚠️ Network name is public, do not put any confidential data here.
diff --git a/components/mycelium/docs/topic_configuration.md b/components/mycelium/docs/topic_configuration.md
new file mode 100644
index 0000000..dc61090
--- /dev/null
+++ b/components/mycelium/docs/topic_configuration.md
@@ -0,0 +1,211 @@
+# Topic Configuration Guide
+
+This document explains how to configure message topics in Mycelium, including how to add new topics, configure socket forwarding paths, and manage whitelisted subnets.
+
+## Overview
+
+Mycelium's messaging system uses topics to categorize and route messages. Each topic can be configured with:
+
+- **Whitelisted subnets**: IP subnets that are allowed to send messages to this topic
+- **Forward socket**: A Unix domain socket path where messages for this topic will be forwarded
+
+When a message is received with a topic that has a configured socket path, the content of the message is pushed to the socket, and the system waits for a reply from the socket, which is then sent back to the original sender.
+
+## Configuration Using JSON-RPC API
+
+The JSON-RPC API provides a comprehensive set of methods for managing topics, socket forwarding paths, and whitelisted subnets.
+
+## Adding a New Topic
+
+### Using the JSON-RPC API
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "addTopic",
+ "params": ["dGVzdC10b3BpYw=="], // base64 encoding of "test-topic"
+ "id": 1
+}
+```
+
+Example using curl:
+
+```bash
+curl -X POST http://localhost:8990/rpc \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "method": "addTopic",
+ "params": ["dGVzdC10b3BpYw=="],
+ "id": 1
+ }'
+```
+
+
+## Configuring a Socket Forwarding Path
+
+When a topic is configured with a socket forwarding path, messages for that topic will be forwarded to the specified Unix domain socket instead of being pushed to the message queue.
+
+### Using the JSON-RPC API
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "setTopicForwardSocket",
+ "params": ["dGVzdC10b3BpYw==", "/path/to/socket"],
+ "id": 1
+}
+```
+
+Example using curl:
+
+```bash
+curl -X POST http://localhost:8990/rpc \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "method": "setTopicForwardSocket",
+ "params": ["dGVzdC10b3BpYw==", "/path/to/socket"],
+ "id": 1
+ }'
+```
+```
+
+## Adding a Whitelisted Subnet
+
+Whitelisted subnets control which IP addresses are allowed to send messages to a specific topic. If a message is received from an IP that is not in the whitelist, it will be dropped.
+
+### Using the JSON-RPC API
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "addTopicSource",
+ "params": ["dGVzdC10b3BpYw==", "192.168.1.0/24"],
+ "id": 1
+}
+```
+
+Example using curl:
+
+```bash
+curl -X POST http://localhost:8990/rpc \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "method": "addTopicSource",
+ "params": ["dGVzdC10b3BpYw==", "192.168.1.0/24"],
+ "id": 1
+ }'
+```
+```
+
+## Setting the Default Topic Action
+
+You can configure the default action to take for topics that don't have explicit whitelist configurations:
+
+### Using the JSON-RPC API
+
+```json
+{
+ "jsonrpc": "2.0",
+ "method": "setDefaultTopicAction",
+ "params": [true],
+ "id": 1
+}
+```
+
+Example using curl:
+
+```bash
+curl -X POST http://localhost:8990/rpc \
+ -H "Content-Type: application/json" \
+ -d '{
+ "jsonrpc": "2.0",
+ "method": "setDefaultTopicAction",
+ "params": [true],
+ "id": 1
+ }'
+```
+```
+
+## Socket Protocol
+
+When a message is forwarded to a socket, the raw message data is sent to the socket. The socket is expected to process the message and send a reply, which will be forwarded back to the original sender.
+
+The socket protocol is simple:
+1. The message data is written to the socket
+2. The system waits for a reply from the socket (with a configurable timeout)
+3. The reply data is read from the socket and sent back to the original sender
+
+## Example: Creating a Socket Server
+
+Here's an example of a simple socket server that echoes back the received data:
+
+```rust
+use std::{
+ io::{Read, Write},
+ os::unix::net::UnixListener,
+ path::Path,
+ thread,
+};
+
+fn main() {
+ let socket_path = "/tmp/mycelium-socket";
+
+ // Remove the socket file if it already exists
+ if Path::new(socket_path).exists() {
+ std::fs::remove_file(socket_path).unwrap();
+ }
+
+ // Create the Unix domain socket
+ let listener = UnixListener::bind(socket_path).unwrap();
+ println!("Socket server listening on {}", socket_path);
+
+ // Accept connections in a loop
+ for stream in listener.incoming() {
+ match stream {
+ Ok(mut stream) => {
+ // Spawn a thread to handle the connection
+ thread::spawn(move || {
+ // Read the data
+ let mut buffer = Vec::new();
+ stream.read_to_end(&mut buffer).unwrap();
+ println!("Received {} bytes", buffer.len());
+
+ // Process the data (in this case, just echo it back)
+ // In a real application, you would parse and process the message here
+
+ // Send the reply
+ stream.write_all(&buffer).unwrap();
+ println!("Sent reply");
+ });
+ }
+ Err(e) => {
+ eprintln!("Error accepting connection: {}", e);
+ }
+ }
+ }
+}
+```
+
+## Troubleshooting
+
+### Message Not Being Forwarded to Socket
+
+1. Check that the topic is correctly configured with a socket path
+2. Verify that the socket server is running and the socket file exists
+3. Ensure that the sender's IP is in the whitelisted subnets for the topic
+4. Check the logs for any socket connection or timeout errors
+
+### Socket Server Not Receiving Messages
+
+1. Verify that the socket path is correct and accessible
+2. Check that the socket server has the necessary permissions to read/write to the socket
+3. Ensure that the socket server is properly handling the connection
+
+### Reply Not Being Sent Back
+
+1. Verify that the socket server is sending a reply
+2. Check for any timeout errors in the logs
+3. Ensure that the original sender is still connected and able to receive the reply
diff --git a/components/mycelium/flake.lock b/components/mycelium/flake.lock
new file mode 100644
index 0000000..03f3fe3
--- /dev/null
+++ b/components/mycelium/flake.lock
@@ -0,0 +1,107 @@
+{
+ "nodes": {
+ "crane": {
+ "locked": {
+ "lastModified": 1742317686,
+ "narHash": "sha256-ScJYnUykEDhYeCepoAWBbZWx2fpQ8ottyvOyGry7HqE=",
+ "owner": "ipetkov",
+ "repo": "crane",
+ "rev": "66cb0013f9a99d710b167ad13cbd8cc4e64f2ddb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ipetkov",
+ "repo": "crane",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "locked": {
+ "lastModified": 1733328505,
+ "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
+ "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
+ "revCount": 69,
+ "type": "tarball",
+ "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
+ },
+ "original": {
+ "type": "tarball",
+ "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "id": "flake-utils",
+ "type": "indirect"
+ }
+ },
+ "nix-filter": {
+ "locked": {
+ "lastModified": 1731533336,
+ "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
+ "owner": "numtide",
+ "repo": "nix-filter",
+ "rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "nix-filter",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1742288794,
+ "narHash": "sha256-Txwa5uO+qpQXrNG4eumPSD+hHzzYi/CdaM80M9XRLCo=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "b6eaf97c6960d97350c584de1b6dcff03c9daf42",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "crane": "crane",
+ "flake-compat": "flake-compat",
+ "flake-utils": "flake-utils",
+ "nix-filter": "nix-filter",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/components/mycelium/flake.nix b/components/mycelium/flake.nix
new file mode 100644
index 0000000..d514c81
--- /dev/null
+++ b/components/mycelium/flake.nix
@@ -0,0 +1,156 @@
+{
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ crane.url = "github:ipetkov/crane";
+
+ flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
+
+ nix-filter.url = "github:numtide/nix-filter";
+ };
+
+ outputs =
+ { self
+ , crane
+ , flake-utils
+ , nix-filter
+ , ...
+ }@inputs:
+ {
+ overlays.default = final: prev:
+ let
+ inherit (final) lib stdenv darwin;
+ craneLib = crane.mkLib final;
+ in
+ {
+ myceliumd =
+ let
+ cargoToml = ./myceliumd/Cargo.toml;
+ cargoLock = ./myceliumd/Cargo.lock;
+ manifest = craneLib.crateNameFromCargoToml { inherit cargoToml; };
+ in
+ lib.makeOverridable craneLib.buildPackage {
+ src = nix-filter {
+ root = ./.;
+
+ # If no include is passed, it will include all the paths.
+ include = [
+ ./Cargo.toml
+ ./Cargo.lock
+ ./mycelium
+ ./mycelium-api
+ ./mycelium-cli
+ ./mycelium-metrics
+ ./myceliumd
+ ./myceliumd-private
+ ./mobile
+ ./docs
+ ];
+ };
+
+ inherit (manifest) pname version;
+ inherit cargoToml cargoLock;
+ sourceRoot = "source/myceliumd";
+
+ doCheck = false;
+
+ nativeBuildInputs = [
+ final.pkg-config
+ # openssl base library
+ final.openssl
+ # required by openssl-sys
+ final.perl
+ ];
+
+ buildInputs = lib.optionals stdenv.isDarwin [
+ darwin.apple_sdk.frameworks.Security
+ darwin.apple_sdk.frameworks.SystemConfiguration
+ final.libiconv
+ ];
+
+ meta = {
+ mainProgram = "mycelium";
+ };
+ };
+ myceliumd-private =
+ let
+ cargoToml = ./myceliumd-private/Cargo.toml;
+ cargoLock = ./myceliumd-private/Cargo.lock;
+ manifest = craneLib.crateNameFromCargoToml { inherit cargoToml; };
+ in
+ lib.makeOverridable craneLib.buildPackage {
+ src = nix-filter {
+ root = ./.;
+
+ include = [
+ ./Cargo.toml
+ ./Cargo.lock
+ ./mycelium
+ ./mycelium-api
+ ./mycelium-cli
+ ./mycelium-metrics
+ ./myceliumd
+ ./myceliumd-private
+ ./mobile
+ ./docs
+ ];
+ };
+
+ inherit (manifest) pname version;
+ inherit cargoToml cargoLock;
+ sourceRoot = "source/myceliumd-private";
+
+ doCheck = false;
+
+ nativeBuildInputs = [
+ final.pkg-config
+ # openssl base library
+ final.openssl
+ # required by openssl-sys
+ final.perl
+ ];
+
+ buildInputs = lib.optionals stdenv.isDarwin [
+ darwin.apple_sdk.frameworks.Security
+ darwin.apple_sdk.frameworks.SystemConfiguration
+ final.libiconv
+ ];
+
+ meta = {
+ mainProgram = "mycelium-private";
+ };
+ };
+ };
+ } //
+ flake-utils.lib.eachSystem
+ [
+ flake-utils.lib.system.x86_64-linux
+ flake-utils.lib.system.aarch64-linux
+ flake-utils.lib.system.x86_64-darwin
+ flake-utils.lib.system.aarch64-darwin
+ ]
+ (system:
+ let
+ craneLib = crane.mkLib pkgs;
+
+ pkgs = import inputs.nixpkgs {
+ inherit system;
+ overlays = [ self.overlays.default ];
+ };
+ in
+ {
+ devShells.default = craneLib.devShell {
+ packages = [
+ pkgs.rust-analyzer
+ ];
+
+ RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}";
+ };
+
+ packages = {
+ default = self.packages.${system}.myceliumd;
+
+ inherit (pkgs) myceliumd myceliumd-private;
+ };
+ });
+}
diff --git a/components/mycelium/installers/windows/wix/LICENSE.rtf b/components/mycelium/installers/windows/wix/LICENSE.rtf
new file mode 100644
index 0000000..79d56b3
--- /dev/null
+++ b/components/mycelium/installers/windows/wix/LICENSE.rtf
@@ -0,0 +1,206 @@
+{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
+{\colortbl ;\red0\green0\blue255;}
+{\*\generator Riched20 10.0.22621}\viewkind4\uc1
+\pard\sa200\sl276\slmult1\f0\fs22\lang9 Apache License\par
+ Version 2.0, January 2004\par
+ {{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/ }}{\fldrslt{http://www.apache.org/licenses/\ul0\cf0}}}}\f0\fs22\par
+\par
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\par
+\par
+ 1. Definitions.\par
+\par
+ "License" shall mean the terms and conditions for use, reproduction,\par
+ and distribution as defined by Sections 1 through 9 of this document.\par
+\par
+ "Licensor" shall mean the copyright owner or entity authorized by\par
+ the copyright owner that is granting the License.\par
+\par
+ "Legal Entity" shall mean the union of the acting entity and all\par
+ other entities that control, are controlled by, or are under common\par
+ control with that entity. For the purposes of this definition,\par
+ "control" means (i) the power, direct or indirect, to cause the\par
+ direction or management of such entity, whether by contract or\par
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the\par
+ outstanding shares, or (iii) beneficial ownership of such entity.\par
+\par
+ "You" (or "Your") shall mean an individual or Legal Entity\par
+ exercising permissions granted by this License.\par
+\par
+ "Source" form shall mean the preferred form for making modifications,\par
+ including but not limited to software source code, documentation\par
+ source, and configuration files.\par
+\par
+ "Object" form shall mean any form resulting from mechanical\par
+ transformation or translation of a Source form, including but\par
+ not limited to compiled object code, generated documentation,\par
+ and conversions to other media types.\par
+\par
+ "Work" shall mean the work of authorship, whether in Source or\par
+ Object form, made available under the License, as indicated by a\par
+ copyright notice that is included in or attached to the work\par
+ (an example is provided in the Appendix below).\par
+\par
+ "Derivative Works" shall mean any work, whether in Source or Object\par
+ form, that is based on (or derived from) the Work and for which the\par
+ editorial revisions, annotations, elaborations, or other modifications\par
+ represent, as a whole, an original work of authorship. For the purposes\par
+ of this License, Derivative Works shall not include works that remain\par
+ separable from, or merely link (or bind by name) to the interfaces of,\par
+ the Work and Derivative Works thereof.\par
+\par
+ "Contribution" shall mean any work of authorship, including\par
+ the original version of the Work and any modifications or additions\par
+ to that Work or Derivative Works thereof, that is intentionally\par
+ submitted to Licensor for inclusion in the Work by the copyright owner\par
+ or by an individual or Legal Entity authorized to submit on behalf of\par
+ the copyright owner. For the purposes of this definition, "submitted"\par
+ means any form of electronic, verbal, or written communication sent\par
+ to the Licensor or its representatives, including but not limited to\par
+ communication on electronic mailing lists, source code control systems,\par
+ and issue tracking systems that are managed by, or on behalf of, the\par
+ Licensor for the purpose of discussing and improving the Work, but\par
+ excluding communication that is conspicuously marked or otherwise\par
+ designated in writing by the copyright owner as "Not a Contribution."\par
+\par
+ "Contributor" shall mean Licensor and any individual or Legal Entity\par
+ on behalf of whom a Contribution has been received by Licensor and\par
+ subsequently incorporated within the Work.\par
+\par
+ 2. Grant of Copyright License. Subject to the terms and conditions of\par
+ this License, each Contributor hereby grants to You a perpetual,\par
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable\par
+ copyright license to reproduce, prepare Derivative Works of,\par
+ publicly display, publicly perform, sublicense, and distribute the\par
+ Work and such Derivative Works in Source or Object form.\par
+\par
+ 3. Grant of Patent License. Subject to the terms and conditions of\par
+ this License, each Contributor hereby grants to You a perpetual,\par
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable\par
+ (except as stated in this section) patent license to make, have made,\par
+ use, offer to sell, sell, import, and otherwise transfer the Work,\par
+ where such license applies only to those patent claims licensable\par
+ by such Contributor that are necessarily infringed by their\par
+ Contribution(s) alone or by combination of their Contribution(s)\par
+ with the Work to which such Contribution(s) was submitted. If You\par
+ institute patent litigation against any entity (including a\par
+ cross-claim or counterclaim in a lawsuit) alleging that the Work\par
+ or a Contribution incorporated within the Work constitutes direct\par
+ or contributory patent infringement, then any patent licenses\par
+ granted to You under this License for that Work shall terminate\par
+ as of the date such litigation is filed.\par
+\par
+ 4. Redistribution. You may reproduce and distribute copies of the\par
+ Work or Derivative Works thereof in any medium, with or without\par
+ modifications, and in Source or Object form, provided that You\par
+ meet the following conditions:\par
+\par
+ (a) You must give any other recipients of the Work or\par
+ Derivative Works a copy of this License; and\par
+\par
+ (b) You must cause any modified files to carry prominent notices\par
+ stating that You changed the files; and\par
+\par
+ (c) You must retain, in the Source form of any Derivative Works\par
+ that You distribute, all copyright, patent, trademark, and\par
+ attribution notices from the Source form of the Work,\par
+ excluding those notices that do not pertain to any part of\par
+ the Derivative Works; and\par
+\par
+ (d) If the Work includes a "NOTICE" text file as part of its\par
+ distribution, then any Derivative Works that You distribute must\par
+ include a readable copy of the attribution notices contained\par
+ within such NOTICE file, excluding those notices that do not\par
+ pertain to any part of the Derivative Works, in at least one\par
+ of the following places: within a NOTICE text file distributed\par
+ as part of the Derivative Works; within the Source form or\par
+ documentation, if provided along with the Derivative Works; or,\par
+ within a display generated by the Derivative Works, if and\par
+ wherever such third-party notices normally appear. The contents\par
+ of the NOTICE file are for informational purposes only and\par
+ do not modify the License. You may add Your own attribution\par
+ notices within Derivative Works that You distribute, alongside\par
+ or as an addendum to the NOTICE text from the Work, provided\par
+ that such additional attribution notices cannot be construed\par
+ as modifying the License.\par
+\par
+ You may add Your own copyright statement to Your modifications and\par
+ may provide additional or different license terms and conditions\par
+ for use, reproduction, or distribution of Your modifications, or\par
+ for any such Derivative Works as a whole, provided Your use,\par
+ reproduction, and distribution of the Work otherwise complies with\par
+ the conditions stated in this License.\par
+\par
+ 5. Submission of Contributions. Unless You explicitly state otherwise,\par
+ any Contribution intentionally submitted for inclusion in the Work\par
+ by You to the Licensor shall be under the terms and conditions of\par
+ this License, without any additional terms or conditions.\par
+ Notwithstanding the above, nothing herein shall supersede or modify\par
+ the terms of any separate license agreement you may have executed\par
+ with Licensor regarding such Contributions.\par
+\par
+ 6. Trademarks. This License does not grant permission to use the trade\par
+ names, trademarks, service marks, or product names of the Licensor,\par
+ except as required for reasonable and customary use in describing the\par
+ origin of the Work and reproducing the content of the NOTICE file.\par
+\par
+ 7. Disclaimer of Warranty. Unless required by applicable law or\par
+ agreed to in writing, Licensor provides the Work (and each\par
+ Contributor provides its Contributions) on an "AS IS" BASIS,\par
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\par
+ implied, including, without limitation, any warranties or conditions\par
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\par
+ PARTICULAR PURPOSE. You are solely responsible for determining the\par
+ appropriateness of using or redistributing the Work and assume any\par
+ risks associated with Your exercise of permissions under this License.\par
+\par
+ 8. Limitation of Liability. In no event and under no legal theory,\par
+ whether in tort (including negligence), contract, or otherwise,\par
+ unless required by applicable law (such as deliberate and grossly\par
+ negligent acts) or agreed to in writing, shall any Contributor be\par
+ liable to You for damages, including any direct, indirect, special,\par
+ incidental, or consequential damages of any character arising as a\par
+ result of this License or out of the use or inability to use the\par
+ Work (including but not limited to damages for loss of goodwill,\par
+ work stoppage, computer failure or malfunction, or any and all\par
+ other commercial damages or losses), even if such Contributor\par
+ has been advised of the possibility of such damages.\par
+\par
+ 9. Accepting Warranty or Additional Liability. While redistributing\par
+ the Work or Derivative Works thereof, You may choose to offer,\par
+ and charge a fee for, acceptance of support, warranty, indemnity,\par
+ or other liability obligations and/or rights consistent with this\par
+ License. However, in accepting such obligations, You may act only\par
+ on Your own behalf and on Your sole responsibility, not on behalf\par
+ of any other Contributor, and only if You agree to indemnify,\par
+ defend, and hold each Contributor harmless for any liability\par
+ incurred by, or claims asserted against, such Contributor by reason\par
+ of your accepting any such warranty or additional liability.\par
+\par
+ END OF TERMS AND CONDITIONS\par
+\par
+ APPENDIX: How to apply the Apache License to your work.\par
+\par
+ To apply the Apache License to your work, attach the following\par
+ boilerplate notice, with the fields enclosed by brackets "[]"\par
+ replaced with your own identifying information. (Don't include\par
+ the brackets!) The text should be enclosed in the appropriate\par
+ comment syntax for the file format. We also recommend that a\par
+ file or class name and description of purpose be included on the\par
+ same "printed page" as the copyright notice for easier\par
+ identification within third-party archives.\par
+\par
+ Copyright TF Tech NV\par
+\par
+ Licensed under the Apache License, Version 2.0 (the "License");\par
+ you may not use this file except in compliance with the License.\par
+ You may obtain a copy of the License at\par
+\par
+ {{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/LICENSE-2.0 }}{\fldrslt{http://www.apache.org/licenses/LICENSE-2.0\ul0\cf0}}}}\f0\fs22\par
+\par
+ Unless required by applicable law or agreed to in writing, software\par
+ distributed under the License is distributed on an "AS IS" BASIS,\par
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\par
+ See the License for the specific language governing permissions and\par
+ limitations under the License.\par
+}
+
\ No newline at end of file
diff --git a/components/mycelium/installers/windows/wix/mycelium.en-us.wxl b/components/mycelium/installers/windows/wix/mycelium.en-us.wxl
new file mode 100644
index 0000000..3f49151
--- /dev/null
+++ b/components/mycelium/installers/windows/wix/mycelium.en-us.wxl
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/components/mycelium/installers/windows/wix/mycelium.wxs b/components/mycelium/installers/windows/wix/mycelium.wxs
new file mode 100644
index 0000000..73d3327
--- /dev/null
+++ b/components/mycelium/installers/windows/wix/mycelium.wxs
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/mycelium/mobile/.gitignore b/components/mycelium/mobile/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/components/mycelium/mobile/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/components/mycelium/mobile/Cargo.lock b/components/mycelium/mobile/Cargo.lock
new file mode 100644
index 0000000..a664559
--- /dev/null
+++ b/components/mycelium/mobile/Cargo.lock
@@ -0,0 +1,3426 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom 0.2.15",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.2.15",
+ "once_cell",
+ "version_check",
+ "zerocopy 0.7.35",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_log-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
+
+[[package]]
+name = "anyhow"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
+
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "axum"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
+dependencies = [
+ "axum-core",
+ "bytes",
+ "form_urlencoded",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-extra"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
+dependencies = [
+ "axum",
+ "axum-core",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
+dependencies = [
+ "bincode_derive",
+ "serde",
+ "unty",
+]
+
+[[package]]
+name = "bincode_derive"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
+dependencies = [
+ "virtue",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
+dependencies = [
+ "bitflags 2.9.0",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash 1.1.0",
+ "shlex",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+
+[[package]]
+name = "blake3"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "c2rust-bitfields"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b43c3f07ab0ef604fa6f595aa46ec2f8a22172c975e186f6f5bf9829a3b72c41"
+dependencies = [
+ "c2rust-bitfields-derive",
+]
+
+[[package]]
+name = "c2rust-bitfields-derive"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3cbc102e2597c9744c8bd8c15915d554300601c91a079430d309816b0912545"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cdn-meta"
+version = "0.1.0"
+source = "git+https://github.com/threefoldtech/mycelium-cdn-registry#2fac04710b60502a5165f1b1d90671730346e977"
+dependencies = [
+ "aes-gcm",
+ "bincode",
+ "serde",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "dashmap"
+version = "6.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core 0.9.10",
+]
+
+[[package]]
+name = "deranged"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "dlopen2"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa"
+dependencies = [
+ "libc",
+ "once_cell",
+ "winapi",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "etherparse"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff83a5facf1a7cbfef93cfb48d6d4fb6a1f42d8ac2341a96b3255acb4d4f860"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "faster-hex"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73"
+dependencies = [
+ "heapless",
+ "serde",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generator"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
+dependencies = [
+ "cc",
+ "libc",
+ "log",
+ "rustversion",
+ "windows 0.48.0",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "glob"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
+
+[[package]]
+name = "h2"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash 0.7.8",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
+
+[[package]]
+name = "heapless"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
+dependencies = [
+ "hash32",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2 0.5.9",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+
+[[package]]
+name = "icu_properties"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.15.4",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-uring"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "ioctl-sys"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c"
+
+[[package]]
+name = "ip_network_table-deps-treebitmap"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d"
+
+[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "iri-string"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "left-right"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cabfddf3ad712b726484562039aa6fc2014bc1b5c088bb211b208052cf0439e6"
+dependencies = [
+ "loom",
+ "slab",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+
+[[package]]
+name = "libloading"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
+dependencies = [
+ "cfg-if",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "litemap"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "loom"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
+dependencies = [
+ "cfg-if",
+ "generator",
+ "scoped-tls",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "lru"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
+dependencies = [
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "matchit"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+dependencies = [
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "mobile"
+version = "0.1.0"
+dependencies = [
+ "mycelium",
+ "once_cell",
+ "thiserror 2.0.12",
+ "tokio",
+ "tracing",
+ "tracing-android",
+ "tracing-oslog",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "mycelium"
+version = "0.6.1"
+dependencies = [
+ "aes-gcm",
+ "ahash 0.8.11",
+ "arc-swap",
+ "axum",
+ "axum-extra",
+ "blake3",
+ "bytes",
+ "cdn-meta",
+ "dashmap",
+ "etherparse",
+ "faster-hex",
+ "futures",
+ "ip_network_table-deps-treebitmap",
+ "ipnet",
+ "left-right",
+ "libc",
+ "netdev",
+ "nix 0.29.0",
+ "nix 0.30.1",
+ "openssl",
+ "quinn",
+ "rand",
+ "rcgen",
+ "redis",
+ "reed-solomon-erasure",
+ "reqwest",
+ "rtnetlink",
+ "rustls",
+ "serde",
+ "tokio",
+ "tokio-stream",
+ "tokio-tun",
+ "tokio-util",
+ "tracing",
+ "tracing-logfmt",
+ "tracing-subscriber",
+ "tun",
+ "wintun 0.5.1",
+ "x25519-dalek",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "netdev"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862209dce034f82a44c95ce2b5183730d616f2a68746b9c1959aa2572e77c0a1"
+dependencies = [
+ "dlopen2",
+ "ipnet",
+ "libc",
+ "netlink-packet-core",
+ "netlink-packet-route 0.22.0",
+ "netlink-sys",
+ "once_cell",
+ "system-configuration",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "netlink-packet-core"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4"
+dependencies = [
+ "anyhow",
+ "bitflags 2.9.0",
+ "byteorder",
+ "libc",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56d83370a96813d7c977f8b63054f1162df6e5784f1c598d689236564fb5a6f2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.9.0",
+ "byteorder",
+ "libc",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-utils"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "paste",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "netlink-proto"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60"
+dependencies = [
+ "bytes",
+ "futures",
+ "log",
+ "netlink-packet-core",
+ "netlink-sys",
+ "thiserror 2.0.12",
+]
+
+[[package]]
+name = "netlink-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23"
+dependencies = [
+ "bytes",
+ "futures",
+ "libc",
+ "log",
+ "tokio",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags 2.9.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-src"
+version = "300.5.0+3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.10",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.11",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pem"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
+dependencies = [
+ "base64",
+ "serde",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy 0.8.24",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash 2.1.1",
+ "rustls",
+ "socket2 0.5.9",
+ "thiserror 2.0.12",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
+dependencies = [
+ "bytes",
+ "getrandom 0.3.2",
+ "lru-slab",
+ "rand",
+ "ring",
+ "rustc-hash 2.1.1",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.12",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2 0.5.9",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "rand"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
+dependencies = [
+ "rand_chacha",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.15",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.2",
+]
+
+[[package]]
+name = "rcgen"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49bc8ffa8a832eb1d7c8000337f8b0d2f4f2f5ec3cf4ddc26f125e3ad2451824"
+dependencies = [
+ "pem",
+ "ring",
+ "rustls-pki-types",
+ "time",
+ "yasna",
+]
+
+[[package]]
+name = "redis"
+version = "0.32.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1f66bf4cac9733a23bcdf1e0e01effbaaad208567beba68be8f67e5f4af3ee1"
+dependencies = [
+ "bytes",
+ "cfg-if",
+ "combine",
+ "futures-util",
+ "itoa",
+ "num-bigint",
+ "percent-encoding",
+ "pin-project-lite",
+ "ryu",
+ "sha1_smol",
+ "socket2 0.6.0",
+ "tokio",
+ "tokio-util",
+ "url",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
+dependencies = [
+ "bitflags 2.9.0",
+]
+
+[[package]]
+name = "reed-solomon-erasure"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7263373d500d4d4f505d43a2a662d475a894aa94503a1ee28e9188b5f3960d4f"
+dependencies = [
+ "libm",
+ "lru",
+ "parking_lot 0.11.2",
+ "smallvec",
+ "spin",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.9",
+ "regex-syntax 0.8.5",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.5",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "reqwest"
+version = "0.12.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.15",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rtnetlink"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbe0a03f6b9b483c67d4b328fc5d66c8db0b6aa274e0fa2def71b5e442a69acf"
+dependencies = [
+ "futures",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-route 0.24.0",
+ "netlink-packet-utils",
+ "netlink-proto",
+ "netlink-sys",
+ "nix 0.29.0",
+ "thiserror 1.0.69",
+ "tokio",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags 2.9.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.9.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1_smol"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
+
+[[package]]
+name = "socket2"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+dependencies = [
+ "bitflags 2.9.0",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.2",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+dependencies = [
+ "thiserror-impl 2.0.12",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.3.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
+
+[[package]]
+name = "time-macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.46.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "io-uring",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "slab",
+ "socket2 0.5.9",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "tokio-tun"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be25a316a2d10d0ddd46de9ddd73cdb4262678e1e52f4a32a31421f1e2b816b4"
+dependencies = [
+ "libc",
+ "nix 0.29.0",
+ "thiserror 2.0.12",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+dependencies = [
+ "bitflags 2.9.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-android"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12612be8f868a09c0ceae7113ff26afe79d81a24473a393cb9120ece162e86c0"
+dependencies = [
+ "android_log-sys",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-logfmt"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1f47d22deb79c3f59fcf2a1f00f60cbdc05462bf17d1cd356c1fefa3f444bd"
+dependencies = [
+ "nu-ansi-term 0.50.1",
+ "time",
+ "tracing",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-oslog"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528bdd1f0e27b5dd9a4ededf154e824b0532731e4af73bb531de46276e0aab1e"
+dependencies = [
+ "bindgen",
+ "cc",
+ "cfg-if",
+ "once_cell",
+ "parking_lot 0.12.3",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
+dependencies = [
+ "matchers",
+ "nu-ansi-term 0.46.0",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tun"
+version = "0.6.1"
+source = "git+https://github.com/LeeSmet/rust-tun#ae4c222e7a7aba5752cb08d2dbaf67bbc669c26a"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "futures-core",
+ "ioctl-sys",
+ "libc",
+ "log",
+ "thiserror 1.0.69",
+ "tokio",
+ "tokio-util",
+ "wintun 0.3.2",
+]
+
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "unty"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "virtue"
+version = "0.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.51.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-registry"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "wintun"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29b83b0eca06dd125dbcd48a45327c708a6da8aada3d95a3f06db0ce4b17e0d4"
+dependencies = [
+ "c2rust-bitfields",
+ "libloading",
+ "log",
+ "thiserror 1.0.69",
+ "windows 0.51.1",
+]
+
+[[package]]
+name = "wintun"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da99be64b5aa3de869c16977994314d0759a698d9a73ab0a5b1d52e2282033ae"
+dependencies = [
+ "c2rust-bitfields",
+ "libloading",
+ "log",
+ "thiserror 1.0.69",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags 2.9.0",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "x25519-dalek"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
+dependencies = [
+ "curve25519-dalek",
+ "rand_core 0.6.4",
+ "serde",
+ "zeroize",
+]
+
+[[package]]
+name = "yasna"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
+dependencies = [
+ "time",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "zerocopy-derive 0.7.35",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
+dependencies = [
+ "zerocopy-derive 0.8.24",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
diff --git a/components/mycelium/mobile/Cargo.toml b/components/mycelium/mobile/Cargo.toml
new file mode 100644
index 0000000..feda14b
--- /dev/null
+++ b/components/mycelium/mobile/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "mobile"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+mactunfd = ["mycelium/mactunfd"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+mycelium = { path = "../mycelium", features = ["vendored-openssl"] }
+tokio = { version = "1.44.0", features = ["signal", "rt-multi-thread"] }
+thiserror = "2.0.12"
+tracing = { version = "0.1.41", features = ["release_max_level_debug"] }
+tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
+once_cell = "1.21.1"
+
+[target.'cfg(target_os = "android")'.dependencies]
+tracing-android = "0.2.0"
+
+[target.'cfg(target_os = "ios")'.dependencies]
+tracing-oslog = "0.2.0"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+tracing-oslog = "0.2.0"
diff --git a/components/mycelium/mobile/README.md b/components/mycelium/mobile/README.md
new file mode 100644
index 0000000..e9f8f9e
--- /dev/null
+++ b/components/mycelium/mobile/README.md
@@ -0,0 +1,3 @@
+# mobile crate
+
+This crate will be called from Dart/Flutter, Kotlin(Android), or Swift(iOS)
\ No newline at end of file
diff --git a/components/mycelium/mobile/src/lib.rs b/components/mycelium/mobile/src/lib.rs
new file mode 100644
index 0000000..bc9b705
--- /dev/null
+++ b/components/mycelium/mobile/src/lib.rs
@@ -0,0 +1,284 @@
+use std::convert::TryFrom;
+use std::io;
+
+use tracing::{error, info};
+
+use metrics::Metrics;
+use mycelium::endpoint::Endpoint;
+use mycelium::{crypto, metrics, Config, Node};
+use once_cell::sync::Lazy;
+use tokio::sync::{mpsc, Mutex};
+use tokio::time::{sleep, timeout, Duration};
+
+const CHANNEL_MSG_OK: &str = "ok";
+const CHANNEL_TIMEOUT: u64 = 2;
+
+#[cfg(target_os = "android")]
+fn setup_logging() {
+ use tracing::level_filters::LevelFilter;
+ use tracing_subscriber::filter::Targets;
+ use tracing_subscriber::layer::SubscriberExt;
+ use tracing_subscriber::util::SubscriberInitExt;
+ let targets = Targets::new()
+ .with_default(LevelFilter::INFO)
+ .with_target("mycelium::router", LevelFilter::WARN);
+ tracing_subscriber::registry()
+ .with(tracing_android::layer("mycelium").expect("failed to setup logger"))
+ .with(targets)
+ .init();
+}
+
+#[cfg(any(target_os = "ios", target_os = "macos"))]
+fn setup_logging() {
+ use tracing::level_filters::LevelFilter;
+ use tracing_oslog::OsLogger;
+ use tracing_subscriber::filter::Targets;
+ use tracing_subscriber::layer::SubscriberExt;
+ use tracing_subscriber::util::SubscriberInitExt;
+ let targets = Targets::new()
+ .with_default(LevelFilter::INFO)
+ .with_target("mycelium::router", LevelFilter::WARN);
+ tracing_subscriber::registry()
+ .with(OsLogger::new("mycelium", "default"))
+ .with(targets)
+ .init();
+}
+
+#[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))]
+static INIT_LOG: Lazy<()> = Lazy::new(|| {
+ setup_logging();
+});
+
+#[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))]
+fn setup_logging_once() {
+ // Accessing the Lazy value will ensure setup_logging is called exactly once
+ let _ = &*INIT_LOG;
+}
+
+// Declare the channel globally so we can use it on the start & stop mycelium functions
+type CommandChannelType = (Mutex>, Mutex>);
+static COMMAND_CHANNEL: Lazy = Lazy::new(|| {
+ let (tx_cmd, rx_cmd) = mpsc::channel::(1);
+ (Mutex::new(tx_cmd), Mutex::new(rx_cmd))
+});
+
+type ResponseChannelType = (
+ Mutex>,
+ Mutex>,
+);
+static RESPONSE_CHANNEL: Lazy = Lazy::new(|| {
+ let (tx_resp, rx_resp) = mpsc::channel::(1);
+ (Mutex::new(tx_resp), Mutex::new(rx_resp))
+});
+
+#[tokio::main]
+#[allow(unused_variables)] // because tun_fd is only used in android and ios
+pub async fn start_mycelium(peers: Vec, tun_fd: i32, priv_key: Vec) {
+ #[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))]
+ setup_logging_once();
+
+ info!("starting mycelium");
+ let endpoints: Vec = peers
+ .into_iter()
+ .filter_map(|peer| peer.parse().ok())
+ .collect();
+
+ let secret_key = build_secret_key(priv_key).await.unwrap();
+
+ let config = Config {
+ node_key: secret_key,
+ peers: endpoints,
+ no_tun: false,
+ tcp_listen_port: DEFAULT_TCP_LISTEN_PORT,
+ quic_listen_port: None,
+ peer_discovery_port: None, // disable multicast discovery
+ #[cfg(any(
+ target_os = "linux",
+ all(target_os = "macos", not(feature = "mactunfd")),
+ target_os = "windows"
+ ))]
+ tun_name: "tun0".to_string(),
+
+ metrics: NoMetrics,
+ private_network_config: None,
+ firewall_mark: None,
+ #[cfg(any(
+ target_os = "android",
+ target_os = "ios",
+ all(target_os = "macos", feature = "mactunfd"),
+ ))]
+ tun_fd: Some(tun_fd),
+ update_workers: 1,
+ cdn_cache: None,
+ };
+ let _node = match Node::new(config).await {
+ Ok(node) => {
+ info!("node successfully created");
+ node
+ }
+ Err(err) => {
+ error!("failed to create mycelium node: {err}");
+ return;
+ }
+ };
+
+ let mut rx = COMMAND_CHANNEL.1.lock().await;
+ loop {
+ tokio::select! {
+ _ = tokio::signal::ctrl_c() => {
+ info!("Received SIGINT, stopping mycelium node");
+ break;
+ }
+ cmd = rx.recv() => {
+ match cmd.unwrap().cmd {
+ CmdType::Stop => {
+ info!("Received stop command, stopping mycelium node");
+ send_response(vec![CHANNEL_MSG_OK.to_string()]).await;
+ break;
+ }
+ CmdType::Status => {
+ let mut vec: Vec = Vec::new();
+ for info in _node.peer_info() {
+ vec.push(info.endpoint.proto().to_string() + ","+ info.endpoint.address().to_string().as_str()+","+ &info.connection_state.to_string());
+ }
+ send_response(vec).await;
+ }
+ }
+ }
+ }
+ }
+ info!("mycelium stopped");
+}
+
+struct Cmd {
+ cmd: CmdType,
+}
+
+enum CmdType {
+ Stop,
+ Status,
+}
+
+struct Response {
+ response: Vec,
+}
+
+// stop_mycelium returns string with the status of the command
+#[tokio::main]
+pub async fn stop_mycelium() -> String {
+ if let Err(e) = send_command(CmdType::Stop).await {
+ return e.to_string();
+ }
+
+ match recv_response().await {
+ Ok(_) => CHANNEL_MSG_OK.to_string(),
+ Err(e) => e.to_string(),
+ }
+}
+
+// get_peer_status returns vector of string
+// first element is always the status of the command (ok or error)
+// next elements are the peer status
+#[tokio::main]
+pub async fn get_peer_status() -> Vec {
+ if let Err(e) = send_command(CmdType::Status).await {
+ return vec![e.to_string()];
+ }
+
+ match recv_response().await {
+ Ok(mut resp) => {
+ resp.insert(0, CHANNEL_MSG_OK.to_string());
+ resp
+ }
+ Err(e) => vec![e.to_string()],
+ }
+}
+
+#[tokio::main]
+pub async fn get_status() -> Result {
+ Err(NodeError::NodeDead)
+}
+
+use thiserror::Error;
+#[derive(Error, Debug)]
+pub enum NodeError {
+ #[error("err_node_dead")]
+ NodeDead,
+
+ #[error("err_node_timeout")]
+ NodeTimeout,
+}
+
+async fn send_command(cmd_type: CmdType) -> Result<(), NodeError> {
+ let tx = COMMAND_CHANNEL.0.lock().await;
+ tokio::select! {
+ _ = sleep(Duration::from_secs(CHANNEL_TIMEOUT)) => {
+ Err(NodeError::NodeTimeout)
+ }
+ result = tx.send(Cmd { cmd: cmd_type }) => {
+ match result {
+ Ok(_) => Ok(()),
+ Err(_) => Err(NodeError::NodeDead)
+ }
+ }
+ }
+}
+
+async fn send_response(resp: Vec) {
+ let tx = RESPONSE_CHANNEL.0.lock().await;
+
+ tokio::select! {
+ _ = sleep(Duration::from_secs(CHANNEL_TIMEOUT)) => {
+ error!("send_response timeout");
+ }
+ result = tx.send(Response { response: resp }) => {
+ match result {
+ Ok(_) => {},
+ Err(_) =>{error!("send_response failed");},
+ }
+ }
+ }
+}
+
+async fn recv_response() -> Result, NodeError> {
+ let mut rx = RESPONSE_CHANNEL.1.lock().await;
+ let duration = Duration::from_secs(CHANNEL_TIMEOUT);
+ match timeout(duration, rx.recv()).await {
+ Ok(result) => match result {
+ Some(resp) => Ok(resp.response),
+ None => Err(NodeError::NodeDead),
+ },
+ Err(_) => Err(NodeError::NodeTimeout),
+ }
+}
+
+#[derive(Clone)]
+pub struct NoMetrics;
+impl Metrics for NoMetrics {}
+
+/// The default port on the underlay to listen on for incoming TCP connections.
+const DEFAULT_TCP_LISTEN_PORT: u16 = 9651;
+
+fn convert_slice_to_array32(slice: &[u8]) -> Result<[u8; 32], std::array::TryFromSliceError> {
+ <[u8; 32]>::try_from(slice)
+}
+
+async fn build_secret_key(bin: Vec) -> Result
+where
+ T: From<[u8; 32]>,
+{
+ Ok(T::from(convert_slice_to_array32(bin.as_slice()).unwrap()))
+}
+
+/// generate secret key
+/// it is used by android & ios app
+pub fn generate_secret_key() -> Vec {
+ crypto::SecretKey::new().as_bytes().into()
+}
+
+/// generate node_address from secret key
+pub fn address_from_secret_key(data: Vec) -> String {
+ let data = <[u8; 32]>::try_from(data.as_slice()).unwrap();
+ let secret_key = crypto::SecretKey::from(data);
+ crypto::PublicKey::from(&secret_key).address().to_string()
+}
diff --git a/components/mycelium/mycelium-api/Cargo.toml b/components/mycelium/mycelium-api/Cargo.toml
new file mode 100644
index 0000000..2067221
--- /dev/null
+++ b/components/mycelium/mycelium-api/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "mycelium-api"
+version = "0.6.1"
+edition = "2021"
+license-file = "../LICENSE"
+readme = "../README.md"
+
+[features]
+message = ["mycelium/message"]
+
+[dependencies]
+axum = { version = "0.8.4", default-features = false, features = [
+ "http1",
+ "http2",
+ "json",
+ "query",
+ "tokio",
+] }
+base64 = "0.22.1"
+jsonrpsee = { version = "0.25.1", features = [
+ "server",
+ "macros",
+ "jsonrpsee-types",
+] }
+serde_json = "1.0.140"
+tracing = "0.1.41"
+tokio = { version = "1.46.1", default-features = false, features = [
+ "net",
+ "rt",
+] }
+mycelium = { path = "../mycelium" }
+mycelium-metrics = { path = "../mycelium-metrics", features = ["prometheus"] }
+serde = { version = "1.0.219", features = ["derive"] }
+async-trait = "0.1.88"
+
+[dev-dependencies]
+serde_json = "1.0.140"
diff --git a/components/mycelium/mycelium-api/src/lib.rs b/components/mycelium/mycelium-api/src/lib.rs
new file mode 100644
index 0000000..ca1c46e
--- /dev/null
+++ b/components/mycelium/mycelium-api/src/lib.rs
@@ -0,0 +1,492 @@
+use core::fmt;
+use std::{net::IpAddr, net::SocketAddr, str::FromStr, sync::Arc};
+
+use axum::{
+ extract::{Path, State},
+ http::StatusCode,
+ routing::{delete, get},
+ Json, Router,
+};
+use serde::{de, Deserialize, Deserializer, Serialize};
+use tokio::{sync::Mutex, time::Instant};
+use tracing::{debug, error};
+
+use mycelium::{
+ crypto::PublicKey,
+ endpoint::Endpoint,
+ metrics::Metrics,
+ peer_manager::{PeerExists, PeerNotFound, PeerStats},
+};
+
+const INFINITE_STR: &str = "infinite";
+
+#[cfg(feature = "message")]
+mod message;
+#[cfg(feature = "message")]
+pub use message::{MessageDestination, MessageReceiveInfo, MessageSendInfo, PushMessageResponse};
+
+pub use rpc::JsonRpc;
+
+// JSON-RPC API implementation
+pub mod rpc;
+
+/// Http API server handle. The server is spawned in a background task. If this handle is dropped,
+/// the server is terminated.
+pub struct Http {
+ /// Channel to send cancellation to the http api server. We just keep a reference to it since
+ /// dropping it will also cancel the receiver and thus the server.
+ _cancel_tx: tokio::sync::oneshot::Sender<()>,
+}
+
+#[derive(Clone)]
+/// Shared state accessible in HTTP endpoint handlers.
+pub struct ServerState {
+ /// Access to the (`node`)(mycelium::Node) state.
+ pub node: Arc>>,
+}
+
+impl Http {
+ /// Spawns a new HTTP API server on the provided listening address.
+ pub fn spawn(node: Arc>>, listen_addr: SocketAddr) -> Self
+ where
+ M: Metrics + Clone + Send + Sync + 'static,
+ {
+ let server_state = ServerState { node };
+ let admin_routes = Router::new()
+ .route("/admin", get(get_info))
+ .route("/admin/peers", get(get_peers).post(add_peer))
+ .route("/admin/peers/{endpoint}", delete(delete_peer))
+ .route("/admin/routes/selected", get(get_selected_routes))
+ .route("/admin/routes/fallback", get(get_fallback_routes))
+ .route("/admin/routes/queried", get(get_queried_routes))
+ .route("/admin/routes/no_route", get(get_no_route_entries))
+ .route("/pubkey/{ip}", get(get_pubk_from_ip))
+ .with_state(server_state.clone());
+ let app = Router::new().nest("/api/v1", admin_routes);
+ #[cfg(feature = "message")]
+ let app = app.nest("/api/v1", message::message_router_v1(server_state));
+
+ let (_cancel_tx, cancel_rx) = tokio::sync::oneshot::channel();
+
+ tokio::spawn(async move {
+ let listener = match tokio::net::TcpListener::bind(listen_addr).await {
+ Ok(listener) => listener,
+ Err(e) => {
+ error!(err=%e, "Failed to bind listener for Http Api server");
+ error!("API disabled");
+ return;
+ }
+ };
+
+ let server =
+ axum::serve(listener, app.into_make_service()).with_graceful_shutdown(async {
+ cancel_rx.await.ok();
+ });
+
+ if let Err(e) = server.await {
+ error!(err=%e, "Http API server error");
+ }
+ });
+ Http { _cancel_tx }
+ }
+}
+
+/// Get the stats of the current known peers
+async fn get_peers(State(state): State>) -> Json>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!("Fetching peer stats");
+ Json(state.node.lock().await.peer_info())
+}
+
+/// Payload of an add_peer request
+#[derive(Deserialize, Serialize)]
+pub struct AddPeer {
+ /// The endpoint used to connect to the peer
+ pub endpoint: String,
+}
+
+/// Add a new peer to the system
+async fn add_peer(
+ State(state): State>,
+ Json(payload): Json,
+) -> Result
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!(
+ peer.endpoint = payload.endpoint,
+ "Attempting to add peer to the system"
+ );
+ let endpoint = match Endpoint::from_str(&payload.endpoint) {
+ Ok(endpoint) => endpoint,
+ Err(e) => return Err((StatusCode::BAD_REQUEST, e.to_string())),
+ };
+
+ match state.node.lock().await.add_peer(endpoint) {
+ Ok(()) => Ok(StatusCode::NO_CONTENT),
+ Err(PeerExists) => Err((
+ StatusCode::CONFLICT,
+ "A peer identified by that endpoint already exists".to_string(),
+ )),
+ }
+}
+
+/// remove an existing peer from the system
+async fn delete_peer(
+ State(state): State>,
+ Path(endpoint): Path,
+) -> Result
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!(peer.endpoint=%endpoint, "Attempting to remove peer from the system");
+ let endpoint = match Endpoint::from_str(&endpoint) {
+ Ok(endpoint) => endpoint,
+ Err(e) => return Err((StatusCode::BAD_REQUEST, e.to_string())),
+ };
+
+ match state.node.lock().await.remove_peer(endpoint) {
+ Ok(()) => Ok(StatusCode::NO_CONTENT),
+ Err(PeerNotFound) => Err((
+ StatusCode::NOT_FOUND,
+ "A peer identified by that endpoint does not exist".to_string(),
+ )),
+ }
+}
+
+/// Alias to a [`Metric`](crate::metric::Metric) for serialization in the API.
+#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
+pub enum Metric {
+ /// Finite metric
+ Value(u16),
+ /// Infinite metric
+ Infinite,
+}
+
+/// Info about a route. This uses base types only to avoid having to introduce too many Serialize
+/// bounds in the core types.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord)]
+#[serde(rename_all = "camelCase")]
+pub struct Route {
+ /// We convert the [`subnet`](Subnet) to a string to avoid introducing a bound on the actual
+ /// type.
+ pub subnet: String,
+ /// Next hop of the route, in the underlay.
+ pub next_hop: String,
+ /// Computed metric of the route.
+ pub metric: Metric,
+ /// Sequence number of the route.
+ pub seqno: u16,
+}
+
+/// List all currently selected routes.
+async fn get_selected_routes(State(state): State>) -> Json>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!("Loading selected routes");
+ let routes = state
+ .node
+ .lock()
+ .await
+ .selected_routes()
+ .into_iter()
+ .map(|sr| Route {
+ subnet: sr.source().subnet().to_string(),
+ next_hop: sr.neighbour().connection_identifier().clone(),
+ metric: if sr.metric().is_infinite() {
+ Metric::Infinite
+ } else {
+ Metric::Value(sr.metric().into())
+ },
+ seqno: sr.seqno().into(),
+ })
+ .collect();
+
+ Json(routes)
+}
+
+/// List all active fallback routes.
+async fn get_fallback_routes(State(state): State>) -> Json>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!("Loading fallback routes");
+ let routes = state
+ .node
+ .lock()
+ .await
+ .fallback_routes()
+ .into_iter()
+ .map(|sr| Route {
+ subnet: sr.source().subnet().to_string(),
+ next_hop: sr.neighbour().connection_identifier().clone(),
+ metric: if sr.metric().is_infinite() {
+ Metric::Infinite
+ } else {
+ Metric::Value(sr.metric().into())
+ },
+ seqno: sr.seqno().into(),
+ })
+ .collect();
+
+ Json(routes)
+}
+
+/// Info about a queried subnet. This uses base types only to avoid having to introduce too
+/// many Serialize bounds in the core types.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord)]
+#[serde(rename_all = "camelCase")]
+pub struct QueriedSubnet {
+ /// We convert the [`subnet`](Subnet) to a string to avoid introducing a bound on the actual
+ /// type.
+ pub subnet: String,
+ /// The amount of time left before the query expires.
+ pub expiration: String,
+}
+
+async fn get_queried_routes(State(state): State>) -> Json>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!("Loading queried subnets");
+ let queries = state
+ .node
+ .lock()
+ .await
+ .queried_subnets()
+ .into_iter()
+ .map(|qs| QueriedSubnet {
+ subnet: qs.subnet().to_string(),
+ expiration: qs
+ .query_expires()
+ .duration_since(Instant::now())
+ .as_secs()
+ .to_string(),
+ })
+ .collect();
+
+ Json(queries)
+}
+
+/// Info about a subnet with no route. This uses base types only to avoid having to introduce too
+/// many Serialize bounds in the core types.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord)]
+#[serde(rename_all = "camelCase")]
+pub struct NoRouteSubnet {
+ /// We convert the [`subnet`](Subnet) to a string to avoid introducing a bound on the actual
+ /// type.
+ pub subnet: String,
+ /// The amount of time left before the query expires.
+ pub expiration: String,
+}
+
+async fn get_no_route_entries(State(state): State>) -> Json>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!("Loading queried subnets");
+ let queries = state
+ .node
+ .lock()
+ .await
+ .no_route_entries()
+ .into_iter()
+ .map(|nrs| NoRouteSubnet {
+ subnet: nrs.subnet().to_string(),
+ expiration: nrs
+ .entry_expires()
+ .duration_since(Instant::now())
+ .as_secs()
+ .to_string(),
+ })
+ .collect();
+
+ Json(queries)
+}
+
+/// General info about a node.
+#[derive(Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Info {
+ /// The overlay subnet in use by the node.
+ pub node_subnet: String,
+ /// The public key of the node
+ pub node_pubkey: PublicKey,
+}
+
+/// Get general info about the node.
+async fn get_info(State(state): State>) -> Json
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ let info = state.node.lock().await.info();
+ Json(Info {
+ node_subnet: info.node_subnet.to_string(),
+ node_pubkey: info.node_pubkey,
+ })
+}
+
+/// Public key from a node.
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PubKey {
+ /// The public key from the node
+ pub public_key: PublicKey,
+}
+
+/// Get public key from IP.
+async fn get_pubk_from_ip(
+ State(state): State>,
+ Path(ip): Path,
+) -> Result, StatusCode>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ match state.node.lock().await.get_pubkey_from_ip(ip) {
+ Some(pubkey) => Ok(Json(PubKey { public_key: pubkey })),
+ None => Err(StatusCode::NOT_FOUND),
+ }
+}
+
+impl Serialize for Metric {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ match self {
+ Self::Infinite => serializer.serialize_str(INFINITE_STR),
+ Self::Value(v) => serializer.serialize_u16(*v),
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for Metric {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ struct MetricVisitor;
+
+ impl serde::de::Visitor<'_> for MetricVisitor {
+ type Value = Metric;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ formatter.write_str("a string or a u16")
+ }
+
+ fn visit_str(self, value: &str) -> Result
+ where
+ E: serde::de::Error,
+ {
+ match value {
+ INFINITE_STR => Ok(Metric::Infinite),
+ _ => Err(serde::de::Error::invalid_value(
+ serde::de::Unexpected::Str(value),
+ &format!("expected '{INFINITE_STR}'").as_str(),
+ )),
+ }
+ }
+
+ fn visit_u64(self, value: u64) -> Result
+ where
+ E: de::Error,
+ {
+ if value <= u16::MAX as u64 {
+ Ok(Metric::Value(value as u16))
+ } else {
+ Err(E::invalid_value(
+ de::Unexpected::Unsigned(value),
+ &"expected a non-negative integer within the range of u16",
+ ))
+ }
+ }
+ }
+ deserializer.deserialize_any(MetricVisitor)
+ }
+}
+
+impl fmt::Display for Metric {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Value(val) => write!(f, "{val}"),
+ Self::Infinite => write!(f, "{INFINITE_STR}"),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_json::json;
+
+ #[test]
+ fn finite_metric_serialization() {
+ let metric = super::Metric::Value(10);
+ let s = serde_json::to_string(&metric).expect("can encode finite metric");
+
+ assert_eq!("10", s);
+ }
+
+ #[test]
+ fn infinite_metric_serialization() {
+ let metric = super::Metric::Infinite;
+ let s = serde_json::to_string(&metric).expect("can encode infinite metric");
+
+ assert_eq!(format!("\"{INFINITE_STR}\""), s);
+ }
+
+ #[test]
+ fn test_deserialize_metric() {
+ // Test deserialization of a Metric::Value
+ let json_value = json!(20);
+ let metric: Metric = serde_json::from_value(json_value).unwrap();
+ assert_eq!(metric, Metric::Value(20));
+
+ // Test deserialization of a Metric::Infinite
+ let json_infinite = json!(INFINITE_STR);
+ let metric: Metric = serde_json::from_value(json_infinite).unwrap();
+ assert_eq!(metric, Metric::Infinite);
+
+ // Test deserialization of an invalid metric
+ let json_invalid = json!("invalid");
+ let result: Result = serde_json::from_value(json_invalid);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_deserialize_route() {
+ let json_data = r#"
+ [
+ {"subnet":"406:1d77:2438:aa7c::/64","nextHop":"TCP [2a02:1811:d584:7400:c503:ff39:de03:9e44]:45694 <-> [2a01:4f8:212:fa6::2]:9651","metric":20,"seqno":0},
+ {"subnet":"407:8458:dbf5:4ed7::/64","nextHop":"TCP [2a02:1811:d584:7400:c503:ff39:de03:9e44]:45694 <-> [2a01:4f8:212:fa6::2]:9651","metric":174,"seqno":0},
+ {"subnet":"408:7ba3:3a4d:808a::/64","nextHop":"TCP [2a02:1811:d584:7400:c503:ff39:de03:9e44]:45694 <-> [2a01:4f8:212:fa6::2]:9651","metric":"infinite","seqno":0}
+ ]
+ "#;
+
+ let routes: Vec = serde_json::from_str(json_data).unwrap();
+
+ assert_eq!(routes[0], Route {
+ subnet: "406:1d77:2438:aa7c::/64".to_string(),
+ next_hop: "TCP [2a02:1811:d584:7400:c503:ff39:de03:9e44]:45694 <-> [2a01:4f8:212:fa6::2]:9651".to_string(),
+ metric: Metric::Value(20),
+ seqno: 0
+ });
+
+ assert_eq!(routes[1], Route {
+ subnet: "407:8458:dbf5:4ed7::/64".to_string(),
+ next_hop: "TCP [2a02:1811:d584:7400:c503:ff39:de03:9e44]:45694 <-> [2a01:4f8:212:fa6::2]:9651".to_string(),
+ metric: Metric::Value(174),
+ seqno: 0
+ });
+
+ assert_eq!(routes[2], Route {
+ subnet: "408:7ba3:3a4d:808a::/64".to_string(),
+ next_hop: "TCP [2a02:1811:d584:7400:c503:ff39:de03:9e44]:45694 <-> [2a01:4f8:212:fa6::2]:9651".to_string(),
+ metric: Metric::Infinite,
+ seqno: 0
+ });
+ }
+}
diff --git a/components/mycelium/mycelium-api/src/message.rs b/components/mycelium/mycelium-api/src/message.rs
new file mode 100644
index 0000000..888b95b
--- /dev/null
+++ b/components/mycelium/mycelium-api/src/message.rs
@@ -0,0 +1,652 @@
+use std::{net::IpAddr, ops::Deref, time::Duration};
+
+use axum::{
+ extract::{Path, Query, State},
+ http::StatusCode,
+ routing::{delete, get, post},
+ Json, Router,
+};
+use serde::{Deserialize, Serialize};
+use tracing::debug;
+
+use mycelium::{
+ crypto::PublicKey,
+ message::{MessageId, MessageInfo},
+ metrics::Metrics,
+ subnet::Subnet,
+};
+use std::path::PathBuf;
+
+use super::ServerState;
+
+/// Default amount of time to try and send a message if it is not explicitly specified.
+const DEFAULT_MESSAGE_TRY_DURATION: Duration = Duration::from_secs(60 * 5);
+
+/// Return a router which has message endpoints and their handlers mounted.
+pub fn message_router_v1(server_state: ServerState) -> Router
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ Router::new()
+ .route("/messages", get(get_message).post(push_message))
+ .route("/messages/status/{id}", get(message_status))
+ .route("/messages/reply/{id}", post(reply_message))
+ // Topic configuration endpoints
+ .route(
+ "/messages/topics/default",
+ get(get_default_topic_action).put(set_default_topic_action),
+ )
+ .route("/messages/topics", get(get_topics).post(add_topic))
+ .route("/messages/topics/{topic}", delete(remove_topic))
+ .route(
+ "/messages/topics/{topic}/sources",
+ get(get_topic_sources).post(add_topic_source),
+ )
+ .route(
+ "/messages/topics/{topic}/sources/{subnet}",
+ delete(remove_topic_source),
+ )
+ .route(
+ "/messages/topics/{topic}/forward",
+ get(get_topic_forward_socket)
+ .put(set_topic_forward_socket)
+ .delete(remove_topic_forward_socket),
+ )
+ .with_state(server_state)
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MessageSendInfo {
+ pub dst: MessageDestination,
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(with = "base64::optional_binary")]
+ pub topic: Option>,
+ #[serde(with = "base64::binary")]
+ pub payload: Vec,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum MessageDestination {
+ Ip(IpAddr),
+ Pk(PublicKey),
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MessageReceiveInfo {
+ pub id: MessageId,
+ pub src_ip: IpAddr,
+ pub src_pk: PublicKey,
+ pub dst_ip: IpAddr,
+ pub dst_pk: PublicKey,
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(with = "base64::optional_binary")]
+ pub topic: Option>,
+ #[serde(with = "base64::binary")]
+ pub payload: Vec,
+}
+
+impl MessageDestination {
+ /// Get the IP address of the destination.
+ fn ip(self) -> IpAddr {
+ match self {
+ MessageDestination::Ip(ip) => ip,
+ MessageDestination::Pk(pk) => IpAddr::V6(pk.address()),
+ }
+ }
+}
+
+#[derive(Deserialize)]
+struct GetMessageQuery {
+ peek: Option,
+ timeout: Option,
+ /// Optional filter for start of the message, base64 encoded.
+ #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(with = "base64::optional_binary")]
+ topic: Option>,
+}
+
+impl GetMessageQuery {
+ /// Did the query indicate we should peek the message instead of pop?
+ fn peek(&self) -> bool {
+ matches!(self.peek, Some(true))
+ }
+
+ /// Amount of seconds to hold and try and get values.
+ fn timeout_secs(&self) -> u64 {
+ self.timeout.unwrap_or(0)
+ }
+}
+
+async fn get_message(
+ State(state): State>,
+ Query(query): Query,
+) -> Result, StatusCode>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!(
+ "Attempt to get message, peek {}, timeout {} seconds",
+ query.peek(),
+ query.timeout_secs()
+ );
+
+ // A timeout of 0 seconds essentially means get a message if there is one, and return
+ // immediatly if there isn't. This is the result of the implementation of Timeout, which does a
+ // poll of the internal future first, before polling the delay.
+ tokio::time::timeout(
+ Duration::from_secs(query.timeout_secs()),
+ state
+ .node
+ .lock()
+ .await
+ .get_message(!query.peek(), query.topic),
+ )
+ .await
+ .or(Err(StatusCode::NO_CONTENT))
+ .map(|m| {
+ Json(MessageReceiveInfo {
+ id: m.id,
+ src_ip: m.src_ip,
+ src_pk: m.src_pk,
+ dst_ip: m.dst_ip,
+ dst_pk: m.dst_pk,
+ topic: if m.topic.is_empty() {
+ None
+ } else {
+ Some(m.topic)
+ },
+ payload: m.data,
+ })
+ })
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MessageIdReply {
+ pub id: MessageId,
+}
+
+#[derive(Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(untagged)]
+pub enum PushMessageResponse {
+ Reply(MessageReceiveInfo),
+ Id(MessageIdReply),
+}
+
+#[derive(Clone, Deserialize)]
+struct PushMessageQuery {
+ reply_timeout: Option,
+}
+
+impl PushMessageQuery {
+ /// The user requested to wait for the reply or not.
+ fn await_reply(&self) -> bool {
+ self.reply_timeout.is_some()
+ }
+
+ /// Amount of seconds to wait for the reply.
+ fn timeout(&self) -> u64 {
+ self.reply_timeout.unwrap_or(0)
+ }
+}
+
+async fn push_message(
+ State(state): State>,
+ Query(query): Query,
+ Json(message_info): Json,
+) -> Result<(StatusCode, Json), StatusCode>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ let dst = message_info.dst.ip();
+ debug!(
+ message.dst=%dst,
+ message.len=message_info.payload.len(),
+ "Pushing new message to stack",
+ );
+
+ let (id, sub) = match state.node.lock().await.push_message(
+ dst,
+ message_info.payload,
+ message_info.topic,
+ DEFAULT_MESSAGE_TRY_DURATION,
+ query.await_reply(),
+ ) {
+ Ok((id, sub)) => (id, sub),
+ Err(_) => {
+ return Err(StatusCode::BAD_REQUEST);
+ }
+ };
+
+ if !query.await_reply() {
+ // If we don't wait for the reply just return here.
+ return Ok((
+ StatusCode::CREATED,
+ Json(PushMessageResponse::Id(MessageIdReply { id })),
+ ));
+ }
+
+ let mut sub = sub.unwrap();
+ tokio::select! {
+ sub_res = sub.changed() => {
+ match sub_res {
+ Ok(_) => {
+ if let Some(m) = sub.borrow().deref() {
+ Ok((StatusCode::OK, Json(PushMessageResponse::Reply(MessageReceiveInfo {
+ id: m.id,
+ src_ip: m.src_ip,
+ src_pk: m.src_pk,
+ dst_ip: m.dst_ip,
+ dst_pk: m.dst_pk,
+ topic: if m.topic.is_empty() { None } else { Some(m.topic.clone()) },
+ payload: m.data.clone(),
+ }))))
+ } else {
+ // This happens if a none value is send, which should not happen.
+ Err(StatusCode::INTERNAL_SERVER_ERROR)
+ }
+ }
+ Err(_) => {
+ // This happens if the sender drops, which should not happen.
+ Err(StatusCode::INTERNAL_SERVER_ERROR)
+ }
+ }
+ },
+ _ = tokio::time::sleep(Duration::from_secs(query.timeout())) => {
+ // Timeout expired while waiting for reply
+ Ok((StatusCode::REQUEST_TIMEOUT, Json(PushMessageResponse::Id(MessageIdReply { id }))))
+ }
+ }
+}
+
+async fn reply_message(
+ State(state): State>,
+ Path(id): Path,
+ Json(message_info): Json,
+) -> StatusCode
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ let dst = message_info.dst.ip();
+ debug!(
+ message.id=id.as_hex(),
+ message.dst=%dst,
+ message.len=message_info.payload.len(),
+ "Pushing new reply to message stack",
+ );
+
+ state.node.lock().await.reply_message(
+ id,
+ dst,
+ message_info.payload,
+ DEFAULT_MESSAGE_TRY_DURATION,
+ );
+
+ StatusCode::NO_CONTENT
+}
+
+async fn message_status(
+ State(state): State>,
+ Path(id): Path,
+) -> Result, StatusCode>
+where
+ M: Metrics + Clone + Send + Sync + 'static,
+{
+ debug!(message.id=%id.as_hex(), "Fetching message status");
+
+ state
+ .node
+ .lock()
+ .await
+ .message_status(id)
+ .ok_or(StatusCode::NOT_FOUND)
+ .map(Json)
+}
+
+/// Module to implement base64 decoding and encoding
+/// Sourced from https://users.rust-lang.org/t/serialize-a-vec-u8-to-json-as-base64/57781, with some
+/// addaptions to work with the new version of the base64 crate
+mod base64 {
+ use base64::engine::{GeneralPurpose, GeneralPurposeConfig};
+ use base64::{alphabet, Engine};
+
+ const B64ENGINE: GeneralPurpose = base64::engine::general_purpose::GeneralPurpose::new(
+ &alphabet::STANDARD,
+ GeneralPurposeConfig::new(),
+ );
+
+ pub fn encode(input: &[u8]) -> String {
+ B64ENGINE.encode(input)
+ }
+
+ pub fn decode(input: &[u8]) -> Result, base64::DecodeError> {
+ B64ENGINE.decode(input)
+ }
+
+ pub mod binary {
+ use super::B64ENGINE;
+ use base64::Engine;
+ use serde::{Deserialize, Serialize};
+ use serde::{Deserializer, Serializer};
+
+ pub fn serialize(v: &Vec, s: S) -> Result {
+ let base64 = B64ENGINE.encode(v);
+ String::serialize(&base64, s)
+ }
+
+ pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> {
+ let base64 = String::deserialize(d)?;
+ B64ENGINE
+ .decode(base64.as_bytes())
+ .map_err(serde::de::Error::custom)
+ }
+ }
+
+ pub mod optional_binary {
+ use super::B64ENGINE;
+ use base64::Engine;
+ use serde::{Deserialize, Serialize};
+ use serde::{Deserializer, Serializer};
+
+ pub fn serialize(v: &Option>, s: S) -> Result {
+ if let Some(v) = v {
+ let base64 = B64ENGINE.encode(v);
+ String::serialize(&base64, s)
+ } else {
+