import { decodeAuthenticatorExtensions, } from './decodeAuthenticatorExtensions.js';
import { isoCBOR, isoUint8Array } from './iso/index.js';
/**
 * Make sense of the authData buffer contained in an Attestation
 */
export function parseAuthenticatorData(authData) {
    if (authData.byteLength < 37) {
        throw new Error(`Authenticator data was ${authData.byteLength} bytes, expected at least 37 bytes`);
    }
    let pointer = 0;
    const dataView = isoUint8Array.toDataView(authData);
    const rpIdHash = authData.slice(pointer, pointer += 32);
    const flagsBuf = authData.slice(pointer, pointer += 1);
    const flagsInt = flagsBuf[0];
    // Bit positions can be referenced here:
    // https://www.w3.org/TR/webauthn-2/#flags
    const flags = {
        up: !!(flagsInt & (1 << 0)), // User Presence
        uv: !!(flagsInt & (1 << 2)), // User Verified
        be: !!(flagsInt & (1 << 3)), // Backup Eligibility
        bs: !!(flagsInt & (1 << 4)), // Backup State
        at: !!(flagsInt & (1 << 6)), // Attested Credential Data Present
        ed: !!(flagsInt & (1 << 7)), // Extension Data Present
        flagsInt,
    };
    const counterBuf = authData.slice(pointer, pointer + 4);
    const counter = dataView.getUint32(pointer, false);
    pointer += 4;
    let aaguid = undefined;
    let credentialID = undefined;
    let credentialPublicKey = undefined;
    if (flags.at) {
        aaguid = authData.slice(pointer, pointer += 16);
        const credIDLen = dataView.getUint16(pointer);
        pointer += 2;
        credentialID = authData.slice(pointer, pointer += credIDLen);
        /**
         * Firefox 117 incorrectly CBOR-encodes authData when EdDSA (-8) is used for the public key.
         * A CBOR "Map of 3 items" (0xa3) should be "Map of 4 items" (0xa4), and if we manually adjust
         * the single byte there's a good chance the authData can be correctly parsed.
         *
         * This browser release also incorrectly uses the string labels "OKP" and "Ed25519" instead of
         * their integer representations for kty and crv respectively. That's why the COSE public key
         * in the hex below looks so odd.
         */
        // Bytes decode to `{ 1: "OKP", 3: -8, -1: "Ed25519" }` (it's missing key -2 a.k.a. COSEKEYS.x)
        const badEdDSACBOR = isoUint8Array.fromHex('a301634f4b500327206745643235353139');
        const bytesAtCurrentPosition = authData.slice(pointer, pointer + badEdDSACBOR.byteLength);
        let foundBadCBOR = false;
        if (isoUint8Array.areEqual(badEdDSACBOR, bytesAtCurrentPosition)) {
            // Change the bad CBOR 0xa3 to 0xa4 so that the credential public key can be recognized
            foundBadCBOR = true;
            authData[pointer] = 0xa4;
        }
        // Decode the next CBOR item in the buffer, then re-encode it back to a Buffer
        const firstDecoded = isoCBOR.decodeFirst(authData.slice(pointer));
        const firstEncoded = Uint8Array.from(
        /**
         * Casting to `Map` via `as unknown` here because TS doesn't make it possible to define Maps
         * with discrete keys and properties with known types per pair, and CBOR libs typically parse
         * CBOR Major Type 5 to `Map` because you can have numbers for keys. A `COSEPublicKey` can be
         * generalized as "a Map with numbers for keys and either numbers or bytes for values" though.
         * If this presumption falls apart then other parts of verification later on will fail so we
         * should be safe doing this here.
         */
        isoCBOR.encode(firstDecoded));
        if (foundBadCBOR) {
            // Restore the bit we changed so that `authData` is the same as it came in and won't break
            // signature verification.
            authData[pointer] = 0xa3;
        }
        credentialPublicKey = firstEncoded;
        pointer += firstEncoded.byteLength;
    }
    let extensionsData = undefined;
    let extensionsDataBuffer = undefined;
    if (flags.ed) {
        const firstDecoded = isoCBOR.decodeFirst(authData.slice(pointer));
        extensionsDataBuffer = Uint8Array.from(isoCBOR.encode(firstDecoded));
        extensionsData = decodeAuthenticatorExtensions(extensionsDataBuffer);
        pointer += extensionsDataBuffer.byteLength;
    }
    // Pointer should be at the end of the authenticator data, otherwise too much data was sent
    if (authData.byteLength > pointer) {
        throw new Error('Leftover bytes detected while parsing authenticator data');
    }
    return _parseAuthenticatorDataInternals.stubThis({
        rpIdHash,
        flagsBuf,
        flags,
        counter,
        counterBuf,
        aaguid,
        credentialID,
        credentialPublicKey,
        extensionsData,
        extensionsDataBuffer,
    });
}
/**
 * Make it possible to stub the return value during testing
 * @ignore Don't include this in docs output
 */
export const _parseAuthenticatorDataInternals = {
    stubThis: (value) => value,
};
