Skip to content

Display error/reason to user, if a transport had to be restarted - #231

Open
msirringhaus wants to merge 10 commits into
linux-credentials:mainfrom
msirringhaus:cancellation_part4
Open

msirringhaus wants to merge 10 commits into
linux-credentials:mainfrom
msirringhaus:cancellation_part4

Conversation

@msirringhaus

Copy link
Copy Markdown
Collaborator

Note: Based on #230 , and needs to be rebased if/when this lands.

Only send events, if the device was actively used by the user (Hybrid: QR scanned, USB: initial touch done, etc.).

Extended the Restarting-events introduced in #230 with an error reason. Display it at the bottom of the start page. Removed those error codes that trigger restarts from the terminal error code list.

Last commit updates the lang-files.

@iinuwa iinuwa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haven't run this yet, but a couple of questions

} else {
// Pre-active: the QR was never consumed or the BLE channel
// failed before the phone responded. Reissue silently.
tracing::debug!(?err, "Hybrid pre-active error, reissuing QR silently");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reading this on the train, so just wanted to double-check: can this get us in a restart loop? Is there a point where we eventually stop retrying a transport (without failing the ceremony)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it could potentially end in a loop. USB has a somewhat similar problem, actually.

I actually have to think about this a bit more. If and how to display this to the user, for example (do we want something like "we removed hybrid from the list, because it failed too often" in the UI?), and how to structure the error handling to deal with this.
It's somewhat hard to distinguish between "this will never work"-errors and those who might be recoverable.
I need a clearer structure for stopping transports (e.g. probably makes sense to stop it on Internal-errors)

Or we never stop/remove a transport, but only throttle it with delays between retries?

Comment thread credentialsd-common/src/model.rs Outdated
/// as invalid by `TryFrom`.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportRestartReason {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're currently using PascalCase strings to communicate enums everywhere else in the API. I find that easier to read in D-Bus output while debugging, and the extra 10 or so bytes in the message doesn't hurt.

Do you have opinions either way? If not, I'd recommend to make this a string for consistency.

@msirringhaus

Copy link
Copy Markdown
Collaborator Author

Ok, I added a way to break the restart loop now. Sorry, this has gotten really big. Please let me know, if you want me to somehow look into splitting this into multiple PRs (although I'm not yet sure, how to easily do that without breaking the functionality. But I'm sure, I can come up with something).

This now basically does what we did for USB already for all transports: Count 5 restarts before user interaction and then stop the transport. For this, we send an update to the UI, telling it that a certain transport has become unavailable.

In doing so, I also changed the initial detection of available transactions a bit. We had duplicates of this. I removed those, and now we have one source of truth for available transports (which now also deactivates NFC, instead of commenting out the handler).

In CreateSession, we have sent a list of fake devices to the UI, where we only ever used the transport. I took the liberty to change CreateSession such, that we only send available transports now instead. Subsequent events then can remove a transport from the list the UI has, causing it to display an error message and not list that section in the start-page anymore. This is a breaking change, obviously.

@iinuwa

iinuwa commented Oct 10, 2026

Copy link
Copy Markdown
Member

This API is getting a little gnarly, and I think it's going to be too hard for other backend implementations to bite off. I think we need to try another direction.

I wonder if we've been thinking about this backwards: what if we send the errors to the UI as normal, and then the UI can send a trigger to restart the request or not? Then a simpler UI implementation can just bail and leave the user to restart the request if they want, and a more complex UI can show the error with "try again"/"cancel" options, and if they select try again, trigger the restart and re-do the flow. I think that leaves us with flat errors while still giving the ability to give the user more context.

I like:

  • deferring send of events until user interaction,
  • limits on the transport errors before user interaction, and
  • the ability to drop transports in the middle of the request if they're being flaky.

Maybe we need to model the transports as a D-Bus property that can be subscribed to instead?

@iinuwa iinuwa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made some comments on the API, but still need to dig into the credential_service changes. I'll try to get to those soon.

Overall, I'm feeling OK about the direction, but have some suggestions.

I'll also note that we removed devices in favor of transports, but I think that we'll need to add something like that back, just not fake, for #256

Comment on lines +148 to +164
async fn get_available_public_key_devices(&self) -> Result<Vec<Device>, ()>;
/// Detects the transports usable for the current request and remembers them,
/// so `start_discovery` starts exactly what was reported to the UI.
async fn detect_transports(&self) -> Vec<Transport>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return this from init_request() instead and remove this method altogether.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense. I still need an accessor-function, though

response_channel: tx,
request_id,
cancellation: cancellation.clone(),
transports: Vec::new(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, let's make get_transports() private and call it here.

Comment thread credentialsd-common/src/model.rs Outdated
Comment on lines +12 to +22

/// Machine-readable reason for a transport restart. Shared across all transports.
/// Human-readable strings are built by the UI (gettext).
///
/// The `#[repr(u8)]` layout doubles as the D-Bus wire format via `From<Self> for u8`
/// and `TryFrom<u8> for Self`. Discriminants must remain stable across releases.
/// Discriminant 0 is deliberately unused so that an all-zeroed message is recognisable
/// as invalid by `TryFrom`.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportRestartReason {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this enum down so that members are ordered alphabetically?

}

/// Stops offering transports that gave up for this request.
async fn drop_transports(&mut self, gone: &[Transport]) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way this is modeled requires the UI to keep state of all the transports it has seen and dropped. I think we should instead expose a Transports property and update that before we send TransportRestartReason::TransportUnavailable, so that the UI can either use that error as as a signal to check the Transports property or it can subscribe to Transports property changes directly. (this could also allow us to add transports during the ceremony if we want to.) This means that the UI and daemon always agree on what Transports are.

If you agree with this, I think it'd be good to do before the next release, but it can be a follow-up PR.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One major concern with this PR (rightfully so), was the complexity of the API. Introducing two steps instead of one (receiving TransportRestartReason::TransportUnavailable and then checking Transports) seems counterproductive?

Comment thread CHANGELOG.md
- daemon: Fix all subsequent requests being rejected after the UI failed to launch.
- ui: Add Bulgarian translation (Thank you, @salif!)
- webext: Try to read app ID from associated desktop entry (Thank you, @michaelbeaumont!)
- ui: Basic recovery from failed attempts to use devices (e.g. flaky bluetooth)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll need to move these up to the unreleased section

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are already in that section?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, ignore that. After merging with main you mean. Yes, moved them.

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