var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
var __typeError = (msg) => {
  throw TypeError(msg);
};
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();
};
/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { NetworkCommissioningBehavior } from "#behaviors/network-commissioning";
import { InteractionServer } from "#node/server/InteractionServer.js";
import {
  AddressInUseError,
  ChannelType,
  Crypto,
  InterfaceType,
  Logger,
  Network,
  NoAddressAvailableError,
  ObserverGroup,
  TransportSet,
  UdpTransport
} from "@matter/general";
import { DeviceClassification } from "@matter/model";
import {
  Ble,
  BleAdvertiser,
  ClientSubscriptions,
  DeviceAdvertiser,
  DeviceCommissioner,
  ExchangeManager,
  MdnsAdvertiser,
  MdnsService,
  NetworkProfiles,
  PeerSet,
  ScannerSet,
  SecureChannelProtocol,
  SessionManager,
  TcpTransport
} from "@matter/protocol";
import { CommissioningServer } from "../commissioning/CommissioningServer.js";
import { ProductDescriptionServer } from "../product-description/ProductDescriptionServer.js";
import { SessionsBehavior } from "../sessions/SessionsBehavior.js";
import { NetworkRuntime } from "./NetworkRuntime.js";
import { NetworkServer } from "./NetworkServer.js";
import { ServerGroupNetworking } from "./ServerGroupNetworking.js";
const logger = Logger.get("ServerNetworkRuntime");
const MAX_PORT_ASSIGNMENT_RETRIES = 10;
function convertNetworkEnvironmentType(type) {
  const convertedType = typeof type === "string" ? InterfaceType[type] : type;
  if (typeof convertedType !== "number" || convertedType < 1 || convertedType > 4) {
    return void 0;
  }
  return convertedType;
}
class ServerNetworkRuntime extends NetworkRuntime {
  #mdnsAdvertiser;
  #bleAdvertiser;
  #bleTransport;
  #ipv6UdpTransport;
  #observers = new ObserverGroup(this);
  #groupNetworking;
  #subscriptionsDrained;
  constructor(owner) {
    super(owner);
  }
  get owner() {
    return super.owner;
  }
  /**
   * Access the MDNS advertiser for the node.
   */
  get mdnsAdvertiser() {
    if (!this.#mdnsAdvertiser) {
      const port = this.owner.state.network.operationalPort;
      const options = {
        lifetime: this.construction,
        ...this.owner.state.commissioning.mdns
      };
      const crypto = this.owner.env.get(Crypto);
      const { server } = this.owner.env.get(MdnsService);
      this.#mdnsAdvertiser = new MdnsAdvertiser(crypto, server, { ...options, port });
    }
    return this.#mdnsAdvertiser;
  }
  get networkInterfaceConfiguration() {
    const interfaceConfig = this.owner.env.vars.get(
      "network.interface",
      {}
    );
    return Object.entries(interfaceConfig).map(([name, { type }]) => ({
      name,
      type: convertNetworkEnvironmentType(type)
    }));
  }
  async getNetworkInterfaces() {
    const network = this.owner.env.get(Network);
    const interfaces = await network.getNetInterfaces(this.networkInterfaceConfiguration);
    const interfaceDetails = new Array();
    for (const { name, type } of interfaces) {
      const details = await network.getIpMac(name);
      if (details !== void 0) {
        interfaceDetails.push({ name, type, ...details });
      }
    }
    return interfaceDetails;
  }
  /**
   * A BLE advertiser.
   */
  get bleAdvertiser() {
    if (this.#bleAdvertiser === void 0) {
      const { peripheralInterface } = this.owner.env.get(Ble);
      const options = {
        lifetime: this.construction,
        ...this.owner.state.commissioning.ble
      };
      this.#bleAdvertiser = new BleAdvertiser(peripheralInterface, options);
    }
    return this.#bleAdvertiser;
  }
  /**
   * A BLE transport.
   */
  get bleTransport() {
    if (this.#bleTransport === void 0) {
      this.#bleTransport = this.owner.env.get(Ble).peripheralInterface;
    }
    return this.#bleTransport;
  }
  /**
   * Add transports to the {@link TransportSet}.
   */
  async addTransports(interfaces) {
    const netconf = this.owner.state.network;
    const network = this.owner.env.get(Network);
    const port = this.owner.state.network.port;
    const maxPortRetries = port ? 1 : MAX_PORT_ASSIGNMENT_RETRIES;
    for (let attempt = 1; attempt <= maxPortRetries; attempt++) {
      let ipv6Interface;
      try {
        ipv6Interface = await UdpTransport.create(
          network,
          "udp6",
          port ? port : void 0,
          netconf.listeningAddressIpv6
        );
      } catch (error) {
        NoAddressAvailableError.accept(error);
        logger.info(`IPv6 UDP interface not created because IPv6 is not available, but required by Matter.`);
        throw error;
      }
      let ipv4Interface;
      if (netconf.ipv4) {
        try {
          ipv4Interface = await UdpTransport.create(
            network,
            "udp4",
            ipv6Interface.port,
            netconf.listeningAddressIpv4
          );
        } catch (error) {
          if (error instanceof AddressInUseError && attempt < maxPortRetries) {
            logger.info(
              `IPv4 UDP port ${ipv6Interface.port} already in use, retrying with new port (attempt ${attempt}/${maxPortRetries})`
            );
            await ipv6Interface.close();
            continue;
          }
          NoAddressAvailableError.accept(error);
          logger.info(`IPv4 UDP interface not created because IPv4 is not available`);
        }
      }
      const tcpConfig = NetworkServer.resolveTcpConfig(netconf.tcp);
      let tcpTransport;
      if (tcpConfig.incoming || tcpConfig.outgoing) {
        try {
          tcpTransport = await TcpTransport.create({
            network,
            listeningPort: tcpConfig.incoming ? ipv6Interface.port : void 0
          });
        } catch (error) {
          if (error instanceof AddressInUseError && attempt < maxPortRetries) {
            logger.info(
              `TCP port ${ipv6Interface.port} already in use, retrying with new port (attempt ${attempt}/${maxPortRetries})`
            );
            await ipv6Interface.close();
            if (ipv4Interface !== void 0) {
              await ipv4Interface.close();
            }
            continue;
          }
          throw error;
        }
      }
      this.#ipv6UdpTransport = ipv6Interface;
      interfaces.add(ipv6Interface);
      if (ipv4Interface !== void 0) {
        interfaces.add(ipv4Interface);
      }
      if (tcpTransport !== void 0) {
        interfaces.add(tcpTransport);
        logger.info(`TCP transport enabled (incoming=${tcpConfig.incoming}, outgoing=${tcpConfig.outgoing})`);
      }
      await this.owner.set({ network: { operationalPort: ipv6Interface.port } });
      break;
    }
    if (netconf.ble) {
      interfaces.add(this.bleTransport);
    }
  }
  /**
   * Add broadcasters to the {@link DeviceAdvertiser}.
   */
  async addBroadcasters(advertiser) {
    await advertiser.clearAdvertisers();
    const isCommissioned = !!this.#commissionedFabrics;
    let discoveryCapabilities = this.owner.state.network.discoveryCapabilities;
    if (isCommissioned) {
      discoveryCapabilities = { onIpNetwork: true };
    }
    if (discoveryCapabilities.onIpNetwork) {
      advertiser.addAdvertiser(this.mdnsAdvertiser);
    }
    if (!isCommissioned && discoveryCapabilities.ble) {
      advertiser.addAdvertiser(this.bleAdvertiser);
    }
  }
  /**
   * When the first Fabric gets added we need to enable MDNS broadcasting.
   */
  ensureMdnsAdvertiser() {
    const device = this.owner.env.get(DeviceAdvertiser);
    const mdnsAdvertiser = this.mdnsAdvertiser;
    if (!device.hasAdvertiser(mdnsAdvertiser)) {
      logger.debug("Enabling MDNS advertising");
      device.addAdvertiser(mdnsAdvertiser);
    }
  }
  /**
   * On commission, we turn off bluetooth and join the IP network if we haven't already.
   *
   * On decommission, we're destroyed so don't need to handle that case.
   */
  endUncommissionedMode() {
    this.ensureMdnsAdvertiser();
    if (this.#bleAdvertiser) {
      this.owner.env.runtime.add(this.#deleteAdvertiser(this.#bleAdvertiser));
      this.#bleAdvertiser = void 0;
    }
    if (this.#bleTransport) {
      this.owner.env.runtime.add(this.#deleteTransport(this.#bleTransport));
      this.#bleTransport = void 0;
    }
  }
  async #deleteAdvertiser(advertiser) {
    const device = this.owner.env.get(DeviceAdvertiser);
    await device.deleteAdvertiser(advertiser);
  }
  async #deleteTransport(transport) {
    const netInterfaces = this.owner.env.get(TransportSet);
    netInterfaces.delete(transport);
    await transport.close();
  }
  get #commissionedFabrics() {
    return this.owner.state.operationalCredentials.commissionedFabrics;
  }
  async start() {
    const { owner } = this;
    const { env } = owner;
    const interfaces = env.get(TransportSet);
    await this.addTransports(interfaces);
    const advertiser = env.get(DeviceAdvertiser);
    const tcpConfig = NetworkServer.resolveTcpConfig(owner.state.network.tcp);
    if (tcpConfig.incoming || tcpConfig.outgoing) {
      advertiser.supportedTransports = { tcpClient: tcpConfig.outgoing, tcpServer: tcpConfig.incoming };
    }
    if (owner.state.network.transportPreference === "tcp") {
      if (tcpConfig.outgoing) {
        env.get(PeerSet).transportPreference = ChannelType.TCP;
      } else {
        logger.warn("transportPreference is 'tcp' but outgoing TCP is disabled; preference ignored");
      }
    }
    await this.addBroadcasters(advertiser);
    await owner.act("start-network", (agent) => agent.load(ProductDescriptionServer));
    env.get(SessionManager).sessionParameters = {
      maxPathsPerInvoke: this.owner.state.basicInformation.maxPathsPerInvoke
    };
    await this.#initializeGroupNetworking();
    const interactionServer = new InteractionServer(this.owner, env.get(SessionManager));
    env.set(InteractionServer, interactionServer);
    const exchanges = env.get(ExchangeManager);
    exchanges.addProtocolHandler(interactionServer);
    env.get(SecureChannelProtocol);
    await this.owner.act("load-sessions", (agent) => agent.load(SessionsBehavior));
    this.#observers.on(this.owner.eventsOf(CommissioningServer).commissioned, this.endUncommissionedMode);
    if (this.owner.stateOf(CommissioningServer).commissioned) {
      this.ensureMdnsAdvertiser();
    }
    env.get(ScannerSet);
    const { timing, profiles } = this.owner.state.network;
    if (timing) {
      env.get(PeerSet).timing = timing;
    }
    const autoUnknown = this.#detectFallbackProfile();
    const effectiveProfiles = autoUnknown !== void 0 ? { unknown: autoUnknown, ...profiles } : profiles;
    if (effectiveProfiles) {
      env.get(NetworkProfiles).defaults = effectiveProfiles;
    }
    env.get(PeerSet).exchanges = exchanges;
    this.abortSignal.addEventListener(
      "abort",
      () => {
        this.owner.env.maybeGet(InteractionServer)?.blockNewActivity();
        this.#subscriptionsDrained = this.owner.env.maybeGet(ClientSubscriptions)?.blockNewActivity();
      },
      { once: true }
    );
    await this.owner.act((agent) => this.owner.lifecycle.online.emit(agent.context));
  }
  async stop() {
    this.#observers.close();
    const { env } = this.owner;
    if (this.#subscriptionsDrained) {
      var _stack = [];
      try {
        const _lifetime = __using(_stack, this.construction.join("subscription reads"));
        await this.#subscriptionsDrained;
      } catch (_) {
        var _error = _, _hasError = true;
      } finally {
        __callDispose(_stack, _error, _hasError);
      }
    }
    {
      var _stack2 = [];
      try {
        const _lifetime = __using(_stack2, this.construction.join("commissioner"));
        await env.close(DeviceCommissioner);
      } catch (_2) {
        var _error2 = _2, _hasError2 = true;
      } finally {
        __callDispose(_stack2, _error2, _hasError2);
      }
    }
    const advertisementShutdown = this.owner.env.has(DeviceAdvertiser) ? this.owner.env.close(DeviceAdvertiser) : this.#mdnsAdvertiser?.close();
    this.#mdnsAdvertiser = void 0;
    {
      var _stack3 = [];
      try {
        const _lifetime = __using(_stack3, this.construction.join("preparing"));
        await this.owner.prepareRuntimeShutdown();
      } catch (_3) {
        var _error3 = _3, _hasError3 = true;
      } finally {
        __callDispose(_stack3, _error3, _hasError3);
      }
    }
    this.#groupNetworking?.close();
    this.#groupNetworking = void 0;
    {
      var _stack4 = [];
      try {
        const _advertiser = __using(_stack4, this.construction.join("advertisement"));
        await advertisementShutdown;
      } catch (_4) {
        var _error4 = _4, _hasError4 = true;
      } finally {
        __callDispose(_stack4, _error4, _hasError4);
      }
    }
    {
      var _stack5 = [];
      try {
        const _lifetime = __using(_stack5, this.construction.join("peers"));
        await env.maybeGet(PeerSet)?.disconnect();
      } catch (_5) {
        var _error5 = _5, _hasError5 = true;
      } finally {
        __callDispose(_stack5, _error5, _hasError5);
      }
    }
    {
      var _stack6 = [];
      try {
        const _lifetime = __using(_stack6, this.construction.join("exchanges"));
        await env.close(ExchangeManager);
      } catch (_6) {
        var _error6 = _6, _hasError6 = true;
      } finally {
        __callDispose(_stack6, _error6, _hasError6);
      }
    }
    {
      var _stack7 = [];
      try {
        const _lifetime = __using(_stack7, this.construction.join("protocols"));
        await env.close(SecureChannelProtocol);
      } catch (_7) {
        var _error7 = _7, _hasError7 = true;
      } finally {
        __callDispose(_stack7, _error7, _hasError7);
      }
    }
    {
      var _stack8 = [];
      try {
        const _lifetime = __using(_stack8, this.construction.join("transports"));
        await env.maybeGet(TransportSet)?.close();
      } catch (_8) {
        var _error8 = _8, _hasError8 = true;
      } finally {
        __callDispose(_stack8, _error8, _hasError8);
      }
    }
    {
      var _stack9 = [];
      try {
        const _lifetime = __using(_stack9, this.construction.join("interactions"));
        await env.close(InteractionServer);
      } catch (_9) {
        var _error9 = _9, _hasError9 = true;
      } finally {
        __callDispose(_stack9, _error9, _hasError9);
      }
    }
    env.delete(ScannerSet);
  }
  /**
   * Auto-detect limits for the unknown profile based on the local node's endpoint structure.
   */
  #detectFallbackProfile() {
    const { owner } = this;
    let hasApplicationEndpoint = false;
    for (const part of owner.parts) {
      if (!DeviceClassification.isUtility(part.type.deviceClass)) {
        hasApplicationEndpoint = true;
        break;
      }
    }
    if (!hasApplicationEndpoint) {
      return void 0;
    }
    const nc = owner.behaviors.typeFor(NetworkCommissioningBehavior);
    if (nc?.schema.supportedFeatures.has("TH")) {
      logger.info("Default network profile for unknown peers set to thread");
      return NetworkProfiles.defaults.thread;
    }
    logger.info("Default network profile for unknown peers set to fast");
    return NetworkProfiles.defaults.fast;
  }
  async #initializeGroupNetworking() {
    if (this.#groupNetworking) {
      logger.warn("Group networking already initialized, skipping.");
      return;
    }
    if (this.#ipv6UdpTransport === void 0) {
      logger.warn("No IPv6 UDP interface available, skipping group networking initialization.");
      return;
    }
    this.#groupNetworking = new ServerGroupNetworking(this.owner.env, this.#ipv6UdpTransport);
    await this.#groupNetworking.construction;
  }
}
export {
  ServerNetworkRuntime
};
//# sourceMappingURL=ServerNetworkRuntime.js.map
