Skip to main content

ant_quic/constrained/
transport.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Constrained Transport Wrapper
9//!
10//! This module provides a wrapper that integrates the constrained protocol engine
11//! with any transport provider. It handles the routing of packets through the
12//! constrained engine for reliable delivery over low-bandwidth transports.
13
14use super::adapter::{AdapterEvent, ConstrainedEngineAdapter, EngineOutput};
15use super::engine::EngineConfig;
16use super::types::{ConnectionId, ConstrainedError};
17use crate::transport::{TransportAddr, TransportCapabilities};
18use std::sync::{Arc, Mutex};
19use tokio::sync::mpsc;
20
21/// Configuration for the constrained transport wrapper
22#[derive(Debug, Clone)]
23pub struct ConstrainedTransportConfig {
24    /// Engine configuration
25    pub engine_config: EngineConfig,
26    /// Channel buffer size for outbound packets
27    pub outbound_buffer_size: usize,
28    /// Channel buffer size for events
29    pub event_buffer_size: usize,
30}
31
32impl Default for ConstrainedTransportConfig {
33    fn default() -> Self {
34        Self {
35            engine_config: EngineConfig::default(),
36            outbound_buffer_size: 64,
37            event_buffer_size: 32,
38        }
39    }
40}
41
42impl ConstrainedTransportConfig {
43    /// Create config for BLE transport
44    pub fn for_ble() -> Self {
45        Self {
46            engine_config: EngineConfig::for_ble(),
47            outbound_buffer_size: 32,
48            event_buffer_size: 16,
49        }
50    }
51
52    /// Create config for LoRa transport
53    pub fn for_lora() -> Self {
54        Self {
55            engine_config: EngineConfig::for_lora(),
56            outbound_buffer_size: 8,
57            event_buffer_size: 8,
58        }
59    }
60}
61
62/// Handle for sending data through the constrained transport
63#[derive(Clone, Debug)]
64pub struct ConstrainedHandle {
65    /// Shared adapter
66    adapter: Arc<Mutex<ConstrainedEngineAdapter>>,
67    /// Channel for outbound packets
68    outbound_tx: mpsc::Sender<EngineOutput>,
69}
70
71impl ConstrainedHandle {
72    /// Initiate a connection to a remote address
73    pub fn connect(&self, remote: &TransportAddr) -> Result<ConnectionId, ConstrainedError> {
74        let mut adapter = self
75            .adapter
76            .lock()
77            .map_err(|_| ConstrainedError::Transport("adapter lock poisoned".into()))?;
78
79        let (conn_id, outputs) = adapter.connect(remote)?;
80
81        // Queue outputs for transmission
82        for output in outputs {
83            let _ = self.outbound_tx.try_send(output);
84        }
85
86        Ok(conn_id)
87    }
88
89    /// Send data on an established connection
90    pub fn send(&self, connection_id: ConnectionId, data: &[u8]) -> Result<(), ConstrainedError> {
91        let mut adapter = self
92            .adapter
93            .lock()
94            .map_err(|_| ConstrainedError::Transport("adapter lock poisoned".into()))?;
95
96        let outputs = adapter.send(connection_id, data)?;
97
98        for output in outputs {
99            let _ = self.outbound_tx.try_send(output);
100        }
101
102        Ok(())
103    }
104
105    /// Receive data from a connection
106    pub fn recv(&self, connection_id: ConnectionId) -> Result<Option<Vec<u8>>, ConstrainedError> {
107        let mut adapter = self
108            .adapter
109            .lock()
110            .map_err(|_| ConstrainedError::Transport("adapter lock poisoned".into()))?;
111
112        Ok(adapter.recv(connection_id))
113    }
114
115    /// Close a connection
116    pub fn close(&self, connection_id: ConnectionId) -> Result<(), ConstrainedError> {
117        let mut adapter = self
118            .adapter
119            .lock()
120            .map_err(|_| ConstrainedError::Transport("adapter lock poisoned".into()))?;
121
122        let outputs = adapter.close(connection_id)?;
123
124        for output in outputs {
125            let _ = self.outbound_tx.try_send(output);
126        }
127
128        Ok(())
129    }
130
131    /// Get the number of active connections
132    pub fn connection_count(&self) -> usize {
133        self.adapter
134            .lock()
135            .map(|a| a.connection_count())
136            .unwrap_or(0)
137    }
138
139    /// Process an incoming packet
140    pub fn process_incoming(
141        &self,
142        source: &TransportAddr,
143        data: &[u8],
144    ) -> Result<(), ConstrainedError> {
145        let mut adapter = self
146            .adapter
147            .lock()
148            .map_err(|_| ConstrainedError::Transport("adapter lock poisoned".into()))?;
149
150        let outputs = adapter.process_incoming(source, data)?;
151
152        for output in outputs {
153            let _ = self.outbound_tx.try_send(output);
154        }
155
156        Ok(())
157    }
158
159    /// Poll for timeouts and get any pending outputs
160    pub fn poll(&self) -> Vec<EngineOutput> {
161        let mut adapter = match self.adapter.lock() {
162            Ok(a) => a,
163            Err(_) => return Vec::new(),
164        };
165
166        adapter.poll()
167    }
168
169    /// Get the next event from the engine
170    pub fn next_event(&self) -> Option<AdapterEvent> {
171        self.adapter.lock().ok().and_then(|mut a| a.next_event())
172    }
173
174    /// Get the state of a specific connection
175    pub fn connection_state(
176        &self,
177        connection_id: ConnectionId,
178    ) -> Option<crate::constrained::ConnectionState> {
179        self.adapter
180            .lock()
181            .ok()
182            .and_then(|a| a.connection_state(connection_id))
183    }
184
185    /// Get all active connection IDs
186    pub fn active_connections(&self) -> Vec<ConnectionId> {
187        self.adapter
188            .lock()
189            .ok()
190            .map(|a| a.active_connections())
191            .unwrap_or_default()
192    }
193}
194
195/// Constrained transport wrapper
196///
197/// Combines a constrained engine adapter with channels for packet I/O.
198/// This is designed to be integrated with a transport provider.
199pub struct ConstrainedTransport {
200    /// Shared adapter
201    adapter: Arc<Mutex<ConstrainedEngineAdapter>>,
202    /// Channel for outbound packets
203    outbound_tx: mpsc::Sender<EngineOutput>,
204    /// Receiver for outbound packets (to be consumed by transport)
205    outbound_rx: mpsc::Receiver<EngineOutput>,
206    /// Configuration
207    config: ConstrainedTransportConfig,
208}
209
210impl ConstrainedTransport {
211    /// Create a new constrained transport wrapper
212    pub fn new(config: ConstrainedTransportConfig) -> Self {
213        let (outbound_tx, outbound_rx) = mpsc::channel(config.outbound_buffer_size);
214        let adapter = ConstrainedEngineAdapter::new(config.engine_config.clone());
215
216        Self {
217            adapter: Arc::new(Mutex::new(adapter)),
218            outbound_tx,
219            outbound_rx,
220            config,
221        }
222    }
223
224    /// Create for BLE transport
225    pub fn for_ble() -> Self {
226        Self::new(ConstrainedTransportConfig::for_ble())
227    }
228
229    /// Create for LoRa transport
230    pub fn for_lora() -> Self {
231        Self::new(ConstrainedTransportConfig::for_lora())
232    }
233
234    /// Get a handle for sending/receiving data
235    pub fn handle(&self) -> ConstrainedHandle {
236        ConstrainedHandle {
237            adapter: Arc::clone(&self.adapter),
238            outbound_tx: self.outbound_tx.clone(),
239        }
240    }
241
242    /// Get the outbound packet receiver
243    ///
244    /// The transport provider should poll this to get packets to send.
245    pub fn take_outbound_rx(&mut self) -> mpsc::Receiver<EngineOutput> {
246        let (new_tx, new_rx) = mpsc::channel(self.config.outbound_buffer_size);
247
248        // Swap the sender and receiver
249        let _ = std::mem::replace(&mut self.outbound_tx, new_tx);
250        std::mem::replace(&mut self.outbound_rx, new_rx)
251    }
252
253    /// Process an incoming packet
254    pub fn process_incoming(
255        &self,
256        source: &TransportAddr,
257        data: &[u8],
258    ) -> Result<(), ConstrainedError> {
259        let mut adapter = self
260            .adapter
261            .lock()
262            .map_err(|_| ConstrainedError::Transport("adapter lock poisoned".into()))?;
263
264        let outputs = adapter.process_incoming(source, data)?;
265
266        for output in outputs {
267            let _ = self.outbound_tx.try_send(output);
268        }
269
270        Ok(())
271    }
272
273    /// Poll for timeouts and retransmissions
274    pub fn poll(&self) {
275        if let Ok(mut adapter) = self.adapter.lock() {
276            let outputs = adapter.poll();
277            for output in outputs {
278                let _ = self.outbound_tx.try_send(output);
279            }
280        }
281    }
282
283    /// Check if a transport should use the constrained engine
284    pub fn should_use_constrained(capabilities: &TransportCapabilities) -> bool {
285        !capabilities.supports_full_quic()
286    }
287}
288
289impl std::fmt::Debug for ConstrainedTransport {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        f.debug_struct("ConstrainedTransport")
292            .field("config", &self.config)
293            .field(
294                "connection_count",
295                &self
296                    .adapter
297                    .lock()
298                    .map(|a| a.connection_count())
299                    .unwrap_or(0),
300            )
301            .finish()
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn test_constrained_transport_creation() {
311        let transport = ConstrainedTransport::for_ble();
312        let handle = transport.handle();
313        assert_eq!(handle.connection_count(), 0);
314    }
315
316    #[test]
317    fn test_constrained_handle_connect() {
318        let transport = ConstrainedTransport::for_ble();
319        let handle = transport.handle();
320
321        let addr = TransportAddr::Ble {
322            device_id: [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF],
323            service_uuid: None,
324        };
325
326        let result = handle.connect(&addr);
327        assert!(result.is_ok());
328        assert_eq!(handle.connection_count(), 1);
329    }
330
331    #[test]
332    fn test_constrained_config_presets() {
333        let ble_config = ConstrainedTransportConfig::for_ble();
334        assert_eq!(ble_config.outbound_buffer_size, 32);
335
336        let lora_config = ConstrainedTransportConfig::for_lora();
337        assert_eq!(lora_config.outbound_buffer_size, 8);
338    }
339
340    #[test]
341    fn test_should_use_constrained() {
342        use crate::transport::TransportCapabilities;
343
344        // BLE should use constrained (MTU < 1200)
345        let ble_caps = TransportCapabilities::ble();
346        assert!(ConstrainedTransport::should_use_constrained(&ble_caps));
347
348        // LoRa should use constrained
349        let lora_caps = TransportCapabilities::lora_long_range();
350        assert!(ConstrainedTransport::should_use_constrained(&lora_caps));
351
352        // Broadband (UDP-like) should NOT use constrained
353        let broadband_caps = TransportCapabilities::broadband();
354        assert!(!ConstrainedTransport::should_use_constrained(
355            &broadband_caps
356        ));
357    }
358
359    #[tokio::test]
360    async fn test_handle_clone() {
361        let transport = ConstrainedTransport::for_ble();
362        let handle1 = transport.handle();
363        let handle2 = transport.handle();
364
365        // Both handles should see the same state
366        let addr = TransportAddr::Ble {
367            device_id: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
368            service_uuid: None,
369        };
370
371        let _ = handle1.connect(&addr);
372        assert_eq!(handle1.connection_count(), 1);
373        assert_eq!(handle2.connection_count(), 1);
374    }
375}