chromium/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.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.components.webauthn;

import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;

import static java.nio.charset.StandardCharsets.UTF_8;

import android.content.Intent;
import android.os.ConditionVariable;
import android.os.Parcel;
import android.os.SystemClock;
import android.util.Base64;

import androidx.annotation.Nullable;

import com.google.common.io.BaseEncoding;

import org.junit.Assert;

import org.chromium.base.test.util.JniMocker;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.blink.mojom.AuthenticationExtensionsClientInputs;
import org.chromium.blink.mojom.AuthenticatorAttachment;
import org.chromium.blink.mojom.AuthenticatorSelectionCriteria;
import org.chromium.blink.mojom.CableAuthentication;
import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
import org.chromium.blink.mojom.MakeCredentialAuthenticatorResponse;
import org.chromium.blink.mojom.PaymentCredentialInstrument;
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.PublicKeyCredentialRpEntity;
import org.chromium.blink.mojom.PublicKeyCredentialType;
import org.chromium.blink.mojom.PublicKeyCredentialUserEntity;
import org.chromium.blink.mojom.UvmEntry;
import org.chromium.content.browser.ClientDataJsonImpl;
import org.chromium.content.browser.ClientDataJsonImplJni;
import org.chromium.mojo_base.mojom.TimeDelta;
import org.chromium.payments.mojom.PaymentCurrencyAmount;
import org.chromium.url.mojom.Url;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.TimeUnit;

/* NO_BUILDER:
 *
 * Several comments below describe how binary blobs in this file were produced. However, they use
 * Builder classes that no longer appear to exist. Because of this, it's difficult to update these
 * blobs.
 *
 * The blobs are in Android's Parcel format. This format defines a tag-value object that represents
 * a (tag, bytestring) pair. A tag-value is serialized as a little-endian, uint32 where the bottom
 * 16 bits are the tag, and the top 16 bits are the length of the value. If the length is 0xffff,
 * then a second uint32 is used to store the actual length. (This two-word format appears to be
 * always used in practice, even when the length would fit in 16 bits.) The bytestring contains are
 * then the |length| following bytes.
 *
 * A Parcel consists of a tag-value object with tag 0x4f45 and whose value is the rest of the
 * Parcel data. That value contains a series of tag-values that define the members of the
 * destination object. Unknown tags are skipped over.
 *
 * The semantics of the values of the inner values are tag-specific. In the case of byte[] objects,
 * the value is, itself, a tag-value object. Since this has its own length, there can be padding at
 * the end and they seem to be padded with zeros to the nearest four-byte boundary.
 */

/* CONVERT_TO_JAVA
 *
 * Since the Builder classes disappeared (see NO_BUILDER tag, above) and since
 * we've actually dropped using the FIDO SDK in Chromium, test data is often
 * now captured from a device. To do this, print the data in question using:
 *   Base64.encodeToString(data, Base64.NO_WRAP);
 *
 * Then it can be converted to a Java array using this Python 3 code:
 *
 * import codecs
 * import sys
 *
 * def f(x):
 *   if x < 128:
 *     return x
 *   return x - 256
 *
 * b = codecs.decode(bytes(sys.argv[1], 'ascii'), 'base64')
 * print([f(x) for x in b])
 */

/** A Helper class for testing Fido2ApiHandlerInternal. */
public class Fido2ApiTestHelper {
    // Test data.
    private static final int OBJECT_MAGIC = 20293;

    /**
     * This byte array was produced by
     * com.google.android.gms.fido.fido2.api.common.AuthenticatorAttestationResponse with test data,
     * i.e.:
     *
     * <pre>{@code
     * AuthenticatorAttestationResponse response = new AuthenticatorAttestationResponse.Builder()
     *      .setAttestationObject(TEST_ATTESTATION_OBJECT)
     *      .setClientDataJSON(TEST_CLIENT_DATA_JSON)
     *      .setKeyHandle(TEST_KEY_HANDLE)
     *      .build().serializeToBytes();
     * }</pre>
     *
     * <p>NOTE: See NO_BUILDER comment, above. Additionally this byte array was modified by
     * prepending an object header and tag with value four so that it's compatible with
     * FIDO2_KEY_CREDENTIAL_EXTRA.
     */
    private static final byte[] TEST_AUTHENTICATOR_ATTESTATION_RESPONSE =
            new byte[] {
                69, 79, -1, -1, 60, 1, 0, 0, 4, 0, -1, -1, 52, 1, 0, 0, 69, 79, -1, -1, 44, 1, 0, 0,
                2, 0, -1, -1, 36, 0, 0, 0, 32, 0, 0, 0, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7,
                8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 9, 3, 0, -1, -1, 8, 0, 0, 0, 3, 0,
                0, 0, 4, 5, 6, 0, 4, 0, -1, -1, -24, 0, 0, 0, -30, 0, 0, 0, -93, 99, 102, 109, 116,
                100, 110, 111, 110, 101, 103, 97, 116, 116, 83, 116, 109, 116, -96, 104, 97, 117,
                116, 104, 68, 97, 116, 97, 88, -60, 38, -67, 114, 120, -66, 70, 55, 97, -15, -6,
                -95, -79, 10, -76, -60, -8, 38, 112, 38, -100, 65, 12, 114, 106, 31, -42, -32, 88,
                85, -31, -101, 70, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 64, 124, 80, -60, -114, 69, -117, 44, -120, 122, -62, 63, 104, 18, -66, 2, -3,
                -56, 35, -24, 66, -4, 74, 48, -128, -52, 80, -100, 46, 97, 93, -25, -21, -53, 40,
                123, 90, -107, -20, 111, -4, 15, 64, 122, 15, -84, -21, -33, -15, 26, 11, 35, 36,
                -49, 116, 52, -74, 107, 63, 113, -59, 125, -27, -120, -63, -91, 1, 2, 3, 38, 32, 1,
                33, 88, 32, -75, -80, 118, 102, -14, 124, -108, -9, -27, -91, 59, -48, -92, -102,
                -38, -44, 92, 95, 14, -62, 41, -117, -70, 101, 9, 64, 35, 31, -20, 79, -71, -71, 34,
                88, 32, -24, -33, 64, 97, -31, -34, 96, -83, -119, -25, 21, -14, -56, -70, -37,
                -116, -21, -33, -128, -66, 61, 41, 107, 16, -25, 120, 106, -113, 54, -62, -102, 42,
                0, 0
            };

