/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Mark } from "#common/Mark.js";
import {
  Bytes,
  Diagnostic,
  ec,
  Logger,
  MatterFlowError,
  Seconds,
  Spake2p,
  Time,
  UnexpectedDataError
} from "#general";
import { NodeId, SECURE_CHANNEL_PROTOCOL_ID, SecureChannelStatusCode } from "#types";
import { ChannelStatusResponseError } from "../../securechannel/SecureChannelMessenger.js";
import { DEFAULT_PASSCODE_ID, PaseServerMessenger, SPAKE_CONTEXT } from "./PaseMessenger.js";
const { bytesToNumberBE } = ec;
const logger = Logger.get("PaseServer");
const PASE_PAIRING_TIMEOUT_MS = Seconds(60);
const PASE_COMMISSIONING_MAX_ERRORS = 20;
class MaximumPasePairingErrorsReachedError extends MatterFlowError {
}
class PaseServer {
  constructor(sessions, w0, L, pbkdfParameters) {
    this.sessions = sessions;
    this.w0 = w0;
    this.L = L;
    this.pbkdfParameters = pbkdfParameters;
  }
  id = SECURE_CHANNEL_PROTOCOL_ID;
  requiresSecureSession = false;
  #pairingTimer;
  #pairingMessenger;
  #pairingErrors = 0;
  #closed = false;
  static async fromPin(sessions, setupPinCode, pbkdfParameters) {
    const { w0, L } = await Spake2p.computeW0L(sessions.crypto, pbkdfParameters, setupPinCode);
    return new PaseServer(sessions, w0, L, pbkdfParameters);
  }
  static fromVerificationValue(sessions, verificationValue, pbkdfParameters) {
    const verificationData = Bytes.of(verificationValue);
    const w0 = bytesToNumberBE(verificationData.slice(0, 32));
    const L = verificationData.slice(32, 32 + 65);
    return new PaseServer(sessions, w0, L, pbkdfParameters);
  }
  async onNewExchange(exchange) {
    if (this.#closed) {
      logger.warn("Received new exchange but server is closed, ignoring exchange");
      return;
    }
    const paseSession = this.sessions.getPaseSession();
    if (paseSession !== void 0 && !paseSession.isClosing) {
      logger.info("Pairing already in progress (PASE session exists), ignoring new exchange");
    } else if (this.#pairingTimer?.isRunning) {
      logger.info("Pairing already in progress (PASE establishment timer running), ignoring new exchange");
    } else if (this.#pairingMessenger !== void 0) {
      logger.info("Already handling a pairing request, ignoring new exchange.");
    } else {
      const messenger = new PaseServerMessenger(exchange);
      try {
        this.#pairingMessenger = messenger;
        await this.handlePairingRequest(this.sessions.crypto, messenger.channel.channel);
      } catch (error) {
        this.#pairingErrors++;
        logger.error(
          `An error occurred during the PASE commissioning (${this.#pairingErrors}/${PASE_COMMISSIONING_MAX_ERRORS}):`,
          this.#pairingMessenger?.exchange.diagnostics,
          error
        );
        const sendError = !(error instanceof ChannelStatusResponseError);
        await this.cancelPairing(messenger, sendError);
        if (this.#pairingErrors >= PASE_COMMISSIONING_MAX_ERRORS) {
          throw new MaximumPasePairingErrorsReachedError(
            `Too many errors during PASE commissioning, aborting commissioning window`
          );
        }
      } finally {
        this.#pairingMessenger = void 0;
        exchange.session.detachChannel();
        await exchange.session.initiateClose();
      }
    }
  }
  async handlePairingRequest(crypto, channel) {
    const messenger = this.#pairingMessenger;
    logger.info("Received pairing request", Mark.INBOUND, Diagnostic.via(messenger.channelName));
    this.#pairingTimer = Time.getTimer(
      "PASE pairing timeout",
      PASE_PAIRING_TIMEOUT_MS,
      () => this.cancelPairing(messenger)
    ).start();
    const {
      requestPayload,
      request: {
        initiatorRandom,
        initiatorSessionParams,
        passcodeId,
        hasPbkdfParameters,
        initiatorSessionId: peerSessionId
      }
    } = await messenger.readPbkdfParamRequest();
    if (passcodeId !== DEFAULT_PASSCODE_ID) {
      throw new UnexpectedDataError(`Unsupported passcode ID ${passcodeId}.`);
    }
    const responderSessionId = await this.sessions.getNextAvailableSessionId();
    const responderRandom = crypto.randomBytes(32);
    const responderSessionParams = this.sessions.sessionParameters;
    if (initiatorSessionParams !== void 0) {
      messenger.channel.session.timingParameters = initiatorSessionParams;
    }
    const responsePayload = await messenger.sendPbkdfParamResponse({
      initiatorRandom,
      responderRandom,
      responderSessionId,
      pbkdfParameters: hasPbkdfParameters ? void 0 : this.pbkdfParameters,
      responderSessionParams
    });
    const spake2p = Spake2p.create(
      crypto,
      await crypto.computeHash([SPAKE_CONTEXT, requestPayload, responsePayload]),
      this.w0
    );
    const { x: X } = await messenger.readPasePake1();
    const Y = spake2p.computeY();
    const { Ke, hAY, hBX } = await spake2p.computeSecretAndVerifiersFromX(this.L, X, Y);
    await messenger.sendPasePake2({ y: Y, verifier: hBX });
    const { verifier } = await messenger.readPasePake3();
    if (!Bytes.areEqual(verifier, hAY)) {
      throw new UnexpectedDataError("Received incorrect key confirmation from the initiator.");
    }
    const session = await this.sessions.createSecureSession({
      channel,
      id: responderSessionId,
      fabric: void 0,
      peerNodeId: NodeId.UNSPECIFIED_NODE_ID,
      peerSessionId,
      sharedSecret: Ke,
      salt: new Uint8Array(0),
      isInitiator: false,
      isResumption: false,
      peerSessionParameters: initiatorSessionParams
    });
    logger.info(
      session.via,
      "New session with",
      Diagnostic.strong(messenger.channelName),
      messenger.exchange.diagnostics
    );
    await messenger.sendSuccess();
    await messenger.close();
    this.#pairingTimer?.stop();
    this.#pairingTimer = void 0;
  }
  async cancelPairing(messenger, sendError = true) {
    this.#pairingTimer?.stop();
    this.#pairingTimer = void 0;
    if (sendError) {
      await messenger.sendError(SecureChannelStatusCode.InvalidParam);
    }
    await messenger.close();
  }
  async close() {
    this.#closed = true;
    await this.#pairingMessenger?.close();
  }
}
export {
  MaximumPasePairingErrorsReachedError,
  PaseServer
};
//# sourceMappingURL=PaseServer.js.map
