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}