// META: title=WebCrypto API: supports method tests
// META: script=util/helpers.js

'use strict';

const standardAlgorithms = {
  // Asymmetric algorithms
  'RSASSA-PKCS1-v1_5': {
    operations: ['generateKey', 'importKey', 'sign', 'verify'],
    keyGenParams: {
      name: 'RSASSA-PKCS1-v1_5',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    },
    importParams: { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
    signParams: { name: 'RSASSA-PKCS1-v1_5' },
  },
  'RSA-PSS': {
    operations: ['generateKey', 'importKey', 'sign', 'verify'],
    keyGenParams: {
      name: 'RSA-PSS',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    },
    importParams: { name: 'RSA-PSS', hash: 'SHA-256' },
    signParams: { name: 'RSA-PSS', saltLength: 32 },
  },
  'RSA-OAEP': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt'],
    keyGenParams: {
      name: 'RSA-OAEP',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    },
    importParams: { name: 'RSA-OAEP', hash: 'SHA-256' },
    encryptParams: { name: 'RSA-OAEP' },
  },
  ECDSA: {
    operations: ['generateKey', 'importKey', 'sign', 'verify'],
    keyGenParams: { name: 'ECDSA', namedCurve: 'P-256' },
    importParams: { name: 'ECDSA', namedCurve: 'P-256' },
    signParams: { name: 'ECDSA', hash: 'SHA-256' },
  },
  ECDH: {
    operations: ['generateKey', 'importKey', 'deriveBits'],
    keyGenParams: { name: 'ECDH', namedCurve: 'P-256' },
    importParams: { name: 'ECDH', namedCurve: 'P-256' },
    deriveBitsParams: {
      name: 'ECDH',
      public: crypto.subtle.generateKey(
        { name: 'ECDH', namedCurve: 'P-256' },
        false,
        ['deriveBits']
      ),
    },
  },
  Ed25519: {
    operations: ['generateKey', 'importKey', 'sign', 'verify'],
    keyGenParams: null,
    signParams: { name: 'Ed25519' },
  },
  X25519: {
    operations: ['generateKey', 'importKey', 'deriveBits'],
    keyGenParams: null,
    deriveBitsParams: {
      name: 'X25519',
      public: crypto.subtle.generateKey('X25519', false, ['deriveBits']),
    },
  },

  // Symmetric algorithms
  'AES-CBC': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt'],
    keyGenParams: { name: 'AES-CBC', length: 256 },
    encryptParams: { name: 'AES-CBC', iv: new Uint8Array(16) },
  },
  'AES-CTR': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt'],
    keyGenParams: { name: 'AES-CTR', length: 256 },
    encryptParams: {
      name: 'AES-CTR',
      counter: new Uint8Array(16),
      length: 128,
    },
  },
  'AES-GCM': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt'],
    keyGenParams: { name: 'AES-GCM', length: 256 },
    encryptParams: { name: 'AES-GCM', iv: new Uint8Array(12) },
  },
  'AES-KW': {
    operations: ['generateKey', 'importKey'], // wrapKey/unwrapKey not in requested operations
    keyGenParams: { name: 'AES-KW', length: 256 },
  },
  HMAC: {
    operations: ['generateKey', 'importKey', 'sign', 'verify'],
    keyGenParams: { name: 'HMAC', hash: 'SHA-256' },
    importParams: { name: 'HMAC', hash: 'SHA-256' },
    signParams: { name: 'HMAC' },
  },

  // Hash algorithms
  'SHA-1': {
    operations: ['digest'],
    keyGenParams: null,
  },
  'SHA-256': {
    operations: ['digest'],
    keyGenParams: null,
  },
  'SHA-384': {
    operations: ['digest'],
    keyGenParams: null,
  },
  'SHA-512': {
    operations: ['digest'],
    keyGenParams: null,
  },

  // Key derivation algorithms
  HKDF: {
    operations: ['importKey', 'deriveBits'],
    keyGenParams: null,
    deriveBitsParams: {
      name: 'HKDF',
      hash: 'SHA-256',
      salt: new Uint8Array(16),
      info: new Uint8Array(0),
    },
  },
  PBKDF2: {
    operations: ['importKey', 'deriveBits'],
    keyGenParams: null,
    deriveBitsParams: {
      name: 'PBKDF2',
      hash: 'SHA-256',
      salt: new Uint8Array(16),
      iterations: 100000,
    },
  },
};

const operations = [
  'generateKey',
  'importKey',
  'sign',
  'verify',
  'encrypt',
  'decrypt',
  'deriveBits',
  'digest',
];

// Test that supports method exists and is a static method
test(() => {
  assert_true(
    typeof SubtleCrypto.supports === 'function',
    'SubtleCrypto.supports should be a function'
  );
}, 'SubtleCrypto.supports method exists');

