# Rust blocking TLS handshake ownership and recovery integration

## Reproduced baseline

The unchanged TLS implementation at `5ab4cdb74` runs the corrected seven-case
public-API fixture. Five controls pass: ordinary client/server timeout,
client/server context role refusal and successful transfer to shared connection
ownership. Both regressions fail: client and server handshakes with
`Duration::MAX` unwind when calculating the deadline, leave their consumed
socket descriptors open and leave the loopback peers returning WouldBlock
instead of EOF. The baseline exits 101 with 5 passed, 2 failed, 0 ignored and
no TLS resource skip.

Each failing case records socket identity and the original peer result before
closing its unreachable baseline descriptor. A reused descriptor is neither
reported as a leak nor closed: the fixture compares `/proc/self/fd` socket
identities, without forced descriptor replacement. The baseline's otherwise
unreachable native TLS allocations disappear when that test process exits.
The regression directly observes socket cleanup; native TLS cleanup is the
guard's existing `SSL_free` path, rather than a measured allocator count.

The initial fixture compile fails with E0624 because `Conn::close` is private.
The correction uses the public clone and drop contract and waits for client
completion before dropping the server connection. That compile failure and
source remain separate from the reproduced runtime failures. The corrected
fixture bytes are identical between the negative baseline and first fixed runs.
Final review changes only the fixture locator to `LIBHFT_SHARED_FIXTURES`, as
the existing TLS unit tests do, so standalone exports resolve their mapped
fixtures too. Certificate bytes, test scenarios and assertions stay the same;
both complete editions and Clippy are rerun for that final fixture.

## Ownership boundary

`Context::finish`, shared by blocking client and server handshakes, temporarily
uses the existing `Handshake` pending owner. It owns the initialized native TLS
object and socket before the deadline calculation and handshake loop. Ordinary
errors and unwinds free the TLS object and close the socket through the same
pending-handshake destructor; unfinished TLS state does not send close_notify.
After successful negotiation, `Conn::new` establishes shared ownership and the
temporary guard is disarmed immediately.

The change adds no public API, enum variant, allocation or per-message work.
Unrepresentable timeout panics still propagate; ordinary timeout/error strings
and role-refusal behavior remain unchanged. Established connections keep their
existing shared close state and final-drop behavior. The asynchronous server
handshake, application callbacks, session checkpointing and initiator writer
fence policy retain their existing contracts. This slice makes no performance
or production endpoint qualification claim.

## Integrated recovery regression

The integrated candidate at `a4cf15faa` includes upstream `1bfc475b2`, which
skips `advance()` after `take` / `take_with` exhaust the receive buffer. Its
complete default stage fails four existing recovery cases: oversized first
application replay, replay of an application missing from the retained slot,
gap-fill recovery and chunked resend recovery. The unit executable reports
1,442 passed, 4 failed; Cargo exits 101 and the selected stage reports FAILED,
0 ran, 1 failed, 0 skipped. Open and Clippy qualification do not run after that
failure. This failed integrated attempt is retained separately.

Two new focused cases exercise the copied and borrowed consume APIs with the
same final replay and an exhausted receive buffer. Both fail with the unchanged
upstream guard: 0 passed, 2 failed. The replay advances the expected sequence,
but `resend_pending` remains set until another advance. This directly reproduces
the state error without a new read, timeout or benchmark interpretation.

Both consume guards now call `advance()` when either wire bytes remain or
recovery is pending. This preserves watermark resolution and retained-frame
delivery while keeping the normal drained-buffer shortcut. Existing recovery
cases and the buffered-next-application control qualify the change. There is no
new session field, API, allocation or measured performance claim.

## Verification and retention

| Gate | Result |
|---|---|
| Unchanged engine with corrected public fixture | 5 controls passed, 2 regressions failed |
| Unchanged parser guard with copied/borrowed recovery fixtures | 0 passed, 2 failed |
| Focused recovery and buffered-next-frame controls | 67 default / 45 open recovery cases, plus 1 buffered-frame control each; passed |
| Focused default/open public fixture | 7 passed each, no TLS resource skip |
| Complete default Rust stage | 2,028 passed, 0 failed, 1 existing ignored builders doctest |
| Complete open Rust stage | 1,841 passed, 0 failed, the same 1 ignored doctest |
| Strict all-target Clippy, `-D warnings` | Passed in both editions |
| New fixture rustfmt, export scan, API inventory and coverage checks | Passed |

Final complete gates use `014d6c2a5bd4aeb9e249adb780318197d88c7c9e`,
including the repaired consume guards, portable TLS fixture locator and updated
upstream numeric-range fixture. Before and after qualification, all 366 tracked
Rust and 632 shared fixture/reference/gate/consumer input path-keysets and SHA256
hashes match. Each selected Rust stage reports 1 ran, 0 failed, 0 skipped and
PARTIAL for the other unselected monorepo stages. The focused TLS runs exercise
real client/server handshakes without a TLS resource skip.

The preceding candidate at `c26de767b` also passes both complete editions and
Clippy before the fixture-locator review; complete gates and Clippy are repeated
for the final locator. Earlier full stages pass 2,025 / 1,838 before upstream
parser integration, then are deliberately interrupted before Clippy finishes
so the new Rust inputs can be integrated. Their captures and the later four-case
failed integrated stage stay separate from the final successful results.

Tests use Rust/Cargo 1.92.0; strict Clippy uses the retained 1.93.1 sysroot.
Heavy jobs run serially on Scan at `-j8` with one test thread. The negative TLS
baseline uses the shared pool, final gates isolated CPUs 10–19. The guard requires
a physical host above 120 GiB total, at least 64 GiB available, observed foreign
process RSS below 16 GiB, and foreign compiler affinity disjoint from its pool.
It cannot run on smaller/WSL hosts. Queued/dequeued waits and the owned-process
integration stop are retained. BEELINK2 and HP run no heavy job for this slice.

Retained source pins, baseline sources, fixture identity, actual commands,
initial compile error, negative baseline, full reports/stdout/stderr, Clippy
logs, resource guard and recoverable candidate/publication bundle live under
`~/archive/libhft/rust-tls-handshake-ownership-20261012`.

API/session/subsystem scores remain unchanged. Live production feed/order
endpoint evidence and venue/FIXP certification remain open; the earlier
two-host shared-store HA/C2 and physical NIC proofs retain their recorded scope.