    /**
     * This byte array was captured from a device and resulted from creating a passkey.
     *
     * <p>It contains fields such as the list of transports, which is useful for testing. See
     * CONVERT_TO_JAVA tag, above, about creating it.
     */
    private static final byte[] TEST_AUTHENTICATOR_PASSKEY_ATTESTATION_RESPONSE =
            new byte[] {
                69, 79, -1, -1, -84, 2, 0, 0, 1, 0, -1, -1, 52, 0, 0, 0, 22, 0, 0, 0, 99, 0, 71, 0,
                67, 0, 103, 0, 71, 0, 99, 0, 71, 0, 54, 0, 115, 0, 75, 0, 90, 0, 48, 0, 114, 0, 84,
                0, 103, 0, 78, 0, 121, 0, 95, 0, 119, 0, 57, 0, 87, 0, 65, 0, 0, 0, 0, 0, 2, 0, -1,
                -1, 28, 0, 0, 0, 10, 0, 0, 0, 112, 0, 117, 0, 98, 0, 108, 0, 105, 0, 99, 0, 45, 0,
                107, 0, 101, 0, 121, 0, 0, 0, 0, 0, 3, 0, -1, -1, 20, 0, 0, 0, 16, 0, 0, 0, 112, 96,
                -96, 25, -63, -70, -80, -90, 116, -83, 56, 13, -53, -4, 61, 88, 4, 0, -1, -1, 8, 2,
                0, 0, 69, 79, -1, -1, 0, 2, 0, 0, 2, 0, -1, -1, 20, 0, 0, 0, 16, 0, 0, 0, 112, 96,
                -96, 25, -63, -70, -80, -90, 116, -83, 56, 13, -53, -4, 61, 88, 3, 0, -1, -1, -72,
                0, 0, 0, -79, 0, 0, 0, 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97,
                117, 116, 104, 110, 46, 99, 114, 101, 97, 116, 101, 34, 44, 34, 99, 104, 97, 108,
                108, 101, 110, 103, 101, 34, 58, 34, 100, 103, 101, 55, 76, 107, 120, 73, 98, 121,
                98, 100, 77, 102, 81, 118, 111, 103, 49, 99, 79, 98, 57, 68, 122, 80, 76, 70, 69,
                119, 107, 79, 52, 95, 90, 55, 111, 117, 109, 79, 48, 69, 99, 34, 44, 34, 111, 114,
                105, 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 92, 47, 92, 47, 115,
                101, 99, 117, 114, 105, 116, 121, 107, 101, 121, 115, 46, 105, 110, 102, 111, 34,
                44, 34, 97, 110, 100, 114, 111, 105, 100, 80, 97, 99, 107, 97, 103, 101, 78, 97,
                109, 101, 34, 58, 34, 99, 111, 109, 46, 103, 111, 111, 103, 108, 101, 46, 97, 110,
                100, 114, 111, 105, 100, 46, 97, 112, 112, 115, 46, 99, 104, 114, 111, 109, 101, 34,
                125, 0, 0, 0, 4, 0, -1, -1, -72, 0, 0, 0, -78, 0, 0, 0, -93, 99, 102, 109, 116, 100,
                110, 111, 110, 101, 103, 97, 116, 116, 83, 116, 109, 116, -96, 104, 97, 117, 116,
                104, 68, 97, 116, 97, 88, -108, 38, -67, 114, 120, -66, 70, 55, 97, -15, -6, -95,
                -79, 10, -76, -60, -8, 38, 112, 38, -100, 65, 12, 114, 106, 31, -42, -32, 88, 85,
                -31, -101, 70, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                16, 112, 96, -96, 25, -63, -70, -80, -90, 116, -83, 56, 13, -53, -4, 61, 88, -91, 1,
                2, 3, 38, 32, 1, 33, 88, 32, 44, 116, -29, 17, -108, 13, 49, 87, 1, 111, 117, 117,
                -110, -42, 6, -108, 108, -120, -2, 31, 62, 75, 4, 51, -5, 73, 70, -84, -30, -123,
                -38, 98, 34, 88, 32, -14, -91, 17, -54, 52, -36, -82, -36, 60, 19, -34, 79, -103,
                80, -71, 92, -40, 113, 12, 98, 107, -88, 95, 7, -27, 39, -43, 52, -111, -85, -77,
                14, 0, 0, 5, 0, -1, -1, 92, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 98, 0, 108, 0, 101, 0,
                0, 0, 2, 0, 0, 0, 98, 0, 116, 0, 0, 0, 0, 0, 5, 0, 0, 0, 99, 0, 97, 0, 98, 0, 108,
                0, 101, 0, 0, 0, 8, 0, 0, 0, 105, 0, 110, 0, 116, 0, 101, 0, 114, 0, 110, 0, 97, 0,
                108, 0, 0, 0, 0, 0, 3, 0, 0, 0, 110, 0, 102, 0, 99, 0, 0, 0, 3, 0, 0, 0, 117, 0,
                115, 0, 98, 0, 0, 0, 8, 0, -1, -1, 24, 0, 0, 0, 8, 0, 0, 0, 112, 0, 108, 0, 97, 0,
                116, 0, 102, 0, 111, 0, 114, 0, 109, 0, 0, 0, 0, 0
            };

    /**
     * This byte array was produced by
     * com.google.android.gms.fido.fido2.api.common.AuthenticatorAssertionResponse with test data,
     * i.e.:
     *
     * <pre>{@code
     * AuthenticatorAssertionResponse.Builder()
     *      .setAuthenticatorData(TEST_AUTHENTICATOR_DATA)
     *      .setSignature(TEST_SIGNATURE)
     *      .setClientDataJSON(TEST_CLIENT_DATA_JSON)
     *      .setKeyHandle(TEST_KEY_HANDLE)
     *      .build().serializeToBytes();
     * }</pre>
     *
     * <p>NOTE: See NO_BUILDER comment, above. Additionally this byte array was modified by
     * prepending an object header and tag with value five so that it's compatible with
     * FIDO2_KEY_CREDENTIAL_EXTRA.
     */
    private static final byte[] TEST_AUTHENTICATOR_ASSERTION_RESPONSE =
            new byte[] {
                69, 79, -1, -1, 108, 0, 0, 0, 5, 0, -1, -1, 100, 0, 0, 0, 69, 79, -1, -1, 92, 0, 0,
                0, 2, 0, -1, -1, 36, 0, 0, 0, 32, 0, 0, 0, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6,
                7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 9, 3, 0, -1, -1, 8, 0, 0, 0, 3,
                0, 0, 0, 4, 5, 6, 0, 4, 0, -1, -1, 8, 0, 0, 0, 3, 0, 0, 0, 7, 8, 9, 0, 5, 0, -1, -1,
                8, 0, 0, 0, 3, 0, 0, 0, 10, 11, 12, 0
            };