// Test invalid operation names
test(() => {
  assert_false(
    SubtleCrypto.supports('invalidOperation', 'AES-GCM'),
    'Invalid operation should return false'
  );
  assert_false(
    SubtleCrypto.supports('', 'AES-GCM'),
    'Empty operation should return false'
  );
  assert_false(
    SubtleCrypto.supports('GENERATEKEY', 'AES-GCM'),
    'Case-sensitive operation check'
  );
}, 'supports returns false for invalid operations');

// Test invalid algorithm identifiers
test(() => {
  assert_false(
    SubtleCrypto.supports('generateKey', 'InvalidAlgorithm'),
    'Invalid algorithm should return false'
  );
  assert_false(
    SubtleCrypto.supports('generateKey', ''),
    'Empty algorithm should return false'
  );
}, 'supports returns false for invalid algorithms');

// Test standard WebCrypto algorithms for requested operations
for (const [algorithmName, algorithmInfo] of Object.entries(
  standardAlgorithms
)) {
  for (const operation of operations) {
    promise_test(async (t) => {
      const isSupported = algorithmInfo.operations.includes(operation);

      // Use appropriate algorithm parameters for each operation
      let algorithm;
      let length;
      switch (operation) {
        case 'generateKey':
          algorithm = algorithmInfo.keyGenParams || algorithmName;
          break;
        case 'importKey':
          algorithm = algorithmInfo.importParams || algorithmName;
          break;
        case 'sign':
        case 'verify':
          algorithm = algorithmInfo.signParams || algorithmName;
          break;
        case 'encrypt':
        case 'decrypt':
          algorithm = algorithmInfo.encryptParams || algorithmName;
          break;
        case 'deriveBits':
          algorithm = algorithmInfo.deriveBitsParams || algorithmName;
          if (algorithm?.public instanceof Promise) {
            algorithm.public = (await algorithm.public).publicKey;
          }
          if (algorithmName === 'PBKDF2' || algorithmName === 'HKDF') {
            length = 256;
          }
          break;
        case 'digest':
          algorithm = algorithmName;
          break;
        default:
          algorithm = algorithmName;
      }

      const result = SubtleCrypto.supports(operation, algorithm, length);

      if (isSupported) {
        assert_true(result, `${algorithmName} should support ${operation}`);
      } else {
        assert_false(result, `${algorithmName} should not support ${operation}`);
      }
    }, `supports(${operation}, ${algorithmName})`);
  }
}

// Test algorithm objects (not just strings)
test(() => {
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'AES-GCM', length: 256 }),
    'Algorithm object should be supported'
  );
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'HMAC', hash: 'SHA-256' }),
    'Algorithm object with parameters should be supported'
  );
}, 'supports works with algorithm objects');

// Test with algorithm objects that have invalid parameters
test(() => {
  assert_false(
    SubtleCrypto.supports('generateKey', { name: 'AES-GCM', length: 100 }),
    'Invalid key length should return false'
  );
  assert_false(
    SubtleCrypto.supports('generateKey', {
      name: 'HMAC',
      hash: 'INVALID-HASH',
    }),
    'Invalid hash parameter should return false'
  );
  assert_false(
      SubtleCrypto.supports(
          'encrypt', {name: 'AES-CBC', iv: new Uint8Array(10)}),
      'Invalid IV for AES-CBC should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-CTR',
        counter: new Uint8Array(10),
        length: 128,
      }),
      'Invalid IV for AES-CTR should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-CTR',
        counter: new Uint8Array(16),
        length: 0,
      }),
      'Invalid length=0 for AES-CTR should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-CTR',
        counter: new Uint8Array(16),
        length: 129,
      }),
      'Invalid length=129 for AES-CTR should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-GCM',
        iv: new Uint8Array(16),
        tagLength: 100,
      }),
      'Invalid tag length for AES-GCM should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {name: 'ECDH', namedCurve: 'P-51'}),
      'Invalid curve for ECDH should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'HKDF',
            hash: 'SHA-25',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          },
          8),
      'Invalid hash for HKDF should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'HKDF',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          },
          11),
      'Invalid length for HKDF should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'HKDF',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          }),
      'null length for HKDF should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'HMAC',
        hash: 'SHA-25',
      }),
      'Invalid hash for HMAC should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'HMAC',
        hash: 'SHA-256',
        length: 0,
      }),
      'Invalid length for HMAC should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'PBKDF2',
            hash: 'SHA-25',
            salt: new Uint8Array(16),
            iterations: 100000,
          },
          8),
      'Invalid hash for PBKDF2 should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'PBKDF2',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            iterations: 100000,
          },
          11),
      'Invalid length for PBKDF2 should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'PBKDF2',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            iterations: 100000,
          }),
      'null length for PBKDF2 should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'RSASSA-PKCS1-v1_5',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-56',
      }),
      'Invalid hash for RSA PKCS1 should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'RSA-PSS',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-56',
      }),
      'Invalid hash for RSA PSS should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'RSA-OAEP',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-26',
      }),
      'Invalid hash for RSA OAEP should return false');

}, 'supports returns false for algorithm objects with invalid parameters');

