/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Channel, ChannelType, UdpNetworkChannel } from "#net/Channel.js"; import { Network, NetworkError } from "#net/Network.js"; import { Transport } from "#net/Transport.js"; import { Bytes } from "#util/Bytes.js"; import { Observable } from "#util/index.js"; import { ServerAddress, ServerAddressUdp } from "../ServerAddress.js"; import { UdpSocket } from "./UdpSocket.js"; export class UdpTransport implements Transport { readonly #server: UdpSocket; static async create(network: Network, type: "udp4" | "udp6", port?: number, host?: string, netInterface?: string) { return new UdpTransport( await network.createUdpSocket({ listeningPort: port, type, netInterface, listeningAddress: host }), ); } constructor(server: UdpSocket) { this.#server = server; } protected get server() { return this.#server; } supports(type: ChannelType, address: string) { return this.#server.supports(type, address); } async openChannel(address: ServerAddress, _options?: Transport.OpenChannelOptions) { if (!ServerAddress.isIp(address)) { throw new NetworkError(`Unsupported address type for UDP interface`); } const { ip, port } = address; return Promise.resolve(new UdpChannel(this.#server, ip, port)); } onData(listener: (channel: Channel, messageBytes: Bytes) => void): Transport.Listener { return this.#server.onData((_netInterface, peerHost, peerPort, data) => { listener(new UdpChannel(this.#server, peerHost, peerPort), data); }); } get port() { return this.#server.port; } close() { return this.#server.close(); } addMembership(address: string) { return this.#server.addMembership(address); } dropMembership(address: string) { return this.#server.dropMembership(address); } } export class UdpChannel implements UdpNetworkChannel { readonly isReliable = false; readonly supportsLargeMessages = false; readonly type = ChannelType.UDP; readonly #server: UdpSocket; #peerAddress: string; #peerPort: number; readonly networkAddressChanged = Observable<[ServerAddressUdp]>(); constructor(server: UdpSocket, peerAddress: string, peerPort: number) { this.#server = server; this.#peerAddress = peerAddress; this.#peerPort = peerPort; } get maxPayloadSize() { return this.#server.maxPayloadSize; } send(data: Bytes, addressOverride?: ServerAddressUdp) { if (addressOverride) { return this.#server.send(addressOverride.ip, addressOverride.port, data); } return this.#server.send(this.#peerAddress, this.#peerPort, data); } get name() { return `${this.type}://${this.#peerAddress.includes(":") ? `[${this.#peerAddress}]` : this.#peerAddress}:${this.#peerPort}`; } get networkAddress(): ServerAddressUdp { return { type: "udp", ip: this.#peerAddress, port: this.#peerPort }; } set networkAddress(address: ServerAddressUdp) { if (address.type !== "udp" || (address.ip === this.#peerAddress && address.port === this.#peerPort)) { return; } this.#peerAddress = address.ip; this.#peerPort = address.port; this.networkAddressChanged.emit(this.networkAddress); } async close() { // UDP is connectionless, so nothing to do here } }