    /**
     * This byte array is produced by
     * com.google.android.gms.fido.fido2.api.common.PublicKeyCredential with test data, i.e.:
     *
     * <pre>{@code
     * AuthenticatorAssertionResponse response = new AuthenticatorAssertionResponse.Builder()
     *      .setAuthenticatorData(TEST_AUTHENTICATOR_DATA)
     *      .setSignature(TEST_SIGNATURE)
     *      .setClientDataJSON(TEST_CLIENT_DATA_JSON)
     *      .setKeyHandle(TEST_KEY_HANDLE)
     *      .build();
     * UvmEntry uvmEntry0 = new UvmEntry.Builder()
     *      .setUserVerificationMethod(TEST_USER_VERIFICATION_METHOD[0])
     *      .setKeyProtectionType(TEST_KEY_PROTECTION_TYPE[0])
     *      .setMatcherProtectionType(TEST_MATCHER_PROTECTION_TYPE[0])
     *      .build();
     * UvmEntry uvmEntry1 = new UvmEntry.Builder()
     *      .setUserVerificationMethod(TEST_USER_VERIFICATION_METHOD[1])
     *      .setKeyProtectionType(TEST_KEY_PROTECTION_TYPE[1])
     *      .setMatcherProtectionType(TEST_MATCHER_PROTECTION_TYPE[1])
     *      .build();
     * UvmEntries uvmEntries = new UvmEntries.Builder()
     *      .addUvmEntry(uvmEntry0)
     *      .addUvmEntry(uvmEntry1)
     *      .build();
     * AuthenticationExtensionsClientOutputs authenticationExtensionsClientOutputs =
     *      new AuthenticationExtensionsClientOutputs.Builder()
     *              .setUserVerificationMethodEntries(uvmEntries)
     *              .build();
     * PublicKeyCredential publicKeyCredential = new PublicKeyCredential.Builder()
     *      .setResponse(response)
     *      .setAuthenticationExtensionsClientOutputs(authenticationExtensionsClientOutputs)
     *      .build().serializeToBytes();
     * }</pre>
     *
     * <p>NOTE: See NO_BUILDER comment, above.
     */
    private static final byte[] TEST_ASSERTION_PUBLIC_KEY_CREDENTIAL_WITH_UVM =
            new byte[] {
                69, 79, -1, -1, 4, 1, 0, 0, 2, 0, -1, -1, 28, 0, 0, 0, 10, 0, 0, 0, 112, 0, 117, 0,
                98, 0, 108, 0, 105, 0, 99, 0, 45, 0, 107, 0, 101, 0, 121, 0, 0, 0, 0, 0, 5, 0, -1,
                -1, 100, 0, 0, 0, 69, 79, -1, -1, 92, 0, 0, 0, 2, 0, -1, -1, 36, 0, 0, 0, 32, 0, 0,
                0, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7,
                8, 5, 6, 7, 9, 3, 0, -1, -1, 8, 0, 0, 0, 3, 0, 0, 0, 4, 5, 6, 0, 4, 0, -1, -1, 8, 0,
                0, 0, 3, 0, 0, 0, 7, 8, 9, 0, 5, 0, -1, -1, 8, 0, 0, 0, 3, 0, 0, 0, 10, 11, 12, 0,
                7, 0, -1, -1, 108, 0, 0, 0, 69, 79, -1, -1, 100, 0, 0, 0, 1, 0, -1, -1, 92, 0, 0, 0,
                69, 79, -1, -1, 84, 0, 0, 0, 1, 0, -1, -1, 76, 0, 0, 0, 2, 0, 0, 0, 32, 0, 0, 0, 69,
                79, -1, -1, 24, 0, 0, 0, 1, 0, 4, 0, 2, 0, 0, 0, 2, 0, 4, 0, 2, 0, 0, 0, 3, 0, 4, 0,
                4, 0, 0, 0, 32, 0, 0, 0, 69, 79, -1, -1, 24, 0, 0, 0, 1, 0, 4, 0, 0, 2, 0, 0, 2, 0,
                4, 0, 1, 0, 0, 0, 3, 0, 4, 0, 1, 0, 0, 0
            };

