feat(cable): caller-driven teardown and linger for linking data - #276
Draft
AlfioEmanueleFresta wants to merge 13 commits into
Draft
AlfioEmanueleFresta wants to merge 13 commits into
AlfioEmanueleFresta wants to merge 13 commits into
Conversation
On hybrid connections the client never sent a Shutdown frame and tore the connection down immediately after the response. This sends a Shutdown control frame when the channel is closed and adds the encrypted test harness for the tunnel protocol. Part of #257.
A Shutdown control frame carries no payload, so the empty-payload check rejected it as invalid framing before the message type was inspected and a peer shutdown surfaced as a UX error instead of a clean close.
Nothing publishes it yet. A lingering connection admits no further operations and reports as closed.
…tics Replace the one-shot close signal with a teardown watch shared between the channel and the connection task. close() sends Shutdown and waits for termination, cancel() drops the connection without a goodbye, and an unattended drop is a hard cancel. The connect and handshake stages and the initial receive are interruptible, so a close during connecting no longer hangs, and every outbound send is bounded.
A state-assisted QR connection can keep receiving after the ceremony so the authenticator's linking update is stored for a later state-assisted reconnect. The caller opts in with a CableLingerConfig on ChannelSettings and calls linger() on the channel. The linger is detached from the channel, bounded by a configurable window under a hard cap, and tracked in a caller-owned CableLingerRegistry so a new connection evicts it and the caller can drain it on suspend.
The tunnel accepted messages up to the tungstenite default of 64 MiB before the protocol layer could reject them. Cap them at the CBOR bound plus framing overhead.
Dropping the last registry clone cancelled every tracked connection, including one still connecting or mid-ceremony, so a registry built inline in the settings was gone by the time channel() returned and the connection died silently. Registry drop now cancels lingerers only, and a connection whose registry is gone by the time it would linger closes instead. Also bound the linking update store write in the active phase so a wedged store cannot hold off a close, and count only undecryptable frames toward the linger budget so unknown frame types are ignored as intended.
A close that does not complete within the flush timeout now cancels the connection so close means closed. A close after linger() no longer waits out the flush timeout for a termination that will not come.
A Noise transport message is at most 65535 bytes, so anything larger could never be decrypted. Bound WebSocket messages there instead of at the CBOR size.
CableChannel::close(CableClose::Immediate) sends Shutdown and waits for termination, CableClose::Linger keeps receiving for a late linking update. The trait close() maps to Immediate.
AlfioEmanueleFresta
force-pushed
the
fix/cable-shutdown-and-linger
branch
from
September 19, 2026 15:48
155ff1c to
f746e4d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On hybrid connections the client never sent a Shutdown frame and tore the connection down as soon as the response arrived, so a linking update the phone sends a little later was always lost.
The caller now decides how a hybrid connection ends. Closing the channel sends Shutdown and waits for the connection to terminate. Cancelling drops it without a goodbye, and so does dropping the channel. Closing with the linger mode sends Shutdown and keeps receiving in the background, for a configurable window under a hard cap, to capture the linking update for a later state-assisted reconnect. Callers opt in by carrying a linger config on the channel settings. Its registry evicts a lingering connection when a new one is opened, and can be drained on suspend or shutdown. Only a state-assisted QR connection with a store can linger, everything else just closes.
Fixed along the way: a close during connecting no longer hangs, an inbound Shutdown decodes as a clean peer close instead of a framing error, every outbound send is bounded, and tunnel messages are capped at the Noise ceiling.
Source breaking and wants a 0.11.0 bump: ChannelSettings gained a field, so struct literals need
..Default::default(), and the cable channel's close used to be a no-op.Part of #257.