pub mod attestation;
pub mod client_data;
#[allow(dead_code)] // TODO(MS): Remove me asap
pub mod commands;
pub mod preflight;
pub mod server;
pub(crate) mod utils;

use crate::authenticatorservice::{RegisterArgs, SignArgs};
use crate::crypto::COSEAlgorithm;
use crate::ctap2::client_data::ClientDataHash;
use crate::ctap2::commands::authenticator_config::{
    AuthConfigCommand, AuthConfigResult, AuthenticatorConfig,
};
use crate::ctap2::commands::bio_enrollment::{
    BioEnrollment, BioEnrollmentCommand, BioEnrollmentResult, FingerprintSensorInfo,
};
use crate::ctap2::commands::client_pin::{
    ChangeExistingPin, Pin, PinError, PinUvAuthTokenPermission, SetNewPin,
};
use crate::ctap2::commands::credential_management::{
    CredManagementCommand, CredentialList, CredentialListEntry, CredentialManagement,
    CredentialManagementResult, CredentialRpListEntry,
};
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionOptions};
use crate::ctap2::commands::make_credentials::{
    dummy_make_credentials_cmd, MakeCredentials, MakeCredentialsOptions,
};
use crate::ctap2::commands::reset::Reset;
use crate::ctap2::commands::{
    repackage_pin_errors, CommandError, PinUvAuthCommand, PinUvAuthResult, RequestCtap2, StatusCode,
};
use crate::ctap2::preflight::{
    do_credential_list_filtering_ctap1, do_credential_list_filtering_ctap2,
    silently_discover_credentials,
};
use crate::ctap2::server::{
    CredentialProtectionPolicy, RelyingParty, ResidentKeyRequirement, UserVerificationRequirement,
};
use crate::errors::{AuthenticatorError, UnsupportedOption};
use crate::statecallback::StateCallback;
use crate::status_update::{send_status, BioEnrollmentCmd, CredManagementCmd, InteractiveUpdate};
use crate::transport::device_selector::{Device, DeviceSelectorEvent};
use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, FidoDeviceIO, FidoProtocol};
use crate::{ManageResult, ResetResult, StatusPinUv, StatusUpdate};
use std::sync::mpsc::{channel, RecvError, Sender};
use std::thread;
use std::time::Duration;

use self::commands::get_info::AuthenticatorVersion;

macro_rules! unwrap_option {
    ($item: expr, $callback: expr) => {
        match $item {
            Some(r) => r,
            None => {
                $callback.call(Err(AuthenticatorError::Platform));
                return false;
            }
        }
    };
}

macro_rules! unwrap_result {
    ($item: expr, $callback: expr) => {
        match $item {
            Ok(r) => r,
            Err(e) => {
                $callback.call(Err(e.into()));
                return false;
            }
        }
    };
}

macro_rules! handle_errors {
    ($error: expr, $status: expr, $callback: expr, $pin_uv_auth_result: expr, $skip_uv: expr) => {
        let mut _dummy_skip_puap = false;
        let mut _dummy_cached_puat = false;
        handle_errors!(
            $error,
            $status,
            $callback,
            $pin_uv_auth_result,
            $skip_uv,
            _dummy_skip_puap,
            _dummy_cached_puat
        )
    };
    ($error: expr, $status: expr, $callback: expr, $pin_uv_auth_result: expr, $skip_uv: expr, $skip_puap: expr) => {
        let mut _dummy_cached_puat = false;
        handle_errors!(
            $error,
            $status,
            $callback,
            $pin_uv_auth_result,
            $skip_uv,
            $skip_puap,
            _dummy_cached_puat
        )
    };
    ($error: expr, $status: expr, $callback: expr, $pin_uv_auth_result: expr, $skip_uv: expr, $skip_puap: expr, $cached_puat: expr) => {
        match $error {
            HIDError::Command(CommandError::StatusCode(StatusCode::ChannelBusy, _)) => {
                // Channel busy. Client SHOULD retry the request after a short delay.
                thread::sleep(Duration::from_millis(100));
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, _))
            | HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _))
                if matches!($pin_uv_auth_result, PinUvAuthResult::UsingInternalUv) =>
            {
                // This should only happen for CTAP2.0 tokens that use internal UV and failed
                // (e.g. wrong fingerprint used), while doing GetAssertion or MakeCredentials.
                send_status(
                    &$status,
                    StatusUpdate::PinUvError(StatusPinUv::InvalidUv(None)),
                );
                $skip_puap = false;
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::PinRequired, _))
                if matches!($pin_uv_auth_result, PinUvAuthResult::UsingInternalUv) =>
            {
                // This should only happen for CTAP2.0 tokens that use internal UV and failed
                // repeatedly, so that we have to fall back to PINs
                $skip_uv = true;
                $skip_puap = false;
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::UvBlocked, _))
                if matches!(
                    $pin_uv_auth_result,
                    PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(..)
                ) =>
            {
                // This should only happen for CTAP2.1 tokens that use internal UV and failed
                // repeatedly, so that we have to fall back to PINs
                $skip_uv = true;
                $skip_puap = false;
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::CredentialExcluded, _)) => {
                $callback.call(Err(AuthenticatorError::CredentialExcluded));
                break;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _))
                if $cached_puat =>
            {
                // We used the cached PUAT, but it was invalid. So we just try again
                // without the cached one and potentially trigger a new PIN/UV entry from
                // the user. This happens e.g. if the PUAT expires, or we get an all-zeros
                // PUAT from outside for whatever reason, etc.
                $cached_puat = false;
                $skip_puap = false;
                continue;
            }
            e => {
                warn!("error happened: {e}");
                $callback.call(Err(AuthenticatorError::HIDError(e)));
                break;
            }
        }
    };
}