    /** Get credential response captured with prf extension enabled. */
    private static final byte[] TEST_ASSERTION_PUBLIC_KEY_CREDENTIAL_WITH_PRF =
            new byte[] {
                69, 79, -1, -1, -104, 2, 0, 0, 1, 0, -1, -1, 52, 0, 0, 0, 22, 0, 0, 0, 52, 0, 121,
                0, 82, 0, 87, 0, 50, 0, 56, 0, 89, 0, 108, 0, 103, 0, 119, 0, 57, 0, 99, 0, 68, 0,
                70, 0, 83, 0, 65, 0, 69, 0, 95, 0, 99, 0, 68, 0, 48, 0, 65, 0, 0, 0, 0, 0, 2, 0, -1,
                -1, 28, 0, 0, 0, 10, 0, 0, 0, 112, 0, 117, 0, 98, 0, 108, 0, 105, 0, 99, 0, 45, 0,
                107, 0, 101, 0, 121, 0, 0, 0, 0, 0, 3, 0, -1, -1, 20, 0, 0, 0, 16, 0, 0, 0, -29, 36,
                86, -37, -58, 37, -125, 15, 92, 12, 84, -128, 19, -9, 3, -48, 5, 0, -1, -1, -96, 1,
                0, 0, 69, 79, -1, -1, -104, 1, 0, 0, 2, 0, -1, -1, 20, 0, 0, 0, 16, 0, 0, 0, -29,
                36, 86, -37, -58, 37, -125, 15, 92, 12, 84, -128, 19, -9, 3, -48, 3, 0, -1, -1, -40,
                0, 0, 0, -45, 0, 0, 0, 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97,
                117, 116, 104, 110, 46, 103, 101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110,
                103, 101, 34, 58, 34, 81, 113, 109, 48, 87, 76, 89, 87, 108, 99, 120, 95, 98, 106,
                112, 84, 100, 113, 65, 80, 52, 50, 98, 102, 107, 118, 48, 97, 74, 116, 90, 95, 90,
                120, 71, 72, 69, 113, 55, 74, 117, 55, 70, 112, 107, 117, 69, 79, 78, 73, 71, 110,
                80, 113, 51, 102, 109, 68, 75, 45, 104, 55, 72, 73, 73, 54, 74, 71, 79, 86, 113, 72,
                56, 52, 57, 53, 49, 66, 68, 83, 71, 45, 118, 68, 76, 65, 34, 44, 34, 111, 114, 105,
                103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 92, 47, 92, 47, 119, 101,
                98, 97, 117, 116, 104, 110, 46, 105, 111, 34, 44, 34, 97, 110, 100, 114, 111, 105,
                100, 80, 97, 99, 107, 97, 103, 101, 78, 97, 109, 101, 34, 58, 34, 99, 111, 109, 46,
                103, 111, 111, 103, 108, 101, 46, 97, 110, 100, 114, 111, 105, 100, 46, 97, 112,
                112, 115, 46, 99, 104, 114, 111, 109, 101, 34, 125, 0, 4, 0, -1, -1, 44, 0, 0, 0,
                37, 0, 0, 0, 116, -90, -22, -110, 19, -55, -100, 47, 116, -78, 36, -110, -77, 32,
                -49, 64, 38, 42, -108, -63, -87, 80, -96, 57, 127, 41, 37, 11, 96, -124, 30, -16,
                29, 0, 0, 0, 0, 0, 0, 0, 5, 0, -1, -1, 76, 0, 0, 0, 70, 0, 0, 0, 48, 68, 2, 32, 80,
                79, -84, 125, 98, -42, -17, 18, 52, 61, -53, -41, -88, -21, -126, -123, 108, -44,
                -20, -6, 95, -82, -67, 111, -13, 43, -127, -123, 105, 106, -29, -9, 2, 32, 11, 44,
                -2, -84, 60, -38, -49, -45, 69, 69, -88, -29, 29, -29, -5, -115, -13, -50, -105, 92,
                -108, 74, -125, 35, 87, 95, -26, -29, -27, 64, -111, -73, 0, 0, 6, 0, -1, -1, 12, 0,
                0, 0, 7, 0, 0, 0, 78, 68, 81, 48, 78, 68, 81, 0, 7, 0, -1, -1, 76, 0, 0, 0, 69, 79,
                -1, -1, 68, 0, 0, 0, 4, 0, -1, -1, 60, 0, 0, 0, 69, 79, -1, -1, 52, 0, 0, 0, 1, 0,
                4, 0, 0, 0, 0, 0, 2, 0, -1, -1, 36, 0, 0, 0, 32, 0, 0, 0, -49, 63, -92, 1, -28, 95,
                -13, -108, -64, 100, 81, 6, 53, -105, 125, 108, 37, 7, 73, 14, 36, -69, 65, 25, 17,
                -91, 61, 29, -43, 93, 70, 32, 8, 0, -1, -1, 24, 0, 0, 0, 8, 0, 0, 0, 112, 0, 108, 0,
                97, 0, 116, 0, 102, 0, 111, 0, 114, 0, 109, 0, 0, 0, 0, 0
            };

    /**
     * The value of the prf extension response in the sample, {@link
     * Fido2ApiTestHelper#TEST_ASSERTION_PUBLIC_KEY_CREDENTIAL_WITH_PRF}.
     */
    private static final byte[] TEST_ASSERTION_PRF_VALUES_BYTES = {
        -49, 63, -92, 1, -28, 95, -13, -108, -64, 100, 81, 6, 53, -105, 125, 108, 37, 7, 73, 14, 36,
        -69, 65, 25, 17, -91, 61, 29, -43, 93, 70, 32
    };

