/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { CRYPTO_AEAD_MIC_LENGTH_BYTES, CRYPTO_AEAD_NONCE_LENGTH_BYTES } from "#crypto/CryptoConstants.js";
import { CryptoInputError } from "#crypto/CryptoError.js";
import { Aes } from "./Aes.js";
import { WordArray } from "./WordArray.js";
function Ccm(key) {
  const aes = Aes(key);
  return {
    encrypt(input) {
      validateNonceAndAdata(input);
      const ptLength = input.pt.length;
      if (ptLength > MAX_PLAINTEXT_LENGTH) {
        throw new CryptoInputError(
          `Cannot encrypt plaintext exceeding maximum length of ${MAX_PLAINTEXT_LENGTH}`
        );
      }
      const ptView = new DataView(input.pt.buffer);
      const ct = new Uint8Array(ptLength + CRYPTO_AEAD_MIC_LENGTH_BYTES);
      const ctView = new DataView(ct.buffer);
      cbcMac(input, ptView, ptLength);
      ctr(input, ptView, ctView, ptLength, computedMic);
      for (let i = 0; i < computedMic.words.length; i++) {
        ctView.setInt32(input.pt.length + i * 4, computedMic.words[i]);
      }
      return ct;
    },
    decrypt(input) {
      validateNonceAndAdata(input);
      if (input.ct.length > MAX_CIPHERTEXT_LENGTH) {
        throw new CryptoInputError(
          `Cannot decrypt ciphertext longer than maximum length of ${MAX_CIPHERTEXT_LENGTH}`
        );
      }
      const ptLength = input.ct.length - CRYPTO_AEAD_MIC_LENGTH_BYTES;
      if (ptLength < 0) {
        throw new CryptoInputError(
          `Cannot decrypt ciphertext shorter than minimum length of ${CRYPTO_AEAD_MIC_LENGTH_BYTES}`
        );
      }
      const ctView = new DataView(input.ct.buffer, input.ct.byteOffset, ptLength);
      WordArray.bytesToBlock(
        new DataView(input.ct.buffer, input.ct.byteOffset, input.ct.byteLength),
        inputMic.words,
        ptLength
      );
      const pt = new Uint8Array(ptLength);
      const ptView = new DataView(pt.buffer);
      ctr(input, ctView, ptView, ptLength, inputMic);
      cbcMac(input, ptView, ptLength);
      for (let i = 0; i < computedMic.words.length; i++) {
        if (inputMic.words[i] !== computedMic.words[i]) {
          throw new CryptoInputError("Message authentication failed due to invalid signature");
        }
      }
      return pt;
    }
  };
  function cbcMac(input, pt, ptLength) {
    const adataLength = input.adata?.length;
    computedMic.bytes[0] = (adataLength ? 1 << 6 : 0) | CRYPTO_AEAD_MIC_LENGTH_BYTES - 2 << 2 | BYTES_IN_LENGTH - 1;
    computedMic.bytes.set(input.nonce, 1);
    WordArray.bytesToBlock(computedMic.view, computedMic.words);
    computedMic.words[3] = computedMic.words[3] & 4294901760 | ptLength;
    aes.encrypt(computedMic.words);
    if (adataLength) {
      tempBlock1.view.setInt16(0, input.adata.length);
      for (let i = 0; i < 14; i++) {
        tempBlock1.bytes[i + 2] = i < adataLength ? input.adata[i] : 0;
      }
      WordArray.bytesToBlock(tempBlock1.view, tempBlock1.words);
      add();
      if (adataLength > 14) {
        const adataView = new DataView(input.adata.buffer);
        for (let i = 14; i < adataLength; i += 16) {
          WordArray.bytesToBlock(adataView, tempBlock1.words, i);
          add();
        }
      }
    }
    if (ptLength) {
      for (let i = 0; i < ptLength; i += 16) {
        WordArray.bytesToBlock(pt, tempBlock1.words, i);
        add();
      }
    }
    function add() {
      computedMic.words[0] ^= tempBlock1.words[0];
      computedMic.words[1] ^= tempBlock1.words[1];
      computedMic.words[2] ^= tempBlock1.words[2];
      computedMic.words[3] ^= tempBlock1.words[3];
      aes.encrypt(computedMic.words);
    }
  }
  function ctr(input, from, to, ptLength, mic) {
    tempBlock1.bytes[0] = BYTES_IN_LENGTH - 1;
    tempBlock1.bytes.set(input.nonce, 1);
    tempBlock1.bytes[14] = 0;
    tempBlock1.bytes[15] = 0;
    WordArray.bytesToBlock(tempBlock1.view, ctrBlock.words);
    aes.encrypt(ctrBlock.words, tempBlock1.words);
    mic.words[0] ^= tempBlock1.words[0];
    mic.words[1] ^= tempBlock1.words[1];
    mic.words[2] ^= tempBlock1.words[2];
    mic.words[3] ^= tempBlock1.words[3];
    for (let i = 0; i < ptLength; ) {
      ctrBlock.words[3]++;
      WordArray.bytesToBlock(from, tempBlock1.words, i);
      aes.encrypt(ctrBlock.words, tempBlock2.words);
      for (let j = 0; j < 4 && i < ptLength; j++, i += 4) {
        const tempWord = tempBlock2.words[j];
        if (i + 4 < ptLength) {
          to.setInt32(i, from.getInt32(i) ^ tempWord);
        } else {
          const partial = WordArray.readPartialWord(from, i, ptLength - i) ^ tempWord;
          WordArray.writePartialWord(partial, to, i, ptLength - i);
        }
      }
    }
  }
}
const BYTES_IN_LENGTH = 2;
const MAX_CIPHERTEXT_LENGTH = Math.pow(2, BYTES_IN_LENGTH * 8);
const MAX_PLAINTEXT_LENGTH = MAX_CIPHERTEXT_LENGTH - CRYPTO_AEAD_MIC_LENGTH_BYTES;
class SingletonBuffer {
  #words;
  #bytes;
  #view;
  get words() {
    if (this.#words === void 0) {
      this.#words = new Int32Array(4);
    }
    return this.#words;
  }
  get bytes() {
    if (this.#bytes === void 0) {
      this.#bytes = new Uint8Array(this.words.buffer);
    }
    return this.#bytes;
  }
  /**
   * The word and byte views of the buffer above are insufficient because we must account for platform endianness.  So
   * we also make a DataView available.
   */
  get view() {
    if (this.#view === void 0) {
      this.#view = new DataView(this.words.buffer);
    }
    return this.#view;
  }
}
const computedMic = new SingletonBuffer();
const inputMic = new SingletonBuffer();
const ctrBlock = new SingletonBuffer();
const tempBlock1 = new SingletonBuffer();
const tempBlock2 = new SingletonBuffer();
function validateNonceAndAdata(input) {
  if (input.nonce.length !== CRYPTO_AEAD_NONCE_LENGTH_BYTES) {
    throw new CryptoInputError("Nonce must be 13 bytes");
  }
  if (input.adata && input.adata.length > 65535) {
    throw new CryptoInputError(`Associated adata exceeds maximum length of ${MAX_PLAINTEXT_LENGTH}`);
  }
}
export {
  BYTES_IN_LENGTH,
  Ccm,
  MAX_CIPHERTEXT_LENGTH,
  MAX_PLAINTEXT_LENGTH
};
//# sourceMappingURL=Ccm.js.map
