Skip to main content

rs_matter/
sc.rs

1/*
2 *
3 *    Copyright (c) 2022-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18use core::borrow::Borrow;
19use core::future::Future;
20use core::mem::MaybeUninit;
21
22use num_derive::FromPrimitive;
23
24use crate::crypto::Crypto;
25use crate::dm::AttrChangeNotifier;
26use crate::error::{Error, ErrorCode};
27use crate::respond::ExchangeHandler;
28use crate::tlv::{FromTLV, ToTLV};
29use crate::transport::exchange::{Exchange, MessageMeta};
30use crate::utils::init::InitMaybeUninit;
31use crate::utils::storage::{ReadBuf, WriteBuf};
32
33use case::CaseResponder;
34use pase::PaseResponder;
35
36pub mod busy;
37pub mod case;
38pub mod pase;
39
40/* Interaction Model ID as per the Matter Spec */
41pub const PROTO_ID_SECURE_CHANNEL: u16 = 0x00;
42
43#[derive(FromPrimitive, Debug, Copy, Clone, Eq, PartialEq)]
44#[cfg_attr(feature = "defmt", derive(defmt::Format))]
45pub enum OpCode {
46    MsgCounterSyncReq = 0x00,
47    MsgCounterSyncResp = 0x01,
48    MRPStandAloneAck = 0x10,
49    PBKDFParamRequest = 0x20,
50    PBKDFParamResponse = 0x21,
51    PASEPake1 = 0x22,
52    PASEPake2 = 0x23,
53    PASEPake3 = 0x24,
54    CASESigma1 = 0x30,
55    CASESigma2 = 0x31,
56    CASESigma3 = 0x32,
57    CASESigma2Resume = 0x33,
58    StatusReport = 0x40,
59}
60
61impl OpCode {
62    pub fn meta(&self) -> MessageMeta {
63        MessageMeta {
64            proto_id: PROTO_ID_SECURE_CHANNEL,
65            proto_opcode: *self as u8,
66            reliable: !matches!(self, Self::MRPStandAloneAck),
67        }
68    }
69
70    pub fn is_tlv(&self) -> bool {
71        !matches!(
72            self,
73            Self::MRPStandAloneAck
74                | Self::StatusReport
75                | Self::MsgCounterSyncReq
76                | Self::MsgCounterSyncResp
77        )
78    }
79}
80
81impl From<OpCode> for MessageMeta {
82    fn from(op: OpCode) -> Self {
83        op.meta()
84    }
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq)]
88#[cfg_attr(feature = "defmt", derive(defmt::Format))]
89pub enum SCStatusCodes {
90    SessionEstablishmentSuccess = 0,
91    NoSharedTrustRoots = 1,
92    InvalidParameter = 2,
93    CloseSession = 3,
94    Busy = 4,
95    SessionNotFound = 5,
96}
97
98impl SCStatusCodes {
99    pub fn reliable(&self) -> bool {
100        // CloseSession, Busy and SessionNotFound are sent without the R flag raised
101        !matches!(
102            self,
103            SCStatusCodes::CloseSession | SCStatusCodes::Busy | SCStatusCodes::SessionNotFound
104        )
105    }
106
107    pub fn as_report<'a>(&self, payload: &'a [u8]) -> StatusReport<'a> {
108        let general_code = match self {
109            SCStatusCodes::SessionEstablishmentSuccess => GeneralCode::Success,
110            SCStatusCodes::CloseSession => GeneralCode::Success,
111            SCStatusCodes::Busy => GeneralCode::Busy,
112            SCStatusCodes::InvalidParameter
113            | SCStatusCodes::NoSharedTrustRoots
114            | SCStatusCodes::SessionNotFound => GeneralCode::Failure,
115        };
116
117        StatusReport {
118            general_code,
119            proto_id: PROTO_ID_SECURE_CHANNEL as u32,
120            proto_code: *self as u16,
121            proto_data: payload,
122        }
123    }
124}
125
126pub async fn complete_with_status(
127    exchange: &mut Exchange<'_>,
128    status_code: SCStatusCodes,
129    payload: &[u8],
130) -> Result<(), Error> {
131    exchange
132        .send_with(|_, wb| sc_write(wb, status_code, payload))
133        .await
134}
135
136pub fn sc_write(
137    wb: &mut WriteBuf,
138    status_code: SCStatusCodes,
139    payload: &[u8],
140) -> Result<Option<MessageMeta>, Error> {
141    status_code.as_report(payload).write(wb)?;
142
143    Ok(Some(
144        OpCode::StatusReport.meta().reliable(status_code.reliable()),
145    ))
146}
147
148#[allow(dead_code)]
149#[derive(FromPrimitive, PartialEq, Eq, Debug, Copy, Clone)]
150#[cfg_attr(feature = "defmt", derive(defmt::Format))]
151pub enum GeneralCode {
152    Success = 0,
153    Failure = 1,
154    BadPrecondition = 2,
155    OutOfRange = 3,
156    BadRequest = 4,
157    Unsupported = 5,
158    Unexpected = 6,
159    ResourceExhausted = 7,
160    Busy = 8,
161    Timeout = 9,
162    Continue = 10,
163    Aborted = 11,
164    InvalidArgument = 12,
165    NotFound = 13,
166    AlreadyExists = 14,
167    PermissionDenied = 15,
168    DataLoss = 16,
169}
170
171/// Represents the session parameters
172/// that might present in a "PBKDFParamRequest"/"PBKDFParamResponse" or "CASE-Sigma1"/"CASE-Sigma2" message
173#[derive(Default, FromTLV, ToTLV, Debug)]
174#[cfg_attr(feature = "defmt", derive(defmt::Format))]
175#[tlvargs(start = 1)]
176pub(crate) struct SessionParameters {
177    /// Session Idle Interval
178    pub(crate) sii: Option<u32>,
179    /// Session Active Interval
180    pub(crate) sai: Option<u32>,
181    /// Session Active Threshold
182    pub(crate) sat: Option<u16>,
183    /// Data Model Revision
184    pub(crate) dm_revision: Option<u16>,
185    /// Interaction Model Revision
186    pub(crate) im_revision: Option<u16>,
187    /// Specification Version
188    pub(crate) spec_version: Option<u32>,
189    /// Maximum number of paths per invoke
190    pub(crate) max_paths_per_invoke: Option<u16>,
191}
192
193/// Represents a Status Report message, as per "Appendix D: Status Report Messages" of the Matter Spec.
194#[derive(Debug, Clone)]
195#[cfg_attr(feature = "defmt", derive(defmt::Format))]
196pub struct StatusReport<'a> {
197    pub general_code: GeneralCode,
198    pub proto_id: u32,
199    pub proto_code: u16,
200    pub proto_data: &'a [u8],
201}
202
203impl<'a> StatusReport<'a> {
204    pub fn read<T>(pb: &'a mut ReadBuf<T>) -> Result<Self, Error>
205    where
206        T: Borrow<[u8]>,
207    {
208        Ok(Self {
209            general_code: num::FromPrimitive::from_u16(pb.le_u16()?)
210                .ok_or(ErrorCode::InvalidOpcode)?,
211            proto_id: pb.le_u32()?,
212            proto_code: pb.le_u16()?,
213            proto_data: pb.as_slice(),
214        })
215    }
216
217    pub fn write(&self, wb: &mut WriteBuf) -> Result<(), Error> {
218        wb.le_u16(self.general_code as u16)?;
219        wb.le_u32(self.proto_id)?;
220        wb.le_u16(self.proto_code)?;
221        wb.copy_from_slice(self.proto_data)?;
222
223        Ok(())
224    }
225}
226
227/// Handle messages related to the Secure Channel
228pub struct SecureChannel<'a, C> {
229    crypto: C,
230    notify: &'a dyn AttrChangeNotifier,
231}
232
233impl<'a, C: Crypto> SecureChannel<'a, C> {
234    #[inline(always)]
235    pub const fn new(crypto: C, notify: &'a dyn AttrChangeNotifier) -> Self {
236        Self { crypto, notify }
237    }
238
239    pub async fn handle(&self, mut exchange: Exchange<'_>) -> Result<(), Error> {
240        if exchange.rx().is_err() {
241            exchange.recv_fetch().await?;
242        }
243
244        let meta = exchange.rx()?.meta();
245        if meta.proto_id != PROTO_ID_SECURE_CHANNEL {
246            Err(ErrorCode::InvalidProto)?;
247        }
248
249        match meta.opcode()? {
250            OpCode::PBKDFParamRequest => {
251                let mut pase = MaybeUninit::uninit(); // TODO LARGE BUFFER
252                pase.init_with(PaseResponder::init(&self.crypto, self.notify))
253                    .handle(&mut exchange)
254                    .await
255            }
256            OpCode::CASESigma1 => {
257                let mut case = MaybeUninit::uninit(); // TODO LARGE BUFFER
258                case.init_with(CaseResponder::init(&self.crypto))
259                    .handle(&mut exchange)
260                    .await
261            }
262            opcode => {
263                error!("Invalid opcode: {:?}", opcode);
264                Err(ErrorCode::InvalidOpcode.into())
265            }
266        }
267    }
268}
269
270impl<C: Crypto> ExchangeHandler for SecureChannel<'_, C> {
271    fn handle(&self, exchange: Exchange<'_>) -> impl Future<Output = Result<(), Error>> {
272        SecureChannel::handle(self, exchange)
273    }
274}
275
276/// Check that the opcode of the received message matches the expected one.
277/// Logs an error if that's not the case, and if the opcode is `StatusReport`,
278/// it also logs the details of the status report.
279fn check_opcode(exchange: &Exchange<'_>, opcode: OpCode) -> Result<(), Error> {
280    let meta = exchange.rx()?.meta();
281    let their_opcode = meta.opcode::<OpCode>()?;
282
283    if their_opcode == opcode {
284        Ok(())
285    } else {
286        error!("Invalid opcode: {:?}, expected: {:?}", their_opcode, opcode);
287
288        if matches!(their_opcode, OpCode::StatusReport) {
289            let mut rb = ReadBuf::new(exchange.rx()?.payload());
290
291            // Show the status code details in the log
292            match StatusReport::read(&mut rb) {
293                Ok(status_report) => error!("Status Report: {:?}", status_report),
294                Err(e) => error!("Failed to parse Status Report: {:?}", e),
295            }
296        }
297
298        Err(ErrorCode::Invalid.into())
299    }
300}