/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {fromRawKey as aesCtrFromRawKey} from './aes_ctr';
import * as Bytes from './bytes';
import * as Random from './random';

describe('aes ctr test', function() {
  beforeEach(function() {
    // Use a generous promise timeout for running continuously.
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 1000;  // 1000s
  });

  afterEach(function() {
    // Reset the timeout.
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;  // 1s
  });

  it('basic', async function() {
    // Set longer time for promiseTimout as the test sometimes takes longer than
    // 1 second in Firefox.
    const key = Random.randBytes(16);
    for (let i = 0; i < 100; i++) {
      const msg = Random.randBytes(20);
      const cipher = await aesCtrFromRawKey(key, 16);
      let ciphertext = await cipher.encrypt(msg);
      let plaintext = await cipher.decrypt(ciphertext);
      expect(Bytes.toHex(plaintext)).toBe(Bytes.toHex(msg));
    }
  });

  it('probabilistic encryption', async function() {
    const cipher = await aesCtrFromRawKey(Random.randBytes(16), 16);
    const msg = Random.randBytes(20);
    const results = new Set();
    for (let i = 0; i < 100; i++) {
      const ciphertext = await cipher.encrypt(msg);
      results.add(Bytes.toHex(ciphertext));
    }
    expect(results.size).toBe(100);
  });

  it('constructor', async function() {
    try {
      await aesCtrFromRawKey(Random.randBytes(16), 11);  // IV size too short
      fail('Should throw an exception.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe(
              'SecurityException: invalid IV length, must be at least 12 and at most 16');
    }
    try {
      await aesCtrFromRawKey(Random.randBytes(16), 17);  // IV size too long
      fail('Should throw an exception.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe(
              'SecurityException: invalid IV length, must be at least 12 and at most 16');
    }
    try {
      await aesCtrFromRawKey(
          Random.randBytes(24), 12);  // 192-bit keys not supported
      fail('Should throw an exception.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe('InvalidArgumentsException: unsupported AES key size: 24');
    }
  });

  it('constructor, invalid iv sizes', async function() {
    try {
      await aesCtrFromRawKey(Random.randBytes(16), NaN);
      fail('Should throw an exception.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe('SecurityException: invalid IV length, must be an integer');
    }

    try {
      await aesCtrFromRawKey(Random.randBytes(16), 12.5);
      fail('Should throw an exception.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe('SecurityException: invalid IV length, must be an integer');
    }

    try {
      await aesCtrFromRawKey(Random.randBytes(16), 0);
      fail('Should throw an exception.');
      // Preserving old behavior when moving to
      // https://www.typescriptlang.org/tsconfig#useUnknownInCatchVariables
      // tslint:disable-next-line:no-any
    } catch (e: any) {
      expect(e.toString())
          .toBe(
              'SecurityException: invalid IV length, must be at least 12 and at most 16');
    }
  });

  it('with test vectors', async function() {
    // Test data from NIST SP 800-38A pp 55.
    const NIST_TEST_VECTORS = [
      {
        'key': '2b7e151628aed2a6abf7158809cf4f3c',
        'message':
            '6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51' +
            '30c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710',
        'ciphertext':
            '874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff' +
            '5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee',
        'iv': 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'
      },
    ];
    for (let i = 0; i < NIST_TEST_VECTORS.length; i++) {
      const testVector = NIST_TEST_VECTORS[i];
      const key = Bytes.fromHex(testVector['key']);
      const iv = Bytes.fromHex(testVector['iv']);
      const msg = Bytes.fromHex(testVector['message']);
      const ciphertext = Bytes.fromHex(testVector['ciphertext']);
      const aesctr = await aesCtrFromRawKey(key, iv.length);
      const plaintext = await aesctr.decrypt(Bytes.concat(iv, ciphertext));
      expect(Bytes.toHex(plaintext)).toBe(Bytes.toHex(msg));
    }
  });
});
