Skip to main content

mavlink_core/connection/tcp/
config.rs

1use core::fmt::Display;
2use std::io;
3use std::net::{TcpListener, TcpStream};
4use std::sync::{Arc, Mutex};
5
6/// Type of TCP connection
7#[derive(Debug, Clone, Copy)]
8pub enum TcpMode {
9    /// Connection will open a TCP server that binds to the provided address
10    TcpIn,
11    /// Connection will connect to the provided TCP server address
12    TcpOut,
13}
14
15/// MAVLink connection address for a TCP server or client
16///
17/// # Example
18///
19/// ```ignore
20/// use mavlink::{Connectable, TcpConfig, TcpMode};
21///
22/// let config = TcpConfig::new("0.0.0.0:14551".to_owned(), false);
23/// config.connect::<mavlink::ardupilotmega::MavMessage>();
24/// ```
25#[derive(Debug, Clone)]
26pub struct TcpConfig {
27    pub(crate) address: String,
28    pub(crate) mode: TcpMode,
29    pub(crate) source: TcpSource,
30}
31
32#[derive(Debug, Clone)]
33pub(crate) enum TcpSource {
34    Address,
35    Stream(Arc<Mutex<Option<TcpStream>>>),
36    Listener(Arc<Mutex<Option<TcpListener>>>),
37}
38
39impl TcpConfig {
40    /// Creates a TCP connection address.
41    pub fn new(address: String, mode: TcpMode) -> Self {
42        Self {
43            address,
44            mode,
45            source: TcpSource::Address,
46        }
47    }
48
49    /// Creates a TCP client configuration from an already connected stream.
50    ///
51    /// Socket-backed configurations are one-shot, including across clones.
52    pub fn from_stream(stream: TcpStream) -> io::Result<Self> {
53        let address = stream.peer_addr()?.to_string();
54        Ok(Self {
55            address,
56            mode: TcpMode::TcpOut,
57            source: TcpSource::Stream(Arc::new(Mutex::new(Some(stream)))),
58        })
59    }
60
61    /// Creates a TCP server configuration from an already bound listener.
62    ///
63    /// Socket-backed configurations are one-shot, including across clones.
64    pub fn from_listener(listener: TcpListener) -> io::Result<Self> {
65        let address = listener.local_addr()?.to_string();
66        Ok(Self {
67            address,
68            mode: TcpMode::TcpIn,
69            source: TcpSource::Listener(Arc::new(Mutex::new(Some(listener)))),
70        })
71    }
72
73    pub(crate) fn take_stream(&self) -> io::Result<Option<TcpStream>> {
74        match &self.source {
75            TcpSource::Address => Ok(None),
76            TcpSource::Stream(stream) => stream
77                .lock()
78                .map_err(|_| io::Error::other("TCP stream lock poisoned"))?
79                .take()
80                .map(Some)
81                .ok_or_else(|| io::Error::other("TCP socket-backed configuration already used")),
82            TcpSource::Listener(_) => Err(io::Error::new(
83                io::ErrorKind::InvalidInput,
84                "TCP listener cannot be used as an outgoing connection",
85            )),
86        }
87    }
88
89    pub(crate) fn take_listener(&self) -> io::Result<Option<TcpListener>> {
90        match &self.source {
91            TcpSource::Address => Ok(None),
92            TcpSource::Listener(listener) => listener
93                .lock()
94                .map_err(|_| io::Error::other("TCP listener lock poisoned"))?
95                .take()
96                .map(Some)
97                .ok_or_else(|| io::Error::other("TCP socket-backed configuration already used")),
98            TcpSource::Stream(_) => Err(io::Error::new(
99                io::ErrorKind::InvalidInput,
100                "TCP stream cannot be used as an incoming connection",
101            )),
102        }
103    }
104}
105impl Display for TcpConfig {
106    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        match self.mode {
108            TcpMode::TcpIn => write!(f, "tcpin:{}", self.address),
109            TcpMode::TcpOut => write!(f, "tcpout:{}", self.address),
110        }
111    }
112}