fn ask_user_for_pin(
    was_invalid: bool,
    retries: Option<u8>,
    status: &Sender<StatusUpdate>,
) -> Result<Pin, AuthenticatorError> {
    info!("PIN Error that requires user interaction detected. Sending it back and waiting for a reply");
    let (tx, rx) = channel();
    if was_invalid {
        send_status(
            status,
            crate::StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx, retries)),
        );
    } else {
        send_status(
            status,
            crate::StatusUpdate::PinUvError(StatusPinUv::PinRequired(tx)),
        );
    }
    match rx.recv() {
        Ok(pin) => Ok(pin),
        Err(RecvError) => {
            // recv() can only fail, if the other side is dropping the Sender.
            info!("Callback dropped the channel. Aborting.");
            Err(AuthenticatorError::CancelledByUser)
        }
    }
}

/// Try to fetch PinUvAuthToken from the device and derive from it PinUvAuthParam.
/// Prefer UV, fallback to PIN.
/// Prefer newer pinUvAuth-methods, if supported by the device.
fn get_pin_uv_auth_param<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2>(
    cmd: &mut T,
    dev: &mut Dev,
    permission: PinUvAuthTokenPermission,
    skip_uv: bool,
    uv_req: UserVerificationRequirement,
    alive: &dyn Fn() -> bool,
    pin: &Option<Pin>,
) -> Result<PinUvAuthResult, AuthenticatorError> {
    // CTAP 2.1 is very specific that the request should either include pinUvAuthParam
    // OR uv=true, but not both at the same time. We now have to decide which (if either)
    // to send. We may omit both values. Will never send an explicit uv=false, because
    //  a) this is the default, and
    //  b) some CTAP 2.0 authenticators return UnsupportedOption when uv=false.

    // We ensure both pinUvAuthParam and uv are not set to start.
    cmd.set_pin_uv_auth_param(None)?;
    cmd.set_uv_option(None);

    // Skip user verification if we're using CTAP1 or if the device does not support CTAP2.
    let info = match (dev.get_protocol(), dev.get_authenticator_info()) {
        (FidoProtocol::CTAP2, Some(info)) => info,
        _ => return Ok(PinUvAuthResult::DeviceIsCtap1),
    };

    // Only use UV, if the device supports it and we don't skip it
    // which happens as a fallback, if UV-usage failed too many times
    // Note: In theory, we could also repeatedly query GetInfo here and check
    //       if uv is set to Some(true), as tokens should set it to Some(false)
    //       if UV is blocked (too many failed attempts). But the CTAP2.0-spec is
    //       vague and I don't trust all tokens to implement it that way. So we
    //       keep track of it ourselves, using `skip_uv`.
    let supports_uv = info.options.user_verification == Some(true);
    let supports_pin = info.options.client_pin.is_some();
    let pin_configured = info.options.client_pin == Some(true);

    // Check if the combination of device-protection and request-options
    // are allowing for 'discouraged', meaning no auth required.
    if cmd.can_skip_user_verification(info, uv_req) {
        return Ok(PinUvAuthResult::NoAuthRequired);
    }

    // Device does not support any (remaining) auth-method
    if (skip_uv || !supports_uv) && !supports_pin {
        if supports_uv && uv_req == UserVerificationRequirement::Required {
            // We should always set the uv option in the Required case, but the CTAP 2.1 spec
            // says 'Platforms MUST NOT include the "uv" option key if the authenticator does
            // not support built-in user verification.' This is to work around some CTAP 2.0
            // authenticators which incorrectly error out with CTAP2_ERR_UNSUPPORTED_OPTION
            // when the "uv" option is set. The RP that requested UV will (hopefully) reject our
            // response in the !supports_uv case.
            cmd.set_uv_option(Some(true));
        }
        return Ok(PinUvAuthResult::NoAuthTypeSupported);
    }

    // Device supports PINs, but a PIN is not configured. Signal that we
    // can complete the operation if the user sets a PIN first.
    if (skip_uv || !supports_uv) && !pin_configured {
        return Err(AuthenticatorError::PinError(PinError::PinNotSet));
    }

    if info.options.pin_uv_auth_token == Some(true) {
        if !skip_uv && supports_uv {
            // CTAP 2.1 - UV
            let pin_auth_token = dev
                .get_pin_uv_auth_token_using_uv_with_permissions(permission, cmd.get_rp_id(), alive)
                .map_err(|e| repackage_pin_errors(dev, e))?;
            cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
            Ok(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(pin_auth_token))
        } else {
            // CTAP 2.1 - PIN
            // We did not take the `!skip_uv && supports_uv` branch, so we have
            // `(skip_uv || !supports_uv)`. Moreover we did not exit early in the
            // `(skip_uv || !supports_uv) && !pin_configured` case. So we have
            // `pin_configured`.
            let pin_auth_token = dev
                .get_pin_uv_auth_token_using_pin_with_permissions(
                    pin,
                    permission,
                    cmd.get_rp_id(),
                    alive,
                )
                .map_err(|e| repackage_pin_errors(dev, e))?;
            cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
            Ok(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(pin_auth_token))
        }
    } else {
        // CTAP 2.0 fallback
        if !skip_uv && supports_uv && pin.is_none() {
            // If the device supports internal user-verification (e.g. fingerprints),
            // skip PIN-stuff

            // We need the shared secret for HMAC-extension, if it was requested, so we
            // have to establish one
            if cmd.hmac_requested() && info.supports_hmac_secret() {
                let _shared_secret = dev.establish_shared_secret(alive)?;
            }
            // CTAP 2.1, Section 6.1.1, Step 1.1.2.1.2.
            cmd.set_uv_option(Some(true));
            return Ok(PinUvAuthResult::UsingInternalUv);
        }

        let pin_auth_token = dev
            .get_pin_token(pin, alive)
            .map_err(|e| repackage_pin_errors(dev, e))?;
        cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
        Ok(PinUvAuthResult::SuccessGetPinToken(pin_auth_token))
    }
}