// Test some specific combinations that should work
test(() => {
  // RSA algorithms
  assert_true(
    SubtleCrypto.supports('generateKey', {
      name: 'RSASSA-PKCS1-v1_5',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    }),
    'RSASSA-PKCS1-v1_5 generateKey'
  );
  assert_true(
    SubtleCrypto.supports('sign', { name: 'RSASSA-PKCS1-v1_5' }),
    'RSASSA-PKCS1-v1_5 sign'
  );
  assert_true(
    SubtleCrypto.supports('verify', { name: 'RSASSA-PKCS1-v1_5' }),
    'RSASSA-PKCS1-v1_5 verify'
  );

  // ECDSA
  assert_true(
    SubtleCrypto.supports('generateKey', {
      name: 'ECDSA',
      namedCurve: 'P-256',
    }),
    'ECDSA generateKey'
  );
  assert_true(
    SubtleCrypto.supports('sign', { name: 'ECDSA', hash: 'SHA-256' }),
    'ECDSA sign'
  );
  assert_true(
    SubtleCrypto.supports('verify', { name: 'ECDSA', hash: 'SHA-256' }),
    'ECDSA verify'
  );

  // AES-GCM
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'AES-GCM', length: 256 }),
    'AES-GCM generateKey'
  );
  assert_true(
    SubtleCrypto.supports('encrypt', {
      name: 'AES-GCM',
      iv: new Uint8Array(12),
    }),
    'AES-GCM encrypt'
  );
  assert_true(
    SubtleCrypto.supports('decrypt', {
      name: 'AES-GCM',
      iv: new Uint8Array(12),
    }),
    'AES-GCM decrypt'
  );

  // HMAC
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'HMAC', hash: 'SHA-256' }),
    'HMAC generateKey'
  );
  assert_true(SubtleCrypto.supports('sign', { name: 'HMAC' }), 'HMAC sign');
  assert_true(SubtleCrypto.supports('verify', { name: 'HMAC' }), 'HMAC verify');
}, 'Common algorithm and operation combinations work');

// Test some specific combinations that should not work
test(() => {
  // Hash algorithms don't support key operations
  assert_false(
    SubtleCrypto.supports('generateKey', 'SHA-256'),
    'SHA-256 generateKey should fail'
  );
  assert_false(
    SubtleCrypto.supports('sign', 'SHA-256'),
    'SHA-256 sign should fail'
  );

  // AES can't sign/verify (these require algorithm parameters due to normalization)
  assert_false(
    SubtleCrypto.supports('sign', 'AES-GCM'),
    'AES-GCM sign should fail'
  );
  assert_false(
    SubtleCrypto.supports('verify', 'AES-GCM'),
    'AES-GCM verify should fail'
  );

  // ECDSA can't encrypt/decrypt
  assert_false(
    SubtleCrypto.supports('encrypt', 'ECDSA'),
    'ECDSA encrypt should fail'
  );
  assert_false(
    SubtleCrypto.supports('decrypt', 'ECDSA'),
    'ECDSA decrypt should fail'
  );

  // HMAC can't encrypt/decrypt
  assert_false(
    SubtleCrypto.supports('encrypt', 'HMAC'),
    'HMAC encrypt should fail'
  );
  assert_false(
    SubtleCrypto.supports('decrypt', 'HMAC'),
    'HMAC decrypt should fail'
  );

  // Non-hash algorithms can't digest
  assert_false(
    SubtleCrypto.supports('digest', 'AES-GCM'),
    'AES-GCM digest should fail'
  );
  assert_false(
    SubtleCrypto.supports('digest', 'ECDSA'),
    'ECDSA digest should fail'
  );
  assert_false(
    SubtleCrypto.supports('digest', 'HMAC'),
    'HMAC digest should fail'
  );
}, 'Invalid algorithm and operation combinations fail');

// Test supports for deriveKey op
test(() => {
  assert_true(
      SubtleCrypto.supports(
          'deriveKey', {
            name: 'HKDF',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          },
          {name: 'HMAC', hash: 'SHA-256'}),

      'deriveKey HKDF-HMAC should pass');
}, 'deriveKey tests');

promise_test(async (t) => {
  let keypair = await crypto.subtle.generateKey(
      {
        name: 'X25519',
      },
      false, ['deriveKey', 'deriveBits']);

  assert_true(
      SubtleCrypto.supports(
          'deriveKey', {
            name: 'X25519',
            public: keypair.publicKey,
          },
          {name: 'AES-GCM', length: 256}),

      'deriveKey X25519-AES-GCM-256 should pass');

  assert_false(
      SubtleCrypto.supports(
          'deriveKey', {
            name: 'X25519',
            public: keypair.publicKey,
          },
          {name: 'HMAC', hash: 'SHA-256'}),

      'deriveKey X25519-HMAC-SHA-256 should fail');
}, 'deriveKey promise tests');



done();
