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