nrf_modem/
tls_stream.rs

1use crate::{
2    dns,
3    error::Error,
4    socket::{
5        CipherSuite, PeerVerification, Socket, SocketFamily, SocketOption, SocketProtocol,
6        SocketType, SplitSocketHandle,
7    },
8    CancellationToken, LteLink,
9};
10use core::net::SocketAddr;
11
12pub struct TlsStream {
13    inner: Socket,
14}
15
16macro_rules! impl_receive {
17    () => {
18        /// Try fill the given buffer with the data that has been received. The written part of the
19        /// buffer is returned.
20        pub async fn receive<'buf>(&self, buf: &'buf mut [u8]) -> Result<&'buf mut [u8], Error> {
21            self.receive_with_cancellation(buf, &Default::default())
22                .await
23        }
24
25        /// Try fill the given buffer with the data that has been received. The written part of the
26        /// buffer is returned.
27        pub async fn receive_with_cancellation<'buf>(
28            &self,
29            buf: &'buf mut [u8],
30            token: &CancellationToken,
31        ) -> Result<&'buf mut [u8], Error> {
32            let max_receive_len = 1024.min(buf.len());
33            let received_bytes = self
34                .socket()
35                .receive(&mut buf[..max_receive_len], token)
36                .await?;
37            Ok(&mut buf[..received_bytes])
38        }
39
40        /// Fill the entire buffer with data that has been received. This will wait as long as necessary to fill up the
41        /// buffer.
42        ///
43        /// If there's an error while receiving, then the error is returned as well as the part of the buffer that was
44        /// partially filled with received data.
45        pub async fn receive_exact<'buf>(
46            &self,
47            buf: &'buf mut [u8],
48        ) -> Result<(), (Error, &'buf mut [u8])> {
49            self.receive_exact_with_cancellation(buf, &Default::default())
50                .await
51        }
52
53        /// Fill the entire buffer with data that has been received. This will wait as long as necessary to fill up the
54        /// buffer.
55        ///
56        /// If there's an error while receiving, then the error is returned as well as the part of the buffer that was
57        /// partially filled with received data.
58        pub async fn receive_exact_with_cancellation<'buf>(
59            &self,
60            buf: &'buf mut [u8],
61            token: &CancellationToken,
62        ) -> Result<(), (Error, &'buf mut [u8])> {
63            let mut received_bytes = 0;
64
65            while received_bytes < buf.len() {
66                match self
67                    .receive_with_cancellation(&mut buf[received_bytes..], token)
68                    .await
69                {
70                    Ok(received_data) => received_bytes += received_data.len(),
71                    Err(e) => return Err((e.into(), &mut buf[..received_bytes])),
72                }
73            }
74
75            Ok(())
76        }
77    };
78}
79
80macro_rules! impl_write {
81    () => {
82        /// Write the entire buffer to the stream
83        pub async fn write(&self, buf: &[u8]) -> Result<(), Error> {
84            self.write_with_cancellation(buf, &Default::default()).await
85        }
86
87        /// Write the entire buffer to the stream
88        pub async fn write_with_cancellation(
89            &self,
90            buf: &[u8],
91            token: &CancellationToken,
92        ) -> Result<(), Error> {
93            let mut written_bytes = 0;
94
95            while written_bytes < buf.len() {
96                // We can't write very huge chunks because then the socket can't process it all at once
97                let max_write_len = 1024.min(buf.len() - written_bytes);
98                written_bytes += self
99                    .socket()
100                    .write(&buf[written_bytes..][..max_write_len], token)
101                    .await?;
102            }
103
104            Ok(())
105        }
106    };
107}
108
109impl TlsStream {
110    /// Connect an encrypted TCP stream to the given address
111    ///
112    /// This function attempts to connect to the given `hostname` and `port` using the specified
113    /// security parameters.
114    ///
115    /// - `hostname`: The hostname of the server to connect to.
116    /// - `port`: The port number of the server to connect to.
117    /// - `peer_verify`: The peer verification policy to apply. Determines how the connection verifies the server's identity.
118    /// - `security_tags`: A slice of [security tag](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/libraries/modem/modem_key_mgmt.html) identifiers containing security elements.
119    /// - `ciphers`: An optional slice of IANA cipher suite identifiers to use for the connection. If `None`, the default set of ciphers is used.
120    /// - `resume_sessions`: Enable TLS session tickets, managed by the modem.
121    pub async fn connect(
122        hostname: &str,
123        port: u16,
124        peer_verify: PeerVerification,
125        security_tags: &[u32],
126        ciphers: Option<&[CipherSuite]>,
127        resume_sessions: bool,
128    ) -> Result<Self, Error> {
129        Self::connect_with_cancellation(
130            hostname,
131            port,
132            peer_verify,
133            security_tags,
134            ciphers,
135            resume_sessions,
136            &Default::default(),
137        )
138        .await
139    }
140
141    /// Connect an encrypted TCP stream to the given address
142    ///
143    /// This function attempts to connect to the given `hostname` and `port` using the specified
144    /// security parameters.
145    ///
146    /// - `hostname`: The hostname of the server to connect to.
147    /// - `port`: The port number of the server to connect to.
148    /// - `peer_verify`: The peer verification policy to apply. Determines how the connection verifies the server's identity.
149    /// - `security_tags`: A slice of [security tag](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/libraries/modem/modem_key_mgmt.html) identifiers containing security elements.
150    /// - `ciphers`: An optional slice of IANA cipher suite identifiers to use for the connection. If `None`, the default set of ciphers is used.
151    /// - `resume_sessions`: Enable TLS session tickets, managed by the modem.
152    /// - `token`: A [`CancellationToken`] that can be used to cancel the connection attempt.
153    pub async fn connect_with_cancellation(
154        hostname: &str,
155        port: u16,
156        peer_verify: PeerVerification,
157        security_tags: &[u32],
158        ciphers: Option<&[CipherSuite]>,
159        resume_sessions: bool,
160        token: &CancellationToken,
161    ) -> Result<Self, Error> {
162        if security_tags.is_empty() {
163            return Err(Error::NoSecurityTag);
164        }
165
166        let lte_link = LteLink::new().await?;
167
168        let ip = dns::get_host_by_name_with_cancellation(hostname, token).await?;
169        let addr = SocketAddr::from((ip, port));
170
171        token.as_result()?;
172
173        let family = match addr {
174            SocketAddr::V4(_) => SocketFamily::Ipv4,
175            SocketAddr::V6(_) => SocketFamily::Ipv6,
176        };
177
178        let socket = Socket::create(family, SocketType::Stream, SocketProtocol::Tls1v2).await?;
179        socket.set_option(SocketOption::TlsPeerVerify(peer_verify.as_integer()))?;
180        socket.set_option(SocketOption::TlsSessionCache(resume_sessions as _))?;
181        socket.set_option(SocketOption::TlsTagList(security_tags))?;
182        socket.set_option(SocketOption::TlsHostName(hostname))?;
183        if let Some(ciphers) = ciphers {
184            socket.set_option(SocketOption::TlsCipherSuiteList(unsafe {
185                core::slice::from_raw_parts(ciphers.as_ptr() as *const i32, ciphers.len())
186            }))?;
187        }
188
189        match unsafe { socket.connect(addr, token).await } {
190            Ok(_) => {
191                lte_link.deactivate().await?;
192                Ok(TlsStream { inner: socket })
193            }
194            Err(e) => {
195                socket.deactivate().await?;
196                lte_link.deactivate().await?;
197                Err(e)
198            }
199        }
200    }
201
202    /// Get the raw underlying file descriptor for when you need to interact with the nrf libraries directly
203    pub fn as_raw_fd(&self) -> i32 {
204        self.inner.as_raw_fd()
205    }
206
207    fn socket(&self) -> &Socket {
208        &self.inner
209    }
210
211    /// Split the stream into an owned read and write half
212    pub async fn split_owned(self) -> Result<(OwnedTlsReadStream, OwnedTlsWriteStream), Error> {
213        let (read_split, write_split) = self.inner.split().await?;
214
215        Ok((
216            OwnedTlsReadStream { stream: read_split },
217            OwnedTlsWriteStream {
218                stream: write_split,
219            },
220        ))
221    }
222
223    /// Split the stream into an owned read and write half
224    pub fn split(&self) -> (TlsReadStream<'_>, TlsWriteStream<'_>) {
225        (
226            TlsReadStream { socket: self },
227            TlsWriteStream { socket: self },
228        )
229    }
230
231    impl_receive!();
232    impl_write!();
233
234    /// Deactivates the socket and the LTE link.
235    /// A normal drop will do the same thing, but blocking.
236    pub async fn deactivate(self) -> Result<(), Error> {
237        self.inner.deactivate().await?;
238        Ok(())
239    }
240}
241
242crate::embedded_io_macros::impl_error_trait!(TlsStream, Error, <>);
243crate::embedded_io_macros::impl_read_trait!(TlsStream, <>);
244crate::embedded_io_macros::impl_write_trait!(TlsStream, <>);
245
246/// A borrowed read half of an encrypted TCP stream
247pub struct TlsReadStream<'a> {
248    socket: &'a TlsStream,
249}
250
251impl TlsReadStream<'_> {
252    fn socket(&self) -> &Socket {
253        &self.socket.inner
254    }
255
256    impl_receive!();
257}
258
259crate::embedded_io_macros::impl_error_trait!(TlsReadStream<'a>, Error, <'a>);
260crate::embedded_io_macros::impl_read_trait!(TlsReadStream<'a>, <'a>);
261
262/// A borrowed write half of an encrypted TCP stream
263pub struct TlsWriteStream<'a> {
264    socket: &'a TlsStream,
265}
266
267impl TlsWriteStream<'_> {
268    fn socket(&self) -> &Socket {
269        &self.socket.inner
270    }
271
272    impl_write!();
273}
274
275crate::embedded_io_macros::impl_error_trait!(TlsWriteStream<'a>, Error, <'a>);
276crate::embedded_io_macros::impl_write_trait!(TlsWriteStream<'a>, <'a>);
277
278/// An owned read half of an acrypted TCP stream
279pub struct OwnedTlsReadStream {
280    stream: SplitSocketHandle,
281}
282
283impl OwnedTlsReadStream {
284    fn socket(&self) -> &Socket {
285        &self.stream
286    }
287
288    impl_receive!();
289
290    /// Deactivates the socket and the LTE link.
291    /// A normal drop will do the same thing, but blocking.
292    pub async fn deactivate(self) -> Result<(), Error> {
293        self.stream.deactivate().await?;
294        Ok(())
295    }
296}
297
298crate::embedded_io_macros::impl_error_trait!(OwnedTlsReadStream, Error, <>);
299crate::embedded_io_macros::impl_read_trait!(OwnedTlsReadStream, <>);
300
301/// An owned write half of an encrypted TCP stream
302pub struct OwnedTlsWriteStream {
303    stream: SplitSocketHandle,
304}
305
306impl OwnedTlsWriteStream {
307    fn socket(&self) -> &Socket {
308        &self.stream
309    }
310
311    impl_write!();
312
313    /// Deactivates the socket and the LTE link.
314    /// A normal drop will do the same thing, but blocking.
315    pub async fn deactivate(self) -> Result<(), Error> {
316        self.stream.deactivate().await?;
317        Ok(())
318    }
319}
320
321crate::embedded_io_macros::impl_error_trait!(OwnedTlsWriteStream, Error, <>);
322crate::embedded_io_macros::impl_write_trait!(OwnedTlsWriteStream, <>);