/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { ServerSubscriptionConfig } from "#node/server/ServerSubscription.js"; import { DeepPartial, Duration, Logger, Minutes } from "@matter/general"; import { duration, field, uint16 } from "@matter/model"; import { Ble, FabricManager, NetworkProfiles, PeerTimingParameters } from "@matter/protocol"; import { DiscoveryCapabilitiesBitmap, TypeFromPartialBitSchema } from "@matter/types"; import { CommissioningServer } from "../commissioning/CommissioningServer.js"; import { NetworkBehavior } from "./NetworkBehavior.js"; import type { ServerNetworkRuntime } from "./ServerNetworkRuntime.js"; const logger = Logger.get("NetworkingServer"); /** * Server implementation of {@link NetworkBehavior}. * * This behavior mostly deals with configuration and events. {@link ServerNetworkRuntime} provides the actual network * implementation. */ export class NetworkServer extends NetworkBehavior { declare readonly state: NetworkServer.State; declare internal: NetworkServer.Internal; override initialize() { if (this.state.ble === undefined) { this.state.ble = this.env.has(Ble); } else if (this.state.ble && !this.env.has(Ble)) { logger.warn("Disabling Bluetooth commissioning because BLE support is not installed"); this.state.ble = false; } const discoveryCaps = this.state.discoveryCapabilities; switch (discoveryCaps.ble) { case undefined: discoveryCaps.ble = this.state.ble; break; case true: if (!this.state.ble) { discoveryCaps.ble = false; } break; } if (discoveryCaps.onIpNetwork === undefined) { discoveryCaps.onIpNetwork = true; } this.reactTo(this.endpoint.eventsOf(CommissioningServer).commissioned, this.#endUncommissionedMode); if (discoveryCaps.ble) { // When fabric is added, we need to allow operational discovery this.reactTo(this.env.get(FabricManager).events.added, this.#ensureMdnsAdvertiser); } return super.initialize(); } #endUncommissionedMode() { if (this.internal.runtime) { this.internal.runtime.endUncommissionedMode(); } } #ensureMdnsAdvertiser() { if (this.internal.runtime) { this.internal.runtime.ensureMdnsAdvertiser(); } } } export namespace NetworkServer { export class Internal extends NetworkBehavior.Internal { declare runtime: ServerNetworkRuntime; } export class KickRestartCooldownConfig implements Partial { @field(duration) addressChange?: Duration; @field(duration) connect?: Duration; } export class AddressChangeProbeCooldownConfig implements Partial< PeerTimingParameters["addressChangeProbeCooldown"] > { @field(duration) minimum?: Duration; @field(duration) maximum?: Duration; } export class TimingConfig implements DeepPartial { @field(duration) defaultConnectionTimeout?: Duration; @field(duration) maxDelayBetweenInitialContactRetries?: Duration; @field(duration) delayBeforeNextAddress?: Duration; @field(duration) delayAfterNetworkError?: Duration; @field(duration) delayAfterPeerError?: Duration; @field(duration) delayAfterUnhandledError?: Duration; @field(duration) kickThrottleInterval?: Duration; @field(uint16) kickMinRetransmissions?: number; @field(duration) kickMinRestartSaving?: Duration; @field(KickRestartCooldownConfig) kickRestartCooldown?: KickRestartCooldownConfig; @field(duration) addressChangeStabilizationDelay?: Duration; @field(AddressChangeProbeCooldownConfig) addressChangeProbeCooldown?: AddressChangeProbeCooldownConfig; } export class ConcreteLimitsConfig implements Partial { @field(uint16) exchanges?: number; @field(duration) delay?: Duration; @field(duration) timeout?: Duration; @field(duration) additionalMrpDelay?: Duration; } export class LimitsConfig extends ConcreteLimitsConfig { @field(ConcreteLimitsConfig) connect?: ConcreteLimitsConfig; @field(ConcreteLimitsConfig) probeAddress?: ConcreteLimitsConfig; } export class ProfilesConfig implements NetworkProfiles.PartialOptions { @field(LimitsConfig) fast?: LimitsConfig; @field(LimitsConfig) wifi?: LimitsConfig; @field(LimitsConfig) thread?: LimitsConfig; @field(LimitsConfig) conservative?: LimitsConfig; @field(LimitsConfig) unknown?: LimitsConfig; @field(LimitsConfig) unlimited?: LimitsConfig; @field(LimitsConfig) icdLit?: LimitsConfig; } type Mismatched = Exclude | Exclude; type SameFields<_Mismatched extends never> = unknown; /** * The config classes above restate their source's fields because {@link field} needs real property declarations, * and `implements Partial` catches neither a field they fail to declare nor one the source has dropped. * This does: either divergence breaks the build here, naming the offending field. */ export type ConfigMatchesItsSource = [ SameFields>, SameFields>, SameFields>, SameFields>, SameFields>, SameFields>, ]; export class State extends NetworkBehavior.State { listeningAddressIpv4?: string = undefined; listeningAddressIpv6?: string = undefined; ipv4 = true; ble?: boolean = undefined; discoveryCapabilities: TypeFromPartialBitSchema = { onIpNetwork: true, }; subscriptionOptions?: ServerSubscriptionConfig = undefined; /** * Enable TCP transport. `true` enables both incoming (server) and outgoing (client). * Fine-grained control via `{ incoming?: boolean; outgoing?: boolean }`. * Disabled by default. */ tcp?: boolean | { incoming?: boolean; outgoing?: boolean }; /** * Preferred transport for outgoing connections. Defaults to UDP when not set. */ transportPreference?: "tcp" | "udp"; /** * Network profile describing our own (local) network — the sender-side MRP additive-delay * axis. Defaults to "fast"; set to "thread" on a Thread device. */ ownNetworkProfileId: keyof NetworkProfiles.Templates = "fast"; @field(TimingConfig) timing?: TimingConfig; @field(ProfilesConfig) profiles?: ProfilesConfig; /** * Interval at which dirty client cache data is flushed to storage. Set to `undefined` to disable buffering * and persist every change immediately. */ @field(duration) clientCacheFlushInterval?: Duration = Minutes(20); } /** * Resolve the tcp shorthand to explicit incoming/outgoing booleans. */ export function resolveTcpConfig(tcp?: boolean | { incoming?: boolean; outgoing?: boolean }): { incoming: boolean; outgoing: boolean; } { if (tcp === true) { return { incoming: true, outgoing: true }; } if (tcp === false || tcp === undefined) { return { incoming: false, outgoing: false }; } return { incoming: tcp.incoming ?? false, outgoing: tcp.outgoing ?? false }; } }