Skip to main content

matter_controller/
icd.rs

1//! ICD (Intermittently Connected Device) client registration state + the
2//! `IcdManagement` (0x0046) `RegisterClient` command `Value`.
3//!
4//! When the controller registers as a check-in client with an ICD, it generates
5//! a 16-byte symmetric key and records a [`IcdRegistration`] so the check-in
6//! listener can later decrypt + verify that device's unsolicited Check-In
7//! messages (see [`crate::icd_listener`]).
8
9use matter_codec::{Tag, Value};
10
11/// ICD Management cluster id.
12pub(crate) const ICD_MANAGEMENT_CLUSTER: u32 = 0x0046;
13
14/// `IcdManagement.ClientTypeEnum` โ€” whether the registration is permanent or
15/// ephemeral (Matter Core ยง9.17).
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum IcdClientType {
19    /// The client stays registered across the device's reboots.
20    Permanent,
21    /// The registration is dropped when the device reboots.
22    Ephemeral,
23}
24
25impl IcdClientType {
26    fn to_u8(self) -> u8 {
27        match self {
28            Self::Permanent => 0,
29            Self::Ephemeral => 1,
30        }
31    }
32}
33
34/// A persisted ICD client registration โ€” the controller registered itself as a
35/// check-in client with the device `node_id`, and holds the shared key needed to
36/// verify that device's Check-In messages.
37#[derive(Clone, Debug, PartialEq, Eq)]
38#[non_exhaustive]
39pub struct IcdRegistration {
40    /// The ICD device this registration is with.
41    pub node_id: u64,
42    /// The node id the ICD sends Check-Ins to (our commissioner node id).
43    pub check_in_node_id: u64,
44    /// The subject the ICD monitors on our behalf (typically our node id).
45    pub monitored_subject: u64,
46    /// The 16-byte symmetric key protecting this device's Check-In messages.
47    pub key: [u8; 16],
48    /// The device's `ICDCounter` at registration time โ€” the floor for the
49    /// monotonicity (replay) check on inbound Check-Ins.
50    pub start_counter: u32,
51}
52
53impl IcdRegistration {
54    /// Construct an [`IcdRegistration`] (the struct is `#[non_exhaustive]`).
55    #[must_use]
56    pub fn new(
57        node_id: u64,
58        check_in_node_id: u64,
59        monitored_subject: u64,
60        key: [u8; 16],
61        start_counter: u32,
62    ) -> Self {
63        Self {
64            node_id,
65            check_in_node_id,
66            monitored_subject,
67            key,
68            start_counter,
69        }
70    }
71}
72
73/// Build the `RegisterClient` command `Value` (0x0046 cmd 0x00): ctx0
74/// `CheckInNodeID`, ctx1 `MonitoredSubject`, ctx2 `Key`, ctx4 `ClientType`. The
75/// optional `VerificationKey` (ctx3) is omitted (only needed to re-key an
76/// existing registration, which is deferred).
77pub(crate) fn register_client_fields(
78    check_in_node_id: u64,
79    monitored_subject: u64,
80    key: &[u8; 16],
81    client_type: IcdClientType,
82) -> Value {
83    Value::Structure(vec![
84        (Tag::Context(0), Value::Uint(check_in_node_id)),
85        (Tag::Context(1), Value::Uint(monitored_subject)),
86        (Tag::Context(2), Value::Bytes(key.to_vec())),
87        (Tag::Context(4), Value::Uint(u64::from(client_type.to_u8()))),
88    ])
89}
90
91#[cfg(test)]
92mod tests {
93    #![allow(clippy::unwrap_used, clippy::expect_used)] // Test code: CLAUDE.md carve-out.
94    use super::*;
95
96    #[test]
97    fn register_client_fields_has_expected_tags() {
98        let key = [0xABu8; 16];
99        let v = register_client_fields(1, 2, &key, IcdClientType::Permanent);
100        let Value::Structure(m) = v else {
101            panic!("expected struct")
102        };
103        assert_eq!(m[0], (Tag::Context(0), Value::Uint(1)));
104        assert_eq!(m[1], (Tag::Context(1), Value::Uint(2)));
105        assert_eq!(m[2], (Tag::Context(2), Value::Bytes(key.to_vec())));
106        assert_eq!(m[3], (Tag::Context(4), Value::Uint(0)));
107    }
108}