├── .gitignore ├── rust-toolchain.toml ├── src ├── lib.rs ├── rathole │ ├── cmd │ │ ├── unknown.rs │ │ ├── ping.rs │ │ ├── resp.rs │ │ ├── exchange.rs │ │ ├── dial.rs │ │ ├── mod.rs │ │ └── hole.rs │ ├── context.rs │ ├── mod.rs │ ├── dispatcher.rs │ └── frame.rs ├── main.rs ├── store.rs ├── setup.rs ├── client.rs ├── server.rs └── config.rs ├── tests ├── examples │ ├── client-proxy.yaml │ ├── client-rathole.yaml │ └── server.yaml ├── socks.rs ├── command.rs ├── rathole.rs └── common │ └── mod.rs ├── examples ├── client-proxy.yaml ├── client-rathole.yaml ├── gen_cert.sh ├── systemd │ ├── client.service │ └── server.service ├── server.yaml ├── server.csr ├── server.crt └── server.key ├── Makefile ├── Cargo.toml ├── .github └── workflows │ ├── ci-test-build.yml │ └── release-build.yml ├── README.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .local-history 3 | .idea 4 | .DS_Store 5 | logs 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.86.0" 3 | components = [ "rustfmt", "clippy" ] 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | pub mod client; 5 | pub mod config; 6 | pub mod rathole; 7 | pub mod server; 8 | pub mod setup; 9 | pub mod store; 10 | -------------------------------------------------------------------------------- /tests/examples/client-proxy.yaml: -------------------------------------------------------------------------------- 1 | run_type: proxy 2 | proxy_addr: 127.0.0.1:4082 3 | remote_addr: 127.0.0.1:4982 4 | ssl_enable: true 5 | invalid_certs: true 6 | password: sQtfRnfhcNoZYZh1wY9u 7 | -------------------------------------------------------------------------------- /examples/client-proxy.yaml: -------------------------------------------------------------------------------- 1 | run_type: proxy 2 | proxy_mode: trojan # trojan,direct,websocket 3 | proxy_addr: 0.0.0.0:1082 4 | ssl_enable: true 5 | invalid_certs: true 6 | remote_addr: 127.0.0.1:4845 7 | password: sQtfRnfhcNoZYZh1wY9u 8 | -------------------------------------------------------------------------------- /examples/client-rathole.yaml: -------------------------------------------------------------------------------- 1 | run_type: rathole 2 | remote_addr: 127.0.0.1:4845 3 | ssl_enable: true 4 | invalid_certs: true 5 | password: 58JCEmvcBkRAk1XkK1iH 6 | 7 | holes: 8 | - name: ssh 9 | remote_addr: 127.0.0.1:4022 10 | local_addr: 127.0.0.1:22 11 | -------------------------------------------------------------------------------- /examples/gen_cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | openssl genrsa -out server.key 2048 4 | openssl req -new -key server.key -out server.csr -subj /C=CH/ST=Fun/L=Fun/O=Aman/OU=Aman/CN=www.example.com 5 | openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt 6 | -------------------------------------------------------------------------------- /tests/examples/client-rathole.yaml: -------------------------------------------------------------------------------- 1 | run_type: rathole 2 | remote_addr: 127.0.0.1:4982 3 | ssl_enable: true 4 | invalid_certs: true 5 | password: 58JCEmvcBkRAk1XkK1iH 6 | 7 | holes: 8 | - name: test 9 | remote_addr: 127.0.0.1:6788 10 | local_addr: 127.0.0.1:6789 11 | -------------------------------------------------------------------------------- /tests/examples/server.yaml: -------------------------------------------------------------------------------- 1 | addrs: 2 | - addr: 127.0.0.1:4982 3 | cert: examples/server.crt 4 | key: examples/server.key 5 | 6 | trojan: 7 | local_addr: 127.0.0.1:8800 #nginx 8 | passwords: 9 | - sQtfRnfhcNoZYZh1wY9u 10 | rathole: 11 | passwords: 12 | - 58JCEmvcBkRAk1XkK1iH 13 | 14 | -------------------------------------------------------------------------------- /examples/systemd/client.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Shuttle Client Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=nobody 8 | Restart=on-failure 9 | RestartSec=5s 10 | ExecStart=/opt/shuttle/shuttle /opt/shuttle/client.yaml 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /examples/systemd/server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Shuttle Server Service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | User=nobody 8 | Restart=on-failure 9 | RestartSec=5s 10 | ExecStart=/opt/shuttle/shuttle /opt/shuttle/server.yaml 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /examples/server.yaml: -------------------------------------------------------------------------------- 1 | addrs: 2 | - addr: 0.0.0.0:4845 3 | cert: examples/server.crt 4 | key: examples/server.key 5 | stats_addr: 127.0.0.1:8500 6 | stats_secret: your_secret 7 | trojan: 8 | local_addr: 127.0.0.1:80 #nginx 9 | passwords: 10 | - sQtfRnfhcNoZYZh1wY9u 11 | rathole: 12 | passwords: 13 | - 58JCEmvcBkRAk1XkK1iH 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := shuttle 2 | PACKAGE_NAME := github.com/cyejing/shuttle 3 | VERSION := `git describe --dirty` 4 | COMMIT := `git rev-parse HEAD` 5 | 6 | .PHONY: build 7 | 8 | build: 9 | cargo build --verbose --release 10 | 11 | test: 12 | cargo test --verbose 13 | 14 | clean: 15 | cargo clean 16 | 17 | check: 18 | cargo check 19 | cargo fmt --check -v 20 | cargo clippy -- -D warnings 21 | 22 | fix: 23 | cargo clippy --all --fix --allow-dirty --allow-staged 24 | cargo fmt 25 | 26 | publish: 27 | cargo publish --registry crates-io --manifest-path shuttle-station/Cargo.toml 28 | cargo doc 29 | 30 | -------------------------------------------------------------------------------- /src/rathole/cmd/unknown.rs: -------------------------------------------------------------------------------- 1 | use crate::rathole::cmd::CommandApply; 2 | use crate::rathole::cmd::resp::Resp; 3 | use crate::rathole::context::Context; 4 | 5 | #[derive(Debug)] 6 | pub struct Unknown { 7 | command_name: String, 8 | } 9 | 10 | impl Unknown { 11 | pub fn new(command_name: String) -> Unknown { 12 | Unknown { command_name } 13 | } 14 | } 15 | 16 | impl CommandApply for Unknown { 17 | async fn apply(&self, _context: Context) -> anyhow::Result> { 18 | let resp = Resp::Err(format!("ERR unknown command '{}'", self.command_name)); 19 | Ok(Some(resp)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/socks.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use log::info; 3 | 4 | use shuttle::setup::setup_log; 5 | 6 | #[tokio::test] 7 | async fn test_socks() { 8 | setup_log(); 9 | 10 | common::start_web_server().await; 11 | common::start_server("tests/examples/server.yaml").await; 12 | common::start_client("tests/examples/client-proxy.yaml").await; 13 | 14 | let client = reqwest::Client::builder() 15 | .proxy(reqwest::Proxy::http("socks5://127.0.0.1:4082").unwrap()) 16 | .build() 17 | .unwrap(); 18 | 19 | let resp = client 20 | .get("http://127.0.0.1:6080") 21 | .send() 22 | .await 23 | .unwrap() 24 | .text() 25 | .await 26 | .unwrap(); 27 | assert_eq!(resp, "Hello world!"); 28 | info!("assert eq {}", resp); 29 | } 30 | -------------------------------------------------------------------------------- /tests/command.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | use log::info; 3 | use shuttle::setup::setup_log; 4 | use tokio::io; 5 | 6 | use shuttle::rathole::cmd::Command; 7 | use shuttle::rathole::cmd::ping::Ping; 8 | use shuttle::rathole::dispatcher::{CommandRead, CommandWrite}; 9 | use tokio::net::TcpStream; 10 | 11 | #[tokio::test] 12 | async fn test_ping() { 13 | setup_log(); 14 | common::start_command_server().await; 15 | let stream = TcpStream::connect("127.0.0.1:6789").await.unwrap(); 16 | let (r, w) = io::split(stream); 17 | let mut command_write = CommandWrite::new(w); 18 | let mut command_read = CommandRead::new(r); 19 | let ping = Command::Ping(Ping::new(Some("hi".to_string()))); 20 | command_write.write_command(10, ping).await.unwrap(); 21 | 22 | let resp = command_read.read_command().await.unwrap(); 23 | info!("{:?}", resp); 24 | assert_eq!(format!("{:?}", resp), "(10, Resp(Ok(\"hi\")))"); 25 | } 26 | -------------------------------------------------------------------------------- /examples/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICpjCCAY4CAQAwYTELMAkGA1UEBhMCQ0gxDDAKBgNVBAgMA0Z1bjEMMAoGA1UE 3 | BwwDRnVuMQ0wCwYDVQQKDARBbWFuMQ0wCwYDVQQLDARBbWFuMRgwFgYDVQQDDA93 4 | d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDt 5 | jtpXaveN04cgq61G6FeQwZuTzDZwFwaxnfvrUBjT14RjchZLWlwHalQu+qlhLqmS 6 | t3yLdgekbI9TsF2WdVoqfs5i8eC5UaCaiKkxLiDHCApOxoyUDWBz/V/bJE+126cw 7 | wuLo/FGKKSKnXlVaGxzhlfkzhupmKkk8urY3UcBzbidThFjkdnEz8TXJQXRN+w9V 8 | JLPI3Pl/6YEnoKqDUrVo9RGeeNv6lSLTkMHptgpJDYpU9+X3I4QB+H02LItszp7e 9 | U0jusnWrXvsmI7dX/+dq0rRwASkm4cKiSQrVyfnlPnZy0/gxfhXCSZRRVytbRv3K 10 | eg1L4WVZ9/TqdtAUV8iDAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEADHJKAk5u 11 | JW88meFCnSsMNlEU4ei8Z7uvC3iahA7jUxqYtpf1ykSHwT5SG/i8JfGDIMk+EgBD 12 | hHMm6/r8GIYZjy90sxwTEbG8ECTxY2NflTj283mU5Y1RErHtIklAJ176SB+C4fqJ 13 | XYYSE5RAtakN77en7jwPhH21gUCGFODljUuo/MgVjIdNVOffc6uGlYrHGYt3wbOg 14 | yZmn+hxmfpHY6V31Jv/uaaRSR+Ems2KWLR//2+t5Mk8V1sJ1UXa3ujr9LVuaXHUo 15 | PHsN3cd2QutXzbvhalBbeyhbBi27Ko9RhdbfKDEGDWJtv+/k7vMBkzLXKGAGuHGc 16 | lKqdgBzy093q4g== 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /tests/rathole.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use shuttle::{ 4 | rathole::{ 5 | cmd::{Command, ping::Ping}, 6 | dispatcher::{CommandRead, CommandWrite}, 7 | }, 8 | setup::setup_log, 9 | }; 10 | use tokio::{io, net::TcpStream}; 11 | 12 | mod common; 13 | 14 | #[tokio::test] 15 | async fn test_rathole_ping() { 16 | setup_log(); 17 | common::start_command_server().await; 18 | common::start_server("tests/examples/server.yaml").await; 19 | common::start_client("tests/examples/client-rathole.yaml").await; 20 | tokio::time::sleep(Duration::from_secs(1)).await; 21 | 22 | let stream = TcpStream::connect("127.0.0.1:6788").await.unwrap(); 23 | let (r, w) = io::split(stream); 24 | let mut command_write = CommandWrite::new(w); 25 | let mut command_read = CommandRead::new(r); 26 | let ping = Command::Ping(Ping::new(Some("hi".to_string()))); 27 | command_write.write_command(10, ping).await.unwrap(); 28 | 29 | let resp = command_read.read_command().await.unwrap(); 30 | assert_eq!(format!("{:?}", resp), "(10, Resp(Ok(\"hi\")))"); 31 | } 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shuttle" 3 | version = "0.7.2" 4 | edition = "2024" 5 | publish = false 6 | 7 | [dependencies] 8 | borer-core = {version="0.4.6",features = ["websocket", "stats"]} 9 | 10 | log = {version="0.4", features=["kv_unstable_serde"]} 11 | tracing="0.1" 12 | tracing-appender="0.2" 13 | tracing-subscriber = {version = "0.3" ,features=["local-time","env-filter"]} 14 | rolling-file = "0.2" 15 | time = {version="0.3", features=["local-offset","macros"]} 16 | anyhow = { version = "1" } 17 | 18 | tokio = { version = "1", features=["full"]} 19 | tokio-rustls = "0.26" 20 | bytes = {version="1.4.0",default-features = false, features=["std"]} 21 | 22 | serde = { version = "1.0", features = ["derive"] } 23 | serde_yaml = "0.9" 24 | serde_json = "1" 25 | 26 | clap = { version = "4", features = ["derive"] } 27 | sha2 = "0.10" 28 | base16ct = { version = "0.2", features = ["alloc"] } 29 | atoi = "2" 30 | itertools = "0.14" 31 | uuid = {version="1.5.0", features=["v4"]} 32 | 33 | 34 | 35 | [dev-dependencies] 36 | reqwest = { version = "0.11", default-features=false, features = ["socks","rustls-tls"] } 37 | 38 | -------------------------------------------------------------------------------- /examples/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDcTCCAlmgAwIBAgIURYDPhq9EUcENWiFTkBucs7vlu7UwDQYJKoZIhvcNAQEL 3 | BQAwYTELMAkGA1UEBhMCQ0gxDDAKBgNVBAgMA0Z1bjEMMAoGA1UEBwwDRnVuMQ0w 4 | CwYDVQQKDARBbWFuMQ0wCwYDVQQLDARBbWFuMRgwFgYDVQQDDA93d3cuZXhhbXBs 5 | ZS5jb20wHhcNMjUwNDA1MjA0ODE0WhcNMzUwNDAzMjA0ODE0WjBhMQswCQYDVQQG 6 | EwJDSDEMMAoGA1UECAwDRnVuMQwwCgYDVQQHDANGdW4xDTALBgNVBAoMBEFtYW4x 7 | DTALBgNVBAsMBEFtYW4xGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJ 8 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO2O2ldq943ThyCrrUboV5DBm5PMNnAX 9 | BrGd++tQGNPXhGNyFktaXAdqVC76qWEuqZK3fIt2B6Rsj1OwXZZ1Wip+zmLx4LlR 10 | oJqIqTEuIMcICk7GjJQNYHP9X9skT7XbpzDC4uj8UYopIqdeVVobHOGV+TOG6mYq 11 | STy6tjdRwHNuJ1OEWOR2cTPxNclBdE37D1Uks8jc+X/pgSegqoNStWj1EZ542/qV 12 | ItOQwem2CkkNilT35fcjhAH4fTYsi2zOnt5TSO6ydate+yYjt1f/52rStHABKSbh 13 | wqJJCtXJ+eU+dnLT+DF+FcJJlFFXK1tG/cp6DUvhZVn39Op20BRXyIMCAwEAAaMh 14 | MB8wHQYDVR0OBBYEFBW7IPBTfqcUOhfJLIyTqxUv5hNjMA0GCSqGSIb3DQEBCwUA 15 | A4IBAQDXSThk2zsIreZL6yIYNBRxPrx/DS0wjySSYa7zODHV5ymYj25vPT6XSXjG 16 | dqcSDVmYYqncVgwJ0pBGS7gQcAcKP2RIqxjKBcnLaEtDnNrkEzpYrXrdpcTpC9ra 17 | XQxhz0MMNBaFKdc8pVmYVLFoirORYRdlROqIDC+ExlTjKa8lFicODuiVN1vBhRos 18 | cZ3p65BTUBYLdQZx+nDZCu4foQUIRfvqN/JO5GQ9RRUK4sDdmJeWC9wVX/SA71az 19 | b7gjg4YkshkYK3LqwISvxwxSKFDczbiLoKDvUvdNzrC2QLN+SJHfSbaLlwlNLFY9 20 | inimD0glsmJ3ruZxUkP4YgeSuo3J 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /src/rathole/cmd/ping.rs: -------------------------------------------------------------------------------- 1 | use crate::rathole::cmd::resp::Resp; 2 | use crate::rathole::cmd::{CommandApply, CommandParse, CommandTo}; 3 | use crate::rathole::context::Context; 4 | use crate::rathole::frame::{Frame, Parse, ParseError}; 5 | use bytes::Bytes; 6 | 7 | #[derive(Debug, Default)] 8 | pub struct Ping { 9 | msg: Option, 10 | } 11 | 12 | impl Ping { 13 | pub const COMMAND_NAME: &'static str = "ping"; 14 | 15 | pub fn new(msg: Option) -> Ping { 16 | Ping { msg } 17 | } 18 | } 19 | 20 | impl CommandParse for Ping { 21 | fn parse_frame(parse: &mut Parse) -> anyhow::Result { 22 | match parse.next_string() { 23 | Ok(msg) => Ok(Ping::new(Some(msg))), 24 | Err(ParseError::EndOfStream) => Ok(Ping::default()), 25 | Err(e) => Err(e.into()), 26 | } 27 | } 28 | } 29 | 30 | impl CommandTo for Ping { 31 | fn to_frame(&self) -> anyhow::Result { 32 | let mut f = Frame::array(); 33 | f.push_bulk(Bytes::from(Ping::COMMAND_NAME)); 34 | let msg = self.msg.as_ref().map_or("pong".to_string(), |x| x.clone()); 35 | f.push_bulk(Bytes::from(msg)); 36 | Ok(f) 37 | } 38 | } 39 | 40 | impl CommandApply for Ping { 41 | async fn apply(&self, _context: Context) -> anyhow::Result> { 42 | let resp = match &self.msg { 43 | None => Resp::Ok("PONG".to_string()), 44 | Some(msg) => Resp::Ok(msg.clone()), 45 | }; 46 | Ok(Some(resp)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Parser, Subcommand, command}; 4 | use shuttle::{ 5 | client::{start_proxy, start_rathole}, 6 | config::{ClientConfig, ServerConfig}, 7 | server::{self, start_stats_server}, 8 | setup::setup_log, 9 | store::ServerStore, 10 | }; 11 | 12 | #[derive(Parser, Debug)] 13 | #[command(version, about, long_about = None)] 14 | pub struct ArgsConfig { 15 | // running mode 16 | #[command(subcommand)] 17 | pub mode: Mode, 18 | 19 | /// config path 20 | #[clap(short, long, global = true)] 21 | pub config: Option, 22 | } 23 | 24 | #[derive(Debug, Subcommand)] 25 | pub enum Mode { 26 | Server, 27 | Client, 28 | } 29 | 30 | #[tokio::main] 31 | async fn main() { 32 | setup_log(); 33 | 34 | let args = ArgsConfig::parse(); 35 | match args.mode { 36 | Mode::Server => { 37 | start_server(args).await; 38 | } 39 | Mode::Client => { 40 | start_client(args).await; 41 | } 42 | } 43 | 44 | tokio::signal::ctrl_c().await.expect("shut down"); 45 | } 46 | 47 | async fn start_server(args: ArgsConfig) { 48 | let config = ServerConfig::load(args.config); 49 | let store = ServerStore::from(&config); 50 | for addr in config.addrs { 51 | server::start_server(&addr, store.clone()).await; 52 | } 53 | start_stats_server(config.stats_addr, config.stats_secret); 54 | } 55 | 56 | async fn start_client(args: ArgsConfig) { 57 | let cc = ClientConfig::load(args.config); 58 | match cc.run_type.as_str() { 59 | "proxy" => start_proxy(cc).await, 60 | "rathole" => start_rathole(cc).await, 61 | _ => panic!("unknown run type : {}", cc.run_type), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/ci-test-build.yml: -------------------------------------------------------------------------------- 1 | name: ci-test-build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ master ] 7 | paths: 8 | - "**/*.rs" 9 | - "Cargo.lock" 10 | - "Cargo.toml" 11 | - ".github/workflows/ci-test-build.yml" 12 | pull_request: 13 | branches: [ master ] 14 | paths: 15 | - "**/*.rs" 16 | - "Cargo.lock" 17 | - "Cargo.toml" 18 | - ".github/workflows/ci-test-build.yml" 19 | 20 | env: 21 | CARGO_TERM_COLOR: always 22 | 23 | jobs: 24 | lints: 25 | name: Lints 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: stable 32 | override: true 33 | components: rustfmt, clippy 34 | - uses: Swatinem/rust-cache@v1 35 | - name: Fmt Check 36 | run: cargo fmt --check -v 37 | - name: Clippy 38 | run: cargo clippy -- -D warnings 39 | - name: Setup cargo-hack 40 | run: cargo install cargo-hack 41 | - name: Check all features 42 | run: cargo hack check --feature-powerset --no-dev-deps 43 | 44 | test: 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | os: [ubuntu-latest, windows-latest, macos-latest] 49 | runs-on: ${{ matrix.os }} 50 | 51 | steps: 52 | - uses: actions/checkout@v2 53 | - uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: stable 57 | - uses: Swatinem/rust-cache@v1 58 | - name: Run tests 59 | if: matrix.os != 'windows-latest' 60 | run: cargo test --verbose 61 | - name: Run build 62 | run: cargo build --verbose 63 | -------------------------------------------------------------------------------- /examples/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA7Y7aV2r3jdOHIKutRuhXkMGbk8w2cBcGsZ3761AY09eEY3IW 3 | S1pcB2pULvqpYS6pkrd8i3YHpGyPU7BdlnVaKn7OYvHguVGgmoipMS4gxwgKTsaM 4 | lA1gc/1f2yRPtdunMMLi6PxRiikip15VWhsc4ZX5M4bqZipJPLq2N1HAc24nU4RY 5 | 5HZxM/E1yUF0TfsPVSSzyNz5f+mBJ6Cqg1K1aPURnnjb+pUi05DB6bYKSQ2KVPfl 6 | 9yOEAfh9NiyLbM6e3lNI7rJ1q177JiO3V//natK0cAEpJuHCokkK1cn55T52ctP4 7 | MX4VwkmUUVcrW0b9ynoNS+FlWff06nbQFFfIgwIDAQABAoIBAQDnghwOrVPrh4Vc 8 | tXH8dFlB3Sw88PrsacRAPHJRdJF9gb6nG3vWm4ysPVclIWsIJm1jaiWZ08DG4HUd 9 | XfcWte7VExcXHhmfAIm/kAcywn12+cLx2lUNw6uH3hkYe16jIBjjUEnKPHtKZaQ7 10 | 75bGZT3UKk/dg1HTlsogwOTrLGSdF4+4G6Fva4Qb+d2f488va6xAi/iS2ylh4+Lq 11 | cu+1C1H8Tf3XYVprdZ8rauYIwXvFLDmtEP4VAxSru2NoDcW6VAXwoYSeKDRhjogV 12 | Cppsq5qsB7cEFqjIXS44BraeCjeaVQ/YS0h736EgCgDbeRa63u+3CDabfmmWoYqA 13 | PgnI1N2BAoGBAPyGBFJ5PvdmTLDwdW2xZYowI5g9uNTcF+Wq5CHUq5IoN/dR5HiT 14 | lpK+pxg4cbGKupLL11fZmfG1O/wO9/2ZTI+IUg/BNNadvcqN+gXveYz0ZXuJz8y0 15 | bJys7PdsRdoPTLfjikW+VstSdFiX3jJHNtPR5kBfVglOqM0tra1g22PRAoGBAPDU 16 | F6CpJgy7fDzJhxp2s9cGSLkKgknQ/rBqxaHOXsSw/H0nTlUlSmu26yoFw1/iMKkp 17 | LLIRy+jZ2/Lk2xD57qq1tVV2GebHM2OIfN/sUxUOkiZ2Ql5kGaMfruh4/BPCt87k 18 | WMf0G2PmEHSzOlbGmcgMlj85lEUEOd/zQ45R0WATAoGAVNqbvTLsy6lef95tNyzz 19 | F9pRO6kR4MUMaqpf+lZCSNym4b5s7W96m1jhQSxGWAhr5UQF2olOR4BE5dAJ2PeY 20 | WGrQWGbt4GglDFlk+HWd/JVrPHX0k67VCFi7T/DcEAgwGWKmZgUtmSjd2588lNyF 21 | OexAWMUwXfRGmJuOzsHLNuECgYAWV5bZkNe21q3v9cJS8jA9Apg8kn0D3B/EviMX 22 | n82q8cB62+qoe40r8QxgGajGfHWHPAqpen0wXQXKwvCP4qABtlncU9iTUGe0FDWB 23 | kKskiPWGG/KMTz0RpR7U8mRwcOAckZKq+jqQSge8y+PdqQ8bQA+wTeczf/DelfLu 24 | gSirgQKBgQCahnLVG101zj2ySduk3bBAPIqTDoGfBwotchDDpuZ4OlCBSO/VgVB5 25 | hq/nvXd28onJXSHThbyGiIdLeRyFZNxy7CLr2N1TqnpirGfMUATAPVRcX8SAjd5y 26 | MBobGfVX9yBmmdpFWP9eLFxJL9HkjFaR6omwgKUd2zD4N78vSK9KzQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/rathole/cmd/resp.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use bytes::Bytes; 3 | 4 | use crate::rathole::cmd::{CommandApply, CommandParse, CommandTo}; 5 | use crate::rathole::context::Context; 6 | use crate::rathole::frame::{Frame, Parse}; 7 | 8 | #[derive(Debug)] 9 | pub enum Resp { 10 | Ok(String), 11 | Err(String), 12 | } 13 | 14 | impl Resp { 15 | pub const COMMAND_NAME: &'static str = "resp"; 16 | } 17 | 18 | impl CommandParse for Resp { 19 | fn parse_frame(parse: &mut Parse) -> anyhow::Result { 20 | match parse.next_part()? { 21 | Frame::Simple(msg) => Ok(Resp::Ok(msg)), 22 | Frame::Error(msg) => Ok(Resp::Err(msg)), 23 | frame => Err(anyhow!(format!( 24 | "protocol error; expected simple frame or err frame, got {:?}", 25 | frame 26 | ))), 27 | } 28 | } 29 | } 30 | 31 | impl CommandTo for Resp { 32 | fn to_frame(&self) -> anyhow::Result { 33 | let mut f = Frame::array(); 34 | f.push_bulk(Bytes::from(Resp::COMMAND_NAME)); 35 | match self { 36 | Self::Ok(msg) => f.push_frame(Frame::Simple(msg.clone())), 37 | Self::Err(msg) => f.push_frame(Frame::Error(msg.clone())), 38 | }; 39 | Ok(f) 40 | } 41 | } 42 | 43 | impl CommandApply for Resp { 44 | async fn apply(&self, context: Context) -> anyhow::Result> { 45 | let rc = context.get_req().await; 46 | if let Some(s) = rc.flatten() { 47 | let sr = match self { 48 | Resp::Ok(_msg) => s.send(Ok(())), 49 | Resp::Err(msg) => s.send(Err(anyhow!(msg.to_string()))), 50 | }; 51 | if sr.is_err() { 52 | debug!("req {:?} channel close: {:?}", context.current_req_id, sr); 53 | } 54 | } else { 55 | error!("req {:?} is None", context.current_req_id); 56 | }; 57 | Ok(None) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/rathole/cmd/exchange.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::rathole::cmd::resp::Resp; 4 | use crate::rathole::cmd::{CommandApply, CommandParse, CommandTo}; 5 | use crate::rathole::context::Context; 6 | use crate::rathole::frame::{Frame, Parse}; 7 | use bytes::Bytes; 8 | 9 | pub struct Exchange { 10 | conn_id: u64, 11 | body: Bytes, 12 | } 13 | 14 | impl Exchange { 15 | pub const COMMAND_NAME: &'static str = "exchange"; 16 | 17 | pub fn new(conn_id: u64, body: Bytes) -> Self { 18 | Exchange { conn_id, body } 19 | } 20 | } 21 | 22 | impl CommandParse for Exchange { 23 | fn parse_frame(parse: &mut Parse) -> anyhow::Result { 24 | let conn_id = parse.next_int()?; 25 | let body = parse.next_bytes()?; 26 | Ok(Exchange::new(conn_id, body)) 27 | } 28 | } 29 | 30 | impl CommandTo for Exchange { 31 | fn to_frame(&self) -> anyhow::Result { 32 | let mut f = Frame::array(); 33 | f.push_bulk(Bytes::from(Self::COMMAND_NAME)); 34 | f.push_int(self.conn_id); 35 | f.push_bulk(self.body.clone()); 36 | Ok(f) 37 | } 38 | } 39 | 40 | impl CommandApply for Exchange { 41 | async fn apply(&self, mut context: Context) -> anyhow::Result> { 42 | let conn_id = self.conn_id; 43 | let bytes = self.body.clone(); 44 | context.with_conn_id(conn_id); 45 | let op_sender = context.get_conn_sender().await; 46 | match op_sender { 47 | Some(sender) => { 48 | if let Err(e) = sender.send(bytes).await { 49 | context.remove_conn_sender().await; 50 | error!("send bytes err : {}", e); 51 | } 52 | } 53 | None => { 54 | debug!("exchange conn {} close", conn_id); 55 | } 56 | } 57 | Ok(Some(Resp::Ok("ok".to_string()))) 58 | } 59 | } 60 | 61 | impl Debug for Exchange { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 63 | f.debug_struct("Exchange") 64 | .field("conn_id", &self.conn_id) 65 | .field("body", &self.body.len()) 66 | .finish() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/store.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use itertools::Itertools; 5 | use tokio::sync::RwLock; 6 | 7 | use crate::config::{RatHole, ServerConfig, Trojan}; 8 | use crate::rathole::context::CommandSender; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct ServerStore { 12 | inner: Arc, 13 | } 14 | 15 | #[derive(Debug)] 16 | struct Inner { 17 | cmd_map: RwLock>, 18 | trojan: Trojan, 19 | rathole: RatHole, 20 | } 21 | 22 | impl ServerStore { 23 | pub fn new(trojan: Trojan, rathole: RatHole) -> Self { 24 | ServerStore { 25 | inner: Arc::new(Inner { 26 | cmd_map: RwLock::new(HashMap::new()), 27 | trojan, 28 | rathole, 29 | }), 30 | } 31 | } 32 | 33 | pub(crate) fn get_trojan(&self) -> Trojan { 34 | self.inner.trojan.clone() 35 | } 36 | 37 | pub(crate) fn get_rahole(&self) -> RatHole { 38 | self.inner.rathole.clone() 39 | } 40 | 41 | pub(crate) async fn set_cmd_sender(&self, sender: CommandSender) { 42 | self.inner 43 | .cmd_map 44 | .write() 45 | .await 46 | .insert(sender.get_hash(), sender); 47 | } 48 | 49 | #[allow(dead_code)] 50 | pub(crate) async fn get_cmd_sender(&self, hash: &String) -> Option { 51 | self.inner.cmd_map.read().await.get(hash).cloned() 52 | } 53 | 54 | #[allow(dead_code)] 55 | pub(crate) async fn list_cmd_sender(&self) -> Vec { 56 | self.inner 57 | .cmd_map 58 | .read() 59 | .await 60 | .values() 61 | .cloned() 62 | .collect_vec() 63 | } 64 | 65 | pub(crate) async fn remove_cmd_sender(&self, hash: &String) { 66 | let _r = self.inner.cmd_map.write().await.remove(hash); 67 | } 68 | } 69 | 70 | impl From<&ServerConfig> for ServerStore { 71 | fn from(sc: &ServerConfig) -> Self { 72 | ServerStore { 73 | inner: Arc::new(Inner { 74 | cmd_map: RwLock::new(HashMap::new()), 75 | trojan: sc.trojan.clone(), 76 | rathole: sc.rathole.clone(), 77 | }), 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use log::{error, info}; 4 | use shuttle::{ 5 | client::{start_proxy, start_rathole}, 6 | config::{ClientConfig, ServerConfig}, 7 | rathole::dispatcher::Dispatcher, 8 | store::ServerStore, 9 | }; 10 | use tokio::{io::AsyncWriteExt, net::TcpListener}; 11 | 12 | #[allow(dead_code)] 13 | pub async fn start_command_server() { 14 | let listener = TcpListener::bind("127.0.0.1:6789").await.unwrap(); 15 | tokio::spawn(async move { 16 | loop { 17 | let (stream, _) = listener.accept().await.unwrap(); 18 | info!("command server accept req"); 19 | let (mut dispatcher, _cs) = Dispatcher::new(stream, String::from("hash")); 20 | 21 | tokio::spawn(async move { 22 | if let Err(e) = dispatcher.dispatch().await { 23 | error!("{:?}", e); 24 | } 25 | }); 26 | } 27 | }); 28 | } 29 | 30 | #[allow(dead_code)] 31 | pub async fn start_server(path: &str) { 32 | let config = ServerConfig::load(Option::Some(PathBuf::from(path))); 33 | 34 | let store = ServerStore::from(&config); 35 | let addr = config.addrs.first().unwrap(); 36 | shuttle::server::start_server(addr, store.clone()).await; 37 | } 38 | 39 | #[allow(dead_code)] 40 | pub async fn start_client(path: &str) { 41 | let cc = ClientConfig::load(Option::Some(PathBuf::from(path))); 42 | match cc.run_type.as_str() { 43 | "proxy" => start_proxy(cc).await, 44 | "rathole" => start_rathole(cc).await, 45 | _ => panic!("unknown run type : {}", cc.run_type), 46 | } 47 | } 48 | 49 | #[allow(dead_code)] 50 | pub async fn start_web_server() { 51 | let listener = TcpListener::bind("127.0.0.1:6080").await.unwrap(); 52 | info!("Listener web server 6080"); 53 | tokio::spawn(async move { 54 | loop { 55 | let (mut ts, _sa) = listener.accept().await.unwrap(); 56 | ts.write_all( 57 | &b"HTTP/1.0 200 ok\r\n\ 58 | Connection: keep-alive\r\n\ 59 | Content-Type: text/plain; charset=utf-8\r\n\ 60 | Content-length: 12\r\n\ 61 | \r\n\ 62 | Hello world!"[..], 63 | ) 64 | .await 65 | .unwrap(); 66 | } 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /src/rathole/cmd/dial.rs: -------------------------------------------------------------------------------- 1 | use crate::rathole::cmd::resp::Resp; 2 | use crate::rathole::cmd::{CommandApply, CommandParse, CommandTo}; 3 | use crate::rathole::context::{ConnSender, Context}; 4 | use crate::rathole::exchange_copy; 5 | use crate::rathole::frame::{Frame, Parse}; 6 | use bytes::Bytes; 7 | use tokio::net::TcpStream; 8 | use tokio::sync::mpsc; 9 | 10 | #[derive(Debug)] 11 | pub struct Dial { 12 | conn_id: u64, 13 | addr: String, 14 | } 15 | 16 | impl Dial { 17 | pub const COMMAND_NAME: &'static str = "dial"; 18 | 19 | pub fn new(conn_id: u64, addr: String) -> Self { 20 | Dial { conn_id, addr } 21 | } 22 | } 23 | 24 | impl CommandParse for Dial { 25 | fn parse_frame(parse: &mut Parse) -> anyhow::Result { 26 | let conn_id = parse.next_int()?; 27 | let addr = parse.next_string()?; 28 | Ok(Self::new(conn_id, addr)) 29 | } 30 | } 31 | 32 | impl CommandTo for Dial { 33 | fn to_frame(&self) -> anyhow::Result { 34 | let mut f = Frame::array(); 35 | f.push_bulk(Bytes::from(Self::COMMAND_NAME)); 36 | f.push_int(self.conn_id); 37 | f.push_bulk(Bytes::from(self.addr.clone())); 38 | Ok(f) 39 | } 40 | } 41 | 42 | impl CommandApply for Dial { 43 | async fn apply(&self, context: Context) -> anyhow::Result> { 44 | let dial_conn = DialConn::new(self.conn_id, self.addr.clone()); 45 | if let Err(err) = dial_conn.start(context).await { 46 | error!("dial conn err : {:?}", err); 47 | } 48 | Ok(Some(Resp::Ok("ok".to_string()))) 49 | } 50 | } 51 | 52 | pub struct DialConn { 53 | conn_id: u64, 54 | addr: String, 55 | } 56 | 57 | impl DialConn { 58 | pub fn new(conn_id: u64, addr: String) -> Self { 59 | DialConn { addr, conn_id } 60 | } 61 | 62 | pub async fn start(&self, mut context: Context) -> anyhow::Result<()> { 63 | let stream = TcpStream::connect(&self.addr).await?; 64 | let conn_id = self.conn_id; 65 | 66 | tokio::spawn(async move { 67 | let (tx, rx) = mpsc::channel(1); 68 | let conn_sender = ConnSender::new(conn_id, tx); 69 | context.with_conn_id(conn_id); 70 | context.set_conn_sender(conn_sender).await; 71 | exchange_copy(stream, rx, context.clone()).await; 72 | context.remove_conn_sender().await; 73 | }); 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/setup.rs: -------------------------------------------------------------------------------- 1 | use rolling_file::{BasicRollingFileAppender, RollingConditionBasic}; 2 | use std::{fs, mem::forget}; 3 | use uuid::Uuid; 4 | 5 | use time::macros::{format_description, offset}; 6 | use tracing::{Level, level_filters::LevelFilter}; 7 | 8 | use tracing_subscriber::{ 9 | EnvFilter, Layer as _, 10 | filter::filter_fn, 11 | fmt::{layer, time::OffsetTime}, 12 | layer::SubscriberExt as _, 13 | util::SubscriberInitExt as _, 14 | }; 15 | 16 | pub fn setup_log() { 17 | let timer = OffsetTime::new( 18 | offset!(+8), 19 | format_description!( 20 | "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]+[offset_hour][offset_minute]" 21 | ), 22 | ); 23 | if fs::metadata("logs").is_err() { 24 | fs::create_dir_all("logs").expect("create logs dir failed"); 25 | } 26 | 27 | let (app_aped, g1) = tracing_appender::non_blocking( 28 | BasicRollingFileAppender::new( 29 | "logs/app.log", 30 | RollingConditionBasic::new() 31 | .daily() 32 | .max_size(1024 * 1024 * 1024), 33 | 9, 34 | ) 35 | .expect("rolling file failed, mabey logs dir"), 36 | ); 37 | let (error_aped, g2) = tracing_appender::non_blocking( 38 | BasicRollingFileAppender::new( 39 | "logs/error.log", 40 | RollingConditionBasic::new() 41 | .daily() 42 | .max_size(1024 * 1024 * 512), 43 | 19, 44 | ) 45 | .expect("rolling file failed, mabey logs dir"), 46 | ); 47 | 48 | let mut layers = Vec::new(); 49 | 50 | let app = layer() 51 | .with_writer(app_aped) 52 | .with_timer(timer.clone()) 53 | .with_line_number(true) 54 | .with_filter(default_env_filter()) 55 | .boxed(); 56 | layers.push(app); 57 | 58 | let error = layer() 59 | .with_writer(error_aped) 60 | .with_ansi(false) 61 | .with_timer(timer.clone()) 62 | .with_line_number(true) 63 | .with_filter(default_env_filter()) 64 | .with_filter(filter_fn(|m| { 65 | m.name().starts_with("tracing") 66 | || m.level() == &Level::ERROR 67 | || m.level() == &Level::WARN 68 | })) 69 | .boxed(); 70 | layers.push(error); 71 | 72 | let stdout = layer() 73 | .with_timer(timer.clone()) 74 | .with_line_number(true) 75 | .with_filter(default_env_filter()) 76 | .boxed(); 77 | layers.push(stdout); 78 | 79 | tracing_subscriber::registry().with(layers).try_init().ok(); 80 | 81 | forget(g1); 82 | forget(g2); 83 | } 84 | 85 | fn default_env_filter() -> EnvFilter { 86 | EnvFilter::builder() 87 | .with_default_directive(LevelFilter::INFO.into()) 88 | .from_env() 89 | .unwrap() 90 | } 91 | pub fn gen_traceid() -> String { 92 | let (_high, low) = Uuid::new_v4().as_u64_pair(); 93 | format!("{:016x}", low) 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![ci-test-build](https://github.com/cyejing/shuttle/actions/workflows/ci-test-build.yml/badge.svg)](https://github.com/cyejing/shuttle/actions/workflows/ci-test-build.yml) 2 | [![Version](https://img.shields.io/crates/v/shuttle-station)](https://crates.io/crates/shuttle-station) 3 | [![Documentation](https://img.shields.io/badge/docs-release-brightgreen.svg?style=flat)](https://docs.rs/shuttle-station) 4 | [![License](https://img.shields.io/crates/l/shuttle-station)](https://github.com/cyejing/shuttle/blob/master/LICENSE) 5 | 6 | Connect to networks without pain 7 | 8 | ## Feature 9 | 10 | - 加密上网 11 | - 内网穿透 12 | - 客户端代理支持 socks5/http 13 | 14 | ## Architecture 15 | 16 | ┌──────┐ ┌──────┐ 17 | │ user │ │ user │ 18 | └──┬───┘ └──┬───┘ 19 | │ │ 20 | │ ┌────▼─────┐ 21 | │ │ local │ 22 | │ │ client │ 23 | │ └────┬─────┘ 24 | │ │ 25 | ┌───────▼───────────────▼────────┐ 26 | │ │ 27 | │ public shuttle server │ 28 | │ │ 29 | └───────┬───────────────┬────────┘ 30 | │ │ 31 | │ │ 32 | ┌────▼─────┐ ┌────▼─────┐ 33 | │ LAN │ │ internet │ 34 | │ client │ └──────────┘ 35 | └──────────┘ 36 | 37 | ## Download 38 | 39 | 下载可执行文件[Release 页面](https://github.com/cyejing/shuttle/releases) 40 | 41 | ## Quick Start 42 | 43 | ### 加密上网使用 44 | 45 | #### Start Server 46 | 47 | `./shuttle server -c examples/server.yaml` 48 | 49 | 配置参数 50 | 51 | ```yaml 52 | #example/server.yaml 53 | addrs: 54 | - addr: 0.0.0.0:4845 55 | cert: examples/server.crt # 最好使用正式域名证书的方式 56 | key: examples/server.key 57 | trojan: 58 | local_addr: 127.0.0.1:80 #nginx伪装 59 | passwords: 60 | - sQtfRnfhcNoZYZh1wY9u 61 | ``` 62 | 63 | #### Start Client 64 | 65 | `./shuttle examples/client-proxy.yaml` 66 | 67 | 配置参数 68 | 69 | ```yaml 70 | run_type: proxy #运行类型 代理模式 71 | ssl_enable: true 72 | invalid_certs: true 73 | proxy_addr: 127.0.0.1:4080 #本地代理地址 74 | remote_addr: 127.0.0.1:4845 #服务器地址, 最好是域名 75 | password: sQtfRnfhcNoZYZh1wY9u #对应服务器密码 76 | ``` 77 | 78 | #### 使用 79 | 80 | 浏览器设置 socks5 代理, 代理端口 proxy_addr 81 | 82 | Enjoy 83 | 84 | ### 内网穿透使用 85 | 86 | #### Start Server 87 | 88 | `./shuttle examples/server.yaml` 89 | 90 | 配置参数 91 | 92 | ```yaml 93 | #example/server.yaml 94 | addrs: 95 | - addr: 0.0.0.0:4845 96 | cert: examples/server.crt 97 | key: examples/server.key 98 | rathole: 99 | passwords: 100 | - 58JCEmvcBkRAk1XkK1iH 101 | ``` 102 | 103 | #### Start Client 104 | 105 | `./shuttle examples/client-rathole.yaml` 106 | 107 | 配置参数 108 | 109 | ```yaml 110 | run_type: rathole 111 | ssl_enable: true 112 | remote_addr: 127.0.0.1:4845 113 | password: 58JCEmvcBkRAk1XkK1iH 114 | 115 | holes: 116 | - name: ssh 117 | remote_addr: 127.0.0.1:4022 118 | local_addr: 127.0.0.1:22 119 | ``` 120 | 121 | #### 使用 122 | 123 | connect -> remote_addr -> local_addr 124 | 125 | ## License 126 | 127 | GNU General Public License v3.0 128 | -------------------------------------------------------------------------------- /src/rathole/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::rathole::cmd::dial::Dial; 2 | use crate::rathole::cmd::exchange::Exchange; 3 | use crate::rathole::cmd::hole::Hole; 4 | use crate::rathole::cmd::ping::Ping; 5 | use crate::rathole::cmd::resp::Resp; 6 | use crate::rathole::cmd::unknown::Unknown; 7 | use crate::rathole::context::Context; 8 | use crate::rathole::frame::{Frame, Parse}; 9 | use anyhow::anyhow; 10 | use std::fmt::Debug; 11 | 12 | pub mod dial; 13 | pub mod exchange; 14 | pub mod hole; 15 | pub mod ping; 16 | pub mod resp; 17 | pub mod unknown; 18 | 19 | #[derive(Debug)] 20 | pub enum Command { 21 | Dial(Dial), 22 | Exchange(Exchange), 23 | Ping(Ping), 24 | Hole(Hole), 25 | Resp(Resp), 26 | Unknown(Unknown), 27 | } 28 | 29 | impl Command { 30 | pub fn from_frame(frame: Frame) -> anyhow::Result<(u64, Command)> { 31 | let mut parse = Parse::new(frame)?; 32 | let command_name = parse.next_string()?.to_lowercase(); 33 | let command = match &command_name[..] { 34 | Dial::COMMAND_NAME => Command::Dial(Dial::parse_frame(&mut parse)?), 35 | Exchange::COMMAND_NAME => Command::Exchange(Exchange::parse_frame(&mut parse)?), 36 | Ping::COMMAND_NAME => Command::Ping(Ping::parse_frame(&mut parse)?), 37 | Hole::COMMAND_NAME => Command::Hole(Hole::parse_frame(&mut parse)?), 38 | Resp::COMMAND_NAME => Command::Resp(Resp::parse_frame(&mut parse)?), 39 | _ => return Ok((0, Command::Unknown(Unknown::new(command_name)))), 40 | }; 41 | 42 | let req_id = parse.next_int()?; 43 | 44 | parse.finish()?; 45 | 46 | Ok((req_id, command)) 47 | } 48 | 49 | pub async fn apply(self, context: Context) -> anyhow::Result> { 50 | use Command::*; 51 | 52 | trace!("apply command {:?}", &self); 53 | let resp = match self { 54 | Dial(dial) => dial.apply(context).await?, 55 | Exchange(exchange) => exchange.apply(context).await?, 56 | Ping(ping) => ping.apply(context).await?, 57 | Hole(hole) => hole.apply(context).await?, 58 | Resp(resp) => resp.apply(context).await?, 59 | Unknown(unknown) => unknown.apply(context).await?, 60 | }; 61 | let oc = resp.map(Command::Resp); 62 | Ok(oc) 63 | } 64 | 65 | pub fn to_frame(self, req_id: u64) -> anyhow::Result { 66 | use Command::*; 67 | 68 | let f = match self { 69 | Dial(dial) => dial.to_frame()?.push_req_id(req_id), 70 | Exchange(exchange) => exchange.to_frame()?.push_req_id(req_id), 71 | Ping(ping) => ping.to_frame()?.push_req_id(req_id), 72 | Hole(hole) => hole.to_frame()?.push_req_id(req_id), 73 | Resp(resp) => resp.to_frame()?.push_req_id(req_id), 74 | _ => return Err(anyhow!("command undo")), 75 | }; 76 | Ok(f) 77 | } 78 | } 79 | 80 | pub trait CommandParse { 81 | fn parse_frame(parse: &mut Parse) -> anyhow::Result; 82 | } 83 | 84 | pub(crate) trait CommandApply { 85 | async fn apply(&self, context: Context) -> anyhow::Result>; 86 | } 87 | 88 | pub trait CommandTo { 89 | fn to_frame(&self) -> anyhow::Result; 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests {} 94 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use borer_core::{ 4 | dial::{DirectDial, TrojanDial, WebSocketDial}, 5 | proxy::ProxyConnection, 6 | }; 7 | use tokio::net::{TcpListener, TcpStream}; 8 | use tokio_rustls::client::TlsStream; 9 | use tracing::{Instrument, info_span}; 10 | 11 | use crate::{ 12 | config::{ClientConfig, ProxyMode}, 13 | rathole, 14 | setup::gen_traceid, 15 | }; 16 | 17 | pub async fn start_rathole(cc: ClientConfig) { 18 | info!("Run with rathole"); 19 | let mut backoff = 400; 20 | tokio::spawn(async move { 21 | loop { 22 | match rathole::start_rathole(cc.clone()).await { 23 | Ok(_) => info!("Rathole status ok"), 24 | Err(e) => error!("Rathole occurs err :{:?}", e), 25 | } 26 | if backoff > 3200 { 27 | backoff = 400 28 | } 29 | info!("Retry after {} millis", backoff); 30 | tokio::time::sleep(Duration::from_millis(backoff)).await; 31 | 32 | backoff *= 2; 33 | } 34 | }); 35 | } 36 | 37 | pub async fn start_proxy(cc: ClientConfig) { 38 | info!( 39 | "Run with proxy @{} use mode {:?}", 40 | cc.proxy_addr, cc.proxy_mode 41 | ); 42 | let listener = TcpListener::bind(&cc.proxy_addr) 43 | .await 44 | .unwrap_or_else(|e| panic!("Can't Listen socks addr {}. err: {e}", cc.proxy_addr)); 45 | 46 | let cc = Arc::new(cc); 47 | 48 | tokio::spawn(async move { 49 | while let Ok((ts, _)) = listener.accept().await { 50 | let cc = cc.clone(); 51 | let span = info_span!("trace", id = gen_traceid()); 52 | tokio::spawn(async move { proxy_handle(cc, ts).instrument(span).await }); 53 | } 54 | }); 55 | } 56 | 57 | async fn proxy_handle(cc: Arc, ts: TcpStream) { 58 | match (&cc.proxy_mode, cc.ssl_enable) { 59 | (ProxyMode::Direct, _) => { 60 | ProxyConnection::new(ts, Box::::default()) 61 | .handle() 62 | .await; 63 | } 64 | (ProxyMode::Trojan, false) => { 65 | ProxyConnection::::new( 66 | ts, 67 | Box::new(TrojanDial::new( 68 | cc.remote_addr.clone(), 69 | cc.hash.clone(), 70 | cc.invalid_certs, 71 | cc.padding, 72 | )), 73 | ) 74 | .handle() 75 | .await; 76 | } 77 | (ProxyMode::Trojan, true) => { 78 | ProxyConnection::>::new( 79 | ts, 80 | Box::new(TrojanDial::new( 81 | cc.remote_addr.clone(), 82 | cc.hash.clone(), 83 | cc.invalid_certs, 84 | cc.padding, 85 | )), 86 | ) 87 | .handle() 88 | .await; 89 | } 90 | (ProxyMode::Websocket, _) => { 91 | ProxyConnection::new( 92 | ts, 93 | Box::new(WebSocketDial::new( 94 | cc.remote_addr.clone(), 95 | cc.hash.clone(), 96 | cc.padding, 97 | )), 98 | ) 99 | .handle() 100 | .await; 101 | } 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /.github/workflows/release-build.yml: -------------------------------------------------------------------------------- 1 | name: release-build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "v*" 8 | branches: [master] 9 | paths: 10 | - ".github/workflows/release-build.yml" 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | - uses: Swatinem/rust-cache@v1 25 | - name: Run tests 26 | run: cargo test --verbose 27 | cross: 28 | needs: test 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | include: 34 | - os: ubuntu-latest 35 | target: x86_64-unknown-linux-musl 36 | name: shuttle-x86_64-unknown-linux-musl.tgz 37 | - os: ubuntu-latest 38 | target: aarch64-unknown-linux-musl 39 | name: shuttle-aarch64-unknown-linux-musl.tgz 40 | 41 | - os: macOS-latest 42 | target: x86_64-apple-darwin 43 | name: shuttle-x86_64-apple-darwin.tgz 44 | - os: macOS-latest 45 | target: aarch64-apple-darwin 46 | name: shuttle-aarch64-apple-darwin.tgz 47 | 48 | - os: windows-latest 49 | target: x86_64-pc-windows-msvc 50 | name: shuttle-x86_64-pc-windows-msvc.zip 51 | 52 | permissions: 53 | contents: write 54 | runs-on: ${{ matrix.os }} 55 | env: 56 | RUST_BACKTRACE: full 57 | continue-on-error: true 58 | steps: 59 | - name: Setup | Checkout 60 | uses: actions/checkout@v3 61 | 62 | - name: Setup | Rust 63 | uses: actions-rs/toolchain@v1 64 | with: 65 | toolchain: stable 66 | default: true 67 | override: true 68 | profile: minimal 69 | target: ${{ matrix.target }} 70 | - name: Setup | Rustup 71 | run: rustup target add ${{ matrix.target }} 72 | - name: Build | Build 73 | uses: actions-rs/cargo@v1 74 | with: 75 | command: build 76 | args: --release --verbose --target ${{ matrix.target }} 77 | use-cross: true 78 | 79 | - name: Post Build | Prepare artifacts [Windows] 80 | if: matrix.os == 'windows-latest' 81 | run: | 82 | xcopy examples target\${{ matrix.target }}\release\examples /s /e /y /i 83 | cd target/${{ matrix.target }}/release 84 | strip shuttle.exe 85 | 7z a ../../../${{ matrix.name }} shuttle.exe examples 86 | cd - 87 | - name: Post Build | Prepare artifacts [unix] 88 | if: matrix.os != 'windows-latest' 89 | run: | 90 | cp -r examples target/${{ matrix.target }}/release 91 | cd target/${{ matrix.target }}/release 92 | strip shuttle || true 93 | tar czvf ../../../${{ matrix.name }} shuttle examples 94 | cd - 95 | 96 | - name: Deploy | Upload artifacts 97 | uses: actions/upload-artifact@v4 98 | with: 99 | name: ${{ matrix.name }} 100 | path: ${{ matrix.name }} 101 | 102 | - name: Publish Release 103 | uses: softprops/action-gh-release@v1 104 | if: startsWith(github.ref, 'refs/tags/') 105 | with: 106 | files: ${{ matrix.name }} 107 | generate_release_notes: true 108 | prerelease: true 109 | token: ${{ secrets.GITHUB_TOKEN }} 110 | -------------------------------------------------------------------------------- /src/rathole/cmd/hole.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use tokio::net::TcpListener; 3 | use tokio::sync::mpsc; 4 | 5 | use crate::rathole::cmd::dial::Dial; 6 | use crate::rathole::cmd::resp::Resp; 7 | use crate::rathole::cmd::{Command, CommandApply, CommandParse, CommandTo}; 8 | use crate::rathole::context::{ConnSender, IdAdder}; 9 | use crate::rathole::frame::{Frame, Parse}; 10 | use crate::rathole::{context, exchange_copy}; 11 | 12 | #[derive(Debug)] 13 | pub struct Hole { 14 | remote_addr: String, 15 | local_addr: String, 16 | } 17 | 18 | impl Hole { 19 | pub const COMMAND_NAME: &'static str = "hole"; 20 | 21 | pub fn new(remote_addr: String, local_addr: String) -> Self { 22 | Hole { 23 | remote_addr, 24 | local_addr, 25 | } 26 | } 27 | } 28 | 29 | impl CommandParse for Hole { 30 | fn parse_frame(parse: &mut Parse) -> anyhow::Result { 31 | let remote_addr = parse.next_string()?; 32 | let local_addr = parse.next_string()?; 33 | Ok(Hole::new(remote_addr, local_addr)) 34 | } 35 | } 36 | 37 | impl CommandTo for Hole { 38 | fn to_frame(&self) -> anyhow::Result { 39 | let mut f = Frame::array(); 40 | f.push_bulk(Bytes::from(Self::COMMAND_NAME)); 41 | f.push_bulk(Bytes::from(self.remote_addr.clone())); 42 | f.push_bulk(Bytes::from(self.local_addr.clone())); 43 | Ok(f) 44 | } 45 | } 46 | 47 | impl CommandApply for Hole { 48 | async fn apply(&self, context: context::Context) -> anyhow::Result> { 49 | let hole_server = HoleServer::new( 50 | self.remote_addr.clone(), 51 | self.local_addr.clone(), 52 | context.clone(), 53 | ); 54 | match hole_server.start().await { 55 | Ok(_) => Ok(Some(Resp::Ok("ok".to_string()))), 56 | Err(e) => { 57 | error!("proxy start err : {:?}", e); 58 | Ok(Some(Resp::Err(format!("{}", e)))) 59 | } 60 | } 61 | } 62 | } 63 | 64 | pub struct HoleServer { 65 | addr: String, 66 | local_addr: String, 67 | context: context::Context, 68 | id_adder: IdAdder, 69 | } 70 | 71 | impl HoleServer { 72 | pub fn new(addr: String, local_addr: String, context: context::Context) -> Self { 73 | HoleServer { 74 | addr, 75 | local_addr, 76 | context, 77 | id_adder: IdAdder::default(), 78 | } 79 | } 80 | 81 | pub async fn start(mut self) -> anyhow::Result<()> { 82 | let listener = TcpListener::bind(&self.addr).await?; 83 | info!("Bind proxy server {}", &self.addr); 84 | let mut shutdown = self.context.notify_shutdown.subscribe(); 85 | tokio::spawn(async move { 86 | tokio::select! { 87 | r1 = self.run(listener) => { 88 | if let Err(e) = r1 { 89 | error!("proxy run accept conn err: {}", e); 90 | } 91 | }, 92 | _ = shutdown.recv() => info!("recv shutdown signal"), 93 | } 94 | }); 95 | Ok(()) 96 | } 97 | 98 | async fn run(&mut self, listener: TcpListener) -> anyhow::Result<()> { 99 | loop { 100 | let (ts, _) = listener.accept().await?; 101 | let (tx, rx) = mpsc::channel(1); 102 | 103 | let conn_id = self.id_adder.add_and_get().await; 104 | info!("Hole accept proxy conn ({conn_id})"); 105 | let conn_sender = ConnSender::new(conn_id, tx); 106 | self.context.set_conn_sender(conn_sender).await; 107 | 108 | let mut context = self.context.clone(); 109 | context.with_conn_id(conn_id); 110 | 111 | let dial = Command::Dial(Dial::new(conn_id, self.local_addr.clone())); 112 | context.command_sender.send_sync(dial).await?; 113 | tokio::spawn(async move { 114 | exchange_copy(ts, rx, context.clone()).await; 115 | context.remove_conn_sender().await; 116 | }); 117 | } 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests {} 123 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::sync::Arc; 3 | 4 | use anyhow::Context; 5 | use borer_core::proto::{self}; 6 | use borer_core::stats_server; 7 | use borer_core::stream::acceptor::{Acceptor, MaybeTlsStream}; 8 | use borer_core::stream::peekable::{AsyncPeek, PeekableStream}; 9 | use borer_core::trojan::TrojanConnection; 10 | use tracing::{Instrument, info_span}; 11 | 12 | use crate::config::Addr; 13 | use crate::rathole::dispatcher::Dispatcher; 14 | use crate::setup::gen_traceid; 15 | use crate::store::ServerStore; 16 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, copy_bidirectional}; 17 | use tokio::net::{TcpListener, TcpStream}; 18 | 19 | #[derive(Clone)] 20 | struct State { 21 | acceptor: Arc, 22 | } 23 | 24 | impl State { 25 | fn new(addr: &Addr) -> State { 26 | let acceptor = Acceptor::new(addr.cert.clone(), addr.key.clone()); 27 | 28 | State { 29 | acceptor: Arc::new(acceptor), 30 | } 31 | } 32 | } 33 | 34 | pub async fn start_server(addr: &Addr, store: ServerStore) { 35 | let addr_str = &addr.addr; 36 | let listener = TcpListener::bind(addr_str) 37 | .await 38 | .context(format!("Can't bind server port {}", addr_str)) 39 | .unwrap(); 40 | info!("server up and running. listen: {}", addr_str); 41 | let state = State::new(addr); 42 | 43 | tokio::spawn(async move { 44 | while let Ok((ts, _)) = listener.accept().await { 45 | let store = store.clone(); 46 | let state = state.clone(); 47 | let span = info_span!("trace", id = gen_traceid()); 48 | tokio::spawn(async move { 49 | if let Err(e) = server_handle(ts, store, state).instrument(span).await { 50 | error!("Server handle failed. e:{e:?}"); 51 | } 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | pub fn start_stats_server(addr: Option, secret: Option) { 58 | if let (Some(addr), Some(secret)) = (addr, secret) { 59 | stats_server::StatsServer::new(&addr, &secret).serve(); 60 | } 61 | } 62 | 63 | async fn server_handle(ts: TcpStream, store: ServerStore, state: State) -> anyhow::Result<()> { 64 | let peer_addr = ts.peer_addr()?; 65 | let ts = state.acceptor.accept(ts).await?; 66 | let mut peek_ts = PeekableStream::new(ts); 67 | 68 | match detect_head(&mut peek_ts, &store).await? { 69 | ConnType::Trojan(user) => handle_trojan(&mut peek_ts, user, peer_addr).await, 70 | ConnType::Rathole(hash) => handle_rathole(&mut peek_ts, &store, hash).await, 71 | ConnType::Proxy => handle_proxy(&mut peek_ts, &store).await, 72 | } 73 | } 74 | 75 | async fn detect_head( 76 | ts: &mut PeekableStream>, 77 | store: &ServerStore, 78 | ) -> anyhow::Result { 79 | let head = proto::trojan::Request::peek_head(ts) 80 | .await 81 | .context("Server peek head failed")?; 82 | if head.len() < 56 { 83 | return Ok(ConnType::Proxy); 84 | } 85 | 86 | let hash_str = String::from_utf8(head).context("Trojan hash to string failed")?; 87 | 88 | let trojan = store.get_trojan(); 89 | let rathole = store.get_rahole(); 90 | 91 | if trojan.password_hash.contains_key(&hash_str) { 92 | debug!("detect trojan {}", hash_str); 93 | Ok(ConnType::Trojan(hash_str)) 94 | } else if rathole.password_hash.contains_key(&hash_str) { 95 | debug!("detect rathole {}", hash_str); 96 | Ok(ConnType::Rathole(hash_str)) 97 | } else { 98 | debug!("detect proxy"); 99 | Ok(ConnType::Proxy) 100 | } 101 | } 102 | 103 | async fn handle_trojan( 104 | ts: &mut PeekableStream>, 105 | user: String, 106 | peer_addr: SocketAddr, 107 | ) -> anyhow::Result<()> { 108 | TrojanConnection::new(ts, user, peer_addr).handle().await 109 | } 110 | 111 | async fn handle_proxy( 112 | ts: &mut PeekableStream>, 113 | store: &ServerStore, 114 | ) -> anyhow::Result<()> { 115 | let trojan = store.get_trojan(); 116 | 117 | match trojan.local_addr { 118 | Some(ref local_addr) => { 119 | info!("requested proxy local {}", local_addr); 120 | let mut local_ts = TcpStream::connect(local_addr) 121 | .await 122 | .context(format!("Proxy can't connect addr {}", local_addr))?; 123 | debug!("Proxy connect success {:?}", &trojan.local_addr); 124 | 125 | copy_bidirectional(ts, &mut local_ts).await.ok(); 126 | } 127 | None => { 128 | info!("response not found"); 129 | resp_html(ts).await 130 | } 131 | } 132 | 133 | Ok(()) 134 | } 135 | 136 | async fn handle_rathole( 137 | ts: &mut PeekableStream>, 138 | store: &ServerStore, 139 | hash: String, 140 | ) -> anyhow::Result<()> { 141 | let _ = ts.drain(); 142 | let _crlf = ts.read_u16().await?; 143 | let (mut dispatcher, cs) = Dispatcher::new(ts, hash.clone()); 144 | store.set_cmd_sender(cs).await; 145 | 146 | dispatcher.dispatch().await.ok(); 147 | 148 | store.remove_cmd_sender(&hash).await; 149 | Ok(()) 150 | } 151 | 152 | pub enum ConnType { 153 | Trojan(String), 154 | Rathole(String), 155 | Proxy, 156 | } 157 | 158 | async fn resp_html(stream: &mut T) { 159 | stream 160 | .write_all( 161 | &b"HTTP/1.0 404 Not Found\r\n\ 162 | Content-Type: text/plain; charset=utf-8\r\n\ 163 | Content-length: 13\r\n\r\n\ 164 | 404 Not Found"[..], 165 | ) 166 | .await 167 | .unwrap(); 168 | } 169 | -------------------------------------------------------------------------------- /src/rathole/context.rs: -------------------------------------------------------------------------------- 1 | use crate::rathole::cmd::Command; 2 | use crate::rathole::{CommandChannel, ReqChannel}; 3 | use anyhow::anyhow; 4 | use bytes::Bytes; 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | use tokio::sync::{Mutex, broadcast, mpsc, oneshot}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct Context { 11 | pub notify_shutdown: broadcast::Sender<()>, 12 | pub command_sender: CommandSender, 13 | pub conn_map: Arc>>, 14 | pub req_map: Arc>>, 15 | pub current_req_id: Option, 16 | pub current_conn_id: Option, 17 | } 18 | 19 | impl Context { 20 | pub fn new(command_sender: CommandSender) -> Self { 21 | let (notify_shutdown, _) = broadcast::channel(1); 22 | 23 | Context { 24 | notify_shutdown, 25 | command_sender, 26 | current_req_id: None, 27 | current_conn_id: None, 28 | conn_map: Arc::new(Mutex::new(HashMap::new())), 29 | req_map: Arc::new(Mutex::new(HashMap::new())), 30 | } 31 | } 32 | 33 | pub(crate) fn with_req_id(&mut self, req_id: u64) -> &Self { 34 | self.current_req_id = Some(req_id); 35 | self 36 | } 37 | 38 | pub(crate) fn with_conn_id(&mut self, conn_id: u64) { 39 | self.current_conn_id = Some(conn_id); 40 | } 41 | 42 | pub(crate) async fn set_req(&self, req_channel: ReqChannel) { 43 | trace!( 44 | "Context set req {:?} {:?}", 45 | self.current_req_id, req_channel 46 | ); 47 | if let Some(req_id) = self.current_req_id { 48 | if req_channel.is_some() { 49 | self.req_map.lock().await.insert(req_id, req_channel); 50 | } 51 | } 52 | } 53 | 54 | pub(crate) async fn get_req(&self) -> Option { 55 | trace!("Context get req {:?}", self.current_req_id); 56 | match &self.current_req_id { 57 | Some(req_id) => self.req_map.lock().await.remove(req_id), 58 | None => None, 59 | } 60 | } 61 | 62 | pub(crate) async fn set_conn_sender(&self, sender: ConnSender) { 63 | trace!("Context set conn sender {:?}", sender); 64 | self.conn_map 65 | .lock() 66 | .await 67 | .insert(sender.get_conn_id(), sender); 68 | } 69 | 70 | pub(crate) async fn get_conn_sender(&self) -> Option { 71 | trace!("Context get conn sender {:?}", self.current_conn_id); 72 | match &self.current_conn_id { 73 | Some(conn_id) => self.conn_map.lock().await.get(conn_id).cloned(), 74 | None => panic!("context current conn id is empty"), 75 | } 76 | } 77 | 78 | pub(crate) async fn remove_conn_sender(&self) { 79 | trace!("Context remove conn sender {:?}", self.current_conn_id); 80 | match &self.current_conn_id { 81 | Some(conn_id) => { 82 | let _discard = self.conn_map.lock().await.remove(conn_id); 83 | } 84 | None => panic!("context current conn id is empty"), 85 | } 86 | } 87 | 88 | pub(crate) fn get_conn_id(&self) -> u64 { 89 | match self.current_conn_id { 90 | Some(conn_id) => conn_id, 91 | None => panic!("context current conn id is empty"), 92 | } 93 | } 94 | } 95 | 96 | #[derive(Debug, Clone)] 97 | pub struct ConnSender { 98 | inner: Arc, 99 | } 100 | 101 | #[derive(Debug)] 102 | struct ConnInner { 103 | pub conn_id: u64, 104 | pub sender: mpsc::Sender, 105 | } 106 | 107 | impl ConnSender { 108 | pub fn new(conn_id: u64, sender: mpsc::Sender) -> Self { 109 | ConnSender { 110 | inner: Arc::new(ConnInner { conn_id, sender }), 111 | } 112 | } 113 | 114 | pub fn get_conn_id(&self) -> u64 { 115 | self.inner.conn_id 116 | } 117 | 118 | pub async fn send(&self, byte: Bytes) -> anyhow::Result<()> { 119 | use anyhow::Context; 120 | 121 | self.inner 122 | .sender 123 | .send(byte) 124 | .await 125 | .context("Can't send byte to conn channel") 126 | } 127 | } 128 | 129 | /// command sender 130 | #[derive(Debug, Clone)] 131 | pub struct CommandSender { 132 | inner: Arc, 133 | } 134 | 135 | #[derive(Debug)] 136 | struct Inner { 137 | pub hash: String, 138 | pub sender: mpsc::Sender, 139 | id_adder: IdAdder, 140 | } 141 | 142 | impl CommandSender { 143 | pub fn new(hash: String, sender: mpsc::Sender) -> Self { 144 | CommandSender { 145 | inner: Arc::new(Inner { 146 | hash, 147 | sender, 148 | id_adder: IdAdder::default(), 149 | }), 150 | } 151 | } 152 | 153 | pub fn get_hash(&self) -> String { 154 | self.inner.hash.clone() 155 | } 156 | 157 | pub async fn send_with_id(&self, req_id: u64, cmd: Command) -> anyhow::Result<()> { 158 | Ok(self.inner.sender.send((req_id, cmd, None)).await?) 159 | } 160 | 161 | pub async fn send_sync(&self, cmd: Command) -> anyhow::Result<()> { 162 | let (tx, rx) = oneshot::channel(); 163 | let req_id = self.inner.id_adder.add_and_get().await; 164 | self.inner.sender.send((req_id, cmd, Some(tx))).await?; 165 | trace!("send_sync {} send", req_id); 166 | match rx.await? { 167 | Ok(_) => { 168 | trace!("send_sync {} recv resp", req_id); 169 | Ok(()) 170 | } 171 | Err(e) => { 172 | error!("send_sync {} resp err {}", req_id, e); 173 | Err(anyhow!(format!("send_sync resp err {}", e))) 174 | } 175 | } 176 | } 177 | } 178 | 179 | #[derive(Debug, Clone)] 180 | pub struct IdAdder { 181 | inner: Arc>, 182 | } 183 | 184 | impl Default for IdAdder { 185 | fn default() -> Self { 186 | IdAdder { 187 | inner: Arc::new(Mutex::new(0)), 188 | } 189 | } 190 | } 191 | 192 | impl IdAdder { 193 | pub async fn add_and_get(&self) -> u64 { 194 | let mut i = self.inner.lock().await; 195 | *i += 1; 196 | *i 197 | } 198 | } 199 | 200 | #[cfg(test)] 201 | mod tests {} 202 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fs::File; 3 | use std::path::PathBuf; 4 | 5 | use anyhow::Context; 6 | use borer_core::tls::{load_certs, load_private_key}; 7 | use serde::{Deserialize, Serialize}; 8 | use sha2::{Digest, Sha224}; 9 | use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; 10 | 11 | #[derive(Debug, Serialize, Deserialize)] 12 | pub struct ServerConfig { 13 | pub addrs: Vec, 14 | pub stats_addr: Option, 15 | pub stats_secret: Option, 16 | pub trojan: Trojan, 17 | pub rathole: RatHole, 18 | } 19 | 20 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 21 | pub struct ClientConfig { 22 | /// proxy, rathole 23 | pub run_type: String, 24 | #[serde(default = "default_proxy_mode")] 25 | pub proxy_mode: ProxyMode, 26 | pub remote_addr: String, 27 | pub password: String, 28 | #[serde(skip)] 29 | pub hash: String, 30 | #[serde(default)] 31 | pub proxy_addr: String, 32 | #[serde(default = "default_true")] 33 | pub ssl_enable: bool, 34 | #[serde(default = "default_false")] 35 | pub invalid_certs: bool, 36 | #[serde(default = "default_true")] 37 | pub padding: bool, 38 | #[serde(default)] 39 | pub holes: Vec, 40 | } 41 | 42 | #[derive(Debug, Serialize, Deserialize)] 43 | pub struct Addr { 44 | pub addr: String, 45 | pub cert: Option, 46 | pub key: Option, 47 | #[serde(skip)] 48 | pub ssl_enable: bool, 49 | #[serde(skip)] 50 | pub cert_loaded: Option>>, 51 | #[serde(skip)] 52 | pub key_loaded: Option>, 53 | } 54 | 55 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 56 | pub struct RatHole { 57 | pub passwords: Vec, 58 | #[serde(skip)] 59 | pub password_hash: HashMap, 60 | } 61 | 62 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 63 | pub struct Trojan { 64 | pub local_addr: Option, 65 | pub passwords: Vec, 66 | #[serde(skip)] 67 | pub password_hash: HashMap, 68 | } 69 | 70 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 71 | pub struct Hole { 72 | pub name: String, 73 | pub remote_addr: String, 74 | pub local_addr: String, 75 | } 76 | 77 | #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 78 | pub enum ProxyMode { 79 | #[serde(alias = "direct")] 80 | Direct, 81 | #[serde(alias = "trojan")] 82 | #[default] 83 | Trojan, 84 | #[serde(alias = "websocket")] 85 | Websocket, 86 | } 87 | 88 | const DEFAULT_SERVER_CONFIG_PATH: [&str; 2] = ["server.yaml", "examples/server.yaml"]; 89 | 90 | impl ServerConfig { 91 | pub fn load(path: Option) -> ServerConfig { 92 | let file = open_config_file(path, Vec::from(DEFAULT_SERVER_CONFIG_PATH)); 93 | 94 | let mut sc: ServerConfig = serde_yaml::from_reader(file) 95 | .context("Can't serde read config file") 96 | .unwrap(); 97 | for addr in &mut sc.addrs { 98 | if let (Some(key), Some(cert)) = (&addr.key, &addr.cert) { 99 | addr.ssl_enable = true; 100 | 101 | addr.cert_loaded = 102 | Some(load_certs(PathBuf::from(cert)).expect("load_certs failed")); 103 | addr.key_loaded = 104 | Some(load_private_key(PathBuf::from(key)).expect("load_private_key falied")); 105 | } 106 | } 107 | for password in &sc.trojan.passwords { 108 | sc.trojan 109 | .password_hash 110 | .insert(sha224(password), password.clone()); 111 | } 112 | for password in &sc.rathole.passwords { 113 | sc.rathole 114 | .password_hash 115 | .insert(sha224(password), password.clone()); 116 | } 117 | sc 118 | } 119 | } 120 | 121 | const DEFAULT_CLIENT_CONFIG_PATH: [&str; 6] = [ 122 | "client.yaml", 123 | "client-proxy.yaml", 124 | "client-rathole.yaml", 125 | "examples/client.yaml", 126 | "examples/client-proxy.yaml", 127 | "examples/client-rathole.yaml", 128 | ]; 129 | 130 | impl ClientConfig { 131 | pub fn load(path: Option) -> ClientConfig { 132 | let file = open_config_file(path, Vec::from(DEFAULT_CLIENT_CONFIG_PATH)); 133 | 134 | let mut cc: ClientConfig = serde_yaml::from_reader(file) 135 | .context("Can't serde read config file") 136 | .unwrap(); 137 | cc.hash = sha224(&cc.password); 138 | cc 139 | } 140 | } 141 | 142 | fn open_config_file(path: Option, default_paths: Vec<&str>) -> File { 143 | if let Some(pb) = path { 144 | let path_str = pb.to_str().unwrap(); 145 | info!("Load config file : {}", path_str); 146 | File::open(pb.as_path()) 147 | .context(format!("Can't load config file {:?}", path_str)) 148 | .unwrap() 149 | } else { 150 | let mut of: Option = None; 151 | for path in &default_paths { 152 | if let Ok(file) = File::open(*path) { 153 | info!("Load config file : {}", *path); 154 | of = Some(file); 155 | break; 156 | } 157 | } 158 | 159 | of.context(format!( 160 | "Can't find default config file [{:?}]", 161 | &default_paths 162 | )) 163 | .unwrap() 164 | } 165 | } 166 | 167 | impl Trojan { 168 | pub fn push(&mut self, pwd: &str) { 169 | self.passwords.push(pwd.to_string()); 170 | self.password_hash.insert(sha224(pwd), pwd.to_string()); 171 | } 172 | } 173 | impl RatHole { 174 | pub fn push(&mut self, pwd: &str) { 175 | self.passwords.push(pwd.to_string()); 176 | self.password_hash.insert(sha224(pwd), pwd.to_string()); 177 | } 178 | } 179 | 180 | pub fn sha224(password: &str) -> String { 181 | let mut hasher = Sha224::new(); 182 | hasher.update(password.as_bytes()); 183 | let hash = hasher.finalize(); 184 | let result = base16ct::lower::encode_string(&hash); 185 | log::debug!( 186 | "sha224({}) = {}, length = {}", 187 | password, 188 | result, 189 | result.len() 190 | ); 191 | result 192 | } 193 | 194 | fn default_true() -> bool { 195 | true 196 | } 197 | 198 | fn default_false() -> bool { 199 | false 200 | } 201 | 202 | fn default_proxy_mode() -> ProxyMode { 203 | ProxyMode::Trojan 204 | } 205 | 206 | #[cfg(test)] 207 | mod tests { 208 | use crate::config::sha224; 209 | 210 | #[test] 211 | fn test_hash() { 212 | assert_eq!( 213 | sha224("sQtfRnfhcNoZYZh1wY9u"), 214 | "6b34e62f6df92b8e9db961410b4f1a6fca1e2dae73f9c1b4b94f4a33", 215 | ); 216 | assert_eq!( 217 | sha224("cyj22334400!"), 218 | "3af1c305cd8ec7eebaf03bab42e42dd686e2ef5db27a7c7176350eb0" 219 | ); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/rathole/mod.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::{Context, anyhow}; 4 | use borer_core::CRLF; 5 | use bytes::{Bytes, BytesMut}; 6 | use tokio::io; 7 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf}; 8 | use tokio::net::TcpStream; 9 | use tokio::sync::{mpsc, oneshot}; 10 | use tokio::time::timeout; 11 | 12 | use crate::config::ClientConfig; 13 | use crate::rathole::cmd::Command; 14 | use crate::rathole::cmd::exchange::Exchange; 15 | use crate::rathole::cmd::hole::Hole; 16 | use crate::rathole::dispatcher::Dispatcher; 17 | use borer_core::tls::{make_server_name, make_tls_connector}; 18 | 19 | use self::cmd::ping::Ping; 20 | 21 | pub mod cmd; 22 | pub mod context; 23 | pub mod dispatcher; 24 | pub mod frame; 25 | 26 | pub type ReqChannel = Option>>; 27 | pub type CommandChannel = (u64, Command, ReqChannel); 28 | 29 | pub async fn start_rathole(cc: ClientConfig) -> anyhow::Result<()> { 30 | let remote_addr = &cc.remote_addr; 31 | let stream = timeout(Duration::from_secs(10), TcpStream::connect(remote_addr)) 32 | .await 33 | .context("Connect remote timeout")? 34 | .context(format!("Can't connect remote addr {}", remote_addr))?; 35 | 36 | info!("Rathole connect remote {} success", remote_addr); 37 | if cc.ssl_enable { 38 | let domain = make_server_name(remote_addr)?; 39 | let tls_stream = timeout( 40 | Duration::from_secs(10), 41 | make_tls_connector(cc.invalid_certs).connect(domain, stream), 42 | ) 43 | .await 44 | .context("Connect remote tls timeout")? 45 | .context("Connect remote tls failed")?; 46 | handle(tls_stream, cc).await 47 | } else { 48 | handle(stream, cc).await 49 | } 50 | } 51 | 52 | async fn handle( 53 | mut stream: T, 54 | cc: ClientConfig, 55 | ) -> anyhow::Result<()> { 56 | let mut buf: Vec = vec![]; 57 | buf.extend_from_slice(cc.hash.as_bytes()); 58 | buf.extend_from_slice(&CRLF); 59 | 60 | timeout(Duration::from_secs(10), stream.write_all(buf.as_slice())) 61 | .await 62 | .context("Remote write shake hands timeout")? 63 | .context("Can't write rathole hash")?; 64 | 65 | let (mut dispatcher, command_sender) = Dispatcher::new(stream, cc.hash); 66 | 67 | let (sx, rx) = oneshot::channel(); 68 | tokio::spawn(async move { sx.send(dispatcher.dispatch().await) }); 69 | 70 | let command_sender_cloned = command_sender.clone(); 71 | tokio::spawn(async move { 72 | let mut last_ping = 60; // 10min 73 | loop { 74 | if last_ping > 0 { 75 | command_sender_cloned 76 | .send_sync(Command::Ping(Ping::new(None))) 77 | .await 78 | .ok(); 79 | last_ping -= 1; 80 | } else { 81 | break; 82 | } 83 | tokio::time::sleep(Duration::from_secs(10)).await; 84 | } 85 | }); 86 | 87 | for hole in cc.holes { 88 | let open_proxy = 89 | Command::Hole(Hole::new(hole.remote_addr.clone(), hole.local_addr.clone())); 90 | timeout( 91 | Duration::from_secs(10), 92 | command_sender.send_sync(open_proxy), 93 | ) 94 | .await 95 | .context("Hole open proxy timeout")? 96 | .context("Hole open proxy failed")?; 97 | info!( 98 | "Hole open proxy for [remote:{}], [local:{}]", 99 | hole.remote_addr, hole.local_addr 100 | ); 101 | } 102 | rx.await.context("Dispatcher stop")? 103 | } 104 | 105 | async fn exchange_copy(ts: TcpStream, mut rx: mpsc::Receiver, context: context::Context) { 106 | let (mut r, mut w) = io::split(ts); 107 | let mut shutdown = context.notify_shutdown.subscribe(); 108 | info!( 109 | "start stream copy by exchange conn_id: {:?}", 110 | context.current_conn_id 111 | ); 112 | tokio::select! { 113 | r1 = read_bytes(&mut r, context.clone()) => r1, 114 | r2 = write_bytes(&mut w, &mut rx) => r2, 115 | _ = shutdown.recv() =>{ 116 | debug!("exchange recv shutdown signal"); 117 | Ok(()) 118 | } 119 | } 120 | .inspect_err(|e| debug!("exchange copy faield {e}")) 121 | .ok(); 122 | 123 | info!( 124 | "stop stream copy by exchange conn_id: {:?}", 125 | context.current_conn_id 126 | ); 127 | } 128 | 129 | async fn read_bytes(r: &mut ReadHalf, context: context::Context) -> anyhow::Result<()> { 130 | loop { 131 | let mut buf = BytesMut::with_capacity(4 * 1024); 132 | let len = r 133 | .read_buf(&mut buf) 134 | .await 135 | .context("connection read byte err")?; 136 | if len > 0 { 137 | let exchange = Command::Exchange(Exchange::new(context.get_conn_id(), buf.freeze())); 138 | context.command_sender.send_sync(exchange).await?; 139 | } else { 140 | let exchange = Command::Exchange(Exchange::new(context.get_conn_id(), Bytes::new())); 141 | context.command_sender.send_sync(exchange).await?; 142 | return Err(anyhow!("exchange local conn EOF")); 143 | } 144 | } 145 | } 146 | 147 | async fn write_bytes( 148 | w: &mut WriteHalf, 149 | rx: &mut mpsc::Receiver, 150 | ) -> anyhow::Result<()> { 151 | loop { 152 | if let Some(bytes) = rx.recv().await { 153 | trace!("recv exchange copy byte and write_all"); 154 | if bytes.is_empty() { 155 | return Err(anyhow!("exchange remote conn close")); 156 | } else { 157 | w.write_all(&bytes) 158 | .await 159 | .context("connection write byte err")?; 160 | } 161 | } else { 162 | return Err(anyhow!("exchange receiver none")); 163 | } 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | use std::cell::RefCell; 170 | use std::io::Cursor; 171 | 172 | use crate::rathole::cmd::Command; 173 | use crate::rathole::cmd::ping::Ping; 174 | use crate::rathole::cmd::resp::Resp; 175 | use crate::rathole::dispatcher::{CommandRead, CommandWrite}; 176 | 177 | pub fn new_command_read(buf: &mut Vec) -> CommandRead>> { 178 | let (r, _w) = tokio::io::split(Cursor::new(Vec::from(buf.as_slice()))); 179 | CommandRead::new(r) 180 | } 181 | 182 | pub fn new_command_write(buf: &mut Vec) -> CommandWrite>> { 183 | let (_r, w) = tokio::io::split(Cursor::new(buf)); 184 | CommandWrite::new(w) 185 | } 186 | 187 | async fn command_write_read(cmd: Command) -> Command { 188 | let mut cell = RefCell::new(Vec::new()); 189 | let mut command_write = new_command_write(cell.get_mut()); 190 | command_write.write_command(10, cmd).await.unwrap(); 191 | let mut command_read = new_command_read(cell.get_mut()); 192 | let (req_id, cmd) = command_read.read_command().await.unwrap(); 193 | assert_eq!(10, req_id); 194 | cmd 195 | } 196 | 197 | #[tokio::test] 198 | pub async fn test_command_ping() { 199 | let ping = Command::Ping(Ping::default()); 200 | let cmd = command_write_read(ping).await; 201 | assert_eq!("Ping(Ping { msg: Some(\"pong\") })", format!("{:?}", cmd)); 202 | } 203 | 204 | #[tokio::test] 205 | pub async fn test_command_resp() { 206 | let resp = Command::Resp(Resp::Ok("hi".to_string())); 207 | let cmd = command_write_read(resp).await; 208 | assert_eq!("Resp(Ok(\"hi\"))", format!("{:?}", cmd)); 209 | } 210 | 211 | #[tokio::test] 212 | pub async fn test_command_resp_err() { 213 | let resp = Command::Resp(Resp::Err("hi".to_string())); 214 | let cmd = command_write_read(resp).await; 215 | assert_eq!("Resp(Err(\"hi\"))", format!("{:?}", cmd)); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/rathole/dispatcher.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | use std::time::{Duration, Instant}; 3 | 4 | use anyhow::{Context, anyhow, bail}; 5 | use bytes::{Buf, BytesMut}; 6 | use tokio::io; 7 | use tokio::io::{ 8 | AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufWriter, ReadHalf, WriteHalf, 9 | }; 10 | use tokio::sync::mpsc; 11 | 12 | use crate::rathole::cmd::Command; 13 | use crate::rathole::context::CommandSender; 14 | use crate::rathole::frame::Frame; 15 | use crate::rathole::{CommandChannel, context}; 16 | 17 | pub struct Dispatcher { 18 | command_read: CommandRead, 19 | command_write: CommandWrite, 20 | 21 | heartbeat: Heartbeat, 22 | heartbeat_sender: mpsc::Sender<()>, 23 | context: context::Context, 24 | receiver: mpsc::Receiver, 25 | start_at: Instant, 26 | } 27 | 28 | /// command read 29 | pub struct CommandRead { 30 | read: ReadHalf, 31 | buffer: BytesMut, 32 | } 33 | 34 | /// command write 35 | pub struct CommandWrite { 36 | write: BufWriter>, 37 | } 38 | 39 | struct Heartbeat { 40 | beat_at: Instant, 41 | receiver: mpsc::Receiver<()>, 42 | } 43 | 44 | impl Dispatcher { 45 | pub fn new(stream: T, hash: String) -> (Self, CommandSender) { 46 | let (sender, receiver) = mpsc::channel(1); 47 | let command_sender = CommandSender::new(hash, sender); 48 | let context = context::Context::new(command_sender.clone()); 49 | let (read, write) = io::split(stream); 50 | let command_read = CommandRead::new(read); 51 | let command_write = CommandWrite::new(write); 52 | 53 | let (heartbeat_sender, heartbeat_receiver) = mpsc::channel(1); 54 | let heartbeat = Heartbeat { 55 | beat_at: Instant::now(), 56 | receiver: heartbeat_receiver, 57 | }; 58 | let start_at = Instant::now(); 59 | 60 | ( 61 | Dispatcher { 62 | command_read, 63 | command_write, 64 | receiver, 65 | context, 66 | heartbeat, 67 | heartbeat_sender, 68 | start_at, 69 | }, 70 | command_sender, 71 | ) 72 | } 73 | 74 | pub async fn dispatch(&mut self) -> anyhow::Result<()> { 75 | tokio::select! { 76 | r1 = Self::stream_command( 77 | &mut self.command_read, 78 | &mut self.heartbeat_sender, 79 | self.context.clone(), 80 | ) => r1, 81 | r2 = Self::channel_command( 82 | &mut self.command_write, 83 | &mut self.receiver, 84 | self.context.clone(), 85 | ) => r2, 86 | r3 = self.heartbeat.watch() => r3, 87 | } 88 | } 89 | 90 | async fn stream_command( 91 | command_read: &mut CommandRead, 92 | heartbead_sender: &mut mpsc::Sender<()>, 93 | context: context::Context, 94 | ) -> anyhow::Result<()> { 95 | loop { 96 | let mut cn = context.clone(); 97 | 98 | let (req_id, cmd) = command_read 99 | .read_command() 100 | .await 101 | .context("Dispatcher can't read command by conn")?; 102 | 103 | heartbead_sender.send(()).await?; 104 | 105 | cn.with_req_id(req_id); 106 | let op_resp = cmd.apply(cn).await.context("Can't apply command")?; 107 | 108 | if let Some(resp) = op_resp { 109 | trace!("Send resp {:?}", resp); 110 | context 111 | .command_sender 112 | .send_with_id(req_id, resp) 113 | .await 114 | .context("Can't send command resp")? 115 | } 116 | } 117 | } 118 | 119 | async fn channel_command( 120 | command_write: &mut CommandWrite, 121 | receiver: &mut mpsc::Receiver, 122 | context: context::Context, 123 | ) -> anyhow::Result<()> { 124 | loop { 125 | let mut cn = context.clone(); 126 | let oc = receiver.recv().await; 127 | trace!("recv command {:?}", oc); 128 | match oc { 129 | Some((req_id, cmd, rc)) => { 130 | match cmd { 131 | Command::Resp(_) => {} 132 | _ => { 133 | cn.with_req_id(req_id).set_req(rc).await; 134 | } 135 | } 136 | command_write 137 | .write_command(req_id, cmd) 138 | .await 139 | .context("Can't write command")?; 140 | } 141 | None => return Err(anyhow!("cmd receiver close")), 142 | }; 143 | } 144 | } 145 | } 146 | 147 | impl CommandRead { 148 | pub fn new(read: ReadHalf) -> Self { 149 | CommandRead { 150 | read, 151 | buffer: BytesMut::new(), 152 | } 153 | } 154 | 155 | pub async fn read_command(&mut self) -> anyhow::Result<(u64, Command)> { 156 | let f = self.read_frame().await.context("Can't read frame")?; 157 | let (req_id, cmd) = Command::from_frame(f).context("Can't cover frame to command")?; 158 | Ok((req_id, cmd)) 159 | } 160 | 161 | async fn read_frame(&mut self) -> anyhow::Result { 162 | loop { 163 | if let Some(frame) = self.parse_frame().context("Can't parse frame")? { 164 | trace!("read frame : {}", frame); 165 | return Ok(frame); 166 | } 167 | 168 | if 0 == self 169 | .read 170 | .read_buf(&mut self.buffer) 171 | .await 172 | .context("Can't read buf for frame")? 173 | { 174 | bail!("connection reset by peer"); 175 | } 176 | } 177 | } 178 | 179 | fn parse_frame(&mut self) -> anyhow::Result> { 180 | use crate::rathole::frame::Error::Incomplete; 181 | 182 | let mut buf = Cursor::new(&self.buffer[..]); 183 | 184 | match Frame::check(&mut buf) { 185 | Ok(_) => { 186 | let len = buf.position() as usize; 187 | 188 | buf.set_position(0); 189 | 190 | let frame = Frame::parse(&mut buf)?; 191 | 192 | self.buffer.advance(len); 193 | 194 | Ok(Some(frame)) 195 | } 196 | 197 | Err(Incomplete) => Ok(None), 198 | Err(e) => Err(e.into()), 199 | } 200 | } 201 | } 202 | 203 | impl CommandWrite { 204 | pub fn new(write: WriteHalf) -> Self { 205 | CommandWrite { 206 | write: BufWriter::new(write), 207 | } 208 | } 209 | 210 | pub async fn write_command(&mut self, req_id: u64, cmd: Command) -> anyhow::Result<()> { 211 | let frame = cmd 212 | .to_frame(req_id) 213 | .context("Can't cover command to frame")?; 214 | 215 | trace!("write frame : {}", frame); 216 | self.write_frame(&frame) 217 | .await 218 | .context("Can't write frame to conn")?; 219 | Ok(()) 220 | } 221 | 222 | async fn write_frame(&mut self, frame: &Frame) -> anyhow::Result<()> { 223 | match frame { 224 | Frame::Array(val) => { 225 | self.write 226 | .write_u8(b'*') 227 | .await 228 | .context("Can't write byte to conn")?; 229 | 230 | self.write_decimal(val.len() as u64).await?; 231 | 232 | for entry in &**val { 233 | self.write_value(entry).await?; 234 | } 235 | } 236 | _ => self.write_value(frame).await?, 237 | } 238 | 239 | self.write.flush().await?; 240 | Ok(()) 241 | } 242 | 243 | /// Write a frame literal to the stream 244 | async fn write_value(&mut self, frame: &Frame) -> anyhow::Result<()> { 245 | match frame { 246 | Frame::Simple(val) => { 247 | self.write.write_u8(b'+').await?; 248 | self.write.write_all(val.as_bytes()).await?; 249 | self.write.write_all(b"\r\n").await?; 250 | } 251 | Frame::Error(val) => { 252 | self.write.write_u8(b'-').await?; 253 | self.write.write_all(val.as_bytes()).await?; 254 | self.write.write_all(b"\r\n").await?; 255 | } 256 | Frame::Integer(val) => { 257 | self.write.write_u8(b':').await?; 258 | self.write_decimal(*val).await?; 259 | } 260 | Frame::Null => { 261 | self.write.write_all(b"$-1\r\n").await?; 262 | } 263 | Frame::Bulk(val) => { 264 | let len = val.len(); 265 | 266 | self.write.write_u8(b'$').await?; 267 | self.write_decimal(len as u64).await?; 268 | self.write.write_all(val).await?; 269 | self.write.write_all(b"\r\n").await?; 270 | } 271 | Frame::Array(_val) => unreachable!(), 272 | } 273 | 274 | Ok(()) 275 | } 276 | 277 | /// Write a decimal frame to the stream 278 | async fn write_decimal(&mut self, val: u64) -> anyhow::Result<()> { 279 | use std::io::Write; 280 | 281 | let mut buf = [0u8; 20]; 282 | let mut buf = Cursor::new(&mut buf[..]); 283 | write!(&mut buf, "{}", val)?; 284 | 285 | let pos = buf.position() as usize; 286 | self.write.write_all(&buf.get_ref()[..pos]).await?; 287 | self.write.write_all(b"\r\n").await?; 288 | 289 | Ok(()) 290 | } 291 | } 292 | 293 | impl Drop for Dispatcher { 294 | fn drop(&mut self) { 295 | self.context.notify_shutdown.send(()).ok(); 296 | info!( 297 | "Drop dispatcher duration: {}s", 298 | self.start_at.elapsed().as_secs() 299 | ); 300 | } 301 | } 302 | 303 | impl Heartbeat { 304 | pub async fn watch(&mut self) -> anyhow::Result<()> { 305 | let mut interval = tokio::time::interval(Duration::from_secs(10)); 306 | loop { 307 | tokio::select! { 308 | r = self.receiver.recv() =>{ 309 | if r.is_none(){ 310 | return Err(anyhow!("can't recv heartbeat")) 311 | } 312 | self.beat_at = Instant::now(); 313 | }, 314 | _ = interval.tick() => { 315 | let elapsed = self.beat_at.elapsed().as_secs(); 316 | let deadline = 60 * 10; 317 | if elapsed > deadline { 318 | info!("The heartbeat has reached deadline: {deadline}s, elapsed: {elapsed}s"); 319 | return Err(anyhow!("heartbeat has reached deadline")); 320 | } 321 | }, 322 | } 323 | } 324 | } 325 | } 326 | 327 | #[cfg(test)] 328 | mod tests { 329 | use std::cell::RefCell; 330 | 331 | use bytes::Bytes; 332 | 333 | use crate::rathole::frame::Frame; 334 | use crate::rathole::tests::{new_command_read, new_command_write}; 335 | 336 | async fn frame_write_read(frame: &Frame) -> Frame { 337 | let mut cell = RefCell::new(Vec::new()); 338 | let mut command_write = new_command_write(cell.get_mut()); 339 | command_write.write_frame(frame).await.unwrap(); 340 | 341 | let mut command_read = new_command_read(cell.get_mut()); 342 | command_read.read_frame().await.unwrap() 343 | } 344 | 345 | #[tokio::test] 346 | async fn test_read_write_frame_simple() { 347 | let frame = Frame::Simple("hello".to_string()); 348 | let f = frame_write_read(&frame).await; 349 | assert!(f.eq(&"hello")) 350 | } 351 | 352 | #[tokio::test] 353 | async fn test_read_write_frame_array() { 354 | let frame = Frame::Array(vec![ 355 | Frame::Simple("hello".to_string()), 356 | Frame::Integer(2), 357 | Frame::Bulk(Bytes::from(vec![0x65, 0x66])), 358 | ]); 359 | let f = frame_write_read(&frame).await; 360 | assert_eq!(format!("{:?}", frame), format!("{:?}", f)) 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/rathole/frame.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use bytes::{Buf, Bytes}; 3 | use std::convert::TryInto; 4 | use std::io::Cursor; 5 | use std::num::TryFromIntError; 6 | use std::string::FromUtf8Error; 7 | use std::{fmt, str, vec}; 8 | 9 | #[derive(Clone, Debug)] 10 | pub enum Frame { 11 | Simple(String), 12 | Error(String), 13 | Integer(u64), 14 | Bulk(Bytes), 15 | Null, 16 | Array(Vec), 17 | } 18 | 19 | #[derive(Debug)] 20 | pub enum Error { 21 | Incomplete, 22 | 23 | Other(anyhow::Error), 24 | } 25 | 26 | #[derive(Debug)] 27 | pub struct Parse { 28 | parts: vec::IntoIter, 29 | } 30 | 31 | #[derive(Debug)] 32 | pub enum ParseError { 33 | EndOfStream, 34 | 35 | Other(anyhow::Error), 36 | } 37 | 38 | impl Frame { 39 | pub(crate) fn array() -> Frame { 40 | Frame::Array(vec![]) 41 | } 42 | 43 | pub(crate) fn push_frame(&mut self, frame: Frame) { 44 | match self { 45 | Frame::Array(vec) => { 46 | vec.push(frame); 47 | } 48 | _ => panic!("not an array frame"), 49 | } 50 | } 51 | 52 | pub(crate) fn push_bulk(&mut self, bytes: Bytes) { 53 | match self { 54 | Frame::Array(vec) => { 55 | vec.push(Frame::Bulk(bytes)); 56 | } 57 | _ => panic!("not an array frame"), 58 | } 59 | } 60 | 61 | pub(crate) fn push_int(&mut self, value: u64) { 62 | match self { 63 | Frame::Array(vec) => { 64 | vec.push(Frame::Integer(value)); 65 | } 66 | _ => panic!("not an array frame"), 67 | } 68 | } 69 | 70 | pub(crate) fn push_req_id(self, req_id: u64) -> Frame { 71 | match self { 72 | Frame::Array(mut vec) => { 73 | vec.push(Frame::Integer(req_id)); 74 | Frame::Array(vec) 75 | } 76 | _ => panic!("not an array frame"), 77 | } 78 | } 79 | 80 | /// Checks if an entire message can be decoded from `src` 81 | pub fn check(src: &mut Cursor<&[u8]>) -> anyhow::Result<(), Error> { 82 | match get_u8(src)? { 83 | b'+' => { 84 | get_line(src)?; 85 | Ok(()) 86 | } 87 | b'-' => { 88 | get_line(src)?; 89 | Ok(()) 90 | } 91 | b':' => { 92 | let _ = get_decimal(src)?; 93 | Ok(()) 94 | } 95 | b'$' => { 96 | if b'-' == peek_u8(src)? { 97 | // Skip '-1\r\n' 98 | skip(src, 4) 99 | } else { 100 | // Read the bulk string 101 | let len: usize = get_decimal(src)?.try_into()?; 102 | 103 | // skip that number of bytes + 2 (\r\n). 104 | skip(src, len + 2) 105 | } 106 | } 107 | b'*' => { 108 | let len = get_decimal(src)?; 109 | 110 | for _ in 0..len { 111 | Frame::check(src)?; 112 | } 113 | 114 | Ok(()) 115 | } 116 | actual => Err(format!("protocol error; invalid frame type byte `{}`", actual).into()), 117 | } 118 | } 119 | 120 | pub fn parse(src: &mut Cursor<&[u8]>) -> anyhow::Result { 121 | match get_u8(src)? { 122 | b'+' => { 123 | // Read the line and convert it to `Vec` 124 | let line = get_line(src)?.to_vec(); 125 | 126 | // Convert the line to a String 127 | let string = String::from_utf8(line)?; 128 | 129 | Ok(Frame::Simple(string)) 130 | } 131 | b'-' => { 132 | // Read the line and convert it to `Vec` 133 | let line = get_line(src)?.to_vec(); 134 | 135 | // Convert the line to a String 136 | let string = String::from_utf8(line)?; 137 | 138 | Ok(Frame::Error(string)) 139 | } 140 | b':' => { 141 | let len = get_decimal(src)?; 142 | Ok(Frame::Integer(len)) 143 | } 144 | b'$' => { 145 | if b'-' == peek_u8(src)? { 146 | let line = get_line(src)?; 147 | 148 | if line != b"-1" { 149 | return Err("protocol error; invalid frame format".into()); 150 | } 151 | 152 | Ok(Frame::Null) 153 | } else { 154 | // Read the bulk string 155 | let len = get_decimal(src)?.try_into()?; 156 | let n = len + 2; 157 | 158 | if src.remaining() < n { 159 | return Err(Error::Incomplete); 160 | } 161 | 162 | let data = Bytes::copy_from_slice(&src.chunk()[..len]); 163 | 164 | // skip that number of bytes + 2 (\r\n). 165 | skip(src, n)?; 166 | 167 | Ok(Frame::Bulk(data)) 168 | } 169 | } 170 | b'*' => { 171 | let len = get_decimal(src)?.try_into()?; 172 | let mut out = Vec::with_capacity(len); 173 | 174 | for _ in 0..len { 175 | out.push(Frame::parse(src)?); 176 | } 177 | 178 | Ok(Frame::Array(out)) 179 | } 180 | _ => unimplemented!(), 181 | } 182 | } 183 | } 184 | 185 | impl Parse { 186 | pub(crate) fn new(frame: Frame) -> anyhow::Result { 187 | let array = match frame { 188 | Frame::Array(array) => array, 189 | frame => return Err(format!("protocol error; expected array, got {:?}", frame).into()), 190 | }; 191 | 192 | Ok(Parse { 193 | parts: array.into_iter(), 194 | }) 195 | } 196 | 197 | pub fn next_part(&mut self) -> anyhow::Result { 198 | self.parts.next().ok_or(ParseError::EndOfStream) 199 | } 200 | 201 | pub(crate) fn next_string(&mut self) -> anyhow::Result { 202 | match self.next_part()? { 203 | Frame::Simple(s) => Ok(s), 204 | Frame::Bulk(data) => str::from_utf8(&data[..]) 205 | .map(|s| s.to_string()) 206 | .map_err(|_| "protocol error; invalid string".into()), 207 | frame => Err(format!( 208 | "protocol error; expected simple frame or bulk frame, got {:?}", 209 | frame 210 | ) 211 | .into()), 212 | } 213 | } 214 | 215 | pub(crate) fn next_bytes(&mut self) -> anyhow::Result { 216 | match self.next_part()? { 217 | Frame::Simple(s) => Ok(Bytes::from(s.into_bytes())), 218 | Frame::Bulk(data) => Ok(data), 219 | frame => Err(format!( 220 | "protocol error; expected simple frame or bulk frame, got {:?}", 221 | frame 222 | ) 223 | .into()), 224 | } 225 | } 226 | 227 | pub(crate) fn next_int(&mut self) -> anyhow::Result { 228 | use atoi::atoi; 229 | 230 | const MSG: &str = "protocol error; invalid number"; 231 | 232 | match self.next_part()? { 233 | Frame::Integer(v) => Ok(v), 234 | Frame::Simple(data) => atoi::(data.as_bytes()).ok_or_else(|| MSG.into()), 235 | Frame::Bulk(data) => atoi::(&data).ok_or_else(|| MSG.into()), 236 | frame => Err(format!("protocol error; expected int frame but got {:?}", frame).into()), 237 | } 238 | } 239 | 240 | pub(crate) fn finish(&mut self) -> anyhow::Result<(), ParseError> { 241 | if self.parts.next().is_none() { 242 | Ok(()) 243 | } else { 244 | Err("protocol error; expected end of frame, but there was more".into()) 245 | } 246 | } 247 | } 248 | 249 | impl PartialEq<&str> for Frame { 250 | fn eq(&self, other: &&str) -> bool { 251 | match self { 252 | Frame::Simple(s) => s.eq(other), 253 | Frame::Bulk(s) => s.eq(other), 254 | _ => false, 255 | } 256 | } 257 | } 258 | 259 | impl fmt::Display for Frame { 260 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 261 | match self { 262 | Frame::Simple(response) => response.fmt(fmt), 263 | Frame::Error(msg) => write!(fmt, "error: {}", msg), 264 | Frame::Integer(num) => num.fmt(fmt), 265 | Frame::Bulk(msg) => { 266 | if msg.len() <= 128 { 267 | match str::from_utf8(msg) { 268 | Ok(string) => string.fmt(fmt), 269 | Err(_) => write!(fmt, "{:?}", msg), 270 | } 271 | } else { 272 | msg.len().fmt(fmt) 273 | } 274 | } 275 | Frame::Null => "(nil)".fmt(fmt), 276 | Frame::Array(parts) => { 277 | write!(fmt, "[")?; 278 | for (i, part) in parts.iter().enumerate() { 279 | if i != 0 { 280 | write!(fmt, ", ")?; 281 | } 282 | part.fmt(fmt)?; 283 | } 284 | write!(fmt, "]")?; 285 | 286 | Ok(()) 287 | } 288 | } 289 | } 290 | } 291 | 292 | fn peek_u8(src: &mut Cursor<&[u8]>) -> anyhow::Result { 293 | if !src.has_remaining() { 294 | return Err(Error::Incomplete); 295 | } 296 | 297 | Ok(src.chunk()[0]) 298 | } 299 | 300 | fn get_u8(src: &mut Cursor<&[u8]>) -> anyhow::Result { 301 | if !src.has_remaining() { 302 | return Err(Error::Incomplete); 303 | } 304 | 305 | Ok(src.get_u8()) 306 | } 307 | 308 | fn skip(src: &mut Cursor<&[u8]>, n: usize) -> anyhow::Result<(), Error> { 309 | if src.remaining() < n { 310 | return Err(Error::Incomplete); 311 | } 312 | 313 | src.advance(n); 314 | Ok(()) 315 | } 316 | 317 | /// Read a new-line terminated decimal 318 | fn get_decimal(src: &mut Cursor<&[u8]>) -> anyhow::Result { 319 | use atoi::atoi; 320 | 321 | let line = get_line(src)?; 322 | 323 | atoi::(line).ok_or_else(|| "protocol error; invalid frame format".into()) 324 | } 325 | 326 | /// Find a line 327 | fn get_line<'a>(src: &mut Cursor<&'a [u8]>) -> anyhow::Result<&'a [u8], Error> { 328 | // Scan the bytes directly 329 | let start = src.position() as usize; 330 | // Scan to the second to last byte 331 | let end = src.get_ref().len() - 1; 332 | 333 | for i in start..end { 334 | if src.get_ref()[i] == b'\r' && src.get_ref()[i + 1] == b'\n' { 335 | // We found a line, update the position to be *after* the \n 336 | src.set_position((i + 2) as u64); 337 | 338 | // Return the line 339 | return Ok(&src.get_ref()[start..i]); 340 | } 341 | } 342 | 343 | Err(Error::Incomplete) 344 | } 345 | 346 | impl From for Error { 347 | fn from(src: String) -> Error { 348 | Error::Other(anyhow!(src)) 349 | } 350 | } 351 | 352 | impl From<&str> for Error { 353 | fn from(src: &str) -> Error { 354 | src.to_string().into() 355 | } 356 | } 357 | 358 | impl From for Error { 359 | fn from(_src: FromUtf8Error) -> Error { 360 | "protocol error; invalid frame format".into() 361 | } 362 | } 363 | 364 | impl From for Error { 365 | fn from(_src: TryFromIntError) -> Error { 366 | "protocol error; invalid frame format".into() 367 | } 368 | } 369 | 370 | impl std::error::Error for Error {} 371 | 372 | impl fmt::Display for Error { 373 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 374 | match self { 375 | Error::Incomplete => "stream ended early".fmt(fmt), 376 | Error::Other(err) => err.fmt(fmt), 377 | } 378 | } 379 | } 380 | 381 | impl From for ParseError { 382 | fn from(src: String) -> ParseError { 383 | ParseError::Other(anyhow!(src)) 384 | } 385 | } 386 | 387 | impl From<&str> for ParseError { 388 | fn from(src: &str) -> ParseError { 389 | src.to_string().into() 390 | } 391 | } 392 | 393 | impl fmt::Display for ParseError { 394 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 395 | match self { 396 | ParseError::EndOfStream => "protocol error; unexpected end of stream".fmt(f), 397 | ParseError::Other(err) => err.fmt(f), 398 | } 399 | } 400 | } 401 | 402 | impl std::error::Error for ParseError {} 403 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anstream" 46 | version = "0.6.18" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 49 | dependencies = [ 50 | "anstyle", 51 | "anstyle-parse", 52 | "anstyle-query", 53 | "anstyle-wincon", 54 | "colorchoice", 55 | "is_terminal_polyfill", 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle" 61 | version = "1.0.10" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 64 | 65 | [[package]] 66 | name = "anstyle-parse" 67 | version = "0.2.6" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 70 | dependencies = [ 71 | "utf8parse", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-query" 76 | version = "1.1.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 79 | dependencies = [ 80 | "windows-sys 0.59.0", 81 | ] 82 | 83 | [[package]] 84 | name = "anstyle-wincon" 85 | version = "3.0.7" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 88 | dependencies = [ 89 | "anstyle", 90 | "once_cell", 91 | "windows-sys 0.59.0", 92 | ] 93 | 94 | [[package]] 95 | name = "anyhow" 96 | version = "1.0.97" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 99 | 100 | [[package]] 101 | name = "ascii" 102 | version = "1.1.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" 105 | 106 | [[package]] 107 | name = "async-trait" 108 | version = "0.1.88" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 111 | dependencies = [ 112 | "proc-macro2", 113 | "quote", 114 | "syn", 115 | ] 116 | 117 | [[package]] 118 | name = "atoi" 119 | version = "2.0.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 122 | dependencies = [ 123 | "num-traits", 124 | ] 125 | 126 | [[package]] 127 | name = "autocfg" 128 | version = "1.4.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 131 | 132 | [[package]] 133 | name = "aws-lc-rs" 134 | version = "1.13.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" 137 | dependencies = [ 138 | "aws-lc-sys", 139 | "zeroize", 140 | ] 141 | 142 | [[package]] 143 | name = "aws-lc-sys" 144 | version = "0.28.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" 147 | dependencies = [ 148 | "bindgen", 149 | "cc", 150 | "cmake", 151 | "dunce", 152 | "fs_extra", 153 | ] 154 | 155 | [[package]] 156 | name = "backtrace" 157 | version = "0.3.74" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 160 | dependencies = [ 161 | "addr2line", 162 | "cfg-if", 163 | "libc", 164 | "miniz_oxide", 165 | "object", 166 | "rustc-demangle", 167 | "windows-targets 0.52.6", 168 | ] 169 | 170 | [[package]] 171 | name = "base16ct" 172 | version = "0.2.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 175 | 176 | [[package]] 177 | name = "base64" 178 | version = "0.21.7" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 181 | 182 | [[package]] 183 | name = "bindgen" 184 | version = "0.69.5" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 187 | dependencies = [ 188 | "bitflags 2.9.0", 189 | "cexpr", 190 | "clang-sys", 191 | "itertools 0.12.1", 192 | "lazy_static", 193 | "lazycell", 194 | "log", 195 | "prettyplease", 196 | "proc-macro2", 197 | "quote", 198 | "regex", 199 | "rustc-hash", 200 | "shlex", 201 | "syn", 202 | "which", 203 | ] 204 | 205 | [[package]] 206 | name = "bitflags" 207 | version = "1.3.2" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 210 | 211 | [[package]] 212 | name = "bitflags" 213 | version = "2.9.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 216 | 217 | [[package]] 218 | name = "block-buffer" 219 | version = "0.10.4" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 222 | dependencies = [ 223 | "generic-array", 224 | ] 225 | 226 | [[package]] 227 | name = "borer-core" 228 | version = "0.4.6" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "401c6ed2e1cab29990f99d7407cae163f5618cbcba164af7a23dc95eb53317f7" 231 | dependencies = [ 232 | "anyhow", 233 | "async-trait", 234 | "bytes", 235 | "dashmap", 236 | "futures", 237 | "log", 238 | "once_cell", 239 | "rand", 240 | "rustls 0.23.25", 241 | "serde", 242 | "serde_json", 243 | "socks5-proto", 244 | "tiny_http", 245 | "tokio", 246 | "tokio-rustls 0.26.2", 247 | "tokio-tungstenite", 248 | "uuid", 249 | "webpki-roots 0.26.8", 250 | ] 251 | 252 | [[package]] 253 | name = "bumpalo" 254 | version = "3.17.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 257 | 258 | [[package]] 259 | name = "bytes" 260 | version = "1.10.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 263 | 264 | [[package]] 265 | name = "cc" 266 | version = "1.2.18" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" 269 | dependencies = [ 270 | "jobserver", 271 | "libc", 272 | "shlex", 273 | ] 274 | 275 | [[package]] 276 | name = "cexpr" 277 | version = "0.6.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 280 | dependencies = [ 281 | "nom", 282 | ] 283 | 284 | [[package]] 285 | name = "cfg-if" 286 | version = "1.0.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 289 | 290 | [[package]] 291 | name = "chrono" 292 | version = "0.4.41" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 295 | dependencies = [ 296 | "android-tzdata", 297 | "iana-time-zone", 298 | "js-sys", 299 | "num-traits", 300 | "wasm-bindgen", 301 | "windows-link", 302 | ] 303 | 304 | [[package]] 305 | name = "chunked_transfer" 306 | version = "1.5.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" 309 | 310 | [[package]] 311 | name = "clang-sys" 312 | version = "1.8.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 315 | dependencies = [ 316 | "glob", 317 | "libc", 318 | "libloading", 319 | ] 320 | 321 | [[package]] 322 | name = "clap" 323 | version = "4.5.35" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" 326 | dependencies = [ 327 | "clap_builder", 328 | "clap_derive", 329 | ] 330 | 331 | [[package]] 332 | name = "clap_builder" 333 | version = "4.5.35" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" 336 | dependencies = [ 337 | "anstream", 338 | "anstyle", 339 | "clap_lex", 340 | "strsim", 341 | ] 342 | 343 | [[package]] 344 | name = "clap_derive" 345 | version = "4.5.32" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 348 | dependencies = [ 349 | "heck", 350 | "proc-macro2", 351 | "quote", 352 | "syn", 353 | ] 354 | 355 | [[package]] 356 | name = "clap_lex" 357 | version = "0.7.4" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 360 | 361 | [[package]] 362 | name = "cmake" 363 | version = "0.1.54" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 366 | dependencies = [ 367 | "cc", 368 | ] 369 | 370 | [[package]] 371 | name = "colorchoice" 372 | version = "1.0.3" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 375 | 376 | [[package]] 377 | name = "core-foundation" 378 | version = "0.9.4" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 381 | dependencies = [ 382 | "core-foundation-sys", 383 | "libc", 384 | ] 385 | 386 | [[package]] 387 | name = "core-foundation-sys" 388 | version = "0.8.7" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 391 | 392 | [[package]] 393 | name = "cpufeatures" 394 | version = "0.2.17" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 397 | dependencies = [ 398 | "libc", 399 | ] 400 | 401 | [[package]] 402 | name = "crossbeam-channel" 403 | version = "0.5.14" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" 406 | dependencies = [ 407 | "crossbeam-utils", 408 | ] 409 | 410 | [[package]] 411 | name = "crossbeam-utils" 412 | version = "0.8.21" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 415 | 416 | [[package]] 417 | name = "crypto-common" 418 | version = "0.1.6" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 421 | dependencies = [ 422 | "generic-array", 423 | "typenum", 424 | ] 425 | 426 | [[package]] 427 | name = "dashmap" 428 | version = "6.1.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 431 | dependencies = [ 432 | "cfg-if", 433 | "crossbeam-utils", 434 | "hashbrown 0.14.5", 435 | "lock_api", 436 | "once_cell", 437 | "parking_lot_core", 438 | ] 439 | 440 | [[package]] 441 | name = "data-encoding" 442 | version = "2.8.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" 445 | 446 | [[package]] 447 | name = "deranged" 448 | version = "0.4.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 451 | dependencies = [ 452 | "powerfmt", 453 | ] 454 | 455 | [[package]] 456 | name = "digest" 457 | version = "0.10.7" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 460 | dependencies = [ 461 | "block-buffer", 462 | "crypto-common", 463 | ] 464 | 465 | [[package]] 466 | name = "displaydoc" 467 | version = "0.2.5" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 470 | dependencies = [ 471 | "proc-macro2", 472 | "quote", 473 | "syn", 474 | ] 475 | 476 | [[package]] 477 | name = "dunce" 478 | version = "1.0.5" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 481 | 482 | [[package]] 483 | name = "either" 484 | version = "1.15.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 487 | 488 | [[package]] 489 | name = "encoding_rs" 490 | version = "0.8.35" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 493 | dependencies = [ 494 | "cfg-if", 495 | ] 496 | 497 | [[package]] 498 | name = "equivalent" 499 | version = "1.0.2" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 502 | 503 | [[package]] 504 | name = "erased-serde" 505 | version = "0.4.6" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" 508 | dependencies = [ 509 | "serde", 510 | "typeid", 511 | ] 512 | 513 | [[package]] 514 | name = "errno" 515 | version = "0.3.11" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 518 | dependencies = [ 519 | "libc", 520 | "windows-sys 0.59.0", 521 | ] 522 | 523 | [[package]] 524 | name = "fnv" 525 | version = "1.0.7" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 528 | 529 | [[package]] 530 | name = "form_urlencoded" 531 | version = "1.2.1" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 534 | dependencies = [ 535 | "percent-encoding", 536 | ] 537 | 538 | [[package]] 539 | name = "fs_extra" 540 | version = "1.3.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 543 | 544 | [[package]] 545 | name = "futures" 546 | version = "0.3.31" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 549 | dependencies = [ 550 | "futures-channel", 551 | "futures-core", 552 | "futures-executor", 553 | "futures-io", 554 | "futures-sink", 555 | "futures-task", 556 | "futures-util", 557 | ] 558 | 559 | [[package]] 560 | name = "futures-channel" 561 | version = "0.3.31" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 564 | dependencies = [ 565 | "futures-core", 566 | "futures-sink", 567 | ] 568 | 569 | [[package]] 570 | name = "futures-core" 571 | version = "0.3.31" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 574 | 575 | [[package]] 576 | name = "futures-executor" 577 | version = "0.3.31" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 580 | dependencies = [ 581 | "futures-core", 582 | "futures-task", 583 | "futures-util", 584 | ] 585 | 586 | [[package]] 587 | name = "futures-io" 588 | version = "0.3.31" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 591 | 592 | [[package]] 593 | name = "futures-macro" 594 | version = "0.3.31" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 597 | dependencies = [ 598 | "proc-macro2", 599 | "quote", 600 | "syn", 601 | ] 602 | 603 | [[package]] 604 | name = "futures-sink" 605 | version = "0.3.31" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 608 | 609 | [[package]] 610 | name = "futures-task" 611 | version = "0.3.31" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 614 | 615 | [[package]] 616 | name = "futures-util" 617 | version = "0.3.31" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 620 | dependencies = [ 621 | "futures-channel", 622 | "futures-core", 623 | "futures-io", 624 | "futures-macro", 625 | "futures-sink", 626 | "futures-task", 627 | "memchr", 628 | "pin-project-lite", 629 | "pin-utils", 630 | "slab", 631 | ] 632 | 633 | [[package]] 634 | name = "generic-array" 635 | version = "0.14.7" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 638 | dependencies = [ 639 | "typenum", 640 | "version_check", 641 | ] 642 | 643 | [[package]] 644 | name = "getrandom" 645 | version = "0.2.15" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 648 | dependencies = [ 649 | "cfg-if", 650 | "libc", 651 | "wasi 0.11.0+wasi-snapshot-preview1", 652 | ] 653 | 654 | [[package]] 655 | name = "getrandom" 656 | version = "0.3.2" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 659 | dependencies = [ 660 | "cfg-if", 661 | "libc", 662 | "r-efi", 663 | "wasi 0.14.2+wasi-0.2.4", 664 | ] 665 | 666 | [[package]] 667 | name = "gimli" 668 | version = "0.31.1" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 671 | 672 | [[package]] 673 | name = "glob" 674 | version = "0.3.2" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 677 | 678 | [[package]] 679 | name = "h2" 680 | version = "0.3.26" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 683 | dependencies = [ 684 | "bytes", 685 | "fnv", 686 | "futures-core", 687 | "futures-sink", 688 | "futures-util", 689 | "http 0.2.12", 690 | "indexmap", 691 | "slab", 692 | "tokio", 693 | "tokio-util", 694 | "tracing", 695 | ] 696 | 697 | [[package]] 698 | name = "hashbrown" 699 | version = "0.14.5" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 702 | 703 | [[package]] 704 | name = "hashbrown" 705 | version = "0.15.2" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 708 | 709 | [[package]] 710 | name = "heck" 711 | version = "0.5.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 714 | 715 | [[package]] 716 | name = "home" 717 | version = "0.5.11" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 720 | dependencies = [ 721 | "windows-sys 0.59.0", 722 | ] 723 | 724 | [[package]] 725 | name = "http" 726 | version = "0.2.12" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 729 | dependencies = [ 730 | "bytes", 731 | "fnv", 732 | "itoa", 733 | ] 734 | 735 | [[package]] 736 | name = "http" 737 | version = "1.3.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 740 | dependencies = [ 741 | "bytes", 742 | "fnv", 743 | "itoa", 744 | ] 745 | 746 | [[package]] 747 | name = "http-body" 748 | version = "0.4.6" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 751 | dependencies = [ 752 | "bytes", 753 | "http 0.2.12", 754 | "pin-project-lite", 755 | ] 756 | 757 | [[package]] 758 | name = "httparse" 759 | version = "1.10.1" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 762 | 763 | [[package]] 764 | name = "httpdate" 765 | version = "1.0.3" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 768 | 769 | [[package]] 770 | name = "hyper" 771 | version = "0.14.32" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 774 | dependencies = [ 775 | "bytes", 776 | "futures-channel", 777 | "futures-core", 778 | "futures-util", 779 | "h2", 780 | "http 0.2.12", 781 | "http-body", 782 | "httparse", 783 | "httpdate", 784 | "itoa", 785 | "pin-project-lite", 786 | "socket2", 787 | "tokio", 788 | "tower-service", 789 | "tracing", 790 | "want", 791 | ] 792 | 793 | [[package]] 794 | name = "hyper-rustls" 795 | version = "0.24.2" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 798 | dependencies = [ 799 | "futures-util", 800 | "http 0.2.12", 801 | "hyper", 802 | "rustls 0.21.12", 803 | "tokio", 804 | "tokio-rustls 0.24.1", 805 | ] 806 | 807 | [[package]] 808 | name = "iana-time-zone" 809 | version = "0.1.63" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 812 | dependencies = [ 813 | "android_system_properties", 814 | "core-foundation-sys", 815 | "iana-time-zone-haiku", 816 | "js-sys", 817 | "log", 818 | "wasm-bindgen", 819 | "windows-core", 820 | ] 821 | 822 | [[package]] 823 | name = "iana-time-zone-haiku" 824 | version = "0.1.2" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 827 | dependencies = [ 828 | "cc", 829 | ] 830 | 831 | [[package]] 832 | name = "icu_collections" 833 | version = "1.5.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 836 | dependencies = [ 837 | "displaydoc", 838 | "yoke", 839 | "zerofrom", 840 | "zerovec", 841 | ] 842 | 843 | [[package]] 844 | name = "icu_locid" 845 | version = "1.5.0" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 848 | dependencies = [ 849 | "displaydoc", 850 | "litemap", 851 | "tinystr", 852 | "writeable", 853 | "zerovec", 854 | ] 855 | 856 | [[package]] 857 | name = "icu_locid_transform" 858 | version = "1.5.0" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 861 | dependencies = [ 862 | "displaydoc", 863 | "icu_locid", 864 | "icu_locid_transform_data", 865 | "icu_provider", 866 | "tinystr", 867 | "zerovec", 868 | ] 869 | 870 | [[package]] 871 | name = "icu_locid_transform_data" 872 | version = "1.5.1" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" 875 | 876 | [[package]] 877 | name = "icu_normalizer" 878 | version = "1.5.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 881 | dependencies = [ 882 | "displaydoc", 883 | "icu_collections", 884 | "icu_normalizer_data", 885 | "icu_properties", 886 | "icu_provider", 887 | "smallvec", 888 | "utf16_iter", 889 | "utf8_iter", 890 | "write16", 891 | "zerovec", 892 | ] 893 | 894 | [[package]] 895 | name = "icu_normalizer_data" 896 | version = "1.5.1" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" 899 | 900 | [[package]] 901 | name = "icu_properties" 902 | version = "1.5.1" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 905 | dependencies = [ 906 | "displaydoc", 907 | "icu_collections", 908 | "icu_locid_transform", 909 | "icu_properties_data", 910 | "icu_provider", 911 | "tinystr", 912 | "zerovec", 913 | ] 914 | 915 | [[package]] 916 | name = "icu_properties_data" 917 | version = "1.5.1" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" 920 | 921 | [[package]] 922 | name = "icu_provider" 923 | version = "1.5.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 926 | dependencies = [ 927 | "displaydoc", 928 | "icu_locid", 929 | "icu_provider_macros", 930 | "stable_deref_trait", 931 | "tinystr", 932 | "writeable", 933 | "yoke", 934 | "zerofrom", 935 | "zerovec", 936 | ] 937 | 938 | [[package]] 939 | name = "icu_provider_macros" 940 | version = "1.5.0" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 943 | dependencies = [ 944 | "proc-macro2", 945 | "quote", 946 | "syn", 947 | ] 948 | 949 | [[package]] 950 | name = "idna" 951 | version = "1.0.3" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 954 | dependencies = [ 955 | "idna_adapter", 956 | "smallvec", 957 | "utf8_iter", 958 | ] 959 | 960 | [[package]] 961 | name = "idna_adapter" 962 | version = "1.2.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 965 | dependencies = [ 966 | "icu_normalizer", 967 | "icu_properties", 968 | ] 969 | 970 | [[package]] 971 | name = "indexmap" 972 | version = "2.9.0" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 975 | dependencies = [ 976 | "equivalent", 977 | "hashbrown 0.15.2", 978 | ] 979 | 980 | [[package]] 981 | name = "ipnet" 982 | version = "2.11.0" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 985 | 986 | [[package]] 987 | name = "is_terminal_polyfill" 988 | version = "1.70.1" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 991 | 992 | [[package]] 993 | name = "itertools" 994 | version = "0.12.1" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 997 | dependencies = [ 998 | "either", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "itertools" 1003 | version = "0.14.0" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 1006 | dependencies = [ 1007 | "either", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "itoa" 1012 | version = "1.0.15" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1015 | 1016 | [[package]] 1017 | name = "jobserver" 1018 | version = "0.1.33" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 1021 | dependencies = [ 1022 | "getrandom 0.3.2", 1023 | "libc", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "js-sys" 1028 | version = "0.3.77" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1031 | dependencies = [ 1032 | "once_cell", 1033 | "wasm-bindgen", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "lazy_static" 1038 | version = "1.5.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1041 | 1042 | [[package]] 1043 | name = "lazycell" 1044 | version = "1.3.0" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 1047 | 1048 | [[package]] 1049 | name = "libc" 1050 | version = "0.2.171" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 1053 | 1054 | [[package]] 1055 | name = "libloading" 1056 | version = "0.8.6" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 1059 | dependencies = [ 1060 | "cfg-if", 1061 | "windows-targets 0.52.6", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "linux-raw-sys" 1066 | version = "0.4.15" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1069 | 1070 | [[package]] 1071 | name = "litemap" 1072 | version = "0.7.5" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 1075 | 1076 | [[package]] 1077 | name = "lock_api" 1078 | version = "0.4.12" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1081 | dependencies = [ 1082 | "autocfg", 1083 | "scopeguard", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "log" 1088 | version = "0.4.27" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1091 | dependencies = [ 1092 | "serde", 1093 | "value-bag", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "matchers" 1098 | version = "0.1.0" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 1101 | dependencies = [ 1102 | "regex-automata 0.1.10", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "memchr" 1107 | version = "2.7.4" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1110 | 1111 | [[package]] 1112 | name = "mime" 1113 | version = "0.3.17" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1116 | 1117 | [[package]] 1118 | name = "minimal-lexical" 1119 | version = "0.2.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1122 | 1123 | [[package]] 1124 | name = "miniz_oxide" 1125 | version = "0.8.7" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" 1128 | dependencies = [ 1129 | "adler2", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "mio" 1134 | version = "1.0.3" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1137 | dependencies = [ 1138 | "libc", 1139 | "wasi 0.11.0+wasi-snapshot-preview1", 1140 | "windows-sys 0.52.0", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "nom" 1145 | version = "7.1.3" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1148 | dependencies = [ 1149 | "memchr", 1150 | "minimal-lexical", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "nu-ansi-term" 1155 | version = "0.46.0" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1158 | dependencies = [ 1159 | "overload", 1160 | "winapi", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "num-conv" 1165 | version = "0.1.0" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1168 | 1169 | [[package]] 1170 | name = "num-traits" 1171 | version = "0.2.19" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1174 | dependencies = [ 1175 | "autocfg", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "num_threads" 1180 | version = "0.1.7" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 1183 | dependencies = [ 1184 | "libc", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "object" 1189 | version = "0.36.7" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1192 | dependencies = [ 1193 | "memchr", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "once_cell" 1198 | version = "1.21.3" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1201 | 1202 | [[package]] 1203 | name = "overload" 1204 | version = "0.1.1" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1207 | 1208 | [[package]] 1209 | name = "parking_lot" 1210 | version = "0.12.3" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1213 | dependencies = [ 1214 | "lock_api", 1215 | "parking_lot_core", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "parking_lot_core" 1220 | version = "0.9.10" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1223 | dependencies = [ 1224 | "cfg-if", 1225 | "libc", 1226 | "redox_syscall", 1227 | "smallvec", 1228 | "windows-targets 0.52.6", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "percent-encoding" 1233 | version = "2.3.1" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1236 | 1237 | [[package]] 1238 | name = "pin-project-lite" 1239 | version = "0.2.16" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1242 | 1243 | [[package]] 1244 | name = "pin-utils" 1245 | version = "0.1.0" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1248 | 1249 | [[package]] 1250 | name = "powerfmt" 1251 | version = "0.2.0" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1254 | 1255 | [[package]] 1256 | name = "ppv-lite86" 1257 | version = "0.2.21" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1260 | dependencies = [ 1261 | "zerocopy", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "prettyplease" 1266 | version = "0.2.32" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" 1269 | dependencies = [ 1270 | "proc-macro2", 1271 | "syn", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "proc-macro2" 1276 | version = "1.0.94" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 1279 | dependencies = [ 1280 | "unicode-ident", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "quote" 1285 | version = "1.0.40" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1288 | dependencies = [ 1289 | "proc-macro2", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "r-efi" 1294 | version = "5.2.0" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1297 | 1298 | [[package]] 1299 | name = "rand" 1300 | version = "0.9.0" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 1303 | dependencies = [ 1304 | "rand_chacha", 1305 | "rand_core", 1306 | "zerocopy", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "rand_chacha" 1311 | version = "0.9.0" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1314 | dependencies = [ 1315 | "ppv-lite86", 1316 | "rand_core", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "rand_core" 1321 | version = "0.9.3" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1324 | dependencies = [ 1325 | "getrandom 0.3.2", 1326 | ] 1327 | 1328 | [[package]] 1329 | name = "redox_syscall" 1330 | version = "0.5.11" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 1333 | dependencies = [ 1334 | "bitflags 2.9.0", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "regex" 1339 | version = "1.11.1" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1342 | dependencies = [ 1343 | "aho-corasick", 1344 | "memchr", 1345 | "regex-automata 0.4.9", 1346 | "regex-syntax 0.8.5", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "regex-automata" 1351 | version = "0.1.10" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1354 | dependencies = [ 1355 | "regex-syntax 0.6.29", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "regex-automata" 1360 | version = "0.4.9" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1363 | dependencies = [ 1364 | "aho-corasick", 1365 | "memchr", 1366 | "regex-syntax 0.8.5", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "regex-syntax" 1371 | version = "0.6.29" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 1374 | 1375 | [[package]] 1376 | name = "regex-syntax" 1377 | version = "0.8.5" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1380 | 1381 | [[package]] 1382 | name = "reqwest" 1383 | version = "0.11.27" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1386 | dependencies = [ 1387 | "base64", 1388 | "bytes", 1389 | "encoding_rs", 1390 | "futures-core", 1391 | "futures-util", 1392 | "h2", 1393 | "http 0.2.12", 1394 | "http-body", 1395 | "hyper", 1396 | "hyper-rustls", 1397 | "ipnet", 1398 | "js-sys", 1399 | "log", 1400 | "mime", 1401 | "once_cell", 1402 | "percent-encoding", 1403 | "pin-project-lite", 1404 | "rustls 0.21.12", 1405 | "rustls-pemfile", 1406 | "serde", 1407 | "serde_json", 1408 | "serde_urlencoded", 1409 | "sync_wrapper", 1410 | "system-configuration", 1411 | "tokio", 1412 | "tokio-rustls 0.24.1", 1413 | "tokio-socks", 1414 | "tower-service", 1415 | "url", 1416 | "wasm-bindgen", 1417 | "wasm-bindgen-futures", 1418 | "web-sys", 1419 | "webpki-roots 0.25.4", 1420 | "winreg", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "ring" 1425 | version = "0.17.14" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1428 | dependencies = [ 1429 | "cc", 1430 | "cfg-if", 1431 | "getrandom 0.2.15", 1432 | "libc", 1433 | "untrusted", 1434 | "windows-sys 0.52.0", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "rolling-file" 1439 | version = "0.2.0" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "8395b4f860856b740f20a296ea2cd4d823e81a2658cf05ef61be22916026a906" 1442 | dependencies = [ 1443 | "chrono", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "rustc-demangle" 1448 | version = "0.1.24" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1451 | 1452 | [[package]] 1453 | name = "rustc-hash" 1454 | version = "1.1.0" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1457 | 1458 | [[package]] 1459 | name = "rustix" 1460 | version = "0.38.44" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1463 | dependencies = [ 1464 | "bitflags 2.9.0", 1465 | "errno", 1466 | "libc", 1467 | "linux-raw-sys", 1468 | "windows-sys 0.59.0", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "rustls" 1473 | version = "0.21.12" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 1476 | dependencies = [ 1477 | "log", 1478 | "ring", 1479 | "rustls-webpki 0.101.7", 1480 | "sct", 1481 | ] 1482 | 1483 | [[package]] 1484 | name = "rustls" 1485 | version = "0.23.25" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" 1488 | dependencies = [ 1489 | "aws-lc-rs", 1490 | "log", 1491 | "once_cell", 1492 | "rustls-pki-types", 1493 | "rustls-webpki 0.103.1", 1494 | "subtle", 1495 | "zeroize", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "rustls-pemfile" 1500 | version = "1.0.4" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1503 | dependencies = [ 1504 | "base64", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "rustls-pki-types" 1509 | version = "1.11.0" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1512 | 1513 | [[package]] 1514 | name = "rustls-webpki" 1515 | version = "0.101.7" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 1518 | dependencies = [ 1519 | "ring", 1520 | "untrusted", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "rustls-webpki" 1525 | version = "0.103.1" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" 1528 | dependencies = [ 1529 | "aws-lc-rs", 1530 | "ring", 1531 | "rustls-pki-types", 1532 | "untrusted", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "rustversion" 1537 | version = "1.0.20" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1540 | 1541 | [[package]] 1542 | name = "ryu" 1543 | version = "1.0.20" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1546 | 1547 | [[package]] 1548 | name = "scopeguard" 1549 | version = "1.2.0" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1552 | 1553 | [[package]] 1554 | name = "sct" 1555 | version = "0.7.1" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 1558 | dependencies = [ 1559 | "ring", 1560 | "untrusted", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "serde" 1565 | version = "1.0.219" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1568 | dependencies = [ 1569 | "serde_derive", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "serde_derive" 1574 | version = "1.0.219" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1577 | dependencies = [ 1578 | "proc-macro2", 1579 | "quote", 1580 | "syn", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "serde_fmt" 1585 | version = "1.0.3" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" 1588 | dependencies = [ 1589 | "serde", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "serde_json" 1594 | version = "1.0.140" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1597 | dependencies = [ 1598 | "itoa", 1599 | "memchr", 1600 | "ryu", 1601 | "serde", 1602 | ] 1603 | 1604 | [[package]] 1605 | name = "serde_urlencoded" 1606 | version = "0.7.1" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1609 | dependencies = [ 1610 | "form_urlencoded", 1611 | "itoa", 1612 | "ryu", 1613 | "serde", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "serde_yaml" 1618 | version = "0.9.34+deprecated" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1621 | dependencies = [ 1622 | "indexmap", 1623 | "itoa", 1624 | "ryu", 1625 | "serde", 1626 | "unsafe-libyaml", 1627 | ] 1628 | 1629 | [[package]] 1630 | name = "sha1" 1631 | version = "0.10.6" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1634 | dependencies = [ 1635 | "cfg-if", 1636 | "cpufeatures", 1637 | "digest", 1638 | ] 1639 | 1640 | [[package]] 1641 | name = "sha2" 1642 | version = "0.10.8" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1645 | dependencies = [ 1646 | "cfg-if", 1647 | "cpufeatures", 1648 | "digest", 1649 | ] 1650 | 1651 | [[package]] 1652 | name = "sharded-slab" 1653 | version = "0.1.7" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1656 | dependencies = [ 1657 | "lazy_static", 1658 | ] 1659 | 1660 | [[package]] 1661 | name = "shlex" 1662 | version = "1.3.0" 1663 | source = "registry+https://github.com/rust-lang/crates.io-index" 1664 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1665 | 1666 | [[package]] 1667 | name = "shuttle" 1668 | version = "0.7.2" 1669 | dependencies = [ 1670 | "anyhow", 1671 | "atoi", 1672 | "base16ct", 1673 | "borer-core", 1674 | "bytes", 1675 | "clap", 1676 | "itertools 0.14.0", 1677 | "log", 1678 | "reqwest", 1679 | "rolling-file", 1680 | "serde", 1681 | "serde_json", 1682 | "serde_yaml", 1683 | "sha2", 1684 | "time", 1685 | "tokio", 1686 | "tokio-rustls 0.26.2", 1687 | "tracing", 1688 | "tracing-appender", 1689 | "tracing-subscriber", 1690 | "uuid", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "signal-hook-registry" 1695 | version = "1.4.2" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1698 | dependencies = [ 1699 | "libc", 1700 | ] 1701 | 1702 | [[package]] 1703 | name = "slab" 1704 | version = "0.4.9" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1707 | dependencies = [ 1708 | "autocfg", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "smallvec" 1713 | version = "1.15.0" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1716 | 1717 | [[package]] 1718 | name = "socket2" 1719 | version = "0.5.9" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 1722 | dependencies = [ 1723 | "libc", 1724 | "windows-sys 0.52.0", 1725 | ] 1726 | 1727 | [[package]] 1728 | name = "socks5-proto" 1729 | version = "0.4.1" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "3d91431c4672e25e372ef46bc554be8f315068c03608f99267a71ad32a12e8c4" 1732 | dependencies = [ 1733 | "bytes", 1734 | "thiserror 1.0.69", 1735 | "tokio", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "stable_deref_trait" 1740 | version = "1.2.0" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1743 | 1744 | [[package]] 1745 | name = "strsim" 1746 | version = "0.11.1" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1749 | 1750 | [[package]] 1751 | name = "subtle" 1752 | version = "2.6.1" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1755 | 1756 | [[package]] 1757 | name = "sval" 1758 | version = "2.14.1" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "7cc9739f56c5d0c44a5ed45473ec868af02eb896af8c05f616673a31e1d1bb09" 1761 | 1762 | [[package]] 1763 | name = "sval_buffer" 1764 | version = "2.14.1" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "f39b07436a8c271b34dad5070c634d1d3d76d6776e938ee97b4a66a5e8003d0b" 1767 | dependencies = [ 1768 | "sval", 1769 | "sval_ref", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "sval_dynamic" 1774 | version = "2.14.1" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "ffcb072d857431bf885580dacecf05ed987bac931230736739a79051dbf3499b" 1777 | dependencies = [ 1778 | "sval", 1779 | ] 1780 | 1781 | [[package]] 1782 | name = "sval_fmt" 1783 | version = "2.14.1" 1784 | source = "registry+https://github.com/rust-lang/crates.io-index" 1785 | checksum = "3f214f427ad94a553e5ca5514c95c6be84667cbc5568cce957f03f3477d03d5c" 1786 | dependencies = [ 1787 | "itoa", 1788 | "ryu", 1789 | "sval", 1790 | ] 1791 | 1792 | [[package]] 1793 | name = "sval_json" 1794 | version = "2.14.1" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "389ed34b32e638dec9a99c8ac92d0aa1220d40041026b625474c2b6a4d6f4feb" 1797 | dependencies = [ 1798 | "itoa", 1799 | "ryu", 1800 | "sval", 1801 | ] 1802 | 1803 | [[package]] 1804 | name = "sval_nested" 1805 | version = "2.14.1" 1806 | source = "registry+https://github.com/rust-lang/crates.io-index" 1807 | checksum = "14bae8fcb2f24fee2c42c1f19037707f7c9a29a0cda936d2188d48a961c4bb2a" 1808 | dependencies = [ 1809 | "sval", 1810 | "sval_buffer", 1811 | "sval_ref", 1812 | ] 1813 | 1814 | [[package]] 1815 | name = "sval_ref" 1816 | version = "2.14.1" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "2a4eaea3821d3046dcba81d4b8489421da42961889902342691fb7eab491d79e" 1819 | dependencies = [ 1820 | "sval", 1821 | ] 1822 | 1823 | [[package]] 1824 | name = "sval_serde" 1825 | version = "2.14.1" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "172dd4aa8cb3b45c8ac8f3b4111d644cd26938b0643ede8f93070812b87fb339" 1828 | dependencies = [ 1829 | "serde", 1830 | "sval", 1831 | "sval_nested", 1832 | ] 1833 | 1834 | [[package]] 1835 | name = "syn" 1836 | version = "2.0.100" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 1839 | dependencies = [ 1840 | "proc-macro2", 1841 | "quote", 1842 | "unicode-ident", 1843 | ] 1844 | 1845 | [[package]] 1846 | name = "sync_wrapper" 1847 | version = "0.1.2" 1848 | source = "registry+https://github.com/rust-lang/crates.io-index" 1849 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1850 | 1851 | [[package]] 1852 | name = "synstructure" 1853 | version = "0.13.1" 1854 | source = "registry+https://github.com/rust-lang/crates.io-index" 1855 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1856 | dependencies = [ 1857 | "proc-macro2", 1858 | "quote", 1859 | "syn", 1860 | ] 1861 | 1862 | [[package]] 1863 | name = "system-configuration" 1864 | version = "0.5.1" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1867 | dependencies = [ 1868 | "bitflags 1.3.2", 1869 | "core-foundation", 1870 | "system-configuration-sys", 1871 | ] 1872 | 1873 | [[package]] 1874 | name = "system-configuration-sys" 1875 | version = "0.5.0" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1878 | dependencies = [ 1879 | "core-foundation-sys", 1880 | "libc", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "thiserror" 1885 | version = "1.0.69" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1888 | dependencies = [ 1889 | "thiserror-impl 1.0.69", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "thiserror" 1894 | version = "2.0.12" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1897 | dependencies = [ 1898 | "thiserror-impl 2.0.12", 1899 | ] 1900 | 1901 | [[package]] 1902 | name = "thiserror-impl" 1903 | version = "1.0.69" 1904 | source = "registry+https://github.com/rust-lang/crates.io-index" 1905 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1906 | dependencies = [ 1907 | "proc-macro2", 1908 | "quote", 1909 | "syn", 1910 | ] 1911 | 1912 | [[package]] 1913 | name = "thiserror-impl" 1914 | version = "2.0.12" 1915 | source = "registry+https://github.com/rust-lang/crates.io-index" 1916 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1917 | dependencies = [ 1918 | "proc-macro2", 1919 | "quote", 1920 | "syn", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "thread_local" 1925 | version = "1.1.8" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1928 | dependencies = [ 1929 | "cfg-if", 1930 | "once_cell", 1931 | ] 1932 | 1933 | [[package]] 1934 | name = "time" 1935 | version = "0.3.41" 1936 | source = "registry+https://github.com/rust-lang/crates.io-index" 1937 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 1938 | dependencies = [ 1939 | "deranged", 1940 | "itoa", 1941 | "libc", 1942 | "num-conv", 1943 | "num_threads", 1944 | "powerfmt", 1945 | "serde", 1946 | "time-core", 1947 | "time-macros", 1948 | ] 1949 | 1950 | [[package]] 1951 | name = "time-core" 1952 | version = "0.1.4" 1953 | source = "registry+https://github.com/rust-lang/crates.io-index" 1954 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 1955 | 1956 | [[package]] 1957 | name = "time-macros" 1958 | version = "0.2.22" 1959 | source = "registry+https://github.com/rust-lang/crates.io-index" 1960 | checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" 1961 | dependencies = [ 1962 | "num-conv", 1963 | "time-core", 1964 | ] 1965 | 1966 | [[package]] 1967 | name = "tiny_http" 1968 | version = "0.12.0" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" 1971 | dependencies = [ 1972 | "ascii", 1973 | "chunked_transfer", 1974 | "httpdate", 1975 | "log", 1976 | ] 1977 | 1978 | [[package]] 1979 | name = "tinystr" 1980 | version = "0.7.6" 1981 | source = "registry+https://github.com/rust-lang/crates.io-index" 1982 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1983 | dependencies = [ 1984 | "displaydoc", 1985 | "zerovec", 1986 | ] 1987 | 1988 | [[package]] 1989 | name = "tokio" 1990 | version = "1.44.2" 1991 | source = "registry+https://github.com/rust-lang/crates.io-index" 1992 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 1993 | dependencies = [ 1994 | "backtrace", 1995 | "bytes", 1996 | "libc", 1997 | "mio", 1998 | "parking_lot", 1999 | "pin-project-lite", 2000 | "signal-hook-registry", 2001 | "socket2", 2002 | "tokio-macros", 2003 | "windows-sys 0.52.0", 2004 | ] 2005 | 2006 | [[package]] 2007 | name = "tokio-macros" 2008 | version = "2.5.0" 2009 | source = "registry+https://github.com/rust-lang/crates.io-index" 2010 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 2011 | dependencies = [ 2012 | "proc-macro2", 2013 | "quote", 2014 | "syn", 2015 | ] 2016 | 2017 | [[package]] 2018 | name = "tokio-rustls" 2019 | version = "0.24.1" 2020 | source = "registry+https://github.com/rust-lang/crates.io-index" 2021 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 2022 | dependencies = [ 2023 | "rustls 0.21.12", 2024 | "tokio", 2025 | ] 2026 | 2027 | [[package]] 2028 | name = "tokio-rustls" 2029 | version = "0.26.2" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 2032 | dependencies = [ 2033 | "rustls 0.23.25", 2034 | "tokio", 2035 | ] 2036 | 2037 | [[package]] 2038 | name = "tokio-socks" 2039 | version = "0.5.2" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" 2042 | dependencies = [ 2043 | "either", 2044 | "futures-util", 2045 | "thiserror 1.0.69", 2046 | "tokio", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "tokio-tungstenite" 2051 | version = "0.26.2" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" 2054 | dependencies = [ 2055 | "futures-util", 2056 | "log", 2057 | "rustls 0.23.25", 2058 | "rustls-pki-types", 2059 | "tokio", 2060 | "tokio-rustls 0.26.2", 2061 | "tungstenite", 2062 | "webpki-roots 0.26.8", 2063 | ] 2064 | 2065 | [[package]] 2066 | name = "tokio-util" 2067 | version = "0.7.14" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" 2070 | dependencies = [ 2071 | "bytes", 2072 | "futures-core", 2073 | "futures-sink", 2074 | "pin-project-lite", 2075 | "tokio", 2076 | ] 2077 | 2078 | [[package]] 2079 | name = "tower-service" 2080 | version = "0.3.3" 2081 | source = "registry+https://github.com/rust-lang/crates.io-index" 2082 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2083 | 2084 | [[package]] 2085 | name = "tracing" 2086 | version = "0.1.41" 2087 | source = "registry+https://github.com/rust-lang/crates.io-index" 2088 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2089 | dependencies = [ 2090 | "pin-project-lite", 2091 | "tracing-attributes", 2092 | "tracing-core", 2093 | ] 2094 | 2095 | [[package]] 2096 | name = "tracing-appender" 2097 | version = "0.2.3" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" 2100 | dependencies = [ 2101 | "crossbeam-channel", 2102 | "thiserror 1.0.69", 2103 | "time", 2104 | "tracing-subscriber", 2105 | ] 2106 | 2107 | [[package]] 2108 | name = "tracing-attributes" 2109 | version = "0.1.28" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 2112 | dependencies = [ 2113 | "proc-macro2", 2114 | "quote", 2115 | "syn", 2116 | ] 2117 | 2118 | [[package]] 2119 | name = "tracing-core" 2120 | version = "0.1.33" 2121 | source = "registry+https://github.com/rust-lang/crates.io-index" 2122 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2123 | dependencies = [ 2124 | "once_cell", 2125 | "valuable", 2126 | ] 2127 | 2128 | [[package]] 2129 | name = "tracing-log" 2130 | version = "0.2.0" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 2133 | dependencies = [ 2134 | "log", 2135 | "once_cell", 2136 | "tracing-core", 2137 | ] 2138 | 2139 | [[package]] 2140 | name = "tracing-subscriber" 2141 | version = "0.3.19" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 2144 | dependencies = [ 2145 | "matchers", 2146 | "nu-ansi-term", 2147 | "once_cell", 2148 | "regex", 2149 | "sharded-slab", 2150 | "smallvec", 2151 | "thread_local", 2152 | "time", 2153 | "tracing", 2154 | "tracing-core", 2155 | "tracing-log", 2156 | ] 2157 | 2158 | [[package]] 2159 | name = "try-lock" 2160 | version = "0.2.5" 2161 | source = "registry+https://github.com/rust-lang/crates.io-index" 2162 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2163 | 2164 | [[package]] 2165 | name = "tungstenite" 2166 | version = "0.26.2" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" 2169 | dependencies = [ 2170 | "bytes", 2171 | "data-encoding", 2172 | "http 1.3.1", 2173 | "httparse", 2174 | "log", 2175 | "rand", 2176 | "rustls 0.23.25", 2177 | "rustls-pki-types", 2178 | "sha1", 2179 | "thiserror 2.0.12", 2180 | "utf-8", 2181 | ] 2182 | 2183 | [[package]] 2184 | name = "typeid" 2185 | version = "1.0.3" 2186 | source = "registry+https://github.com/rust-lang/crates.io-index" 2187 | checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" 2188 | 2189 | [[package]] 2190 | name = "typenum" 2191 | version = "1.18.0" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 2194 | 2195 | [[package]] 2196 | name = "unicode-ident" 2197 | version = "1.0.18" 2198 | source = "registry+https://github.com/rust-lang/crates.io-index" 2199 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2200 | 2201 | [[package]] 2202 | name = "unsafe-libyaml" 2203 | version = "0.2.11" 2204 | source = "registry+https://github.com/rust-lang/crates.io-index" 2205 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 2206 | 2207 | [[package]] 2208 | name = "untrusted" 2209 | version = "0.9.0" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2212 | 2213 | [[package]] 2214 | name = "url" 2215 | version = "2.5.4" 2216 | source = "registry+https://github.com/rust-lang/crates.io-index" 2217 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2218 | dependencies = [ 2219 | "form_urlencoded", 2220 | "idna", 2221 | "percent-encoding", 2222 | ] 2223 | 2224 | [[package]] 2225 | name = "utf-8" 2226 | version = "0.7.6" 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" 2228 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2229 | 2230 | [[package]] 2231 | name = "utf16_iter" 2232 | version = "1.0.5" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2235 | 2236 | [[package]] 2237 | name = "utf8_iter" 2238 | version = "1.0.4" 2239 | source = "registry+https://github.com/rust-lang/crates.io-index" 2240 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2241 | 2242 | [[package]] 2243 | name = "utf8parse" 2244 | version = "0.2.2" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2247 | 2248 | [[package]] 2249 | name = "uuid" 2250 | version = "1.16.0" 2251 | source = "registry+https://github.com/rust-lang/crates.io-index" 2252 | checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" 2253 | dependencies = [ 2254 | "getrandom 0.3.2", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "valuable" 2259 | version = "0.1.1" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2262 | 2263 | [[package]] 2264 | name = "value-bag" 2265 | version = "1.11.1" 2266 | source = "registry+https://github.com/rust-lang/crates.io-index" 2267 | checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" 2268 | dependencies = [ 2269 | "value-bag-serde1", 2270 | "value-bag-sval2", 2271 | ] 2272 | 2273 | [[package]] 2274 | name = "value-bag-serde1" 2275 | version = "1.11.1" 2276 | source = "registry+https://github.com/rust-lang/crates.io-index" 2277 | checksum = "35540706617d373b118d550d41f5dfe0b78a0c195dc13c6815e92e2638432306" 2278 | dependencies = [ 2279 | "erased-serde", 2280 | "serde", 2281 | "serde_fmt", 2282 | ] 2283 | 2284 | [[package]] 2285 | name = "value-bag-sval2" 2286 | version = "1.11.1" 2287 | source = "registry+https://github.com/rust-lang/crates.io-index" 2288 | checksum = "6fe7e140a2658cc16f7ee7a86e413e803fc8f9b5127adc8755c19f9fefa63a52" 2289 | dependencies = [ 2290 | "sval", 2291 | "sval_buffer", 2292 | "sval_dynamic", 2293 | "sval_fmt", 2294 | "sval_json", 2295 | "sval_ref", 2296 | "sval_serde", 2297 | ] 2298 | 2299 | [[package]] 2300 | name = "version_check" 2301 | version = "0.9.5" 2302 | source = "registry+https://github.com/rust-lang/crates.io-index" 2303 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2304 | 2305 | [[package]] 2306 | name = "want" 2307 | version = "0.3.1" 2308 | source = "registry+https://github.com/rust-lang/crates.io-index" 2309 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2310 | dependencies = [ 2311 | "try-lock", 2312 | ] 2313 | 2314 | [[package]] 2315 | name = "wasi" 2316 | version = "0.11.0+wasi-snapshot-preview1" 2317 | source = "registry+https://github.com/rust-lang/crates.io-index" 2318 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2319 | 2320 | [[package]] 2321 | name = "wasi" 2322 | version = "0.14.2+wasi-0.2.4" 2323 | source = "registry+https://github.com/rust-lang/crates.io-index" 2324 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2325 | dependencies = [ 2326 | "wit-bindgen-rt", 2327 | ] 2328 | 2329 | [[package]] 2330 | name = "wasm-bindgen" 2331 | version = "0.2.100" 2332 | source = "registry+https://github.com/rust-lang/crates.io-index" 2333 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2334 | dependencies = [ 2335 | "cfg-if", 2336 | "once_cell", 2337 | "rustversion", 2338 | "wasm-bindgen-macro", 2339 | ] 2340 | 2341 | [[package]] 2342 | name = "wasm-bindgen-backend" 2343 | version = "0.2.100" 2344 | source = "registry+https://github.com/rust-lang/crates.io-index" 2345 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2346 | dependencies = [ 2347 | "bumpalo", 2348 | "log", 2349 | "proc-macro2", 2350 | "quote", 2351 | "syn", 2352 | "wasm-bindgen-shared", 2353 | ] 2354 | 2355 | [[package]] 2356 | name = "wasm-bindgen-futures" 2357 | version = "0.4.50" 2358 | source = "registry+https://github.com/rust-lang/crates.io-index" 2359 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2360 | dependencies = [ 2361 | "cfg-if", 2362 | "js-sys", 2363 | "once_cell", 2364 | "wasm-bindgen", 2365 | "web-sys", 2366 | ] 2367 | 2368 | [[package]] 2369 | name = "wasm-bindgen-macro" 2370 | version = "0.2.100" 2371 | source = "registry+https://github.com/rust-lang/crates.io-index" 2372 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2373 | dependencies = [ 2374 | "quote", 2375 | "wasm-bindgen-macro-support", 2376 | ] 2377 | 2378 | [[package]] 2379 | name = "wasm-bindgen-macro-support" 2380 | version = "0.2.100" 2381 | source = "registry+https://github.com/rust-lang/crates.io-index" 2382 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2383 | dependencies = [ 2384 | "proc-macro2", 2385 | "quote", 2386 | "syn", 2387 | "wasm-bindgen-backend", 2388 | "wasm-bindgen-shared", 2389 | ] 2390 | 2391 | [[package]] 2392 | name = "wasm-bindgen-shared" 2393 | version = "0.2.100" 2394 | source = "registry+https://github.com/rust-lang/crates.io-index" 2395 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2396 | dependencies = [ 2397 | "unicode-ident", 2398 | ] 2399 | 2400 | [[package]] 2401 | name = "web-sys" 2402 | version = "0.3.77" 2403 | source = "registry+https://github.com/rust-lang/crates.io-index" 2404 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2405 | dependencies = [ 2406 | "js-sys", 2407 | "wasm-bindgen", 2408 | ] 2409 | 2410 | [[package]] 2411 | name = "webpki-roots" 2412 | version = "0.25.4" 2413 | source = "registry+https://github.com/rust-lang/crates.io-index" 2414 | checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2415 | 2416 | [[package]] 2417 | name = "webpki-roots" 2418 | version = "0.26.8" 2419 | source = "registry+https://github.com/rust-lang/crates.io-index" 2420 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 2421 | dependencies = [ 2422 | "rustls-pki-types", 2423 | ] 2424 | 2425 | [[package]] 2426 | name = "which" 2427 | version = "4.4.2" 2428 | source = "registry+https://github.com/rust-lang/crates.io-index" 2429 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 2430 | dependencies = [ 2431 | "either", 2432 | "home", 2433 | "once_cell", 2434 | "rustix", 2435 | ] 2436 | 2437 | [[package]] 2438 | name = "winapi" 2439 | version = "0.3.9" 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" 2441 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2442 | dependencies = [ 2443 | "winapi-i686-pc-windows-gnu", 2444 | "winapi-x86_64-pc-windows-gnu", 2445 | ] 2446 | 2447 | [[package]] 2448 | name = "winapi-i686-pc-windows-gnu" 2449 | version = "0.4.0" 2450 | source = "registry+https://github.com/rust-lang/crates.io-index" 2451 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2452 | 2453 | [[package]] 2454 | name = "winapi-x86_64-pc-windows-gnu" 2455 | version = "0.4.0" 2456 | source = "registry+https://github.com/rust-lang/crates.io-index" 2457 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2458 | 2459 | [[package]] 2460 | name = "windows-core" 2461 | version = "0.61.0" 2462 | source = "registry+https://github.com/rust-lang/crates.io-index" 2463 | checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" 2464 | dependencies = [ 2465 | "windows-implement", 2466 | "windows-interface", 2467 | "windows-link", 2468 | "windows-result", 2469 | "windows-strings", 2470 | ] 2471 | 2472 | [[package]] 2473 | name = "windows-implement" 2474 | version = "0.60.0" 2475 | source = "registry+https://github.com/rust-lang/crates.io-index" 2476 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 2477 | dependencies = [ 2478 | "proc-macro2", 2479 | "quote", 2480 | "syn", 2481 | ] 2482 | 2483 | [[package]] 2484 | name = "windows-interface" 2485 | version = "0.59.1" 2486 | source = "registry+https://github.com/rust-lang/crates.io-index" 2487 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 2488 | dependencies = [ 2489 | "proc-macro2", 2490 | "quote", 2491 | "syn", 2492 | ] 2493 | 2494 | [[package]] 2495 | name = "windows-link" 2496 | version = "0.1.1" 2497 | source = "registry+https://github.com/rust-lang/crates.io-index" 2498 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 2499 | 2500 | [[package]] 2501 | name = "windows-result" 2502 | version = "0.3.2" 2503 | source = "registry+https://github.com/rust-lang/crates.io-index" 2504 | checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" 2505 | dependencies = [ 2506 | "windows-link", 2507 | ] 2508 | 2509 | [[package]] 2510 | name = "windows-strings" 2511 | version = "0.4.0" 2512 | source = "registry+https://github.com/rust-lang/crates.io-index" 2513 | checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" 2514 | dependencies = [ 2515 | "windows-link", 2516 | ] 2517 | 2518 | [[package]] 2519 | name = "windows-sys" 2520 | version = "0.48.0" 2521 | source = "registry+https://github.com/rust-lang/crates.io-index" 2522 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2523 | dependencies = [ 2524 | "windows-targets 0.48.5", 2525 | ] 2526 | 2527 | [[package]] 2528 | name = "windows-sys" 2529 | version = "0.52.0" 2530 | source = "registry+https://github.com/rust-lang/crates.io-index" 2531 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2532 | dependencies = [ 2533 | "windows-targets 0.52.6", 2534 | ] 2535 | 2536 | [[package]] 2537 | name = "windows-sys" 2538 | version = "0.59.0" 2539 | source = "registry+https://github.com/rust-lang/crates.io-index" 2540 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2541 | dependencies = [ 2542 | "windows-targets 0.52.6", 2543 | ] 2544 | 2545 | [[package]] 2546 | name = "windows-targets" 2547 | version = "0.48.5" 2548 | source = "registry+https://github.com/rust-lang/crates.io-index" 2549 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2550 | dependencies = [ 2551 | "windows_aarch64_gnullvm 0.48.5", 2552 | "windows_aarch64_msvc 0.48.5", 2553 | "windows_i686_gnu 0.48.5", 2554 | "windows_i686_msvc 0.48.5", 2555 | "windows_x86_64_gnu 0.48.5", 2556 | "windows_x86_64_gnullvm 0.48.5", 2557 | "windows_x86_64_msvc 0.48.5", 2558 | ] 2559 | 2560 | [[package]] 2561 | name = "windows-targets" 2562 | version = "0.52.6" 2563 | source = "registry+https://github.com/rust-lang/crates.io-index" 2564 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2565 | dependencies = [ 2566 | "windows_aarch64_gnullvm 0.52.6", 2567 | "windows_aarch64_msvc 0.52.6", 2568 | "windows_i686_gnu 0.52.6", 2569 | "windows_i686_gnullvm", 2570 | "windows_i686_msvc 0.52.6", 2571 | "windows_x86_64_gnu 0.52.6", 2572 | "windows_x86_64_gnullvm 0.52.6", 2573 | "windows_x86_64_msvc 0.52.6", 2574 | ] 2575 | 2576 | [[package]] 2577 | name = "windows_aarch64_gnullvm" 2578 | version = "0.48.5" 2579 | source = "registry+https://github.com/rust-lang/crates.io-index" 2580 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2581 | 2582 | [[package]] 2583 | name = "windows_aarch64_gnullvm" 2584 | version = "0.52.6" 2585 | source = "registry+https://github.com/rust-lang/crates.io-index" 2586 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2587 | 2588 | [[package]] 2589 | name = "windows_aarch64_msvc" 2590 | version = "0.48.5" 2591 | source = "registry+https://github.com/rust-lang/crates.io-index" 2592 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2593 | 2594 | [[package]] 2595 | name = "windows_aarch64_msvc" 2596 | version = "0.52.6" 2597 | source = "registry+https://github.com/rust-lang/crates.io-index" 2598 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2599 | 2600 | [[package]] 2601 | name = "windows_i686_gnu" 2602 | version = "0.48.5" 2603 | source = "registry+https://github.com/rust-lang/crates.io-index" 2604 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2605 | 2606 | [[package]] 2607 | name = "windows_i686_gnu" 2608 | version = "0.52.6" 2609 | source = "registry+https://github.com/rust-lang/crates.io-index" 2610 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2611 | 2612 | [[package]] 2613 | name = "windows_i686_gnullvm" 2614 | version = "0.52.6" 2615 | source = "registry+https://github.com/rust-lang/crates.io-index" 2616 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2617 | 2618 | [[package]] 2619 | name = "windows_i686_msvc" 2620 | version = "0.48.5" 2621 | source = "registry+https://github.com/rust-lang/crates.io-index" 2622 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2623 | 2624 | [[package]] 2625 | name = "windows_i686_msvc" 2626 | version = "0.52.6" 2627 | source = "registry+https://github.com/rust-lang/crates.io-index" 2628 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2629 | 2630 | [[package]] 2631 | name = "windows_x86_64_gnu" 2632 | version = "0.48.5" 2633 | source = "registry+https://github.com/rust-lang/crates.io-index" 2634 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2635 | 2636 | [[package]] 2637 | name = "windows_x86_64_gnu" 2638 | version = "0.52.6" 2639 | source = "registry+https://github.com/rust-lang/crates.io-index" 2640 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2641 | 2642 | [[package]] 2643 | name = "windows_x86_64_gnullvm" 2644 | version = "0.48.5" 2645 | source = "registry+https://github.com/rust-lang/crates.io-index" 2646 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2647 | 2648 | [[package]] 2649 | name = "windows_x86_64_gnullvm" 2650 | version = "0.52.6" 2651 | source = "registry+https://github.com/rust-lang/crates.io-index" 2652 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2653 | 2654 | [[package]] 2655 | name = "windows_x86_64_msvc" 2656 | version = "0.48.5" 2657 | source = "registry+https://github.com/rust-lang/crates.io-index" 2658 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2659 | 2660 | [[package]] 2661 | name = "windows_x86_64_msvc" 2662 | version = "0.52.6" 2663 | source = "registry+https://github.com/rust-lang/crates.io-index" 2664 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2665 | 2666 | [[package]] 2667 | name = "winreg" 2668 | version = "0.50.0" 2669 | source = "registry+https://github.com/rust-lang/crates.io-index" 2670 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2671 | dependencies = [ 2672 | "cfg-if", 2673 | "windows-sys 0.48.0", 2674 | ] 2675 | 2676 | [[package]] 2677 | name = "wit-bindgen-rt" 2678 | version = "0.39.0" 2679 | source = "registry+https://github.com/rust-lang/crates.io-index" 2680 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2681 | dependencies = [ 2682 | "bitflags 2.9.0", 2683 | ] 2684 | 2685 | [[package]] 2686 | name = "write16" 2687 | version = "1.0.0" 2688 | source = "registry+https://github.com/rust-lang/crates.io-index" 2689 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2690 | 2691 | [[package]] 2692 | name = "writeable" 2693 | version = "0.5.5" 2694 | source = "registry+https://github.com/rust-lang/crates.io-index" 2695 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2696 | 2697 | [[package]] 2698 | name = "yoke" 2699 | version = "0.7.5" 2700 | source = "registry+https://github.com/rust-lang/crates.io-index" 2701 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2702 | dependencies = [ 2703 | "serde", 2704 | "stable_deref_trait", 2705 | "yoke-derive", 2706 | "zerofrom", 2707 | ] 2708 | 2709 | [[package]] 2710 | name = "yoke-derive" 2711 | version = "0.7.5" 2712 | source = "registry+https://github.com/rust-lang/crates.io-index" 2713 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2714 | dependencies = [ 2715 | "proc-macro2", 2716 | "quote", 2717 | "syn", 2718 | "synstructure", 2719 | ] 2720 | 2721 | [[package]] 2722 | name = "zerocopy" 2723 | version = "0.8.24" 2724 | source = "registry+https://github.com/rust-lang/crates.io-index" 2725 | checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" 2726 | dependencies = [ 2727 | "zerocopy-derive", 2728 | ] 2729 | 2730 | [[package]] 2731 | name = "zerocopy-derive" 2732 | version = "0.8.24" 2733 | source = "registry+https://github.com/rust-lang/crates.io-index" 2734 | checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" 2735 | dependencies = [ 2736 | "proc-macro2", 2737 | "quote", 2738 | "syn", 2739 | ] 2740 | 2741 | [[package]] 2742 | name = "zerofrom" 2743 | version = "0.1.6" 2744 | source = "registry+https://github.com/rust-lang/crates.io-index" 2745 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2746 | dependencies = [ 2747 | "zerofrom-derive", 2748 | ] 2749 | 2750 | [[package]] 2751 | name = "zerofrom-derive" 2752 | version = "0.1.6" 2753 | source = "registry+https://github.com/rust-lang/crates.io-index" 2754 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2755 | dependencies = [ 2756 | "proc-macro2", 2757 | "quote", 2758 | "syn", 2759 | "synstructure", 2760 | ] 2761 | 2762 | [[package]] 2763 | name = "zeroize" 2764 | version = "1.8.1" 2765 | source = "registry+https://github.com/rust-lang/crates.io-index" 2766 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2767 | 2768 | [[package]] 2769 | name = "zerovec" 2770 | version = "0.10.4" 2771 | source = "registry+https://github.com/rust-lang/crates.io-index" 2772 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2773 | dependencies = [ 2774 | "yoke", 2775 | "zerofrom", 2776 | "zerovec-derive", 2777 | ] 2778 | 2779 | [[package]] 2780 | name = "zerovec-derive" 2781 | version = "0.10.3" 2782 | source = "registry+https://github.com/rust-lang/crates.io-index" 2783 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2784 | dependencies = [ 2785 | "proc-macro2", 2786 | "quote", 2787 | "syn", 2788 | ] 2789 | --------------------------------------------------------------------------------