rs_matter/transport/network.rs
1/*
2 *
3 * Copyright (c) 2022-2026 Project CHIP Authors
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use core::fmt::{self, Debug, Display};
19use core::future::Future;
20use core::pin::pin;
21
22pub use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
23
24use embassy_futures::select::{select, Either};
25
26use crate::error::{Error, ErrorCode};
27
28pub mod btp;
29pub mod mdns;
30pub mod tcp;
31pub mod udp;
32pub mod wifi;
33
34// Maximum UDP RX packet size per Matter spec
35pub const MAX_RX_PACKET_SIZE: usize = 1583;
36
37// Maximum UDP TX packet size per Matter spec
38pub const MAX_TX_PACKET_SIZE: usize = 1280 - 40/*IPV6 header size*/ - 8/*UDP header size*/;
39
40// Maximum TCP RX packet size per Matter spec
41pub const MAX_RX_LARGE_PACKET_SIZE: usize = 1024 * 1024;
42
43// Maximum TCP TX packet size per Matter spec
44pub const MAX_TX_LARGE_PACKET_SIZE: usize = MAX_RX_LARGE_PACKET_SIZE;
45
46/// A Matter service that **this** node advertises (publishes) over a discovery
47/// transport such as mDNS.
48///
49/// This is the *publish-side* identity; the *query-side* analog is
50/// [`MatterRemoteService`]. The discovery-transport encoding (e.g. the mDNS
51/// `MdnsLocalService` record) lives in the [`mdns`] module
52/// (`MatterLocalService::service`).
53#[derive(Debug, Clone, Eq, PartialEq, Hash)]
54#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55pub enum MatterLocalService {
56 /// A commissioned Matter service for a particular fabric
57 ///
58 /// The published name is in the form `<compressed-fabric-id-hex>-<node-id-hex>`.
59 Commissioned {
60 compressed_fabric_id: u64,
61 node_id: u64,
62 },
63 /// A non-commissioned Matter service
64 ///
65 /// The published name is in the form `<id-hex>`. The discriminator should be used as an mDNS TXT entry
66 Commissionable {
67 id: u64,
68 /// The discriminator to be communicated over mDNS
69 discriminator: u16,
70 /// Whether this is an enhanced (ECM) commissioning window (`CM=2`) vs basic (`CM=1`)
71 enhanced: bool,
72 },
73}
74
75/// A Matter service **elsewhere** that this node resolves / looks up over a
76/// discovery transport such as mDNS.
77///
78/// This is the *query-side* analog of the *publish-side* [`MatterLocalService`]:
79/// it identifies a single Matter service instance to resolve (SRV/TXT/A/AAAA),
80/// rather than describing one to advertise. The discovery-transport encoding
81/// (e.g. the mDNS instance name) lives in the [`mdns`] module
82/// (`MatterRemoteService::instance_name`).
83///
84/// Note that *browsing* (enumerating all commissionable or operational nodes)
85/// does not need a `MatterRemoteService` - it is a PTR query against the bare
86/// service type.
87#[derive(Debug, Clone, Eq, PartialEq, Hash)]
88#[cfg_attr(feature = "defmt", derive(defmt::Format))]
89pub enum MatterRemoteService {
90 /// A specific operational (commissioned) node.
91 ///
92 /// The instance name is `<compressed-fabric-id-hex>-<node-id-hex>._matter._tcp.local`.
93 Operational {
94 compressed_fabric_id: u64,
95 node_id: u64,
96 },
97 /// A specific commissionable instance.
98 ///
99 /// The instance name is `<id-hex>._matterc._udp.local`.
100 Commissionable { id: u64 },
101}
102
103/// A Bluetooth address.
104#[derive(Copy, Clone, Eq, PartialEq, Debug)]
105pub struct BtAddr(pub [u8; 6]);
106
107impl Display for BtAddr {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(
110 f,
111 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
112 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
113 )
114 }
115}
116
117#[cfg(feature = "defmt")]
118impl defmt::Format for BtAddr {
119 fn format(&self, f: defmt::Formatter<'_>) {
120 defmt::write!(
121 f,
122 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
123 self.0[0],
124 self.0[1],
125 self.0[2],
126 self.0[3],
127 self.0[4],
128 self.0[5]
129 )
130 }
131}
132
133/// An enum representing a network address for all supported protocols by the Matter specification (UDP, TCP and BTP).
134#[derive(Eq, PartialEq, Copy, Clone)]
135pub enum Address {
136 Udp(SocketAddr),
137 Tcp(SocketAddr),
138 Btp(BtAddr),
139}
140
141impl Address {
142 pub const fn new() -> Self {
143 Self::Udp(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0))
144 }
145
146 pub const fn is_reliable(&self) -> bool {
147 matches!(self, Self::Tcp(_) | Self::Btp(_))
148 }
149
150 pub const fn is_udp(&self) -> bool {
151 matches!(self, Self::Udp(_))
152 }
153
154 pub const fn is_tcp(&self) -> bool {
155 matches!(self, Self::Tcp(_))
156 }
157
158 pub const fn is_btp(&self) -> bool {
159 matches!(self, Self::Btp(_))
160 }
161
162 /// Return this address with its IP canonicalized: an IPv4-mapped IPv6
163 /// address (`::ffff:a.b.c.d`) is rewritten to its true IPv4 form, leaving
164 /// genuine IPv4 / IPv6 / BTP addresses unchanged.
165 ///
166 /// This matters because a dual-stack IPv6 socket reports an IPv4 peer's
167 /// packets to `recv_from` in IPv4-mapped form, whereas the same peer is
168 /// usually *sent to* (and stored on the session) as a plain `V4` address.
169 /// `Address` derives `PartialEq`/`Eq` over the `SocketAddr` (family
170 /// included), so without canonicalization `V4(x)` and `V6(::ffff:x)` would
171 /// compare unequal and session lookup by peer address would fail (PASE then
172 /// reports "PAKE session not found").
173 ///
174 /// This is used only when *comparing* a session's peer address against a
175 /// received one (see `Session::is_for_rx` / `is_pase_for_addr`); the address
176 /// stored on the session is left untouched so it still routes replies to the
177 /// exact address the peer was reached at.
178 pub fn canonical(self) -> Self {
179 match self {
180 Self::Udp(addr) => Self::Udp(canonical_sockaddr(addr)),
181 Self::Tcp(addr) => Self::Tcp(canonical_sockaddr(addr)),
182 other => other,
183 }
184 }
185
186 pub const fn udp(self) -> Option<SocketAddr> {
187 match self {
188 Self::Udp(addr) => Some(addr),
189 _ => None,
190 }
191 }
192
193 pub const fn tcp(self) -> Option<SocketAddr> {
194 match self {
195 Self::Tcp(addr) => Some(addr),
196 _ => None,
197 }
198 }
199
200 pub const fn btp(self) -> Option<BtAddr> {
201 match self {
202 Self::Btp(addr) => Some(addr),
203 _ => None,
204 }
205 }
206}
207
208/// Canonicalize a [`SocketAddr`]: an IPv4-mapped IPv6 address
209/// (`::ffff:a.b.c.d`) is rewritten to its true IPv4 form (preserving port),
210/// everything else is returned unchanged. See [`Address::canonical`].
211fn canonical_sockaddr(addr: SocketAddr) -> SocketAddr {
212 match addr {
213 SocketAddr::V6(v6) => match v6.ip().to_canonical() {
214 // `IpAddr::to_canonical` (stable since Rust 1.75) maps
215 // `::ffff:a.b.c.d` to `a.b.c.d` and leaves true IPv6 untouched.
216 IpAddr::V4(v4) => SocketAddr::new(IpAddr::V4(v4), v6.port()),
217 IpAddr::V6(_) => addr,
218 },
219 SocketAddr::V4(_) => addr,
220 }
221}
222
223impl Default for Address {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228
229impl Display for Address {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 match self {
232 Address::Udp(addr) => write!(f, "UDP {}", addr),
233 Address::Tcp(addr) => write!(f, "TCP {}", addr),
234 Address::Btp(addr) => write!(f, "BTP {}", addr),
235 }
236 }
237}
238
239impl Debug for Address {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 match self {
242 Address::Udp(addr) => writeln!(f, "{}", addr),
243 Address::Tcp(addr) => writeln!(f, "{}", addr),
244 Address::Btp(addr) => writeln!(f, "{:?}", addr),
245 }
246 }
247}
248
249#[cfg(feature = "defmt")]
250impl defmt::Format for Address {
251 fn format(&self, f: defmt::Formatter<'_>) {
252 match self {
253 Address::Udp(addr) => defmt::write!(f, "UDP {}", addr),
254 Address::Tcp(addr) => defmt::write!(f, "TCP {}", addr),
255 Address::Btp(addr) => defmt::write!(f, "BTP {}", addr),
256 }
257 }
258}
259
260/// A trait for sending data to a network address.
261///
262/// All network communication in the Matter transport is packetized (including via TCP and Bluetooth), hence
263/// this trait models the sending of a single Matter packet of data to a network address.
264///
265/// Data packetization is expected to be handled by the implementation of this trait, and is trivial
266/// for e.g. the UDP transport which is packetized by default, but more complex for e.g. the TCP transport and especially for BTP.
267pub trait NetworkSend {
268 /// Send a Matter packet represented as a sequence of bytes (`data`) to the specified address.
269 ///
270 /// Might return an error if the address is not supported, or if there is a general error on the network interface.
271 async fn send_to(&mut self, data: &[u8], addr: Address) -> Result<(), Error>;
272}
273
274impl<T> NetworkSend for &mut T
275where
276 T: NetworkSend,
277{
278 fn send_to(&mut self, data: &[u8], addr: Address) -> impl Future<Output = Result<(), Error>> {
279 (*self).send_to(data, addr)
280 }
281}
282
283/// A trait for receiving data from a network address.
284///
285/// All network communication in the Matter transport is packetized (including via TCP and Bluetooth), hence
286/// this trait models the receiving of a single Matter packet of data from a network address.
287///
288/// Data packetization is expected to be handled by the implementation of this trait, and is trivial
289/// for e.g. the UDP transport which is packetized by default, but more complex for e.g. the TCP transport and especially for BTP.
290pub trait NetworkReceive {
291 /// Wait until a data packet is available to be received.
292 ///
293 /// Allows the Matter transport layer to re-use a single RX buffer accross all network protocol implementatiins.
294 ///
295 /// Might return an error if there is a general error on the network interface.
296 async fn wait_available(&mut self) -> Result<(), Error>;
297
298 /// Receive a single data packet from the network.
299 ///
300 /// Might return an error if there is a general error on the network interface.
301 async fn recv_from(&mut self, buffer: &mut [u8]) -> Result<(usize, Address), Error>;
302}
303
304impl<T> NetworkReceive for &mut T
305where
306 T: NetworkReceive,
307{
308 fn wait_available(&mut self) -> impl Future<Output = Result<(), Error>> {
309 (*self).wait_available()
310 }
311
312 fn recv_from(
313 &mut self,
314 buffer: &mut [u8],
315 ) -> impl Future<Output = Result<(usize, Address), Error>> {
316 (*self).recv_from(buffer)
317 }
318}
319
320/// A trait to listen for IPv6 multicast on supported network types
321///
322/// This is used for listening to groupcast messages
323pub trait NetworkMulticast {
324 /// Join a multicast group with the specified address.
325 async fn join(&mut self, addr: IpAddr) -> Result<(), Error>;
326
327 /// Leave a multicast group with the specified address.
328 async fn leave(&mut self, addr: IpAddr) -> Result<(), Error>;
329}
330
331impl<T> NetworkMulticast for &mut T
332where
333 T: NetworkMulticast,
334{
335 fn join(&mut self, addr: IpAddr) -> impl Future<Output = Result<(), Error>> {
336 (*self).join(addr)
337 }
338
339 fn leave(&mut self, addr: IpAddr) -> impl Future<Output = Result<(), Error>> {
340 (*self).leave(addr)
341 }
342}
343
344/// A network implementation that does not support any network communication:
345/// - Trying to send a packet always results in a `ErrorCode::NoNetworkInterface` error.
346/// - Trying to wait/receive a packet pends forever.
347/// - Joining/leaving multicast groups is a no-op that always succeeds.
348///
349/// Useful when chaining multiple network interfaces together to serve as the last network interface in the chain.
350pub struct NoNetwork;
351
352impl NetworkSend for NoNetwork {
353 async fn send_to(&mut self, _data: &[u8], _addr: Address) -> Result<(), Error> {
354 Err(ErrorCode::NoNetworkInterface.into())
355 }
356}
357
358impl NetworkReceive for NoNetwork {
359 async fn wait_available(&mut self) -> Result<(), Error> {
360 core::future::pending().await
361 }
362
363 async fn recv_from(&mut self, _buffer: &mut [u8]) -> Result<(usize, Address), Error> {
364 core::future::pending().await
365 }
366}
367
368impl NetworkMulticast for NoNetwork {
369 async fn join(&mut self, _addr: IpAddr) -> Result<(), Error> {
370 Ok(())
371 }
372
373 async fn leave(&mut self, _addr: IpAddr) -> Result<(), Error> {
374 Ok(())
375 }
376}
377
378/// A network implementation that chains two network implementations together in a composite network interface.
379///
380/// This allows for e.g. a network implementation that can send/receive data to/from both a UDP and a TCP network interface - or -
381/// with e.g. further chaining - from all of UDP, TCP and BTP network interfaces.
382#[derive(Clone)]
383pub struct ChainedNetwork<H, T, F> {
384 pub handler_can_send: F,
385 pub handler: H,
386 pub next: T,
387}
388
389impl<H, T, F> ChainedNetwork<H, T, F> {
390 /// Construct a chained handler that works as follows:
391 /// - When a packet is about to be send, the `handler_can_send` function is called with the destination address.
392 /// If it returns `true`, the packet is sent via the `handler` network interface, otherwise it is sent via the `next` network interface.
393 /// - When `wait_available` is called, the function waits until a packet is available on either network interface.
394 /// - When `recv_from` is called, the function receives a packet from the first network interface that has a packet available.
395 pub const fn new(handler_can_send: F, handler: H, next: T) -> Self {
396 Self {
397 handler_can_send,
398 handler,
399 next,
400 }
401 }
402
403 /// Chain itself with another handler.
404 ///
405 /// The returned chained handler works as follows:
406 /// - When a packet is about to be send, the `handler_can_send` function is called with the destination address.
407 /// If it returns `true`, the packet is sent via the `handler` network interface, otherwise it is sent via `self`.
408 /// - When `wait_available` is called, the function waits until a packet is available on either network interface.
409 /// - When `recv_from` is called, the function receives a packet from the first network interface that has a packet available.
410 pub const fn chain<H2, F2>(
411 self,
412 handler_can_send: F2,
413 handler: H2,
414 ) -> ChainedNetwork<H2, Self, F2> {
415 ChainedNetwork::new(handler_can_send, handler, self)
416 }
417}
418
419impl<H, T, F> NetworkReceive for ChainedNetwork<H, T, F>
420where
421 H: NetworkReceive,
422 T: NetworkReceive,
423{
424 async fn wait_available(&mut self) -> Result<(), Error> {
425 let mut first = pin!(self.handler.wait_available());
426 let mut second = pin!(self.next.wait_available());
427
428 select(&mut first, &mut second).await;
429
430 Ok(())
431 }
432
433 async fn recv_from(&mut self, buffer: &mut [u8]) -> Result<(usize, Address), Error> {
434 let first = {
435 let mut first_available = pin!(self.handler.wait_available());
436 let mut second_available = pin!(self.next.wait_available());
437
438 matches!(
439 select(&mut first_available, &mut second_available).await,
440 Either::First(_)
441 )
442 };
443
444 if first {
445 self.handler.recv_from(buffer).await
446 } else {
447 self.next.recv_from(buffer).await
448 }
449 }
450}
451
452impl<H, T, F> NetworkSend for ChainedNetwork<H, T, F>
453where
454 H: NetworkSend,
455 T: NetworkSend,
456 F: Fn(&Address) -> bool,
457{
458 async fn send_to(&mut self, data: &[u8], addr: Address) -> Result<(), Error> {
459 if (self.handler_can_send)(&addr) {
460 self.handler.send_to(data, addr).await
461 } else {
462 self.next.send_to(data, addr).await
463 }
464 }
465}
466
467impl<H, T, F> NetworkMulticast for ChainedNetwork<H, T, F>
468where
469 H: NetworkMulticast,
470 T: NetworkMulticast,
471{
472 async fn join(&mut self, addr: IpAddr) -> Result<(), Error> {
473 self.handler.join(addr).await?;
474 self.next.join(addr).await
475 }
476
477 async fn leave(&mut self, addr: IpAddr) -> Result<(), Error> {
478 self.handler.leave(addr).await?;
479 self.next.leave(addr).await
480 }
481}