/* * * Copyright (c) 2020-2022 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use core::fmt; use core::time::Duration; use log::{error, info, trace}; use owo_colors::OwoColorize; use crate::error::{Error, ErrorCode}; use crate::interaction_model::core::{ResumeReadReq, ResumeSubscribeReq}; use crate::secure_channel; use crate::secure_channel::case::CaseSession; use crate::utils::epoch::Epoch; use crate::utils::rand::Rand; use heapless::LinearMap; use super::session::CloneData; use super::{mrp::ReliableMessage, packet::Packet, session::SessionHandle, session::SessionMgr}; pub struct ExchangeCtx<'a> { pub exch: &'a mut Exchange, pub sess: SessionHandle<'a>, pub epoch: Epoch, } impl<'a> ExchangeCtx<'a> { pub fn send(&mut self, tx: &mut Packet) -> Result { self.exch.send(tx, &mut self.sess) } } #[derive(Debug, PartialEq, Eq, Copy, Clone, Default)] pub enum Role { #[default] Initiator = 0, Responder = 1, } #[derive(Debug, PartialEq, Default)] enum State { /// The exchange is open and active #[default] Open, /// The exchange is closed, but keys are active since retransmissions/acks may be pending Close, /// The exchange is terminated, keys are destroyed, no communication can happen Terminate, } // Instead of just doing an Option<>, we create some special handling // where the commonly used higher layer data store does't have to do a Box #[derive(Default)] pub enum DataOption { CaseSession(CaseSession), Time(Duration), SuspendedReadReq(ResumeReadReq), SubscriptionId(u32), SuspendedSubscibeReq(ResumeSubscribeReq), #[default] None, } #[derive(Default)] pub struct Exchange { id: u16, sess_idx: usize, role: Role, state: State, mrp: ReliableMessage, // Currently I see this primarily used in PASE and CASE. If that is the limited use // of this, we might move this into a separate data structure, so as not to burden // all 'exchanges'. data: DataOption, } impl Exchange { pub fn new(id: u16, sess_idx: usize, role: Role) -> Exchange { Exchange { id, sess_idx, role, state: State::Open, mrp: ReliableMessage::new(), ..Default::default() } } pub fn terminate(&mut self) { self.data = DataOption::None; self.state = State::Terminate; } pub fn close(&mut self) { self.data = DataOption::None; self.state = State::Close; } pub fn is_state_open(&self) -> bool { self.state == State::Open } pub fn is_purgeable(&self) -> bool { // No Users, No pending ACKs/Retrans self.state == State::Terminate || (self.state == State::Close && self.mrp.is_empty()) } pub fn get_id(&self) -> u16 { self.id } pub fn get_role(&self) -> Role { self.role } pub fn clear_data(&mut self) { self.data = DataOption::None; } pub fn set_case_session(&mut self, session: CaseSession) { self.data = DataOption::CaseSession(session); } pub fn get_case_session(&mut self) -> Option<&mut CaseSession> { if let DataOption::CaseSession(session) = &mut self.data { Some(session) } else { None } } pub fn take_case_session(&mut self) -> Option { let old = core::mem::replace(&mut self.data, DataOption::None); if let DataOption::CaseSession(session) = old { Some(session) } else { self.data = old; None } } pub fn set_suspended_read_req(&mut self, req: ResumeReadReq) { self.data = DataOption::SuspendedReadReq(req); } pub fn take_suspended_read_req(&mut self) -> Option { let old = core::mem::replace(&mut self.data, DataOption::None); if let DataOption::SuspendedReadReq(req) = old { Some(req) } else { self.data = old; None } } pub fn set_subscription_id(&mut self, id: u32) { self.data = DataOption::SubscriptionId(id); } pub fn take_subscription_id(&mut self) -> Option { let old = core::mem::replace(&mut self.data, DataOption::None); if let DataOption::SubscriptionId(id) = old { Some(id) } else { self.data = old; None } } pub fn set_suspended_subscribe_req(&mut self, req: ResumeSubscribeReq) { self.data = DataOption::SuspendedSubscibeReq(req); } pub fn take_suspended_subscribe_req(&mut self) -> Option { let old = core::mem::replace(&mut self.data, DataOption::None); if let DataOption::SuspendedSubscibeReq(req) = old { Some(req) } else { self.data = old; None } } pub fn set_data_time(&mut self, expiry_ts: Option) { if let Some(t) = expiry_ts { self.data = DataOption::Time(t); } } pub fn get_data_time(&self) -> Option { match self.data { DataOption::Time(t) => Some(t), _ => None, } } pub(crate) fn send( &mut self, tx: &mut Packet, session: &mut SessionHandle, ) -> Result { if self.state == State::Terminate { info!("Skipping tx for terminated exchange {}", self.id); return Ok(false); } trace!("payload: {:x?}", tx.as_slice()); info!( "{} with proto id: {} opcode: {}, tlv:\n", "Sending".blue(), tx.get_proto_id(), tx.get_proto_opcode(), ); //print_tlv_list(tx.as_slice()); tx.proto.exch_id = self.id; if self.role == Role::Initiator { tx.proto.set_initiator(); } session.pre_send(tx)?; self.mrp.pre_send(tx)?; session.send(tx)?; Ok(true) } } impl fmt::Display for Exchange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "exch_id: {:?}, sess_index: {}, role: {:?}, mrp: {:?}, state: {:?}", self.id, self.sess_idx, self.role, self.mrp, self.state ) } } pub fn get_role(is_initiator: bool) -> Role { if is_initiator { Role::Initiator } else { Role::Responder } } pub fn get_complementary_role(is_initiator: bool) -> Role { if is_initiator { Role::Responder } else { Role::Initiator } } const MAX_EXCHANGES: usize = 8; pub struct ExchangeMgr { // keys: exch-id exchanges: LinearMap, sess_mgr: SessionMgr, epoch: Epoch, } pub const MAX_MRP_ENTRIES: usize = 4; impl ExchangeMgr { #[inline(always)] pub fn new(epoch: Epoch, rand: Rand) -> Self { Self { sess_mgr: SessionMgr::new(epoch, rand), exchanges: LinearMap::new(), epoch, } } pub fn get_sess_mgr(&mut self) -> &mut SessionMgr { &mut self.sess_mgr } pub fn _get_with_id( exchanges: &mut LinearMap, exch_id: u16, ) -> Option<&mut Exchange> { exchanges.get_mut(&exch_id) } pub fn get_with_id(&mut self, exch_id: u16) -> Option<&mut Exchange> { ExchangeMgr::_get_with_id(&mut self.exchanges, exch_id) } fn _get( exchanges: &mut LinearMap, sess_idx: usize, id: u16, role: Role, create_new: bool, ) -> Result<&mut Exchange, Error> { // I don't prefer that we scan the list twice here (once for contains_key and other) if !exchanges.contains_key(&(id)) { if create_new { // If an exchange doesn't exist, create a new one info!("Creating new exchange"); let e = Exchange::new(id, sess_idx, role); if exchanges.insert(id, e).is_err() { Err(ErrorCode::NoSpace)?; } } else { Err(ErrorCode::NoSpace)?; } } // At this point, we would either have inserted the record if 'create_new' was set // or it existed already if let Some(result) = exchanges.get_mut(&id) { if result.get_role() == role && sess_idx == result.sess_idx { Ok(result) } else { Err(ErrorCode::NoExchange.into()) } } else { error!("This should never happen"); Err(ErrorCode::NoSpace.into()) } } /// The Exchange Mgr receive is like a big processing function pub fn recv(&mut self, rx: &mut Packet) -> Result, Error> { // Get the session let index = self.sess_mgr.post_recv(rx)?; let mut session = self.sess_mgr.get_session_handle(index); // Decrypt the message session.recv(self.epoch, rx)?; // Get the exchange let exch = ExchangeMgr::_get( &mut self.exchanges, index, rx.proto.exch_id, get_complementary_role(rx.proto.is_initiator()), // We create a new exchange, only if the peer is the initiator rx.proto.is_initiator(), )?; // Message Reliability Protocol exch.mrp.recv(rx, self.epoch)?; if exch.is_state_open() { Ok(Some(ExchangeCtx { exch, sess: session, epoch: self.epoch, })) } else { // Instead of an error, we send None here, because it is likely that // we just processed an acknowledgement that cleared the exchange Ok(None) } } pub fn send(&mut self, exch_id: u16, tx: &mut Packet) -> Result { let exchange = ExchangeMgr::_get_with_id(&mut self.exchanges, exch_id).ok_or(ErrorCode::NoExchange)?; let mut session = self.sess_mgr.get_session_handle(exchange.sess_idx); exchange.send(tx, &mut session) } pub fn purge(&mut self) { let mut to_purge: LinearMap = LinearMap::new(); for (exch_id, exchange) in self.exchanges.iter() { if exchange.is_purgeable() { let _ = to_purge.insert(*exch_id, ()); } } for (exch_id, _) in to_purge.iter() { self.exchanges.remove(exch_id); } } pub fn pending_ack(&mut self) -> Option { self.exchanges .iter() .find(|(_, exchange)| exchange.mrp.is_ack_ready(self.epoch)) .map(|(exch_id, _)| *exch_id) } pub fn evict_session(&mut self, tx: &mut Packet) -> Result { if let Some(index) = self.sess_mgr.get_session_for_eviction() { info!("Sessions full, vacating session with index: {}", index); // If we enter here, we have an LRU session that needs to be reclaimed // As per the spec, we need to send a CLOSE here let mut session = self.sess_mgr.get_session_handle(index); secure_channel::common::create_sc_status_report( tx, secure_channel::common::SCStatusCodes::CloseSession, None, )?; if let Some((_, exchange)) = self.exchanges.iter_mut().find(|(_, e)| e.sess_idx == index) { // Send Close_session on this exchange, and then close the session // Should this be done for all exchanges? error!("Sending Close Session"); exchange.send(tx, &mut session)?; // TODO: This wouldn't actually send it out, because 'transport' isn't owned yet. } let remove_exchanges: heapless::Vec = self .exchanges .iter() .filter_map(|(eid, e)| { if e.sess_idx == index { Some(*eid) } else { None } }) .collect(); info!( "Terminating the following exchanges: {:?}", remove_exchanges ); for exch_id in remove_exchanges { // Remove from exchange list self.exchanges.remove(&exch_id); } self.sess_mgr.remove(index); Ok(true) } else { Ok(false) } } pub fn add_session(&mut self, clone_data: &CloneData) -> Result { let sess_idx = self.sess_mgr.clone_session(clone_data)?; Ok(self.sess_mgr.get_session_handle(sess_idx)) } } impl fmt::Display for ExchangeMgr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "{{ Session Mgr: {},", self.sess_mgr)?; writeln!(f, " Exchanges: [")?; for s in &self.exchanges { writeln!(f, "{{ {}, }},", s.1)?; } writeln!(f, " ]")?; write!(f, "}}") } } #[cfg(test)] #[allow(clippy::bool_assert_comparison)] mod tests { use crate::{ error::ErrorCode, transport::{ network::Address, session::{CloneData, SessionMode}, }, utils::{epoch::dummy_epoch, rand::dummy_rand}, }; use super::{ExchangeMgr, Role}; #[test] fn test_purge() { let mut mgr = ExchangeMgr::new(dummy_epoch, dummy_rand); let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, true).unwrap(); let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, true).unwrap(); mgr.purge(); assert_eq!( ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).is_ok(), true ); assert_eq!( ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, false).is_ok(), true ); // Close e1 let e1 = ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).unwrap(); e1.close(); mgr.purge(); assert_eq!( ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).is_ok(), false ); assert_eq!( ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, false).is_ok(), true ); } fn get_clone_data(peer_sess_id: u16, local_sess_id: u16) -> CloneData { CloneData::new( 12341234, 43211234, peer_sess_id, local_sess_id, Address::default(), SessionMode::Pase, ) } fn fill_sessions(mgr: &mut ExchangeMgr, count: usize) { let mut local_sess_id = 1; let mut peer_sess_id = 100; for _ in 1..count { let clone_data = get_clone_data(peer_sess_id, local_sess_id); match mgr.add_session(&clone_data) { Ok(s) => assert_eq!(peer_sess_id, s.get_peer_sess_id()), Err(e) => { if e.code() == ErrorCode::NoSpace { break; } else { panic!("Could not create sessions"); } } } local_sess_id += 1; peer_sess_id += 1; } } #[cfg(feature = "std")] #[test] /// We purposefuly overflow the sessions /// and when the overflow happens, we confirm that /// - The sessions are evicted in LRU /// - The exchanges associated with those sessions are evicted too fn test_sess_evict() { use crate::transport::packet::{Packet, MAX_TX_BUF_SIZE}; use crate::transport::session::MAX_SESSIONS; let mut mgr = ExchangeMgr::new(crate::utils::epoch::sys_epoch, dummy_rand); fill_sessions(&mut mgr, MAX_SESSIONS + 1); // Sessions are now full from local session id 1 to 16 // Create exchanges for sessions 2 (i.e. session index 1) and 3 (session index 2) // Exchange IDs are 20 and 30 respectively let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 20, Role::Responder, true).unwrap(); let _ = ExchangeMgr::_get(&mut mgr.exchanges, 2, 30, Role::Responder, true).unwrap(); // Confirm that session ids 1 to MAX_SESSIONS exists for i in 1..(MAX_SESSIONS + 1) { assert_eq!(mgr.sess_mgr.get_with_id(i as u16).is_none(), false); } // Confirm that the exchanges are around assert_eq!(mgr.get_with_id(20).is_none(), false); assert_eq!(mgr.get_with_id(30).is_none(), false); let mut old_local_sess_id = 1; let mut new_local_sess_id = 100; let mut new_peer_sess_id = 200; for i in 1..(MAX_SESSIONS + 1) { // Now purposefully overflow the sessions by adding another session let result = mgr.add_session(&get_clone_data(new_peer_sess_id, new_local_sess_id)); assert!(matches!( result.map_err(|e| e.code()), Err(ErrorCode::NoSpace) )); let mut buf = [0; MAX_TX_BUF_SIZE]; let tx = &mut Packet::new_tx(&mut buf); let evicted = mgr.evict_session(tx).unwrap(); assert!(evicted); let session = mgr .add_session(&get_clone_data(new_peer_sess_id, new_local_sess_id)) .unwrap(); assert_eq!(session.get_peer_sess_id(), new_peer_sess_id); // This should have evicted session with local sess_id assert_eq!(mgr.sess_mgr.get_with_id(old_local_sess_id).is_none(), true); new_local_sess_id += 1; new_peer_sess_id += 1; old_local_sess_id += 1; match i { 1 => { // Both exchanges should exist assert_eq!(mgr.get_with_id(20).is_none(), false); assert_eq!(mgr.get_with_id(30).is_none(), false); } 2 => { // Exchange 20 would have been evicted assert_eq!(mgr.get_with_id(20).is_none(), true); assert_eq!(mgr.get_with_id(30).is_none(), false); } 3 => { // Exchange 20 and 30 would have been evicted assert_eq!(mgr.get_with_id(20).is_none(), true); assert_eq!(mgr.get_with_id(30).is_none(), true); } _ => {} } } // println!("Session mgr {}", mgr.sess_mgr); } }