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}