/// PUAP, as per spec: PinUvAuthParam
/// Determines, if we need to establish a PinUvAuthParam, based on the
/// capabilities of the device and the incoming request.
/// If it is needed, tries to establish one and save it inside the Request.
/// Returns Ok() if we can proceed with sending the actual Request to
/// the device, Err() otherwise.
/// Handles asking the user for a PIN, if needed and sending StatusUpdates
/// regarding PIN and UV usage.
#[allow(clippy::too_many_arguments)]
fn determine_puap_if_needed<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2>(
    cmd: &mut T,
    dev: &mut Dev,
    mut skip_uv: bool,
    permission: PinUvAuthTokenPermission,
    uv_req: UserVerificationRequirement,
    status: &Sender<StatusUpdate>,
    alive: &dyn Fn() -> bool,
    pin: &mut Option<Pin>,
) -> Result<PinUvAuthResult, AuthenticatorError> {
    while alive() {
        debug!("-----------------------------------------------------------------");
        debug!("Getting pinUvAuthParam");
        match get_pin_uv_auth_param(cmd, dev, permission, skip_uv, uv_req, alive, pin) {
            Ok(r) => {
                return Ok(r);
            }

            Err(AuthenticatorError::PinError(PinError::PinRequired)) => {
                let new_pin = ask_user_for_pin(false, None, status)?;
                *pin = Some(new_pin);
                skip_uv = true;
                continue;
            }
            Err(AuthenticatorError::PinError(PinError::InvalidPin(retries))) => {
                let new_pin = ask_user_for_pin(true, retries, status)?;
                *pin = Some(new_pin);
                continue;
            }
            Err(AuthenticatorError::PinError(PinError::InvalidUv(retries))) => {
                if retries == Some(0) {
                    skip_uv = true;
                }
                send_status(
                    status,
                    StatusUpdate::PinUvError(StatusPinUv::InvalidUv(retries)),
                )
            }
            Err(e @ AuthenticatorError::PinError(PinError::PinAuthBlocked)) => {
                send_status(
                    status,
                    StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked),
                );
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
            Err(e @ AuthenticatorError::PinError(PinError::PinBlocked)) => {
                send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinBlocked));
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
            Err(e @ AuthenticatorError::PinError(PinError::PinNotSet)) => {
                send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinNotSet));
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
            Err(AuthenticatorError::PinError(PinError::UvBlocked)) => {
                skip_uv = true;
                send_status(status, StatusUpdate::PinUvError(StatusPinUv::UvBlocked))
            }
            // Used for CTAP2.0 UV (fingerprints)
            Err(AuthenticatorError::PinError(PinError::PinAuthInvalid)) => {
                skip_uv = true;
                send_status(
                    status,
                    StatusUpdate::PinUvError(StatusPinUv::InvalidUv(None)),
                )
            }
            Err(e) => {
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
        }
    }
    Err(AuthenticatorError::CancelledByUser)
}

pub fn register<Dev: FidoDevice>(
    dev: &mut Dev,
    args: RegisterArgs,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::RegisterResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let mut options = MakeCredentialsOptions::default();

    if dev.get_protocol() == FidoProtocol::CTAP2 {
        let info = match dev.get_authenticator_info() {
            Some(info) => info,
            None => {
                callback.call(Err(HIDError::DeviceNotInitialized.into()));
                return false;
            }
        };

        // Set options based on the arguments and the device info.
        // The user verification option will be set in `determine_puap_if_needed`.
        options.resident_key = match args.resident_key_req {
            ResidentKeyRequirement::Required => Some(true),
            ResidentKeyRequirement::Preferred => {
                // Use a resident key if the authenticator supports it
                Some(info.options.resident_key)
            }
            ResidentKeyRequirement::Discouraged => Some(false),
        }
    } else {
        // Check that the request can be processed by a CTAP1 device.
        // See CTAP 2.1 Section 10.2. Some additional checks are performed in
        // MakeCredentials::RequestCtap1
        if args.resident_key_req == ResidentKeyRequirement::Required {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::ResidentKey,
            )));
            return false;
        }
        if args.user_verification_req == UserVerificationRequirement::Required {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::UserVerification,
            )));
            return false;
        }
        if !args
            .pub_cred_params
            .iter()
            .any(|x| x.alg == COSEAlgorithm::ES256)
        {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::PubCredParams,
            )));
            return false;
        }
    }

    // Client extension processing for credProtect:
    // "When enforceCredentialProtectionPolicy is true, and credentialProtectionPolicy's value is
    // [not "Optional"], the platform SHOULD NOT create the credential in a way that does not
    // implement the requested protection policy. (For example, by creating it on an authenticator
    // that does not support this extension.)"
    let dev_supports_cred_protect = dev
        .get_authenticator_info()
        .map_or(false, |info| info.supports_cred_protect());
    if args.extensions.enforce_credential_protection_policy == Some(true)
        && args.extensions.credential_protection_policy
            != Some(CredentialProtectionPolicy::UserVerificationOptional)
        && !dev_supports_cred_protect
    {
        callback.call(Err(AuthenticatorError::UnsupportedOption(
            UnsupportedOption::CredProtect,
        )));
        return false;
    }

    let mut makecred = MakeCredentials::new(
        ClientDataHash(args.client_data_hash),
        args.relying_party,
        Some(args.user),
        args.pub_cred_params,
        args.exclude_list,
        options,
        args.extensions.into(),
    );

    let mut skip_uv = false;
    let mut pin = args.pin;
    while alive() {
        // Requesting both because pre-flighting (credential list filtering)
        // can potentially send GetAssertion-commands
        let permissions =
            PinUvAuthTokenPermission::MakeCredential | PinUvAuthTokenPermission::GetAssertion;

        let pin_uv_auth_result = unwrap_result!(
            determine_puap_if_needed(
                &mut makecred,
                dev,
                skip_uv,
                permissions,
                args.user_verification_req,
                &status,
                alive,
                &mut pin,
            ),
            callback
        );
        // Do "pre-flight": Filter the exclude-list
        if dev.get_protocol() == FidoProtocol::CTAP2 {
            makecred.exclude_list = unwrap_result!(
                do_credential_list_filtering_ctap2(
                    dev,
                    &makecred.exclude_list,
                    &makecred.rp,
                    pin_uv_auth_result.get_pin_uv_auth_token(),
                ),
                callback
            );
        } else {
            let key_handle = do_credential_list_filtering_ctap1(
                dev,
                &makecred.exclude_list,
                &makecred.rp,
                &makecred.client_data_hash,
            );
            // That handle was already registered with the token
            if key_handle.is_some() {
                // Now we need to send a dummy registration request, to make the token blink
                // Spec says "dummy appid and invalid challenge". We use the same, as we do for
                // making the token blink upon device selection.
                send_status(&status, crate::StatusUpdate::PresenceRequired);
                let msg = dummy_make_credentials_cmd();
                let _ = dev.send_msg_cancellable(&msg, alive); // Ignore answer, return "CredentialExcluded"
                callback.call(Err(AuthenticatorError::CredentialExcluded));
                return false;
            }
        }

        debug!("------------------------------------------------------------------");
        debug!("{makecred:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");
        send_status(&status, crate::StatusUpdate::PresenceRequired);
        let resp = dev.send_msg_cancellable(&makecred, alive);
        match resp {
            Ok(result) => {
                callback.call(Ok(result));
                return true;
            }
            Err(e) => {
                handle_errors!(e, status, callback, pin_uv_auth_result, skip_uv);
            }
        }
    }
    false
}

