Skip to main content

mbedtls_rs/session/
asynch.rs

1use core::ffi::{c_int, c_uchar, c_void, CStr};
2use core::future::{poll_fn, Future};
3use core::pin::pin;
4use core::ptr::NonNull;
5use core::task::{Context, Poll};
6
7use embedded_io::ErrorKind;
8
9use io::{ErrorType, Read, Write};
10
11use crate::sys::*;
12use crate::{SessionError, TlsReference};
13
14use super::{
15    check_saved_session_server_name, SavedSession, ServerName, SessionConfig, SessionState,
16};
17
18/// Re-export of the `embedded-io-async` crate so that users don't have to explicitly depend on it
19/// to use e.g. `write_all` or `read_exact`.
20pub mod io {
21    pub use embedded_io_async::*;
22}
23
24/// An async TLS session over a stream represented by `embedded-io-async`'s `Read` and `Write` traits.
25pub struct Session<'a, T>
26where
27    T: Read + Write,
28{
29    /// The underlying stream
30    stream: T,
31    /// The session state
32    state: SessionState<'a>,
33    /// Whether the session is connected
34    connected: bool,
35    /// Whether we received a close notify from the peer
36    eof: bool,
37    /// A state necessary so as to implement `MBio::readable`
38    read_byte: Option<u8>,
39    /// A state necessary so as to implement `MBio::writable`
40    write_byte: Option<u8>,
41    /// `true` while MbedTLS holds an undrained outgoing record (it returned
42    /// `WANT_WRITE` and a matching `mbedtls_ssl_write` has not yet returned
43    /// `>= 0`). If a `write` future is dropped at that point, this survives so
44    /// the next `write`/`flush`/`close` can flush the pending record before
45    /// doing anything else - otherwise a later `write` with a different buffer
46    /// would flush the old record but report the new buffer's length.
47    write_in_flight: bool,
48    /// Reference to the active Tls instance
49    _token: TlsReference<'a>,
50}
51
52impl<'a, T> Session<'a, T>
53where
54    T: Read + Write,
55{
56    /// Create a session for a TLS stream.
57    ///
58    /// # Arguments
59    /// - `tls` - A reference to the active `Tls` instance.
60    /// - `stream` - The stream for the connection, implementing `Read` and `Write`.
61    /// - `config`` - The session configuration
62    ///
63    /// # Returns
64    /// - A `Session` instance or a `TlsError` on failure.
65    pub fn new(
66        tls: TlsReference<'a>,
67        stream: T,
68        config: &SessionConfig<'a>,
69    ) -> Result<Self, SessionError> {
70        Ok(Self {
71            stream,
72            state: SessionState::new(config)?,
73            connected: false,
74            eof: false,
75            read_byte: None,
76            write_byte: None,
77            write_in_flight: false,
78            _token: tls,
79        })
80    }
81
82    /// Get the TLS verification details
83    ///
84    /// The details are a bitmask of various flags indicating the result of the certificate verification.
85    ///
86    /// # Returns
87    /// - 0 if verification succeeded
88    /// - A bitmask of verification failure flags otherwise
89    ///
90    /// NOTE: This function should be called only after a `connect()` call.
91    pub fn tls_verification_details(&self) -> u32 {
92        unsafe { mbedtls_ssl_get_verify_result(&*self.state.ssl_context) }
93    }
94
95    /// Get the negotiated ALPN protocol, if any.
96    ///
97    /// NOTE: This function should be called only after a `connect()` call.
98    pub fn tls_alpn(&self) -> Option<&CStr> {
99        unsafe {
100            let ptr = mbedtls_ssl_get_alpn_protocol(&*self.state.ssl_context);
101            if ptr.is_null() {
102                None
103            } else {
104                Some(CStr::from_ptr(ptr))
105            }
106        }
107    }
108
109    /// Get a mutable reference to the underlying stream
110    pub fn stream(&mut self) -> &mut T {
111        &mut self.stream
112    }
113
114    /// Set the server name for the TLS connection.
115    ///
116    /// Must be called before the handshake is triggered (by `connect`,
117    /// `connect_with_session`, `read`, `write`, or `split`); changing the server
118    /// name on an already-connected session is rejected, because the saved
119    /// session would otherwise be bound to a name that was not used to negotiate
120    /// it.
121    ///
122    /// # Arguments
123    /// - `server_name`: The server name as a C string
124    pub fn set_server_name(&mut self, server_name: &CStr) -> Result<(), SessionError> {
125        if self.connected {
126            return Err(SessionError::MbedTls(MbedtlsError::new(
127                MBEDTLS_ERR_SSL_BAD_INPUT_DATA,
128            )));
129        }
130
131        merr!(unsafe {
132            mbedtls_ssl_set_hostname(&mut *self.state.ssl_context, server_name.as_ptr())
133        })?;
134
135        Ok(())
136    }
137
138    // NOT cancel-safe: drives the handshake across awaits after resetting the
139    // SSL context; see `Session::connect`'s `# Cancel safety`.
140    async fn connect_internal(
141        &mut self,
142        saved_session: Option<&SavedSession>,
143    ) -> Result<(), SessionError> {
144        if self.connected {
145            return Ok(());
146        }
147
148        // Reject resuming a session captured for a different server name before
149        // it can be installed (cross-host resume can skip cert validation on
150        // TLS 1.2). See `check_saved_session_server_name`.
151        if let Some(saved_session) = saved_session {
152            check_saved_session_server_name(
153                &saved_session.server_name,
154                self.state.ssl_context.private_hostname,
155            )?;
156        }
157
158        MBio::from_session(self).connect(saved_session).await?;
159
160        self.connected = true;
161        self.eof = false;
162
163        Ok(())
164    }
165
166    /// Negotiate the TLS connection
167    ///
168    /// This function will perform the TLS handshake with the server.
169    ///
170    /// Note that calling it is not mandatory, because the TLS session is anyway
171    /// negotiated during the first read or write operation, or when splitting the session.
172    ///
173    /// # Cancel safety
174    ///
175    /// NOT cancel-safe. The handshake resets the SSL context (`mbedtls_ssl_session_reset`)
176    /// before driving it across multiple `.await` points; if this future is dropped
177    /// mid-handshake, the local TLS state is left partway through a handshake the peer may
178    /// have advanced, and a retry can reset it out from under the peer.
179    // NOT cancel-safe: see `# Cancel safety`.
180    pub async fn connect(&mut self) -> Result<(), SessionError> {
181        self.connect_internal(None).await
182    }
183
184    /// Negotiate the TLS connection attempting to reuse a previously captured session.
185    ///
186    /// Use [`Session::save`] to get a copy of the session to use here  
187    ///
188    /// # Cancel safety
189    ///
190    /// NOT cancel-safe. Same as [`Session::connect`].
191    // NOT cancel-safe: see `# Cancel safety`.
192    pub async fn connect_with_session(
193        &mut self,
194        saved_session: &SavedSession,
195    ) -> Result<(), SessionError> {
196        self.connect_internal(Some(saved_session)).await
197    }
198
199    /// Split the TLS session into read and write halves
200    ///
201    /// # Returns
202    /// - A tuple containing the read and write halves of the session
203    ///
204    /// # Cancel safety
205    ///
206    /// NOT cancel-safe. This negotiates the connection first (see
207    /// [`Session::connect`]); once connected the split itself has no further
208    /// `.await` points.
209    // NOT cancel-safe: see `# Cancel safety`.
210    pub async fn split(
211        &mut self,
212    ) -> Result<
213        (
214            SessionRead<'_, impl Read + '_>,
215            SessionWrite<'_, impl Write + '_>,
216        ),
217        SessionError,
218    >
219    where
220        T: Split,
221    {
222        self.connect().await?;
223
224        let (read, write) = self.stream.split();
225
226        // Derive one write-provenance pointer from a unique borrow of the owning
227        // MBox; both halves use it to drive the same context.
228        let ssl_context = unsafe { NonNull::new_unchecked(self.state.ssl_context.as_mut_ptr()) };
229
230        Ok((
231            SessionRead {
232                stream: NoWrite(read),
233                ssl_context,
234                eof: &mut self.eof,
235                read_byte: &mut self.read_byte,
236                write_byte: None,
237                write_in_flight: false,
238            },
239            SessionWrite {
240                stream: NoRead(write),
241                ssl_context,
242                eof: false,
243                read_byte: None,
244                write_byte: &mut self.write_byte,
245                write_in_flight: &mut self.write_in_flight,
246            },
247        ))
248    }
249
250    /// Read unencrypted data from the TLS connection
251    ///
252    /// # Arguments
253    /// - `buf` - The buffer to read the data into
254    ///
255    /// # Returns
256    /// - The number of bytes read or an error
257    ///
258    /// # Cancel safety
259    ///
260    /// NOT cancel-safe. This drives MbedTLS across multiple `.await` points. A
261    /// dropped read does not lose application data (a buffered transport byte is
262    /// kept, and bytes already consumed by MbedTLS live in the SSL context), but
263    /// it can leave partial TLS input/record state, so re-issuing is not
264    /// side-effect-free.
265    // NOT cancel-safe: see `# Cancel safety`.
266    pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, SessionError> {
267        self.connect().await?;
268
269        if self.eof || buf.is_empty() {
270            return Ok(0);
271        }
272
273        MBio::from_session(self).read(buf).await
274    }
275
276    /// Write unencrypted data to the TLS connection
277    ///
278    /// # Arguments:
279    /// - `data` - The data to write
280    ///
281    /// # Returns:
282    /// - The number of bytes written or an error
283    ///
284    /// # Cancel safety
285    ///
286    /// NOT cancel-safe, but never misreports. If this future is dropped after
287    /// MbedTLS has buffered part of `data` into a record (an internal
288    /// `WANT_WRITE`), that record may be partially on the wire, so the write is
289    /// not side-effect-free. However the accounting stays correct: the next
290    /// `write`/`flush`/`close` first finishes sending that pending record (it is
291    /// never attributed to the next call's buffer), so re-issuing with a
292    /// *different* buffer is safe and returns only that buffer's own byte count.
293    // NOT cancel-safe: see `# Cancel safety`.
294    pub async fn write(&mut self, data: &[u8]) -> Result<usize, SessionError> {
295        self.connect().await?;
296
297        if data.is_empty() {
298            return Ok(0);
299        }
300
301        MBio::from_session(self).write(data).await
302    }
303
304    /// Flush the TLS connection
305    ///
306    /// This function will flush the TLS connection, ensuring that all data is sent.
307    ///
308    /// # Returns:
309    /// - An error if the flush failed
310    ///
311    /// # Cancel safety
312    ///
313    /// NOT cancel-safe. A dropped flush may leave a queued transport byte unsent
314    /// or the underlying stream only partially flushed, so it is not
315    /// side-effect-free; re-flushing is generally fine if the underlying `Write`
316    /// is well-behaved.
317    // NOT cancel-safe: see `# Cancel safety`.
318    pub async fn flush(&mut self) -> Result<(), SessionError> {
319        self.connect().await?;
320
321        MBio::from_session(self).flush().await
322    }
323
324    /// Close the TLS connection
325    ///
326    /// This function will close the TLS connection, sending the TLS "close notify" info to the peer.
327    ///
328    /// # Returns:
329    /// - An error if the close failed
330    ///
331    /// # Cancel safety
332    ///
333    /// NOT cancel-safe. Sends the close-notify alert and flushes; a drop may
334    /// leave the alert partially sent.
335    // NOT cancel-safe: see `# Cancel safety`.
336    pub async fn close(&mut self) -> Result<(), SessionError> {
337        if !self.connected {
338            return Ok(());
339        }
340
341        MBio::from_session(self).close().await?;
342
343        self.connected = false;
344
345        Ok(())
346    }
347
348    /// Capture the negotiated MbedTLS session for possible reuse.
349    pub fn save(&self) -> Result<SavedSession, SessionError> {
350        let mut mbedtls_session: super::super::MBox<mbedtls_ssl_session> =
351            super::super::MBox::new().ok_or(MbedtlsError::new(MBEDTLS_ERR_SSL_ALLOC_FAILED))?;
352
353        merr!(unsafe { mbedtls_ssl_get_session(&*self.state.ssl_context, &mut *mbedtls_session) })?;
354
355        let hostname_ptr = self.state.ssl_context.private_hostname;
356        let server_name = if hostname_ptr.is_null() {
357            None
358        } else {
359            // SAFETY: a non-null hostname pointer on `mbedtls_ssl_context` is a
360            // heap-allocated, nul-terminated string owned by the SSL context;
361            // we only borrow it long enough to copy its bytes.
362            let cstr = unsafe { CStr::from_ptr(hostname_ptr) };
363            Some(
364                ServerName::from_cstr(cstr)
365                    .ok_or(MbedtlsError::new(MBEDTLS_ERR_SSL_ALLOC_FAILED))?,
366            )
367        };
368
369        Ok(SavedSession {
370            mbedtls_session,
371            server_name,
372        })
373    }
374}
375
376impl<T> Drop for Session<'_, T>
377where
378    T: Read + Write,
379{
380    fn drop(&mut self) {
381        if self.connected {
382            warn!("Session dropped without being closed properly");
383        }
384
385        debug!("Session dropped - freeing memory");
386    }
387}
388
389impl<T> ErrorType for Session<'_, T>
390where
391    T: Read + Write,
392{
393    type Error = SessionError;
394}
395
396impl<T> Read for Session<'_, T>
397where
398    T: Read + Write,
399{
400    // NOT cancel-safe: forwards to `Session::read`; see its `# Cancel safety`.
401    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
402        Self::read(self, buf).await
403    }
404}
405
406impl<T> Write for Session<'_, T>
407where
408    T: Read + Write,
409{
410    // NOT cancel-safe: forwards to `Session::write`; see its `# Cancel safety`.
411    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
412        Self::write(self, buf).await
413    }
414
415    // NOT cancel-safe: forwards to `Session::flush`; see its `# Cancel safety`.
416    async fn flush(&mut self) -> Result<(), Self::Error> {
417        Self::flush(self).await
418    }
419}
420
421/// A trait for splitting a stream into read and write halves.
422///
423/// This is used by the `Session::split` method to split the underlying stream and the stream MUST implement
424/// this trait for the `split` method to be available.
425///
426/// NOTE: While the `edge-nal` crate does have its own `Split` trait, we provide our own trait
427/// so as to keep the core of this library independent of `edge-nal`.
428pub trait Split: ErrorType {
429    /// The read half of the stream.
430    type Read<'a>: Read<Error = Self::Error>
431    where
432        Self: 'a;
433    /// The write half of the stream.
434    type Write<'a>: Write<Error = Self::Error>
435    where
436        Self: 'a;
437
438    /// Split the stream into read and write halves.
439    fn split(&mut self) -> (Self::Read<'_>, Self::Write<'_>);
440}
441
442impl<T> Split for &mut T
443where
444    T: Split,
445{
446    type Read<'a>
447        = T::Read<'a>
448    where
449        Self: 'a;
450    type Write<'a>
451        = T::Write<'a>
452    where
453        Self: 'a;
454
455    fn split(&mut self) -> (Self::Read<'_>, Self::Write<'_>) {
456        T::split(self)
457    }
458}
459
460/// A type representing the read half of a TLS session
461/// when the session has been split into read and write halves.
462pub struct SessionRead<'a, T>
463where
464    T: Read,
465{
466    /// The underlying stream
467    stream: NoWrite<T>,
468    /// The MbedTLS SSL context (write-provenance pointer; see `MBio`).
469    ssl_context: NonNull<mbedtls_ssl_context>,
470    /// Whether we had received a close notify from the peer
471    eof: &'a mut bool,
472    /// A state necessary so as to implement `MBio::wait_readable`
473    read_byte: &'a mut Option<u8>,
474    /// A state necessary so as to implement `MBio::wait_writable`
475    write_byte: Option<u8>,
476    /// A dummy value, as the read half never drives an outgoing record.
477    write_in_flight: bool,
478}
479
480impl<T> SessionRead<'_, T>
481where
482    T: Read,
483{
484    /// Read unencrypted data from the read half of the TLS connection.
485    ///
486    /// # Cancel safety
487    ///
488    /// Cancel-safe. The TLS handshake has already completed by the time
489    /// [`Session::split`] could produce this half, so this method only drives
490    /// MbedTLS's data-read loop with no handshake `.await` points. All partial
491    /// state (a buffered transport byte and MbedTLS's own record state) lives
492    /// on the read half, not in the future, so a drop strands nothing and the
493    /// next call resumes from where the previous one left off. (Unlike
494    /// [`Session::read`], which calls [`Session::connect`] on first use and
495    /// inherits its handshake cancellation hazard.)
496    // cancel-safe: see `# Cancel safety`.
497    pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, SessionError> {
498        if *self.eof || buf.is_empty() {
499            return Ok(0);
500        }
501
502        MBio::from_read(self).read(buf).await
503    }
504}
505
506impl<T> ErrorType for SessionRead<'_, T>
507where
508    T: Read,
509{
510    type Error = SessionError;
511}
512
513impl<T> Read for SessionRead<'_, T>
514where
515    T: Read,
516{
517    // cancel-safe: forwards to `SessionRead::read`; see its `# Cancel safety`.
518    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
519        Self::read(self, buf).await
520    }
521}
522
523/// A type representing the write half of a TLS session
524/// when the session has been split into read and write halves.
525pub struct SessionWrite<'a, T>
526where
527    T: Write,
528{
529    /// The underlying stream
530    stream: NoRead<T>,
531    /// The MbedTLS SSL context (write-provenance pointer; see `MBio`).
532    ssl_context: NonNull<mbedtls_ssl_context>,
533    /// A dummy value, as we don't need to track EOF in the write half
534    eof: bool,
535    /// A state necessary so as to implement `MBio::wait_readable`
536    read_byte: Option<u8>,
537    /// A state necessary so as to implement `MBio::wait_writable`
538    write_byte: &'a mut Option<u8>,
539    /// Pending-outgoing-record guard borrowed from the owning `Session`; see
540    /// `Session::write_in_flight`.
541    write_in_flight: &'a mut bool,
542}
543
544impl<T> SessionWrite<'_, T>
545where
546    T: Write,
547{
548    /// Write unencrypted data to the TLS connection
549    ///
550    /// # Arguments
551    /// - `data` - The data to write
552    ///
553    /// # Returns
554    /// - The number of bytes written or an error
555    ///
556    /// # Cancel safety
557    ///
558    /// NOT cancel-safe, but never misreports. Same as [`Session::write`]: a
559    /// dropped write may leave a partially-sent record, but the next write
560    /// finishes it first and reports only its own buffer's byte count.
561    // NOT cancel-safe: see `# Cancel safety`.
562    pub async fn write(&mut self, data: &[u8]) -> Result<usize, SessionError> {
563        if data.is_empty() {
564            return Ok(0);
565        }
566
567        MBio::from_write(self).write(data).await
568    }
569
570    /// Flush the TLS connection
571    ///
572    /// # Cancel safety
573    ///
574    /// NOT cancel-safe. Same as [`Session::flush`].
575    // NOT cancel-safe: see `# Cancel safety`.
576    pub async fn flush(&mut self) -> Result<(), SessionError> {
577        MBio::from_write(self).flush().await
578    }
579}
580
581impl<T> ErrorType for SessionWrite<'_, T>
582where
583    T: Write,
584{
585    type Error = SessionError;
586}
587
588impl<T> Write for SessionWrite<'_, T>
589where
590    T: Write,
591{
592    // NOT cancel-safe: forwards to `SessionWrite::write`; see its `# Cancel safety`.
593    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
594        Self::write(self, buf).await
595    }
596
597    // NOT cancel-safe: forwards to `SessionWrite::flush`; see its `# Cancel safety`.
598    async fn flush(&mut self) -> Result<(), Self::Error> {
599        Self::flush(self).await
600    }
601}
602
603/// A type for using the async `Read` and `Write` traits from within the synchronous MbedTLS "mbio" callbacks
604/// **without any additional buffers** / memory.
605///
606/// Using the MbedTLS callback-based IO metaphor is a bit of a challenge with the async `Read` and `Write` traits,
607/// in that these cannot be `await`-ed from within the MbedTLS mbio callbacks, as the latter are synchronous callback
608/// functions.
609///
610/// What this type implements therefore is the following trick:
611/// - While we cannot `await` on the `Read` and `Write` traits directly from within the "mbio" callbacks, we can still
612///   poll them (with `Future::poll`). This is because the `poll` method is synchronous in that it either resolves the
613///   future immediately (`Poll::Ready`), or returns `Poll::Pending` if the future needs to be polled again.
614/// - Because of the `Read` and `Write` traits' semantics, polling them MUST return immediately, if there is even one
615///   byte available for reading from the networking stack buffers (or - correspondingly - if there is space to write
616///   even one byte in the networking stack buffers).
617/// - Since the network stack usually does not operate byte-by-byte, what this means is that by just calling `Future::poll`
618///   on the `Read` / `Write` trait, we can efficiently transfer the incoming/outgoing data from/to the network stack, without
619///   any additional network buffers.
620/// - Of course, if the network read buffers are empty (or write buffers are full), we still need to `await` outside the
621///   MbedTLS callbacks, in the `Session::read` / `Session::write` / `Session::connect` methods.
622///
623/// Note also, that the implementation is a tad more complex, because it is implemented purely in terms of the
624/// `Read` and `Write` traits, rather than `edge-nal`'s `Readable` and (future) `Writable`, so we need to shuffle single bytes
625/// between the "mbio" callbacks and the `Session` asunc context to make it work.
626///
627/// On the other hand, this enables `Session` to be used over any streaming transport that implements the `Read` and `Write` traits
628/// (i.e. UART and others).
629struct MBio<'a, T> {
630    /// The underlying stream
631    stream: T,
632    /// The MbedTLS SSL context, held as a write-provenance pointer so MbedTLS
633    /// can write through it via FFI. Must be derived from a unique borrow of
634    /// the owning context, never from a shared reference. The `'a` lifetime is
635    /// pinned by the `&'a mut` fields below, so the pointer cannot outlive the
636    /// `Session` that owns the context.
637    ssl_context: NonNull<mbedtls_ssl_context>,
638    /// `true` if we had received a close notify from the peer
639    eof: &'a mut bool,
640    /// A state necessary so as to implement `MBio::wait_readable`
641    read_byte: &'a mut Option<u8>,
642    /// A state necessary so as to implement `MBio::wait_writable`
643    write_byte: &'a mut Option<u8>,
644    /// Pending-outgoing-record guard; see `Session::write_in_flight`.
645    write_in_flight: &'a mut bool,
646}
647
648impl<'a, T> MBio<'a, &'a mut T>
649where
650    T: Read + Write,
651{
652    fn from_session(session: &'a mut Session<'_, T>) -> Self {
653        // Derive the context pointer from a unique borrow of the owning MBox so
654        // it carries write provenance.
655        let ssl_context = unsafe { NonNull::new_unchecked(session.state.ssl_context.as_mut_ptr()) };
656        Self::new(
657            &mut session.stream,
658            ssl_context,
659            &mut session.eof,
660            &mut session.read_byte,
661            &mut session.write_byte,
662            &mut session.write_in_flight,
663        )
664    }
665}
666
667impl<'a, T> MBio<'a, &'a mut NoWrite<T>>
668where
669    T: Read,
670{
671    fn from_read(session: &'a mut SessionRead<'_, T>) -> Self {
672        Self::new(
673            &mut session.stream,
674            session.ssl_context,
675            session.eof,
676            session.read_byte,
677            &mut session.write_byte,
678            &mut session.write_in_flight,
679        )
680    }
681}
682
683impl<'a, T> MBio<'a, &'a mut NoRead<T>>
684where
685    T: Write,
686{
687    fn from_write(session: &'a mut SessionWrite<'_, T>) -> Self {
688        Self::new(
689            &mut session.stream,
690            session.ssl_context,
691            &mut session.eof,
692            &mut session.read_byte,
693            session.write_byte,
694            session.write_in_flight,
695        )
696    }
697}
698
699impl<'a, T> MBio<'a, T>
700where
701    T: Read + Write,
702{
703    const fn new(
704        stream: T,
705        ssl_context: NonNull<mbedtls_ssl_context>,
706        eof: &'a mut bool,
707        read_byte: &'a mut Option<u8>,
708        write_byte: &'a mut Option<u8>,
709        write_in_flight: &'a mut bool,
710    ) -> Self {
711        Self {
712            stream,
713            ssl_context,
714            eof,
715            read_byte,
716            write_byte,
717            write_in_flight,
718        }
719    }
720
721    /// Establish the SSL connection
722    // NOT cancel-safe: see `Session::connect`'s `# Cancel safety`.
723    async fn connect(&mut self, saved_session: Option<&SavedSession>) -> Result<(), SessionError> {
724        debug!("Establishing SSL connection");
725
726        merr!(unsafe { mbedtls_ssl_session_reset(self.ssl_context.as_ptr()) })?;
727
728        if let Some(saved_session) = saved_session {
729            merr!(unsafe {
730                mbedtls_ssl_set_session(self.ssl_context.as_ptr(), &*saved_session.mbedtls_session)
731            })?;
732        }
733
734        loop {
735            match self
736                .call_mbedtls(|ssl_ctx| unsafe { mbedtls_ssl_handshake(ssl_ctx) })
737                .await
738            {
739                MBEDTLS_ERR_SSL_WANT_READ => {
740                    if !self.wait_readable().await.map_err(SessionError::from_io)? {
741                        return Err(SessionError::Io(ErrorKind::ConnectionReset));
742                    }
743                }
744                MBEDTLS_ERR_SSL_WANT_WRITE => {
745                    if !self.wait_writable().await.map_err(SessionError::from_io)? {
746                        return Err(SessionError::Io(ErrorKind::ConnectionReset));
747                    }
748                }
749                // See https://github.com/Mbed-TLS/mbedtls/issues/8749
750                MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET => continue,
751                other => {
752                    merr!(other)?;
753                    break Ok(());
754                }
755            }
756        }
757    }
758
759    /// Read unencrypted data from the TLS connection
760    ///
761    /// # Arguments
762    /// - `buf` - The buffer to read the data into
763    ///
764    /// # Returns
765    /// - The number of bytes read or an error
766    // cancel-safe: the only `.await` is `wait_readable`, whose partial state
767    // lives on the read half (`read_byte`, MbedTLS's record state), not in the
768    // future. `Session::read` is unsafe only via its preceding `connect()`
769    // call, not because of this primitive.
770    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, SessionError> {
771        loop {
772            match self
773                .call_mbedtls(|ssl_ctx| unsafe {
774                    mbedtls_ssl_read(ssl_ctx, buf.as_mut_ptr() as *mut _, buf.len() as _)
775                })
776                .await
777            {
778                MBEDTLS_ERR_SSL_WANT_READ => {
779                    if !self.wait_readable().await.map_err(SessionError::from_io)? {
780                        return Err(SessionError::Io(ErrorKind::ConnectionReset));
781                    }
782                }
783                // See https://github.com/Mbed-TLS/mbedtls/issues/8749
784                MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET => continue,
785                MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY => {
786                    *self.eof = true;
787                    break Ok(0);
788                }
789                other => {
790                    let len = merr!(other)?;
791                    break Ok(len as usize);
792                }
793            }
794        }
795    }
796
797    /// Write unencrypted data to the TLS connection
798    ///
799    /// Arguments:
800    /// - `data` - The data to write
801    ///
802    /// Returns:
803    /// - The number of bytes written or an error
804    // NOT cancel-safe: see `Session::write`'s `# Cancel safety`.
805    async fn write(&mut self, data: &[u8]) -> Result<usize, SessionError> {
806        // If a previous write was dropped mid-record, finish sending that record
807        // before touching `data`, so its bytes are never attributed to `data`.
808        self.drain_pending().await?;
809
810        loop {
811            match self
812                .call_mbedtls(|ssl_ctx| unsafe {
813                    mbedtls_ssl_write(ssl_ctx, data.as_ptr() as *const _, data.len() as _)
814                })
815                .await
816            {
817                MBEDTLS_ERR_SSL_WANT_WRITE => {
818                    *self.write_in_flight = true;
819                    if !self.wait_writable().await.map_err(SessionError::from_io)? {
820                        return Err(SessionError::Io(ErrorKind::ConnectionReset));
821                    }
822                }
823                // See https://github.com/Mbed-TLS/mbedtls/issues/8749
824                MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET => continue,
825                other => {
826                    let len = merr!(other)?;
827                    *self.write_in_flight = false;
828                    break Ok(len as usize);
829                }
830            }
831        }
832    }
833
834    /// Finish sending a TLS record that MbedTLS still holds from an interrupted
835    /// write (`write_in_flight`), without writing any new application data.
836    ///
837    /// A zero-length `mbedtls_ssl_write` re-enters MbedTLS's flush-output path
838    /// when `out_left != 0` (the only public way to do so), ignoring the buffer
839    /// and returning 0 once drained. It is guarded by `write_in_flight` because
840    /// a zero-length write on an idle context would instead emit an empty TLS
841    /// application record.
842    // NOT cancel-safe: drives the pending record across awaits; a drop leaves
843    // `write_in_flight` set so the next call resumes the drain.
844    async fn drain_pending(&mut self) -> Result<(), SessionError> {
845        if !*self.write_in_flight {
846            return Ok(());
847        }
848
849        let dummy = [0u8; 1];
850
851        loop {
852            match self
853                .call_mbedtls(|ssl_ctx| unsafe {
854                    mbedtls_ssl_write(ssl_ctx, dummy.as_ptr() as *const _, 0)
855                })
856                .await
857            {
858                MBEDTLS_ERR_SSL_WANT_WRITE => {
859                    if !self.wait_writable().await.map_err(SessionError::from_io)? {
860                        return Err(SessionError::Io(ErrorKind::ConnectionReset));
861                    }
862                }
863                // See https://github.com/Mbed-TLS/mbedtls/issues/8749
864                MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET => continue,
865                other => {
866                    // Any non-negative return means the pending record drained; a
867                    // negative one is propagated as an error by `merr!`.
868                    let _ = merr!(other)?;
869                    *self.write_in_flight = false;
870                    break Ok(());
871                }
872            }
873        }
874    }
875
876    /// Flush the TLS connection by writing any outstanding data to the underlying stream
877    /// and then flushing the stream
878    // NOT cancel-safe: see `Session::flush`'s `# Cancel safety`.
879    async fn flush(&mut self) -> Result<(), SessionError> {
880        // Push any record MbedTLS still holds, then the byte staged by
881        // `wait_writable`, before flushing the transport.
882        self.drain_pending().await?;
883
884        if !self.wait_writable().await.map_err(SessionError::from_io)? {
885            return Err(SessionError::Io(ErrorKind::ConnectionReset));
886        }
887
888        self.stream.flush().await.map_err(SessionError::from_io)
889    }
890
891    /// Close the TLS connection by sending the "close notify" alert to the peer and flushing the stream
892    // NOT cancel-safe: see `Session::close`'s `# Cancel safety`.
893    pub async fn close(&mut self) -> Result<(), SessionError> {
894        // Drain any pending application record first; otherwise close-notify can
895        // merely flush that record and report success without being queued.
896        self.drain_pending().await?;
897
898        merr!(
899            self.call_mbedtls(|ssl| unsafe { mbedtls_ssl_close_notify(ssl) })
900                .await
901        )?;
902
903        self.flush().await?;
904
905        Ok(())
906    }
907
908    /// Wait until the underlying stream is readable
909    ///
910    /// A side effect of this function is that it reads one byte from the stream
911    /// and stores it for later consumption by the `bio_receive` method.
912    ///
913    /// Return `Ok(true)` if the stream is readable, `Ok(false)` if EOF is reached,
914    /// or an error otherwise.
915    // NOT cancel-safe: a buffered byte may be left in `read_byte`; see
916    // `Session::read`'s `# Cancel safety`.
917    async fn wait_readable(&mut self) -> Result<bool, T::Error> {
918        if self.read_byte.is_none() {
919            let mut buf = [0u8; 1];
920            let len = self.stream.read(&mut buf).await?;
921            if len == 0 {
922                return Ok(false);
923            }
924
925            *self.read_byte = Some(buf[0]);
926        }
927
928        Ok(true)
929    }
930
931    /// Wait until the underlying stream is writable
932    ///
933    /// A side effect of this function is that it writes one byte to the stream
934    /// where that byte had been provided by the `bio_send` method.
935    ///
936    /// Return `Ok(true)` if the stream is writable (or there is no byte to write), `Ok(false)` if EOF is reached,
937    /// or an error otherwise.
938    // NOT cancel-safe: a queued byte may be left in `write_byte`; see
939    // `Session::write`'s `# Cancel safety`.
940    async fn wait_writable(&mut self) -> Result<bool, T::Error> {
941        if let Some(byte) = self.write_byte.as_ref() {
942            let len = self.stream.write(&[*byte]).await?;
943            if len == 0 {
944                return Ok(false);
945            }
946
947            self.write_byte.take();
948        }
949
950        Ok(true)
951    }
952
953    /// Call an MbedTLS function with the proper BIO callbacks set
954    /// and with a proper context for the async operations on the underlying stream
955    // NOT cancel-safe: each poll advances MbedTLS's internal state; callers drive
956    // it in a loop and must observe the same-arguments retry contract.
957    async fn call_mbedtls<F>(&mut self, mut f: F) -> i32
958    where
959        F: FnMut(*mut mbedtls_ssl_context) -> i32,
960    {
961        poll_fn(|ctx| {
962            let mut io_ctx = MBioCallCtx { io: self, ctx };
963
964            let ssl_context = io_ctx.io.ssl_context.as_ptr();
965
966            unsafe {
967                mbedtls_ssl_set_bio(
968                    ssl_context,
969                    &mut io_ctx as *const _ as *mut MBioCallCtx<'_, '_, '_, T> as *mut c_void,
970                    Some(Self::raw_send),
971                    Some(Self::raw_receive),
972                    None,
973                );
974            }
975
976            let result = f(ssl_context);
977
978            // Remove the callbacks so that we get a warning from MbedTLS in case
979            // it needs to invoke them when we don't anticipate so (for bugs detection)
980            unsafe {
981                mbedtls_ssl_set_bio(ssl_context, core::ptr::null_mut(), None, None, None);
982            }
983
984            Poll::Ready(result)
985        })
986        .await
987    }
988
989    /// The MbedTLS BIO receive callback
990    fn bio_receive(&mut self, buf: &mut [u8], ctx: &mut Context<'_>) -> i32 {
991        trace!("Receive {}B", buf.len());
992
993        match self.poll_read(ctx, buf) {
994            Poll::Ready(len) => len as _,
995            Poll::Pending => MBEDTLS_ERR_SSL_WANT_READ,
996        }
997    }
998
999    /// The MbedTLS BIO send callback
1000    fn bio_send(&mut self, buf: &[u8], ctx: &mut Context<'_>) -> i32 {
1001        trace!("Send {}B", buf.len());
1002
1003        match self.poll_write(ctx, buf) {
1004            Poll::Ready(len) => len as _,
1005            Poll::Pending => MBEDTLS_ERR_SSL_WANT_WRITE,
1006        }
1007    }
1008
1009    /// Read data from the underlying stream without blocking
1010    fn poll_read(&mut self, ctx: &mut Context<'_>, buf: &mut [u8]) -> Poll<usize> {
1011        if buf.is_empty() {
1012            // Buffer is empty, nothing to read
1013            return Poll::Ready(0);
1014        }
1015
1016        let mut len = 0;
1017
1018        if let Some(byte) = self.read_byte.take() {
1019            // We have one byte ready via `wait_readable`
1020            // Push it to the buffer
1021
1022            buf[0] = byte;
1023            len += 1;
1024        }
1025
1026        if buf.len() > len {
1027            // Buffer has extra space, try to read more, if data is available
1028
1029            let mut fut = pin!(self.stream.read(&mut buf[len..]));
1030
1031            if let Poll::Ready(Ok(poll_len)) = fut.as_mut().poll(ctx) {
1032                len += poll_len;
1033            }
1034        }
1035
1036        if len > 0 {
1037            Poll::Ready(len)
1038        } else {
1039            Poll::Pending
1040        }
1041    }
1042
1043    /// Write data to the underlying stream without blocking
1044    fn poll_write(&mut self, ctx: &mut Context<'_>, data: &[u8]) -> Poll<usize> {
1045        if self.write_byte.is_some() {
1046            // First, try to send the pending byte from `wait_writable`
1047
1048            let data = [self.write_byte.unwrap()];
1049            let mut fut = pin!(self.stream.write(&data));
1050
1051            if let Poll::Ready(Ok(1)) = fut.as_mut().poll(ctx) {
1052                *self.write_byte = None;
1053            }
1054        }
1055
1056        if data.is_empty() {
1057            // Data is empty, nothing to write
1058            return Poll::Ready(0);
1059        }
1060
1061        let mut len = 0;
1062
1063        if self.write_byte.is_none() {
1064            // Since there is no outstanding byte to write, try to write the data
1065
1066            // First, try to write directly to the stream as much as possible without blocking
1067
1068            let mut fut = pin!(self.stream.write(data));
1069
1070            if let Poll::Ready(Ok(poll_len)) = fut.as_mut().poll(ctx) {
1071                len += poll_len;
1072            }
1073
1074            if data.len() > len {
1075                // Next, store the next byte to be written later via `wait_writable`
1076
1077                *self.write_byte = Some(data[len]);
1078                len += 1;
1079            }
1080        }
1081
1082        if len > 0 {
1083            Poll::Ready(len)
1084        } else {
1085            Poll::Pending
1086        }
1087    }
1088
1089    /// The raw MbedTLS BIO receive callback
1090    unsafe extern "C" fn raw_receive(ctx: *mut c_void, buf: *mut c_uchar, len: usize) -> c_int {
1091        let ctx = (ctx as *mut MBioCallCtx<'_, '_, '_, T>).as_mut().unwrap();
1092
1093        ctx.io
1094            .bio_receive(core::slice::from_raw_parts_mut(buf as *mut _, len), ctx.ctx)
1095    }
1096
1097    /// The raw MbedTLS BIO send callback
1098    unsafe extern "C" fn raw_send(ctx: *mut c_void, buf: *const c_uchar, len: usize) -> c_int {
1099        let ctx = (ctx as *mut MBioCallCtx<'_, '_, '_, T>).as_mut().unwrap();
1100
1101        ctx.io
1102            .bio_send(core::slice::from_raw_parts(buf as *const _, len), ctx.ctx)
1103    }
1104}
1105
1106/// The context passed to the MbedTLS BIO callbacks.
1107///
1108/// Basically, a pair of a mutable reference to the `MBio` instance
1109/// and a mutable reference to the async `Context` where the latter is necessary
1110/// so that we can poll the stream from within the BIO callbacks.
1111struct MBioCallCtx<'a, 'b, 'c, T> {
1112    io: &'a mut MBio<'b, T>,
1113    ctx: &'a mut Context<'c>,
1114}
1115
1116/// A wrapper around a type implementing `Write` which turns it into
1117/// a type implementing both `Read` and `Write`, but where the `Read` implementation
1118/// is unreachable.
1119///
1120/// Used when splitting a `Session` into a read-only and write-only halves, for the
1121/// "write" half.
1122///
1123/// This type is necessary because the `MBio` struct requires both `Read` and `Write`
1124/// traits to be implemented on the stream.
1125struct NoRead<T>(T);
1126
1127impl<T> ErrorType for NoRead<T>
1128where
1129    T: ErrorType,
1130{
1131    type Error = T::Error;
1132}
1133
1134impl<T> Read for NoRead<T>
1135where
1136    T: ErrorType,
1137{
1138    // NOT cancel-safe: unreachable (this is the write-only half's `Read`).
1139    async fn read(&mut self, _buf: &mut [u8]) -> Result<usize, Self::Error> {
1140        unreachable!()
1141    }
1142}
1143
1144impl<T> Write for NoRead<T>
1145where
1146    T: Write,
1147{
1148    // cancel-safe: forwards directly to the underlying stream's `write`; inherits
1149    // its cancel safety.
1150    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
1151        self.0.write(buf).await
1152    }
1153
1154    // cancel-safe: forwards directly to the underlying stream's `flush`; inherits
1155    // its cancel safety.
1156    async fn flush(&mut self) -> Result<(), Self::Error> {
1157        self.0.flush().await
1158    }
1159}
1160
1161/// A wrapper around a type implementing `Read` which turns it into
1162/// a type implementing both `Read` and `Write`, but where the `Write` implementation
1163/// is unreachable.
1164///
1165/// Used when splitting a `Session` into a read-only and write-only halves, for the
1166/// "read" half.
1167///
1168/// This type is necessary because the `MBio` struct requires both `Read` and `Write`
1169/// traits to be implemented on the stream.
1170struct NoWrite<T>(T);
1171
1172impl<T> ErrorType for NoWrite<T>
1173where
1174    T: ErrorType,
1175{
1176    type Error = T::Error;
1177}
1178
1179impl<T> Read for NoWrite<T>
1180where
1181    T: Read,
1182{
1183    // cancel-safe: forwards directly to the underlying stream's `read`; inherits
1184    // its cancel safety.
1185    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
1186        self.0.read(buf).await
1187    }
1188}
1189
1190impl<T> Write for NoWrite<T>
1191where
1192    T: ErrorType,
1193{
1194    // NOT cancel-safe: unreachable (this is the read-only half's `Write`).
1195    async fn write(&mut self, _buf: &[u8]) -> Result<usize, Self::Error> {
1196        unreachable!()
1197    }
1198
1199    // NOT cancel-safe: unreachable (this is the read-only half's `Write`).
1200    async fn flush(&mut self) -> Result<(), Self::Error> {
1201        unreachable!()
1202    }
1203}