Skip to main content

ant_quic/constrained/
engine.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 Protocol Engine
9//!
10//! The main protocol engine that manages multiple connections over constrained transports.
11//! This integrates with the transport layer to provide reliable messaging over BLE, LoRa,
12//! and other low-bandwidth transports.
13
14use super::connection::{ConnectionConfig, ConnectionEvent, ConstrainedConnection};
15use super::header::ConstrainedPacket;
16use super::state::ConnectionState;
17use super::types::{ConnectionId, ConstrainedError};
18use std::collections::HashMap;
19use std::net::SocketAddr;
20use std::time::{Duration, Instant};
21
22/// Configuration for the constrained protocol engine
23#[derive(Debug, Clone)]
24pub struct EngineConfig {
25    /// Maximum number of concurrent connections
26    pub max_connections: usize,
27    /// Default connection configuration
28    pub connection_config: ConnectionConfig,
29    /// How often to poll connections for maintenance
30    pub poll_interval: Duration,
31    /// Enable connection reuse after TIME_WAIT
32    pub enable_connection_reuse: bool,
33}
34
35impl Default for EngineConfig {
36    fn default() -> Self {
37        Self {
38            max_connections: 8,
39            connection_config: ConnectionConfig::default(),
40            poll_interval: Duration::from_millis(100),
41            enable_connection_reuse: true,
42        }
43    }
44}
45
46impl EngineConfig {
47    /// Create configuration for BLE transport
48    pub fn for_ble() -> Self {
49        Self {
50            max_connections: 4,
51            connection_config: ConnectionConfig::for_ble(),
52            poll_interval: Duration::from_millis(50),
53            enable_connection_reuse: true,
54        }
55    }
56
57    /// Create configuration for LoRa transport
58    pub fn for_lora() -> Self {
59        Self {
60            max_connections: 2,
61            connection_config: ConnectionConfig::for_lora(),
62            poll_interval: Duration::from_millis(500),
63            enable_connection_reuse: true,
64        }
65    }
66}
67
68/// Events from the engine
69#[derive(Debug, Clone)]
70pub enum EngineEvent {
71    /// New incoming connection accepted
72    ConnectionAccepted {
73        /// Connection ID
74        connection_id: ConnectionId,
75        /// Remote address
76        remote_addr: SocketAddr,
77    },
78    /// Outbound connection established
79    ConnectionEstablished {
80        /// Connection ID
81        connection_id: ConnectionId,
82    },
83    /// Data received on a connection
84    DataReceived {
85        /// Connection ID
86        connection_id: ConnectionId,
87        /// The data
88        data: Vec<u8>,
89    },
90    /// Connection closed
91    ConnectionClosed {
92        /// Connection ID
93        connection_id: ConnectionId,
94    },
95    /// Connection error
96    ConnectionError {
97        /// Connection ID
98        connection_id: ConnectionId,
99        /// Error message
100        error: String,
101    },
102    /// Packet ready to transmit
103    Transmit {
104        /// Destination address
105        remote_addr: SocketAddr,
106        /// Packet data
107        packet: Vec<u8>,
108    },
109}
110
111/// The constrained protocol engine
112///
113/// Manages multiple connections and provides a simple API for sending/receiving data.
114#[derive(Debug)]
115pub struct ConstrainedEngine {
116    /// Configuration
117    config: EngineConfig,
118    /// Active connections by ID
119    connections: HashMap<ConnectionId, ConstrainedConnection>,
120    /// Connection ID to remote address mapping
121    addr_to_conn: HashMap<SocketAddr, ConnectionId>,
122    /// Pending events
123    events: Vec<EngineEvent>,
124    /// Next connection ID to use
125    next_conn_id: u16,
126    /// Last poll time
127    last_poll: Instant,
128}
129
130impl ConstrainedEngine {
131    /// Create a new constrained protocol engine
132    pub fn new(config: EngineConfig) -> Self {
133        Self {
134            config,
135            connections: HashMap::new(),
136            addr_to_conn: HashMap::new(),
137            events: Vec::new(),
138            next_conn_id: 1,
139            last_poll: Instant::now(),
140        }
141    }
142
143    /// Create with default configuration
144    pub fn with_defaults() -> Self {
145        Self::new(EngineConfig::default())
146    }
147
148    /// Number of active connections
149    pub fn connection_count(&self) -> usize {
150        self.connections.len()
151    }
152
153    /// Check if we can accept more connections
154    pub fn can_accept_connection(&self) -> bool {
155        self.connections.len() < self.config.max_connections
156    }
157
158    /// Generate a new connection ID
159    fn generate_conn_id(&mut self) -> ConnectionId {
160        let id = ConnectionId::new(self.next_conn_id);
161        self.next_conn_id = self.next_conn_id.wrapping_add(1);
162        if self.next_conn_id == 0 {
163            self.next_conn_id = 1;
164        }
165        id
166    }
167
168    /// Initiate a connection to a remote address
169    ///
170    /// Returns the connection ID and a SYN packet to transmit.
171    pub fn connect(
172        &mut self,
173        remote_addr: SocketAddr,
174    ) -> Result<(ConnectionId, Vec<u8>), ConstrainedError> {
175        if !self.can_accept_connection() {
176            return Err(ConstrainedError::SendBufferFull);
177        }
178
179        // Check if we already have a connection to this address
180        if self.addr_to_conn.contains_key(&remote_addr) {
181            return Err(ConstrainedError::ConnectionExists(
182                *self
183                    .addr_to_conn
184                    .get(&remote_addr)
185                    .unwrap_or(&ConnectionId::new(0)),
186            ));
187        }
188
189        let conn_id = self.generate_conn_id();
190        let mut conn = ConstrainedConnection::new_outbound_with_config(
191            conn_id,
192            remote_addr,
193            self.config.connection_config.clone(),
194        );
195
196        let syn_packet = conn.initiate()?;
197        let packet_bytes = syn_packet.to_bytes();
198
199        self.connections.insert(conn_id, conn);
200        self.addr_to_conn.insert(remote_addr, conn_id);
201
202        Ok((conn_id, packet_bytes))
203    }
204
205    /// Process an incoming packet
206    ///
207    /// Returns any response packets that need to be transmitted.
208    pub fn process_incoming(
209        &mut self,
210        remote_addr: SocketAddr,
211        data: &[u8],
212    ) -> Result<Vec<(SocketAddr, Vec<u8>)>, ConstrainedError> {
213        let packet = ConstrainedPacket::from_bytes(data)?;
214        let header = &packet.header;
215        let mut responses = Vec::new();
216
217        // Check if this is for an existing connection
218        if let Some(conn) = self.connections.get_mut(&header.connection_id) {
219            conn.process_packet(&packet)?;
220
221            // Collect events from the connection
222            while let Some(event) = conn.next_event() {
223                match event {
224                    ConnectionEvent::Connected => {
225                        self.events.push(EngineEvent::ConnectionEstablished {
226                            connection_id: header.connection_id,
227                        });
228                    }
229                    ConnectionEvent::DataReceived(_) => {
230                        // Data is retrieved separately via recv()
231                    }
232                    ConnectionEvent::Closed => {
233                        self.events.push(EngineEvent::ConnectionClosed {
234                            connection_id: header.connection_id,
235                        });
236                    }
237                    ConnectionEvent::Reset => {
238                        self.events.push(EngineEvent::ConnectionClosed {
239                            connection_id: header.connection_id,
240                        });
241                    }
242                    ConnectionEvent::Error(err) => {
243                        self.events.push(EngineEvent::ConnectionError {
244                            connection_id: header.connection_id,
245                            error: err,
246                        });
247                    }
248                    ConnectionEvent::Transmit(data) => {
249                        responses.push((remote_addr, data));
250                    }
251                }
252            }
253
254            // Poll the connection for any outbound packets
255            let packets = conn.poll();
256            for pkt in packets {
257                responses.push((remote_addr, pkt.to_bytes()));
258            }
259        } else if header.is_syn() && !header.is_ack() {
260            // New incoming connection
261            if !self.can_accept_connection() {
262                // Send RST
263                let rst = super::header::ConstrainedHeader::reset(header.connection_id);
264                responses.push((
265                    remote_addr,
266                    super::header::ConstrainedPacket::control(rst).to_bytes(),
267                ));
268                return Ok(responses);
269            }
270
271            let mut conn = ConstrainedConnection::new_inbound_with_config(
272                header.connection_id,
273                remote_addr,
274                self.config.connection_config.clone(),
275            );
276
277            let syn_ack = conn.accept(header.seq)?;
278            responses.push((remote_addr, syn_ack.to_bytes()));
279
280            self.connections.insert(header.connection_id, conn);
281            self.addr_to_conn.insert(remote_addr, header.connection_id);
282
283            self.events.push(EngineEvent::ConnectionAccepted {
284                connection_id: header.connection_id,
285                remote_addr,
286            });
287        }
288        // Otherwise, packet for unknown connection - ignore
289
290        Ok(responses)
291    }
292
293    /// Send data on a connection
294    pub fn send(
295        &mut self,
296        connection_id: ConnectionId,
297        data: &[u8],
298    ) -> Result<Vec<(SocketAddr, Vec<u8>)>, ConstrainedError> {
299        let conn = self
300            .connections
301            .get_mut(&connection_id)
302            .ok_or(ConstrainedError::ConnectionNotFound(connection_id))?;
303
304        conn.send(data)?;
305
306        let remote_addr = conn.remote_addr();
307        let packets = conn.poll();
308
309        Ok(packets
310            .into_iter()
311            .map(|p| (remote_addr, p.to_bytes()))
312            .collect())
313    }
314
315    /// Receive data from a connection
316    pub fn recv(&mut self, connection_id: ConnectionId) -> Option<Vec<u8>> {
317        self.connections.get_mut(&connection_id)?.recv()
318    }
319
320    /// Close a connection gracefully
321    pub fn close(
322        &mut self,
323        connection_id: ConnectionId,
324    ) -> Result<Vec<(SocketAddr, Vec<u8>)>, ConstrainedError> {
325        let conn = self
326            .connections
327            .get_mut(&connection_id)
328            .ok_or(ConstrainedError::ConnectionNotFound(connection_id))?;
329
330        let fin = conn.close()?;
331        let remote_addr = conn.remote_addr();
332
333        Ok(vec![(remote_addr, fin.to_bytes())])
334    }
335
336    /// Reset a connection immediately
337    pub fn reset(
338        &mut self,
339        connection_id: ConnectionId,
340    ) -> Result<Vec<(SocketAddr, Vec<u8>)>, ConstrainedError> {
341        let conn = self
342            .connections
343            .get_mut(&connection_id)
344            .ok_or(ConstrainedError::ConnectionNotFound(connection_id))?;
345
346        let rst = conn.reset();
347        let remote_addr = conn.remote_addr();
348
349        // Remove the connection immediately
350        self.connections.remove(&connection_id);
351        self.addr_to_conn.retain(|_, id| *id != connection_id);
352
353        Ok(vec![(remote_addr, rst.to_bytes())])
354    }
355
356    /// Poll the engine for maintenance tasks
357    ///
358    /// This should be called periodically. Returns packets that need to be transmitted.
359    pub fn poll(&mut self) -> Vec<(SocketAddr, Vec<u8>)> {
360        let now = Instant::now();
361        if now.duration_since(self.last_poll) < self.config.poll_interval {
362            return Vec::new();
363        }
364        self.last_poll = now;
365
366        let mut responses = Vec::new();
367        let mut to_remove = Vec::new();
368
369        for (conn_id, conn) in &mut self.connections {
370            // Poll connection for retransmissions and keepalives
371            let packets = conn.poll();
372            let remote_addr = conn.remote_addr();
373
374            for pkt in packets {
375                responses.push((remote_addr, pkt.to_bytes()));
376            }
377
378            // Check for events
379            while let Some(event) = conn.next_event() {
380                match event {
381                    ConnectionEvent::Closed | ConnectionEvent::Reset => {
382                        to_remove.push(*conn_id);
383                        self.events.push(EngineEvent::ConnectionClosed {
384                            connection_id: *conn_id,
385                        });
386                    }
387                    ConnectionEvent::Error(err) => {
388                        to_remove.push(*conn_id);
389                        self.events.push(EngineEvent::ConnectionError {
390                            connection_id: *conn_id,
391                            error: err,
392                        });
393                    }
394                    _ => {}
395                }
396            }
397
398            // Check if connection should be cleaned up
399            if conn.is_closed() {
400                to_remove.push(*conn_id);
401            }
402        }
403
404        // Clean up closed connections
405        for conn_id in to_remove {
406            if let Some(conn) = self.connections.remove(&conn_id) {
407                self.addr_to_conn.remove(&conn.remote_addr());
408            }
409        }
410
411        responses
412    }
413
414    /// Get next pending event
415    pub fn next_event(&mut self) -> Option<EngineEvent> {
416        if self.events.is_empty() {
417            None
418        } else {
419            Some(self.events.remove(0))
420        }
421    }
422
423    /// Check if a connection exists
424    pub fn has_connection(&self, connection_id: ConnectionId) -> bool {
425        self.connections.contains_key(&connection_id)
426    }
427
428    /// Get connection by remote address
429    pub fn connection_for_addr(&self, addr: &SocketAddr) -> Option<ConnectionId> {
430        self.addr_to_conn.get(addr).copied()
431    }
432
433    /// Get list of active connection IDs
434    pub fn active_connections(&self) -> Vec<ConnectionId> {
435        self.connections.keys().copied().collect()
436    }
437
438    /// Get the state of a specific connection
439    pub fn connection_state(&self, connection_id: ConnectionId) -> Option<ConnectionState> {
440        self.connections.get(&connection_id).map(|c| c.state())
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use std::net::{IpAddr, Ipv4Addr};
448
449    fn test_addr(port: u16) -> SocketAddr {
450        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)
451    }
452
453    #[test]
454    fn test_engine_new() {
455        let engine = ConstrainedEngine::with_defaults();
456        assert_eq!(engine.connection_count(), 0);
457        assert!(engine.can_accept_connection());
458    }
459
460    #[test]
461    fn test_engine_connect() {
462        let mut engine = ConstrainedEngine::with_defaults();
463        let (conn_id, packet) = engine.connect(test_addr(8080)).expect("connect");
464
465        assert_eq!(engine.connection_count(), 1);
466        assert!(engine.has_connection(conn_id));
467        assert!(!packet.is_empty());
468
469        // Verify it's a SYN packet
470        let pkt = ConstrainedPacket::from_bytes(&packet).expect("parse");
471        assert!(pkt.header.is_syn());
472        assert!(!pkt.header.is_ack());
473    }
474
475    #[test]
476    fn test_engine_connect_duplicate() {
477        let mut engine = ConstrainedEngine::with_defaults();
478        let addr = test_addr(8080);
479
480        engine.connect(addr).expect("first connect");
481        let result = engine.connect(addr);
482
483        assert!(result.is_err());
484    }
485
486    #[test]
487    fn test_engine_max_connections() {
488        let config = EngineConfig {
489            max_connections: 2,
490            ..Default::default()
491        };
492        let mut engine = ConstrainedEngine::new(config);
493
494        engine.connect(test_addr(8080)).expect("connect 1");
495        engine.connect(test_addr(8081)).expect("connect 2");
496
497        // Third should fail
498        let result = engine.connect(test_addr(8082));
499        assert!(result.is_err());
500    }
501
502    #[test]
503    fn test_engine_accept_connection() {
504        let mut engine = ConstrainedEngine::with_defaults();
505
506        // Create a SYN packet
507        let syn = ConstrainedPacket::control(super::super::header::ConstrainedHeader::syn(
508            ConnectionId::new(0x1234),
509        ));
510
511        let responses = engine
512            .process_incoming(test_addr(8080), &syn.to_bytes())
513            .expect("process SYN");
514
515        // Should have a SYN-ACK response
516        assert_eq!(responses.len(), 1);
517        let syn_ack = ConstrainedPacket::from_bytes(&responses[0].1).expect("parse");
518        assert!(syn_ack.header.is_syn_ack());
519
520        // Check event
521        let event = engine.next_event();
522        assert!(matches!(
523            event,
524            Some(EngineEvent::ConnectionAccepted { .. })
525        ));
526    }
527
528    #[test]
529    fn test_engine_handshake() {
530        let mut initiator = ConstrainedEngine::with_defaults();
531        let mut responder = ConstrainedEngine::with_defaults();
532
533        let initiator_addr = test_addr(8080);
534        let responder_addr = test_addr(9090);
535
536        // Initiator sends SYN
537        let (conn_id, syn_packet) = initiator.connect(responder_addr).expect("connect");
538
539        // Responder receives SYN, sends SYN-ACK
540        let responses = responder
541            .process_incoming(initiator_addr, &syn_packet)
542            .expect("process SYN");
543        assert_eq!(responses.len(), 1);
544
545        // Initiator receives SYN-ACK
546        let responses = initiator
547            .process_incoming(responder_addr, &responses[0].1)
548            .expect("process SYN-ACK");
549
550        // Should have ACK response (from poll)
551        assert!(!responses.is_empty());
552
553        // Check initiator got connected event
554        let event = initiator.next_event();
555        assert!(
556            matches!(event, Some(EngineEvent::ConnectionEstablished { connection_id }) if connection_id == conn_id)
557        );
558    }
559
560    #[test]
561    fn test_engine_config_for_ble() {
562        let config = EngineConfig::for_ble();
563        assert_eq!(config.max_connections, 4);
564        assert_eq!(config.connection_config.mss, 235);
565    }
566
567    #[test]
568    fn test_engine_config_for_lora() {
569        let config = EngineConfig::for_lora();
570        assert_eq!(config.max_connections, 2);
571        assert_eq!(config.connection_config.mss, 50);
572    }
573
574    #[test]
575    fn test_engine_close_not_found() {
576        let mut engine = ConstrainedEngine::with_defaults();
577
578        // Try to close a non-existent connection
579        let result = engine.close(ConnectionId::new(0x9999));
580        assert!(result.is_err());
581        assert!(matches!(
582            result,
583            Err(ConstrainedError::ConnectionNotFound(_))
584        ));
585    }
586
587    #[test]
588    fn test_engine_reset() {
589        let mut engine = ConstrainedEngine::with_defaults();
590        let (conn_id, _) = engine.connect(test_addr(8080)).expect("connect");
591
592        let responses = engine.reset(conn_id).expect("reset");
593
594        assert_eq!(responses.len(), 1);
595        let rst = ConstrainedPacket::from_bytes(&responses[0].1).expect("parse");
596        assert!(rst.header.is_rst());
597
598        // Connection should be removed
599        assert!(!engine.has_connection(conn_id));
600    }
601
602    #[test]
603    fn test_engine_poll() {
604        let mut engine = ConstrainedEngine::with_defaults();
605        engine.connect(test_addr(8080)).expect("connect");
606
607        // Poll should work without panicking
608        let _ = engine.poll();
609    }
610
611    #[test]
612    fn test_engine_active_connections() {
613        let mut engine = ConstrainedEngine::with_defaults();
614        let (id1, _) = engine.connect(test_addr(8080)).expect("connect 1");
615        let (id2, _) = engine.connect(test_addr(8081)).expect("connect 2");
616
617        let active = engine.active_connections();
618        assert_eq!(active.len(), 2);
619        assert!(active.contains(&id1));
620        assert!(active.contains(&id2));
621    }
622}