chromium/chrome/android/javatests/src/org/chromium/chrome/browser/webauth/Fido2CredentialRequestTest.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.webauth;

import static org.mockito.ArgumentMatchers.eq;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;

import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.FeatureList;
import org.chromium.base.PackageUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
import org.chromium.base.test.params.ParameterProvider;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.JniMocker;
import org.chromium.base.test.util.Restriction;
import org.chromium.blink.mojom.AuthenticatorAttachment;
import org.chromium.blink.mojom.AuthenticatorStatus;
import org.chromium.blink.mojom.AuthenticatorTransport;
import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
import org.chromium.blink.mojom.PaymentOptions;
import org.chromium.blink.mojom.PrfValues;
import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
import org.chromium.blink.mojom.PublicKeyCredentialDescriptor;
import org.chromium.blink.mojom.PublicKeyCredentialParameters;
import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
import org.chromium.blink.mojom.PublicKeyCredentialType;
import org.chromium.blink.mojom.ResidentKeyRequirement;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.components.webauthn.AuthenticationContextProvider;
import org.chromium.components.webauthn.AuthenticatorImpl;
import org.chromium.components.webauthn.CreateConfirmationUiDelegate;
import org.chromium.components.webauthn.Fido2Api;
import org.chromium.components.webauthn.Fido2ApiCallHelper;
import org.chromium.components.webauthn.Fido2ApiTestHelper;
import org.chromium.components.webauthn.Fido2CredentialRequest;
import org.chromium.components.webauthn.FidoIntentSender;
import org.chromium.components.webauthn.GpmBrowserOptionsHelper;
import org.chromium.components.webauthn.InternalAuthenticator;
import org.chromium.components.webauthn.InternalAuthenticatorJni;
import org.chromium.components.webauthn.WebauthnBrowserBridge;
import org.chromium.components.webauthn.WebauthnCredentialDetails;
import org.chromium.components.webauthn.WebauthnMode;
import org.chromium.components.webauthn.WebauthnModeProvider;
import org.chromium.content.browser.ClientDataJsonImpl;
import org.chromium.content.browser.ClientDataJsonImplJni;
import org.chromium.content_public.browser.ClientDataRequestType;
import org.chromium.content_public.browser.RenderFrameHost;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.mock.MockRenderFrameHost;
import org.chromium.content_public.browser.test.mock.MockWebContents;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.test.util.GmsCoreVersionRestriction;
import org.chromium.url.GURL;
import org.chromium.url.Origin;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/** Unit tests for {@link Fido2CredentialRequestTest}. */
@RunWith(ParameterizedRunner.class)
@UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@DisabledTest(message = "Disable whole test class for crbug.com/347310677")
@CommandLineFlags.Add({
    ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
    ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1",
    "ignore-certificate-errors",
})
@Batch(Batch.PER_CLASS)
@Restriction(GmsCoreVersionRestriction.RESTRICTION_TYPE_VERSION_GE_19W13)
public class Fido2CredentialRequestTest {
    private static final String TAG = "Fido2CredentialRequestTest";

    @ClassRule
    public static final ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public final BlankCTATabInitialStateRule mInitialStateRule =
            new BlankCTATabInitialStateRule(sActivityTestRule, false);

    @Rule public JniMocker mMocker = new JniMocker();

    @Mock ClientDataJsonImpl.Natives mClientDataJsonImplMock;

    private Context mContext;
    private MockIntentSender mIntentSender;
    private EmbeddedTestServer mTestServer;
    private MockAuthenticatorRenderFrameHost mFrameHost;
    private MockWebContents mWebContents;
    private MockBrowserBridge mMockBrowserBridge;
    private MockFido2ApiCallHelper mFido2ApiCallHelper;
    private AuthenticationContextProvider mAuthenticationContextProvider;
    private Origin mOrigin;
    private Bundle mBrowserOptions;
    private InternalAuthenticator.Natives mTestAuthenticatorImplJni;
    private Fido2CredentialRequest mRequest;
    private PublicKeyCredentialCreationOptions mCreationOptions;
    private PublicKeyCredentialRequestOptions mRequestOptions;
    private static final String FILLER_ERROR_MSG = "Error Error";
    private Fido2ApiTestHelper.AuthenticatorCallback mCallback;
    private long mStartTimeMs;

