├── .gitignore ├── .rustfmt.toml ├── README.md ├── rust-toolchain.toml ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── tests ├── RootCA.crt ├── localhost.crt ├── localhost.key └── test.rs ├── src └── lib.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | edition = "2024" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deno_tunnel 2 | 3 | Tunnels used to communicate between `deno` and Deno Deploy. 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.88.0" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | profile: [dev, release] 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: denoland/setup-deno@v2 20 | - uses: actions/checkout@v3 21 | - uses: dsherret/rust-toolchain-file@v1 22 | - name: Build 23 | run: cargo build --profile=${{ matrix.profile }} --verbose 24 | - name: Run tests 25 | run: cargo test --profile=${{ matrix.profile }} --verbose 26 | env: 27 | RUST_BACKTRACE: 1 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2025 the Deno authors. MIT license. 2 | 3 | [package] 4 | name = "deno_tunnel" 5 | version = "0.8.0" 6 | authors = ["the Deno authors"] 7 | edition = "2024" 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/denoland/deno_tunnel" 11 | description = "Deno Tunnels" 12 | 13 | [dependencies] 14 | pin-project = "1.1" 15 | quinn = { version = "0.11", default-features = false, features = ["bloom", "log", "runtime-tokio", "rustls-aws-lc-rs"] } 16 | serde = { version = "1", features = ["derive"] } 17 | serde_json = "1" 18 | thiserror = "2.0" 19 | tokio = { version = "1.46", features = ["full"] } 20 | tracing = "0.1" 21 | 22 | [dev-dependencies] 23 | rustls-pemfile = "2" 24 | -------------------------------------------------------------------------------- /tests/RootCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDIzCCAgugAwIBAgIJAMKPPW4tsOymMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV 3 | BAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwIBcNMTkxMDIxMTYyODIy 4 | WhgPMjExODA5MjcxNjI4MjJaMCcxCzAJBgNVBAYTAlVTMRgwFgYDVQQDDA9FeGFt 5 | cGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMH/IO 6 | 2qtHfyBKwANNPB4K0q5JVSg8XxZdRpTTlz0CwU0oRO3uHrI52raCCfVeiQutyZop 7 | eFZTDWeXGudGAFA2B5m3orWt0s+touPi8MzjsG2TQ+WSI66QgbXTNDitDDBtTVcV 8 | 5G3Ic+3SppQAYiHSekLISnYWgXLl+k5CnEfTowg6cjqjVr0KjL03cTN3H7b+6+0S 9 | ws4rYbW1j4ExR7K6BFNH6572yq5qR20E6GqlY+EcOZpw4CbCk9lS8/CWuXze/vMs 10 | OfDcc6K+B625d27wyEGZHedBomT2vAD7sBjvO8hn/DP1Qb46a8uCHR6NSfnJ7bXO 11 | G1igaIbgY1zXirNdAgMBAAGjUDBOMB0GA1UdDgQWBBTzut+pwwDfqmMYcI9KNWRD 12 | hxcIpTAfBgNVHSMEGDAWgBTzut+pwwDfqmMYcI9KNWRDhxcIpTAMBgNVHRMEBTAD 13 | AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB9AqSbZ+hEglAgSHxAMCqRFdhVu7MvaQM0 14 | P090mhGlOCt3yB7kdGfsIrUW6nQcTz7PPQFRaJMrFHPvFvPootkBUpTYR4hTkdce 15 | H6RCRu2Jxl4Y9bY/uezd9YhGCYfUtfjA6/TH9FcuZfttmOOlxOt01XfNvVMIR6RM 16 | z/AYhd+DeOXjr35F/VHeVpnk+55L0PYJsm1CdEbOs5Hy1ecR7ACuDkXnbM4fpz9I 17 | kyIWJwk2zJReKcJMgi1aIinDM9ao/dca1G99PHOw8dnr4oyoTiv8ao6PWiSRHHMi 18 | MNf4EgWfK+tZMnuqfpfO9740KzfcVoMNo4QJD4yn5YxroUOO/Azi 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /tests/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDajCCAlKgAwIBAgIJAOPyQVdy/UpPMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV 3 | BAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwIBcNMTkxMDIxMTYyODU4 4 | WhgPMjExODA5MjcxNjI4NThaMG0xCzAJBgNVBAYTAlVTMRIwEAYDVQQIDAlZb3Vy 5 | U3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFtcGxlLUNlcnRp 6 | ZmljYXRlczEYMBYGA1UEAwwPbG9jYWxob3N0LmxvY2FsMIIBIjANBgkqhkiG9w0B 7 | AQEFAAOCAQ8AMIIBCgKCAQEAz9svjVdf5jihUBtofd84XKdb8dEHQRJfDNKaJ4Ar 8 | baqMHAdnqi/fWtlqEEMn8gweZ7+4hshECY5mnx4Hhy7IAbePDsTTbSm01dChhlxF 9 | uvd9QuvzvrqSjSq+v4Jlau+pQIhUzzV12dF5bFvrIrGWxCZp+W7lLDZI6Pd6Su+y 10 | ZIeiwrUaPMzdUePNf2hZI/IvWCUMCIyoqrrKHdHoPuvQCW17IyxsnFQJNbmN+Rtp 11 | BQilhtwvBbggCBWhHxEdiqBaZHDw6Zl+bU7ejx1mu9A95wpQ9SCL2cRkAlz2LDOy 12 | wznrTAwGcvqvFKxlV+3HsaD7rba4kCA1Ihp5mm/dS2k94QIDAQABo1EwTzAfBgNV 13 | HSMEGDAWgBTzut+pwwDfqmMYcI9KNWRDhxcIpTAJBgNVHRMEAjAAMAsGA1UdDwQE 14 | AwIE8DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAKVu 15 | vVpu5nPGAGn1SX4FQUcbn9Z5wgBkjnZxfJHJQX4sYIRlcirZviPHCZGPWex4VHC+ 16 | lFMm+70YEN2uoe5jGrdgcugzx2Amc7/mLrsvvpMsaS0PlxNMcqhdM1WHbGjjdNln 17 | XICVITSKnB1fSGH6uo9CMCWw5kgPS9o4QWrLLkxnds3hoz7gVEUyi/6V65mcfFNA 18 | lof9iKcK9JsSHdBs35vpv7UKLX+96RM7Nm2Mu0yue5JiS79/zuMA/Kryxot4jv5z 19 | ecdWFl0eIyQBZmBzMw2zPUqkxEnXLiKjV8jutEg/4qovTOB6YiA41qbARXdzNA2V 20 | FYuchcTcWmnmVVRFyyU= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /tests/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDP2y+NV1/mOKFQ 3 | G2h93zhcp1vx0QdBEl8M0pongCttqowcB2eqL99a2WoQQyfyDB5nv7iGyEQJjmaf 4 | HgeHLsgBt48OxNNtKbTV0KGGXEW6931C6/O+upKNKr6/gmVq76lAiFTPNXXZ0Xls 5 | W+sisZbEJmn5buUsNkjo93pK77Jkh6LCtRo8zN1R481/aFkj8i9YJQwIjKiqusod 6 | 0eg+69AJbXsjLGycVAk1uY35G2kFCKWG3C8FuCAIFaEfER2KoFpkcPDpmX5tTt6P 7 | HWa70D3nClD1IIvZxGQCXPYsM7LDOetMDAZy+q8UrGVX7cexoPuttriQIDUiGnma 8 | b91LaT3hAgMBAAECggEBAJABfn+BQorBP1m9s3ZJmcXvmW7+7/SwYrQCkRS+4te2 9 | 6h1dMAAj7K4HpUkhDeLPbJ1aoeCXjTPFuemRp4uL6Lvvzahgy059L7FXOyFYemMf 10 | pmQgDx5cKr6tF7yc/eDJrExuZ7urgTvouiRNxqmhuh+psZBDuXkZHwhwtQSH7uNg 11 | KBDKu0qWO73vFLcLckdGEU3+H9oIWs5xcvvOkWzyvHbRGFJSihgcRpPPHodF5xB9 12 | T/gZIoJHMmCbUMlWaSasUyNXTuvCnkvBDol8vXrMJCVzKZj9GpPDcIFdc08GSn4I 13 | pTdSNwzUcHbdERzdVU28Xt+t6W5rvp/4FWrssi4IzkUCgYEA//ZcEcBguRD4OFrx 14 | 6wbSjzCcUW1NWhzA8uTOORZi4SvndcH1cU4S2wznuHNubU1XlrGwJX6PUGebmY/l 15 | 53B5PJvStbVtZCVIxllR+ZVzRuL8wLodRHzlYH8GOzHwoa4ivSupkzl72ij1u/tI 16 | NMLGfYEKVdNd8zXIESUY88NszvsCgYEAz+MDp3xOhFaCe+CPv80A592cJcfzc8Al 17 | +rahEOu+VdN2QBZf86PIf2Bfv/t0QvnRvs1z648TuH6h83YSggOAbmfHyd789jkq 18 | UWlktIaXbVn+VaHmPTcBWTg3ZTlvG+fiFCbZXiYhm+UUf1MDqZHdiifAoyVIjV/Z 19 | YhCNJo3q39MCgYEAknrpK5t9fstwUcfyA/9OhnVaL9suVjB4V0iLn+3ovlXCywgp 20 | ryLv9X3IKi2c9144jtu3I23vFCOGz3WjKzSZnQ7LogNmy9XudNxu5jcZ1mpWHPEl 21 | iKk1F2j6Juwoek5OQRX4oHFYKHwiTOa75r3Em9Q6Fu20KVgQ24bwZafj3/sCgYAy 22 | k0AoVw2jFIjaKl/Ogclen4OFjYek+XJD9Hpq62964d866Dafx5DXrFKfGkXGpZBp 23 | owI4pK5fjC9KU8dc6g0szwLEEgPowy+QbtuZL8VXTTWbD7A75E3nrs2LStXFLDzM 24 | OkdXqF801h6Oe1vAvUPwgItVJZTpEBCK0wwD/TLPEQKBgQDRkhlTtAoHW7W6STd0 25 | A/OWc0dxhzMurpxg0bLgCqUjw1ESGrSCGhffFn0IWa8sv19VWsZuBhTgjNatZsYB 26 | AhDs/6OosT/3nJoh2/t0hYDj1FBI0lPXWYD4pesuZ5yIMrmSaAOtIzp4BGY7ui8N 27 | wOqcq/jdiHj/MKEdqOXy3YAJrA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use deno_tunnel::Authentication; 2 | use deno_tunnel::CLOSE_GENERIC; 3 | use deno_tunnel::CLOSE_MIGRATE; 4 | use deno_tunnel::CLOSE_NOT_FOUND; 5 | use deno_tunnel::CLOSE_PROTOCOL; 6 | use deno_tunnel::CLOSE_UNAUTHORIZED; 7 | use deno_tunnel::ControlMessage; 8 | use deno_tunnel::Error; 9 | use deno_tunnel::Event; 10 | use deno_tunnel::Metadata; 11 | use deno_tunnel::StreamHeader; 12 | use deno_tunnel::TunnelConnection; 13 | use deno_tunnel::VERSION; 14 | use deno_tunnel::read_message; 15 | use deno_tunnel::write_message; 16 | use std::collections::HashMap; 17 | use std::io::Cursor; 18 | use std::net::SocketAddr; 19 | use std::sync::Arc; 20 | use std::sync::Mutex; 21 | use std::time::Duration; 22 | use tokio::io::AsyncReadExt; 23 | use tokio::io::AsyncWriteExt; 24 | use tokio::sync::Mutex as AsyncMutex; 25 | 26 | struct Server { 27 | events: ( 28 | tokio::sync::mpsc::Sender, 29 | AsyncMutex>, 30 | ), 31 | endpoint: quinn::Endpoint, 32 | qaddr: SocketAddr, 33 | #[allow(clippy::type_complexity)] 34 | connections: Arc< 35 | Mutex< 36 | HashMap< 37 | String, 38 | (quinn::Connection, tokio::sync::mpsc::Sender), 39 | >, 40 | >, 41 | >, 42 | } 43 | 44 | impl Server { 45 | async fn stream( 46 | &self, 47 | routing_id: &str, 48 | ) -> tokio::io::Join { 49 | let c = self 50 | .connections 51 | .lock() 52 | .unwrap() 53 | .get(routing_id) 54 | .unwrap() 55 | .0 56 | .clone(); 57 | let mut s = c.open_bi().await.unwrap(); 58 | write_message( 59 | &mut s.0, 60 | StreamHeader::Stream { 61 | local_addr: self.qaddr, 62 | remote_addr: self.qaddr, 63 | }, 64 | ) 65 | .await 66 | .unwrap(); 67 | tokio::io::join(s.1, s.0) 68 | } 69 | 70 | async fn migrate(&self, routing_id: &str) { 71 | let (c, tx) = self 72 | .connections 73 | .lock() 74 | .unwrap() 75 | .get(routing_id) 76 | .unwrap() 77 | .clone(); 78 | tokio::spawn(async move { 79 | tokio::time::sleep(Duration::from_secs(2)).await; 80 | c.close(CLOSE_MIGRATE.into(), b"migrate pls"); 81 | }); 82 | let _ = tx.send(ControlMessage::Migrate {}).await; 83 | } 84 | 85 | async fn disconnect(&self, routing_id: &str) { 86 | let c = self 87 | .connections 88 | .lock() 89 | .unwrap() 90 | .get(routing_id) 91 | .unwrap() 92 | .0 93 | .clone(); 94 | c.close(CLOSE_GENERIC.into(), b"explicit close"); 95 | } 96 | 97 | async fn accept_agent( 98 | &self, 99 | routing_id: &str, 100 | ) -> tokio::io::Join { 101 | let c = self 102 | .connections 103 | .lock() 104 | .unwrap() 105 | .get(routing_id) 106 | .unwrap() 107 | .0 108 | .clone(); 109 | 110 | let mut s = c.accept_bi().await.unwrap(); 111 | 112 | let StreamHeader::Agent {} = read_message(&mut s.1).await.unwrap() else { 113 | panic!() 114 | }; 115 | 116 | tokio::io::join(s.1, s.0) 117 | } 118 | 119 | fn on_event(self: &Arc) -> impl Fn(Event) + use<> { 120 | let this = self.clone(); 121 | move |event: Event| { 122 | let this = this.clone(); 123 | tokio::spawn(async move { this.events.0.send(event).await }); 124 | } 125 | } 126 | 127 | async fn next_event(&self) -> Option { 128 | self.events.1.lock().await.recv().await 129 | } 130 | 131 | fn tls_config(&self) -> quinn::rustls::ClientConfig { 132 | let mut reader = Cursor::new(include_bytes!("./RootCA.crt")); 133 | let certs = rustls_pemfile::certs(&mut reader) 134 | .filter_map(|v| v.ok()) 135 | .collect::>(); 136 | let mut root_store = quinn::rustls::RootCertStore::empty(); 137 | root_store.add_parsable_certificates(certs); 138 | quinn::rustls::ClientConfig::builder() 139 | .with_root_certificates(root_store) 140 | .with_no_client_auth() 141 | } 142 | } 143 | 144 | impl Drop for Server { 145 | fn drop(&mut self) { 146 | self.endpoint.close(0u32.into(), b"ended"); 147 | } 148 | } 149 | 150 | async fn server() -> Arc { 151 | let mut reader = Cursor::new(include_bytes!("./localhost.crt")); 152 | let cert_chain = rustls_pemfile::certs(&mut reader) 153 | .filter_map(|v| v.ok()) 154 | .collect::>(); 155 | let mut reader = Cursor::new(include_bytes!("./localhost.key")); 156 | let key_der = rustls_pemfile::private_key(&mut reader).unwrap().unwrap(); 157 | let mut crypto = quinn::rustls::server::ServerConfig::builder() 158 | .with_no_client_auth() 159 | .with_single_cert(cert_chain, key_der) 160 | .unwrap(); 161 | crypto.alpn_protocols = vec!["🦕🕳️".into()]; 162 | 163 | let mut transport_config = quinn::TransportConfig::default(); 164 | transport_config 165 | .max_idle_timeout(Some(Duration::from_secs(15).try_into().unwrap())); 166 | 167 | let crypto = Arc::new( 168 | quinn::crypto::rustls::QuicServerConfig::try_from(crypto).unwrap(), 169 | ); 170 | let mut config = quinn::ServerConfig::with_crypto(crypto); 171 | config.transport_config(Arc::new(transport_config)); 172 | 173 | let endpoint = 174 | quinn::Endpoint::server(config, "[::]:0".parse().unwrap()).unwrap(); 175 | 176 | let qaddr = SocketAddr::new( 177 | "::1".parse().unwrap(), 178 | endpoint.local_addr().unwrap().port(), 179 | ); 180 | 181 | let connections = Arc::new(Mutex::new(HashMap::new())); 182 | 183 | tokio::spawn({ 184 | let endpoint = endpoint.clone(); 185 | let connections = connections.clone(); 186 | async move { 187 | while let Some(incoming) = endpoint.accept().await { 188 | let connections = connections.clone(); 189 | tokio::spawn(async move { 190 | let conn = incoming.await.unwrap(); 191 | 192 | let mut control = conn.accept_bi().await.unwrap(); 193 | 194 | let version = control.1.read_u32_le().await.unwrap(); 195 | if version != VERSION { 196 | conn.close(CLOSE_PROTOCOL.into(), b"invalid version"); 197 | return; 198 | } 199 | control.0.write_u32_le(version).await.unwrap(); 200 | 201 | let StreamHeader::Control { metadata } = 202 | read_message(&mut control.1).await.unwrap() 203 | else { 204 | conn.close(CLOSE_PROTOCOL.into(), b"unexpected header"); 205 | return; 206 | }; 207 | 208 | let routing_id = match read_message(&mut control.1).await.unwrap() { 209 | ControlMessage::AuthenticateApp { org, app, token } => { 210 | if token == "invalid" { 211 | conn.close(CLOSE_UNAUTHORIZED.into(), b"invalid token"); 212 | return; 213 | } 214 | if org == "unknown" || app == "unknown" { 215 | conn.close(CLOSE_NOT_FOUND.into(), b"unknown app or org"); 216 | return; 217 | } 218 | format!("{org}-{app}") 219 | } 220 | ControlMessage::AuthenticateCluster { token } => { 221 | if token == "invalid" { 222 | conn.close(CLOSE_UNAUTHORIZED.into(), b"invalid token"); 223 | return; 224 | } 225 | if token == "unknown" { 226 | conn.close(CLOSE_NOT_FOUND.into(), b"unknown cluster"); 227 | return; 228 | } 229 | token 230 | } 231 | _ => { 232 | conn.close(CLOSE_PROTOCOL.into(), b"unexpected message"); 233 | return; 234 | } 235 | }; 236 | 237 | write_message( 238 | &mut control.0, 239 | ControlMessage::Authenticated { 240 | metadata: metadata.unwrap_or_default(), 241 | addr: qaddr, 242 | hostnames: vec![format!("{routing_id}.localhost")], 243 | env: Default::default(), 244 | }, 245 | ) 246 | .await 247 | .unwrap(); 248 | 249 | let (tx, mut rx) = tokio::sync::mpsc::channel(1); 250 | 251 | connections 252 | .lock() 253 | .unwrap() 254 | .insert(routing_id, (conn.clone(), tx)); 255 | 256 | if let ControlMessage::Listening {} = 257 | read_message(&mut control.1).await.unwrap() 258 | { 259 | write_message(&mut control.0, ControlMessage::Routed {}) 260 | .await 261 | .unwrap(); 262 | } 263 | 264 | while let Some(msg) = rx.recv().await { 265 | write_message(&mut control.0, msg).await.unwrap(); 266 | } 267 | }); 268 | } 269 | } 270 | }); 271 | 272 | let (tx, rx) = tokio::sync::mpsc::channel(1); 273 | Arc::new(Server { 274 | events: (tx, AsyncMutex::new(rx)), 275 | endpoint, 276 | qaddr, 277 | connections, 278 | }) 279 | } 280 | 281 | #[tokio::test] 282 | async fn test_basic() { 283 | let server = server().await; 284 | 285 | let mut metadata = HashMap::new(); 286 | metadata.insert("a".into(), "1".into()); 287 | metadata.insert("b".into(), "2".into()); 288 | 289 | let c = TunnelConnection::connect( 290 | server.qaddr, 291 | "localhost".into(), 292 | server.tls_config(), 293 | Authentication::App { 294 | token: "1234".into(), 295 | org: "org".into(), 296 | app: "app".into(), 297 | }, 298 | metadata.clone(), 299 | server.on_event(), 300 | ) 301 | .await 302 | .unwrap(); 303 | 304 | assert_eq!( 305 | c.metadata().unwrap(), 306 | Metadata { 307 | env: Default::default(), 308 | hostnames: vec!["org-app.localhost".into()], 309 | metadata, 310 | } 311 | ); 312 | 313 | let server2 = server.clone(); 314 | let t = tokio::spawn(async move { 315 | let mut stream = server2.stream("org-app").await; 316 | 317 | stream.write_all(b"hello!").await.unwrap(); 318 | 319 | let mut data = [0; 32]; 320 | let n = stream.read(&mut data).await.unwrap(); 321 | 322 | assert_eq!(&data[0..n], b"meow back"); 323 | }); 324 | 325 | let q = tokio::spawn(async move { 326 | let (mut stream, ..) = c.accept().await.unwrap(); 327 | 328 | let mut data = [0; 32]; 329 | let n = stream.read(&mut data).await.unwrap(); 330 | assert_eq!(&data[0..n], b"hello!"); 331 | 332 | stream.write_all(b"meow back").await.unwrap(); 333 | }); 334 | 335 | assert!(matches!( 336 | server.next_event().await.unwrap(), 337 | Event::Routed(_) 338 | )); 339 | 340 | t.await.unwrap(); 341 | q.await.unwrap(); 342 | } 343 | 344 | #[tokio::test] 345 | async fn test_unauthorized() { 346 | let server = server().await; 347 | 348 | let r = TunnelConnection::connect( 349 | server.qaddr, 350 | "localhost".into(), 351 | server.tls_config(), 352 | Authentication::App { 353 | token: "invalid".into(), 354 | org: "org".into(), 355 | app: "app".into(), 356 | }, 357 | Default::default(), 358 | server.on_event(), 359 | ) 360 | .await; 361 | 362 | assert!(matches!(r, Err(Error::Unauthorized))); 363 | } 364 | 365 | #[tokio::test] 366 | async fn test_not_found() { 367 | let server = server().await; 368 | 369 | let r = TunnelConnection::connect( 370 | server.qaddr, 371 | "localhost".into(), 372 | server.tls_config(), 373 | Authentication::App { 374 | token: "1234".into(), 375 | org: "unknown".into(), 376 | app: "app".into(), 377 | }, 378 | Default::default(), 379 | server.on_event(), 380 | ) 381 | .await; 382 | 383 | assert!(matches!(r, Err(Error::NotFound))); 384 | } 385 | 386 | #[tokio::test] 387 | async fn test_disconnect() { 388 | let server = server().await; 389 | 390 | let conn = TunnelConnection::connect( 391 | server.qaddr, 392 | "localhost".into(), 393 | server.tls_config(), 394 | Authentication::App { 395 | token: "1234".into(), 396 | org: "org".into(), 397 | app: "app".into(), 398 | }, 399 | Default::default(), 400 | server.on_event(), 401 | ) 402 | .await 403 | .unwrap(); 404 | 405 | tokio::spawn(async move { while conn.accept().await.is_ok() {} }); 406 | 407 | assert!(matches!( 408 | server.next_event().await.unwrap(), 409 | Event::Routed(_) 410 | )); 411 | 412 | server.disconnect("org-app").await; 413 | 414 | let duration = Duration::from_secs(3); 415 | assert!(matches!( 416 | server.next_event().await.unwrap(), 417 | Event::Reconnect(d, Some(_)) if d == duration 418 | )); 419 | 420 | assert!(matches!( 421 | server.next_event().await.unwrap(), 422 | Event::Routed(_) 423 | )); 424 | } 425 | 426 | #[tokio::test] 427 | async fn test_migrate() { 428 | let server = server().await; 429 | 430 | let conn = TunnelConnection::connect( 431 | server.qaddr, 432 | "localhost".into(), 433 | server.tls_config(), 434 | Authentication::App { 435 | token: "1234".into(), 436 | org: "org".into(), 437 | app: "app".into(), 438 | }, 439 | Default::default(), 440 | server.on_event(), 441 | ) 442 | .await 443 | .unwrap(); 444 | 445 | tokio::spawn(async move { while conn.accept().await.is_ok() {} }); 446 | 447 | assert!(matches!( 448 | server.next_event().await.unwrap(), 449 | Event::Routed(_) 450 | )); 451 | 452 | server.migrate("org-app").await; 453 | 454 | assert!(matches!( 455 | server.next_event().await.unwrap(), 456 | Event::Routed(_) 457 | )); 458 | } 459 | 460 | #[tokio::test] 461 | async fn test_agent() { 462 | let server = server().await; 463 | 464 | let conn = TunnelConnection::connect( 465 | server.qaddr, 466 | "localhost".into(), 467 | server.tls_config(), 468 | Authentication::App { 469 | token: "1234".into(), 470 | org: "org".into(), 471 | app: "app".into(), 472 | }, 473 | Default::default(), 474 | server.on_event(), 475 | ) 476 | .await 477 | .unwrap(); 478 | 479 | let t = tokio::spawn(async move { 480 | let mut stream = server.accept_agent("org-app").await; 481 | let mut data = [0; 32]; 482 | let n = stream.read(&mut data).await.unwrap(); 483 | assert_eq!(&data[0..n], b"hello agent!"); 484 | }); 485 | 486 | let q = tokio::spawn(async move { 487 | let mut stream = conn.create_agent_stream().await.unwrap(); 488 | stream.write_all(b"hello agent!").await.unwrap(); 489 | }); 490 | 491 | t.await.unwrap(); 492 | q.await.unwrap(); 493 | } 494 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 the Deno authors. MIT license. 2 | 3 | use std::collections::HashMap; 4 | use std::net::IpAddr; 5 | use std::net::SocketAddr; 6 | use std::net::UdpSocket; 7 | use std::sync::Arc; 8 | use std::sync::atomic::AtomicBool; 9 | use std::sync::atomic::Ordering; 10 | use std::time::Duration; 11 | use tokio::sync::Mutex; 12 | 13 | use quinn::crypto::rustls::QuicClientConfig; 14 | use tokio::io::AsyncRead; 15 | use tokio::io::AsyncWrite; 16 | 17 | pub use quinn; 18 | 19 | pub const VERSION: u32 = 2; 20 | 21 | pub const CLOSE_GENERIC: u32 = 0; 22 | pub const CLOSE_PROTOCOL: u32 = 1; 23 | pub const CLOSE_UNAUTHORIZED: u32 = 2; 24 | pub const CLOSE_NOT_FOUND: u32 = 3; 25 | pub const CLOSE_MIGRATE: u32 = 4; 26 | 27 | #[derive(thiserror::Error, Debug, Clone)] 28 | pub enum Error { 29 | #[error(transparent)] 30 | StdIo(Arc), 31 | #[error(transparent)] 32 | SerdeJson(Arc), 33 | #[error(transparent)] 34 | QuinnConnect(#[from] quinn::ConnectError), 35 | #[error(transparent)] 36 | QuinnConnection(quinn::ConnectionError), 37 | #[error(transparent)] 38 | QuinnRead(quinn::ReadError), 39 | #[error(transparent)] 40 | QuinnReadExact(quinn::ReadExactError), 41 | #[error(transparent)] 42 | QuinnWrite(quinn::WriteError), 43 | 44 | #[error("Unsupported version")] 45 | UnsupportedVersion, 46 | #[error("Unexpected header")] 47 | UnexpectedHeader, 48 | #[error("Protocol violation")] 49 | Protocol, 50 | #[error("Unauthorized")] 51 | Unauthorized, 52 | #[error("Not found")] 53 | NotFound, 54 | #[error("Migrate")] 55 | Migrate, 56 | #[error("Invalid message length")] 57 | MessageLengthInvalid, 58 | } 59 | 60 | impl From for Error { 61 | fn from(value: quinn::ConnectionError) -> Self { 62 | match value { 63 | quinn::ConnectionError::ApplicationClosed(ref e) => { 64 | match e.error_code.into_inner() as u32 { 65 | CLOSE_PROTOCOL => Self::Protocol, 66 | CLOSE_UNAUTHORIZED => Self::Unauthorized, 67 | CLOSE_NOT_FOUND => Self::NotFound, 68 | CLOSE_MIGRATE => Self::Migrate, 69 | _ => Self::QuinnConnection(value), 70 | } 71 | } 72 | _ => Self::QuinnConnection(value), 73 | } 74 | } 75 | } 76 | 77 | impl From for Error { 78 | fn from(value: quinn::ReadExactError) -> Self { 79 | match value { 80 | quinn::ReadExactError::FinishedEarly(..) => Self::QuinnReadExact(value), 81 | quinn::ReadExactError::ReadError(e) => Self::from(e), 82 | } 83 | } 84 | } 85 | 86 | impl From for Error { 87 | fn from(value: quinn::ReadError) -> Self { 88 | if let quinn::ReadError::ConnectionLost(e) = value { 89 | Self::from(e) 90 | } else { 91 | Self::QuinnRead(value) 92 | } 93 | } 94 | } 95 | 96 | impl From for Error { 97 | fn from(value: quinn::WriteError) -> Self { 98 | if let quinn::WriteError::ConnectionLost(e) = value { 99 | Self::from(e) 100 | } else { 101 | Self::QuinnWrite(value) 102 | } 103 | } 104 | } 105 | 106 | impl From for Error { 107 | fn from(value: std::io::Error) -> Self { 108 | Self::StdIo(Arc::new(value)) 109 | } 110 | } 111 | 112 | impl From for Error { 113 | fn from(value: serde_json::Error) -> Self { 114 | Self::SerdeJson(Arc::new(value)) 115 | } 116 | } 117 | 118 | /// Essentially a SocketAddr, except we prefer a human 119 | /// readable hostname to identify the remote endpoint. 120 | #[derive(Debug, Clone)] 121 | pub struct TunnelAddr { 122 | socket: SocketAddr, 123 | hostname: Option, 124 | } 125 | 126 | impl TunnelAddr { 127 | pub fn hostname(&self) -> String { 128 | self 129 | .hostname 130 | .clone() 131 | .unwrap_or_else(|| self.socket.ip().to_string()) 132 | } 133 | 134 | pub fn ip(&self) -> IpAddr { 135 | self.socket.ip() 136 | } 137 | 138 | pub fn port(&self) -> u16 { 139 | self.socket.port() 140 | } 141 | } 142 | 143 | impl From for SocketAddr { 144 | fn from(addr: TunnelAddr) -> Self { 145 | addr.socket 146 | } 147 | } 148 | 149 | /// Data obtained from the server handshake 150 | #[derive(Debug, Clone, PartialEq)] 151 | pub struct Metadata { 152 | pub hostnames: Vec, 153 | pub env: HashMap, 154 | pub metadata: HashMap, 155 | } 156 | 157 | /// Server event 158 | #[derive(Debug, Clone)] 159 | #[non_exhaustive] 160 | pub enum Event { 161 | /// All endpoints are routed 162 | Routed(TunnelAddr), 163 | /// Client will reconnect after the given duration 164 | Reconnect(Duration, Option), 165 | } 166 | 167 | enum InternalEvent { 168 | Routed(TunnelAddr), 169 | Migrate, 170 | } 171 | 172 | #[derive(Debug, Clone)] 173 | pub enum Authentication { 174 | App { 175 | token: String, 176 | org: String, 177 | app: String, 178 | }, 179 | Cluster { 180 | token: String, 181 | }, 182 | } 183 | 184 | #[derive(Debug)] 185 | struct InnerConnection { 186 | connection: quinn::Connection, 187 | local_addr: TunnelAddr, 188 | metadata: Metadata, 189 | sent_listening: AtomicBool, 190 | control_tx: Mutex, 191 | } 192 | 193 | impl InnerConnection { 194 | async fn connect( 195 | outer: TunnelConnection, 196 | ) -> Result<(Self, tokio::sync::mpsc::Receiver), Error> { 197 | let connecting = outer 198 | .endpoint 199 | .connect(outer.connect_info.addr, &outer.connect_info.server_name)?; 200 | 201 | let connection = connecting.await?; 202 | 203 | let (mut control_tx, mut control_rx) = connection.open_bi().await?; 204 | write_u32_le(&mut control_tx, VERSION).await?; 205 | if read_u32_le(&mut control_rx).await? != VERSION { 206 | return Err(Error::UnsupportedVersion); 207 | } 208 | 209 | write_message( 210 | &mut control_tx, 211 | StreamHeader::Control { 212 | metadata: Some(outer.connect_info.metadata.clone()), 213 | }, 214 | ) 215 | .await?; 216 | write_message( 217 | &mut control_tx, 218 | match outer.connect_info.authentication.clone() { 219 | Authentication::App { token, org, app } => { 220 | ControlMessage::AuthenticateApp { token, org, app } 221 | } 222 | Authentication::Cluster { token } => { 223 | ControlMessage::AuthenticateCluster { token } 224 | } 225 | }, 226 | ) 227 | .await?; 228 | 229 | let ControlMessage::Authenticated { 230 | addr, 231 | hostnames, 232 | env, 233 | metadata, 234 | } = read_message(&mut control_rx).await? 235 | else { 236 | return Err(Error::UnexpectedHeader); 237 | }; 238 | 239 | let local_addr = TunnelAddr { 240 | socket: addr, 241 | hostname: hostnames.first().cloned(), 242 | }; 243 | 244 | let (event_tx, event_rx) = tokio::sync::mpsc::channel(1); 245 | tokio::spawn({ 246 | let local_addr = local_addr.clone(); 247 | async move { 248 | while let Ok(message) = read_message(&mut control_rx).await { 249 | tracing::trace!(?message); 250 | let event = match message { 251 | ControlMessage::Routed {} => { 252 | InternalEvent::Routed(local_addr.clone()) 253 | } 254 | ControlMessage::Migrate {} => InternalEvent::Migrate, 255 | _ => { 256 | continue; 257 | } 258 | }; 259 | if event_tx.send(event).await.is_err() { 260 | break; 261 | } 262 | } 263 | } 264 | }); 265 | 266 | let metadata = Metadata { 267 | hostnames, 268 | env, 269 | metadata, 270 | }; 271 | 272 | Ok(( 273 | Self { 274 | connection, 275 | local_addr, 276 | metadata, 277 | sent_listening: AtomicBool::new(false), 278 | control_tx: Mutex::new(control_tx), 279 | }, 280 | event_rx, 281 | )) 282 | } 283 | 284 | async fn listening(&self) { 285 | if self.sent_listening.swap(true, Ordering::SeqCst) { 286 | return; 287 | } 288 | let mut stream = self.control_tx.lock().await; 289 | let _ = write_message(&mut stream, ControlMessage::Listening {}).await; 290 | } 291 | } 292 | 293 | #[derive(Debug)] 294 | struct ConnectInfo { 295 | authentication: Authentication, 296 | metadata: HashMap, 297 | addr: SocketAddr, 298 | server_name: String, 299 | } 300 | 301 | #[derive(Debug, Clone)] 302 | pub struct TunnelConnection { 303 | endpoint: quinn::Endpoint, 304 | connect_info: Arc, 305 | active: 306 | tokio::sync::watch::Sender, Error>>>, 307 | } 308 | 309 | impl TunnelConnection { 310 | pub async fn connect( 311 | addr: std::net::SocketAddr, 312 | server_name: String, 313 | tls_config: quinn::rustls::ClientConfig, 314 | authentication: Authentication, 315 | metadata: HashMap, 316 | on_event: impl Fn(Event) + Send + 'static, 317 | ) -> Result { 318 | Self::connect_with( 319 | UdpSocket::bind(("::", 0))?, 320 | addr, 321 | server_name, 322 | tls_config, 323 | authentication, 324 | metadata, 325 | on_event, 326 | ) 327 | .await 328 | } 329 | 330 | pub async fn connect_with( 331 | socket: UdpSocket, 332 | addr: std::net::SocketAddr, 333 | server_name: String, 334 | mut tls_config: quinn::rustls::ClientConfig, 335 | authentication: Authentication, 336 | metadata: HashMap, 337 | on_event: impl Fn(Event) + Send + 'static, 338 | ) -> Result { 339 | let config = quinn::EndpointConfig::default(); 340 | let mut endpoint = quinn::Endpoint::new( 341 | config, 342 | None, 343 | socket, 344 | quinn::default_runtime().unwrap(), 345 | )?; 346 | 347 | tls_config.alpn_protocols = vec!["🦕🕳️".into()]; 348 | tls_config.enable_early_data = true; 349 | 350 | let mut transport_config = quinn::TransportConfig::default(); 351 | transport_config.keep_alive_interval(Some(Duration::from_secs(5))); 352 | transport_config 353 | .max_idle_timeout(Some(Duration::from_secs(15).try_into().unwrap())); 354 | 355 | let client_config = 356 | QuicClientConfig::try_from(tls_config).expect("TLS13 supported"); 357 | let mut client_config = quinn::ClientConfig::new(Arc::new(client_config)); 358 | client_config.transport_config(Arc::new(transport_config)); 359 | 360 | endpoint.set_default_client_config(client_config); 361 | 362 | let this = Self { 363 | endpoint, 364 | connect_info: Arc::new(ConnectInfo { 365 | authentication, 366 | metadata, 367 | addr, 368 | server_name, 369 | }), 370 | active: tokio::sync::watch::channel(None).0, 371 | }; 372 | 373 | tokio::spawn({ 374 | let this = this.clone(); 375 | async move { 376 | let this2 = this.clone(); 377 | let r = async move { 378 | let mut retries = 0; 379 | let mut reason = None; 380 | let mut watch = this.active.subscribe(); 381 | 382 | 'outer: loop { 383 | if matches!(this.active.borrow().as_ref(), Some(Err(_))) { 384 | break; 385 | } 386 | 387 | if retries > 0 { 388 | let d = Duration::from_secs((retries * 3).min(30)); 389 | on_event(Event::Reconnect(d, reason.take())); 390 | let s = tokio::time::sleep(d); 391 | tokio::pin!(s); 392 | loop { 393 | tokio::select! { 394 | _ = &mut s => break, 395 | _ = watch.changed() => { 396 | if matches!(watch.borrow().as_ref(), Some(Err(_))) { 397 | break 'outer; 398 | } 399 | } 400 | } 401 | } 402 | } 403 | 404 | let (inner, mut event_rx) = 405 | match InnerConnection::connect(this.clone()).await { 406 | Ok(r) => r, 407 | Err(e) => { 408 | tracing::debug!("connect: {e}"); 409 | if let Error::QuinnConnection(qe) = &e { 410 | if is_retry_error(qe) { 411 | reason = Some(e); 412 | retries += 1; 413 | continue; 414 | } else { 415 | return Err(e); 416 | } 417 | } else { 418 | return Err(e); 419 | } 420 | } 421 | }; 422 | 423 | let existing = this.active.borrow().clone(); 424 | if matches!(existing.as_ref(), Some(Err(_))) { 425 | inner.connection.close(0u32.into(), b""); 426 | break; 427 | } 428 | 429 | let c = inner.connection.clone(); 430 | this.active.send_replace(Some(Ok(Arc::new(inner)))); 431 | 432 | if let Some(Ok(existing)) = existing.as_ref() { 433 | existing.connection.close(CLOSE_MIGRATE.into(), b"migrated"); 434 | } 435 | 436 | retries = 0; 437 | 438 | let e = loop { 439 | tokio::select! { 440 | e = c.closed() => break e, 441 | Some(event) = event_rx.recv() => { 442 | match event { 443 | InternalEvent::Migrate => { 444 | retries = 0; 445 | continue 'outer; 446 | } 447 | InternalEvent::Routed(addr) => { 448 | on_event(Event::Routed(addr)); 449 | } 450 | } 451 | } 452 | _ = watch.changed() => { 453 | if matches!(this.active.borrow().as_ref(), Some(Err(_))) { 454 | break 'outer; 455 | } 456 | } 457 | } 458 | }; 459 | 460 | if is_retry_error(&e) { 461 | this.active.send_replace(None); 462 | reason = Some(e.into()); 463 | retries += 1; 464 | } else { 465 | return Err(e.into()); 466 | } 467 | } 468 | 469 | Ok(()) 470 | }; 471 | 472 | if let Err(e) = r.await { 473 | this2.active.send_replace(Some(Err(e))); 474 | } 475 | } 476 | }); 477 | 478 | this.active().await?; 479 | 480 | Ok(this) 481 | } 482 | } 483 | 484 | impl TunnelConnection { 485 | // compat method with other common connection types, keep signature the same 486 | pub fn local_addr(&self) -> Result { 487 | if let Some(Ok(inner)) = self.active.borrow().as_ref() { 488 | return Ok(inner.local_addr.clone()); 489 | } 490 | let socket = self.endpoint.local_addr()?; 491 | Ok(TunnelAddr { 492 | hostname: None, 493 | socket, 494 | }) 495 | } 496 | 497 | async fn active(&self) -> Result, Error> { 498 | // fast path, check without subscribing 499 | if let Some(inner) = self.active.borrow().as_ref() { 500 | match inner { 501 | Ok(inner) => match inner.connection.close_reason() { 502 | None => return Ok(inner.clone()), 503 | Some(e) => { 504 | if !is_retry_error(&e) { 505 | return Err(e.into()); 506 | } 507 | } 508 | }, 509 | Err(e) => { 510 | return Err(e.clone()); 511 | } 512 | } 513 | } 514 | 515 | let mut w = self.active.subscribe(); 516 | loop { 517 | let _ = w.changed().await; 518 | 519 | if let Some(inner) = w.borrow().as_ref() { 520 | match inner { 521 | Ok(inner) => match inner.connection.close_reason() { 522 | None => return Ok(inner.clone()), 523 | Some(e) => { 524 | if !is_retry_error(&e) { 525 | return Err(e.into()); 526 | } 527 | } 528 | }, 529 | Err(e) => { 530 | return Err(e.clone()); 531 | } 532 | } 533 | } 534 | } 535 | } 536 | 537 | // compat method with other common connection types, keep signature the same 538 | pub async fn accept( 539 | &self, 540 | ) -> Result<(TunnelStream, TunnelAddr), std::io::Error> { 541 | loop { 542 | let inner = self.active().await.map_err(std::io::Error::other)?; 543 | 544 | inner.listening().await; 545 | 546 | let (tx, mut rx) = match inner.connection.accept_bi().await { 547 | Ok(c) => c, 548 | Err(e) => { 549 | if is_retry_error(&e) { 550 | continue; 551 | } 552 | return Err(e.into()); 553 | } 554 | }; 555 | 556 | match read_message(&mut rx).await { 557 | Ok(StreamHeader::Stream { 558 | remote_addr, 559 | local_addr, 560 | }) => { 561 | return Ok(( 562 | TunnelStream { 563 | tx, 564 | rx, 565 | local_addr, 566 | remote_addr, 567 | }, 568 | TunnelAddr { 569 | hostname: None, 570 | socket: remote_addr, 571 | }, 572 | )); 573 | } 574 | Err(e) => { 575 | if let Error::QuinnConnection(qe) = e { 576 | if is_retry_error(&qe) { 577 | continue; 578 | } 579 | return Err(qe.into()); 580 | } 581 | return Err(std::io::Error::other(e)); 582 | } 583 | _ => { 584 | return Err(std::io::Error::other(Error::UnexpectedHeader)); 585 | } 586 | } 587 | } 588 | } 589 | 590 | pub fn metadata(&self) -> Option { 591 | self 592 | .active 593 | .borrow() 594 | .as_ref() 595 | .and_then(|b| b.as_ref().ok()) 596 | .map(|c| c.metadata.clone()) 597 | } 598 | 599 | pub async fn create_agent_stream(&self) -> Result { 600 | let ((mut tx, rx), remote_addr) = loop { 601 | let inner = self.active().await?; 602 | match inner.connection.open_bi().await { 603 | Ok(c) => break (c, inner.connection.remote_address()), 604 | Err(e) => { 605 | if is_retry_error(&e) { 606 | continue; 607 | } 608 | return Err(e.into()); 609 | } 610 | }; 611 | }; 612 | 613 | write_message(&mut tx, StreamHeader::Agent {}).await?; 614 | 615 | Ok(TunnelStream { 616 | tx, 617 | rx, 618 | local_addr: self.endpoint.local_addr()?, 619 | remote_addr, 620 | }) 621 | } 622 | 623 | pub async fn close(&self, code: impl Into, reason: &[u8]) { 624 | self.active.send_replace(Some(Err(Error::QuinnConnection( 625 | quinn::ConnectionError::LocallyClosed, 626 | )))); 627 | self.endpoint.close(code.into(), reason); 628 | self.endpoint.wait_idle().await; 629 | } 630 | } 631 | 632 | fn is_retry_code(c: u64) -> bool { 633 | !matches!( 634 | c as u32, 635 | CLOSE_PROTOCOL | CLOSE_NOT_FOUND | CLOSE_UNAUTHORIZED 636 | ) 637 | } 638 | 639 | fn is_retry_error(e: &quinn::ConnectionError) -> bool { 640 | match e { 641 | quinn::ConnectionError::ApplicationClosed(c) => { 642 | is_retry_code(c.error_code.into_inner()) 643 | } 644 | quinn::ConnectionError::ConnectionClosed(_) 645 | | quinn::ConnectionError::TimedOut 646 | | quinn::ConnectionError::Reset 647 | | quinn::ConnectionError::LocallyClosed => true, 648 | quinn::ConnectionError::VersionMismatch 649 | | quinn::ConnectionError::CidsExhausted 650 | | quinn::ConnectionError::TransportError(_) => false, 651 | } 652 | } 653 | 654 | #[derive(Debug)] 655 | #[pin_project::pin_project] 656 | pub struct TunnelStream { 657 | #[pin] 658 | tx: quinn::SendStream, 659 | #[pin] 660 | rx: quinn::RecvStream, 661 | 662 | local_addr: SocketAddr, 663 | remote_addr: SocketAddr, 664 | } 665 | 666 | impl TunnelStream { 667 | pub fn local_addr(&self) -> Result { 668 | Ok(self.local_addr) 669 | } 670 | 671 | pub fn peer_addr(&self) -> Result { 672 | Ok(self.remote_addr) 673 | } 674 | 675 | pub fn into_split(self) -> (OwnedReadHalf, OwnedWriteHalf) { 676 | ( 677 | OwnedReadHalf { 678 | rx: self.rx, 679 | local_addr: self.local_addr, 680 | remote_addr: self.remote_addr, 681 | }, 682 | OwnedWriteHalf { tx: self.tx }, 683 | ) 684 | } 685 | } 686 | 687 | impl AsyncRead for TunnelStream { 688 | fn poll_read( 689 | self: std::pin::Pin<&mut Self>, 690 | cx: &mut std::task::Context<'_>, 691 | buf: &mut tokio::io::ReadBuf<'_>, 692 | ) -> std::task::Poll> { 693 | self.project().rx.poll_read(cx, buf) 694 | } 695 | } 696 | 697 | impl AsyncWrite for TunnelStream { 698 | fn poll_write( 699 | self: std::pin::Pin<&mut Self>, 700 | cx: &mut std::task::Context<'_>, 701 | buf: &[u8], 702 | ) -> std::task::Poll> { 703 | AsyncWrite::poll_write(self.project().tx, cx, buf) 704 | } 705 | 706 | fn poll_flush( 707 | self: std::pin::Pin<&mut Self>, 708 | cx: &mut std::task::Context<'_>, 709 | ) -> std::task::Poll> { 710 | self.project().tx.poll_flush(cx) 711 | } 712 | 713 | fn poll_shutdown( 714 | self: std::pin::Pin<&mut Self>, 715 | cx: &mut std::task::Context<'_>, 716 | ) -> std::task::Poll> { 717 | self.project().tx.poll_shutdown(cx) 718 | } 719 | } 720 | 721 | /// The readable half returned from `into_split` 722 | #[pin_project::pin_project] 723 | pub struct OwnedReadHalf { 724 | #[pin] 725 | rx: quinn::RecvStream, 726 | 727 | local_addr: SocketAddr, 728 | remote_addr: SocketAddr, 729 | } 730 | 731 | impl OwnedReadHalf { 732 | /// Whether this OwnedReadHalf and an OwnedWriteHalf came from the same TunnelStream 733 | pub fn is_pair_of(&self, write_half: &OwnedWriteHalf) -> bool { 734 | self.rx.id() == write_half.tx.id() 735 | } 736 | 737 | /// Re-join a split OwnedReadHalf and OwnedWriteHalf. 738 | /// 739 | /// # Panics 740 | /// 741 | /// If this OwnedReadHalf and the given OwnedWriteHalf do not originate 742 | /// from the same split operation this method will panic. This can be 743 | /// checked ahead of time by calling `is_pair_of()`. 744 | pub fn unsplit(self, write_half: OwnedWriteHalf) -> TunnelStream { 745 | if self.is_pair_of(&write_half) { 746 | TunnelStream { 747 | tx: write_half.tx, 748 | rx: self.rx, 749 | local_addr: self.local_addr, 750 | remote_addr: self.remote_addr, 751 | } 752 | } else { 753 | panic!("Unrelated `OwnedWriteHalf` passed to `OwnedReadHalf::unsplit`"); 754 | } 755 | } 756 | } 757 | 758 | impl AsyncRead for OwnedReadHalf { 759 | fn poll_read( 760 | self: std::pin::Pin<&mut Self>, 761 | cx: &mut std::task::Context<'_>, 762 | buf: &mut tokio::io::ReadBuf<'_>, 763 | ) -> std::task::Poll> { 764 | self.project().rx.poll_read(cx, buf) 765 | } 766 | } 767 | 768 | /// The writable half returned from `into_split` 769 | #[pin_project::pin_project] 770 | pub struct OwnedWriteHalf { 771 | #[pin] 772 | tx: quinn::SendStream, 773 | } 774 | 775 | impl AsyncWrite for OwnedWriteHalf { 776 | fn poll_write( 777 | self: std::pin::Pin<&mut Self>, 778 | cx: &mut std::task::Context<'_>, 779 | buf: &[u8], 780 | ) -> std::task::Poll> { 781 | AsyncWrite::poll_write(self.project().tx, cx, buf) 782 | } 783 | 784 | fn poll_flush( 785 | self: std::pin::Pin<&mut Self>, 786 | cx: &mut std::task::Context<'_>, 787 | ) -> std::task::Poll> { 788 | self.project().tx.poll_flush(cx) 789 | } 790 | 791 | fn poll_shutdown( 792 | self: std::pin::Pin<&mut Self>, 793 | cx: &mut std::task::Context<'_>, 794 | ) -> std::task::Poll> { 795 | self.project().tx.poll_shutdown(cx) 796 | } 797 | } 798 | 799 | impl OwnedWriteHalf { 800 | pub fn reset( 801 | &mut self, 802 | code: impl Into, 803 | ) -> Result<(), quinn::ClosedStream> { 804 | self.tx.reset(code.into()) 805 | } 806 | } 807 | 808 | /// Header for new streams 809 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 810 | #[non_exhaustive] 811 | pub enum StreamHeader { 812 | Control { 813 | metadata: Option>, 814 | }, 815 | Stream { 816 | local_addr: SocketAddr, 817 | remote_addr: SocketAddr, 818 | }, 819 | Agent {}, 820 | } 821 | 822 | /// Messages for control streams 823 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 824 | #[non_exhaustive] 825 | pub enum ControlMessage { 826 | AuthenticateApp { 827 | token: String, 828 | org: String, 829 | app: String, 830 | }, 831 | AuthenticateCluster { 832 | token: String, 833 | }, 834 | Authenticated { 835 | metadata: HashMap, 836 | addr: SocketAddr, 837 | hostnames: Vec, 838 | env: HashMap, 839 | }, 840 | Listening {}, 841 | Routed {}, 842 | Migrate {}, 843 | } 844 | 845 | // Using this function instead of WriteExt::write_u32_le to avoid std::io::Error 846 | async fn write_u32_le(tx: &mut quinn::SendStream, v: u32) -> Result<(), Error> { 847 | Ok(tx.write_all(&v.to_le_bytes()).await?) 848 | } 849 | 850 | // Using this function instead of WriteExt::read_u32_le to avoid std::io::Error 851 | async fn read_u32_le(rx: &mut quinn::RecvStream) -> Result { 852 | let mut data = [0; std::mem::size_of::()]; 853 | rx.read_exact(&mut data).await?; 854 | Ok(u32::from_le_bytes(data)) 855 | } 856 | 857 | pub async fn write_message( 858 | tx: &mut quinn::SendStream, 859 | message: T, 860 | ) -> Result<(), Error> { 861 | let data = serde_json::to_vec(&message)?; 862 | write_u32_le(tx, data.len() as _).await?; 863 | tx.write_all(&data).await?; 864 | Ok(()) 865 | } 866 | 867 | pub async fn read_message( 868 | rx: &mut quinn::RecvStream, 869 | ) -> Result { 870 | let length = read_u32_le(rx).await?; 871 | if length > 1024 * 512 { 872 | return Err(Error::MessageLengthInvalid); 873 | } 874 | let mut data = vec![0; length as usize]; 875 | rx.read_exact(&mut data).await?; 876 | let message = serde_json::from_slice(&data)?; 877 | Ok(message) 878 | } 879 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.5.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 34 | 35 | [[package]] 36 | name = "aws-lc-rs" 37 | version = "1.13.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "08b5d4e069cbc868041a64bd68dc8cb39a0d79585cd6c5a24caa8c2d622121be" 40 | dependencies = [ 41 | "aws-lc-sys", 42 | "zeroize", 43 | ] 44 | 45 | [[package]] 46 | name = "aws-lc-sys" 47 | version = "0.30.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" 50 | dependencies = [ 51 | "bindgen", 52 | "cc", 53 | "cmake", 54 | "dunce", 55 | "fs_extra", 56 | ] 57 | 58 | [[package]] 59 | name = "backtrace" 60 | version = "0.3.75" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 63 | dependencies = [ 64 | "addr2line", 65 | "cfg-if", 66 | "libc", 67 | "miniz_oxide", 68 | "object", 69 | "rustc-demangle", 70 | "windows-targets", 71 | ] 72 | 73 | [[package]] 74 | name = "bindgen" 75 | version = "0.69.5" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 78 | dependencies = [ 79 | "bitflags", 80 | "cexpr", 81 | "clang-sys", 82 | "itertools", 83 | "lazy_static", 84 | "lazycell", 85 | "log", 86 | "prettyplease", 87 | "proc-macro2", 88 | "quote", 89 | "regex", 90 | "rustc-hash 1.1.0", 91 | "shlex", 92 | "syn", 93 | "which", 94 | ] 95 | 96 | [[package]] 97 | name = "bitflags" 98 | version = "2.9.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 101 | 102 | [[package]] 103 | name = "bumpalo" 104 | version = "3.19.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 107 | 108 | [[package]] 109 | name = "bytemuck" 110 | version = "1.23.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" 113 | 114 | [[package]] 115 | name = "bytes" 116 | version = "1.10.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 119 | 120 | [[package]] 121 | name = "cc" 122 | version = "1.2.29" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" 125 | dependencies = [ 126 | "jobserver", 127 | "libc", 128 | "shlex", 129 | ] 130 | 131 | [[package]] 132 | name = "cexpr" 133 | version = "0.6.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 136 | dependencies = [ 137 | "nom", 138 | ] 139 | 140 | [[package]] 141 | name = "cfg-if" 142 | version = "1.0.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 145 | 146 | [[package]] 147 | name = "cfg_aliases" 148 | version = "0.2.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 151 | 152 | [[package]] 153 | name = "clang-sys" 154 | version = "1.8.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 157 | dependencies = [ 158 | "glob", 159 | "libc", 160 | "libloading", 161 | ] 162 | 163 | [[package]] 164 | name = "cmake" 165 | version = "0.1.54" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 168 | dependencies = [ 169 | "cc", 170 | ] 171 | 172 | [[package]] 173 | name = "deno_tunnel" 174 | version = "0.8.0" 175 | dependencies = [ 176 | "pin-project", 177 | "quinn", 178 | "rustls-pemfile", 179 | "serde", 180 | "serde_json", 181 | "thiserror", 182 | "tokio", 183 | "tracing", 184 | ] 185 | 186 | [[package]] 187 | name = "dunce" 188 | version = "1.0.5" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 191 | 192 | [[package]] 193 | name = "either" 194 | version = "1.15.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 197 | 198 | [[package]] 199 | name = "errno" 200 | version = "0.3.13" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 203 | dependencies = [ 204 | "libc", 205 | "windows-sys 0.59.0", 206 | ] 207 | 208 | [[package]] 209 | name = "fastbloom" 210 | version = "0.9.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "27cea6e7f512d43b098939ff4d5a5d6fe3db07971e1d05176fe26c642d33f5b8" 213 | dependencies = [ 214 | "getrandom 0.3.3", 215 | "rand", 216 | "siphasher", 217 | "wide", 218 | ] 219 | 220 | [[package]] 221 | name = "fs_extra" 222 | version = "1.3.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 225 | 226 | [[package]] 227 | name = "getrandom" 228 | version = "0.2.16" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 231 | dependencies = [ 232 | "cfg-if", 233 | "js-sys", 234 | "libc", 235 | "wasi 0.11.1+wasi-snapshot-preview1", 236 | "wasm-bindgen", 237 | ] 238 | 239 | [[package]] 240 | name = "getrandom" 241 | version = "0.3.3" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 244 | dependencies = [ 245 | "cfg-if", 246 | "js-sys", 247 | "libc", 248 | "r-efi", 249 | "wasi 0.14.2+wasi-0.2.4", 250 | "wasm-bindgen", 251 | ] 252 | 253 | [[package]] 254 | name = "gimli" 255 | version = "0.31.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 258 | 259 | [[package]] 260 | name = "glob" 261 | version = "0.3.2" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 264 | 265 | [[package]] 266 | name = "home" 267 | version = "0.5.11" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 270 | dependencies = [ 271 | "windows-sys 0.59.0", 272 | ] 273 | 274 | [[package]] 275 | name = "io-uring" 276 | version = "0.7.8" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" 279 | dependencies = [ 280 | "bitflags", 281 | "cfg-if", 282 | "libc", 283 | ] 284 | 285 | [[package]] 286 | name = "itertools" 287 | version = "0.12.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 290 | dependencies = [ 291 | "either", 292 | ] 293 | 294 | [[package]] 295 | name = "itoa" 296 | version = "1.0.15" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 299 | 300 | [[package]] 301 | name = "jobserver" 302 | version = "0.1.33" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 305 | dependencies = [ 306 | "getrandom 0.3.3", 307 | "libc", 308 | ] 309 | 310 | [[package]] 311 | name = "js-sys" 312 | version = "0.3.77" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 315 | dependencies = [ 316 | "once_cell", 317 | "wasm-bindgen", 318 | ] 319 | 320 | [[package]] 321 | name = "lazy_static" 322 | version = "1.5.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 325 | 326 | [[package]] 327 | name = "lazycell" 328 | version = "1.3.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 331 | 332 | [[package]] 333 | name = "libc" 334 | version = "0.2.174" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 337 | 338 | [[package]] 339 | name = "libloading" 340 | version = "0.8.8" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" 343 | dependencies = [ 344 | "cfg-if", 345 | "windows-targets", 346 | ] 347 | 348 | [[package]] 349 | name = "linux-raw-sys" 350 | version = "0.4.15" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 353 | 354 | [[package]] 355 | name = "lock_api" 356 | version = "0.4.13" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 359 | dependencies = [ 360 | "autocfg", 361 | "scopeguard", 362 | ] 363 | 364 | [[package]] 365 | name = "log" 366 | version = "0.4.27" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 369 | 370 | [[package]] 371 | name = "lru-slab" 372 | version = "0.1.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 375 | 376 | [[package]] 377 | name = "memchr" 378 | version = "2.7.5" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 381 | 382 | [[package]] 383 | name = "minimal-lexical" 384 | version = "0.2.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 387 | 388 | [[package]] 389 | name = "miniz_oxide" 390 | version = "0.8.9" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 393 | dependencies = [ 394 | "adler2", 395 | ] 396 | 397 | [[package]] 398 | name = "mio" 399 | version = "1.0.4" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 402 | dependencies = [ 403 | "libc", 404 | "wasi 0.11.1+wasi-snapshot-preview1", 405 | "windows-sys 0.59.0", 406 | ] 407 | 408 | [[package]] 409 | name = "nom" 410 | version = "7.1.3" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 413 | dependencies = [ 414 | "memchr", 415 | "minimal-lexical", 416 | ] 417 | 418 | [[package]] 419 | name = "object" 420 | version = "0.36.7" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 423 | dependencies = [ 424 | "memchr", 425 | ] 426 | 427 | [[package]] 428 | name = "once_cell" 429 | version = "1.21.3" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 432 | 433 | [[package]] 434 | name = "parking_lot" 435 | version = "0.12.4" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 438 | dependencies = [ 439 | "lock_api", 440 | "parking_lot_core", 441 | ] 442 | 443 | [[package]] 444 | name = "parking_lot_core" 445 | version = "0.9.11" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 448 | dependencies = [ 449 | "cfg-if", 450 | "libc", 451 | "redox_syscall", 452 | "smallvec", 453 | "windows-targets", 454 | ] 455 | 456 | [[package]] 457 | name = "pin-project" 458 | version = "1.1.10" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 461 | dependencies = [ 462 | "pin-project-internal", 463 | ] 464 | 465 | [[package]] 466 | name = "pin-project-internal" 467 | version = "1.1.10" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 470 | dependencies = [ 471 | "proc-macro2", 472 | "quote", 473 | "syn", 474 | ] 475 | 476 | [[package]] 477 | name = "pin-project-lite" 478 | version = "0.2.16" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 481 | 482 | [[package]] 483 | name = "ppv-lite86" 484 | version = "0.2.21" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 487 | dependencies = [ 488 | "zerocopy", 489 | ] 490 | 491 | [[package]] 492 | name = "prettyplease" 493 | version = "0.2.35" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" 496 | dependencies = [ 497 | "proc-macro2", 498 | "syn", 499 | ] 500 | 501 | [[package]] 502 | name = "proc-macro2" 503 | version = "1.0.95" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 506 | dependencies = [ 507 | "unicode-ident", 508 | ] 509 | 510 | [[package]] 511 | name = "quinn" 512 | version = "0.11.8" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" 515 | dependencies = [ 516 | "bytes", 517 | "cfg_aliases", 518 | "pin-project-lite", 519 | "quinn-proto", 520 | "quinn-udp", 521 | "rustc-hash 2.1.1", 522 | "rustls", 523 | "socket2", 524 | "thiserror", 525 | "tokio", 526 | "tracing", 527 | "web-time", 528 | ] 529 | 530 | [[package]] 531 | name = "quinn-proto" 532 | version = "0.11.12" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" 535 | dependencies = [ 536 | "aws-lc-rs", 537 | "bytes", 538 | "fastbloom", 539 | "getrandom 0.3.3", 540 | "lru-slab", 541 | "rand", 542 | "ring", 543 | "rustc-hash 2.1.1", 544 | "rustls", 545 | "rustls-pki-types", 546 | "slab", 547 | "thiserror", 548 | "tinyvec", 549 | "tracing", 550 | "web-time", 551 | ] 552 | 553 | [[package]] 554 | name = "quinn-udp" 555 | version = "0.5.13" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" 558 | dependencies = [ 559 | "cfg_aliases", 560 | "libc", 561 | "once_cell", 562 | "socket2", 563 | "tracing", 564 | "windows-sys 0.59.0", 565 | ] 566 | 567 | [[package]] 568 | name = "quote" 569 | version = "1.0.40" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 572 | dependencies = [ 573 | "proc-macro2", 574 | ] 575 | 576 | [[package]] 577 | name = "r-efi" 578 | version = "5.3.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 581 | 582 | [[package]] 583 | name = "rand" 584 | version = "0.9.1" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" 587 | dependencies = [ 588 | "rand_chacha", 589 | "rand_core", 590 | ] 591 | 592 | [[package]] 593 | name = "rand_chacha" 594 | version = "0.9.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 597 | dependencies = [ 598 | "ppv-lite86", 599 | "rand_core", 600 | ] 601 | 602 | [[package]] 603 | name = "rand_core" 604 | version = "0.9.3" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 607 | dependencies = [ 608 | "getrandom 0.3.3", 609 | ] 610 | 611 | [[package]] 612 | name = "redox_syscall" 613 | version = "0.5.13" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" 616 | dependencies = [ 617 | "bitflags", 618 | ] 619 | 620 | [[package]] 621 | name = "regex" 622 | version = "1.11.1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 625 | dependencies = [ 626 | "aho-corasick", 627 | "memchr", 628 | "regex-automata", 629 | "regex-syntax", 630 | ] 631 | 632 | [[package]] 633 | name = "regex-automata" 634 | version = "0.4.9" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 637 | dependencies = [ 638 | "aho-corasick", 639 | "memchr", 640 | "regex-syntax", 641 | ] 642 | 643 | [[package]] 644 | name = "regex-syntax" 645 | version = "0.8.5" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 648 | 649 | [[package]] 650 | name = "ring" 651 | version = "0.17.14" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 654 | dependencies = [ 655 | "cc", 656 | "cfg-if", 657 | "getrandom 0.2.16", 658 | "libc", 659 | "untrusted", 660 | "windows-sys 0.52.0", 661 | ] 662 | 663 | [[package]] 664 | name = "rustc-demangle" 665 | version = "0.1.25" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" 668 | 669 | [[package]] 670 | name = "rustc-hash" 671 | version = "1.1.0" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 674 | 675 | [[package]] 676 | name = "rustc-hash" 677 | version = "2.1.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 680 | 681 | [[package]] 682 | name = "rustix" 683 | version = "0.38.44" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 686 | dependencies = [ 687 | "bitflags", 688 | "errno", 689 | "libc", 690 | "linux-raw-sys", 691 | "windows-sys 0.59.0", 692 | ] 693 | 694 | [[package]] 695 | name = "rustls" 696 | version = "0.23.28" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" 699 | dependencies = [ 700 | "aws-lc-rs", 701 | "once_cell", 702 | "rustls-pki-types", 703 | "rustls-webpki", 704 | "subtle", 705 | "zeroize", 706 | ] 707 | 708 | [[package]] 709 | name = "rustls-pemfile" 710 | version = "2.2.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 713 | dependencies = [ 714 | "rustls-pki-types", 715 | ] 716 | 717 | [[package]] 718 | name = "rustls-pki-types" 719 | version = "1.12.0" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 722 | dependencies = [ 723 | "web-time", 724 | "zeroize", 725 | ] 726 | 727 | [[package]] 728 | name = "rustls-webpki" 729 | version = "0.103.3" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" 732 | dependencies = [ 733 | "aws-lc-rs", 734 | "ring", 735 | "rustls-pki-types", 736 | "untrusted", 737 | ] 738 | 739 | [[package]] 740 | name = "ryu" 741 | version = "1.0.20" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 744 | 745 | [[package]] 746 | name = "safe_arch" 747 | version = "0.7.4" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" 750 | dependencies = [ 751 | "bytemuck", 752 | ] 753 | 754 | [[package]] 755 | name = "scopeguard" 756 | version = "1.2.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 759 | 760 | [[package]] 761 | name = "serde" 762 | version = "1.0.219" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 765 | dependencies = [ 766 | "serde_derive", 767 | ] 768 | 769 | [[package]] 770 | name = "serde_derive" 771 | version = "1.0.219" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 774 | dependencies = [ 775 | "proc-macro2", 776 | "quote", 777 | "syn", 778 | ] 779 | 780 | [[package]] 781 | name = "serde_json" 782 | version = "1.0.140" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 785 | dependencies = [ 786 | "itoa", 787 | "memchr", 788 | "ryu", 789 | "serde", 790 | ] 791 | 792 | [[package]] 793 | name = "shlex" 794 | version = "1.3.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 797 | 798 | [[package]] 799 | name = "signal-hook-registry" 800 | version = "1.4.5" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 803 | dependencies = [ 804 | "libc", 805 | ] 806 | 807 | [[package]] 808 | name = "siphasher" 809 | version = "1.0.1" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 812 | 813 | [[package]] 814 | name = "slab" 815 | version = "0.4.10" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" 818 | 819 | [[package]] 820 | name = "smallvec" 821 | version = "1.15.1" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 824 | 825 | [[package]] 826 | name = "socket2" 827 | version = "0.5.10" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 830 | dependencies = [ 831 | "libc", 832 | "windows-sys 0.52.0", 833 | ] 834 | 835 | [[package]] 836 | name = "subtle" 837 | version = "2.6.1" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 840 | 841 | [[package]] 842 | name = "syn" 843 | version = "2.0.104" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 846 | dependencies = [ 847 | "proc-macro2", 848 | "quote", 849 | "unicode-ident", 850 | ] 851 | 852 | [[package]] 853 | name = "thiserror" 854 | version = "2.0.12" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 857 | dependencies = [ 858 | "thiserror-impl", 859 | ] 860 | 861 | [[package]] 862 | name = "thiserror-impl" 863 | version = "2.0.12" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 866 | dependencies = [ 867 | "proc-macro2", 868 | "quote", 869 | "syn", 870 | ] 871 | 872 | [[package]] 873 | name = "tinyvec" 874 | version = "1.9.0" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 877 | dependencies = [ 878 | "tinyvec_macros", 879 | ] 880 | 881 | [[package]] 882 | name = "tinyvec_macros" 883 | version = "0.1.1" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 886 | 887 | [[package]] 888 | name = "tokio" 889 | version = "1.46.1" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" 892 | dependencies = [ 893 | "backtrace", 894 | "bytes", 895 | "io-uring", 896 | "libc", 897 | "mio", 898 | "parking_lot", 899 | "pin-project-lite", 900 | "signal-hook-registry", 901 | "slab", 902 | "socket2", 903 | "tokio-macros", 904 | "windows-sys 0.52.0", 905 | ] 906 | 907 | [[package]] 908 | name = "tokio-macros" 909 | version = "2.5.0" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 912 | dependencies = [ 913 | "proc-macro2", 914 | "quote", 915 | "syn", 916 | ] 917 | 918 | [[package]] 919 | name = "tracing" 920 | version = "0.1.41" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 923 | dependencies = [ 924 | "log", 925 | "pin-project-lite", 926 | "tracing-attributes", 927 | "tracing-core", 928 | ] 929 | 930 | [[package]] 931 | name = "tracing-attributes" 932 | version = "0.1.30" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 935 | dependencies = [ 936 | "proc-macro2", 937 | "quote", 938 | "syn", 939 | ] 940 | 941 | [[package]] 942 | name = "tracing-core" 943 | version = "0.1.34" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 946 | dependencies = [ 947 | "once_cell", 948 | ] 949 | 950 | [[package]] 951 | name = "unicode-ident" 952 | version = "1.0.18" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 955 | 956 | [[package]] 957 | name = "untrusted" 958 | version = "0.9.0" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 961 | 962 | [[package]] 963 | name = "wasi" 964 | version = "0.11.1+wasi-snapshot-preview1" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 967 | 968 | [[package]] 969 | name = "wasi" 970 | version = "0.14.2+wasi-0.2.4" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 973 | dependencies = [ 974 | "wit-bindgen-rt", 975 | ] 976 | 977 | [[package]] 978 | name = "wasm-bindgen" 979 | version = "0.2.100" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 982 | dependencies = [ 983 | "cfg-if", 984 | "once_cell", 985 | "wasm-bindgen-macro", 986 | ] 987 | 988 | [[package]] 989 | name = "wasm-bindgen-backend" 990 | version = "0.2.100" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 993 | dependencies = [ 994 | "bumpalo", 995 | "log", 996 | "proc-macro2", 997 | "quote", 998 | "syn", 999 | "wasm-bindgen-shared", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "wasm-bindgen-macro" 1004 | version = "0.2.100" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1007 | dependencies = [ 1008 | "quote", 1009 | "wasm-bindgen-macro-support", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "wasm-bindgen-macro-support" 1014 | version = "0.2.100" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1017 | dependencies = [ 1018 | "proc-macro2", 1019 | "quote", 1020 | "syn", 1021 | "wasm-bindgen-backend", 1022 | "wasm-bindgen-shared", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "wasm-bindgen-shared" 1027 | version = "0.2.100" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1030 | dependencies = [ 1031 | "unicode-ident", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "web-time" 1036 | version = "1.1.0" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1039 | dependencies = [ 1040 | "js-sys", 1041 | "wasm-bindgen", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "which" 1046 | version = "4.4.2" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1049 | dependencies = [ 1050 | "either", 1051 | "home", 1052 | "once_cell", 1053 | "rustix", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "wide" 1058 | version = "0.7.33" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" 1061 | dependencies = [ 1062 | "bytemuck", 1063 | "safe_arch", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "windows-sys" 1068 | version = "0.52.0" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1071 | dependencies = [ 1072 | "windows-targets", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "windows-sys" 1077 | version = "0.59.0" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1080 | dependencies = [ 1081 | "windows-targets", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "windows-targets" 1086 | version = "0.52.6" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1089 | dependencies = [ 1090 | "windows_aarch64_gnullvm", 1091 | "windows_aarch64_msvc", 1092 | "windows_i686_gnu", 1093 | "windows_i686_gnullvm", 1094 | "windows_i686_msvc", 1095 | "windows_x86_64_gnu", 1096 | "windows_x86_64_gnullvm", 1097 | "windows_x86_64_msvc", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "windows_aarch64_gnullvm" 1102 | version = "0.52.6" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1105 | 1106 | [[package]] 1107 | name = "windows_aarch64_msvc" 1108 | version = "0.52.6" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1111 | 1112 | [[package]] 1113 | name = "windows_i686_gnu" 1114 | version = "0.52.6" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1117 | 1118 | [[package]] 1119 | name = "windows_i686_gnullvm" 1120 | version = "0.52.6" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1123 | 1124 | [[package]] 1125 | name = "windows_i686_msvc" 1126 | version = "0.52.6" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1129 | 1130 | [[package]] 1131 | name = "windows_x86_64_gnu" 1132 | version = "0.52.6" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1135 | 1136 | [[package]] 1137 | name = "windows_x86_64_gnullvm" 1138 | version = "0.52.6" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1141 | 1142 | [[package]] 1143 | name = "windows_x86_64_msvc" 1144 | version = "0.52.6" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1147 | 1148 | [[package]] 1149 | name = "wit-bindgen-rt" 1150 | version = "0.39.0" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1153 | dependencies = [ 1154 | "bitflags", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "zerocopy" 1159 | version = "0.8.26" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" 1162 | dependencies = [ 1163 | "zerocopy-derive", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "zerocopy-derive" 1168 | version = "0.8.26" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" 1171 | dependencies = [ 1172 | "proc-macro2", 1173 | "quote", 1174 | "syn", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "zeroize" 1179 | version = "1.8.1" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1182 | --------------------------------------------------------------------------------