    private static final byte[] TEST_KEY_HANDLE =
            BaseEncoding.base16()
                    .decode("0506070805060708050607080506070805060708050607080506070805060709");
    private static final String TEST_ENCODED_KEY_HANDLE =
            Base64.encodeToString(
                    TEST_KEY_HANDLE, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
    private static final byte[] TEST_ATTESTATION_OBJECT =
            new byte[] {
                -93, 99, 102, 109, 116, 100, 110, 111, 110, 101, 103, 97, 116, 116, 83, 116, 109,
                116, -96, 104, 97, 117, 116, 104, 68, 97, 116, 97, 88, -60, 38, -67, 114, 120, -66,
                70, 55, 97, -15, -6, -95, -79, 10, -76, -60, -8, 38, 112, 38, -100, 65, 12, 114,
                106, 31, -42, -32, 88, 85, -31, -101, 70, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 64, 124, 80, -60, -114, 69, -117, 44, -120, 122, -62, 63,
                104, 18, -66, 2, -3, -56, 35, -24, 66, -4, 74, 48, -128, -52, 80, -100, 46, 97, 93,
                -25, -21, -53, 40, 123, 90, -107, -20, 111, -4, 15, 64, 122, 15, -84, -21, -33, -15,
                26, 11, 35, 36, -49, 116, 52, -74, 107, 63, 113, -59, 125, -27, -120, -63, -91, 1,
                2, 3, 38, 32, 1, 33, 88, 32, -75, -80, 118, 102, -14, 124, -108, -9, -27, -91, 59,
                -48, -92, -102, -38, -44, 92, 95, 14, -62, 41, -117, -70, 101, 9, 64, 35, 31, -20,
                79, -71, -71, 34, 88, 32, -24, -33, 64, 97, -31, -34, 96, -83, -119, -25, 21, -14,
                -56, -70, -37, -116, -21, -33, -128, -66, 61, 41, 107, 16, -25, 120, 106, -113, 54,
                -62, -102, 42
            };

    // TEST_DISCOVERABLE_CREDENTIAL_ASSERTION is the payload of an Intent response for an assertion
    // with an empty allowList. This was captured from Play Services.
    private static final byte[] TEST_DISCOVERABLE_CREDENTIAL_ASSERTION =
            new byte[] {
                69, 79, -1, -1, 100, 1, 0, 0, 1, 0, -1, -1, 52, 0, 0, 0, 22, 0, 0, 0, 99, 0, 72, 0,
                116, 0, 85, 0, 70, 0, 86, 0, 112, 0, 82, 0, 88, 0, 99, 0, 73, 0, 50, 0, 109, 0, 67,
                0, 49, 0, 76, 0, 119, 0, 106, 0, 95, 0, 85, 0, 72, 0, 65, 0, 0, 0, 0, 0, 2, 0, -1,
                -1, 28, 0, 0, 0, 10, 0, 0, 0, 112, 0, 117, 0, 98, 0, 108, 0, 105, 0, 99, 0, 45, 0,
                107, 0, 101, 0, 121, 0, 0, 0, 0, 0, 3, 0, -1, -1, 20, 0, 0, 0, 16, 0, 0, 0, 112,
                123, 84, 21, 90, 81, 93, -62, 54, -104, 45, 75, -62, 63, -44, 28, 5, 0, -1, -1, -32,
                0, 0, 0, 69, 79, -1, -1, -40, 0, 0, 0, 2, 0, -1, -1, 20, 0, 0, 0, 16, 0, 0, 0, 112,
                123, 84, 21, 90, 81, 93, -62, 54, -104, 45, 75, -62, 63, -44, 28, 3, 0, -1, -1, 16,
                0, 0, 0, 9, 0, 0, 0, 60, 105, 110, 118, 97, 108, 105, 100, 62, 0, 0, 0, 4, 0, -1,
                -1, 44, 0, 0, 0, 37, 0, 0, 0, -28, 83, 41, -48, 58, 32, 104, -47, -54, -9, -9, -69,
                10, -23, 84, -26, -80, -26, 37, -105, 69, -13, 47, 72, 41, -9, 80, -16, 80, 17, -7,
                -62, 5, 0, 0, 0, 0, 0, 0, 0, 5, 0, -1, -1, 76, 0, 0, 0, 70, 0, 0, 0, 48, 68, 2, 32,
                86, -36, 80, 3, 65, -90, 66, 76, 100, -126, -34, 82, -22, 96, 3, 71, 3, 68, 57, 62,
                -4, -80, 34, 119, 97, 30, 100, -65, -36, 95, -68, 19, 2, 32, 91, 52, -110, -105,
                -104, 105, 94, 41, 63, -97, -86, 15, 35, 117, 61, 73, 119, -113, 95, 69, 44, -13,
                -22, 61, 32, 79, 53, 106, 127, -67, 32, 4, 0, 0, 6, 0, -1, -1, 20, 0, 0, 0, 15, 0,
                0, 0, 98, 111, 98, 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 0
            };

    // Serialized registration response converted from JSON received from the Credential Manager
    // API.
    private static final byte[] TEST_SERIALIZED_CREDMAN_MAKE_CREDENTIAL_RESPONSE =
            new byte[] {
                72, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 127, 7, 0, 0, 80, 1,
                0, 0, 0, 0, 0, 0, 24, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 88, 2, 0, 0, 0,
                0, 0, 0, 121, 127, 127, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0,
                0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 80,
                0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 10, 0, 0, 0, 100, 71, 86, 122, 100, 67, 66, 112,
                90, 65, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 7, 0, 0, 0, 116, 101, 115, 116, 32, 105, 100,
                0, 29, 0, 0, 0, 21, 0, 0, 0, 116, 101, 115, 116, 32, 99, 108, 105, 101, 110, 116,
                32, 100, 97, 116, 97, 32, 106, 115, 111, 110, 0, 0, 0, 44, 0, 0, 0, 36, 0, 0, 0, 38,
                61, 114, 120, 62, 70, 55, 97, 113, 122, 33, 49, 10, 52, 68, 120, 38, 112, 38, 28,
                65, 12, 114, 106, 31, 86, 96, 88, 85, 97, 27, 70, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 4, 39, 41, 1, 40, 110, 3, 80, 15, 47, 41, 58,
                19, 51, 47, 127, 27, 40, 33, 99, 49, 9, 37, 68, 106, 84, 45, 115, 43, 28, 110, 22,
                37, 1, 2, 3, 38, 32, 1, 33, 88, 32, 22, 69, 112, 93, 97, 55, 24, 88, 99, 78, 82, 22,
                61, 23, 119, 47, 51, 94, 68, 116, 101, 45, 126, 101, 120, 104, 68, 110, 61, 57, 18,
                117, 34, 88, 32, 113, 19, 12, 37, 54, 32, 60, 39, 101, 115, 54, 117, 31, 126, 42,
                68, 15, 37, 32, 99, 6, 61, 26, 103, 84, 38, 33, 7, 81, 62, 12, 2, 0, 0, 0, 0, 74, 0,
                0, 0, 66, 0, 0, 0, 35, 99, 102, 109, 116, 100, 110, 111, 110, 101, 103, 97, 116,
                116, 83, 116, 109, 116, 32, 104, 97, 117, 116, 104, 68, 97, 116, 97, 88, 36, 38, 61,
                114, 120, 62, 70, 55, 97, 113, 122, 33, 49, 10, 52, 68, 120, 38, 112, 38, 28, 65,
                12, 114, 106, 31, 86, 96, 88, 85, 97, 27, 70, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 4, 39, 41, 1, 40, 110, 3, 80, 15, 47, 41, 58, 19,
                51, 47, 127, 27, 40, 33, 99, 49, 9, 37, 68, 106, 84, 45, 115, 43, 28, 110, 22, 37,
                1, 2, 3, 38, 32, 1, 33, 88, 32, 22, 69, 112, 93, 97, 55, 24, 88, 99, 78, 82, 22, 61,
                23, 119, 47, 51, 94, 68, 116, 101, 45, 126, 101, 120, 104, 68, 110, 61, 57, 18, 117,
                34, 88, 32, 113, 19, 12, 37, 54, 32, 60, 39, 101, 115, 54, 117, 31, 126, 42, 68, 15,
                37, 32, 99, 6, 61, 26, 103, 84, 38, 33, 7, 81, 62, 12, 2, 0, 0, 0, 0, 0, 0, 12, 0,
                0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, 1,
                2, 3, 4, 0, 0, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0, 99, 0, 0, 0,
                91, 0, 0, 0, 48, 89, 48, 19, 6, 7, 42, 6, 72, 78, 61, 2, 1, 6, 8, 42, 6, 72, 78, 61,
                3, 1, 7, 3, 66, 0, 4, 22, 69, 112, 93, 97, 55, 24, 88, 99, 78, 82, 22, 61, 23, 119,
                47, 51, 94, 68, 116, 101, 45, 126, 101, 120, 104, 68, 110, 61, 57, 18, 117, 113, 19,
                12, 37, 54, 32, 60, 39, 101, 115, 54, 117, 31, 126, 42, 68, 15, 37, 32, 99, 6, 61,
                26, 103, 84, 38, 33, 7, 81, 62, 12, 2, 0, 0, 0, 0, 0
            };

    // Serialized assertion response converted from JSON received from the Credential Manager API.
    private static final byte[] TEST_SERIALIZED_CREDMAN_GET_CREDENTIAL_RESPONSE =
            new byte[] {
                48, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -64, 0, 0,
                0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0,
                0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 88,
                0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 22, 0, 0, 0, 78, 81, 119, 83, 88, 81, 109, 80,
                113, 99, 107, 112, 54, 86, 66, 114, 117, 74, 52, 84, 45, 65, 0, 0, 24, 0, 0, 0, 16,
                0, 0, 0, 53, 12, 18, 93, 9, -113, -87, -55, 41, -23, 80, 107, -72, -98, 19, -8, 17,
                0, 0, 0, 9, 0, 0, 0, 60, 105, 110, 118, 97, 108, 105, 100, 62, 0, 0, 0, 0, 0, 0, 0,
                45, 0, 0, 0, 37, 0, 0, 0, -56, 89, -63, 83, -10, -10, 58, 88, -74, 13, 49, -64, 95,
                85, -99, 61, -90, 100, -74, -120, 11, 20, 82, 85, -69, -47, 2, 101, 101, -105, -58,
                -109, 29, 0, 0, 0, 0, 0, 0, 0, 79, 0, 0, 0, 71, 0, 0, 0, 48, 69, 2, 33, 0, -104, -2,
                -11, -58, 98, 106, -47, 80, 63, 82, 35, 47, 121, -87, -40, -119, 39, 121, -58, 107,
                -119, -108, 90, -22, -12, -36, -113, -56, -112, -63, 21, 44, 2, 32, 47, 7, 8, 107,
                110, 36, -60, -49, -32, 118, 54, -92, 84, 124, 77, -80, 87, -9, 7, -50, -1, 24, -49,
                -7, -116, -42, -93, -23, -111, 91, 80, 87, 0, 26, 0, 0, 0, 18, 0, 0, 0, 49, 55, 51,
                48, 54, 51, 55, 54, 56, 46, 57, 56, 53, 54, 49, 54, 57, 53, 0, 0, 0, 0, 0, 0, 56, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
            };

    // TEST_USER_HANDLE is the user ID contained within `TEST_DISCOVERABLE_CREDENTIAL_ASSERTION`.
    public static final byte[] TEST_USER_HANDLE = "[email protected]".getBytes(UTF_8);

    private static final byte[] TEST_CLIENT_DATA_JSON = new byte[] {4, 5, 6};
    private static final byte[] TEST_AUTHENTICATOR_DATA = new byte[] {7, 8, 9};
    private static final byte[] TEST_SIGNATURE = new byte[] {10, 11, 12};
    private static final long TIMEOUT_MS = scaleTimeout(TimeUnit.SECONDS.toMillis(1));
    private static final int[] TEST_USER_VERIFICATION_METHOD = new int[] {0x00000002, 0x00000200};
    private static final short[] TEST_KEY_PROTECTION_TYPE = new short[] {0x0002, 0x0001};
    private static final short[] TEST_MATCHER_PROTECTION_TYPE = new short[] {0x0004, 0x0001};
    private static final String TEST_SERIALIZED_MAKE_CREDENTIAL_REQUEST_JSON =
            "{serialized_make_request}";
    private static final String TEST_SERIALIZED_GET_ASSERTION_REQUEST_JSON =
            "{serialized_get_request}";

    /**
     * Builds a test intent to be returned by a successful call to makeCredential.
     *
     * @return Intent containing the response from the Fido2 API.
     */
    public static Intent createSuccessfulMakeCredentialIntent() {
        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, TEST_AUTHENTICATOR_ATTESTATION_RESPONSE);
        return intent;
    }

