/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { CertificateAuthority } from "#certificate/CertificateAuthority.js";
import { Noc } from "#certificate/kinds/Noc.js";
import {
  AsyncObservable,
  Bytes,
  Construction,
  CRYPTO_SYMMETRIC_KEY_LENGTH,
  Environmental,
  ImplementationError,
  Logger
} from "@matter/general";
import { FabricId, NodeId, VendorId } from "@matter/types";
import { FabricBuilder } from "./Fabric.js";
import { FabricManager } from "./FabricManager.js";
const logger = Logger.get("FabricAuthority");
const DEFAULT_ADMIN_VENDOR_ID = VendorId(65521);
class FabricAuthority {
  #construction;
  #ca;
  #fabrics;
  #fabricAdded = new AsyncObservable();
  #rotatedFabricIndices = /* @__PURE__ */ new Set();
  // Remember which we already rotated in this run
  constructor({ ca, fabrics }) {
    this.#ca = ca;
    this.#fabrics = fabrics;
    this.#construction = Construction(this, async () => {
      await this.#ca.construction;
      await this.#fabrics.construction;
    });
  }
  get construction() {
    return this.#construction;
  }
  close() {
    return this.#construction.close();
  }
  /**
   * Access the certificate authority.
   */
  get ca() {
    return this.#ca;
  }
  /**
   * Get the default fabric for this authority.
   * When rotateNoc is true (the default), the NOC key pair is rotated once per runtime when the fabric already exists.
   */
  async defaultFabric(config, rotateNoc = true) {
    const caRootCert = this.#ca.rootCert;
    let fabric = this.fabrics.find((fabric2) => Bytes.areEqual(fabric2.rootCert, caRootCert));
    if (fabric !== void 0) {
      if (rotateNoc) {
        fabric = await this.#rotateFabricNocKey(fabric);
      }
      if (fabric.label !== config.adminFabricLabel) {
        await fabric.setLabel(config.adminFabricLabel);
      }
      return fabric;
    }
    return await this.createFabric(config);
  }
  /**
   * List all controlled fabrics.
   */
  get fabrics() {
    return Array.from(this.#fabrics).filter(this.hasControlOf.bind(this));
  }
  /**
   * Emits after creating a new fabric.
   */
  get fabricAdded() {
    return this.#fabricAdded;
  }
  /**
   * Determine whether a fabric belongs to this authority.
   */
  hasControlOf(fabric) {
    return Bytes.areEqual(fabric.rootCert, this.#ca.rootCert);
  }
  /**
   * Create new fabric under our control.
   */
  async createFabric(config) {
    const rootNodeId = config.adminNodeId ?? NodeId.randomOperationalNodeId(this.#fabrics.crypto);
    const ipkValue = this.#fabrics.crypto.randomBytes(CRYPTO_SYMMETRIC_KEY_LENGTH);
    let vendorId = config.adminVendorId;
    if (vendorId === void 0) {
      vendorId = DEFAULT_ADMIN_VENDOR_ID;
      logger.warn(`Using test vendor ID 0x${vendorId.toString(16)} for controller fabric`);
    }
    const fabricBuilder = await FabricBuilder.create(this.#fabrics.crypto);
    await fabricBuilder.setRootCert(this.#ca.rootCert);
    fabricBuilder.setRootNodeId(rootNodeId).setIdentityProtectionKey(ipkValue).setRootVendorId(vendorId).setLabel(config.adminFabricLabel);
    const fabricId = config.adminFabricId ?? FabricId(this.#fabrics.crypto.randomBigInt(8));
    await fabricBuilder.setOperationalCert(
      await this.#ca.generateNoc(fabricBuilder.publicKey, fabricId, rootNodeId, config.caseAuthenticatedTags),
      this.#ca.icacCert
    );
    let index = config.adminFabricIndex;
    if (index === void 0) {
      index = this.#fabrics.allocateFabricIndex();
    } else if (this.#fabrics.maybeFor(index) !== void 0) {
      throw new ImplementationError(`Cannot allocate controller fabric ${index} because index is in use`);
    }
    const fabric = await fabricBuilder.build(index);
    this.#fabrics.addFabric(fabric);
    fabric.persist();
    logger.debug(`Created new controller fabric ${index}`);
    await this.#fabricAdded.emit(fabric);
    return fabric;
  }
  async #rotateFabricNocKey(fabric) {
    if (this.#rotatedFabricIndices.has(fabric.fabricIndex)) {
      return fabric;
    }
    const builder = await FabricBuilder.create(this.#fabrics.crypto);
    builder.initializeFromFabricForUpdate(fabric);
    const {
      subject: { nodeId, fabricId, caseAuthenticatedTags }
    } = Noc.fromTlv(fabric.operationalCert).cert;
    if (nodeId !== fabric.nodeId) {
      throw new ImplementationError(`Cannot rotate NOC for fabric ${fabric.fabricIndex} because node ID changed`);
    }
    await builder.setOperationalCert(
      await this.#ca.generateNoc(builder.publicKey, fabricId, nodeId, caseAuthenticatedTags),
      fabric.intermediateCACert
    );
    const newFabric = await builder.build(fabric.fabricIndex);
    logger.info(`Rotated NOC for fabric ${fabric.fabricIndex}`);
    await this.#fabrics.replaceFabric(newFabric);
    this.#rotatedFabricIndices.add(fabric.fabricIndex);
    return newFabric;
  }
  static [Environmental.create](env) {
    const instance = new FabricAuthority({
      ca: env.get(CertificateAuthority),
      fabrics: env.get(FabricManager)
    });
    env.set(FabricAuthority, instance);
    return instance;
  }
}
export {
  DEFAULT_ADMIN_VENDOR_ID,
  FabricAuthority
};
//# sourceMappingURL=FabricAuthority.js.map
