├── .gitignore ├── src ├── lib.rs ├── register.rs ├── bin │ └── demo.rs ├── error.rs ├── firebase.rs ├── proto │ ├── android_checkin.proto │ ├── checkin.proto │ └── mcs.proto ├── fcm.rs ├── gcm.rs └── push.rs ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod mcs { 2 | include!(concat!(env!("OUT_DIR"), "/mcs_proto.rs")); 3 | } 4 | 5 | mod error; 6 | mod fcm; 7 | mod firebase; 8 | mod gcm; 9 | mod push; 10 | mod register; 11 | 12 | pub use error::Error; 13 | pub use fcm::WebPushKeys; 14 | pub use gcm::Session; 15 | pub use push::new_heartbeat_ack; 16 | pub use push::DataMessage; 17 | pub use push::Message; 18 | pub use push::MessageStream; 19 | pub use push::MessageTag; 20 | pub use register::register; 21 | pub use register::Registration; 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fcm-push-listener" 3 | version = "4.0.2" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Listen for push messages from Firebase Cloud Messaging (FCM)." 7 | repository = "https://github.com/RandomEngy/fcm-push-listener" 8 | keywords = ["push", "fcm"] 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | base64 = "0.22" 14 | bytes = "1.10" 15 | ece = "2.3.1" 16 | log = "0.4" 17 | pin-project-lite = "0.2.16" 18 | prost = "0.13.5" 19 | rand = "0.9" 20 | reqwest = { version = "0.12", features = ["json"] } 21 | rustls = { version = "0.23", features = ["ring"] } 22 | serde = "1.0" 23 | serde_with = "3.12" 24 | tokio = { version = "1", default-features = false, features = [ 25 | "macros", 26 | "rt-multi-thread", 27 | "net", 28 | ] } 29 | tokio-rustls = "0.26.2" 30 | tokio-stream = "0.1" 31 | webpki-roots = "0.26.8" 32 | 33 | [dependencies.uuid] 34 | version = "1.15" 35 | features = [ 36 | "v4", # Lets you generate random UUIDs 37 | "fast-rng", # Use a faster (but still sufficiently random) RNG 38 | "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs 39 | ] 40 | 41 | [build-dependencies] 42 | prost-build = "0.13.5" 43 | -------------------------------------------------------------------------------- /src/register.rs: -------------------------------------------------------------------------------- 1 | use crate::{fcm, firebase, gcm, Error}; 2 | use serde::Deserialize; 3 | use serde::Serialize; 4 | use uuid::Uuid; 5 | 6 | #[derive(Clone, Serialize, Deserialize)] 7 | pub struct Registration { 8 | pub fcm_token: String, 9 | pub gcm: gcm::Session, 10 | pub keys: fcm::WebPushKeys, 11 | } 12 | 13 | pub async fn register( 14 | http: &reqwest::Client, 15 | firebase_app_id: &str, 16 | firebase_project_id: &str, 17 | firebase_api_key: &str, 18 | vapid_key: Option<&str>, 19 | ) -> Result { 20 | log::debug!("Checking in to GCM"); 21 | let gcm_session = gcm::Session::create(http).await?; 22 | 23 | let id = Uuid::new_v4(); 24 | let gcm_app_id = format!("wp:receiver.push.com#{id}"); 25 | 26 | log::debug!("Registering to GCM"); 27 | let gcm_token = gcm_session.request_token(&gcm_app_id).await?; 28 | 29 | log::debug!("Getting Firebase installation token"); 30 | let firebase_installation_token = firebase::InstallationAuthToken::request( 31 | http, 32 | firebase_app_id, 33 | firebase_project_id, 34 | firebase_api_key, 35 | ) 36 | .await?; 37 | 38 | log::debug!("Calling FCM register"); 39 | let fcm_register_result = fcm::Registration::request( 40 | http, 41 | firebase_project_id, 42 | firebase_api_key, 43 | vapid_key, 44 | &firebase_installation_token.value, 45 | &gcm_token, 46 | ) 47 | .await?; 48 | 49 | log::debug!("Registration complete"); 50 | 51 | Ok(Registration { 52 | gcm: gcm_session, 53 | fcm_token: fcm_register_result.fcm_token, 54 | keys: fcm_register_result.keys, 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /src/bin/demo.rs: -------------------------------------------------------------------------------- 1 | pub use fcm_push_listener::Error; 2 | use fcm_push_listener::{new_heartbeat_ack, MessageStream, Registration, Session as GcmSession, WebPushKeys}; 3 | use tokio::io::AsyncWriteExt; 4 | 5 | async fn run(registration: Registration) -> Result<(), fcm_push_listener::Error> { 6 | use tokio_stream::StreamExt; 7 | 8 | let http = reqwest::Client::new(); 9 | let session = registration.gcm.checkin(&http).await?; 10 | let connection = session.new_connection(vec![]).await?; 11 | let mut stream = MessageStream::wrap(connection, ®istration.keys); 12 | 13 | while let Some(message) = stream.next().await { 14 | match message? { 15 | fcm_push_listener::Message::Data(data) => { 16 | println!("Message {:?} Data: {:?}", data.persistent_id, data.body); 17 | } 18 | fcm_push_listener::Message::HeartbeatPing => { 19 | println!("Heartbeat"); 20 | let result = stream.write_all(&new_heartbeat_ack()).await; 21 | if let Err(e) = result { 22 | println!("Error sending heartbeat ack: {:?}", e); 23 | } 24 | } 25 | fcm_push_listener::Message::Other(tag, bytes) => { 26 | println!("Got non-data message: {tag:?}, {bytes:?}"); 27 | } 28 | } 29 | } 30 | 31 | Ok(()) 32 | } 33 | 34 | #[tokio::main] 35 | async fn main() { 36 | use std::io::Read; 37 | 38 | let registration = Registration { 39 | fcm_token: "abc".to_owned(), 40 | gcm: GcmSession { 41 | android_id: 123, 42 | security_token: 456, 43 | }, 44 | keys: WebPushKeys { 45 | auth_secret: vec![], 46 | private_key: vec![], 47 | public_key: vec![], 48 | }, 49 | }; 50 | 51 | tokio::spawn(run(registration)); 52 | 53 | println!("Listening for push messages. Press any key to exit"); 54 | let mut buf = [0u8; 1]; 55 | let _ = std::io::stdin().read(&mut buf).expect("read error"); 56 | } 57 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | 3 | #[derive(Debug)] 4 | pub enum Error { 5 | /// Dependency failed, i.e. we blame them 6 | DependencyFailure(&'static str, &'static str), 7 | /// Dependency rejection, i.e. they blame us 8 | DependencyRejection(&'static str, String), 9 | /// Received an encrypted message with no decryption params 10 | MissingCryptoMetadata(&'static str), 11 | /// Protobuf deserialization failure, probably a contract change 12 | ProtobufDecode(&'static str, prost::DecodeError), 13 | EmptyPayload, 14 | Request(&'static str, reqwest::Error), 15 | Response(&'static str, reqwest::Error), 16 | Base64Decode(&'static str, base64::DecodeError), 17 | Crypto(&'static str, ece::Error), 18 | Socket(std::io::Error), 19 | } 20 | 21 | impl std::fmt::Display for Error { 22 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 23 | match self { 24 | Self::DependencyFailure(api, problem) => write!(f, "{api} API {problem}"), 25 | Self::DependencyRejection(api, reason) => { 26 | write!(f, "{api} API rejected request: {reason}") 27 | } 28 | Self::MissingCryptoMetadata(kind) => write!(f, "Missing {kind} metadata on message"), 29 | Self::ProtobufDecode(kind, e) => write!(f, "Error decoding {kind}: {e}"), 30 | Self::EmptyPayload => write!(f, "Received a data message with no payload"), 31 | Self::Base64Decode(kind, e) => write!(f, "Error decoding {kind}: {e}"), 32 | Self::Request(kind, e) => write!(f, "{kind} API request error: {e}"), 33 | Self::Response(kind, e) => write!(f, "{kind} API response error: {e}"), 34 | Self::Crypto(kind, e) => write!(f, "Crypto {kind} error: {e}"), 35 | Self::Socket(e) => write!(f, "TCP error: {e}"), 36 | } 37 | } 38 | } 39 | 40 | impl std::error::Error for Error { 41 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 42 | match *self { 43 | Self::DependencyFailure(_, _) => None, 44 | Self::DependencyRejection(_, _) => None, 45 | Self::MissingCryptoMetadata(_) => None, 46 | Self::ProtobufDecode(_, ref e) => Some(e), 47 | Self::EmptyPayload => None, 48 | Self::Base64Decode(_, ref e) => Some(e), 49 | Self::Request(_, ref e) => Some(e), 50 | Self::Response(_, ref e) => Some(e), 51 | Self::Crypto(_, ref e) => Some(e), 52 | Self::Socket(ref e) => Some(e), 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/firebase.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | const INSTALL_API: &str = "https://firebaseinstallations.googleapis.com/v1"; 5 | 6 | #[derive(Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | struct InstallationRequest<'a> { 9 | app_id: &'a str, 10 | auth_version: &'a str, 11 | fid: &'a str, 12 | sdk_version: &'a str, 13 | } 14 | 15 | #[derive(Deserialize)] 16 | #[serde(rename_all = "camelCase")] 17 | struct InstallationResponse { 18 | auth_token: InstallationAuthToken, 19 | // fid: String, 20 | // name: String, 21 | // refresh_token: String, 22 | } 23 | 24 | #[derive(Deserialize)] 25 | #[serde(rename_all = "camelCase")] 26 | pub struct InstallationAuthToken { 27 | // expires_in: String, 28 | #[serde(rename = "token")] 29 | pub value: String, 30 | } 31 | 32 | impl InstallationAuthToken { 33 | pub async fn request( 34 | http: &reqwest::Client, 35 | application_id: &str, 36 | project_id: &str, 37 | api_key: &str, 38 | ) -> Result { 39 | use base64::engine::general_purpose::URL_SAFE_NO_PAD as Base64; 40 | use base64::engine::Engine; 41 | 42 | let fid = generate_fid(); 43 | 44 | let request = InstallationRequest { 45 | app_id: application_id, 46 | auth_version: "FIS_v2", 47 | fid: &fid, 48 | sdk_version: "w:0.6.4", 49 | }; 50 | 51 | let heartbeat_json = "{\"heartbeats\": [], \"version\": 2}"; 52 | let heartbeat_header_value = Base64.encode(heartbeat_json.as_bytes()); 53 | 54 | const API: &str = "Firebase installation"; 55 | 56 | let response = http 57 | .post(format!("{INSTALL_API}/projects/{project_id}/installations")) 58 | .json(&request) 59 | .header("x-firebase-client", heartbeat_header_value) 60 | .header("x-goog-api-key", api_key) 61 | .send() 62 | .await 63 | .map_err(|e| Error::Request(API, e))?; 64 | 65 | let response: InstallationResponse = 66 | response.json().await.map_err(|e| Error::Response(API, e))?; 67 | 68 | Ok(response.auth_token) 69 | } 70 | } 71 | 72 | fn generate_fid() -> String { 73 | use base64::engine::general_purpose::URL_SAFE_NO_PAD as Base64; 74 | use base64::engine::Engine; 75 | use rand::RngCore; 76 | 77 | let mut fid: [u8; 17] = [0; 17]; 78 | rand::rng().fill_bytes(&mut fid); 79 | fid[0] = 0b01110000 + (fid[0] % 0b00010000); 80 | Base64.encode(fid) 81 | } 82 | -------------------------------------------------------------------------------- /src/proto/android_checkin.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | // 5 | // Logging information for Android "checkin" events (automatic, periodic 6 | // requests made by Android devices to the server). 7 | 8 | syntax = "proto2"; 9 | 10 | option optimize_for = LITE_RUNTIME; 11 | package checkin_proto; 12 | 13 | // Build characteristics unique to the Chrome browser, and Chrome OS 14 | message ChromeBuildProto { 15 | enum Platform { 16 | PLATFORM_WIN = 1; 17 | PLATFORM_MAC = 2; 18 | PLATFORM_LINUX = 3; 19 | PLATFORM_CROS = 4; 20 | PLATFORM_IOS = 5; 21 | // Just a placeholder. Likely don't need it due to the presence of the 22 | // Android GCM on phone/tablet devices. 23 | PLATFORM_ANDROID = 6; 24 | } 25 | 26 | enum Channel { 27 | CHANNEL_STABLE = 1; 28 | CHANNEL_BETA = 2; 29 | CHANNEL_DEV = 3; 30 | CHANNEL_CANARY = 4; 31 | CHANNEL_UNKNOWN = 5; // for tip of tree or custom builds 32 | } 33 | 34 | // The platform of the device. 35 | optional Platform platform = 1; 36 | 37 | // The Chrome instance's version. 38 | optional string chrome_version = 2; 39 | 40 | // The Channel (build type) of Chrome. 41 | optional Channel channel = 3; 42 | } 43 | 44 | // Information sent by the device in a "checkin" request. 45 | message AndroidCheckinProto { 46 | // Miliseconds since the Unix epoch of the device's last successful checkin. 47 | optional int64 last_checkin_msec = 2; 48 | 49 | // The current MCC+MNC of the mobile device's current cell. 50 | optional string cell_operator = 6; 51 | 52 | // The MCC+MNC of the SIM card (different from operator if the 53 | // device is roaming, for instance). 54 | optional string sim_operator = 7; 55 | 56 | // The device's current roaming state (reported starting in eclair builds). 57 | // Currently one of "{,not}mobile-{,not}roaming", if it is present at all. 58 | optional string roaming = 8; 59 | 60 | // For devices supporting multiple user profiles (which may be 61 | // supported starting in jellybean), the ordinal number of the 62 | // profile that is checking in. This is 0 for the primary profile 63 | // (which can't be changed without wiping the device), and 1,2,3,... 64 | // for additional profiles (which can be added and deleted freely). 65 | optional int32 user_number = 9; 66 | 67 | // Class of device. Indicates the type of build proto 68 | // (IosBuildProto/ChromeBuildProto/AndroidBuildProto) 69 | // That is included in this proto 70 | optional DeviceType type = 12 [default = DEVICE_ANDROID_OS]; 71 | 72 | // For devices running MCS on Chrome, build-specific characteristics 73 | // of the browser. There are no hardware aspects (except for ChromeOS). 74 | // This will only be populated for Chrome builds/ChromeOS devices 75 | optional checkin_proto.ChromeBuildProto chrome_build = 13; 76 | 77 | // Note: Some of the Android specific optional fields were skipped to limit 78 | // the protobuf definition. 79 | // Next 14 80 | } 81 | 82 | // enum values correspond to the type of device. 83 | // Used in the AndroidCheckinProto and Device proto. 84 | enum DeviceType { 85 | // Android Device 86 | DEVICE_ANDROID_OS = 1; 87 | 88 | // Apple IOS device 89 | DEVICE_IOS_OS = 2; 90 | 91 | // Chrome browser - Not Chrome OS. No hardware records. 92 | DEVICE_CHROME_BROWSER = 3; 93 | 94 | // Chrome OS 95 | DEVICE_CHROME_OS = 4; 96 | } 97 | -------------------------------------------------------------------------------- /src/fcm.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | fn to_base64(v: &[u8], serializer: S) -> Result { 5 | use base64::Engine; 6 | 7 | let str = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(v); 8 | serializer.serialize_str(&str) 9 | } 10 | 11 | fn from_base64<'de, D: serde::de::Deserializer<'de>>( 12 | deserializer: D, 13 | ) -> std::result::Result, D::Error> { 14 | use base64::Engine; 15 | 16 | <&str>::deserialize(deserializer).and_then(|s| { 17 | base64::engine::general_purpose::URL_SAFE_NO_PAD 18 | .decode(s) 19 | .map_err(serde::de::Error::custom) 20 | }) 21 | } 22 | 23 | pub struct Registration { 24 | pub fcm_token: String, 25 | pub keys: WebPushKeys, 26 | } 27 | 28 | impl Registration { 29 | pub async fn request( 30 | http: &reqwest::Client, 31 | project_id: &str, 32 | api_key: &str, 33 | application_pub_key: Option<&str>, 34 | firebase_installation_auth_token: &str, 35 | gcm_token: &str, 36 | ) -> Result { 37 | const FCM_API: &str = "https://fcm.googleapis.com/fcm"; 38 | const FCM_REGISTRATION_API: &str = "https://fcmregistrations.googleapis.com/v1"; 39 | 40 | let endpoint = format!("{FCM_API}/send/{gcm_token}"); 41 | let push_keys = WebPushKeys::new().map_err(|e| Error::Crypto("key creation", e))?; 42 | let request = RegisterRequest { 43 | web: WebRegistrationRequest { 44 | application_pub_key, 45 | endpoint: &endpoint, 46 | auth: &push_keys.auth_secret, 47 | p256dh: &push_keys.public_key, 48 | }, 49 | }; 50 | 51 | const API_NAME: &str = "FCM Registration"; 52 | const API_KEY_HEADER: &str = "x-goog-api-key"; 53 | const AUTH_HEADER: &str = "x-goog-firebase-installations-auth"; 54 | 55 | let url = format!("{FCM_REGISTRATION_API}/projects/{project_id}/registrations"); 56 | let response = http 57 | .post(url) 58 | .json(&request) 59 | .header(API_KEY_HEADER, api_key) 60 | .header(AUTH_HEADER, firebase_installation_auth_token) 61 | .send() 62 | .await 63 | .map_err(|e| Error::Request(API_NAME, e))?; 64 | 65 | let response: RegisterResponse = response 66 | .json() 67 | .await 68 | .map_err(|e| Error::Response(API_NAME, e))?; 69 | 70 | Ok(Self { 71 | fcm_token: response.token, 72 | keys: push_keys, 73 | }) 74 | } 75 | } 76 | 77 | #[derive(Serialize)] 78 | struct RegisterRequest<'a> { 79 | web: WebRegistrationRequest<'a>, 80 | } 81 | 82 | #[derive(Serialize)] 83 | #[serde(rename_all = "camelCase")] 84 | struct WebRegistrationRequest<'a> { 85 | application_pub_key: Option<&'a str>, 86 | endpoint: &'a str, 87 | 88 | #[serde(serialize_with = "to_base64")] 89 | auth: &'a [u8], 90 | 91 | #[serde(serialize_with = "to_base64")] 92 | p256dh: &'a [u8], 93 | } 94 | 95 | #[derive(Deserialize)] 96 | struct RegisterResponse { 97 | // name: String, 98 | token: String, 99 | // web: WebRegistrationResponse, 100 | } 101 | 102 | #[derive(Clone, Serialize, Deserialize)] 103 | pub struct WebPushKeys { 104 | /// Public key with URL safe base64 encoding, no padding 105 | #[serde(deserialize_with = "from_base64", serialize_with = "to_base64")] 106 | pub public_key: Vec, 107 | 108 | /// Private key with URL safe base64 encoding, no padding 109 | #[serde(deserialize_with = "from_base64", serialize_with = "to_base64")] 110 | pub private_key: Vec, 111 | 112 | /// Generated random auth secret, with URL safe base64 encoding, no padding 113 | #[serde(deserialize_with = "from_base64", serialize_with = "to_base64")] 114 | pub auth_secret: Vec, 115 | } 116 | 117 | impl WebPushKeys { 118 | fn new() -> Result { 119 | let (key_pair, auth_secret) = ece::generate_keypair_and_auth_secret()?; 120 | let components = key_pair.raw_components()?; 121 | Ok(WebPushKeys { 122 | public_key: components.public_key().into(), 123 | private_key: components.private_key().into(), 124 | auth_secret: auth_secret.into(), 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/proto/checkin.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | // 5 | // Request and reply to the "checkin server" devices poll every few hours. 6 | 7 | syntax = "proto2"; 8 | 9 | option optimize_for = LITE_RUNTIME; 10 | 11 | package checkin_proto; 12 | 13 | import "android_checkin.proto"; 14 | 15 | // A concrete name/value pair sent to the device's Gservices database. 16 | message GservicesSetting { 17 | required bytes name = 1; 18 | required bytes value = 2; 19 | } 20 | 21 | // Devices send this every few hours to tell us how they're doing. 22 | message AndroidCheckinRequest { 23 | // IMEI (used by GSM phones) is sent and stored as 15 decimal 24 | // digits; the 15th is a check digit. 25 | optional string imei = 1; // IMEI, reported but not logged. 26 | 27 | // MEID (used by CDMA phones) is sent and stored as 14 hexadecimal 28 | // digits (no check digit). 29 | optional string meid = 10; // MEID, reported but not logged. 30 | 31 | // MAC address (used by non-phone devices). 12 hexadecimal digits; 32 | // no separators (eg "0016E6513AC2", not "00:16:E6:51:3A:C2"). 33 | repeated string mac_addr = 9; // MAC address, reported but not logged. 34 | 35 | // An array parallel to mac_addr, describing the type of interface. 36 | // Currently accepted values: "wifi", "ethernet", "bluetooth". If 37 | // not present, "wifi" is assumed. 38 | repeated string mac_addr_type = 19; 39 | 40 | // Serial number (a manufacturer-defined unique hardware 41 | // identifier). Alphanumeric, case-insensitive. 42 | optional string serial_number = 16; 43 | 44 | // Older CDMA networks use an ESN (8 hex digits) instead of an MEID. 45 | optional string esn = 17; // ESN, reported but not logged 46 | 47 | optional int64 id = 2; // Android device ID, not logged 48 | optional int64 logging_id = 7; // Pseudonymous logging ID for Sawmill 49 | optional string digest = 3; // Digest of device provisioning, not logged. 50 | optional string locale = 6; // Current locale in standard (xx_XX) format 51 | required AndroidCheckinProto checkin = 4; 52 | 53 | // DEPRECATED, see AndroidCheckinProto.requested_group 54 | optional string desired_build = 5; 55 | 56 | // Blob of data from the Market app to be passed to Market API server 57 | optional string market_checkin = 8; 58 | 59 | // SID cookies of any google accounts stored on the phone. Not logged. 60 | repeated string account_cookie = 11; 61 | 62 | // Time zone. Not currently logged. 63 | optional string time_zone = 12; 64 | 65 | // Security token used to validate the checkin request. 66 | // Required for android IDs issued to Froyo+ devices, not for legacy IDs. 67 | optional fixed64 security_token = 13; 68 | 69 | // Version of checkin protocol. 70 | // 71 | // There are currently two versions: 72 | // 73 | // - version field missing: android IDs are assigned based on 74 | // hardware identifiers. unsecured in the sense that you can 75 | // "unregister" someone's phone by sending a registration request 76 | // with their IMEI/MEID/MAC. 77 | // 78 | // - version=2: android IDs are assigned randomly. The device is 79 | // sent a security token that must be included in all future 80 | // checkins for that android id. 81 | // 82 | // - version=3: same as version 2, but the 'fragment' field is 83 | // provided, and the device understands incremental updates to the 84 | // gservices table (ie, only returning the keys whose values have 85 | // changed.) 86 | // 87 | // (version=1 was skipped to avoid confusion with the "missing" 88 | // version field that is effectively version 1.) 89 | optional int32 version = 14; 90 | 91 | // OTA certs accepted by device (base-64 SHA-1 of cert files). Not 92 | // logged. 93 | repeated string ota_cert = 15; 94 | 95 | // Honeycomb and newer devices send configuration data with their checkin. 96 | // optional DeviceConfigurationProto device_configuration = 18; 97 | 98 | // A single CheckinTask on the device may lead to multiple checkin 99 | // requests if there is too much log data to upload in a single 100 | // request. For version 3 and up, this field will be filled in with 101 | // the number of the request, starting with 0. 102 | optional int32 fragment = 20; 103 | 104 | // For devices supporting multiple users, the name of the current 105 | // profile (they all check in independently, just as if they were 106 | // multiple physical devices). This may not be set, even if the 107 | // device is using multiuser. (checkin.user_number should be set to 108 | // the ordinal of the user.) 109 | optional string user_name = 21; 110 | 111 | // For devices supporting multiple user profiles, the serial number 112 | // for the user checking in. Not logged. May not be set, even if 113 | // the device supportes multiuser. checkin.user_number is the 114 | // ordinal of the user (0, 1, 2, ...), which may be reused if users 115 | // are deleted and re-created. user_serial_number is never reused 116 | // (unless the device is wiped). 117 | optional int32 user_serial_number = 22; 118 | 119 | // NEXT TAG: 23 120 | } 121 | 122 | // The response to the device. 123 | message AndroidCheckinResponse { 124 | required bool stats_ok = 1; // Whether statistics were recorded properly. 125 | optional int64 time_msec = 3; // Time of day from server (Java epoch). 126 | // repeated AndroidIntentProto intent = 2; 127 | 128 | // Provisioning is sent if the request included an obsolete digest. 129 | // 130 | // For version <= 2, 'digest' contains the digest that should be 131 | // sent back to the server on the next checkin, and 'setting' 132 | // contains the entire gservices table (which replaces the entire 133 | // current table on the device). 134 | // 135 | // for version >= 3, 'digest' will be absent. If 'settings_diff' 136 | // is false, then 'setting' contains the entire table, as in version 137 | // 2. If 'settings_diff' is true, then 'delete_setting' contains 138 | // the keys to delete, and 'setting' contains only keys to be added 139 | // or for which the value has changed. All other keys in the 140 | // current table should be left untouched. If 'settings_diff' is 141 | // absent, don't touch the existing gservices table. 142 | // 143 | optional string digest = 4; 144 | optional bool settings_diff = 9; 145 | repeated string delete_setting = 10; 146 | repeated GservicesSetting setting = 5; 147 | 148 | optional bool market_ok = 6; // If Market got the market_checkin data OK. 149 | 150 | optional fixed64 android_id = 7; // From the request, or newly assigned 151 | optional fixed64 security_token = 8; // The associated security token 152 | 153 | optional string version_info = 11; 154 | // NEXT TAG: 12 155 | } 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This crate will listen for push messages from Firebase Cloud Messaging (FCM). 4 | 5 | # Prerequisites 6 | 7 | 1. **Firebase App ID** - Firebase console -> Project settings -> General -> Your apps -> App ID 8 | 9 | Make this an Android app, since we will be calling the Android device checkin API. 10 | 11 | 2. **Firebase Project ID** - Firebase console -> Project settings -> General -> Project ID 12 | 3. **Firebase API Key** - Google Cloud console -> APIs and Services -> Credentials -> API Keys 13 | 14 | Needed permissions for the API key: Firebase Cloud Messaging API, Cloud Messaging, Firebase Installations API, FCM Registration API. 15 | 16 | # Registration and basic usage 17 | 18 | ```rust 19 | use fcm_push_listener::FcmPushListener; 20 | 21 | let http = reqwest::Client::new(); 22 | let firebase_app_id = "1:1001234567890:android:2665128ba997ffab830a24"; 23 | let firebase_project_id = "myapp-1234567890123"; 24 | let firebase_api_key = "aBcDeFgHiJkLmNoPqRsTu01234_aBcD0123456789"; 25 | 26 | let registration = fcm_push_listener::register( 27 | &http, 28 | firebase_app_id, 29 | firebase_project_id, 30 | firebase_api_key, 31 | None).await?; 32 | 33 | // Send registration.fcm_token to the server to allow it to send push messages to you. 34 | 35 | let http = reqwest::Client::new(); 36 | let session = registration.gcm.checkin(&http).await?; 37 | let connection = session.new_connection(vec!["0:1677356129944104%7031b2e6f9fd7ecd"]).await?; 38 | let mut stream = MessageStream::wrap(connection, ®istration.keys); 39 | 40 | while let Some(message) = stream.next().await { 41 | match message? { 42 | fcm_push_listener::Message::Data(data) => { 43 | println!("Message {:?} Data: {:?}", data.persistent_id, data.body); 44 | } 45 | fcm_push_listener::Message::HeartbeatPing => { 46 | println!("Heartbeat"); 47 | let result = stream.write_all(&new_heartbeat_ack()).await; 48 | if let Err(e) = result { 49 | println!("Error sending heartbeat ack: {:?}", e); 50 | } 51 | } 52 | fcm_push_listener::Message::Other(tag, bytes) => { 53 | println!("Got non-data message: {tag:?}, {bytes:?}"); 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | You need to save the persistent IDs of the messages you receive, then pass them in on the next call to `connect()`. That way you acknowledge receipt of the messages and avoid firing them again. 60 | 61 | The push service sends heartbeats every 30 minutes to make sure the client is still connected. Right now you need to manually acknowledge them via `new_heartbeat_ack()`, but a future version of the library may automate this. If you don't ack the heartbeats, push messages will cease within an hour. 62 | 63 | The registration has secrets needed the decrypt the push messages; store it in a secure location and re-use it on the next call to `connect()`. `Registration` is marked as `Serialize` and `Deserialize` so you can directly use it. 64 | 65 | Example `body`: 66 | ```json 67 | { 68 | "data": { 69 | "myProp": "myValue" 70 | }, 71 | "from": "1001234567890", 72 | "priority": "normal", 73 | "fcmMessageId": "2cca9428-b164-401c-be3b-e01d8bce6dcd" 74 | } 75 | ``` 76 | 77 | You can do JSON parsing with whatever library you choose. Since `body` is a byte array, you can use `serde_json::from_slice(&message.body)` to directly JSON parse the bytes into the expected types. The `data` property holds the object that was pushed. 78 | 79 | ## Cancellation, tracking, and message parsing 80 | 81 | Since `connect()` returns a `Future` and runs for a long time, I recommend creating and starting the listener from a task. Then you can cancel/abort the task to stop the push listener, and it leaves your app free to do other activities on the main thread. 82 | 83 | For example, you could set up a service to manage the push listener: 84 | 85 | ```rust 86 | struct PushService { 87 | task: Option>, 88 | some_state: String, 89 | } 90 | 91 | impl PushService { 92 | pub fn new() -> Self { 93 | PushService { 94 | task: None, 95 | some_state: "abc".to_owned() 96 | } 97 | } 98 | 99 | pub fn start(&mut self) { 100 | let registration = /* Get registration from storage or call fcm_push_listener::register() */; 101 | let received_persistent_ids = /* Get persistent IDs received from last time */; 102 | 103 | self.task = Some(tauri::async_runtime::spawn(run_outer(registration, received_persistent_ids))); 104 | } 105 | 106 | pub fn stop(&mut self) { 107 | if let Some(task) = &self.task { 108 | task.abort(); 109 | self.task = None; 110 | } 111 | } 112 | 113 | pub fn get_status(&self) -> PushServiceStatus { 114 | if let Some(task) = &self.task { 115 | if !task.inner().is_finished() { 116 | return PushServiceStatus::Running; 117 | } 118 | } 119 | 120 | PushServiceStatus::Stopped 121 | } 122 | } 123 | 124 | async fn run_outer(registration: Registration, received_persistent_ids: Vec) { 125 | let result = run(registration, received_persistent_ids, app_handle).await; 126 | if let Err(err) = result { 127 | error!("Error running push service: {:?}", err); 128 | } 129 | } 130 | 131 | async fn run(registration: Registration, received_persistent_ids: Vec) -> Result<(), fcm_push_listener::Error> { 132 | use tokio_stream::StreamExt; 133 | 134 | let http = reqwest::Client::new(); 135 | let session = registration.gcm.checkin(&http).await?; 136 | let connection = session.new_connection(received_persistent_ids).await?; 137 | let mut stream = MessageStream::wrap(connection, ®istration.keys); 138 | 139 | while let Some(message) = stream.next().await { 140 | match message? { 141 | fcm_push_listener::Message::Data(data_message) => { 142 | println!("Message arrived with ID {:?}", data_message.persistent_id); 143 | 144 | // PushMessagePayload is your custom type with #[derive(Deserialize)] 145 | let message_payload: PushMessagePayload = serde_json::from_slice(&message.body)?; 146 | 147 | println!("Message arrived with property {:?}", message_payload.data.my_prop); 148 | } 149 | _ => {} 150 | } 151 | } 152 | 153 | Ok(()) 154 | } 155 | ``` 156 | 157 | Then keep an instance of PushService around and call `stop()` on it when you need to cancel. 158 | 159 | # Implementation 160 | 161 | ## Dependencies 162 | 163 | * `tokio` for async/TCP. 164 | * `rustls` / `tokio-rustls` for the push listener TLS connection. 165 | * `reqwest` for HTTP calls. 166 | * `prost` for protobuf. 167 | * `ece` for creating the web push key pair and decrypting messages. 168 | 169 | ## `register()` 170 | 171 | 1) Calls https://android.clients.google.com/checkin to get an android ID. 172 | 2) Calls https://android.clients.google.com/c2dm/register3 to register with GCM. Gives you a GCM token and a security token. (The GCM token is sometimes called an ACG token by other libraries) 173 | 3) Calls https://firebaseinstallations.googleapis.com/v1/projects/{project_id}/installations to get a Firebase installation token. 174 | 4) Creates an encryption key pair using the legacy `aesgcm` mode of the `ece` crate. 175 | 5) Calls https://fcmregistrations.googleapis.com/v1/projects/{project_id}/registrations to do the final FCM registration and get the FCM token. 176 | 177 | ## `registration.gcm.checkin()` 178 | 179 | Makes another checkin call to keep our "device" up to date. 180 | 181 | ## `new_connection()` 182 | 183 | 1) Makes a TLS/TCP connection to `mtalk.google.com:5228` and sends information encoded via protobuf to log in with our generated device ID and the list of persistent IDs that we have seen. 184 | 2) Keeps the socket connection open to listen for push messages. 185 | 186 | ## Messages 187 | 188 | When a push message arrives, it uses protobuf to parse out the payload and metadata, then uses the private key and auth secret stored in the registration to decrypt the payload and decode to a UTF-8 string. It then invokes the provided closure with the JSON payload and persistent ID. 189 | 190 | ## Reconnection 191 | 192 | If the connection is closed after successfully establishing, it will automatically try and re-open the connection. 193 | 194 | # Acknowledgements 195 | 196 | The original version is based on the NPM package [push-reciever](https://github.com/MatthieuLemoine/push-receiver) by Matthieu Lemoine. His [reverse-engineering effort](https://medium.com/@MatthieuLemoine/my-journey-to-bring-web-push-support-to-node-and-electron-ce70eea1c0b0) was quite heroic! 197 | 198 | The changes for v3 were based on [@aracna/fcm](https://aracna.dariosechi.it/fcm/get-started/) by Dario Sechi. 199 | 200 | v4 (async overhaul) was written by [WXY](https://github.com/unreadablewxy). 201 | 202 | # Minimum version 203 | 204 | Registration for versions older than 3.0.0 have stopped working as of June 20, 2024, since Google shut down an API it calls. 205 | 206 | # Build setup 207 | 208 | 1) Go to https://github.com/protocolbuffers/protobuf/releases , find the latest stable, then extract protoc.exe from protoc-{version}-{platform}.zip and put it in path. 209 | 2) Install CMake from https://cmake.org/download/ 210 | 3) Set up OpenSSL. For Windows, install from https://slproweb.com/products/Win32OpenSSL.html and set the environment variable `OPENSSL_DIR` to `C:\Program Files\OpenSSL-Win64` (or wherever you installed it) 211 | 212 | // If you encounter ``could not find native static library `libssl`, perhaps an -L flag is missing`` or a similar compilation error - try to set the environment variable `OPENSSL_LIB_DIR` to `C:\Program Files\OpenSSL-Win64\lib\VC\x64\MD` 213 | -------------------------------------------------------------------------------- /src/gcm.rs: -------------------------------------------------------------------------------- 1 | pub mod contract { 2 | include!(concat!(env!("OUT_DIR"), "/checkin_proto.rs")); 3 | } 4 | 5 | use crate::Error; 6 | use prost::bytes::BufMut; 7 | use serde::{Deserialize, Serialize}; 8 | use serde_with::serde_as; 9 | use tokio_rustls::rustls::pki_types::ServerName; 10 | 11 | fn require_some(value: Option, reason: &'static str) -> Result { 12 | match value { 13 | Some(value) => Ok(value), 14 | None => Err(Error::DependencyFailure("Android device check-in", reason)), 15 | } 16 | } 17 | 18 | const CHECKIN_URL: &str = "https://android.clients.google.com/checkin"; 19 | const REGISTER_URL: &str = "https://android.clients.google.com/c2dm/register3"; 20 | 21 | // Normal JSON serialization will lose precision and change the number, so we must 22 | // force the i64/u64 to serialize to string. 23 | #[serde_as] 24 | #[derive(Clone, Serialize, Deserialize)] 25 | pub struct Session { 26 | #[serde_as(as = "serde_with::DisplayFromStr")] 27 | pub android_id: i64, 28 | 29 | #[serde_as(as = "serde_with::DisplayFromStr")] 30 | pub security_token: u64, 31 | } 32 | 33 | impl Session { 34 | async fn request( 35 | http: &reqwest::Client, 36 | android_id: Option, 37 | security_token: Option, 38 | ) -> Result { 39 | use prost::Message; 40 | 41 | let request = contract::AndroidCheckinRequest { 42 | version: Some(3), 43 | id: android_id, 44 | security_token, 45 | user_serial_number: Some(0), 46 | checkin: contract::AndroidCheckinProto { 47 | r#type: Some(3), 48 | chrome_build: Some(contract::ChromeBuildProto { 49 | platform: Some(2), 50 | channel: Some(1), 51 | chrome_version: Some(String::from("63.0.3234.0")), 52 | }), 53 | ..Default::default() 54 | }, 55 | ..Default::default() 56 | }; 57 | 58 | const API_NAME: &str = "GCM checkin"; 59 | 60 | let response = http 61 | .post(CHECKIN_URL) 62 | .body(request.encode_to_vec()) 63 | .header(reqwest::header::CONTENT_TYPE, "application/x-protobuf") 64 | .send() 65 | .await 66 | .map_err(|e| Error::Request(API_NAME, e))?; 67 | 68 | let response_bytes = response 69 | .bytes() 70 | .await 71 | .map_err(|e| Error::Response(API_NAME, e))?; 72 | let response = contract::AndroidCheckinResponse::decode(response_bytes) 73 | .map_err(|e| Error::ProtobufDecode("android checkin response", e))?; 74 | 75 | let android_id = require_some(response.android_id, "response is missing android id")?; 76 | 77 | const BAD_ID: Result = Err(Error::DependencyFailure( 78 | API_NAME, 79 | "responded with non-numeric android id", 80 | )); 81 | let android_id = i64::try_from(android_id).or(BAD_ID)?; 82 | let security_token = require_some( 83 | response.security_token, 84 | "response is missing security token", 85 | )?; 86 | 87 | Ok(Self { 88 | android_id, 89 | security_token, 90 | }) 91 | } 92 | 93 | /// check in to the device registration service, possibly obtaining a new security token 94 | pub async fn checkin(&self, http: &reqwest::Client) -> Result { 95 | let r = Self::request(http, Some(self.android_id), Some(self.security_token)).await?; 96 | Ok(CheckedSession(r)) 97 | } 98 | 99 | /// check in to the device registration service for the first time 100 | pub fn create<'a>( 101 | http: &'a reqwest::Client, 102 | ) -> impl std::future::Future> + 'a { 103 | Self::request(http, None, None) 104 | } 105 | 106 | pub async fn request_token(&self, app_id: &str) -> Result { 107 | /// Server key in URL-safe base64 108 | const SERVER_KEY: &str = 109 | "BDOU99-h67HcA6JeFXHbSNMu7e2yNNu3RzoMj8TM4W88jITfq7ZmPvIM1Iv-4_l2LxQcYwhqby2xGpWwzjfAnG4"; 110 | 111 | let android_id = self.android_id.to_string(); 112 | let auth_header = format!("AidLogin {}:{}", &android_id, &self.security_token); 113 | let mut params = std::collections::HashMap::with_capacity(4); 114 | params.insert("app", "org.chromium.linux"); 115 | params.insert("X-subtype", app_id); 116 | params.insert("device", &android_id); 117 | params.insert("sender", SERVER_KEY); 118 | 119 | const API_NAME: &str = "GCM registration"; 120 | let result = reqwest::Client::new() 121 | .post(REGISTER_URL) 122 | .form(¶ms) 123 | .header(reqwest::header::AUTHORIZATION, auth_header) 124 | .send() 125 | .await 126 | .map_err(|e| Error::Request(API_NAME, e))?; 127 | 128 | let response_text = result 129 | .text() 130 | .await 131 | .map_err(|e| Error::Response(API_NAME, e))?; 132 | 133 | const ERR_EOF: Error = Error::DependencyFailure(API_NAME, "malformed response"); 134 | 135 | let mut tokens = response_text.split('='); 136 | match tokens.next() { 137 | Some("Error") => { 138 | return Err(Error::DependencyRejection( 139 | API_NAME, 140 | tokens.next().unwrap_or("no reasons given").into(), 141 | )) 142 | } 143 | None => return Err(ERR_EOF), 144 | _ => {} 145 | } 146 | 147 | match tokens.next() { 148 | Some(v) => Ok(String::from(v)), 149 | None => Err(ERR_EOF), 150 | } 151 | } 152 | } 153 | 154 | fn new_tls_initiator() -> tokio_rustls::TlsConnector { 155 | let root_store = tokio_rustls::rustls::RootCertStore { 156 | roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), 157 | }; 158 | 159 | let config = tokio_rustls::rustls::ClientConfig::builder() 160 | .with_root_certificates(root_store) 161 | .with_no_client_auth(); 162 | 163 | tokio_rustls::TlsConnector::from(std::sync::Arc::new(config)) 164 | } 165 | 166 | pub struct CheckedSession(Session); 167 | 168 | impl CheckedSession { 169 | const MCS_VERSION: u8 = 41; 170 | const LOGIN_REQUEST_TAG: u8 = 2; 171 | 172 | pub fn changed(&self, from: &Session) -> bool { 173 | self.0.security_token != from.security_token || self.0.android_id != from.android_id 174 | } 175 | 176 | fn new_mcs_login_request( 177 | &self, 178 | received_persistent_id: Vec, 179 | ) -> crate::mcs::LoginRequest { 180 | let android_id = self.0.android_id.to_string(); 181 | crate::mcs::LoginRequest { 182 | adaptive_heartbeat: Some(false), 183 | auth_service: Some(2), 184 | auth_token: self.0.security_token.to_string(), 185 | id: "chrome-63.0.3234.0".into(), 186 | domain: "mcs.android.com".into(), 187 | device_id: Some(format!("android-{:x}", self.0.android_id)), 188 | network_type: Some(1), 189 | resource: android_id.clone(), 190 | user: android_id, 191 | use_rmq2: Some(true), 192 | setting: vec![crate::mcs::Setting { 193 | name: "new_vc".into(), 194 | value: "1".into(), 195 | }], 196 | received_persistent_id, 197 | ..Default::default() 198 | } 199 | } 200 | 201 | async fn try_connect( 202 | domain: ServerName<'static>, 203 | login_bytes: &[u8], 204 | ) -> Result { 205 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 206 | let stream = tokio::net::TcpStream::connect("mtalk.google.com:5228").await?; 207 | let tls = new_tls_initiator(); 208 | let mut stream = tls.connect(domain, stream).await?; 209 | 210 | stream.write_all(login_bytes).await?; 211 | 212 | // Read the version 213 | stream.read_i8().await?; 214 | 215 | Ok(Connection(stream)) 216 | } 217 | 218 | pub async fn new_connection( 219 | &self, 220 | received_persistent_id: Vec, 221 | ) -> Result { 222 | use prost::Message; 223 | 224 | // Install the default crypto provider. If a different one is already registered, this 225 | // will do nothing. 226 | let _ = rustls::crypto::ring::default_provider().install_default(); 227 | 228 | const ERR_RESOLVE: Error = 229 | Error::DependencyFailure("name resolution", "unable to resolve google talk host name"); 230 | 231 | let domain = ServerName::try_from("mtalk.google.com").or(Err(ERR_RESOLVE))?; 232 | 233 | let login_request = self.new_mcs_login_request(received_persistent_id); 234 | 235 | let mut login_bytes = bytes::BytesMut::with_capacity(2 + login_request.encoded_len() + 4); 236 | login_bytes.put_u8(Self::MCS_VERSION); 237 | login_bytes.put_u8(Self::LOGIN_REQUEST_TAG); 238 | login_request 239 | .encode_length_delimited(&mut login_bytes) 240 | .expect("login request encoding failure"); 241 | 242 | Self::try_connect(domain.clone(), &login_bytes) 243 | .await 244 | .map_err(Error::Socket) 245 | } 246 | } 247 | 248 | impl std::ops::Deref for CheckedSession { 249 | type Target = Session; 250 | 251 | fn deref(&self) -> &Self::Target { 252 | &self.0 253 | } 254 | } 255 | 256 | pub struct Connection(pub(crate) tokio_rustls::client::TlsStream); 257 | 258 | impl std::ops::Deref for Connection { 259 | type Target = tokio_rustls::client::TlsStream; 260 | 261 | fn deref(&self) -> &Self::Target { 262 | &self.0 263 | } 264 | } 265 | 266 | impl std::ops::DerefMut for Connection { 267 | fn deref_mut(&mut self) -> &mut Self::Target { 268 | &mut self.0 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/proto/mcs.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | // 5 | // MCS protocol for communication between Chrome client and Mobile Connection 6 | // Server . 7 | 8 | syntax = "proto2"; 9 | 10 | option optimize_for = LITE_RUNTIME; 11 | 12 | package mcs_proto; 13 | 14 | /* 15 | Common fields/comments: 16 | 17 | stream_id: no longer sent by server, each side keeps a counter 18 | last_stream_id_received: sent only if a packet was received since last time 19 | a last_stream was sent 20 | status: new bitmask including the 'idle' as bit 0. 21 | 22 | */ 23 | 24 | /** 25 | TAG: 0 26 | */ 27 | message HeartbeatPing { 28 | optional int32 stream_id = 1; 29 | optional int32 last_stream_id_received = 2; 30 | optional int64 status = 3; 31 | } 32 | 33 | /** 34 | TAG: 1 35 | */ 36 | message HeartbeatAck { 37 | optional int32 stream_id = 1; 38 | optional int32 last_stream_id_received = 2; 39 | optional int64 status = 3; 40 | } 41 | 42 | message ErrorInfo { 43 | required int32 code = 1; 44 | optional string message = 2; 45 | optional string type = 3; 46 | optional Extension extension = 4; 47 | } 48 | 49 | // MobileSettings class. 50 | // "u:f", "u:b", "u:s" - multi user devices reporting foreground, background 51 | // and stopped users. 52 | // hbping: heatbeat ping interval 53 | // rmq2v: include explicit stream IDs 54 | 55 | message Setting { 56 | required string name = 1; 57 | required string value = 2; 58 | } 59 | 60 | message HeartbeatStat { 61 | required string ip = 1; 62 | required bool timeout = 2; 63 | required int32 interval_ms = 3; 64 | } 65 | 66 | message HeartbeatConfig { 67 | optional bool upload_stat = 1; 68 | optional string ip = 2; 69 | optional int32 interval_ms = 3; 70 | } 71 | 72 | // ClientEvents are used to inform the server of failed and successful 73 | // connections. 74 | message ClientEvent { 75 | enum Type { 76 | UNKNOWN = 0; 77 | // Count of discarded events if the buffer filled up and was trimmed. 78 | DISCARDED_EVENTS = 1; 79 | // Failed connection event: the connection failed to be established or we 80 | // had a login error. 81 | FAILED_CONNECTION = 2; 82 | // Successful connection event: information about the last successful 83 | // connection, including the time at which it was established. 84 | SUCCESSFUL_CONNECTION = 3; 85 | } 86 | 87 | // Common fields [1-99] 88 | optional Type type = 1; 89 | 90 | // Fields for DISCARDED_EVENTS messages [100-199] 91 | optional uint32 number_discarded_events = 100; 92 | 93 | // Fields for FAILED_CONNECTION and SUCCESSFUL_CONNECTION messages [200-299] 94 | // Network type is a value in net::NetworkChangeNotifier::ConnectionType. 95 | optional int32 network_type = 200; 96 | // Reserved for network_port. 97 | reserved 201; 98 | optional uint64 time_connection_started_ms = 202; 99 | optional uint64 time_connection_ended_ms = 203; 100 | // Error code should be a net::Error value. 101 | optional int32 error_code = 204; 102 | 103 | // Fields for SUCCESSFUL_CONNECTION messages [300-399] 104 | optional uint64 time_connection_established_ms = 300; 105 | } 106 | 107 | /** 108 | TAG: 2 109 | */ 110 | message LoginRequest { 111 | enum AuthService { 112 | ANDROID_ID = 2; 113 | } 114 | required string id = 1; // Must be present ( proto required ), may be empty 115 | // string. 116 | // mcs.android.com. 117 | required string domain = 2; 118 | // Decimal android ID 119 | required string user = 3; 120 | 121 | required string resource = 4; 122 | 123 | // Secret 124 | required string auth_token = 5; 125 | 126 | // Format is: android-HEX_DEVICE_ID 127 | // The user is the decimal value. 128 | optional string device_id = 6; 129 | 130 | // RMQ1 - no longer used 131 | optional int64 last_rmq_id = 7; 132 | 133 | repeated Setting setting = 8; 134 | //optional int32 compress = 9; 135 | repeated string received_persistent_id = 10; 136 | 137 | // Replaced by "rmq2v" setting 138 | // optional bool include_stream_ids = 11; 139 | 140 | optional bool adaptive_heartbeat = 12; 141 | optional HeartbeatStat heartbeat_stat = 13; 142 | // Must be true. 143 | optional bool use_rmq2 = 14; 144 | optional int64 account_id = 15; 145 | 146 | // ANDROID_ID = 2 147 | optional AuthService auth_service = 16; 148 | 149 | optional int32 network_type = 17; 150 | optional int64 status = 18; 151 | 152 | // 19, 20, and 21 are not currently populated by Chrome. 153 | reserved 19, 20, 21; 154 | 155 | // Events recorded on the client after the last successful connection. 156 | repeated ClientEvent client_event = 22; 157 | } 158 | 159 | /** 160 | * TAG: 3 161 | */ 162 | message LoginResponse { 163 | required string id = 1; 164 | // Not used. 165 | optional string jid = 2; 166 | // Null if login was ok. 167 | optional ErrorInfo error = 3; 168 | repeated Setting setting = 4; 169 | optional int32 stream_id = 5; 170 | // Should be "1" 171 | optional int32 last_stream_id_received = 6; 172 | optional HeartbeatConfig heartbeat_config = 7; 173 | // used by the client to synchronize with the server timestamp. 174 | optional int64 server_timestamp = 8; 175 | } 176 | 177 | message StreamErrorStanza { 178 | required string type = 1; 179 | optional string text = 2; 180 | } 181 | 182 | /** 183 | * TAG: 4 184 | */ 185 | message Close { 186 | } 187 | 188 | message Extension { 189 | // 12: SelectiveAck 190 | // 13: StreamAck 191 | required int32 id = 1; 192 | required bytes data = 2; 193 | } 194 | 195 | /** 196 | * TAG: 7 197 | * IqRequest must contain a single extension. IqResponse may contain 0 or 1 198 | * extensions. 199 | */ 200 | message IqStanza { 201 | enum IqType { 202 | GET = 0; 203 | SET = 1; 204 | RESULT = 2; 205 | IQ_ERROR = 3; 206 | } 207 | 208 | optional int64 rmq_id = 1; 209 | required IqType type = 2; 210 | required string id = 3; 211 | optional string from = 4; 212 | optional string to = 5; 213 | optional ErrorInfo error = 6; 214 | 215 | // Only field used in the 38+ protocol (besides common last_stream_id_received, status, rmq_id) 216 | optional Extension extension = 7; 217 | 218 | optional string persistent_id = 8; 219 | optional int32 stream_id = 9; 220 | optional int32 last_stream_id_received = 10; 221 | optional int64 account_id = 11; 222 | optional int64 status = 12; 223 | } 224 | 225 | message AppData { 226 | required string key = 1; 227 | required string value = 2; 228 | } 229 | 230 | /** 231 | * TAG: 8 232 | */ 233 | message DataMessageStanza { 234 | // Not used. 235 | // optional int64 rmq_id = 1; 236 | 237 | // This is the message ID, set by client, DMP.9 (message_id) 238 | optional string id = 2; 239 | 240 | // Project ID of the sender, DMP.1 241 | required string from = 3; 242 | 243 | // Part of DMRequest - also the key in DataMessageProto. 244 | optional string to = 4; 245 | 246 | // Package name. DMP.2 247 | required string category = 5; 248 | 249 | // The collapsed key, DMP.3 250 | optional string token = 6; 251 | 252 | // User data + GOOGLE. prefixed special entries, DMP.4 253 | repeated AppData app_data = 7; 254 | 255 | // Not used. 256 | optional bool from_trusted_server = 8; 257 | 258 | // Part of the ACK protocol, returned in DataMessageResponse on server side. 259 | // It's part of the key of DMP. 260 | optional string persistent_id = 9; 261 | 262 | // In-stream ack. Increments on each message sent - a bit redundant 263 | // Not used in DMP/DMR. 264 | optional int32 stream_id = 10; 265 | optional int32 last_stream_id_received = 11; 266 | 267 | // Not used. 268 | // optional string permission = 12; 269 | 270 | // Sent by the device shortly after registration. 271 | optional string reg_id = 13; 272 | 273 | // Not used. 274 | // optional string pkg_signature = 14; 275 | // Not used. 276 | // optional string client_id = 15; 277 | 278 | // serial number of the target user, DMP.8 279 | // It is the 'serial number' according to user manager. 280 | optional int64 device_user_id = 16; 281 | 282 | // Time to live, in seconds. 283 | optional int32 ttl = 17; 284 | // Timestamp ( according to client ) when message was sent by app, in seconds 285 | optional int64 sent = 18; 286 | 287 | // How long has the message been queued before the flush, in seconds. 288 | // This is needed to account for the time difference between server and 289 | // client: server should adjust 'sent' based on its 'receive' time. 290 | optional int32 queued = 19; 291 | 292 | optional int64 status = 20; 293 | 294 | // Optional field containing the binary payload of the message. 295 | optional bytes raw_data = 21; 296 | 297 | // Not used. 298 | // The maximum delay of the message, in seconds. 299 | // optional int32 max_delay = 22; 300 | 301 | // Not used. 302 | // How long the message was delayed before it was sent, in seconds. 303 | // optional int32 actual_delay = 23; 304 | 305 | // If set the server requests immediate ack. Used for important messages and 306 | // for testing. 307 | optional bool immediate_ack = 24; 308 | 309 | // Not used. 310 | // Enables message receipts from MCS/GCM back to CCS clients 311 | // optional bool delivery_receipt_requested = 25; 312 | } 313 | 314 | /** 315 | Included in IQ with ID 13, sent from client or server after 10 unconfirmed 316 | messages. 317 | */ 318 | message StreamAck { 319 | // No last_streamid_received required. This is included within an IqStanza, 320 | // which includes the last_stream_id_received. 321 | } 322 | 323 | /** 324 | Included in IQ sent after LoginResponse from server with ID 12. 325 | */ 326 | message SelectiveAck { 327 | repeated string id = 1; 328 | } 329 | -------------------------------------------------------------------------------- /src/push.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use bytes::{Bytes, BytesMut}; 3 | use ece::EcKeyComponents; 4 | use pin_project_lite::pin_project; 5 | use std::pin::Pin; 6 | use std::task::{Context, Poll}; 7 | 8 | #[allow(dead_code)] 9 | #[derive(PartialEq, Debug)] 10 | pub enum MessageTag { 11 | HeartbeatPing = 0, 12 | HeartbeatAck, 13 | LoginRequest, 14 | LoginResponse, 15 | Close, 16 | MessageStanza, 17 | PresenceStanza, 18 | IqStanza, 19 | DataMessageStanza, 20 | BatchPresenceStanza, 21 | StreamErrorStanza, 22 | HttpRequest, 23 | HttpResponse, 24 | BindAccountRequest, 25 | BindAccountResponse, 26 | TalkMetadata, 27 | NumProtoTypes, 28 | } 29 | 30 | impl TryFrom for MessageTag { 31 | type Error = u8; 32 | 33 | fn try_from(value: u8) -> std::result::Result { 34 | if value < Self::NumProtoTypes as u8 { 35 | Ok(unsafe { std::mem::transmute(value) }) 36 | } else { 37 | Err(value) 38 | } 39 | } 40 | } 41 | 42 | pub enum Message { 43 | HeartbeatPing, 44 | Data(DataMessage), 45 | Other(u8, Bytes), 46 | } 47 | 48 | pub struct DataMessage { 49 | pub body: Vec, 50 | pub persistent_id: Option, 51 | } 52 | 53 | impl DataMessage { 54 | fn decode(eckey: &EcKeyComponents, auth_secret: &[u8], bytes: &[u8]) -> Result { 55 | use base64::engine::general_purpose::URL_SAFE; 56 | use base64::Engine; 57 | use ece::legacy::AesGcmEncryptedBlock; 58 | use prost::Message; 59 | 60 | let message = crate::mcs::DataMessageStanza::decode(bytes) 61 | .map_err(|e| Error::ProtobufDecode("FCM data message", e))?; 62 | 63 | let bytes = match message.raw_data { 64 | Some(v) => v, 65 | None => { 66 | return Err(Error::EmptyPayload); 67 | } 68 | }; 69 | 70 | let mut kex: Vec = Vec::default(); 71 | let mut salt: Vec = Vec::default(); 72 | for field in message.app_data { 73 | match field.key.as_str() { 74 | "crypto-key" => { 75 | // crypto_key format: dh=abc... 76 | kex = URL_SAFE 77 | .decode(&field.value[3..]) 78 | .map_err(|e| Error::Base64Decode("FCM message crypto-key", e))?; 79 | 80 | if !salt.is_empty() { 81 | break; 82 | } 83 | } 84 | "encryption" => { 85 | // encryption format: salt=abc... 86 | salt = URL_SAFE 87 | .decode(&field.value[5..]) 88 | .map_err(|e| Error::Base64Decode("FCM message encryption params", e))?; 89 | 90 | if !kex.is_empty() { 91 | break; 92 | } 93 | } 94 | _ => {} 95 | } 96 | } 97 | 98 | if kex.is_empty() { 99 | return Err(Error::MissingCryptoMetadata("crypto-key")); 100 | } else if salt.is_empty() { 101 | return Err(Error::MissingCryptoMetadata("encryption")); 102 | } 103 | 104 | // The record size default is 4096 and doesn't seem to be overridden for FCM. 105 | const RECORD_SIZE: u32 = 4096; 106 | const OPERATION: &str = "message decryption"; 107 | let block = AesGcmEncryptedBlock::new(&kex, &salt, RECORD_SIZE, bytes) 108 | .map_err(|e| Error::Crypto(OPERATION, e))?; 109 | let body = ece::legacy::decrypt_aesgcm(eckey, auth_secret, &block) 110 | .map_err(|e| Error::Crypto(OPERATION, e))?; 111 | Ok(Self { 112 | body, 113 | persistent_id: message.persistent_id, 114 | }) 115 | } 116 | } 117 | 118 | pin_project! { 119 | pub struct MessageStream { 120 | #[pin] 121 | inner: T, 122 | eckey: EcKeyComponents, 123 | auth_secret: Vec, 124 | bytes_required: usize, 125 | receive_buffer: BytesMut, 126 | } 127 | } 128 | 129 | impl MessageStream> { 130 | pub fn wrap(connection: crate::gcm::Connection, keys: &crate::fcm::WebPushKeys) -> Self { 131 | Self::new(connection.0, keys) 132 | } 133 | } 134 | 135 | impl MessageStream { 136 | fn new(inner: T, keys: &crate::fcm::WebPushKeys) -> Self { 137 | Self { 138 | inner, 139 | eckey: EcKeyComponents::new(keys.private_key.clone(), keys.public_key.clone()), 140 | auth_secret: keys.auth_secret.clone(), 141 | bytes_required: 2, 142 | receive_buffer: BytesMut::with_capacity(1024), 143 | } 144 | } 145 | 146 | /// returns a decoded protobuf varint or a state change if there is insufficient data 147 | fn try_read_varint<'a>(mut bytes: impl Iterator) -> (usize, usize) { 148 | let mut result = 0; 149 | let mut bytes_read = 0; 150 | 151 | loop { 152 | let byte = match bytes.next() { 153 | // since data is little endian, partially read sizes will always be smaller than 154 | // the actual message size, on average we expect size / fragmentation + 1 reads 155 | None => return (result, 2 + bytes_read), 156 | Some(v) => v, 157 | }; 158 | 159 | // Strip the continuation bit 160 | let value_part = byte & !0x80u8; 161 | 162 | // accumulate little endian bits 163 | result += (value_part as usize) << (bytes_read * 7); 164 | 165 | // IFF equal -> No continuation bit -> Varint has concluded 166 | if value_part.eq(byte) { 167 | return (result, 2 + bytes_read); 168 | } 169 | 170 | bytes_read += 1; 171 | } 172 | } 173 | } 174 | 175 | impl tokio_stream::Stream for MessageStream 176 | where 177 | T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin, 178 | { 179 | type Item = Result; 180 | 181 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 182 | use bytes::Buf; 183 | use std::future::Future; 184 | use tokio::io::AsyncReadExt; 185 | 186 | loop { 187 | let mut bytes = self.receive_buffer.iter(); 188 | if let Some(tag_value) = bytes.next() { 189 | let tag_value = *tag_value; 190 | let tag = MessageTag::try_from(tag_value); 191 | if matches!(tag, Ok(MessageTag::Close)) { 192 | self.bytes_required = 0; 193 | self.receive_buffer.clear(); 194 | return Poll::Ready(None); 195 | } 196 | 197 | // determine size of the message 198 | let (size, offset) = Self::try_read_varint(bytes); 199 | let bytes_required = offset + size; 200 | if bytes_required <= self.receive_buffer.len() { 201 | // sizeof next_message is unknown, if sizeof next_message < sizeof this_message 202 | // && we don't resetting expectations -> we block despite having received the 203 | // smaller message in its entirety 204 | self.bytes_required = 2; 205 | 206 | self.receive_buffer.advance(offset); 207 | let bytes = self.receive_buffer.split_to(size); 208 | return Poll::Ready(Some(Ok(match tag { 209 | Ok(MessageTag::DataMessageStanza) => { 210 | match DataMessage::decode(&self.eckey, &self.auth_secret, &bytes) { 211 | Err(e) => return Poll::Ready(Some(Err(e))), 212 | Ok(m) => Message::Data(m), 213 | } 214 | } 215 | Ok(MessageTag::HeartbeatPing) => Message::HeartbeatPing, 216 | _ => Message::Other(tag_value, bytes.into()), 217 | }))); 218 | } 219 | 220 | // ensure buffer can contain at least the current message 221 | let capacity = self.receive_buffer.capacity(); 222 | if bytes_required > capacity { 223 | self.receive_buffer.reserve(bytes_required - capacity); 224 | } 225 | 226 | self.bytes_required = bytes_required; 227 | } else if self.bytes_required == 0 { 228 | return Poll::Ready(None); 229 | } 230 | 231 | loop { 232 | // insufficient data in the buffer, fill from inner 233 | let mut that = self.as_mut().project(); 234 | let task = that.inner.read_buf(that.receive_buffer); 235 | tokio::pin!(task); 236 | match task.poll(cx) { 237 | Poll::Pending => return Poll::Pending, 238 | Poll::Ready(Err(e)) => { 239 | // failfast 240 | self.bytes_required = 0; 241 | self.receive_buffer.clear(); 242 | return Poll::Ready(Some(Err(Error::Socket(e)))); 243 | } 244 | Poll::Ready(Ok(0)) => { 245 | // probably a broken pipe, which means whatever incomplete 246 | // message we have buffered will just have to be chucked 247 | self.bytes_required = 0; 248 | self.receive_buffer.clear(); 249 | return Poll::Ready(None); 250 | } 251 | _ => { 252 | if self.receive_buffer.len() >= self.bytes_required { 253 | break; 254 | } 255 | } 256 | } 257 | } 258 | } 259 | } 260 | } 261 | 262 | impl std::ops::Deref for MessageStream { 263 | type Target = T; 264 | 265 | fn deref(&self) -> &Self::Target { 266 | &self.inner 267 | } 268 | } 269 | 270 | impl std::ops::DerefMut for MessageStream { 271 | fn deref_mut(&mut self) -> &mut Self::Target { 272 | &mut self.inner 273 | } 274 | } 275 | 276 | pub fn new_heartbeat_ack() -> BytesMut { 277 | use bytes::BufMut; 278 | 279 | let ack = crate::mcs::HeartbeatAck::default(); 280 | let mut bytes = BytesMut::with_capacity(prost::Message::encoded_len(&ack) + 5); 281 | bytes.put_u8(MessageTag::HeartbeatAck as u8); 282 | prost::Message::encode_length_delimited(&ack, &mut bytes) 283 | .expect("heartbeat ack serialization should succeed"); 284 | 285 | bytes 286 | } 287 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anyhow" 46 | version = "1.0.97" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 49 | 50 | [[package]] 51 | name = "atomic-waker" 52 | version = "1.1.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 55 | 56 | [[package]] 57 | name = "autocfg" 58 | version = "1.4.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 61 | 62 | [[package]] 63 | name = "aws-lc-rs" 64 | version = "1.12.5" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "5e4e8200b9a4a5801a769d50eeabc05670fec7e959a8cb7a63a93e4e519942ae" 67 | dependencies = [ 68 | "aws-lc-sys", 69 | "paste", 70 | "zeroize", 71 | ] 72 | 73 | [[package]] 74 | name = "aws-lc-sys" 75 | version = "0.26.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" 78 | dependencies = [ 79 | "bindgen", 80 | "cc", 81 | "cmake", 82 | "dunce", 83 | "fs_extra", 84 | "paste", 85 | ] 86 | 87 | [[package]] 88 | name = "backtrace" 89 | version = "0.3.74" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 92 | dependencies = [ 93 | "addr2line", 94 | "cfg-if", 95 | "libc", 96 | "miniz_oxide", 97 | "object", 98 | "rustc-demangle", 99 | "windows-targets", 100 | ] 101 | 102 | [[package]] 103 | name = "base64" 104 | version = "0.21.7" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 107 | 108 | [[package]] 109 | name = "base64" 110 | version = "0.22.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 113 | 114 | [[package]] 115 | name = "bindgen" 116 | version = "0.69.5" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 119 | dependencies = [ 120 | "bitflags", 121 | "cexpr", 122 | "clang-sys", 123 | "itertools 0.12.1", 124 | "lazy_static", 125 | "lazycell", 126 | "log", 127 | "prettyplease", 128 | "proc-macro2", 129 | "quote", 130 | "regex", 131 | "rustc-hash", 132 | "shlex", 133 | "syn", 134 | "which", 135 | ] 136 | 137 | [[package]] 138 | name = "bitflags" 139 | version = "2.9.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 142 | 143 | [[package]] 144 | name = "block-buffer" 145 | version = "0.10.4" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 148 | dependencies = [ 149 | "generic-array", 150 | ] 151 | 152 | [[package]] 153 | name = "bumpalo" 154 | version = "3.17.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 157 | 158 | [[package]] 159 | name = "byteorder" 160 | version = "1.5.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 163 | 164 | [[package]] 165 | name = "bytes" 166 | version = "1.10.1" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 169 | 170 | [[package]] 171 | name = "cc" 172 | version = "1.2.16" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" 175 | dependencies = [ 176 | "jobserver", 177 | "libc", 178 | "shlex", 179 | ] 180 | 181 | [[package]] 182 | name = "cexpr" 183 | version = "0.6.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 186 | dependencies = [ 187 | "nom", 188 | ] 189 | 190 | [[package]] 191 | name = "cfg-if" 192 | version = "1.0.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 195 | 196 | [[package]] 197 | name = "chrono" 198 | version = "0.4.40" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 201 | dependencies = [ 202 | "android-tzdata", 203 | "iana-time-zone", 204 | "num-traits", 205 | "serde", 206 | "windows-link", 207 | ] 208 | 209 | [[package]] 210 | name = "clang-sys" 211 | version = "1.8.1" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 214 | dependencies = [ 215 | "glob", 216 | "libc", 217 | "libloading", 218 | ] 219 | 220 | [[package]] 221 | name = "cmake" 222 | version = "0.1.54" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 225 | dependencies = [ 226 | "cc", 227 | ] 228 | 229 | [[package]] 230 | name = "core-foundation" 231 | version = "0.9.4" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 234 | dependencies = [ 235 | "core-foundation-sys", 236 | "libc", 237 | ] 238 | 239 | [[package]] 240 | name = "core-foundation-sys" 241 | version = "0.8.7" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 244 | 245 | [[package]] 246 | name = "cpufeatures" 247 | version = "0.2.17" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 250 | dependencies = [ 251 | "libc", 252 | ] 253 | 254 | [[package]] 255 | name = "crypto-common" 256 | version = "0.1.6" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 259 | dependencies = [ 260 | "generic-array", 261 | "typenum", 262 | ] 263 | 264 | [[package]] 265 | name = "darling" 266 | version = "0.20.10" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 269 | dependencies = [ 270 | "darling_core", 271 | "darling_macro", 272 | ] 273 | 274 | [[package]] 275 | name = "darling_core" 276 | version = "0.20.10" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 279 | dependencies = [ 280 | "fnv", 281 | "ident_case", 282 | "proc-macro2", 283 | "quote", 284 | "strsim", 285 | "syn", 286 | ] 287 | 288 | [[package]] 289 | name = "darling_macro" 290 | version = "0.20.10" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 293 | dependencies = [ 294 | "darling_core", 295 | "quote", 296 | "syn", 297 | ] 298 | 299 | [[package]] 300 | name = "deranged" 301 | version = "0.3.11" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 304 | dependencies = [ 305 | "powerfmt", 306 | "serde", 307 | ] 308 | 309 | [[package]] 310 | name = "digest" 311 | version = "0.10.7" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 314 | dependencies = [ 315 | "block-buffer", 316 | "crypto-common", 317 | "subtle", 318 | ] 319 | 320 | [[package]] 321 | name = "displaydoc" 322 | version = "0.2.5" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 325 | dependencies = [ 326 | "proc-macro2", 327 | "quote", 328 | "syn", 329 | ] 330 | 331 | [[package]] 332 | name = "dunce" 333 | version = "1.0.5" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 336 | 337 | [[package]] 338 | name = "ece" 339 | version = "2.3.1" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "c2ea1d2f2cc974957a4e2575d8e5bb494549bab66338d6320c2789abcfff5746" 342 | dependencies = [ 343 | "base64 0.21.7", 344 | "byteorder", 345 | "hex", 346 | "hkdf", 347 | "lazy_static", 348 | "once_cell", 349 | "openssl", 350 | "serde", 351 | "sha2", 352 | "thiserror", 353 | ] 354 | 355 | [[package]] 356 | name = "either" 357 | version = "1.15.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 360 | 361 | [[package]] 362 | name = "encoding_rs" 363 | version = "0.8.35" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 366 | dependencies = [ 367 | "cfg-if", 368 | ] 369 | 370 | [[package]] 371 | name = "equivalent" 372 | version = "1.0.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 375 | 376 | [[package]] 377 | name = "errno" 378 | version = "0.3.10" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 381 | dependencies = [ 382 | "libc", 383 | "windows-sys 0.59.0", 384 | ] 385 | 386 | [[package]] 387 | name = "fastrand" 388 | version = "2.3.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 391 | 392 | [[package]] 393 | name = "fcm-push-listener" 394 | version = "4.0.2" 395 | dependencies = [ 396 | "base64 0.22.1", 397 | "bytes", 398 | "ece", 399 | "log", 400 | "pin-project-lite", 401 | "prost", 402 | "prost-build", 403 | "rand", 404 | "reqwest", 405 | "rustls", 406 | "serde", 407 | "serde_with", 408 | "tokio", 409 | "tokio-rustls", 410 | "tokio-stream", 411 | "uuid", 412 | "webpki-roots", 413 | ] 414 | 415 | [[package]] 416 | name = "fixedbitset" 417 | version = "0.5.7" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" 420 | 421 | [[package]] 422 | name = "fnv" 423 | version = "1.0.7" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 426 | 427 | [[package]] 428 | name = "foreign-types" 429 | version = "0.3.2" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 432 | dependencies = [ 433 | "foreign-types-shared", 434 | ] 435 | 436 | [[package]] 437 | name = "foreign-types-shared" 438 | version = "0.1.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 441 | 442 | [[package]] 443 | name = "form_urlencoded" 444 | version = "1.2.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 447 | dependencies = [ 448 | "percent-encoding", 449 | ] 450 | 451 | [[package]] 452 | name = "fs_extra" 453 | version = "1.3.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 456 | 457 | [[package]] 458 | name = "futures-channel" 459 | version = "0.3.31" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 462 | dependencies = [ 463 | "futures-core", 464 | ] 465 | 466 | [[package]] 467 | name = "futures-core" 468 | version = "0.3.31" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 471 | 472 | [[package]] 473 | name = "futures-sink" 474 | version = "0.3.31" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 477 | 478 | [[package]] 479 | name = "futures-task" 480 | version = "0.3.31" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 483 | 484 | [[package]] 485 | name = "futures-util" 486 | version = "0.3.31" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 489 | dependencies = [ 490 | "futures-core", 491 | "futures-task", 492 | "pin-project-lite", 493 | "pin-utils", 494 | ] 495 | 496 | [[package]] 497 | name = "generic-array" 498 | version = "0.14.7" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 501 | dependencies = [ 502 | "typenum", 503 | "version_check", 504 | ] 505 | 506 | [[package]] 507 | name = "getrandom" 508 | version = "0.2.15" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 511 | dependencies = [ 512 | "cfg-if", 513 | "libc", 514 | "wasi 0.11.0+wasi-snapshot-preview1", 515 | ] 516 | 517 | [[package]] 518 | name = "getrandom" 519 | version = "0.3.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 522 | dependencies = [ 523 | "cfg-if", 524 | "libc", 525 | "wasi 0.13.3+wasi-0.2.2", 526 | "windows-targets", 527 | ] 528 | 529 | [[package]] 530 | name = "gimli" 531 | version = "0.31.1" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 534 | 535 | [[package]] 536 | name = "glob" 537 | version = "0.3.2" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 540 | 541 | [[package]] 542 | name = "h2" 543 | version = "0.4.8" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" 546 | dependencies = [ 547 | "atomic-waker", 548 | "bytes", 549 | "fnv", 550 | "futures-core", 551 | "futures-sink", 552 | "http", 553 | "indexmap 2.7.1", 554 | "slab", 555 | "tokio", 556 | "tokio-util", 557 | "tracing", 558 | ] 559 | 560 | [[package]] 561 | name = "hashbrown" 562 | version = "0.12.3" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 565 | 566 | [[package]] 567 | name = "hashbrown" 568 | version = "0.15.2" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 571 | 572 | [[package]] 573 | name = "heck" 574 | version = "0.5.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 577 | 578 | [[package]] 579 | name = "hex" 580 | version = "0.4.3" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 583 | 584 | [[package]] 585 | name = "hkdf" 586 | version = "0.12.4" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 589 | dependencies = [ 590 | "hmac", 591 | ] 592 | 593 | [[package]] 594 | name = "hmac" 595 | version = "0.12.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 598 | dependencies = [ 599 | "digest", 600 | ] 601 | 602 | [[package]] 603 | name = "home" 604 | version = "0.5.11" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 607 | dependencies = [ 608 | "windows-sys 0.59.0", 609 | ] 610 | 611 | [[package]] 612 | name = "http" 613 | version = "1.2.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 616 | dependencies = [ 617 | "bytes", 618 | "fnv", 619 | "itoa", 620 | ] 621 | 622 | [[package]] 623 | name = "http-body" 624 | version = "1.0.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 627 | dependencies = [ 628 | "bytes", 629 | "http", 630 | ] 631 | 632 | [[package]] 633 | name = "http-body-util" 634 | version = "0.1.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 637 | dependencies = [ 638 | "bytes", 639 | "futures-util", 640 | "http", 641 | "http-body", 642 | "pin-project-lite", 643 | ] 644 | 645 | [[package]] 646 | name = "httparse" 647 | version = "1.10.1" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 650 | 651 | [[package]] 652 | name = "hyper" 653 | version = "1.6.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 656 | dependencies = [ 657 | "bytes", 658 | "futures-channel", 659 | "futures-util", 660 | "h2", 661 | "http", 662 | "http-body", 663 | "httparse", 664 | "itoa", 665 | "pin-project-lite", 666 | "smallvec", 667 | "tokio", 668 | "want", 669 | ] 670 | 671 | [[package]] 672 | name = "hyper-rustls" 673 | version = "0.27.5" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" 676 | dependencies = [ 677 | "futures-util", 678 | "http", 679 | "hyper", 680 | "hyper-util", 681 | "rustls", 682 | "rustls-pki-types", 683 | "tokio", 684 | "tokio-rustls", 685 | "tower-service", 686 | ] 687 | 688 | [[package]] 689 | name = "hyper-tls" 690 | version = "0.6.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 693 | dependencies = [ 694 | "bytes", 695 | "http-body-util", 696 | "hyper", 697 | "hyper-util", 698 | "native-tls", 699 | "tokio", 700 | "tokio-native-tls", 701 | "tower-service", 702 | ] 703 | 704 | [[package]] 705 | name = "hyper-util" 706 | version = "0.1.10" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 709 | dependencies = [ 710 | "bytes", 711 | "futures-channel", 712 | "futures-util", 713 | "http", 714 | "http-body", 715 | "hyper", 716 | "pin-project-lite", 717 | "socket2", 718 | "tokio", 719 | "tower-service", 720 | "tracing", 721 | ] 722 | 723 | [[package]] 724 | name = "iana-time-zone" 725 | version = "0.1.61" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 728 | dependencies = [ 729 | "android_system_properties", 730 | "core-foundation-sys", 731 | "iana-time-zone-haiku", 732 | "js-sys", 733 | "wasm-bindgen", 734 | "windows-core", 735 | ] 736 | 737 | [[package]] 738 | name = "iana-time-zone-haiku" 739 | version = "0.1.2" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 742 | dependencies = [ 743 | "cc", 744 | ] 745 | 746 | [[package]] 747 | name = "icu_collections" 748 | version = "1.5.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 751 | dependencies = [ 752 | "displaydoc", 753 | "yoke", 754 | "zerofrom", 755 | "zerovec", 756 | ] 757 | 758 | [[package]] 759 | name = "icu_locid" 760 | version = "1.5.0" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 763 | dependencies = [ 764 | "displaydoc", 765 | "litemap", 766 | "tinystr", 767 | "writeable", 768 | "zerovec", 769 | ] 770 | 771 | [[package]] 772 | name = "icu_locid_transform" 773 | version = "1.5.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 776 | dependencies = [ 777 | "displaydoc", 778 | "icu_locid", 779 | "icu_locid_transform_data", 780 | "icu_provider", 781 | "tinystr", 782 | "zerovec", 783 | ] 784 | 785 | [[package]] 786 | name = "icu_locid_transform_data" 787 | version = "1.5.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 790 | 791 | [[package]] 792 | name = "icu_normalizer" 793 | version = "1.5.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 796 | dependencies = [ 797 | "displaydoc", 798 | "icu_collections", 799 | "icu_normalizer_data", 800 | "icu_properties", 801 | "icu_provider", 802 | "smallvec", 803 | "utf16_iter", 804 | "utf8_iter", 805 | "write16", 806 | "zerovec", 807 | ] 808 | 809 | [[package]] 810 | name = "icu_normalizer_data" 811 | version = "1.5.0" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 814 | 815 | [[package]] 816 | name = "icu_properties" 817 | version = "1.5.1" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 820 | dependencies = [ 821 | "displaydoc", 822 | "icu_collections", 823 | "icu_locid_transform", 824 | "icu_properties_data", 825 | "icu_provider", 826 | "tinystr", 827 | "zerovec", 828 | ] 829 | 830 | [[package]] 831 | name = "icu_properties_data" 832 | version = "1.5.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 835 | 836 | [[package]] 837 | name = "icu_provider" 838 | version = "1.5.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 841 | dependencies = [ 842 | "displaydoc", 843 | "icu_locid", 844 | "icu_provider_macros", 845 | "stable_deref_trait", 846 | "tinystr", 847 | "writeable", 848 | "yoke", 849 | "zerofrom", 850 | "zerovec", 851 | ] 852 | 853 | [[package]] 854 | name = "icu_provider_macros" 855 | version = "1.5.0" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 858 | dependencies = [ 859 | "proc-macro2", 860 | "quote", 861 | "syn", 862 | ] 863 | 864 | [[package]] 865 | name = "ident_case" 866 | version = "1.0.1" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 869 | 870 | [[package]] 871 | name = "idna" 872 | version = "1.0.3" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 875 | dependencies = [ 876 | "idna_adapter", 877 | "smallvec", 878 | "utf8_iter", 879 | ] 880 | 881 | [[package]] 882 | name = "idna_adapter" 883 | version = "1.2.0" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 886 | dependencies = [ 887 | "icu_normalizer", 888 | "icu_properties", 889 | ] 890 | 891 | [[package]] 892 | name = "indexmap" 893 | version = "1.9.3" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 896 | dependencies = [ 897 | "autocfg", 898 | "hashbrown 0.12.3", 899 | "serde", 900 | ] 901 | 902 | [[package]] 903 | name = "indexmap" 904 | version = "2.7.1" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 907 | dependencies = [ 908 | "equivalent", 909 | "hashbrown 0.15.2", 910 | "serde", 911 | ] 912 | 913 | [[package]] 914 | name = "ipnet" 915 | version = "2.11.0" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 918 | 919 | [[package]] 920 | name = "itertools" 921 | version = "0.12.1" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 924 | dependencies = [ 925 | "either", 926 | ] 927 | 928 | [[package]] 929 | name = "itertools" 930 | version = "0.14.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 933 | dependencies = [ 934 | "either", 935 | ] 936 | 937 | [[package]] 938 | name = "itoa" 939 | version = "1.0.15" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 942 | 943 | [[package]] 944 | name = "jobserver" 945 | version = "0.1.32" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 948 | dependencies = [ 949 | "libc", 950 | ] 951 | 952 | [[package]] 953 | name = "js-sys" 954 | version = "0.3.77" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 957 | dependencies = [ 958 | "once_cell", 959 | "wasm-bindgen", 960 | ] 961 | 962 | [[package]] 963 | name = "lazy_static" 964 | version = "1.5.0" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 967 | 968 | [[package]] 969 | name = "lazycell" 970 | version = "1.3.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 973 | 974 | [[package]] 975 | name = "libc" 976 | version = "0.2.170" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" 979 | 980 | [[package]] 981 | name = "libloading" 982 | version = "0.8.6" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 985 | dependencies = [ 986 | "cfg-if", 987 | "windows-targets", 988 | ] 989 | 990 | [[package]] 991 | name = "linux-raw-sys" 992 | version = "0.4.15" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 995 | 996 | [[package]] 997 | name = "linux-raw-sys" 998 | version = "0.9.2" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" 1001 | 1002 | [[package]] 1003 | name = "litemap" 1004 | version = "0.7.5" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 1007 | 1008 | [[package]] 1009 | name = "log" 1010 | version = "0.4.26" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 1013 | 1014 | [[package]] 1015 | name = "memchr" 1016 | version = "2.7.4" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1019 | 1020 | [[package]] 1021 | name = "mime" 1022 | version = "0.3.17" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1025 | 1026 | [[package]] 1027 | name = "minimal-lexical" 1028 | version = "0.2.1" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1031 | 1032 | [[package]] 1033 | name = "miniz_oxide" 1034 | version = "0.8.5" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 1037 | dependencies = [ 1038 | "adler2", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "mio" 1043 | version = "1.0.3" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1046 | dependencies = [ 1047 | "libc", 1048 | "wasi 0.11.0+wasi-snapshot-preview1", 1049 | "windows-sys 0.52.0", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "multimap" 1054 | version = "0.10.0" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" 1057 | 1058 | [[package]] 1059 | name = "native-tls" 1060 | version = "0.2.14" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1063 | dependencies = [ 1064 | "libc", 1065 | "log", 1066 | "openssl", 1067 | "openssl-probe", 1068 | "openssl-sys", 1069 | "schannel", 1070 | "security-framework", 1071 | "security-framework-sys", 1072 | "tempfile", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "nom" 1077 | version = "7.1.3" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1080 | dependencies = [ 1081 | "memchr", 1082 | "minimal-lexical", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "num-conv" 1087 | version = "0.1.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1090 | 1091 | [[package]] 1092 | name = "num-traits" 1093 | version = "0.2.19" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1096 | dependencies = [ 1097 | "autocfg", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "object" 1102 | version = "0.36.7" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1105 | dependencies = [ 1106 | "memchr", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "once_cell" 1111 | version = "1.21.0" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" 1114 | 1115 | [[package]] 1116 | name = "openssl" 1117 | version = "0.10.71" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" 1120 | dependencies = [ 1121 | "bitflags", 1122 | "cfg-if", 1123 | "foreign-types", 1124 | "libc", 1125 | "once_cell", 1126 | "openssl-macros", 1127 | "openssl-sys", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "openssl-macros" 1132 | version = "0.1.1" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1135 | dependencies = [ 1136 | "proc-macro2", 1137 | "quote", 1138 | "syn", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "openssl-probe" 1143 | version = "0.1.6" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1146 | 1147 | [[package]] 1148 | name = "openssl-sys" 1149 | version = "0.9.106" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" 1152 | dependencies = [ 1153 | "cc", 1154 | "libc", 1155 | "pkg-config", 1156 | "vcpkg", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "paste" 1161 | version = "1.0.15" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1164 | 1165 | [[package]] 1166 | name = "percent-encoding" 1167 | version = "2.3.1" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1170 | 1171 | [[package]] 1172 | name = "petgraph" 1173 | version = "0.7.1" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" 1176 | dependencies = [ 1177 | "fixedbitset", 1178 | "indexmap 2.7.1", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "pin-project-lite" 1183 | version = "0.2.16" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1186 | 1187 | [[package]] 1188 | name = "pin-utils" 1189 | version = "0.1.0" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1192 | 1193 | [[package]] 1194 | name = "pkg-config" 1195 | version = "0.3.32" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1198 | 1199 | [[package]] 1200 | name = "powerfmt" 1201 | version = "0.2.0" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1204 | 1205 | [[package]] 1206 | name = "ppv-lite86" 1207 | version = "0.2.21" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1210 | dependencies = [ 1211 | "zerocopy", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "prettyplease" 1216 | version = "0.2.30" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" 1219 | dependencies = [ 1220 | "proc-macro2", 1221 | "syn", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "proc-macro2" 1226 | version = "1.0.94" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 1229 | dependencies = [ 1230 | "unicode-ident", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "prost" 1235 | version = "0.13.5" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" 1238 | dependencies = [ 1239 | "bytes", 1240 | "prost-derive", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "prost-build" 1245 | version = "0.13.5" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" 1248 | dependencies = [ 1249 | "heck", 1250 | "itertools 0.14.0", 1251 | "log", 1252 | "multimap", 1253 | "once_cell", 1254 | "petgraph", 1255 | "prettyplease", 1256 | "prost", 1257 | "prost-types", 1258 | "regex", 1259 | "syn", 1260 | "tempfile", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "prost-derive" 1265 | version = "0.13.5" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" 1268 | dependencies = [ 1269 | "anyhow", 1270 | "itertools 0.14.0", 1271 | "proc-macro2", 1272 | "quote", 1273 | "syn", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "prost-types" 1278 | version = "0.13.5" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" 1281 | dependencies = [ 1282 | "prost", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "quote" 1287 | version = "1.0.39" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" 1290 | dependencies = [ 1291 | "proc-macro2", 1292 | ] 1293 | 1294 | [[package]] 1295 | name = "rand" 1296 | version = "0.9.0" 1297 | source = "registry+https://github.com/rust-lang/crates.io-index" 1298 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 1299 | dependencies = [ 1300 | "rand_chacha", 1301 | "rand_core", 1302 | "zerocopy", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "rand_chacha" 1307 | version = "0.9.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1310 | dependencies = [ 1311 | "ppv-lite86", 1312 | "rand_core", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "rand_core" 1317 | version = "0.9.3" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1320 | dependencies = [ 1321 | "getrandom 0.3.1", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "regex" 1326 | version = "1.11.1" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1329 | dependencies = [ 1330 | "aho-corasick", 1331 | "memchr", 1332 | "regex-automata", 1333 | "regex-syntax", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "regex-automata" 1338 | version = "0.4.9" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1341 | dependencies = [ 1342 | "aho-corasick", 1343 | "memchr", 1344 | "regex-syntax", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "regex-syntax" 1349 | version = "0.8.5" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1352 | 1353 | [[package]] 1354 | name = "reqwest" 1355 | version = "0.12.12" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" 1358 | dependencies = [ 1359 | "base64 0.22.1", 1360 | "bytes", 1361 | "encoding_rs", 1362 | "futures-core", 1363 | "futures-util", 1364 | "h2", 1365 | "http", 1366 | "http-body", 1367 | "http-body-util", 1368 | "hyper", 1369 | "hyper-rustls", 1370 | "hyper-tls", 1371 | "hyper-util", 1372 | "ipnet", 1373 | "js-sys", 1374 | "log", 1375 | "mime", 1376 | "native-tls", 1377 | "once_cell", 1378 | "percent-encoding", 1379 | "pin-project-lite", 1380 | "rustls-pemfile", 1381 | "serde", 1382 | "serde_json", 1383 | "serde_urlencoded", 1384 | "sync_wrapper", 1385 | "system-configuration", 1386 | "tokio", 1387 | "tokio-native-tls", 1388 | "tower", 1389 | "tower-service", 1390 | "url", 1391 | "wasm-bindgen", 1392 | "wasm-bindgen-futures", 1393 | "web-sys", 1394 | "windows-registry", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "ring" 1399 | version = "0.17.13" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" 1402 | dependencies = [ 1403 | "cc", 1404 | "cfg-if", 1405 | "getrandom 0.2.15", 1406 | "libc", 1407 | "untrusted", 1408 | "windows-sys 0.52.0", 1409 | ] 1410 | 1411 | [[package]] 1412 | name = "rustc-demangle" 1413 | version = "0.1.24" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1416 | 1417 | [[package]] 1418 | name = "rustc-hash" 1419 | version = "1.1.0" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1422 | 1423 | [[package]] 1424 | name = "rustix" 1425 | version = "0.38.44" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1428 | dependencies = [ 1429 | "bitflags", 1430 | "errno", 1431 | "libc", 1432 | "linux-raw-sys 0.4.15", 1433 | "windows-sys 0.59.0", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "rustix" 1438 | version = "1.0.2" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" 1441 | dependencies = [ 1442 | "bitflags", 1443 | "errno", 1444 | "libc", 1445 | "linux-raw-sys 0.9.2", 1446 | "windows-sys 0.59.0", 1447 | ] 1448 | 1449 | [[package]] 1450 | name = "rustls" 1451 | version = "0.23.23" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" 1454 | dependencies = [ 1455 | "aws-lc-rs", 1456 | "log", 1457 | "once_cell", 1458 | "ring", 1459 | "rustls-pki-types", 1460 | "rustls-webpki", 1461 | "subtle", 1462 | "zeroize", 1463 | ] 1464 | 1465 | [[package]] 1466 | name = "rustls-pemfile" 1467 | version = "2.2.0" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1470 | dependencies = [ 1471 | "rustls-pki-types", 1472 | ] 1473 | 1474 | [[package]] 1475 | name = "rustls-pki-types" 1476 | version = "1.11.0" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1479 | 1480 | [[package]] 1481 | name = "rustls-webpki" 1482 | version = "0.102.8" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1485 | dependencies = [ 1486 | "aws-lc-rs", 1487 | "ring", 1488 | "rustls-pki-types", 1489 | "untrusted", 1490 | ] 1491 | 1492 | [[package]] 1493 | name = "rustversion" 1494 | version = "1.0.20" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1497 | 1498 | [[package]] 1499 | name = "ryu" 1500 | version = "1.0.20" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1503 | 1504 | [[package]] 1505 | name = "schannel" 1506 | version = "0.1.27" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1509 | dependencies = [ 1510 | "windows-sys 0.59.0", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "security-framework" 1515 | version = "2.11.1" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1518 | dependencies = [ 1519 | "bitflags", 1520 | "core-foundation", 1521 | "core-foundation-sys", 1522 | "libc", 1523 | "security-framework-sys", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "security-framework-sys" 1528 | version = "2.14.0" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1531 | dependencies = [ 1532 | "core-foundation-sys", 1533 | "libc", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "serde" 1538 | version = "1.0.219" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1541 | dependencies = [ 1542 | "serde_derive", 1543 | ] 1544 | 1545 | [[package]] 1546 | name = "serde_derive" 1547 | version = "1.0.219" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1550 | dependencies = [ 1551 | "proc-macro2", 1552 | "quote", 1553 | "syn", 1554 | ] 1555 | 1556 | [[package]] 1557 | name = "serde_json" 1558 | version = "1.0.140" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1561 | dependencies = [ 1562 | "itoa", 1563 | "memchr", 1564 | "ryu", 1565 | "serde", 1566 | ] 1567 | 1568 | [[package]] 1569 | name = "serde_urlencoded" 1570 | version = "0.7.1" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1573 | dependencies = [ 1574 | "form_urlencoded", 1575 | "itoa", 1576 | "ryu", 1577 | "serde", 1578 | ] 1579 | 1580 | [[package]] 1581 | name = "serde_with" 1582 | version = "3.12.0" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" 1585 | dependencies = [ 1586 | "base64 0.22.1", 1587 | "chrono", 1588 | "hex", 1589 | "indexmap 1.9.3", 1590 | "indexmap 2.7.1", 1591 | "serde", 1592 | "serde_derive", 1593 | "serde_json", 1594 | "serde_with_macros", 1595 | "time", 1596 | ] 1597 | 1598 | [[package]] 1599 | name = "serde_with_macros" 1600 | version = "3.12.0" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" 1603 | dependencies = [ 1604 | "darling", 1605 | "proc-macro2", 1606 | "quote", 1607 | "syn", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "sha2" 1612 | version = "0.10.8" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1615 | dependencies = [ 1616 | "cfg-if", 1617 | "cpufeatures", 1618 | "digest", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "shlex" 1623 | version = "1.3.0" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1626 | 1627 | [[package]] 1628 | name = "slab" 1629 | version = "0.4.9" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1632 | dependencies = [ 1633 | "autocfg", 1634 | ] 1635 | 1636 | [[package]] 1637 | name = "smallvec" 1638 | version = "1.14.0" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 1641 | 1642 | [[package]] 1643 | name = "socket2" 1644 | version = "0.5.8" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1647 | dependencies = [ 1648 | "libc", 1649 | "windows-sys 0.52.0", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "stable_deref_trait" 1654 | version = "1.2.0" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1657 | 1658 | [[package]] 1659 | name = "strsim" 1660 | version = "0.11.1" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1663 | 1664 | [[package]] 1665 | name = "subtle" 1666 | version = "2.6.1" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1669 | 1670 | [[package]] 1671 | name = "syn" 1672 | version = "2.0.100" 1673 | source = "registry+https://github.com/rust-lang/crates.io-index" 1674 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 1675 | dependencies = [ 1676 | "proc-macro2", 1677 | "quote", 1678 | "unicode-ident", 1679 | ] 1680 | 1681 | [[package]] 1682 | name = "sync_wrapper" 1683 | version = "1.0.2" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1686 | dependencies = [ 1687 | "futures-core", 1688 | ] 1689 | 1690 | [[package]] 1691 | name = "synstructure" 1692 | version = "0.13.1" 1693 | source = "registry+https://github.com/rust-lang/crates.io-index" 1694 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1695 | dependencies = [ 1696 | "proc-macro2", 1697 | "quote", 1698 | "syn", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "system-configuration" 1703 | version = "0.6.1" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1706 | dependencies = [ 1707 | "bitflags", 1708 | "core-foundation", 1709 | "system-configuration-sys", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "system-configuration-sys" 1714 | version = "0.6.0" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1717 | dependencies = [ 1718 | "core-foundation-sys", 1719 | "libc", 1720 | ] 1721 | 1722 | [[package]] 1723 | name = "tempfile" 1724 | version = "3.18.0" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" 1727 | dependencies = [ 1728 | "cfg-if", 1729 | "fastrand", 1730 | "getrandom 0.3.1", 1731 | "once_cell", 1732 | "rustix 1.0.2", 1733 | "windows-sys 0.59.0", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "thiserror" 1738 | version = "1.0.69" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1741 | dependencies = [ 1742 | "thiserror-impl", 1743 | ] 1744 | 1745 | [[package]] 1746 | name = "thiserror-impl" 1747 | version = "1.0.69" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1750 | dependencies = [ 1751 | "proc-macro2", 1752 | "quote", 1753 | "syn", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "time" 1758 | version = "0.3.39" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" 1761 | dependencies = [ 1762 | "deranged", 1763 | "itoa", 1764 | "num-conv", 1765 | "powerfmt", 1766 | "serde", 1767 | "time-core", 1768 | "time-macros", 1769 | ] 1770 | 1771 | [[package]] 1772 | name = "time-core" 1773 | version = "0.1.3" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" 1776 | 1777 | [[package]] 1778 | name = "time-macros" 1779 | version = "0.2.20" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" 1782 | dependencies = [ 1783 | "num-conv", 1784 | "time-core", 1785 | ] 1786 | 1787 | [[package]] 1788 | name = "tinystr" 1789 | version = "0.7.6" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1792 | dependencies = [ 1793 | "displaydoc", 1794 | "zerovec", 1795 | ] 1796 | 1797 | [[package]] 1798 | name = "tokio" 1799 | version = "1.44.0" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" 1802 | dependencies = [ 1803 | "backtrace", 1804 | "bytes", 1805 | "libc", 1806 | "mio", 1807 | "pin-project-lite", 1808 | "socket2", 1809 | "tokio-macros", 1810 | "windows-sys 0.52.0", 1811 | ] 1812 | 1813 | [[package]] 1814 | name = "tokio-macros" 1815 | version = "2.5.0" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1818 | dependencies = [ 1819 | "proc-macro2", 1820 | "quote", 1821 | "syn", 1822 | ] 1823 | 1824 | [[package]] 1825 | name = "tokio-native-tls" 1826 | version = "0.3.1" 1827 | source = "registry+https://github.com/rust-lang/crates.io-index" 1828 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1829 | dependencies = [ 1830 | "native-tls", 1831 | "tokio", 1832 | ] 1833 | 1834 | [[package]] 1835 | name = "tokio-rustls" 1836 | version = "0.26.2" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 1839 | dependencies = [ 1840 | "rustls", 1841 | "tokio", 1842 | ] 1843 | 1844 | [[package]] 1845 | name = "tokio-stream" 1846 | version = "0.1.17" 1847 | source = "registry+https://github.com/rust-lang/crates.io-index" 1848 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 1849 | dependencies = [ 1850 | "futures-core", 1851 | "pin-project-lite", 1852 | "tokio", 1853 | ] 1854 | 1855 | [[package]] 1856 | name = "tokio-util" 1857 | version = "0.7.13" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" 1860 | dependencies = [ 1861 | "bytes", 1862 | "futures-core", 1863 | "futures-sink", 1864 | "pin-project-lite", 1865 | "tokio", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "tower" 1870 | version = "0.5.2" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1873 | dependencies = [ 1874 | "futures-core", 1875 | "futures-util", 1876 | "pin-project-lite", 1877 | "sync_wrapper", 1878 | "tokio", 1879 | "tower-layer", 1880 | "tower-service", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "tower-layer" 1885 | version = "0.3.3" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1888 | 1889 | [[package]] 1890 | name = "tower-service" 1891 | version = "0.3.3" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1894 | 1895 | [[package]] 1896 | name = "tracing" 1897 | version = "0.1.41" 1898 | source = "registry+https://github.com/rust-lang/crates.io-index" 1899 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1900 | dependencies = [ 1901 | "pin-project-lite", 1902 | "tracing-core", 1903 | ] 1904 | 1905 | [[package]] 1906 | name = "tracing-core" 1907 | version = "0.1.33" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1910 | dependencies = [ 1911 | "once_cell", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "try-lock" 1916 | version = "0.2.5" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1919 | 1920 | [[package]] 1921 | name = "typenum" 1922 | version = "1.18.0" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 1925 | 1926 | [[package]] 1927 | name = "unicode-ident" 1928 | version = "1.0.18" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1931 | 1932 | [[package]] 1933 | name = "untrusted" 1934 | version = "0.9.0" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1937 | 1938 | [[package]] 1939 | name = "url" 1940 | version = "2.5.4" 1941 | source = "registry+https://github.com/rust-lang/crates.io-index" 1942 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1943 | dependencies = [ 1944 | "form_urlencoded", 1945 | "idna", 1946 | "percent-encoding", 1947 | ] 1948 | 1949 | [[package]] 1950 | name = "utf16_iter" 1951 | version = "1.0.5" 1952 | source = "registry+https://github.com/rust-lang/crates.io-index" 1953 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1954 | 1955 | [[package]] 1956 | name = "utf8_iter" 1957 | version = "1.0.4" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1960 | 1961 | [[package]] 1962 | name = "uuid" 1963 | version = "1.15.1" 1964 | source = "registry+https://github.com/rust-lang/crates.io-index" 1965 | checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" 1966 | dependencies = [ 1967 | "getrandom 0.3.1", 1968 | "rand", 1969 | "uuid-macro-internal", 1970 | ] 1971 | 1972 | [[package]] 1973 | name = "uuid-macro-internal" 1974 | version = "1.15.1" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "9521621447c21497fac206ffe6e9f642f977c4f82eeba9201055f64884d9cb01" 1977 | dependencies = [ 1978 | "proc-macro2", 1979 | "quote", 1980 | "syn", 1981 | ] 1982 | 1983 | [[package]] 1984 | name = "vcpkg" 1985 | version = "0.2.15" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1988 | 1989 | [[package]] 1990 | name = "version_check" 1991 | version = "0.9.5" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1994 | 1995 | [[package]] 1996 | name = "want" 1997 | version = "0.3.1" 1998 | source = "registry+https://github.com/rust-lang/crates.io-index" 1999 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2000 | dependencies = [ 2001 | "try-lock", 2002 | ] 2003 | 2004 | [[package]] 2005 | name = "wasi" 2006 | version = "0.11.0+wasi-snapshot-preview1" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2009 | 2010 | [[package]] 2011 | name = "wasi" 2012 | version = "0.13.3+wasi-0.2.2" 2013 | source = "registry+https://github.com/rust-lang/crates.io-index" 2014 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 2015 | dependencies = [ 2016 | "wit-bindgen-rt", 2017 | ] 2018 | 2019 | [[package]] 2020 | name = "wasm-bindgen" 2021 | version = "0.2.100" 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" 2023 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2024 | dependencies = [ 2025 | "cfg-if", 2026 | "once_cell", 2027 | "rustversion", 2028 | "wasm-bindgen-macro", 2029 | ] 2030 | 2031 | [[package]] 2032 | name = "wasm-bindgen-backend" 2033 | version = "0.2.100" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2036 | dependencies = [ 2037 | "bumpalo", 2038 | "log", 2039 | "proc-macro2", 2040 | "quote", 2041 | "syn", 2042 | "wasm-bindgen-shared", 2043 | ] 2044 | 2045 | [[package]] 2046 | name = "wasm-bindgen-futures" 2047 | version = "0.4.50" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2050 | dependencies = [ 2051 | "cfg-if", 2052 | "js-sys", 2053 | "once_cell", 2054 | "wasm-bindgen", 2055 | "web-sys", 2056 | ] 2057 | 2058 | [[package]] 2059 | name = "wasm-bindgen-macro" 2060 | version = "0.2.100" 2061 | source = "registry+https://github.com/rust-lang/crates.io-index" 2062 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2063 | dependencies = [ 2064 | "quote", 2065 | "wasm-bindgen-macro-support", 2066 | ] 2067 | 2068 | [[package]] 2069 | name = "wasm-bindgen-macro-support" 2070 | version = "0.2.100" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2073 | dependencies = [ 2074 | "proc-macro2", 2075 | "quote", 2076 | "syn", 2077 | "wasm-bindgen-backend", 2078 | "wasm-bindgen-shared", 2079 | ] 2080 | 2081 | [[package]] 2082 | name = "wasm-bindgen-shared" 2083 | version = "0.2.100" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2086 | dependencies = [ 2087 | "unicode-ident", 2088 | ] 2089 | 2090 | [[package]] 2091 | name = "web-sys" 2092 | version = "0.3.77" 2093 | source = "registry+https://github.com/rust-lang/crates.io-index" 2094 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2095 | dependencies = [ 2096 | "js-sys", 2097 | "wasm-bindgen", 2098 | ] 2099 | 2100 | [[package]] 2101 | name = "webpki-roots" 2102 | version = "0.26.8" 2103 | source = "registry+https://github.com/rust-lang/crates.io-index" 2104 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 2105 | dependencies = [ 2106 | "rustls-pki-types", 2107 | ] 2108 | 2109 | [[package]] 2110 | name = "which" 2111 | version = "4.4.2" 2112 | source = "registry+https://github.com/rust-lang/crates.io-index" 2113 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 2114 | dependencies = [ 2115 | "either", 2116 | "home", 2117 | "once_cell", 2118 | "rustix 0.38.44", 2119 | ] 2120 | 2121 | [[package]] 2122 | name = "windows-core" 2123 | version = "0.52.0" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2126 | dependencies = [ 2127 | "windows-targets", 2128 | ] 2129 | 2130 | [[package]] 2131 | name = "windows-link" 2132 | version = "0.1.0" 2133 | source = "registry+https://github.com/rust-lang/crates.io-index" 2134 | checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" 2135 | 2136 | [[package]] 2137 | name = "windows-registry" 2138 | version = "0.2.0" 2139 | source = "registry+https://github.com/rust-lang/crates.io-index" 2140 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 2141 | dependencies = [ 2142 | "windows-result", 2143 | "windows-strings", 2144 | "windows-targets", 2145 | ] 2146 | 2147 | [[package]] 2148 | name = "windows-result" 2149 | version = "0.2.0" 2150 | source = "registry+https://github.com/rust-lang/crates.io-index" 2151 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 2152 | dependencies = [ 2153 | "windows-targets", 2154 | ] 2155 | 2156 | [[package]] 2157 | name = "windows-strings" 2158 | version = "0.1.0" 2159 | source = "registry+https://github.com/rust-lang/crates.io-index" 2160 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 2161 | dependencies = [ 2162 | "windows-result", 2163 | "windows-targets", 2164 | ] 2165 | 2166 | [[package]] 2167 | name = "windows-sys" 2168 | version = "0.52.0" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2171 | dependencies = [ 2172 | "windows-targets", 2173 | ] 2174 | 2175 | [[package]] 2176 | name = "windows-sys" 2177 | version = "0.59.0" 2178 | source = "registry+https://github.com/rust-lang/crates.io-index" 2179 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2180 | dependencies = [ 2181 | "windows-targets", 2182 | ] 2183 | 2184 | [[package]] 2185 | name = "windows-targets" 2186 | version = "0.52.6" 2187 | source = "registry+https://github.com/rust-lang/crates.io-index" 2188 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2189 | dependencies = [ 2190 | "windows_aarch64_gnullvm", 2191 | "windows_aarch64_msvc", 2192 | "windows_i686_gnu", 2193 | "windows_i686_gnullvm", 2194 | "windows_i686_msvc", 2195 | "windows_x86_64_gnu", 2196 | "windows_x86_64_gnullvm", 2197 | "windows_x86_64_msvc", 2198 | ] 2199 | 2200 | [[package]] 2201 | name = "windows_aarch64_gnullvm" 2202 | version = "0.52.6" 2203 | source = "registry+https://github.com/rust-lang/crates.io-index" 2204 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2205 | 2206 | [[package]] 2207 | name = "windows_aarch64_msvc" 2208 | version = "0.52.6" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2211 | 2212 | [[package]] 2213 | name = "windows_i686_gnu" 2214 | version = "0.52.6" 2215 | source = "registry+https://github.com/rust-lang/crates.io-index" 2216 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2217 | 2218 | [[package]] 2219 | name = "windows_i686_gnullvm" 2220 | version = "0.52.6" 2221 | source = "registry+https://github.com/rust-lang/crates.io-index" 2222 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2223 | 2224 | [[package]] 2225 | name = "windows_i686_msvc" 2226 | version = "0.52.6" 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" 2228 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2229 | 2230 | [[package]] 2231 | name = "windows_x86_64_gnu" 2232 | version = "0.52.6" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2235 | 2236 | [[package]] 2237 | name = "windows_x86_64_gnullvm" 2238 | version = "0.52.6" 2239 | source = "registry+https://github.com/rust-lang/crates.io-index" 2240 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2241 | 2242 | [[package]] 2243 | name = "windows_x86_64_msvc" 2244 | version = "0.52.6" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2247 | 2248 | [[package]] 2249 | name = "wit-bindgen-rt" 2250 | version = "0.33.0" 2251 | source = "registry+https://github.com/rust-lang/crates.io-index" 2252 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 2253 | dependencies = [ 2254 | "bitflags", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "write16" 2259 | version = "1.0.0" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2262 | 2263 | [[package]] 2264 | name = "writeable" 2265 | version = "0.5.5" 2266 | source = "registry+https://github.com/rust-lang/crates.io-index" 2267 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2268 | 2269 | [[package]] 2270 | name = "yoke" 2271 | version = "0.7.5" 2272 | source = "registry+https://github.com/rust-lang/crates.io-index" 2273 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2274 | dependencies = [ 2275 | "serde", 2276 | "stable_deref_trait", 2277 | "yoke-derive", 2278 | "zerofrom", 2279 | ] 2280 | 2281 | [[package]] 2282 | name = "yoke-derive" 2283 | version = "0.7.5" 2284 | source = "registry+https://github.com/rust-lang/crates.io-index" 2285 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2286 | dependencies = [ 2287 | "proc-macro2", 2288 | "quote", 2289 | "syn", 2290 | "synstructure", 2291 | ] 2292 | 2293 | [[package]] 2294 | name = "zerocopy" 2295 | version = "0.8.23" 2296 | source = "registry+https://github.com/rust-lang/crates.io-index" 2297 | checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" 2298 | dependencies = [ 2299 | "zerocopy-derive", 2300 | ] 2301 | 2302 | [[package]] 2303 | name = "zerocopy-derive" 2304 | version = "0.8.23" 2305 | source = "registry+https://github.com/rust-lang/crates.io-index" 2306 | checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" 2307 | dependencies = [ 2308 | "proc-macro2", 2309 | "quote", 2310 | "syn", 2311 | ] 2312 | 2313 | [[package]] 2314 | name = "zerofrom" 2315 | version = "0.1.6" 2316 | source = "registry+https://github.com/rust-lang/crates.io-index" 2317 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2318 | dependencies = [ 2319 | "zerofrom-derive", 2320 | ] 2321 | 2322 | [[package]] 2323 | name = "zerofrom-derive" 2324 | version = "0.1.6" 2325 | source = "registry+https://github.com/rust-lang/crates.io-index" 2326 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2327 | dependencies = [ 2328 | "proc-macro2", 2329 | "quote", 2330 | "syn", 2331 | "synstructure", 2332 | ] 2333 | 2334 | [[package]] 2335 | name = "zeroize" 2336 | version = "1.8.1" 2337 | source = "registry+https://github.com/rust-lang/crates.io-index" 2338 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2339 | 2340 | [[package]] 2341 | name = "zerovec" 2342 | version = "0.10.4" 2343 | source = "registry+https://github.com/rust-lang/crates.io-index" 2344 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2345 | dependencies = [ 2346 | "yoke", 2347 | "zerofrom", 2348 | "zerovec-derive", 2349 | ] 2350 | 2351 | [[package]] 2352 | name = "zerovec-derive" 2353 | version = "0.10.3" 2354 | source = "registry+https://github.com/rust-lang/crates.io-index" 2355 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2356 | dependencies = [ 2357 | "proc-macro2", 2358 | "quote", 2359 | "syn", 2360 | ] 2361 | --------------------------------------------------------------------------------