pub fn sign<Dev: FidoDevice>(
    dev: &mut Dev,
    args: SignArgs,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::SignResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    if dev.get_protocol() == FidoProtocol::CTAP1 {
        // Check that the request can be processed by a CTAP1 device.
        // See CTAP 2.1 Section 10.3. Some additional checks are performed in
        // GetAssertion::RequestCtap1
        if args.user_verification_req == UserVerificationRequirement::Required {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::UserVerification,
            )));
            return false;
        }
        if args.allow_list.is_empty() {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::EmptyAllowList,
            )));
            return false;
        }
    }

    let mut allow_list = args.allow_list;
    let mut rp_id = RelyingParty::from(args.relying_party_id);
    let client_data_hash = ClientDataHash(args.client_data_hash);
    if let Some(ref app_id) = args.extensions.app_id {
        if !allow_list.is_empty() {
            // Try to silently discover U2F credentials that require the FIDO App ID extension. If
            // any are found, we should use the alternate RP ID instead of the provided RP ID.
            let alt_rp_id = RelyingParty::from(app_id);
            let silent_creds =
                silently_discover_credentials(dev, &allow_list, &alt_rp_id, &client_data_hash);
            if !silent_creds.is_empty() {
                allow_list = silent_creds;
                rp_id = alt_rp_id;
            }
        }
    }

    let mut get_assertion = GetAssertion::new(
        client_data_hash,
        rp_id,
        allow_list,
        GetAssertionOptions {
            user_presence: Some(args.user_presence_req),
            user_verification: None,
        },
        args.extensions.into(),
    );

    let mut skip_uv = false;
    let mut pin = args.pin;
    while alive() {
        let pin_uv_auth_result = unwrap_result!(
            determine_puap_if_needed(
                &mut get_assertion,
                dev,
                skip_uv,
                PinUvAuthTokenPermission::GetAssertion,
                args.user_verification_req,
                &status,
                alive,
                &mut pin,
            ),
            callback
        );

        // Do "pre-flight": Filter the allow-list
        let original_allow_list_was_empty = get_assertion.allow_list.is_empty();
        if dev.get_protocol() == FidoProtocol::CTAP2 {
            get_assertion.allow_list = unwrap_result!(
                do_credential_list_filtering_ctap2(
                    dev,
                    &get_assertion.allow_list,
                    &get_assertion.rp,
                    pin_uv_auth_result.get_pin_uv_auth_token(),
                ),
                callback
            );
        } else {
            let key_handle = do_credential_list_filtering_ctap1(
                dev,
                &get_assertion.allow_list,
                &get_assertion.rp,
                &get_assertion.client_data_hash,
            );
            match key_handle {
                Some(key_handle) => {
                    get_assertion.allow_list = vec![key_handle];
                }
                None => {
                    get_assertion.allow_list.clear();
                }
            }
        }

        // If the incoming list was not empty, but the filtered list is, we have to error out
        if !original_allow_list_was_empty && get_assertion.allow_list.is_empty() {
            // We have to collect a user interaction
            send_status(&status, crate::StatusUpdate::PresenceRequired);
            let msg = dummy_make_credentials_cmd();
            let _ = dev.send_msg_cancellable(&msg, alive); // Ignore answer, return "NoCredentials"
            callback.call(Err(HIDError::Command(CommandError::StatusCode(
                StatusCode::NoCredentials,
                None,
            ))
            .into()));
            return false;
        }

        // Use the shared secret in the extensions, if requested
        get_assertion = match get_assertion.process_hmac_secret_and_prf_extension(
            dev.get_shared_secret().map(|s| (s, &pin_uv_auth_result)),
        ) {
            Ok(value) => value,
            Err(e) => {
                callback.call(Err(e));
                return false;
            }
        };

        debug!("------------------------------------------------------------------");
        debug!("{get_assertion:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");
        send_status(&status, crate::StatusUpdate::PresenceRequired);
        let mut results = match dev.send_msg_cancellable(&get_assertion, alive) {
            Ok(results) => results,
            Err(e) => {
                handle_errors!(e, status, callback, pin_uv_auth_result, skip_uv);
            }
        };
        if results.len() == 1 {
            callback.call(Ok(results.swap_remove(0)));
            return true;
        }
        let (tx, rx) = channel();
        let user_entities = results
            .iter()
            .filter_map(|x| x.assertion.user.clone())
            .collect();
        send_status(
            &status,
            crate::StatusUpdate::SelectResultNotice(tx, user_entities),
        );
        match rx.recv() {
            Ok(Some(index)) if index < results.len() => {
                callback.call(Ok(results.swap_remove(index)));
                return true;
            }
            _ => {
                callback.call(Err(AuthenticatorError::CancelledByUser));
                return true;
            }
        }
    }
    false
}

