/*---
includes: [compatFs.js, compatBuffer.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js]
flags: [async]
---*/

function has_usage(usage, x) {
    for (let i = 0; i < usage.length; i++) {
        if (x === usage[i]) {
            return true;
        }
    }

    return false;
}

async function test(params) {
    let r;
    let encoder = new TextEncoder();
    let keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(params.pass),
                                                    params.algorithm.name,
                                                    false, [ "deriveBits", "deriveKey" ]);
    if (params.derive === "key") {
        let key = await crypto.subtle.deriveKey(params.algorithm, keyMaterial,
                                                params.derivedAlgorithm,
                                                params.extractable, params.usage);

        if (key.extractable !== params.extractable) {
            throw Error(`${params.algorithm.name} failed extractable ${params.extractable} vs ${key.extractable}`);
        }

        if (has_usage(params.usage, "encrypt")) {
            r = await crypto.subtle.encrypt(params.derivedAlgorithm, key,
                                            encoder.encode(params.text));

        } else if (has_usage(params.usage, "sign")) {
            r = await crypto.subtle.sign(params.derivedAlgorithm, key,
                                         encoder.encode(params.text));
        }

    } else {

        r = await crypto.subtle.deriveBits(params.algorithm, keyMaterial, params.length);
    }

    r = Buffer.from(r).toString("hex");

    if (params.expected != r) {
        throw Error(`${params.algorithm.name} failed expected: "${params.expected}" vs "${r}"`);
    }

    return "SUCCESS";
}

function p(args, default_opts) {
    let params = Object.assign({}, default_opts);
    params = merge(params, args);

    params.algorithm.salt = Buffer.from(params.algorithm.salt, "hex");
    params.algorithm.info = Buffer.from(params.algorithm.info, "hex");
    params.derivedAlgorithm.iv = Buffer.from(params.derivedAlgorithm.iv, "hex");

    return params;
}

let derive_tsuite = {
    name: "derive",
    skip: () => (!has_buffer() || !has_webcrypto()),
    T: test,
    prepare_args: p,
    opts: {
        text: "secReT",
        pass: "passW0rd",
        derive: "key",
        optional: false,
        length: 256,
        algorithm: {
            name: "PBKDF2",
            salt: "00112233001122330011223300112233",
            hash: "SHA-256",
            info: "deadbeef",
            iterations: 100000
        },
        derivedAlgorithm: {
          name: "AES-GCM",
          length: 256,
          iv: "55667788556677885566778855667788"
        },
        extractable: true,
        usage: [ "encrypt", "decrypt" ]
    },

    tests: [
        { expected: "e7b55c9f9fda69b87648585f76c58109174aaa400cfa" },
        { extractable: false, expected: "e7b55c9f9fda69b87648585f76c58109174aaa400cfa" },
        { pass: "pass2", expected: "e87d1787f2807ea0e1f7e1cb265b23004c575cf2ad7e" },
        { algorithm: { iterations: 10000 }, expected: "5add0059931ed1db1ca24c26dbe4de5719c43ed18a54" },
        { algorithm: { hash: "SHA-512" }, expected: "544d64e5e246fdd2ba290ea932b2d80ef411c76139f4" },
        { algorithm: { salt: "aabbccddaabbccddaabbccddaabbccdd" }, expected: "5c1304bedf840b1f6f7d1aa804fe870a8f949d762c32" },
        { algorithm: { salt: "aabbccddaabbccddaabbccddaabb" },
          exception: "TypeError: PBKDF2 algorithm.salt must be at least 16 bytes long" },
        { derivedAlgorithm: { length: 128 }, expected: "9e2d7bcc1f21f30ec3c32af9129b64507d086d129f2a" },
        { derivedAlgorithm: { length: 32 },
          exception: "TypeError: deriveKey \"AES-GCM\" length must be 128 or 256" },
        { derivedAlgorithm: { name: "AES-CBC" }, expected: "3ad6523692d44b6a7a90be7c2721786f" },

        { derive: "bits", expected: "6458ed6e16b998d4e646422171087be8a1ee34bed463dfcb3dcd30842b1228fe" },
        { derive: "bits", pass: "pass2", expected: "ef8f75073fcadfd504d26610c743873e297ad90340c23ddc0e5f6bdb83cbabb2" },
        { derive: "bits", algorithm: { salt: "aabbccddaabbccddaabbccddaabbccdd" },
          expected: "22ceb295aa25b59c6bc5b383a089bd6999006c03f273ce3614a4fa0d90bd29ae" },
        { derive: "bits", algorithm: { hash: "SHA-1" },
          expected: "a2fc83498f7d07b4c8180c7ebfec2af0f3a7d6cb08bf8593d41d3c5c1e1c4d67" },
        { derive: "bits", algorithm: { hash: "SHA-1" }, length: 128,
          expected: "a2fc83498f7d07b4c8180c7ebfec2af0" },
        { derive: "bits", algorithm: { hash: "SHA-1" }, length: 64,
          expected: "a2fc83498f7d07b4" },

        { algorithm: { name: "HKDF" }, optional: true,
          expected: "18ea069ee3317d2db02e02f4a228f50dc80d9a2396e6" },
        { algorithm: { name: "HKDF" },
          derivedAlgorithm: { name: "HMAC", hash: "SHA-256", length: 256 },
          usage: [ "sign", "verify" ], optional: true,
          expected: "0b06bd37de54c08cedde2cbb649d6f26d066acfd51717d83b52091e2ae6829c2" },
        { derive: "bits", algorithm: { name: "HKDF" }, optional: true,
          expected: "e089c7491711306c69e077aa19fae6bfd2d4a6d240b0d37317d50472d7291a3e" },
]};

run([derive_tsuite])
.then($DONE, $DONE);
