Skip to content

Add TlsStream::from_parts() as the inverse of into_inner() - #203

Open
freza wants to merge 1 commit into
rustls:mainfrom
freza:from-parts
Open

freza wants to merge 1 commit into
rustls:mainfrom
freza:from-parts

Conversation

@freza

@freza freza commented Sep 23, 2026

Copy link
Copy Markdown

Closes #138.

client::TlsStream::into_inner() and server::TlsStream::into_inner() hand out the IO
and the rustls connection of an established stream, but there was no constructor back.
This adds from_parts(io, session) to both, taking the parts in the order into_inner()
returns them.

Motivation (same as #138): moving an established TLS connection to another tokio runtime.
With plain TCP that is TcpStream::into_std() + TcpStream::from_std(); with TLS it needs
the wrapper to be rebuilt around the re-registered socket. The other use in #138, a
handshake completed outside this crate, is covered by the same constructor.

Why this is safe: the rustls connection carries all protocol state, including plaintext
already decrypted but not yet read. into_inner() only drops the wrapper's own
bookkeeping (TlsState, need_flush, the early-data waker), which for an established,
not shut down connection is always "Stream / no flush pending / no waker". from_parts()
resets it to exactly that and documents the requirement.

Tests:

  • from_parts_roundtrip: duplex pair; both sides are rebuilt, the server side with 8 bytes
    of a record still buffered in the session, which stay readable afterwards; clean
    bidirectional close_notify.
  • from_parts_across_runtimes: server side is handshaken on the test runtime, detached with
    into_std(), rebuilt with from_std() + from_parts() on a current_thread runtime on
    another thread, and serves a request/response there.

Checked locally: cargo test, cargo clippy --all-targets with --deny warnings,
cargo doc --document-private-items with -Dwarnings, rustfmt with the repo's 2024 style
config, cargo check --lib on 1.81 with the feature set the MSRV job uses (fips excluded
locally).

@djc djc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks okay to me.

Comment thread src/client.rs Outdated
Comment thread src/client.rs Outdated
into_inner() hands out the IO and the rustls connection of an established
stream, but there was no way back. A caller who wants to swap the IO under a
live TLS session, for instance to re-register a socket with another tokio
runtime via TcpStream::into_std() and from_std(), or who completed the
handshake outside this crate, had to keep the original TlsStream alive or
reimplement the wrapper.

The rustls connection carries all protocol state, including plaintext that
was already decrypted but not yet read, so rebuilding the stream only needs
the wrapper's own bookkeeping reset to "established": TlsState::Stream, no
pending flush and no early-data waker. The constructor documents that the
session must have completed its handshake and must not have been shut down,
which is exactly what into_inner() of an established stream produces.

Tests cover a round trip on both the client and the server side with
plaintext left buffered in the session across the rebuild, and moving an
established server stream to a runtime on another thread.

@djc djc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@djc
djc requested review from cpu and ctz September 25, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: TlsStream::from_inner

3 participants