"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 ExchangeManager_exports = {};
__export(ExchangeManager_exports, {
  ExchangeCounter: () => ExchangeCounter,
  ExchangeManager: () => ExchangeManager
});
module.exports = __toCommonJS(ExchangeManager_exports);
var import_MessageCodec = require("#codec/MessageCodec.js");
var import_Mark = require("#common/Mark.js");
var import_SecureChannelMessenger = require("#securechannel/SecureChannelMessenger.js");
var import_NodeSession = require("#session/NodeSession.js");
var import_Session = require("#session/Session.js");
var import_SessionManager = require("#session/SessionManager.js");
var import_UnsecuredSession = require("#session/UnsecuredSession.js");
var import_general = require("@matter/general");
var import_types = require("@matter/types");
var import_MessageExchange = require("./MessageExchange.js");
var import_MessageReceptionState = require("./MessageReceptionState.js");
var import_MRP = require("./MRP.js");
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("ExchangeManager");
const MAXIMUM_CONCURRENT_OUTGOING_EXCHANGES_PER_SESSION = 30;
class ExchangeManager {
  #lifetime;
  #transports;
  #sessions;
  #exchangeCounter;
  #exchanges = /* @__PURE__ */ new Map();
  #protocols = /* @__PURE__ */ new Map();
  #listeners = /* @__PURE__ */ new Map();
  #workers;
  #observers = new import_general.ObserverGroup(this);
  #sessionObservers = /* @__PURE__ */ new Map();
  #isClosing = false;
  constructor(context) {
    this.#lifetime = context.lifetime.join("exchanges");
    this.#workers = new import_general.BasicMultiplex();
    this.#transports = context.transports;
    this.#sessions = context.sessions;
    this.#exchangeCounter = new ExchangeCounter(context.entropy);
    for (const netInterface of this.#transports) {
      this.#addTransport(netInterface);
    }
    this.#observers.on(this.#transports.added, this.#addTransport);
    this.#observers.on(this.#transports.deleted, this.#deleteTransport);
    this.#observers.on(this.#sessions.sessions.added, this.#addSession);
    this.#observers.on(this.#sessions.sessions.deleted, this.#deleteSession);
  }
  static [import_general.Environmental.create](env) {
    const instance = new ExchangeManager({
      lifetime: env,
      entropy: env.get(import_general.Entropy),
      transports: env.get(import_general.ConnectionlessTransportSet),
      sessions: env.get(import_SessionManager.SessionManager)
    });
    env.set(ExchangeManager, instance);
    return instance;
  }
  hasProtocolHandler(protocolId) {
    return this.#protocols.has(protocolId);
  }
  getProtocolHandler(protocolId) {
    return this.#protocols.get(protocolId);
  }
  addProtocolHandler(protocol) {
    if (this.hasProtocolHandler(protocol.id)) {
      throw new import_general.ImplementationError(`Handler for protocol ${protocol.id} already registered.`);
    }
    this.#protocols.set(protocol.id, protocol);
  }
  interfaceFor(type, address) {
    return this.#transports.interfaceFor(type, address);
  }
  hasInterfaceFor(type, address) {
    return this.#transports.hasInterfaceFor(type, address);
  }
  initiateExchange(address, protocolId) {
    return this.initiateExchangeForSession(this.#sessions.sessionFor(address), protocolId);
  }
  initiateExchangeForSession(session, protocolId, options) {
    const exchangeId = this.#exchangeCounter.getIncrementedCounter();
    const exchangeIndex = exchangeId | 65536;
    const exchange = import_MessageExchange.MessageExchange.initiate(
      this.#messageExchangeContextFor(session),
      exchangeId,
      protocolId,
      options
    );
    this.#addExchange(exchangeIndex, exchange);
    return exchange;
  }
  async close() {
    var _stack3 = [];
    try {
      if (this.#isClosing) {
        return;
      }
      const closing = __using(_stack3, this.#lifetime.closing());
      this.#isClosing = true;
      const exchangesClosed = new import_general.BasicMultiplex();
      for (const exchange of this.#exchanges.values()) {
        exchangesClosed.add(exchange.close(new import_SessionManager.ShutdownError("Exchange closed by node shutdown")));
      }
      {
        var _stack = [];
        try {
          const _closing = __using(_stack, closing.join("exchanges"));
          await exchangesClosed;
        } catch (_) {
          var _error = _, _hasError = true;
        } finally {
          __callDispose(_stack, _error, _hasError);
        }
      }
      for (const listener of this.#listeners.keys()) {
        this.#deleteTransport(listener);
      }
      for (const protocol of this.#protocols.values()) {
        this.#workers.add(protocol.close());
      }
      {
        var _stack2 = [];
        try {
          const _closing = __using(_stack2, closing.join("workers"));
          await this.#workers;
        } catch (_2) {
          var _error2 = _2, _hasError2 = true;
        } finally {
          __callDispose(_stack2, _error2, _hasError2);
        }
      }
      this.#exchanges.clear();
      this.#observers.close();
    } catch (_3) {
      var _error3 = _3, _hasError3 = true;
    } finally {
      __callDispose(_stack3, _error3, _hasError3);
    }
  }
  async #onMessage(channel, messageBytes) {
    var _stack = [];
    try {
      const _lifetime = __using(_stack, this.#lifetime.join("receiving from", import_general.Diagnostic.strong(channel.name)));
      const packet = import_MessageCodec.MessageCodec.decodePacket(messageBytes);
      const bytes = import_general.Bytes.of(messageBytes);
      const aad = bytes.slice(0, bytes.length - packet.applicationPayload.byteLength);
      const messageId = packet.header.messageId;
      let isDuplicate;
      let session;
      let message;
      if (packet.header.sessionType === import_MessageCodec.SessionType.Unicast) {
        if (packet.header.sessionId === import_UnsecuredSession.UNICAST_UNSECURE_SESSION_ID) {
          if (this.#isClosing) return;
          const initiatorNodeId = packet.header.destNodeId ?? packet.header.sourceNodeId ?? import_types.NodeId.UNSPECIFIED_NODE_ID;
          session = this.#sessions.getUnsecuredSession(initiatorNodeId);
          if (session === void 0) {
            if (packet.header.destNodeId !== void 0) {
              logger.debug(
                import_general.Diagnostic.via(
                  `${packet.header.sourceNodeId === void 0 ? "" : `@${(0, import_general.hex)(packet.header.sourceNodeId)}:`}${import_Mark.Mark.SESSION}unsecured#${import_general.hex.word(initiatorNodeId)}`
                ),
                `Ignoring unsecured response for unknown session`
              );
              return;
            }
            session = this.#sessions.createUnsecuredSession({
              channel,
              initiatorNodeId
            });
          }
        } else {
          session = this.#sessions.getSession(packet.header.sessionId);
        }
        if (session === void 0) {
          logger.warn(
            import_general.Diagnostic.via(
              `@${packet.header.sourceNodeId === void 0 ? "?" : (0, import_general.hex)(packet.header.sourceNodeId)}:?${import_Mark.Mark.SESSION}${import_Session.Session.idStrOf(packet)}`
            ),
            "Ignoring message for unknown session"
          );
          return;
        }
        message = session.decode(packet, aad);
        try {
          session.updateMessageCounter(messageId);
          isDuplicate = false;
        } catch (e) {
          import_MessageReceptionState.DuplicateMessageError.accept(e);
          isDuplicate = true;
        }
      } else if (packet.header.sessionType === import_MessageCodec.SessionType.Group) {
        if (this.#isClosing) return;
        if (packet.header.sourceNodeId === void 0) {
          throw new import_general.UnexpectedDataError("Group session message must include a source NodeId");
        }
        let key;
        ({ session, message, key } = this.#sessions.groupSessionFromPacket(packet, aad));
        try {
          session.updateMessageCounter(messageId, packet.header.sourceNodeId, key);
          isDuplicate = false;
        } catch (e) {
          import_MessageReceptionState.DuplicateMessageError.accept(e);
          isDuplicate = true;
        }
      } else {
        throw new import_general.MatterFlowError(`Unsupported session type: ${packet.header.sessionType}`);
      }
      const exchangeIndex = message.payloadHeader.isInitiatorMessage ? message.payloadHeader.exchangeId : message.payloadHeader.exchangeId | 65536;
      let exchange = this.#exchanges.get(exchangeIndex);
      if (exchange !== void 0 && (exchange.session.id !== session.id || exchange.isInitiator === message.payloadHeader.isInitiatorMessage)) {
        exchange = void 0;
      }
      const isStandaloneAck = import_types.SecureMessageType.isStandaloneAck(
        message.payloadHeader.protocolId,
        message.payloadHeader.messageType
      );
      const messageDiagnostics = import_general.Diagnostic.dict({
        message: import_general.hex.fixed(messageId, 8),
        protocol: message.payloadHeader.protocolId,
        exId: import_general.hex.word(message.payloadHeader.exchangeId),
        via: channel.name
      });
      if (exchange !== void 0) {
        try {
          this.#lifetime.details.exchange = exchange.idStr;
          if (exchange.session.id !== packet.header.sessionId || exchange.considerClosed && !isStandaloneAck) {
            logger.debug(
              exchange.via,
              "Ignore",
              import_Mark.Mark.INBOUND,
              "message because",
              exchange.considerClosed ? "exchange is closing" : `session ID mismatch (header session is ${import_Session.Session.idStrOf(packet)}`,
              messageDiagnostics
            );
            try {
              await exchange.sendStandaloneAckForMessage(message);
            } finally {
              await exchange.close();
            }
            return;
          }
          await exchange.onMessageReceived(message, isDuplicate);
        } catch (error) {
          this.#handleIncomingMessageError("message", error, exchange, message);
        }
      } else {
        if (this.#isClosing) return;
        if (session.isClosing) {
          logger.debug(`Declining new exchange because session ${import_Session.Session.idStrOf(packet)} is closing`);
          return;
        }
        const protocolHandler = this.#protocols.get(message.payloadHeader.protocolId);
        const handlerSecurityMismatch = protocolHandler?.requiresSecureSession !== void 0 && protocolHandler.requiresSecureSession !== session.isSecure;
        if (handlerSecurityMismatch) {
          logger.debug(
            "Ignore",
            import_Mark.Mark.INBOUND,
            `message because not matching the security requirements (${protocolHandler.requiresSecureSession} vs. ${session.isSecure})`,
            messageDiagnostics
          );
        }
        if (protocolHandler !== void 0 && message.payloadHeader.isInitiatorMessage && !isDuplicate && !handlerSecurityMismatch) {
          if (isStandaloneAck && !message.payloadHeader.requiresAck) {
            logger.debug("Ignore", import_Mark.Mark.INBOUND, "unsolicited standalone ack message", messageDiagnostics);
            return;
          }
          const exchange2 = import_MessageExchange.MessageExchange.fromInitialMessage(this.#messageExchangeContextFor(session), message);
          if (!(session instanceof import_UnsecuredSession.UnsecuredSession) && !session.isClosed) {
            session.channel.socket = channel;
          }
          this.#lifetime.details.exchange = exchange2.idStr;
          this.#addExchange(exchangeIndex, exchange2);
          try {
            await exchange2.onMessageReceived(message);
            await protocolHandler.onNewExchange(exchange2, message);
          } catch (error) {
            this.#handleIncomingMessageError("initial message", error, exchange2, message);
          }
        } else if (message.payloadHeader.requiresAck) {
          const exchange2 = import_MessageExchange.MessageExchange.fromInitialMessage(this.#messageExchangeContextFor(session), message);
          this.#lifetime.details.exchange = exchange2.idStr;
          this.#addExchange(exchangeIndex, exchange2);
          try {
            await exchange2.sendStandaloneAckForMessage(message);
            await exchange2.close();
            logger.debug("Ignore", import_Mark.Mark.INBOUND, "unsolicited message", messageDiagnostics);
          } catch (error) {
            this.#handleIncomingMessageError("unsolicited message", error, exchange2, message);
          }
        } else {
          if (protocolHandler === void 0) {
            throw new import_general.MatterFlowError(`Unsupported protocol ${message.payloadHeader.protocolId}`);
          }
          if (isDuplicate) {
            if (message.packetHeader.destGroupId === void 0) {
              logger.debug("Ignore", import_Mark.Mark.INBOUND, "duplicate message", messageDiagnostics);
            }
            return;
          }
          if (!isStandaloneAck) {
            logger.info(
              "Discard",
              import_Mark.Mark.INBOUND,
              "unexpected message",
              messageDiagnostics,
              import_general.Diagnostic.json(message)
            );
          }
        }
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      __callDispose(_stack, _error, _hasError);
    }
  }
  #handleIncomingMessageError(what, error, exchange, message) {
    if ((0, import_general.causedBy)(error, import_SessionManager.ShutdownError)) {
      logger.info(
        import_MessageCodec.Message.via(exchange, message),
        `Rejected incoming ${what}:`,
        import_general.Diagnostic.errorMessage((0, import_general.asError)(error))
      );
      return;
    }
    logger.error(import_MessageCodec.Message.via(exchange, message), "Unhandled error handling incoming message:", error);
  }
  deleteExchange(exchangeIndex) {
    this.#exchanges.delete(exchangeIndex);
  }
  #addExchange(exchangeIndex, exchange) {
    exchange.closed.on(() => this.deleteExchange(exchangeIndex));
    this.#exchanges.set(exchangeIndex, exchange);
    this.#cleanupSessionExchanges(exchange.session.id);
  }
  #cleanupSessionExchanges(sessionId) {
    if (sessionId === import_UnsecuredSession.UNICAST_UNSECURE_SESSION_ID) {
      return;
    }
    const sessionExchanges = Array.from(this.#exchanges.values()).filter(
      (exchange) => exchange.session.id === sessionId && !exchange.considerClosed
    );
    if (sessionExchanges.length <= MAXIMUM_CONCURRENT_OUTGOING_EXCHANGES_PER_SESSION) {
      return;
    }
    const exchangeToClose = sessionExchanges[0];
    logger.info(
      exchangeToClose.via,
      `Closing oldest exchange for session because of too many concurrent outgoing exchanges. Ensure to not send that many parallel messages to one peer.`
    );
    logger.debug(exchangeToClose.via, "Closing oldest exchange");
    this.#workers.add(exchangeToClose.close());
  }
  calculateMaximumPeerResponseTimeMsFor(session, expectedProcessingTime = import_MRP.MRP.DEFAULT_EXPECTED_PROCESSING_TIME, includeMaximumSendingTime = false) {
    return session.channel.calculateMaximumPeerResponseTime(
      session.parameters,
      this.#sessions.sessionParameters,
      expectedProcessingTime,
      includeMaximumSendingTime
    );
  }
  #messageExchangeContextFor(session) {
    const createdAt = import_general.Time.nowMs;
    return {
      session,
      localSessionParameters: this.#sessions.sessionParameters,
      peerLost: async (exchange, cause) => {
        if (!(session instanceof import_NodeSession.NodeSession)) {
          return;
        }
        if (session.peerAddress.fabricIndex === import_types.FabricIndex.NO_FABRIC || session.peerAddress.nodeId === import_types.NodeId.UNSPECIFIED_NODE_ID) {
          await session.handlePeerLoss({
            cause,
            currentExchange: exchange
          });
          return;
        }
        await this.#sessions.handlePeerLoss(session.peerAddress, cause, createdAt);
      },
      retry: (number) => this.#sessions.retry.emit(session, number)
    };
  }
  #addTransport(netInterface) {
    const udpInterface = netInterface instanceof import_general.UdpInterface;
    this.#listeners.set(
      netInterface,
      netInterface.onData((socket, data) => {
        if (udpInterface && data.byteLength > socket.maxPayloadSize) {
          logger.warn(
            `Received UDP message from ${socket.name} with size ${data.byteLength}, which is larger than the maximum allowed size of ${socket.maxPayloadSize}`
          );
        }
        this.#workers.add(this.#onMessage(socket, data));
      })
    );
  }
  #deleteTransport(netInterface) {
    const listener = this.#listeners.get(netInterface);
    if (listener === void 0) {
      return;
    }
    this.#listeners.delete(netInterface);
    this.#workers.add(listener.close());
  }
  #addSession(session) {
    if (!(session instanceof import_NodeSession.NodeSession)) {
      return;
    }
    let observers = this.#sessionObservers.get(session);
    if (!observers) {
      this.#sessionObservers.set(session, observers = new import_general.ObserverGroup());
    }
    observers.on(session.gracefulClose, () => this.#sendCloseSession(session));
  }
  #deleteSession(session) {
    const observers = this.#sessionObservers.get(session);
    if (!observers) {
      return;
    }
    observers.close();
    this.#sessionObservers.delete(session);
  }
  async #sendCloseSession(session) {
    var _stack = [];
    try {
      const exchange = __using(_stack, this.initiateExchangeForSession(session, import_types.SECURE_CHANNEL_PROTOCOL_ID), true);
      logger.debug(exchange.via, "Closing session");
      try {
        const messenger = new import_SecureChannelMessenger.SecureChannelMessenger(exchange);
        await messenger.sendCloseSession();
        await messenger.close();
      } catch (error) {
        logger.error(exchange.via, "Error closing session:", error);
      }
    } catch (_) {
      var _error = _, _hasError = true;
    } finally {
      var _promise = __callDispose(_stack, _error, _hasError);
      _promise && await _promise;
    }
  }
}
class ExchangeCounter {
  #exchangeCounter;
  constructor(entropy) {
    this.#exchangeCounter = entropy.randomUint16;
  }
  getIncrementedCounter() {
    this.#exchangeCounter++;
    if (this.#exchangeCounter > 65535) {
      this.#exchangeCounter = 0;
    }
    return this.#exchangeCounter;
  }
}
//# sourceMappingURL=ExchangeManager.js.map
