├── .gitignore ├── src ├── cfst_rpc.rs ├── args.rs ├── ping.rs ├── speed.rs ├── main.rs ├── server_comm.rs └── install_upgrade.rs ├── .idea ├── .gitignore ├── vcs.xml ├── modules.xml └── CloudflareSpeedtest-Slave.iml ├── Cross.toml ├── LICENSE ├── Dockerfile ├── .github └── workflows │ ├── windows.yaml │ └── linux.yaml ├── Cargo.toml ├── tls_cert ├── server.pem └── server.key ├── proto ├── cfst_rpc.proto └── cfst_rpc.rs ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /binary 3 | /build.sh 4 | -------------------------------------------------------------------------------- /src/cfst_rpc.rs: -------------------------------------------------------------------------------- 1 | tonic::include_proto!("cfst_rpc"); 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-musl] 2 | pre-build = [ 3 | "apt-get update", 4 | "apt-get install --assume-yes musl musl-dev musl-tools protobuf-compiler" 5 | ] 6 | 7 | [target.aarch64-unknown-linux-musl] 8 | pre-build = [ 9 | "apt-get update", 10 | "apt-get install --assume-yes musl musl-dev musl-tools protobuf-compiler" 11 | ] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2004 Sam Hocevar 2 | 3 | Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. 4 | 5 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 6 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 7 | 8 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | ARG TARGETARCH 4 | 5 | ENV SERVER=backend.cloudflare.su:2333 6 | ENV TOKEN=cfst1234 7 | ENV MAX_MBPS=500 8 | 9 | ## 你必须要先将二进制文件保存在 ./binary/linux/{amd|arm}64 目录下 10 | COPY ./binary/$TARGETARCH /CloudflareSpeedtest-Slave 11 | 12 | RUN chmod +x /CloudflareSpeedtest-Slave 13 | 14 | CMD /CloudflareSpeedtest-Slave -s "$SERVER" -t "$TOKEN" -m "${MAX_MBPS}" --debug --disable-auto-upgrade 15 | -------------------------------------------------------------------------------- /.idea/CloudflareSpeedtest-Slave.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/windows.yaml: -------------------------------------------------------------------------------- 1 | name: Windows Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | jobs: 9 | release: 10 | name: Release - Windows 11 | runs-on: windows-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | - name: Install Protoc 16 | uses: arduino/setup-protoc@v3 17 | - name: Set up nasm 18 | uses: ilammy/setup-nasm@v1 19 | - name: Set up Rust 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | profile: minimal 24 | override: true 25 | - name: Build 26 | run: cargo build --release 27 | - name: Upload binary 28 | uses: actions/upload-artifact@v3 29 | with: 30 | name: x86_64-pc-windows-msvc 31 | path: target/release/CloudflareSpeedtest-Slave.exe 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "CloudflareSpeedtest-Slave" 3 | version = "0.0.6" 4 | authors = ["GenshinMinecrat "] 5 | edition = "2021" 6 | description = "A tool, written in Rust, for testing the speed of Cloudflare IPs." 7 | readme = "README.md" 8 | repository = "https://github.com/GenshinMinecraft/CloudflareSpeedtest-Slave" 9 | license = "WTFPL" 10 | keywords = ["Cloudflare", "Speedtest"] 11 | 12 | 13 | [dependencies] 14 | clap = { version = "4.5.9", features = ["derive"] } 15 | ipnetwork = "0.20.0" 16 | log = "0.4.22" 17 | prost = "0.13.1" 18 | reqwest = { version = "0.12.5", features = ["json", "blocking", "rustls-tls"], default-features = false } 19 | rustls = "0.23.11" 20 | simple_logger = "5.0.0" 21 | tokio = { version = "1.38.0", features = ["full"] } 22 | tokio-stream = "0.1.15" 23 | tonic = "0.12.0" 24 | tonic-build = "0.12.0" 25 | url = "2.5.2" 26 | uuid = { version = "1.10.0", features = [ "v4" ] } 27 | webpki-roots = "0.26.3" 28 | rand = "0.9.0-alpha.1" 29 | futures = "0.3.30" 30 | tokio-rustls = "0.26.0" 31 | 32 | [build-dependencies] 33 | tonic-build = "0.12.0" 34 | 35 | [profile.release] 36 | codegen-units = 1 37 | lto = "fat" 38 | opt-level = "s" 39 | panic = "abort" 40 | -------------------------------------------------------------------------------- /tls_cert/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEBDCCAuygAwIBAgIEZW3j1TANBgkqhkiG9w0BAQsFADCBmjELMAkGA1UEBhMC 3 | R0IxGjAYBgNVBAMMEUhpZ2ggUGluZyBOZXR3b3JrMQ8wDQYDVQQIDAZMb25kb24x 4 | DzANBgNVBAcMBkxvbmRvbjEXMBUGA1UECgwOSElHSCBQSU5HIExURC4xFzAVBgNV 5 | BAsMDkhJR0ggUElORyBMVEQuMRswGQYJKoZIhvcNAQkBFgxnbUBoaWdocC5pbmcw 6 | HhcNMjQwNzAyMTIzMTIzWhcNMzQwNjMwMTIzMTIzWjCBmjELMAkGA1UEBhMCR0Ix 7 | GjAYBgNVBAMMEUhpZ2ggUGluZyBOZXR3b3JrMQ8wDQYDVQQIDAZMb25kb24xDzAN 8 | BgNVBAcMBkxvbmRvbjEXMBUGA1UECgwOSElHSCBQSU5HIExURC4xFzAVBgNVBAsM 9 | DkhJR0ggUElORyBMVEQuMRswGQYJKoZIhvcNAQkBFgxnbUBoaWdocC5pbmcwggEi 10 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAm6Wsu6DitzLj52DPWrjGRcYI 11 | zbpJZ3fRatnAgyCyfBSFEWP3s4nn0EXf8LH7HAnSOS2XD81DJ+I92PMmLAm9+Ynn 12 | DyelOHuudGb/ndB64Be3OIEe3+hluYPt7V7Dl7azTUJh7vYT9KdLfaOO4vZoLHPl 13 | Wmx7b9cSIyQKBu7Vz07Vo/Z4aKUWSLYFGtcoa/0ao6rE4R5c/a0hS2W0GeSvmYWe 14 | ZJRHSHoIeDsv9ob8xlkPa2k8S6eiSwh3Vb0cGeE4LPZ1UDF3XeymEdgPDsLtMIzY 15 | x3dUHQIPiBVWtFON8QsME7Eq3OPtchlBt+GZ6RVKgTZsucqV5FB0Mx1twzNFAgMB 16 | AAGjUDBOMB0GA1UdDgQWBBT3e5ICWXokWniWHu3saUx5cv5vhDAfBgNVHSMEGDAW 17 | gBT3e5ICWXokWniWHu3saUx5cv5vhDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB 18 | CwUAA4IBAQBTjQbLr9uv7N8kE7UEV35uhuWB8379Fr4j/ACF+Hye02w3j1DQ+Fem 19 | V4PvxBixaK0AFKam/TDfQrfYrGpAW5hK5fcohTp9OBkHimmCiS/JE2vpQFwXYGk7 20 | Y0Cqdw9/jW7VasMLJ4qFdxO464918r+7H8bQK8OsmAq0C/24R6TG9T21AxwbxUic 21 | 1sMBa38npvHJxbQ84kwCoN40xrZyxZ9Yy0VDIpF7cG+QYqKVDy/sgv9T3VbAAh8f 22 | tkBGy4frBisZNAvYFwHzB7d09MuIHBF+7Z9rp2ZLnyWh+UA7jkRWzmlqt9Vg1HcY 23 | 2g89uAA0j7T+owgPeWmCV6713WqWYUED 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /.github/workflows/linux.yaml: -------------------------------------------------------------------------------- 1 | # 定义一个名为Rust Release的GitHub Actions工作流,用于发布Rust程序 2 | name: Linux Build 3 | 4 | # 工作流在push到main分支时触发 5 | on: 6 | push: 7 | branches: 8 | - "main" 9 | 10 | jobs: 11 | release: 12 | name: Release - ${{ matrix.platform.release_for }} 13 | strategy: 14 | matrix: 15 | platform: 16 | - release_for: linux_x86_64 17 | os: ubuntu-latest 18 | target: x86_64-unknown-linux-musl 19 | bin: CloudflareSpeedtest-Slave 20 | name: CloudflareSpeedtest-Slave-linux-x86_64.tar.gz 21 | command: build 22 | - release_for: linux_aarch64 23 | os: ubuntu-latest 24 | target: aarch64-unknown-linux-musl 25 | bin: CloudflareSpeedtest-Slave 26 | name: CloudflareSpeedtest-Slave-linux-aarch64.tar.gz 27 | command: build 28 | runs-on: ${{ matrix.platform.os }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | - name: Install Lib 33 | run: sudo apt update && sudo apt install -y protobuf-compiler musl-tools musl-dev musl 34 | - name: Build binary 35 | uses: houseabsolute/actions-rust-cross@v0 36 | with: 37 | command: ${{ matrix.platform.command }} 38 | target: ${{ matrix.platform.target }} 39 | args: "--locked --release" 40 | strip: true 41 | - name: Upload binary 42 | uses: actions/upload-artifact@v3 43 | with: 44 | name: ${{ matrix.platform.target }} 45 | path: target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} 46 | -------------------------------------------------------------------------------- /proto/cfst_rpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cfst_rpc; 4 | 5 | service CloudflareSpeedtest { 6 | rpc Bootstrap(BootstrapRequest) returns (BootstrapResponse); 7 | rpc Speedtest(SpeedtestRequest) returns (stream SpeedtestResponse); 8 | rpc SpeedtestResult(SpeedtestResultRequest) returns (SpeedtestResultResponse); 9 | rpc Upgrade(UpgradeRequest) returns (UpgradeResponse); 10 | rpc Alive(Ping) returns (Pong); 11 | } 12 | 13 | message BootstrapRequest { 14 | int32 maximum_mbps = 1; 15 | string client_version = 2; 16 | string bootstrap_token = 3; 17 | string node_id = 4; 18 | } 19 | 20 | message BootstrapResponse { 21 | bool success = 1; 22 | bool should_upgrade = 2; 23 | string message = 3; 24 | string session_token = 4; // token to use for communicating with the control node thereafter until the exit of the process 25 | } 26 | 27 | message UpgradeRequest {} 28 | 29 | message UpgradeResponse { 30 | bool success = 1; 31 | string message = 2; 32 | string upgrade_url = 3; 33 | } 34 | 35 | message IPResult { 36 | string ip_address = 1; 37 | int32 latency = 2; 38 | int32 speed = 3; 39 | } 40 | 41 | message SpeedtestRequest { 42 | string session_token = 1; 43 | string node_id = 2; 44 | } 45 | 46 | message SpeedtestResponse { 47 | repeated string ip_ranges = 1; 48 | int32 minimum_mbps = 2; 49 | int32 maximum_ping = 3; 50 | string speed_url = 4; 51 | } 52 | 53 | message SpeedtestResultRequest { 54 | repeated IPResult ip_results = 1; 55 | string session_token = 2; 56 | string node_id = 3; 57 | } 58 | 59 | message SpeedtestResultResponse { 60 | bool success = 1; 61 | string message = 2; 62 | } 63 | 64 | message Ping{} 65 | message Pong{} -------------------------------------------------------------------------------- /tls_cert/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDAm6Wsu6DitzLj 3 | 52DPWrjGRcYIzbpJZ3fRatnAgyCyfBSFEWP3s4nn0EXf8LH7HAnSOS2XD81DJ+I9 4 | 2PMmLAm9+YnnDyelOHuudGb/ndB64Be3OIEe3+hluYPt7V7Dl7azTUJh7vYT9KdL 5 | faOO4vZoLHPlWmx7b9cSIyQKBu7Vz07Vo/Z4aKUWSLYFGtcoa/0ao6rE4R5c/a0h 6 | S2W0GeSvmYWeZJRHSHoIeDsv9ob8xlkPa2k8S6eiSwh3Vb0cGeE4LPZ1UDF3Xeym 7 | EdgPDsLtMIzYx3dUHQIPiBVWtFON8QsME7Eq3OPtchlBt+GZ6RVKgTZsucqV5FB0 8 | Mx1twzNFAgMBAAECggEAI1Noy4mONIbNOqeRRfaptRPPAoZZCGWFwE8MqRZjxNil 9 | GtLZtvCi9nVom24V9qxp0LjT98TaLNr/z8AeuH2TO9isxkgtSUxWwuRuj6tuNVss 10 | flpKtFL02NNxsw26N713sOMWrceaMobIuDvO58IQ14Jvrz0qcGlO1PDFB2HGJEiT 11 | sbS9H2/cm9IAoV/Jh3t5VBd8+oz37psOihO2ecT8z9WKKPhJeps7STwoSXt23x+7 12 | AHBRabvr4HL9o+MqNKEupTwIxRY/PRlX86evBvWrDglVMXaA0nOFfcSOz+WUjoPG 13 | lk2u3ENp23LyNA9UN0ug6UzUeMKfRUMRKPMYaGNaAQKBgQDyxsibqhGnF8Z2TpP6 14 | nZneSQwkqfV7OectFs1Qywya1o41dLY/zcqrMn9bVff2oUPEZIGvMYiyN7MR6cMD 15 | X7IkEFv7h+DdfJO9wJjBQTbmhlcvXWAOP3C2SNw59or4na24FNaTtxFsIz3SqIMk 16 | bu4h3Z2dk4+xlaf9KO34e1SxIQKBgQDLGVN/3EpMNOP0KVsYs/JkANsOyzmeoYiM 17 | B2CtzBkpCBrcVZZsm3e4FG3lGTYvsgJSjjfOyPcliqZQ4TbCbgrPgWFsCad2U2Ba 18 | aGt8jSsMy/aEWt4IieOWHuLLAluF9Ug94RVOT4pAWsG+UD/kpApduXBhcKdp2Zgc 19 | LMQivjbppQKBgGGYvAioA9SyYBwrVp3HQZX0s6cBlCfnjSG5Kuyx0+1jF2Qx+RoJ 20 | NtI/yKcFFlvVVJLc/K1bMmLCtYAcA0OV8t1AnlmttB4V+KatiDsYZmOh2ea2mOjh 21 | ZARDohTDIfb0HGQGLITRcXWRbUcEa0P4PE7s8nHoYjm3ugKxs4jSu6dBAoGAAPRZ 22 | hBxQ7RLCj38yQmd2GCo43VTvLGOt9JqERczTwXGcTrTIRDJm2aKe8ZiwvIClqiWo 23 | 9XvUTYTdSzwDud4yhs8g2hUhiFjT9xjOiINRVHoQ6oZSzM95FleG0VVtgK+qa0AH 24 | jZqqF6tVhcNyyWxL8CzS7mJNJx4yrM85DMDAGVECgYAydI7iyW8WGKfr0m5lP+IY 25 | OEPXDmmwcBm3IzuI/xIsbEsJf8ZffsOR1Krvf17PEYkAMezNVBg6IVkRWZ/5MWC3 26 | rVrYsUqkfqcqSKzizXaPCGRG1mJ0pdjp+OZIf33bj+TXg0H4u9XTDoM73SNobErB 27 | t2oXP/DQhz4pG6vDyxZZ+Q== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Cloudflare IP Speedtest Backend 4 | #[derive(Parser, Debug, Clone)] 5 | #[command(version, about, long_about = None)] 6 | pub struct Args { 7 | // 主端地址 8 | /// Frontend Server Address 9 | #[arg(short, long, default_value_t = return_default_server())] 10 | pub server: String, 11 | 12 | // Bootstrap Token 设置 13 | /// Token Setting 14 | #[arg(short, long, default_value_t = return_default_bootstrap_token())] 15 | pub token: String, 16 | 17 | // 最大带宽 18 | /// Bandwidth (in Mbps) 19 | #[arg(short, long, default_value_t = 114514)] 20 | pub max_mbps: i32, 21 | 22 | // Debug Log 设置 23 | /// Enable Debug Log 24 | #[arg(long, default_value_t = false)] 25 | pub debug: bool, 26 | 27 | // 开始 Install 28 | /// Install For Systemd 29 | #[arg(long, default_value_t = false)] 30 | pub install: bool, 31 | 32 | // 关闭自动更新 33 | /// Disable Auto Upgrade Mode 34 | #[arg(long, default_value_t = false)] 35 | pub disable_auto_upgrade: bool, 36 | } 37 | 38 | /** 39 | * 返回默认服务器的地址。 40 | * 41 | * 该函数生成并返回一个字符串, 包含了默认服务器的IP地址和端口号。 42 | * 这是一个硬编码的值, 用于在没有特定服务器配置的情况下提供一个默认的选择。 43 | * 44 | * @return String 返回一个字符串, 格式为"IP地址:端口号"。 45 | */ 46 | fn return_default_server() -> String { 47 | return "backend.cloudflare.su:2333".to_string(); 48 | } 49 | 50 | /** 51 | * 返回默认的启动令牌字符串。 52 | * 53 | * 此函数生成一个固定的字符串作为默认的启动令牌。这个令牌用于应用程序启动时的特定验证或配置过程。 54 | * 选择“cfst1234”作为默认值是因为它是一个预先定义的、不会引起混淆的值。 55 | * 56 | * @return 字符串类型的默认启动令牌。 57 | */ 58 | fn return_default_bootstrap_token() -> String { 59 | return "cfst1234".to_string(); 60 | } 61 | 62 | /** 63 | * 初始化程序的参数对象。 64 | * 65 | * 该函数通过解析命令行参数, 创建并返回一个Args对象。 66 | * Args对象包含了程序运行时的所有配置参数, 这些参数可以通过命令行进行定制。 67 | * 68 | * 返回值: 69 | * Args - 一个包含了程序运行参数的数据结构。 70 | */ 71 | pub fn init_args() -> Args { 72 | // 使用Args::parse方法从命令行参数中构建Args对象。 73 | let args: Args = Args::parse(); 74 | // 返回构建好的Args对象。 75 | return args; 76 | } 77 | -------------------------------------------------------------------------------- /src/ping.rs: -------------------------------------------------------------------------------- 1 | use futures::{stream::iter, StreamExt}; 2 | use ipnetwork::IpNetwork; 3 | use log::debug; 4 | use std::{collections::HashMap, error::Error, time::Duration}; 5 | use tokio::io::AsyncWriteExt; 6 | use tokio::{ 7 | net::TcpStream, 8 | sync::Mutex, 9 | time::{timeout, Instant}, 10 | }; 11 | 12 | async fn ping_single_ip(ip: String, timeout_ms: i32) -> i32 { 13 | let addr = format!("{}:80", ip); 14 | let time_out = Duration::from_millis(timeout_ms as u64); 15 | let start = Instant::now(); 16 | match timeout(time_out, TcpStream::connect(&addr)).await { 17 | Ok(tmp) => match tmp { 18 | Ok(mut tcpstream) => { 19 | let duration = start.elapsed().as_millis() as i32; 20 | tcpstream.shutdown().await.unwrap(); 21 | drop(tcpstream); 22 | if duration <= 10 { 23 | -1 24 | } else { 25 | duration 26 | } 27 | } 28 | Err(_) => -1, 29 | }, 30 | Err(_) => -1, 31 | } 32 | } 33 | 34 | pub async fn ping_ips(ips: Vec, maximum_ping: i32) -> HashMap { 35 | let ip_and_ping_map = std::sync::Arc::new(Mutex::new(HashMap::new())); 36 | iter(ips) 37 | .for_each_concurrent(Some(100), |ip| { 38 | let clone_map = ip_and_ping_map.clone(); 39 | async move { 40 | let duration = ping_single_ip(ip.clone(), maximum_ping).await; 41 | if duration != -1 { 42 | debug!("IP {} Ping {}ms", ip, duration); 43 | let mut map_lock = clone_map.lock().await; 44 | map_lock.insert(ip, duration as u128); 45 | } else { 46 | debug!("IP {} 不可达", ip); 47 | let mut map_lock = clone_map.lock().await; 48 | map_lock.insert(ip, u128::MAX); 49 | } 50 | } 51 | }) 52 | .await; 53 | let mut inner_map = ip_and_ping_map.lock().await; 54 | std::mem::take(&mut *inner_map) 55 | } 56 | 57 | pub async fn ip_cidr_to_ips(ip_cidr: Vec) -> Result, Box> { 58 | let ip_cidr_string: Vec = ip_cidr.into_iter().map(|fs| fs.to_string()).collect(); 59 | 60 | let mut ip_addresses: Vec = Vec::new(); 61 | 62 | for ips in ip_cidr_string { 63 | let network = ips.parse::()?; 64 | for single_ip in network.iter() { 65 | ip_addresses.push(single_ip.to_string()); 66 | } 67 | } 68 | 69 | Ok(ip_addresses) 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudflareSpeedtest-Slave 2 | 3 | ![LICENSE](https://img.shields.io/github/license/GenshinMinecraft/CloudflareSpeedtest-Slave.svg) 4 | ![GitHub top language](https://img.shields.io/github/languages/top/GenshinMinecraft/CloudflareSpeedtest-Slave) 5 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/GenshinMinecraft/CloudflareSpeedtest-Slave) 6 | ![GitHub repo size](https://img.shields.io/github/repo-size/GenshinMinecraft/CloudflareSpeedtest-Slave) 7 | 8 | 一个轻量级、高性能的 Cloudflare IP Speedtest 后端, 采用 Rust 编写 9 | 10 | ## 简介 11 | 12 | 本后端适用于 Moohr 开发的 Cloudflare Speedtest 主端, 用于分布式 Cloudflare 测速 13 | 14 | 为 Moohr 开发的 Cloudflare Speedtest 主端**官方钦定**后端, 如果有需要自行编写后端的需求可参考该项目 15 | 16 | ## 使用 17 | 18 | ``` 19 | A tool, written in Rust, for testing the speed of Cloudflare IPs. 20 | 21 | Usage: CloudflareSpeedtest-Slave [OPTIONS] 22 | 23 | Options: 24 | -s, --server Frontend Server Address [default: 47.238.130.86:2333] 25 | -t, --token Token Setting [default: cfst1234] 26 | -m, --max-mbps Bandwidth (in Mbps) [default: 500] 27 | --debug Enable Debug Log 28 | --install Install For Systemd 29 | --disable-auto-upgrade Disable Auto Upgrade ModeD 30 | -h, --help Print help 31 | -V, --version Print version 32 | ``` 33 | 34 | - `-s`/`--server`: 指定主端服务器, 默认为该项目官方服务器, 请自行更改 35 | - `-t`/`--token`: 连接主端时的鉴权 Token, 请自行更改 36 | - `-m`/`--max-mbps`: 报告给主端的最大带宽, 单位 Mbps 37 | - `--debug`: 开启 Debug Log 38 | - `--install`: 使用 Systemd 安装 CloudflareSpeedtest-Slave, 仅限于使用 Systemd 的 Linux 39 | - `--disable-auto-upgrade`: 禁用自动升级, 默认为开启 40 | - `-h`: 显示此帮助 41 | - `-V`/`--version`: 显示版本 42 | 43 | ## Docker 使用 44 | 45 | 首先, 请安装 Docker: 46 | 47 | ```bash 48 | curl -fsSL https://test.docker.com -o test-docker.sh 49 | sudo sh test-docker.sh 50 | 51 | # 如果您在中国大陆, 可能需要 Docker 镜像: 52 | curl -fsSL https://gdk.rtc.ovh | bash -s docker --mirror Aliyun 53 | ``` 54 | 55 | 随后运行 Docker: 56 | 57 | ```bash 58 | docker run -d --restart=always --name CloudflareSpeedtest-Slave \ 59 | -e TOKEN=cfst1234 \ 60 | -e MAX_MBPS=500 \ 61 | -e SERVER=backend.cloudflare.su:2333 \ 62 | dp.rtc.ovh/genshinminecraft/cloudflarespeedtest-slave:v0.0.6 63 | ``` 64 | 65 | 目前, 我们只提供了 `arm64` / `amd64` 架构的镜像, 如果需要其他架构的镜像, 请自行编译主程序后编写 Dockerfile 66 | 67 | ## 贡献 68 | 69 | 我们欢迎 Issue 和 Pull Request, 也可以前往我们的[内测群组](https://t.me/+Gbqf_XAhVIphZmY1)反馈 70 | 71 | ## 编译 72 | 73 | ### Cargo 74 | 75 | ```bash 76 | git clone https://github.com/GenshinMinecraft/CloudflareSpeedtest-Slave.git 77 | cd CloudflareSpeedtest-Slave 78 | cargo build --release --target x86_64-unknown-linux-musl # Or aarch64-unknown-linux-musl 79 | ./target/x86_64-unknown-linux-musl/release/CloudflareSpeedtest-Slave # Or aarch64-unknown-linux-musl 80 | ``` 81 | 82 | 请注意: 当前我们尚未测试除了 `linux-x86_64` 与 `linux-arm64` 还有 `windows-x86_64` 的其他平台, 当您有其他系统 / 架构的需求, 请自行编译 83 | 84 | ### Docker 85 | 86 | 请事先将已经编译好的 `arm64` / `amd64` 二进制文件放入本项目根目录下的 `binary/` 文件夹 (没有请自行创建) 87 | 88 | 需要: `binary/arm64` 与 `binary/amd64` 89 | 90 | ```bash 91 | docker buildx build --platform linux/amd64,linux/arm64 . 92 | ``` 93 | 94 | ## 鸣谢 95 | 96 | 感谢所有开源工作者! 97 | 98 | - Cloudflare: 赞美大爹! 99 | - 通义灵码: 为本项目提供了详细的注释编写, 所以该项目中几乎所有的注释都是它写的 -------------------------------------------------------------------------------- /src/speed.rs: -------------------------------------------------------------------------------- 1 | use log::{error, info}; 2 | use std::net::ToSocketAddrs; 3 | use std::process::exit; 4 | use std::sync::Arc; 5 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 6 | use tokio::net::TcpStream; 7 | use tokio::time::Instant; 8 | use tokio_rustls::{rustls, TlsConnector}; 9 | use url::Url; 10 | 11 | /** 12 | * 测量给定IP地址和URL的下载速度。 13 | * 14 | * @param speedtest_url 测速URL, 用于发起下载请求。 15 | * @param ip 要测试速度的IP地址。 16 | * @param speed_time 测速时间(秒), 用于限制下载时间。 17 | * @return 返回下载速度(Mbps)。 18 | */ 19 | pub async fn speed_one_ip(speedtest_url: String, ip: String, speed_time: u32) -> f64 { 20 | let url = match Url::parse(speedtest_url.as_str()) { 21 | Ok(parsed_url) => parsed_url, 22 | Err(e) => { 23 | error!("无法正确解析 Speedtest URL: {}", e); 24 | return -1.0; 25 | } 26 | }; 27 | 28 | let domain = match &url.domain() { 29 | Some(tmp) => match rustls::pki_types::ServerName::try_from(tmp.to_string()) { 30 | Ok(tmp) => tmp, 31 | Err(e) => { 32 | error!("无法获取 Speedtest URL 中的域名: {}", e); 33 | return -1.0; 34 | } 35 | }, 36 | None => { 37 | error!("无法获取 Speedtest URL 中的域名"); 38 | return -1.0; 39 | } 40 | }; 41 | 42 | let port = url.port().unwrap_or(443); 43 | 44 | let addr = match (ip.as_str(), port).to_socket_addrs() { 45 | Ok(mut iter) => match iter.next() { 46 | Some(addr) => addr, 47 | None => { 48 | error!("无法正确解析 Speedtest URL"); 49 | return -1.0; 50 | } 51 | }, 52 | Err(e) => { 53 | error!("无法正确解析 Speedtest URL: {}", e); 54 | return -1.0; 55 | } 56 | }; 57 | 58 | let path = url.path(); 59 | 60 | let request = format!( 61 | "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", 62 | path, 63 | domain.to_str() 64 | ); 65 | 66 | let mut root_cert_store = rustls::RootCertStore::empty(); 67 | root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); 68 | 69 | let config = rustls::ClientConfig::builder() 70 | .with_root_certificates(root_cert_store) 71 | .with_no_client_auth(); 72 | 73 | let connector = TlsConnector::from(Arc::new(config)); 74 | 75 | let stream = match TcpStream::connect(&addr).await { 76 | Ok(tmp) => tmp, 77 | Err(e) => { 78 | error!("无法创立 Tcp 连接: {}", e); 79 | return -1.0; 80 | } 81 | }; 82 | 83 | let mut stream = match connector.connect(domain, stream).await { 84 | Ok(tmp) => tmp, 85 | Err(e) => { 86 | error!("无法创立 Tls 连接: {}", e); 87 | return -1.0; 88 | } 89 | }; 90 | 91 | match stream.write_all(request.as_bytes()).await { 92 | Ok(_) => {} 93 | Err(e) => { 94 | error!("无法写入请求: {}", e) 95 | } 96 | } 97 | 98 | let start_time = Instant::now(); 99 | 100 | let mut buffer = [0; 1024]; 101 | 102 | let mut data = 0; 103 | 104 | loop { 105 | match stream.read(&mut buffer).await { 106 | // 读取结束, 退出循环。 107 | // 没有则退出 108 | Ok(0) => break, 109 | // 成功读取数据, 累加到总下载大小。 110 | // 有则把接收到的放到计数器里 111 | Ok(n) => { 112 | data += n; 113 | if start_time.elapsed().as_secs_f64() >= speed_time as f64 { 114 | break; 115 | } 116 | } 117 | Err(e) => { 118 | eprintln!("下载文件出现错误: {}", e); 119 | exit(1); 120 | } 121 | } 122 | } 123 | 124 | stream.shutdown().await.unwrap(); 125 | drop(stream); 126 | 127 | // 计算下载速度(Mbps)。 128 | // 已下载 129 | let bytes_downloaded = data; 130 | 131 | // 总用时 132 | let time_taken: f64 = start_time.elapsed().as_secs_f64(); 133 | 134 | // 下载总 Bits 135 | let bits_downloaded: f64 = bytes_downloaded as f64 * 8.0; 136 | 137 | // bps 计算 138 | let download_speed_bps: f64 = bits_downloaded / time_taken; 139 | 140 | // bps -> kbps 141 | let download_speed_kbps: f64 = download_speed_bps / 1000.0; 142 | 143 | // kbps -> mbps 144 | let download_speed_mbps: f64 = download_speed_kbps / 1000.0; 145 | 146 | // 记录测速结果。 147 | info!("IP: {}, 速度: {}mbps", addr.ip(), download_speed_mbps); 148 | 149 | download_speed_mbps 150 | } 151 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | mod cfst_rpc; 3 | mod install_upgrade; 4 | mod ping; 5 | mod server_comm; 6 | mod speed; 7 | 8 | use crate::{args::*, cfst_rpc::*, install_upgrade::*, ping::*, server_comm::*, speed::*}; 9 | 10 | use cloudflare_speedtest_client::CloudflareSpeedtestClient; 11 | use log::{debug, error, info, warn}; 12 | use rustls::crypto::aws_lc_rs; 13 | use simple_logger::init_with_level; 14 | use std::{process::exit, time::Duration}; 15 | use tokio::time::timeout; 16 | use tonic::transport::Channel; 17 | 18 | #[tokio::main] 19 | async fn main() { 20 | // 初始化命令行参数 21 | let args: Args = init_args(); 22 | 23 | // 根据调试模式设置日志级别 24 | if args.debug { 25 | init_with_level(log::Level::Debug).unwrap(); 26 | } else { 27 | init_with_level(log::Level::Info).unwrap(); 28 | } 29 | 30 | if args.max_mbps == 114514 && args.install == false { 31 | error!("必须设置 Max Mbps 参数: -m / --max-mbps "); 32 | exit(1); 33 | } 34 | 35 | // 如果命令行参数包含安装选项, 则执行安装操作并退出 36 | if args.install { 37 | install_systemd(args); 38 | exit(1); 39 | } 40 | 41 | let _ = aws_lc_rs::default_provider().install_default().unwrap(); 42 | 43 | // 主循环, 用于定期执行速度测试 44 | loop { 45 | // 初始化Cloudflare Speedtest客户端 46 | let client: CloudflareSpeedtestClient = 47 | match init_client(args.clone().server).await { 48 | Ok(tmp) => { 49 | info!("成功初始化 Cloudflare Speedtest 客户端"); 50 | tmp 51 | } 52 | Err(e) => { 53 | error!( 54 | "未能成功初始化 Cloudflare Speedtest 客户端, 15sec 后重新连接服务器: {}", 55 | e 56 | ); 57 | tokio::time::sleep(Duration::from_secs(15)).await; 58 | continue; 59 | } 60 | }; 61 | 62 | // 发送启动请求, 获取节点ID和会话令牌 63 | let (bootstrap_res, node_id, session_token) = 64 | match send_bootstrap(client.clone(), args.max_mbps, args.token.clone()).await { 65 | Ok(tmp) => { 66 | info!("成功获取 Bootstrap 信息"); 67 | tmp 68 | } 69 | Err(e) => { 70 | error!("未能成功获取 Bootstrap 信息, 15sec 后重新连接服务器: {}", e); 71 | tokio::time::sleep(Duration::from_secs(15)).await; 72 | continue; 73 | } 74 | }; 75 | 76 | // 日志记录当前节点ID和会话令牌 77 | info!( 78 | "当前 Node_ID: {}, Session_token: {}", 79 | node_id, session_token 80 | ); 81 | 82 | // 升级客户端二进制文件 83 | upgrade_bin(client.clone(), args.clone(), bootstrap_res.clone()).await; 84 | 85 | loop { 86 | // 发送速度测试请求, 获取测试结果和需要ping的IP列表 87 | let (speedtest_response, need_ping_ips) = match send_speedtest( 88 | client.clone(), 89 | node_id.clone(), 90 | session_token.clone(), 91 | ) 92 | .await 93 | { 94 | Ok((res, str)) => { 95 | info!("成功获取 Speedtest 信息, 开始启动测速程序"); 96 | (res, str) 97 | } 98 | Err(e) => { 99 | error!("未能成功获取需要测试的 IP, 正在重新连接服务器: {}", e); 100 | break; 101 | } 102 | }; 103 | 104 | // 对需要ping的IP进行ping测试, 记录延迟 105 | let mut ips_ping: std::collections::HashMap = 106 | ping_ips(need_ping_ips, speedtest_response.maximum_ping).await; 107 | info!("获取到 {} 个 IP, 开始测试", ips_ping.len()); 108 | // 移除延迟过高的IP 109 | ips_ping.retain(|_, &mut value| value != u128::MAX); 110 | info!("符合条件 IP 有 {} 个", ips_ping.len()); 111 | debug!("符合条件 IP: {:?}", ips_ping); 112 | 113 | // 测试每个IP的速度, 选择最快且符合最小速度要求的IP 114 | let mut the_last_ip: String = String::new(); 115 | let mut the_last_ip_ping: i32 = -1; 116 | let mut the_last_ip_speed: i32 = -1; 117 | 118 | for (speed_ip, ping) in ips_ping.clone() { 119 | let tmp_speed = match timeout( 120 | Duration::from_secs(12), 121 | speed_one_ip(speedtest_response.speed_url.clone(), speed_ip.clone(), 10), 122 | ) 123 | .await 124 | { 125 | Ok(tmp) => tmp, 126 | Err(e) => { 127 | error!("IP {} 测速超时: {}", speed_ip, e); 128 | continue; 129 | } 130 | }; 131 | if tmp_speed.round() as i32 >= speedtest_response.minimum_mbps { 132 | the_last_ip = speed_ip; 133 | the_last_ip_ping = ping as i32; 134 | the_last_ip_speed = tmp_speed.round() as i32; 135 | break; 136 | } else { 137 | continue; 138 | } 139 | } 140 | 141 | if the_last_ip.is_empty() { 142 | warn!("在测试完所有的 IP 后, 没有发现符合条件的 IP, 请检查您的网络环境, 或请求主端提供者降低最小带宽要求与 Ping 要求"); 143 | } 144 | 145 | // 发送速度测试结果 146 | match send_speedtest_result( 147 | the_last_ip, 148 | the_last_ip_ping, 149 | the_last_ip_speed, 150 | client.clone(), 151 | node_id.clone(), 152 | session_token.clone(), 153 | ) 154 | .await 155 | { 156 | Ok(_) => info!("成功完成一次 Speedtest, 开始继续接受 Speedtest 信息"), 157 | Err(e) => { 158 | error!("无法发送测试结果, 将会跳过本次测试: {}", e); 159 | continue; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/server_comm.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, process::exit, time::Duration}; 2 | 3 | use crate::{ 4 | cfst_rpc::*, cloudflare_speedtest_client::CloudflareSpeedtestClient, ping::ip_cidr_to_ips, 5 | }; 6 | 7 | use log::{debug, error, info, warn}; 8 | use tonic::transport::{Channel, Endpoint}; 9 | use uuid::Uuid; 10 | 11 | /** 12 | * 异步初始化CloudflareSpeedtest客户端。 13 | * 14 | * 本函数尝试与指定的服务器建立连接, 并返回一个CloudflareSpeedtestClient实例。 15 | * 如果连接成功, 它将打印一条成功连接的消息, 并返回客户端实例。 16 | * 如果连接失败, 它将打印连接错误的消息, 并退出程序。 17 | * 18 | * @param server_url 服务器URL, 用于建立连接。 19 | * @return CloudflareSpeedtestClient实例, 用于后续速度测试操作。 20 | */ 21 | pub async fn init_client( 22 | server_url: String, 23 | ) -> Result, Box> { 24 | // 尝试连接到指定的服务器 25 | let endpoint = match Endpoint::from_shared("http://".to_string() + &server_url) { 26 | Ok(tmp) => tmp, 27 | Err(e) => { 28 | error!("无法解析服务器地址: {}", e); 29 | return Err(Box::new(tonic::Status::aborted("无法解析服务器地址"))); 30 | } 31 | }; 32 | 33 | let client = match CloudflareSpeedtestClient::connect( 34 | endpoint 35 | .timeout(Duration::from_secs(5)) 36 | .connect_timeout(Duration::from_secs(5)) 37 | .tcp_keepalive(Some(Duration::from_secs(5))) 38 | .http2_keep_alive_interval(Duration::from_secs(5)) 39 | .keep_alive_timeout(Duration::from_secs(5)) 40 | .keep_alive_while_idle(true), 41 | ) 42 | .await 43 | { 44 | Ok(tmp) => { 45 | // 连接成功, 打印成功消息并返回客户端实例 46 | info!("成功连接服务器"); 47 | tmp 48 | } 49 | Err(e) => { 50 | // 连接失败, 打印错误消息并返回错误 51 | error!("无法连接服务器: {}", e); 52 | return Err(Box::new(tonic::Status::aborted("无法连接服务器"))); 53 | } 54 | }; 55 | return Ok(client); 56 | } 57 | 58 | /// 异步发送启动配置请求并处理响应。 59 | /// 60 | /// 此函数创建一个唯一的节点ID, 构造一个启动请求, 并使用给定的CloudflareSpeedtestClient发送该请求。 61 | /// 它处理可能的错误, 检查响应是否成功, 并返回相关的响应数据。 62 | /// 63 | /// 参数: 64 | /// - client: 用于发送启动请求的CloudflareSpeedtestClient实例。 65 | /// - maximum_mbps: 测试允许的最大Mbps值。 66 | /// - bootstrap_token: 用于身份验证的启动令牌。 67 | /// 68 | /// 返回: 69 | /// - BootstrapResponse: 启动请求的响应。 70 | /// - String: 生成的节点ID。 71 | /// - String: 响应中的会话令牌。 72 | pub async fn send_bootstrap( 73 | client: CloudflareSpeedtestClient, 74 | maximum_mbps: i32, 75 | bootstrap_token: String, 76 | ) -> Result<(BootstrapResponse, String, String), Box> { 77 | // 生成一个唯一的节点ID 78 | let node_id: String = Uuid::new_v4().to_string(); 79 | 80 | // 构造启动请求对象 81 | let reqwest: BootstrapRequest = BootstrapRequest { 82 | maximum_mbps, 83 | client_version: env!("CARGO_PKG_VERSION").to_string(), 84 | bootstrap_token, 85 | node_id: node_id.clone(), 86 | }; 87 | 88 | // 在发送请求前记录请求详情 89 | debug!("BootStrapRequest Message: {:?}", reqwest); 90 | 91 | // 尝试发送启动请求并处理可能的错误 92 | let response: BootstrapResponse = match client.clone().bootstrap(reqwest).await { 93 | Ok(res) => res.get_ref().clone(), 94 | Err(e) => { 95 | error!("发送 Bootstrap 时发送错误: {}", e); 96 | return Err(Box::new(tonic::Status::aborted("Bootstrap Boomed!"))); 97 | } 98 | }; 99 | 100 | // 记录响应详情 101 | debug!("BootStrapResponse Message: {:?}", response); 102 | 103 | // 检查启动请求是否成功 104 | if response.clone().success != true { 105 | error!( 106 | "Bootstrap 信息已成功, 但返回错误 (也许是 Bootstrap Token 设置错误): {:?}", 107 | response.clone() 108 | ); 109 | exit(1); 110 | } 111 | 112 | // 从响应中提取会话令牌 113 | let session_token: String = response.clone().session_token; 114 | 115 | // 如果响应指示需要升级, 则发出警告 116 | if response.clone().should_upgrade == true { 117 | warn!("该后端需更新, 建议更新至最新版本"); 118 | } 119 | 120 | // 返回响应、节点ID和会话令牌 121 | return Ok((response, node_id, session_token)); 122 | } 123 | 124 | /// 异步发送速度测试请求到主端, 并返回速度测试响应和IP范围列表。 125 | /// 126 | /// 此函数使用提供的Cloudflare Speedtest客户端、节点ID和会话令牌来发起速度测试请求。 127 | /// 它首先构建一个速度测试请求, 然后发送该请求并处理响应。如果请求成功, 它将解析响应中的IP范围, 128 | /// 并将这些信息以及原始响应一起返回。 129 | /// 130 | /// 参数: 131 | /// - `client`: 用于与主端通信的客户端。 132 | /// - `node_id`: 用于速度测试的节点ID。 133 | /// - `session_token`: 用于验证会话的令牌。 134 | /// 135 | /// 返回值: 136 | /// - `Result<(SpeedtestResponse, Vec), Box>`: 包含速度测试响应和IP范围列表的结果。 137 | /// 如果发生错误, 返回一个包含错误详情的Box。 138 | pub async fn send_speedtest( 139 | client: CloudflareSpeedtestClient, 140 | node_id: String, 141 | session_token: String, 142 | ) -> Result<(SpeedtestResponse, Vec), Box> { 143 | // 构建速度测试请求 144 | let reqwest: SpeedtestRequest = SpeedtestRequest { 145 | session_token, 146 | node_id, 147 | }; 148 | 149 | // 在发送请求前, 记录请求的详细信息 150 | debug!("SpeedtestRequest Message: {:?}", reqwest); 151 | 152 | // 发送速度测试请求并处理响应 153 | let mut stream = match client.clone().speedtest(reqwest).await { 154 | Ok(tmp) => tmp.into_inner(), 155 | Err(e) => return Err(Box::new(e)), 156 | }; 157 | 158 | // 该代码解决了无法检测是否与主端断开连接的问题 159 | // 让我们感谢 Moohr! 160 | // 尝试读取流中的消息并处理可能的错误 161 | loop { 162 | return match stream.message().await { 163 | Ok(Some(response)) => { 164 | // Process the valid response message here 165 | debug!("SpeedtestResponse Message: {:?}", response); 166 | 167 | // Convert IP ranges to specific IP addresses list 168 | let ip_ranges_ips = ip_cidr_to_ips(response.clone().ip_ranges).await?; 169 | 170 | // Exit the loop after processing the message or continue as needed 171 | Ok((response, ip_ranges_ips)) 172 | } 173 | Ok(None) => { 174 | // The stream was closed by the sender 175 | error!("与主端的流传输被迫关闭, 这可能是因为后端网络波动或主端崩溃, 正在尝试重新连接..."); 176 | // Handle the closure of the stream, possibly attempt to reconnect or exit 177 | Err(Box::new(tonic::Status::aborted( 178 | "Stream was closed by the sender.", 179 | ))) 180 | } 181 | Err(e) => { 182 | // An error occurred while fetching the next message 183 | error!("无法接收主端发送的消息, 正在尝试重新连接: {}", e); 184 | Err(Box::new(e)) 185 | } 186 | }; 187 | } 188 | } 189 | 190 | /// 异步发送速度测试结果到主端。 191 | /// 192 | /// 此函数接收速度测试的IP地址、ping值和速度, 以及一个Cloudflare速度测试客户端, 193 | /// 用于向主端发送速度测试结果。它还接收一个节点ID和会话令牌, 这些可能是用于 194 | /// 鉴权或标识测试来源的。 195 | /// 196 | /// 返回结果为速度测试响应, 或者一个错误盒子。如果成功发送了测试结果, 它将返回测试结果的副本。 197 | pub async fn send_speedtest_result( 198 | ip: String, 199 | ping: i32, 200 | speed: i32, 201 | mut client: CloudflareSpeedtestClient, 202 | node_id: String, 203 | session_token: String, 204 | ) -> Result> { 205 | // 构建IP结果对象, 包含IP地址、延迟和速度信息。 206 | let ipresult = IpResult { 207 | ip_address: ip, 208 | latency: ping, 209 | speed, 210 | }; 211 | 212 | // 将IP结果对象封装成一个vector, 以满足请求格式的要求。 213 | let ipresult_vec: Vec = vec![ipresult]; 214 | 215 | // 构建速度测试结果请求, 包含IP结果、会话令牌和节点ID。 216 | let reqwest = SpeedtestResultRequest { 217 | ip_results: ipresult_vec, 218 | session_token, 219 | node_id, 220 | }; 221 | 222 | // 打印调试信息, 显示即将发送的速度测试结果请求。 223 | debug!("SpeedtestResultResponse Message: {:?}", reqwest); 224 | 225 | // 尝试发送速度测试结果请求, 并处理结果。 226 | return match client.speedtest_result(reqwest).await { 227 | Ok(tmp) => { 228 | // 如果发送成功, 记录信息并返回结果的副本。 229 | info!("成功发送 Speedtest Result 信息"); 230 | Ok(tmp.get_ref().clone()) 231 | } 232 | Err(e) => { 233 | // 如果发送失败, 记录错误并返回错误盒子。 234 | error!("无法发送 Speedtest Result 信息: {}", e); 235 | Err(Box::new(e)) 236 | } 237 | }; 238 | } 239 | -------------------------------------------------------------------------------- /src/install_upgrade.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | fs::{self, File}, 4 | io::{self, Write}, 5 | process::{exit, Command}, 6 | }; 7 | 8 | use crate::{args::Args, cfst_rpc::*, cloudflare_speedtest_client::CloudflareSpeedtestClient}; 9 | 10 | use log::{error, info, warn}; 11 | use reqwest::Client; 12 | use tonic::transport::Channel; 13 | 14 | /// 安装并配置 Systemd 服务。 15 | /// 16 | /// 此函数检查当前系统是否为 Linux, 并确认是否使用 Systemd 作为服务管理器。 17 | /// 它还需要以 root 用户身份运行, 以复制可执行文件并修改系统服务配置。 18 | /// 最后, 它将根据提供的参数配置并启动一个名为 cfst_slave.service 的 Systemd 服务。 19 | pub fn install_systemd(args: Args) { 20 | // 检查操作系统是否为 Linux 21 | if env::consts::OS != "linux" { 22 | error!("Install 功能仅适用于 Linux 系统"); 23 | exit(1); 24 | } 25 | 26 | // 检查系统是否使用 Systemd 27 | match fs::metadata("/usr/bin/systemctl") { 28 | Ok(_) => { 29 | info!("您的系统使用的是 Systemd 服务管理器, 可以正常使用 Install 功能") 30 | } 31 | Err(_) => { 32 | error!("您的系统并非使用 Systemd 作为服务管理器, 无法使用 Install 功能, 请自行配置进程守护"); 33 | exit(1); 34 | } 35 | } 36 | 37 | // 确认以 root 用户身份运行 38 | if env::var("USER") == Ok("root".to_string()) { 39 | info!("正在使用 root 用户"); 40 | } else { 41 | error!("非 root 用户, 请使用 root 用户运行 Install 功能"); 42 | exit(1); 43 | } 44 | 45 | // 检查是否已存在相同名称的服务文件 46 | match fs::metadata("/etc/systemd/system/cfst_slave.service") { 47 | Ok(_) => loop { 48 | warn!("您的当前系统曾经配置过 Systemd 保活服务, 是否覆盖? (Y/N)"); 49 | let mut input = String::new(); 50 | io::stdin().read_line(&mut input).unwrap(); 51 | input = input.trim().to_uppercase(); 52 | 53 | if input == "Y" { 54 | info!("正在为您覆盖先前的文件"); 55 | break; 56 | } else if input == "N" { 57 | info!("不覆盖, 退出程序"); 58 | exit(1); 59 | } else { 60 | warn!("输入错误, 请重新输入 Y 或 N。"); 61 | } 62 | }, 63 | Err(_) => {} 64 | } 65 | 66 | // 复制可执行文件到 /usr/bin 67 | match env::current_exe() { 68 | Ok(path_to_bin) => { 69 | if path_to_bin.to_str().unwrap() == "/usr/bin/CloudflareSpeedtest-Slave" { 70 | info!("无需复制可执行文件"); 71 | } else { 72 | match fs::copy(path_to_bin, "/usr/bin/CloudflareSpeedtest-Slave") { 73 | Ok(_) => { 74 | info!("成功将可执行文件复制到 /usr/bin/CloudflareSpeedtest-Slave"); 75 | } 76 | Err(e) => { 77 | error!( 78 | "无法将可执行文件复制到 /usr/bin/CloudflareSpeedtest-Slave: {}", 79 | e 80 | ); 81 | exit(1); 82 | } 83 | } 84 | } 85 | } 86 | Err(e) => { 87 | error!("无法获取可执行文件路径: {}", e); 88 | exit(1); 89 | } 90 | } 91 | info!("请输入您机器的最高带宽值(单位:megabit/s mbps):"); 92 | let mut max_mbps = String::new(); 93 | match io::stdin().read_line(&mut max_mbps) { 94 | Ok(_) => { 95 | let _ = max_mbps 96 | .trim() 97 | .parse::() 98 | .expect("寄! 你输入的不是个合法数值"); 99 | } 100 | Err(_) => { 101 | error!("请输入一个合法的最高带宽数值! "); 102 | exit(1); 103 | } 104 | } 105 | let mut debug = ""; 106 | if args.debug { 107 | debug = "--debug"; 108 | } 109 | // 配置服务文件的内容 110 | let service_config = format!( 111 | "[Unit] 112 | Description=Cloudflare Speedtest Slave 113 | After=network.target 114 | 115 | [Install] 116 | WantedBy=multi-user.target 117 | 118 | [Service] 119 | Type=simple 120 | ExecStart=/usr/bin/CloudflareSpeedtest-Slave -s SERVERURL -t TOKEN -m {} {} 121 | Restart=always 122 | ", 123 | max_mbps, debug 124 | ); 125 | 126 | // 根据参数替换服务文件中的占位符 127 | let mut replaced_service_config = service_config.replace("SERVERURL", args.server.as_str()); 128 | replaced_service_config = replaced_service_config.replace("TOKEN", args.token.as_str()); 129 | /*else { 130 | replaced_service_config = 131 | replaced_service_config.replace("MAXMBPS", args.max_mbps.to_string().as_str()); 132 | } 133 | */ 134 | 135 | // 删除旧的服务文件 136 | match fs::remove_file("/etc/systemd/system/cfst_slave.service") { 137 | Ok(_) => { 138 | info!("成功删除 /etc/systemd/system/cfst_slave.service"); 139 | } 140 | Err(e) => { 141 | error!("无法删除 /etc/systemd/system/cfst_slave.service: {}", e); 142 | } 143 | } 144 | 145 | // 创建并写入新的服务文件 146 | let mut service_file = match File::create("/etc/systemd/system/cfst_slave.service") { 147 | Ok(tmp) => tmp, 148 | Err(e) => { 149 | error!("无法新建 /etc/systemd/system/cfst_slave.service: {}", e); 150 | exit(1); 151 | } 152 | }; 153 | 154 | match service_file.write_all(replaced_service_config.as_bytes()) { 155 | Ok(_) => { 156 | info!("成功写入 Systemd 配置文件") 157 | } 158 | Err(e) => { 159 | error!("无法写入 Systemd 配置文件: {}", e); 160 | exit(1); 161 | } 162 | } 163 | 164 | // 重新加载 Systemd 配置 165 | match Command::new("systemctl").arg("daemon-reload").output() { 166 | Ok(tmp) => { 167 | if tmp.status.success() { 168 | info!("成功运行 systemctl daemon-reload"); 169 | } else { 170 | error!("无法运行 systemctl daemon-reload") 171 | } 172 | } 173 | Err(e) => { 174 | error!("无法运行 systemctl daemon-reload: {}", e); 175 | exit(1); 176 | } 177 | } 178 | 179 | // 启动服务 180 | match Command::new("systemctl") 181 | .arg("start") 182 | .arg("cfst_slave.service") 183 | .output() 184 | { 185 | Ok(tmp) => { 186 | if tmp.status.success() { 187 | info!("成功启动 Cloudflare Speedtest Slave"); 188 | } else { 189 | error!("无法启动 Cloudflare Speedtest Slave") 190 | } 191 | } 192 | Err(e) => { 193 | error!("无法启动 Cloudflare Speedtest Slave: {}", e); 194 | exit(1); 195 | } 196 | } 197 | 198 | // 询问用户是否开启开机自启动 199 | loop { 200 | info!("是否打开开机自启? (Y/N)"); 201 | let mut input = String::new(); 202 | io::stdin().read_line(&mut input).unwrap(); 203 | input = input.trim().to_uppercase(); 204 | 205 | if input == "Y" { 206 | match Command::new("systemctl") 207 | .arg("enable") 208 | .arg("cfst_slave.service") 209 | .output() 210 | { 211 | Ok(tmp) => { 212 | if tmp.status.success() { 213 | info!("成功打开开机自启"); 214 | } else { 215 | error!("无法打开开机自启") 216 | } 217 | } 218 | Err(e) => { 219 | error!("无法打开开机自启: {}", e); 220 | exit(1); 221 | } 222 | } 223 | break; 224 | } else if input == "N" { 225 | info!("不打开, 退出程序"); 226 | exit(1); 227 | } else { 228 | warn!("输入错误, 请重新输入 Y 或 N。"); 229 | } 230 | } 231 | } 232 | 233 | // 异步函数, 负责检查并执行云flare速度测试客户端的更新。 234 | // 参数: 235 | // - client: 云flare速度测试客户端实例, 使用channel进行通信。 236 | // - args: 命令行参数, 包含是否禁用自动升级等信息。 237 | // - bootstrapres: 启动时从服务器获取的响应, 包含是否需要升级的信息。 238 | pub async fn upgrade_bin( 239 | mut client: CloudflareSpeedtestClient, 240 | args: Args, 241 | bootstrapres: BootstrapResponse, 242 | ) { 243 | // 检查是否需要升级, 如果不需升级则直接返回。 244 | if !bootstrapres.should_upgrade { 245 | info!("该后端为最新版本, 无需更新"); 246 | return; 247 | } else { 248 | info!("准备开始更新后端"); 249 | } 250 | 251 | // 如果配置了禁用自动升级, 则即使需要升级也不执行更新。 252 | if args.disable_auto_upgrade { 253 | warn!("该后端版本需更新, 但由于配置了 Disable Auto Upgrade, 不予更新"); 254 | return; 255 | } 256 | 257 | info!("开始更新后端"); 258 | 259 | // 尝试从客户端获取更新信息。 260 | let upgrade_message = match client.upgrade(UpgradeRequest {}).await { 261 | Ok(tmp) => { 262 | let result = tmp.into_inner(); 263 | // 如果更新请求成功, 检查是否成功获取了更新链接。 264 | if result.success { 265 | info!("成功获取更新链接: {}", result.message); 266 | result 267 | } else { 268 | error!("无法获取更新链接, 终止更新继续运行: {}", result.message); 269 | return; 270 | } 271 | } 272 | Err(e) => { 273 | error!("无法获取更新链接, 终止更新继续运行: {}", e); 274 | return; 275 | } 276 | }; 277 | 278 | // 根据更新信息下载对应的操作系统和架构的更新文件。 279 | let version_bin = match Client::new() 280 | .get(format!( 281 | "{}-{}-{}", 282 | upgrade_message.upgrade_url, 283 | env::consts::OS, 284 | env::consts::ARCH 285 | )) 286 | .send() 287 | .await 288 | { 289 | Ok(tmp) => { 290 | // 检查下载是否成功。 291 | if tmp.status().is_success() { 292 | tmp 293 | } else { 294 | error!( 295 | "无法下载文件 URL: {}, Code: {}, 终止更新继续运行", 296 | tmp.url().to_string(), 297 | tmp.status().to_string() 298 | ); 299 | return; 300 | } 301 | } 302 | Err(e) => { 303 | error!( 304 | "无法下载文件 URL: {}, 终止更新并继续运行: {}", 305 | format!( 306 | "{}-{}-{}", 307 | upgrade_message.upgrade_url, 308 | env::consts::OS, 309 | env::consts::ARCH 310 | ), 311 | e 312 | ); 313 | return; 314 | } 315 | }; 316 | 317 | // 在临时目录创建一个文件用于存储下载的更新二进制文件。 318 | let tmp_dir = env::temp_dir(); 319 | let file_path = tmp_dir.join("CloudflareSpeedtest-Slave"); 320 | 321 | match File::create(&file_path) { 322 | Ok(mut tmp) => { 323 | // 下载二进制文件内容。 324 | let binary = match version_bin.bytes().await { 325 | Ok(tmp) => tmp, 326 | Err(e) => { 327 | error!("无法获取 Binary, 终止更新并继续运行: {}", e); 328 | return; 329 | } 330 | }; 331 | // 将二进制文件内容写入到临时文件。 332 | match tmp.write_all(&binary) { 333 | Ok(_) => { 334 | info!("成功将 Binary 保存到 Temp Dir"); 335 | } 336 | Err(e) => { 337 | error!("无法将 Binary 保存到 Temp Dir, 终止更新并继续运行: {}", e); 338 | return; 339 | } 340 | } 341 | } 342 | Err(e) => { 343 | error!("无法将 Binary 保存到 Temp Dir, 终止更新并继续运行: {}", e); 344 | return; 345 | } 346 | } 347 | 348 | // 为临时文件添加可执行权限。 349 | match Command::new("chmod") 350 | .arg("+x") 351 | .arg(file_path.clone()) 352 | .output() 353 | { 354 | Ok(_) => { 355 | info!("成功添加可执行权限"); 356 | } 357 | Err(e) => { 358 | error!("无法添加可执行权限, 终止更新并继续运行: {}", e); 359 | return; 360 | } 361 | } 362 | 363 | // 复制临时文件到当前执行程序的路径, 以替换旧版本。 364 | match env::current_exe() { 365 | Ok(path_to_bin) => match fs::copy(file_path, path_to_bin) { 366 | Ok(_) => { 367 | info!("成功将可执行文件替换"); 368 | } 369 | Err(e) => { 370 | error!("无法将可执行文件替换, 终止更新并继续运行: {}", e); 371 | return; 372 | } 373 | }, 374 | Err(e) => { 375 | error!("无法获取当前运行程序路径, 终止更新并继续运行: {}", e); 376 | return; 377 | } 378 | } 379 | 380 | // 启动新的可执行文件, 替换当前进程。 381 | let mut command = Command::new(env::current_exe().unwrap()); 382 | command.args(env::args().skip(1)); 383 | 384 | let _ = match command.spawn() { 385 | Ok(_) => { 386 | info!("成功启动新程序"); 387 | exit(1); 388 | } 389 | Err(e) => { 390 | error!( 391 | "无法启动新程序, 主程序将退出, 请自行重新启动新版本程序: {}", 392 | e 393 | ); 394 | exit(1); 395 | } 396 | }; 397 | } 398 | -------------------------------------------------------------------------------- /proto/cfst_rpc.rs: -------------------------------------------------------------------------------- 1 | // This file is @generated by prost-build. 2 | #[allow(clippy::derive_partial_eq_without_eq)] 3 | #[derive(Clone, PartialEq, ::prost::Message)] 4 | pub struct BootstrapRequest { 5 | #[prost(int32, tag = "1")] 6 | pub province_id: i32, 7 | #[prost(int32, tag = "2")] 8 | pub isp_type: i32, 9 | #[prost(int32, tag = "3")] 10 | pub maximum_mbps: i32, 11 | #[prost(string, tag = "4")] 12 | pub client_version: ::prost::alloc::string::String, 13 | #[prost(string, tag = "5")] 14 | pub bootstrap_token: ::prost::alloc::string::String, 15 | } 16 | #[allow(clippy::derive_partial_eq_without_eq)] 17 | #[derive(Clone, PartialEq, ::prost::Message)] 18 | pub struct BootstrapResponse { 19 | #[prost(bool, tag = "1")] 20 | pub success: bool, 21 | #[prost(bool, tag = "2")] 22 | pub should_upgrade: bool, 23 | #[prost(string, tag = "3")] 24 | pub message: ::prost::alloc::string::String, 25 | /// token to use for communicating with the control node thereafter until the exit of the process 26 | #[prost(string, tag = "4")] 27 | pub session_token: ::prost::alloc::string::String, 28 | } 29 | #[allow(clippy::derive_partial_eq_without_eq)] 30 | #[derive(Clone, PartialEq, ::prost::Message)] 31 | pub struct UpgradeRequest { 32 | #[prost(int32, tag = "1")] 33 | pub province_id: i32, 34 | #[prost(int32, tag = "2")] 35 | pub isp_type: i32, 36 | #[prost(string, tag = "3")] 37 | pub session_token: ::prost::alloc::string::String, 38 | } 39 | #[allow(clippy::derive_partial_eq_without_eq)] 40 | #[derive(Clone, PartialEq, ::prost::Message)] 41 | pub struct UpgradeResponse { 42 | #[prost(bool, tag = "1")] 43 | pub success: bool, 44 | #[prost(string, tag = "2")] 45 | pub message: ::prost::alloc::string::String, 46 | #[prost(string, tag = "3")] 47 | pub upgrade_url: ::prost::alloc::string::String, 48 | } 49 | #[allow(clippy::derive_partial_eq_without_eq)] 50 | #[derive(Clone, PartialEq, ::prost::Message)] 51 | pub struct IpResult { 52 | #[prost(string, tag = "1")] 53 | pub ip_address: ::prost::alloc::string::String, 54 | #[prost(int32, tag = "2")] 55 | pub latency: i32, 56 | #[prost(int32, tag = "3")] 57 | pub speed: i32, 58 | } 59 | #[allow(clippy::derive_partial_eq_without_eq)] 60 | #[derive(Clone, PartialEq, ::prost::Message)] 61 | pub struct SpeedtestRequest { 62 | #[prost(string, repeated, tag = "1")] 63 | pub ip_ranges: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 64 | } 65 | #[allow(clippy::derive_partial_eq_without_eq)] 66 | #[derive(Clone, PartialEq, ::prost::Message)] 67 | pub struct SpeedtestResponse { 68 | #[prost(bool, tag = "1")] 69 | pub success: bool, 70 | #[prost(string, tag = "2")] 71 | pub message: ::prost::alloc::string::String, 72 | #[prost(int32, tag = "3")] 73 | pub province_id: i32, 74 | #[prost(int32, tag = "4")] 75 | pub isp_type: i32, 76 | #[prost(message, repeated, tag = "5")] 77 | pub ip_results: ::prost::alloc::vec::Vec, 78 | #[prost(string, tag = "6")] 79 | pub session_token: ::prost::alloc::string::String, 80 | } 81 | /// Generated client implementations. 82 | pub mod cloudflare_speedtest_service_client { 83 | #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 84 | use tonic::codegen::*; 85 | use tonic::codegen::http::Uri; 86 | #[derive(Debug, Clone)] 87 | pub struct CloudflareSpeedtestServiceClient { 88 | inner: tonic::client::Grpc, 89 | } 90 | impl CloudflareSpeedtestServiceClient { 91 | /// Attempt to create a new client by connecting to a given endpoint. 92 | pub async fn connect(dst: D) -> Result 93 | where 94 | D: TryInto, 95 | D::Error: Into, 96 | { 97 | let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 98 | Ok(Self::new(conn)) 99 | } 100 | } 101 | impl CloudflareSpeedtestServiceClient 102 | where 103 | T: tonic::client::GrpcService, 104 | T::Error: Into, 105 | T::ResponseBody: Body + Send + 'static, 106 | ::Error: Into + Send, 107 | { 108 | pub fn new(inner: T) -> Self { 109 | let inner = tonic::client::Grpc::new(inner); 110 | Self { inner } 111 | } 112 | pub fn with_origin(inner: T, origin: Uri) -> Self { 113 | let inner = tonic::client::Grpc::with_origin(inner, origin); 114 | Self { inner } 115 | } 116 | pub fn with_interceptor( 117 | inner: T, 118 | interceptor: F, 119 | ) -> CloudflareSpeedtestServiceClient> 120 | where 121 | F: tonic::service::Interceptor, 122 | T::ResponseBody: Default, 123 | T: tonic::codegen::Service< 124 | http::Request, 125 | Response = http::Response< 126 | >::ResponseBody, 127 | >, 128 | >, 129 | , 131 | >>::Error: Into + Send + Sync, 132 | { 133 | CloudflareSpeedtestServiceClient::new( 134 | InterceptedService::new(inner, interceptor), 135 | ) 136 | } 137 | /// Compress requests with the given encoding. 138 | /// 139 | /// This requires the server to support it otherwise it might respond with an 140 | /// error. 141 | #[must_use] 142 | pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 143 | self.inner = self.inner.send_compressed(encoding); 144 | self 145 | } 146 | /// Enable decompressing responses. 147 | #[must_use] 148 | pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 149 | self.inner = self.inner.accept_compressed(encoding); 150 | self 151 | } 152 | /// Limits the maximum size of a decoded message. 153 | /// 154 | /// Default: `4MB` 155 | #[must_use] 156 | pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 157 | self.inner = self.inner.max_decoding_message_size(limit); 158 | self 159 | } 160 | /// Limits the maximum size of an encoded message. 161 | /// 162 | /// Default: `usize::MAX` 163 | #[must_use] 164 | pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 165 | self.inner = self.inner.max_encoding_message_size(limit); 166 | self 167 | } 168 | pub async fn bootstrap( 169 | &mut self, 170 | request: impl tonic::IntoRequest, 171 | ) -> std::result::Result< 172 | tonic::Response, 173 | tonic::Status, 174 | > { 175 | self.inner 176 | .ready() 177 | .await 178 | .map_err(|e| { 179 | tonic::Status::new( 180 | tonic::Code::Unknown, 181 | format!("Service was not ready: {}", e.into()), 182 | ) 183 | })?; 184 | let codec = tonic::codec::ProstCodec::default(); 185 | let path = http::uri::PathAndQuery::from_static( 186 | "/cfst_rpc.CloudflareSpeedtestService/Bootstrap", 187 | ); 188 | let mut req = request.into_request(); 189 | req.extensions_mut() 190 | .insert( 191 | GrpcMethod::new("cfst_rpc.CloudflareSpeedtestService", "Bootstrap"), 192 | ); 193 | self.inner.unary(req, path, codec).await 194 | } 195 | /// Deprecated 196 | /// rpc GetCandidateIPRanges(CandidateIPRangesRequest) returns (CandidateIPRangesResponse); 197 | /// rpc SpeedtestResultUpload(SpeedtestResultUploadRequest) returns (SpeedtestResultUploadResponse); 198 | pub async fn speedtest( 199 | &mut self, 200 | request: impl tonic::IntoRequest, 201 | ) -> std::result::Result< 202 | tonic::Response, 203 | tonic::Status, 204 | > { 205 | self.inner 206 | .ready() 207 | .await 208 | .map_err(|e| { 209 | tonic::Status::new( 210 | tonic::Code::Unknown, 211 | format!("Service was not ready: {}", e.into()), 212 | ) 213 | })?; 214 | let codec = tonic::codec::ProstCodec::default(); 215 | let path = http::uri::PathAndQuery::from_static( 216 | "/cfst_rpc.CloudflareSpeedtestService/Speedtest", 217 | ); 218 | let mut req = request.into_request(); 219 | req.extensions_mut() 220 | .insert( 221 | GrpcMethod::new("cfst_rpc.CloudflareSpeedtestService", "Speedtest"), 222 | ); 223 | self.inner.unary(req, path, codec).await 224 | } 225 | pub async fn upgrade( 226 | &mut self, 227 | request: impl tonic::IntoRequest, 228 | ) -> std::result::Result< 229 | tonic::Response, 230 | tonic::Status, 231 | > { 232 | self.inner 233 | .ready() 234 | .await 235 | .map_err(|e| { 236 | tonic::Status::new( 237 | tonic::Code::Unknown, 238 | format!("Service was not ready: {}", e.into()), 239 | ) 240 | })?; 241 | let codec = tonic::codec::ProstCodec::default(); 242 | let path = http::uri::PathAndQuery::from_static( 243 | "/cfst_rpc.CloudflareSpeedtestService/Upgrade", 244 | ); 245 | let mut req = request.into_request(); 246 | req.extensions_mut() 247 | .insert( 248 | GrpcMethod::new("cfst_rpc.CloudflareSpeedtestService", "Upgrade"), 249 | ); 250 | self.inner.unary(req, path, codec).await 251 | } 252 | } 253 | } 254 | /// Generated server implementations. 255 | pub mod cloudflare_speedtest_service_server { 256 | #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 257 | use tonic::codegen::*; 258 | /// Generated trait containing gRPC methods that should be implemented for use with CloudflareSpeedtestServiceServer. 259 | #[async_trait] 260 | pub trait CloudflareSpeedtestService: Send + Sync + 'static { 261 | async fn bootstrap( 262 | &self, 263 | request: tonic::Request, 264 | ) -> std::result::Result< 265 | tonic::Response, 266 | tonic::Status, 267 | >; 268 | /// Deprecated 269 | /// rpc GetCandidateIPRanges(CandidateIPRangesRequest) returns (CandidateIPRangesResponse); 270 | /// rpc SpeedtestResultUpload(SpeedtestResultUploadRequest) returns (SpeedtestResultUploadResponse); 271 | async fn speedtest( 272 | &self, 273 | request: tonic::Request, 274 | ) -> std::result::Result< 275 | tonic::Response, 276 | tonic::Status, 277 | >; 278 | async fn upgrade( 279 | &self, 280 | request: tonic::Request, 281 | ) -> std::result::Result, tonic::Status>; 282 | } 283 | #[derive(Debug)] 284 | pub struct CloudflareSpeedtestServiceServer { 285 | inner: _Inner, 286 | accept_compression_encodings: EnabledCompressionEncodings, 287 | send_compression_encodings: EnabledCompressionEncodings, 288 | max_decoding_message_size: Option, 289 | max_encoding_message_size: Option, 290 | } 291 | struct _Inner(Arc); 292 | impl CloudflareSpeedtestServiceServer { 293 | pub fn new(inner: T) -> Self { 294 | Self::from_arc(Arc::new(inner)) 295 | } 296 | pub fn from_arc(inner: Arc) -> Self { 297 | let inner = _Inner(inner); 298 | Self { 299 | inner, 300 | accept_compression_encodings: Default::default(), 301 | send_compression_encodings: Default::default(), 302 | max_decoding_message_size: None, 303 | max_encoding_message_size: None, 304 | } 305 | } 306 | pub fn with_interceptor( 307 | inner: T, 308 | interceptor: F, 309 | ) -> InterceptedService 310 | where 311 | F: tonic::service::Interceptor, 312 | { 313 | InterceptedService::new(Self::new(inner), interceptor) 314 | } 315 | /// Enable decompressing requests with the given encoding. 316 | #[must_use] 317 | pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 318 | self.accept_compression_encodings.enable(encoding); 319 | self 320 | } 321 | /// Compress responses with the given encoding, if the client supports it. 322 | #[must_use] 323 | pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 324 | self.send_compression_encodings.enable(encoding); 325 | self 326 | } 327 | /// Limits the maximum size of a decoded message. 328 | /// 329 | /// Default: `4MB` 330 | #[must_use] 331 | pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 332 | self.max_decoding_message_size = Some(limit); 333 | self 334 | } 335 | /// Limits the maximum size of an encoded message. 336 | /// 337 | /// Default: `usize::MAX` 338 | #[must_use] 339 | pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 340 | self.max_encoding_message_size = Some(limit); 341 | self 342 | } 343 | } 344 | impl tonic::codegen::Service> 345 | for CloudflareSpeedtestServiceServer 346 | where 347 | T: CloudflareSpeedtestService, 348 | B: Body + Send + 'static, 349 | B::Error: Into + Send + 'static, 350 | { 351 | type Response = http::Response; 352 | type Error = std::convert::Infallible; 353 | type Future = BoxFuture; 354 | fn poll_ready( 355 | &mut self, 356 | _cx: &mut Context<'_>, 357 | ) -> Poll> { 358 | Poll::Ready(Ok(())) 359 | } 360 | fn call(&mut self, req: http::Request) -> Self::Future { 361 | let inner = self.inner.clone(); 362 | match req.uri().path() { 363 | "/cfst_rpc.CloudflareSpeedtestService/Bootstrap" => { 364 | #[allow(non_camel_case_types)] 365 | struct BootstrapSvc(pub Arc); 366 | impl< 367 | T: CloudflareSpeedtestService, 368 | > tonic::server::UnaryService 369 | for BootstrapSvc { 370 | type Response = super::BootstrapResponse; 371 | type Future = BoxFuture< 372 | tonic::Response, 373 | tonic::Status, 374 | >; 375 | fn call( 376 | &mut self, 377 | request: tonic::Request, 378 | ) -> Self::Future { 379 | let inner = Arc::clone(&self.0); 380 | let fut = async move { 381 | ::bootstrap( 382 | &inner, 383 | request, 384 | ) 385 | .await 386 | }; 387 | Box::pin(fut) 388 | } 389 | } 390 | let accept_compression_encodings = self.accept_compression_encodings; 391 | let send_compression_encodings = self.send_compression_encodings; 392 | let max_decoding_message_size = self.max_decoding_message_size; 393 | let max_encoding_message_size = self.max_encoding_message_size; 394 | let inner = self.inner.clone(); 395 | let fut = async move { 396 | let inner = inner.0; 397 | let method = BootstrapSvc(inner); 398 | let codec = tonic::codec::ProstCodec::default(); 399 | let mut grpc = tonic::server::Grpc::new(codec) 400 | .apply_compression_config( 401 | accept_compression_encodings, 402 | send_compression_encodings, 403 | ) 404 | .apply_max_message_size_config( 405 | max_decoding_message_size, 406 | max_encoding_message_size, 407 | ); 408 | let res = grpc.unary(method, req).await; 409 | Ok(res) 410 | }; 411 | Box::pin(fut) 412 | } 413 | "/cfst_rpc.CloudflareSpeedtestService/Speedtest" => { 414 | #[allow(non_camel_case_types)] 415 | struct SpeedtestSvc(pub Arc); 416 | impl< 417 | T: CloudflareSpeedtestService, 418 | > tonic::server::UnaryService 419 | for SpeedtestSvc { 420 | type Response = super::SpeedtestResponse; 421 | type Future = BoxFuture< 422 | tonic::Response, 423 | tonic::Status, 424 | >; 425 | fn call( 426 | &mut self, 427 | request: tonic::Request, 428 | ) -> Self::Future { 429 | let inner = Arc::clone(&self.0); 430 | let fut = async move { 431 | ::speedtest( 432 | &inner, 433 | request, 434 | ) 435 | .await 436 | }; 437 | Box::pin(fut) 438 | } 439 | } 440 | let accept_compression_encodings = self.accept_compression_encodings; 441 | let send_compression_encodings = self.send_compression_encodings; 442 | let max_decoding_message_size = self.max_decoding_message_size; 443 | let max_encoding_message_size = self.max_encoding_message_size; 444 | let inner = self.inner.clone(); 445 | let fut = async move { 446 | let inner = inner.0; 447 | let method = SpeedtestSvc(inner); 448 | let codec = tonic::codec::ProstCodec::default(); 449 | let mut grpc = tonic::server::Grpc::new(codec) 450 | .apply_compression_config( 451 | accept_compression_encodings, 452 | send_compression_encodings, 453 | ) 454 | .apply_max_message_size_config( 455 | max_decoding_message_size, 456 | max_encoding_message_size, 457 | ); 458 | let res = grpc.unary(method, req).await; 459 | Ok(res) 460 | }; 461 | Box::pin(fut) 462 | } 463 | "/cfst_rpc.CloudflareSpeedtestService/Upgrade" => { 464 | #[allow(non_camel_case_types)] 465 | struct UpgradeSvc(pub Arc); 466 | impl< 467 | T: CloudflareSpeedtestService, 468 | > tonic::server::UnaryService 469 | for UpgradeSvc { 470 | type Response = super::UpgradeResponse; 471 | type Future = BoxFuture< 472 | tonic::Response, 473 | tonic::Status, 474 | >; 475 | fn call( 476 | &mut self, 477 | request: tonic::Request, 478 | ) -> Self::Future { 479 | let inner = Arc::clone(&self.0); 480 | let fut = async move { 481 | ::upgrade(&inner, request) 482 | .await 483 | }; 484 | Box::pin(fut) 485 | } 486 | } 487 | let accept_compression_encodings = self.accept_compression_encodings; 488 | let send_compression_encodings = self.send_compression_encodings; 489 | let max_decoding_message_size = self.max_decoding_message_size; 490 | let max_encoding_message_size = self.max_encoding_message_size; 491 | let inner = self.inner.clone(); 492 | let fut = async move { 493 | let inner = inner.0; 494 | let method = UpgradeSvc(inner); 495 | let codec = tonic::codec::ProstCodec::default(); 496 | let mut grpc = tonic::server::Grpc::new(codec) 497 | .apply_compression_config( 498 | accept_compression_encodings, 499 | send_compression_encodings, 500 | ) 501 | .apply_max_message_size_config( 502 | max_decoding_message_size, 503 | max_encoding_message_size, 504 | ); 505 | let res = grpc.unary(method, req).await; 506 | Ok(res) 507 | }; 508 | Box::pin(fut) 509 | } 510 | _ => { 511 | Box::pin(async move { 512 | Ok( 513 | http::Response::builder() 514 | .status(200) 515 | .header("grpc-status", "12") 516 | .header("content-type", "application/grpc") 517 | .body(empty_body()) 518 | .unwrap(), 519 | ) 520 | }) 521 | } 522 | } 523 | } 524 | } 525 | impl Clone for CloudflareSpeedtestServiceServer { 526 | fn clone(&self) -> Self { 527 | let inner = self.inner.clone(); 528 | Self { 529 | inner, 530 | accept_compression_encodings: self.accept_compression_encodings, 531 | send_compression_encodings: self.send_compression_encodings, 532 | max_decoding_message_size: self.max_decoding_message_size, 533 | max_encoding_message_size: self.max_encoding_message_size, 534 | } 535 | } 536 | } 537 | impl Clone for _Inner { 538 | fn clone(&self) -> Self { 539 | Self(Arc::clone(&self.0)) 540 | } 541 | } 542 | impl std::fmt::Debug for _Inner { 543 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 544 | write!(f, "{:?}", self.0) 545 | } 546 | } 547 | impl tonic::server::NamedService 548 | for CloudflareSpeedtestServiceServer { 549 | const NAME: &'static str = "cfst_rpc.CloudflareSpeedtestService"; 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "CloudflareSpeedtest-Slave" 7 | version = "0.0.6" 8 | dependencies = [ 9 | "clap", 10 | "futures", 11 | "ipnetwork", 12 | "log", 13 | "prost", 14 | "rand 0.9.0-alpha.1", 15 | "reqwest", 16 | "rustls", 17 | "simple_logger", 18 | "tokio", 19 | "tokio-rustls", 20 | "tokio-stream", 21 | "tonic", 22 | "tonic-build", 23 | "url", 24 | "uuid", 25 | "webpki-roots", 26 | ] 27 | 28 | [[package]] 29 | name = "addr2line" 30 | version = "0.22.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 33 | dependencies = [ 34 | "gimli", 35 | ] 36 | 37 | [[package]] 38 | name = "adler" 39 | version = "1.0.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 42 | 43 | [[package]] 44 | name = "aho-corasick" 45 | version = "1.1.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 48 | dependencies = [ 49 | "memchr", 50 | ] 51 | 52 | [[package]] 53 | name = "anstream" 54 | version = "0.6.14" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 57 | dependencies = [ 58 | "anstyle", 59 | "anstyle-parse", 60 | "anstyle-query", 61 | "anstyle-wincon", 62 | "colorchoice", 63 | "is_terminal_polyfill", 64 | "utf8parse", 65 | ] 66 | 67 | [[package]] 68 | name = "anstyle" 69 | version = "1.0.7" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 72 | 73 | [[package]] 74 | name = "anstyle-parse" 75 | version = "0.2.4" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 78 | dependencies = [ 79 | "utf8parse", 80 | ] 81 | 82 | [[package]] 83 | name = "anstyle-query" 84 | version = "1.1.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 87 | dependencies = [ 88 | "windows-sys 0.52.0", 89 | ] 90 | 91 | [[package]] 92 | name = "anstyle-wincon" 93 | version = "3.0.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 96 | dependencies = [ 97 | "anstyle", 98 | "windows-sys 0.52.0", 99 | ] 100 | 101 | [[package]] 102 | name = "anyhow" 103 | version = "1.0.86" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 106 | 107 | [[package]] 108 | name = "async-stream" 109 | version = "0.3.5" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" 112 | dependencies = [ 113 | "async-stream-impl", 114 | "futures-core", 115 | "pin-project-lite", 116 | ] 117 | 118 | [[package]] 119 | name = "async-stream-impl" 120 | version = "0.3.5" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "syn", 127 | ] 128 | 129 | [[package]] 130 | name = "async-trait" 131 | version = "0.1.80" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" 134 | dependencies = [ 135 | "proc-macro2", 136 | "quote", 137 | "syn", 138 | ] 139 | 140 | [[package]] 141 | name = "atomic-waker" 142 | version = "1.1.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 145 | 146 | [[package]] 147 | name = "autocfg" 148 | version = "1.3.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 151 | 152 | [[package]] 153 | name = "aws-lc-rs" 154 | version = "1.8.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "a8a47f2fb521b70c11ce7369a6c5fa4bd6af7e5d62ec06303875bafe7c6ba245" 157 | dependencies = [ 158 | "aws-lc-sys", 159 | "mirai-annotations", 160 | "paste", 161 | "zeroize", 162 | ] 163 | 164 | [[package]] 165 | name = "aws-lc-sys" 166 | version = "0.19.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "2927c7af777b460b7ccd95f8b67acd7b4c04ec8896bf0c8e80ba30523cffc057" 169 | dependencies = [ 170 | "bindgen", 171 | "cc", 172 | "cmake", 173 | "dunce", 174 | "fs_extra", 175 | "libc", 176 | "paste", 177 | ] 178 | 179 | [[package]] 180 | name = "axum" 181 | version = "0.7.5" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" 184 | dependencies = [ 185 | "async-trait", 186 | "axum-core", 187 | "bytes", 188 | "futures-util", 189 | "http", 190 | "http-body", 191 | "http-body-util", 192 | "itoa", 193 | "matchit", 194 | "memchr", 195 | "mime", 196 | "percent-encoding", 197 | "pin-project-lite", 198 | "rustversion", 199 | "serde", 200 | "sync_wrapper 1.0.1", 201 | "tower", 202 | "tower-layer", 203 | "tower-service", 204 | ] 205 | 206 | [[package]] 207 | name = "axum-core" 208 | version = "0.4.3" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" 211 | dependencies = [ 212 | "async-trait", 213 | "bytes", 214 | "futures-util", 215 | "http", 216 | "http-body", 217 | "http-body-util", 218 | "mime", 219 | "pin-project-lite", 220 | "rustversion", 221 | "sync_wrapper 0.1.2", 222 | "tower-layer", 223 | "tower-service", 224 | ] 225 | 226 | [[package]] 227 | name = "backtrace" 228 | version = "0.3.73" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 231 | dependencies = [ 232 | "addr2line", 233 | "cc", 234 | "cfg-if", 235 | "libc", 236 | "miniz_oxide", 237 | "object", 238 | "rustc-demangle", 239 | ] 240 | 241 | [[package]] 242 | name = "base64" 243 | version = "0.22.1" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 246 | 247 | [[package]] 248 | name = "bindgen" 249 | version = "0.69.4" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" 252 | dependencies = [ 253 | "bitflags", 254 | "cexpr", 255 | "clang-sys", 256 | "itertools", 257 | "lazy_static", 258 | "lazycell", 259 | "log", 260 | "prettyplease", 261 | "proc-macro2", 262 | "quote", 263 | "regex", 264 | "rustc-hash", 265 | "shlex", 266 | "syn", 267 | "which", 268 | ] 269 | 270 | [[package]] 271 | name = "bitflags" 272 | version = "2.6.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 275 | 276 | [[package]] 277 | name = "bumpalo" 278 | version = "3.16.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 281 | 282 | [[package]] 283 | name = "bytes" 284 | version = "1.6.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 287 | 288 | [[package]] 289 | name = "cc" 290 | version = "1.0.104" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" 293 | dependencies = [ 294 | "jobserver", 295 | "libc", 296 | "once_cell", 297 | ] 298 | 299 | [[package]] 300 | name = "cexpr" 301 | version = "0.6.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 304 | dependencies = [ 305 | "nom", 306 | ] 307 | 308 | [[package]] 309 | name = "cfg-if" 310 | version = "1.0.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 313 | 314 | [[package]] 315 | name = "clang-sys" 316 | version = "1.8.1" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 319 | dependencies = [ 320 | "glob", 321 | "libc", 322 | "libloading", 323 | ] 324 | 325 | [[package]] 326 | name = "clap" 327 | version = "4.5.9" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" 330 | dependencies = [ 331 | "clap_builder", 332 | "clap_derive", 333 | ] 334 | 335 | [[package]] 336 | name = "clap_builder" 337 | version = "4.5.9" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" 340 | dependencies = [ 341 | "anstream", 342 | "anstyle", 343 | "clap_lex", 344 | "strsim", 345 | ] 346 | 347 | [[package]] 348 | name = "clap_derive" 349 | version = "4.5.8" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" 352 | dependencies = [ 353 | "heck", 354 | "proc-macro2", 355 | "quote", 356 | "syn", 357 | ] 358 | 359 | [[package]] 360 | name = "clap_lex" 361 | version = "0.7.1" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 364 | 365 | [[package]] 366 | name = "cmake" 367 | version = "0.1.50" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" 370 | dependencies = [ 371 | "cc", 372 | ] 373 | 374 | [[package]] 375 | name = "colorchoice" 376 | version = "1.0.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 379 | 380 | [[package]] 381 | name = "colored" 382 | version = "2.1.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" 385 | dependencies = [ 386 | "lazy_static", 387 | "windows-sys 0.48.0", 388 | ] 389 | 390 | [[package]] 391 | name = "deranged" 392 | version = "0.3.11" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 395 | dependencies = [ 396 | "powerfmt", 397 | ] 398 | 399 | [[package]] 400 | name = "dunce" 401 | version = "1.0.4" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" 404 | 405 | [[package]] 406 | name = "either" 407 | version = "1.13.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 410 | 411 | [[package]] 412 | name = "equivalent" 413 | version = "1.0.1" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 416 | 417 | [[package]] 418 | name = "errno" 419 | version = "0.3.9" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 422 | dependencies = [ 423 | "libc", 424 | "windows-sys 0.52.0", 425 | ] 426 | 427 | [[package]] 428 | name = "fastrand" 429 | version = "2.1.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 432 | 433 | [[package]] 434 | name = "fixedbitset" 435 | version = "0.4.2" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 438 | 439 | [[package]] 440 | name = "fnv" 441 | version = "1.0.7" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 444 | 445 | [[package]] 446 | name = "form_urlencoded" 447 | version = "1.2.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 450 | dependencies = [ 451 | "percent-encoding", 452 | ] 453 | 454 | [[package]] 455 | name = "fs_extra" 456 | version = "1.3.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 459 | 460 | [[package]] 461 | name = "futures" 462 | version = "0.3.30" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 465 | dependencies = [ 466 | "futures-channel", 467 | "futures-core", 468 | "futures-executor", 469 | "futures-io", 470 | "futures-sink", 471 | "futures-task", 472 | "futures-util", 473 | ] 474 | 475 | [[package]] 476 | name = "futures-channel" 477 | version = "0.3.30" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 480 | dependencies = [ 481 | "futures-core", 482 | "futures-sink", 483 | ] 484 | 485 | [[package]] 486 | name = "futures-core" 487 | version = "0.3.30" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 490 | 491 | [[package]] 492 | name = "futures-executor" 493 | version = "0.3.30" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 496 | dependencies = [ 497 | "futures-core", 498 | "futures-task", 499 | "futures-util", 500 | ] 501 | 502 | [[package]] 503 | name = "futures-io" 504 | version = "0.3.30" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 507 | 508 | [[package]] 509 | name = "futures-macro" 510 | version = "0.3.30" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 513 | dependencies = [ 514 | "proc-macro2", 515 | "quote", 516 | "syn", 517 | ] 518 | 519 | [[package]] 520 | name = "futures-sink" 521 | version = "0.3.30" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 524 | 525 | [[package]] 526 | name = "futures-task" 527 | version = "0.3.30" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 530 | 531 | [[package]] 532 | name = "futures-util" 533 | version = "0.3.30" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 536 | dependencies = [ 537 | "futures-channel", 538 | "futures-core", 539 | "futures-io", 540 | "futures-macro", 541 | "futures-sink", 542 | "futures-task", 543 | "memchr", 544 | "pin-project-lite", 545 | "pin-utils", 546 | "slab", 547 | ] 548 | 549 | [[package]] 550 | name = "getrandom" 551 | version = "0.2.15" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 554 | dependencies = [ 555 | "cfg-if", 556 | "libc", 557 | "wasi", 558 | ] 559 | 560 | [[package]] 561 | name = "gimli" 562 | version = "0.29.0" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 565 | 566 | [[package]] 567 | name = "glob" 568 | version = "0.3.1" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 571 | 572 | [[package]] 573 | name = "h2" 574 | version = "0.4.5" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" 577 | dependencies = [ 578 | "atomic-waker", 579 | "bytes", 580 | "fnv", 581 | "futures-core", 582 | "futures-sink", 583 | "http", 584 | "indexmap 2.2.6", 585 | "slab", 586 | "tokio", 587 | "tokio-util", 588 | "tracing", 589 | ] 590 | 591 | [[package]] 592 | name = "hashbrown" 593 | version = "0.12.3" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 596 | 597 | [[package]] 598 | name = "hashbrown" 599 | version = "0.14.5" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 602 | 603 | [[package]] 604 | name = "heck" 605 | version = "0.5.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 608 | 609 | [[package]] 610 | name = "hermit-abi" 611 | version = "0.3.9" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 614 | 615 | [[package]] 616 | name = "home" 617 | version = "0.5.9" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 620 | dependencies = [ 621 | "windows-sys 0.52.0", 622 | ] 623 | 624 | [[package]] 625 | name = "http" 626 | version = "1.1.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 629 | dependencies = [ 630 | "bytes", 631 | "fnv", 632 | "itoa", 633 | ] 634 | 635 | [[package]] 636 | name = "http-body" 637 | version = "1.0.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 640 | dependencies = [ 641 | "bytes", 642 | "http", 643 | ] 644 | 645 | [[package]] 646 | name = "http-body-util" 647 | version = "0.1.2" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 650 | dependencies = [ 651 | "bytes", 652 | "futures-util", 653 | "http", 654 | "http-body", 655 | "pin-project-lite", 656 | ] 657 | 658 | [[package]] 659 | name = "httparse" 660 | version = "1.9.4" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" 663 | 664 | [[package]] 665 | name = "httpdate" 666 | version = "1.0.3" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 669 | 670 | [[package]] 671 | name = "hyper" 672 | version = "1.4.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" 675 | dependencies = [ 676 | "bytes", 677 | "futures-channel", 678 | "futures-util", 679 | "h2", 680 | "http", 681 | "http-body", 682 | "httparse", 683 | "httpdate", 684 | "itoa", 685 | "pin-project-lite", 686 | "smallvec", 687 | "tokio", 688 | "want", 689 | ] 690 | 691 | [[package]] 692 | name = "hyper-rustls" 693 | version = "0.27.2" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" 696 | dependencies = [ 697 | "futures-util", 698 | "http", 699 | "hyper", 700 | "hyper-util", 701 | "rustls", 702 | "rustls-pki-types", 703 | "tokio", 704 | "tokio-rustls", 705 | "tower-service", 706 | "webpki-roots", 707 | ] 708 | 709 | [[package]] 710 | name = "hyper-timeout" 711 | version = "0.5.1" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" 714 | dependencies = [ 715 | "hyper", 716 | "hyper-util", 717 | "pin-project-lite", 718 | "tokio", 719 | "tower-service", 720 | ] 721 | 722 | [[package]] 723 | name = "hyper-util" 724 | version = "0.1.6" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" 727 | dependencies = [ 728 | "bytes", 729 | "futures-channel", 730 | "futures-util", 731 | "http", 732 | "http-body", 733 | "hyper", 734 | "pin-project-lite", 735 | "socket2", 736 | "tokio", 737 | "tower", 738 | "tower-service", 739 | "tracing", 740 | ] 741 | 742 | [[package]] 743 | name = "idna" 744 | version = "0.5.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 747 | dependencies = [ 748 | "unicode-bidi", 749 | "unicode-normalization", 750 | ] 751 | 752 | [[package]] 753 | name = "indexmap" 754 | version = "1.9.3" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 757 | dependencies = [ 758 | "autocfg", 759 | "hashbrown 0.12.3", 760 | ] 761 | 762 | [[package]] 763 | name = "indexmap" 764 | version = "2.2.6" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 767 | dependencies = [ 768 | "equivalent", 769 | "hashbrown 0.14.5", 770 | ] 771 | 772 | [[package]] 773 | name = "ipnet" 774 | version = "2.9.0" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 777 | 778 | [[package]] 779 | name = "ipnetwork" 780 | version = "0.20.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" 783 | dependencies = [ 784 | "serde", 785 | ] 786 | 787 | [[package]] 788 | name = "is_terminal_polyfill" 789 | version = "1.70.0" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 792 | 793 | [[package]] 794 | name = "itertools" 795 | version = "0.12.1" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 798 | dependencies = [ 799 | "either", 800 | ] 801 | 802 | [[package]] 803 | name = "itoa" 804 | version = "1.0.11" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 807 | 808 | [[package]] 809 | name = "jobserver" 810 | version = "0.1.31" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" 813 | dependencies = [ 814 | "libc", 815 | ] 816 | 817 | [[package]] 818 | name = "js-sys" 819 | version = "0.3.69" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 822 | dependencies = [ 823 | "wasm-bindgen", 824 | ] 825 | 826 | [[package]] 827 | name = "lazy_static" 828 | version = "1.5.0" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 831 | 832 | [[package]] 833 | name = "lazycell" 834 | version = "1.3.0" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 837 | 838 | [[package]] 839 | name = "libc" 840 | version = "0.2.155" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 843 | 844 | [[package]] 845 | name = "libloading" 846 | version = "0.8.4" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" 849 | dependencies = [ 850 | "cfg-if", 851 | "windows-targets 0.52.6", 852 | ] 853 | 854 | [[package]] 855 | name = "linux-raw-sys" 856 | version = "0.4.14" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 859 | 860 | [[package]] 861 | name = "lock_api" 862 | version = "0.4.12" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 865 | dependencies = [ 866 | "autocfg", 867 | "scopeguard", 868 | ] 869 | 870 | [[package]] 871 | name = "log" 872 | version = "0.4.22" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 875 | 876 | [[package]] 877 | name = "matchit" 878 | version = "0.7.3" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 881 | 882 | [[package]] 883 | name = "memchr" 884 | version = "2.7.4" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 887 | 888 | [[package]] 889 | name = "mime" 890 | version = "0.3.17" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 893 | 894 | [[package]] 895 | name = "minimal-lexical" 896 | version = "0.2.1" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 899 | 900 | [[package]] 901 | name = "miniz_oxide" 902 | version = "0.7.4" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 905 | dependencies = [ 906 | "adler", 907 | ] 908 | 909 | [[package]] 910 | name = "mio" 911 | version = "0.8.11" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 914 | dependencies = [ 915 | "libc", 916 | "wasi", 917 | "windows-sys 0.48.0", 918 | ] 919 | 920 | [[package]] 921 | name = "mirai-annotations" 922 | version = "1.12.0" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" 925 | 926 | [[package]] 927 | name = "multimap" 928 | version = "0.10.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" 931 | 932 | [[package]] 933 | name = "nom" 934 | version = "7.1.3" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 937 | dependencies = [ 938 | "memchr", 939 | "minimal-lexical", 940 | ] 941 | 942 | [[package]] 943 | name = "num-conv" 944 | version = "0.1.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 947 | 948 | [[package]] 949 | name = "num_cpus" 950 | version = "1.16.0" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 953 | dependencies = [ 954 | "hermit-abi", 955 | "libc", 956 | ] 957 | 958 | [[package]] 959 | name = "num_threads" 960 | version = "0.1.7" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 963 | dependencies = [ 964 | "libc", 965 | ] 966 | 967 | [[package]] 968 | name = "object" 969 | version = "0.36.1" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" 972 | dependencies = [ 973 | "memchr", 974 | ] 975 | 976 | [[package]] 977 | name = "once_cell" 978 | version = "1.19.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 981 | 982 | [[package]] 983 | name = "parking_lot" 984 | version = "0.12.3" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 987 | dependencies = [ 988 | "lock_api", 989 | "parking_lot_core", 990 | ] 991 | 992 | [[package]] 993 | name = "parking_lot_core" 994 | version = "0.9.10" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 997 | dependencies = [ 998 | "cfg-if", 999 | "libc", 1000 | "redox_syscall", 1001 | "smallvec", 1002 | "windows-targets 0.52.6", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "paste" 1007 | version = "1.0.15" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1010 | 1011 | [[package]] 1012 | name = "percent-encoding" 1013 | version = "2.3.1" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1016 | 1017 | [[package]] 1018 | name = "petgraph" 1019 | version = "0.6.5" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 1022 | dependencies = [ 1023 | "fixedbitset", 1024 | "indexmap 2.2.6", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "pin-project" 1029 | version = "1.1.5" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1032 | dependencies = [ 1033 | "pin-project-internal", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "pin-project-internal" 1038 | version = "1.1.5" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1041 | dependencies = [ 1042 | "proc-macro2", 1043 | "quote", 1044 | "syn", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "pin-project-lite" 1049 | version = "0.2.14" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1052 | 1053 | [[package]] 1054 | name = "pin-utils" 1055 | version = "0.1.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1058 | 1059 | [[package]] 1060 | name = "powerfmt" 1061 | version = "0.2.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1064 | 1065 | [[package]] 1066 | name = "ppv-lite86" 1067 | version = "0.2.17" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1070 | 1071 | [[package]] 1072 | name = "prettyplease" 1073 | version = "0.2.20" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" 1076 | dependencies = [ 1077 | "proc-macro2", 1078 | "syn", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "proc-macro2" 1083 | version = "1.0.86" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1086 | dependencies = [ 1087 | "unicode-ident", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "prost" 1092 | version = "0.13.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" 1095 | dependencies = [ 1096 | "bytes", 1097 | "prost-derive", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "prost-build" 1102 | version = "0.13.0" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "04952a6dc0bf60a696e83fc8c58e912d4f6c1d06c6637db44c1cce9c6735c920" 1105 | dependencies = [ 1106 | "bytes", 1107 | "heck", 1108 | "itertools", 1109 | "log", 1110 | "multimap", 1111 | "once_cell", 1112 | "petgraph", 1113 | "prettyplease", 1114 | "prost", 1115 | "prost-types", 1116 | "regex", 1117 | "syn", 1118 | "tempfile", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "prost-derive" 1123 | version = "0.13.1" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" 1126 | dependencies = [ 1127 | "anyhow", 1128 | "itertools", 1129 | "proc-macro2", 1130 | "quote", 1131 | "syn", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "prost-types" 1136 | version = "0.13.0" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "36c1964ef64b94480df8c92ae14e5764d470014ad951d2f99e5a6c0c7712710c" 1139 | dependencies = [ 1140 | "prost", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "quinn" 1145 | version = "0.11.2" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" 1148 | dependencies = [ 1149 | "bytes", 1150 | "pin-project-lite", 1151 | "quinn-proto", 1152 | "quinn-udp", 1153 | "rustc-hash", 1154 | "rustls", 1155 | "thiserror", 1156 | "tokio", 1157 | "tracing", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "quinn-proto" 1162 | version = "0.11.3" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" 1165 | dependencies = [ 1166 | "bytes", 1167 | "rand 0.8.5", 1168 | "ring", 1169 | "rustc-hash", 1170 | "rustls", 1171 | "slab", 1172 | "thiserror", 1173 | "tinyvec", 1174 | "tracing", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "quinn-udp" 1179 | version = "0.5.2" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" 1182 | dependencies = [ 1183 | "libc", 1184 | "once_cell", 1185 | "socket2", 1186 | "tracing", 1187 | "windows-sys 0.52.0", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "quote" 1192 | version = "1.0.36" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1195 | dependencies = [ 1196 | "proc-macro2", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "rand" 1201 | version = "0.8.5" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1204 | dependencies = [ 1205 | "libc", 1206 | "rand_chacha 0.3.1", 1207 | "rand_core 0.6.4", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "rand" 1212 | version = "0.9.0-alpha.1" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "8d31e63ea85be51c423e52ba8f2e68a3efd53eed30203ee029dd09947333693e" 1215 | dependencies = [ 1216 | "rand_chacha 0.9.0-alpha.1", 1217 | "rand_core 0.9.0-alpha.1", 1218 | "zerocopy", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "rand_chacha" 1223 | version = "0.3.1" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1226 | dependencies = [ 1227 | "ppv-lite86", 1228 | "rand_core 0.6.4", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "rand_chacha" 1233 | version = "0.9.0-alpha.1" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "78674ef918c19451dbd250f8201f8619b494f64c9aa6f3adb28fd8a0f1f6da46" 1236 | dependencies = [ 1237 | "ppv-lite86", 1238 | "rand_core 0.9.0-alpha.1", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "rand_core" 1243 | version = "0.6.4" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1246 | dependencies = [ 1247 | "getrandom", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "rand_core" 1252 | version = "0.9.0-alpha.1" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "cc89dffba8377c5ec847d12bb41492bda235dba31a25e8b695cd0fe6589eb8c9" 1255 | dependencies = [ 1256 | "getrandom", 1257 | "zerocopy", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "redox_syscall" 1262 | version = "0.5.2" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" 1265 | dependencies = [ 1266 | "bitflags", 1267 | ] 1268 | 1269 | [[package]] 1270 | name = "regex" 1271 | version = "1.10.5" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 1274 | dependencies = [ 1275 | "aho-corasick", 1276 | "memchr", 1277 | "regex-automata", 1278 | "regex-syntax", 1279 | ] 1280 | 1281 | [[package]] 1282 | name = "regex-automata" 1283 | version = "0.4.7" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1286 | dependencies = [ 1287 | "aho-corasick", 1288 | "memchr", 1289 | "regex-syntax", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "regex-syntax" 1294 | version = "0.8.4" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1297 | 1298 | [[package]] 1299 | name = "reqwest" 1300 | version = "0.12.5" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" 1303 | dependencies = [ 1304 | "base64", 1305 | "bytes", 1306 | "futures-channel", 1307 | "futures-core", 1308 | "futures-util", 1309 | "http", 1310 | "http-body", 1311 | "http-body-util", 1312 | "hyper", 1313 | "hyper-rustls", 1314 | "hyper-util", 1315 | "ipnet", 1316 | "js-sys", 1317 | "log", 1318 | "mime", 1319 | "once_cell", 1320 | "percent-encoding", 1321 | "pin-project-lite", 1322 | "quinn", 1323 | "rustls", 1324 | "rustls-pemfile", 1325 | "rustls-pki-types", 1326 | "serde", 1327 | "serde_json", 1328 | "serde_urlencoded", 1329 | "sync_wrapper 1.0.1", 1330 | "tokio", 1331 | "tokio-rustls", 1332 | "tower-service", 1333 | "url", 1334 | "wasm-bindgen", 1335 | "wasm-bindgen-futures", 1336 | "web-sys", 1337 | "webpki-roots", 1338 | "winreg", 1339 | ] 1340 | 1341 | [[package]] 1342 | name = "ring" 1343 | version = "0.17.8" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1346 | dependencies = [ 1347 | "cc", 1348 | "cfg-if", 1349 | "getrandom", 1350 | "libc", 1351 | "spin", 1352 | "untrusted", 1353 | "windows-sys 0.52.0", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "rustc-demangle" 1358 | version = "0.1.24" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1361 | 1362 | [[package]] 1363 | name = "rustc-hash" 1364 | version = "1.1.0" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1367 | 1368 | [[package]] 1369 | name = "rustix" 1370 | version = "0.38.34" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1373 | dependencies = [ 1374 | "bitflags", 1375 | "errno", 1376 | "libc", 1377 | "linux-raw-sys", 1378 | "windows-sys 0.52.0", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "rustls" 1383 | version = "0.23.11" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" 1386 | dependencies = [ 1387 | "aws-lc-rs", 1388 | "log", 1389 | "once_cell", 1390 | "ring", 1391 | "rustls-pki-types", 1392 | "rustls-webpki", 1393 | "subtle", 1394 | "zeroize", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "rustls-pemfile" 1399 | version = "2.1.2" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 1402 | dependencies = [ 1403 | "base64", 1404 | "rustls-pki-types", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "rustls-pki-types" 1409 | version = "1.7.0" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" 1412 | 1413 | [[package]] 1414 | name = "rustls-webpki" 1415 | version = "0.102.5" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" 1418 | dependencies = [ 1419 | "aws-lc-rs", 1420 | "ring", 1421 | "rustls-pki-types", 1422 | "untrusted", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "rustversion" 1427 | version = "1.0.17" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 1430 | 1431 | [[package]] 1432 | name = "ryu" 1433 | version = "1.0.18" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1436 | 1437 | [[package]] 1438 | name = "scopeguard" 1439 | version = "1.2.0" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1442 | 1443 | [[package]] 1444 | name = "serde" 1445 | version = "1.0.203" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 1448 | dependencies = [ 1449 | "serde_derive", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "serde_derive" 1454 | version = "1.0.203" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 1457 | dependencies = [ 1458 | "proc-macro2", 1459 | "quote", 1460 | "syn", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "serde_json" 1465 | version = "1.0.120" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" 1468 | dependencies = [ 1469 | "itoa", 1470 | "ryu", 1471 | "serde", 1472 | ] 1473 | 1474 | [[package]] 1475 | name = "serde_urlencoded" 1476 | version = "0.7.1" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1479 | dependencies = [ 1480 | "form_urlencoded", 1481 | "itoa", 1482 | "ryu", 1483 | "serde", 1484 | ] 1485 | 1486 | [[package]] 1487 | name = "shlex" 1488 | version = "1.3.0" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1491 | 1492 | [[package]] 1493 | name = "signal-hook-registry" 1494 | version = "1.4.2" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1497 | dependencies = [ 1498 | "libc", 1499 | ] 1500 | 1501 | [[package]] 1502 | name = "simple_logger" 1503 | version = "5.0.0" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" 1506 | dependencies = [ 1507 | "colored", 1508 | "log", 1509 | "time", 1510 | "windows-sys 0.48.0", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "slab" 1515 | version = "0.4.9" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1518 | dependencies = [ 1519 | "autocfg", 1520 | ] 1521 | 1522 | [[package]] 1523 | name = "smallvec" 1524 | version = "1.13.2" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1527 | 1528 | [[package]] 1529 | name = "socket2" 1530 | version = "0.5.7" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1533 | dependencies = [ 1534 | "libc", 1535 | "windows-sys 0.52.0", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "spin" 1540 | version = "0.9.8" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1543 | 1544 | [[package]] 1545 | name = "strsim" 1546 | version = "0.11.1" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1549 | 1550 | [[package]] 1551 | name = "subtle" 1552 | version = "2.6.1" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1555 | 1556 | [[package]] 1557 | name = "syn" 1558 | version = "2.0.68" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" 1561 | dependencies = [ 1562 | "proc-macro2", 1563 | "quote", 1564 | "unicode-ident", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "sync_wrapper" 1569 | version = "0.1.2" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1572 | 1573 | [[package]] 1574 | name = "sync_wrapper" 1575 | version = "1.0.1" 1576 | source = "registry+https://github.com/rust-lang/crates.io-index" 1577 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 1578 | 1579 | [[package]] 1580 | name = "tempfile" 1581 | version = "3.10.1" 1582 | source = "registry+https://github.com/rust-lang/crates.io-index" 1583 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 1584 | dependencies = [ 1585 | "cfg-if", 1586 | "fastrand", 1587 | "rustix", 1588 | "windows-sys 0.52.0", 1589 | ] 1590 | 1591 | [[package]] 1592 | name = "thiserror" 1593 | version = "1.0.61" 1594 | source = "registry+https://github.com/rust-lang/crates.io-index" 1595 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1596 | dependencies = [ 1597 | "thiserror-impl", 1598 | ] 1599 | 1600 | [[package]] 1601 | name = "thiserror-impl" 1602 | version = "1.0.61" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1605 | dependencies = [ 1606 | "proc-macro2", 1607 | "quote", 1608 | "syn", 1609 | ] 1610 | 1611 | [[package]] 1612 | name = "time" 1613 | version = "0.3.36" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1616 | dependencies = [ 1617 | "deranged", 1618 | "itoa", 1619 | "libc", 1620 | "num-conv", 1621 | "num_threads", 1622 | "powerfmt", 1623 | "serde", 1624 | "time-core", 1625 | "time-macros", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "time-core" 1630 | version = "0.1.2" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1633 | 1634 | [[package]] 1635 | name = "time-macros" 1636 | version = "0.2.18" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1639 | dependencies = [ 1640 | "num-conv", 1641 | "time-core", 1642 | ] 1643 | 1644 | [[package]] 1645 | name = "tinyvec" 1646 | version = "1.7.0" 1647 | source = "registry+https://github.com/rust-lang/crates.io-index" 1648 | checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" 1649 | dependencies = [ 1650 | "tinyvec_macros", 1651 | ] 1652 | 1653 | [[package]] 1654 | name = "tinyvec_macros" 1655 | version = "0.1.1" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1658 | 1659 | [[package]] 1660 | name = "tokio" 1661 | version = "1.38.0" 1662 | source = "registry+https://github.com/rust-lang/crates.io-index" 1663 | checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 1664 | dependencies = [ 1665 | "backtrace", 1666 | "bytes", 1667 | "libc", 1668 | "mio", 1669 | "num_cpus", 1670 | "parking_lot", 1671 | "pin-project-lite", 1672 | "signal-hook-registry", 1673 | "socket2", 1674 | "tokio-macros", 1675 | "windows-sys 0.48.0", 1676 | ] 1677 | 1678 | [[package]] 1679 | name = "tokio-macros" 1680 | version = "2.3.0" 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" 1682 | checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" 1683 | dependencies = [ 1684 | "proc-macro2", 1685 | "quote", 1686 | "syn", 1687 | ] 1688 | 1689 | [[package]] 1690 | name = "tokio-rustls" 1691 | version = "0.26.0" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1694 | dependencies = [ 1695 | "rustls", 1696 | "rustls-pki-types", 1697 | "tokio", 1698 | ] 1699 | 1700 | [[package]] 1701 | name = "tokio-stream" 1702 | version = "0.1.15" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" 1705 | dependencies = [ 1706 | "futures-core", 1707 | "pin-project-lite", 1708 | "tokio", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "tokio-util" 1713 | version = "0.7.11" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" 1716 | dependencies = [ 1717 | "bytes", 1718 | "futures-core", 1719 | "futures-sink", 1720 | "pin-project-lite", 1721 | "tokio", 1722 | ] 1723 | 1724 | [[package]] 1725 | name = "tonic" 1726 | version = "0.12.0" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "f738b6a169a29bca4e39656db89c44a08e09c5b700b896ee9e7459f0652e81dd" 1729 | dependencies = [ 1730 | "async-stream", 1731 | "async-trait", 1732 | "axum", 1733 | "base64", 1734 | "bytes", 1735 | "h2", 1736 | "http", 1737 | "http-body", 1738 | "http-body-util", 1739 | "hyper", 1740 | "hyper-timeout", 1741 | "hyper-util", 1742 | "percent-encoding", 1743 | "pin-project", 1744 | "prost", 1745 | "socket2", 1746 | "tokio", 1747 | "tokio-stream", 1748 | "tower", 1749 | "tower-layer", 1750 | "tower-service", 1751 | "tracing", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "tonic-build" 1756 | version = "0.12.0" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "690943cc223adcdd67bb597a2e573ead1b88e999ba37528fe8e6356bf44b29b6" 1759 | dependencies = [ 1760 | "prettyplease", 1761 | "proc-macro2", 1762 | "prost-build", 1763 | "quote", 1764 | "syn", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "tower" 1769 | version = "0.4.13" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1772 | dependencies = [ 1773 | "futures-core", 1774 | "futures-util", 1775 | "indexmap 1.9.3", 1776 | "pin-project", 1777 | "pin-project-lite", 1778 | "rand 0.8.5", 1779 | "slab", 1780 | "tokio", 1781 | "tokio-util", 1782 | "tower-layer", 1783 | "tower-service", 1784 | "tracing", 1785 | ] 1786 | 1787 | [[package]] 1788 | name = "tower-layer" 1789 | version = "0.3.2" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1792 | 1793 | [[package]] 1794 | name = "tower-service" 1795 | version = "0.3.2" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1798 | 1799 | [[package]] 1800 | name = "tracing" 1801 | version = "0.1.40" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1804 | dependencies = [ 1805 | "pin-project-lite", 1806 | "tracing-attributes", 1807 | "tracing-core", 1808 | ] 1809 | 1810 | [[package]] 1811 | name = "tracing-attributes" 1812 | version = "0.1.27" 1813 | source = "registry+https://github.com/rust-lang/crates.io-index" 1814 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1815 | dependencies = [ 1816 | "proc-macro2", 1817 | "quote", 1818 | "syn", 1819 | ] 1820 | 1821 | [[package]] 1822 | name = "tracing-core" 1823 | version = "0.1.32" 1824 | source = "registry+https://github.com/rust-lang/crates.io-index" 1825 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1826 | dependencies = [ 1827 | "once_cell", 1828 | ] 1829 | 1830 | [[package]] 1831 | name = "try-lock" 1832 | version = "0.2.5" 1833 | source = "registry+https://github.com/rust-lang/crates.io-index" 1834 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1835 | 1836 | [[package]] 1837 | name = "unicode-bidi" 1838 | version = "0.3.15" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1841 | 1842 | [[package]] 1843 | name = "unicode-ident" 1844 | version = "1.0.12" 1845 | source = "registry+https://github.com/rust-lang/crates.io-index" 1846 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1847 | 1848 | [[package]] 1849 | name = "unicode-normalization" 1850 | version = "0.1.23" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1853 | dependencies = [ 1854 | "tinyvec", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "untrusted" 1859 | version = "0.9.0" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1862 | 1863 | [[package]] 1864 | name = "url" 1865 | version = "2.5.2" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1868 | dependencies = [ 1869 | "form_urlencoded", 1870 | "idna", 1871 | "percent-encoding", 1872 | ] 1873 | 1874 | [[package]] 1875 | name = "utf8parse" 1876 | version = "0.2.2" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1879 | 1880 | [[package]] 1881 | name = "uuid" 1882 | version = "1.10.0" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" 1885 | dependencies = [ 1886 | "getrandom", 1887 | ] 1888 | 1889 | [[package]] 1890 | name = "want" 1891 | version = "0.3.1" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1894 | dependencies = [ 1895 | "try-lock", 1896 | ] 1897 | 1898 | [[package]] 1899 | name = "wasi" 1900 | version = "0.11.0+wasi-snapshot-preview1" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1903 | 1904 | [[package]] 1905 | name = "wasm-bindgen" 1906 | version = "0.2.92" 1907 | source = "registry+https://github.com/rust-lang/crates.io-index" 1908 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1909 | dependencies = [ 1910 | "cfg-if", 1911 | "wasm-bindgen-macro", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "wasm-bindgen-backend" 1916 | version = "0.2.92" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1919 | dependencies = [ 1920 | "bumpalo", 1921 | "log", 1922 | "once_cell", 1923 | "proc-macro2", 1924 | "quote", 1925 | "syn", 1926 | "wasm-bindgen-shared", 1927 | ] 1928 | 1929 | [[package]] 1930 | name = "wasm-bindgen-futures" 1931 | version = "0.4.42" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 1934 | dependencies = [ 1935 | "cfg-if", 1936 | "js-sys", 1937 | "wasm-bindgen", 1938 | "web-sys", 1939 | ] 1940 | 1941 | [[package]] 1942 | name = "wasm-bindgen-macro" 1943 | version = "0.2.92" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1946 | dependencies = [ 1947 | "quote", 1948 | "wasm-bindgen-macro-support", 1949 | ] 1950 | 1951 | [[package]] 1952 | name = "wasm-bindgen-macro-support" 1953 | version = "0.2.92" 1954 | source = "registry+https://github.com/rust-lang/crates.io-index" 1955 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1956 | dependencies = [ 1957 | "proc-macro2", 1958 | "quote", 1959 | "syn", 1960 | "wasm-bindgen-backend", 1961 | "wasm-bindgen-shared", 1962 | ] 1963 | 1964 | [[package]] 1965 | name = "wasm-bindgen-shared" 1966 | version = "0.2.92" 1967 | source = "registry+https://github.com/rust-lang/crates.io-index" 1968 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1969 | 1970 | [[package]] 1971 | name = "web-sys" 1972 | version = "0.3.69" 1973 | source = "registry+https://github.com/rust-lang/crates.io-index" 1974 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1975 | dependencies = [ 1976 | "js-sys", 1977 | "wasm-bindgen", 1978 | ] 1979 | 1980 | [[package]] 1981 | name = "webpki-roots" 1982 | version = "0.26.3" 1983 | source = "registry+https://github.com/rust-lang/crates.io-index" 1984 | checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" 1985 | dependencies = [ 1986 | "rustls-pki-types", 1987 | ] 1988 | 1989 | [[package]] 1990 | name = "which" 1991 | version = "4.4.2" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1994 | dependencies = [ 1995 | "either", 1996 | "home", 1997 | "once_cell", 1998 | "rustix", 1999 | ] 2000 | 2001 | [[package]] 2002 | name = "windows-sys" 2003 | version = "0.48.0" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2006 | dependencies = [ 2007 | "windows-targets 0.48.5", 2008 | ] 2009 | 2010 | [[package]] 2011 | name = "windows-sys" 2012 | version = "0.52.0" 2013 | source = "registry+https://github.com/rust-lang/crates.io-index" 2014 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2015 | dependencies = [ 2016 | "windows-targets 0.52.6", 2017 | ] 2018 | 2019 | [[package]] 2020 | name = "windows-targets" 2021 | version = "0.48.5" 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" 2023 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2024 | dependencies = [ 2025 | "windows_aarch64_gnullvm 0.48.5", 2026 | "windows_aarch64_msvc 0.48.5", 2027 | "windows_i686_gnu 0.48.5", 2028 | "windows_i686_msvc 0.48.5", 2029 | "windows_x86_64_gnu 0.48.5", 2030 | "windows_x86_64_gnullvm 0.48.5", 2031 | "windows_x86_64_msvc 0.48.5", 2032 | ] 2033 | 2034 | [[package]] 2035 | name = "windows-targets" 2036 | version = "0.52.6" 2037 | source = "registry+https://github.com/rust-lang/crates.io-index" 2038 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2039 | dependencies = [ 2040 | "windows_aarch64_gnullvm 0.52.6", 2041 | "windows_aarch64_msvc 0.52.6", 2042 | "windows_i686_gnu 0.52.6", 2043 | "windows_i686_gnullvm", 2044 | "windows_i686_msvc 0.52.6", 2045 | "windows_x86_64_gnu 0.52.6", 2046 | "windows_x86_64_gnullvm 0.52.6", 2047 | "windows_x86_64_msvc 0.52.6", 2048 | ] 2049 | 2050 | [[package]] 2051 | name = "windows_aarch64_gnullvm" 2052 | version = "0.48.5" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2055 | 2056 | [[package]] 2057 | name = "windows_aarch64_gnullvm" 2058 | version = "0.52.6" 2059 | source = "registry+https://github.com/rust-lang/crates.io-index" 2060 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2061 | 2062 | [[package]] 2063 | name = "windows_aarch64_msvc" 2064 | version = "0.48.5" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2067 | 2068 | [[package]] 2069 | name = "windows_aarch64_msvc" 2070 | version = "0.52.6" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2073 | 2074 | [[package]] 2075 | name = "windows_i686_gnu" 2076 | version = "0.48.5" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2079 | 2080 | [[package]] 2081 | name = "windows_i686_gnu" 2082 | version = "0.52.6" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2085 | 2086 | [[package]] 2087 | name = "windows_i686_gnullvm" 2088 | version = "0.52.6" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2091 | 2092 | [[package]] 2093 | name = "windows_i686_msvc" 2094 | version = "0.48.5" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2097 | 2098 | [[package]] 2099 | name = "windows_i686_msvc" 2100 | version = "0.52.6" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2103 | 2104 | [[package]] 2105 | name = "windows_x86_64_gnu" 2106 | version = "0.48.5" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2109 | 2110 | [[package]] 2111 | name = "windows_x86_64_gnu" 2112 | version = "0.52.6" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2115 | 2116 | [[package]] 2117 | name = "windows_x86_64_gnullvm" 2118 | version = "0.48.5" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2121 | 2122 | [[package]] 2123 | name = "windows_x86_64_gnullvm" 2124 | version = "0.52.6" 2125 | source = "registry+https://github.com/rust-lang/crates.io-index" 2126 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2127 | 2128 | [[package]] 2129 | name = "windows_x86_64_msvc" 2130 | version = "0.48.5" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2133 | 2134 | [[package]] 2135 | name = "windows_x86_64_msvc" 2136 | version = "0.52.6" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2139 | 2140 | [[package]] 2141 | name = "winreg" 2142 | version = "0.52.0" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" 2145 | dependencies = [ 2146 | "cfg-if", 2147 | "windows-sys 0.48.0", 2148 | ] 2149 | 2150 | [[package]] 2151 | name = "zerocopy" 2152 | version = "0.8.0-alpha.6" 2153 | source = "registry+https://github.com/rust-lang/crates.io-index" 2154 | checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57" 2155 | dependencies = [ 2156 | "zerocopy-derive", 2157 | ] 2158 | 2159 | [[package]] 2160 | name = "zerocopy-derive" 2161 | version = "0.8.0-alpha.6" 2162 | source = "registry+https://github.com/rust-lang/crates.io-index" 2163 | checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62" 2164 | dependencies = [ 2165 | "proc-macro2", 2166 | "quote", 2167 | "syn", 2168 | ] 2169 | 2170 | [[package]] 2171 | name = "zeroize" 2172 | version = "1.8.1" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2175 | dependencies = [ 2176 | "zeroize_derive", 2177 | ] 2178 | 2179 | [[package]] 2180 | name = "zeroize_derive" 2181 | version = "1.4.2" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 2184 | dependencies = [ 2185 | "proc-macro2", 2186 | "quote", 2187 | "syn", 2188 | ] 2189 | --------------------------------------------------------------------------------