pub struct TlsCodec { /* private fields */ }Expand description
Sans-IO TLS codec. Decrypts inbound bytes, encrypts outbound bytes.
Wraps a rustls ClientConnection with an API shaped for nexus-net.
The codec is a pure state machine: callers drive IO and buffering;
the codec only transforms bytes.
§API at a glance
- Inbound:
read_tlsfeeds buffered ciphertext one packet step at a time;read_tls_fromdrives a syncReadsource directly;read_and_process_tlsloops over bounded input. - Drain plaintext:
read_plaintextinto a slice;drain_plaintext_intofeeds anyParserSink(e.g.FrameReader) with one fewer copy. - Outbound:
encryptreturns bytes accepted (chunked);write_tls_todrains ciphertext to a writer. - Shutdown:
send_close_notifyqueues the alert; flush viawrite_tls_tobefore transport close.
Implementations§
Source§impl TlsCodec
impl TlsCodec
Sourcepub fn new(config: &TlsConfig, hostname: &str) -> Result<TlsCodec, TlsError>
pub fn new(config: &TlsConfig, hostname: &str) -> Result<TlsCodec, TlsError>
Create a new TLS codec for the given hostname.
The hostname is used for SNI (Server Name Indication) and certificate verification.
Sourcepub fn read_tls(&mut self, src: &[u8]) -> Result<usize, TlsError>
pub fn read_tls(&mut self, src: &[u8]) -> Result<usize, TlsError>
Advance the codec by a single TLS packet step: one read + one
process_new_packets pair.
Returns the number of ciphertext bytes consumed from src. The
caller drains any plaintext between calls (via
read_plaintext or
drain_plaintext_into) — feeding
more ciphertext while plaintext is queued can overflow rustls’s
internal plaintext buffer. This is the canonical primitive for
streaming app-data adapters (poll socket → step codec → drain
plaintext → repeat).
For bounded input that fits in rustls’s plaintext queue
(handshake bytes, in-memory tests), use the drain-loop helper
read_and_process_tls.
§Returns
Ok(0) if src is empty, or if rustls’s deframer cannot
progress on the input alone (matches Read::read idiom — the
caller’s loop is responsible for detecting stuck state).
Otherwise Ok(n) where n > 0 is bytes consumed (always
<= src.len(); rustls’s deframer caps each call at its
internal READ_SIZE).
§Errors
Any rustls error from the read or process step (alerts, decryption failures, plaintext-buffer overflow, protocol violations).
Sourcepub fn read_and_process_tls(&mut self, src: &[u8]) -> Result<usize, TlsError>
pub fn read_and_process_tls(&mut self, src: &[u8]) -> Result<usize, TlsError>
Feed buffered TLS bytes through rustls in a loop until the entire slice is consumed.
Use only for bounded input that fits in rustls’s plaintext
queue — in-memory tests, custom adapters that pre-buffer a
known-bounded byte sequence. Do not use for streaming app
data: large ciphertext slices fed without intervening plaintext
drains overflow rustls’s internal plaintext buffer
(received plaintext buffer full). For streaming adapters,
drive read_tls step-by-step yourself.
No production callers in this crate — kept as a
user-facing safety helper. The async TlsInner::connect
(nexus-async-web) and sync TlsStream::connect drive their
own loops over read_tls /
read_tls_from. External adapter
authors who pre-buffer ciphertext can reach for this helper
to avoid reimplementing the consume-loop.
§Why this exists
rustls::Connection::read_tls is not guaranteed to consume the
full provided slice on a single call. The naive pattern
codec.read_tls(&buf)? silently drops the unconsumed tail
(issue #200 — a TLS handshake against a server that splits its
response into multiple records inside one TCP segment fails
because the unconsumed bytes vanish). This helper encodes the
correct loop so naive callers don’t reintroduce the bug.
§Returns
Ok(src.len()) when the entire slice has been consumed.
§Errors
TlsError::Io(InvalidData)if rustls’s deframer can’t make progress (returned 0 bytes consumed) — malformed input.- Any rustls error from the underlying read/process steps.
Sourcepub fn read_tls_from<R>(&mut self, src: &mut R) -> Result<usize, TlsError>where
R: Read,
pub fn read_tls_from<R>(&mut self, src: &mut R) -> Result<usize, TlsError>where
R: Read,
Drive a sync Read source: read up to rustls’s internal
READ_SIZE from src, then process the records.
Equivalent to one read_tls step but pulls bytes from a
Read source instead of a buffer. Returns the bytes read from
src, or 0 on EOF / no bytes available. The caller’s loop
handles the rest.
Sourcepub fn drain_plaintext_into<P>(
&mut self,
sink: &mut P,
) -> Result<usize, TlsError>where
P: ParserSink,
pub fn drain_plaintext_into<P>(
&mut self,
sink: &mut P,
) -> Result<usize, TlsError>where
P: ParserSink,
Drain decrypted plaintext into a ParserSink.
Direct-feed path: uses BufRead::fill_buf to borrow rustls’s
internal plaintext queue and copy directly into sink.spare(),
skipping the intermediate &mut [u8] that the
read_plaintext shape requires. Returns
the number of plaintext bytes delivered.
Implements the zero-copy seam between rustls and parsers
(FrameReader for WebSocket framing, ResponseReader for
HTTP). Used by adapters’ WireStream::poll_fill_into to fold
plaintext draining into the same call that drives ciphertext
reads.
Sourcepub fn read_plaintext(&mut self, dst: &mut [u8]) -> Result<usize, TlsError>
pub fn read_plaintext(&mut self, dst: &mut [u8]) -> Result<usize, TlsError>
Read decrypted plaintext into a buffer (sans-IO path).
For users who want to feed bytes into FrameReader manually or use a different parser.
Sourcepub fn encrypt(&mut self, plaintext: &[u8]) -> Result<usize, TlsError>
pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<usize, TlsError>
Encrypt up to plaintext.len() bytes, returning the number of
bytes actually accepted by rustls’s outbound plaintext queue.
Chunked semantics — the caller’s write_all (or equivalent)
handles re-driving on partial acceptance. This is the
AsyncWrite::poll_write contract: surface backpressure as a
partial count, not a hard error.
§Returns
Ok(0) if rustls’s queue is full and cannot accept any bytes
(caller should drain ciphertext to the socket and retry).
Otherwise Ok(n) where n > 0 is plaintext bytes queued for
encryption. n may be less than plaintext.len().
§Errors
Any rustls writer error other than WriteZero (which is
translated to Ok(0) so callers treat queue-full as
backpressure rather than a hard failure).
Sourcepub fn set_buffer_limit(&mut self, limit: Option<usize>)
pub fn set_buffer_limit(&mut self, limit: Option<usize>)
Set rustls’s outbound plaintext queue limit. None for
unlimited (rustls accepts as much plaintext as memory allows;
pair with a caller-side bound).
Default is rustls’s DEFAULT_BUFFER_LIMIT = 64 KiB. Trading
workloads with small messages typically don’t need to change
this. Bulk-transfer workloads (large snapshots, file uploads
over TLS) may benefit from raising it to reduce drain/refill
cycles in encrypt.
Sourcepub fn send_close_notify(&mut self)
pub fn send_close_notify(&mut self)
Queue a TLS close_notify alert.
Subsequent calls to wants_write will
return true until the alert ciphertext has been written via
write_tls_to.
Idempotent: rustls tracks whether close_notify has been sent and no-ops on duplicate calls.
Use in AsyncWrite::poll_shutdown (or equivalent) before
closing the underlying transport. Without close_notify, the
peer sees TCP FIN as a potential truncation and may error its
read loop mid-stream.
Sourcepub fn write_tls_to<W>(&mut self, dst: &mut W) -> Result<usize, Error>where
W: Write,
pub fn write_tls_to<W>(&mut self, dst: &mut W) -> Result<usize, Error>where
W: Write,
Flush encrypted bytes to a socket.
Returns the number of bytes written. Call in a loop or when
wants_write returns true.
Sourcepub fn is_handshaking(&self) -> bool
pub fn is_handshaking(&self) -> bool
Whether the TLS handshake is still in progress.
Sourcepub fn wants_read(&self) -> bool
pub fn wants_read(&self) -> bool
Whether the codec has buffered TLS data to read.
Sourcepub fn wants_write(&self) -> bool
pub fn wants_write(&self) -> bool
Whether the codec has encrypted data to write.