/**
 * @license
 * Copyright 2022-2024 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { AdministratorCommissioning } from "#clusters/administrator-commissioning";
import { InternalError, Logger, Time } from "#general";
import { AccessLevel } from "#model";
import { DeviceCommissioner, FailsafeContext, PaseServer, SessionManager } from "#protocol";
import {
  Command,
  MAXIMUM_COMMISSIONING_TIMEOUT_S,
  MINIMUM_COMMISSIONING_TIMEOUT_S,
  PAKE_PASSCODE_VERIFIER_LENGTH,
  StatusCode,
  StatusResponseError,
  TlvByteString,
  TlvField,
  TlvNoResponse,
  TlvObject,
  TlvUInt16,
  TlvUInt32
} from "#types";
import { AdministratorCommissioningBehavior } from "./AdministratorCommissioningBehavior.js";
AdministratorCommissioning.Cluster.commands = {
  ...AdministratorCommissioning.Cluster.commands,
  openCommissioningWindow: Command(
    0,
    TlvObject({
      commissioningTimeout: TlvField(0, TlvUInt16),
      pakePasscodeVerifier: TlvField(1, TlvByteString),
      discriminator: TlvField(2, TlvUInt16.bound({ max: 4095 })),
      iterations: TlvField(3, TlvUInt32),
      salt: TlvField(4, TlvByteString)
    }),
    0,
    TlvNoResponse,
    {
      invokeAcl: AccessLevel.Administer,
      timed: true
    }
  )
};
const logger = Logger.get("AdministratorCommissioningServer");
class AdministratorCommissioningServer extends AdministratorCommissioningBehavior {
  /**
   * This method opens an Enhanced Commissioning Window (A dynamic passcode is used which was provided by the caller).
   */
  async openCommissioningWindow({
    pakePasscodeVerifier,
    discriminator,
    iterations,
    salt,
    commissioningTimeout
  }) {
    if (pakePasscodeVerifier.length !== PAKE_PASSCODE_VERIFIER_LENGTH) {
      throw new StatusResponseError(
        "PAKE Passcode verifier length is invalid.",
        StatusCode.Failure,
        AdministratorCommissioning.StatusCode.PakeParameterError
      );
    }
    if (iterations < 1e3 || iterations > 1e5) {
      throw new StatusResponseError(
        "PAKE iterations invalid.",
        StatusCode.Failure,
        AdministratorCommissioning.StatusCode.PakeParameterError
      );
    }
    if (salt.length < 16 || salt.length > 32) {
      throw new StatusResponseError(
        "PAKE salt has invalid length.",
        StatusCode.Failure,
        AdministratorCommissioning.StatusCode.PakeParameterError
      );
    }
    const commissioner = this.env.get(DeviceCommissioner);
    this.#assertCommissioningWindowRequirements(commissioningTimeout, commissioner);
    this.#initializeCommissioningWindow(
      commissioningTimeout,
      AdministratorCommissioning.CommissioningWindowStatus.EnhancedWindowOpen
    );
    await this.env.get(DeviceCommissioner).allowEnhancedCommissioning(
      discriminator,
      PaseServer.fromVerificationValue(this.env.get(SessionManager), pakePasscodeVerifier, {
        iterations,
        salt
      }),
      this.callback(this.#endCommissioning)
    );
  }
  /** This method opens a Basic Commissioning Window. The default passcode is used. */
  async openBasicCommissioningWindow({
    commissioningTimeout
  }) {
    const commissioner = this.env.get(DeviceCommissioner);
    this.#assertCommissioningWindowRequirements(commissioningTimeout, commissioner);
    this.#initializeCommissioningWindow(
      commissioningTimeout,
      AdministratorCommissioning.CommissioningWindowStatus.BasicWindowOpen
    );
    await commissioner.allowBasicCommissioning(this.callback(this.#endCommissioning));
  }
  /** This method is used to revoke a commissioning window. */
  async revokeCommissioning() {
    if (this.internal.commissioningWindowTimeout === void 0) {
      throw new StatusResponseError(
        "No commissioning window is opened that could be revoked.",
        StatusCode.Failure,
        AdministratorCommissioning.StatusCode.WindowNotOpen
      );
    }
    logger.debug("Revoking commissioning window.");
    await this.#closeCommissioningWindow();
    if (this.env.has(FailsafeContext)) {
      const failsafeContext = this.env.get(FailsafeContext);
      if (failsafeContext) {
        await failsafeContext.close();
      }
    }
  }
  /**
   * Called whenever a Commissioning/Announcement Window is opened by this cluster. This method starts the timer and
   * adjusts the needed attributes.
   */
  #initializeCommissioningWindow(commissioningTimeout, windowStatus) {
    if (this.internal.commissioningWindowTimeout !== void 0) {
      throw new InternalError("Commissioning window already initialized.");
    }
    logger.debug(
      `Commissioning window timer started for ${commissioningTimeout} seconds for ${this.context.session?.name}.`
    );
    this.internal.commissioningWindowTimeout = Time.getTimer(
      "Commissioning timeout",
      commissioningTimeout * 1e3,
      this.callback(this.#commissioningTimeout)
    ).start();
    const adminFabric = this.session.associatedFabric;
    this.state.windowStatus = windowStatus;
    this.state.adminFabricIndex = adminFabric.fabricIndex;
    this.state.adminVendorId = adminFabric.rootVendorId;
    const removeCallback = this.callback(this.#fabricRemovedCallback);
    this.internal.stopMonitoringFabricForRemoval = () => {
      adminFabric.deleteRemoveCallback(removeCallback);
    };
    this.session.associatedFabric.addRemoveCallback(removeCallback);
  }
  /**
   * This method validates if a commissioning window can be opened and throws various exceptions in case of failures.
   */
  #assertCommissioningWindowRequirements(commissioningTimeout, commissioner) {
    if (this.internal.commissioningWindowTimeout !== void 0) {
      throw new StatusResponseError(
        "A commissioning window is already opened.",
        StatusCode.Failure,
        AdministratorCommissioning.StatusCode.Busy
      );
    }
    if (commissioningTimeout > MAXIMUM_COMMISSIONING_TIMEOUT_S) {
      throw new StatusResponseError(
        `Commissioning timeout must not exceed ${MAXIMUM_COMMISSIONING_TIMEOUT_S} seconds.`,
        StatusCode.InvalidCommand
      );
    }
    if (commissioningTimeout < MINIMUM_COMMISSIONING_TIMEOUT_S) {
      throw new StatusResponseError(
        `Commissioning timeout must not be lower then ${MINIMUM_COMMISSIONING_TIMEOUT_S} seconds.`,
        StatusCode.InvalidCommand
      );
    }
    if (commissioner.isFailsafeArmed) {
      throw new StatusResponseError(
        "Failsafe timer armed, assume commissioning in progress.",
        StatusCode.Failure,
        AdministratorCommissioning.StatusCode.Busy
      );
    }
  }
  /**
   * This method is used internally when the commissioning window timer expires or the commissioning was completed.
   */
  #endCommissioning() {
    logger.debug("End commissioning window.");
    if (this.internal.commissioningWindowTimeout !== void 0) {
      this.internal.commissioningWindowTimeout.stop();
      this.internal.commissioningWindowTimeout = void 0;
    }
    this.internal.stopMonitoringFabricForRemoval?.();
    this.state.adminFabricIndex = null;
    this.state.windowStatus = AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen;
    this.state.adminFabricIndex = null;
    this.state.adminVendorId = null;
  }
  /**
   * Closes the commissioning window per the matter specification.
   */
  async #closeCommissioningWindow() {
    this.callback(this.#endCommissioning);
    await this.env.get(DeviceCommissioner).endCommissioning();
  }
  /**
   * Close commissioning window on timeout when there's nobody to await the resulting promise
   * */
  #commissioningTimeout() {
    this.env.runtime.add(this.#closeCommissioningWindow());
  }
  /**
   * Invoked when fabric is removed.
   */
  #fabricRemovedCallback() {
    this.state.adminFabricIndex = null;
    this.internal.stopMonitoringFabricForRemoval?.();
  }
  /**
   * Clean up resources and stop the timer when the behavior is destroyed.
   */
  [Symbol.asyncDispose]() {
    if (this.internal.commissioningWindowTimeout !== void 0) {
      this.internal.commissioningWindowTimeout.stop();
      this.internal.commissioningWindowTimeout = void 0;
    }
  }
}
((AdministratorCommissioningServer2) => {
  class Internal {
    commissioningWindowTimeout;
    stopMonitoringFabricForRemoval;
  }
  AdministratorCommissioningServer2.Internal = Internal;
  class State extends AdministratorCommissioningBehavior.State {
    // Spec doesn't declare a default here so set manually
    windowStatus = AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen;
  }
  AdministratorCommissioningServer2.State = State;
})(AdministratorCommissioningServer || (AdministratorCommissioningServer = {}));
export {
  AdministratorCommissioningServer
};
//# sourceMappingURL=AdministratorCommissioningServer.js.map
