matc/device/
types.rs

1use std::net::{Ipv4Addr, Ipv6Addr};
2
3use anyhow::{Context, Result};
4
5use crate::{fabric, sigma, spake2p, tlv};
6
7#[derive(Clone)]
8pub struct DeviceConfig {
9    pub pin: u32,
10    pub discriminator: u16,
11    pub listen_address: String,
12    pub vendor_id: u16,
13    pub product_id: u16,
14    pub dac_cert_path: String,
15    pub pai_cert_path: String,
16    pub dac_key_path: String,
17    pub hostname: String,
18    /// If Some, load/save commissioned state from this directory.
19    pub state_dir: Option<String>,
20    pub vendor_name: String,
21    pub product_name: String,
22    pub hardware_version: u16,
23    pub software_version: u32,
24    pub serial_number: String,
25    pub unique_id: String,
26    /// IP addresses to advertise in mDNS A/AAAA records.
27    /// When `None`, all local non-loopback addresses are advertised automatically.
28    /// Each registered service (commissionable and per-fabric operational) will use these IPs.
29    pub advertise_addresses: Option<Vec<std::net::IpAddr>>,
30}
31
32impl DeviceConfig {
33    /// Split `advertise_addresses` into separate IPv4 and IPv6 lists for mDNS registration.
34    /// Returns `(None, None)` when no override is configured (auto-detect fallback).
35    pub fn split_advertise_ips(&self) -> (Option<Vec<Ipv4Addr>>, Option<Vec<Ipv6Addr>>) {
36        match &self.advertise_addresses {
37            None => (None, None),
38            Some(addrs) => {
39                let v4: Vec<Ipv4Addr> = addrs.iter().filter_map(|a| match a {
40                    std::net::IpAddr::V4(ip) => Some(*ip),
41                    _ => None,
42                }).collect();
43                let v6: Vec<Ipv6Addr> = addrs.iter().filter_map(|a| match a {
44                    std::net::IpAddr::V6(ip) => Some(*ip),
45                    _ => None,
46                }).collect();
47                (Some(v4), Some(v6))
48            }
49        }
50    }
51}
52
53/// Serde helper: serialize `Vec<u8>` as a lowercase hex string.
54mod hex_bytes {
55    use serde::{Deserialize, Deserializer, Serializer};
56
57    pub fn serialize<S: Serializer>(bytes: &[u8], s: S) -> Result<S::Ok, S::Error> {
58        s.serialize_str(&hex::encode(bytes))
59    }
60
61    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
62        let s = String::deserialize(d)?;
63        hex::decode(&s).map_err(serde::de::Error::custom)
64    }
65}
66
67/// Serde helper: serialize `Option<Vec<u8>>` as an optional lowercase hex string.
68mod hex_bytes_opt {
69    use serde::{Deserialize, Deserializer, Serializer};
70
71    pub fn serialize<S: Serializer>(bytes: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error> {
72        match bytes {
73            Some(b) => s.serialize_some(&hex::encode(b)),
74            None => s.serialize_none(),
75        }
76    }
77
78    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<u8>>, D::Error> {
79        let opt: Option<String> = Option::deserialize(d)?;
80        opt.map(|s| hex::decode(&s).map_err(serde::de::Error::custom))
81            .transpose()
82    }
83}
84
85/// Per-fabric state in the persisted JSON.
86#[derive(serde::Serialize, serde::Deserialize)]
87pub struct PersistedFabricState {
88    pub fabric_index: u8,
89    #[serde(with = "hex_bytes")]
90    pub trusted_root_cert: Vec<u8>,
91    #[serde(with = "hex_bytes")]
92    pub noc: Vec<u8>,
93    #[serde(with = "hex_bytes_opt")]
94    pub icac: Option<Vec<u8>>,
95    #[serde(with = "hex_bytes")]
96    pub ipk: Vec<u8>,
97    pub controller_id: u64,
98    pub vendor_id: u16,
99    #[serde(with = "hex_bytes")]
100    pub device_matter_cert: Vec<u8>,
101    pub label: String,
102}
103
104/// Full commissioned state serialized to disk.
105#[derive(serde::Serialize, serde::Deserialize)]
106pub struct PersistedDeviceState {
107    pub operational_key_hex: String,
108    pub next_fabric_index: u8,
109    pub fabrics: Vec<PersistedFabricState>,
110    pub attribute_overrides: Vec<AttributeOverride>,
111}
112
113/// A single attribute value snapshot, encoded as hex TLV.
114#[derive(serde::Serialize, serde::Deserialize)]
115pub struct AttributeOverride {
116    pub endpoint: u16,
117    pub cluster: u32,
118    pub attribute: u32,
119    pub tlv_hex: String,
120}
121
122pub(crate) struct PaseState {
123    pub(crate) engine: spake2p::Engine,
124    pub(crate) verifier: spake2p::Verifier,
125    #[allow(dead_code)]
126    pub(crate) exchange_id: u16,
127    pub(crate) pbkdf_req_payload: Vec<u8>,
128    pub(crate) pbkdf_resp_payload: Vec<u8>,
129    pub(crate) responder_session_id: u16,
130    pub(crate) initiator_session_id: u16,
131}
132
133pub(crate) struct CaseState {
134    pub(crate) sigma2_ctx: sigma::Sigma2ResponseCtx,
135    #[allow(dead_code)]
136    pub(crate) exchange_id: u16,
137    /// Which fabric is being established in this CASE exchange.
138    pub(crate) fabric_index: u8,
139}
140
141/// Tracks which attributes a subscriber is watching.
142#[derive(Clone)]
143pub(crate) enum SubscribedPaths {
144    /// Wildcard subscribe (no AttributeRequests in the subscribe message).
145    All,
146    /// Resolved concrete (endpoint, cluster, attribute) keys.
147    Specific(Vec<(u16, u32, u32)>),
148}
149
150pub(crate) struct SubscribeState {
151    pub(crate) exchange_id: u16,
152    pub(crate) subscription_id: u32,
153    pub(crate) paths: SubscribedPaths,
154    pub(crate) max_interval_secs: u16,
155}
156
157pub(crate) struct ActiveSubscription {
158    pub(crate) subscription_id: u32,
159    pub(crate) session_id: u16,
160    pub(crate) peer_addr: std::net::SocketAddr,
161    pub(crate) max_interval_secs: u16,
162    pub(crate) paths: SubscribedPaths,
163}
164
165pub(crate) struct PendingChunkState {
166    pub(crate) exchange_id: u16,
167    /// Reports not yet sent; drained from the front as chunks are dispatched.
168    pub(crate) remaining: Vec<crate::device_messages::AttrReport>,
169    pub(crate) subscription_id: Option<u32>,
170}
171
172pub(crate) struct FabricInfo {
173    /// 1-based fabric index assigned at AddNOC time.
174    pub(crate) fabric_index: u8,
175    pub(crate) ipk: Vec<u8>,
176    pub(crate) fabric: Option<fabric::Fabric>,
177    pub(crate) device_matter_cert: Vec<u8>,
178    pub(crate) controller_id: u64,
179    pub(crate) vendor_id: u16,
180    /// Root certificate (TLV-encoded Matter cert)
181    pub(crate) trusted_root_cert: Vec<u8>,
182    /// Node Operational Certificate (TLV-encoded)
183    pub(crate) noc: Vec<u8>,
184    /// Intermediate CA certificate, if provided.
185    pub(crate) icac: Option<Vec<u8>>,
186    pub(crate) label: String,
187}
188
189impl FabricInfo {
190    /// Extract the CA public key (uncompressed SEC1) from the trusted root cert TLV.
191    pub(crate) fn ca_public_key(&self) -> Result<Vec<u8>> {
192        let decoded = tlv::decode_tlv(&self.trusted_root_cert)?;
193        let pubkey = decoded
194            .get_octet_string(&[9])
195            .context("CA cert: public key (tag 9) missing")?;
196        Ok(pubkey.to_vec())
197    }
198
199    fn noc_field(&self, tag_path: &[u8], field_name: &str) -> Result<u64> {
200        let decoded = tlv::decode_tlv(&self.noc)?;
201        decoded
202            .get_int(tag_path)
203            .with_context(|| format!("NOC: {} missing from subject", field_name))
204    }
205
206    pub(crate) fn fabric_id(&self) -> Result<u64> {
207        self.noc_field(&[6u8, 21], "fabric_id")
208    }
209
210    pub(crate) fn device_node_id(&self) -> Result<u64> {
211        self.noc_field(&[6u8, 17], "node_id")
212    }
213
214    pub(crate) fn ca_id(&self) -> Result<u64> {
215        let decoded = tlv::decode_tlv(&self.trusted_root_cert)?;
216        decoded
217            .get_int(&[6, 20])
218            .context("CA cert: ca_id missing from subject")
219    }
220}