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::{canon, CanonAeadKey, CanonAeadKeyRef, Crypto, CryptoSensitive, Kdf};
27use crate::dm::clusters::basic_info::BasicInfoConfig;
28use crate::error::{Error, ErrorCode};
29use crate::fabric::Fabrics;
30use crate::group_keys::KeySet;
31use crate::sc::SessionParameters;
32use crate::transport::exchange::ExchangeId;
33use crate::transport::mrp::{self, ReliableMessage};
34use crate::transport::TransportRunner;
35use crate::utils::init::{init, Init, IntoFallibleInit};
36use crate::utils::storage::{ParseBuf, Vec, WriteBuf};
37use crate::{Matter, MatterState};
38
39use super::dedup::{GroupCtrStore, RxCtrState};
40use super::exchange::{ExchangeState, MessageMeta, Role};
41use super::mrp::RetransEntry;
42use super::network::Address;
43use super::packet::PacketHdr;
44use super::plain_hdr::PlainHdr;
45use super::proto_hdr::ProtoHdr;
46use super::Packet;
47
48pub const MAX_CAT_IDS_PER_NOC: usize = 3;
49pub type NocCatIds = [u32; MAX_CAT_IDS_PER_NOC];
50
51pub const ATT_CHALLENGE_LEN: usize = 16;
52
53canon!(
54 ATT_CHALLENGE_LEN,
55 ATT_CHALLENGE_ZEROED,
56 AttChallenge,
57 AttChallengeRef
58);
59
60#[derive(Debug, PartialEq, Eq, Clone, Default)]
61#[cfg_attr(feature = "defmt", derive(defmt::Format))]
62pub enum SessionMode {
63 Case {
66 fab_idx: NonZeroU8,
67 cat_ids: NocCatIds,
68 },
69 Pase {
73 fab_idx: u8,
74 },
75 Group {
77 fab_idx: NonZeroU8,
78 group_id: u16,
79 },
80 #[default]
81 PlainText,
82}
83
84impl SessionMode {
85 pub fn fab_idx(&self) -> u8 {
86 match self {
87 SessionMode::Case { fab_idx, .. } => fab_idx.get(),
88 SessionMode::Pase { fab_idx, .. } => *fab_idx,
89 SessionMode::Group { fab_idx, .. } => fab_idx.get(),
90 SessionMode::PlainText => 0,
91 }
92 }
93}
94
95pub struct Session {
96 pub(crate) id: u32,
98 peer_addr: Address,
99 local_nodeid: u64,
100 peer_nodeid: Option<u64>,
101 dec_key: CanonAeadKey,
104 enc_key: CanonAeadKey,
105 att_challenge: AttChallenge,
106 local_sess_id: u16,
107 peer_sess_id: u16,
108 msg_ctr: u32,
109 rx_ctr_state: RxCtrState,
110 mode: SessionMode,
111 pub(crate) exchanges: Vec<Option<ExchangeState>, MAX_EXCHANGES>,
112 last_use: Instant,
113 peer_active_interval_ms: u32,
116 peer_idle_interval_ms: u32,
120 peer_active_threshold_ms: u16,
122 expired: bool,
128 reserved: bool,
129}
130
131impl Session {
132 #[allow(clippy::too_many_arguments)]
133 pub fn new(
134 id: u32,
135 msg_ctr: u32,
136 reserved: bool,
137 peer_addr: Address,
138 peer_nodeid: Option<u64>,
139 peer_active_interval_ms: u32,
140 peer_idle_interval_ms: u32,
141 peer_active_threshold_ms: u16,
142 ) -> Self {
143 Self {
144 id,
145 reserved,
146 peer_addr,
147 local_nodeid: 0,
148 peer_nodeid,
149 dec_key: CanonAeadKey::new(),
150 enc_key: CanonAeadKey::new(),
151 att_challenge: AttChallenge::new(),
152 peer_sess_id: 0,
153 local_sess_id: 0,
154 msg_ctr: msg_ctr & MATTER_MSG_CTR_RANGE,
155 rx_ctr_state: RxCtrState::new(0),
156 mode: SessionMode::PlainText,
157 exchanges: Vec::new(),
158 last_use: Instant::now(),
159 peer_active_interval_ms,
160 peer_idle_interval_ms,
161 peer_active_threshold_ms,
162 expired: false,
163 }
164 }
165
166 #[allow(clippy::too_many_arguments)]
167 pub fn init(
168 id: u32,
169 msg_ctr: u32,
170 reserved: bool,
171 peer_addr: Address,
172 peer_nodeid: Option<u64>,
173 peer_active_interval_ms: u32,
174 peer_idle_interval_ms: u32,
175 peer_active_threshold_ms: u16,
176 ) -> impl Init<Self> {
177 init!(Self {
178 id,
179 reserved,
180 peer_addr,
181 local_nodeid: 0,
182 peer_nodeid,
183 dec_key <- CanonAeadKey::init(),
184 enc_key <- CanonAeadKey::init(),
185 att_challenge <- AttChallenge::init(),
186 peer_sess_id: 0,
187 local_sess_id: 0,
188 msg_ctr: msg_ctr & MATTER_MSG_CTR_RANGE,
189 rx_ctr_state: RxCtrState::new(0),
190 mode: SessionMode::PlainText,
191 exchanges <- Vec::init(),
192 last_use: Instant::now(),
193 peer_active_interval_ms,
194 peer_idle_interval_ms,
195 peer_active_threshold_ms,
196 expired: false,
197 })
198 }
199
200 pub const fn id(&self) -> u32 {
203 self.id
204 }
205
206 pub fn get_local_sess_id(&self) -> u16 {
207 self.local_sess_id
208 }
209
210 #[cfg(test)]
211 pub fn set_local_sess_id(&mut self, sess_id: u16) {
212 self.local_sess_id = sess_id;
213 }
214
215 pub(crate) fn set_local_nodeid(&mut self, nodeid: u64) {
216 self.local_nodeid = nodeid;
217 }
218
219 pub fn get_peer_sess_id(&self) -> u16 {
220 self.peer_sess_id
221 }
222
223 pub fn get_peer_addr(&self) -> Address {
224 self.peer_addr
225 }
226
227 pub fn is_encrypted(&self) -> bool {
228 match self.mode {
229 SessionMode::Case { .. } | SessionMode::Pase { .. } | SessionMode::Group { .. } => true,
230 SessionMode::PlainText => false,
231 }
232 }
233
234 pub fn get_peer_node_id(&self) -> Option<u64> {
235 self.peer_nodeid
236 }
237
238 pub fn get_local_fabric_idx(&self) -> u8 {
239 self.mode.fab_idx()
240 }
241
242 pub fn get_session_mode(&self) -> &SessionMode {
243 &self.mode
244 }
245
246 pub(crate) fn set_session_mode(&mut self, mode: SessionMode) {
247 self.mode = mode;
248 }
249
250 pub fn get_peer_active_interval_ms(&self) -> u32 {
251 self.peer_active_interval_ms
252 }
253
254 pub fn get_peer_idle_interval_ms(&self) -> u32 {
255 self.peer_idle_interval_ms
256 }
257
258 pub fn get_peer_active_threshold_ms(&self) -> u16 {
259 self.peer_active_threshold_ms
260 }
261
262 pub(crate) fn set_peer_session_params(&mut self, params: &SessionParameters) {
276 if let Some(sai) = params.sai {
277 if sai > 0 {
278 self.peer_active_interval_ms = sai;
279 } else {
280 warn!("Peer advertised session_parameters.sai=0; ignoring");
281 }
282 }
283
284 if let Some(sii) = params.sii {
285 if sii > 0 {
286 self.peer_idle_interval_ms = sii;
287 } else {
288 warn!("Peer advertised session_parameters.sii=0; ignoring");
289 }
290 }
291
292 if let Some(sat) = params.sat {
293 if sat > 0 {
294 self.peer_active_threshold_ms = sat;
295 } else {
296 warn!("Peer advertised session_parameters.sat=0; ignoring");
297 }
298 }
299 }
300
301 fn get_msg_ctr(&mut self) -> u32 {
302 let ctr = self.msg_ctr;
303 self.msg_ctr += 1;
304 ctr
305 }
306
307 pub fn get_dec_key(&self) -> Option<CanonAeadKeyRef<'_>> {
308 match self.mode {
309 SessionMode::Case { .. } | SessionMode::Pase { .. } | SessionMode::Group { .. } => {
310 Some(self.dec_key.reference())
311 }
312 SessionMode::PlainText => None,
313 }
314 }
315
316 pub fn get_enc_key(&self) -> Option<CanonAeadKeyRef<'_>> {
317 match self.mode {
318 SessionMode::Case { .. } | SessionMode::Pase { .. } | SessionMode::Group { .. } => {
319 Some(self.enc_key.reference())
320 }
321 SessionMode::PlainText => None,
322 }
323 }
324
325 pub fn get_att_challenge(&self) -> Option<AttChallengeRef<'_>> {
326 match self.mode {
327 SessionMode::Case { .. } | SessionMode::Pase { .. } => {
328 Some(self.att_challenge.reference())
329 }
330 SessionMode::PlainText | SessionMode::Group { .. } => None,
331 }
332 }
333
334 pub(crate) fn is_for_node(&self, fabric_idx: NonZeroU8, peer_node_id: u64) -> bool {
336 self.get_local_fabric_idx() == fabric_idx.get()
337 && self.peer_nodeid == Some(peer_node_id)
338 && self.is_encrypted()
339 && !self.reserved
340 }
341
342 pub(crate) fn is_pase_for_addr(&self, peer_addr: &Address) -> bool {
349 matches!(self.mode, SessionMode::Pase { .. })
350 && self.peer_addr.canonical() == peer_addr.canonical()
351 && !self.reserved
352 }
353
354 pub(crate) fn is_for_rx(&self, rx_peer: &Address, rx_plain: &PlainHdr) -> bool {
355 let nodeid_matches = self.peer_nodeid.is_none()
356 || rx_plain.get_src_nodeid().is_none()
357 || self.peer_nodeid == rx_plain.get_src_nodeid();
358
359 let dest_nodeid_matches = self.is_encrypted()
363 || self.local_nodeid == 0
364 || rx_plain.get_dst_unicast_nodeid().is_none()
365 || rx_plain.get_dst_unicast_nodeid() == Some(self.local_nodeid);
366
367 nodeid_matches
368 && dest_nodeid_matches
369 && self.local_sess_id == rx_plain.sess_id
370 && self.peer_addr.canonical() == rx_peer.canonical()
377 && self.is_encrypted() == rx_plain.is_encrypted()
378 && !self.reserved
379 }
380
381 pub(crate) fn is_for_tx(&self, session_id: u32) -> bool {
382 self.id == session_id
383 }
384
385 pub(crate) fn is_expired(&self) -> bool {
387 self.expired
388 }
389
390 pub fn upgrade_fabric_idx(&mut self, fabric_idx: NonZeroU8) -> Result<(), Error> {
391 if let SessionMode::Pase { fab_idx } = &mut self.mode {
392 if *fab_idx == 0 {
393 *fab_idx = fabric_idx.get();
394 } else {
395 Err(ErrorCode::Invalid)?;
397 }
398 } else {
399 Err(ErrorCode::Invalid)?;
402 }
403
404 Ok(())
405 }
406
407 pub(crate) fn post_recv(&mut self, rx_header: &PacketHdr) -> Result<bool, Error> {
411 if !self
412 .rx_ctr_state
413 .post_recv(rx_header.plain.ctr, self.is_encrypted(), false)
414 {
415 Err(ErrorCode::Duplicate)?;
416 }
417
418 let exch_index = self.get_exch_for_rx(&rx_header.proto);
419 if let Some(exch_index) = exch_index {
420 let exch = unwrap!(self.exchanges[exch_index].as_mut());
421
422 exch.post_recv(&rx_header.plain, &rx_header.proto)?;
423
424 Ok(false)
425 } else {
426 if !rx_header.proto.is_initiator()
427 || !MessageMeta::from(&rx_header.proto).is_new_exchange()
428 {
429 Err(ErrorCode::NoExchange)?;
433 }
434
435 if self.expired {
436 Err(ErrorCode::NoSession)?;
441 }
442
443 if let Some(exch_index) =
444 self.add_exch(rx_header.proto.exch_id, Role::Responder(Default::default()))
445 {
446 let exch = unwrap!(self.exchanges[exch_index].as_mut());
448
449 exch.post_recv(&rx_header.plain, &rx_header.proto)?;
450
451 Ok(true)
452 } else {
453 Err(ErrorCode::NoSpaceExchanges)?
454 }
455 }
456 }
457
458 pub(crate) fn pre_send(
459 &mut self,
460 exch_index: Option<usize>,
461 tx_header: &mut PacketHdr,
462 session_active_interval_ms: Option<u32>,
463 session_idle_interval_ms: Option<u32>,
464 ) -> Result<(Address, bool), Error> {
465 let ctr = if let Some(exchange_index) = exch_index {
466 let exchange = unwrap!(self.exchanges[exchange_index].as_mut());
467 exchange.mrp.retrans.as_ref().map(RetransEntry::get_msg_ctr)
468 } else {
469 None
470 };
471
472 let retransmission = ctr.is_some();
473
474 tx_header.plain.sess_id = self.get_peer_sess_id();
475 tx_header.plain.ctr = ctr.unwrap_or_else(|| self.get_msg_ctr());
476 tx_header.plain.set_src_nodeid(
480 (!self.is_encrypted() && self.local_nodeid != 0).then_some(self.local_nodeid),
481 );
482 tx_header.plain.set_dst_unicast_nodeid(
483 (self.mode == SessionMode::PlainText)
484 .then_some(self.peer_nodeid)
485 .flatten(),
486 );
487
488 tx_header.proto.adjust_reliability(false, &self.peer_addr);
489
490 if let Some(exchange_index) = exch_index {
491 let exchange = unwrap!(self.exchanges[exchange_index].as_mut());
492
493 exchange.pre_send(
494 &tx_header.plain,
495 &mut tx_header.proto,
496 session_active_interval_ms,
497 session_idle_interval_ms,
498 )?;
499 }
500
501 Ok((self.peer_addr, retransmission))
502 }
503
504 pub(crate) fn decode_remaining<C: Crypto>(
509 &self,
510 crypto: C,
511 rx_header: &mut PacketHdr,
512 mut pb: ParseBuf,
513 ) -> Result<(usize, usize), Error> {
514 rx_header.decode_remaining(
515 crypto,
516 self.get_dec_key(),
517 self.peer_nodeid.unwrap_or_default(),
518 &mut pb,
519 )?;
520
521 rx_header.proto.adjust_reliability(true, &self.peer_addr);
522
523 Ok(pb.slice_range())
524 }
525
526 pub(crate) fn encode<C: Crypto>(
527 &self,
528 crypto: C,
529 tx: &PacketHdr,
530 wb: &mut WriteBuf,
531 ) -> Result<(), Error> {
532 tx.encode(crypto, self.get_enc_key(), self.local_nodeid, wb)
533 }
534
535 fn update_last_used(&mut self) {
536 self.last_use = Instant::now();
537 }
538
539 pub(crate) fn get_exch_for_rx(&self, rx_proto: &ProtoHdr) -> Option<usize> {
540 self.exchanges
541 .iter()
542 .enumerate()
543 .filter(|(_, exch)| {
544 exch.as_ref()
545 .map(|exch| exch.is_for_rx(rx_proto))
546 .unwrap_or(false)
547 })
548 .map(|(index, _)| index)
549 .next()
550 }
551
552 pub(crate) fn add_exch(&mut self, exch_id: u16, role: Role) -> Option<usize> {
553 let exch_state = Some(ExchangeState {
554 exch_id,
555 role,
556 mrp: ReliableMessage::new(),
557 });
558
559 let exch_index = if self.exchanges.len() < MAX_EXCHANGES {
560 let _ = self.exchanges.push(exch_state);
561
562 self.exchanges.len() - 1
563 } else {
564 let index = self.exchanges.iter().position(Option::is_none);
565
566 if let Some(index) = index {
567 self.exchanges[index] = exch_state;
568
569 index
570 } else {
571 error!(
572 "Too many exchanges for session {} [SID:{:x},RSID:{:x}]; exchange creation failed",
573 self.id,
574 self.get_local_sess_id(),
575 self.get_peer_sess_id()
576 );
577
578 return None;
579 }
580 };
581
582 let exch_id = ExchangeId::new(self.id, exch_index);
583
584 debug!("New exchange: {} :: {:?}", exch_id.display(self), role);
585
586 Some(exch_index)
587 }
588
589 pub(crate) fn remove_exch(&mut self, index: usize) -> bool {
590 let exchange = unwrap!(self.exchanges[index].as_mut());
591 let exchange_id = ExchangeId::new(self.id, index);
592
593 if exchange.mrp.is_retrans_pending() {
594 exchange.role.set_dropped_state();
595 error!("Exchange {}: A packet is still (re)transmitted! Marking as dropped, but session will be closed", exchange_id.display(self));
596
597 false
598 } else if exchange.mrp.is_ack_pending() {
599 exchange.role.set_dropped_state();
600 warn!(
601 "Exchange {}: Pending ACK. Marking as dropped",
602 exchange_id.display(self)
603 );
604
605 false
606 } else {
607 trace!("Exchange {}: Dropped cleanly", exchange_id.display(self));
608 self.exchanges[index] = None;
609
610 true
611 }
612 }
613}
614
615impl fmt::Display for Session {
616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617 write!(
618 f,
619 "peer: {:?}, peer_nodeid: {:?}, local: {}, remote: {}, msg_ctr: {}, mode: {:?}, ts: {:?}, expired: {}",
620 self.peer_addr,
621 self.peer_nodeid,
622 self.local_sess_id,
623 self.peer_sess_id,
624 self.msg_ctr,
625 self.mode,
626 self.last_use,
627 self.expired,
628 )
629 }
630}
631
632pub struct ReservedSession<'a> {
636 id: u32,
637 matter: &'a Matter<'a>,
638 complete: bool,
639}
640
641impl<'a> ReservedSession<'a> {
642 pub fn reserve_now<C: Crypto>(matter: &'a Matter<'a>, crypto: C) -> Result<Self, Error> {
643 let dev_det = matter.dev_det();
644 matter.with_state(|state| {
645 let mut rand = crypto.weak_rand()?;
646
647 let id = state
648 .sessions
649 .add(rand.next_u32(), true, Address::new(), None, dev_det)?
650 .id;
651
652 Ok(Self {
653 id,
654 matter,
655 complete: false,
656 })
657 })
658 }
659
660 pub async fn reserve<C: Crypto>(
661 matter: &'a Matter<'a>,
662 crypto: C,
663 ) -> Result<ReservedSession<'a>, Error> {
664 let session = Self::reserve_now(matter, &crypto);
665
666 if let Ok(session) = session {
667 Ok(session)
668 } else {
669 TransportRunner::new(matter, &crypto)
670 .evict_some_session()
671 .await?;
672
673 Self::reserve_now(matter, &crypto)
674 }
675 }
676
677 #[allow(clippy::too_many_arguments)]
678 pub fn update(
679 &mut self,
680 local_nodeid: u64,
681 peer_nodeid: u64,
682 peer_sessid: u16,
683 local_sessid: u16,
684 peer_addr: Address,
685 mode: SessionMode,
686 dec_key: Option<CanonAeadKeyRef<'_>>,
687 enc_key: Option<CanonAeadKeyRef<'_>>,
688 att_challenge: Option<AttChallengeRef<'_>>,
689 ) -> Result<(), Error> {
690 self.matter.with_state(|state| {
691 self.update_with_state(
692 state,
693 local_nodeid,
694 peer_nodeid,
695 peer_sessid,
696 local_sessid,
697 peer_addr,
698 mode,
699 dec_key,
700 enc_key,
701 att_challenge,
702 )
703 })
704 }
705
706 #[allow(clippy::too_many_arguments)]
707 pub fn update_with_state(
708 &mut self,
709 state: &mut MatterState,
710 local_nodeid: u64,
711 peer_nodeid: u64,
712 peer_sessid: u16,
713 local_sessid: u16,
714 peer_addr: Address,
715 mode: SessionMode,
716 dec_key: Option<CanonAeadKeyRef<'_>>,
717 enc_key: Option<CanonAeadKeyRef<'_>>,
718 att_challenge: Option<AttChallengeRef<'_>>,
719 ) -> Result<(), Error> {
720 let session = state.sessions.get(self.id).ok_or(ErrorCode::NoSession)?;
721
722 session.local_nodeid = local_nodeid;
723 session.peer_nodeid = Some(peer_nodeid);
724 session.peer_sess_id = peer_sessid;
725 session.local_sess_id = local_sessid;
726 session.peer_addr = peer_addr;
727 session.mode = mode;
728
729 if let Some(dec_key) = dec_key {
730 session.dec_key.load(dec_key);
731 }
732
733 if let Some(enc_key) = enc_key {
734 session.enc_key.load(enc_key);
735 }
736
737 if let Some(att_challenge) = att_challenge {
738 session.att_challenge.load(att_challenge);
739 }
740
741 Ok(())
742 }
743
744 pub(crate) fn set_peer_session_params(
751 &mut self,
752 params: &SessionParameters,
753 ) -> Result<(), Error> {
754 self.matter.with_state(|state| {
755 let session = state.sessions.get(self.id).ok_or(ErrorCode::NoSession)?;
756 session.set_peer_session_params(params);
757 Ok(())
758 })
759 }
760
761 pub fn complete(mut self) {
762 self.complete = true;
763 }
764}
765
766impl Drop for ReservedSession<'_> {
767 fn drop(&mut self) {
768 self.matter.with_state(|state| {
769 if self.complete {
770 let session = unwrap!(state.sessions.get(self.id));
771 session.reserved = false;
772 } else {
773 state.sessions.remove(self.id);
774 }
775 })
776 }
777}
778
779cfg_if! {
780 if #[cfg(feature = "max-sessions-64")] {
781 pub const MAX_SESSIONS: usize = 64;
783 } else if #[cfg(feature = "max-sessions-32")] {
784 pub const MAX_SESSIONS: usize = 32;
786 } else if #[cfg(feature = "max-sessions-16")] {
787 pub const MAX_SESSIONS: usize = 16;
789 } else if #[cfg(feature = "max-sessions-8")] {
790 pub const MAX_SESSIONS: usize = 8;
792 } else if #[cfg(feature = "max-sessions-7")] {
793 pub const MAX_SESSIONS: usize = 7;
795 } else if #[cfg(feature = "max-sessions-6")] {
796 pub const MAX_SESSIONS: usize = 6;
798 } else if #[cfg(feature = "max-sessions-5")] {
799 pub const MAX_SESSIONS: usize = 5;
801 } else if #[cfg(feature = "max-sessions-4")] {
802 pub const MAX_SESSIONS: usize = 4;
804 } else if #[cfg(feature = "max-sessions-3")] {
805 pub const MAX_SESSIONS: usize = 3;
807 } else {
808 pub const MAX_SESSIONS: usize = 16;
810 }
811}
812
813cfg_if! {
814 if #[cfg(feature = "max-exchanges-per-session-16")] {
815 pub const MAX_EXCHANGES: usize = 16;
817 } else if #[cfg(feature = "max-exchanges-per-session-8")] {
818 pub const MAX_EXCHANGES: usize = 8;
820 } else if #[cfg(feature = "max-exchanges-per-session-7")] {
821 pub const MAX_EXCHANGES: usize = 7;
823 } else if #[cfg(feature = "max-exchanges-per-session-6")] {
824 pub const MAX_EXCHANGES: usize = 6;
826 } else if #[cfg(feature = "max-exchanges-per-session-5")] {
827 pub const MAX_EXCHANGES: usize = 5;
829 } else if #[cfg(feature = "max-exchanges-per-session-4")] {
830 pub const MAX_EXCHANGES: usize = 4;
832 } else if #[cfg(feature = "max-exchanges-per-session-3")] {
833 pub const MAX_EXCHANGES: usize = 3;
835 } else {
836 pub const MAX_EXCHANGES: usize = 5;
838 }
839}
840
841const MATTER_MSG_CTR_RANGE: u32 = 0x0fffffff;
842
843pub struct Sessions {
845 next_sess_unique_id: u32,
846 next_sess_id: u16,
847 next_exch_id: u16,
848 sessions: Vec<Session, MAX_SESSIONS>,
849 group_ctr_store: GroupCtrStore,
850}
851
852impl Sessions {
853 #[inline(always)]
855 pub const fn new() -> Self {
856 Self {
857 sessions: Vec::new(),
858 group_ctr_store: GroupCtrStore::new(),
859 next_sess_unique_id: 0,
860 next_sess_id: 1,
861 next_exch_id: 1,
862 }
863 }
864
865 pub fn init() -> impl Init<Self> {
867 init!(Self {
868 sessions <- Vec::init(),
869 group_ctr_store: GroupCtrStore::new(),
870 next_sess_unique_id: 0,
871 next_sess_id: 1,
872 next_exch_id: 1,
873 })
874 }
875
876 pub fn reset(&mut self) {
877 self.sessions.clear();
878 self.group_ctr_store = GroupCtrStore::new();
879 self.next_sess_id = 1;
880 self.next_exch_id = 1;
881 }
882
883 pub(crate) fn get_or_create_for_group_rx<const N: usize, C: Crypto>(
893 &mut self,
894 crypto: C,
895 fabrics: &Fabrics,
896 packet: &mut Packet<N>,
897 dev_det: &BasicInfoConfig<'_>,
898 ) -> Result<(&mut Session, (usize, usize)), Error> {
899 let src_nodeid = packet
900 .header
901 .plain
902 .get_src_nodeid()
903 .ok_or(ErrorCode::InvalidData)?;
904 let group_id = packet
905 .header
906 .plain
907 .get_dst_groupcast_nodeid()
908 .ok_or(ErrorCode::InvalidData)?;
909 let expected_sess_id = packet.header.plain.sess_id;
910 let msg_ctr = packet.header.plain.ctr;
911
912 debug!(
913 "Group: Attempting decrypt for PEER={:?} SID=0x{:04x}, GRP=0x{:04x}, SRC=0x{:016x}, CTR={}",
914 packet.peer, expected_sess_id, group_id, src_nodeid, msg_ctr
915 );
916
917 let mut pb = ParseBuf::new(&mut packet.buf[packet.payload_start..]);
919 packet.header.plain.decode(&mut pb)?;
920
921 let encrypted_offset = pb.read_off();
923 let encrypted_len = pb.as_slice().len();
924 let mut saved_encrypted = [0u8; 1280];
925 if encrypted_len > saved_encrypted.len() {
926 return Err(ErrorCode::BufferTooSmall.into());
927 }
928 saved_encrypted[..encrypted_len].copy_from_slice(pb.as_slice());
929
930 let mut group_key_found: Option<(NonZeroU8, (usize, usize))> = None;
932
933 'outer: for fabric in fabrics.iter() {
934 let fab_idx = fabric.fab_idx();
935 let compressed_fabric_id = fabric.compressed_fabric_id();
936
937 for map_entry in fabric.groups().key_map_iter() {
938 if map_entry.group_id != group_id {
939 continue;
940 }
941
942 let Some(key_set_entry) = fabric.groups().key_set_get(map_entry.group_key_set_id)
943 else {
944 continue;
945 };
946
947 for epoch_key_entry in key_set_entry.epoch_keys.iter() {
948 let mut temp_key_set = KeySet::new();
949 if temp_key_set
950 .update(
951 &crypto,
952 epoch_key_entry.epoch_key.reference(),
953 &compressed_fabric_id,
954 )
955 .is_err()
956 {
957 continue;
958 }
959
960 let op_key_ref = temp_key_set.op_key();
961 let Ok(session_id) = derive_group_session_id(&crypto, op_key_ref) else {
962 continue;
963 };
964
965 if session_id != expected_sess_id {
966 continue;
967 }
968
969 if let Some(payload_range) = Self::try_group_decrypt(
970 &crypto,
971 packet,
972 &saved_encrypted[..encrypted_len],
973 encrypted_offset,
974 op_key_ref,
975 src_nodeid,
976 ) {
977 group_key_found = Some((fab_idx, payload_range));
978 break 'outer;
979 }
980 }
981 }
982 }
983
984 if group_key_found.is_none() {
985 debug!(
986 "Group: No key could decrypt the message (SID=0x{:04x}, GRP=0x{:04x})",
987 expected_sess_id, group_id
988 );
989 }
990
991 let (fab_idx, payload_range) = group_key_found.ok_or(ErrorCode::NoSession)?;
992
993 if !self
995 .group_ctr_store
996 .post_recv(fab_idx.get(), src_nodeid, msg_ctr)
997 {
998 debug!(
999 "Group: Duplicate message counter {} from node 0x{:016x} fab_idx={}",
1000 msg_ctr, src_nodeid, fab_idx
1001 );
1002 return Err(ErrorCode::Duplicate.into());
1003 }
1004
1005 let peer = packet.peer;
1007 let mut rand = crypto.weak_rand()?;
1008 let session = match self.add(rand.next_u32(), false, peer, Some(src_nodeid), dev_det) {
1009 Ok(session) => session,
1010 Err(_) => {
1011 if let Some(lru_id) = self.get_session_for_eviction().map(|sess| sess.id) {
1013 debug!("Group: Evicting session {} to make room", lru_id);
1014 self.remove(lru_id);
1015 self.add(rand.next_u32(), false, peer, Some(src_nodeid), dev_det)?
1016 } else {
1017 return Err(ErrorCode::NoSpaceSessions.into());
1018 }
1019 }
1020 };
1021 session.set_session_mode(SessionMode::Group { fab_idx, group_id });
1022 session.local_sess_id = expected_sess_id;
1023
1024 debug!(
1025 "Group: Created group session for fab_idx={}, group_id=0x{:04x}, src_nodeid=0x{:016x}",
1026 fab_idx, group_id, src_nodeid
1027 );
1028
1029 let session = unwrap!(self.sessions.last_mut());
1031 session.update_last_used();
1032
1033 Ok((session, payload_range))
1034 }
1035
1036 fn try_group_decrypt<const N: usize, C: Crypto>(
1040 crypto: C,
1041 packet: &mut Packet<N>,
1042 saved_encrypted: &[u8],
1043 encrypted_offset: usize,
1044 op_key: CanonAeadKeyRef<'_>,
1045 src_nodeid: u64,
1046 ) -> Option<(usize, usize)> {
1047 let start = packet.payload_start + encrypted_offset;
1049 let encrypted_len = saved_encrypted.len();
1050 packet.buf[start..start + encrypted_len].copy_from_slice(saved_encrypted);
1051
1052 let mut pb = ParseBuf::new(&mut packet.buf[packet.payload_start..]);
1054 if packet.header.plain.decode(&mut pb).is_err() {
1055 error!("Plain header parse error");
1056 return None;
1057 }
1058
1059 if packet
1060 .header
1061 .decode_remaining(crypto, Some(op_key), src_nodeid, &mut pb)
1062 .is_ok()
1063 {
1064 packet.header.proto.adjust_reliability(true, &packet.peer);
1065 Some(pb.slice_range())
1066 } else {
1067 None
1068 }
1069 }
1070
1071 pub fn get_next_sess_id(&mut self) -> u16 {
1072 let mut next_sess_id: u16;
1073 loop {
1074 next_sess_id = self.next_sess_id;
1075
1076 self.next_sess_id = self.next_sess_id.overflowing_add(1).0;
1078 if self.next_sess_id == 0 {
1079 self.next_sess_id = 1;
1080 }
1081
1082 if self
1084 .sessions
1085 .iter()
1086 .all(|sess| sess.get_local_sess_id() != next_sess_id)
1087 {
1088 break;
1089 }
1090 }
1091 next_sess_id
1092 }
1093
1094 pub fn get_next_exch_id(&mut self) -> u16 {
1095 let mut next_exch_id: u16;
1096 loop {
1097 next_exch_id = self.next_exch_id;
1098
1099 self.next_exch_id = self.next_exch_id.overflowing_add(1).0;
1101 if self.next_exch_id == 0 {
1102 self.next_exch_id = 1;
1103 }
1104
1105 if self
1107 .sessions
1108 .iter()
1109 .flat_map(|sess| sess.exchanges.iter())
1110 .filter_map(|exch| exch.as_ref())
1111 .all(|exch| {
1112 !matches!(exch.role, Role::Responder(_)) || exch.exch_id != next_exch_id
1113 })
1114 {
1115 break;
1116 }
1117 }
1118 next_exch_id
1119 }
1120
1121 pub fn get_session_for_eviction(&mut self) -> Option<&mut Session> {
1122 let mut lru_index = None;
1123 let mut lru_ts = Instant::now();
1124 for (i, s) in self.sessions.iter().enumerate() {
1125 if (s.expired || s.last_use < lru_ts)
1126 && !s.reserved
1127 && s.exchanges.iter().all(Option::is_none)
1128 {
1129 lru_ts = s.last_use;
1130 lru_index = Some(i);
1131
1132 if s.expired {
1133 break;
1136 }
1137 }
1138 }
1139
1140 lru_index.map(|index| &mut self.sessions[index])
1141 }
1142
1143 pub fn add(
1144 &mut self,
1145 msg_ctr: u32,
1146 reserved: bool,
1147 peer_addr: Address,
1148 peer_nodeid: Option<u64>,
1149 dev_det: &BasicInfoConfig<'_>,
1150 ) -> Result<&mut Session, Error> {
1151 let session_id = self.next_sess_unique_id;
1152
1153 self.next_sess_unique_id += 1;
1154 if self.next_sess_unique_id > 0x0fff_ffff {
1155 self.next_sess_unique_id = 0;
1157 }
1158
1159 let (peer_active_interval_ms, peer_idle_interval_ms, peer_active_threshold_ms) =
1164 mrp::default_peer_mrp_params(dev_det);
1165
1166 let session = Session::init(
1167 session_id,
1168 msg_ctr,
1169 reserved,
1170 peer_addr,
1171 peer_nodeid,
1172 peer_active_interval_ms,
1173 peer_idle_interval_ms,
1174 peer_active_threshold_ms,
1175 );
1176
1177 self.sessions
1178 .push_init(session.into_fallible::<Error>(), || {
1179 ErrorCode::NoSpaceSessions.into()
1180 })?;
1181
1182 Ok(unwrap!(self.sessions.last_mut()))
1183 }
1184
1185 pub fn remove(&mut self, id: u32) -> Option<Session> {
1188 if let Some(index) = self.sessions.iter().position(|sess| sess.id == id) {
1189 Some(self.sessions.swap_remove(index))
1190 } else {
1191 None
1192 }
1193 }
1194
1195 pub fn remove_for_fabric(&mut self, fabric_idx: NonZeroU8, expire_sess_id: Option<u32>) {
1198 while let Some(index) = self.sessions.iter().position(|sess| {
1199 sess.get_local_fabric_idx() == fabric_idx.get() && Some(sess.id) != expire_sess_id
1200 }) {
1201 info!(
1202 "Dropping session with ID {} for fabric index {} immediately",
1203 self.sessions[index].id, fabric_idx
1204 );
1205 self.sessions.swap_remove(index);
1206 }
1207
1208 if let Some(expire_sess_id) = expire_sess_id {
1209 let expire_sess = self
1210 .sessions
1211 .iter_mut()
1212 .find(|sess| sess.id == expire_sess_id);
1213 if let Some(expire_sess) = expire_sess {
1214 expire_sess.expired = true;
1215 info!(
1216 "Marking session with ID {} as expired for fabric index {}",
1217 expire_sess_id,
1218 fabric_idx.get()
1219 );
1220 } else {
1221 warn!(
1222 "No session with ID {} found for fabric index {} to mark as expired",
1223 expire_sess_id,
1224 fabric_idx.get()
1225 );
1226 }
1227 }
1228 }
1229
1230 pub fn get(&mut self, id: u32) -> Option<&mut Session> {
1231 let mut session = self.sessions.iter_mut().find(|sess| sess.id == id);
1232
1233 if let Some(session) = session.as_mut() {
1234 session.update_last_used();
1235 }
1236
1237 session
1238 }
1239
1240 pub(crate) fn get_for_node(
1247 &mut self,
1248 fabric_idx: NonZeroU8,
1249 peer_node_id: u64,
1250 ) -> Option<&mut Session> {
1251 let idx = self
1256 .sessions
1257 .iter()
1258 .enumerate()
1259 .filter(|(_, s)| !s.expired && s.is_for_node(fabric_idx, peer_node_id))
1260 .max_by_key(|(_, s)| i32::from(s.peer_addr.is_tcp()))
1261 .map(|(i, _)| i)?;
1262
1263 let session = &mut self.sessions[idx];
1264
1265 session.update_last_used();
1266
1267 Some(session)
1268 }
1269
1270 pub(crate) fn get_pase_for_addr(&mut self, peer_addr: &Address) -> Option<&mut Session> {
1276 let mut session = self
1277 .sessions
1278 .iter_mut()
1279 .find(|s| !s.expired && s.is_pase_for_addr(peer_addr));
1280
1281 if let Some(session) = session.as_mut() {
1282 session.update_last_used();
1283 }
1284
1285 session
1286 }
1287
1288 pub(crate) fn get_for_rx(
1289 &mut self,
1290 rx_peer: &Address,
1291 rx_plain: &PlainHdr,
1292 ) -> Option<&mut Session> {
1293 let mut session = self
1294 .sessions
1295 .iter_mut()
1296 .find(|sess| sess.is_for_rx(rx_peer, rx_plain));
1297
1298 if let Some(session) = session.as_mut() {
1299 session.update_last_used();
1300 }
1301
1302 session
1303 }
1304
1305 pub(crate) fn get_for_tx(&mut self, session_id: u32) -> Option<&mut Session> {
1306 let mut session = self
1307 .sessions
1308 .iter_mut()
1309 .find(|sess| sess.is_for_tx(session_id));
1310
1311 if let Some(session) = session.as_mut() {
1312 session.update_last_used();
1313 }
1314
1315 session
1316 }
1317
1318 pub(crate) fn get_exch<F>(&mut self, f: F) -> Option<(&mut Session, usize)>
1319 where
1320 F: Fn(&Session, &ExchangeState) -> bool,
1321 {
1322 let exch = self
1323 .sessions
1324 .iter()
1325 .flat_map(|sess| {
1326 sess.exchanges
1327 .iter()
1328 .enumerate()
1329 .filter_map(move |(exch_index, exch)| {
1330 exch.as_ref().map(|exch| (sess, exch, exch_index))
1331 })
1332 })
1333 .filter(|(sess, exch, _)| f(sess, exch))
1334 .map(|(sess, _, exch_index)| (sess.id, exch_index))
1335 .next();
1336
1337 if let Some((id, exch_index)) = exch {
1338 let session = unwrap!(self.get(id));
1339 session.update_last_used();
1340
1341 Some((session, exch_index))
1342 } else {
1343 None
1344 }
1345 }
1346
1347 pub fn iter(&self) -> impl Iterator<Item = &Session> {
1349 self.sessions.iter()
1350 }
1351
1352 pub fn remove_pase(&mut self, expire_sess_id: Option<u32>) {
1376 while let Some(index) = self.sessions.iter().position(|sess| {
1377 matches!(sess.get_session_mode(), SessionMode::Pase { .. })
1378 && Some(sess.id) != expire_sess_id
1379 }) {
1380 info!("Dropping PASE session with ID {}", self.sessions[index].id);
1381 self.sessions.swap_remove(index);
1382 }
1383
1384 if let Some(expire_sess_id) = expire_sess_id {
1385 if let Some(sess) = self.sessions.iter_mut().find(|sess| {
1386 sess.id == expire_sess_id
1387 && matches!(sess.get_session_mode(), SessionMode::Pase { .. })
1388 }) {
1389 sess.expired = true;
1390 info!("Marking PASE session with ID {} as expired", expire_sess_id);
1391 }
1392 }
1393 }
1394}
1395
1396impl Default for Sessions {
1397 fn default() -> Self {
1398 Self::new()
1399 }
1400}
1401
1402impl fmt::Display for Sessions {
1403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1404 writeln!(f, "{{[")?;
1405 for s in &self.sessions {
1406 writeln!(f, "{{ {}, }},", s)?;
1407 }
1408 write!(f, "], next_sess_id: {}", self.next_sess_id)?;
1409 write!(f, "}}")
1410 }
1411}
1412
1413pub fn derive_group_session_id<C: Crypto>(
1426 crypto: C,
1427 op_key: CanonAeadKeyRef<'_>,
1428) -> Result<u16, Error> {
1429 const GRP_KEY_HASH_INFO: &[u8] = b"GroupKeyHash";
1430
1431 let mut hash = CryptoSensitive::<2>::new();
1432
1433 crypto
1434 .kdf()?
1435 .expand(&[], op_key, GRP_KEY_HASH_INFO, &mut hash)
1436 .map_err(|_| ErrorCode::InvalidData)?;
1437
1438 let bytes = hash.access();
1439 Ok(((bytes[0] as u16) << 8) | (bytes[1] as u16))
1440}
1441
1442#[cfg(test)]
1443mod tests {
1444 use crate::crypto::{test_only_crypto, AEAD_KEY_ZEROED};
1445 use crate::dm::clusters::basic_info::BasicInfoConfig;
1446 use crate::transport::network::Address;
1447
1448 use super::*;
1449
1450 const TEST_DEV_DET: BasicInfoConfig<'static> = BasicInfoConfig::new();
1453
1454 #[test]
1455 fn test_next_sess_id_doesnt_reuse() {
1456 let mut sm = Sessions::new();
1457 let sess = unwrap!(sm.add(0, false, Address::default(), None, &TEST_DEV_DET));
1458 sess.set_local_sess_id(1);
1459 assert_eq!(sm.get_next_sess_id(), 2);
1460 assert_eq!(sm.get_next_sess_id(), 3);
1461 let sess = unwrap!(sm.add(0, false, Address::default(), None, &TEST_DEV_DET));
1462 sess.set_local_sess_id(4);
1463 assert_eq!(sm.get_next_sess_id(), 5);
1464 }
1465
1466 #[test]
1467 fn test_next_sess_id_overflows() {
1468 let mut sm = Sessions::new();
1469 let sess = unwrap!(sm.add(0, false, Address::default(), None, &TEST_DEV_DET));
1470 sess.set_local_sess_id(1);
1471 assert_eq!(sm.get_next_sess_id(), 2);
1472 sm.next_sess_id = 65534;
1473 assert_eq!(sm.get_next_sess_id(), 65534);
1474 assert_eq!(sm.get_next_sess_id(), 65535);
1475 assert_eq!(sm.get_next_sess_id(), 2);
1476 }
1477
1478 #[test]
1479 fn test_derive_group_session_id() {
1480 let op_key_bytes: [u8; 16] = [
1484 0xa6, 0xf5, 0x30, 0x6b, 0xaf, 0x6d, 0x05, 0x0a, 0xf2, 0x3b, 0xa4, 0xbd, 0x6b, 0x9d,
1485 0xd9, 0x60,
1486 ];
1487
1488 let mut op_key = AEAD_KEY_ZEROED;
1489 op_key.try_load_from_slice(&op_key_bytes).unwrap();
1490
1491 let crypto = test_only_crypto();
1492 let session_id = derive_group_session_id(&crypto, op_key.reference()).unwrap();
1493
1494 assert_eq!(
1495 session_id, 0xB9F7,
1496 "Group Session ID mismatch: got 0x{:04X}, expected 0xB9F7",
1497 session_id
1498 );
1499 }
1500}