    /**
     * This class constructs the parameters array that is used for testMakeCredential_with_param and
     * testGetAssertion_with_param as input parameters.
     */
    public static class ErrorTestParams implements ParameterProvider {
        private static List<ParameterSet> sErrorTestParams =
                Arrays.asList(
                        new ParameterSet()
                                .value(
                                        Fido2Api.SECURITY_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(AuthenticatorStatus.INVALID_DOMAIN))
                                .name("securityError"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.TIMEOUT_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR))
                                .name("timeoutError"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.ENCODING_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR))
                                .name("encodingError"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.NOT_ALLOWED_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR))
                                .name("notAllowedError"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.DATA_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(
                                                AuthenticatorStatus.ANDROID_NOT_SUPPORTED_ERROR))
                                .name("dataError"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.NOT_SUPPORTED_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(
                                                AuthenticatorStatus.ANDROID_NOT_SUPPORTED_ERROR))
                                .name("notSupportedError"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.CONSTRAINT_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR))
                                .name("constraintErrorReRegistration"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.INVALID_STATE_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR))
                                .name("invalidStateError"),
                        new ParameterSet()
                                .value(
                                        Fido2Api.UNKNOWN_ERR,
                                        FILLER_ERROR_MSG,
                                        Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR))
                                .name("unknownError"));

        @Override
        public List<ParameterSet> getParameters() {
            return sErrorTestParams;
        }
    }

    private static class MockIntentSender implements FidoIntentSender {
        private Pair<Integer, Intent> mNextResult;
        private boolean mInvokeCallbackImmediately = true;
        private Callback<Pair<Integer, Intent>> mCallback;
        private final ConditionVariable mShowIntentCalled = new ConditionVariable();

        void setNextResult(int responseCode, Intent intent) {
            mNextResult = new Pair(responseCode, intent);
        }

        void setNextResultIntent(Intent intent) {
            setNextResult(Activity.RESULT_OK, intent);
        }

        public void setInvokeCallbackImmediately(boolean invokeImmediately) {
            mInvokeCallbackImmediately = invokeImmediately;
        }

        public void invokeCallback() {
            Pair<Integer, Intent> result = mNextResult;
            Callback<Pair<Integer, Intent>> callback = mCallback;
            mNextResult = null;
            mCallback = null;
            callback.onResult(result);
        }

        public void blockUntilShowIntentCalled() {
            mShowIntentCalled.block();
        }

        @Override
        public boolean showIntent(PendingIntent intent, Callback<Pair<Integer, Intent>> callback) {
            if (mNextResult == null) {
                return false;
            }
            if (mInvokeCallbackImmediately) {
                Pair<Integer, Intent> result = mNextResult;
                mNextResult = null;
                callback.onResult(result);
            } else {
                assert mCallback == null;
                mCallback = callback;
            }
            mShowIntentCalled.open();
            return true;
        }
    }

    private static class MockFido2ApiCallHelper extends Fido2ApiCallHelper {
        private List<WebauthnCredentialDetails> mReturnedCredentialDetails;
        private boolean mInvokeCallbackImmediately = true;
        private OnSuccessListener<List<WebauthnCredentialDetails>> mSuccessCallback;

        @Override
        public void invokeFido2GetCredentials(
                AuthenticationContextProvider authenticationContextProvider,
                String relyingPartyId,
                OnSuccessListener<List<WebauthnCredentialDetails>> successCallback,
                OnFailureListener failureCallback) {
            if (mInvokeCallbackImmediately) {
                successCallback.onSuccess(mReturnedCredentialDetails);
                return;
            }
            mSuccessCallback = successCallback;
        }

        public void setReturnedCredentialDetails(List<WebauthnCredentialDetails> details) {
            mReturnedCredentialDetails = details;
        }

        public void setInvokeCallbackImmediately(boolean invokeImmediately) {
            mInvokeCallbackImmediately = invokeImmediately;
        }

        public void invokeSuccessCallback() {
            mSuccessCallback.onSuccess(mReturnedCredentialDetails);
        }
    }

    private static class TestAuthenticatorImplJni implements InternalAuthenticator.Natives {
        private Fido2ApiTestHelper.AuthenticatorCallback mCallback;

        TestAuthenticatorImplJni(Fido2ApiTestHelper.AuthenticatorCallback callback) {
            mCallback = callback;
        }

        @Override
        public void invokeMakeCredentialResponse(
                long nativeInternalAuthenticator, int status, ByteBuffer byteBuffer) {
            mCallback.onRegisterResponse(
                    status,
                    byteBuffer == null
                            ? null
                            : MakeCredentialAuthenticatorResponse.deserialize(byteBuffer));
        }

        @Override
        public void invokeGetAssertionResponse(
                long nativeInternalAuthenticator, int status, ByteBuffer byteBuffer) {
            mCallback.onSignResponse(
                    status,
                    byteBuffer == null
                            ? null
                            : GetAssertionAuthenticatorResponse.deserialize(byteBuffer));
        }

        @Override
        public void invokeIsUserVerifyingPlatformAuthenticatorAvailableResponse(
                long nativeInternalAuthenticator, boolean isUVPAA) {}

        @Override
        public void invokeGetMatchingCredentialIdsResponse(
                long nativeInternalAuthenticator, byte[][] matchingCredentials) {}
    }

    private static class MockBrowserBridge extends WebauthnBrowserBridge {
        public enum CallbackInvocationType {
            IMMEDIATE_GET_ASSERTION,
            IMMEDIATE_HYBRID,
            DELAYED
        }

        private byte[] mSelectedCredentialId;
        private List<WebauthnCredentialDetails> mExpectedCredentialList;
        private CallbackInvocationType mInvokeCallbackImmediately =
                CallbackInvocationType.IMMEDIATE_GET_ASSERTION;
        private Callback<byte[]> mGetAssertionCallback;
        private Runnable mHybridCallback;
        private int mCleanupCalled;

        @Override
        public void onCredentialsDetailsListReceived(
                RenderFrameHost frameHost,
                List<WebauthnCredentialDetails> credentialList,
                boolean isConditionalRequest,
                Callback<byte[]> getAssertionCallback,
                Runnable hybridCallback) {
            Assert.assertEquals(mExpectedCredentialList.size(), credentialList.size());
            for (int i = 0; i < credentialList.size(); i++) {
                Assert.assertEquals(
                        mExpectedCredentialList.get(0).mUserName, credentialList.get(0).mUserName);
                Assert.assertEquals(
                        mExpectedCredentialList.get(0).mUserDisplayName,
                        credentialList.get(0).mUserDisplayName);
                Assert.assertArrayEquals(
                        mExpectedCredentialList.get(0).mCredentialId,
                        credentialList.get(0).mCredentialId);
                Assert.assertArrayEquals(
                        mExpectedCredentialList.get(0).mUserId, credentialList.get(0).mUserId);
            }

            mGetAssertionCallback = getAssertionCallback;
            mHybridCallback = hybridCallback;

            if (mInvokeCallbackImmediately == CallbackInvocationType.IMMEDIATE_GET_ASSERTION) {
                invokeGetAssertionCallback();
            }

            if (mInvokeCallbackImmediately == CallbackInvocationType.IMMEDIATE_HYBRID) {
                invokeHybridCallback();
            }
        }

        @Override
        public void cleanupRequest(RenderFrameHost frameHost) {
            mCleanupCalled++;
        }

        public void setSelectedCredentialId(byte[] credentialId) {
            mSelectedCredentialId = credentialId;
        }

        public void setExpectedCredentialDetailsList(
                List<WebauthnCredentialDetails> credentialList) {
            mExpectedCredentialList = credentialList;
        }

        public void setInvokeCallbackImmediately(CallbackInvocationType type) {
            mInvokeCallbackImmediately = type;
        }

        public void invokeGetAssertionCallback() {
            if (mSelectedCredentialId != null) {
                mGetAssertionCallback.onResult(mSelectedCredentialId);
                return;
            }

            /* An empty credential list can occur if a device has no applicable credentials.
             * In production this is passed to native code and the callback will never be
             * invoked. For the sake of testing we invoke with an empty credential selection,
             * which normally would imply an error response.
             */
            if (mExpectedCredentialList.isEmpty()) {
                mGetAssertionCallback.onResult(new byte[0]);
                return;
            }

            /* Return the first ID in the list if one has not been explicitly set. */
            mGetAssertionCallback.onResult(mExpectedCredentialList.get(0).mCredentialId);
        }

        public void invokeHybridCallback() {
            mHybridCallback.run();
        }

        public int getCleanupCalledCount() {
            return mCleanupCalled;
        }
    }

    private static class MockAuthenticatorRenderFrameHost extends MockRenderFrameHost {
        private GURL mLastUrl;
        private boolean mIsPaymentCredentialCreation;

        MockAuthenticatorRenderFrameHost() {}

        @Override
        public GURL getLastCommittedURL() {
            return mLastUrl;
        }

        @Override
        public Origin getLastCommittedOrigin() {
            return Origin.create(mLastUrl);
        }

        public void setLastCommittedURL(GURL url) {
            mLastUrl = url;
        }

        @Override
        public void performMakeCredentialWebAuthSecurityChecks(
                String relyingPartyId,
                Origin effectiveOrigin,
                boolean isPaymentCredentialCreation,
                Callback<WebAuthSecurityChecksResults> callback) {
            mIsPaymentCredentialCreation = isPaymentCredentialCreation;
            super.performMakeCredentialWebAuthSecurityChecks(
                    relyingPartyId, effectiveOrigin, isPaymentCredentialCreation, callback);
        }
    }

    private static final String FIDO_OVERRIDE_COMMAND =
            "su root am broadcast -a com.google.android.gms.phenotype.FLAG_OVERRIDE --es package"
                    + " com.google.android.gms.fido --es user * --esa flags"
                    + " Fido2ApiKnownBrowsers__fingerprints --esa values"
                    + " %s --esa types"
                    + " string --ez commit true --user 0 com.google.android.gms";

    @Before
    public void setUp() throws Exception {
        mContext = ContextUtils.getApplicationContext();

        String fingerprints =
                PackageUtils.getCertificateSHA256FingerprintForPackage(mContext.getPackageName())
                        .stream()
                        .map(s -> s.replaceAll(":", ""))
                        .collect(Collectors.joining(","));
        InstrumentationRegistry.getInstrumentation()
                .getUiAutomation()
                .executeShellCommand(String.format(FIDO_OVERRIDE_COMMAND, fingerprints));

        mIntentSender = new MockIntentSender();
        mTestServer = sActivityTestRule.getTestServer();
        mCallback = Fido2ApiTestHelper.getAuthenticatorCallback();
        String url =
                mTestServer.getURLWithHostName(
                        "subdomain.example.test", "/content/test/data/android/authenticator.html");
        GURL gurl = new GURL(url);
        mOrigin = Origin.create(gurl);
        mBrowserOptions = GpmBrowserOptionsHelper.createDefaultBrowserOptions();
        GpmBrowserOptionsHelper.setIsIncognitoExtraUntilTearDown(false);
        sActivityTestRule.loadUrl(url);
        mFrameHost = new MockAuthenticatorRenderFrameHost();
        mFrameHost.setLastCommittedURL(gurl);
        mWebContents = new MockWebContents();
        mWebContents.renderFrameHost = mFrameHost;

        MockitoAnnotations.initMocks(this);
        mTestAuthenticatorImplJni = new TestAuthenticatorImplJni(mCallback);
        mMocker.mock(InternalAuthenticatorJni.TEST_HOOKS, mTestAuthenticatorImplJni);

        mCreationOptions = Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
        mRequestOptions = Fido2ApiTestHelper.createDefaultGetAssertionOptions();
        WebauthnModeProvider.getInstance().setGlobalWebauthnMode(WebauthnMode.CHROME);
        mAuthenticationContextProvider =
                new AuthenticationContextProvider() {
                    @Override
                    public Context getContext() {
                        return mContext;
                    }

                    @Override
                    public RenderFrameHost getRenderFrameHost() {
                        return mFrameHost;
                    }

                    @Override
                    public FidoIntentSender getIntentSender() {
                        return mIntentSender;
                    }

                    @Override
                    public WebContents getWebContents() {
                        return mWebContents;
                    }
                };
        mRequest = new Fido2CredentialRequest(mAuthenticationContextProvider);
        AuthenticatorImpl.overrideFido2CredentialRequestForTesting(mRequest);

        mFido2ApiCallHelper = new MockFido2ApiCallHelper();
        mFido2ApiCallHelper.setReturnedCredentialDetails(
                Arrays.asList(Fido2ApiTestHelper.getCredentialDetails()));
        Fido2ApiCallHelper.overrideInstanceForTesting(mFido2ApiCallHelper);

        mMockBrowserBridge = new MockBrowserBridge();
        mRequest.overrideBrowserBridgeForTesting(mMockBrowserBridge);

        mStartTimeMs = SystemClock.elapsedRealtime();
    }

    @Test
    @SmallTest
    public void testConvertOriginToString_defaultPortRemoved() {
        Origin origin = Origin.create(new GURL("https://www.example.com:443"));
        String parsedOrigin = Fido2CredentialRequest.convertOriginToString(origin);
        Assert.assertEquals(parsedOrigin, "https://www.example.com/");
    }

    @Test
    @SmallTest
    public void testMakeCredential_success() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testPasskeyMakeCredential_success() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulPasskeyMakeCredentialIntent());

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));

        MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
        Assert.assertArrayEquals(
                response.transports,
                new int[] {
                    AuthenticatorTransport.BLE,
                    AuthenticatorTransport.HYBRID,
                    AuthenticatorTransport.INTERNAL,
                    AuthenticatorTransport.NFC,
                    AuthenticatorTransport.USB
                });
        Assert.assertEquals(response.authenticatorAttachment, AuthenticatorAttachment.PLATFORM);
    }

    @Test
    @SmallTest
    public void testMakeCredential_unsuccessfulAttemptToShowCancelableIntent() {
        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_missingExtra() {
        // An intent missing FIDO2_KEY_RESPONSE_EXTRA.
        mIntentSender.setNextResultIntent(new Intent());

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_nullIntent() {
        // Don't set an intent to be returned at all.
        mIntentSender.setNextResultIntent(null);

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_resultCanceled() {
        mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_resultUnknown() {
        mIntentSender.setNextResult(
                Activity.RESULT_FIRST_USER,
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_noEligibleParameters() {
        PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
        PublicKeyCredentialParameters parameters = new PublicKeyCredentialParameters();
        parameters.algorithmIdentifier = 1; // Not a valid algorithm identifier.
        parameters.type = PublicKeyCredentialType.PUBLIC_KEY;
        customOptions.publicKeyParameters = new PublicKeyCredentialParameters[] {parameters};

        mRequest.handleMakeCredentialRequest(
                customOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.ALGORITHM_UNSUPPORTED));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_parametersContainEligibleAndNoneligible() {
        PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
        PublicKeyCredentialParameters parameters = new PublicKeyCredentialParameters();
        parameters.algorithmIdentifier = 1; // Not a valid algorithm identifier.
        parameters.type = PublicKeyCredentialType.PUBLIC_KEY;
        PublicKeyCredentialParameters[] multiParams =
                new PublicKeyCredentialParameters[] {
                    customOptions.publicKeyParameters[0], parameters
                };
        customOptions.publicKeyParameters = multiParams;
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        mRequest.handleMakeCredentialRequest(
                customOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_noExcludeCredentials() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
        customOptions.excludeCredentials = new PublicKeyCredentialDescriptor[0];
        mRequest.handleMakeCredentialRequest(
                customOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testAuthenticatorImplMakeCredential_success() {
        AuthenticatorImpl authenticator =
                new AuthenticatorImpl(
                        mContext,
                        mWebContents,
                        mIntentSender,
                        /* createConfirmationUiDelegate= */ null,
                        mFrameHost,
                        mOrigin);
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.makeCredential(
                            mCreationOptions,
                            (status, response, dom_exception) ->
                                    mCallback.onRegisterResponse(status, response));
                });

        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        authenticator.close();
    }

    @Test
    @SmallTest
    public void testAuthenticatorImplMakeCredential_withConfirmationUi_success() {
        boolean[] wasCalled = new boolean[1];
        CreateConfirmationUiDelegate createConfirmationUiDelegate =
                (accept, reject) -> {
                    wasCalled[0] = true;
                    accept.run();
                    return true;
                };
        AuthenticatorImpl authenticator =
                new AuthenticatorImpl(
                        mContext,
                        mWebContents,
                        mIntentSender,
                        createConfirmationUiDelegate,
                        mFrameHost,
                        mOrigin);
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.makeCredential(
                            mCreationOptions,
                            (status, response, dom_exception) ->
                                    mCallback.onRegisterResponse(status, response));
                });

        mCallback.blockUntilCalled();
        Assert.assertTrue(wasCalled[0]);
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        authenticator.close();
    }

    @Test
    @SmallTest
    public void testAuthenticatorImplMakeCredential_withConfirmationUi_rejected() {
        boolean[] wasCalled = new boolean[1];
        CreateConfirmationUiDelegate createConfirmationUiDelegate =
                (accept, reject) -> {
                    wasCalled[0] = true;
                    reject.run();
                    return true;
                };
        AuthenticatorImpl authenticator =
                new AuthenticatorImpl(
                        mContext,
                        mWebContents,
                        mIntentSender,
                        createConfirmationUiDelegate,
                        mFrameHost,
                        mOrigin);
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.makeCredential(
                            mCreationOptions,
                            (status, response, dom_exception) ->
                                    mCallback.onRegisterResponse(status, response));
                });

        mCallback.blockUntilCalled();
        Assert.assertTrue(wasCalled[0]);
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        authenticator.close();
    }

    @Test
    @SmallTest
    public void testAuthenticatorImplMakeCredential_resultCanceled() {
        AuthenticatorImpl authenticator =
                new AuthenticatorImpl(
                        mContext,
                        mWebContents,
                        mIntentSender,
                        /* createConfirmationUiDelegate= */ null,
                        mFrameHost,
                        mOrigin);
        mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.makeCredential(
                            mCreationOptions,
                            (status, response, dom_exception) ->
                                    mCallback.onRegisterResponse(status, response));
                });

        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        authenticator.close();
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorMakeCredential_success() {
        InternalAuthenticator authenticator =
                InternalAuthenticator.createForTesting(
                        mContext, mIntentSender, mFrameHost, mOrigin);
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.makeCredential(mCreationOptions.serialize());
                });

        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
        Fido2ApiTestHelper.validateMakeCredentialResponse(response);
        Assert.assertFalse(response.echoCredProps);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorMakeCredential_resultCanceled() {
        InternalAuthenticator authenticator =
                InternalAuthenticator.createForTesting(
                        mContext, mIntentSender, mFrameHost, mOrigin);
        mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.makeCredential(mCreationOptions.serialize());
                });

        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Assert.assertNull(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorMakeCredential_attestationIncluded() {
        // This test can't work on Android N because it lacks the java.nio.file
        // APIs used to load the test data.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return;
        }

        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntentWithAttestation());

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
        assertHasAttestation(response);
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorMakeCredential_rkRequired_attestationKept()
            throws Exception {
        // This test can't work on Android N because it lacks the java.nio.file
        // APIs used to load the test data.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return;
        }

        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntentWithAttestation());

        // Set the residentKey option to trigger attestation removal.
        PublicKeyCredentialCreationOptions creationOptions =
                Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
        creationOptions.authenticatorSelection.residentKey = ResidentKeyRequirement.REQUIRED;

        mRequest.handleMakeCredentialRequest(
                creationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
        assertHasAttestation(response);
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorMakeCredential_rkPreferred_attestationKept()
            throws Exception {
        // This test can't work on Android N because it lacks the java.nio.file
        // APIs used to load the test data.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return;
        }

        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntentWithAttestation());

        // Set the residentKey option to trigger attestation removal.
        PublicKeyCredentialCreationOptions creationOptions =
                Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
        creationOptions.authenticatorSelection.residentKey = ResidentKeyRequirement.PREFERRED;

        mRequest.handleMakeCredentialRequest(
                creationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
        assertHasAttestation(response);
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorMakeCredential_credprops() throws Exception {
        // This test can't work on Android N because it lacks the java.nio.file
        // APIs used to load the test data.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return;
        }

        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntentWithCredProps());

        PublicKeyCredentialCreationOptions creationOptions =
                Fido2ApiTestHelper.createDefaultMakeCredentialOptions();
        creationOptions.credProps = true;

        mRequest.handleMakeCredentialRequest(
                creationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        MakeCredentialAuthenticatorResponse response = mCallback.getMakeCredentialResponse();
        Assert.assertTrue(response.echoCredProps);
        Assert.assertTrue(response.hasCredPropsRk);
        Assert.assertTrue(response.credPropsRk);
    }

    @Test
    @SmallTest
    public void testGetAssertionWithoutUvmRequested_success() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertionWithUvmRequestedWithoutUvmResponded_success() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());

        mRequestOptions.extensions.userVerificationMethods = true;
        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertionWithUvmRequestedWithUvmResponded_success() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());

        mRequestOptions.extensions.userVerificationMethods = true;
        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        GetAssertionAuthenticatorResponse response = mCallback.getGetAssertionResponse();
        Assert.assertTrue(response.extensions.echoUserVerificationMethods);
        Fido2ApiTestHelper.validateGetAssertionResponse(response);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertionWithUserId_success() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUserId());

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        GetAssertionAuthenticatorResponse response = mCallback.getGetAssertionResponse();
        Assert.assertArrayEquals(response.userHandle, Fido2ApiTestHelper.TEST_USER_HANDLE);
    }

    @Test
    @SmallTest
    public void testGetAssertion_unsuccessfulAttemptToShowCancelableIntent() {
        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_missingExtra() {
        // An intent missing FIDO2_KEY_RESPONSE_EXTRA.
        mIntentSender.setNextResultIntent(new Intent());

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_nullIntent() {
        // Don't set an intent to be returned at all.
        mIntentSender.setNextResultIntent(null);

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_resultCanceled() {
        mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_resultUnknown() {
        mIntentSender.setNextResult(
                Activity.RESULT_FIRST_USER,
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_unknownErrorCredentialNotRecognized() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(
                        Fido2Api.UNKNOWN_ERR, "Low level error 0x6a80"));

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_appIdUsed() {
        PublicKeyCredentialRequestOptions customOptions = mRequestOptions;
        customOptions.extensions.appid = "www.example.com";
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        GetAssertionAuthenticatorResponse response = mCallback.getGetAssertionResponse();
        Fido2ApiTestHelper.validateGetAssertionResponse(response);
        Assert.assertEquals(response.extensions.echoAppidExtension, true);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testAuthenticatorImplGetAssertionWithUvmRequestedWithUvmResponded_success() {
        AuthenticatorImpl authenticator =
                new AuthenticatorImpl(
                        mContext,
                        mWebContents,
                        mIntentSender,
                        /* createConfirmationUiDelegate= */ null,
                        mFrameHost,
                        mOrigin);
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());
        mRequestOptions.extensions.userVerificationMethods = true;

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.getAssertion(
                            mRequestOptions,
                            (status, response, dom_exception) ->
                                    mCallback.onSignResponse(status, response));
                });
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        authenticator.close();
    }

    @Test
    @SmallTest
    public void testAuthenticatorImplGetAssertionWithPrf_success() {
        AuthenticatorImpl authenticator =
                new AuthenticatorImpl(
                        mContext,
                        mWebContents,
                        mIntentSender,
                        /* createConfirmationUiDelegate= */ null,
                        mFrameHost,
                        mOrigin);
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithPrf());
        PrfValues prfValues = new PrfValues();
        prfValues.first = new byte[] {1, 2, 3, 4, 5, 6};
        mRequestOptions.extensions.prf = true;
        mRequestOptions.extensions.prfInputs =
                new PrfValues[] {
                    prfValues,
                };

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.getAssertion(
                            mRequestOptions,
                            (status, response, dom_exception) ->
                                    mCallback.onSignResponse(status, response));
                });
        mCallback.blockUntilCalled();

        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validatePrfResults(
                mCallback.getGetAssertionResponse().extensions.prfResults);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        authenticator.close();
    }

    @Test
    @SmallTest
    public void testAuthenticatorImplGetAssertion_resultCanceled() {
        AuthenticatorImpl authenticator =
                new AuthenticatorImpl(
                        mContext,
                        mWebContents,
                        mIntentSender,
                        /* createConfirmationUiDelegate= */ null,
                        mFrameHost,
                        mOrigin);
        mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.getAssertion(
                            mRequestOptions,
                            (status, response, dom_exception) ->
                                    mCallback.onSignResponse(status, response));
                });
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        authenticator.close();
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorGetAssertionWithUvmRequestedWithUvmResponded_success() {
        InternalAuthenticator authenticator =
                InternalAuthenticator.createForTesting(
                        mContext, mIntentSender, mFrameHost, mOrigin);
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());
        mRequestOptions.extensions.userVerificationMethods = true;

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.getAssertion(mRequestOptions.serialize());
                });
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testInternalAuthenticatorGetAssertion_resultCanceled() {
        InternalAuthenticator authenticator =
                InternalAuthenticator.createForTesting(
                        mContext, mIntentSender, mFrameHost, mOrigin);
        mIntentSender.setNextResult(Activity.RESULT_CANCELED, null);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    authenticator.getAssertion(mRequestOptions.serialize());
                });

        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR));
        Assert.assertNull(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_attestationNone() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
        customOptions.attestation = org.chromium.blink.mojom.AttestationConveyancePreference.NONE;
        mRequest.handleMakeCredentialRequest(
                customOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_attestationIndirect() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
        customOptions.attestation =
                org.chromium.blink.mojom.AttestationConveyancePreference.INDIRECT;
        mRequest.handleMakeCredentialRequest(
                customOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_attestationDirect() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
        customOptions.attestation = org.chromium.blink.mojom.AttestationConveyancePreference.DIRECT;
        mRequest.handleMakeCredentialRequest(
                customOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_attestationEnterprise() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        PublicKeyCredentialCreationOptions customOptions = mCreationOptions;
        customOptions.attestation =
                org.chromium.blink.mojom.AttestationConveyancePreference.ENTERPRISE;
        mRequest.handleMakeCredentialRequest(
                customOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Fido2ApiTestHelper.validateMakeCredentialResponse(mCallback.getMakeCredentialResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_invalidStateErrorDuplicateRegistration() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(
                        Fido2Api.INVALID_STATE_ERR,
                        "One of the excluded credentials exists on the local device"));

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.CREDENTIAL_EXCLUDED));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_isPaymentCredentialCreationPassedToFrameHost() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(
                        Fido2Api.INVALID_STATE_ERR,
                        "One of the excluded credentials exists on the local device"));

        mCreationOptions.isPaymentCredentialCreation = true;
        Assert.assertFalse(mFrameHost.mIsPaymentCredentialCreation);
        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        Assert.assertTrue(mFrameHost.mIsPaymentCredentialCreation);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_emptyAllowCredentials1() {
        // Passes conversion and gets rejected by GmsCore
        PublicKeyCredentialRequestOptions customOptions = mRequestOptions;
        customOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(
                        Fido2Api.NOT_ALLOWED_ERR,
                        "Authentication request must have non-empty allowList"));

        // Requests with empty allowCredentials are only passed to GMSCore if there are no
        // local passkeys available.
        mFido2ApiCallHelper.setReturnedCredentialDetails(new ArrayList<>());

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(),
                Integer.valueOf(AuthenticatorStatus.EMPTY_ALLOW_CREDENTIALS));
        // Verify the response returned immediately.
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_emptyAllowCredentials2() {
        // Passes conversion and gets rejected by GmsCore
        PublicKeyCredentialRequestOptions customOptions = mRequestOptions;
        customOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(
                        Fido2Api.NOT_ALLOWED_ERR,
                        "Request doesn't have a valid list of allowed credentials."));

        // Requests with empty allowCredentials are only passed to GMSCore if there are no
        // local passkeys available.
        mFido2ApiCallHelper.setReturnedCredentialDetails(new ArrayList<>());

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(),
                Integer.valueOf(AuthenticatorStatus.EMPTY_ALLOW_CREDENTIALS));
        // Verify the response returned immediately.
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_constraintErrorNoScreenlock() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(
                        Fido2Api.CONSTRAINT_ERR, "The device is not secured with any screen lock"));

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(),
                Integer.valueOf(AuthenticatorStatus.USER_VERIFICATION_UNSUPPORTED));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_constraintErrorNoScreenlock() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(
                        Fido2Api.CONSTRAINT_ERR, "The device is not secured with any screen lock"));

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                mCallback.getStatus(),
                Integer.valueOf(AuthenticatorStatus.USER_VERIFICATION_UNSUPPORTED));
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    @UseMethodParameter(ErrorTestParams.class)
    public void testMakeCredential_with_param(Integer errorCode, String errorMsg, Integer status) {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(errorCode, errorMsg));

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), status);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    @UseMethodParameter(ErrorTestParams.class)
    public void testGetAssertion_with_param(Integer errorCode, String errorMsg, Integer status) {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(errorCode, errorMsg));

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), status);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    @UseMethodParameter(ErrorTestParams.class)
    public void testMakeCredential_with_param_nullErrorMessage(
            Integer errorCode, String errorMsg, Integer status) {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createErrorIntent(errorCode, null));

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), status);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    @UseMethodParameter(ErrorTestParams.class)
    public void testGetAssertion_with_param_nullErrorMessage(
            Integer errorCode, String errorMsg, Integer status) {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createErrorIntent(errorCode, null));

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), status);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_securePaymentConfirmation_canReplaceClientDataJson() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());

        String clientDataJson = "520";
        Fido2ApiTestHelper.mockClientDataJson(mMocker, clientDataJson);

        mFrameHost.setLastCommittedURL(new GURL("https://www.chromium.org/pay"));

        PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
        mRequestOptions.challenge = new byte[3];
        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                payment,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Assert.assertEquals(
                new String(
                        mCallback.getGetAssertionResponse().info.clientDataJson,
                        StandardCharsets.UTF_8),
                clientDataJson);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_securePaymentConfirmation_clientDataJsonCannotBeEmpty() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());

        Fido2ApiTestHelper.mockClientDataJson(mMocker, null);

        mFrameHost.setLastCommittedURL(new GURL("https://www.chromium.org/pay"));

        PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
        mRequestOptions.challenge = new byte[3];
        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                payment,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
        Assert.assertNull(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_securePaymentConfirmation_buildClientDataJsonParameters() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());

        Origin topOrigin = Origin.create(new GURL("https://www.chromium.org/pay"));

        // ClientDataJsonImplJni is mocked directly instead of using
        // Fido2ApiTestHelper.mockClientDataJson so that it can be used to verify the call arguments
        // below.
        mMocker.mock(ClientDataJsonImplJni.TEST_HOOKS, mClientDataJsonImplMock);

        PaymentOptions payment = Fido2ApiTestHelper.createPaymentOptions();
        mRequestOptions.challenge = new byte[3];
        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                topOrigin,
                payment,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);

        ArgumentCaptor<Origin> topOriginCaptor = ArgumentCaptor.forClass(Origin.class);
        Mockito.verify(mClientDataJsonImplMock, Mockito.times(1))
                .buildClientDataJson(
                        eq(ClientDataRequestType.PAYMENT_GET),
                        eq(Fido2CredentialRequest.convertOriginToString(mOrigin)),
                        eq(mRequestOptions.challenge),
                        eq(false),
                        eq(payment.serialize()),
                        eq(mRequestOptions.relyingPartyId),
                        topOriginCaptor.capture());

        String topOriginString =
                Fido2CredentialRequest.convertOriginToString(topOriginCaptor.getValue());
        String expectedTopOriginString = Fido2CredentialRequest.convertOriginToString(topOrigin);
        Assert.assertEquals(expectedTopOriginString, topOriginString);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_emptyAllowCredentials_success() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_emptyAllowCredentialsUserCancels_notAllowedError() {
        mMockBrowserBridge.setSelectedCredentialId(new byte[0]);
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.NOT_ALLOWED_ERROR), mCallback.getStatus());
        Assert.assertNull(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUi_success() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUi_failureEmptyCredential() {
        mMockBrowserBridge.setSelectedCredentialId(new byte[0]);
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));
        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR), mCallback.getStatus());
        Assert.assertNull(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUiNondiscoverableCredential_failure() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        WebauthnCredentialDetails nonDiscoverableCredDetails =
                Fido2ApiTestHelper.getCredentialDetails();
        nonDiscoverableCredDetails.mIsDiscoverable = false;
        mFido2ApiCallHelper.setReturnedCredentialDetails(Arrays.asList(nonDiscoverableCredDetails));
        mMockBrowserBridge.setExpectedCredentialDetailsList(new ArrayList<>());

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR), mCallback.getStatus());
        Assert.assertNull(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testGetAssertion_conditionalUi_cancelWhileFetchingCredentials() {
        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mFido2ApiCallHelper.setInvokeCallbackImmediately(false);

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mRequest.cancelConditionalGetAssertion();
        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.ABORT_ERROR), mCallback.getStatus());

        // Also validate that when the FIDO getCredentials call is completed, nothing happens.
        // The MockBrowserBridge will assert if onCredentialsDetailsListReceived is called.
        mMockBrowserBridge.setExpectedCredentialDetailsList(new ArrayList<>());
        mFido2ApiCallHelper.invokeSuccessCallback();
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 0);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUi_cancelWhileWaitingForSelection() {
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));
        mMockBrowserBridge.setInvokeCallbackImmediately(
                MockBrowserBridge.CallbackInvocationType.DELAYED);
        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mRequest.cancelConditionalGetAssertion();

        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.ABORT_ERROR), mCallback.getStatus());
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUiCancelWhileRequestSentToPlatform_ignored() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mIntentSender.setInvokeCallbackImmediately(false);

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mIntentSender.blockUntilShowIntentCalled();
        mRequest.cancelConditionalGetAssertion();
        mIntentSender.invokeCallback();
        Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUiCancelWhileRequestSentToPlatformUserDeny_cancelled() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(Fido2Api.NOT_ALLOWED_ERR, ""));
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mIntentSender.setInvokeCallbackImmediately(false);

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mIntentSender.blockUntilShowIntentCalled();
        mRequest.cancelConditionalGetAssertion();
        mIntentSender.invokeCallback();
        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.ABORT_ERROR), mCallback.getStatus());
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUiRequestSentToPlatformUserDeny_doesNotComplete() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(Fido2Api.NOT_ALLOWED_ERR, ""));
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mIntentSender.blockUntilShowIntentCalled();
        // Null status indicates the callback was not invoked.
        Assert.assertNull(mCallback.getStatus());
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 0);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUiRetryAfterUserDeny_success() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createErrorIntent(Fido2Api.NOT_ALLOWED_ERR, ""));
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mIntentSender.blockUntilShowIntentCalled();
        // Null status indicates the callback was not invoked.
        Assert.assertNull(mCallback.getStatus());
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 0);

        // Select credential again, and provide a success response this time.
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        mMockBrowserBridge.invokeGetAssertionCallback();
        mCallback.blockUntilCalled();
        Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUiWithAllowCredentialMatch_success() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(
                        new WebauthnCredentialDetails[] {
                            Fido2ApiTestHelper.getCredentialDetails()
                        }));

        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    public void testGetAssertion_conditionalUiWithAllowCredentialMismatch_failure() {
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        mMockBrowserBridge.setExpectedCredentialDetailsList(new ArrayList<>());

        PublicKeyCredentialDescriptor descriptor = new PublicKeyCredentialDescriptor();
        descriptor.type = 0;
        descriptor.id = new byte[] {3, 2, 1};
        descriptor.transports = new int[] {0};
        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[] {descriptor};
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(
                Integer.valueOf(AuthenticatorStatus.UNKNOWN_ERROR), mCallback.getStatus());
        Assert.assertNull(mCallback.getGetAssertionResponse());
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    @Test
    @SmallTest
    public void testGetMatchingCredentialIds_success() {
        String relyingPartyId = "subdomain.example.test";
        byte[][] allowCredentialIds =
                new byte[][] {
                    {1, 2, 3},
                    {10, 11, 12},
                    {13, 14, 15},
                };
        boolean requireThirdPartyPayment = false;

        WebauthnCredentialDetails credential1 = Fido2ApiTestHelper.getCredentialDetails();
        credential1.mCredentialId = new byte[] {1, 2, 3};
        credential1.mIsPayment = true;

        WebauthnCredentialDetails credential2 = Fido2ApiTestHelper.getCredentialDetails();
        credential2.mCredentialId = new byte[] {4, 5, 6};
        credential2.mIsPayment = false;

        WebauthnCredentialDetails credential3 = Fido2ApiTestHelper.getCredentialDetails();
        credential3.mCredentialId = new byte[] {7, 8, 9};
        credential3.mIsPayment = true;

        WebauthnCredentialDetails credential4 = Fido2ApiTestHelper.getCredentialDetails();
        credential4.mCredentialId = new byte[] {10, 11, 12};
        credential4.mIsPayment = false;

        mFido2ApiCallHelper.setReturnedCredentialDetails(
                Arrays.asList(credential1, credential2, credential3, credential4));

        mRequest.handleGetMatchingCredentialIdsRequest(
                relyingPartyId,
                allowCredentialIds,
                requireThirdPartyPayment,
                mCallback::onGetMatchingCredentialIds,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertArrayEquals(
                mCallback.getGetMatchingCredentialIdsResponse().toArray(),
                new byte[][] {{1, 2, 3}, {10, 11, 12}});
    }

    @Test
    @SmallTest
    public void testGetMatchingCredentialIds_requireThirdPartyBit() {
        String relyingPartyId = "subdomain.example.test";
        byte[][] allowCredentialIds =
                new byte[][] {
                    {1, 2, 3},
                    {10, 11, 12},
                    {13, 14, 15},
                };
        boolean requireThirdPartyPayment = true;

        WebauthnCredentialDetails credential1 = Fido2ApiTestHelper.getCredentialDetails();
        credential1.mCredentialId = new byte[] {1, 2, 3};
        credential1.mIsPayment = true;

        WebauthnCredentialDetails credential2 = Fido2ApiTestHelper.getCredentialDetails();
        credential2.mCredentialId = new byte[] {4, 5, 6};
        credential2.mIsPayment = false;

        WebauthnCredentialDetails credential3 = Fido2ApiTestHelper.getCredentialDetails();
        credential3.mCredentialId = new byte[] {7, 8, 9};
        credential3.mIsPayment = true;

        WebauthnCredentialDetails credential4 = Fido2ApiTestHelper.getCredentialDetails();
        credential4.mCredentialId = new byte[] {10, 11, 12};
        credential4.mIsPayment = false;

        mFido2ApiCallHelper.setReturnedCredentialDetails(
                Arrays.asList(credential1, credential2, credential3, credential4));

        mRequest.handleGetMatchingCredentialIdsRequest(
                relyingPartyId,
                allowCredentialIds,
                requireThirdPartyPayment,
                mCallback::onGetMatchingCredentialIds,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertArrayEquals(
                mCallback.getGetMatchingCredentialIdsResponse().toArray(),
                new byte[][] {{1, 2, 3}});
    }

    @Test
    @SmallTest
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.TIRAMISU,
            message = "crbug.com/347310677")
    @Restriction(GmsCoreVersionRestriction.RESTRICTION_TYPE_VERSION_GE_23W12)
    public void testGetAssertion_conditionalUiHybrid_success() {
        FeatureList.TestValues testValues = new FeatureList.TestValues();
        FeatureList.setTestValues(testValues);
        mIntentSender.setNextResultIntent(Fido2ApiTestHelper.createSuccessfulGetAssertionIntent());
        mMockBrowserBridge.setExpectedCredentialDetailsList(
                Arrays.asList(Fido2ApiTestHelper.getCredentialDetails()));
        mMockBrowserBridge.setInvokeCallbackImmediately(
                MockBrowserBridge.CallbackInvocationType.IMMEDIATE_HYBRID);

        mRequestOptions.allowCredentials = new PublicKeyCredentialDescriptor[0];
        mRequestOptions.isConditional = true;

        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(Integer.valueOf(AuthenticatorStatus.SUCCESS), mCallback.getStatus());
        Fido2ApiTestHelper.validateGetAssertionResponse(mCallback.getGetAssertionResponse());
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
        Assert.assertEquals(mMockBrowserBridge.getCleanupCalledCount(), 1);
    }

    private static class TestParcelable implements Parcelable {
        static final String CLASS_NAME =
                "org.chromium.chrome.browser.webauth.Fido2CredentialRequestTest$TestParcelable";

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(42);
        }
    }

    @Test
    @SmallTest
    public void parcelWriteValue_knownFormat() {
        // This test confirms that the format of Parcel.writeValue is as
        // expected. Sadly, Fido2Api needs to be sensitive to this because,
        // in one place, the interactions with the FIDO module are not just
        // passing SafeParcelable objects around, but rather an array of
        // them. This pulls in higher-level aspect of the Parcel class that
        // can change from release to release. We can't just use the Parcel
        // class because it's tightly coupled with assumptions about using
        // ClassLoaders for deserialisation.

        byte[][] encodings = new byte[2][];
        for (int i = 0; i < encodings.length; i++) {
            Parcel parcel = Parcel.obtain();
            parcel.writeInt(16 /* VAL_PARCELABLEARRRAY */);
            if (i > 0) {
                // include length prefix
                final int l = TestParcelable.CLASS_NAME.length();
                parcel.writeInt(
                        4 /* array length */
                                + 4 /* string length */
                                + 2 * (l + (l % 2)) /* two bytes per char, 4-byte aligned */
                                + 4 /* parcelable contents */);
            }
            parcel.writeInt(1); // array length
            parcel.writeString(TestParcelable.CLASS_NAME);
            parcel.writeInt(42); // Parcelable contents.

            encodings[i] = parcel.marshall();
            parcel.recycle();
        }

        Parcel parcel = Parcel.obtain();
        parcel.writeValue(new Parcelable[] {new TestParcelable()});
        byte[] data = parcel.marshall();

        boolean ok = false;
        for (final byte[] encoding : encodings) {
            if (Arrays.equals(encoding, data)) {
                ok = true;
                break;
            }
        }

        parcel.recycle();
        if (ok) {
            return;
        }

        Log.e(TAG, "Encoding was: " + Base64.encodeToString(data, Base64.NO_WRAP));
        for (final byte[] encoding : encodings) {
            Log.e(TAG, "    expected: " + Base64.encodeToString(encoding, Base64.NO_WRAP));
        }

        // If you're here because this test has broken. Firstly, I'm sorry.
        // Find agl@ and demand he fix it. The logcat output from the failing
        // test will be very helpful. If I'm unavailable then look in Android's
        // Parcel.java to figure out what changed in writeValue(), update
        // Fido2Api.java around the use of VAL_PARCELABLE and update this test
        // to have a 3rd acceptable encoding that matches the changes.
        Assert.fail("No matching encoding found");
    }

    private void assertHasAttestation(MakeCredentialAuthenticatorResponse response) {
        // Since the CBOR must be in canonical form, the "fmt" key comes first
        // and we can match against "fmt=android-safetynet".
        final byte[] expectedPrefix =
                new byte[] {
                    0xa3 - 256,
                    0x63,
                    0x66,
                    0x6d,
                    0x74,
                    0x71,
                    0x61,
                    0x6e,
                    0x64,
                    0x72,
                    0x6f,
                    0x69,
                    0x64,
                    0x2d,
                    0x73,
                    0x61,
                    0x66,
                    0x65,
                    0x74,
                    0x79,
                    0x6e,
                    0x65,
                    0x74
                };
        byte[] actualPrefix =
                Arrays.copyOfRange(response.attestationObject, 0, expectedPrefix.length);
        Assert.assertArrayEquals(expectedPrefix, actualPrefix);
    }

    @Test
    @SmallTest
    public void testGetAssertion_generatesClientDataJson() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulGetAssertionIntentWithUvm());

        String clientDataJson = "520";
        Fido2ApiTestHelper.mockClientDataJson(mMocker, clientDataJson);

        mFrameHost.setLastCommittedURL(new GURL("https://www.example.com"));

        mRequestOptions.challenge = new byte[3];
        mRequest.handleGetAssertionRequest(
                mRequestOptions,
                /* maybeClientDataHash= */ null,
                mOrigin,
                mOrigin,
                /* payment= */ null,
                mCallback::onSignResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Assert.assertEquals(
                new String(
                        mCallback.getGetAssertionResponse().info.clientDataJson,
                        StandardCharsets.UTF_8),
                clientDataJson);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }

    @Test
    @SmallTest
    public void testMakeCredential_generatesClientDataJson() {
        mIntentSender.setNextResultIntent(
                Fido2ApiTestHelper.createSuccessfulMakeCredentialIntent());

        String clientDataJson = "520";
        Fido2ApiTestHelper.mockClientDataJson(mMocker, clientDataJson);

        mFrameHost.setLastCommittedURL(new GURL("https://www.example.com"));

        mRequest.handleMakeCredentialRequest(
                mCreationOptions,
                /* maybeClientDataHash= */ null,
                mBrowserOptions,
                mOrigin,
                mOrigin,
                mCallback::onRegisterResponse,
                mCallback::onError);
        mCallback.blockUntilCalled();
        Assert.assertEquals(mCallback.getStatus(), Integer.valueOf(AuthenticatorStatus.SUCCESS));
        Assert.assertEquals(
                new String(
                        mCallback.getMakeCredentialResponse().info.clientDataJson,
                        StandardCharsets.UTF_8),
                clientDataJson);
        Fido2ApiTestHelper.verifyRespondedBeforeTimeout(mStartTimeMs);
    }
}