Skip to main content

rs_matter/
transport.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, Display};
19use core::future::Future;
20use core::num::NonZeroU8;
21use core::ops::{Deref, DerefMut};
22use core::pin::pin;
23
24use domain::base::name::ToLabelIter;
25
26#[cfg(feature = "groups")]
27use embassy_futures::select::select4;
28use embassy_futures::select::{select, select3, Either};
29use embassy_time::{Duration, Timer};
30
31use rand_core::RngCore;
32
33use crate::crypto::Crypto;
34use crate::dm::clusters::basic_info::BasicInfoConfig;
35use crate::dm::NodeId;
36use crate::error::{Error, ErrorCode};
37#[cfg(feature = "groups")]
38use crate::fabric::{MAX_FABRICS, MAX_GROUPS_PER_FABRIC};
39use crate::fmt::Bytes;
40use crate::im::PROTO_ID_INTERACTION_MODEL;
41#[cfg(not(feature = "case-responder-only"))]
42use crate::sc::case::CaseInitiator;
43use crate::sc::pase::PaseInitiator;
44#[cfg(not(feature = "case-responder-only"))]
45use crate::sc::SessionParameters;
46use crate::sc::{sc_write, OpCode, SCStatusCodes, StatusReport, PROTO_ID_SECURE_CHANNEL};
47use crate::tlv::TLVElement;
48use crate::transport::network::mdns::{
49    commissionable_instance_id, score_ip_address, BrowseExclude, CommissionableFilter,
50    MdnsBrowseState, MdnsRemoteService, MdnsResolveState, ResolvedNode,
51};
52use crate::transport::network::{MatterRemoteService, NetworkMulticast};
53use crate::utils::cell::RefCell;
54use crate::utils::init::{init, Init};
55#[cfg(feature = "groups")]
56use crate::utils::ipv6::compute_group_multicast_addr;
57use crate::utils::select::Coalesce;
58use crate::utils::storage::Vec;
59use crate::utils::storage::{pooled::Buffers, ParseBuf, WriteBuf};
60
61use crate::utils::sync::blocking::Mutex;
62use crate::utils::sync::{IfMutex, IfMutexGuard, Notification, Signal};
63use crate::{Matter, MATTER_PORT};
64
65use exchange::{Exchange, ExchangeId, ExchangeState, MessageMeta, ResponderState, Role};
66use network::{Address, IpAddr, Ipv6Addr, NetworkReceive, NetworkSend, SocketAddr, SocketAddrV6};
67use packet::PacketHdr;
68use proto_hdr::ProtoHdr;
69use session::{Session, Sessions};
70
71use self::mrp::mrp_log;
72
73mod dedup;
74
75pub mod exchange;
76pub mod mrp;
77pub mod network;
78pub mod packet;
79pub mod plain_hdr;
80pub mod proto_hdr;
81pub mod session;
82
83pub const MATTER_SOCKET_BIND_ADDR: SocketAddr =
84    SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, MATTER_PORT, 0, 0));
85
86#[cfg(feature = "groups")]
87const MAX_GROUP_ADDRS: usize = MAX_FABRICS * MAX_GROUPS_PER_FABRIC + 1; // +1 for the IANA-assigned Groupcast address
88/// Without multicast group support no group addresses are ever joined, so the
89/// join-list is zero-capacity. Keeping the field (rather than gating it) avoids
90/// splitting the in-place `Transport` initializer, and a `Vec<_, 0>` costs no
91/// RAM.
92#[cfg(not(feature = "groups"))]
93const MAX_GROUP_ADDRS: usize = 0;
94
95const ACCEPT_TIMEOUT_MS: u64 = 1000;
96
97#[cfg(feature = "large-buffers")]
98pub(crate) const MAX_RX_BUF_SIZE: usize = network::MAX_RX_LARGE_PACKET_SIZE;
99#[cfg(feature = "large-buffers")]
100pub(crate) const MAX_TX_BUF_SIZE: usize = network::MAX_TX_LARGE_PACKET_SIZE;
101
102#[cfg(not(feature = "large-buffers"))]
103pub(crate) const MAX_RX_BUF_SIZE: usize = network::MAX_RX_PACKET_SIZE;
104#[cfg(not(feature = "large-buffers"))]
105pub(crate) const MAX_TX_BUF_SIZE: usize = network::MAX_TX_PACKET_SIZE;
106
107/// The maximum application payload of an incoming (RX) packet, i.e. the RX buffer
108/// minus the two packet headers (plain + protocol) and the trailing AEAD tag.
109pub const MAX_RX_PAYLOAD_SIZE: usize =
110    MAX_RX_BUF_SIZE - PacketHdr::HDR_RESERVE - PacketHdr::TAIL_RESERVE;
111
112/// The maximum application payload of an outgoing (TX) packet, i.e. the TX buffer
113/// minus the two packet headers (plain + protocol) and the trailing AEAD tag.
114pub const MAX_TX_PAYLOAD_SIZE: usize =
115    MAX_TX_BUF_SIZE - PacketHdr::HDR_RESERVE - PacketHdr::TAIL_RESERVE;
116
117/// Represents the state of the transport layer of a `Matter` instance.
118pub struct Transport {
119    /// Buffer for an incoming (RX) packet.
120    // TODO XXX FIXME: Needs multiple wakers for work-stealing executors
121    rx: IfMutex<Packet<MAX_RX_BUF_SIZE>>,
122    /// Buffer for an outgoing (TX) packet.
123    // TODO XXX FIXME: Needs multiple wakers for work-stealing executors
124    tx: IfMutex<Packet<MAX_TX_BUF_SIZE>>,
125    /// List of currently joined group addresses, used for managing multicast group membership.
126    /// Zero-capacity (and thus never populated) without the `groups` feature.
127    group_addrs: IfMutex<Vec<Ipv6Addr, MAX_GROUP_ADDRS>>,
128    /// Notification for when an exchange is dropped.
129    exchange_dropped: Notification,
130    /// A notification that the Matter mDNS services might have changed
131    mdns_changed: Notification,
132    /// The single in-flight mDNS resolve rendezvous, shared between [`Transport::resolve`]
133    /// callers and the running mDNS responder.
134    mdns_resolve: Signal<MdnsResolveState>,
135    /// The single in-flight mDNS commissionable-browse rendezvous, shared between
136    /// [`Transport::browse_commissionable`] callers and the running mDNS responder.
137    mdns_browse: Signal<MdnsBrowseState>,
138    /// A notification that a session had been removed
139    session_removed: Notification,
140    /// A notification that the groups have been modified.
141    /// Unused without the `groups` feature, but kept unconditionally so the
142    /// in-place `Transport` initializer needs no feature-specific variant.
143    #[cfg_attr(not(feature = "groups"), allow(dead_code))]
144    groups_modified: Notification,
145    /// A notification that the CASE session resumption cache has been
146    /// mutated and should be re-persisted by the background persist
147    /// task (see [`crate::Matter::run_persist_resumption`]).
148    ///
149    /// Kept as a field unconditionally (a zero-cost `Notification`) so the
150    /// `new`/`init` constructors need no `case-resumption`-specific variant;
151    /// only the notify/wait accessors are gated.
152    #[cfg_attr(not(feature = "case-resumption"), allow(dead_code))]
153    resumption_dirty: Notification,
154    /// Counters for the messages crossing this node.
155    counters: Mutex<RefCell<MessageCounters>>,
156    /// Device SAI (Secure Association Identifier)
157    device_sai: Option<u32>,
158    /// Device SII (Secure Identity Identifier)
159    device_sii: Option<u32>,
160}
161
162impl Transport {
163    /// Create a new `Transport` with empty RX and TX buffers, and the given device SAI/SII.
164    #[inline(always)]
165    pub(crate) const fn new(dev_det: &BasicInfoConfig<'_>) -> Self {
166        Self {
167            rx: IfMutex::new(Packet::new()),
168            tx: IfMutex::new(Packet::new()),
169            group_addrs: IfMutex::new(Vec::new()),
170            exchange_dropped: Notification::new(),
171            mdns_changed: Notification::new(),
172            mdns_resolve: Signal::new(MdnsResolveState::Idle),
173            mdns_browse: Signal::new(MdnsBrowseState::Idle),
174            session_removed: Notification::new(),
175            groups_modified: Notification::new(),
176            resumption_dirty: Notification::new(),
177            counters: Mutex::new(RefCell::new(MessageCounters::new())),
178            device_sai: dev_det.sai,
179            device_sii: dev_det.sii,
180        }
181    }
182
183    /// Initialize the transport state by initializing the RX and TX buffers, and setting up the exchange dropped notification.
184    pub(crate) fn init<'m>(dev_det: &'m BasicInfoConfig<'m>) -> impl Init<Self> + 'm {
185        init!(Self {
186            rx <- IfMutex::init(Packet::init()),
187            tx <- IfMutex::init(Packet::init()),
188            group_addrs <- IfMutex::init(Vec::init()),
189            exchange_dropped <- Notification::init(),
190            mdns_changed <- Notification::init(),
191            mdns_resolve <- Signal::init(MdnsResolveState::Idle),
192            mdns_browse <- Signal::init(MdnsBrowseState::Idle),
193            session_removed <- Notification::init(),
194            groups_modified <- Notification::init(),
195            resumption_dirty <- Notification::init(),
196            counters <- Mutex::init(RefCell::init(MessageCounters::new())),
197            device_sai: dev_det.sai,
198            device_sii: dev_det.sii,
199        })
200    }
201
202    /// Reset the transport state by clearing the RX buffer and the TX buffer
203    /// NOTE: User should be careful _not_ to call this method while the transport layer and/or the built-in mDNS is running.
204    pub fn reset(&self) -> Result<(), Error> {
205        self.rx
206            .try_lock()
207            .map_err(|_| ErrorCode::InvalidState)?
208            .buf
209            .clear();
210        self.tx
211            .try_lock()
212            .map_err(|_| ErrorCode::InvalidState)?
213            .buf
214            .clear();
215
216        Ok(())
217    }
218
219    /// Return a reference to the transport RX buffer.
220    ///
221    /// Useful when external code (like i.e. a user-provided mDNS implementation)
222    /// needs an RX buffer.
223    pub fn rx_buffer(&self) -> PacketBufferExternalAccess<'_, MAX_RX_BUF_SIZE> {
224        PacketBufferExternalAccess(&self.rx)
225    }
226
227    /// Return a reference to the transport TX buffer.
228    ///
229    /// Useful when external code (like i.e. a user-provided mDNS implementation)
230    /// needs a TX buffer.
231    pub fn tx_buffer(&self) -> PacketBufferExternalAccess<'_, MAX_TX_BUF_SIZE> {
232        PacketBufferExternalAccess(&self.tx)
233    }
234
235    /// Notify that the Matter mDNS services _might_ have changed.
236    pub(crate) fn notify_mdns_changed(&self) {
237        self.mdns_changed.notify();
238    }
239
240    /// A hook for user code to wait for notification that the Matter mDNS services might have changed.
241    ///
242    /// Once this future resolves, user code is supposed to inspect the mDNS services for changes, and
243    /// if there are changes, re-publish the changed mDNS services in an mDNS responder accordingly.
244    pub fn wait_mdns(&self) -> impl Future<Output = ()> + '_ {
245        self.mdns_changed.wait()
246    }
247
248    /// Notify that groups have changed (keys, key maps, or membership) and
249    /// multicast registrations need updating.
250    #[cfg(feature = "groups")]
251    pub(crate) fn notify_groups_changed(&self) {
252        self.groups_modified.notify();
253    }
254
255    /// Wait until the groups have changed (see [`Transport::notify_groups_changed`]).
256    #[cfg(feature = "groups")]
257    fn wait_groups_changed(&self) -> impl Future<Output = ()> + '_ {
258        self.groups_modified.wait()
259    }
260
261    /// The message counters of this node.
262    pub fn counters(&self) -> MessageCounters {
263        self.counters.lock(|counters| counters.borrow().clone())
264    }
265
266    /// Notify that a session has been removed.
267    pub(crate) fn notify_session_removed(&self) {
268        self.session_removed.notify();
269    }
270
271    /// Wait until a session has been removed (see [`Transport::notify_session_removed`]).
272    pub(crate) fn wait_session_removed(&self) -> impl Future<Output = ()> + '_ {
273        self.session_removed.wait()
274    }
275
276    /// Notify that the CASE session resumption cache was mutated and
277    /// should be re-persisted. Fired from every code path that inserts,
278    /// rotates or evicts a [`crate::sc::case::ResumableSession`].
279    #[cfg(feature = "case-resumption")]
280    pub(crate) fn notify_resumption_dirty(&self) {
281        self.resumption_dirty.notify();
282    }
283
284    /// Wait until the CASE session resumption cache has been mutated
285    /// (see [`Transport::notify_resumption_dirty`]). Used by the
286    /// [`crate::Matter::run_persist_resumption`] background task to
287    /// know when to flush the cache to storage.
288    #[cfg(feature = "case-resumption")]
289    pub(crate) fn wait_resumption_dirty(&self) -> impl Future<Output = ()> + '_ {
290        self.resumption_dirty.wait()
291    }
292
293    /// Resolve a Matter service instance's address over mDNS.
294    ///
295    /// Places a single resolve request into the shared rendezvous and waits up
296    /// to `timeout_ms` for the running mDNS responder to fire the query and
297    /// deposit a service answer. Multiple concurrent callers serialize: each waits for
298    /// any in-flight resolve to finish before placing its own.
299    ///
300    /// Returns [`ErrorCode::NotFound`] if no service answer arrives within the timeout.
301    /// If this future is dropped before completing, the rendezvous is reset so
302    /// other callers can proceed.
303    ///
304    /// Requires a running mDNS responder (e.g. `BuiltinMdns::run`) to
305    /// service the request; without one, every resolve will time out.
306    async fn resolve(
307        &self,
308        service: MatterRemoteService,
309        timeout_ms: u32,
310    ) -> Result<ResolvedNode, Error> {
311        // 1. Serialize with other resolvers and place the request.
312        self.mdns_resolve
313            .wait(|state| {
314                if matches!(state, MdnsResolveState::Idle) {
315                    *state = MdnsResolveState::Requested {
316                        service: service.clone(),
317                    };
318                    Some(())
319                } else {
320                    None
321                }
322            })
323            .await;
324
325        // 2. Ensure the slot is released if this future is dropped (or times out).
326        let mut guard = MdnsResolveGuard {
327            signal: &self.mdns_resolve,
328            armed: true,
329        };
330
331        // 3. Wait for the responder to deposit our answer, or time out.
332        let mut wait = pin!(self.mdns_resolve.wait(|state| match state {
333            MdnsResolveState::Resolved {
334                ip,
335                port,
336                scope_id,
337                sii,
338                sai,
339                sat,
340            } => {
341                let node = ResolvedNode {
342                    addr: Self::scoped_socket_addr(*ip, *port, *scope_id),
343                    sii: *sii,
344                    sai: *sai,
345                    sat: *sat,
346                };
347                *state = MdnsResolveState::Idle;
348                Some(node)
349            }
350            _ => None,
351        }));
352
353        let mut timer = pin!(Timer::after(Duration::from_millis(timeout_ms as u64)));
354
355        match select(&mut wait, &mut timer).await {
356            Either::First(node) => {
357                // The `wait` above already reset the slot to `Idle`.
358                guard.armed = false;
359
360                Ok(node)
361            }
362            Either::Second(_) => Err(ErrorCode::NotFound.into()),
363        }
364    }
365
366    /// Responder-side: await the next pending mDNS resolve request, marking it
367    /// in-flight.
368    ///
369    /// Part of the public responder contract: a third-party mDNS responder
370    /// (living outside this crate) drives the resolve use case by awaiting a
371    /// request here, issuing its own query, and depositing any answers via
372    /// [`Transport::try_deposit_mdns_resolve`].
373    pub async fn wait_mdns_resolve_request(&self) -> MatterRemoteService {
374        self.mdns_resolve
375            .wait(|state| match state {
376                MdnsResolveState::Requested { service } => {
377                    let service = service.clone();
378                    *state = MdnsResolveState::InFlight {
379                        service: service.clone(),
380                    };
381                    Some(service)
382                }
383                _ => None,
384            })
385            .await
386    }
387
388    /// Whether an operational resolve is currently in flight.
389    ///
390    /// Used by mDNS implementations to poll-drive their resolve
391    /// loop only while a request is outstanding.
392    #[allow(dead_code)]
393    pub fn mdns_resolve_in_flight(&self) -> bool {
394        self.mdns_resolve
395            .modify(|state| (false, matches!(state, MdnsResolveState::InFlight { .. })))
396    }
397
398    /// Deposit a discovered [`MdnsRemoteService`] against an in-flight resolve
399    /// request, transitioning it to `Resolved` if the answer's instance name
400    /// matches. Best-effort: a non-matching, address-less, or absent request is
401    /// a no-op.
402    ///
403    /// The single resolve-deposit entry point shared by the builtin parser, the
404    /// OS-backed responders, and any third-party responder - all hand it an
405    /// [`MdnsRemoteService`] (the builtin lazily over the packet, the others over
406    /// their native records).
407    pub fn try_deposit_mdns_resolve<'a, I, A, T>(&self, answer: &MdnsRemoteService<I, A, T>)
408    where
409        I: ToLabelIter,
410        A: Iterator<Item = IpAddr> + Clone,
411        T: Iterator<Item = (&'a str, &'a str)> + Clone,
412    {
413        let Some(ip) = answer.addrs.clone().max_by_key(score_ip_address) else {
414            return;
415        };
416        let Some(port) = answer.port else {
417            return;
418        };
419        let scope_id = answer.scope_id;
420
421        let (sii, sai, sat) = answer.session_params();
422
423        self.mdns_resolve.modify(|state| match state {
424            MdnsResolveState::InFlight { service }
425                if service.matches_instance(&answer.instance_name) =>
426            {
427                *state = MdnsResolveState::Resolved {
428                    ip,
429                    port,
430                    scope_id,
431                    sii,
432                    sai,
433                    sat,
434                };
435                (true, ())
436            }
437            // Already resolved (same in-flight target — the rendezvous is
438            // single-slot) but not yet consumed: keep the better-scoring address
439            // so a later IPv6 deposit can upgrade an earlier IPv4 one (e.g.
440            // zeroconf surfaces one address per family at a time).
441            //
442            // Preserve any session params already resolved: a per-address deposit
443            // may carry no TXT (e.g. zeroconf's `ServiceDiscovery.txt` is
444            // optional and can arrive only on a different callback than the
445            // address), in which case `sii`/`sai`/`sat` are `None` here and would
446            // otherwise clobber the good values from the earlier deposit.
447            MdnsResolveState::Resolved {
448                ip: cur_ip,
449                sii: cur_sii,
450                sai: cur_sai,
451                sat: cur_sat,
452                ..
453            } if score_ip_address(&ip) > score_ip_address(cur_ip) => {
454                *state = MdnsResolveState::Resolved {
455                    ip,
456                    port,
457                    scope_id,
458                    sii: sii.or(*cur_sii),
459                    sai: sai.or(*cur_sai),
460                    sat: sat.or(*cur_sat),
461                };
462                (true, ())
463            }
464            _ => (false, ()),
465        });
466    }
467
468    /// Browse the mDNS network for a **commissionable** node matching `filter`,
469    /// returning the first match's address and commissionable instance id.
470    ///
471    /// Places a single browse request into the shared rendezvous and waits up to
472    /// `timeout_ms` for the running mDNS responder to fire the browse query and
473    /// deposit the first node whose advertisement matches **all** non-`None`
474    /// fields of `filter` (see [`CommissionableFilter::matches`]) and whose
475    /// commissionable instance id is **not** in `exclude`. Multiple concurrent
476    /// callers serialize.
477    ///
478    /// `exclude` is how a caller steps to the **next** candidate when several
479    /// nodes share a (short) discriminator: pass the ids already tried (PASE
480    /// failed), and the next un-tried match is returned; repeat until
481    /// [`ErrorCode::NotFound`] (exhausted). Pass `&[]` for the first attempt.
482    /// At most [`MAX_BROWSE_EXCLUDE`](crate::transport::network::mdns) ids - more
483    /// returns [`ErrorCode::ResourceExhausted`].
484    ///
485    /// Returns `(address, commissionable_instance_id)` - the address can be fed
486    /// straight into [`Transport::initiate_pase`] to start PASE. Returns
487    /// [`ErrorCode::NotFound`] on timeout; the rendezvous is reset if this future
488    /// is dropped.
489    ///
490    /// Requires a running mDNS responder (e.g. `BuiltinMdns::run`).
491    ///
492    // TODO: A BLE/BTP equivalent is needed to discover wireless devices that
493    // advertise commissionable over BLE rather than mDNS - future work.
494    pub async fn browse_commissionable(
495        &self,
496        filter: &CommissionableFilter,
497        exclude: &[u64],
498        timeout_ms: u32,
499    ) -> Result<(Address, u64), Error> {
500        let mut exclude_vec = BrowseExclude::new();
501        exclude_vec
502            .extend_from_slice(exclude)
503            .map_err(|_| ErrorCode::ResourceExhausted)?;
504
505        // 1. Serialize with other browsers and place the request.
506        self.mdns_browse
507            .wait(|state| {
508                if matches!(state, MdnsBrowseState::Idle) {
509                    *state = MdnsBrowseState::Requested {
510                        filter: filter.clone(),
511                        exclude: exclude_vec.clone(),
512                    };
513                    Some(())
514                } else {
515                    None
516                }
517            })
518            .await;
519
520        // 2. Release the slot if this future is dropped (or times out).
521        let mut guard = MdnsBrowseGuard {
522            signal: &self.mdns_browse,
523            armed: true,
524        };
525
526        // 3. Wait for the first matching, non-excluded commissionable node, or time out.
527        let mut wait = pin!(self.mdns_browse.wait(|state| match state {
528            MdnsBrowseState::Found {
529                ip,
530                port,
531                scope_id,
532                id,
533            } => {
534                let found = (
535                    Address::Udp(Self::scoped_socket_addr(*ip, *port, *scope_id)),
536                    *id,
537                );
538                *state = MdnsBrowseState::Idle;
539                Some(found)
540            }
541            _ => None,
542        }));
543
544        let mut timer = pin!(Timer::after(Duration::from_millis(timeout_ms as u64)));
545
546        match select(&mut wait, &mut timer).await {
547            Either::First(found) => {
548                guard.armed = false;
549
550                Ok(found)
551            }
552            Either::Second(_) => Err(ErrorCode::NotFound.into()),
553        }
554    }
555
556    /// Responder-side: await the next pending mDNS browse request, marking it
557    /// in-flight. Returns the filter to query for (the exclude set is consulted
558    /// later, at deposit time).
559    ///
560    /// Part of the public responder contract: a third-party mDNS responder
561    /// (living outside this crate) drives the commissionable-browse use case by
562    /// awaiting a request here, issuing its own query, and depositing matches via
563    /// [`Transport::try_deposit_mdns_browse`].
564    pub async fn wait_mdns_browse_request(&self) -> CommissionableFilter {
565        self.mdns_browse
566            .wait(|state| match state {
567                MdnsBrowseState::Requested { filter, exclude } => {
568                    let filter = filter.clone();
569                    *state = MdnsBrowseState::InFlight {
570                        filter: filter.clone(),
571                        exclude: core::mem::take(exclude),
572                    };
573
574                    Some(filter)
575                }
576                _ => None,
577            })
578            .await
579    }
580
581    /// Whether a commissionable browse is currently in flight.
582    ///
583    /// Used by mDNS implementations to poll-drive their browse
584    /// loop only while a request is outstanding.
585    #[allow(dead_code)]
586    pub fn mdns_browse_in_flight(&self) -> bool {
587        self.mdns_browse
588            .modify(|state| (false, matches!(state, MdnsBrowseState::InFlight { .. })))
589    }
590
591    /// Deposit a discovered [`MdnsRemoteService`] against an in-flight browse
592    /// request, transitioning it to `Found` if the instance is not excluded and
593    /// its TXT records match the filter. Best-effort: a non-matching, excluded,
594    /// address-less, or absent request is a no-op.
595    ///
596    /// The single browse-deposit entry point shared by the builtin parser, the
597    /// OS-backed responders, and any third-party responder - all hand it an
598    /// [`MdnsRemoteService`].
599    pub fn try_deposit_mdns_browse<'a, I, A, T>(&self, answer: &MdnsRemoteService<I, A, T>)
600    where
601        I: ToLabelIter,
602        A: Iterator<Item = IpAddr> + Clone,
603        T: Iterator<Item = (&'a str, &'a str)> + Clone,
604    {
605        let Some(id) = commissionable_instance_id(&answer.instance_name) else {
606            return;
607        };
608        let Some(ip) = answer.addrs.clone().max_by_key(score_ip_address) else {
609            return;
610        };
611        let Some(port) = answer.port else {
612            return;
613        };
614        let scope_id = answer.scope_id;
615
616        self.mdns_browse.modify(|state| match state {
617            MdnsBrowseState::InFlight { filter, exclude }
618                if !exclude.contains(&id) && filter.matches(answer) =>
619            {
620                *state = MdnsBrowseState::Found {
621                    ip,
622                    port,
623                    scope_id,
624                    id,
625                };
626                (true, ())
627            }
628            // Already matched this same instance but not yet consumed: keep the
629            // better-scoring address. Backends that surface one address per
630            // family at a time (e.g. zeroconf fires a callback per A/AAAA record)
631            // can thus still yield the preferred IPv6 address even if the IPv4
632            // one was deposited first.
633            MdnsBrowseState::Found {
634                ip: cur_ip,
635                id: cur_id,
636                ..
637            } if *cur_id == id && score_ip_address(&ip) > score_ip_address(cur_ip) => {
638                *state = MdnsBrowseState::Found {
639                    ip,
640                    port,
641                    scope_id,
642                    id,
643                };
644                (true, ())
645            }
646            _ => (false, ()),
647        });
648    }
649
650    pub(crate) async fn accept_if<'a, F>(
651        &self,
652        matter: &'a Matter<'a>,
653        mut f: F,
654    ) -> Result<Exchange<'a>, Error>
655    where
656        F: FnMut(&Session, &ExchangeState, &Packet<MAX_RX_BUF_SIZE>) -> bool,
657    {
658        let exchange = self
659            .rx
660            .with(|packet| {
661                matter.with_state(|state| {
662                    let session = state
663                        .sessions
664                        .get_for_rx(&packet.peer, &packet.header.plain)?;
665                    let exch_index = session.get_exch_for_rx(&packet.header.proto)?;
666
667                    let matches = {
668                        // `unwrap` is safe because the transport code is single threaded, and since we don't `await`
669                        // after computing `exch_index` no code can remove the exchange from the session
670                        let exch = unwrap!(session.exchanges[exch_index].as_ref());
671
672                        matches!(exch.role, Role::Responder(ResponderState::AcceptPending))
673                            && f(session, exch, packet)
674                    };
675
676                    if !matches {
677                        return None;
678                    }
679
680                    // `unwrap` is safe because the transport code is single threaded, and since we don't `await`
681                    // after computing `exch_index` no code can remove the exchange from the session
682                    let exch = unwrap!(session.exchanges[exch_index].as_mut());
683
684                    exch.role = Role::Responder(ResponderState::Owned);
685
686                    let id = ExchangeId::new(session.id, exch_index);
687
688                    debug!("Exchange {}: Accepted", id.display(session));
689
690                    let exchange = Exchange::new(id, matter);
691
692                    Some(exchange)
693                })
694            })
695            .await;
696
697        Ok(exchange)
698    }
699
700    /// The mDNS resolve timeout used when auto-establishing a session
701    /// (CASE operational resolve, or PASE commissionable resolve).
702    /// Single-shot for now (no backoff re-query); see follow-ups.
703    const RESOLVE_TIMEOUT_MS: u32 = 5_000;
704
705    /// Open an exchange over a CASE session to an already-commissioned node.
706    ///
707    /// If a CASE session for `(fabric_idx, peer_node_id)` already exists, an
708    /// exchange is opened on it directly (the common case - session and peer
709    /// address reused, no mDNS). Otherwise the peer's operational address is
710    /// resolved over mDNS and a fresh CASE session is established (driving
711    /// [`CaseInitiator`]) before the exchange is opened on it; the peer's
712    /// MRP/session parameters advertised in the mDNS TXT records seed the
713    /// session.
714    pub(crate) async fn initiate<'a, C: Crypto>(
715        &self,
716        matter: &'a Matter<'a>,
717        crypto: C,
718        fabric_idx: NonZeroU8,
719        peer_node_id: NodeId,
720    ) -> Result<Exchange<'a>, Error> {
721        // Reuse an existing CASE session if present.
722        let existing = matter.with_state(|state| {
723            Ok::<_, Error>(
724                state
725                    .sessions
726                    .get_for_node(fabric_idx, peer_node_id)
727                    .map(|s| s.id),
728            )
729        })?;
730
731        if let Some(session_id) = existing {
732            return self.initiate_for_session(matter, crypto, session_id);
733        }
734
735        self.initiate_new_case(matter, crypto, fabric_idx, peer_node_id)
736            .await
737    }
738
739    /// Establish a fresh CASE session to an already-commissioned peer (resolving
740    /// its operational address over mDNS), then open an exchange on it. Called by
741    /// [`initiate`](Self::initiate) only when no live session to the peer exists.
742    ///
743    /// With the `case-responder-only` feature this is compiled out and replaced
744    /// by an immediate [`ErrorCode::NoSession`]: reporting (and every other
745    /// on-demand initiator) then reuses an existing session or fails, and the
746    /// linker drops the whole CASE-initiator and mDNS-resolver machinery.
747    #[cfg(not(feature = "case-responder-only"))]
748    async fn initiate_new_case<'a, C: Crypto>(
749        &self,
750        matter: &'a Matter<'a>,
751        crypto: C,
752        fabric_idx: NonZeroU8,
753        peer_node_id: NodeId,
754    ) -> Result<Exchange<'a>, Error> {
755        // No CASE session: resolve the operational address and establish one.
756        let compressed_fabric_id = matter.with_state(|state| {
757            Ok::<_, Error>(state.fabrics.fabric(fabric_idx)?.compressed_fabric_id())
758        })?;
759
760        let service = MatterRemoteService::Operational {
761            compressed_fabric_id,
762            node_id: peer_node_id,
763        };
764
765        let resolved = self.resolve(service, Self::RESOLVE_TIMEOUT_MS).await?;
766
767        // Establish CASE over a fresh, one-shot unsecured exchange to the
768        // resolved address. On success a secure session keyed at
769        // `(fabric_idx, peer_node_id)` is recorded in the stack.
770        let exchange = self
771            .initiate_plaintext(matter, &crypto, Address::Udp(resolved.addr))
772            .await?;
773
774        CaseInitiator::perform(exchange, &crypto, fabric_idx, peer_node_id).await?;
775
776        // Seed the new CASE session's peer MRP/session params from the resolve
777        // TXT (rs-matter does not yet exchange these in CASE Sigma1/2) and grab
778        // its id for the exchange.
779        let params = SessionParameters {
780            sii: resolved.sii,
781            sai: resolved.sai,
782            sat: resolved.sat,
783            ..Default::default()
784        };
785
786        let session_id = matter.with_state(|state| {
787            let session = state
788                .sessions
789                .get_for_node(fabric_idx, peer_node_id)
790                .ok_or(ErrorCode::NoSession)?;
791
792            session.set_peer_session_params(&params);
793
794            Ok::<_, Error>(session.id)
795        })?;
796
797        self.initiate_for_session(matter, crypto, session_id)
798    }
799
800    /// The `case-responder-only` variant: never establish a new CASE session.
801    ///
802    /// See the other [`initiate_new_case`](Self::initiate_new_case) for why this
803    /// exists. Reporting (and any other on-demand initiator) reuses an existing
804    /// session or gets [`ErrorCode::NoSession`]; nothing here references the CASE
805    /// initiator or the mDNS resolver, so the linker can drop them.
806    #[cfg(feature = "case-responder-only")]
807    async fn initiate_new_case<'a, C: Crypto>(
808        &self,
809        _matter: &'a Matter<'a>,
810        _crypto: C,
811        _fabric_idx: NonZeroU8,
812        _peer_node_id: NodeId,
813    ) -> Result<Exchange<'a>, Error> {
814        Err(ErrorCode::NoSession.into())
815    }
816
817    /// Open an exchange over a fresh **unsecured** session to an already-
818    /// commissioned node, resolving its operational address over mDNS.
819    ///
820    /// Unlike [`initiate`](Self::initiate) this establishes no secure session; it
821    /// is for sessionless protocols that carry their own security (e.g. the
822    /// Check-In message), and it never reuses or creates a CASE session.
823    ///
824    /// Requires a running mDNS responder to service the resolve; without one the
825    /// resolve times out and this returns [`ErrorCode::NotFound`].
826    pub(crate) async fn initiate_plaintext_operational<'a, C: Crypto>(
827        &self,
828        matter: &'a Matter<'a>,
829        crypto: C,
830        fabric_idx: NonZeroU8,
831        peer_node_id: NodeId,
832    ) -> Result<Exchange<'a>, Error> {
833        let compressed_fabric_id = matter.with_state(|state| {
834            Ok::<_, Error>(state.fabrics.fabric(fabric_idx)?.compressed_fabric_id())
835        })?;
836
837        let service = MatterRemoteService::Operational {
838            compressed_fabric_id,
839            node_id: peer_node_id,
840        };
841
842        let resolved = self.resolve(service, Self::RESOLVE_TIMEOUT_MS).await?;
843
844        self.initiate_plaintext(matter, crypto, Address::Udp(resolved.addr))
845            .await
846    }
847
848    /// Open an exchange over a PASE session to a not-yet-commissioned node at the
849    /// given peer address.
850    ///
851    /// If a PASE session **to that peer** already exists, an exchange is opened on
852    /// it directly. Otherwise a new PASE session is established: a plaintext
853    /// session is opened and the PASE protocol ([`PaseInitiator`]) is run with
854    /// `passcode`, then an exchange is opened on the resulting PASE session.
855    ///
856    /// Reuse is keyed by peer address (not a single global PASE session), so a
857    /// commissioner can drive several concurrent commissionings.
858    ///
859    /// Discovery of the address is out of scope (mDNS via
860    /// [`Transport::browse_commissionable`], a BLE/BTP advertisement, etc.); this
861    /// method is transport-agnostic and takes the already-known address.
862    pub(crate) async fn initiate_pase<'a, C: Crypto>(
863        &self,
864        matter: &'a Matter<'a>,
865        crypto: C,
866        peer_addr: Address,
867        passcode: u32,
868    ) -> Result<Exchange<'a>, Error> {
869        // Reuse an existing PASE session to this peer, if present.
870        let existing = matter.with_state(|state| {
871            Ok::<_, Error>(state.sessions.get_pase_for_addr(&peer_addr).map(|s| s.id))
872        })?;
873
874        if let Some(session_id) = existing {
875            return self.initiate_for_session(matter, crypto, session_id);
876        }
877
878        // Establish a new PASE session to this peer.
879        let exchange = self.initiate_plaintext(matter, &crypto, peer_addr).await?;
880        PaseInitiator::perform(exchange, &crypto, passcode).await?;
881
882        let session_id = matter.with_state(|state| {
883            state
884                .sessions
885                .get_pase_for_addr(&peer_addr)
886                .map(|s| s.id)
887                .ok_or_else(|| Error::from(ErrorCode::NoSession))
888        })?;
889
890        self.initiate_for_session(matter, crypto, session_id)
891    }
892
893    pub(crate) fn initiate_for_session<'a, C: Crypto>(
894        &self,
895        matter: &'a Matter<'a>,
896        crypto: C,
897        session_id: u32,
898    ) -> Result<Exchange<'a>, Error> {
899        matter.with_state(|state| {
900            state
901                .sessions
902                .get(session_id)
903                // Expired sessions are not allowed to initiate new exchanges
904                .filter(|sess| !sess.is_expired())
905                .ok_or(ErrorCode::NoSession)?;
906
907            let exch_id = state.sessions.get_next_exch_id(crypto)?;
908
909            // `unwrap` is safe because we know we have a session or else the early return from above would've triggered
910            // The reason why we call `get_for_node` twice is to ensure that we don't waste an `exch_id` in case
911            // we don't have a session in the first place
912            let session = unwrap!(state.sessions.get(session_id));
913
914            let exch_index = session
915                .add_exch(exch_id, Role::Initiator(Default::default()))
916                .ok_or(ErrorCode::NoSpaceExchanges)?;
917
918            let id = ExchangeId::new(session.id, exch_index);
919
920            debug!("Exchange {}: Initiated", id.display(session));
921
922            Ok(Exchange::new(id, matter))
923        })
924    }
925
926    /// Create a new initiator exchange on a new plaintext session to
927    /// the given peer address, evicting an existing session and retrying once if
928    /// there is no space.
929    ///
930    /// Low-level primitive used to carry the first handshake message of PASE
931    /// ([`PaseInitiator`]) or CASE ([`CaseInitiator`]).
932    async fn initiate_plaintext<'a, C: Crypto>(
933        &self,
934        matter: &'a Matter<'a>,
935        crypto: C,
936        peer_addr: Address,
937    ) -> Result<Exchange<'a>, Error> {
938        match self.try_initiate_plaintext(matter, &crypto, peer_addr) {
939            Ok(exchange) => Ok(exchange),
940            Err(e) if e.code() == ErrorCode::NoSpaceSessions => {
941                matter
942                    .transport_runner(&crypto)
943                    .evict_some_session()
944                    .await?;
945                self.try_initiate_plaintext(matter, &crypto, peer_addr)
946            }
947            Err(e) => Err(e),
948        }
949    }
950
951    /// Create a new plaintext session and initiate an exchange on it in one step.
952    ///
953    /// This is a convenience method that combines `create_plaintext_session()` and
954    /// `initiate_for_session()`. Fails immediately if there is no space for a new session.
955    ///
956    /// For flows that need the session ID (e.g. to upgrade the session after PASE/CASE),
957    /// use `create_plaintext_session()` + `initiate_for_session()` separately.
958    fn try_initiate_plaintext<'a, C: Crypto>(
959        &self,
960        matter: &'a Matter<'a>,
961        crypto: C,
962        peer_addr: Address,
963    ) -> Result<Exchange<'a>, Error> {
964        let session_id = self.create_plaintext_session(matter, &crypto, peer_addr)?;
965
966        self.initiate_for_session(matter, crypto, session_id)
967    }
968
969    /// Create a new unsecured (plain-text) session to a given peer address.
970    ///
971    /// Returns the internal session ID that can be used with `initiate_for_session()`.
972    ///
973    /// This is the low-level building block for controller-initiated communication
974    /// (e.g. PASE/CASE initiator flows), analogous to the SDK's
975    /// `SessionManager::CreateUnauthenticatedSession()`.
976    fn create_plaintext_session<C: Crypto>(
977        &self,
978        matter: &Matter<'_>,
979        crypto: C,
980        peer_addr: Address,
981    ) -> Result<u32, Error> {
982        matter.with_state(|state| {
983            let mut rand = crypto.rand()?;
984
985            let session =
986                state
987                    .sessions
988                    .add(rand.next_u32(), false, peer_addr, None, matter.dev_det())?;
989
990            // Generate ephemeral initiator node ID per spec:
991            // "Randomly selected for each session by the initiator from the Operational Node ID range"
992            // Operational Node ID range is 0x0000_0000_0000_0001 to 0xFFFF_FFEF_FFFF_FFFF
993            // (Matter Core spec).
994            const MAX_OPERATIONAL_NODE_ID: u64 = 0xFFFF_FFEF_FFFF_FFFF;
995            let mut ephemeral_id = rand.next_u64();
996            while ephemeral_id == 0 || ephemeral_id > MAX_OPERATIONAL_NODE_ID {
997                ephemeral_id = rand.next_u64();
998            }
999            session.set_local_nodeid(ephemeral_id);
1000
1001            let session_id = session.id;
1002
1003            debug!(
1004                "Unsecured session {} created for peer {}",
1005                session_id, peer_addr
1006            );
1007
1008            Ok(session_id)
1009        })
1010    }
1011
1012    pub(crate) async fn get_if_rx<F>(&self, f: F) -> PacketAccess<'_, MAX_RX_BUF_SIZE>
1013    where
1014        F: Fn(&Packet<MAX_RX_BUF_SIZE>) -> bool,
1015    {
1016        Self::get_if(&self.rx, f).await
1017    }
1018
1019    pub(crate) async fn get_if_tx<F>(&self, f: F) -> PacketAccess<'_, MAX_TX_BUF_SIZE>
1020    where
1021        F: Fn(&Packet<MAX_TX_BUF_SIZE>) -> bool,
1022    {
1023        Self::get_if(&self.tx, f).await
1024    }
1025
1026    async fn get_if<'b, F, const N: usize>(
1027        packet_mutex: &'b IfMutex<Packet<N>>,
1028        f: F,
1029    ) -> PacketAccess<'b, N>
1030    where
1031        F: Fn(&Packet<N>) -> bool,
1032    {
1033        PacketAccess(packet_mutex.lock_if(f).await, false)
1034    }
1035
1036    /// Build a `SocketAddr` from a resolved `(ip, port)`, attaching the IPv6
1037    /// `scope_id` (interface zone) so a **link-local** destination (`fe80::/10`)
1038    /// is routable.
1039    ///
1040    /// The scope is only meaningful — and only applied — for link-local IPv6;
1041    /// for any other address it is irrelevant and `SocketAddr::new` is used
1042    /// as-is.
1043    fn scoped_socket_addr(ip: IpAddr, port: u16, scope_id: u32) -> SocketAddr {
1044        match ip {
1045            IpAddr::V6(v6) if v6.is_unicast_link_local() => {
1046                SocketAddr::V6(SocketAddrV6::new(v6, port, 0, scope_id))
1047            }
1048            _ => SocketAddr::new(ip, port),
1049        }
1050    }
1051}
1052
1053/// Resets the mDNS resolve rendezvous to `Idle` on drop, unless disarmed.
1054///
1055/// This guarantees that a dropped (cancelled or timed-out) [`Transport::resolve`]
1056/// future does not leave the single-slot rendezvous occupied for other callers.
1057struct MdnsResolveGuard<'a> {
1058    signal: &'a Signal<MdnsResolveState>,
1059    armed: bool,
1060}
1061
1062impl Drop for MdnsResolveGuard<'_> {
1063    fn drop(&mut self) {
1064        if self.armed {
1065            self.signal.modify(|state| {
1066                if matches!(state, MdnsResolveState::Idle) {
1067                    (false, ())
1068                } else {
1069                    *state = MdnsResolveState::Idle;
1070                    (true, ())
1071                }
1072            });
1073        }
1074    }
1075}
1076
1077/// Resets the mDNS browse rendezvous to `Idle` on drop, unless disarmed (the
1078/// browse analog of [`ResolveGuard`]).
1079struct MdnsBrowseGuard<'a> {
1080    signal: &'a Signal<MdnsBrowseState>,
1081    armed: bool,
1082}
1083
1084impl Drop for MdnsBrowseGuard<'_> {
1085    fn drop(&mut self) {
1086        if self.armed {
1087            self.signal.modify(|state| {
1088                if matches!(state, MdnsBrowseState::Idle) {
1089                    (false, ())
1090                } else {
1091                    *state = MdnsBrowseState::Idle;
1092                    (true, ())
1093                }
1094            });
1095        }
1096    }
1097}
1098
1099/// The Matter Transport Runner, responsible for running the network transport by processing incoming and ougoing packets
1100/// and thus also managing sessions and exchanges.
1101///
1102/// The transport runner is wrapping the whole Matter Object, because it needs access to various states, like
1103/// sessions, fabrics and the transport buffers / state itself.
1104pub struct TransportRunner<'a, C> {
1105    matter: &'a Matter<'a>,
1106    crypto: C,
1107}
1108
1109impl<'a, C: Crypto> TransportRunner<'a, C> {
1110    /// Create a new `TransportRunner` instance with the given `Matter` instance and `Crypto` implementation.
1111    pub const fn new(matter: &'a Matter<'a>, crypto: C) -> Self {
1112        Self { matter, crypto }
1113    }
1114
1115    /// Run the transport runner with the given network send, receive and multicast implementations.
1116    ///
1117    /// The `multicast` implementation is only used to manage group (multicast)
1118    /// membership, so without the `groups` feature it is accepted but never used.
1119    pub async fn run<S, R, M>(
1120        &mut self,
1121        send: S,
1122        recv: R,
1123        #[cfg_attr(not(feature = "groups"), allow(unused_variables))] multicast: M,
1124    ) -> Result<(), Error>
1125    where
1126        S: NetworkSend,
1127        R: NetworkReceive,
1128        M: NetworkMulticast,
1129    {
1130        info!("Running Matter transport");
1131
1132        // Do not remove this logging line or change its formatting.
1133        // C++ E2E tests rely on this log line to determine when the tested app is ready
1134        debug!("APP STATUS: Starting event loop");
1135
1136        let send = IfMutex::new(send);
1137
1138        let mut rx = pin!(self.process_rx(recv, &send));
1139        let mut tx = pin!(self.process_tx(&send));
1140        let mut orphaned = pin!(self.process_orphaned());
1141
1142        #[cfg(feature = "groups")]
1143        {
1144            let mut joined = self.transport().group_addrs.lock().await;
1145            let mut groups = pin!(self.process_groups(multicast, &mut joined));
1146
1147            select4(&mut rx, &mut tx, &mut orphaned, &mut groups)
1148                .coalesce()
1149                .await
1150        }
1151
1152        #[cfg(not(feature = "groups"))]
1153        {
1154            select3(&mut rx, &mut tx, &mut orphaned).coalesce().await
1155        }
1156    }
1157
1158    /// Record a `GroupcastTesting` observation for a multicast group data
1159    /// message that failed decode/decryption, when the Groupcast listener
1160    /// testing mode is armed. See the Matter Core spec's `GroupcastTesting`
1161    /// command for the result classification.
1162    #[cfg(feature = "groups")]
1163    fn observe_group_rx_failure<const N: usize>(
1164        &self,
1165        packet: &Packet<N>,
1166        fabrics: &crate::fabric::Fabrics,
1167        e: &Error,
1168    ) {
1169        use crate::dm::clusters::groupcast;
1170
1171        // Only multicast group *data* messages participate (not unicast
1172        // group-encrypted MCSP control messages)
1173        let Some(group_id) = packet.header.plain.get_dst_groupcast_nodeid() else {
1174            return;
1175        };
1176
1177        let Some(mode) = self
1178            .matter
1179            .groupcast_testing()
1180            .armed(groupcast::GroupcastTestingEnum::EnableListenerTesting)
1181        else {
1182            return;
1183        };
1184
1185        let (result, authenticated) = match e.code() {
1186            // No candidate key was available to even attempt decryption
1187            ErrorCode::NoSession => (groupcast::GroupcastTestResultEnum::NoAvailableKey, false),
1188            // Candidate key(s) were tried, none authenticated the message
1189            ErrorCode::InvalidSignature => (groupcast::GroupcastTestResultEnum::FailedAuth, false),
1190            // Authenticated, but a duplicate of an already-received message
1191            ErrorCode::Duplicate => (groupcast::GroupcastTestResultEnum::MessageReplay, true),
1192            _ => (groupcast::GroupcastTestResultEnum::GeneralError, false),
1193        };
1194
1195        self.matter
1196            .groupcast_testing()
1197            .observe(groupcast::TestingObservation {
1198                src_ip: groupcast::TestingObservation::addr_ip(&packet.peer),
1199                dst_ip: Some(groupcast::TestingObservation::group_dst_ip(
1200                    fabrics,
1201                    mode.fab_idx,
1202                    group_id,
1203                )),
1204                // Per the Matter Core spec, the `GroupID` field is filled
1205                // from the request only when the message was properly
1206                // authenticated for the fabric under test
1207                group_id: authenticated.then_some(group_id),
1208                endpoint_id: None,
1209                cluster_id: None,
1210                element_id: None,
1211                access_allowed: None,
1212                result,
1213            });
1214    }
1215
1216    #[cfg(feature = "groups")]
1217    async fn process_groups<M>(
1218        &self,
1219        mut multicast: M,
1220        joined: &mut Vec<Ipv6Addr, MAX_GROUP_ADDRS>,
1221    ) -> Result<(), Error>
1222    where
1223        M: NetworkMulticast,
1224    {
1225        joined.clear();
1226
1227        loop {
1228            let addr_op = self.matter.with_state(|state| {
1229                let group_addrs = || {
1230                    state.fabrics.iter().flat_map(|fabric| {
1231                        fabric.groups().iter().map(|group| {
1232                            // Groups with the `IanaAddr` Groupcast policy all
1233                            // share the IANA-assigned address; `PerGroup` ones
1234                            // (and legacy Groups-cluster entries, which behave
1235                            // as `PerGroup`) each use a fabric+group-scoped
1236                            // address. Duplicate enumeration of the shared
1237                            // address is fine - the join/leave reconciliation
1238                            // below is idempotent per address.
1239                            use crate::dm::clusters::decl::groupcast::MulticastAddrPolicyEnum;
1240
1241                            match group.effective_mcast_policy() {
1242                                MulticastAddrPolicyEnum::IanaAddr => {
1243                                    crate::utils::ipv6::IANA_GROUPCAST_MULTICAST_ADDR
1244                                }
1245                                MulticastAddrPolicyEnum::PerGroup => {
1246                                    compute_group_multicast_addr(fabric.fabric_id(), group.group_id)
1247                                }
1248                            }
1249                        })
1250                    })
1251                };
1252
1253                if let Some(new_addr) = group_addrs().find(|addr| !joined.contains(addr)) {
1254                    Some((new_addr, true))
1255                } else {
1256                    joined
1257                        .iter()
1258                        .find(|addr| !group_addrs().any(|a| a == **addr))
1259                        .map(|&removed_addr| (removed_addr, false))
1260                }
1261            });
1262
1263            match addr_op {
1264                Some((new_addr, true)) => {
1265                    match multicast.join(new_addr.into()).await {
1266                        Ok(_) => {
1267                            debug!("Joined multicast group: {}", new_addr);
1268                            // `joined` should be able to contain theoretical maximum number of multicast address
1269                            // So this unwrap should be safe
1270                            unwrap!(joined.push(new_addr));
1271                        }
1272                        Err(e) => error!(
1273                            "Joining multicast group {} failed with error: {}",
1274                            new_addr, e
1275                        ),
1276                    }
1277                }
1278                Some((removed_addr, false)) => match multicast.leave(removed_addr.into()).await {
1279                    Ok(_) => {
1280                        debug!("Left multicast group: {}", removed_addr);
1281                        let index = joined
1282                            .iter()
1283                            .position(|&addr| addr == removed_addr)
1284                            .unwrap();
1285                        joined.swap_remove(index);
1286                    }
1287                    Err(e) => error!(
1288                        "Leaving multicast group {} failed with error: {}",
1289                        removed_addr, e
1290                    ),
1291                },
1292                None => {
1293                    self.transport().wait_groups_changed().await;
1294                }
1295            }
1296        }
1297    }
1298
1299    async fn process_tx<S>(&self, send: &IfMutex<S>) -> Result<(), Error>
1300    where
1301        S: NetworkSend,
1302    {
1303        loop {
1304            trace!("Waiting for outgoing packet");
1305
1306            let mut tx = self
1307                .matter
1308                .transport
1309                .get_if_tx(|packet| !packet.buf.is_empty())
1310                .await;
1311            tx.clear_on_drop(true);
1312
1313            if let TxPayloadState::NotEncoded { session_id } = tx.tx_info.payload_state {
1314                let encoded = self.matter.with_state(|state| {
1315                    if let Some(session) = state.sessions.get_for_tx(session_id) {
1316                        self.encode_packet(&mut tx, Some(session))?;
1317
1318                        Ok::<_, Error>(true)
1319                    } else {
1320                        error!(
1321                            "TX packet has session ID {}, but no such session exists, dropping",
1322                            session_id
1323                        );
1324
1325                        Ok(false)
1326                    }
1327                })?;
1328
1329                if !encoded {
1330                    continue;
1331                }
1332            }
1333
1334            Self::netw_send(send, tx.peer, &tx.buf[tx.payload_start..], false).await?;
1335
1336            if !tx.tx_info.retransmission {
1337                self.transport()
1338                    .counters
1339                    .lock(|counters| counters.borrow_mut().record_sent(&tx.header.proto));
1340            }
1341        }
1342    }
1343
1344    async fn process_rx<R, S>(&self, mut recv: R, send: &IfMutex<S>) -> Result<(), Error>
1345    where
1346        R: NetworkReceive,
1347        S: NetworkSend,
1348    {
1349        loop {
1350            trace!("Waiting for incoming packet");
1351
1352            recv.wait_available().await?;
1353
1354            let mut rx = self
1355                .matter
1356                .transport
1357                .get_if_rx(|packet| packet.buf.is_empty())
1358                .await;
1359            rx.clear_on_drop(true); // In case of error, or if the future is dropped
1360
1361            // TODO: Resizing might be a bit expensive with large buffers
1362            // Resizing to `MAX_RX_BUF_SIZE` is always safe because the size of the `buf` heapless vec `MAX_RX_BUF_SIZE`
1363            unwrap!(rx.buf.resize_default(MAX_RX_BUF_SIZE));
1364
1365            let (len, peer) = Self::netw_recv(&mut recv, &mut rx.buf).await?;
1366
1367            rx.peer = peer;
1368            rx.buf.truncate(len);
1369            rx.payload_start = 0;
1370
1371            match self.handle_rx_packet(&mut rx, send).await {
1372                Ok(true) => {
1373                    // Leave the packet in place for accepting by responders
1374                    rx.clear_on_drop(false);
1375                }
1376                Ok(false) => {
1377                    // Drop the packet, as no further processing is necessary
1378                }
1379                Err(e) => {
1380                    // Drop the packet and report the unexpected error
1381                    error!("UNEXPECTED RX ERROR: {:?}", e);
1382                }
1383            }
1384        }
1385    }
1386
1387    async fn process_orphaned(&self) -> Result<(), Error> {
1388        let mut rx_accept_timeout = pin!(self.process_accept_timeout_rx());
1389        let mut rx_orphaned = pin!(self.process_orphaned_rx());
1390        let mut exch_dropped = pin!(self.process_dropped_exchanges());
1391
1392        select3(&mut rx_accept_timeout, &mut rx_orphaned, &mut exch_dropped)
1393            .coalesce()
1394            .await
1395    }
1396
1397    async fn process_accept_timeout_rx(&self) -> Result<(), Error> {
1398        loop {
1399            trace!("Waiting for accept timeout");
1400
1401            let mut accept_timeout = pin!(self
1402                .matter
1403                .transport
1404                .rx
1405                .with(|packet| { self.handle_accept_timeout_rx_packet(packet).then_some(()) }));
1406
1407            let mut timer = pin!(Timer::after(embassy_time::Duration::from_millis(50)));
1408
1409            select(&mut accept_timeout, &mut timer).await;
1410        }
1411    }
1412
1413    async fn process_orphaned_rx(&self) -> Result<(), Error> {
1414        loop {
1415            trace!("Waiting for orphaned RX packets");
1416
1417            self.transport()
1418                .rx
1419                .with(|packet| self.handle_orphaned_rx_packet(packet).then_some(()))
1420                .await;
1421        }
1422    }
1423
1424    async fn process_dropped_exchanges(&self) -> Result<(), Error> {
1425        loop {
1426            trace!("Waiting for dropped exchanges");
1427
1428            let mut tx = self
1429                .matter
1430                .transport
1431                .get_if_tx(|packet| packet.buf.is_empty())
1432                .await;
1433            tx.clear_on_drop(true); // In case of error, or if the future is dropped
1434
1435            let wait = match self.handle_dropped_exchange(&mut tx) {
1436                Ok(wait) => {
1437                    tx.clear_on_drop(false);
1438                    wait
1439                }
1440                Err(e) => {
1441                    error!("UNEXPECTED RX ERROR: {:?}", e);
1442                    false
1443                }
1444            };
1445
1446            drop(tx);
1447
1448            if wait {
1449                let mut timeout = pin!(Timer::after(embassy_time::Duration::from_millis(100)));
1450                let mut wait = pin!(self.transport().exchange_dropped.wait());
1451
1452                select(&mut timeout, &mut wait).await;
1453            }
1454        }
1455    }
1456
1457    async fn handle_rx_packet<const N: usize, S>(
1458        &self,
1459        packet: &mut Packet<N>,
1460        send: &IfMutex<S>,
1461    ) -> Result<bool, Error>
1462    where
1463        S: NetworkSend,
1464    {
1465        let result = self.decode_packet(packet);
1466        match result {
1467            Err(e) if matches!(e.code(), ErrorCode::Duplicate) => {
1468                if packet.header.plain.is_group_session() {
1469                    // Group messages are multicast and don't use MRP; silently discard duplicates
1470                    mrp_log!(
1471                        "\n>>RCV {}\n      => Duplicate group message, discarding",
1472                        packet
1473                    );
1474                } else if !packet.peer.is_reliable()
1475                    && !MessageMeta::from(&packet.header.proto).is_standalone_ack()
1476                {
1477                    mrp_log!("\n>>RCV {}\n      => Duplicate, sending ACK", packet);
1478
1479                    self.matter.with_state(|state| {
1480                        // `unwrap` is safe because we know we have a session.
1481                        // If we didn't have a session, the error code would've been `NoSession`
1482                        //
1483                        // Also, since the transport code is single threaded, and since we don't `await`
1484                        // after decoding the packet, no code can the session
1485                        let session = unwrap!(state
1486                            .sessions
1487                            .get_for_rx(&packet.peer, &packet.header.plain));
1488
1489                        let ack = packet.header.plain.ctr;
1490
1491                        packet.header.proto.toggle_initiator();
1492                        packet.header.proto.set_ack(Some(ack));
1493
1494                        self.write_packet(packet, Some(session), None, true, |_| {
1495                            Ok(Some(OpCode::MRPStandAloneAck.into()))
1496                        })
1497                    })?;
1498
1499                    Self::netw_send(send, packet.peer, &packet.buf[packet.payload_start..], true)
1500                        .await?;
1501                } else {
1502                    mrp_log!("\n>>RCV {}\n      => Duplicate, discarding", packet);
1503                }
1504            }
1505            Err(e) if matches!(e.code(), ErrorCode::NoSpaceSessions) => {
1506                if !packet.header.plain.is_encrypted()
1507                    && MessageMeta::from(&packet.header.proto).is_new_session()
1508                {
1509                    warn!(
1510                        "\n>>RCV {}\n      => No space for a new unencrypted session, sending Busy",
1511                        packet
1512                    );
1513
1514                    let ack = packet.header.plain.ctr;
1515
1516                    packet.header.proto.toggle_initiator();
1517                    packet.header.proto.set_ack(Some(ack));
1518
1519                    self.write_packet(packet, None, None, true, |wb| {
1520                        sc_write(wb, SCStatusCodes::Busy, &[0xF4, 0x01])
1521                    })?;
1522
1523                    Self::netw_send(send, packet.peer, &packet.buf[packet.payload_start..], true)
1524                        .await?;
1525
1526                    if self.write_evict_some_session_packet(packet, true)? {
1527                        Self::netw_send(
1528                            send,
1529                            packet.peer,
1530                            &packet.buf[packet.payload_start..],
1531                            true,
1532                        )
1533                        .await?;
1534                    }
1535                } else {
1536                    error!(
1537                        "\n>>RCV {}\n      => No space for a new encrypted session, dropping",
1538                        packet
1539                    );
1540                }
1541            }
1542            Err(e) if matches!(e.code(), ErrorCode::NoSpaceExchanges) => {
1543                // TODO: Before closing the session, try to take other measures:
1544                // - For CASESigma1 & PBKDFParamRequest - send Busy instead
1545                // - For Interaction Model interactions that do need an ACK - send IM Busy,
1546                //   wait for ACK and retransmit without releasing the RX buffer, potentially
1547                //   blocking all other interactions
1548
1549                error!(
1550                    "\n>>RCV {}\n      => No space for a new exchange, closing session",
1551                    packet
1552                );
1553
1554                self.matter.with_state(|state| {
1555                    // `unwrap` is safe because we know we have a session.
1556                    // If we didn't have a session, the error code would've been `NoSession`
1557                    //
1558                    // Also, since the transport code is single threaded, and since we don't `await`
1559                    // after decoding the packet, no code can the session
1560                    let session_id = unwrap!(state
1561                        .sessions
1562                        .get_for_rx(&packet.peer, &packet.header.plain))
1563                    .id;
1564
1565                    packet.header.proto.exch_id = state.sessions.get_next_exch_id(&self.crypto)?;
1566                    packet.header.proto.set_initiator();
1567
1568                    // See above why `unwrap` is safe
1569                    let mut session = unwrap!(state.sessions.remove(session_id));
1570                    self.transport().notify_session_removed();
1571
1572                    self.write_packet(packet, Some(&mut session), None, true, |wb| {
1573                        sc_write(wb, SCStatusCodes::CloseSession, &[])
1574                    })
1575                })?;
1576
1577                Self::netw_send(send, packet.peer, &packet.buf[packet.payload_start..], true)
1578                    .await?;
1579            }
1580            Err(e) if matches!(e.code(), ErrorCode::NoExchange) => {
1581                mrp_log!(
1582                    "\n>>RCV {}\n      => No valid exchange found, dropping",
1583                    packet
1584                );
1585            }
1586            Err(e) if matches!(e.code(), ErrorCode::NoSession) => {
1587                // Per Matter Core spec, when a session-bearing
1588                // message arrives for which we have no matching secure session
1589                // (e.g. after a reboot has wiped the session table while the
1590                // peer still believes the old session is alive), we reply with
1591                // an unsecured `SessionNotFound` Status Report on the Secure
1592                // Channel protocol. This nudges the peer to drop its stale
1593                // session and re-establish CASE, instead of waiting for MRP
1594                // retries to exhaust on its side.
1595                warn!(
1596                    "\n>>RCV {}\n      => No valid session found, replying with SessionNotFound",
1597                    packet
1598                );
1599
1600                // `write_packet` with `session = None` requires the incoming
1601                // header to look like an unsecured, non-reliable, source-tagged
1602                // packet (see its preconditions). Clear `sess_id` so it is not
1603                // considered encrypted, drop the reliable/ack flags since we
1604                // are not party to any exchange, and stamp a placeholder
1605                // `src_nodeid` (echoed as the response's `dst_nodeid` — UDP
1606                // peer addressing is what actually routes the reply).
1607                packet.header.plain.sess_id = 0;
1608                packet.header.plain.set_src_nodeid(Some(0));
1609                packet.header.proto.unset_reliable();
1610                packet.header.proto.set_ack(None);
1611
1612                self.write_packet(packet, None, None, true, |wb| {
1613                    sc_write(wb, SCStatusCodes::SessionNotFound, &[])
1614                })?;
1615
1616                Self::netw_send(send, packet.peer, &packet.buf[packet.payload_start..], true)
1617                    .await?;
1618            }
1619            Err(e) => {
1620                error!("\n>>RCV {}\n      => Error ({:?}), dropping", packet, e);
1621            }
1622            Ok(new_exchange) => {
1623                let meta = MessageMeta::from(&packet.header.proto);
1624
1625                if meta.is_standalone_ack() {
1626                    // No need to propagate this further
1627                    debug!("\n>>RCV {}\n      => Standalone Ack, dropping", packet);
1628                } else if meta.is_sc_status()
1629                    && matches!(
1630                        Self::is_close_session(&mut packet.buf[packet.payload_start..]),
1631                        Ok(true)
1632                    )
1633                {
1634                    warn!(
1635                        "\n>>RCV {}\n      => Close session received, removing this session",
1636                        packet
1637                    );
1638
1639                    self.matter.with_state(|state| {
1640                        if let Some(session_id) = state
1641                            .sessions
1642                            .get_for_rx(&packet.peer, &packet.header.plain)
1643                            .map(|sess| sess.id)
1644                        {
1645                            state.sessions.remove(session_id);
1646                            self.transport().notify_session_removed();
1647                        }
1648                    });
1649                } else {
1650                    self.transport()
1651                        .counters
1652                        .lock(|counters| counters.borrow_mut().record_recv(&packet.header.proto));
1653
1654                    debug!(
1655                        "\n>>RCV {}\n      => Processing{}",
1656                        packet,
1657                        if new_exchange { " (new exchange)" } else { "" }
1658                    );
1659
1660                    #[cfg(feature = "log-tlv-payload")]
1661                    debug!(
1662                        "{}",
1663                        Packet::<0>::display_payload(
1664                            &packet.header.proto,
1665                            &packet.buf[core::cmp::min(packet.payload_start, packet.buf.len())..]
1666                        )
1667                    );
1668
1669                    #[cfg(not(feature = "log-tlv-payload"))]
1670                    trace!(
1671                        "{}",
1672                        Packet::<0>::display_payload(
1673                            &packet.header.proto,
1674                            &packet.buf[core::cmp::min(packet.payload_start, packet.buf.len())..]
1675                        )
1676                    );
1677
1678                    return Ok(true);
1679                }
1680            }
1681        }
1682
1683        Ok(false)
1684    }
1685
1686    fn handle_accept_timeout_rx_packet<const N: usize>(&self, packet: &mut Packet<N>) -> bool {
1687        if packet.buf.is_empty() {
1688            return false;
1689        }
1690
1691        self.matter.with_state(|state| {
1692            let Some(session) = state
1693                .sessions
1694                .get_for_rx(&packet.peer, &packet.header.plain)
1695            else {
1696                return false;
1697            };
1698
1699            let Some(exch_index) = session.get_exch_for_rx(&packet.header.proto) else {
1700                return false;
1701            };
1702
1703            // `unwrap` is safe because we know we have a session and an exchange, or else the early returns from above would've triggered
1704            let exchange = unwrap!(session.exchanges[exch_index].as_mut());
1705
1706            if !matches!(
1707                exchange.role,
1708                Role::Responder(ResponderState::AcceptPending)
1709            ) || !exchange.mrp.has_rx_timed_out(ACCEPT_TIMEOUT_MS)
1710            {
1711                return false;
1712            }
1713
1714            mrp_log!(
1715                "\n>>RCV {}\n => Accept timeout, marking exchange as dropped",
1716                packet
1717            );
1718
1719            exchange.role = Role::Responder(ResponderState::Dropped);
1720            packet.buf.clear();
1721            self.transport().exchange_dropped.notify();
1722
1723            true
1724        })
1725    }
1726
1727    fn handle_orphaned_rx_packet<const N: usize>(&self, packet: &mut Packet<N>) -> bool {
1728        if packet.buf.is_empty() {
1729            return false;
1730        }
1731
1732        self.matter.with_state(|state| {
1733            let Some(session) = state
1734                .sessions
1735                .get_for_rx(&packet.peer, &packet.header.plain)
1736            else {
1737                mrp_log!("\n>>RCV {}\n => No session, dropping", packet);
1738
1739                packet.buf.clear();
1740                return true;
1741            };
1742
1743            let Some(exch_index) = session.get_exch_for_rx(&packet.header.proto) else {
1744                mrp_log!("\n>>RCV {}\n => No exchange, dropping", packet);
1745
1746                packet.buf.clear();
1747                return true;
1748            };
1749
1750            // `unwrap` is safe because we know we have a session and an exchange, or else the early returns from above would've triggered
1751            let exchange = unwrap!(session.exchanges[exch_index].as_mut());
1752
1753            if exchange.role.is_dropped_state() {
1754                mrp_log!(
1755                    "\n>>RCV {}\n => Owned by orphaned dropped {}, dropping packet",
1756                    packet,
1757                    ExchangeId::new(session.id, exch_index)
1758                );
1759
1760                packet.buf.clear();
1761                return true;
1762            }
1763
1764            false
1765        })
1766    }
1767
1768    fn handle_dropped_exchange<const N: usize>(
1769        &self,
1770        packet: &mut Packet<N>,
1771    ) -> Result<bool, Error> {
1772        self.matter.with_state(|state| {
1773            let exch = state
1774                .sessions
1775                .get_exch(|_, exch| exch.role.is_dropped_state() && exch.mrp.is_retrans_pending())
1776                .map(|(sess, exch_index)| (sess.id, exch_index, true))
1777                .or_else(|| {
1778                    state
1779                        .sessions
1780                        .get_exch(|_, exch| {
1781                            exch.role.is_dropped_state() && !exch.mrp.is_retrans_pending()
1782                        })
1783                        .map(|(sess, exch_index)| (sess.id, exch_index, false))
1784                });
1785
1786            let Some((session_id, exch_index, close_session)) = exch else {
1787                return Ok(exch.is_none());
1788            };
1789
1790            let exchange_id = ExchangeId::new(session_id, exch_index);
1791
1792            if close_session {
1793                // Found a dropped exchange which has an incomplete (re)transmission
1794                // Close the whole session
1795
1796                error!(
1797                    "Dropped exchange {}: Closing session because the exchange cannot be closed cleanly",
1798                    exchange_id.display(unwrap!(state.sessions.get(session_id))) // Session exists or else we wouldn't be here
1799                );
1800
1801                self.write_evict_session_packet(packet, &mut state.sessions, session_id, false)?;
1802            } else {
1803                // Found a dropped exchange which has no outstanding (re)transmission
1804                // Send a standalone ACK if necessary and then close it
1805
1806                // `unwrap` is safe because we know we have a session and an exchange, or else the early returns from above would've triggered
1807                let session = unwrap!(state.sessions.get(session_id));
1808                // Ditto
1809                let exchange = unwrap!(session.exchanges[exch_index].as_mut());
1810
1811                if exchange.mrp.is_ack_pending() {
1812                    self.write_packet(
1813                        packet,
1814                        Some(session),
1815                        Some(exch_index),
1816                        false,
1817                        |_| Ok(Some(OpCode::MRPStandAloneAck.into())),
1818                    )?;
1819                }
1820
1821                warn!("Dropped exchange {}: Closed", exchange_id.display(session));
1822                session.exchanges[exch_index] = None;
1823            }
1824
1825            Ok(exch.is_none())
1826        })
1827    }
1828
1829    pub(crate) async fn evict_some_session(&self) -> Result<(), Error> {
1830        let mut tx = self
1831            .matter
1832            .transport
1833            .get_if_tx(|packet| packet.buf.is_empty())
1834            .await;
1835        tx.clear_on_drop(true); // By default, if an error occurs
1836
1837        let evicted = self.write_evict_some_session_packet(&mut tx, true)?;
1838
1839        if evicted {
1840            // Send it
1841            tx.clear_on_drop(false);
1842
1843            Ok(())
1844        } else {
1845            Err(ErrorCode::NoSpaceSessions.into())
1846        }
1847    }
1848
1849    fn decode_packet<const N: usize>(&self, packet: &mut Packet<N>) -> Result<bool, Error> {
1850        self.matter.with_state(|state| {
1851            packet.header.reset();
1852
1853            let mut pb = ParseBuf::new(&mut packet.buf[packet.payload_start..]);
1854            packet.header.plain.decode(&mut pb)?;
1855
1856            let set_payload = |packet: &mut Packet<N>, (start, end)| {
1857                packet.payload_start = start;
1858                packet.buf.truncate(end);
1859            };
1860
1861            if let Some(session) = state
1862                .sessions
1863                .get_for_rx(&packet.peer, &packet.header.plain)
1864            {
1865                // Found existing session: decode, indicate packet payload slice and process further
1866
1867                let payload_range =
1868                    session.decode_remaining(&self.crypto, &mut packet.header, pb)?;
1869                set_payload(packet, payload_range);
1870
1871                return session.post_recv(&packet.header);
1872            }
1873
1874            // No existing session: we either have to create one, or return an error
1875
1876            if !packet.header.plain.is_encrypted() {
1877                // Unencrypted packets can be decoded without a session, and we need to anyway do that
1878                // in order to determine (based on proto hdr data) whether to create a new session or not
1879                packet
1880                    .header
1881                    .decode_remaining(&self.crypto, None, 0, &mut pb)?;
1882                packet.header.proto.adjust_reliability(true, &packet.peer);
1883
1884                let payload_range = pb.slice_range();
1885                set_payload(packet, payload_range);
1886
1887                if MessageMeta::from(&packet.header.proto).is_new_session() {
1888                    // As per spec, new unencrypted sessions are only created for
1889                    // `PBKDFParamRequest` or `CASESigma1` unencrypted messages
1890
1891                    let mut rand = self.crypto.rand()?;
1892
1893                    let session = state.sessions.add(
1894                        rand.next_u32(),
1895                        false,
1896                        packet.peer,
1897                        packet.header.plain.get_src_nodeid(),
1898                        self.matter.dev_det(),
1899                    )?;
1900
1901                    // Session created successfully: decode, indicate packet payload slice and process further
1902                    return session.post_recv(&packet.header);
1903                }
1904            } else {
1905                #[cfg(feature = "groups")]
1906                if packet.header.plain.is_group_session() {
1907                    // Group (multicast) message — derive keys on-the-fly and decrypt
1908                    let result = state.sessions.get_or_create_for_group_rx(
1909                        &self.crypto,
1910                        &state.fabrics,
1911                        packet,
1912                        self.matter.dev_det(),
1913                    );
1914
1915                    let (session, payload_range) = match result {
1916                        Ok(ok) => ok,
1917                        Err(e) => {
1918                            // When the Groupcast listener testing mode is
1919                            // armed, report failed multicast group messages
1920                            // as `GroupcastTesting` observations (turned
1921                            // into events by the Groupcast handler)
1922                            self.observe_group_rx_failure(packet, &state.fabrics, &e);
1923
1924                            return Err(e);
1925                        }
1926                    };
1927
1928                    set_payload(packet, payload_range);
1929
1930                    return session.post_recv(&packet.header);
1931                }
1932
1933                // Encrypted unicast packet with no matching session — cannot be decoded
1934                set_payload(packet, (0, 0));
1935            }
1936
1937            Err(ErrorCode::NoSession.into())
1938        })
1939    }
1940
1941    fn encode_packet<const N: usize>(
1942        &self,
1943        packet: &mut Packet<N>,
1944        session: Option<&mut Session>,
1945    ) -> Result<(), Error> {
1946        assert!(matches!(
1947            packet.tx_info.payload_state,
1948            TxPayloadState::NotEncoded { .. }
1949        ));
1950
1951        let payload_end = packet.buf.len();
1952
1953        if packet.tx_info.retransmission {
1954            mrp_log!(
1955                "\n<<SND {}\n      => Re-sending",
1956                Packet::<0>::display(&packet.peer, &packet.header),
1957            );
1958        } else {
1959            debug!(
1960                "\n<<SND {}\n      => Sending",
1961                Packet::<0>::display(&packet.peer, &packet.header),
1962            );
1963        }
1964
1965        #[cfg(feature = "log-tlv-payload")]
1966        debug!(
1967            "{}",
1968            Packet::<0>::display_payload(
1969                &packet.header.proto,
1970                &packet.buf[packet.payload_start..payload_end]
1971            )
1972        );
1973
1974        #[cfg(not(feature = "log-tlv-payload"))]
1975        trace!(
1976            "{}",
1977            Packet::<0>::display_payload(
1978                &packet.header.proto,
1979                &packet.buf[packet.payload_start..payload_end]
1980            )
1981        );
1982
1983        unwrap!(packet.buf.resize_default(N));
1984
1985        let mut wb = WriteBuf::new_with(&mut packet.buf, packet.payload_start, payload_end);
1986        if let Some(session) = session {
1987            session.encode(&self.crypto, &packet.header, &mut wb)?;
1988        } else {
1989            packet.header.encode(&self.crypto, None, 0, &mut wb)?;
1990        }
1991
1992        let encoded_payload_start = wb.get_start();
1993        let encoded_payload_end = wb.get_tail();
1994
1995        packet.payload_start = encoded_payload_start;
1996        packet.tx_info.payload_state = TxPayloadState::Encoded;
1997        packet.buf.truncate(encoded_payload_end);
1998
1999        Ok(())
2000    }
2001
2002    fn write_packet<const N: usize, F>(
2003        &self,
2004        packet: &mut Packet<N>,
2005        mut session: Option<&mut Session>,
2006        exchange_index: Option<usize>,
2007        encode: bool,
2008        payload_writer: F,
2009    ) -> Result<(), Error>
2010    where
2011        F: FnOnce(&mut WriteBuf) -> Result<Option<MessageMeta>, Error>,
2012    {
2013        // TODO: Resizing might be a bit expensive with large buffers
2014        // Resizing to `N` is always safe because it is a responsibility of the caller to ensure that N is <= `MAX_RX_BUF_SIZE`,
2015        // which is the size of `buf` heapless vec
2016        unwrap!(packet.buf.resize_default(N));
2017
2018        let mut wb = WriteBuf::new_with(
2019            &mut packet.buf,
2020            PacketHdr::HDR_RESERVE,
2021            PacketHdr::HDR_RESERVE,
2022        );
2023
2024        let Some(meta) = payload_writer(&mut wb)? else {
2025            packet.buf.clear();
2026            return Ok(());
2027        };
2028
2029        let (start, end) = (wb.get_start(), wb.get_tail());
2030
2031        packet.payload_start = start;
2032        packet.buf.truncate(end);
2033
2034        meta.set_into(&mut packet.header.proto);
2035
2036        if let Some(session) = &mut session {
2037            packet.header.plain = Default::default();
2038
2039            let (peer, retransmission) = session.pre_send(
2040                exchange_index,
2041                &mut packet.header,
2042                self.transport().device_sai,
2043                self.transport().device_sii,
2044            )?;
2045
2046            packet.peer = peer;
2047            packet.tx_info.retransmission = retransmission;
2048            packet.tx_info.payload_state = TxPayloadState::NotEncoded {
2049                session_id: session.id,
2050            };
2051        } else {
2052            if packet.header.plain.is_encrypted()
2053                || packet.header.plain.get_src_nodeid().is_none()
2054                || packet.header.proto.is_reliable()
2055            {
2056                // We can encode packets without a session only when they are unencrypted and do not need a retransmission
2057                Err(ErrorCode::NoSession)?;
2058            }
2059
2060            let src_nodeid = packet.header.plain.get_src_nodeid();
2061
2062            packet.header.plain = Default::default();
2063
2064            packet.header.plain.sess_id = 0;
2065            packet.header.plain.ctr = 1;
2066            packet.header.plain.set_src_nodeid(None);
2067            packet.header.plain.set_dst_unicast_nodeid(src_nodeid);
2068
2069            packet.header.proto.unset_initiator();
2070            packet.header.proto.adjust_reliability(false, &packet.peer);
2071
2072            packet.tx_info.retransmission = false;
2073            packet.tx_info.payload_state = TxPayloadState::NotEncoded { session_id: 0 };
2074        }
2075
2076        if encode {
2077            self.encode_packet(packet, session)?;
2078        }
2079
2080        Ok(())
2081    }
2082
2083    fn write_evict_some_session_packet<const N: usize>(
2084        &self,
2085        packet: &mut Packet<N>,
2086        encode: bool,
2087    ) -> Result<bool, Error> {
2088        self.matter.with_state(|state| {
2089            let id = state
2090                .sessions
2091                .get_session_for_eviction()
2092                .map(|sess| sess.id);
2093            if let Some(id) = id {
2094                self.write_evict_session_packet(packet, &mut state.sessions, id, encode)?;
2095
2096                Ok(true)
2097            } else {
2098                error!("All sessions have active exchanges, cannot evict any session");
2099
2100                Ok(false)
2101            }
2102        })
2103    }
2104
2105    fn write_evict_session_packet<const N: usize>(
2106        &self,
2107        packet: &mut Packet<N>,
2108        sessions: &mut Sessions,
2109        id: u32,
2110        encode: bool,
2111    ) -> Result<(), Error> {
2112        packet.header.proto.exch_id = sessions.get_next_exch_id(&self.crypto)?;
2113        packet.header.proto.set_initiator();
2114
2115        // It is a responsibility of the caller to ensure that this method is called with a valid session ID
2116        let mut session = unwrap!(sessions.remove(id));
2117        self.transport().notify_session_removed();
2118
2119        debug!(
2120            "Evicting session {} [SID:{:x},RSID:{:x}]",
2121            session.id,
2122            session.get_local_sess_id(),
2123            session.get_peer_sess_id()
2124        );
2125
2126        self.write_packet(packet, Some(&mut session), None, encode, |wb| {
2127            sc_write(wb, SCStatusCodes::CloseSession, &[])
2128        })?;
2129
2130        Ok(())
2131    }
2132
2133    fn is_close_session(payload: &mut [u8]) -> Result<bool, Error> {
2134        let mut pb = ParseBuf::new(payload);
2135        let report = StatusReport::read(&mut pb)?;
2136
2137        let close_session = report.proto_id == PROTO_ID_SECURE_CHANNEL as u32
2138            && report.proto_code == SCStatusCodes::CloseSession as u16;
2139
2140        Ok(close_session)
2141    }
2142
2143    async fn netw_recv<R>(mut recv: R, buf: &mut [u8]) -> Result<(usize, Address), Error>
2144    where
2145        R: NetworkReceive,
2146    {
2147        match recv.recv_from(buf).await {
2148            Ok((len, addr)) => {
2149                trace!("\n>>RCV {} {}B:\n     {}", addr, len, Bytes(&buf[..len]));
2150
2151                Ok((len, addr))
2152            }
2153            Err(e) => {
2154                error!("FAILED network recv: {:?}", e);
2155
2156                Err(e)
2157            }
2158        }
2159    }
2160
2161    async fn netw_send<S>(
2162        send: &IfMutex<S>,
2163        peer: Address,
2164        data: &[u8],
2165        system: bool,
2166    ) -> Result<(), Error>
2167    where
2168        S: NetworkSend,
2169    {
2170        match send.lock().await.send_to(data, peer).await {
2171            Ok(_) => {
2172                trace!(
2173                    "\n<<SND {} {}B{}: {}",
2174                    peer,
2175                    data.len(),
2176                    if system { " (system)" } else { "" },
2177                    Bytes(data)
2178                );
2179
2180                Ok(())
2181            }
2182            Err(e) => {
2183                error!(
2184                    "\n<<SND {} {}B{} !FAILED!: {:?}",
2185                    peer,
2186                    data.len(),
2187                    if system { " (system)" } else { "" },
2188                    e
2189                );
2190
2191                // Do not return an error as that would unroll the main `rs-matter` loop
2192                // and sending errors are normal and can happen for various reasons
2193                // TODO: Provide the error as a feedback to the packet creator instead, in the mutex data
2194                Ok(())
2195            }
2196        }
2197    }
2198
2199    #[inline(always)]
2200    const fn transport(&self) -> &Transport {
2201        self.matter.transport()
2202    }
2203}
2204
2205#[derive(Copy, Clone, Default, PartialEq, Eq, Debug, Hash)]
2206#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2207pub(crate) enum TxPayloadState {
2208    #[default]
2209    Encoded,
2210    NotEncoded {
2211        session_id: u32,
2212    },
2213}
2214
2215#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
2216#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2217pub(crate) struct TxInfo {
2218    pub(crate) retransmission: bool,
2219    pub(crate) payload_state: TxPayloadState,
2220}
2221
2222impl TxInfo {
2223    pub const fn new() -> Self {
2224        Self {
2225            retransmission: false,
2226            payload_state: TxPayloadState::Encoded,
2227        }
2228    }
2229}
2230
2231impl Default for TxInfo {
2232    fn default() -> Self {
2233        Self::new()
2234    }
2235}
2236
2237// The internal representation of a packet in the transport layer.
2238// There are only two such packets - RX and TX.
2239//
2240// This type is only known and used by the `transport` and the `exchange` modules
2241pub(crate) struct Packet<const N: usize> {
2242    pub(crate) peer: Address,
2243    pub(crate) header: PacketHdr,
2244    pub(crate) buf: PacketBuffer<N>,
2245    pub(crate) payload_start: usize,
2246    pub(crate) tx_info: TxInfo,
2247}
2248
2249impl<const N: usize> Packet<N> {
2250    #[inline(always)]
2251    pub(crate) const fn new() -> Self {
2252        Self {
2253            peer: Address::new(),
2254            header: PacketHdr::new(),
2255            buf: PacketBuffer::new(),
2256            payload_start: 0,
2257            tx_info: TxInfo::new(),
2258        }
2259    }
2260
2261    pub(crate) fn init() -> impl Init<Self> {
2262        init!(Self {
2263            peer: Address::new(),
2264            header: PacketHdr::new(),
2265            buf <- PacketBuffer::init(),
2266            payload_start: 0,
2267            tx_info: TxInfo::new(),
2268        })
2269    }
2270
2271    #[cfg(feature = "defmt")]
2272    pub fn display<'a>(
2273        peer: &'a Address,
2274        header: &'a PacketHdr,
2275    ) -> impl Display + defmt::Format + 'a {
2276        PacketInfo(peer, header)
2277    }
2278
2279    #[cfg(not(feature = "defmt"))]
2280    pub fn display<'a>(peer: &'a Address, header: &'a PacketHdr) -> impl Display + 'a {
2281        PacketInfo(peer, header)
2282    }
2283
2284    #[cfg(feature = "defmt")]
2285    pub fn display_payload<'a>(
2286        proto: &'a ProtoHdr,
2287        buf: &'a [u8],
2288    ) -> impl Display + defmt::Format + 'a {
2289        DetailedPacketInfo(proto, buf)
2290    }
2291
2292    #[cfg(not(feature = "defmt"))]
2293    pub fn display_payload<'a>(proto: &'a ProtoHdr, buf: &'a [u8]) -> impl Display + 'a {
2294        DetailedPacketInfo(proto, buf)
2295    }
2296
2297    fn fmt(f: &mut fmt::Formatter<'_>, peer: &Address, header: &PacketHdr) -> fmt::Result {
2298        write!(f, "{peer} {header}")?;
2299
2300        if header.proto.is_decoded() {
2301            let meta = MessageMeta::from(&header.proto);
2302
2303            write!(f, "\n      {meta}")?;
2304        }
2305
2306        Ok(())
2307    }
2308
2309    #[cfg(feature = "defmt")]
2310    fn format(f: defmt::Formatter<'_>, peer: &Address, header: &PacketHdr) {
2311        defmt::write!(f, "{} {}", peer, header);
2312
2313        if header.proto.is_decoded() {
2314            let meta = MessageMeta::from(&header.proto);
2315
2316            defmt::write!(f, "\n      {}", meta);
2317        }
2318    }
2319
2320    fn fmt_payload(f: &mut fmt::Formatter<'_>, proto: &ProtoHdr, buf: &[u8]) -> fmt::Result {
2321        let meta = MessageMeta::from(proto);
2322
2323        write!(f, "{meta}")?;
2324
2325        if meta.is_tlv() {
2326            write!(
2327                f,
2328                "; TLV:\n----------------\n{}\n----------------\n",
2329                TLVElement::new(buf)
2330            )?;
2331        } else {
2332            write!(
2333                f,
2334                "; Payload:\n----------------\n{:02x?}\n----------------\n",
2335                buf
2336            )?;
2337        }
2338
2339        Ok(())
2340    }
2341
2342    #[cfg(feature = "defmt")]
2343    fn format_payload(f: defmt::Formatter<'_>, proto: &ProtoHdr, buf: &[u8]) {
2344        let meta = MessageMeta::from(proto);
2345
2346        defmt::write!(f, "{}", meta);
2347
2348        if meta.is_tlv() {
2349            defmt::write!(
2350                f,
2351                "; TLV:\n----------------\n{}\n----------------\n",
2352                TLVElement::new(buf)
2353            );
2354        } else {
2355            defmt::write!(
2356                f,
2357                "; Payload:\n----------------\n{}\n----------------\n",
2358                crate::fmt::Bytes(buf)
2359            );
2360        }
2361    }
2362}
2363
2364impl<const N: usize> Display for Packet<N> {
2365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2366        Self::fmt(f, &self.peer, &self.header)
2367    }
2368}
2369
2370#[cfg(feature = "defmt")]
2371impl<const N: usize> defmt::Format for Packet<N> {
2372    fn format(&self, f: defmt::Formatter<'_>) {
2373        Self::format(f, &self.peer, &self.header)
2374    }
2375}
2376
2377struct PacketInfo<'a>(&'a Address, &'a PacketHdr);
2378
2379impl Display for PacketInfo<'_> {
2380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2381        Packet::<0>::fmt(f, self.0, self.1)
2382    }
2383}
2384
2385#[cfg(feature = "defmt")]
2386impl defmt::Format for PacketInfo<'_> {
2387    fn format(&self, f: defmt::Formatter<'_>) {
2388        Packet::<0>::format(f, self.0, self.1)
2389    }
2390}
2391
2392struct DetailedPacketInfo<'a>(&'a ProtoHdr, &'a [u8]);
2393
2394impl Display for DetailedPacketInfo<'_> {
2395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2396        Packet::<0>::fmt_payload(f, self.0, self.1)
2397    }
2398}
2399
2400#[cfg(feature = "defmt")]
2401impl defmt::Format for DetailedPacketInfo<'_> {
2402    fn format(&self, f: defmt::Formatter<'_>) {
2403        Packet::<0>::format_payload(f, self.0, self.1)
2404    }
2405}
2406
2407// The buffer used inside the pair of RX and TX `Packet` instances.
2408//
2409// The payload is allocated inline. With `large-buffers` enabled the inner
2410// `Vec` is ~1 MiB, so prefer constructing the buffer in place via
2411// [`PacketBuffer::init`].
2412// Constructing one by value with `new()` works too, as long as the resulting
2413// `Matter` is not moved through a small stack.
2414//
2415// This type is only known and used by the `transport` and the `exchange` modules
2416pub(crate) struct PacketBuffer<const N: usize> {
2417    buffer: crate::utils::storage::Vec<u8, N>,
2418}
2419
2420impl<const N: usize> PacketBuffer<N> {
2421    pub const fn new() -> Self {
2422        Self {
2423            buffer: crate::utils::storage::Vec::new(),
2424        }
2425    }
2426
2427    pub fn init() -> impl Init<Self> {
2428        init!(Self {
2429            buffer <- crate::utils::storage::Vec::init(),
2430        })
2431    }
2432
2433    pub fn buf_mut(&mut self) -> &mut crate::utils::storage::Vec<u8, N> {
2434        &mut self.buffer
2435    }
2436
2437    pub fn buf_ref(&self) -> &crate::utils::storage::Vec<u8, N> {
2438        &self.buffer
2439    }
2440}
2441
2442impl<const N: usize> Deref for PacketBuffer<N> {
2443    type Target = crate::utils::storage::Vec<u8, N>;
2444
2445    fn deref(&self) -> &Self::Target {
2446        self.buf_ref()
2447    }
2448}
2449
2450impl<const N: usize> DerefMut for PacketBuffer<N> {
2451    fn deref_mut(&mut self) -> &mut Self::Target {
2452        self.buf_mut()
2453    }
2454}
2455
2456// Represents the fact that either `Transport` or some `Exchange` instace has an exclusive access to the
2457// RX or TX packet of the transport layer.
2458//
2459// At any point in time, either the `Transport` singleton, or exactly one `Exchange` instance, or nobody
2460// holds a lock on the RX or TX packet. This is enforced by protecting the packets with an `IfMutex` asynchronous mutex.
2461//
2462// This type is only known and used by the `transport` and the `exchange` modules
2463pub(crate) struct PacketAccess<'a, const N: usize>(IfMutexGuard<'a, Packet<N>>, bool);
2464
2465impl<const N: usize> PacketAccess<'_, N> {
2466    pub fn clear_on_drop(&mut self, clear: bool) {
2467        self.1 = clear;
2468    }
2469}
2470
2471impl<const N: usize> Deref for PacketAccess<'_, N> {
2472    type Target = Packet<N>;
2473
2474    fn deref(&self) -> &Self::Target {
2475        &self.0
2476    }
2477}
2478
2479impl<const N: usize> DerefMut for PacketAccess<'_, N> {
2480    fn deref_mut(&mut self) -> &mut Self::Target {
2481        &mut self.0
2482    }
2483}
2484
2485impl<const N: usize> Drop for PacketAccess<'_, N> {
2486    fn drop(&mut self) {
2487        if self.1 {
2488            self.buf.clear();
2489        }
2490    }
2491}
2492
2493impl<const N: usize> Display for PacketAccess<'_, N> {
2494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2495        self.0.fmt(f)
2496    }
2497}
2498
2499// Allows other code in `rs-matter` to (ab)use the packet buffers of the transport layer
2500// in case it needs temporary access to a `&mut [u8]`-shaped memory
2501//
2502// Used by the builtin mDNS responder, as well as by the QR code generator
2503pub struct PacketBufferExternalAccess<'a, const N: usize>(pub(crate) &'a IfMutex<Packet<N>>);
2504
2505impl<const N: usize> Buffers<[u8]> for PacketBufferExternalAccess<'_, N> {
2506    type Buffer<'b>
2507        = ExternalPacketBuffer<'b, N>
2508    where
2509        Self: 'b;
2510
2511    async fn get(&self) -> Option<ExternalPacketBuffer<'_, N>> {
2512        let mut packet = self.0.lock_if(|packet| packet.buf.is_empty()).await;
2513
2514        // TODO: Resizing might be a bit expensive with large buffers
2515        // Resizing to `N` is always safe because the size of `buf` heapless vec is `N`
2516        unwrap!(packet.buf.resize_default(N));
2517
2518        Some(ExternalPacketBuffer(packet))
2519    }
2520
2521    fn get_immediate(&self) -> Option<Self::Buffer<'_>> {
2522        self.0
2523            .try_lock_if(|packet| packet.buf.is_empty())
2524            .ok()
2525            .map(|mut packet| {
2526                // TODO: Resizing might be a bit expensive with large buffers
2527                // Resizing to `N` is always safe because the size of `buf` heapless vec is `N`
2528                unwrap!(packet.buf.resize_default(N));
2529
2530                ExternalPacketBuffer(packet)
2531            })
2532    }
2533}
2534
2535// Wraps the RX or TX packet of the transport manager in something that looks like a `&mut [u8]` buffer.
2536pub struct ExternalPacketBuffer<'a, const N: usize>(IfMutexGuard<'a, Packet<N>>);
2537
2538impl<const N: usize> Deref for ExternalPacketBuffer<'_, N> {
2539    type Target = [u8];
2540
2541    fn deref(&self) -> &Self::Target {
2542        &self.0.buf
2543    }
2544}
2545
2546impl<const N: usize> DerefMut for ExternalPacketBuffer<'_, N> {
2547    fn deref_mut(&mut self) -> &mut Self::Target {
2548        &mut self.0.buf
2549    }
2550}
2551
2552impl<const N: usize> Drop for ExternalPacketBuffer<'_, N> {
2553    fn drop(&mut self) {
2554        self.0.buf.clear();
2555    }
2556}
2557
2558/// The number of messages sent and received by the transport layer.
2559#[derive(Default, Clone, Eq, PartialEq, Debug, Hash)]
2560#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2561pub struct MessageCounters {
2562    /// The number of IM messages sent by the transport layer.
2563    pub im_sent: u32,
2564    /// The number of IM messages received by the transport layer.
2565    pub im_received: u32,
2566}
2567
2568impl MessageCounters {
2569    const fn new() -> Self {
2570        Self {
2571            im_sent: 0,
2572            im_received: 0,
2573        }
2574    }
2575
2576    fn record_sent(&mut self, hdr: &ProtoHdr) {
2577        if hdr.proto_id == PROTO_ID_INTERACTION_MODEL {
2578            self.im_sent = self.im_sent.saturating_add(1);
2579        }
2580    }
2581
2582    fn record_recv(&mut self, hdr: &ProtoHdr) {
2583        if hdr.proto_id == PROTO_ID_INTERACTION_MODEL {
2584            self.im_received = self.im_received.saturating_add(1);
2585        }
2586    }
2587}
2588
2589#[cfg(test)]
2590mod tests {
2591    use super::*;
2592    use crate::crypto::test_only_crypto;
2593    use crate::dm::devices::test::{TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET};
2594
2595    fn test_matter() -> Matter<'static> {
2596        Matter::new(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, 0)
2597    }
2598
2599    #[test]
2600    fn test_create_plaintext_session() {
2601        let matter = test_matter();
2602        let crypto = test_only_crypto();
2603        let peer = Address::new();
2604
2605        let session_id = matter
2606            .transport
2607            .create_plaintext_session(&matter, &crypto, peer)
2608            .unwrap();
2609
2610        matter.with_state(|state| {
2611            let session = state.sessions.get(session_id).unwrap();
2612
2613            assert_eq!(session.id, session_id);
2614            assert!(!session.is_encrypted());
2615            assert_eq!(session.get_peer_node_id(), None);
2616            assert_eq!(*session.get_session_mode(), session::SessionMode::PlainText);
2617        });
2618    }
2619
2620    #[test]
2621    fn test_initiate_plaintext_now_creates_initiator_exchange() {
2622        let matter = test_matter();
2623        let crypto = test_only_crypto();
2624        let peer = Address::new();
2625
2626        let exchange = matter
2627            .transport
2628            .try_initiate_plaintext(&matter, &crypto, peer)
2629            .unwrap();
2630
2631        exchange
2632            .with_state(|state| {
2633                let sess = exchange.id().session(&mut state.sessions);
2634                let exch = exchange.id().exch(sess);
2635
2636                assert!(matches!(exch.role, Role::Initiator(_)));
2637                assert_eq!(sess.id, exchange.id().session_id());
2638                Ok(())
2639            })
2640            .unwrap();
2641    }
2642}
2643
2644#[cfg(test)]
2645mod resolve_tests {
2646    use core::net::{IpAddr, Ipv4Addr, SocketAddr};
2647
2648    use futures_lite::future::{block_on, zip};
2649
2650    use crate::error::ErrorCode;
2651    use crate::test::test_matter;
2652    use crate::transport::network::mdns::{DottedName, MdnsRemoteService};
2653    use crate::transport::network::MatterRemoteService;
2654
2655    fn op_service() -> MatterRemoteService {
2656        MatterRemoteService::Operational {
2657            compressed_fabric_id: 0x1122,
2658            node_id: 0x3344,
2659        }
2660    }
2661
2662    /// A resolver and the responder rendezvous on the single in-flight slot: the
2663    /// responder picks up the request and deposits an answer, which the resolver
2664    /// returns as the resolved `Address`.
2665    #[test]
2666    fn resolve_rendezvous_delivers_answer() {
2667        let matter = test_matter();
2668        let service = op_service();
2669
2670        let resolved = block_on(async {
2671            let resolver = matter.transport().resolve(service.clone(), 5_000);
2672
2673            let responder = async {
2674                let picked = matter.transport().wait_mdns_resolve_request().await;
2675                assert_eq!(picked, service);
2676
2677                let mut name = heapless::String::<128>::new();
2678                service.instance_name(&mut name);
2679
2680                let answer = MdnsRemoteService {
2681                    instance_name: DottedName(name.as_str()),
2682                    port: Some(1234),
2683                    addrs: [IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5))].into_iter(),
2684                    txt: [("SII", "300"), ("SAI", "4000"), ("SAT", "5000")].into_iter(),
2685                    scope_id: 0,
2686                };
2687
2688                matter.transport().try_deposit_mdns_resolve(&answer);
2689            };
2690
2691            let (node, ()) = zip(resolver, responder).await;
2692            node
2693        })
2694        .unwrap();
2695
2696        assert_eq!(
2697            resolved.addr,
2698            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)), 1234)
2699        );
2700        // The peer's MRP/session params are carried out of the resolve TXT.
2701        assert_eq!(resolved.sii, Some(300));
2702        assert_eq!(resolved.sai, Some(4000));
2703        assert_eq!(resolved.sat, Some(5000));
2704    }
2705
2706    /// Without a responder, a resolve times out and releases the slot (drop-clean),
2707    /// so a subsequent resolve also simply times out rather than hanging.
2708    #[test]
2709    fn resolve_times_out_and_releases_slot() {
2710        let matter = test_matter();
2711
2712        let err = block_on(matter.transport().resolve(op_service(), 50)).unwrap_err();
2713        assert!(matches!(err.code(), ErrorCode::NotFound));
2714
2715        let err = block_on(matter.transport().resolve(op_service(), 50)).unwrap_err();
2716        assert!(matches!(err.code(), ErrorCode::NotFound));
2717    }
2718
2719    /// Depositing an answer with no in-flight request is a no-op (does not strand
2720    /// a phantom answer): a later resolve still times out.
2721    #[test]
2722    fn deposit_without_request_is_noop() {
2723        let matter = test_matter();
2724
2725        let mut name = heapless::String::<128>::new();
2726        op_service().instance_name(&mut name);
2727        let answer = MdnsRemoteService {
2728            instance_name: DottedName(name.as_str()),
2729            port: Some(1234),
2730            addrs: [IpAddr::V4(Ipv4Addr::new(10, 0, 0, 9))].into_iter(),
2731            txt: core::iter::empty::<(&str, &str)>(),
2732            scope_id: 0,
2733        };
2734        matter.transport().try_deposit_mdns_resolve(&answer);
2735
2736        let err = block_on(matter.transport().resolve(op_service(), 50)).unwrap_err();
2737        assert!(matches!(err.code(), ErrorCode::NotFound));
2738    }
2739}
2740
2741#[cfg(test)]
2742mod browse_tests {
2743    use core::net::{IpAddr, Ipv4Addr, SocketAddr};
2744
2745    use futures_lite::future::{block_on, zip};
2746
2747    use crate::error::ErrorCode;
2748    use crate::test::test_matter;
2749    use crate::transport::network::mdns::{CommissionableFilter, DottedName, MdnsRemoteService};
2750    use crate::transport::network::Address;
2751
2752    /// A browser and the responder rendezvous: the responder picks up the request,
2753    /// deposits a matching commissionable answer, and the browser returns its
2754    /// address + commissionable instance id.
2755    #[test]
2756    fn browse_rendezvous_delivers_first_match() {
2757        let matter = test_matter();
2758
2759        let filter = CommissionableFilter {
2760            discriminator: Some(0xA5A),
2761            vendor_id: Some(0xFFF1),
2762            ..Default::default()
2763        };
2764
2765        let found = block_on(async {
2766            let browser = matter
2767                .transport()
2768                .browse_commissionable(&filter, &[], 5_000);
2769
2770            let responder = async {
2771                let picked = matter.transport().wait_mdns_browse_request().await;
2772                assert_eq!(picked, filter);
2773
2774                // A non-matching node (wrong vendor) must be ignored.
2775                let other = MdnsRemoteService {
2776                    instance_name: DottedName("0000000000000001._matterc._udp.local"),
2777                    port: Some(5540),
2778                    addrs: [IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))].into_iter(),
2779                    txt: [("D", "2650"), ("VP", "9999+1"), ("CM", "1")].into_iter(),
2780                    scope_id: 0,
2781                };
2782                matter.transport().try_deposit_mdns_browse(&other);
2783
2784                // The matching node (D=0xA5A=2650, VP vendor 0xFFF1=65521).
2785                let answer = MdnsRemoteService {
2786                    instance_name: DottedName("00000000ABCD1234._matterc._udp.local"),
2787                    port: Some(5541),
2788                    addrs: [IpAddr::V4(Ipv4Addr::new(10, 0, 0, 7))].into_iter(),
2789                    txt: [("D", "2650"), ("VP", "65521+42"), ("CM", "1")].into_iter(),
2790                    scope_id: 0,
2791                };
2792                matter.transport().try_deposit_mdns_browse(&answer);
2793            };
2794
2795            let (found, ()) = zip(browser, responder).await;
2796            found
2797        })
2798        .unwrap();
2799
2800        assert_eq!(
2801            found,
2802            (
2803                Address::Udp(SocketAddr::new(
2804                    IpAddr::V4(Ipv4Addr::new(10, 0, 0, 7)),
2805                    5541
2806                )),
2807                0x00000000ABCD1234,
2808            )
2809        );
2810    }
2811
2812    /// Without a responder, a browse times out and releases the slot (drop-clean).
2813    #[test]
2814    fn browse_times_out_and_releases_slot() {
2815        let matter = test_matter();
2816        let filter = CommissionableFilter {
2817            short_discriminator: Some(0xA),
2818            ..Default::default()
2819        };
2820
2821        let err = block_on(matter.transport().browse_commissionable(&filter, &[], 50)).unwrap_err();
2822        assert!(matches!(err.code(), ErrorCode::NotFound));
2823
2824        let err = block_on(matter.transport().browse_commissionable(&filter, &[], 50)).unwrap_err();
2825        assert!(matches!(err.code(), ErrorCode::NotFound));
2826    }
2827
2828    /// `exclude` steps past an already-tried candidate: with two nodes sharing the
2829    /// (short) discriminator, excluding the first id yields the second.
2830    #[test]
2831    fn browse_exclude_steps_to_next_match() {
2832        let matter = test_matter();
2833
2834        // Short discriminator 0xA = top 4 bits of 0xA12 (2578) and 0xAFF (2815).
2835        let filter = CommissionableFilter {
2836            short_discriminator: Some(0xA),
2837            ..Default::default()
2838        };
2839
2840        // Both nodes match the filter; exclude the first id, expect the second.
2841        let found = block_on(async {
2842            let browser = matter
2843                .transport()
2844                .browse_commissionable(&filter, &[0x1111], 5_000);
2845
2846            let responder = async {
2847                matter.transport().wait_mdns_browse_request().await;
2848
2849                let node_a = MdnsRemoteService {
2850                    instance_name: DottedName("0000000000001111._matterc._udp.local"),
2851                    port: Some(5540),
2852                    addrs: [IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))].into_iter(),
2853                    txt: [("D", "2578"), ("CM", "1")].into_iter(), // 0xA12, short 0xA
2854                    scope_id: 0,
2855                };
2856                // Excluded id -> ignored.
2857                matter.transport().try_deposit_mdns_browse(&node_a);
2858
2859                let node_b = MdnsRemoteService {
2860                    instance_name: DottedName("0000000000002222._matterc._udp.local"),
2861                    port: Some(5541),
2862                    addrs: [IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2))].into_iter(),
2863                    txt: [("D", "2815"), ("CM", "1")].into_iter(), // 0xAFF, short 0xA
2864                    scope_id: 0,
2865                };
2866                matter.transport().try_deposit_mdns_browse(&node_b);
2867            };
2868
2869            let (found, ()) = zip(browser, responder).await;
2870            found
2871        })
2872        .unwrap();
2873
2874        assert_eq!(
2875            found,
2876            (
2877                Address::Udp(SocketAddr::new(
2878                    IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)),
2879                    5541
2880                )),
2881                0x2222,
2882            )
2883        );
2884    }
2885}