"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
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 __using = (stack, value, async) => {
  if (value != null) {
    if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
    var dispose, inner;
    if (async) dispose = value[__knownSymbol("asyncDispose")];
    if (dispose === void 0) {
      dispose = value[__knownSymbol("dispose")];
      if (async) inner = dispose;
    }
    if (typeof dispose !== "function") __typeError("Object not disposable");
    if (inner) dispose = function() {
      try {
        inner.call(this);
      } catch (e) {
        return Promise.reject(e);
      }
    };
    stack.push([async, dispose, value]);
  } else if (async) {
    stack.push([async]);
  }
  return value;
};
var __callDispose = (stack, error, hasError) => {
  var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
    return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
  };
  var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
  var next = (it) => {
    while (it = stack.pop()) {
      try {
        var result = it[1] && it[1].call(it[2]);
        if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
      } catch (e) {
        fail(e);
      }
    }
    if (hasError) throw error;
  };
  return next();
};
var PeerSet_exports = {};
__export(PeerSet_exports, {
  NodeDiscoveryType: () => NodeDiscoveryType,
  PeerSet: () => PeerSet,
  UnknownNodeError: () => UnknownNodeError
});
module.exports = __toCommonJS(PeerSet_exports);
var import_Scanner = require("#common/Scanner.js");
var import_general = require("#general");
var import_PeerAddress = require("#peer/PeerAddress.js");
var import_errors = require("#protocol/errors.js");
var import_ExchangeManager = require("#protocol/ExchangeManager.js");
var import_ExchangeProvider = require("#protocol/ExchangeProvider.js");
var import_SecureChannelMessenger = require("#securechannel/SecureChannelMessenger.js");
var import_CaseClient = require("#session/case/CaseClient.js");
var import_session = require("#session/index.js");
var import_SecureSession = require("#session/SecureSession.js");
var import_SessionManager = require("#session/SessionManager.js");
var import_types = require("#types");
var import_ControllerDiscovery = require("./ControllerDiscovery.js");
var import_Peer = require("./Peer.js");
var import_PeerAddressStore = require("./PeerAddressStore.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("PeerSet");
const RECONNECTION_POLLING_INTERVAL = (0, import_general.Minutes)(10);
const RETRANSMISSION_DISCOVERY_TIMEOUT = (0, import_general.Seconds)(5);
var NodeDiscoveryType = /* @__PURE__ */ ((NodeDiscoveryType2) => {
  NodeDiscoveryType2[NodeDiscoveryType2["None"] = 0] = "None";
  NodeDiscoveryType2[NodeDiscoveryType2["RetransmissionDiscovery"] = 1] = "RetransmissionDiscovery";
  NodeDiscoveryType2[NodeDiscoveryType2["TimedDiscovery"] = 2] = "TimedDiscovery";
  NodeDiscoveryType2[NodeDiscoveryType2["FullDiscovery"] = 3] = "FullDiscovery";
  return NodeDiscoveryType2;
})(NodeDiscoveryType || {});
class UnknownNodeError extends import_general.MatterError {
}
class PeerSet {
  #lifetime;
  #sessions;
  #exchanges;
  #scanners;
  #transports;
  #caseClient;
  #peers = new import_general.BasicSet();
  #construction;
  #store;
  #disconnected = (0, import_general.AsyncObservable)();
  #peerContext;
  constructor(context) {
    const { lifetime, sessions, exchanges, scanners, transports: netInterfaces, store } = context;
    this.#lifetime = lifetime.join("peers");
    this.#sessions = sessions;
    this.#exchanges = exchanges;
    this.#scanners = scanners;
    this.#transports = netInterfaces;
    this.#store = store;
    this.#caseClient = new import_CaseClient.CaseClient(this.#sessions);
    this.#peerContext = {
      lifetime: this.#lifetime,
      sessions,
      savePeer: (peer) => this.#store.updatePeer(peer.descriptor),
      deletePeer: (peer) => this.#store.deletePeer(peer.address),
      closed: (peer) => this.#peers.delete(peer)
    };
    this.#peers.added.on((peer) => {
      peer.sessions.deleted.on(() => {
        if (!peer.sessions.size) {
          this.#disconnected.emit(peer);
        }
      });
    });
    this.#sessions.sessions.added.on((session) => this.#addOrUpdatePeer(session.peerAddress, session));
    this.#sessions.retry.on((session, count) => {
      if (count !== 1) {
        return;
      }
      this.#handleFirstRetry(session);
    });
    this.#construction = (0, import_general.Construction)(this, async () => {
      for (const descriptor of await this.#store.loadPeers()) {
        this.#peers.add(new import_Peer.Peer(descriptor, this.#peerContext));
      }
    });
  }
  get added() {
    return this.#peers.added;
  }
  get deleted() {
    return this.#peers.deleted;
  }
  get empty() {
    return this.#peers.empty;
  }
  get disconnected() {
    return this.#disconnected;
  }
  /**
   * Unconditional get.
   *
   * Creates the peer if not already present.
   */
  for(address) {
    let peer = this.get(address);
    if (peer) {
      return peer;
    }
    peer = new import_Peer.Peer({ address }, this.#peerContext);
    this.#peers.add(peer);
    return peer;
  }
  has(item) {
    return !!this.get(item);
  }
  get size() {
    return this.#peers.size;
  }
  find(predicate) {
    return this.#peers.find(predicate);
  }
  filter(predicate) {
    return this.#peers.filter(predicate);
  }
  map(mapper) {
    return this.#peers.map(mapper);
  }
  [Symbol.iterator]() {
    return this.#peers[Symbol.iterator]();
  }
  get construction() {
    return this.#construction;
  }
  static [import_general.Environmental.create](env) {
    const instance = new PeerSet({
      lifetime: env,
      sessions: env.get(import_SessionManager.SessionManager),
      exchanges: env.get(import_ExchangeManager.ExchangeManager),
      scanners: env.get(import_Scanner.ScannerSet),
      transports: env.get(import_general.ConnectionlessTransportSet),
      store: env.get(import_PeerAddressStore.PeerAddressStore)
    });
    env.set(PeerSet, instance);
    return instance;
  }
  get peers() {
    return this.#peers;
  }
  async connect(address, options) {
    await this.#ensureConnection(address, { ...options, allowUnknownPeer: true });
  }
  /**
   * Ensure there is a channel to the designated peer.
   */
  async #ensureConnection(address, options) {
    address = (0, import_PeerAddress.PeerAddress)(address);
    if (import_PeerAddress.PeerAddress.isGroup(address)) {
      return;
    }
    const { allowUnknownPeer, operationalAddress } = options;
    if (!this.has(address) && !allowUnknownPeer) {
      throw new UnknownNodeError(`Cannot connect to unknown device ${(0, import_PeerAddress.PeerAddress)(address)}`);
    }
    const peer = this.for(address);
    if (this.#sessions.maybeSessionFor(address)) {
      return;
    }
    if (peer.activeDiscovery?.type === 3 /* FullDiscovery */) {
      return;
    }
    const { promise: existingReconnectPromise } = peer.activeReconnection ?? {};
    if (existingReconnectPromise !== void 0) {
      if (options.discoveryOptions?.discoveryType !== 3 /* FullDiscovery */) {
        return existingReconnectPromise;
      } else {
        return;
      }
    }
    const { promise, resolver, rejecter } = (0, import_general.createPromise)();
    peer.activeReconnection = { promise, resolver, rejecter };
    this.#resume(address, options, operationalAddress).then((channel) => {
      if (peer.activeReconnection !== void 0) {
        peer.activeReconnection = void 0;
        resolver(channel);
      }
    }).catch((error) => {
      if (peer.activeReconnection !== void 0) {
        peer.activeReconnection = void 0;
        rejecter(error);
      }
    });
    return promise;
  }
  /**
   * Obtain an exchange provider for the designated peer.
   * TODO enhance PeerConnectionOptions.discoveryOptions.discoveryData with "addresses" for known operational addresses
   */
  async exchangeProviderFor(addressOrSession, options = {}) {
    if (addressOrSession instanceof import_SecureSession.SecureSession) {
      return new import_ExchangeProvider.DedicatedChannelExchangeProvider(this.#exchanges, addressOrSession);
    }
    const address = addressOrSession;
    if (import_PeerAddress.PeerAddress.isGroup(address)) {
      const session = await this.#sessions.groupSessionForAddress(address, this.#transports);
      return new import_ExchangeProvider.DedicatedChannelExchangeProvider(this.#exchanges, session);
    }
    const peer = this.for(address);
    let initiallyConnected = !!this.#sessions.maybeSessionFor(address);
    return new import_ExchangeProvider.ReconnectableExchangeProvider(this.#exchanges, this.#sessions, address, async (reconnOptions) => {
      const { asOf, resetInitialState } = reconnOptions ?? {};
      const { caseAuthenticatedTags, discoveryOptions } = options;
      const fullDiscoveryRunning = peer.activeDiscovery?.type === 3 /* FullDiscovery */;
      if (resetInitialState && !fullDiscoveryRunning) {
        initiallyConnected = !!this.#sessions.maybeSessionFor(address);
      }
      if (!initiallyConnected && !this.#sessions.maybeSessionFor(address) && !fullDiscoveryRunning) {
        const discoveryType = this.#getLastOperationalAddress(address) === void 0 ? 2 /* TimedDiscovery */ : 0 /* None */;
        await this.#ensureConnection(address, {
          discoveryOptions: {
            discoveryType,
            timeout: discoveryType === 2 /* TimedDiscovery */ ? RETRANSMISSION_DISCOVERY_TIMEOUT : void 0
          },
          caseAuthenticatedTags
        });
        initiallyConnected = true;
        if (this.#sessions.maybeSessionFor(address)) {
          return;
        }
      }
      if (!this.#sessions.maybeSessionFor(address) && discoveryOptions?.discoveryType !== 3 /* FullDiscovery */ && (!peer.activeDiscovery || peer.activeDiscovery.type === 3 /* FullDiscovery */)) {
        throw new import_errors.RetransmissionLimitReachedError(`Device ${(0, import_PeerAddress.PeerAddress)(address)} is unreachable`);
      }
      await this.#sessions.handlePeerLoss(address, asOf);
      if (this.#sessions.maybeSessionFor(address)) {
        return;
      }
      const { discoveryData } = discoveryOptions ?? {
        discoveryData: peer.descriptor.discoveryData
      };
      const operationalAddress = this.#knownOperationalAddressFor(address, true);
      if (operationalAddress === void 0) {
        logger.info(
          `Re-discovering device failed (no address found), remove all sessions for ${(0, import_PeerAddress.PeerAddress)(address)}`
        );
        throw new import_errors.RetransmissionLimitReachedError(`No operational address found for ${(0, import_PeerAddress.PeerAddress)(address)}`);
      }
      if (await this.#reconnectKnownAddress(address, operationalAddress, discoveryData, {
        expectedProcessingTime: (0, import_general.Seconds)(2)
      }) === void 0) {
        throw new import_errors.RetransmissionLimitReachedError(`${(0, import_PeerAddress.PeerAddress)(address)} is not reachable`);
      }
    });
  }
  /**
   * Retrieve a peer by address.
   */
  get(peer) {
    if ("address" in peer) {
      return this.#peers.get("address", (0, import_PeerAddress.PeerAddress)(peer.address));
    }
    return this.#peers.get("address", (0, import_PeerAddress.PeerAddress)(peer));
  }
  async close() {
    var _stack = [];
    try {
      const _closing = __using(_stack, this.#lifetime.closing());
      for (const peer of this.#peers) {
        await peer.close();
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  /**
   * Resume a device connection and establish a CASE session that was previously paired with the controller. This
   * method will try to connect to the device using the previously used server address (if set). If that fails, the
   * device is discovered again using its operational instance details.
   * It returns the operational MessageChannel on success.
   */
  async #resume(address, options, tryOperationalAddress) {
    const { discoveryOptions: { discoveryType } = {} } = options;
    const operationalAddress = tryOperationalAddress ?? (discoveryType === 0 /* None */ ? this.#getLastOperationalAddress(address) : this.#knownOperationalAddressFor(address));
    const startTime = import_general.Time.nowMs;
    try {
      return await this.#connectOrDiscoverNode(address, operationalAddress, options);
    } catch (error) {
      if ((error instanceof import_ControllerDiscovery.DiscoveryError || error instanceof import_general.NoResponseTimeoutError) && this.has(address) && tryOperationalAddress === void 0) {
        logger.info(
          `Resume failed, remove all sessions for ${(0, import_PeerAddress.PeerAddress)(address)} before ${import_general.Timestamp.dateOf(startTime)}`
        );
        await this.#sessions.handlePeerLoss(address, startTime);
      }
      if (this.#sessions.maybeSessionFor(address) === void 0) {
        throw error;
      }
    }
  }
  async #connectOrDiscoverNode(address, operationalAddress, options) {
    address = (0, import_PeerAddress.PeerAddress)(address);
    const {
      discoveryOptions: {
        discoveryType: requestedDiscoveryType = 3 /* FullDiscovery */,
        timeout,
        discoveryData = this.get(address)?.descriptor.discoveryData
      } = {},
      caseAuthenticatedTags,
      queue
    } = options ?? {};
    if (timeout !== void 0 && requestedDiscoveryType !== 2 /* TimedDiscovery */) {
      throw new import_general.ImplementationError("Cannot set timeout without timed discovery.");
    }
    if (requestedDiscoveryType === 1 /* RetransmissionDiscovery */) {
      throw new import_general.ImplementationError("Cannot set retransmission discovery type.");
    }
    const mdnsScanner = this.#scanners.scannerFor(import_general.ChannelType.UDP);
    if (!mdnsScanner) {
      throw new import_general.ImplementationError("Cannot discover device without mDNS scanner.");
    }
    const peer = this.for(address);
    const existingDiscoveryDetails = peer.activeDiscovery ?? {
      type: 0 /* None */
    };
    if (existingDiscoveryDetails.type !== 0 /* None */ && existingDiscoveryDetails.type < requestedDiscoveryType) {
      mdnsScanner.cancelOperationalDeviceDiscovery(this.#sessions.fabricFor(address), address.nodeId);
      peer.activeDiscovery = void 0;
      existingDiscoveryDetails.type = 0 /* None */;
    }
    const { type: runningDiscoveryType, promises } = existingDiscoveryDetails;
    if (operationalAddress !== void 0 && (runningDiscoveryType === 0 /* None */ || requestedDiscoveryType === 0 /* None */)) {
      const knownDiscoveredDevice = mdnsScanner.getDiscoveredOperationalDevice(
        this.#sessions.fabricFor(address),
        address.nodeId
      );
      if (knownDiscoveredDevice === void 0 || knownDiscoveredDevice.addresses.some((addr) => import_general.ServerAddress.isEqual(addr, operationalAddress))) {
        const session = this.#sessions.maybeSessionFor(address);
        const queueSlot = await queue?.obtainSlot();
        if (queueSlot !== void 0) {
          const currentSession = this.#sessions.maybeSessionFor(address);
          if (currentSession?.isSecure && session !== currentSession) {
            queueSlot.close();
            return currentSession;
          }
        }
        try {
          const directReconnection = await this.#reconnectKnownAddress(
            address,
            operationalAddress,
            discoveryData,
            // Attempt to reconnect using the last known operational address and current discovery context
            { caseAuthenticatedTags }
          );
          if (directReconnection !== void 0) {
            return directReconnection;
          }
        } finally {
          queueSlot?.close();
        }
        if (requestedDiscoveryType === 0 /* None */) {
          throw new import_ControllerDiscovery.DiscoveryError(`${address} is not reachable right now.`);
        }
      } else {
        logger.info(address, "Formerly known address is no longer in the MDNS results, discard ...");
        operationalAddress = void 0;
      }
    }
    if (operationalAddress === void 0 && requestedDiscoveryType === 0 /* None */) {
      throw new import_ControllerDiscovery.DiscoveryError(`${address} has no known address and No discovery was requested.`);
    }
    if (promises !== void 0) {
      if (runningDiscoveryType > requestedDiscoveryType) {
        throw new import_ControllerDiscovery.DiscoveryError(`${address} is not reachable right now and discovery already running.`);
      } else {
        return await (0, import_general.anyPromise)(promises);
      }
    }
    const discoveryPromises = new Array();
    let reconnectionPollingTimer;
    let stopTimerFunc;
    const lastOperationalAddress = this.#getLastOperationalAddress(address);
    if (lastOperationalAddress !== void 0) {
      if (requestedDiscoveryType === 3 /* FullDiscovery */) {
        const { promise, resolver, rejecter } = (0, import_general.createPromise)();
        logger.debug(
          address,
          `Starting reconnection polling for ${import_general.ServerAddress.urlFor(lastOperationalAddress)} (interval ${import_general.Duration.format(RECONNECTION_POLLING_INTERVAL)})`
        );
        reconnectionPollingTimer = import_general.Time.getPeriodicTimer(
          "Controller reconnect",
          RECONNECTION_POLLING_INTERVAL,
          async () => {
            const queueSlot = await queue?.obtainSlot();
            try {
              logger.debug(
                address,
                `Polling for device at ${import_general.ServerAddress.urlFor(lastOperationalAddress)} ...`
              );
              const result = await this.#reconnectKnownAddress(
                address,
                lastOperationalAddress,
                discoveryData,
                { caseAuthenticatedTags }
              );
              if (result !== void 0 && reconnectionPollingTimer?.isRunning) {
                reconnectionPollingTimer?.stop();
                mdnsScanner.cancelOperationalDeviceDiscovery(
                  this.#sessions.fabricFor(address),
                  address.nodeId
                );
                peer.activeDiscovery = void 0;
                resolver(result);
              }
            } catch (error) {
              if (reconnectionPollingTimer?.isRunning) {
                reconnectionPollingTimer?.stop();
                mdnsScanner.cancelOperationalDeviceDiscovery(
                  this.#sessions.fabricFor(address),
                  address.nodeId
                );
                peer.activeDiscovery = void 0;
                rejecter(error);
              }
            } finally {
              queueSlot?.close();
            }
          }
        ).start();
        stopTimerFunc = () => {
          reconnectionPollingTimer?.stop();
          reconnectionPollingTimer = void 0;
          rejecter(new import_general.NoResponseTimeoutError(`${address} Reconnection polling cancelled`));
        };
        discoveryPromises.push(() => promise);
      }
    }
    discoveryPromises.push(async () => {
      const scanResult = await import_ControllerDiscovery.ControllerDiscovery.discoverOperationalDevice(
        this.#sessions.fabricFor(address),
        address.nodeId,
        mdnsScanner,
        timeout,
        timeout === void 0
      );
      if (peer.activeDiscovery === void 0) {
        const session = this.#sessions.maybeSessionFor(address);
        if (session !== void 0) {
          return session;
        }
        throw new import_general.NoResponseTimeoutError(`${address} Discovery was cancelled but we have no session, retry`);
      }
      const { stopTimerFunc: stopTimerFunc2 } = peer.activeDiscovery;
      stopTimerFunc2?.();
      peer.activeDiscovery = void 0;
      const { result } = await import_ControllerDiscovery.ControllerDiscovery.iterateServerAddresses(
        [scanResult],
        import_general.NoResponseTimeoutError,
        async () => {
          const device = mdnsScanner.getDiscoveredOperationalDevice(
            this.#sessions.fabricFor(address),
            address.nodeId
          );
          return device !== void 0 ? [device] : [];
        },
        async (operationalAddress2, peer2) => {
          const session = this.#sessions.maybeSessionFor(address);
          if (session !== void 0) {
            return session;
          }
          const peerData = {
            ...discoveryData,
            ...peer2
          };
          const queueSlot = await queue?.obtainSlot();
          try {
            const session2 = await this.#pair(address, operationalAddress2, peerData, {
              caseAuthenticatedTags
            });
            this.#addOrUpdatePeer(address, session2, peerData);
            return session2;
          } finally {
            queueSlot?.close();
          }
        }
      );
      return result;
    });
    peer.activeDiscovery = {
      type: requestedDiscoveryType,
      promises: discoveryPromises,
      stopTimerFunc,
      mdnsClient: mdnsScanner
    };
    return await (0, import_general.anyPromise)(discoveryPromises).finally(() => {
      peer.activeDiscovery = void 0;
    });
  }
  async #reconnectKnownAddress(address, operationalAddress, discoveryData, options) {
    address = (0, import_PeerAddress.PeerAddress)(address);
    const { expectedProcessingTime } = options ?? {};
    const startTime = import_general.Time.nowMs;
    try {
      logger.debug(
        `Resuming connection to ${address} at ${import_general.ServerAddress.urlFor(operationalAddress)}${expectedProcessingTime !== void 0 ? ` with expected processing time of ${import_general.Duration.format(expectedProcessingTime)}` : ""}`
      );
      const session = await this.#pair(address, operationalAddress, discoveryData, options);
      this.#addOrUpdatePeer(address, session, discoveryData);
      return session;
    } catch (error) {
      if (error instanceof import_general.NoResponseTimeoutError || error instanceof import_SecureChannelMessenger.ChannelStatusResponseError || error instanceof import_general.AbortedError) {
        logger.debug(
          `Failed to resume connection to ${address} with ${import_general.ServerAddress.urlFor(operationalAddress)}, discovering the node now:`,
          error.message ? error.message : error
        );
        await this.#sessions.handlePeerLoss(address, startTime);
        return this.#sessions.maybeSessionFor(address);
      } else {
        throw error;
      }
    }
  }
  /** Pair with an operational device (already commissioned) and establish a CASE session. */
  async #pair(address, operationalServerAddress, discoveryData, options) {
    logger.debug(`Pair with ${address} at ${import_general.ServerAddress.urlFor(operationalServerAddress)}`);
    const { ip, port } = operationalServerAddress;
    const isIpv6Address = (0, import_general.isIPv6)(ip);
    const operationalInterface = this.#transports.interfaceFor(import_general.ChannelType.UDP, isIpv6Address ? "::" : "0.0.0.0");
    if (operationalInterface === void 0) {
      throw new import_ControllerDiscovery.PairRetransmissionLimitReachedError(
        `IPv${isIpv6Address ? "6" : "4"} interface not initialized for port ${port}. Cannot use ${ip} for pairing.`
      );
    }
    const operationalChannel = await operationalInterface.openChannel(operationalServerAddress);
    const { sessionParameters } = this.#sessions.findResumptionRecordByAddress(address) ?? {};
    const idleInterval = discoveryData?.SII ?? sessionParameters?.idleInterval;
    const activeInterval = discoveryData?.SAI ?? sessionParameters?.activeInterval;
    const activeThreshold = discoveryData?.SAT ?? sessionParameters?.activeThreshold;
    const mergedSessionParameters = {
      ...sessionParameters,
      ...idleInterval !== void 0 ? { idleInterval } : {},
      ...activeInterval !== void 0 ? { activeInterval } : {},
      ...activeThreshold !== void 0 ? { activeThreshold } : {}
    };
    const unsecuredSession = this.#sessions.createUnsecuredSession({
      channel: operationalChannel,
      // Use the session parameters from MDNS announcements when available and rest is assumed to be fall-backs
      sessionParameters: mergedSessionParameters,
      isInitiator: true
    });
    try {
      return await this.#doCasePair(unsecuredSession, address, options);
    } catch (error) {
      import_general.NoResponseTimeoutError.accept(error);
      const currentSession = this.#sessions.maybeSessionFor(address);
      if (currentSession !== void 0) {
        return currentSession;
      }
      throw new import_ControllerDiscovery.PairRetransmissionLimitReachedError(error.message);
    } finally {
      await unsecuredSession.initiateClose();
    }
  }
  async #doCasePair(paseSession, address, options) {
    const fabric = this.#sessions.fabricFor(address);
    let exchange;
    try {
      exchange = this.#exchanges.initiateExchangeForSession(paseSession, import_types.SECURE_CHANNEL_PROTOCOL_ID);
      const { session } = await this.#caseClient.pair(exchange, fabric, address.nodeId, options);
      return session;
    } catch (error) {
      await exchange?.close();
      if (error instanceof import_SecureChannelMessenger.ChannelStatusResponseError && error.protocolStatusCode === import_types.SecureChannelStatusCode.NoSharedTrustRoots) {
        if (await this.#sessions.deleteResumptionRecord(fabric.addressOf(address.nodeId))) {
          logger.info(
            `Case client: Resumption record seems outdated for Fabric ${import_types.NodeId.strOf(fabric.nodeId)} (index ${fabric.fabricIndex}) and PeerNode ${import_types.NodeId.strOf(address.nodeId)}. Retrying pairing without resumption...`
          );
          return await this.#doCasePair(paseSession, address, options);
        }
      }
      throw error;
    }
  }
  /**
   * Obtain an operational address for a logical address from cache.
   */
  #knownOperationalAddressFor(address, ignoreDiscoveredAddresses = false) {
    const lastKnownAddress = this.#getLastOperationalAddress(address);
    if (lastKnownAddress !== void 0 && ignoreDiscoveredAddresses) {
      return lastKnownAddress;
    }
    const mdnsScanner = this.#scanners.scannerFor(import_general.ChannelType.UDP);
    const discoveredAddresses = mdnsScanner?.getDiscoveredOperationalDevice(
      this.#sessions.fabricFor(address),
      address.nodeId
    );
    if (lastKnownAddress !== void 0 && discoveredAddresses !== void 0 && discoveredAddresses.addresses.some(
      ({ ip, port }) => ip === lastKnownAddress.ip && port === lastKnownAddress.port
    )) {
      discoveredAddresses.addresses.length = 0;
    }
    return discoveredAddresses?.addresses[0];
  }
  #addOrUpdatePeer(address, session, discoveryData) {
    let peer = this.get(address);
    if (peer === void 0) {
      peer = new import_Peer.Peer({ address }, this.#peerContext);
      this.#peers.add(peer);
    }
    if (session !== void 0 && !session.isClosed && import_session.NodeSession.is(session)) {
      peer.sessions.add(session);
    }
    if (discoveryData !== void 0) {
      peer.descriptor.discoveryData = {
        ...peer.descriptor.discoveryData,
        ...discoveryData
      };
    }
    if (peer.descriptor.operationalAddress !== void 0 && peer.activeDiscovery) {
      logger.info(`Found ${address} during discovery, cancel discovery.`);
      const { mdnsClient: mdnsScanner } = peer.activeDiscovery ?? {};
      mdnsScanner?.cancelOperationalDeviceDiscovery(this.#sessions.fabricFor(address), address.nodeId, true);
    }
  }
  addKnownPeer(address, operationalServerAddress, discoveryData) {
    this.#addOrUpdatePeer(address, void 0, discoveryData);
    if (operationalServerAddress !== void 0) {
      const peer = this.get(address);
      if (peer !== void 0) {
        peer.descriptor.operationalAddress = operationalServerAddress;
      }
    }
  }
  #getLastOperationalAddress(address) {
    return this.get(address)?.descriptor.operationalAddress;
  }
  #handleFirstRetry(session) {
    if (!session.isSecure || session.fabric === void 0) {
      return;
    }
    const { associatedFabric: fabric, peerNodeId: nodeId } = session;
    if (fabric === void 0 || nodeId === void 0) {
      return;
    }
    const address = fabric.addressOf(nodeId);
    const peer = this.for(address);
    if (peer.activeDiscovery) {
      return;
    }
    peer.activeDiscovery = { type: 1 /* RetransmissionDiscovery */ };
    this.#scanners.scannerFor(import_general.ChannelType.UDP)?.findOperationalDevice(fabric, nodeId, RETRANSMISSION_DISCOVERY_TIMEOUT, true).then((device) => {
      if (device === void 0 || session.isClosed || !(0, import_general.isIpNetworkChannel)(session.channel)) {
        return;
      }
      const currentAddress = session.channel.networkAddress;
      const { addresses } = device;
      if (!addresses.length || addresses.some((found) => import_general.ServerAddress.isEqual(currentAddress, found))) {
        return;
      }
      session.channel.networkAddress = addresses[0];
    }).catch((error) => {
      logger.error(`Failed to discover ${address} after resubmission started.`, error);
    }).finally(() => {
      if (peer.activeDiscovery?.type === 1 /* RetransmissionDiscovery */) {
        peer.activeDiscovery = void 0;
      }
    });
  }
}
//# sourceMappingURL=PeerSet.js.map
