Skip to main content

nym_bridges_types/
lib.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: GPL-3.0-only
3
4//! Minimal compatible Types shared between [`nym-bridges`](https://docs.rs/nym-bridges) and other crates.
5//!
6//! ## Abstract
7//!
8//! - This crate contains all types necessary for interaction with crates (nym-vpn-lib-types) and others
9//! - Types visible via bindings should contain proper attributes and feature gated to `uniffi-bindings` for uniffi, `typescript-bindings` for TypeScript bindings.
10//! - TypeScript bindings use serde for conversion from Rust to TS and feature-gated to `typescript-bindings`.
11//! - Be mindful of limitations of TypeScript and uniffi limitations. Keep exported types simple.
12//!
13//! ## Dependency considerations
14//!
15//! Please keep direct dependencies to other crates to a minimum to avoid dependency conflicts which can happen, especially when using it in other large projects such as Tauri.
16
17//! ## Supported bindings
18//!
19//! 1. [uniffi](https://mozilla.github.io/uniffi-rs/latest/) bindings (feature flag: uniffi-bindings). The following limitations apply:
20//! - Namespaces are not supported, all exported types should have unique names.
21//! - Not all types are supported or can be bridged. Keep exported types simple.
22//!
23//! 2. TypeScript bindings using [ts-rs](https://docs.rs/ts-rs) (feature flag: `typescript-bindings`). Serialization ([using serde](https://docs.rs/serde)) uses `snake_case`.
24//!
25//!    Run the following command to generate TypeScript bindings:
26//!    ```sh
27//!    cargo test -p nym-vpn-lib-types -F typescript-bindings
28//!    ```
29//!
30//! ## Serde support
31//!
32//! Serde can be enabled using `serde` feature flag.
33
34#[cfg(feature = "serde")]
35use serde::{Deserialize, Serialize};
36
37#[cfg(feature = "typescript-bindings")]
38use ts_rs::TS;
39
40#[cfg(feature = "uniffi-bindings")]
41uniffi::setup_scaffolding!();
42
43use std::net::SocketAddr;
44#[cfg(feature = "uniffi-bindings")]
45use std::str::FromStr;
46#[cfg(feature = "uniffi-bindings")]
47uniffi::custom_type!(SocketAddr, String, {
48    remote,
49    try_lift: |val| Ok(SocketAddr::from_str(&val)?),
50    lower: |val| val.to_string()
51});
52
53#[derive(Debug, PartialEq, Clone)]
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55#[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Record))]
56#[cfg_attr(
57    feature = "typescript-bindings",
58    derive(TS),
59    ts(export),
60    ts(export_to = "bindings.ts")
61)]
62#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
63pub struct PersistedClientConfig {
64    pub version: String,
65    pub transports: Vec<ClientConfig>,
66}
67
68impl PersistedClientConfig {
69    pub fn get_addrs(&self) -> Vec<SocketAddr> {
70        let mut addrs = Vec::new();
71        for transport in &self.transports {
72            match transport {
73                ClientConfig::QuicPlain(params) => addrs.extend(&params.addresses),
74                ClientConfig::TlsPlain(params) => addrs.extend(&params.addresses),
75            }
76        }
77        addrs
78    }
79}
80
81#[derive(Debug, PartialEq, Clone)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83#[cfg_attr(feature = "serde", serde(tag = "transport_type", content = "args"))]
84#[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Enum))]
85#[cfg_attr(
86    feature = "typescript-bindings",
87    derive(TS),
88    ts(export),
89    ts(export_to = "bindings.ts")
90)]
91#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
92pub enum ClientConfig {
93    QuicPlain(quic::ClientOptions),
94    TlsPlain(tls::ClientOptions),
95}
96
97impl From<quic::ClientOptions> for ClientConfig {
98    fn from(value: quic::ClientOptions) -> Self {
99        ClientConfig::QuicPlain(value)
100    }
101}
102
103impl From<tls::ClientOptions> for ClientConfig {
104    fn from(value: tls::ClientOptions) -> Self {
105        ClientConfig::TlsPlain(value)
106    }
107}
108
109pub mod quic {
110    #[cfg(feature = "serde")]
111    use serde::{Deserialize, Serialize};
112    use std::net::SocketAddr;
113
114    #[cfg(feature = "typescript-bindings")]
115    use ts_rs::TS;
116
117    #[derive(Debug, PartialEq, Clone)]
118    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
119    #[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Record))]
120    #[cfg_attr(
121        feature = "typescript-bindings",
122        derive(TS),
123        ts(export),
124        ts(export_to = "bindings.ts")
125    )]
126    #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
127    pub struct QuicPlainClientOptions {
128        /// Address describing the remote transport server. This is a vec to support multiple addresses
129        /// so as to support both IPv4 and IPv6. These addresses are meant to describe a single bridge
130        /// as the key material should not be used across multiple instances.
131        ///
132        /// Must parse as a valid [`std::net::SocketAddr`] - e.g. `123.45.67.89:443`
133        pub addresses: Vec<SocketAddr>,
134
135        /// Override hostname used for certificate verification
136        pub host: Option<String>,
137
138        /// Use identity public key to verify server self signed certificate
139        pub id_pubkey: String,
140    }
141
142    pub type ClientOptions = QuicPlainClientOptions;
143}
144
145pub mod tls {
146    #[cfg(feature = "serde")]
147    use serde::{Deserialize, Serialize};
148    use std::net::SocketAddr;
149
150    #[cfg(feature = "typescript-bindings")]
151    use ts_rs::TS;
152
153    #[derive(Debug, PartialEq, Clone)]
154    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
155    #[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Record))]
156    #[cfg_attr(
157        feature = "typescript-bindings",
158        derive(TS),
159        ts(export),
160        ts(export_to = "bindings.ts")
161    )]
162    #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
163    pub struct TlsPlainClientOptions {
164        /// Address describing the remote transport server. This is a vec to support multiple addresses
165        /// so as to support both IPv4 and IPv6. These addresses are meant to describe a single bridge
166        /// as the key material should not be used across multiple instances.
167        ///
168        /// Must parse as a valid [`std::net::SocketAddr`] - e.g. `123.45.67.89:443`
169        pub addresses: Vec<SocketAddr>,
170
171        /// Override hostname used for certificate verification
172        pub host: Option<String>,
173
174        /// Use identity public key to verify server self signed certificate base64 encoded
175        pub id_pubkey: String,
176    }
177
178    pub type ClientOptions = TlsPlainClientOptions;
179}
180
181#[cfg(test)]
182mod test {
183    use crate::{ClientConfig, PersistedClientConfig};
184
185    const RAW_V0_CLIENT_CONFIG: &str = r#"{"version":"0","transports":[{"transport_type":"quic_plain","args":{"addresses":["139.162.33.226:4443","[2400:8901::2000:faff:fea6:87f2]:4443"],"host":"netdna.bootstrapcdn.com","id_pubkey":"9JC91ZiszhIn3n4FG+MDYE/lYwhGdpHGWQTKUqGl+sE="}}]}"#;
186
187    /// The initial version of the bridge descriptors that are provided by the gateways use a snake case
188    /// for the enum differentiator. This test validates that under normal circumstances that the descriptor
189    /// is parsed as  expected. The only situation under which the enum differentiator has a different format
190    /// is when using the `typescript-bindings` feature.
191    #[test]
192    fn ensure_bridge_v0_parsing_compatibility() -> Result<(), Box<dyn std::error::Error>> {
193        // Parse the JSON to verify structure
194        let parsed: PersistedClientConfig = serde_json::from_str(RAW_V0_CLIENT_CONFIG)?;
195
196        // Verify version
197        assert_eq!(parsed.version, "0");
198
199        // Verify transport type
200        let params = match &parsed.transports[0] {
201            ClientConfig::QuicPlain(p) => p,
202            ClientConfig::TlsPlain(_) => return Err("expected quic transport args".into()),
203        };
204
205        // Verify addresses contain our test IPs
206        let addresses = &params.addresses;
207
208        let address_strings: Vec<String> = addresses.iter().map(|v| v.to_string()).collect();
209
210        // Should contain both IPv4 and IPv6 addresses with port 4443
211        assert!(
212            address_strings
213                .iter()
214                .any(|addr| addr.contains("139.162.33.226:4443"))
215        );
216        assert!(
217            address_strings
218                .iter()
219                .any(|addr| addr.contains("[2400:8901::2000:faff:fea6:87f2]:4443"))
220        );
221
222        // Verify host field
223        assert_eq!(params.host, Some("netdna.bootstrapcdn.com".to_string()),);
224
225        Ok(())
226    }
227}