summaryrefslogtreecommitdiff
path: root/vendor/rustls/src/conn
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-10 13:11:11 -0600
committermo khan <mo@mokhan.ca>2025-07-10 13:11:11 -0600
commit01959b16a21b22b5df5f16569c2a8e8f92beecef (patch)
tree32afa5d747c5466345c59ec52161a7cba3d6d755 /vendor/rustls/src/conn
parentff30574117a996df332e23d1fb6f65259b316b5b (diff)
chore: vendor dependencies
Diffstat (limited to 'vendor/rustls/src/conn')
-rw-r--r--vendor/rustls/src/conn/kernel.rs268
-rw-r--r--vendor/rustls/src/conn/unbuffered.rs613
2 files changed, 881 insertions, 0 deletions
diff --git a/vendor/rustls/src/conn/kernel.rs b/vendor/rustls/src/conn/kernel.rs
new file mode 100644
index 00000000..b90fad39
--- /dev/null
+++ b/vendor/rustls/src/conn/kernel.rs
@@ -0,0 +1,268 @@
+//! Kernel connection API.
+//!
+//! This module gives you the bare minimum you need to implement a TLS connection
+//! that does its own encryption and decryption while still using rustls to manage
+//! connection secrets and session tickets. It is intended for use cases like kTLS
+//! where you want to use rustls to establish the connection but want to use
+//! something else to do the encryption/decryption after that.
+//!
+//! There are only two things that [`KernelConnection`] is able to do:
+//! 1. Compute new traffic secrets when a key update occurs.
+//! 2. Save received session tickets sent by a server peer.
+//!
+//! That's it. Everything else you will need to implement yourself.
+//!
+//! # Entry Point
+//! The entry points into this API are
+//! [`UnbufferedClientConnection::dangerous_into_kernel_connection`][client-into]
+//! and
+//! [`UnbufferedServerConnection::dangerous_into_kernel_connection`][server-into].
+//!
+//! In order to actually create an [`KernelConnection`] all of the following
+//! must be true:
+//! - the connection must have completed its handshake,
+//! - the connection must have no buffered TLS data waiting to be sent, and,
+//! - the config used to create the connection must have `enable_extract_secrets`
+//! set to true.
+//!
+//! This sounds fairly complicated to achieve at first glance. However, if you
+//! drive an unbuffered connection through the handshake until it returns
+//! [`WriteTraffic`] then it will end up in an appropriate state to convert
+//! into an external connection.
+//!
+//! [client-into]: crate::client::UnbufferedClientConnection::dangerous_into_kernel_connection
+//! [server-into]: crate::server::UnbufferedServerConnection::dangerous_into_kernel_connection
+//! [`WriteTraffic`]: crate::unbuffered::ConnectionState::WriteTraffic
+//!
+//! # Cipher Suite Confidentiality Limits
+//! Some cipher suites (notably AES-GCM) have vulnerabilities where they are no
+//! longer secure once a certain number of messages have been sent. Normally,
+//! rustls tracks how many messages have been written or read and will
+//! automatically either refresh keys or emit an error when approaching the
+//! confidentiality limit of the cipher suite.
+//!
+//! [`KernelConnection`] has no way to track this. It is the responsibility
+//! of the user of the API to track approximately how many messages have been
+//! sent and either refresh the traffic keys or abort the connection before the
+//! confidentiality limit is reached.
+//!
+//! You can find the current confidentiality limit by looking at
+//! [`CipherSuiteCommon::confidentiality_limit`] for the cipher suite selected
+//! by the connection.
+//!
+//! [`CipherSuiteCommon::confidentiality_limit`]: crate::CipherSuiteCommon::confidentiality_limit
+//! [`KernelConnection`]: crate::kernel::KernelConnection
+
+use alloc::boxed::Box;
+use core::marker::PhantomData;
+
+use crate::client::ClientConnectionData;
+use crate::common_state::Protocol;
+use crate::msgs::codec::Codec;
+use crate::msgs::handshake::{CertificateChain, NewSessionTicketPayloadTls13};
+use crate::quic::Quic;
+use crate::{CommonState, ConnectionTrafficSecrets, Error, ProtocolVersion, SupportedCipherSuite};
+
+/// A kernel connection.
+///
+/// This does not directly wrap a kernel connection, rather it gives you the
+/// minimal interfaces you need to implement a well-behaved TLS connection on
+/// top of kTLS.
+///
+/// See the [`crate::kernel`] module docs for more details.
+pub struct KernelConnection<Data> {
+ state: Box<dyn KernelState>,
+
+ peer_certificates: Option<CertificateChain<'static>>,
+ quic: Quic,
+
+ negotiated_version: ProtocolVersion,
+ protocol: Protocol,
+ suite: SupportedCipherSuite,
+
+ _data: PhantomData<Data>,
+}
+
+impl<Data> KernelConnection<Data> {
+ pub(crate) fn new(state: Box<dyn KernelState>, common: CommonState) -> Result<Self, Error> {
+ Ok(Self {
+ state,
+
+ peer_certificates: common.peer_certificates,
+ quic: common.quic,
+ negotiated_version: common
+ .negotiated_version
+ .ok_or(Error::HandshakeNotComplete)?,
+ protocol: common.protocol,
+ suite: common
+ .suite
+ .ok_or(Error::HandshakeNotComplete)?,
+
+ _data: PhantomData,
+ })
+ }
+
+ /// Retrieves the ciphersuite agreed with the peer.
+ pub fn negotiated_cipher_suite(&self) -> SupportedCipherSuite {
+ self.suite
+ }
+
+ /// Retrieves the protocol version agreed with the peer.
+ pub fn protocol_version(&self) -> ProtocolVersion {
+ self.negotiated_version
+ }
+
+ /// Update the traffic secret used for encrypting messages sent to the peer.
+ ///
+ /// Returns the new traffic secret and initial sequence number to use.
+ ///
+ /// In order to use the new secret you should send a TLS 1.3 key update to
+ /// the peer and then use the new traffic secrets to encrypt any future
+ /// messages.
+ ///
+ /// Note that it is only possible to update the traffic secrets on a TLS 1.3
+ /// connection. Attempting to do so on a non-TLS 1.3 connection will result
+ /// in an error.
+ pub fn update_tx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> {
+ // The sequence number always starts at 0 after a key update.
+ self.state
+ .update_secrets(Direction::Transmit)
+ .map(|secret| (0, secret))
+ }
+
+ /// Update the traffic secret used for decrypting messages received from the
+ /// peer.
+ ///
+ /// Returns the new traffic secret and initial sequence number to use.
+ ///
+ /// You should call this method once you receive a TLS 1.3 key update message
+ /// from the peer.
+ ///
+ /// Note that it is only possible to update the traffic secrets on a TLS 1.3
+ /// connection. Attempting to do so on a non-TLS 1.3 connection will result
+ /// in an error.
+ pub fn update_rx_secret(&mut self) -> Result<(u64, ConnectionTrafficSecrets), Error> {
+ // The sequence number always starts at 0 after a key update.
+ self.state
+ .update_secrets(Direction::Receive)
+ .map(|secret| (0, secret))
+ }
+}
+
+impl KernelConnection<ClientConnectionData> {
+ /// Handle a `new_session_ticket` message from the peer.
+ ///
+ /// This will register the session ticket within with rustls so that it can
+ /// be used to establish future TLS connections.
+ ///
+ /// # Getting the right payload
+ ///
+ /// This method expects to be passed the inner payload of the handshake
+ /// message. This means that you will need to parse the header of the
+ /// handshake message in order to determine the correct payload to pass in.
+ /// The message format is described in [RFC 8446 section 4][0]. `payload`
+ /// should not include the `msg_type` or `length` fields.
+ ///
+ /// Code to parse out the payload should look something like this
+ /// ```no_run
+ /// use rustls::{ContentType, HandshakeType};
+ /// use rustls::kernel::KernelConnection;
+ /// use rustls::client::ClientConnectionData;
+ ///
+ /// # fn doctest(conn: &mut KernelConnection<ClientConnectionData>, typ: ContentType, message: &[u8]) -> Result<(), rustls::Error> {
+ /// let conn: &mut KernelConnection<ClientConnectionData> = // ...
+ /// # conn;
+ /// let typ: ContentType = // ...
+ /// # typ;
+ /// let mut message: &[u8] = // ...
+ /// # message;
+ ///
+ /// // Processing for other messages not included in this example
+ /// assert_eq!(typ, ContentType::Handshake);
+ ///
+ /// // There may be multiple handshake payloads within a single handshake message.
+ /// while !message.is_empty() {
+ /// let (typ, len, rest) = match message {
+ /// &[typ, a, b, c, ref rest @ ..] => (
+ /// HandshakeType::from(typ),
+ /// u32::from_be_bytes([0, a, b, c]) as usize,
+ /// rest
+ /// ),
+ /// _ => panic!("error handling not included in this example")
+ /// };
+ ///
+ /// // Processing for other messages not included in this example.
+ /// assert_eq!(typ, HandshakeType::NewSessionTicket);
+ /// assert!(rest.len() >= len, "invalid handshake message");
+ ///
+ /// let (payload, rest) = rest.split_at(len);
+ /// message = rest;
+ ///
+ /// conn.handle_new_session_ticket(payload)?;
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ /// This method will return an error if:
+ /// - This connection is not a TLS 1.3 connection (in TLS 1.2 session tickets
+ /// are sent as part of the handshake).
+ /// - The provided payload is not a valid `new_session_ticket` payload or has
+ /// extra unparsed trailing data.
+ /// - An error occurs while the connection updates the session ticket store.
+ ///
+ /// [0]: https://datatracker.ietf.org/doc/html/rfc8446#section-4
+ pub fn handle_new_session_ticket(&mut self, payload: &[u8]) -> Result<(), Error> {
+ // We want to return a more specific error here first if this is called
+ // on a non-TLS 1.3 connection since a parsing error isn't the real issue
+ // here.
+ if self.protocol_version() != ProtocolVersion::TLSv1_3 {
+ return Err(Error::General(
+ "TLS 1.2 session tickets may not be sent once the handshake has completed".into(),
+ ));
+ }
+
+ let nst = NewSessionTicketPayloadTls13::read_bytes(payload)?;
+ let mut cx = KernelContext {
+ peer_certificates: self.peer_certificates.as_ref(),
+ protocol: self.protocol,
+ quic: &self.quic,
+ };
+ self.state
+ .handle_new_session_ticket(&mut cx, &nst)
+ }
+}
+
+pub(crate) trait KernelState: Send + Sync {
+ /// Update the traffic secret for the specified direction on the connection.
+ fn update_secrets(&mut self, dir: Direction) -> Result<ConnectionTrafficSecrets, Error>;
+
+ /// Handle a new session ticket.
+ ///
+ /// This will only ever be called for client connections, as [`KernelConnection`]
+ /// only exposes the relevant API for client connections.
+ fn handle_new_session_ticket(
+ &mut self,
+ cx: &mut KernelContext<'_>,
+ message: &NewSessionTicketPayloadTls13,
+ ) -> Result<(), Error>;
+}
+
+pub(crate) struct KernelContext<'a> {
+ pub(crate) peer_certificates: Option<&'a CertificateChain<'static>>,
+ pub(crate) protocol: Protocol,
+ pub(crate) quic: &'a Quic,
+}
+
+impl KernelContext<'_> {
+ pub(crate) fn is_quic(&self) -> bool {
+ self.protocol == Protocol::Quic
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) enum Direction {
+ Transmit,
+ Receive,
+}
diff --git a/vendor/rustls/src/conn/unbuffered.rs b/vendor/rustls/src/conn/unbuffered.rs
new file mode 100644
index 00000000..84fe3f09
--- /dev/null
+++ b/vendor/rustls/src/conn/unbuffered.rs
@@ -0,0 +1,613 @@
+//! Unbuffered connection API
+
+use alloc::vec::Vec;
+use core::num::NonZeroUsize;
+use core::{fmt, mem};
+#[cfg(feature = "std")]
+use std::error::Error as StdError;
+
+use super::UnbufferedConnectionCommon;
+use crate::Error;
+use crate::client::ClientConnectionData;
+use crate::msgs::deframer::buffers::DeframerSliceBuffer;
+use crate::server::ServerConnectionData;
+
+impl UnbufferedConnectionCommon<ClientConnectionData> {
+ /// Processes the TLS records in `incoming_tls` buffer until a new [`UnbufferedStatus`] is
+ /// reached.
+ pub fn process_tls_records<'c, 'i>(
+ &'c mut self,
+ incoming_tls: &'i mut [u8],
+ ) -> UnbufferedStatus<'c, 'i, ClientConnectionData> {
+ self.process_tls_records_common(incoming_tls, |_| false, |_, _| unreachable!())
+ }
+}
+
+impl UnbufferedConnectionCommon<ServerConnectionData> {
+ /// Processes the TLS records in `incoming_tls` buffer until a new [`UnbufferedStatus`] is
+ /// reached.
+ pub fn process_tls_records<'c, 'i>(
+ &'c mut self,
+ incoming_tls: &'i mut [u8],
+ ) -> UnbufferedStatus<'c, 'i, ServerConnectionData> {
+ self.process_tls_records_common(
+ incoming_tls,
+ |conn| conn.peek_early_data().is_some(),
+ |conn, incoming_tls| ReadEarlyData::new(conn, incoming_tls).into(),
+ )
+ }
+}
+
+impl<Data> UnbufferedConnectionCommon<Data> {
+ fn process_tls_records_common<'c, 'i>(
+ &'c mut self,
+ incoming_tls: &'i mut [u8],
+ mut early_data_available: impl FnMut(&mut Self) -> bool,
+ early_data_state: impl FnOnce(&'c mut Self, &'i mut [u8]) -> ConnectionState<'c, 'i, Data>,
+ ) -> UnbufferedStatus<'c, 'i, Data> {
+ let mut buffer = DeframerSliceBuffer::new(incoming_tls);
+ let mut buffer_progress = self.core.hs_deframer.progress();
+
+ let (discard, state) = loop {
+ if early_data_available(self) {
+ break (
+ buffer.pending_discard(),
+ early_data_state(self, incoming_tls),
+ );
+ }
+
+ if !self
+ .core
+ .common_state
+ .received_plaintext
+ .is_empty()
+ {
+ break (
+ buffer.pending_discard(),
+ ReadTraffic::new(self, incoming_tls).into(),
+ );
+ }
+
+ if let Some(chunk) = self
+ .core
+ .common_state
+ .sendable_tls
+ .pop()
+ {
+ break (
+ buffer.pending_discard(),
+ EncodeTlsData::new(self, chunk).into(),
+ );
+ }
+
+ let deframer_output =
+ match self
+ .core
+ .deframe(None, buffer.filled_mut(), &mut buffer_progress)
+ {
+ Err(err) => {
+ buffer.queue_discard(buffer_progress.take_discard());
+ return UnbufferedStatus {
+ discard: buffer.pending_discard(),
+ state: Err(err),
+ };
+ }
+ Ok(r) => r,
+ };
+
+ if let Some(msg) = deframer_output {
+ let mut state =
+ match mem::replace(&mut self.core.state, Err(Error::HandshakeNotComplete)) {
+ Ok(state) => state,
+ Err(e) => {
+ buffer.queue_discard(buffer_progress.take_discard());
+ self.core.state = Err(e.clone());
+ return UnbufferedStatus {
+ discard: buffer.pending_discard(),
+ state: Err(e),
+ };
+ }
+ };
+
+ match self.core.process_msg(msg, state, None) {
+ Ok(new) => state = new,
+
+ Err(e) => {
+ buffer.queue_discard(buffer_progress.take_discard());
+ self.core.state = Err(e.clone());
+ return UnbufferedStatus {
+ discard: buffer.pending_discard(),
+ state: Err(e),
+ };
+ }
+ }
+
+ buffer.queue_discard(buffer_progress.take_discard());
+
+ self.core.state = Ok(state);
+ } else if self.wants_write {
+ break (
+ buffer.pending_discard(),
+ TransmitTlsData { conn: self }.into(),
+ );
+ } else if self
+ .core
+ .common_state
+ .has_received_close_notify
+ && !self.emitted_peer_closed_state
+ {
+ self.emitted_peer_closed_state = true;
+ break (buffer.pending_discard(), ConnectionState::PeerClosed);
+ } else if self
+ .core
+ .common_state
+ .has_received_close_notify
+ && self
+ .core
+ .common_state
+ .has_sent_close_notify
+ {
+ break (buffer.pending_discard(), ConnectionState::Closed);
+ } else if self
+ .core
+ .common_state
+ .may_send_application_data
+ {
+ break (
+ buffer.pending_discard(),
+ ConnectionState::WriteTraffic(WriteTraffic { conn: self }),
+ );
+ } else {
+ break (buffer.pending_discard(), ConnectionState::BlockedHandshake);
+ }
+ };
+
+ UnbufferedStatus {
+ discard,
+ state: Ok(state),
+ }
+ }
+}
+
+/// The current status of the `UnbufferedConnection*`
+#[must_use]
+#[derive(Debug)]
+pub struct UnbufferedStatus<'c, 'i, Data> {
+ /// Number of bytes to discard
+ ///
+ /// After the `state` field of this object has been handled, `discard` bytes must be
+ /// removed from the *front* of the `incoming_tls` buffer that was passed to
+ /// the [`UnbufferedConnectionCommon::process_tls_records`] call that returned this object.
+ ///
+ /// This discard operation MUST happen *before*
+ /// [`UnbufferedConnectionCommon::process_tls_records`] is called again.
+ pub discard: usize,
+
+ /// The current state of the handshake process
+ ///
+ /// This value MUST be handled prior to calling
+ /// [`UnbufferedConnectionCommon::process_tls_records`] again. See the documentation on the
+ /// variants of [`ConnectionState`] for more details.
+ pub state: Result<ConnectionState<'c, 'i, Data>, Error>,
+}
+
+/// The state of the [`UnbufferedConnectionCommon`] object
+#[non_exhaustive] // for forwards compatibility; to support caller-side certificate verification
+pub enum ConnectionState<'c, 'i, Data> {
+ /// One, or more, application data records are available
+ ///
+ /// See [`ReadTraffic`] for more details on how to use the enclosed object to access
+ /// the received data.
+ ReadTraffic(ReadTraffic<'c, 'i, Data>),
+
+ /// Connection has been cleanly closed by the peer.
+ ///
+ /// This state is encountered at most once by each connection -- it is
+ /// "edge" triggered, rather than "level" triggered.
+ ///
+ /// It delimits the data received from the peer, meaning you can be sure you
+ /// have received all the data the peer sent.
+ ///
+ /// No further application data will be received from the peer, so no further
+ /// `ReadTraffic` states will be produced.
+ ///
+ /// However, it is possible to _send_ further application data via `WriteTraffic`
+ /// states, or close the connection cleanly by calling
+ /// [`WriteTraffic::queue_close_notify()`].
+ PeerClosed,
+
+ /// Connection has been cleanly closed by both us and the peer.
+ ///
+ /// This is a terminal state. No other states will be produced for this
+ /// connection.
+ Closed,
+
+ /// One, or more, early (RTT-0) data records are available
+ ReadEarlyData(ReadEarlyData<'c, 'i, Data>),
+
+ /// A Handshake record is ready for encoding
+ ///
+ /// Call [`EncodeTlsData::encode`] on the enclosed object, providing an `outgoing_tls`
+ /// buffer to store the encoding
+ EncodeTlsData(EncodeTlsData<'c, Data>),
+
+ /// Previously encoded handshake records need to be transmitted
+ ///
+ /// Transmit the contents of the `outgoing_tls` buffer that was passed to previous
+ /// [`EncodeTlsData::encode`] calls to the peer.
+ ///
+ /// After transmitting the contents, call [`TransmitTlsData::done`] on the enclosed object.
+ /// The transmitted contents MUST not be sent to the peer more than once so they SHOULD be
+ /// discarded at this point.
+ ///
+ /// At some stages of the handshake process, it's possible to send application-data alongside
+ /// handshake records. Call [`TransmitTlsData::may_encrypt_app_data`] on the enclosed
+ /// object to probe if that's allowed.
+ TransmitTlsData(TransmitTlsData<'c, Data>),
+
+ /// More TLS data is needed to continue with the handshake
+ ///
+ /// Request more data from the peer and append the contents to the `incoming_tls` buffer that
+ /// was passed to [`UnbufferedConnectionCommon::process_tls_records`].
+ BlockedHandshake,
+
+ /// The handshake process has been completed.
+ ///
+ /// [`WriteTraffic::encrypt`] can be called on the enclosed object to encrypt application
+ /// data into an `outgoing_tls` buffer. Similarly, [`WriteTraffic::queue_close_notify`] can
+ /// be used to encrypt a close_notify alert message into a buffer to signal the peer that the
+ /// connection is being closed. Data written into `outgoing_buffer` by either method MAY be
+ /// transmitted to the peer during this state.
+ ///
+ /// Once this state has been reached, data MAY be requested from the peer and appended to an
+ /// `incoming_tls` buffer that will be passed to a future
+ /// [`UnbufferedConnectionCommon::process_tls_records`] invocation. When enough data has been
+ /// appended to `incoming_tls`, [`UnbufferedConnectionCommon::process_tls_records`] will yield
+ /// the [`ConnectionState::ReadTraffic`] state.
+ WriteTraffic(WriteTraffic<'c, Data>),
+}
+
+impl<'c, 'i, Data> From<ReadTraffic<'c, 'i, Data>> for ConnectionState<'c, 'i, Data> {
+ fn from(v: ReadTraffic<'c, 'i, Data>) -> Self {
+ Self::ReadTraffic(v)
+ }
+}
+
+impl<'c, 'i, Data> From<ReadEarlyData<'c, 'i, Data>> for ConnectionState<'c, 'i, Data> {
+ fn from(v: ReadEarlyData<'c, 'i, Data>) -> Self {
+ Self::ReadEarlyData(v)
+ }
+}
+
+impl<'c, Data> From<EncodeTlsData<'c, Data>> for ConnectionState<'c, '_, Data> {
+ fn from(v: EncodeTlsData<'c, Data>) -> Self {
+ Self::EncodeTlsData(v)
+ }
+}
+
+impl<'c, Data> From<TransmitTlsData<'c, Data>> for ConnectionState<'c, '_, Data> {
+ fn from(v: TransmitTlsData<'c, Data>) -> Self {
+ Self::TransmitTlsData(v)
+ }
+}
+
+impl<Data> fmt::Debug for ConnectionState<'_, '_, Data> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::ReadTraffic(..) => f.debug_tuple("ReadTraffic").finish(),
+
+ Self::PeerClosed => write!(f, "PeerClosed"),
+
+ Self::Closed => write!(f, "Closed"),
+
+ Self::ReadEarlyData(..) => f.debug_tuple("ReadEarlyData").finish(),
+
+ Self::EncodeTlsData(..) => f.debug_tuple("EncodeTlsData").finish(),
+
+ Self::TransmitTlsData(..) => f
+ .debug_tuple("TransmitTlsData")
+ .finish(),
+
+ Self::BlockedHandshake => f
+ .debug_tuple("BlockedHandshake")
+ .finish(),
+
+ Self::WriteTraffic(..) => f.debug_tuple("WriteTraffic").finish(),
+ }
+ }
+}
+
+/// Application data is available
+pub struct ReadTraffic<'c, 'i, Data> {
+ conn: &'c mut UnbufferedConnectionCommon<Data>,
+ // for forwards compatibility; to support in-place decryption in the future
+ _incoming_tls: &'i mut [u8],
+
+ // owner of the latest chunk obtained in `next_record`, as borrowed by
+ // `AppDataRecord`
+ chunk: Option<Vec<u8>>,
+}
+
+impl<'c, 'i, Data> ReadTraffic<'c, 'i, Data> {
+ fn new(conn: &'c mut UnbufferedConnectionCommon<Data>, _incoming_tls: &'i mut [u8]) -> Self {
+ Self {
+ conn,
+ _incoming_tls,
+ chunk: None,
+ }
+ }
+
+ /// Decrypts and returns the next available app-data record
+ // TODO deprecate in favor of `Iterator` implementation, which requires in-place decryption
+ pub fn next_record(&mut self) -> Option<Result<AppDataRecord<'_>, Error>> {
+ self.chunk = self
+ .conn
+ .core
+ .common_state
+ .received_plaintext
+ .pop();
+ self.chunk.as_ref().map(|chunk| {
+ Ok(AppDataRecord {
+ discard: 0,
+ payload: chunk,
+ })
+ })
+ }
+
+ /// Returns the payload size of the next app-data record *without* decrypting it
+ ///
+ /// Returns `None` if there are no more app-data records
+ pub fn peek_len(&self) -> Option<NonZeroUsize> {
+ self.conn
+ .core
+ .common_state
+ .received_plaintext
+ .peek()
+ .and_then(|ch| NonZeroUsize::new(ch.len()))
+ }
+}
+
+/// Early application-data is available.
+pub struct ReadEarlyData<'c, 'i, Data> {
+ conn: &'c mut UnbufferedConnectionCommon<Data>,
+
+ // for forwards compatibility; to support in-place decryption in the future
+ _incoming_tls: &'i mut [u8],
+
+ // owner of the latest chunk obtained in `next_record`, as borrowed by
+ // `AppDataRecord`
+ chunk: Option<Vec<u8>>,
+}
+
+impl<'c, 'i> ReadEarlyData<'c, 'i, ServerConnectionData> {
+ fn new(
+ conn: &'c mut UnbufferedConnectionCommon<ServerConnectionData>,
+ _incoming_tls: &'i mut [u8],
+ ) -> Self {
+ Self {
+ conn,
+ _incoming_tls,
+ chunk: None,
+ }
+ }
+
+ /// decrypts and returns the next available app-data record
+ // TODO deprecate in favor of `Iterator` implementation, which requires in-place decryption
+ pub fn next_record(&mut self) -> Option<Result<AppDataRecord<'_>, Error>> {
+ self.chunk = self.conn.pop_early_data();
+ self.chunk.as_ref().map(|chunk| {
+ Ok(AppDataRecord {
+ discard: 0,
+ payload: chunk,
+ })
+ })
+ }
+
+ /// returns the payload size of the next app-data record *without* decrypting it
+ ///
+ /// returns `None` if there are no more app-data records
+ pub fn peek_len(&self) -> Option<NonZeroUsize> {
+ self.conn
+ .peek_early_data()
+ .and_then(|ch| NonZeroUsize::new(ch.len()))
+ }
+}
+
+/// A decrypted application-data record
+pub struct AppDataRecord<'i> {
+ /// Number of additional bytes to discard
+ ///
+ /// This number MUST be added to the value of [`UnbufferedStatus.discard`] *prior* to the
+ /// discard operation. See [`UnbufferedStatus.discard`] for more details
+ pub discard: usize,
+
+ /// The payload of the app-data record
+ pub payload: &'i [u8],
+}
+
+/// Allows encrypting app-data
+pub struct WriteTraffic<'c, Data> {
+ conn: &'c mut UnbufferedConnectionCommon<Data>,
+}
+
+impl<Data> WriteTraffic<'_, Data> {
+ /// Encrypts `application_data` into the `outgoing_tls` buffer
+ ///
+ /// Returns the number of bytes that were written into `outgoing_tls`, or an error if
+ /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified
+ pub fn encrypt(
+ &mut self,
+ application_data: &[u8],
+ outgoing_tls: &mut [u8],
+ ) -> Result<usize, EncryptError> {
+ self.conn
+ .core
+ .maybe_refresh_traffic_keys();
+ self.conn
+ .core
+ .common_state
+ .write_plaintext(application_data.into(), outgoing_tls)
+ }
+
+ /// Encrypts a close_notify warning alert in `outgoing_tls`
+ ///
+ /// Returns the number of bytes that were written into `outgoing_tls`, or an error if
+ /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified
+ pub fn queue_close_notify(&mut self, outgoing_tls: &mut [u8]) -> Result<usize, EncryptError> {
+ self.conn
+ .core
+ .common_state
+ .eager_send_close_notify(outgoing_tls)
+ }
+
+ /// Arranges for a TLS1.3 `key_update` to be sent.
+ ///
+ /// This consumes the `WriteTraffic` state: to actually send the message,
+ /// call [`UnbufferedConnectionCommon::process_tls_records`] again which will
+ /// return a `ConnectionState::EncodeTlsData` that emits the `key_update`
+ /// message.
+ ///
+ /// See [`ConnectionCommon::refresh_traffic_keys()`] for full documentation,
+ /// including why you might call this and in what circumstances it will fail.
+ ///
+ /// [`ConnectionCommon::refresh_traffic_keys()`]: crate::ConnectionCommon::refresh_traffic_keys
+ pub fn refresh_traffic_keys(self) -> Result<(), Error> {
+ self.conn.core.refresh_traffic_keys()
+ }
+}
+
+/// A handshake record must be encoded
+pub struct EncodeTlsData<'c, Data> {
+ conn: &'c mut UnbufferedConnectionCommon<Data>,
+ chunk: Option<Vec<u8>>,
+}
+
+impl<'c, Data> EncodeTlsData<'c, Data> {
+ fn new(conn: &'c mut UnbufferedConnectionCommon<Data>, chunk: Vec<u8>) -> Self {
+ Self {
+ conn,
+ chunk: Some(chunk),
+ }
+ }
+
+ /// Encodes a handshake record into the `outgoing_tls` buffer
+ ///
+ /// Returns the number of bytes that were written into `outgoing_tls`, or an error if
+ /// the provided buffer is too small. In the error case, `outgoing_tls` is not modified
+ pub fn encode(&mut self, outgoing_tls: &mut [u8]) -> Result<usize, EncodeError> {
+ let Some(chunk) = self.chunk.take() else {
+ return Err(EncodeError::AlreadyEncoded);
+ };
+
+ let required_size = chunk.len();
+
+ if required_size > outgoing_tls.len() {
+ self.chunk = Some(chunk);
+ Err(InsufficientSizeError { required_size }.into())
+ } else {
+ let written = chunk.len();
+ outgoing_tls[..written].copy_from_slice(&chunk);
+
+ self.conn.wants_write = true;
+
+ Ok(written)
+ }
+ }
+}
+
+/// Previously encoded TLS data must be transmitted
+pub struct TransmitTlsData<'c, Data> {
+ pub(crate) conn: &'c mut UnbufferedConnectionCommon<Data>,
+}
+
+impl<Data> TransmitTlsData<'_, Data> {
+ /// Signals that the previously encoded TLS data has been transmitted
+ pub fn done(self) {
+ self.conn.wants_write = false;
+ }
+
+ /// Returns an adapter that allows encrypting application data
+ ///
+ /// If allowed at this stage of the handshake process
+ pub fn may_encrypt_app_data(&mut self) -> Option<WriteTraffic<'_, Data>> {
+ if self
+ .conn
+ .core
+ .common_state
+ .may_send_application_data
+ {
+ Some(WriteTraffic { conn: self.conn })
+ } else {
+ None
+ }
+ }
+}
+
+/// Errors that may arise when encoding a handshake record
+#[derive(Debug)]
+pub enum EncodeError {
+ /// Provided buffer was too small
+ InsufficientSize(InsufficientSizeError),
+
+ /// The handshake record has already been encoded; do not call `encode` again
+ AlreadyEncoded,
+}
+
+impl From<InsufficientSizeError> for EncodeError {
+ fn from(v: InsufficientSizeError) -> Self {
+ Self::InsufficientSize(v)
+ }
+}
+
+impl fmt::Display for EncodeError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InsufficientSize(InsufficientSizeError { required_size }) => write!(
+ f,
+ "cannot encode due to insufficient size, {required_size} bytes are required"
+ ),
+ Self::AlreadyEncoded => "cannot encode, data has already been encoded".fmt(f),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl StdError for EncodeError {}
+
+/// Errors that may arise when encrypting application data
+#[derive(Debug)]
+pub enum EncryptError {
+ /// Provided buffer was too small
+ InsufficientSize(InsufficientSizeError),
+
+ /// Encrypter has been exhausted
+ EncryptExhausted,
+}
+
+impl From<InsufficientSizeError> for EncryptError {
+ fn from(v: InsufficientSizeError) -> Self {
+ Self::InsufficientSize(v)
+ }
+}
+
+impl fmt::Display for EncryptError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::InsufficientSize(InsufficientSizeError { required_size }) => write!(
+ f,
+ "cannot encrypt due to insufficient size, {required_size} bytes are required"
+ ),
+ Self::EncryptExhausted => f.write_str("encrypter has been exhausted"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl StdError for EncryptError {}
+
+/// Provided buffer was too small
+#[derive(Clone, Copy, Debug)]
+pub struct InsufficientSizeError {
+ /// buffer must be at least this size
+ pub required_size: usize,
+}