dittolive_ditto/transport/
transport_config.rs

1use std::{collections::HashSet, path::PathBuf, time::Duration};
2
3use serde::{Deserialize, Serialize};
4use serde_with::serde_as;
5
6pub const NO_PREFERRED_ROUTE_HINT: u32 = 0;
7
8/// A configuration object specifying which network transports Ditto should use
9/// to sync data.
10///
11/// A `Ditto` object comes with a default transport configuration where all
12/// available peer-to-peer transports are enabled. You can customize this by
13/// initializing a `TransportConfig`, adjusting its properties, and supplying it
14/// to `set_transport_config()` on `Ditto`.
15///
16/// When you initialize a `TransportConfig` yourself it starts with all
17/// transports disabled. You must enable each one directly.
18///
19/// Peer-to-peer transports will automatically discover peers in the vicinity
20/// and create connections without any configuration. These are configured
21/// inside the `peer_to_peer` property. To turn each one on, set its `enabled`
22/// property to `true`.
23///
24/// To connect to a peer at a known location, such as a Ditto Big Peer, add its
25/// address inside the `connect` configuration. These are either `host:port`
26/// strings for raw TCP sync, or a `wss://…` URL for websockets.
27///
28/// The `listen` configurations are for specific less common data sync
29/// scenarios. Please read the documentation on the Ditto website for examples.
30/// Incorrect use of `listen` can result in insecure configurations.
31#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
32#[serde(default)]
33pub struct TransportConfig {
34    /// Configure Bluetooth and LAN transports.
35    pub peer_to_peer: PeerToPeer,
36
37    /// Configure TCP and WebSocket outbound transports.
38    pub connect: Connect,
39
40    /// Configure TCP and HTTP inbound transports.
41    pub listen: Listen,
42
43    /// Set peer-global transport settings.
44    pub global: Global,
45}
46
47impl TransportConfig {
48    /// Create a new [`TransportConfig`] with default settings.
49    pub fn new() -> Self {
50        Self {
51            peer_to_peer: PeerToPeer {
52                bluetooth_le: BluetoothLEConfig::new(),
53                lan: LanConfig::new(),
54                awdl: AwdlConfig::new(),
55                wifi_aware: WifiAwareConfig::new(),
56            },
57            connect: Connect {
58                tcp_servers: HashSet::new(),
59                websocket_urls: HashSet::new(),
60                retry_interval: Duration::from_secs(5),
61            },
62            listen: Listen {
63                tcp: TcpListenConfig::new(),
64                http: HttpListenConfig::new(),
65            },
66            global: Global {
67                sync_group: 0,
68                routing_hint: NO_PREFERRED_ROUTE_HINT,
69            },
70        }
71    }
72
73    /// Enables peer-to-peer transports in this config.
74    pub fn enable_all_peer_to_peer(&mut self) {
75        self.peer_to_peer.bluetooth_le.enabled = true;
76        self.peer_to_peer.lan.enabled = true;
77        self.peer_to_peer.awdl.enabled = true;
78        self.peer_to_peer.wifi_aware.enabled = true;
79    }
80}
81
82/// Configure peer-to-peer transports such as Bluetooth and LAN.
83#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
84#[serde(default)]
85pub struct PeerToPeer {
86    /// Bluetooth transport configurations.
87    pub bluetooth_le: BluetoothLEConfig,
88
89    /// LAN transport configurations.
90    pub lan: LanConfig,
91
92    /// AWDL transport configurations.
93    pub awdl: AwdlConfig,
94
95    /// Wi-Fi Aware transport configurations.
96    pub wifi_aware: WifiAwareConfig,
97}
98
99/// Configure outbound transports such as TCP and WebSocket dialing.
100#[serde_as]
101#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
102#[serde(default)]
103pub struct Connect {
104    /// A set of TCP servers to attempt connection to.
105    pub tcp_servers: HashSet<String>,
106
107    /// A set of websocket servers to attempt connection to.
108    pub websocket_urls: HashSet<String>,
109
110    /// The retry interval between failed connection attempts. For
111    /// cross-compatibility, this must be less than 2^32 - 1 milliseconds.
112    #[serde_as(as = "::serde_with::DurationMilliSeconds<u64>")]
113    pub retry_interval: Duration,
114}
115
116/// Configure inbound transports such as TCP and HTTP servers.
117#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
118#[serde(default)]
119pub struct Listen {
120    /// Configure inbound TCP transports.
121    pub tcp: TcpListenConfig,
122
123    /// Configure inbound HTTP transports.
124    pub http: HttpListenConfig,
125}
126
127/// Settings not associated with any specific type of transport.
128#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
129#[serde(default)]
130pub struct Global {
131    /// The sync group for this device.
132    ///
133    /// When peer-to-peer transports are enabled, all devices with the same App
134    /// ID will normally form an interconnected mesh network. In some
135    /// situations it may be desirable to have distinct groups of devices
136    /// within the same app, so that connections will only be formed within
137    /// each group. The `sync_group` parameter changes that group
138    /// membership. A device can only ever be in one sync group, which
139    /// by default is group 0. Up to 2^32 distinct group numbers can be used in
140    /// an app.
141    ///
142    /// This is an optimization, not a security control. If a connection is
143    /// created manually, such as by specifying a `connect` transport, then
144    /// devices from different sync groups will still sync as normal. If
145    /// two groups of devices are intended to have access to different data
146    /// sets, this must be enforced using Ditto's permissions system.
147    pub sync_group: u32,
148
149    /// The routing hint for this device.
150    ///
151    /// A routing hint is a performance tuning option which can improve the performance of
152    /// applications that use large collections. Ditto will make a best effort to co-locate data
153    /// for the same routing key. In most circumstances, this should substantially improve
154    /// responsiveness of the Ditto Cloud.
155    ///
156    /// The value of the routing hint is application specific - you are free to choose any value.
157    /// Devices which you expect to operate on much the same data should be configured to
158    /// use the same value.
159    ///
160    /// A routing hint does not partition data. The value of the routing hint will not affect the
161    /// data returned for a query. The routing hint only improves the efficiency of the Cloud's
162    /// ability to satisfy the query.
163    pub routing_hint: u32,
164}
165
166/// Configure inbound HTTP transports.
167#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
168#[serde(default)]
169pub struct HttpListenConfig {
170    /// Whether inbound HTTP is enabled.
171    pub enabled: bool,
172
173    /// The IP address on which to bind a listening HTTP server.
174    pub interface_ip: String,
175
176    /// The port on which to bind a listening HTTP server.
177    pub port: u16,
178
179    /// Whether to enable Websocket sync on this transport.
180    pub websocket_sync: bool,
181
182    /// Optional path to a TLS key file.
183    pub tls_key_path: Option<PathBuf>,
184
185    /// Optional path to a TLS certificate file.
186    pub tls_certificate_path: Option<PathBuf>,
187}
188
189impl HttpListenConfig {
190    /// Create a new [`HttpListenConfig`] with default settings.
191    pub fn new() -> Self {
192        Self {
193            enabled: false,
194            interface_ip: "[::]".to_string(),
195            port: 80,
196            websocket_sync: true,
197            tls_key_path: None,
198            tls_certificate_path: None,
199        }
200    }
201}
202
203/// Configure inbound TCP transports.
204#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
205#[serde(default)]
206pub struct TcpListenConfig {
207    /// Whether inbound TCP is enabled.
208    pub enabled: bool,
209
210    /// The address to bind a TCP listener, such as `0.0.0.0`.
211    pub interface_ip: String,
212
213    /// The port to bind the TCP listener to.
214    pub port: u16,
215}
216
217impl TcpListenConfig {
218    /// Create a new [`TcpListenConfig`] with default settings.
219    pub fn new() -> Self {
220        Self {
221            enabled: false,
222            interface_ip: "[::]".to_string(),
223            port: 4040,
224        }
225    }
226}
227
228/// Configure the Bluetooth Low-Energy transport.
229#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
230#[serde(default)]
231pub struct BluetoothLEConfig {
232    /// Whether to enable the Bluetooth Low-Energy transport.
233    pub enabled: bool,
234}
235
236impl BluetoothLEConfig {
237    /// Create a new [`BluetoothLEConfig`] with default settings.
238    pub fn new() -> Self {
239        Self { enabled: false }
240    }
241}
242
243/// Configure the Local Area Network (LAN) transport.
244#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
245#[serde(default)]
246pub struct LanConfig {
247    /// Whether to enable the LAN transport.
248    pub enabled: bool,
249
250    /// Whether to enable mDNS peer discovery.
251    pub mdns_enabled: bool,
252
253    /// Whether to enable multicast.
254    pub multicast_enabled: bool,
255}
256
257impl LanConfig {
258    /// Create a new [`LanConfig`] with default settings.
259    pub fn new() -> Self {
260        Self {
261            enabled: false,
262            mdns_enabled: true,
263            multicast_enabled: true,
264        }
265    }
266}
267
268/// Configuration for Apple Wireless Direct Link (AWDL) network transport.
269/// Not supported on all platforms.
270#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
271#[serde(default)]
272pub struct AwdlConfig {
273    /// Whether to enable the AWDL transport.
274    pub enabled: bool,
275}
276
277impl AwdlConfig {
278    /// Create a new [`AwdlConfig`] with default settings.
279    pub fn new() -> Self {
280        Self { enabled: false }
281    }
282}
283
284/// Configuration for Wi-Fi Aware network transport.
285/// Not supported on all platforms.
286#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
287#[serde(default)]
288pub struct WifiAwareConfig {
289    /// Whether to enable the Wi-Fi Aware transport.
290    pub enabled: bool,
291}
292
293impl WifiAwareConfig {
294    /// Create a new [`WifiAwareConfig`] with default settings.
295    pub fn new() -> Self {
296        Self { enabled: false }
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn awdl_config_default_disabled() {
306        assert!(!AwdlConfig::default().enabled);
307        assert!(!AwdlConfig::new().enabled);
308    }
309
310    #[test]
311    fn wifi_aware_config_default_disabled() {
312        assert!(!WifiAwareConfig::default().enabled);
313        assert!(!WifiAwareConfig::new().enabled);
314    }
315
316    #[test]
317    fn enable_all_peer_to_peer_enables_all_transports() {
318        let mut config = TransportConfig::new();
319        config.enable_all_peer_to_peer();
320        assert!(config.peer_to_peer.bluetooth_le.enabled);
321        assert!(config.peer_to_peer.lan.enabled);
322        assert!(config.peer_to_peer.awdl.enabled);
323        assert!(config.peer_to_peer.wifi_aware.enabled);
324    }
325
326    #[test]
327    fn awdl_config_serde_roundtrip() {
328        let original = AwdlConfig { enabled: true };
329        let json = serde_json::to_string(&original).unwrap();
330        let decoded: AwdlConfig = serde_json::from_str(&json).unwrap();
331        assert_eq!(original, decoded);
332    }
333
334    #[test]
335    fn wifi_aware_config_serde_roundtrip() {
336        let original = WifiAwareConfig { enabled: true };
337        let json = serde_json::to_string(&original).unwrap();
338        let decoded: WifiAwareConfig = serde_json::from_str(&json).unwrap();
339        assert_eq!(original, decoded);
340    }
341
342    #[test]
343    fn peer_to_peer_missing_fields_default_to_disabled() {
344        let json = r#"{"bluetooth_le":{"enabled":true},"lan":{"enabled":true,"mdns_enabled":true,"multicast_enabled":true}}"#;
345        let peer_to_peer: PeerToPeer = serde_json::from_str(json).unwrap();
346        assert!(!peer_to_peer.awdl.enabled);
347        assert!(!peer_to_peer.wifi_aware.enabled);
348    }
349}