    /**
     * Builds a test intent to be returned by a successful call to makeCredential.
     *
     * @return Intent containing the response from the Fido2 API.
     */
    public static Intent createSuccessfulPasskeyMakeCredentialIntent() {
        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, TEST_AUTHENTICATOR_PASSKEY_ATTESTATION_RESPONSE);
        return intent;
    }

    private static Intent intentFromPath(String path) {
        byte[] response;
        try {
            response = Files.readAllBytes(Paths.get(UrlUtils.getTestFilePath(path)));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, response);
        return intent;
    }

    public static Intent createSuccessfulMakeCredentialIntentWithAttestation() {
        // This blob is too large (9KB) to reasonably include as a literal.
        return intentFromPath("webauthn/android_make_credential_bundle_with_attestation");
    }

    public static Intent createSuccessfulMakeCredentialIntentWithCredProps() {
        return intentFromPath("webauthn/android_make_credential_bundle_with_credprops_rk_true");
    }

    /**
     * Construct default options for a makeCredential request.
     *
     * @return Options for the Fido2 API.
     * @throws Exception
     */
    public static PublicKeyCredentialCreationOptions createDefaultMakeCredentialOptions()
            throws Exception {
        PublicKeyCredentialCreationOptions options = new PublicKeyCredentialCreationOptions();
        options.challenge = "climb a mountain".getBytes("UTF8");
        options.hints = new int[0];

        options.relyingParty = new PublicKeyCredentialRpEntity();
        options.relyingParty.id = "subdomain.example.test";
        options.relyingParty.name = "Acme";

        options.user = new PublicKeyCredentialUserEntity();
        options.user.id = "1098237235409872".getBytes("UTF8");
        options.user.name = "[email protected]";
        options.user.displayName = "Avery A. Jones";

        options.timeout = new TimeDelta();
        options.timeout.microseconds = TimeUnit.MILLISECONDS.toMicros(TIMEOUT_MS);

        PublicKeyCredentialParameters parameters = new PublicKeyCredentialParameters();
        parameters.algorithmIdentifier = -7;
        parameters.type = PublicKeyCredentialType.PUBLIC_KEY;
        options.publicKeyParameters = new PublicKeyCredentialParameters[] {parameters};

        PublicKeyCredentialDescriptor descriptor = new PublicKeyCredentialDescriptor();
        descriptor.type = 0;
        descriptor.id = new byte[] {8, 7, 6};
        descriptor.transports = new int[] {0};
        options.excludeCredentials = new PublicKeyCredentialDescriptor[] {descriptor};

        options.authenticatorSelection = new AuthenticatorSelectionCriteria();
        /* TODO add UserVerificationRequirement and ResidentKeyRequirement when the FIDO2 API
         * supports it */
        options.authenticatorSelection.authenticatorAttachment =
                AuthenticatorAttachment.CROSS_PLATFORM;

        options.attestationFormats = new String[0];
        return options;
    }

    /**
     * Verifies that the returned response matches expected values.
     *
     * @param response The response from the Fido2 API.
     */
    public static void validateMakeCredentialResponse(
            MakeCredentialAuthenticatorResponse response) {
        Assert.assertArrayEquals(response.attestationObject, TEST_ATTESTATION_OBJECT);
        Assert.assertArrayEquals(response.info.rawId, TEST_KEY_HANDLE);
        Assert.assertEquals(response.info.id, TEST_ENCODED_KEY_HANDLE);
        Assert.assertArrayEquals(response.info.clientDataJson, TEST_CLIENT_DATA_JSON);
    }

    /**
     * Constructs default options for a getAssertion request.
     *
     * @return Options for the Fido2 API
     * @throws Exception
     */
    public static PublicKeyCredentialRequestOptions createDefaultGetAssertionOptions()
            throws Exception {
        PublicKeyCredentialRequestOptions options = new PublicKeyCredentialRequestOptions();
        options.extensions = new AuthenticationExtensionsClientInputs();
        options.challenge = "climb a mountain".getBytes("UTF8");
        options.timeout = new TimeDelta();
        options.timeout.microseconds = TimeUnit.MILLISECONDS.toMicros(TIMEOUT_MS);
        options.relyingPartyId = "subdomain.example.test";
        options.hints = new int[0];

        PublicKeyCredentialDescriptor descriptor = new PublicKeyCredentialDescriptor();
        descriptor.type = 0;
        descriptor.id = new byte[] {8, 7, 6};
        descriptor.transports = new int[] {0};
        options.allowCredentials = new PublicKeyCredentialDescriptor[] {descriptor};

        options.extensions.cableAuthenticationData = new CableAuthentication[] {};
        options.extensions.prfInputs = new PrfValues[] {};
        return options;
    }

    /**
     * Builds a test intent without uvm extension to be returned by a successful call to
     * makeCredential.
     *
     * @return Intent containing the response from the Fido2 API.
     */
    public static Intent createSuccessfulGetAssertionIntent() {
        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, TEST_AUTHENTICATOR_ASSERTION_RESPONSE);
        return intent;
    }

    /**
     * Builds a test intent with uvm extension to be returned by a successful call to
     * makeCredential.
     *
     * @return Intent containing the response from the Fido2 API.
     */
    public static Intent createSuccessfulGetAssertionIntentWithUvm() {
        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, TEST_ASSERTION_PUBLIC_KEY_CREDENTIAL_WITH_UVM);
        return intent;
    }

    /**
     * Builds a test intent with prf extension to be returned by a successful call to
     * makeCredential.
     *
     * @return Intent containing the response from the Fido2 API.
     */
    public static Intent createSuccessfulGetAssertionIntentWithPrf() {
        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, TEST_ASSERTION_PUBLIC_KEY_CREDENTIAL_WITH_PRF);
        return intent;
    }

    /**
     * Verifies the values in the prf extension.
     *
     * @param prfValues The {@link PrfValues} from Fido2Api's extensions.
     */
    public static void validatePrfResults(PrfValues prfValues) {
        Assert.assertNotNull(prfValues);
        Assert.assertArrayEquals(prfValues.first, TEST_ASSERTION_PRF_VALUES_BYTES);
    }

    /**
     * Builds a test intent that contains a response to an assertion that had an empty allowList.
     */
    public static Intent createSuccessfulGetAssertionIntentWithUserId() {
        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, TEST_DISCOVERABLE_CREDENTIAL_ASSERTION);
        return intent;
    }

    /**
     * Verifies that the returned userVerificationMethod matches expected values.
     *
     * @param userVerificationMethods The userVerificationMethods from the Fido2 API.
     */
    public static void validateUserVerificationMethods(
            boolean echoUserVerificationMethods, UvmEntry[] userVerificationMethods) {
        if (echoUserVerificationMethods) {
            Assert.assertEquals(
                    userVerificationMethods.length, TEST_USER_VERIFICATION_METHOD.length);
            for (int i = 0; i < userVerificationMethods.length; i++) {
                Assert.assertEquals(
                        userVerificationMethods[i].userVerificationMethod,
                        TEST_USER_VERIFICATION_METHOD[i]);
                Assert.assertEquals(
                        userVerificationMethods[i].keyProtectionType, TEST_KEY_PROTECTION_TYPE[i]);
                Assert.assertEquals(
                        userVerificationMethods[i].matcherProtectionType,
                        TEST_MATCHER_PROTECTION_TYPE[i]);
            }
        }
    }

    /**
     * Verifies that the returned response matches expected values.
     *
     * @param response The response from the Fido2 API.
     */
    public static void validateGetAssertionResponse(GetAssertionAuthenticatorResponse response) {
        Assert.assertArrayEquals(response.info.authenticatorData, TEST_AUTHENTICATOR_DATA);
        Assert.assertArrayEquals(response.signature, TEST_SIGNATURE);
        Assert.assertArrayEquals(response.info.rawId, TEST_KEY_HANDLE);
        Assert.assertEquals(response.info.id, TEST_ENCODED_KEY_HANDLE);
        Assert.assertArrayEquals(response.info.clientDataJson, TEST_CLIENT_DATA_JSON);
        validateUserVerificationMethods(
                response.extensions.echoUserVerificationMethods,
                response.extensions.userVerificationMethods);
    }

    /**
     * Verifies that the response did not return before timeout.
     *
     * @param startTimeMs The start time of the operation.
     */
    public static void verifyRespondedBeforeTimeout(long startTimeMs) {
        long elapsedTime = SystemClock.elapsedRealtime() - startTimeMs;
        Assert.assertTrue(elapsedTime < TIMEOUT_MS);
    }

    private static void appendErrorResponseToParcel(
            int errorCode, @Nullable String message, Parcel parcel) {
        final int a = writeHeader(OBJECT_MAGIC, parcel);
        final int b = writeHeader(6, parcel);
        final int c = writeHeader(OBJECT_MAGIC, parcel);

        int z = writeHeader(2, parcel);
        parcel.writeInt(errorCode);
        writeLength(z, parcel);

        if (message != null) {
            z = writeHeader(3, parcel);
            parcel.writeString(message);
            writeLength(z, parcel);
        }

        writeLength(c, parcel);
        writeLength(b, parcel);
        writeLength(a, parcel);
    }

    private static int writeHeader(int tag, Parcel parcel) {
        parcel.writeInt(0xffff0000 | tag);
        return startLength(parcel);
    }

    private static int startLength(Parcel parcel) {
        int pos = parcel.dataPosition();
        parcel.writeInt(0xdddddddd);
        return pos;
    }

    private static void writeLength(int pos, Parcel parcel) {
        int totalLength = parcel.dataPosition();
        parcel.setDataPosition(pos);
        parcel.writeInt(totalLength - pos - 4);
        parcel.setDataPosition(totalLength);
    }

    /**
     * Constructs an intent that returns an error response from the Fido2 API.
     *
     * @param errorCode Numeric values corresponding to a Fido2 error.
     * @return an Intent containing the error response.
     */
    public static Intent createErrorIntent(int errorCode, @Nullable String errorMsg) {
        Parcel parcel = Parcel.obtain();
        appendErrorResponseToParcel(errorCode, errorMsg, parcel);

        Intent intent = new Intent();
        intent.putExtra(Fido2Api.CREDENTIAL_EXTRA, parcel.marshall());
        return intent;
    }

    /** Creates a PaymentOptions object with normal values. */
    public static PaymentOptions createPaymentOptions() {
        PaymentOptions options = new PaymentOptions();
        options.total = new PaymentCurrencyAmount();
        options.total.currency = "USD";
        options.total.value = "888";
        options.instrument = new PaymentCredentialInstrument();
        options.instrument.displayName = "MaxPay";
        options.instrument.icon = new Url();
        options.instrument.icon.url = "https://www.google.com/icon.png";
        options.payeeOrigin = new org.chromium.url.internal.mojom.Origin();
        options.payeeOrigin.scheme = "https";
        options.payeeOrigin.host = "test.example";
        options.payeeOrigin.port = 443;
        return options;
    }

    /**
     * Mocks ClientDataJson so that it returns the provided result.
     *
     * @param mocker The JNI mocker
     * @param mockResult The mock value for {@link ClientDataJson#buildClientDataJson} to return.
     */
    public static void mockClientDataJson(JniMocker mocker, String mockResult) {
        ClientDataJsonImpl.Natives clientDataJsonJni =
                new ClientDataJsonImpl.Natives() {
                    @Override
                    public String buildClientDataJson(
                            int clientDataRequestType,
                            String callerOrigin,
                            byte[] challenge,
                            boolean isCrossOrigin,
                            ByteBuffer optionsByteBuffer,
                            String relyingPartyId,
                            org.chromium.url.Origin topOrigin) {
                        return mockResult;
                    }
                };
        mocker.mock(ClientDataJsonImplJni.TEST_HOOKS, clientDataJsonJni);
    }

    /**
     * Creates a {@link WebauthnCredentailDetails} object for testing.
     *
     * @return a newly created {@link WebauthnCredentialDetails}.
     */
    public static WebauthnCredentialDetails getCredentialDetails() {
        WebauthnCredentialDetails credential = new WebauthnCredentialDetails();
        credential.mUserId = "1098237235409872".getBytes(UTF_8);
        credential.mUserName = "[email protected]";
        credential.mUserDisplayName = "Avery A. Jones";
        credential.mCredentialId = new byte[] {8, 7, 6};
        credential.mIsDiscoverable = true;
        credential.mIsPayment = false;
        return credential;
    }

    public static void mockFido2CredentialRequestJni(JniMocker mocker) {
        Fido2CredentialRequest.Natives fido2CredentialRequestJni =
                new Fido2CredentialRequest.Natives() {
                    @Override
                    public String createOptionsToJson(ByteBuffer serializedOptions) {
                        return TEST_SERIALIZED_MAKE_CREDENTIAL_REQUEST_JSON;
                    }

                    @Override
                    public byte[] makeCredentialResponseFromJson(String json) {
                        return TEST_SERIALIZED_CREDMAN_MAKE_CREDENTIAL_RESPONSE;
                    }

                    @Override
                    public String getOptionsToJson(ByteBuffer serializedOptions) {
                        return TEST_SERIALIZED_GET_ASSERTION_REQUEST_JSON;
                    }

                    @Override
                    public byte[] getCredentialResponseFromJson(String json) {
                        return TEST_SERIALIZED_CREDMAN_GET_CREDENTIAL_RESPONSE;
                    }
                };
        mocker.mock(Fido2CredentialRequestJni.TEST_HOOKS, fido2CredentialRequestJni);
    }

    public static AuthenticatorCallback getAuthenticatorCallback() {
        return new AuthenticatorCallback();
    }

    /** Callback class to pass to Fido2CredentialRequest WebAuthn operations. */
    public static class AuthenticatorCallback {
        private Integer mStatus;
        private MakeCredentialAuthenticatorResponse mMakeCredentialResponse;
        private GetAssertionAuthenticatorResponse mGetAssertionAuthenticatorResponse;
        private List<byte[]> mGetMatchingCredentialIdsResponse;

        // Signals when request is complete.
        private final ConditionVariable mDone = new ConditionVariable();

        AuthenticatorCallback() {}

        public void onRegisterResponse(int status, MakeCredentialAuthenticatorResponse response) {
            assert mStatus == null;
            mStatus = status;
            mMakeCredentialResponse = response;
            unblock();
        }

        public void onSignResponse(int status, GetAssertionAuthenticatorResponse response) {
            assert mStatus == null;
            mStatus = status;
            mGetAssertionAuthenticatorResponse = response;
            unblock();
        }

        public void onGetMatchingCredentialIds(List<byte[]> matchingCredentialIds) {
            mGetMatchingCredentialIdsResponse = matchingCredentialIds;
            unblock();
        }

        public void onError(int status) {
            assert mStatus == null;
            mStatus = status;
            unblock();
        }

        public Integer getStatus() {
            return mStatus;
        }

        public MakeCredentialAuthenticatorResponse getMakeCredentialResponse() {
            return mMakeCredentialResponse;
        }

        public GetAssertionAuthenticatorResponse getGetAssertionResponse() {
            return mGetAssertionAuthenticatorResponse;
        }

        public List<byte[]> getGetMatchingCredentialIdsResponse() {
            return mGetMatchingCredentialIdsResponse;
        }

        public void blockUntilCalled() {
            mDone.block();
        }

        private void unblock() {
            mDone.open();
        }
    }
}