pub(crate) fn reset_helper<T: From<ResetResult>>(
    dev: &mut Device,
    selector: Sender<DeviceSelectorEvent>,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<T>>,
    keep_alive: &dyn Fn() -> bool,
) {
    let reset = Reset {};
    info!("Device {:?} continues with the reset process", dev.id());

    debug!("------------------------------------------------------------------");
    debug!("{:?}", reset);
    debug!("------------------------------------------------------------------");
    send_status(&status, crate::StatusUpdate::PresenceRequired);
    let resp = dev.send_cbor_cancellable(&reset, keep_alive);
    if resp.is_ok() {
        // The DeviceSelector could already be dead, but it might also wait
        // for us to respond, in order to cancel all other tokens in case
        // we skipped the "blinking"-action and went straight for the actual
        // request.
        let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
    }

    match resp {
        Ok(()) => callback.call(Ok(T::from(()))),
        Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
        Err(HIDError::Command(CommandError::StatusCode(StatusCode::ChannelBusy, _))) => {}
        Err(e) => {
            warn!("error happened: {}", e);
            callback.call(Err(AuthenticatorError::HIDError(e)));
        }
    }
}

pub(crate) fn set_or_change_pin_helper<T: From<()>>(
    dev: &mut Device,
    mut current_pin: Option<Pin>,
    new_pin: Pin,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<T>>,
    alive: &dyn Fn() -> bool,
) {
    let mut shared_secret = match dev.establish_shared_secret(alive) {
        Ok(s) => s,
        Err(e) => {
            callback.call(Err(AuthenticatorError::HIDError(e)));
            return;
        }
    };

    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i.clone(),
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return;
        }
    };

    // If the device has a min PIN use that, otherwise default to 4 according to Spec
    if new_pin.as_bytes().len() < authinfo.min_pin_length.unwrap_or(4) as usize {
        callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
        return;
    }

    // As per Spec: "Maximum PIN Length: UTF-8 representation MUST NOT exceed 63 bytes"
    if new_pin.as_bytes().len() >= 64 {
        callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong(
            new_pin.as_bytes().len(),
        ))));
        return;
    }

    // Check if a client-pin is already set, or if a new one should be created
    let res = if Some(true) == authinfo.options.client_pin {
        let mut res;
        let mut was_invalid = false;
        let mut retries = None;
        loop {
            // current_pin will only be Some() in the interactive mode (running `manage()`)
            // In case that PIN is wrong, we want to avoid an endless-loop here with re-trying
            // that wrong PIN all the time. So we `take()` it, and only test it once.
            // If that PIN is wrong, we fall back to the "ask_user_for_pin"-method.
            let curr_pin = match current_pin.take() {
                None => match ask_user_for_pin(was_invalid, retries, &status) {
                    Ok(pin) => pin,
                    Err(e) => {
                        callback.call(Err(e));
                        return;
                    }
                },
                Some(pin) => pin,
            };

            res = ChangeExistingPin::new(&authinfo, &shared_secret, &curr_pin, &new_pin)
                .map_err(HIDError::Command)
                .and_then(|msg| dev.send_cbor_cancellable(&msg, alive))
                .map_err(|e| repackage_pin_errors(dev, e));

            if let Err(AuthenticatorError::PinError(PinError::InvalidPin(r))) = res {
                was_invalid = true;
                retries = r;
                // We need to re-establish the shared secret for the next round.
                match dev.establish_shared_secret(alive) {
                    Ok(s) => {
                        shared_secret = s;
                    }
                    Err(e) => {
                        callback.call(Err(AuthenticatorError::HIDError(e)));
                        return;
                    }
                };

                continue;
            } else {
                break;
            }
        }
        res
    } else {
        dev.send_cbor_cancellable(&SetNewPin::new(&shared_secret, &new_pin), alive)
            .map_err(AuthenticatorError::HIDError)
    };
    // the callback is expecting `Result<(), AuthenticatorError>`, but `ChangeExistingPin`
    // and `SetNewPin` return the default `ClientPinResponse` on success. Just discard it.
    callback.call(res.map(|_| T::from(())));
}

