// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { Buffer } from 'node:buffer';
import { type Hmac } from 'node:crypto';
import { Transform } from 'node:stream';

import { MAC_LENGTH } from '../types/Crypto.std.ts';

/**
 * Updates an hmac with the stream except for the last MAC_LENGTH
 * bytes. The last MAC_LENGTH bytes are passed to the callback.
 */
export function getMacAndUpdateHmac(
  hmac: Hmac,
  onTheirMac: (theirMac: Uint8Array<ArrayBuffer>) => void
): Transform {
  // Because we don't have a view of the entire stream, we don't know when we're
  // at the end. We need to omit the last MAC_LENGTH bytes from
  // `hmac.update` so we only push what we know is not the mac.
  let maybeMacBytes = Buffer.alloc(0);

  function updateWithKnownNonMacBytes() {
    let knownNonMacBytes = null;
    if (maybeMacBytes.byteLength > MAC_LENGTH) {
      knownNonMacBytes = maybeMacBytes.subarray(0, -MAC_LENGTH);
      maybeMacBytes = maybeMacBytes.subarray(-MAC_LENGTH);
      hmac.update(knownNonMacBytes);
    }
    return knownNonMacBytes;
  }

  return new Transform({
    transform(chunk, _encoding, callback) {
      try {
        maybeMacBytes = Buffer.concat([maybeMacBytes, chunk]);
        const knownNonMac = updateWithKnownNonMacBytes();
        callback(null, knownNonMac);
      } catch (error) {
        callback(error);
      }
    },
    flush(callback) {
      try {
        onTheirMac(maybeMacBytes);
        callback(null, null);
      } catch (error) {
        callback(error);
      }
    },
  });
}
