"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 Fabric_exports = {};
__export(Fabric_exports, {
  Fabric: () => Fabric,
  FabricBuilder: () => FabricBuilder,
  PublicKeyError: () => PublicKeyError
});
module.exports = __toCommonJS(Fabric_exports);
var import_Certificate = require("#certificate/kinds/Certificate.js");
var import_Icac = require("#certificate/kinds/Icac.js");
var import_Noc = require("#certificate/kinds/Noc.js");
var import_Rcac = require("#certificate/kinds/Rcac.js");
var import_FabricGroups = require("#groups/FabricGroups.js");
var import_FabricAccessControl = require("#interaction/FabricAccessControl.js");
var import_PeerAddress = require("#peer/PeerAddress.js");
var import_PeerCommunicationError = require("#peer/PeerCommunicationError.js");
var import_general = require("@matter/general");
var import_types = require("@matter/types");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("Fabric");
class PublicKeyError extends import_general.MatterError {
}
class Fabric {
  #crypto;
  fabricIndex;
  fabricId;
  nodeId;
  rootNodeId;
  globalId;
  #rootPublicKey;
  #rootVendorId;
  rootCert;
  identityProtectionKey;
  operationalIdentityProtectionKey;
  intermediateCACert;
  operationalCert;
  #keyPair;
  #sessions = /* @__PURE__ */ new Set();
  #groups;
  #accessControl;
  #leaving = (0, import_general.AsyncObservable)();
  #deleting = (0, import_general.AsyncObservable)();
  #deleted = (0, import_general.AsyncObservable)();
  #vidVerificationStatement;
  #vvsc;
  #label;
  #persistCallback;
  #storage;
  #isDeleting;
  /**
   * Create a fabric synchronously.
   *
   * Certain derived fields that require async crypto operations to compute must be supplied here.  Use {@link create}
   * to populate these fields automatically.
   */
  constructor(crypto, config) {
    this.#crypto = crypto;
    this.fabricIndex = config.fabricIndex;
    this.fabricId = config.fabricId;
    this.nodeId = config.nodeId;
    this.rootNodeId = config.rootNodeId;
    if ("operationalId" in config) {
      this.globalId = (0, import_types.GlobalFabricId)(config.operationalId);
    } else {
      this.globalId = config.globalId;
    }
    this.#rootPublicKey = config.rootPublicKey;
    this.#rootVendorId = config.rootVendorId;
    this.rootCert = config.rootCert;
    this.identityProtectionKey = config.identityProtectionKey;
    this.operationalIdentityProtectionKey = config.operationalIdentityProtectionKey;
    this.intermediateCACert = config.intermediateCACert;
    this.operationalCert = config.operationalCert;
    this.#vidVerificationStatement = config.vidVerificationStatement;
    this.#vvsc = config.vvsc;
    this.#label = config.label;
    this.#keyPair = (0, import_general.PrivateKey)(config.keyPair);
    this.#accessControl = new import_FabricAccessControl.FabricAccessControl(this);
    this.#groups = new import_FabricGroups.FabricGroups(this);
  }
  /**
   * Create a fabric.
   *
   * This async creation path populates derived fields that require async crypto operations to compute.
   */
  static async create(crypto, config) {
    let { globalId, operationalIdentityProtectionKey } = config;
    if (globalId === void 0) {
      const caKey = config.rootPublicKey ?? import_Rcac.Rcac.publicKeyOfTlv(config.rootCert);
      globalId = await import_types.GlobalFabricId.compute(crypto, config.fabricId, caKey);
    }
    if (operationalIdentityProtectionKey === void 0) {
      operationalIdentityProtectionKey = await crypto.createHkdfKey(
        config.identityProtectionKey,
        import_general.Bytes.fromBigInt(globalId, 8),
        import_FabricGroups.GROUP_SECURITY_INFO
      );
    }
    return new Fabric(crypto, {
      ...config,
      globalId,
      operationalIdentityProtectionKey
    });
  }
  get crypto() {
    return this.#crypto;
  }
  /**
   * Obtain configuration required to recreate fabric.
   *
   * TODO - we currently use this for persistence; remove when we move to OperationalCredentials as "source of truth"
   */
  get config() {
    const config = {
      fabricIndex: this.fabricIndex,
      fabricId: this.fabricId,
      nodeId: this.nodeId,
      rootNodeId: this.rootNodeId,
      globalId: this.globalId,
      rootPublicKey: this.rootPublicKey,
      keyPair: this.#keyPair.keyPair,
      rootVendorId: this.rootVendorId,
      rootCert: this.rootCert,
      identityProtectionKey: this.identityProtectionKey,
      operationalIdentityProtectionKey: this.operationalIdentityProtectionKey,
      intermediateCACert: this.intermediateCACert,
      operationalCert: this.operationalCert,
      vidVerificationStatement: this.vidVerificationStatement,
      label: this.#label
    };
    config.operationalId = import_general.Bytes.fromBigInt(this.globalId, 8);
    return config;
  }
  get label() {
    return this.#label;
  }
  async setLabel(label) {
    if (label.length === 0 || label.length > 32) {
      throw new import_general.ImplementationError("Fabric label must be between 1 and 32 characters long.");
    }
    if (this.#label === label) {
      return;
    }
    this.#label = label;
    await this.persist();
  }
  get vidVerificationStatement() {
    return this.#vidVerificationStatement;
  }
  async updateVendorVerificationData(vendorId, vidVerificationStatement, vvsc) {
    if (vvsc !== void 0 && this.intermediateCACert !== void 0) {
      throw new import_types.StatusResponse.InvalidCommandError("A VVSC is only allowed without an ICAC.");
    }
    if (vidVerificationStatement !== void 0) {
      if (vidVerificationStatement.byteLength === 0) {
        this.#vidVerificationStatement = void 0;
      } else if (vidVerificationStatement.byteLength === 85) {
        this.#vidVerificationStatement = vidVerificationStatement;
      } else {
        throw new import_types.StatusResponse.ConstraintErrorError("VID Verification Statement must be 0 or 85 bytes long.");
      }
    }
    if (vendorId !== void 0) {
      this.#rootVendorId = vendorId;
    }
    if (vvsc !== void 0) {
      if (vvsc.byteLength === 0) {
        this.#vvsc = void 0;
      } else {
        this.#vvsc = vvsc;
      }
    }
    logger.info(
      "Updated Vendor Verification Data for Fabric",
      this.#rootVendorId,
      this.#vidVerificationStatement,
      this.#vvsc
    );
    await this.persist();
  }
  get vvsc() {
    return this.#vvsc;
  }
  get rootPublicKey() {
    if (this.#rootPublicKey === void 0) {
      this.#rootPublicKey = import_Rcac.Rcac.publicKeyOfTlv(this.rootCert);
    }
    return this.#rootPublicKey;
  }
  get rootVendorId() {
    return this.#rootVendorId;
  }
  set storage(storage) {
    this.#storage = storage;
    this.#groups.storage = storage;
  }
  get storage() {
    return this.#storage;
  }
  get groups() {
    return this.#groups;
  }
  get accessControl() {
    return this.#accessControl;
  }
  get publicKey() {
    return this.#keyPair.publicKey;
  }
  get isDeleting() {
    return this.#isDeleting;
  }
  get leaving() {
    return this.#leaving;
  }
  get deleting() {
    return this.#deleting;
  }
  get deleted() {
    return this.#deleted;
  }
  sign(data) {
    return this.crypto.signEcdsa(this.#keyPair, data);
  }
  async verifyCredentials(operationalCert, intermediateCACert) {
    const rootCert = import_Rcac.Rcac.fromTlv(this.rootCert);
    const nocCert = import_Noc.Noc.fromTlv(operationalCert);
    const icaCert = intermediateCACert !== void 0 ? import_Icac.Icac.fromTlv(intermediateCACert) : void 0;
    if (icaCert !== void 0) {
      await icaCert.verify(this.#crypto, rootCert);
    }
    await nocCert.verify(this.#crypto, rootCert, icaCert);
  }
  matchesFabricIdAndRootPublicKey(fabricId, rootPublicKey) {
    return this.fabricId === fabricId && import_general.Bytes.areEqual(this.rootPublicKey, rootPublicKey);
  }
  matchesKeyPair(keyPair) {
    return import_general.Bytes.areEqual(this.#keyPair.publicKey, keyPair.publicKey) && import_general.Bytes.areEqual(this.#keyPair.privateKey, keyPair.privateKey);
  }
  #generateSalt(nodeId, random) {
    const writer = new import_general.DataWriter(import_general.Endian.Little);
    writer.writeByteArray(random);
    writer.writeByteArray(this.rootPublicKey);
    writer.writeUInt64(this.fabricId);
    writer.writeUInt64(nodeId);
    return writer.toByteArray();
  }
  /**
   * Returns the destination IDs for a given nodeId, random value and optional groupId. When groupId is provided, it
   * returns the time-wise valid operational keys for that groupId.
   */
  async currentDestinationIdFor(nodeId, random) {
    return this.#crypto.signHmac(this.groups.keySets.currentKeyForId(0).key, this.#generateSalt(nodeId, random));
  }
  /**
   * Returns the destination IDs for a given nodeId, random value and optional groupId. When groupId is provided, it
   * returns all operational keys for that groupId.
   */
  async destinationIdsFor(nodeId, random) {
    const salt = this.#generateSalt(nodeId, random);
    const destinationIds = this.groups.keySets.allKeysForId(0).map(({ key }) => this.#crypto.signHmac(key, salt));
    return await Promise.all(destinationIds);
  }
  addSession(session) {
    this.#sessions.add(session);
  }
  deleteSession(session) {
    this.#sessions.delete(session);
  }
  hasSessionForPeer(peerNodeId) {
    for (const session of this.#sessions) {
      if (session.peerNodeId === peerNodeId) {
        return true;
      }
    }
  }
  set persistCallback(callback) {
    this.#persistCallback = callback;
  }
  /**
   * Handles actions when a fabric got replaced.
   *
   * It flushes subscriptions to ensure fabric updates are reported and closes sessions.
   */
  async replaced(currentExchange) {
    for (const session of [...this.#sessions]) {
      await session.initiateClose(async () => {
        await session.closeSubscriptions(true);
      });
      await session.initiateForceClose({ cause: new import_PeerCommunicationError.FabricChangedError(), currentExchange });
    }
  }
  /**
   * Gracefully exit the fabric.
   *
   * Devices should use this to cleanly exit a fabric.  It flushes subscriptions to ensure the "leave" event emits
   * and closes sessions.
   */
  async leave(currentExchange) {
    await this.#leaving.emit();
    for (const session of [...this.#sessions]) {
      await session.initiateClose(async () => {
        await session.closeSubscriptions(true);
      });
    }
    await this.delete(currentExchange);
  }
  /**
   * Permanently remove the fabric.
   *
   * Does not emit the leave event.
   */
  async delete(currentExchange) {
    this.#isDeleting = true;
    await this.#deleting.emit();
    for (const session of [...this.#sessions]) {
      await session.initiateForceClose({ cause: new import_PeerCommunicationError.FabricRemovedError(), currentExchange });
    }
    await this.#deleted.emit();
  }
  persist(isUpdate = true) {
    return this.#persistCallback?.(isUpdate);
  }
  get externalInformation() {
    return {
      fabricIndex: this.fabricIndex,
      fabricId: this.fabricId,
      nodeId: this.nodeId,
      rootNodeId: this.rootNodeId,
      rootVendorId: this.rootVendorId,
      label: this.#label
    };
  }
  addressOf(nodeId) {
    return (0, import_PeerAddress.PeerAddress)({ fabricIndex: this.fabricIndex, nodeId });
  }
  groupAddressOf(groupId) {
    import_types.GroupId.assertGroupId(groupId);
    return (0, import_PeerAddress.PeerAddress)({ fabricIndex: this.fabricIndex, nodeId: import_types.NodeId.fromGroupId(groupId) });
  }
}
class FabricBuilder {
  #crypto;
  #keyPair;
  #rootVendorId;
  #rootCert;
  #intermediateCACert;
  #operationalCert;
  #fabricId;
  #nodeId;
  #rootNodeId;
  #identityProtectionKey;
  #vidVerificationStatement;
  #vvsc;
  #fabricIndex;
  #label = "";
  constructor(crypto, key) {
    this.#crypto = crypto;
    this.#keyPair = key;
  }
  static async create(crypto) {
    return new FabricBuilder(crypto, await crypto.createKeyPair());
  }
  get publicKey() {
    return this.#keyPair.publicKey;
  }
  get fabricIndex() {
    return this.#fabricIndex;
  }
  createCertificateSigningRequest() {
    return import_Certificate.Certificate.createCertificateSigningRequest(this.#crypto, this.#keyPair);
  }
  async setRootCert(rootCert) {
    const root = import_Rcac.Rcac.fromTlv(rootCert);
    await root.verify(this.#crypto);
    this.#rootCert = rootCert;
    return this;
  }
  get rootCert() {
    return this.#rootCert;
  }
  async setOperationalCert(operationalCert, intermediateCACert) {
    if (intermediateCACert !== void 0 && intermediateCACert.byteLength === 0) {
      intermediateCACert = void 0;
    }
    const {
      subject: { nodeId, fabricId, caseAuthenticatedTags },
      ellipticCurvePublicKey
    } = import_Noc.Noc.fromTlv(operationalCert).cert;
    logger.debug(
      "Installing operational certificate",
      import_general.Diagnostic.dict({ nodeId: import_types.NodeId.strOf(nodeId), fabricId, caseAuthenticatedTags })
    );
    if (caseAuthenticatedTags !== void 0) {
      import_types.CaseAuthenticatedTag.validateNocTagList(caseAuthenticatedTags);
    }
    if (!import_general.Bytes.areEqual(ellipticCurvePublicKey, this.#keyPair.publicKey)) {
      throw new PublicKeyError("Operational certificate does not match public key");
    }
    if (this.#rootCert === void 0) {
      throw new import_general.MatterFlowError("Root certificate needs to be set first");
    }
    const rootCert = import_Rcac.Rcac.fromTlv(this.#rootCert);
    const nocCert = import_Noc.Noc.fromTlv(operationalCert);
    const icaCert = intermediateCACert !== void 0 ? import_Icac.Icac.fromTlv(intermediateCACert) : void 0;
    if (icaCert !== void 0) {
      await icaCert.verify(this.#crypto, rootCert);
    }
    await nocCert.verify(this.#crypto, rootCert, icaCert);
    this.#operationalCert = operationalCert;
    this.#intermediateCACert = intermediateCACert;
    this.#fabricId = (0, import_types.FabricId)(fabricId);
    this.#nodeId = nodeId;
    return this;
  }
  setRootVendorId(rootVendorId) {
    this.#rootVendorId = rootVendorId;
    return this;
  }
  setRootNodeId(rootNodeId) {
    this.#rootNodeId = rootNodeId;
    return this;
  }
  setIdentityProtectionKey(key) {
    this.#identityProtectionKey = key;
    return this;
  }
  setLabel(label) {
    if (label.length === 0 || label.length > 32) {
      throw new import_general.ImplementationError("Fabric label must be between 1 and 32 characters long.");
    }
    this.#label = label;
    return this;
  }
  initializeFromFabricForUpdate(fabric) {
    this.#rootVendorId = fabric.rootVendorId;
    this.#rootNodeId = fabric.rootNodeId;
    this.#identityProtectionKey = fabric.identityProtectionKey;
    this.#rootCert = fabric.rootCert;
    this.#vidVerificationStatement = fabric.vidVerificationStatement;
    this.#vvsc = fabric.vvsc;
    this.#label = fabric.label;
  }
  get globalId() {
    if (this.#fabricId === void 0 || this.#rootCert === void 0) {
      throw new import_general.MatterFlowError("Node Operational Data needs to be set first.");
    }
    return import_types.GlobalFabricId.compute(this.#crypto, this.#fabricId, import_Rcac.Rcac.publicKeyOfTlv(this.#rootCert));
  }
  get nodeId() {
    return this.#nodeId;
  }
  get fabricId() {
    return this.#fabricId;
  }
  get keyPair() {
    return this.#keyPair;
  }
  async build(fabricIndex) {
    if (this.#fabricIndex !== void 0) throw new import_general.InternalError("FabricBuilder can only be built once");
    if (this.#rootNodeId === void 0) throw new import_general.InternalError("rootNodeId needs to be set");
    if (this.#rootVendorId === void 0) throw new import_general.InternalError("vendorId needs to be set");
    if (this.#rootCert === void 0) throw new import_general.InternalError("rootCert needs to be set");
    if (this.#identityProtectionKey === void 0) throw new import_general.InternalError("identityProtectionKey needs to be set");
    if (this.#operationalCert === void 0 || this.#fabricId === void 0 || this.#nodeId === void 0)
      throw new import_general.InternalError("operationalCert needs to be set");
    this.#fabricIndex = fabricIndex;
    return await Fabric.create(this.#crypto, {
      fabricIndex: this.#fabricIndex,
      fabricId: this.#fabricId,
      nodeId: this.#nodeId,
      rootNodeId: this.#rootNodeId,
      keyPair: this.#keyPair,
      rootVendorId: this.#rootVendorId,
      rootCert: this.#rootCert,
      identityProtectionKey: this.#identityProtectionKey,
      // Epoch Key
      intermediateCACert: this.#intermediateCACert,
      operationalCert: this.#operationalCert,
      vidVerificationStatement: this.#vidVerificationStatement,
      vvsc: this.#vvsc,
      label: this.#label
    });
  }
}
//# sourceMappingURL=Fabric.js.map
