Skip to content

daemon: Unify and harden credential transport lifecycle - #220

Open
Preovaleo wants to merge 10 commits into
linux-credentials:mainfrom
Preovaleo:perso/generic_transport
Open

daemon: Unify and harden credential transport lifecycle#220
Preovaleo wants to merge 10 commits into
linux-credentials:mainfrom
Preovaleo:perso/generic_transport

Conversation

@Preovaleo

@Preovaleo Preovaleo commented Aug 30, 2026

Copy link
Copy Markdown

Hello 👋,

Summary

Follow-up to #204 and #214.

Credential transports previously handled state conversion, request completion, and cancellation in separate USB, hybrid, and NFC stream wrappers. This duplicated lifecycle logic and left several edge cases around idle cancellation, stale terminal events, and transports ending without producing a result.

This PR:

  • extracts the existing push-based test helper into reusable typed scripted transports;
  • replaces the transport-specific lifecycle wrappers with one shared lifecycle stream while preserving each transport's public states;
  • wakes pending streams immediately when their request is cancelled;
  • binds terminal events to the request that created their stream;
  • takes one lifecycle snapshot and shares it across all selected transports;
  • fails the request when every selected transport exits without a terminal result;
  • limits each request to one active discovery stream at the trusted UI boundary;
  • releases that discovery slot when the stream ends, so a later discovery attempt remains possible;
  • updates the architecture documentation and changelog.

Result

The first valid terminal event completes the active request and cancels the competing transports.

Explicit cancellation no longer depends on additional transport activity. Events from stale streams cannot complete a newer request, and silent transport exhaustion produces an error instead of leaving the caller pending.

Repeated DiscoveryRequested signals cannot create concurrent discovery tasks for the same request. This is a concurrency bound rather than a single-discovery policy: once the active stream ends, another discovery attempt can start.

Scope

This PR does not change how available authenticators are enumerated, implement dynamic source-list updates, or define a new retry policy. Those remain separate concerns tracked by #32 and #206.

Testing

The daemon now has 39 unit tests, including coverage for:

  • USB, hybrid, and NFC success and failure;
  • explicit cancellation and cancellation while streams are pending;
  • cancellation with queued transport events;
  • simultaneous cancellation and terminal completion;
  • stale events from an earlier request;
  • concurrent terminal events from multiple transports;
  • partial and global transport exhaustion;
  • concurrent discovery requests and subsequent retry.

Validation performed:

  • cargo test -p credentialsd --bin credentialsd — 39 tests passed
  • cargo clippy -p credentialsd --bin credentialsd -- -Dwarnings
  • cargo fmt --all --check
  • git diff --check origin/main...HEAD

The targeted daemon test suite also passes independently at every commit in the PR.

@Preovaleo
Preovaleo force-pushed the perso/generic_transport branch 2 times, most recently from 6137928 to 8ab093c Compare August 30, 2026 14:14
@Preovaleo

This comment was marked as resolved.

@Preovaleo
Preovaleo force-pushed the perso/generic_transport branch 2 times, most recently from e9dfb89 to e6c45c1 Compare August 30, 2026 15:38
@Preovaleo
Preovaleo marked this pull request as draft August 30, 2026 16:28
Théo Bougé added 2 commits August 30, 2026 18:54
Correct grammar and wording in the architecture overview without changing the documented design.
The push-based helper was embedded in the credential service tests, and its cancellation-focused names hid that it could script any transport state. Move it to test_support, rename it around that broader role, and share typed completion and failure helpers across USB, hybrid, and NFC tests.
@Preovaleo
Preovaleo force-pushed the perso/generic_transport branch from e6c45c1 to 77c526b Compare August 30, 2026 17:07
Théo Bougé added 8 commits August 30, 2026 20:19
USB, hybrid, and NFC duplicated state conversion and request completion in separate stream wrappers, making their terminal behavior easy to desynchronize. Route typed transport events through one lifecycle stream and cover success and failure for every backend.
A lifecycle stream only noticed cancellation after another transport event, so idle discovery could remain pending. Await transport polling through CancellationToken::run_until_cancelled so cancellation wakes the stream immediately.
A late terminal event could consume whichever request context was active and complete a newer request. Attach the request ID and a unique marker to each lifecycle stream, then ignore terminal results whose ownership no longer matches.
Discovery let each transport read the active request independently, so a replacement between reads could mix ownership and cancellation data. Snapshot the request ID, marker, and token once and pass that typed lifecycle to every selected transport.
When the final selected transport ended without a terminal result, discovery returned EOF while the request stayed active and its caller could wait indefinitely. Complete that request with a transport-independent failure while ignoring exhaustion from stale or explicitly cancelled discovery.
A terminal event can become ready in the same poll that cancels its request. Without the post-poll cancellation check, that event could still consume the active context.

Cancel the token from inside the scripted poll and verify that the terminal result is discarded without sending a response.
Describe request-scoped terminal handling, immediate cancellation wakeups, and transport exhaustion. Keep the changelog focused on the resulting fixes for stalled discovery and stale authenticator results.
Repeated DiscoveryRequested signals could spawn independent streams for the same request, consuming resources and racing request completion.

Hold one request-scoped permit for the lifetime of the forwarded stream. Concurrent signals are ignored, while releasing the permit allows a later discovery attempt.
@Preovaleo
Preovaleo force-pushed the perso/generic_transport branch from 77c526b to 9e97418 Compare August 30, 2026 18:25
@Preovaleo
Preovaleo marked this pull request as ready for review August 30, 2026 19:31
@iinuwa

iinuwa commented Sep 1, 2026

Copy link
Copy Markdown
Member

Hello! Thanks for your contribution!

This looks like a good improvement. I typically only have 10 minutes blocks of time to do review, so this will take me quite a while to get through all these changes. Even though it seems you've split your commits well, limitations in GitHub's review features will also make it hard for me to get through these.

Could you post these as separate PRs? You could use GitHub's new stack feature to group them together if you like.

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.

2 participants