1use core::fmt;
19use core::num::NonZeroU8;
20use embassy_time::Instant;
21
22use cfg_if::cfg_if;
23
24use rand_core::RngCore;
25
26use crate::crypto::{
27 canon, CanonAeadKey, CanonAeadKeyRef, CanonPkcSharedSecret, CanonPkcSharedSecretRef, Crypto,
28 CryptoSensitive, Kdf,
29};
30use crate::dm::clusters::basic_info::BasicInfoConfig;
31use crate::error::{Error, ErrorCode};
32#[cfg(feature = "groups")]
33use crate::fabric::Fabrics;
34#[cfg(feature = "groups")]
35use crate::group_keys::KeySet;
36#[cfg(feature = "groups")]
37use crate::persist::{KvBlobStore, GROUP_DATA_COUNTER_KEY};
38use crate::sc::SessionParameters;
39use crate::transport::exchange::ExchangeId;
40use crate::transport::mrp::{self, ReliableMessage};
41use crate::transport::TransportRunner;
42use crate::utils::init::{init, Init, IntoFallibleInit};
43use crate::utils::storage::{ParseBuf, Vec, WriteBuf};
44use crate::{Matter, MatterState};
45
46#[cfg(feature = "groups")]
47use super::dedup::GroupCtrStore;
48use super::dedup::RxCtrState;
49use super::exchange::{ExchangeState, MessageMeta, Role};
50use super::mrp::{mrp_log, RetransEntry};
51use super::network::Address;
52use super::packet::PacketHdr;
53use super::plain_hdr::PlainHdr;
54use super::proto_hdr::ProtoHdr;
55#[cfg(feature = "groups")]
56use super::Packet;
57
58pub const MAX_CAT_IDS_PER_NOC: usize = 3;
59pub type NocCatIds = [u32; MAX_CAT_IDS_PER_NOC];
60
61pub const ATT_CHALLENGE_LEN: usize = 16;
62
63canon!(
64 ATT_CHALLENGE_LEN,
65 ATT_CHALLENGE_ZEROED,
66 AttChallenge,
67 AttChallengeRef
68);
69
70#[derive(Debug, PartialEq, Eq, Clone, Default)]
71#[cfg_attr(feature = "defmt", derive(defmt::Format))]
72pub enum SessionMode {
73 Case {
76 fab_idx: NonZeroU8,
77 cat_ids: NocCatIds,
78 },
79 Pase {
83 fab_idx: u8,
84 },
85 Group {
87 fab_idx: NonZeroU8,
88 group_id: u16,
89 },
90 #[default]
91 PlainText,
92}
93
94impl SessionMode {
95 pub fn fab_idx(&self) -> u8 {
96 match self {
97 SessionMode::Case { fab_idx, .. } => fab_idx.get(),
98 SessionMode::Pase { fab_idx, .. } => *fab_idx,
99 SessionMode::Group { fab_idx, .. } => fab_idx.get(),
100 SessionMode::PlainText => 0,
101 }
102 }
103}
104
105pub struct Session {
106 pub(crate) id: u32,
108 peer_addr: Address,
109 local_nodeid: u64,
110 peer_nodeid: Option<u64>,
111 dec_key: CanonAeadKey,
114 enc_key: CanonAeadKey,
115 #[cfg_attr(not(feature = "case-resumption"), allow(dead_code))]
124 shared_secret: CanonPkcSharedSecret,
125 att_challenge: AttChallenge,
126 local_sess_id: u16,
127 peer_sess_id: u16,
128 msg_ctr: u32,
129 rx_ctr_state: RxCtrState,
130 mode: SessionMode,
131 pub(crate) exchanges: Vec<Option<ExchangeState>, MAX_EXCHANGES>,
132 last_use: Instant,
133 peer_active_interval_ms: u32,
136 peer_idle_interval_ms: u32,
140 peer_active_threshold_ms: u16,
142 expired: bool,
148 reserved: bool,
149}
150
151impl Session {
152 #[allow(clippy::too_many_arguments)]
153 pub fn new(
154 id: u32,
155 msg_ctr: u32,
156 reserved: bool,
157 peer_addr: Address,
158 peer_nodeid: Option<u64>,
159 peer_active_interval_ms: u32,
160 peer_idle_interval_ms: u32,
161 peer_active_threshold_ms: u16,
162 ) -> Self {
163 Self {
164 id,
165 reserved,
166 peer_addr,
167 local_nodeid: 0,
168 peer_nodeid,
169 dec_key: CanonAeadKey::new(),
170 enc_key: CanonAeadKey::new(),
171 shared_secret: CanonPkcSharedSecret::new(),
172 att_challenge: AttChallenge::new(),
173 peer_sess_id: 0,
174 local_sess_id: 0,
175 msg_ctr: msg_ctr & MATTER_MSG_CTR_RANGE,
176 rx_ctr_state: RxCtrState::new(0),
177 mode: SessionMode::PlainText,
178 exchanges: Vec::new(),
179 last_use: Instant::now(),
180 peer_active_interval_ms,
181 peer_idle_interval_ms,
182 peer_active_threshold_ms,
183 expired: false,
184 }
185 }
186
187 #[allow(clippy::too_many_arguments)]
188 pub fn init(
189 id: u32,
190 msg_ctr: u32,
191 reserved: bool,
192 peer_addr: Address,
193 peer_nodeid: Option<u64>,
194 peer_active_interval_ms: u32,
195 peer_idle_interval_ms: u32,
196 peer_active_threshold_ms: u16,
197 ) -> impl Init<Self> {
198 init!(Self {
199 id,
200 reserved,
201 peer_addr,
202 local_nodeid: 0,
203 peer_nodeid,
204 dec_key <- CanonAeadKey::init(),
205 enc_key <- CanonAeadKey::init(),
206 shared_secret <- CanonPkcSharedSecret::init(),
207 att_challenge <- AttChallenge::init(),
208 peer_sess_id: 0,
209 local_sess_id: 0,
210 msg_ctr: msg_ctr & MATTER_MSG_CTR_RANGE,
211 rx_ctr_state: RxCtrState::new(0),
212 mode: SessionMode::PlainText,
213 exchanges <- Vec::init(),
214 last_use: Instant::now(),
215 peer_active_interval_ms,
216 peer_idle_interval_ms,
217 peer_active_threshold_ms,
218 expired: false,
219 })
220 }
221
222 pub const fn id(&self) -> u32 {
225 self.id
226 }
227
228 pub fn get_local_sess_id(&self) -> u16 {
229 self.local_sess_id
230 }
231
232 #[cfg(test)]
233 pub fn set_local_sess_id(&mut self, sess_id: u16) {
234 self.local_sess_id = sess_id;
235 }
236
237 pub(crate) fn set_local_nodeid(&mut self, nodeid: u64) {
238 self.local_nodeid = nodeid;
239 }
240
241 pub fn get_peer_sess_id(&self) -> u16 {
242 self.peer_sess_id
243 }
244
245 pub fn get_peer_addr(&self) -> Address {
246 self.peer_addr
247 }
248
249 pub fn is_encrypted(&self) -> bool {
250 match self.mode {
251 SessionMode::Case { .. } | SessionMode::Pase { .. } | SessionMode::Group { .. } => true,
252 SessionMode::PlainText => false,
253 }
254 }
255
256 pub fn get_peer_node_id(&self) -> Option<u64> {
257 self.peer_nodeid
258 }
259
260 pub fn get_local_fabric_idx(&self) -> u8 {
261 self.mode.fab_idx()
262 }
263
264 pub fn get_session_mode(&self) -> &SessionMode {
265 &self.mode
266 }
267
268 #[cfg(feature = "groups")]
269 pub(crate) fn set_session_mode(&mut self, mode: SessionMode) {
270 self.mode = mode;
271 }
272
273 pub fn get_peer_active_interval_ms(&self) -> u32 {
274 self.peer_active_interval_ms
275 }
276
277 pub fn get_peer_idle_interval_ms(&self) -> u32 {
278 self.peer_idle_interval_ms
279 }
280
281 pub fn get_peer_active_threshold_ms(&self) -> u16 {
282 self.peer_active_threshold_ms
283 }
284
285 pub(crate) fn set_peer_session_params(&mut self, params: &SessionParameters) {
299 if let Some(sai) = params.sai {
300 if sai > 0 {
301 self.peer_active_interval_ms = sai;
302 } else {
303 warn!("Peer advertised session_parameters.sai=0; ignoring");
304 }
305 }
306
307 if let Some(sii) = params.sii {
308 if sii > 0 {
309 self.peer_idle_interval_ms = sii;
310 } else {
311 warn!("Peer advertised session_parameters.sii=0; ignoring");
312 }
313 }
314
315 if let Some(sat) = params.sat {
316 if sat > 0 {
317 self.peer_active_threshold_ms = sat;
318 } else {
319 warn!("Peer advertised session_parameters.sat=0; ignoring");
320 }
321 }
322 }
323
324 fn get_msg_ctr(&mut self) -> u32 {
325 let ctr = self.msg_ctr;
326 self.msg_ctr += 1;
327 ctr
328 }
329
330 pub fn get_dec_key(&self) -> Option<CanonAeadKeyRef<'_>> {
331 match self.mode {
332 SessionMode::Case { .. } | SessionMode::Pase { .. } | SessionMode::Group { .. } => {
333 Some(self.dec_key.reference())
334 }
335 SessionMode::PlainText => None,
336 }
337 }
338
339 pub fn get_enc_key(&self) -> Option<CanonAeadKeyRef<'_>> {
340 match self.mode {
341 SessionMode::Case { .. } | SessionMode::Pase { .. } | SessionMode::Group { .. } => {
342 Some(self.enc_key.reference())
343 }
344 SessionMode::PlainText => None,
345 }
346 }
347
348 #[cfg(feature = "case-resumption")]
354 #[allow(dead_code)]
355 pub fn get_shared_secret(&self) -> Option<CanonPkcSharedSecretRef<'_>> {
356 match self.mode {
357 SessionMode::Case { .. } => Some(self.shared_secret.reference()),
358 SessionMode::Pase { .. } | SessionMode::Group { .. } | SessionMode::PlainText => None,
359 }
360 }
361
362 pub fn get_att_challenge(&self) -> Option<AttChallengeRef<'_>> {
363 match self.mode {
364 SessionMode::Case { .. } | SessionMode::Pase { .. } => {
365 Some(self.att_challenge.reference())
366 }
367 SessionMode::PlainText | SessionMode::Group { .. } => None,
368 }
369 }
370
371 pub(crate) fn is_for_node(&self, fabric_idx: NonZeroU8, peer_node_id: u64) -> bool {
373 self.get_local_fabric_idx() == fabric_idx.get()
374 && self.peer_nodeid == Some(peer_node_id)
375 && self.is_encrypted()
376 && !self.reserved
377 }
378
379 pub(crate) fn is_pase_for_addr(&self, peer_addr: &Address) -> bool {
386 matches!(self.mode, SessionMode::Pase { .. })
387 && self.peer_addr.canonical() == peer_addr.canonical()
388 && !self.reserved
389 }
390
391 pub(crate) fn is_for_rx(&self, rx_peer: &Address, rx_plain: &PlainHdr) -> bool {
392 let nodeid_matches = self.peer_nodeid.is_none()
393 || rx_plain.get_src_nodeid().is_none()
394 || self.peer_nodeid == rx_plain.get_src_nodeid();
395
396 let dest_nodeid_matches = self.is_encrypted()
400 || self.local_nodeid == 0
401 || rx_plain.get_dst_unicast_nodeid().is_none()
402 || rx_plain.get_dst_unicast_nodeid() == Some(self.local_nodeid);
403
404 nodeid_matches
405 && dest_nodeid_matches
406 && self.local_sess_id == rx_plain.sess_id
407 && self.peer_addr.canonical() == rx_peer.canonical()
414 && self.is_encrypted() == rx_plain.is_encrypted()
415 && !self.reserved
416 }
417
418 pub(crate) fn is_for_tx(&self, session_id: u32) -> bool {
419 self.id == session_id
420 }
421
422 pub(crate) fn is_expired(&self) -> bool {
424 self.expired
425 }
426
427 pub fn upgrade_fabric_idx(&mut self, fabric_idx: NonZeroU8) -> Result<(), Error> {
428 if let SessionMode::Pase { fab_idx } = &mut self.mode {
429 if *fab_idx == 0 {
430 *fab_idx = fabric_idx.get();
431 } else {
432 Err(ErrorCode::Invalid)?;
434 }
435 } else {
436 Err(ErrorCode::Invalid)?;
439 }
440
441 Ok(())
442 }
443
444 pub(crate) fn post_recv(&mut self, rx_header: &PacketHdr) -> Result<bool, Error> {
448 if !self
449 .rx_ctr_state
450 .post_recv(rx_header.plain.ctr, self.is_encrypted(), false)
451 {
452 Err(ErrorCode::Duplicate)?;
453 }
454
455 let exch_index = self.get_exch_for_rx(&rx_header.proto);
456 if let Some(exch_index) = exch_index {
457 let exch = unwrap!(self.exchanges[exch_index].as_mut());
458
459 exch.post_recv(&rx_header.plain, &rx_header.proto)?;
460
461 Ok(false)
462 } else {
463 if !rx_header.proto.is_initiator()
464 || !MessageMeta::from(&rx_header.proto).is_new_exchange()
465 {
466 Err(ErrorCode::NoExchange)?;
470 }
471
472 if self.expired {
473 Err(ErrorCode::NoSession)?;
478 }
479
480 if let Some(exch_index) =
481 self.add_exch(rx_header.proto.exch_id, Role::Responder(Default::default()))
482 {
483 let exch = unwrap!(self.exchanges[exch_index].as_mut());
485
486 exch.post_recv(&rx_header.plain, &rx_header.proto)?;
487
488 Ok(true)
489 } else {
490 Err(ErrorCode::NoSpaceExchanges)?
491 }
492 }
493 }
494
495 pub(crate) fn is_peer_multicast(&self) -> bool {
498 match &self.peer_addr {
499 Address::Udp(crate::transport::network::SocketAddr::V6(addr)) => {
500 addr.ip().is_multicast()
501 }
502 Address::Udp(crate::transport::network::SocketAddr::V4(addr)) => {
503 addr.ip().is_multicast()
504 }
505 _ => false,
506 }
507 }
508
509 pub(crate) fn pre_send(
510 &mut self,
511 exch_index: Option<usize>,
512 tx_header: &mut PacketHdr,
513 session_active_interval_ms: Option<u32>,
514 session_idle_interval_ms: Option<u32>,
515 ) -> Result<(Address, bool), Error> {
516 let ctr = if let Some(exchange_index) = exch_index {
517 let exchange = unwrap!(self.exchanges[exchange_index].as_mut());
518 exchange.mrp.retrans.as_ref().map(RetransEntry::get_msg_ctr)
519 } else {
520 None
521 };
522
523 #[cfg(feature = "groups")]
526 let group_data_ctr = exch_index.and_then(|exchange_index| {
527 unwrap!(self.exchanges[exchange_index].as_mut())
528 .group_data_ctr
529 .take()
530 });
531
532 let retransmission = ctr.is_some();
533
534 let is_group = matches!(self.mode, SessionMode::Group { .. });
535
536 let is_control = is_group && MessageMeta::from(&tx_header.proto).is_control_msg();
541
542 tx_header.plain.sess_id = self.get_peer_sess_id();
547 tx_header.plain.ctr = if let Some(ctr) = ctr {
548 ctr
549 } else if is_group && !is_control {
550 #[cfg(feature = "groups")]
557 {
558 group_data_ctr.ok_or(ErrorCode::InvalidState)?
559 }
560 #[cfg(not(feature = "groups"))]
562 {
563 Err(ErrorCode::InvalidState)?
564 }
565 } else {
566 self.get_msg_ctr()
567 };
568
569 tx_header.plain.set_src_nodeid(
575 ((!self.is_encrypted() || is_group) && self.local_nodeid != 0)
576 .then_some(self.local_nodeid),
577 );
578
579 #[allow(irrefutable_let_patterns)]
587 if self.mode == SessionMode::PlainText || is_control {
588 tx_header.plain.set_dst_unicast_nodeid(self.peer_nodeid);
589 } else if let SessionMode::Group { group_id, .. } = self.mode {
590 tx_header.plain.set_dst_groupcast_nodeid(Some(group_id));
591 } else {
592 tx_header.plain.set_dst_unicast_nodeid(None);
593 }
594
595 if is_group {
596 use super::plain_hdr::SecFlags;
597 tx_header.plain.sec_flags |= SecFlags::GROUP_SESSION;
598 tx_header.plain.set_control_msg(is_control);
599 }
600
601 tx_header.proto.adjust_reliability(false, &self.peer_addr);
602
603 if is_group && !is_control {
604 tx_header.proto.unset_reliable();
610 tx_header.proto.set_ack(None);
611 }
612
613 if let Some(exchange_index) = exch_index {
614 let exchange = unwrap!(self.exchanges[exchange_index].as_mut());
615
616 exchange.pre_send(
617 &tx_header.plain,
618 &mut tx_header.proto,
619 session_active_interval_ms,
620 session_idle_interval_ms,
621 )?;
622 }
623
624 Ok((self.peer_addr, retransmission))
625 }
626
627 pub(crate) fn decode_remaining<C: Crypto>(
632 &self,
633 crypto: C,
634 rx_header: &mut PacketHdr,
635 mut pb: ParseBuf,
636 ) -> Result<(usize, usize), Error> {
637 rx_header.decode_remaining(
638 crypto,
639 self.get_dec_key(),
640 self.peer_nodeid.unwrap_or_default(),
641 &mut pb,
642 )?;
643
644 rx_header.proto.adjust_reliability(true, &self.peer_addr);
645
646 Ok(pb.slice_range())
647 }
648
649 pub(crate) fn encode<C: Crypto>(
650 &self,
651 crypto: C,
652 tx: &PacketHdr,
653 wb: &mut WriteBuf,
654 ) -> Result<(), Error> {
655 tx.encode(crypto, self.get_enc_key(), self.local_nodeid, wb)
656 }
657
658 fn update_last_used(&mut self) {
659 self.last_use = Instant::now();
660 }
661
662 pub(crate) fn get_exch_for_rx(&self, rx_proto: &ProtoHdr) -> Option<usize> {
663 self.exchanges
664 .iter()
665 .enumerate()
666 .filter(|(_, exch)| {
667 exch.as_ref()
668 .map(|exch| exch.is_for_rx(rx_proto))
669 .unwrap_or(false)
670 })
671 .map(|(index, _)| index)
672 .next()
673 }
674
675 pub(crate) fn add_exch(&mut self, exch_id: u16, role: Role) -> Option<usize> {
676 let exch_state = Some(ExchangeState {
677 exch_id,
678 role,
679 mrp: ReliableMessage::new(),
680 #[cfg(feature = "groups")]
681 group_data_ctr: None,
682 });
683
684 let exch_index = if self.exchanges.len() < MAX_EXCHANGES {
685 let _ = self.exchanges.push(exch_state);
686
687 self.exchanges.len() - 1
688 } else {
689 let index = self.exchanges.iter().position(Option::is_none);
690
691 if let Some(index) = index {
692 self.exchanges[index] = exch_state;
693
694 index
695 } else {
696 error!(
697 "Too many exchanges for session {} [SID:{:x},RSID:{:x}]; exchange creation failed",
698 self.id,
699 self.get_local_sess_id(),
700 self.get_peer_sess_id()
701 );
702
703 return None;
704 }
705 };
706
707 let exch_id = ExchangeId::new(self.id, exch_index);
708
709 debug!("New exchange: {} :: {:?}", exch_id.display(self), role);
710
711 Some(exch_index)
712 }
713
714 pub(crate) fn remove_exch(&mut self, index: usize) -> bool {
715 let exchange = unwrap!(self.exchanges[index].as_mut());
716 let exchange_id = ExchangeId::new(self.id, index);
717
718 if exchange.mrp.is_retrans_pending() {
719 exchange.role.set_dropped_state();
720 error!("Exchange {}: A packet is still (re)transmitted! Marking as dropped, but session will be closed", exchange_id.display(self));
721
722 false
723 } else if exchange.mrp.is_ack_pending() {
724 exchange.role.set_dropped_state();
725 mrp_log!(
726 "Exchange {}: Pending ACK. Marking as dropped",
727 exchange_id.display(self)
728 );
729
730 false
731 } else {
732 trace!("Exchange {}: Dropped cleanly", exchange_id.display(self));
733 self.exchanges[index] = None;
734
735 true
736 }
737 }
738}
739
740impl fmt::Display for Session {
741 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742 write!(
743 f,
744 "peer: {:?}, peer_nodeid: {:?}, local: {}, remote: {}, msg_ctr: {}, mode: {:?}, ts: {:?}, expired: {}",
745 self.peer_addr,
746 self.peer_nodeid,
747 self.local_sess_id,
748 self.peer_sess_id,
749 self.msg_ctr,
750 self.mode,
751 self.last_use,
752 self.expired,
753 )
754 }
755}
756
757pub struct ReservedSession<'a> {
761 id: u32,
762 matter: &'a Matter<'a>,
763 complete: bool,
764}
765
766impl<'a> ReservedSession<'a> {
767 pub fn reserve_now<C: Crypto>(matter: &'a Matter<'a>, crypto: C) -> Result<Self, Error> {
768 let dev_det = matter.dev_det();
769 matter.with_state(|state| {
770 let mut rand = crypto.weak_rand()?;
771
772 let id = state
773 .sessions
774 .add(rand.next_u32(), true, Address::new(), None, dev_det)?
775 .id;
776
777 Ok(Self {
778 id,
779 matter,
780 complete: false,
781 })
782 })
783 }
784
785 pub async fn reserve<C: Crypto>(
786 matter: &'a Matter<'a>,
787 crypto: C,
788 ) -> Result<ReservedSession<'a>, Error> {
789 let session = Self::reserve_now(matter, &crypto);
790
791 if let Ok(session) = session {
792 Ok(session)
793 } else {
794 TransportRunner::new(matter, &crypto)
795 .evict_some_session()
796 .await?;
797
798 Self::reserve_now(matter, &crypto)
799 }
800 }
801
802 #[allow(clippy::too_many_arguments)]
803 pub fn update(
804 &mut self,
805 local_nodeid: u64,
806 peer_nodeid: u64,
807 peer_sessid: u16,
808 local_sessid: u16,
809 peer_addr: Address,
810 mode: SessionMode,
811 dec_key: Option<CanonAeadKeyRef<'_>>,
812 enc_key: Option<CanonAeadKeyRef<'_>>,
813 att_challenge: Option<AttChallengeRef<'_>>,
814 shared_secret: Option<CanonPkcSharedSecretRef<'_>>,
815 ) -> Result<(), Error> {
816 self.matter.with_state(|state| {
817 self.update_with_state(
818 state,
819 local_nodeid,
820 peer_nodeid,
821 peer_sessid,
822 local_sessid,
823 peer_addr,
824 mode,
825 dec_key,
826 enc_key,
827 att_challenge,
828 shared_secret,
829 )
830 })
831 }
832
833 #[allow(clippy::too_many_arguments)]
834 pub fn update_with_state(
835 &mut self,
836 state: &mut MatterState,
837 local_nodeid: u64,
838 peer_nodeid: u64,
839 peer_sessid: u16,
840 local_sessid: u16,
841 peer_addr: Address,
842 mode: SessionMode,
843 dec_key: Option<CanonAeadKeyRef<'_>>,
844 enc_key: Option<CanonAeadKeyRef<'_>>,
845 att_challenge: Option<AttChallengeRef<'_>>,
846 shared_secret: Option<CanonPkcSharedSecretRef<'_>>,
847 ) -> Result<(), Error> {
848 let session = state.sessions.get(self.id).ok_or(ErrorCode::NoSession)?;
849
850 session.local_nodeid = local_nodeid;
851 session.peer_nodeid = Some(peer_nodeid);
852 session.peer_sess_id = peer_sessid;
853 session.local_sess_id = local_sessid;
854 session.peer_addr = peer_addr;
855 session.mode = mode;
856
857 if let Some(dec_key) = dec_key {
858 session.dec_key.load(dec_key);
859 }
860
861 if let Some(enc_key) = enc_key {
862 session.enc_key.load(enc_key);
863 }
864
865 if let Some(att_challenge) = att_challenge {
866 session.att_challenge.load(att_challenge);
867 }
868
869 if let Some(shared_secret) = shared_secret {
870 session.shared_secret.load(shared_secret);
871 }
872
873 Ok(())
874 }
875
876 pub(crate) fn set_peer_session_params(
883 &mut self,
884 params: &SessionParameters,
885 ) -> Result<(), Error> {
886 self.matter.with_state(|state| {
887 let session = state.sessions.get(self.id).ok_or(ErrorCode::NoSession)?;
888 session.set_peer_session_params(params);
889 Ok(())
890 })
891 }
892
893 pub fn complete(&mut self) {
894 self.complete = true;
895 }
896}
897
898impl Drop for ReservedSession<'_> {
899 fn drop(&mut self) {
900 self.matter.with_state(|state| {
901 if self.complete {
902 let session = unwrap!(state.sessions.get(self.id));
903 session.reserved = false;
904 } else {
905 state.sessions.remove(self.id);
906 }
907 })
908 }
909}
910
911cfg_if! {
912 if #[cfg(feature = "max-sessions-64")] {
913 pub const MAX_SESSIONS: usize = 64;
915 } else if #[cfg(feature = "max-sessions-32")] {
916 pub const MAX_SESSIONS: usize = 32;
918 } else if #[cfg(feature = "max-sessions-16")] {
919 pub const MAX_SESSIONS: usize = 16;
921 } else if #[cfg(feature = "max-sessions-8")] {
922 pub const MAX_SESSIONS: usize = 8;
924 } else if #[cfg(feature = "max-sessions-7")] {
925 pub const MAX_SESSIONS: usize = 7;
927 } else if #[cfg(feature = "max-sessions-6")] {
928 pub const MAX_SESSIONS: usize = 6;
930 } else if #[cfg(feature = "max-sessions-5")] {
931 pub const MAX_SESSIONS: usize = 5;
933 } else if #[cfg(feature = "max-sessions-4")] {
934 pub const MAX_SESSIONS: usize = 4;
936 } else if #[cfg(feature = "max-sessions-3")] {
937 pub const MAX_SESSIONS: usize = 3;
939 } else {
940 pub const MAX_SESSIONS: usize = 16;
942 }
943}
944
945cfg_if! {
946 if #[cfg(feature = "max-exchanges-per-session-16")] {
947 pub const MAX_EXCHANGES: usize = 16;
949 } else if #[cfg(feature = "max-exchanges-per-session-8")] {
950 pub const MAX_EXCHANGES: usize = 8;
952 } else if #[cfg(feature = "max-exchanges-per-session-7")] {
953 pub const MAX_EXCHANGES: usize = 7;
955 } else if #[cfg(feature = "max-exchanges-per-session-6")] {
956 pub const MAX_EXCHANGES: usize = 6;
958 } else if #[cfg(feature = "max-exchanges-per-session-5")] {
959 pub const MAX_EXCHANGES: usize = 5;
961 } else if #[cfg(feature = "max-exchanges-per-session-4")] {
962 pub const MAX_EXCHANGES: usize = 4;
964 } else if #[cfg(feature = "max-exchanges-per-session-3")] {
965 pub const MAX_EXCHANGES: usize = 3;
967 } else {
968 pub const MAX_EXCHANGES: usize = 5;
970 }
971}
972
973const MATTER_MSG_CTR_RANGE: u32 = 0x0fffffff;
974
975pub struct Sessions {
977 next_sess_unique_id: u32,
978 next_sess_id: u16,
979 next_exch_id: u16,
980 sessions: Vec<Session, MAX_SESSIONS>,
981 #[cfg(feature = "groups")]
982 group_ctr_store: GroupCtrStore,
983 #[cfg(feature = "groups")]
998 global_group_data_ctr: u32,
999 #[cfg(feature = "groups")]
1010 group_data_ctr_boundary: u32,
1011}
1012
1013#[cfg(feature = "groups")]
1021pub const GROUP_DATA_CTR_EPOCH: u32 = 1000;
1022
1023impl Sessions {
1024 #[inline(always)]
1026 pub const fn new() -> Self {
1027 Self {
1028 sessions: Vec::new(),
1029 #[cfg(feature = "groups")]
1030 group_ctr_store: GroupCtrStore::new(),
1031 next_sess_unique_id: 0,
1032 next_sess_id: 1,
1033 next_exch_id: 0,
1034 #[cfg(feature = "groups")]
1035 global_group_data_ctr: 0,
1036 #[cfg(feature = "groups")]
1037 group_data_ctr_boundary: 0,
1038 }
1039 }
1040
1041 pub fn init() -> impl Init<Self> {
1043 #[cfg(feature = "groups")]
1046 let r = init!(Self {
1047 sessions <- Vec::init(),
1048 group_ctr_store: GroupCtrStore::new(),
1049 next_sess_unique_id: 0,
1050 next_sess_id: 1,
1051 next_exch_id: 0,
1052 global_group_data_ctr: 0,
1053 group_data_ctr_boundary: 0,
1054 });
1055 #[cfg(not(feature = "groups"))]
1056 let r = init!(Self {
1057 sessions <- Vec::init(),
1058 next_sess_unique_id: 0,
1059 next_sess_id: 1,
1060 next_exch_id: 0,
1061 });
1062 r
1063 }
1064
1065 pub fn reset(&mut self) {
1066 self.sessions.clear();
1067 #[cfg(feature = "groups")]
1068 {
1069 self.group_ctr_store = GroupCtrStore::new();
1070 }
1071 self.next_sess_id = 1;
1072 self.next_exch_id = 0;
1073 }
1077}
1078
1079#[cfg(feature = "groups")]
1083impl Sessions {
1084 pub fn load_persist<S: KvBlobStore>(
1099 &mut self,
1100 mut store: S,
1101 buf: &mut [u8],
1102 ) -> Result<(), Error> {
1103 if let Some(data) = store.load(GROUP_DATA_COUNTER_KEY, buf)? {
1104 let boundary = u32::from_le_bytes(data.try_into().map_err(|_| ErrorCode::InvalidData)?);
1105
1106 self.resume_global_group_data_ctr(boundary);
1107 }
1108
1109 Ok(())
1110 }
1111
1112 pub fn reset_persist<S: KvBlobStore>(
1120 &mut self,
1121 mut store: S,
1122 buf: &mut [u8],
1123 ) -> Result<(), Error> {
1124 self.global_group_data_ctr = 0;
1125 self.group_data_ctr_boundary = 0;
1126
1127 store.remove(GROUP_DATA_COUNTER_KEY, buf)?;
1128
1129 Ok(())
1130 }
1131
1132 pub(crate) fn get_or_init_global_group_data_ctr<C: Crypto>(
1137 &mut self,
1138 crypto: C,
1139 ) -> Result<u32, Error> {
1140 if self.global_group_data_ctr == 0 {
1141 let candidate = crypto.rand()?.next_u32() & MATTER_MSG_CTR_RANGE;
1145 self.set_global_group_data_ctr(if candidate == 0 { 1 } else { candidate });
1146 }
1147 Ok(self.global_group_data_ctr)
1148 }
1149
1150 pub(crate) fn resume_global_group_data_ctr(&mut self, start: u32) {
1154 self.set_global_group_data_ctr(if start == 0 { 1 } else { start });
1155 }
1156
1157 fn set_global_group_data_ctr(&mut self, value: u32) {
1161 self.global_group_data_ctr = value;
1162 self.group_data_ctr_boundary = value;
1163 }
1164
1165 fn advance_group_data_ctr(value: u32, delta: u32) -> u32 {
1169 let next = value.wrapping_add(delta) & MATTER_MSG_CTR_RANGE;
1170 if next == 0 {
1171 1
1172 } else {
1173 next
1174 }
1175 }
1176
1177 #[must_use = "a returned boundary must be persisted before the value is sent"]
1186 pub(crate) fn reserve_global_group_data_ctr<C: Crypto>(
1187 &mut self,
1188 crypto: C,
1189 ) -> Result<(u32, Option<u32>), Error> {
1190 self.get_or_init_global_group_data_ctr(crypto)?;
1192
1193 let to_persist = if self.global_group_data_ctr == self.group_data_ctr_boundary {
1199 self.group_data_ctr_boundary =
1200 Self::advance_group_data_ctr(self.global_group_data_ctr, GROUP_DATA_CTR_EPOCH);
1201
1202 Some(self.group_data_ctr_boundary)
1203 } else {
1204 None
1205 };
1206
1207 let value = self.global_group_data_ctr;
1208
1209 self.global_group_data_ctr = Self::advance_group_data_ctr(value, 1);
1210
1211 Ok((value, to_persist))
1212 }
1213
1214 pub(crate) fn get_or_create_for_group_tx<C: Crypto>(
1227 &mut self,
1228 crypto: C,
1229 fabrics: &Fabrics,
1230 fab_idx: NonZeroU8,
1231 group_id: u16,
1232 dev_det: &BasicInfoConfig<'_>,
1233 ) -> Result<&mut Session, Error> {
1234 use crate::dm::clusters::decl::groupcast::MulticastAddrPolicyEnum;
1235 use crate::transport::network::{SocketAddr, SocketAddrV6};
1236
1237 let fabric = fabrics.fabric(fab_idx)?;
1238
1239 let map_entry = fabric
1242 .groups()
1243 .key_map_iter()
1244 .find(|entry| entry.group_id == group_id)
1245 .ok_or(ErrorCode::NotFound)?;
1246 let key_set = fabric
1247 .groups()
1248 .key_set_get(map_entry.group_key_set_id)
1249 .ok_or(ErrorCode::NotFound)?;
1250 let epoch_key_entry = key_set
1251 .epoch_keys
1252 .iter()
1253 .max_by_key(|entry| entry.epoch_start_time)
1254 .ok_or(ErrorCode::NotFound)?;
1255
1256 let mut derived = KeySet::new();
1257 derived.update(
1258 &crypto,
1259 epoch_key_entry.epoch_key.reference(),
1260 &fabric.compressed_fabric_id(),
1261 )?;
1262 let op_key = derived.op_key();
1263 let session_id = derive_group_session_id(&crypto, op_key)?;
1264
1265 let ip = match fabric
1269 .groups()
1270 .get(group_id)
1271 .map(|entry| entry.effective_mcast_policy())
1272 .unwrap_or(MulticastAddrPolicyEnum::PerGroup)
1273 {
1274 MulticastAddrPolicyEnum::IanaAddr => crate::utils::ipv6::IANA_GROUPCAST_MULTICAST_ADDR,
1275 MulticastAddrPolicyEnum::PerGroup => {
1276 crate::utils::ipv6::compute_group_multicast_addr(fabric.fabric_id(), group_id)
1277 }
1278 };
1279 let peer = Address::Udp(SocketAddr::V6(SocketAddrV6::new(
1280 ip,
1281 crate::MATTER_PORT,
1282 0,
1283 0,
1284 )));
1285 let fabric_node_id = fabric.node_id();
1286
1287 self.get_or_init_global_group_data_ctr(&crypto)?;
1289
1290 let existing = self.sessions.iter().position(|sess| {
1296 matches!(
1297 sess.mode,
1298 SessionMode::Group {
1299 fab_idx: f,
1300 group_id: g
1301 } if f == fab_idx && g == group_id
1302 ) && sess.peer_addr == peer
1303 && sess.local_sess_id == session_id
1304 });
1305
1306 if let Some(index) = existing {
1307 let session = unwrap!(self.sessions.get_mut(index));
1308 session.update_last_used();
1309 return Ok(session);
1310 }
1311
1312 let mut rand = crypto.weak_rand()?;
1313
1314 let session = match self.add(rand.next_u32(), false, peer, None, dev_det) {
1315 Ok(session) => session,
1316 Err(_) => {
1317 if let Some(lru_id) = self.get_session_for_eviction().map(|sess| sess.id) {
1319 debug!("Group TX: Evicting session {} to make room", lru_id);
1320 self.remove(lru_id);
1321 self.add(rand.next_u32(), false, peer, None, dev_det)?
1322 } else {
1323 return Err(ErrorCode::NoSpaceSessions.into());
1324 }
1325 }
1326 };
1327
1328 session.set_session_mode(SessionMode::Group { fab_idx, group_id });
1329 session.local_sess_id = session_id;
1330 session.peer_sess_id = session_id;
1331 session.set_local_nodeid(fabric_node_id);
1333 session.enc_key.load(op_key);
1334 session.dec_key.load(op_key);
1335
1336 debug!(
1337 "Group TX: Created group session for fab_idx={}, group_id=0x{:04x}, dst={}",
1338 fab_idx, group_id, peer
1339 );
1340
1341 let session = unwrap!(self.sessions.last_mut());
1342 session.update_last_used();
1343
1344 Ok(session)
1345 }
1346
1347 pub(crate) fn get_or_create_for_group_rx<const N: usize, C: Crypto>(
1368 &mut self,
1369 crypto: C,
1370 fabrics: &Fabrics,
1371 packet: &mut Packet<N>,
1372 dev_det: &BasicInfoConfig<'_>,
1373 ) -> Result<(&mut Session, (usize, usize)), Error> {
1374 let src_nodeid = packet
1375 .header
1376 .plain
1377 .get_src_nodeid()
1378 .ok_or(ErrorCode::InvalidData)?;
1379 let dst_group_id = packet.header.plain.get_dst_groupcast_nodeid();
1382 let dst_unicast_nodeid = packet.header.plain.get_dst_unicast_nodeid();
1383
1384 if dst_group_id.is_none() && dst_unicast_nodeid.is_none() {
1385 return Err(ErrorCode::InvalidData.into());
1386 }
1387
1388 let expected_sess_id = packet.header.plain.sess_id;
1389 let msg_ctr = packet.header.plain.ctr;
1390 let is_control = packet.header.plain.is_control_msg();
1391
1392 debug!(
1393 "Group: Attempting decrypt for PEER={:?} SID=0x{:04x}, GRP={:?}, DSTU={:?}, SRC=0x{:016x}, CTR={}, C={}",
1394 packet.peer,
1395 expected_sess_id,
1396 dst_group_id,
1397 dst_unicast_nodeid,
1398 src_nodeid,
1399 msg_ctr,
1400 is_control
1401 );
1402
1403 let mut pb = ParseBuf::new(&mut packet.buf[packet.payload_start..]);
1405 packet.header.plain.decode(&mut pb)?;
1406
1407 let encrypted_offset = pb.read_off();
1409 let encrypted_len = pb.as_slice().len();
1410 let mut saved_encrypted = [0u8; 1280];
1411
1412 if encrypted_len > saved_encrypted.len() {
1413 return Err(ErrorCode::BufferTooSmall.into());
1414 }
1415
1416 saved_encrypted[..encrypted_len].copy_from_slice(pb.as_slice());
1417
1418 struct GroupKeyFound {
1430 fab_idx: NonZeroU8,
1431 group_id: u16,
1432 fabric_node_id: u64,
1433 op_key: CanonAeadKey,
1434 payload_range: (usize, usize),
1435 }
1436
1437 let mut group_key_found: Option<GroupKeyFound> = None;
1438 let mut key_attempted = false;
1443
1444 'outer: for fabric in fabrics.iter() {
1445 if let Some(dst_node) = dst_unicast_nodeid {
1450 if fabric.node_id() != dst_node {
1451 continue;
1452 }
1453 }
1454
1455 let fab_idx = fabric.fab_idx();
1456 let compressed_fabric_id = fabric.compressed_fabric_id();
1457 let fabric_node_id = fabric.node_id();
1458
1459 for map_entry in fabric.groups().key_map_iter() {
1460 if let Some(gid) = dst_group_id {
1464 if map_entry.group_id != gid {
1465 continue;
1466 }
1467 }
1468
1469 let Some(key_set_entry) = fabric.groups().key_set_get(map_entry.group_key_set_id)
1470 else {
1471 continue;
1472 };
1473
1474 for epoch_key_entry in key_set_entry.epoch_keys.iter() {
1475 let mut temp_key_set = KeySet::new();
1476
1477 if temp_key_set
1478 .update(
1479 &crypto,
1480 epoch_key_entry.epoch_key.reference(),
1481 &compressed_fabric_id,
1482 )
1483 .is_err()
1484 {
1485 continue;
1486 }
1487
1488 let op_key_ref = temp_key_set.op_key();
1489
1490 let Ok(session_id) = derive_group_session_id(&crypto, op_key_ref) else {
1491 continue;
1492 };
1493
1494 if session_id != expected_sess_id {
1495 continue;
1496 }
1497
1498 key_attempted = true;
1499
1500 if let Some(payload_range) = Self::try_group_decrypt(
1501 &crypto,
1502 packet,
1503 &saved_encrypted[..encrypted_len],
1504 encrypted_offset,
1505 op_key_ref,
1506 src_nodeid,
1507 ) {
1508 let mut op_key_owned = crate::crypto::AEAD_KEY_ZEROED;
1510 op_key_owned.load(op_key_ref);
1511 let effective_group_id = dst_group_id.unwrap_or(map_entry.group_id);
1512 group_key_found = Some(GroupKeyFound {
1513 fab_idx,
1514 group_id: effective_group_id,
1515 fabric_node_id,
1516 op_key: op_key_owned,
1517 payload_range,
1518 });
1519
1520 break 'outer;
1521 }
1522 }
1523 }
1524 }
1525
1526 if group_key_found.is_none() {
1527 debug!(
1528 "Group: No key could decrypt the message (SID=0x{:04x}, GRP={:?}, DSTU={:?})",
1529 expected_sess_id, dst_group_id, dst_unicast_nodeid
1530 );
1531 }
1532
1533 let GroupKeyFound {
1539 fab_idx,
1540 group_id,
1541 fabric_node_id,
1542 op_key,
1543 payload_range,
1544 } = group_key_found.ok_or(if key_attempted {
1545 ErrorCode::InvalidSignature
1546 } else {
1547 ErrorCode::NoSession
1548 })?;
1549
1550 if !is_control
1554 && !self
1555 .group_ctr_store
1556 .post_recv(fab_idx.get(), src_nodeid, msg_ctr)
1557 {
1558 debug!(
1559 "Group: Duplicate message counter {} from node 0x{:016x} fab_idx={}",
1560 msg_ctr, src_nodeid, fab_idx
1561 );
1562
1563 return Err(ErrorCode::Duplicate.into());
1564 }
1565
1566 let peer = packet.peer;
1568 let mut rand = crypto.weak_rand()?;
1569
1570 let session = match self.add(rand.next_u32(), false, peer, Some(src_nodeid), dev_det) {
1571 Ok(session) => session,
1572 Err(_) => {
1573 if let Some(lru_id) = self.get_session_for_eviction().map(|sess| sess.id) {
1575 debug!("Group: Evicting session {} to make room", lru_id);
1576 self.remove(lru_id);
1577 self.add(rand.next_u32(), false, peer, Some(src_nodeid), dev_det)?
1578 } else {
1579 return Err(ErrorCode::NoSpaceSessions.into());
1580 }
1581 }
1582 };
1583
1584 session.set_session_mode(SessionMode::Group { fab_idx, group_id });
1585 session.local_sess_id = expected_sess_id;
1586 session.peer_sess_id = expected_sess_id;
1589 session.set_local_nodeid(fabric_node_id);
1592 session.dec_key.load(op_key.reference());
1596 session.enc_key.load(op_key.reference());
1597
1598 debug!(
1599 "Group: Created group session for fab_idx={}, group_id=0x{:04x}, src_nodeid=0x{:016x}",
1600 fab_idx, group_id, src_nodeid
1601 );
1602
1603 let session = unwrap!(self.sessions.last_mut());
1605 session.update_last_used();
1606
1607 Ok((session, payload_range))
1608 }
1609
1610 fn try_group_decrypt<const N: usize, C: Crypto>(
1614 crypto: C,
1615 packet: &mut Packet<N>,
1616 saved_encrypted: &[u8],
1617 encrypted_offset: usize,
1618 op_key: CanonAeadKeyRef<'_>,
1619 src_nodeid: u64,
1620 ) -> Option<(usize, usize)> {
1621 let start = packet.payload_start + encrypted_offset;
1623 let encrypted_len = saved_encrypted.len();
1624 packet.buf[start..start + encrypted_len].copy_from_slice(saved_encrypted);
1625
1626 let mut pb = ParseBuf::new(&mut packet.buf[packet.payload_start..]);
1628 if packet.header.plain.decode(&mut pb).is_err() {
1629 error!("Plain header parse error");
1630 return None;
1631 }
1632
1633 if packet
1634 .header
1635 .decode_remaining(crypto, Some(op_key), src_nodeid, &mut pb)
1636 .is_ok()
1637 {
1638 packet.header.proto.adjust_reliability(true, &packet.peer);
1639 Some(pb.slice_range())
1640 } else {
1641 None
1642 }
1643 }
1644}
1645
1646impl Sessions {
1647 pub fn get_next_sess_id(&mut self) -> u16 {
1648 let mut next_sess_id: u16;
1649 loop {
1650 next_sess_id = self.next_sess_id;
1651
1652 self.next_sess_id = self.next_sess_id.overflowing_add(1).0;
1654 if self.next_sess_id == 0 {
1655 self.next_sess_id = 1;
1656 }
1657
1658 if self
1660 .sessions
1661 .iter()
1662 .all(|sess| sess.get_local_sess_id() != next_sess_id)
1663 {
1664 break;
1665 }
1666 }
1667 next_sess_id
1668 }
1669
1670 pub fn get_next_exch_id<C: Crypto>(&mut self, crypto: C) -> Result<u16, Error> {
1671 if self.next_exch_id == 0 {
1672 let candidate = crypto.rand()?.next_u32() as u16;
1680 self.next_exch_id = if candidate == 0 { 1 } else { candidate };
1681 }
1682
1683 let mut next_exch_id: u16;
1684 loop {
1685 next_exch_id = self.next_exch_id;
1686
1687 self.next_exch_id = self.next_exch_id.overflowing_add(1).0;
1689 if self.next_exch_id == 0 {
1690 self.next_exch_id = 1;
1691 }
1692
1693 if self
1695 .sessions
1696 .iter()
1697 .flat_map(|sess| sess.exchanges.iter())
1698 .filter_map(|exch| exch.as_ref())
1699 .all(|exch| {
1700 !matches!(exch.role, Role::Responder(_)) || exch.exch_id != next_exch_id
1701 })
1702 {
1703 break;
1704 }
1705 }
1706
1707 Ok(next_exch_id)
1708 }
1709
1710 pub fn get_session_for_eviction(&mut self) -> Option<&mut Session> {
1711 let mut lru_index = None;
1712 let mut lru_ts = Instant::now();
1713 for (i, s) in self.sessions.iter().enumerate() {
1714 if (s.expired || s.last_use < lru_ts)
1715 && !s.reserved
1716 && s.exchanges.iter().all(Option::is_none)
1717 {
1718 lru_ts = s.last_use;
1719 lru_index = Some(i);
1720
1721 if s.expired {
1722 break;
1725 }
1726 }
1727 }
1728
1729 lru_index.map(|index| &mut self.sessions[index])
1730 }
1731
1732 pub fn add(
1733 &mut self,
1734 msg_ctr: u32,
1735 reserved: bool,
1736 peer_addr: Address,
1737 peer_nodeid: Option<u64>,
1738 dev_det: &BasicInfoConfig<'_>,
1739 ) -> Result<&mut Session, Error> {
1740 let session_id = self.next_sess_unique_id;
1741
1742 self.next_sess_unique_id += 1;
1743 if self.next_sess_unique_id > 0x0fff_ffff {
1744 self.next_sess_unique_id = 0;
1746 }
1747
1748 let (peer_active_interval_ms, peer_idle_interval_ms, peer_active_threshold_ms) =
1753 mrp::default_peer_mrp_params(dev_det);
1754
1755 let session = Session::init(
1756 session_id,
1757 msg_ctr,
1758 reserved,
1759 peer_addr,
1760 peer_nodeid,
1761 peer_active_interval_ms,
1762 peer_idle_interval_ms,
1763 peer_active_threshold_ms,
1764 );
1765
1766 self.sessions
1767 .push_init(session.into_fallible::<Error>(), || {
1768 ErrorCode::NoSpaceSessions.into()
1769 })?;
1770
1771 Ok(unwrap!(self.sessions.last_mut()))
1772 }
1773
1774 pub fn remove(&mut self, id: u32) -> Option<Session> {
1777 if let Some(index) = self.sessions.iter().position(|sess| sess.id == id) {
1778 Some(self.sessions.swap_remove(index))
1779 } else {
1780 None
1781 }
1782 }
1783
1784 pub fn remove_for_fabric(&mut self, fabric_idx: NonZeroU8, expire_sess_id: Option<u32>) {
1787 while let Some(index) = self.sessions.iter().position(|sess| {
1788 sess.get_local_fabric_idx() == fabric_idx.get() && Some(sess.id) != expire_sess_id
1789 }) {
1790 info!(
1791 "Dropping session with ID {} for fabric index {} immediately",
1792 self.sessions[index].id, fabric_idx
1793 );
1794 self.sessions.swap_remove(index);
1795 }
1796
1797 if let Some(expire_sess_id) = expire_sess_id {
1798 let expire_sess = self
1799 .sessions
1800 .iter_mut()
1801 .find(|sess| sess.id == expire_sess_id);
1802 if let Some(expire_sess) = expire_sess {
1803 expire_sess.expired = true;
1804 info!(
1805 "Marking session with ID {} as expired for fabric index {}",
1806 expire_sess_id,
1807 fabric_idx.get()
1808 );
1809 } else {
1810 warn!(
1811 "No session with ID {} found for fabric index {} to mark as expired",
1812 expire_sess_id,
1813 fabric_idx.get()
1814 );
1815 }
1816 }
1817 }
1818
1819 pub fn get(&mut self, id: u32) -> Option<&mut Session> {
1820 let mut session = self.sessions.iter_mut().find(|sess| sess.id == id);
1821
1822 if let Some(session) = session.as_mut() {
1823 session.update_last_used();
1824 }
1825
1826 session
1827 }
1828
1829 pub(crate) fn get_for_node(
1836 &mut self,
1837 fabric_idx: NonZeroU8,
1838 peer_node_id: u64,
1839 ) -> Option<&mut Session> {
1840 let idx = self
1850 .sessions
1851 .iter()
1852 .enumerate()
1853 .filter(|(_, s)| !s.expired && s.is_for_node(fabric_idx, peer_node_id))
1854 .max_by_key(|(_, s)| (s.peer_addr.is_tcp(), s.last_use))
1855 .map(|(i, _)| i)?;
1856
1857 let session = &mut self.sessions[idx];
1858
1859 session.update_last_used();
1860
1861 Some(session)
1862 }
1863
1864 pub(crate) fn get_pase_for_addr(&mut self, peer_addr: &Address) -> Option<&mut Session> {
1870 let mut session = self
1871 .sessions
1872 .iter_mut()
1873 .find(|s| !s.expired && s.is_pase_for_addr(peer_addr));
1874
1875 if let Some(session) = session.as_mut() {
1876 session.update_last_used();
1877 }
1878
1879 session
1880 }
1881
1882 pub(crate) fn get_for_rx(
1883 &mut self,
1884 rx_peer: &Address,
1885 rx_plain: &PlainHdr,
1886 ) -> Option<&mut Session> {
1887 let mut session = self
1888 .sessions
1889 .iter_mut()
1890 .find(|sess| sess.is_for_rx(rx_peer, rx_plain));
1891
1892 if let Some(session) = session.as_mut() {
1893 session.update_last_used();
1894 }
1895
1896 session
1897 }
1898
1899 pub(crate) fn get_for_tx(&mut self, session_id: u32) -> Option<&mut Session> {
1900 let mut session = self
1901 .sessions
1902 .iter_mut()
1903 .find(|sess| sess.is_for_tx(session_id));
1904
1905 if let Some(session) = session.as_mut() {
1906 session.update_last_used();
1907 }
1908
1909 session
1910 }
1911
1912 pub(crate) fn get_exch<F>(&mut self, f: F) -> Option<(&mut Session, usize)>
1913 where
1914 F: Fn(&Session, &ExchangeState) -> bool,
1915 {
1916 let exch = self
1917 .sessions
1918 .iter()
1919 .flat_map(|sess| {
1920 sess.exchanges
1921 .iter()
1922 .enumerate()
1923 .filter_map(move |(exch_index, exch)| {
1924 exch.as_ref().map(|exch| (sess, exch, exch_index))
1925 })
1926 })
1927 .filter(|(sess, exch, _)| f(sess, exch))
1928 .map(|(sess, _, exch_index)| (sess.id, exch_index))
1929 .next();
1930
1931 if let Some((id, exch_index)) = exch {
1932 let session = unwrap!(self.get(id));
1933 session.update_last_used();
1934
1935 Some((session, exch_index))
1936 } else {
1937 None
1938 }
1939 }
1940
1941 pub fn iter(&self) -> impl Iterator<Item = &Session> {
1943 self.sessions.iter()
1944 }
1945
1946 pub fn remove_pase(&mut self, expire_sess_id: Option<u32>) {
1970 while let Some(index) = self.sessions.iter().position(|sess| {
1971 matches!(sess.get_session_mode(), SessionMode::Pase { .. })
1972 && Some(sess.id) != expire_sess_id
1973 }) {
1974 info!("Dropping PASE session with ID {}", self.sessions[index].id);
1975 self.sessions.swap_remove(index);
1976 }
1977
1978 if let Some(expire_sess_id) = expire_sess_id {
1979 if let Some(sess) = self.sessions.iter_mut().find(|sess| {
1980 sess.id == expire_sess_id
1981 && matches!(sess.get_session_mode(), SessionMode::Pase { .. })
1982 }) {
1983 sess.expired = true;
1984 info!("Marking PASE session with ID {} as expired", expire_sess_id);
1985 }
1986 }
1987 }
1988}
1989
1990impl Default for Sessions {
1991 fn default() -> Self {
1992 Self::new()
1993 }
1994}
1995
1996impl fmt::Display for Sessions {
1997 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1998 writeln!(f, "{{[")?;
1999 for s in &self.sessions {
2000 writeln!(f, "{{ {}, }},", s)?;
2001 }
2002 write!(f, "], next_sess_id: {}", self.next_sess_id)?;
2003 write!(f, "}}")
2004 }
2005}
2006
2007pub fn derive_group_session_id<C: Crypto>(
2020 crypto: C,
2021 op_key: CanonAeadKeyRef<'_>,
2022) -> Result<u16, Error> {
2023 const GRP_KEY_HASH_INFO: &[u8] = b"GroupKeyHash";
2024
2025 let mut hash = CryptoSensitive::<2>::new();
2026
2027 crypto
2028 .kdf()?
2029 .expand(&[], op_key, GRP_KEY_HASH_INFO, &mut hash)
2030 .map_err(|_| ErrorCode::InvalidData)?;
2031
2032 let bytes = hash.access();
2033 Ok(((bytes[0] as u16) << 8) | (bytes[1] as u16))
2034}
2035
2036#[cfg(test)]
2037mod tests {
2038 use crate::crypto::{test_only_crypto, AEAD_KEY_ZEROED};
2039 use crate::dm::clusters::basic_info::BasicInfoConfig;
2040 use crate::transport::network::Address;
2041
2042 use super::*;
2043
2044 const TEST_DEV_DET: BasicInfoConfig<'static> = BasicInfoConfig::new();
2047
2048 #[test]
2049 fn test_next_sess_id_doesnt_reuse() {
2050 let mut sm = Sessions::new();
2051 let sess = unwrap!(sm.add(0, false, Address::default(), None, &TEST_DEV_DET));
2052 sess.set_local_sess_id(1);
2053 assert_eq!(sm.get_next_sess_id(), 2);
2054 assert_eq!(sm.get_next_sess_id(), 3);
2055 let sess = unwrap!(sm.add(0, false, Address::default(), None, &TEST_DEV_DET));
2056 sess.set_local_sess_id(4);
2057 assert_eq!(sm.get_next_sess_id(), 5);
2058 }
2059
2060 #[test]
2061 fn test_next_sess_id_overflows() {
2062 let mut sm = Sessions::new();
2063 let sess = unwrap!(sm.add(0, false, Address::default(), None, &TEST_DEV_DET));
2064 sess.set_local_sess_id(1);
2065 assert_eq!(sm.get_next_sess_id(), 2);
2066 sm.next_sess_id = 65534;
2067 assert_eq!(sm.get_next_sess_id(), 65534);
2068 assert_eq!(sm.get_next_sess_id(), 65535);
2069 assert_eq!(sm.get_next_sess_id(), 2);
2070 }
2071
2072 #[test]
2073 fn test_derive_group_session_id() {
2074 let op_key_bytes: [u8; 16] = [
2078 0xa6, 0xf5, 0x30, 0x6b, 0xaf, 0x6d, 0x05, 0x0a, 0xf2, 0x3b, 0xa4, 0xbd, 0x6b, 0x9d,
2079 0xd9, 0x60,
2080 ];
2081
2082 let mut op_key = AEAD_KEY_ZEROED;
2083 op_key.try_load_from_slice(&op_key_bytes).unwrap();
2084
2085 let crypto = test_only_crypto();
2086 let session_id = derive_group_session_id(&crypto, op_key.reference()).unwrap();
2087
2088 assert_eq!(
2089 session_id, 0xB9F7,
2090 "Group Session ID mismatch: got 0x{:04X}, expected 0xB9F7",
2091 session_id
2092 );
2093 }
2094
2095 #[cfg(feature = "groups")]
2100 #[test]
2101 fn test_group_data_ctr_reserve_boundary() {
2102 let crypto = test_only_crypto();
2103
2104 let mut sessions = Sessions::new();
2105
2106 sessions.resume_global_group_data_ctr(1000);
2108
2109 let (value, boundary) = sessions.reserve_global_group_data_ctr(&crypto).unwrap();
2113 assert_eq!(value, 1000);
2114 assert_eq!(boundary, Some(1000 + GROUP_DATA_CTR_EPOCH));
2115
2116 let durable = boundary.unwrap();
2119 for expected in 1001..1000 + GROUP_DATA_CTR_EPOCH {
2120 let (value, boundary) = sessions.reserve_global_group_data_ctr(&crypto).unwrap();
2121 assert_eq!(value, expected);
2122 assert_eq!(boundary, None);
2123 assert!(value < durable);
2124 }
2125
2126 let (value, boundary) = sessions.reserve_global_group_data_ctr(&crypto).unwrap();
2129 assert_eq!(value, durable);
2130 assert_eq!(boundary, Some(1000 + 2 * GROUP_DATA_CTR_EPOCH));
2131 }
2132
2133 #[cfg(feature = "groups")]
2136 #[test]
2137 fn test_group_data_ctr_first_reservation_persists() {
2138 let crypto = test_only_crypto();
2139
2140 let mut sessions = Sessions::new();
2141
2142 let (value, boundary) = sessions.reserve_global_group_data_ctr(&crypto).unwrap();
2143
2144 assert_ne!(value, 0);
2145 let boundary = boundary.expect("the first reservation must demand a persist");
2146 assert!(value < boundary);
2147 }
2148
2149 #[cfg(feature = "groups")]
2153 #[test]
2154 fn test_group_data_ctr_wraps_within_range() {
2155 let crypto = test_only_crypto();
2156 let top = MATTER_MSG_CTR_RANGE;
2157
2158 let mut sessions = Sessions::new();
2159 sessions.resume_global_group_data_ctr(top);
2160
2161 assert_eq!(
2162 sessions.reserve_global_group_data_ctr(&crypto).unwrap().0,
2163 top
2164 );
2165 assert_eq!(
2167 sessions.reserve_global_group_data_ctr(&crypto).unwrap().0,
2168 1
2169 );
2170
2171 let mut sessions = Sessions::new();
2173 sessions.resume_global_group_data_ctr(top + 1 - GROUP_DATA_CTR_EPOCH);
2174 assert_eq!(
2175 sessions.reserve_global_group_data_ctr(&crypto).unwrap().1,
2176 Some(1)
2177 );
2178 }
2179
2180 #[cfg(all(feature = "groups", feature = "std"))]
2186 #[test]
2187 fn test_group_data_ctr_persist_covers_every_value_across_wrap() {
2188 let crypto = test_only_crypto();
2189
2190 let mut sessions = Sessions::new();
2191
2192 let start = MATTER_MSG_CTR_RANGE - 2 * GROUP_DATA_CTR_EPOCH;
2195 sessions.resume_global_group_data_ctr(start);
2196
2197 let mut used = std::collections::HashSet::new();
2198 let mut last_stored = start;
2199
2200 for _ in 0..5 * GROUP_DATA_CTR_EPOCH {
2201 let (value, boundary) = sessions.reserve_global_group_data_ctr(&crypto).unwrap();
2202
2203 if let Some(boundary) = boundary {
2205 last_stored = boundary;
2206 }
2207
2208 used.insert(value);
2209
2210 assert!(
2211 !used.contains(&last_stored),
2212 "a restart would resume at an already used counter value"
2213 );
2214 }
2215 }
2216
2217 #[cfg(feature = "groups")]
2220 #[test]
2221 fn test_group_data_ctr_resume_zero() {
2222 let crypto = test_only_crypto();
2223
2224 let mut sessions = Sessions::new();
2225 sessions.resume_global_group_data_ctr(0);
2226
2227 assert_eq!(
2228 sessions.reserve_global_group_data_ctr(&crypto).unwrap().0,
2229 1
2230 );
2231 }
2232
2233 #[cfg(all(feature = "groups", feature = "std"))]
2236 struct MemKv(std::collections::HashMap<u16, std::vec::Vec<u8>>);
2237
2238 #[cfg(all(feature = "groups", feature = "std"))]
2239 impl crate::persist::KvBlobStore for &mut MemKv {
2240 fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
2241 Ok(self.0.get(&key).map(|v| {
2242 buf[..v.len()].copy_from_slice(v);
2243 &buf[..v.len()]
2244 }))
2245 }
2246
2247 fn store(&mut self, key: u16, data: &[u8], _buf: &mut [u8]) -> Result<(), Error> {
2248 self.0.insert(key, data.to_vec());
2249 Ok(())
2250 }
2251
2252 fn remove(&mut self, key: u16, _buf: &mut [u8]) -> Result<(), Error> {
2253 self.0.remove(&key);
2254 Ok(())
2255 }
2256 }
2257
2258 #[cfg(all(feature = "groups", feature = "std"))]
2265 #[test]
2266 fn test_group_data_ctr_resumes_from_storage() {
2267 use crate::dm::devices::test::{TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET};
2268 use crate::persist::GROUP_DATA_COUNTER_KEY;
2269 use crate::Matter;
2270
2271 const STORED: u32 = 5000;
2273
2274 let mut kv = MemKv(std::collections::HashMap::new());
2275 kv.0.insert(GROUP_DATA_COUNTER_KEY, STORED.to_le_bytes().to_vec());
2276
2277 let matter = Matter::new(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, 0);
2278 matter.startup(matter.kv(&mut kv)).unwrap();
2279
2280 let stored = kv.0.get(&GROUP_DATA_COUNTER_KEY).unwrap();
2283 assert_eq!(
2284 u32::from_le_bytes(stored.as_slice().try_into().unwrap()),
2285 STORED
2286 );
2287
2288 let (first, boundary) = matter
2292 .with_state(|state| {
2293 state
2294 .sessions
2295 .reserve_global_group_data_ctr(test_only_crypto())
2296 })
2297 .unwrap();
2298
2299 assert_eq!(first, STORED);
2300 assert_eq!(boundary, Some(STORED + GROUP_DATA_CTR_EPOCH));
2301 }
2302
2303 #[cfg(all(feature = "groups", feature = "std"))]
2307 #[test]
2308 fn test_group_data_ctr_factory_reset() {
2309 use crate::dm::devices::test::{TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET};
2310 use crate::persist::GROUP_DATA_COUNTER_KEY;
2311 use crate::Matter;
2312
2313 let mut kv = MemKv(std::collections::HashMap::new());
2314 kv.0.insert(GROUP_DATA_COUNTER_KEY, 5000u32.to_le_bytes().to_vec());
2315
2316 let matter = Matter::new(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, 0);
2317 matter.startup(matter.kv(&mut kv)).unwrap();
2318
2319 matter.factory_reset(matter.kv(&mut kv)).unwrap();
2320
2321 assert!(!kv.0.contains_key(&GROUP_DATA_COUNTER_KEY));
2322
2323 let (value, boundary) = matter
2326 .with_state(|state| {
2327 state
2328 .sessions
2329 .reserve_global_group_data_ctr(test_only_crypto())
2330 })
2331 .unwrap();
2332
2333 assert_ne!(value, 0);
2334 assert_eq!(boundary, Some(value + GROUP_DATA_CTR_EPOCH));
2335 }
2336}