pub(crate) fn bio_enrollment(
    dev: &mut Device,
    puat_result: Option<PinUvAuthResult>,
    command: BioEnrollmentCmd,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::ManageResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i,
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return false;
        }
    };

    if authinfo.options.bio_enroll.is_none()
        && authinfo.options.user_verification_mgmt_preview.is_none()
    {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    let use_legacy_preview = authinfo.options.bio_enroll.is_none();

    // We are not allowed to request the BE-permission using UV, so we have to skip UV
    let mut skip_uv = authinfo.options.uv_bio_enroll != Some(true);
    // Currently not used, but if we want, we can just set the value here.
    let timeout = None;

    let mut bio_cmd = match &command {
        BioEnrollmentCmd::StartNewEnrollment(_name) => BioEnrollment::new(
            BioEnrollmentCommand::EnrollBegin(timeout),
            use_legacy_preview,
        ),
        BioEnrollmentCmd::DeleteEnrollment(id) => BioEnrollment::new(
            BioEnrollmentCommand::RemoveEnrollment(id.clone()),
            use_legacy_preview,
        ),
        BioEnrollmentCmd::ChangeName(id, name) => BioEnrollment::new(
            BioEnrollmentCommand::SetFriendlyName((id.clone(), name.clone())),
            use_legacy_preview,
        ),
        BioEnrollmentCmd::GetEnrollments => BioEnrollment::new(
            BioEnrollmentCommand::EnumerateEnrollments,
            use_legacy_preview,
        ),
        BioEnrollmentCmd::GetFingerprintSensorInfo => BioEnrollment::new(
            BioEnrollmentCommand::GetFingerprintSensorInfo,
            use_legacy_preview,
        ),
    };

    let mut skip_puap = false;
    let mut cached_puat = false; // If we were provided with a cached puat from the outside
    let mut pin_uv_auth_result = puat_result
        .clone()
        .unwrap_or(PinUvAuthResult::NoAuthRequired);
    // See, if we have a cached PUAT with matching permissions.
    match puat_result {
        Some(PinUvAuthResult::SuccessGetPinToken(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
            if !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1) // Only 2.1 has a permission-system
                || use_legacy_preview // Preview doesn't use permissions
                || t.permissions
                    .contains(PinUvAuthTokenPermission::BioEnrollment) =>
        {
            skip_puap = true;
            cached_puat = true;
            unwrap_result!(bio_cmd.set_pin_uv_auth_param(Some(t)), callback);
        }
        _ => {}
    }
    let mut pin = None;
    while alive() {
        if !skip_puap {
            pin_uv_auth_result = unwrap_result!(
                determine_puap_if_needed(
                    &mut bio_cmd,
                    dev,
                    skip_uv,
                    PinUvAuthTokenPermission::BioEnrollment,
                    UserVerificationRequirement::Preferred,
                    &status,
                    alive,
                    &mut pin,
                ),
                callback
            );
        }

        debug!("------------------------------------------------------------------");
        debug!("{bio_cmd:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");

        let resp = dev.send_cbor_cancellable(&bio_cmd, alive);
        match resp {
            Ok(result) => {
                skip_puap = true;
                match bio_cmd.subcommand {
                    BioEnrollmentCommand::EnrollBegin(..)
                    | BioEnrollmentCommand::EnrollCaptureNextSample(..) => {
                        let template_id =
                            if let BioEnrollmentCommand::EnrollCaptureNextSample((id, ..)) =
                                bio_cmd.subcommand
                            {
                                id
                            } else {
                                unwrap_option!(result.template_id, callback)
                            };
                        let last_enroll_sample_status =
                            unwrap_option!(result.last_enroll_sample_status, callback);
                        let remaining_samples = unwrap_option!(result.remaining_samples, callback);

                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::SampleStatus(
                                        last_enroll_sample_status,
                                        remaining_samples,
                                    ),
                                    Some(pin_uv_auth_result.clone()),
                                )),
                            ),
                        );

                        if remaining_samples == 0 {
                            if let BioEnrollmentCmd::StartNewEnrollment(Some(ref name)) = command {
                                bio_cmd.subcommand = BioEnrollmentCommand::SetFriendlyName((
                                    template_id.to_vec(),
                                    name.clone(),
                                ));
                                // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                                // of the command has changed, and that is part of the PUAP-calculation
                                unwrap_result!(
                                    bio_cmd.set_pin_uv_auth_param(
                                        pin_uv_auth_result.get_pin_uv_auth_token()
                                    ),
                                    callback
                                );
                                continue;
                            } else {
                                let auth_info =
                                    unwrap_option!(dev.refresh_authenticator_info(), callback);
                                send_status(
                                    &status,
                                    StatusUpdate::InteractiveManagement(
                                        InteractiveUpdate::BioEnrollmentUpdate((
                                            BioEnrollmentResult::AddSuccess(auth_info.clone()),
                                            Some(pin_uv_auth_result),
                                        )),
                                    ),
                                );
                                return true;
                            }
                        } else {
                            bio_cmd.subcommand = BioEnrollmentCommand::EnrollCaptureNextSample((
                                template_id,
                                timeout,
                            ));
                            // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                            // of the command has changed, and that is part of the PUAP-calculation
                            unwrap_result!(
                                bio_cmd.set_pin_uv_auth_param(
                                    pin_uv_auth_result.get_pin_uv_auth_token()
                                ),
                                callback
                            );
                            continue;
                        }
                    }
                    BioEnrollmentCommand::EnumerateEnrollments => {
                        let list = result.template_infos.iter().map(|x| x.into()).collect();
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::EnrollmentList(list),
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    BioEnrollmentCommand::SetFriendlyName(_) => {
                        let res = match command {
                            BioEnrollmentCmd::StartNewEnrollment(..) => {
                                let auth_info =
                                    unwrap_option!(dev.refresh_authenticator_info(), callback);
                                BioEnrollmentResult::AddSuccess(auth_info.clone())
                            }
                            _ => BioEnrollmentResult::UpdateSuccess,
                        };
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    res,
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    BioEnrollmentCommand::RemoveEnrollment(_) => {
                        let auth_info = unwrap_option!(dev.refresh_authenticator_info(), callback);
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::DeleteSuccess(auth_info.clone()),
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    BioEnrollmentCommand::CancelCurrentEnrollment => {
                        callback.call(Ok(ManageResult::Success));
                        return true;
                    }
                    BioEnrollmentCommand::GetFingerprintSensorInfo => {
                        let fingerprint_kind = unwrap_option!(result.fingerprint_kind, callback);
                        let max_capture_samples_required_for_enroll = unwrap_option!(
                            result.max_capture_samples_required_for_enroll,
                            callback
                        );
                        // FIDO_2_1_PRE-devices do not report this field. So we leave it optional.
                        let max_template_friendly_name = result.max_template_friendly_name;
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::FingerprintSensorInfo(
                                        FingerprintSensorInfo {
                                            fingerprint_kind,
                                            max_capture_samples_required_for_enroll,
                                            max_template_friendly_name,
                                        },
                                    ),
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                };
            }
            Err(e) => {
                handle_errors!(
                    e,
                    status,
                    callback,
                    pin_uv_auth_result,
                    skip_uv,
                    skip_puap,
                    cached_puat
                );
            }
        }
    }
    false
}

pub(crate) fn credential_management(
    dev: &mut Device,
    puat_result: Option<PinUvAuthResult>,
    command: CredManagementCmd,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::ManageResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let mut skip_uv = false;
    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i.clone(),
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return false;
        }
    };

    if authinfo.options.cred_mgmt != Some(true)
        && authinfo.options.credential_mgmt_preview != Some(true)
    {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    let use_legacy_preview = authinfo.options.cred_mgmt != Some(true);

    // FIDO_2_1_PRE-devices do not support UpdateUserInformation.
    if use_legacy_preview
        && !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1)
        && matches!(command, CredManagementCmd::UpdateUserInformation(..))
    {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    // If puap is provided, we can skip puap-determination (i.e. PIN entry)
    let mut cred_management = match command {
        CredManagementCmd::GetCredentials => {
            CredentialManagement::new(CredManagementCommand::GetCredsMetadata, use_legacy_preview)
        }
        CredManagementCmd::DeleteCredential(cred_id) => CredentialManagement::new(
            CredManagementCommand::DeleteCredential(cred_id),
            use_legacy_preview,
        ),
        CredManagementCmd::UpdateUserInformation(cred_id, user) => CredentialManagement::new(
            CredManagementCommand::UpdateUserInformation((cred_id, user)),
            use_legacy_preview,
        ),
    };
    let mut credential_result = CredentialList::new();
    let mut remaining_rps = 0;
    let mut remaining_cred_ids = 0;
    let mut current_rp = 0;
    let mut skip_puap = false;
    let mut cached_puat = false; // If we were provided with a cached puat from the outside
    let mut pin_uv_auth_result = puat_result
        .clone()
        .unwrap_or(PinUvAuthResult::NoAuthRequired);
    // See, if we have a cached PUAT with matching permissions.
    match puat_result {
        Some(PinUvAuthResult::SuccessGetPinToken(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
            if !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1) // Only 2.1 has a permission-system
                || use_legacy_preview // Preview doesn't use permissions
                || t.permissions
                    .contains(PinUvAuthTokenPermission::CredentialManagement) =>
        {
            skip_puap = true;
            cached_puat = true;
            unwrap_result!(cred_management.set_pin_uv_auth_param(Some(t)), callback);
        }
        _ => {}
    }
    let mut pin = None;
    while alive() {
        if !skip_puap {
            pin_uv_auth_result = unwrap_result!(
                determine_puap_if_needed(
                    &mut cred_management,
                    dev,
                    skip_uv,
                    PinUvAuthTokenPermission::CredentialManagement,
                    UserVerificationRequirement::Preferred,
                    &status,
                    alive,
                    &mut pin,
                ),
                callback
            );
        }

        debug!("------------------------------------------------------------------");
        debug!("{cred_management:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");

        let resp = dev.send_cbor_cancellable(&cred_management, alive);
        match resp {
            Ok(result) => {
                skip_puap = true;
                match cred_management.subcommand {
                    CredManagementCommand::GetCredsMetadata => {
                        let existing_resident_credentials_count =
                            unwrap_option!(result.existing_resident_credentials_count, callback);
                        let max_possible_remaining_resident_credentials_count = unwrap_option!(
                            result.max_possible_remaining_resident_credentials_count,
                            callback
                        );
                        credential_result.existing_resident_credentials_count =
                            existing_resident_credentials_count;
                        credential_result.max_possible_remaining_resident_credentials_count =
                            max_possible_remaining_resident_credentials_count;
                        if existing_resident_credentials_count > 0 {
                            cred_management.subcommand = CredManagementCommand::EnumerateRPsBegin;
                            // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                            // of the command has changed, and that is part of the PUAP-calculation
                            unwrap_result!(
                                cred_management.set_pin_uv_auth_param(
                                    pin_uv_auth_result.get_pin_uv_auth_token()
                                ),
                                callback
                            );
                            continue;
                        } else {
                            // This token doesn't have any resident keys, but its not an error,
                            // so we send an empty list.
                            send_status(
                                &status,
                                StatusUpdate::InteractiveManagement(
                                    InteractiveUpdate::CredentialManagementUpdate((
                                        CredentialManagementResult::CredentialList(
                                            credential_result,
                                        ),
                                        Some(pin_uv_auth_result),
                                    )),
                                ),
                            );
                            return true;
                        }
                    }
                    CredManagementCommand::EnumerateRPsBegin
                    | CredManagementCommand::EnumerateRPsGetNextRP => {
                        if matches!(
                            cred_management.subcommand,
                            CredManagementCommand::EnumerateRPsBegin
                        ) {
                            let total_rps = unwrap_option!(result.total_rps, callback);
                            if total_rps == 0 {
                                // This token doesn't have any RPs, but its not an error,
                                // so we return an Ok with an empty list.
                                send_status(
                                    &status,
                                    StatusUpdate::InteractiveManagement(
                                        InteractiveUpdate::CredentialManagementUpdate((
                                            CredentialManagementResult::CredentialList(
                                                credential_result,
                                            ),
                                            Some(pin_uv_auth_result),
                                        )),
                                    ),
                                );
                                return true;
                            }
                            remaining_rps = total_rps - 1;
                        } else {
                            remaining_rps -= 1;
                        }

                        let rp = unwrap_option!(result.rp, callback);
                        let rp_id_hash = unwrap_option!(result.rp_id_hash, callback);
                        let rp_res = CredentialRpListEntry {
                            rp,
                            rp_id_hash,
                            credentials: vec![],
                        };
                        credential_result.credential_list.push(rp_res);
                        if remaining_rps > 0 {
                            cred_management.subcommand =
                                CredManagementCommand::EnumerateRPsGetNextRP;
                        } else {
                            // We have queried all RPs, now start querying the corresponding credentials for each RP
                            cred_management.subcommand =
                                CredManagementCommand::EnumerateCredentialsBegin(
                                    credential_result.credential_list[0].rp_id_hash.clone(),
                                );
                        }
                        // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                        // of the command has changed, and that is part of the PUAP-calculation
                        unwrap_result!(
                            cred_management
                                .set_pin_uv_auth_param(pin_uv_auth_result.get_pin_uv_auth_token()),
                            callback
                        );
                        continue;
                    }
                    CredManagementCommand::EnumerateCredentialsBegin(..)
                    | CredManagementCommand::EnumerateCredentialsGetNextCredential => {
                        let user = unwrap_option!(result.user, callback);
                        let credential_id = unwrap_option!(result.credential_id, callback);
                        let public_key = unwrap_option!(result.public_key, callback);
                        let cred_protect = unwrap_option!(result.cred_protect, callback);
                        let large_blob_key = result.large_blob_key;

                        if matches!(
                            cred_management.subcommand,
                            CredManagementCommand::EnumerateCredentialsBegin(..)
                        ) {
                            remaining_cred_ids =
                                unwrap_option!(result.total_credentials, callback) - 1;
                        } else {
                            remaining_cred_ids -= 1;
                        }
                        // We might have to change the global variable, but need the unmodified below
                        let current_rp_backup = current_rp;
                        let mut we_are_done = false;
                        if remaining_cred_ids > 0 {
                            cred_management.subcommand =
                                CredManagementCommand::EnumerateCredentialsGetNextCredential;
                        } else {
                            current_rp += 1;
                            // We have all credentials from this RP. Starting with the next RP.
                            if current_rp < credential_result.credential_list.len() {
                                cred_management.subcommand =
                                    CredManagementCommand::EnumerateCredentialsBegin(
                                        credential_result.credential_list[current_rp]
                                            .rp_id_hash
                                            .clone(),
                                    );
                                // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                                // of the command has changed, and that is part of the PUAP-calculation
                                unwrap_result!(
                                    cred_management.set_pin_uv_auth_param(
                                        pin_uv_auth_result.get_pin_uv_auth_token()
                                    ),
                                    callback
                                );
                            } else {
                                // Finally done iterating over all RPs and their Credentials
                                we_are_done = true;
                            }
                        }
                        let key = CredentialListEntry {
                            user,
                            credential_id,
                            public_key,
                            cred_protect,
                            large_blob_key,
                        };
                        credential_result.credential_list[current_rp_backup]
                            .credentials
                            .push(key);
                        if we_are_done {
                            send_status(
                                &status,
                                StatusUpdate::InteractiveManagement(
                                    InteractiveUpdate::CredentialManagementUpdate((
                                        CredentialManagementResult::CredentialList(
                                            credential_result,
                                        ),
                                        Some(pin_uv_auth_result),
                                    )),
                                ),
                            );
                            return true;
                        } else {
                            continue;
                        }
                    }
                    CredManagementCommand::DeleteCredential(_) => {
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::CredentialManagementUpdate((
                                    CredentialManagementResult::DeleteSucess,
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    CredManagementCommand::UpdateUserInformation(_) => {
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::CredentialManagementUpdate((
                                    CredentialManagementResult::UpdateSuccess,
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                };
            }
            Err(e) => {
                handle_errors!(
                    e,
                    status,
                    callback,
                    pin_uv_auth_result,
                    skip_uv,
                    skip_puap,
                    cached_puat
                );
            }
        }
    }
    false
}

pub(crate) fn configure_authenticator(
    dev: &mut Device,
    puat_result: Option<PinUvAuthResult>,
    cfg_subcommand: AuthConfigCommand,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::ManageResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let mut authcfg = AuthenticatorConfig::new(cfg_subcommand);
    let mut skip_uv = false;
    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i.clone(),
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return false;
        }
    };

    if authinfo.options.authnr_cfg != Some(true) {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    let mut skip_puap = false;
    let mut cached_puat = false; // If we were provided with a cached puat from the outside
    let mut pin_uv_auth_result = puat_result
        .clone()
        .unwrap_or(PinUvAuthResult::NoAuthRequired);
    match puat_result {
        Some(PinUvAuthResult::SuccessGetPinToken(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
            if t.permissions
                .contains(PinUvAuthTokenPermission::AuthenticatorConfiguration) =>
        {
            skip_puap = true;
            cached_puat = true;
            unwrap_result!(authcfg.set_pin_uv_auth_param(Some(t)), callback);
        }
        _ => {}
    }
    let mut pin = None;
    while alive() {
        // We can use the AuthenticatorConfiguration-command only in two cases:
        // 1. The device also supports the uv_acfg-permission (otherwise we can't establish a PUAP)
        // 2. The device is NOT protected by PIN/UV (yet). This allows organizations to configure
        //    the token, before handing them out.
        // If authinfo.options.uv_acfg is not supported, this will return UnauthorizedPermission
        if !skip_puap {
            pin_uv_auth_result = unwrap_result!(
                determine_puap_if_needed(
                    &mut authcfg,
                    dev,
                    skip_uv,
                    PinUvAuthTokenPermission::AuthenticatorConfiguration,
                    UserVerificationRequirement::Preferred,
                    &status,
                    alive,
                    &mut pin,
                ),
                callback
            );
        }

        debug!("------------------------------------------------------------------");
        debug!("{authcfg:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");

        let resp = dev.send_cbor_cancellable(&authcfg, alive);
        match resp {
            Ok(()) => {
                let auth_info = unwrap_option!(dev.refresh_authenticator_info(), callback);
                send_status(
                    &status,
                    StatusUpdate::InteractiveManagement(InteractiveUpdate::AuthConfigUpdate((
                        AuthConfigResult::Success(auth_info.clone()),
                        Some(pin_uv_auth_result),
                    ))),
                );
                return true;
            }
            Err(e) => {
                handle_errors!(
                    e,
                    status,
                    callback,
                    pin_uv_auth_result,
                    skip_uv,
                    skip_puap,
                    cached_puat
                );
            }
        }
    }
    false
}
