From 1bbfb981d4bc9b91f57142d69ba2dc4afcae528f Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Apr 2026 12:57:49 -0700 Subject: [PATCH 1/5] rust: update dependencies - chrono->jiff as chrono is going into maintence mode https://github.com/chronotope/chrono/issues/1423#issuecomment-3846148832 - async-trait replaced when possible with Rust 1.75+ native async traits; kept for compat with russh - futures->futures_util, underlying library for just the things we need - urlencoding->percent-encoding as it's already a dep of the url module, so just one less dependency - general version updates Verified in the VS Code CLI that tunnel functionality still works as expected with these updates. --- rs/.vscode/settings.json | 4 + rs/Cargo.lock | 107 ++++++++++++------ rs/Cargo.toml | 14 ++- rs/src/connections/relay_tunnel_host.rs | 4 +- rs/src/connections/ws.rs | 8 +- rs/src/contracts/tunnel.rs | 6 +- .../contracts/tunnel_access_control_entry.rs | 4 +- rs/src/contracts/tunnel_event.rs | 4 +- rs/src/management/authorization.rs | 12 +- rs/src/management/http_client.rs | 23 ++-- rs/src/management/policy_provider.rs | 4 +- rs/src/tunnel.rs | 4 +- 12 files changed, 119 insertions(+), 75 deletions(-) create mode 100644 rs/.vscode/settings.json diff --git a/rs/.vscode/settings.json b/rs/.vscode/settings.json new file mode 100644 index 00000000..ae4be7dc --- /dev/null +++ b/rs/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.cargo.features": ["connections", "vendored-openssl"], + "editor.formatOnSave": true +} diff --git a/rs/Cargo.lock b/rs/Cargo.lock index 7eec8a14..cc1f80ca 100644 --- a/rs/Cargo.lock +++ b/rs/Cargo.lock @@ -197,17 +197,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "num-integer", - "num-traits", - "serde", -] - [[package]] name = "cipher" version = "0.4.3" @@ -495,7 +484,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.117", ] [[package]] @@ -758,6 +747,30 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "jiff" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -791,12 +804,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "matches" @@ -937,7 +947,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.117", ] [[package]] @@ -1080,9 +1090,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -1125,6 +1135,21 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1133,18 +1158,18 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1432,22 +1457,32 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 1.0.96", + "syn 2.0.117", ] [[package]] @@ -1564,9 +1599,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1771,12 +1806,13 @@ name = "tunnels" version = "0.1.0" dependencies = [ "async-trait", - "chrono", - "futures", + "futures-util", "hyper", + "jiff", "log", "opentelemetry", "os_info", + "percent-encoding", "rand 0.8.5", "regex", "reqwest", @@ -1790,7 +1826,6 @@ dependencies = [ "tokio-util", "tungstenite", "url", - "urlencoding", "uuid", "winreg 0.8.0", ] @@ -1926,7 +1961,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -1960,7 +1995,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.117", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/rs/Cargo.toml b/rs/Cargo.toml index 32bfe532..427bf8cc 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -5,29 +5,29 @@ edition = "2021" [dependencies] serde = { version = "1", features = ["derive"] } -chrono = { version = "0.4", features = ["serde"], default-features = false } +jiff = { version = "0.2", default-features = false, features = ["std", "serde"] } reqwest = { version = "0.11", features = ["default", "json"] } url = "2" opentelemetry = { version = "0.20", features = ["trace"], optional = true } serde_json = "1" -async-trait = "0.1" +async-trait = { version = "0.1", optional = true } thiserror = "1.0" log = "0.4" tokio = { version = "1.20", features = ["macros", "io-util", "time"], optional = true } tokio-util = { version = "0.7", optional = true } tokio-tungstenite = { version = "0.20", optional = true, features = ["native-tls"] } -futures = { version = "0.3", optional = true } +futures-util = { version = "0.3", default-features = false, features = ["sink"], optional = true } tungstenite = { version = "0.20", optional = true, features = ["native-tls"] } uuid = { version = "1.4", features = ["v4"], optional = true } rand = "0.8.5" russh = { version = "0.37.1", default-features = false, features = ["openssl", "flate2"], optional = true } russh-keys = { version = "0.37.1", default-features = false, features = ["openssl"], optional = true } -hyper = "0.14" +hyper = { version = "0.14", optional = true } os_info = { version = "3", default-features = false } [target.'cfg(windows)'.dependencies] winreg = "0.8" -urlencoding = "2.1.3" +percent-encoding = "2.3" [dev-dependencies] tokio = { version = "1.20", features = ["full"] } @@ -39,9 +39,11 @@ default = [] end_to_end = [] instrumentation = ["dep:opentelemetry"] connections = [ + "dep:async-trait", "dep:tokio", "dep:tokio-util", - "dep:futures", + "dep:futures-util", + "dep:hyper", "dep:tokio-tungstenite", "dep:tungstenite", "dep:uuid", diff --git a/rs/src/connections/relay_tunnel_host.rs b/rs/src/connections/relay_tunnel_host.rs index b800164a..62338980 100644 --- a/rs/src/connections/relay_tunnel_host.rs +++ b/rs/src/connections/relay_tunnel_host.rs @@ -19,7 +19,7 @@ use crate::{ }, }; use async_trait::async_trait; -use futures::{stream::FuturesUnordered, StreamExt, TryFutureExt}; +use futures_util::{stream::FuturesUnordered, StreamExt}; use russh::{server::Server as ServerTrait, CryptoVec}; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, @@ -452,8 +452,8 @@ impl ForwardedPortConnection { pub async fn send(&mut self, d: &[u8]) -> Result<(), ()> { self.handle .data(self.channel, CryptoVec::from_slice(d)) - .map_err(|_| ()) .await + .map_err(|_| ()) } /// Receives data from the connection, returning None when it's closed. diff --git a/rs/src/connections/ws.rs b/rs/src/connections/ws.rs index d94c1e8b..79a99ff3 100644 --- a/rs/src/connections/ws.rs +++ b/rs/src/connections/ws.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -use std::{io, net::SocketAddr, pin::Pin, task::Poll, time::Duration}; +use std::{future::Future, io, net::SocketAddr, pin::Pin, task::Poll, time::Duration}; -use futures::{Future, Sink, Stream}; +use futures_util::{Sink, Stream}; use tokio::{ io::{AsyncRead, AsyncWrite}, net::TcpStream, @@ -321,7 +321,7 @@ pub(crate) fn build_websocket_request( mod test { use std::time::Duration; - use futures::{StreamExt, TryStreamExt}; + use futures_util::{StreamExt, TryStreamExt}; use rand::RngCore; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, @@ -393,7 +393,7 @@ mod test { let (write, read) = ws_stream.split(); // We should not forward messages other than text or binary. - read.try_filter(|msg| futures::future::ready(msg.is_text() || msg.is_binary())) + read.try_filter(|msg| futures_util::future::ready(msg.is_text() || msg.is_binary())) .forward(write) .await .ok(); diff --git a/rs/src/contracts/tunnel.rs b/rs/src/contracts/tunnel.rs index 16a78133..c7d893db 100644 --- a/rs/src/contracts/tunnel.rs +++ b/rs/src/contracts/tunnel.rs @@ -2,7 +2,7 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/Tunnel.cs -use chrono::{DateTime, Utc}; +use jiff::Timestamp; use crate::contracts::TunnelAccessControl; use crate::contracts::TunnelEndpoint; use crate::contracts::TunnelOptions; @@ -68,10 +68,10 @@ pub struct Tunnel { pub ports: Vec, // Gets or sets the time in UTC of tunnel creation. - pub created: Option>, + pub created: Option, // Gets or the time the tunnel will be deleted if it is not used or updated. - pub expiration: Option>, + pub expiration: Option, // Gets or the custom amount of time the tunnel will be valid if it is not used or // updated in seconds. diff --git a/rs/src/contracts/tunnel_access_control_entry.rs b/rs/src/contracts/tunnel_access_control_entry.rs index b36863a9..328e8e72 100644 --- a/rs/src/contracts/tunnel_access_control_entry.rs +++ b/rs/src/contracts/tunnel_access_control_entry.rs @@ -2,7 +2,7 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/TunnelAccessControlEntry.cs -use chrono::{DateTime, Utc}; +use jiff::Timestamp; use crate::contracts::TunnelAccessControlEntryType; use serde::{Deserialize, Serialize}; @@ -82,7 +82,7 @@ pub struct TunnelAccessControlEntry { // Gets or sets the expiration for an access control entry. // // If no value is set then this value is null. - pub expiration: Option>, + pub expiration: Option, } // Constants for well-known identity providers. diff --git a/rs/src/contracts/tunnel_event.rs b/rs/src/contracts/tunnel_event.rs index 1d43890b..e1ae9b1f 100644 --- a/rs/src/contracts/tunnel_event.rs +++ b/rs/src/contracts/tunnel_event.rs @@ -2,7 +2,7 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/TunnelEvent.cs -use chrono::{DateTime, Utc}; +use jiff::Timestamp; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -11,7 +11,7 @@ use std::collections::HashMap; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelEvent { // Gets or sets the UTC timestamp of the event (using the client's clock). - pub timestamp: Option>, + pub timestamp: Option, // Gets or sets name of the event. This should be a short descriptive identifier. pub name: String, diff --git a/rs/src/management/authorization.rs b/rs/src/management/authorization.rs index 58d343f9..1d5a62a8 100644 --- a/rs/src/management/authorization.rs +++ b/rs/src/management/authorization.rs @@ -1,4 +1,5 @@ -use async_trait::async_trait; +use std::future::Future; +use std::pin::Pin; use super::HttpError; @@ -28,16 +29,15 @@ impl Authorization { } } -#[async_trait] pub trait AuthorizationProvider: Send + Sync { - async fn get_authorization(&self) -> Result; + fn get_authorization(&self) -> Pin> + Send + '_>>; } pub(crate) struct StaticAuthorizationProvider(pub Authorization); -#[async_trait] impl AuthorizationProvider for StaticAuthorizationProvider { - async fn get_authorization(&self) -> Result { - Ok(self.0.clone()) + fn get_authorization(&self) -> Pin> + Send + '_>> { + let auth = self.0.clone(); + Box::pin(async move { Ok(auth) }) } } diff --git a/rs/src/management/http_client.rs b/rs/src/management/http_client.rs index 3053729d..8b0a74ff 100644 --- a/rs/src/management/http_client.rs +++ b/rs/src/management/http_client.rs @@ -688,7 +688,9 @@ fn add_query(url: &mut Url, tunnel_opts: &TunnelRequestOptions, api_version: &st mod test_end_to_end { use std::{env, time::Duration}; - use async_trait::async_trait; + use std::future::Future; + use std::pin::Pin; + use serde::Deserialize; use tokio::time::sleep; @@ -789,16 +791,17 @@ mod test_end_to_end { struct AuthCodeProvider(); - #[async_trait] impl AuthorizationProvider for AuthCodeProvider { - async fn get_authorization(&self) -> Result { - let token = match env::var("TUNNEL_TEST_AAD_TOKEN") { - Ok(value) => value, - _ => do_device_code_flow(&reqwest::Client::new()).await, - }; - - env::set_var("TUNNEL_TEST_AAD_TOKEN", &token); - Ok(Authorization::Bearer(token)) + fn get_authorization(&self) -> Pin> + Send + '_>> { + Box::pin(async { + let token = match env::var("TUNNEL_TEST_AAD_TOKEN") { + Ok(value) => value, + _ => do_device_code_flow(&reqwest::Client::new()).await, + }; + + env::set_var("TUNNEL_TEST_AAD_TOKEN", &token); + Ok(Authorization::Bearer(token)) + }) } } diff --git a/rs/src/management/policy_provider.rs b/rs/src/management/policy_provider.rs index 0719aea6..aa11eade 100644 --- a/rs/src/management/policy_provider.rs +++ b/rs/src/management/policy_provider.rs @@ -2,7 +2,7 @@ use std::io; #[cfg(target_os = "windows")] pub fn get_policy_header_value() -> io::Result> { - use urlencoding::encode; + use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use winreg::enums::*; use winreg::RegKey; @@ -20,7 +20,7 @@ pub fn get_policy_header_value() -> io::Result> { for (name, value) in sub_key.enum_values().filter_map(Result::ok) { let value_str: String = value.to_string(); if !value_str.is_empty() { - header_values.push(format!("{}={}", encode(&name), encode(&value_str))); + header_values.push(format!("{}={}", utf8_percent_encode(&name, NON_ALPHANUMERIC), utf8_percent_encode(&value_str, NON_ALPHANUMERIC))); } } diff --git a/rs/src/tunnel.rs b/rs/src/tunnel.rs index b543ae84..26d08456 100644 --- a/rs/src/tunnel.rs +++ b/rs/src/tunnel.rs @@ -1,9 +1,9 @@ -use chrono::{DateTime, Utc}; +use jiff::Timestamp; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct Tunnel { tunnel_id: String, - created: DateTime, + created: Timestamp, } From 977196ac6038609144c2e734a05d9e0eb77f1321 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Apr 2026 13:08:36 -0700 Subject: [PATCH 2/5] hyper and reqwest to latest --- rs/Cargo.lock | 1470 ++++++++++++++++++++++++------ rs/Cargo.toml | 15 +- rs/src/connections/errors.rs | 2 +- rs/src/connections/ws.rs | 36 +- rs/src/management/http_client.rs | 2 +- 5 files changed, 1223 insertions(+), 302 deletions(-) diff --git a/rs/Cargo.lock b/rs/Cargo.lock index cc1f80ca..91594a47 100644 --- a/rs/Cargo.lock +++ b/rs/Cargo.lock @@ -63,17 +63,45 @@ dependencies = [ "syn 1.0.96", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" -version = "0.13.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -106,9 +134,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "block-buffer" @@ -161,9 +189,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cbc" @@ -176,9 +204,21 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" @@ -186,6 +226,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.0" @@ -207,6 +253,25 @@ dependencies = [ "inout", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -217,11 +282,21 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -283,9 +358,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", @@ -311,9 +386,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.2", "crypto-common", @@ -340,6 +415,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ed25519" version = "1.5.2" @@ -372,12 +464,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "fastrand" version = "1.7.0" @@ -387,6 +473,12 @@ dependencies = [ "instant", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "flate2" version = "1.0.24" @@ -397,12 +489,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foreign-types" version = "0.3.2" @@ -420,14 +506,19 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "matches", "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.23" @@ -455,9 +546,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" @@ -540,42 +631,39 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] -name = "ghash" -version = "0.5.0" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "opaque-debug", - "polyval", + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", ] [[package]] -name = "h2" -version = "0.3.26" +name = "ghash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.1.0", - "slab", - "tokio", - "tokio-util", - "tracing", + "opaque-debug", + "polyval", ] [[package]] @@ -584,21 +672,6 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hex-literal" version = "0.3.4" @@ -611,109 +684,235 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", ] [[package]] name = "http" -version = "0.2.8" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "0.4.5" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", - "pin-project-lite", ] [[package]] -name = "httparse" -version = "1.7.1" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] [[package]] -name = "httpdate" -version = "1.0.2" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "0.14.19" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] name = "idna" -version = "0.2.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "indexmap" -version = "1.8.2" +name = "idna_adapter" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "autocfg", - "hashbrown 0.11.2", + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "2.1.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" dependencies = [ - "equivalent", - "hashbrown 0.14.3", + "autocfg", + "hashbrown", ] [[package]] @@ -737,9 +936,19 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.0" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "itoa" @@ -771,12 +980,67 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.31", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -788,9 +1052,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "litemap" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -809,10 +1079,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "matches" -version = "0.1.9" +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "md5" @@ -843,14 +1113,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -863,10 +1132,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.5", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.6.1", "security-framework-sys", "tempfile", ] @@ -902,21 +1171,11 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "once_cell" -version = "1.13.1" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -930,7 +1189,7 @@ version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.11.1", "cfg-if", "foreign-types", "libc", @@ -956,6 +1215,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-src" version = "300.5.5+3.5.5" @@ -996,11 +1261,11 @@ checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" dependencies = [ "futures-channel", "futures-util", - "indexmap 1.8.2", + "indexmap", "js-sys", "once_cell", "pin-project-lite", - "thiserror", + "thiserror 1.0.31", "urlencoding", ] @@ -1020,7 +1285,7 @@ dependencies = [ "ordered-float", "percent-encoding", "rand 0.8.5", - "thiserror", + "thiserror 1.0.31", ] [[package]] @@ -1082,7 +1347,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", "hmac", "password-hash", "sha2 0.10.2", @@ -1096,9 +1361,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -1150,6 +1415,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1165,6 +1439,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -1174,6 +1504,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.7.3" @@ -1198,6 +1534,16 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -1218,6 +1564,16 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -1233,7 +1589,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -1260,9 +1625,9 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.17", "redox_syscall", - "thiserror", + "thiserror 1.0.31", ] [[package]] @@ -1293,39 +1658,58 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", - "futures-util", - "h2", "http", "http-body", + "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", - "ipnet", + "hyper-util", "js-sys", - "lazy_static", "log", "mime", "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", + "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.10.1", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -1342,7 +1726,7 @@ dependencies = [ "chacha20", "ctr", "curve25519-dalek", - "digest 0.10.3", + "digest 0.10.7", "flate2", "futures", "generic-array", @@ -1359,7 +1743,7 @@ dependencies = [ "sha1", "sha2 0.10.2", "subtle", - "thiserror", + "thiserror 1.0.31", "tokio", "tokio-util", ] @@ -1404,18 +1788,114 @@ dependencies = [ "russh-cryptovec", "serde", "sha2 0.10.2", - "thiserror", + "thiserror 1.0.31", "tokio", "tokio-stream", "yasna", ] +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustls" +version = "0.23.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 3.7.0", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.20" @@ -1439,7 +1919,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.3", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1447,9 +1940,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -1496,18 +1989,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha1" version = "0.10.1" @@ -1516,7 +1997,7 @@ checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -1540,9 +2021,15 @@ checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.7", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1566,25 +2053,31 @@ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.4.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "winapi", + "windows-sys 0.61.2", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1608,6 +2101,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -1620,6 +2122,38 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.3", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1640,7 +2174,16 @@ version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.31", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -1654,6 +2197,27 @@ dependencies = [ "syn 1.0.96", ] +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1671,33 +2235,30 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.4" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb78f30e4b41e98ca4cce5acb51168a033839a7af9e42b380355808e14e98ee0" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ - "autocfg", "bytes", "libc", - "memchr", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 1.0.96", + "syn 2.0.117", ] [[package]] @@ -1710,6 +2271,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.9" @@ -1723,9 +2294,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.20.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", @@ -1746,14 +2317,52 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -1783,21 +2392,19 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.20.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ - "byteorder", "bytes", "data-encoding", "http", "httparse", "log", "native-tls", - "rand 0.8.5", + "rand 0.9.4", "sha1", - "thiserror", - "url", + "thiserror 2.0.18", "utf-8", ] @@ -1807,7 +2414,9 @@ version = "0.1.0" dependencies = [ "async-trait", "futures-util", + "http-body-util", "hyper", + "hyper-util", "jiff", "log", "opentelemetry", @@ -1820,14 +2429,13 @@ dependencies = [ "russh-keys", "serde", "serde_json", - "thiserror", + "thiserror 1.0.31", "tokio", "tokio-tungstenite", "tokio-util", - "tungstenite", "url", "uuid", - "winreg 0.8.0", + "winreg", ] [[package]] @@ -1836,26 +2444,11 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -1865,24 +2458,30 @@ checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" -version = "2.2.2" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", + "serde", ] [[package]] @@ -1897,13 +2496,19 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.17", ] [[package]] @@ -1918,6 +2523,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -1941,27 +2556,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.87" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.87" +name = "wasm-bindgen" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ - "bumpalo", - "log", + "cfg-if", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.117", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -1979,9 +2591,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1989,22 +2601,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.117", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" @@ -2016,6 +2631,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2032,12 +2666,56 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.36.1" @@ -2053,33 +2731,105 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2089,9 +2839,21 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2101,9 +2863,33 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2113,9 +2899,21 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2125,15 +2923,39 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2143,9 +2965,21 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winreg" @@ -2157,13 +2991,16 @@ dependencies = [ ] [[package]] -name = "winreg" -version = "0.10.1" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yasna" @@ -2175,11 +3012,55 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + [[package]] name = "zeroize" -version = "1.3.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -2193,5 +3074,38 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.96", - "synstructure", + "synstructure 0.12.6", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] diff --git a/rs/Cargo.toml b/rs/Cargo.toml index 427bf8cc..a4073fe2 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] serde = { version = "1", features = ["derive"] } jiff = { version = "0.2", default-features = false, features = ["std", "serde"] } -reqwest = { version = "0.11", features = ["default", "json"] } +reqwest = { version = "0.13",default-features = false, features = ["charset", "system-proxy", "json"] } url = "2" opentelemetry = { version = "0.20", features = ["trace"], optional = true } serde_json = "1" @@ -15,14 +15,15 @@ thiserror = "1.0" log = "0.4" tokio = { version = "1.20", features = ["macros", "io-util", "time"], optional = true } tokio-util = { version = "0.7", optional = true } -tokio-tungstenite = { version = "0.20", optional = true, features = ["native-tls"] } +tokio-tungstenite = { version = "0.26", optional = true, features = ["native-tls"] } futures-util = { version = "0.3", default-features = false, features = ["sink"], optional = true } -tungstenite = { version = "0.20", optional = true, features = ["native-tls"] } uuid = { version = "1.4", features = ["v4"], optional = true } rand = "0.8.5" russh = { version = "0.37.1", default-features = false, features = ["openssl", "flate2"], optional = true } russh-keys = { version = "0.37.1", default-features = false, features = ["openssl"], optional = true } -hyper = { version = "0.14", optional = true } +hyper = { version = "1.9", optional = true } +http-body-util = { version = "0.1", optional = true } +hyper-util = { version = "0.1", features = ["tokio"], optional = true } os_info = { version = "3", default-features = false } [target.'cfg(windows)'.dependencies] @@ -35,7 +36,7 @@ rand = "0.8" regex = "1" [features] -default = [] +default = ["reqwest/default-tls"] end_to_end = [] instrumentation = ["dep:opentelemetry"] connections = [ @@ -44,8 +45,9 @@ connections = [ "dep:tokio-util", "dep:futures-util", "dep:hyper", + "dep:http-body-util", + "dep:hyper-util", "dep:tokio-tungstenite", - "dep:tungstenite", "dep:uuid", "dep:russh", "dep:russh-keys", @@ -53,7 +55,6 @@ connections = [ vendored-openssl = [ "reqwest/native-tls-vendored", "tokio-tungstenite?/native-tls-vendored", - "tungstenite?/native-tls-vendored", "russh?/vendored-openssl", "russh-keys?/vendored-openssl" ] diff --git a/rs/src/connections/errors.rs b/rs/src/connections/errors.rs index ec1c83cf..5aa61db9 100644 --- a/rs/src/connections/errors.rs +++ b/rs/src/connections/errors.rs @@ -22,7 +22,7 @@ pub enum TunnelError { InvalidHostEndpoint(String), #[error("websocket error: {0}")] - WebSocketError(#[from] tungstenite::Error), + WebSocketError(#[from] tokio_tungstenite::tungstenite::error::Error), #[error("port {0} already exists in the relay")] PortAlreadyExists(u32), diff --git a/rs/src/connections/ws.rs b/rs/src/connections/ws.rs index 79a99ff3..d1bfe755 100644 --- a/rs/src/connections/ws.rs +++ b/rs/src/connections/ws.rs @@ -9,7 +9,7 @@ use tokio::{ net::TcpStream, time::{sleep, Instant, Sleep}, }; -use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; +use tokio_tungstenite::{connect_async, tungstenite, MaybeTlsStream, WebSocketStream}; use crate::management::{HttpError, ResponseError}; @@ -76,7 +76,7 @@ where fn tung_to_io_error(e: tungstenite::Error) -> io::Error { match e { - tungstenite::Error::Io(e) => e, + tungstenite::Error::Io(e) => e.into(), _ => io::Error::new(io::ErrorKind::Other, e.to_string()), } } @@ -95,7 +95,7 @@ where match sm.get_ws().poll_ready(cx) { Poll::Ready(Ok(())) => { sm.get_ws() - .start_send(tungstenite::Message::Binary(buf.to_vec())) + .start_send(tungstenite::Message::Binary(buf.to_vec().into())) .map_err(tung_to_io_error)?; Poll::Ready(Ok(buf.len())) } @@ -162,7 +162,7 @@ where } PingState::WillPing => match self.get_ws().poll_ready(cx) { Poll::Ready(Ok(_)) => { - if let Err(e) = self.get_ws().start_send(tungstenite::Message::Ping(vec![])) + if let Err(e) = self.get_ws().start_send(tungstenite::Message::Ping(vec![].into())) { return Poll::Ready(Err(tung_to_io_error(e))); } @@ -189,10 +189,10 @@ where match msg { tungstenite::Message::Text(text) => { - return self.readbuf.put_data(buf, text.into_bytes(), 0); + return self.readbuf.put_data(buf, text.as_bytes().to_vec(), 0); } tungstenite::Message::Binary(bin) => { - return self.readbuf.put_data(buf, bin, 0); + return self.readbuf.put_data(buf, bin.into(), 0); } tungstenite::Message::Close(_) => return Poll::Ready(Ok(())), tungstenite::Message::Pong(_) => { @@ -251,13 +251,15 @@ pub(crate) async fn connect_via_proxy( let stream = stream.map_err(TunnelError::ProxyConnectionFailed)?; - let (mut request_sender, conn) = hyper::client::conn::handshake(stream) - .await - .map_err(TunnelError::ProxyHandshakeFailed)?; + let (mut request_sender, conn) = hyper::client::conn::http1::handshake( + hyper_util::rt::TokioIo::new(stream), + ) + .await + .map_err(TunnelError::ProxyHandshakeFailed)?; let conn = tokio::spawn(conn.without_shutdown()); let connect_req = hyper::Request::connect(&authority) - .body(hyper::Body::empty()) + .body(http_body_util::Empty::::new()) .expect("expected to make connect request"); let res = request_sender @@ -271,16 +273,20 @@ pub(crate) async fn connect_via_proxy( error: HttpError::ResponseError(ResponseError { url: reqwest::Url::parse(proxy_addr).unwrap(), status_code: res.status(), - data: hyper::body::to_bytes(res.into_body()) - .await - .map(|b| String::from_utf8_lossy(&b).to_string()) - .ok(), + data: { + use http_body_util::BodyExt; + res.into_body() + .collect() + .await + .map(|b| String::from_utf8_lossy(&b.to_bytes()).to_string()) + .ok() + }, request_id: None, }), }); } - let tcp = conn.await.unwrap().unwrap().io; + let tcp = conn.await.unwrap().unwrap().io.into_inner(); let (ws_stream, _) = tokio_tungstenite::client_async_tls(ws_req, tcp).await?; Ok(ws_stream) } diff --git a/rs/src/management/http_client.rs b/rs/src/management/http_client.rs index 8b0a74ff..b40b17cb 100644 --- a/rs/src/management/http_client.rs +++ b/rs/src/management/http_client.rs @@ -854,7 +854,7 @@ mod tests { #[test] fn custom_domain_does_not_modify_hostname() { - let mut builder = super::new_tunnel_management_for_custom_domain( + let builder = super::new_tunnel_management_for_custom_domain( "rs-sdk-tests", "app.github.dev", ); From ab82d12c701d108f939fbc84a10947730a4f41bc Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Apr 2026 14:36:54 -0700 Subject: [PATCH 3/5] pr comments and clippy warnings --- rs/Cargo.toml | 2 +- rs/src/connections/relay_tunnel_host.rs | 9 ++++----- rs/src/connections/ws.rs | 20 ++++++++++++++++---- rs/src/management/authorization.rs | 7 +++++-- rs/src/management/http_client.rs | 8 +++----- rs/src/management/policy_provider.rs | 8 ++++++-- 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/rs/Cargo.toml b/rs/Cargo.toml index a4073fe2..97ba3eda 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] serde = { version = "1", features = ["derive"] } jiff = { version = "0.2", default-features = false, features = ["std", "serde"] } -reqwest = { version = "0.13",default-features = false, features = ["charset", "system-proxy", "json"] } +reqwest = { version = "0.13", default-features = false, features = ["charset", "system-proxy", "json"] } url = "2" opentelemetry = { version = "0.20", features = ["trace"], optional = true } serde_json = "1" diff --git a/rs/src/connections/relay_tunnel_host.rs b/rs/src/connections/relay_tunnel_host.rs index 62338980..55b0e186 100644 --- a/rs/src/connections/relay_tunnel_host.rs +++ b/rs/src/connections/relay_tunnel_host.rs @@ -128,7 +128,6 @@ pub struct RelayTunnelHost { /// Ports are handled by a client calling `add_port()` or `add_port_raw()`, /// which either forward to a local TCP connection or return the /// ForwardedPortConnection directly, respectively. - #[allow(dead_code)] impl RelayTunnelHost { pub fn new(locator: TunnelLocator, mgmt: TunnelManagementClient) -> Self { @@ -542,7 +541,7 @@ impl AsyncWrite for ForwardedPortWriter { } Poll::Ready(Err(_)) => { self.is_write_fut_valid = false; - Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "EOF"))) + Poll::Ready(Err(io::Error::other("EOF"))) } } } @@ -573,7 +572,7 @@ impl AsyncRead for ForwardedPortReader { match self.receiver.poll_recv(cx) { Poll::Ready(Some(msg)) => self.readbuf.put_data(buf, msg, 0), - Poll::Ready(None) => Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "EOF"))), + Poll::Ready(None) => Poll::Ready(Err(io::Error::other("EOF"))), Poll::Pending => Poll::Pending, } } @@ -1005,7 +1004,7 @@ impl AsyncWrite for AsyncRWChannel { } Poll::Ready(Err(_)) => { self.is_write_fut_valid = false; - Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "EOF"))) + Poll::Ready(Err(io::Error::other("EOF"))) } } } @@ -1030,7 +1029,7 @@ impl AsyncRead for AsyncRWChannel { match self.incoming.poll_recv(cx) { Poll::Ready(Some(msg)) => self.readbuf.put_data(buf, msg, 0), - Poll::Ready(None) => Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, "EOF"))), + Poll::Ready(None) => Poll::Ready(Err(io::Error::other("EOF"))), Poll::Pending => Poll::Pending, } } diff --git a/rs/src/connections/ws.rs b/rs/src/connections/ws.rs index d1bfe755..cf31d785 100644 --- a/rs/src/connections/ws.rs +++ b/rs/src/connections/ws.rs @@ -76,8 +76,8 @@ where fn tung_to_io_error(e: tungstenite::Error) -> io::Error { match e { - tungstenite::Error::Io(e) => e.into(), - _ => io::Error::new(io::ErrorKind::Other, e.to_string()), + tungstenite::Error::Io(e) => e, + _ => io::Error::other(e.to_string()), } } @@ -268,10 +268,13 @@ pub(crate) async fn connect_via_proxy( .map_err(TunnelError::ProxyConnectRequestFailed)?; if !res.status().is_success() { + let proxy_url = reqwest::Url::parse(proxy_addr) + .or_else(|_| reqwest::Url::parse(&format!("http://{}", proxy_addr))) + .map_err(TunnelError::ProxyAddressInvalid)?; return Err(TunnelError::HttpError { reason: "error sending tunnel CONNECT request", error: HttpError::ResponseError(ResponseError { - url: reqwest::Url::parse(proxy_addr).unwrap(), + url: proxy_url, status_code: res.status(), data: { use http_body_util::BodyExt; @@ -286,7 +289,16 @@ pub(crate) async fn connect_via_proxy( }); } - let tcp = conn.await.unwrap().unwrap().io.into_inner(); + let tcp = conn + .await + .map_err(|e| { + TunnelError::ProxyConnectionFailed(io::Error::other( + format!("proxy connection task failed: {e}"), + )) + })? + .map_err(TunnelError::ProxyHandshakeFailed)? + .io + .into_inner(); let (ws_stream, _) = tokio_tungstenite::client_async_tls(ws_req, tcp).await?; Ok(ws_stream) } diff --git a/rs/src/management/authorization.rs b/rs/src/management/authorization.rs index 1d5a62a8..01fa58af 100644 --- a/rs/src/management/authorization.rs +++ b/rs/src/management/authorization.rs @@ -3,6 +3,9 @@ use std::pin::Pin; use super::HttpError; +/// A boxed future type for async trait methods that need to be object-safe. +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + #[derive(Clone)] pub enum Authorization { /// No authorization. @@ -30,13 +33,13 @@ impl Authorization { } pub trait AuthorizationProvider: Send + Sync { - fn get_authorization(&self) -> Pin> + Send + '_>>; + fn get_authorization(&self) -> BoxFuture<'_, Result>; } pub(crate) struct StaticAuthorizationProvider(pub Authorization); impl AuthorizationProvider for StaticAuthorizationProvider { - fn get_authorization(&self) -> Pin> + Send + '_>> { + fn get_authorization(&self) -> BoxFuture<'_, Result> { let auth = self.0.clone(); Box::pin(async move { Ok(auth) }) } diff --git a/rs/src/management/http_client.rs b/rs/src/management/http_client.rs index b40b17cb..af5ce81a 100644 --- a/rs/src/management/http_client.rs +++ b/rs/src/management/http_client.rs @@ -688,16 +688,14 @@ fn add_query(url: &mut Url, tunnel_opts: &TunnelRequestOptions, api_version: &st mod test_end_to_end { use std::{env, time::Duration}; - use std::future::Future; - use std::pin::Pin; - use serde::Deserialize; use tokio::time::sleep; use crate::{ contracts::{Tunnel, PROD_FIRST_PARTY_APP_ID}, management::{ - Authorization, AuthorizationProvider, HttpError, TunnelLocator, NO_REQUEST_OPTIONS, + Authorization, AuthorizationProvider, BoxFuture, HttpError, TunnelLocator, + NO_REQUEST_OPTIONS, }, }; @@ -792,7 +790,7 @@ mod test_end_to_end { struct AuthCodeProvider(); impl AuthorizationProvider for AuthCodeProvider { - fn get_authorization(&self) -> Pin> + Send + '_>> { + fn get_authorization(&self) -> BoxFuture<'_, Result> { Box::pin(async { let token = match env::var("TUNNEL_TEST_AAD_TOKEN") { Ok(value) => value, diff --git a/rs/src/management/policy_provider.rs b/rs/src/management/policy_provider.rs index aa11eade..0671feb5 100644 --- a/rs/src/management/policy_provider.rs +++ b/rs/src/management/policy_provider.rs @@ -2,10 +2,14 @@ use std::io; #[cfg(target_os = "windows")] pub fn get_policy_header_value() -> io::Result> { - use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; + use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; use winreg::enums::*; use winreg::RegKey; + // Encode everything except RFC3986 unreserved characters (ALPHA / DIGIT / "-" / "." / "_" / "~") + const URI_COMPONENT: &AsciiSet = &NON_ALPHANUMERIC + .remove(b'-').remove(b'.').remove(b'_').remove(b'~'); + pub const REGISTRY_KEY_PATH: &str = r"Software\Policies\Microsoft\DevTunnels"; let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); @@ -20,7 +24,7 @@ pub fn get_policy_header_value() -> io::Result> { for (name, value) in sub_key.enum_values().filter_map(Result::ok) { let value_str: String = value.to_string(); if !value_str.is_empty() { - header_values.push(format!("{}={}", utf8_percent_encode(&name, NON_ALPHANUMERIC), utf8_percent_encode(&value_str, NON_ALPHANUMERIC))); + header_values.push(format!("{}={}", utf8_percent_encode(&name, URI_COMPONENT), utf8_percent_encode(&value_str, URI_COMPONENT))); } } From 4be50b3cc5ade8cb6beec4038c53ea4f2cdac5a2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 27 Apr 2026 11:20:37 -0700 Subject: [PATCH 4/5] rust: implement relay client Rust had a relay host, but never actually implemented a relay client. - Imnplement relay_tunnel_client.rs (of course) - Updated incorrect type generation of some Rust models. These were just never hit in my code paths before, previously list_tunnels/get_tunnels could never return TunnelRelayTunnelEndpoint information. Now our Rust structure matches what Go does where TunnelRelayTunnelEndpoint is a field inside the TunnelEndpoint struct, so its fields are accessible. - GPT 5.5 did some very impressive analysis to find the bug that led to adding `CHANNEL_WRITE_CHUNK_SIZE`. This was just never something I hit consistently enough before to repro and fix. --- cs/src/Contracts/DevTunnels.Contracts.csproj | 2 +- .../RustContractWriter.cs | 49 ++- rs/Cargo.lock | 15 +- rs/Cargo.toml | 2 +- rs/src/connections/errors.rs | 3 + rs/src/connections/mod.rs | 2 + rs/src/connections/relay_tunnel_client.rs | 386 ++++++++++++++++++ rs/src/connections/relay_tunnel_host.rs | 101 +++-- rs/src/connections/ws.rs | 6 + .../local_network_tunnel_endpoint.rs | 7 +- rs/src/contracts/named_rate_status.rs | 4 - rs/src/contracts/rate_status.rs | 8 +- rs/src/contracts/resource_status.rs | 4 + rs/src/contracts/tunnel.rs | 2 +- .../contracts/tunnel_access_control_entry.rs | 2 +- rs/src/contracts/tunnel_endpoint.rs | 8 + rs/src/contracts/tunnel_port.rs | 2 +- .../contracts/tunnel_relay_tunnel_endpoint.rs | 6 +- rs/src/management/http_client.rs | 10 +- 19 files changed, 550 insertions(+), 69 deletions(-) create mode 100644 rs/src/connections/relay_tunnel_client.rs diff --git a/cs/src/Contracts/DevTunnels.Contracts.csproj b/cs/src/Contracts/DevTunnels.Contracts.csproj index 40a634b2..a781b024 100644 --- a/cs/src/Contracts/DevTunnels.Contracts.csproj +++ b/cs/src/Contracts/DevTunnels.Contracts.csproj @@ -1,4 +1,4 @@ - + Microsoft.DevTunnels.Contracts diff --git a/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs b/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs index 97d11150..2de2715c 100644 --- a/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs +++ b/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs @@ -188,7 +188,12 @@ private void WriteInterfaceContract( s.Append(FormatDocComment(type.GetDocumentationCommentXml(), "")); s.Append("#[derive(Clone, Debug, Deserialize, Serialize"); - if (DefaultDerivers.Contains(rsName)) + // Add Default for types in the explicit list, and for types that will be + // embedded into their base type via #[serde(flatten)]. + var willBeEmbedded = type.BaseType?.ToString() is string bt && + bt.StartsWith(this.csNamespace) && + allTypes.Any((t) => SymbolEqualityComparer.Default.Equals(t.BaseType, type.BaseType)); + if (DefaultDerivers.Contains(rsName) || willBeEmbedded) { s.Append(", Default"); } @@ -196,8 +201,20 @@ private void WriteInterfaceContract( s.AppendLine("#[serde(rename_all(serialize = \"camelCase\", deserialize = \"camelCase\"))]"); s.Append($"pub struct {rsName} {{"); + // Check if this type has derived types. If so, we embed the derived types + // into this base type (like Go's struct embedding) rather than having + // derived types embed the base. + var derivedTypes = allTypes.Where( + (t) => SymbolEqualityComparer.Default.Equals(t.BaseType, type)).ToArray(); + var fullBaseType = type.BaseType?.ToString(); - if (fullBaseType != null && fullBaseType.StartsWith(this.csNamespace)) + // Only add #[serde(flatten)] pub base if the base type does NOT embed + // derived types. When a base type has derived types, it embeds them + // (like Go's struct embedding), so derived types must not embed back. + var baseEmbedsDerived = fullBaseType != null && + fullBaseType.StartsWith(this.csNamespace) && + allTypes.Any((t) => SymbolEqualityComparer.Default.Equals(t.BaseType, type.BaseType)); + if (fullBaseType != null && fullBaseType.StartsWith(this.csNamespace) && !baseEmbedsDerived) { var rsBaseType = fullBaseType.Substring(this.csNamespace.Length + 1); s.AppendLine(); @@ -206,6 +223,10 @@ private void WriteInterfaceContract( imports.Add($"crate::contracts::{rsBaseType}"); } + // A type is "embedded" if its base type embeds it via #[serde(flatten)]. + // In that case, all fields must tolerate missing values in JSON. + var isEmbeddedType = baseEmbedsDerived; + var properties = type.GetMembers() .OfType() .Where((p) => !p.IsStatic) @@ -216,7 +237,19 @@ private void WriteInterfaceContract( { s.AppendLine(); s.Append(FormatDocComment(property.GetDocumentationCommentXml(), " ")); - AppendStructProperty(type, property, imports, s); + AppendStructProperty(type, property, imports, s, isEmbeddedType); + } + + // Embed derived types via #[serde(flatten)], similar to Go's struct + // embedding. This allows the base type to deserialize fields from all + // derived types. + foreach (var derivedType in derivedTypes.OrderBy((t) => t.Name)) + { + var fieldName = ToSnakeCase(derivedType.Name); + s.AppendLine(); + s.AppendLine(" #[serde(flatten)]"); + s.AppendLine($" pub {fieldName}: {derivedType.Name},"); + imports.Add($"crate::contracts::{derivedType.Name}"); } s.AppendLine("}"); @@ -394,7 +427,7 @@ private string FormatDocComment(string? comment, string prefix) return s.ToString(); } - private void AppendStructProperty(ITypeSymbol parentType, IPropertySymbol property, SortedSet imports, StringBuilder s) + private void AppendStructProperty(ITypeSymbol parentType, IPropertySymbol property, SortedSet imports, StringBuilder s, bool isEmbeddedType = false) { var csType = property.Type.ToString(); var isNullable = csType.EndsWith("?"); @@ -418,7 +451,9 @@ private void AppendStructProperty(ITypeSymbol parentType, IPropertySymbol proper if (isArray) { csType = csType.Substring(0, csType.Length - 2); - if (isNullable || ignoreWhenDefault) + // When a type is embedded (flattened) into a base type, all array + // fields must default to empty since they may not be present in JSON. + if (isNullable || ignoreWhenDefault || isEmbeddedType) { serdeDeclarations.Add("skip_serializing_if = \"Vec::is_empty\""); serdeDeclarations.Add("default"); @@ -472,7 +507,7 @@ private void AppendStructProperty(ITypeSymbol parentType, IPropertySymbol proper "long" => "i64", "ulong" => "u64", "string" => "String", - "System.DateTime" => "DateTime", + "System.DateTime" => "Timestamp", "System.Text.RegularExpressions.Regex" => "regexp.Regexp", "System.Collections.Generic.IDictionary" => "HashMap", @@ -494,7 +529,7 @@ private void AppendStructProperty(ITypeSymbol parentType, IPropertySymbol proper if (csType == "System.DateTime") { - imports.Add("chrono::{DateTime, Utc}"); + imports.Add("jiff::Timestamp"); } else if (csType.Contains("IDictionary<")) { diff --git a/rs/Cargo.lock b/rs/Cargo.lock index 91594a47..a4230df4 100644 --- a/rs/Cargo.lock +++ b/rs/Cargo.lock @@ -2294,9 +2294,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", @@ -2392,9 +2392,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", @@ -2405,7 +2405,6 @@ dependencies = [ "rand 0.9.4", "sha1", "thiserror 2.0.18", - "utf-8", ] [[package]] @@ -2490,12 +2489,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/rs/Cargo.toml b/rs/Cargo.toml index 97ba3eda..f5e12049 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -15,7 +15,7 @@ thiserror = "1.0" log = "0.4" tokio = { version = "1.20", features = ["macros", "io-util", "time"], optional = true } tokio-util = { version = "0.7", optional = true } -tokio-tungstenite = { version = "0.26", optional = true, features = ["native-tls"] } +tokio-tungstenite = { version = "0.29", optional = true, features = ["native-tls"] } futures-util = { version = "0.3", default-features = false, features = ["sink"], optional = true } uuid = { version = "1.4", features = ["v4"], optional = true } rand = "0.8.5" diff --git a/rs/src/connections/errors.rs b/rs/src/connections/errors.rs index 5aa61db9..7e785790 100644 --- a/rs/src/connections/errors.rs +++ b/rs/src/connections/errors.rs @@ -38,4 +38,7 @@ pub enum TunnelError { #[error("proxy connect request failed: {0}")] ProxyConnectRequestFailed(hyper::Error), + + #[error("no tunnel endpoint has a client relay URI")] + MissingClientEndpoint, } diff --git a/rs/src/connections/mod.rs b/rs/src/connections/mod.rs index 2bc9a205..54c58278 100644 --- a/rs/src/connections/mod.rs +++ b/rs/src/connections/mod.rs @@ -3,7 +3,9 @@ mod errors; mod io; +mod relay_tunnel_client; mod relay_tunnel_host; mod ws; +pub use relay_tunnel_client::*; pub use relay_tunnel_host::*; diff --git a/rs/src/connections/relay_tunnel_client.rs b/rs/src/connections/relay_tunnel_client.rs new file mode 100644 index 00000000..ec3ac93e --- /dev/null +++ b/rs/src/connections/relay_tunnel_client.rs @@ -0,0 +1,386 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +use std::{ + env, io, + net::{IpAddr, Ipv4Addr, SocketAddr}, + pin::Pin, + sync::Arc, + task::Poll, + time::Duration, +}; + +use crate::{contracts::TunnelEndpoint, management::TunnelManagementClient}; +use async_trait::async_trait; +use russh::ChannelMsg; +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + net::TcpListener, + sync::watch, +}; + +use super::{ + errors::TunnelError, + ws::{build_websocket_request, connect_directly, connect_via_proxy, AsyncRWWebSocket}, +}; + +/// The RelayTunnelClient connects to a tunnel as a client via the tunneling +/// service's relay. After connecting, you can open connections to individual +/// forwarded ports via `connect_to_port`, or forward them to local TCP +/// listeners via `forward_port_locally`. +/// +/// # Interoperability +/// +/// This client uses SSH `direct-tcpip` channels to connect to forwarded ports. +/// This works with Rust and TypeScript/C# tunnel hosts. Go tunnel hosts do +/// not currently handle `direct-tcpip` channels and will reject connections +/// from this client. +/// +/// # Example +/// +/// ```ignore +/// let mgmt = TunnelManagementClient::new(/* ... */); +/// let client = RelayTunnelClient::new(mgmt); +/// +/// // endpoint and token are obtained from the tunnel management API +/// let handle = client.connect(&endpoint, "access_token").await?; +/// +/// // Connect to port 8080 on the tunnel host +/// let channel = handle.connect_to_port(8080).await?; +/// +/// // Or forward a remote port to a local TCP listener +/// let local_addr = handle.forward_port_locally(8080).await?; +/// ``` +pub struct RelayTunnelClient { + pub proxy: Option, + mgmt: TunnelManagementClient, +} + +impl RelayTunnelClient { + pub fn new(mgmt: TunnelManagementClient) -> Self { + RelayTunnelClient { + proxy: env::var("HTTPS_PROXY").or(env::var("https_proxy")).ok(), + mgmt, + } + } + + /// Connects to the tunnel relay as a client. The endpoint must have a + /// `client_relay_uri`, and the `access_token` should be a tunnel access + /// token with the "connect" scope. + /// + /// Returns a `ClientRelayHandle` that can be used to open port connections. + pub async fn connect( + &self, + endpoint: &TunnelEndpoint, + access_token: &str, + ) -> Result { + let client_relay_uri = endpoint + .tunnel_relay_tunnel_endpoint + .client_relay_uri + .as_deref() + .ok_or(TunnelError::MissingClientEndpoint)?; + + let req = build_websocket_request( + client_relay_uri, + &[ + ("Sec-WebSocket-Protocol", "tunnel-relay-client"), + ("Authorization", &format!("tunnel {}", access_token)), + ("User-Agent", self.mgmt.user_agent.to_str().unwrap()), + ], + )?; + + let cnx = if let Some(proxy) = &self.proxy { + log::debug!("connecting via http_proxy on {}", proxy); + connect_via_proxy(req, proxy).await? + } else { + connect_directly(req).await? + }; + + let cnx = AsyncRWWebSocket::new(super::ws::AsyncRWWebSocketOptions { + websocket: cnx, + ping_interval: Duration::from_secs(60), + ping_timeout: Duration::from_secs(10), + }); + + let mut session = Self::make_ssh_session(cnx) + .await + .map_err(TunnelError::TunnelRelayDisconnected)?; + + log::debug!("established client relay session"); + + let authed = session + .authenticate_none("tunnel") + .await + .map_err(TunnelError::TunnelRelayDisconnected)?; + + if !authed { + return Err(TunnelError::TunnelRelayDisconnected( + russh::Error::NotAuthenticated, + )); + } + + log::debug!("client relay session authenticated"); + + let (close_tx, close_rx) = watch::channel(false); + Ok(ClientRelayHandle { + session: Arc::new(session), + close_tx, + close_rx, + }) + } + + async fn make_ssh_session( + rw: impl AsyncRead + AsyncWrite + Unpin + Send + 'static, + ) -> Result, russh::Error> { + let config = russh::client::Config { + window_size: 1024 * 1024, + preferred: russh::Preferred { + compression: &["none"], + ..russh::Preferred::DEFAULT + }, + limits: russh::Limits { + rekey_read_limit: usize::MAX, + rekey_time_limit: Duration::MAX, + rekey_write_limit: usize::MAX, + }, + ..Default::default() + }; + + let config = Arc::new(config); + let handler = TunnelClientHandler; + russh::client::connect_stream(config, rw, handler).await + } +} + +/// Handle to an active client relay connection. Use this to open connections +/// to forwarded ports on the tunnel host. +pub struct ClientRelayHandle { + session: Arc>, + close_tx: watch::Sender, + close_rx: watch::Receiver, +} + +impl ClientRelayHandle { + /// Opens a connection to the specified port on the tunnel host, returning + /// a `PortConnection` that can be used to send and receive data. + pub async fn connect_to_port(&self, port: u16) -> Result { + let channel = self + .session + .channel_open_direct_tcpip("127.0.0.1", port as u32, "127.0.0.1", 0) + .await + .map_err(TunnelError::TunnelRelayDisconnected)?; + Ok(PortConnection { channel }) + } + + /// Opens a local TCP listener that forwards connections to the specified + /// port on the tunnel host. Returns the local address the listener is + /// bound to (useful when the requested port is already in use and a + /// fallback port was chosen). + /// + /// The listener will be stopped when `close()` is called on this handle. + pub async fn forward_port_locally(&self, port: u16) -> Result { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + let listener = match TcpListener::bind(addr).await { + Ok(l) => l, + Err(e) => { + log::warn!( + "Failed to bind port {} ({}), falling back to ephemeral port", + port, + e + ); + TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)) + .await + .map_err(TunnelError::ProxyConnectionFailed)? + } + }; + + let local_addr = listener + .local_addr() + .map_err(TunnelError::ProxyConnectionFailed)?; + + let session = self.session.clone(); + let mut close_rx = self.close_rx.clone(); + tokio::spawn(async move { + log::info!("Listening on {} for forwarded port {}", local_addr, port); + loop { + tokio::select! { + result = listener.accept() => match result { + Ok((stream, peer)) => { + log::debug!("Accepted connection from {} for port {}", peer, port); + let session = session.clone(); + tokio::spawn(async move { + if let Err(e) = relay_tcp_to_channel(stream, &session, port).await { + log::debug!("Error relaying connection on port {}: {}", port, e); + } + }); + } + Err(e) => { + log::info!("Error accepting connection on port {}: {}", port, e); + break; + } + }, + _ = close_rx.changed() => { + log::debug!("Shutting down listener for port {}", port); + break; + } + } + } + }); + + Ok(local_addr) + } + + /// Closes the tunnel client connection and stops all local port listeners. + pub async fn close(self) -> Result<(), TunnelError> { + self.close_tx.send(true).ok(); + self.session + .disconnect(russh::Disconnect::ByApplication, "disconnect", "en") + .await + .map_err(TunnelError::TunnelRelayDisconnected) + } + + /// Returns true if the underlying SSH session has been closed. + pub fn is_closed(&self) -> bool { + self.session.is_closed() + } +} + +/// Relays data between a local TCP stream and a tunnel channel. +async fn relay_tcp_to_channel( + mut stream: tokio::net::TcpStream, + session: &russh::client::Handle, + port: u16, +) -> Result<(), io::Error> { + let channel = session + .channel_open_direct_tcpip("127.0.0.1", port as u32, "127.0.0.1", 0) + .await + .map_err(|e| io::Error::other(format!("failed to open channel: {}", e)))?; + let mut conn = PortConnection { channel }; + + let mut read_buf = vec![0u8; 1024 * 64].into_boxed_slice(); + loop { + tokio::select! { + n = stream.read(&mut read_buf) => match n { + Ok(0) => { + log::debug!("EOF from local TCP stream on port {}", port); + conn.close().await; + break; + } + Ok(n) => { + if conn.send(&read_buf[..n]).await.is_err() { + log::debug!("channel closed while writing on port {}", port); + break; + } + } + Err(e) => { + log::debug!("error reading from local TCP on port {}: {}", port, e); + conn.close().await; + break; + } + }, + data = conn.recv() => match data { + Some(data) => { + if let Err(e) = stream.write_all(&data).await { + log::debug!("error writing to local TCP on port {}: {}", port, e); + break; + } + } + None => { + log::debug!("channel closed on port {}", port); + break; + } + }, + } + } + + Ok(()) +} + +/// A connection to a forwarded port on the tunnel host. This is the +/// client-side equivalent of `ForwardedPortConnection`. +pub struct PortConnection { + channel: russh::Channel, +} + +impl PortConnection { + /// Sends data on the connection. + pub async fn send(&mut self, d: &[u8]) -> Result<(), ()> { + self.channel.data(d).await.map_err(|_| ()) + } + + /// Receives data from the connection, returning None when it's closed. + pub async fn recv(&mut self) -> Option> { + loop { + match self.channel.wait().await { + Some(ChannelMsg::Data { data }) => return Some(data.to_vec()), + Some(ChannelMsg::Eof | ChannelMsg::Close) => return None, + None => return None, + _ => {} // skip other message types + } + } + } + + /// Closes the connection. + pub async fn close(mut self) { + self.channel.eof().await.ok(); + self.channel.close().await.ok(); + } + + /// Returns an AsyncRead/AsyncWrite implementation for the connection. + pub fn into_rw(self) -> PortConnectionRW { + PortConnectionRW(self.channel.into_stream()) + } +} + +/// AsyncRead + AsyncWrite wrapper for a client port connection. +/// This is the client-side equivalent of `ForwardedPortRW`. +pub struct PortConnectionRW(russh::ChannelStream); + +impl AsyncRead for PortConnectionRW { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl AsyncWrite for PortConnectionRW { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + Pin::new(&mut self.0).poll_shutdown(cx) + } +} + +/// SSH client handler for the tunnel client connection. +struct TunnelClientHandler; + +#[async_trait] +impl russh::client::Handler for TunnelClientHandler { + type Error = russh::Error; + + async fn check_server_key( + self, + _server_public_key: &russh_keys::key::PublicKey, + ) -> Result<(Self, bool), Self::Error> { + // The relay authenticates via the access token; we don't need to + // verify the host key. + Ok((self, true)) + } +} diff --git a/rs/src/connections/relay_tunnel_host.rs b/rs/src/connections/relay_tunnel_host.rs index 55b0e186..971e0218 100644 --- a/rs/src/connections/relay_tunnel_host.rs +++ b/rs/src/connections/relay_tunnel_host.rs @@ -12,7 +12,7 @@ use std::{ }; use crate::{ - contracts::{TunnelConnectionMode, TunnelEndpoint, TunnelPort, TunnelRelayTunnelEndpoint}, + contracts::{TunnelConnectionMode, TunnelEndpoint, TunnelPort}, management::{ Authorization, HttpError, TunnelLocator, TunnelManagementClient, TunnelRequestOptions, NO_REQUEST_OPTIONS, @@ -39,6 +39,15 @@ use super::{ /// sent. Shared by the host relay to each connected session. type PortMap = HashMap>; +// WebSocket writes can arrive here as a single large frame. If we hand that +// whole buffer to russh and report it fully written, russh may queue data until +// the SSH channel window is exhausted before the caller gets another chance to +// poll and drain the other side. 32 KiB matches the SSH channel max packet size +// used by the relay, so each accepted write maps to at most one SSH packet. +// Reporting bounded chunks preserves normal AsyncWrite backpressure and lets +// large responses continue making progress. +const CHANNEL_WRITE_CHUNK_SIZE: usize = 32 * 1024; + /// The RelayTunnelHost can host connections via the tunneling service. After /// creating it, you will generally want to run `connect()` to create a new /// a new connection. @@ -320,10 +329,13 @@ impl RelayTunnelHost { } fn make_ssh_server(keypair: russh_keys::key::KeyPair) -> Server { + let keypair_sha2_256 = keypair + .with_signature_hash(russh_keys::key::SignatureHash::SHA2_256) + .unwrap_or_else(|| keypair.clone()); let c = russh::server::Config { connection_timeout: None, auth_rejection_time: std::time::Duration::from_secs(5), - keys: vec![keypair], + keys: vec![keypair_sha2_256, keypair], window_size: 1024 * 1024, preferred: russh::Preferred::COMPRESSED, limits: russh::Limits { @@ -378,7 +390,7 @@ impl RelayTunnelHost { ) -> Result< ( WebSocketStream>, - TunnelRelayTunnelEndpoint, + TunnelEndpoint, ), TunnelError, > { @@ -386,20 +398,18 @@ impl RelayTunnelHost { .mgmt .update_tunnel_relay_endpoints( &self.locator, - &TunnelRelayTunnelEndpoint { - base: TunnelEndpoint { - id: Some(format!("{}-relay", self.host_id)), - connection_mode: TunnelConnectionMode::TunnelRelay, - host_id: self.host_id.to_string(), - host_public_keys: vec![], - port_uri_format: None, - port_ssh_command_format: None, - ssh_gateway_public_key: None, - tunnel_ssh_command: None, - tunnel_uri: None, - }, - client_relay_uri: None, - host_relay_uri: None, + &TunnelEndpoint { + id: Some(format!("{}-relay", self.host_id)), + connection_mode: TunnelConnectionMode::TunnelRelay, + host_id: self.host_id.to_string(), + host_public_keys: vec![], + port_uri_format: None, + port_ssh_command_format: None, + ssh_gateway_public_key: None, + tunnel_ssh_command: None, + tunnel_uri: None, + local_network_tunnel_endpoint: Default::default(), + tunnel_relay_tunnel_endpoint: Default::default(), }, &TunnelRequestOptions { authorization: Some(Authorization::Tunnel(host_token.to_string())), @@ -413,6 +423,7 @@ impl RelayTunnelHost { })?; let url = endpoint + .tunnel_relay_tunnel_endpoint .host_relay_uri .as_deref() .ok_or(TunnelError::MissingHostEndpoint)?; @@ -478,6 +489,7 @@ impl ForwardedPortConnection { channel: self.channel, handle: self.handle, is_write_fut_valid: false, + write_len: 0, write_fut: tokio_util::sync::ReusableBoxFuture::new(make_server_write_fut(None)), }, ForwardedPortReader { @@ -493,6 +505,7 @@ pub struct ForwardedPortWriter { channel: russh::ChannelId, handle: russh::server::Handle, is_write_fut_valid: bool, + write_len: usize, write_fut: tokio_util::sync::ReusableBoxFuture<'static, Result<(), russh::CryptoVec>>, } @@ -514,15 +527,22 @@ impl AsyncWrite for ForwardedPortWriter { cx: &mut std::task::Context<'_>, buf: &[u8], ) -> Poll> { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + if !self.is_write_fut_valid { let handle = self.handle.clone(); let id = self.channel; + let write_len = buf.len().min(CHANNEL_WRITE_CHUNK_SIZE); + self.write_len = write_len; self.write_fut - .set(make_server_write_fut(Some((handle, id, buf.to_vec())))); + .set(make_server_write_fut(Some((handle, id, buf[..write_len].to_vec())))); self.is_write_fut_valid = true; } - self.poll_flush(cx).map(|r| r.map(|_| buf.len())) + let write_len = self.write_len; + self.poll_flush(cx).map(|r| r.map(|_| write_len)) } fn poll_flush( @@ -654,6 +674,9 @@ impl Server { Some(cnx) => { if let Some(p) = known_ports.get(&cnx.port) { p.send(cnx).ok(); // ignore error, could have dropped in the meantime + } else { + log::debug!("rejecting connection to unknown port {}", cnx.port); + cnx.close().await; } }, None => { @@ -839,6 +862,29 @@ impl russh::server::Handler for ServerHandle { Ok((self, true, session)) } + async fn channel_open_direct_tcpip( + mut self, + channel: russh::Channel, + _host_to_connect: &str, + port_to_connect: u32, + _originator_address: &str, + _originator_port: u32, + session: russh::server::Session, + ) -> Result<(Self, bool, russh::server::Session), Self::Error> { + let (sender, receiver) = mpsc::channel(10); + let txd = self.cnx_tx.send(ForwardedPortConnection { + port: port_to_connect, + channel: channel.id(), + handle: session.handle(), + receiver, + }); + if txd.is_ok() { + self.channel_senders.insert(channel.id(), sender); + } + + Ok((self, true, session)) + } + async fn data( mut self, channel: russh::ChannelId, @@ -932,6 +978,7 @@ struct AsyncRWChannel { readbuf: super::io::ReadBuffer, is_write_fut_valid: bool, + write_len: usize, write_fut: tokio_util::sync::ReusableBoxFuture<'static, Result<(), russh::CryptoVec>>, } @@ -948,6 +995,7 @@ impl AsyncRWChannel { incoming: rx, readbuf: super::io::ReadBuffer::default(), is_write_fut_valid: false, + write_len: 0, write_fut: tokio_util::sync::ReusableBoxFuture::new(make_client_write_fut(None)), }, tx, @@ -977,15 +1025,22 @@ impl AsyncWrite for AsyncRWChannel { cx: &mut std::task::Context<'_>, buf: &[u8], ) -> Poll> { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + if !self.is_write_fut_valid { let session = self.session.clone(); let id = self.id; + let write_len = buf.len().min(CHANNEL_WRITE_CHUNK_SIZE); + self.write_len = write_len; self.write_fut - .set(make_client_write_fut(Some((session, id, buf.to_vec())))); + .set(make_client_write_fut(Some((session, id, buf[..write_len].to_vec())))); self.is_write_fut_valid = true; } - self.poll_flush(cx).map(|r| r.map(|_| buf.len())) + let write_len = self.write_len; + self.poll_flush(cx).map(|r| r.map(|_| write_len)) } fn poll_flush( @@ -1036,14 +1091,14 @@ impl AsyncRead for AsyncRWChannel { } pub struct RelayHandle { - endpoint: TunnelRelayTunnelEndpoint, + endpoint: TunnelEndpoint, session: Arc>, join: JoinHandle>, } impl RelayHandle { /// Gets the endpoint this relay is connected to. - pub fn endpoint(&self) -> &TunnelRelayTunnelEndpoint { + pub fn endpoint(&self) -> &TunnelEndpoint { &self.endpoint } diff --git a/rs/src/connections/ws.rs b/rs/src/connections/ws.rs index cf31d785..a626f7d3 100644 --- a/rs/src/connections/ws.rs +++ b/rs/src/connections/ws.rs @@ -138,6 +138,12 @@ where return self.readbuf.put_data(buf, v, s); } + match self.get_ws().poll_flush(cx) { + Poll::Ready(Ok(())) => {} + Poll::Ready(Err(e)) => return Poll::Ready(Err(tung_to_io_error(e))), + Poll::Pending => return Poll::Pending, + } + // The following blocks implement the state machine for liveness checks // via a websocket ping/pong. There is a "sleep" on the struct, which // is bumped every time we get a new message, along with a "state". diff --git a/rs/src/contracts/local_network_tunnel_endpoint.rs b/rs/src/contracts/local_network_tunnel_endpoint.rs index 976ed83d..4a9ef4c7 100644 --- a/rs/src/contracts/local_network_tunnel_endpoint.rs +++ b/rs/src/contracts/local_network_tunnel_endpoint.rs @@ -2,19 +2,15 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/LocalNetworkTunnelEndpoint.cs -use crate::contracts::TunnelEndpoint; use serde::{Deserialize, Serialize}; // Parameters for connecting to a tunnel via a local network connection. // // While a direct connection is technically not "tunneling", tunnel hosts may accept // connections via the local network as an optional more-efficient alternative to a relay. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct LocalNetworkTunnelEndpoint { - #[serde(flatten)] - pub base: TunnelEndpoint, - // Gets or sets a list of IP endpoints where the host may accept connections. // // A host may accept connections on multiple IP endpoints simultaneously if there are @@ -23,5 +19,6 @@ pub struct LocalNetworkTunnelEndpoint { // an indication of the network connection protocol), an IP address (IPv4 or IPv6) and // a port number. The URIs do not typically include any paths, because the connection // is not normally HTTP-based. + #[serde(skip_serializing_if = "Vec::is_empty", default)] pub host_endpoints: Vec, } diff --git a/rs/src/contracts/named_rate_status.rs b/rs/src/contracts/named_rate_status.rs index 0986ef3c..62d4fc33 100644 --- a/rs/src/contracts/named_rate_status.rs +++ b/rs/src/contracts/named_rate_status.rs @@ -2,16 +2,12 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/NamedRateStatus.cs -use crate::contracts::RateStatus; use serde::{Deserialize, Serialize}; // A named `RateStatus`. #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct NamedRateStatus { - #[serde(flatten)] - pub base: RateStatus, - // The name of the rate status. pub name: Option, } diff --git a/rs/src/contracts/rate_status.rs b/rs/src/contracts/rate_status.rs index 7bc9a397..e56878c7 100644 --- a/rs/src/contracts/rate_status.rs +++ b/rs/src/contracts/rate_status.rs @@ -2,7 +2,7 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/RateStatus.cs -use crate::contracts::ResourceStatus; +use crate::contracts::NamedRateStatus; use serde::{Deserialize, Serialize}; // Current value and limit information for a rate-limited operation related to a tunnel or @@ -10,9 +10,6 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct RateStatus { - #[serde(flatten)] - pub base: ResourceStatus, - // Gets or sets the length of each period, in seconds, over which the rate is // measured. // @@ -22,4 +19,7 @@ pub struct RateStatus { // Gets or sets the unix time in seconds when this status will be reset. pub reset_time: Option, + + #[serde(flatten)] + pub named_rate_status: NamedRateStatus, } diff --git a/rs/src/contracts/resource_status.rs b/rs/src/contracts/resource_status.rs index 9f01a395..3a9407a7 100644 --- a/rs/src/contracts/resource_status.rs +++ b/rs/src/contracts/resource_status.rs @@ -2,6 +2,7 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/ResourceStatus.cs +use crate::contracts::RateStatus; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize)] @@ -35,4 +36,7 @@ pub struct DetailedResourceStatus { // Gets or sets an optional source of the `ResourceStatus.Limit`, or null if there is // no limit. pub limit_source: Option, + + #[serde(flatten)] + pub rate_status: RateStatus, } diff --git a/rs/src/contracts/tunnel.rs b/rs/src/contracts/tunnel.rs index c7d893db..4a962fd7 100644 --- a/rs/src/contracts/tunnel.rs +++ b/rs/src/contracts/tunnel.rs @@ -2,12 +2,12 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/Tunnel.cs -use jiff::Timestamp; use crate::contracts::TunnelAccessControl; use crate::contracts::TunnelEndpoint; use crate::contracts::TunnelOptions; use crate::contracts::TunnelPort; use crate::contracts::TunnelStatus; +use jiff::Timestamp; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/rs/src/contracts/tunnel_access_control_entry.rs b/rs/src/contracts/tunnel_access_control_entry.rs index 328e8e72..8b682931 100644 --- a/rs/src/contracts/tunnel_access_control_entry.rs +++ b/rs/src/contracts/tunnel_access_control_entry.rs @@ -2,8 +2,8 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/TunnelAccessControlEntry.cs -use jiff::Timestamp; use crate::contracts::TunnelAccessControlEntryType; +use jiff::Timestamp; use serde::{Deserialize, Serialize}; // Data contract for an access control entry on a `Tunnel` or `TunnelPort`. diff --git a/rs/src/contracts/tunnel_endpoint.rs b/rs/src/contracts/tunnel_endpoint.rs index aef96832..8d4031d3 100644 --- a/rs/src/contracts/tunnel_endpoint.rs +++ b/rs/src/contracts/tunnel_endpoint.rs @@ -2,7 +2,9 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/TunnelEndpoint.cs +use crate::contracts::LocalNetworkTunnelEndpoint; use crate::contracts::TunnelConnectionMode; +use crate::contracts::TunnelRelayTunnelEndpoint; use serde::{Deserialize, Serialize}; // Base class for tunnel connection parameters. @@ -59,6 +61,12 @@ pub struct TunnelEndpoint { // Gets or sets the Ssh gateway public key which should be added to the // authorized_keys file so that tunnel service can connect to the shared ssh server. pub ssh_gateway_public_key: Option, + + #[serde(flatten)] + pub local_network_tunnel_endpoint: LocalNetworkTunnelEndpoint, + + #[serde(flatten)] + pub tunnel_relay_tunnel_endpoint: TunnelRelayTunnelEndpoint, } // Token included in `TunnelEndpoint.PortUriFormat` and diff --git a/rs/src/contracts/tunnel_port.rs b/rs/src/contracts/tunnel_port.rs index 1fd3e41a..89611508 100644 --- a/rs/src/contracts/tunnel_port.rs +++ b/rs/src/contracts/tunnel_port.rs @@ -43,7 +43,7 @@ pub struct TunnelPort { // A client that connects to a tunnel (by ID or name) without specifying a port number // will connect to the default port for the tunnel, if a default is configured. Or if // the tunnel has only one port then the single port is the implicit default. - // + // // Selection of a default port for a connection also depends on matching the // connection to the port `TunnelPort.Protocol`, so it is possible to configure // separate defaults for distinct protocols like `TunnelProtocol.Http` and diff --git a/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs b/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs index cb6e8029..dd7bab5a 100644 --- a/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs +++ b/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs @@ -2,16 +2,12 @@ // Licensed under the MIT license. // Generated from ../../../cs/src/Contracts/TunnelRelayTunnelEndpoint.cs -use crate::contracts::TunnelEndpoint; use serde::{Deserialize, Serialize}; // Parameters for connecting to a tunnel via the tunnel service's built-in relay function. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelRelayTunnelEndpoint { - #[serde(flatten)] - pub base: TunnelEndpoint, - // Gets or sets the host URI. pub host_relay_uri: Option, diff --git a/rs/src/management/http_client.rs b/rs/src/management/http_client.rs index af5ce81a..b445a423 100644 --- a/rs/src/management/http_client.rs +++ b/rs/src/management/http_client.rs @@ -15,7 +15,7 @@ use rand::Rng; use crate::contracts::{ env_production, NamedRateStatus, Tunnel, TunnelEndpoint, TunnelListByRegionResponse, - TunnelPort, TunnelPortListResponse, TunnelRelayTunnelEndpoint, TunnelServiceProperties, + TunnelPort, TunnelPortListResponse, TunnelServiceProperties, }; use super::{ @@ -177,15 +177,15 @@ impl TunnelManagementClient { pub async fn update_tunnel_relay_endpoints( &self, locator: &TunnelLocator, - endpoint: &TunnelRelayTunnelEndpoint, + endpoint: &TunnelEndpoint, options: &TunnelRequestOptions, - ) -> HttpResult { + ) -> HttpResult { let mut url = self.build_tunnel_uri( locator, - Some(&format!("{}/{}", ENDPOINTS_API_SUB_PATH, endpoint.base.id.as_deref().unwrap())), + Some(&format!("{}/{}", ENDPOINTS_API_SUB_PATH, endpoint.id.as_deref().unwrap())), ); url.query_pairs_mut() - .append_pair("connectionMode", &endpoint.base.connection_mode.to_string()); + .append_pair("connectionMode", &endpoint.connection_mode.to_string()); let mut request = self.make_tunnel_request(Method::PUT, url, options).await?; json_body(&mut request, endpoint); self.execute_json("update_tunnel_relay_endpoints", request) From 08b53a162a7cc09cbaef59c20ab2f507e318ca6c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 28 Apr 2026 14:34:25 -0700 Subject: [PATCH 5/5] comments --- .vscode/settings.json | 3 + .../RustContractWriter.cs | 5 + rs/.vscode/settings.json | 4 - rs/src/connections/errors.rs | 3 + rs/src/connections/relay_tunnel_client.rs | 101 +++++++++++++----- rs/src/connections/relay_tunnel_host.rs | 81 +++++++++++--- rs/src/connections/ws.rs | 19 ++-- rs/src/contracts/error_detail.rs | 3 +- rs/src/contracts/inner_error_detail.rs | 2 +- .../local_network_tunnel_endpoint.rs | 2 +- rs/src/contracts/named_rate_status.rs | 3 +- rs/src/contracts/problem_details.rs | 3 + rs/src/contracts/rate_status.rs | 4 +- rs/src/contracts/resource_status.rs | 2 + rs/src/contracts/service_version_details.rs | 5 + rs/src/contracts/tunnel.rs | 12 +++ .../contracts/tunnel_access_control_entry.rs | 3 + rs/src/contracts/tunnel_access_subject.rs | 3 + rs/src/contracts/tunnel_endpoint.rs | 6 ++ rs/src/contracts/tunnel_event.rs | 4 + rs/src/contracts/tunnel_list_by_region.rs | 3 + .../tunnel_list_by_region_response.rs | 1 + rs/src/contracts/tunnel_options.rs | 12 +-- rs/src/contracts/tunnel_port.rs | 13 ++- rs/src/contracts/tunnel_port_list_response.rs | 1 + rs/src/contracts/tunnel_port_status.rs | 4 + .../contracts/tunnel_relay_tunnel_endpoint.rs | 4 +- .../tunnel_report_progress_event_args.rs | 1 + rs/src/contracts/tunnel_status.rs | 12 +++ rs/src/management/http_client.rs | 18 ++-- rs/src/management/policy_provider.rs | 11 +- 31 files changed, 278 insertions(+), 70 deletions(-) delete mode 100644 rs/.vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 516fdb79..5eee226f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,4 +3,7 @@ "ts" ], "rust-analyzer.cargo.features": ["connections", "vendored-openssl"], + "rust-analyzer.linkedProjects": [ + "rs/Cargo.toml" + ], } diff --git a/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs b/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs index 2de2715c..1dcc2dba 100644 --- a/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs +++ b/cs/tools/TunnelsSDK.Generator/RustContractWriter.cs @@ -467,6 +467,11 @@ private void AppendStructProperty(ITypeSymbol parentType, IPropertySymbol proper serdeDeclarations.Add("default"); } + if (isNullable) + { + serdeDeclarations.Add("skip_serializing_if = \"Option::is_none\""); + } + if (serdeDeclarations.Count > 0) { s.AppendLine($" #[serde({string.Join(", ", serdeDeclarations)})]"); diff --git a/rs/.vscode/settings.json b/rs/.vscode/settings.json deleted file mode 100644 index ae4be7dc..00000000 --- a/rs/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rust-analyzer.cargo.features": ["connections", "vendored-openssl"], - "editor.formatOnSave": true -} diff --git a/rs/src/connections/errors.rs b/rs/src/connections/errors.rs index 7e785790..2b29fb01 100644 --- a/rs/src/connections/errors.rs +++ b/rs/src/connections/errors.rs @@ -41,4 +41,7 @@ pub enum TunnelError { #[error("no tunnel endpoint has a client relay URI")] MissingClientEndpoint, + + #[error("error listening on address: {0}")] + ErrorListeningOnAddress(std::io::Error), } diff --git a/rs/src/connections/relay_tunnel_client.rs b/rs/src/connections/relay_tunnel_client.rs index ec3ac93e..63ffa8b0 100644 --- a/rs/src/connections/relay_tunnel_client.rs +++ b/rs/src/connections/relay_tunnel_client.rs @@ -13,6 +13,7 @@ use std::{ use crate::{contracts::TunnelEndpoint, management::TunnelManagementClient}; use async_trait::async_trait; use russh::ChannelMsg; +use russh_keys::PublicKeyBase64; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::TcpListener, @@ -102,7 +103,7 @@ impl RelayTunnelClient { ping_timeout: Duration::from_secs(10), }); - let mut session = Self::make_ssh_session(cnx) + let mut session = Self::make_ssh_session(cnx, endpoint.host_public_keys.clone()) .await .map_err(TunnelError::TunnelRelayDisconnected)?; @@ -131,6 +132,7 @@ impl RelayTunnelClient { async fn make_ssh_session( rw: impl AsyncRead + AsyncWrite + Unpin + Send + 'static, + host_public_keys: Vec, ) -> Result, russh::Error> { let config = russh::client::Config { window_size: 1024 * 1024, @@ -147,7 +149,7 @@ impl RelayTunnelClient { }; let config = Arc::new(config); - let handler = TunnelClientHandler; + let handler = TunnelClientHandler { host_public_keys }; russh::client::connect_stream(config, rw, handler).await } } @@ -180,23 +182,10 @@ impl ClientRelayHandle { /// The listener will be stopped when `close()` is called on this handle. pub async fn forward_port_locally(&self, port: u16) -> Result { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); - let listener = match TcpListener::bind(addr).await { - Ok(l) => l, - Err(e) => { - log::warn!( - "Failed to bind port {} ({}), falling back to ephemeral port", - port, - e - ); - TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)) - .await - .map_err(TunnelError::ProxyConnectionFailed)? - } - }; - + let listener = TcpListener::bind(addr).await.map_err(TunnelError::ErrorListeningOnAddress)?; let local_addr = listener .local_addr() - .map_err(TunnelError::ProxyConnectionFailed)?; + .map_err(TunnelError::ErrorListeningOnAddress)?; let session = self.session.clone(); let mut close_rx = self.close_rx.clone(); @@ -269,6 +258,7 @@ async fn relay_tcp_to_channel( Ok(n) => { if conn.send(&read_buf[..n]).await.is_err() { log::debug!("channel closed while writing on port {}", port); + stream.shutdown().await.ok(); break; } } @@ -282,11 +272,13 @@ async fn relay_tcp_to_channel( Some(data) => { if let Err(e) = stream.write_all(&data).await { log::debug!("error writing to local TCP on port {}: {}", port, e); + conn.close().await; break; } } None => { log::debug!("channel closed on port {}", port); + stream.shutdown().await.ok(); break; } }, @@ -304,8 +296,8 @@ pub struct PortConnection { impl PortConnection { /// Sends data on the connection. - pub async fn send(&mut self, d: &[u8]) -> Result<(), ()> { - self.channel.data(d).await.map_err(|_| ()) + pub async fn send(&mut self, d: &[u8]) -> Result<(), TunnelError> { + self.channel.data(d).await.map_err(|e| e.into()) } /// Receives data from the connection, returning None when it's closed. @@ -369,7 +361,18 @@ impl AsyncWrite for PortConnectionRW { } /// SSH client handler for the tunnel client connection. -struct TunnelClientHandler; +struct TunnelClientHandler { + host_public_keys: Vec, +} + +impl TunnelClientHandler { + fn is_server_key_allowed(&self, server_public_key: &russh_keys::key::PublicKey) -> bool { + let server_public_key = server_public_key.public_key_base64(); + self.host_public_keys + .iter() + .any(|host_public_key| host_public_key == &server_public_key) + } +} #[async_trait] impl russh::client::Handler for TunnelClientHandler { @@ -377,10 +380,60 @@ impl russh::client::Handler for TunnelClientHandler { async fn check_server_key( self, - _server_public_key: &russh_keys::key::PublicKey, + server_public_key: &russh_keys::key::PublicKey, ) -> Result<(Self, bool), Self::Error> { - // The relay authenticates via the access token; we don't need to - // verify the host key. - Ok((self, true)) + let verified = self.is_server_key_allowed(server_public_key); + if verified { + log::debug!("verified relay tunnel host public key"); + } else if self.host_public_keys.is_empty() { + log::warn!("relay tunnel host public key could not be verified because no endpoint host keys were provided"); + } else { + log::warn!("relay tunnel host public key verification failed"); + } + + Ok((self, verified)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn public_key() -> russh_keys::key::PublicKey { + russh_keys::key::KeyPair::generate_ed25519() + .unwrap() + .clone_public_key() + .unwrap() + } + + #[test] + fn allows_expected_server_key() { + let key = public_key(); + let handler = TunnelClientHandler { + host_public_keys: vec![key.public_key_base64()], + }; + + assert!(handler.is_server_key_allowed(&key)); + } + + #[test] + fn rejects_missing_server_keys() { + let key = public_key(); + let handler = TunnelClientHandler { + host_public_keys: Vec::new(), + }; + + assert!(!handler.is_server_key_allowed(&key)); + } + + #[test] + fn rejects_unexpected_server_key() { + let expected_key = public_key(); + let actual_key = public_key(); + let handler = TunnelClientHandler { + host_public_keys: vec![expected_key.public_key_base64()], + }; + + assert!(!handler.is_server_key_allowed(&actual_key)); } } diff --git a/rs/src/connections/relay_tunnel_host.rs b/rs/src/connections/relay_tunnel_host.rs index 971e0218..c3fddaa9 100644 --- a/rs/src/connections/relay_tunnel_host.rs +++ b/rs/src/connections/relay_tunnel_host.rs @@ -21,6 +21,7 @@ use crate::{ use async_trait::async_trait; use futures_util::{stream::FuturesUnordered, StreamExt}; use russh::{server::Server as ServerTrait, CryptoVec}; +use russh_keys::PublicKeyBase64; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::TcpStream, @@ -350,6 +351,10 @@ impl RelayTunnelHost { Server { config } } + fn host_public_keys_for_endpoint(&self) -> Vec { + encode_host_public_keys(&self.host_keypair) + } + async fn make_ssh_client( rw: impl AsyncRead + AsyncWrite + Unpin + Send + 'static, ) -> Result< @@ -387,13 +392,7 @@ impl RelayTunnelHost { async fn create_websocket( &self, host_token: &str, - ) -> Result< - ( - WebSocketStream>, - TunnelEndpoint, - ), - TunnelError, - > { + ) -> Result<(WebSocketStream>, TunnelEndpoint), TunnelError> { let endpoint = self .mgmt .update_tunnel_relay_endpoints( @@ -402,7 +401,7 @@ impl RelayTunnelHost { id: Some(format!("{}-relay", self.host_id)), connection_mode: TunnelConnectionMode::TunnelRelay, host_id: self.host_id.to_string(), - host_public_keys: vec![], + host_public_keys: self.host_public_keys_for_endpoint(), port_uri_format: None, port_ssh_command_format: None, ssh_gateway_public_key: None, @@ -448,6 +447,25 @@ impl RelayTunnelHost { } } +/// Encodes the host's public key(s) for advertisement in a TunnelEndpoint. +/// +/// We use the canonical SSH wire-format encoding of the public key (for RSA, +/// this is the "ssh-rsa" blob, regardless of which signature-hash variant the +/// keypair was generated with). This is the same form the russh client +/// recovers from the server's host key during key exchange and base64-encodes +/// via `PublicKey::public_key_base64()`, which is what `RelayTunnelClient` +/// compares against. +/// +/// Note: `KeyPair::public_key_base64()` is *not* equivalent — for RSA it +/// includes the hash variant (e.g. "rsa-sha2-256") in the blob, which would +/// never match what the client sees on the wire. +fn encode_host_public_keys(keypair: &russh_keys::key::KeyPair) -> Vec { + let public_key = keypair + .clone_public_key() + .expect("expected to clone host public key"); + vec![public_key.public_key_base64()] +} + /// Type returned in a channel from `add_forwarded_port_raw`, implementing /// `AsyncRead` and `AsyncWrite`. pub struct ForwardedPortConnection { @@ -536,8 +554,11 @@ impl AsyncWrite for ForwardedPortWriter { let id = self.channel; let write_len = buf.len().min(CHANNEL_WRITE_CHUNK_SIZE); self.write_len = write_len; - self.write_fut - .set(make_server_write_fut(Some((handle, id, buf[..write_len].to_vec())))); + self.write_fut.set(make_server_write_fut(Some(( + handle, + id, + buf[..write_len].to_vec(), + )))); self.is_write_fut_valid = true; } @@ -1034,8 +1055,11 @@ impl AsyncWrite for AsyncRWChannel { let id = self.id; let write_len = buf.len().min(CHANNEL_WRITE_CHUNK_SIZE); self.write_len = write_len; - self.write_fut - .set(make_client_write_fut(Some((session, id, buf[..write_len].to_vec())))); + self.write_fut.set(make_client_write_fut(Some(( + session, + id, + buf[..write_len].to_vec(), + )))); self.is_write_fut_valid = true; } @@ -1126,3 +1150,36 @@ impl std::future::Future for RelayHandle { } } } + +#[cfg(test)] +mod tests { + use super::*; + use russh_keys::key::{KeyPair, SignatureHash}; + + /// Regression test for an interop bug where the host advertised RSA + /// public keys using `KeyPair::public_key_base64()` (which encodes the + /// hash-variant name like "rsa-sha2-256") while the client compared + /// against `PublicKey::public_key_base64()` (which always encodes the + /// canonical SSH wire name "ssh-rsa"). The two never matched and all + /// connections failed with "Unknown server key". + #[test] + fn encodes_rsa_public_key_in_canonical_ssh_wire_form() { + // Generate an RSA keypair with a non-default hash variant — this is + // what `RelayTunnelHost::new` does (SHA2_512). + let keypair = KeyPair::generate_rsa(2048, SignatureHash::SHA2_512) + .expect("generate rsa keypair"); + + let advertised = encode_host_public_keys(&keypair); + assert_eq!(advertised.len(), 1, "expected exactly one host public key"); + + // The advertised key must equal the base64 encoding of the + // corresponding `PublicKey` — this is what `RelayTunnelClient` + // computes from the server's host key during SSH key exchange. + let public_key_b64 = keypair + .clone_public_key() + .expect("clone public key") + .public_key_base64(); + assert_eq!(advertised[0], public_key_b64); + } +} + diff --git a/rs/src/connections/ws.rs b/rs/src/connections/ws.rs index a626f7d3..d7722ad2 100644 --- a/rs/src/connections/ws.rs +++ b/rs/src/connections/ws.rs @@ -168,7 +168,9 @@ where } PingState::WillPing => match self.get_ws().poll_ready(cx) { Poll::Ready(Ok(_)) => { - if let Err(e) = self.get_ws().start_send(tungstenite::Message::Ping(vec![].into())) + if let Err(e) = self + .get_ws() + .start_send(tungstenite::Message::Ping(vec![].into())) { return Poll::Ready(Err(tung_to_io_error(e))); } @@ -257,11 +259,10 @@ pub(crate) async fn connect_via_proxy( let stream = stream.map_err(TunnelError::ProxyConnectionFailed)?; - let (mut request_sender, conn) = hyper::client::conn::http1::handshake( - hyper_util::rt::TokioIo::new(stream), - ) - .await - .map_err(TunnelError::ProxyHandshakeFailed)?; + let (mut request_sender, conn) = + hyper::client::conn::http1::handshake(hyper_util::rt::TokioIo::new(stream)) + .await + .map_err(TunnelError::ProxyHandshakeFailed)?; let conn = tokio::spawn(conn.without_shutdown()); let connect_req = hyper::Request::connect(&authority) @@ -298,9 +299,9 @@ pub(crate) async fn connect_via_proxy( let tcp = conn .await .map_err(|e| { - TunnelError::ProxyConnectionFailed(io::Error::other( - format!("proxy connection task failed: {e}"), - )) + TunnelError::ProxyConnectionFailed(io::Error::other(format!( + "proxy connection task failed: {e}" + ))) })? .map_err(TunnelError::ProxyHandshakeFailed)? .io diff --git a/rs/src/contracts/error_detail.rs b/rs/src/contracts/error_detail.rs index 23853d16..45685b15 100644 --- a/rs/src/contracts/error_detail.rs +++ b/rs/src/contracts/error_detail.rs @@ -16,6 +16,7 @@ pub struct ErrorDetail { pub message: String, // The target of the error. + #[serde(skip_serializing_if = "Option::is_none")] pub target: Option, // An array of details about specific errors that led to this reported error. @@ -24,6 +25,6 @@ pub struct ErrorDetail { // An object containing more specific information than the current object about the // error. - #[serde(rename = "innererror")] + #[serde(rename = "innererror", skip_serializing_if = "Option::is_none")] pub inner_error: Option, } diff --git a/rs/src/contracts/inner_error_detail.rs b/rs/src/contracts/inner_error_detail.rs index eb445a27..ca60d6e4 100644 --- a/rs/src/contracts/inner_error_detail.rs +++ b/rs/src/contracts/inner_error_detail.rs @@ -14,6 +14,6 @@ pub struct InnerErrorDetail { // An object containing more specific information than the current object about the // error. - #[serde(rename = "innererror")] + #[serde(rename = "innererror", skip_serializing_if = "Option::is_none")] pub inner_error: Option>, } diff --git a/rs/src/contracts/local_network_tunnel_endpoint.rs b/rs/src/contracts/local_network_tunnel_endpoint.rs index 4a9ef4c7..c64e8c7b 100644 --- a/rs/src/contracts/local_network_tunnel_endpoint.rs +++ b/rs/src/contracts/local_network_tunnel_endpoint.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; // // While a direct connection is technically not "tunneling", tunnel hosts may accept // connections via the local network as an optional more-efficient alternative to a relay. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct LocalNetworkTunnelEndpoint { // Gets or sets a list of IP endpoints where the host may accept connections. diff --git a/rs/src/contracts/named_rate_status.rs b/rs/src/contracts/named_rate_status.rs index 62d4fc33..0bc6d61b 100644 --- a/rs/src/contracts/named_rate_status.rs +++ b/rs/src/contracts/named_rate_status.rs @@ -5,9 +5,10 @@ use serde::{Deserialize, Serialize}; // A named `RateStatus`. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct NamedRateStatus { // The name of the rate status. + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, } diff --git a/rs/src/contracts/problem_details.rs b/rs/src/contracts/problem_details.rs index ccff3bc1..0f4e9be0 100644 --- a/rs/src/contracts/problem_details.rs +++ b/rs/src/contracts/problem_details.rs @@ -15,11 +15,14 @@ use std::collections::HashMap; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct ProblemDetails { // Gets or sets the error title. + #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, // Gets or sets the error detail. + #[serde(skip_serializing_if = "Option::is_none")] pub detail: Option, // Gets or sets additional details about individual request properties. + #[serde(skip_serializing_if = "Option::is_none")] pub errors: Option>>, } diff --git a/rs/src/contracts/rate_status.rs b/rs/src/contracts/rate_status.rs index e56878c7..4105c03f 100644 --- a/rs/src/contracts/rate_status.rs +++ b/rs/src/contracts/rate_status.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; // Current value and limit information for a rate-limited operation related to a tunnel or // port. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct RateStatus { // Gets or sets the length of each period, in seconds, over which the rate is @@ -15,9 +15,11 @@ pub struct RateStatus { // // For rates that are limited by month (or billing period), this value may represent // an estimate, since the actual duration may vary by the calendar. + #[serde(skip_serializing_if = "Option::is_none")] pub period_seconds: Option, // Gets or sets the unix time in seconds when this status will be reset. + #[serde(skip_serializing_if = "Option::is_none")] pub reset_time: Option, #[serde(flatten)] diff --git a/rs/src/contracts/resource_status.rs b/rs/src/contracts/resource_status.rs index 3a9407a7..2d7bed93 100644 --- a/rs/src/contracts/resource_status.rs +++ b/rs/src/contracts/resource_status.rs @@ -31,10 +31,12 @@ pub struct DetailedResourceStatus { // Any requests that would cause the limit to be exceeded may be denied by the // service. For HTTP requests, the response is generally a 403 Forbidden status, with // details about the limit in the response body. + #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, // Gets or sets an optional source of the `ResourceStatus.Limit`, or null if there is // no limit. + #[serde(skip_serializing_if = "Option::is_none")] pub limit_source: Option, #[serde(flatten)] diff --git a/rs/src/contracts/service_version_details.rs b/rs/src/contracts/service_version_details.rs index b7b132c2..704bdb89 100644 --- a/rs/src/contracts/service_version_details.rs +++ b/rs/src/contracts/service_version_details.rs @@ -10,17 +10,22 @@ use serde::{Deserialize, Serialize}; pub struct ServiceVersionDetails { // Gets or sets the version of the service. E.g. "1.0.6615.53976". The version // corresponds to the build number. + #[serde(skip_serializing_if = "Option::is_none")] pub version: Option, // Gets or sets the commit ID of the service. + #[serde(skip_serializing_if = "Option::is_none")] pub commit_id: Option, // Gets or sets the commit date of the service. + #[serde(skip_serializing_if = "Option::is_none")] pub commit_date: Option, // Gets or sets the cluster ID of the service that handled the request. + #[serde(skip_serializing_if = "Option::is_none")] pub cluster_id: Option, // Gets or sets the Azure location of the service that handled the request. + #[serde(skip_serializing_if = "Option::is_none")] pub azure_location: Option, } diff --git a/rs/src/contracts/tunnel.rs b/rs/src/contracts/tunnel.rs index 4a962fd7..60bd3fbd 100644 --- a/rs/src/contracts/tunnel.rs +++ b/rs/src/contracts/tunnel.rs @@ -16,18 +16,22 @@ use std::collections::HashMap; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct Tunnel { // Gets or sets the ID of the cluster the tunnel was created in. + #[serde(skip_serializing_if = "Option::is_none")] pub cluster_id: Option, // Gets or sets the generated ID of the tunnel, unique within the cluster. + #[serde(skip_serializing_if = "Option::is_none")] pub tunnel_id: Option, // Gets or sets the optional short name (alias) of the tunnel. // // The name must be globally unique within the parent domain, and must be a valid // subdomain. + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, // Gets or sets the description of the tunnel. + #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, // Gets or sets the labels of the tunnel. @@ -36,20 +40,25 @@ pub struct Tunnel { // Gets or sets the optional parent domain of the tunnel, if it is not using the // default parent domain. + #[serde(skip_serializing_if = "Option::is_none")] pub domain: Option, // Gets or sets a dictionary mapping from scopes to tunnel access tokens. + #[serde(skip_serializing_if = "Option::is_none")] pub access_tokens: Option>, // Gets or sets access control settings for the tunnel. // // See `TunnelAccessControl` documentation for details about the access control model. + #[serde(skip_serializing_if = "Option::is_none")] pub access_control: Option, // Gets or sets default options for the tunnel. + #[serde(skip_serializing_if = "Option::is_none")] pub options: Option, // Gets or sets current connection status of the tunnel. + #[serde(skip_serializing_if = "Option::is_none")] pub status: Option, // Gets or sets an array of endpoints where hosts are currently accepting client @@ -68,12 +77,15 @@ pub struct Tunnel { pub ports: Vec, // Gets or sets the time in UTC of tunnel creation. + #[serde(skip_serializing_if = "Option::is_none")] pub created: Option, // Gets or the time the tunnel will be deleted if it is not used or updated. + #[serde(skip_serializing_if = "Option::is_none")] pub expiration: Option, // Gets or the custom amount of time the tunnel will be valid if it is not used or // updated in seconds. + #[serde(skip_serializing_if = "Option::is_none")] pub custom_expiration: Option, } diff --git a/rs/src/contracts/tunnel_access_control_entry.rs b/rs/src/contracts/tunnel_access_control_entry.rs index 8b682931..073a23cd 100644 --- a/rs/src/contracts/tunnel_access_control_entry.rs +++ b/rs/src/contracts/tunnel_access_control_entry.rs @@ -30,6 +30,7 @@ pub struct TunnelAccessControlEntry { // For IP address range ACEs, this value is the IP address version, "ipv4" or "ipv6", // or "service-tag" if the range is defined by an Azure service tag. For anonymous // ACEs, this value is null. + #[serde(skip_serializing_if = "Option::is_none")] pub provider: Option, // Gets or sets a value indicating whether this is an access control entry on a tunnel @@ -67,6 +68,7 @@ pub struct TunnelAccessControlEntry { // // For AAD users and group ACEs, this value is the AAD tenant ID. It is not currently // used with any other types of ACEs. + #[serde(skip_serializing_if = "Option::is_none")] pub organization: Option, // Gets or sets the subjects for the entry, such as user or group IDs. The format of @@ -82,6 +84,7 @@ pub struct TunnelAccessControlEntry { // Gets or sets the expiration for an access control entry. // // If no value is set then this value is null. + #[serde(skip_serializing_if = "Option::is_none")] pub expiration: Option, } diff --git a/rs/src/contracts/tunnel_access_subject.rs b/rs/src/contracts/tunnel_access_subject.rs index 1188604b..69bfecc8 100644 --- a/rs/src/contracts/tunnel_access_subject.rs +++ b/rs/src/contracts/tunnel_access_subject.rs @@ -19,10 +19,12 @@ pub struct TunnelAccessSubject { // // The ID is typically a guid or integer that is unique within the scope of the // identity provider or organization, and never changes for that subject. + #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, // Gets or sets the subject organization ID, which may be required if an organization // is not implied by the authentication context. + #[serde(skip_serializing_if = "Option::is_none")] pub organization_id: Option, // Gets or sets the partial or full subject name. @@ -30,6 +32,7 @@ pub struct TunnelAccessSubject { // When resolving a subject name to ID, a partial name may be provided, and the full // name is returned if the partial name was successfully resolved. When formatting a // subject ID to name, the full name is returned if the ID was found. + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, // Gets or sets an array of possible subject matches, if a partial name was provided diff --git a/rs/src/contracts/tunnel_endpoint.rs b/rs/src/contracts/tunnel_endpoint.rs index 8d4031d3..58014aa9 100644 --- a/rs/src/contracts/tunnel_endpoint.rs +++ b/rs/src/contracts/tunnel_endpoint.rs @@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelEndpoint { // Gets or sets the ID of this endpoint. + #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, // Gets or sets the connection mode of the endpoint. @@ -43,23 +44,28 @@ pub struct TunnelEndpoint { // Gets or sets a string used to format URIs where a web client can connect to ports // of the tunnel. The string includes a `TunnelEndpoint.PortToken` that must be // replaced with the actual port number. + #[serde(skip_serializing_if = "Option::is_none")] pub port_uri_format: Option, // Gets or sets the URI where a web client can connect to the default port of the // tunnel. + #[serde(skip_serializing_if = "Option::is_none")] pub tunnel_uri: Option, // Gets or sets a string used to format ssh command where ssh client can connect to // shared ssh port of the tunnel. The string includes a `TunnelEndpoint.PortToken` // that must be replaced with the actual port number. + #[serde(skip_serializing_if = "Option::is_none")] pub port_ssh_command_format: Option, // Gets or sets the Ssh command where the Ssh client can connect to the default ssh // port of the tunnel. + #[serde(skip_serializing_if = "Option::is_none")] pub tunnel_ssh_command: Option, // Gets or sets the Ssh gateway public key which should be added to the // authorized_keys file so that tunnel service can connect to the shared ssh server. + #[serde(skip_serializing_if = "Option::is_none")] pub ssh_gateway_public_key: Option, #[serde(flatten)] diff --git a/rs/src/contracts/tunnel_event.rs b/rs/src/contracts/tunnel_event.rs index e1ae9b1f..d0d3c846 100644 --- a/rs/src/contracts/tunnel_event.rs +++ b/rs/src/contracts/tunnel_event.rs @@ -11,6 +11,7 @@ use std::collections::HashMap; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelEvent { // Gets or sets the UTC timestamp of the event (using the client's clock). + #[serde(skip_serializing_if = "Option::is_none")] pub timestamp: Option, // Gets or sets name of the event. This should be a short descriptive identifier. @@ -20,13 +21,16 @@ pub struct TunnelEvent { // `TunnelEvent.Warning`, or `TunnelEvent.Error`. // // If not specified, the default severity is "info". + #[serde(skip_serializing_if = "Option::is_none")] pub severity: Option, // Gets or sets optional unstructured details about the event, such as a message or // description. For warning or error events this may include a stack trace. + #[serde(skip_serializing_if = "Option::is_none")] pub details: Option, // Gets or sets semi-structured event properties. + #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>, } diff --git a/rs/src/contracts/tunnel_list_by_region.rs b/rs/src/contracts/tunnel_list_by_region.rs index 9cd3b8d2..5a8b8cf5 100644 --- a/rs/src/contracts/tunnel_list_by_region.rs +++ b/rs/src/contracts/tunnel_list_by_region.rs @@ -11,9 +11,11 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelListByRegion { // Azure region name. + #[serde(skip_serializing_if = "Option::is_none")] pub region_name: Option, // Cluster id in the region. + #[serde(skip_serializing_if = "Option::is_none")] pub cluster_id: Option, // List of tunnels. @@ -21,5 +23,6 @@ pub struct TunnelListByRegion { pub value: Vec, // Error detail if getting list of tunnels in the region failed. + #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, } diff --git a/rs/src/contracts/tunnel_list_by_region_response.rs b/rs/src/contracts/tunnel_list_by_region_response.rs index a74b971b..a3fec8c8 100644 --- a/rs/src/contracts/tunnel_list_by_region_response.rs +++ b/rs/src/contracts/tunnel_list_by_region_response.rs @@ -14,5 +14,6 @@ pub struct TunnelListByRegionResponse { pub value: Vec, // Link to get next page of results. + #[serde(skip_serializing_if = "Option::is_none")] pub next_link: Option, } diff --git a/rs/src/contracts/tunnel_options.rs b/rs/src/contracts/tunnel_options.rs index 64df7139..aafdc5f2 100644 --- a/rs/src/contracts/tunnel_options.rs +++ b/rs/src/contracts/tunnel_options.rs @@ -19,7 +19,7 @@ pub struct TunnelOptions { // "localhost" to rewrite the header. Web-fowarding will use this property instead if // it is not null or empty. Port-level option, if set, takes precedence over this // option on the tunnel level. The option is ignored if IsHostHeaderUnchanged is true. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub host_header: Option, // Gets or sets a value indicating whether `Host` header is rewritten or the header @@ -37,7 +37,7 @@ pub struct TunnelOptions { // instead if it is not null or empty. Port-level option, if set, takes precedence // over this option on the tunnel level. The option is ignored if // IsOriginHeaderUnchanged is true. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub origin_header: Option, // Gets or sets a value indicating whether `Origin` header is rewritten or the header @@ -58,7 +58,7 @@ pub struct TunnelOptions { // controls whether the tunnel web-forwarding authentication cookie is marked as // SameSite=None. The default is false, which means the cookie is marked as // SameSite=Lax. This only applies to tunnels that require authentication. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub is_cross_site_authentication_enabled: Option, // Gets or sets a value indicating whether the tunnel web-forwarding authentication @@ -67,7 +67,7 @@ pub struct TunnelOptions { // // A partitioned cookie always also has SameSite=None for compatbility with browsers // that do not support partitioning. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub is_partitioned_site_authentication_enabled: Option, // Gets or sets a value indicating whether web requests to the tunnel or port can be @@ -88,7 +88,7 @@ pub struct TunnelOptions { // implement bearer token authentication, which is why this option is disabled by // default. This option does not apply to the tunnel management API, which always // supports bearer token authentication using the `Authorization` header. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub is_bearer_token_authentication_enabled: Option, // Gets or sets the timeout for HTTP requests to the tunnel or port. @@ -99,6 +99,6 @@ pub struct TunnelOptions { // keep-alives and HTTP/2 protocol pings will not reset the timeout, but WebSocket // pings will. When a request times out, the tunnel relay aborts the request and // returns 504 Gateway Timeout. - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub request_timeout_seconds: Option, } diff --git a/rs/src/contracts/tunnel_port.rs b/rs/src/contracts/tunnel_port.rs index 89611508..fecb2b8b 100644 --- a/rs/src/contracts/tunnel_port.rs +++ b/rs/src/contracts/tunnel_port.rs @@ -13,9 +13,11 @@ use std::collections::HashMap; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelPort { // Gets or sets the ID of the cluster the tunnel was created in. + #[serde(skip_serializing_if = "Option::is_none")] pub cluster_id: Option, // Gets or sets the generated ID of the tunnel, unique within the cluster. + #[serde(skip_serializing_if = "Option::is_none")] pub tunnel_id: Option, // Gets or sets the IP port number of the tunnel port. @@ -24,9 +26,11 @@ pub struct TunnelPort { // Gets or sets the optional short name of the port. // // The name must be unique among named ports of the same tunnel. + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, // Gets or sets the optional description of the port. + #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, // Gets or sets the labels of the port. @@ -36,6 +40,7 @@ pub struct TunnelPort { // Gets or sets the protocol of the tunnel port. // // Should be one of the string constants from `TunnelProtocol`. + #[serde(skip_serializing_if = "Option::is_none")] pub protocol: Option, // Gets or sets a value indicating whether this port is a default port for the tunnel. @@ -43,7 +48,7 @@ pub struct TunnelPort { // A client that connects to a tunnel (by ID or name) without specifying a port number // will connect to the default port for the tunnel, if a default is configured. Or if // the tunnel has only one port then the single port is the implicit default. - // + // // Selection of a default port for a connection also depends on matching the // connection to the port `TunnelPort.Protocol`, so it is possible to configure // separate defaults for distinct protocols like `TunnelProtocol.Http` and @@ -55,22 +60,27 @@ pub struct TunnelPort { // // Unlike the tokens in `Tunnel.AccessTokens`, these tokens are restricted to the // individual port. + #[serde(skip_serializing_if = "Option::is_none")] pub access_tokens: Option>, // Gets or sets access control settings for the tunnel port. // // See `TunnelAccessControl` documentation for details about the access control model. + #[serde(skip_serializing_if = "Option::is_none")] pub access_control: Option, // Gets or sets options for the tunnel port. + #[serde(skip_serializing_if = "Option::is_none")] pub options: Option, // Gets or sets current connection status of the tunnel port. + #[serde(skip_serializing_if = "Option::is_none")] pub status: Option, // Gets or sets the username for the ssh service user is trying to forward. // // Should be provided if the `TunnelProtocol` is Ssh. + #[serde(skip_serializing_if = "Option::is_none")] pub ssh_user: Option, // Gets or sets web forwarding URIs. If set, it's a list of absolute URIs where the @@ -80,5 +90,6 @@ pub struct TunnelPort { // Gets or sets inspection URI. If set, it's an absolute URIs where the port's traffic // can be inspected. + #[serde(skip_serializing_if = "Option::is_none")] pub inspection_uri: Option, } diff --git a/rs/src/contracts/tunnel_port_list_response.rs b/rs/src/contracts/tunnel_port_list_response.rs index a92a4043..23640367 100644 --- a/rs/src/contracts/tunnel_port_list_response.rs +++ b/rs/src/contracts/tunnel_port_list_response.rs @@ -13,5 +13,6 @@ pub struct TunnelPortListResponse { pub value: Vec, // Link to get next page of results + #[serde(skip_serializing_if = "Option::is_none")] pub next_link: Option, } diff --git a/rs/src/contracts/tunnel_port_status.rs b/rs/src/contracts/tunnel_port_status.rs index 6b5a6699..dc9dfe1b 100644 --- a/rs/src/contracts/tunnel_port_status.rs +++ b/rs/src/contracts/tunnel_port_status.rs @@ -18,10 +18,12 @@ pub struct TunnelPortStatus { // connections. This count also does not include HTTP client connections, unless they // are upgraded to websockets. HTTP connections are counted per-request rather than // per-connection: see `TunnelPortStatus.HttpRequestRate`. + #[serde(skip_serializing_if = "Option::is_none")] pub client_connection_count: Option, // Gets or sets the UTC date time when a client was last connected to the port, or // null if a client has never connected. + #[serde(skip_serializing_if = "Option::is_none")] pub last_client_connection_time: Option, // Gets or sets the current value and limit for the rate of client connections to the @@ -32,9 +34,11 @@ pub struct TunnelPortStatus { // types. This also does not include HTTP connections, unless they are upgraded to // websockets. HTTP connections are counted per-request rather than per-connection: // see `TunnelPortStatus.HttpRequestRate`. + #[serde(skip_serializing_if = "Option::is_none")] pub client_connection_rate: Option, // Gets or sets the current value and limit for the rate of HTTP requests to the // tunnel port. + #[serde(skip_serializing_if = "Option::is_none")] pub http_request_rate: Option, } diff --git a/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs b/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs index dd7bab5a..a0a8c52c 100644 --- a/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs +++ b/rs/src/contracts/tunnel_relay_tunnel_endpoint.rs @@ -5,12 +5,14 @@ use serde::{Deserialize, Serialize}; // Parameters for connecting to a tunnel via the tunnel service's built-in relay function. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelRelayTunnelEndpoint { // Gets or sets the host URI. + #[serde(skip_serializing_if = "Option::is_none")] pub host_relay_uri: Option, // Gets or sets the client URI. + #[serde(skip_serializing_if = "Option::is_none")] pub client_relay_uri: Option, } diff --git a/rs/src/contracts/tunnel_report_progress_event_args.rs b/rs/src/contracts/tunnel_report_progress_event_args.rs index 8df88a94..fe24168d 100644 --- a/rs/src/contracts/tunnel_report_progress_event_args.rs +++ b/rs/src/contracts/tunnel_report_progress_event_args.rs @@ -14,5 +14,6 @@ pub struct TunnelReportProgressEventArgs { pub progress: String, // The session number associated with an SSH session progress event. + #[serde(skip_serializing_if = "Option::is_none")] pub session_number: Option, } diff --git a/rs/src/contracts/tunnel_status.rs b/rs/src/contracts/tunnel_status.rs index 6e6b7d89..81056379 100644 --- a/rs/src/contracts/tunnel_status.rs +++ b/rs/src/contracts/tunnel_status.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] pub struct TunnelStatus { // Gets or sets the current value and limit for the number of ports on the tunnel. + #[serde(skip_serializing_if = "Option::is_none")] pub port_count: Option, // Gets or sets the current value and limit for the number of hosts currently @@ -18,10 +19,12 @@ pub struct TunnelStatus { // // This is typically 0 or 1, but may be more than 1 if the tunnel options allow // multiple hosts. + #[serde(skip_serializing_if = "Option::is_none")] pub host_connection_count: Option, // Gets or sets the UTC time when a host was last accepting connections to the tunnel, // or null if a host has never connected. + #[serde(skip_serializing_if = "Option::is_none")] pub last_host_connection_time: Option, // Gets or sets the current value and limit for the number of clients connected to the @@ -29,6 +32,7 @@ pub struct TunnelStatus { // // This counts non-port-specific client connections, which is SDK and SSH clients. See // `TunnelPortStatus` for status of per-port client connections. + #[serde(skip_serializing_if = "Option::is_none")] pub client_connection_count: Option, // Gets or sets the UTC time when a client last connected to the tunnel, or null if a @@ -36,6 +40,7 @@ pub struct TunnelStatus { // // This reports times for non-port-specific client connections, which is SDK client // and SSH clients. See `TunnelPortStatus` for per-port client connections. + #[serde(skip_serializing_if = "Option::is_none")] pub last_client_connection_time: Option, // Gets or sets the current value and limit for the rate of client connections to the @@ -43,6 +48,7 @@ pub struct TunnelStatus { // // This counts non-port-specific client connections, which is SDK client and SSH // clients. See `TunnelPortStatus` for status of per-port client connections. + #[serde(skip_serializing_if = "Option::is_none")] pub client_connection_rate: Option, // Gets or sets the current value and limit for the rate of bytes being received by @@ -53,6 +59,7 @@ pub struct TunnelStatus { // measurable by applications, due to protocol overhead. Data rate status reporting is // delayed by a few seconds, so this value is a snapshot of the data transfer rate // from a few seconds earlier. + #[serde(skip_serializing_if = "Option::is_none")] pub upload_rate: Option, // Gets or sets the current value and limit for the rate of bytes being sent by the @@ -63,6 +70,7 @@ pub struct TunnelStatus { // measurable by applications, due to protocol overhead. Data rate status reporting is // delayed by a few seconds, so this value is a snapshot of the data transfer rate // from a few seconds earlier. + #[serde(skip_serializing_if = "Option::is_none")] pub download_rate: Option, // Gets or sets the total number of bytes received by the tunnel host and uploaded by @@ -72,6 +80,7 @@ pub struct TunnelStatus { // contribute to this total. The reported value may differ slightly from the value // measurable by applications, due to protocol overhead. Data transfer status // reporting is delayed by a few seconds. + #[serde(skip_serializing_if = "Option::is_none")] pub upload_total: Option, // Gets or sets the total number of bytes sent by the tunnel host and downloaded by @@ -81,13 +90,16 @@ pub struct TunnelStatus { // contribute to this total. The reported value may differ slightly from the value // measurable by applications, due to protocol overhead. Data transfer status // reporting is delayed by a few seconds. + #[serde(skip_serializing_if = "Option::is_none")] pub download_total: Option, // Gets or sets the current value and limit for the rate of management API read // operations for the tunnel or tunnel ports. + #[serde(skip_serializing_if = "Option::is_none")] pub api_read_rate: Option, // Gets or sets the current value and limit for the rate of management API update // operations for the tunnel or tunnel ports. + #[serde(skip_serializing_if = "Option::is_none")] pub api_update_rate: Option, } diff --git a/rs/src/management/http_client.rs b/rs/src/management/http_client.rs index b445a423..f9df9ed2 100644 --- a/rs/src/management/http_client.rs +++ b/rs/src/management/http_client.rs @@ -164,7 +164,11 @@ impl TunnelManagementClient { ) -> HttpResult { let mut url = self.build_tunnel_uri( locator, - Some(&format!("{}/{}", ENDPOINTS_API_SUB_PATH, endpoint.id.as_deref().unwrap())), + Some(&format!( + "{}/{}", + ENDPOINTS_API_SUB_PATH, + endpoint.id.as_deref().unwrap() + )), ); url.query_pairs_mut() .append_pair("connectionMode", &endpoint.connection_mode.to_string()); @@ -182,7 +186,11 @@ impl TunnelManagementClient { ) -> HttpResult { let mut url = self.build_tunnel_uri( locator, - Some(&format!("{}/{}", ENDPOINTS_API_SUB_PATH, endpoint.id.as_deref().unwrap())), + Some(&format!( + "{}/{}", + ENDPOINTS_API_SUB_PATH, + endpoint.id.as_deref().unwrap() + )), ); url.query_pairs_mut() .append_pair("connectionMode", &endpoint.connection_mode.to_string()); @@ -852,10 +860,8 @@ mod tests { #[test] fn custom_domain_does_not_modify_hostname() { - let builder = super::new_tunnel_management_for_custom_domain( - "rs-sdk-tests", - "app.github.dev", - ); + let builder = + super::new_tunnel_management_for_custom_domain("rs-sdk-tests", "app.github.dev"); let client: super::TunnelManagementClient = builder.into(); let url = client.build_uri(Some("usw2"), "/tunnels/tnnl0001"); assert_eq!(url.host_str().unwrap(), "cp.app.github.dev"); diff --git a/rs/src/management/policy_provider.rs b/rs/src/management/policy_provider.rs index 0671feb5..82663e8e 100644 --- a/rs/src/management/policy_provider.rs +++ b/rs/src/management/policy_provider.rs @@ -8,7 +8,10 @@ pub fn get_policy_header_value() -> io::Result> { // Encode everything except RFC3986 unreserved characters (ALPHA / DIGIT / "-" / "." / "_" / "~") const URI_COMPONENT: &AsciiSet = &NON_ALPHANUMERIC - .remove(b'-').remove(b'.').remove(b'_').remove(b'~'); + .remove(b'-') + .remove(b'.') + .remove(b'_') + .remove(b'~'); pub const REGISTRY_KEY_PATH: &str = r"Software\Policies\Microsoft\DevTunnels"; @@ -24,7 +27,11 @@ pub fn get_policy_header_value() -> io::Result> { for (name, value) in sub_key.enum_values().filter_map(Result::ok) { let value_str: String = value.to_string(); if !value_str.is_empty() { - header_values.push(format!("{}={}", utf8_percent_encode(&name, URI_COMPONENT), utf8_percent_encode(&value_str, URI_COMPONENT))); + header_values.push(format!( + "{}={}", + utf8_percent_encode(&name, URI_COMPONENT), + utf8_percent_encode(&value_str, URI_COMPONENT) + )); } }