pub struct PhantomSession { /* private fields */ }Expand description
Client-first session — instant connect(), non-blocking send().
§Design
let session = PhantomSession::connect("server:443"); // instant!
session.send(data).await; // queued until handshake completes
session.send(data2).await; // also queued
// ... handshake completes in background ...
// queued data auto-flushed, new sends go directlyThe session progresses through states:
Connecting → ClassicalReady → PqcUpgrading → PqcReady → Connected
Implementations§
Source§impl PhantomSession
impl PhantomSession
Sourcepub fn connect_with_transport<T: SessionTransport>(
peer_addr: &str,
transport: T,
expected_server_key: HybridVerifyingKey,
) -> Self
pub fn connect_with_transport<T: SessionTransport>( peer_addr: &str, transport: T, expected_server_key: HybridVerifyingKey, ) -> Self
Create a new session and start the background handshake task.
Requires expected_server_key for MITM resistance — the client will
abort the handshake unless the server presents this exact verifying key.
Callers obtain this key out-of-band (e.g. from PhantomListener::verifying_key_bytes).
The handshake runs in the background:
- Exchange hybrid PQC
ClientHello/ServerHello. - Verify server identity against
expected_server_key. - Derive AEAD keys; flush queued sends as encrypted packets.
All network I/O goes through the provided SessionTransport. The
task that drives the handshake + data pump runs on the default
TokioRuntime; use
connect_with_transport_with_runtime
to substitute a different Runtime.
Sourcepub fn connect_with_transport_with_runtime<T: SessionTransport>(
peer_addr: &str,
transport: T,
expected_server_key: HybridVerifyingKey,
runtime: Arc<dyn Runtime>,
) -> Self
pub fn connect_with_transport_with_runtime<T: SessionTransport>( peer_addr: &str, transport: T, expected_server_key: HybridVerifyingKey, runtime: Arc<dyn Runtime>, ) -> Self
Like connect_with_transport but
runs the background task on the supplied Runtime. Intended for
WASM / embedded / test backends that don’t drive tokio::spawn.
Sourcepub fn connect_with_resumption<T: SessionTransport>(
peer_addr: &str,
transport: T,
expected_server_key: HybridVerifyingKey,
resumption_hint: ([u8; 32], [u8; 32]),
early_data: Vec<u8>,
) -> Result<Self, CoreError>
pub fn connect_with_resumption<T: SessionTransport>( peer_addr: &str, transport: T, expected_server_key: HybridVerifyingKey, resumption_hint: ([u8; 32], [u8; 32]), early_data: Vec<u8>, ) -> Result<Self, CoreError>
Connect with a 0-RTT resumption attempt.
resumption_hint is the (session_id, resumption_secret) tuple
from a prior session’s PhantomSession::resumption_hint.
early_data (≤ EARLY_DATA_MAX_LEN bytes) is sealed and carried
inside the resuming ClientHello so it reaches the server on the very
first flight — saving a round-trip versus 1-RTT.
Acceptance is best-effort: a stale/unknown ticket or an AEAD failure
leaves early_data_accepted at
Some(false) and the handshake completes as a normal 1-RTT exchange —
the caller must then send that payload over the normal channel.
Returns Err only when early_data exceeds the cap.
Runs on the default TokioRuntime.
Source§impl PhantomSession
impl PhantomSession
Sourcepub fn observability(&self) -> Arc<Observability>
pub fn observability(&self) -> Arc<Observability>
Session observability handle (Rust-only — Observability is not a
UniFFI type). For a server-accepted session this is the
PhantomListener’s shared instance; for a client it is the session’s
own. Read .snapshot() for the lock-free metric counters.
Source§impl PhantomSession
impl PhantomSession
Sourcepub fn connect(peer_addr: String) -> Arc<Self>
pub fn connect(peer_addr: String) -> Arc<Self>
Create a placeholder session — returns instantly and performs no handshake.
§⚠️ This does not connect
Despite the name, this constructor never opens a transport, never runs
the PQC handshake, and never spawns the background data pump. It returns
an inert shell stuck in ConnectionState::Connecting: any send()
only queues into an in-memory buffer that is never flushed, and recv()
never yields application bytes. No bytes ever reach the network. It
exists only as a pre-handshake placeholder from an earlier API shape.
Deprecated — use a real entry point instead:
PhantomSession::connect_with_transport(Rust) — supply aSessionTransportand the pinnedexpected_server_key; this spawns the handshake + pump.connect_pinned(native FFI / mobile) — one-shot TCP connect with a pinned key.
§Why no #[deprecated] attribute (T5.7)
A #[deprecated] attribute would be the natural way to flag this, but it
cannot be applied here: this constructor is #[uniffi::constructor],
and UniFFI 0.31 emits FFI scaffolding that calls Self::connect() from
generated code in this same crate. That generated call would trip the
deprecated lint, which CI promotes to a hard error under
clippy --lib -D warnings — and no item-scoped #[allow(deprecated)]
reaches the macro-generated call site (only a module-wide
#![allow(deprecated)] would, which would silently mask every future
genuine deprecation across this module). So the deprecation is documented
loudly here instead. UniFFI copies this doc-comment into the generated
Python / Swift / Kotlin docstrings (the C header carries no docstrings),
so they were regenerated and committed alongside this change — the
bindings drift CI job stays green. See
tests::deprecated_connect_is_inert_and_sends_no_bytes for the regression
pinning the inert behaviour.
Sourcepub fn open_stream(&self) -> Arc<PhantomStream>
pub fn open_stream(&self) -> Arc<PhantomStream>
Open a new multiplexed stream
Sourcepub async fn send(&self, data: Vec<u8>) -> Result<(), CoreError>
pub async fn send(&self, data: Vec<u8>) -> Result<(), CoreError>
Send data through the session.
- If the session is connected: sends immediately
- If still handshaking: queues the data for auto-flush later
Sourcepub async fn recv(&self) -> Result<Vec<u8>, CoreError>
pub async fn recv(&self) -> Result<Vec<u8>, CoreError>
Receive data from the session.
Internally the recv pipeline keeps payloads as Bytes to avoid the
per-packet Vec clone that used to fan out to the stream demux. The
FFI surface still hands callers a Vec<u8>; if this is the last
refcount the Vec is moved out of the underlying buffer, otherwise
Bytes::to_vec copies.
Sourcepub fn connection_state(&self) -> ConnectionState
pub fn connection_state(&self) -> ConnectionState
Get the current connection state (lock-free).
Sourcepub fn is_data_ready(&self) -> bool
pub fn is_data_ready(&self) -> bool
Whether the session is ready for data transmission.
Sourcepub fn is_pqc_ready(&self) -> bool
pub fn is_pqc_ready(&self) -> bool
Whether the session has full PQC protection.
Sourcepub async fn flush_queue(&self) -> Result<u32, CoreError>
pub async fn flush_queue(&self) -> Result<u32, CoreError>
Flush all queued messages (called when handshake completes).
Sourcepub async fn queued_count(&self) -> u32
pub async fn queued_count(&self) -> u32
Number of messages queued (waiting for handshake).
Sourcepub async fn early_data_accepted(&self) -> Option<bool>
pub async fn early_data_accepted(&self) -> Option<bool>
The 0-RTT verdict for this session.
None— still handshaking, the handshake failed, or the client sent no early-data on this connect.Some(true)— the server consumed the 0-RTT early-data.Some(false)— the client sent early-data and the server rejected it (stale/unknown ticket, oversized blob, or AEAD failure). The caller must re-send that payload over the normal channel.
Sourcepub async fn resumption_hint(&self) -> Option<ResumptionHint>
pub async fn resumption_hint(&self) -> Option<ResumptionHint>
Extract a ResumptionHint for a future 0-RTT reconnect.
Returns Some after a successful handshake; None while still
handshaking, after a failure, or before the inner session has
been published.
Store the hint alongside the pinned HybridVerifyingKey of the
server it was negotiated against and feed it back to
connect_pinned_with_resumption. Reusing a hint across
servers is a configuration bug — the resumption_secret is
server-pinned.
Sourcepub async fn current_epoch(&self) -> Option<u8>
pub async fn current_epoch(&self) -> Option<u8>
Current rekey epoch of the established session (None while still
connecting). Rust-only — used by soak / integration tests to confirm
that automatic mid-session rekey (C1) advanced the epoch.
Sourcepub async fn set_rekey_threshold(&self, n: u64) -> bool
pub async fn set_rekey_threshold(&self, n: u64) -> bool
Override the automatic-rekey send-invocation high-watermark on the
established session (default REKEY_SOFT_LIMIT). Returns false if
the session is still connecting. Rust-only — primarily for soak/load
harnesses that need to exercise mid-session rekey without sending 2^47
packets.
Sourcepub async fn set_traffic_shaping(&self, config: TrafficShapingConfig) -> bool
pub async fn set_traffic_shaping(&self, config: TrafficShapingConfig) -> bool
Apply an anti-fingerprint traffic-shaping configuration to the established
session (WIRE v6, direction #4). Returns false if the session is still
connecting. All shaping is opt-in (default: none); enabling size padding
(PaddingPolicy::Padme) makes outbound packets pad up to a PADÉ bucket so
the datagram size no longer tracks the payload size, at a bounded (≈ ≤12%
worst-case) bandwidth cost. FFI-exported so mobile / other embedders can
tune it.
Sourcepub async fn traffic_shaping(&self) -> Option<TrafficShapingConfig>
pub async fn traffic_shaping(&self) -> Option<TrafficShapingConfig>
Read back the traffic-shaping config currently applied to the established
session (#9). None while still connecting (the session is not installed
yet — the pending config set via set_traffic_shaping
will apply on install). FFI-exported.
Sourcepub async fn migrate(&self, local_addr: String) -> Result<(), CoreError>
pub async fn migrate(&self, local_addr: String) -> Result<(), CoreError>
Migrate the session to a new local network address (Phase 4 — embedder-
triggered connection migration). The embedder calls this when the OS reports a
network change (Wi-Fi↔cellular, NAT rebind); local_addr is the new local
bind address (e.g. "0.0.0.0:0" to let the OS pick an ephemeral port on the
new interface).
Best-effort and non-blocking on validation. It hands the request to the
background pump, which rebinds the transport (keeping the old socket for the
overlap) and bumps the send path_id; the path validation + server-side peer
switch then complete asynchronously. The keys and session persist — no
re-handshake. A failed rebind never tears the session down: it keeps running
on the existing socket (broken-rebind safety). Err here means only that the
session was already closed (the command channel is gone).
Sourcepub async fn disconnect(&self) -> Result<(), CoreError>
pub async fn disconnect(&self) -> Result<(), CoreError>
Send the graceful close frame and shut the session down.
Named disconnect rather than close because UniFFI’s Kotlin
generator unconditionally adds AutoCloseable.close() to every
object, and a Rust-side close here would conflict with it.
Source§impl PhantomSession
impl PhantomSession
Sourcepub fn demux(&self) -> Arc<StreamDemultiplexer>
pub fn demux(&self) -> Arc<StreamDemultiplexer>
Get the stream demultiplexer (internal use, not exposed to UniFFI)
Sourcepub async fn set_liveness_config(&self, cfg: LivenessConfig) -> bool
pub async fn set_liveness_config(&self, cfg: LivenessConfig) -> bool
Override the path-liveness thresholds on the established session (Phase 4 /
P4.3). Returns false if the session is still connecting. Rust-only (the
LivenessConfig type is not on the UniFFI surface) — for tests / advanced
embedders that want a faster or slower path-down / migration-idle timeout than
the default.
Sourcepub async fn migrate_server(&self, local_addr: String) -> Result<(), CoreError>
pub async fn migrate_server(&self, local_addr: String) -> Result<(), CoreError>
Migrate the server side of this session to a new local send address (the
server-side mirror of migrate). Intended for an accepted server
session whose network path changes (failover, multi-homing, an egress NAT rebind):
the server rebinds its send socket to local_addr and rotates its server→client
path_id + connection-ID in lock-step, so the peer follows the fresh s2c source
(its unconnected socket hears it) and an observer cannot relink the session by the
s2c ConnId across the move. The keys and session persist — no re-handshake.
The server keeps RECEIVING client→server traffic on the established (listen) address
through the overlap, so the session stays bidirectional immediately. Best-effort: a
failed rebind leaves the session on the old send socket and never tears it down.
Err here means only that the session was already closed.
Rust-only (deliberately not on the UniFFI/FFI surface): server migration is a native-deployment operation, not a mobile-client one. The peer’s symmetric c2s follow (switching its send target to the new server address once the old one is unreachable) and the matching c2s CID rotation are added in the follow-up security-core change.