"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 GroupKeyManagementServer_exports = {};
__export(GroupKeyManagementServer_exports, {
  GroupKeyManagementServer: () => GroupKeyManagementServer
});
module.exports = __toCommonJS(GroupKeyManagementServer_exports);
var import_general = require("@matter/general");
var import_model = require("@matter/model");
var import_protocol = require("@matter/protocol");
var import_types = require("@matter/types");
var import_group_key_management = require("@matter/types/clusters/group-key-management");
var import_GroupKeyManagementBehavior = require("./GroupKeyManagementBehavior.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("GroupKeyManagementServer");
const MAX_64BIT_TIME = BigInt("0xffffffffffffffff");
const GroupKeyManagementBase = import_GroupKeyManagementBehavior.GroupKeyManagementBehavior;
const groupKeySetStruct = GroupKeyManagementBase.schema.datatypes.require("GroupKeySetStruct");
const groupKeySetStructFS = groupKeySetStruct.extend(
  {
    name: "GroupKeySetStructFS"
  },
  (0, import_model.FieldElement)({ name: "FabricIndex", id: 254, type: "FabricIndex", conformance: "M" })
);
const schema = GroupKeyManagementBase.schema.extend(
  {},
  groupKeySetStructFS,
  (0, import_model.FieldElement)(
    {
      name: "groupKeySets",
      type: "list",
      quality: "N",
      access: "RW VM F",
      conformance: "M"
    },
    (0, import_model.FieldElement)({ name: "entry", type: "GroupKeySetStructFS" })
  )
);
class GroupKeyManagementServer extends GroupKeyManagementBase {
  static schema = schema;
  initialize() {
    if (this.features.cacheAndSync) {
      throw new import_general.ImplementationError("The CacheAndSync feature is provisional. Do not use it.");
    }
    if (this.features.groupcast) {
      throw new import_general.ImplementationError(
        "The Groupcast feature of GroupKeyManagement is provisional in Matter 1.6. Do not enable it."
      );
    }
    this.reactTo(this.events.groupKeyMap$Changing, this.#validateGroupKeyMap);
    this.reactTo(this.events.groupTable$Changing, this.#validateGroupTable);
    this.reactTo(this.events.groupKeyMap$Changed, this.#updateGroupKeyMap);
    const lifecycle = this.endpoint.lifecycle;
    this.reactTo(lifecycle.online, this.#online);
  }
  async #online() {
    if (this.state.maxGroupKeysPerFabric === 1 && this.state.maxGroupsPerFabric === 0) {
      let groupsFound = false;
      this.endpoint.visit((endpoint) => {
        if (!groupsFound && "groups" in endpoint.behaviors.supported) {
          groupsFound = true;
        }
      });
      if (groupsFound) {
        throw new import_general.ImplementationError(
          "One of the device types you use has a Groups cluster. Please adjust the Group Key Management cluster maximum defaults. You need to support groups management."
        );
      }
    }
    const fabrics = this.env.get(import_protocol.FabricManager);
    this.reactTo(fabrics.events.replaced, this.#handleFabricUpdate);
    if (this.state.groupKeySets.length) {
      const groupKeysForFabric = /* @__PURE__ */ new Map();
      const groupKeySets = (0, import_general.deepCopy)(this.state.groupKeySets);
      for (let i = 0; i < groupKeySets.length; i++) {
        const groupKeySet = groupKeySets[i];
        const fabricIndex = groupKeySet.fabricIndex;
        const keys = groupKeysForFabric.get(fabricIndex) ?? [];
        if (keys.length === 0 && !fabrics.has(fabricIndex)) {
          logger.warn(
            `Stored GroupKeySets for FabricIndex ${fabricIndex}, but no such fabric exists anymore. Cleaning up stale entries.`
          );
          groupKeySets.splice(i, 1);
          i--;
          continue;
        }
        keys.push(groupKeySet);
        groupKeysForFabric.set(fabricIndex, keys);
      }
      if (groupKeySets.length !== this.state.groupKeySets.length) {
        this.state.groupKeySets = groupKeySets;
      }
      for (const [fabricIndex, keys] of groupKeysForFabric.entries()) {
        const fabric = fabrics.for(fabricIndex);
        for (const groupKeySet of keys) {
          await fabric.groups.setFromGroupKeySet(groupKeySet);
        }
      }
    }
    if (this.state.groupKeyMap.length) {
      this.#updateGroupKeyMap(this.state.groupKeyMap);
    }
    if (this.state.groupTable.length) {
      for (const { fabricIndex, groupId, endpoints } of this.state.groupTable) {
        if (fabrics.has(fabricIndex)) {
          fabrics.for(fabricIndex).groups.endpoints.set(groupId, [...endpoints]);
        }
      }
    }
  }
  /** Handle the recreation (update) of a fabric, so we need to reinitialize the group key sets */
  async #handleFabricUpdate(fabric) {
    if (this.state.groupKeySets.length === 0) {
      return;
    }
    const fabricIndex = fabric.fabricIndex;
    const groupKeysForFabric = this.state.groupKeySets.filter(
      ({ fabricIndex: entryIndex }) => entryIndex === fabricIndex
    );
    for (const groupKeySet of groupKeysForFabric) {
      await fabric.groups.setFromGroupKeySet(groupKeySet);
    }
    if (this.state.groupKeyMap.length) {
      fabric.groups.groupKeyIdMap = new Map(
        this.state.groupKeyMap.filter(({ fabricIndex: entryIndex }) => entryIndex === fabricIndex).map(({ groupId, groupKeySetId }) => [groupId, groupKeySetId])
      );
    }
    if (this.state.groupTable.length) {
      const groupTable = this.state.groupTable.filter(
        ({ fabricIndex: entryIndex }) => entryIndex === fabricIndex
      );
      fabric.groups.endpoints.clear();
      for (const entry of groupTable) {
        fabric.groups.endpoints.set(entry.groupId, entry.endpoints);
      }
    }
  }
  #validateGroupKeyMap(groupKeyMap) {
    if (this.features.groupcast && (0, import_protocol.hasRemoteActor)(this.context)) {
      const fabricIndex = this.context.session?.fabric?.fabricIndex;
      if (fabricIndex !== void 0 && this.#isGroupcastAdoptedForFabric(fabricIndex)) {
        throw new import_types.StatusResponseError(
          "GroupKeyMap is read-only when GroupcastAdoption.GroupcastAdopted is true",
          import_types.Status.InvalidInState
        );
      }
    }
    const knownGroupKeys = /* @__PURE__ */ new Set();
    for (const keySetId of this.state.groupKeySets) {
      const { groupKeySetId, fabricIndex } = keySetId;
      const id = `${fabricIndex}-${groupKeySetId}`;
      knownGroupKeys.add(id);
    }
    const knownGroupIds = /* @__PURE__ */ new Set();
    const groupIdsPerFabric = new Array();
    for (const entry of groupKeyMap) {
      const { groupId, fabricIndex } = entry;
      if (!import_types.GroupId.isApplicationGroupId(groupId)) {
        throw new import_types.StatusResponseError(
          "Only operational GroupIds are allowed in GroupKeyMap",
          import_types.Status.InvalidAction
        );
      }
      groupIdsPerFabric[fabricIndex] = (groupIdsPerFabric[fabricIndex] ?? 0) + 1;
      const id = `${fabricIndex}-${groupId}`;
      if (knownGroupIds.has(id)) {
        throw new import_types.StatusResponseError(
          `Duplicate GroupId ${groupId} for FabricIndex ${fabricIndex}`,
          import_types.Status.ConstraintError
        );
      }
      knownGroupIds.add(id);
    }
    if (groupIdsPerFabric.some((count) => count > this.state.maxGroupsPerFabric)) {
      throw new import_types.StatusResponseError(
        `Too many groups per fabric, maximum is ${this.state.maxGroupsPerFabric}`,
        import_types.Status.ResourceExhausted
      );
    }
  }
  #validateGroupTable(groupTable) {
    const knownGroupIds = /* @__PURE__ */ new Set();
    for (const entry of groupTable) {
      const { groupId, fabricIndex, endpoints } = entry;
      if (groupId === 0) {
        throw new import_types.StatusResponseError(
          "GroupId 0 can not be used as operational group id",
          import_types.Status.ConstraintError
        );
      }
      const id = `${fabricIndex}-${groupId}`;
      if (knownGroupIds.has(id)) {
        throw new import_types.StatusResponseError(
          `Duplicate GroupId ${groupId} for FabricIndex ${fabricIndex}`,
          import_types.Status.ConstraintError
        );
      }
      knownGroupIds.add(id);
      const endpointIds = new Set(endpoints);
      if (endpointIds.size !== endpoints.length) {
        throw new import_types.StatusResponseError(
          `Duplicate endpoint IDs in GroupId ${groupId} for FabricIndex ${fabricIndex}`,
          import_types.Status.ConstraintError
        );
      }
    }
  }
  #updateGroupKeyMap(groupKeyMap, _oldMap, context) {
    if (context !== void 0 && (0, import_protocol.hasRemoteActor)(context)) {
      const fabric = context.session?.associatedFabric;
      const fabricIndex = fabric?.fabricIndex;
      if (fabric !== void 0 && fabricIndex !== void 0) {
        fabric.groups.groupKeyIdMap = new Map(
          groupKeyMap.filter(({ fabricIndex: entryIndex }) => entryIndex === fabricIndex).map(({ groupId, groupKeySetId }) => [groupId, groupKeySetId])
        );
        return;
      }
    }
    const fabrics = this.env.get(import_protocol.FabricManager);
    const fabricMaps = /* @__PURE__ */ new Map();
    for (const entry of groupKeyMap) {
      const { fabricIndex, groupId, groupKeySetId } = entry;
      const fabricMap = fabricMaps.get(fabricIndex) ?? /* @__PURE__ */ new Map();
      fabricMap.set(groupId, groupKeySetId);
      fabricMaps.set(fabricIndex, fabricMap);
    }
    for (const fabric of fabrics) {
      const fabricIndex = fabric.fabricIndex;
      const map = fabricMaps.get(fabricIndex);
      if (map === void 0) {
        continue;
      }
      fabric.groups.groupKeyIdMap = map;
    }
  }
  async keySetWrite({ groupKeySet }) {
    (0, import_protocol.assertRemoteActor)(this.context);
    const {
      groupKeySetId,
      epochKey0,
      epochKey1,
      epochKey2,
      epochStartTime0,
      epochStartTime1,
      epochStartTime2,
      groupKeySecurityPolicy,
      groupKeyMulticastPolicy = import_group_key_management.GroupKeyManagement.GroupKeyMulticastPolicy.PerGroupId
    } = groupKeySet;
    if (groupKeySet.epochStartTime0 === MAX_64BIT_TIME) {
      groupKeySet.epochStartTime0 = null;
      groupKeySet.epochKey0 = null;
    }
    if (groupKeySet.epochStartTime1 === MAX_64BIT_TIME) {
      groupKeySet.epochStartTime1 = null;
      groupKeySet.epochKey1 = null;
    }
    if (groupKeySet.epochStartTime2 === MAX_64BIT_TIME) {
      groupKeySet.epochStartTime2 = null;
      groupKeySet.epochKey2 = null;
    }
    if (epochKey0 === null || epochStartTime0 === null) {
      throw new import_types.StatusResponseError("EpochKey0 and EpochStartTime0 must be set", import_types.Status.InvalidCommand);
    }
    if (epochStartTime0 <= import_protocol.IPK_DEFAULT_EPOCH_START_TIME) {
      throw new import_types.StatusResponseError("EpochStartTime0 must not be 0", import_types.Status.InvalidCommand);
    }
    if (epochKey1 !== null && (epochStartTime1 === null || epochStartTime1 <= epochStartTime0)) {
      throw new import_types.StatusResponseError(
        "EpochStartTime1 must be set and greater than EpochStartTime0",
        import_types.Status.InvalidCommand
      );
    }
    if (epochKey1 === null && epochStartTime1 !== null) {
      throw new import_types.StatusResponseError("EpochKey1 must be set if EpochStartTime1 is set", import_types.Status.InvalidCommand);
    }
    if (epochKey2 !== null && epochKey1 === null) {
      throw new import_types.StatusResponseError("EpochKey1 must be set if EpochKey2 is set", import_types.Status.InvalidCommand);
    }
    if (epochKey2 !== null && (epochStartTime2 === null || epochStartTime1 === null || epochStartTime2 <= epochStartTime1)) {
      throw new import_types.StatusResponseError(
        "EpochStartTime2 must be set and greater than EpochStartTime1",
        import_types.Status.InvalidCommand
      );
    }
    if (epochKey2 === null && epochStartTime2 !== null) {
      throw new import_types.StatusResponseError("EpochKey2 must be set if EpochStartTime2 is set", import_types.Status.InvalidCommand);
    }
    if (groupKeySecurityPolicy !== import_group_key_management.GroupKeyManagement.GroupKeySecurityPolicy.TrustFirst) {
      throw new import_types.StatusResponseError("GroupKeySecurityPolicy must be TrustFirst", import_types.Status.InvalidCommand);
    }
    if (groupKeyMulticastPolicy !== import_group_key_management.GroupKeyManagement.GroupKeyMulticastPolicy.PerGroupId) {
      throw new import_types.StatusResponseError("GroupKeyMulticastPolicy must be PerGroupId", import_types.Status.InvalidCommand);
    }
    const fabric = this.context.session.associatedFabric;
    const fabricIndex = fabric.fabricIndex;
    const existingIndex = this.state.groupKeySets.findIndex(
      ({ groupKeySetId: entryId, fabricIndex: entryIndex }) => entryIndex === fabricIndex && entryId === groupKeySetId
    );
    if (existingIndex !== -1) {
      this.state.groupKeySets[existingIndex] = { ...groupKeySet, fabricIndex };
    } else {
      const keySetsOfFabric = this.state.groupKeySets.filter(({ fabricIndex: entryIndex }) => entryIndex === fabricIndex).length + 1;
      if (keySetsOfFabric >= this.state.maxGroupKeysPerFabric) {
        throw new import_types.StatusResponseError(
          `Too many group key sets for fabric ${fabricIndex}, maximum is ${this.state.maxGroupKeysPerFabric}`,
          import_types.Status.ResourceExhausted
        );
      }
      this.state.groupKeySets.push({ ...groupKeySet, fabricIndex });
    }
    await fabric.groups.setFromGroupKeySet(groupKeySet);
  }
  keySetRead({
    groupKeySetId
  }) {
    (0, import_protocol.assertRemoteActor)(this.context);
    const fabric = this.context.session.associatedFabric;
    const groupKeySet = fabric.groups.keySets.asGroupKeySet(groupKeySetId);
    if (groupKeySet === void 0) {
      throw new import_types.StatusResponseError(`GroupKeySet ${groupKeySetId} not found`, import_types.Status.NotFound);
    }
    return {
      groupKeySet: {
        ...groupKeySet,
        epochKey0: null,
        epochKey1: null,
        epochKey2: null
      }
    };
  }
  keySetRemove({ groupKeySetId }) {
    if (groupKeySetId === 0) {
      throw new import_types.StatusResponseError(`GroupKeySet ${groupKeySetId} cannot be removed`, import_types.Status.InvalidCommand);
    }
    (0, import_protocol.assertRemoteActor)(this.context);
    const fabric = this.context.session.associatedFabric;
    const fabricIndex = fabric.fabricIndex;
    const existingIndex = this.state.groupKeySets.findIndex(
      ({ groupKeySetId: entryId, fabricIndex: entryIndex }) => entryIndex === fabricIndex && entryId === groupKeySetId
    );
    if (existingIndex === -1) {
      throw new import_types.StatusResponseError(`GroupKeySet ${groupKeySetId} not found`, import_types.Status.NotFound);
    }
    this.state.groupKeySets.splice(existingIndex, 1);
    this.state.groupKeyMap = this.state.groupKeyMap.filter(
      (entry) => entry.fabricIndex !== fabricIndex || entry.groupKeySetId !== groupKeySetId
    );
    fabric.groups.removeGroupKeySet(groupKeySetId);
  }
  keySetReadAllIndices() {
    (0, import_protocol.assertRemoteActor)(this.context);
    const fabric = this.context.session.associatedFabric;
    const fabricIndex = fabric.fabricIndex;
    const groupKeySetIDs = this.state.groupKeySets.filter(({ fabricIndex: entryIndex }) => entryIndex === fabricIndex).map(({ groupKeySetId }) => groupKeySetId);
    groupKeySetIDs.unshift(0);
    return {
      groupKeySetIDs
    };
  }
  addEndpointForGroup(fabric, groupId, endpointId, groupName) {
    const fabricIndex = fabric.fabricIndex;
    const existingGroupIndex = this.state.groupTable.findIndex(
      (entry) => entry.groupId === groupId && entry.fabricIndex === fabricIndex
    );
    if (existingGroupIndex !== -1) {
      if (!this.state.groupTable[existingGroupIndex].endpoints.includes(endpointId)) {
        this.state.groupTable[existingGroupIndex].endpoints.push(endpointId);
        fabric.groups.endpoints.set(groupId, this.state.groupTable[existingGroupIndex].endpoints);
      }
      this.state.groupTable[existingGroupIndex].groupName = groupName;
    } else {
      if (this.state.groupTable.filter(({ fabricIndex: entryFabricIndex }) => entryFabricIndex === fabricIndex).length >= this.state.maxGroupsPerFabric) {
        throw new import_types.StatusResponseError(
          `Too many groups for fabric ${fabricIndex}, maximum is ${this.state.maxGroupsPerFabric}`,
          import_types.Status.ResourceExhausted
        );
      }
      this.state.groupTable.push({
        groupId,
        endpoints: [endpointId],
        groupName,
        fabricIndex
      });
      fabric.groups.endpoints.set(groupId, [endpointId]);
    }
    logger.info(
      `Added endpoint ${endpointId} to group ${groupId} on fabric ${fabricIndex} with name "${groupName}"`
    );
  }
  /**
   * Remove endpoint from the provided group, or all groups if no groupId is provided.
   */
  removeEndpoint(fabric, endpointId, groupId) {
    let existing = false;
    const groupTable = this.state.groupTable;
    const fabricIndex = fabric.fabricIndex;
    for (let i = groupTable.length - 1; i >= 0; i--) {
      const entry = groupTable[i];
      if (entry.fabricIndex !== fabricIndex || groupId !== void 0 && entry.groupId !== groupId) {
        continue;
      }
      const endpointExists = entry.endpoints.includes(endpointId);
      if (endpointExists) {
        if (entry.endpoints.length === 1 && entry.endpoints[0] === endpointId) {
          const groupId2 = entry.groupId;
          groupTable.splice(i, 1);
          fabric.groups.endpoints.delete(groupId2);
        } else {
          entry.endpoints = entry.endpoints.filter((id) => id !== endpointId);
          fabric.groups.endpoints.set(entry.groupId, entry.endpoints);
        }
        existing = true;
      }
    }
    return existing;
  }
  #isGroupcastAdoptedForFabric(fabricIndex) {
    if (!this.features.groupcast) return false;
    if (!this.state.groupcastAdoption) return false;
    return this.state.groupcastAdoption.some((entry) => entry.fabricIndex === fabricIndex && entry.groupcastAdopted);
  }
  /**
   * Returns whether a GroupKeySetId exists for a given fabric. Called by GroupcastServer to validate KeySetIDs.
   */
  validateKeySetId(fabricIndex, keySetId) {
    if (keySetId === 0) {
      return true;
    }
    return this.state.groupKeySets.some(
      (entry) => entry.fabricIndex === fabricIndex && entry.groupKeySetId === keySetId
    );
  }
  /**
   * Creates a new GroupKeySet for a Groupcast group key, bypassing normal KeySetWrite validation.
   * Used by GroupcastServer when JoinGroup or UpdateGroupKey is called with a `key` parameter.
   * The key set uses TrustFirst policy and epochStartTime0=IPK_DEFAULT_EPOCH_START_TIME (Matter epoch zero = Jan 1, 2000).
   *
   * @returns true on success, throws if the key set already exists or resources are exhausted
   */
  createKeySetForGroupcast(fabric, keySetId, epochKey0) {
    const fabricIndex = fabric.fabricIndex;
    const keySetsOfFabric = this.state.groupKeySets.filter(({ fabricIndex: fi }) => fi === fabricIndex).length + 1;
    if (keySetsOfFabric >= this.state.maxGroupKeysPerFabric) {
      throw new import_types.StatusResponseError(
        `Too many group key sets for fabric ${fabricIndex}`,
        import_types.Status.ResourceExhausted
      );
    }
    const groupKeySet = {
      groupKeySetId: keySetId,
      groupKeySecurityPolicy: import_group_key_management.GroupKeyManagement.GroupKeySecurityPolicy.TrustFirst,
      epochKey0,
      epochStartTime0: import_protocol.IPK_DEFAULT_EPOCH_START_TIME,
      // Matter epoch zero
      epochKey1: null,
      epochStartTime1: null,
      epochKey2: null,
      epochStartTime2: null,
      fabricIndex
    };
    this.state.groupKeySets.push(groupKeySet);
    return fabric.groups.setFromGroupKeySet(groupKeySet);
  }
  setGroupcastAdopted(fabricIndex, adopted) {
    if (!this.state.groupcastAdoption) return;
    const existing = this.state.groupcastAdoption.findIndex((e) => e.fabricIndex === fabricIndex);
    if (existing !== -1) {
      this.state.groupcastAdoption[existing] = {
        ...this.state.groupcastAdoption[existing],
        groupcastAdopted: adopted
      };
    } else {
      this.state.groupcastAdoption.push({ fabricIndex, groupcastAdopted: adopted });
    }
  }
}
((GroupKeyManagementServer2) => {
  class State extends GroupKeyManagementBase.State {
    /**
     * Extended state to hold and persist the group key sets for this server. This structure contains all
     * GroupKeySet entries for all fabrics beside the fabric specific entries of the groupKeyset 0
     */
    groupKeySets = [];
    // Overwrite defaults to allow more than 3 group keys and 4 groups per fabric, because we can
    maxGroupKeysPerFabric = 20;
    // The Minimum would be 3;
    maxGroupsPerFabric = 22;
  }
  GroupKeyManagementServer2.State = State;
})(GroupKeyManagementServer || (GroupKeyManagementServer = {}));
//# sourceMappingURL=GroupKeyManagementServer.js.map
