"use strict";
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLocalCryptographicMaterialsCache = void 0;
const lru_cache_1 = __importDefault(require("lru-cache"));
const material_management_1 = require("@aws-crypto/material-management");
function getLocalCryptographicMaterialsCache(capacity, proactiveFrequency = 1000 * 60) {
    const cache = new lru_cache_1.default({
        max: capacity,
        dispose(_key, value) {
            /* Zero out the unencrypted dataKey, when the material is removed from the cache. */
            value.response.zeroUnencryptedDataKey();
        },
    });
    (function proactivelyTryAndEvictTail() {
        const timeout = setTimeout(() => {
            mayEvictTail();
            proactivelyTryAndEvictTail();
        }, proactiveFrequency);
        /* In Node.js the event loop will _only_ exit if there are no outstanding events.
         * This means that if I did nothing the event loop would *always* be blocked.
         * This is unfortunate and very bad for things like Lambda.
         * So, I tell Node.js to not wait for this timer.
         * See: https://nodejs.org/api/timers.html#timers_timeout_unref
         */
        // @ts-ignore
        timeout.unref && timeout.unref();
    })();
    return {
        putEncryptionMaterial(key, material, plaintextLength, maxAge) {
            /* Precondition: putEncryptionMaterial plaintextLength can not be negative. */
            (0, material_management_1.needs)(plaintextLength >= 0, 'Malformed plaintextLength');
            /* Precondition: Only cache EncryptionMaterial. */
            (0, material_management_1.needs)((0, material_management_1.isEncryptionMaterial)(material), 'Malformed response.');
            /* Precondition: Only cache EncryptionMaterial that is cacheSafe. */
            (0, material_management_1.needs)(material.suite.cacheSafe, 'Can not cache non-cache safe material');
            const entry = Object.seal({
                response: material,
                bytesEncrypted: plaintextLength,
                messagesEncrypted: 1,
                now: Date.now(),
            });
            cache.set(key, entry, maxAge);
        },
        putDecryptionMaterial(key, material, maxAge) {
            /* Precondition: Only cache DecryptionMaterial. */
            (0, material_management_1.needs)((0, material_management_1.isDecryptionMaterial)(material), 'Malformed response.');
            /* Precondition: Only cache DecryptionMaterial that is cacheSafe. */
            (0, material_management_1.needs)(material.suite.cacheSafe, 'Can not cache non-cache safe material');
            const entry = Object.seal({
                response: material,
                bytesEncrypted: 0,
                messagesEncrypted: 0,
                now: Date.now(),
            });
            cache.set(key, entry, maxAge);
        },
        putBranchKeyMaterial(key, material, maxAge) {
            /* Precondition: Only cache BranchKeyMaterial */
            (0, material_management_1.needs)((0, material_management_1.isBranchKeyMaterial)(material), 'Malformed response.');
            const entry = Object.seal({
                response: material,
                now: Date.now(),
            });
            cache.set(key, entry, maxAge);
        },
        getEncryptionMaterial(key, plaintextLength) {
            /* Precondition: plaintextLength can not be negative. */
            (0, material_management_1.needs)(plaintextLength >= 0, 'Malformed plaintextLength');
            const entry = cache.get(key);
            /* Check for early return (Postcondition): If this key does not have an EncryptionMaterial, return false. */
            if (!entry)
                return false;
            /* Postcondition: Only return EncryptionMaterial. */
            (0, material_management_1.needs)((0, material_management_1.isEncryptionMaterial)(entry.response), 'Malformed response.');
            const encryptionMaterialEntry = entry;
            encryptionMaterialEntry.bytesEncrypted += plaintextLength;
            encryptionMaterialEntry.messagesEncrypted += 1;
            return entry;
        },
        getDecryptionMaterial(key) {
            const entry = cache.get(key);
            /* Check for early return (Postcondition): If this key does not have a DecryptionMaterial, return false. */
            if (!entry)
                return false;
            /* Postcondition: Only return DecryptionMaterial. */
            (0, material_management_1.needs)((0, material_management_1.isDecryptionMaterial)(entry.response), 'Malformed response.');
            return entry;
        },
        getBranchKeyMaterial(key) {
            const entry = cache.get(key);
            /* Postcondition: If this key does not have a BranchKeyMaterial, return false */
            if (!entry)
                return false;
            /* Postcondition: Only return BranchKeyMaterial */
            (0, material_management_1.needs)((0, material_management_1.isBranchKeyMaterial)(entry.response), 'Malformed response.');
            return entry;
        },
        del(key) {
            cache.del(key);
        },
    };
    function mayEvictTail() {
        // @ts-ignore
        const { tail } = cache.dumpLru();
        /* Check for early return (Postcondition) UNTESTED: If there is no tail, then the cache is empty. */
        if (!tail)
            return;
        /* The underlying Yallist tail Node has a `value`.
         * This value is a lru-cache Entry and has a `key`.
         */
        const { key } = tail.value;
        // Peek will evict, but not update the "recently used"-ness of the key.
        cache.peek(key);
    }
}
exports.getLocalCryptographicMaterialsCache = getLocalCryptographicMaterialsCache;
//# sourceMappingURL=data:application/json;base64, 