"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var Manager2_exports = {};
__export(Manager2_exports, {
  SecurityManager2: () => SecurityManager2
});
module.exports = __toCommonJS(Manager2_exports);
var import_shared = require("@zwave-js/shared");
var import_crypto = require("../crypto/index.js");
var import_shared2 = require("../crypto/shared.js");
var import_SecurityClass = require("../definitions/SecurityClass.js");
var import_consts = require("../definitions/consts.js");
var import_ZWaveError = require("../error/ZWaveError.js");
var import_compression = require("../util/compression.js");
var import_date = require("../util/date.js");
var import_Primitive = require("../values/Primitive.js");
var import_Manager2Types = require("./Manager2Types.js");
var import_ctr_drbg = require("./ctr_drbg.js");
const SINGLECAST_MAX_SEQ_NUMS = 1;
const SINGLECAST_NONCE_EXPIRY_NS = 500 * 1e3 * 1e3;
class SecurityManager2 {
  static {
    __name(this, "SecurityManager2");
  }
  constructor() {
  }
  static async create() {
    const ret = new SecurityManager2();
    await ret.rng.init((0, import_crypto.randomBytes)(32));
    return ret;
  }
  /** PRNG used to initialize the others */
  rng = new import_ctr_drbg.CtrDRBG();
  /** A map of SPAN states for each node */
  spanTable = /* @__PURE__ */ new Map();
  /** A map of temporary keys for each node that are used for the key exchange */
  tempKeys = /* @__PURE__ */ new Map();
  /** A map of sequence numbers that were last used in communication with a node */
  ownSequenceNumbers = /* @__PURE__ */ new Map();
  peerSequenceNumbers = /* @__PURE__ */ new Map();
  /** A map of the inner MPAN states for each multicast group we manage */
  mpanStates = /* @__PURE__ */ new Map();
  /** MPANs used to decrypt multicast messages from other nodes. Peer Node ID -> Multicast Group -> MPAN */
  peerMPANs = /* @__PURE__ */ new Map();
  /** A map of permanent network keys per security class */
  networkKeys = /* @__PURE__ */ new Map();
  /** A map of the defined multicast groups */
  multicastGroups = /* @__PURE__ */ new Map();
  /** Reverse lookup from node IDs (as stringified bitmask) to multicast group ID */
  multicastGroupLookup = /* @__PURE__ */ new Map();
  getNextMulticastGroupId = (0, import_shared.createWrappingCounter)(255);
  /** Sets the PNK for a given security class and derives the encryption keys from it */
  async setKey(securityClass, key) {
    if (key.length !== 16) {
      throw new import_ZWaveError.ZWaveError(`The network key must consist of 16 bytes!`, import_ZWaveError.ZWaveErrorCodes.Argument_Invalid);
    } else if (!(securityClass in import_SecurityClass.SecurityClass) || securityClass <= import_SecurityClass.SecurityClass.None) {
      throw new import_ZWaveError.ZWaveError(`Invalid security class!`, import_ZWaveError.ZWaveErrorCodes.Argument_Invalid);
    }
    this.networkKeys.set(securityClass, {
      pnk: key,
      ...await (0, import_crypto.deriveNetworkKeys)(key)
    });
  }
  /**
   * Creates (or re-uses) a multicast group for the given node IDs and remembers the security class.
   * The returned value is the group ID to be used in multicast commands
   */
  createMulticastGroup(nodeIDs, s2SecurityClass) {
    const newHash = hashNodeIds(nodeIDs);
    if (this.multicastGroupLookup.has(newHash)) {
      return this.multicastGroupLookup.get(newHash);
    }
    const groupId = this.getNextMulticastGroupId();
    if (this.multicastGroups.has(groupId)) {
      const oldGroup = this.multicastGroups.get(groupId);
      this.multicastGroups.delete(groupId);
      const oldHash = hashNodeIds(oldGroup.nodeIDs);
      this.multicastGroupLookup.delete(oldHash);
    }
    this.multicastGroups.set(groupId, {
      nodeIDs,
      securityClass: s2SecurityClass,
      sequenceNumber: (0, import_crypto.randomBytes)(1)[0]
    });
    this.multicastGroupLookup.set(newHash, groupId);
    this.mpanStates.delete(groupId);
    return groupId;
  }
  getMulticastGroup(group) {
    return this.multicastGroups.get(group);
  }
  hasKeysForSecurityClass(securityClass) {
    return this.networkKeys.has(securityClass);
  }
  getKeysForSecurityClass(securityClass) {
    const keys = this.networkKeys.get(securityClass);
    if (!keys) {
      throw new import_ZWaveError.ZWaveError(`The network key for the security class ${(0, import_shared.getEnumMemberName)(import_SecurityClass.SecurityClass, securityClass)} has not been set up yet!`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    }
    return { ...keys };
  }
  getKeysForNode(peerNodeID) {
    const spanState = this.getSPANState(peerNodeID);
    if (spanState.type === import_Manager2Types.SPANState.SPAN && spanState.securityClass === import_SecurityClass.SecurityClass.Temporary) {
      if (this.tempKeys.has(peerNodeID)) {
        return this.tempKeys.get(peerNodeID);
      }
      throw new import_ZWaveError.ZWaveError(`Temporary encryption key for node ${peerNodeID} is not known!`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    } else if (spanState.type !== import_Manager2Types.SPANState.SPAN) {
      throw new import_ZWaveError.ZWaveError(`Security class for node ${peerNodeID} is not yet known!`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    }
    return this.getKeysForSecurityClass(spanState.securityClass);
  }
  getSPANState(peerNodeID) {
    return this.spanTable.get(peerNodeID) ?? { type: import_Manager2Types.SPANState.None };
  }
  /** Tests whether the most recent secure command for a node has used the given security class */
  hasUsedSecurityClass(peerNodeID, securityClass) {
    const spanState = this.spanTable.get(peerNodeID);
    if (!spanState)
      return false;
    if (spanState.type !== import_Manager2Types.SPANState.SPAN)
      return false;
    return spanState.securityClass === securityClass;
  }
  /**
   * Prepares the generation of a new SPAN by creating a random sequence number and (local) entropy input
   * @param receiver The node this nonce is for. If none is given, the nonce is not stored.
   */
  async generateNonce(receiver) {
    const receiverEI = await this.rng.generate(16);
    if (receiver != void 0) {
      this.spanTable.set(receiver, {
        type: import_Manager2Types.SPANState.LocalEI,
        receiverEI
      });
    }
    return receiverEI;
  }
  /**
   * Stores the given SPAN state in the table. This should NEVER be called by user code.
   */
  setSPANState(peerNodeID, state) {
    if (state.type === import_Manager2Types.SPANState.None) {
      this.spanTable.delete(peerNodeID);
    } else {
      this.spanTable.set(peerNodeID, state);
    }
  }
  /** Invalidates the SPAN state for the given receiver */
  deleteNonce(receiver) {
    this.spanTable.delete(receiver);
    this.peerSequenceNumbers.delete(receiver);
  }
  /** Initializes the singlecast PAN generator for a given node based on the given entropy inputs */
  async initializeSPAN(peerNodeId, securityClass, senderEI, receiverEI) {
    if (senderEI.length !== 16 || receiverEI.length !== 16) {
      throw new import_ZWaveError.ZWaveError(`The entropy input must consist of 16 bytes`, import_ZWaveError.ZWaveErrorCodes.Argument_Invalid);
    }
    const keys = this.getKeysForSecurityClass(securityClass);
    const noncePRK = await (0, import_crypto.computeNoncePRK)(senderEI, receiverEI);
    const MEI = await (0, import_crypto.deriveMEI)(noncePRK);
    const rng = new import_ctr_drbg.CtrDRBG();
    await rng.init(MEI, keys.personalizationString);
    this.spanTable.set(peerNodeId, {
      securityClass,
      type: import_Manager2Types.SPANState.SPAN,
      rng
    });
  }
  /** Initializes the singlecast PAN generator for a given node based on the given entropy inputs */
  async initializeTempSPAN(peerNodeId, senderEI, receiverEI) {
    if (senderEI.length !== 16 || receiverEI.length !== 16) {
      throw new import_ZWaveError.ZWaveError(`The entropy input must consist of 16 bytes`, import_ZWaveError.ZWaveErrorCodes.Argument_Invalid);
    }
    const keys = this.tempKeys.get(peerNodeId);
    const noncePRK = await (0, import_crypto.computeNoncePRK)(senderEI, receiverEI);
    const MEI = await (0, import_crypto.deriveMEI)(noncePRK);
    const rng = new import_ctr_drbg.CtrDRBG();
    await rng.init(MEI, keys.personalizationString);
    this.spanTable.set(peerNodeId, {
      securityClass: import_SecurityClass.SecurityClass.Temporary,
      type: import_Manager2Types.SPANState.SPAN,
      rng
    });
  }
  /** Tests if the given combination of peer node ID and sequence number is a duplicate */
  isDuplicateSinglecast(peerNodeId, sequenceNumber) {
    return this.peerSequenceNumbers.get(peerNodeId)?.includes(sequenceNumber) ?? false;
  }
  /** Stores the latest sequence number for the given peer node ID and returns the previous one */
  storeSequenceNumber(peerNodeId, sequenceNumber) {
    if (this.peerSequenceNumbers.has(peerNodeId)) {
      const arr = this.peerSequenceNumbers.get(peerNodeId);
      const prev = arr.at(-1);
      arr.push(sequenceNumber);
      if (arr.length > SINGLECAST_MAX_SEQ_NUMS)
        arr.shift();
      return prev;
    } else {
      this.peerSequenceNumbers.set(peerNodeId, [sequenceNumber]);
    }
  }
  storeRemoteEI(peerNodeId, remoteEI) {
    if (remoteEI.length !== 16) {
      throw new import_ZWaveError.ZWaveError(`The entropy input must consist of 16 bytes`, import_ZWaveError.ZWaveErrorCodes.Argument_Invalid);
    }
    this.spanTable.set(peerNodeId, {
      type: import_Manager2Types.SPANState.RemoteEI,
      receiverEI: remoteEI
    });
  }
  /**
   * Generates the next nonce for the given peer and returns it.
   * @param store - Whether the nonce should be stored/remembered as the current SPAN.
   */
  async nextNonce(peerNodeId, store) {
    const spanState = this.spanTable.get(peerNodeId);
    if (spanState?.type !== import_Manager2Types.SPANState.SPAN) {
      throw new import_ZWaveError.ZWaveError(`The Singlecast PAN has not been initialized for Node ${peerNodeId}`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    }
    const nonce = (await spanState.rng.generate(16)).subarray(0, 13);
    spanState.currentSPAN = store ? {
      nonce,
      expires: (0, import_date.highResTimestamp)() + SINGLECAST_NONCE_EXPIRY_NS
    } : void 0;
    return nonce;
  }
  /** Returns the next sequence number to use for outgoing messages to the given node */
  nextSequenceNumber(peerNodeId) {
    let seq = this.ownSequenceNumbers.get(peerNodeId);
    if (seq == void 0) {
      seq = (0, import_crypto.randomBytes)(1)[0];
    } else {
      seq = seq + 1 & 255;
    }
    this.ownSequenceNumbers.set(peerNodeId, seq);
    return seq;
  }
  /** Returns the next sequence number to use for outgoing messages to the given multicast group */
  nextMulticastSequenceNumber(groupId) {
    const group = this.multicastGroups.get(groupId);
    if (!group) {
      throw new import_ZWaveError.ZWaveError(`Multicast group ${groupId} does not exist!`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    }
    let seq = group.sequenceNumber;
    seq = seq + 1 & 255;
    group.sequenceNumber = seq;
    return seq;
  }
  getInnerMPANState(groupId) {
    return this.mpanStates.get(groupId);
  }
  async getMulticastKeyAndIV(groupId) {
    const group = this.getMulticastGroup(groupId);
    if (!group) {
      throw new import_ZWaveError.ZWaveError(`Multicast group ${groupId} does not exist!`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    }
    const keys = this.getKeysForSecurityClass(group.securityClass);
    if (!this.mpanStates.has(groupId)) {
      this.mpanStates.set(groupId, await this.rng.generate(16));
    }
    const stateN = this.mpanStates.get(groupId);
    const ret = (await (0, import_crypto.encryptAES128ECB)(stateN, keys.keyMPAN)).subarray(0, 13);
    (0, import_shared2.increment)(stateN);
    return {
      key: keys.keyCCM,
      iv: ret
    };
  }
  /** As part of MPAN maintenance, this increments our own MPAN for a group */
  tryIncrementMPAN(groupId) {
    const stateN = this.mpanStates.get(groupId);
    if (stateN)
      (0, import_shared2.increment)(stateN);
  }
  /**
   * Generates the next nonce for the given peer and returns it.
   */
  async nextPeerMPAN(peerNodeId, groupId) {
    const mpanState = this.getPeerMPAN(peerNodeId, groupId);
    if (mpanState.type !== import_Manager2Types.MPANState.MPAN) {
      throw new import_ZWaveError.ZWaveError(`No peer multicast PAN exists for Node ${peerNodeId}, group ${groupId}`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    }
    const keys = this.getKeysForNode(peerNodeId);
    if (!keys || !("keyMPAN" in keys)) {
      throw new import_ZWaveError.ZWaveError(`The network keys for the security class of Node ${peerNodeId} have not been set up yet!`, import_ZWaveError.ZWaveErrorCodes.Security2CC_NotInitialized);
    }
    const stateN = mpanState.currentMPAN;
    const ret = (await (0, import_crypto.encryptAES128ECB)(stateN, keys.keyMPAN)).subarray(0, 13);
    (0, import_shared2.increment)(stateN);
    return ret;
  }
  /** As part of MPAN maintenance, this increments the peer's MPAN if it is known */
  tryIncrementPeerMPAN(peerNodeId, groupId) {
    const mpanState = this.getPeerMPAN(peerNodeId, groupId);
    if (mpanState?.type !== import_Manager2Types.MPANState.MPAN)
      return;
    const stateN = mpanState.currentMPAN;
    (0, import_shared2.increment)(stateN);
  }
  /** Returns the stored MPAN used to decrypt messages from `peerNodeId`, MPAN group `groupId` */
  getPeerMPAN(peerNodeId, groupId) {
    return this.peerMPANs.get(peerNodeId)?.get(groupId) ?? {
      type: import_Manager2Types.MPANState.None
    };
  }
  /** Reset all out of sync MPANs for the given node */
  resetOutOfSyncMPANs(peerNodeId) {
    const entries = this.peerMPANs.get(peerNodeId);
    if (!entries)
      return;
    for (const [groupId, state] of entries) {
      if (state.type === import_Manager2Types.MPANState.OutOfSync) {
        entries.delete(groupId);
      }
    }
  }
  storePeerMPAN(peerNodeId, groupId, mpanState) {
    if (!this.peerMPANs.has(peerNodeId)) {
      this.peerMPANs.set(peerNodeId, /* @__PURE__ */ new Map());
    }
    this.peerMPANs.get(peerNodeId).set(groupId, mpanState);
  }
}
function hashNodeIds(nodeIds) {
  const raw = (0, import_Primitive.encodeBitMask)(nodeIds, import_consts.MAX_NODES_LR);
  const compressed = (0, import_compression.deflateSync)(raw);
  return import_shared.Bytes.view(compressed).toString("hex");
}
__name(hashNodeIds, "hashNodeIds");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  SecurityManager2
});
//# sourceMappingURL=Manager2.js.map
