/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { CommissioningServer } from "#behavior/system/commissioning/CommissioningServer.js";
import { ProductDescriptionServer } from "#behavior/system/product-description/ProductDescriptionServer.js";
import { AccessControlServer } from "#behaviors/access-control";
import {
  CertificateError,
  Crypto,
  CryptoVerifyError,
  Logger,
  MatterFlowError,
  UnexpectedDataError
} from "@matter/general";
import { AccessLevel } from "@matter/model";
import {
  assertRemoteActor,
  DeviceCertification,
  DeviceCommissioner,
  FabricManager,
  FabricTableFullError,
  MatterFabricConflictError,
  MatterFabricInvalidAdminSubjectError,
  NodeSession,
  PublicKeyError,
  TlvAttestation,
  TlvCertSigningRequest,
  Val
} from "@matter/protocol";
import {
  Command,
  FabricIndex,
  NodeId,
  StatusCode,
  StatusResponse,
  StatusResponseError,
  TlvBoolean,
  TlvByteString,
  TlvField,
  TlvObject,
  TlvOptionalField,
  ValidationError,
  VendorId
} from "@matter/types";
import { OperationalCredentials } from "@matter/types/clusters/operational-credentials";
import { OperationalCredentialsBehavior } from "./OperationalCredentialsBehavior.js";
import { VendorIdVerification } from "./VendorIdVerification.js";
const logger = Logger.get("OperationalCredentials");
OperationalCredentials.Cluster.commands = {
  ...OperationalCredentials.Cluster.commands,
  attestationRequest: Command(
    0,
    TlvObject({ attestationNonce: TlvField(0, TlvByteString) }),
    1,
    OperationalCredentials.TlvAttestationResponse,
    { invokeAcl: AccessLevel.Administer }
  ),
  csrRequest: Command(
    4,
    TlvObject({
      csrNonce: TlvField(0, TlvByteString),
      isForUpdateNoc: TlvOptionalField(1, TlvBoolean)
    }),
    5,
    OperationalCredentials.TlvCsrResponse,
    { invokeAcl: AccessLevel.Administer }
  )
};
class OperationalCredentialsServer extends OperationalCredentialsBehavior {
  initialize() {
    if (!this.state.supportedFabrics) {
      this.state.supportedFabrics = 254;
    }
    this.state.commissionedFabrics = this.state.fabrics.length;
    this.reactTo(this.endpoint.lifecycle.online, this.#nodeOnline);
  }
  async attestationRequest({ attestationNonce }) {
    assertRemoteActor(this.context);
    if (attestationNonce.byteLength !== 32) {
      throw new StatusResponseError("Invalid attestation nonce length", StatusCode.InvalidCommand);
    }
    const certification = await this.getCertification();
    const session = this.context.session;
    NodeSession.assert(session);
    const attestationElements = TlvAttestation.encode({
      declaration: certification.declaration,
      attestationNonce,
      timestamp: 0
    });
    const attestationSignature = (await certification.sign(session, attestationElements)).bytes;
    return { attestationElements, attestationSignature };
  }
  async csrRequest({ csrNonce, isForUpdateNoc }) {
    assertRemoteActor(this.context);
    if (csrNonce.byteLength !== 32) {
      throw new StatusResponseError("Invalid csr nonce length", StatusCode.InvalidCommand);
    }
    const session = this.context.session;
    NodeSession.assert(session);
    if (isForUpdateNoc && session.isPase) {
      throw new StatusResponseError(
        "csrRequest for UpdateNoc received on a PASE session",
        StatusCode.InvalidCommand
      );
    }
    const commissioner = this.env.get(DeviceCommissioner);
    const failsafeContext = commissioner.failsafeContext;
    if (failsafeContext.fabricIndex !== void 0) {
      throw new StatusResponseError(
        `csrRequest received after ${failsafeContext.forUpdateNoc ? "UpdateNOC" : "AddNOC"} already invoked`,
        StatusCode.ConstraintError
      );
    }
    const certification = await this.getCertification();
    assertRemoteActor(this.context);
    const certSigningRequest = await failsafeContext.createCertificateSigningRequest(
      isForUpdateNoc ?? false,
      this.context.session.id
    );
    const nocsrElements = TlvCertSigningRequest.encode({ certSigningRequest, csrNonce });
    const attestationSignature = (await certification.sign(session, nocsrElements)).bytes;
    return { nocsrElements, attestationSignature };
  }
  async certificateChainRequest({ certificateType }) {
    const certification = await this.getCertification();
    switch (certificateType) {
      case OperationalCredentials.CertificateChainType.DacCertificate:
        return { certificate: certification.certificate };
      case OperationalCredentials.CertificateChainType.PaiCertificate:
        return { certificate: certification.intermediateCertificate };
      default:
        throw new StatusResponseError(
          `Unsupported certificate type: ${certificateType}`,
          StatusCode.InvalidCommand
        );
    }
  }
  #mapNocErrors(error) {
    if (error instanceof MatterFabricConflictError) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.FabricConflict,
        debugText: error.message
      };
    } else if (error instanceof FabricTableFullError) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.TableFull,
        debugText: error.message
      };
    } else if (error instanceof CryptoVerifyError || error instanceof CertificateError || error instanceof ValidationError || error instanceof UnexpectedDataError) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.InvalidNoc,
        debugText: error.message
      };
    } else if (error instanceof PublicKeyError) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.InvalidPublicKey,
        debugText: error.message
      };
    } else if (error instanceof MatterFabricInvalidAdminSubjectError) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.InvalidAdminSubject,
        debugText: error.message
      };
    }
    throw error;
  }
  async addNoc({
    nocValue,
    icacValue,
    ipkValue,
    caseAdminSubject,
    adminVendorId
  }) {
    assertRemoteActor(this.context);
    const failsafeContext = this.#failsafeContext;
    if (failsafeContext.fabricIndex !== void 0) {
      throw new StatusResponseError(
        `AddNoc is illegal after ${failsafeContext.forUpdateNoc ? "UpdateNOC" : "AddNOC"} in the same failsafe context`,
        StatusCode.ConstraintError
      );
    }
    if (!failsafeContext.hasRootCert) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.InvalidNoc,
        debugText: "Root certificate not found"
      };
    }
    if (failsafeContext.csrSessionId !== this.context.session.id) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.MissingCsr,
        debugText: "CSR not found in failsafe context"
      };
    }
    if (failsafeContext.forUpdateNoc) {
      throw new StatusResponseError(
        `AddNoc is illegal after CsrRequest for UpdateNOC in same failsafe context`,
        StatusCode.ConstraintError
      );
    }
    const state = this.state;
    if (state.commissionedFabrics >= state.supportedFabrics) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.TableFull,
        debugText: `No more fabrics can be added because limit ${state.supportedFabrics} reached`
      };
    }
    let fabric;
    try {
      fabric = await failsafeContext.buildFabric({
        nocValue,
        icacValue,
        adminVendorId,
        ipkValue,
        caseAdminSubject
      });
    } catch (error) {
      logger.info("Building fabric for addNoc failed", error);
      return this.#mapNocErrors(error);
    }
    const session = this.context.session;
    NodeSession.assert(session);
    try {
      if (session.isPase) {
        logger.debug(`Add Fabric ${fabric.fabricIndex} to PASE session ${session.via}`);
        session.fabric = fabric;
      }
      const existingFabricIndex = this.state.fabrics.findIndex((f) => f.fabricIndex === fabric.fabricIndex);
      const existingNocIndex = this.state.nocs.findIndex((n) => n.fabricIndex === fabric.fabricIndex);
      if (existingFabricIndex !== -1 || existingNocIndex !== -1) {
        throw new MatterFlowError(
          `FabricIndex ${fabric.fabricIndex} already exists in state. This should not happen`
        );
      }
    } catch (e) {
      await fabric.delete(this.context.exchange);
      throw e;
    }
    await this.endpoint.act((agent) => agent.get(AccessControlServer).addDefaultCaseAcl(fabric, [caseAdminSubject]));
    logger.info(
      `addNoc success, adminVendorId ${adminVendorId}, caseAdminSubject ${NodeId.strOf(caseAdminSubject)}`
    );
    return {
      statusCode: OperationalCredentials.NodeOperationalCertStatus.Ok,
      fabricIndex: fabric.fabricIndex
    };
  }
  async updateNoc({ nocValue, icacValue }) {
    assertRemoteActor(this.context);
    NodeSession.assert(this.context.session);
    const timedOp = this.#failsafeContext;
    if (timedOp.fabricIndex !== void 0) {
      throw new StatusResponseError(
        `UpdateNoc is illegal after ${timedOp.forUpdateNoc ? "UpdateNOC" : "AddNOC"} in same failsafe context`,
        StatusCode.ConstraintError
      );
    }
    if (timedOp.forUpdateNoc === false) {
      throw new StatusResponseError(
        "UpdateNoc is illegal after CsrRequest for AddNOC in same failsafe context",
        StatusCode.ConstraintError
      );
    }
    if (timedOp.rootCertSet) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.MissingCsr
      };
    }
    if (timedOp.forUpdateNoc === void 0) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.MissingCsr
      };
    }
    if (this.context.session.associatedFabric.fabricIndex !== timedOp.associatedFabric?.fabricIndex) {
      throw new StatusResponseError(
        "Fabric of this session and the failsafe context do not match",
        StatusCode.ConstraintError
      );
    }
    try {
      const updatedFabric = await timedOp.buildUpdatedFabric(nocValue, icacValue);
      await timedOp.replaceFabric(updatedFabric);
      await timedOp.associatedFabric.replaced(this.context.exchange);
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.Ok,
        fabricIndex: updatedFabric.fabricIndex
      };
    } catch (error) {
      logger.info("Building fabric for updateNoc failed", error);
      return this.#mapNocErrors(error);
    }
  }
  async updateFabricLabel({ label }) {
    assertRemoteActor(this.context);
    const fabric = this.context.session.associatedFabric;
    const currentFabricIndex = fabric.fabricIndex;
    const fabrics = this.env.get(FabricManager);
    const conflictingLabelFabric = fabrics.find((f) => f.label === label && f.fabricIndex !== currentFabricIndex);
    if (conflictingLabelFabric !== void 0) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.LabelConflict,
        debugText: `Label ${label} already used by fabric ${conflictingLabelFabric.fabricIndex}`
      };
    }
    await fabric.setLabel(label);
    return { statusCode: OperationalCredentials.NodeOperationalCertStatus.Ok, fabricIndex: fabric.fabricIndex };
  }
  async removeFabric({ fabricIndex }) {
    assertRemoteActor(this.context);
    const fabric = this.env.get(FabricManager).maybeFor(fabricIndex);
    if (fabric === void 0) {
      return {
        statusCode: OperationalCredentials.NodeOperationalCertStatus.InvalidFabricIndex,
        debugText: `Fabric ${fabricIndex} not found`
      };
    }
    await this.context.transaction.rollback();
    await fabric.leave(this.context.exchange);
    return {
      statusCode: OperationalCredentials.NodeOperationalCertStatus.Ok,
      fabricIndex
    };
  }
  async addTrustedRootCertificate({
    rootCaCertificate
  }) {
    const failsafeContext = this.#failsafeContext;
    if (failsafeContext.rootCertSet) {
      throw new StatusResponseError(
        "Trusted root certificate already added in this FailSafe context",
        StatusCode.ConstraintError
      );
    }
    if (failsafeContext.fabricIndex !== void 0) {
      throw new StatusResponseError(
        `Cannot add trusted root certificates after ${failsafeContext.forUpdateNoc ? "UpdateNOC" : "AddNOC"}`,
        StatusCode.ConstraintError
      );
    }
    try {
      await failsafeContext.setRootCert(rootCaCertificate);
    } catch (error) {
      logger.info("Error installing root certificate:", error);
      if (error instanceof CryptoVerifyError || error instanceof CertificateError || error instanceof ValidationError || error instanceof UnexpectedDataError) {
        throw new StatusResponseError(error.message, StatusCode.InvalidCommand);
      }
      throw error;
    }
    const fabrics = this.env.get(FabricManager);
    const trustedRootCertificates = fabrics.map((fabric) => fabric.rootCert);
    trustedRootCertificates.push(rootCaCertificate);
    this.state.trustedRootCertificates = trustedRootCertificates;
  }
  async signVidVerificationRequest({
    fabricIndex,
    clientChallenge
  }) {
    assertRemoteActor(this.context);
    NodeSession.assert(this.context.session);
    const fabrics = this.env.get(FabricManager);
    if (!fabrics.has(fabricIndex)) {
      throw new StatusResponse.ConstraintErrorError(`Fabric with index ${fabricIndex} does not exist`);
    }
    const fabric = fabrics.for(fabricIndex);
    const { fabricBindingVersion, signatureData } = VendorIdVerification.dataToSign({
      attChallenge: this.context.session.attestationChallengeKey,
      clientChallenge,
      fabricIndex,
      fabric
    });
    return {
      fabricIndex,
      fabricBindingVersion,
      signature: (await fabric.sign(signatureData)).bytes
    };
  }
  async setVidVerificationStatement({
    vendorId,
    vidVerificationStatement,
    vvsc
  }) {
    assertRemoteActor(this.context);
    const fabric = this.context.session.associatedFabric;
    if (vendorId === void 0 && vidVerificationStatement === void 0 && vvsc === void 0) {
      throw new StatusResponse.InvalidCommandError(
        "At least one of vendorId, vidVerificationStatement or vvsc must be provided"
      );
    }
    if (vendorId !== void 0 && !VendorId.isValid(vendorId)) {
      throw new StatusResponse.ConstraintErrorError(`Invalid vendorId: ${vendorId}`);
    }
    await fabric.updateVendorVerificationData(vendorId, vidVerificationStatement, vvsc);
  }
  async #updateFabrics() {
    await this.context.transaction.addResources(this);
    await this.context.transaction.begin();
    const fabrics = this.env.get(FabricManager);
    this.state.fabrics = fabrics.map((fabric) => ({
      fabricId: fabric.fabricId,
      label: fabric.label,
      nodeId: fabric.nodeId,
      rootPublicKey: fabric.rootPublicKey,
      vendorId: fabric.rootVendorId,
      vidVerificationStatement: fabric.vidVerificationStatement,
      fabricIndex: fabric.fabricIndex
    }));
    this.state.nocs = fabrics.map((fabric) => ({
      noc: fabric.operationalCert,
      icac: fabric.intermediateCACert ?? null,
      vvsc: fabric.vvsc,
      fabricIndex: fabric.fabricIndex
    }));
    this.state.trustedRootCertificates = fabrics.map((fabric) => fabric.rootCert);
    this.state.commissionedFabrics = fabrics.length;
    await this.context.transaction.commit();
  }
  async getCertification() {
    const certification = this.internal.certification ?? (this.internal.certification = new DeviceCertification(
      this.env.get(Crypto),
      this.state.certification,
      this.agent.get(ProductDescriptionServer).state
    ));
    await certification.construction;
    return certification;
  }
  async #handleAddedFabric({ fabricIndex }) {
    await this.#updateFabrics();
    this.agent.get(CommissioningServer).handleFabricChange(fabricIndex, "added");
  }
  async #handleUpdatedFabric({ fabricIndex }) {
    await this.#updateFabrics();
    this.agent.get(CommissioningServer).handleFabricChange(fabricIndex, "updated");
  }
  async #handleRemovedFabric({ fabricIndex }) {
    await this.#updateFabrics();
    this.agent.get(CommissioningServer).handleFabricChange(fabricIndex, "deleted");
  }
  async #handleFailsafeClosed() {
    await this.#updateFabrics();
  }
  async #nodeOnline() {
    const fabricManager = this.env.get(FabricManager);
    this.reactTo(fabricManager.events.added, this.#handleAddedFabric, { lock: true });
    this.reactTo(fabricManager.events.replaced, this.#handleUpdatedFabric, { lock: true });
    this.reactTo(fabricManager.events.deleting, this.#handleRemovedFabric, { lock: true });
    this.reactTo(fabricManager.events.failsafeClosed, this.#handleFailsafeClosed, { lock: true });
    await this.#updateFabrics();
  }
  get #failsafeContext() {
    return this.env.get(DeviceCommissioner).failsafeContext;
  }
}
((OperationalCredentialsServer2) => {
  class Internal {
    certification;
  }
  OperationalCredentialsServer2.Internal = Internal;
  class State extends OperationalCredentialsBehavior.State {
    /**
     * Device certification information.
     *
     * Device certification provides a cryptographic certificate that asserts the official status of a device.
     * Production consumer-facing devices are certified by the CSA.
     *
     * Development devices and those intended for personal use may use a development certificate.  This is the
     * default if you do not provide an official certification in {@link ServerOptions.certification}.
     */
    certification = void 0;
    [Val.properties](_endpoint, session) {
      return {
        get currentFabricIndex() {
          return session.fabric ?? FabricIndex.NO_FABRIC;
        }
      };
    }
  }
  OperationalCredentialsServer2.State = State;
})(OperationalCredentialsServer || (OperationalCredentialsServer = {}));
export {
  OperationalCredentialsServer
};
//# sourceMappingURL=OperationalCredentialsServer.js.map
