"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 OtaSoftwareUpdateProviderServer_exports = {};
__export(OtaSoftwareUpdateProviderServer_exports, {
  OtaSoftwareUpdateConsentState: () => OtaSoftwareUpdateConsentState,
  OtaSoftwareUpdateProviderServer: () => OtaSoftwareUpdateProviderServer
});
module.exports = __toCommonJS(OtaSoftwareUpdateProviderServer_exports);
var import_SoftwareUpdateManager = require("#behavior/system/software-update/SoftwareUpdateManager.js");
var import_access_control = require("#behaviors/access-control");
var import_descriptor = require("#behaviors/descriptor");
var import_access_control2 = require("#clusters/access-control");
var import_ota_software_update_provider = require("#clusters/ota-software-update-provider");
var import_ota_software_update_requestor = require("#clusters/ota-software-update-requestor");
var import_general = require("#general");
var import_Node = require("#node/Node.js");
var import_protocol = require("#protocol");
var import_OtaSoftwareUpdateProviderBehavior = require("./OtaSoftwareUpdateProviderBehavior.js");
/**
 * @license
 * Copyright 2022-2025 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("OtaSoftwareUpdateProviderServer");
const OTA_UPDATE_TOKEN_LENGTH_BYTES = 32;
var OtaSoftwareUpdateConsentState = /* @__PURE__ */ ((OtaSoftwareUpdateConsentState2) => {
  OtaSoftwareUpdateConsentState2[OtaSoftwareUpdateConsentState2["Granted"] = 0] = "Granted";
  OtaSoftwareUpdateConsentState2[OtaSoftwareUpdateConsentState2["Denied"] = 1] = "Denied";
  OtaSoftwareUpdateConsentState2[OtaSoftwareUpdateConsentState2["Obtaining"] = 2] = "Obtaining";
  OtaSoftwareUpdateConsentState2[OtaSoftwareUpdateConsentState2["Unknown"] = 3] = "Unknown";
  return OtaSoftwareUpdateConsentState2;
})(OtaSoftwareUpdateConsentState || {});
class OtaSoftwareUpdateProviderServer extends import_OtaSoftwareUpdateProviderBehavior.OtaSoftwareUpdateProviderBehavior {
  async initialize() {
    (await this.agent.load(import_descriptor.DescriptorServer)).addDeviceTypes("OtaProvider");
    this.agent.require(import_SoftwareUpdateManager.SoftwareUpdateManager);
    await this.agent.load(import_SoftwareUpdateManager.SoftwareUpdateManager);
    const node = import_Node.Node.forEndpoint(this.endpoint);
    this.reactTo(node.lifecycle.online, this.#nodeOnline);
    if (node.lifecycle.isOnline) {
      await this.#nodeOnline();
    }
  }
  get updateStorage() {
    if (this.internal.updateStorage === void 0) {
      this.internal.updateStorage = this.agent.get(import_SoftwareUpdateManager.SoftwareUpdateManager).storage;
    }
    return this.internal.updateStorage;
  }
  async #nodeOnline() {
    const fabricAuthority = this.env.get(import_protocol.FabricAuthority);
    const ownFabric = fabricAuthority.fabrics[0];
    if (!ownFabric) {
      logger.error(`No owning fabric, delay initialization`);
      fabricAuthority.fabricAdded.once(() => this.#nodeOnline());
      return;
    }
    const node = import_Node.Node.forEndpoint(this.endpoint);
    await node.act((agent) => agent.load(import_access_control.AccessControlServer));
    if (node.behaviors.has(import_access_control.AccessControlServer)) {
      if (!node.stateOf(import_access_control.AccessControlServer).acl.some(
        ({ fabricIndex, privilege, authMode, subjects, targets }) => fabricIndex === ownFabric.fabricIndex && privilege === import_access_control2.AccessControl.AccessControlEntryPrivilege.Operate && authMode === import_access_control2.AccessControl.AccessControlEntryAuthMode.Case && subjects?.length === 0 && targets?.length === 1 && targets[0].endpoint === this.endpoint.number && targets[0].cluster === import_ota_software_update_provider.OtaSoftwareUpdateProvider.Cluster.id
      )) {
        const acl = [
          ...node.stateOf(import_access_control.AccessControlServer).acl,
          {
            fabricIndex: ownFabric.fabricIndex,
            privilege: import_access_control2.AccessControl.AccessControlEntryPrivilege.Operate,
            authMode: import_access_control2.AccessControl.AccessControlEntryAuthMode.Case,
            subjects: [],
            targets: [{ endpoint: this.endpoint.number, cluster: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Cluster.id }]
          }
        ];
        await node.setStateOf(import_access_control.AccessControlServer, { acl });
        logger.info(`Added ACL entry to allow access to OTA Software Update Provider for anyone in the fabric`);
      }
    }
  }
  /**
   * Default implementation of the QueryImage command according to Matter specification.
   */
  async queryImage(request) {
    const { protocolsSupported } = request;
    (0, import_protocol.assertRemoteActor)(this.context);
    const session = this.context.session;
    import_protocol.NodeSession.assert(session);
    const peerAddress = session.peerAddress;
    const updateDetails = await this.checkUpdateAvailable(request, peerAddress);
    if (updateDetails === void 0) {
      return {
        status: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Status.NotAvailable
      };
    }
    const {
      fileDesignator,
      httpsUri,
      newSoftwareVersion,
      newSoftwareVersionString,
      consentRequired,
      metadataForRequestor
    } = updateDetails;
    const updateInProgress = this.#inProgressDetailsForPeer(peerAddress);
    if (updateInProgress !== void 0) {
      logger.info(
        `OTA Update for Requestor`,
        peerAddress,
        `already in progress (${import_ota_software_update_requestor.OtaSoftwareUpdateRequestor.UpdateState[updateInProgress.lastState]})`
      );
      return {
        status: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Status.Busy,
        delayedActionTime: import_general.Seconds.of((0, import_general.Minutes)(5))
        // the usual bdx session timeout is 5 minutes, so let's use this
      };
    }
    const crypto = this.env.get(import_general.Crypto);
    const updateToken = crypto.randomBytes(OTA_UPDATE_TOKEN_LENGTH_BYTES);
    if (consentRequired && !request.requestorCanConsent) {
      this.#updateInProgressDetails(peerAddress, updateToken, import_SoftwareUpdateManager.OtaUpdateStatus.WaitForConsent, newSoftwareVersion);
      const { consentState, delayTime = (0, import_general.Seconds)(120) } = await this.requestUserConsentForUpdate(
        request,
        updateDetails,
        peerAddress
      );
      logger.info(
        `OTA Update for Requestor`,
        peerAddress,
        `(${import_general.Bytes.toHex(updateToken)}) consent is ${OtaSoftwareUpdateConsentState[consentState]}`
      );
      switch (consentState) {
        case 0 /* Granted */:
          this.#updateInProgressDetails(
            peerAddress,
            updateToken,
            import_SoftwareUpdateManager.OtaUpdateStatus.Querying,
            newSoftwareVersion,
            true
          );
          break;
        case 2 /* Obtaining */:
          return {
            status: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Status.Busy,
            delayedActionTime: import_general.Seconds.of(delayTime)
          };
        case 1 /* Denied */:
        case 3 /* Unknown */:
        default:
          return {
            status: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Status.NotAvailable
          };
      }
    } else {
      this.#updateInProgressDetails(peerAddress, updateToken, import_SoftwareUpdateManager.OtaUpdateStatus.Querying, newSoftwareVersion);
    }
    let imageUri;
    if (protocolsSupported.includes(import_ota_software_update_provider.OtaSoftwareUpdateProvider.DownloadProtocol.BdxSynchronous)) {
      const bdxProtocol = this.env.get(import_protocol.BdxProtocol);
      if (!bdxProtocol.enablePeerForScope(peerAddress, this.updateStorage, {
        preferredDriverModes: [import_protocol.Flow.DriverMode.ReceiverDrive],
        // That's also the default but especially states for OTA, but let's set it explicitly
        messageTimeout: (0, import_general.Minutes)(5)
        // maxBlockSize 1024 (non-TCP), 8192 (TCP) - We support whatever the peer wants, so do not set that
      })) {
        logger.info("Could not enable Bdx");
        return {
          status: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Status.Busy,
          delayedActionTime: import_general.Seconds.of((0, import_general.Minutes)(5))
        };
      }
      bdxProtocol.sessionStarted.on((bdxSession, scope) => {
        if (scope !== this.updateStorage.scope || !import_protocol.PeerAddress.is(bdxSession.peerAddress, peerAddress)) {
          return;
        }
        this.#updateInProgressDetails(
          peerAddress,
          updateToken,
          import_SoftwareUpdateManager.OtaUpdateStatus.Downloading,
          newSoftwareVersion
        );
        bdxSession.progressFinished.on(
          () => this.#updateInProgressDetails(
            peerAddress,
            updateToken,
            import_SoftwareUpdateManager.OtaUpdateStatus.WaitForApply,
            newSoftwareVersion
          )
        );
      });
      imageUri = fileDesignator.asBdxUri(this.context.session.associatedFabric.rootNodeId);
    } else if (httpsUri !== void 0 && protocolsSupported.includes(import_ota_software_update_provider.OtaSoftwareUpdateProvider.DownloadProtocol.Https)) {
      imageUri = httpsUri;
    } else {
      return {
        status: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Status.DownloadProtocolNotSupported
      };
    }
    return {
      status: import_ota_software_update_provider.OtaSoftwareUpdateProvider.Status.UpdateAvailable,
      imageUri,
      softwareVersion: newSoftwareVersion,
      softwareVersionString: newSoftwareVersionString,
      updateToken,
      userConsentNeeded: consentRequired === true ? true : void 0,
      metadataForRequestor
    };
  }
  /**
   * Default implementation of the ApplyUpdate command according to Matter specification.
   * We always allow updated to be executed immediately by the device.
   */
  async applyUpdateRequest({
    updateToken,
    newVersion
  }) {
    (0, import_protocol.assertRemoteActor)(this.context);
    const session = this.context.session;
    import_protocol.NodeSession.assert(session);
    const progressInfo = this.#inProgressDetailsForPeer(session.peerAddress, updateToken);
    const bdxProtocol = this.env.get(import_protocol.BdxProtocol);
    try {
      await bdxProtocol.disablePeerForScope(
        session.peerAddress,
        this.updateStorage,
        progressInfo?.lastState !== import_SoftwareUpdateManager.OtaUpdateStatus.WaitForApply
        // Force close when not known as completed
      );
    } catch (error) {
      import_general.MatterError.accept(error);
      logger.info("Error while closing BDX session for OTA update apply request, continuing anyway:", error);
    }
    if (!this.#hasUpdateConsent(session.peerAddress, newVersion)) {
      this.#updateInProgressDetails(session.peerAddress, updateToken, import_SoftwareUpdateManager.OtaUpdateStatus.Unknown, newVersion);
      return {
        action: import_ota_software_update_provider.OtaSoftwareUpdateProvider.ApplyUpdateAction.Discontinue,
        delayedActionTime: (0, import_general.Minutes)(2)
      };
    }
    this.#updateInProgressDetails(session.peerAddress, updateToken, import_SoftwareUpdateManager.OtaUpdateStatus.Applying, newVersion);
    return {
      action: import_ota_software_update_provider.OtaSoftwareUpdateProvider.ApplyUpdateAction.Proceed,
      delayedActionTime: 0
      // Allow immediate update
    };
  }
  /**
   * Default implementation of the NotifyUpdateApplied command according to Matter specification.
   */
  notifyUpdateApplied({
    updateToken,
    softwareVersion
  }) {
    (0, import_protocol.assertRemoteActor)(this.context);
    const session = this.context.session;
    import_protocol.NodeSession.assert(session);
    this.#updateInProgressDetails(session.peerAddress, updateToken, import_SoftwareUpdateManager.OtaUpdateStatus.Done, softwareVersion);
  }
  /**
   * Extension method if the node wants to handle automatic user consent gathering itself. By default, it declines
   * the update request and informs the node of the available update.
   */
  requestUserConsentForUpdate(_request, _updateDetails, _peerAddress) {
    return { consentState: 3 /* Unknown */ };
  }
  /**
   * Override to customize how to check for available updates.
   * The default logic also validates the node details like vendorId and productId and the current version.
   * When the requestorCanConsent is true, we send the latest update we have also without a consent.
   * All additional data like hardware, location, and MetadataForProvider can be checked here for specific logic
   */
  checkUpdateAvailable(request, peerAddress) {
    return this.endpoint.act((agent) => agent.get(import_SoftwareUpdateManager.SoftwareUpdateManager).updateExistsFor(peerAddress, request));
  }
  #hasUpdateConsent(peerAddress, targetSoftwareVersion) {
    return !!this.#inProgressDetailsForPeer(peerAddress)?.directConsentObtained || this.endpoint.act((agent) => agent.get(import_SoftwareUpdateManager.SoftwareUpdateManager).hasConsent(peerAddress, targetSoftwareVersion));
  }
  /**
   * Retrieves the in-progress details for a specific peer based on the peer address and an optional update token.
   */
  #inProgressDetailsForPeer(peerAddress, updateToken) {
    const { fabricIndex, nodeId: requestorNodeId } = peerAddress;
    if (updateToken !== void 0) {
      const key = `${requestorNodeId}-${fabricIndex}-${import_general.Bytes.toHex(updateToken)}`;
      return this.internal.inProgressDetails.get(key);
    }
    for (const details of this.internal.inProgressDetails.values()) {
      if (details.requestorNodeId === requestorNodeId && details.fabricIndex === fabricIndex) {
        return details;
      }
    }
  }
  /**
   * Updates the details of an in-progress OTA update process for a specific requestor,
   * tracking the update's state and optionally the target version to apply.
   */
  #updateInProgressDetails(peerAddress, updateToken, lastState, versionToApply, directConsentObtained = false) {
    const { fabricIndex, nodeId: requestorNodeId } = peerAddress;
    const key = `${requestorNodeId}-${fabricIndex}-${import_general.Bytes.toHex(updateToken)}`;
    const origDetails = this.internal.inProgressDetails.get(key);
    const details = origDetails ?? {
      requestorNodeId,
      fabricIndex,
      lastState,
      timestamp: import_general.Time.nowMs,
      directConsentObtained
    };
    if (versionToApply !== void 0) {
      if (details.versionToApply !== void 0 && details.versionToApply !== versionToApply) {
        logger.warn(
          `OTA Update for Requestor`,
          peerAddress.toString(),
          `(${import_general.Bytes.toHex(updateToken)}) versionToApply changed from ${details.versionToApply} to ${versionToApply}`
        );
      }
      details.versionToApply = versionToApply;
    }
    details.directConsentObtained = directConsentObtained;
    logger.info(
      `OTA Update ${details.versionToApply !== void 0 ? `to version ${details.versionToApply} ` : ""}for Requestor`,
      peerAddress.toString(),
      `(${import_general.Bytes.toHex(updateToken)}) is now ${lastState}${origDetails === void 0 ? "" : ` (formerly ${origDetails.lastState})`}`
    );
    this.internal.inProgressDetails.set(key, details);
    this.endpoint.act(
      (agent) => agent.get(import_SoftwareUpdateManager.SoftwareUpdateManager).onOtaStatusChange(peerAddress, lastState, details.versionToApply)
    );
  }
}
((OtaSoftwareUpdateProviderServer2) => {
  class Internal {
    /** Keyed by the requestorNodeId+fabricIndex+updateToken */
    inProgressDetails = /* @__PURE__ */ new Map();
    updateStorage;
  }
  OtaSoftwareUpdateProviderServer2.Internal = Internal;
})(OtaSoftwareUpdateProviderServer || (OtaSoftwareUpdateProviderServer = {}));
//# sourceMappingURL=OtaSoftwareUpdateProviderServer.js.map
