"use strict";
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.serializeMessageHeaderAuth = exports.serializeFactory = void 0;
/*
 * This public interface for serializing the AWS Encryption SDK Message Header Format
 * is provided for the use of the Encryption SDK for JavaScript only.  It can be used
 * as a reference but is not intended to be use by any packages other than the
 * Encryption SDK for JavaScript.
 *
 * See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html#header-structure
 */
const concat_buffers_1 = require("./concat_buffers");
const material_management_1 = require("@aws-crypto/material-management");
const identifiers_1 = require("./identifiers");
const uint_util_1 = require("./uint_util");
function serializeFactory(fromUtf8, sorting) {
    return {
        frameIv,
        nonFramedBodyIv,
        headerAuthIv,
        frameHeader,
        finalFrameHeader,
        encodeEncryptionContext,
        serializeEncryptionContext,
        serializeEncryptedDataKeys,
        serializeEncryptedDataKey,
        serializeMessageHeader,
        buildMessageHeader,
    };
    function frameIv(ivLength, sequenceNumber) {
        /* Precondition: sequenceNumber must conform to the specification. i.e. 1 - (2^32 - 1)
         * The sequence number starts at 1
         * https://github.com/awslabs/aws-encryption-sdk-specification/blob/master/data-format/message-body.md#sequence-number
         */
        (0, material_management_1.needs)(sequenceNumber > 0 &&
            identifiers_1.SequenceIdentifier.SEQUENCE_NUMBER_END >= sequenceNumber, 'sequenceNumber out of bounds');
        const buff = new Uint8Array(ivLength);
        const view = new DataView(buff.buffer, buff.byteOffset, buff.byteLength);
        view.setUint32(ivLength - 4, sequenceNumber, false); // big-endian
        return buff;
    }
    function nonFramedBodyIv(ivLength) {
        return frameIv(ivLength, 1);
    }
    function headerAuthIv(ivLength) {
        return new Uint8Array(ivLength); // new Uint8Array is 0 filled by default
    }
    function frameHeader(sequenceNumber, iv) {
        return (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt32BE)(sequenceNumber), iv);
    }
    function finalFrameHeader(sequenceNumber, iv, contentLength) {
        return (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt32BE)(identifiers_1.SequenceIdentifier.SEQUENCE_NUMBER_END), // Final Frame identifier
        (0, uint_util_1.uInt32BE)(sequenceNumber), iv, (0, uint_util_1.uInt32BE)(contentLength));
    }
    function encodeEncryptionContext(encryptionContext) {
        // use closure value from the serializeFactory
        // If the encryption context contains high order
        // utf8 code points the "old" implementation would sort these values
        // based on their values, see the false branch of this function.
        // This led to different sorting if using these high order utf8 code points,
        // which led to decryption failures from other ESDK language implementations
        // that correctly sorted the encryption context by sorting based on the utf8
        // values as opposed to the string value.
        // See, https://github.com/aws/aws-encryption-sdk-javascript/issues/428
        // for mote details
        const { utf8Sorting } = sorting;
        if (utf8Sorting) {
            return Object.entries(encryptionContext)
                .map((entries) => entries.map(fromUtf8))
                .sort(([aKey], [bKey]) => compare(aKey, bKey))
                .map(([key, value]) => (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt16BE)(key.byteLength), key, (0, uint_util_1.uInt16BE)(value.byteLength), value));
        }
        else {
            return (Object.entries(encryptionContext)
                /* Precondition: The serialized encryption context entries must be sorted by UTF-8 key value. */
                .sort(([aKey], [bKey]) => aKey.localeCompare(bKey))
                .map((entries) => entries.map(fromUtf8))
                .map(([key, value]) => (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt16BE)(key.byteLength), key, (0, uint_util_1.uInt16BE)(value.byteLength), value)));
        }
    }
    function compare(a, b) {
        for (let i = 0; i < a.byteLength; i++) {
            if (a[i] < b[i]) {
                return -1;
            }
            if (a[i] > b[i]) {
                return 1;
            }
        }
        if (a.byteLength > b.byteLength) {
            return 1;
        }
        if (a.byteLength < b.byteLength) {
            return -1;
        }
        return 0;
    }
    function serializeEncryptionContext(encryptionContext) {
        const encryptionContextElements = encodeEncryptionContext(encryptionContext);
        /* Check for early return (Postcondition): If there is no context then the length of the _whole_ serialized portion is 0.
         * This is part of the specification of the AWS Encryption SDK Message Format.
         * It is not 0 for length and 0 for count.  The count element is omitted.
         */
        if (!encryptionContextElements.length)
            return (0, uint_util_1.uInt16BE)(0);
        const aadData = (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt16BE)(encryptionContextElements.length), ...encryptionContextElements);
        const aadLength = (0, uint_util_1.uInt16BE)(aadData.byteLength);
        return (0, concat_buffers_1.concatBuffers)(aadLength, aadData);
    }
    function serializeEncryptedDataKeys(encryptedDataKeys) {
        const encryptedKeyInfo = encryptedDataKeys.map(serializeEncryptedDataKey);
        return (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt16BE)(encryptedDataKeys.length), ...encryptedKeyInfo);
    }
    function serializeEncryptedDataKey(edk) {
        const { providerId, providerInfo, encryptedDataKey, rawInfo } = edk;
        const providerIdBytes = fromUtf8(providerId);
        // The providerInfo is technically a binary field, so I prefer rawInfo
        const providerInfoBytes = rawInfo || fromUtf8(providerInfo);
        return (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt16BE)(providerIdBytes.byteLength), providerIdBytes, (0, uint_util_1.uInt16BE)(providerInfoBytes.byteLength), providerInfoBytes, (0, uint_util_1.uInt16BE)(encryptedDataKey.byteLength), encryptedDataKey);
    }
    function serializeMessageHeader(messageHeader) {
        /* Precondition: Must be a version that can be serialized. */
        (0, material_management_1.needs)(identifiers_1.SerializationVersion[messageHeader.version], 'Unsupported version.');
        if (messageHeader.version === 1) {
            return serializeMessageHeaderV1(messageHeader);
        }
        else {
            return serializeMessageHeaderV2(messageHeader);
        }
    }
    function serializeMessageHeaderV1(messageHeader) {
        return (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt8)(messageHeader.version), (0, uint_util_1.uInt8)(messageHeader.type), (0, uint_util_1.uInt16BE)(messageHeader.suiteId), messageHeader.messageId, serializeEncryptionContext(messageHeader.encryptionContext), serializeEncryptedDataKeys(messageHeader.encryptedDataKeys), new Uint8Array([messageHeader.contentType]), new Uint8Array([0, 0, 0, 0]), (0, uint_util_1.uInt8)(messageHeader.headerIvLength), (0, uint_util_1.uInt32BE)(messageHeader.frameLength));
    }
    function serializeMessageHeaderV2(messageHeader) {
        return (0, concat_buffers_1.concatBuffers)((0, uint_util_1.uInt8)(messageHeader.version), (0, uint_util_1.uInt16BE)(messageHeader.suiteId), messageHeader.messageId, serializeEncryptionContext(messageHeader.encryptionContext), serializeEncryptedDataKeys(messageHeader.encryptedDataKeys), new Uint8Array([messageHeader.contentType]), (0, uint_util_1.uInt32BE)(messageHeader.frameLength), messageHeader.suiteData);
    }
    /* This _could_ take the material directly.
     * But I don't do that on purpose.
     * It may be overly paranoid,
     * but this way once the material is created,
     * it has a minimum of egress.
     */
    function buildMessageHeader({ encryptionContext, encryptedDataKeys, suite, messageId, frameLength, suiteData, }) {
        const { messageFormat: version, id: suiteId } = suite;
        const contentType = identifiers_1.ContentType.FRAMED_DATA;
        if (version === material_management_1.MessageFormat.V1) {
            const type = identifiers_1.ObjectType.CUSTOMER_AE_DATA;
            const { ivLength: headerIvLength } = suite;
            return {
                version,
                type,
                suiteId,
                messageId,
                encryptionContext,
                encryptedDataKeys,
                contentType,
                headerIvLength,
                frameLength,
            };
        }
        else if (version === material_management_1.MessageFormat.V2) {
            return {
                version,
                suiteId,
                messageId,
                encryptionContext: encryptionContext,
                encryptedDataKeys: encryptedDataKeys,
                contentType,
                frameLength,
                suiteData,
            };
        }
        (0, material_management_1.needs)(false, 'Unsupported message format version.');
    }
}
exports.serializeFactory = serializeFactory;
function serializeMessageHeaderAuth({ headerIv, headerAuthTag, messageHeader, }) {
    if (messageHeader.version === material_management_1.MessageFormat.V1) {
        return (0, concat_buffers_1.concatBuffers)(headerIv, headerAuthTag);
    }
    return headerAuthTag;
}
exports.serializeMessageHeaderAuth = serializeMessageHeaderAuth;
//# sourceMappingURL=data:application/json;base64, 