matc/device/
persist.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3use std::sync::atomic::AtomicU32;
4
5use anyhow::{Context, Result};
6
7use crate::fabric;
8
9use super::Device;
10use super::types::{AttributeOverride, DeviceConfig, FabricInfo, PersistedDeviceState, PersistedFabricState};
11
12static PERSISTED_ATTRIBUTES: &[(u16, u32, u32)] = &[];
13
14impl Device {
15    pub(crate) fn collect_attribute_overrides(&self) -> Vec<AttributeOverride> {
16        let mut overrides = Vec::new();
17        for &(endpoint, cluster, attribute) in PERSISTED_ATTRIBUTES.iter().chain(self.extra_persisted.iter()) {
18            if let Some(tlv) = self.attributes.get(&(endpoint, cluster, attribute)) {
19                overrides.push(AttributeOverride {
20                    endpoint,
21                    cluster,
22                    attribute,
23                    tlv_hex: hex::encode(tlv),
24                });
25            }
26        }
27        overrides
28    }
29
30    /// Register an additional attribute key to include in persistence.
31    pub fn add_persisted_attribute(&mut self, endpoint: u16, cluster: u32, attribute: u32) {
32        let key = (endpoint, cluster, attribute);
33        if !self.extra_persisted.contains(&key) {
34            self.extra_persisted.push(key);
35        }
36    }
37
38    /// Persist current commissioned state to `{state_dir}/device_state.json`.
39    pub(crate) fn save_state(&self, state_dir: &str) -> Result<()> {
40        if self.fabrics.is_empty() {
41            anyhow::bail!("Device not commissioned: no fabrics");
42        }
43
44        let fabrics: Vec<PersistedFabricState> = self
45            .fabrics
46            .iter()
47            .map(|fi| PersistedFabricState {
48                fabric_index: fi.fabric_index,
49                trusted_root_cert: fi.trusted_root_cert.clone(),
50                noc: fi.noc.clone(),
51                icac: fi.icac.clone(),
52                ipk: fi.ipk.clone(),
53                controller_id: fi.controller_id,
54                vendor_id: fi.vendor_id,
55                device_matter_cert: fi.device_matter_cert.clone(),
56                label: fi.label.clone(),
57            })
58            .collect();
59
60        let state = PersistedDeviceState {
61            operational_key_hex: hex::encode(self.operational_key.to_bytes()),
62            next_fabric_index: self.next_fabric_index,
63            fabrics,
64            attribute_overrides: self.collect_attribute_overrides(),
65        };
66
67        std::fs::create_dir_all(state_dir)?;
68        let path = format!("{}/device_state.json", state_dir);
69        let json = serde_json::to_string_pretty(&state)?;
70        std::fs::write(&path, json)?;
71        log::info!("Device state saved to {}", path);
72        Ok(())
73    }
74
75    /// Register the operational `_matter._tcp.local` mDNS service for one fabric.
76    /// `fabric_idx` is an index into `self.fabrics`.
77    pub(crate) async fn register_operational_mdns(&self, fabric_idx: usize) -> Result<()> {
78        let fi = &self.fabrics[fabric_idx];
79        let nod_id = fi.device_node_id()?;
80        let ca_public_key = fi.ca_public_key()?;
81        let fabric_id = fi.fabric_id()?;
82        let ca_id = fi.ca_id()?;
83        let fabric = fabric::Fabric::new(fabric_id, ca_id, &ca_public_key);
84
85        let iname = format!(
86            "{}-{:016X}",
87            hex::encode_upper(fabric.compressed()?),
88            nod_id
89        );
90        let op_port: u16 = self
91            .config
92            .listen_address
93            .rsplit(':')
94            .next()
95            .and_then(|p| p.parse().ok())
96            .unwrap_or(5540);
97        let svc = crate::mdns2::ServiceRegistration {
98            instance_name: iname,
99            service_type: "_matter._tcp.local".to_string(),
100            port: op_port,
101            txt_records: vec![],
102            hostname: self.config.hostname.clone(),
103            ttl: 120,
104            subtypes: vec![],
105        };
106        self.mdns.register_service(svc).await;
107        Ok(())
108    }
109
110    /// Restore a previously commissioned device from `{state_dir}/device_state.json`.
111    ///
112    /// On success the device is ready to accept CASE sessions — it will NOT re-advertise
113    /// the commissionable `_matterc._udp` service.
114    pub async fn from_persisted_state(
115        config: DeviceConfig,
116        mdns: Arc<crate::mdns2::MdnsService>,
117        state_dir: &str,
118    ) -> Result<Self> {
119        let path = format!("{}/device_state.json", state_dir);
120        let json = std::fs::read_to_string(&path)
121            .with_context(|| format!("Cannot read persisted state from {}", path))?;
122        let state: PersistedDeviceState = serde_json::from_str(&json)?;
123
124        // Restore P-256 operational key from hex-encoded scalar bytes.
125        let key_bytes = hex::decode(&state.operational_key_hex)
126            .context("Invalid hex in operational_key_hex")?;
127        let operational_key = p256::SecretKey::from_slice(&key_bytes)
128            .context("Invalid P-256 scalar in persisted operational key")?;
129
130        let socket = tokio::net::UdpSocket::bind(&config.listen_address).await?;
131
132        let mut salt = vec![0u8; 32];
133        rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut salt);
134
135        let fabrics: Vec<FabricInfo> = state
136            .fabrics
137            .iter()
138            .map(|pf| FabricInfo {
139                fabric_index: pf.fabric_index,
140                ipk: pf.ipk.clone(),
141                fabric: None,
142                device_matter_cert: pf.device_matter_cert.clone(),
143                controller_id: pf.controller_id,
144                vendor_id: pf.vendor_id,
145                trusted_root_cert: pf.trusted_root_cert.clone(),
146                noc: pf.noc.clone(),
147                icac: pf.icac.clone(),
148                label: pf.label.clone(),
149            })
150            .collect();
151
152        let mut device = Self {
153            config,
154            socket,
155            salt,
156            pbkdf_iterations: 1000,
157            operational_key,
158            message_counter: AtomicU32::new(rand::random()),
159            pase_state: None,
160            pase_session: None,
161            case_states: HashMap::new(),
162            case_sessions: Vec::new(),
163            subscribe_states: Vec::new(),
164            active_subscriptions: Vec::new(),
165            pending_chunks: Vec::new(),
166            fabrics,
167            next_fabric_index: state.next_fabric_index,
168            pending_root_cert: None,
169            received_counters: HashSet::new(),
170            attributes: HashMap::new(),
171            dirty_attributes: HashSet::new(),
172            mdns,
173            extra_persisted: Vec::new(),
174        };
175
176        device.setup_default_attributes()?;
177
178        // Overlay persisted attribute values on top of the defaults.
179        // Old JSON files may still contain Fabrics/CommissionedFabrics entries here;
180        // they are overwritten by rebuild_fabrics_attribute() below.
181        for ov in &state.attribute_overrides {
182            let tlv = hex::decode(&ov.tlv_hex)
183                .with_context(|| format!("Bad tlv_hex for ({},{},{})", ov.endpoint, ov.cluster, ov.attribute))?;
184            device.attributes.insert((ov.endpoint, ov.cluster, ov.attribute), tlv);
185        }
186
187        // Rebuild Fabrics and CommissionedFabrics from the restored fabric list.
188        device.rebuild_fabrics_attribute()?;
189        device.dirty_attributes.clear();
190
191        // Re-register operational mDNS for each fabric.
192        for i in 0..device.fabrics.len() {
193            device.register_operational_mdns(i).await?;
194        }
195
196        log::info!(
197            "Device state restored from {} ({} fabric(s))",
198            path,
199            device.fabrics.len()
200        );
201        Ok(device)
202    }
203}