matc/clusters/codec/
operational_credential_cluster.rs

1//! Matter TLV encoders and decoders for Operational Credentials Cluster
2//! Cluster ID: 0x003E
3//!
4//! This file is automatically generated from OperationalCredentialCluster.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Import serialization helpers for octet strings
12use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
13
14// Enum definitions
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[repr(u8)]
18pub enum CertificateChainType {
19    /// Request the DER-encoded DAC certificate
20    Daccertificate = 1,
21    /// Request the DER-encoded PAI certificate
22    Paicertificate = 2,
23}
24
25impl CertificateChainType {
26    /// Convert from u8 value
27    pub fn from_u8(value: u8) -> Option<Self> {
28        match value {
29            1 => Some(CertificateChainType::Daccertificate),
30            2 => Some(CertificateChainType::Paicertificate),
31            _ => None,
32        }
33    }
34
35    /// Convert to u8 value
36    pub fn to_u8(self) -> u8 {
37        self as u8
38    }
39}
40
41impl From<CertificateChainType> for u8 {
42    fn from(val: CertificateChainType) -> Self {
43        val as u8
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
48#[repr(u8)]
49pub enum NodeOperationalCertStatus {
50    /// OK, no error
51    Ok = 0,
52    /// Public Key in the NOC does not match the public key in the NOCSR
53    Invalidpublickey = 1,
54    /// The Node Operational ID in the NOC is not formatted correctly.
55    Invalidnodeopid = 2,
56    /// Any other validation error in NOC chain
57    Invalidnoc = 3,
58    /// No record of prior CSR for which this NOC could match
59    Missingcsr = 4,
60    /// NOCs table full, cannot add another one
61    Tablefull = 5,
62    /// Invalid CaseAdminSubject field for an AddNOC command.
63    Invalidadminsubject = 6,
64    /// Reserved for future use
65    Reservedforfutureuse = 7,
66    /// Reserved for future use
67    Reservedforfutureuse8 = 8,
68    /// Trying to AddNOC instead of UpdateNOC against an existing Fabric.
69    Fabricconflict = 9,
70    /// Label already exists on another Fabric.
71    Labelconflict = 10,
72    /// FabricIndex argument is invalid.
73    Invalidfabricindex = 11,
74}
75
76impl NodeOperationalCertStatus {
77    /// Convert from u8 value
78    pub fn from_u8(value: u8) -> Option<Self> {
79        match value {
80            0 => Some(NodeOperationalCertStatus::Ok),
81            1 => Some(NodeOperationalCertStatus::Invalidpublickey),
82            2 => Some(NodeOperationalCertStatus::Invalidnodeopid),
83            3 => Some(NodeOperationalCertStatus::Invalidnoc),
84            4 => Some(NodeOperationalCertStatus::Missingcsr),
85            5 => Some(NodeOperationalCertStatus::Tablefull),
86            6 => Some(NodeOperationalCertStatus::Invalidadminsubject),
87            7 => Some(NodeOperationalCertStatus::Reservedforfutureuse),
88            8 => Some(NodeOperationalCertStatus::Reservedforfutureuse8),
89            9 => Some(NodeOperationalCertStatus::Fabricconflict),
90            10 => Some(NodeOperationalCertStatus::Labelconflict),
91            11 => Some(NodeOperationalCertStatus::Invalidfabricindex),
92            _ => None,
93        }
94    }
95
96    /// Convert to u8 value
97    pub fn to_u8(self) -> u8 {
98        self as u8
99    }
100}
101
102impl From<NodeOperationalCertStatus> for u8 {
103    fn from(val: NodeOperationalCertStatus) -> Self {
104        val as u8
105    }
106}
107
108// Struct definitions
109
110#[derive(Debug, serde::Serialize)]
111pub struct FabricDescriptor {
112    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
113    pub root_public_key: Option<Vec<u8>>,
114    pub vendor_id: Option<u16>,
115    pub fabric_id: Option<u8>,
116    pub node_id: Option<u64>,
117    pub label: Option<String>,
118    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
119    pub vid_verification_statement: Option<Vec<u8>>,
120}
121
122#[derive(Debug, serde::Serialize)]
123pub struct NOC {
124    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
125    pub noc: Option<Vec<u8>>,
126    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
127    pub icac: Option<Vec<u8>>,
128    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
129    pub vvsc: Option<Vec<u8>>,
130}
131
132// Command encoders
133
134/// Encode AttestationRequest command (0x00)
135pub fn encode_attestation_request(attestation_nonce: Vec<u8>) -> anyhow::Result<Vec<u8>> {
136    let tlv = tlv::TlvItemEnc {
137        tag: 0,
138        value: tlv::TlvItemValueEnc::StructInvisible(vec![
139        (0, tlv::TlvItemValueEnc::OctetString(attestation_nonce)).into(),
140        ]),
141    };
142    Ok(tlv.encode()?)
143}
144
145/// Encode CertificateChainRequest command (0x02)
146pub fn encode_certificate_chain_request(certificate_type: CertificateChainType) -> anyhow::Result<Vec<u8>> {
147    let tlv = tlv::TlvItemEnc {
148        tag: 0,
149        value: tlv::TlvItemValueEnc::StructInvisible(vec![
150        (0, tlv::TlvItemValueEnc::UInt8(certificate_type.to_u8())).into(),
151        ]),
152    };
153    Ok(tlv.encode()?)
154}
155
156/// Encode CSRRequest command (0x04)
157pub fn encode_csr_request(csr_nonce: Vec<u8>, is_for_update_noc: bool) -> anyhow::Result<Vec<u8>> {
158    let tlv = tlv::TlvItemEnc {
159        tag: 0,
160        value: tlv::TlvItemValueEnc::StructInvisible(vec![
161        (0, tlv::TlvItemValueEnc::OctetString(csr_nonce)).into(),
162        (1, tlv::TlvItemValueEnc::Bool(is_for_update_noc)).into(),
163        ]),
164    };
165    Ok(tlv.encode()?)
166}
167
168/// Encode AddNOC command (0x06)
169pub fn encode_add_noc(noc_value: Vec<u8>, icac_value: Vec<u8>, ipk_value: Vec<u8>, case_admin_subject: u64, admin_vendor_id: u16) -> anyhow::Result<Vec<u8>> {
170    let tlv = tlv::TlvItemEnc {
171        tag: 0,
172        value: tlv::TlvItemValueEnc::StructInvisible(vec![
173        (0, tlv::TlvItemValueEnc::OctetString(noc_value)).into(),
174        (1, tlv::TlvItemValueEnc::OctetString(icac_value)).into(),
175        (2, tlv::TlvItemValueEnc::OctetString(ipk_value)).into(),
176        (3, tlv::TlvItemValueEnc::UInt64(case_admin_subject)).into(),
177        (4, tlv::TlvItemValueEnc::UInt16(admin_vendor_id)).into(),
178        ]),
179    };
180    Ok(tlv.encode()?)
181}
182
183/// Encode UpdateNOC command (0x07)
184pub fn encode_update_noc(noc_value: Vec<u8>, icac_value: Vec<u8>) -> anyhow::Result<Vec<u8>> {
185    let tlv = tlv::TlvItemEnc {
186        tag: 0,
187        value: tlv::TlvItemValueEnc::StructInvisible(vec![
188        (0, tlv::TlvItemValueEnc::OctetString(noc_value)).into(),
189        (1, tlv::TlvItemValueEnc::OctetString(icac_value)).into(),
190        ]),
191    };
192    Ok(tlv.encode()?)
193}
194
195/// Encode UpdateFabricLabel command (0x09)
196pub fn encode_update_fabric_label(label: String) -> anyhow::Result<Vec<u8>> {
197    let tlv = tlv::TlvItemEnc {
198        tag: 0,
199        value: tlv::TlvItemValueEnc::StructInvisible(vec![
200        (0, tlv::TlvItemValueEnc::String(label)).into(),
201        ]),
202    };
203    Ok(tlv.encode()?)
204}
205
206/// Encode RemoveFabric command (0x0A)
207pub fn encode_remove_fabric(fabric_index: u8) -> anyhow::Result<Vec<u8>> {
208    let tlv = tlv::TlvItemEnc {
209        tag: 0,
210        value: tlv::TlvItemValueEnc::StructInvisible(vec![
211        (0, tlv::TlvItemValueEnc::UInt8(fabric_index)).into(),
212        ]),
213    };
214    Ok(tlv.encode()?)
215}
216
217/// Encode AddTrustedRootCertificate command (0x0B)
218pub fn encode_add_trusted_root_certificate(root_ca_certificate: Vec<u8>) -> anyhow::Result<Vec<u8>> {
219    let tlv = tlv::TlvItemEnc {
220        tag: 0,
221        value: tlv::TlvItemValueEnc::StructInvisible(vec![
222        (0, tlv::TlvItemValueEnc::OctetString(root_ca_certificate)).into(),
223        ]),
224    };
225    Ok(tlv.encode()?)
226}
227
228/// Encode SetVIDVerificationStatement command (0x0C)
229pub fn encode_set_vid_verification_statement(vendor_id: u16, vid_verification_statement: Vec<u8>, vvsc: Vec<u8>) -> anyhow::Result<Vec<u8>> {
230    let tlv = tlv::TlvItemEnc {
231        tag: 0,
232        value: tlv::TlvItemValueEnc::StructInvisible(vec![
233        (0, tlv::TlvItemValueEnc::UInt16(vendor_id)).into(),
234        (1, tlv::TlvItemValueEnc::OctetString(vid_verification_statement)).into(),
235        (2, tlv::TlvItemValueEnc::OctetString(vvsc)).into(),
236        ]),
237    };
238    Ok(tlv.encode()?)
239}
240
241/// Encode SignVIDVerificationRequest command (0x0D)
242pub fn encode_sign_vid_verification_request(fabric_index: u8, client_challenge: Vec<u8>) -> anyhow::Result<Vec<u8>> {
243    let tlv = tlv::TlvItemEnc {
244        tag: 0,
245        value: tlv::TlvItemValueEnc::StructInvisible(vec![
246        (0, tlv::TlvItemValueEnc::UInt8(fabric_index)).into(),
247        (1, tlv::TlvItemValueEnc::OctetString(client_challenge)).into(),
248        ]),
249    };
250    Ok(tlv.encode()?)
251}
252
253// Attribute decoders
254
255/// Decode NOCs attribute (0x0000)
256pub fn decode_no_cs(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<NOC>> {
257    let mut res = Vec::new();
258    if let tlv::TlvItemValue::List(v) = inp {
259        for item in v {
260            res.push(NOC {
261                noc: item.get_octet_string_owned(&[1]),
262                icac: item.get_octet_string_owned(&[2]),
263                vvsc: item.get_octet_string_owned(&[3]),
264            });
265        }
266    }
267    Ok(res)
268}
269
270/// Decode Fabrics attribute (0x0001)
271pub fn decode_fabrics(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<FabricDescriptor>> {
272    let mut res = Vec::new();
273    if let tlv::TlvItemValue::List(v) = inp {
274        for item in v {
275            res.push(FabricDescriptor {
276                root_public_key: item.get_octet_string_owned(&[1]),
277                vendor_id: item.get_int(&[2]).map(|v| v as u16),
278                fabric_id: item.get_int(&[3]).map(|v| v as u8),
279                node_id: item.get_int(&[4]),
280                label: item.get_string_owned(&[5]),
281                vid_verification_statement: item.get_octet_string_owned(&[6]),
282            });
283        }
284    }
285    Ok(res)
286}
287
288/// Decode SupportedFabrics attribute (0x0002)
289pub fn decode_supported_fabrics(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
290    if let tlv::TlvItemValue::Int(v) = inp {
291        Ok(*v as u8)
292    } else {
293        Err(anyhow::anyhow!("Expected UInt8"))
294    }
295}
296
297/// Decode CommissionedFabrics attribute (0x0003)
298pub fn decode_commissioned_fabrics(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
299    if let tlv::TlvItemValue::Int(v) = inp {
300        Ok(*v as u8)
301    } else {
302        Err(anyhow::anyhow!("Expected UInt8"))
303    }
304}
305
306/// Decode TrustedRootCertificates attribute (0x0004)
307pub fn decode_trusted_root_certificates(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Vec<u8>>> {
308    let mut res = Vec::new();
309    if let tlv::TlvItemValue::List(v) = inp {
310        for item in v {
311            if let tlv::TlvItemValue::OctetString(o) = &item.value {
312                res.push(o.clone());
313            }
314        }
315    }
316    Ok(res)
317}
318
319/// Decode CurrentFabricIndex attribute (0x0005)
320pub fn decode_current_fabric_index(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
321    if let tlv::TlvItemValue::Int(v) = inp {
322        Ok(*v as u8)
323    } else {
324        Err(anyhow::anyhow!("Expected UInt8"))
325    }
326}
327
328
329// JSON dispatcher function
330
331/// Decode attribute value and return as JSON string
332///
333/// # Parameters
334/// * `cluster_id` - The cluster identifier
335/// * `attribute_id` - The attribute identifier
336/// * `tlv_value` - The TLV value to decode
337///
338/// # Returns
339/// JSON string representation of the decoded value or error
340pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
341    // Verify this is the correct cluster
342    if cluster_id != 0x003E {
343        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x003E, got {}\"}}", cluster_id);
344    }
345
346    match attribute_id {
347        0x0000 => {
348            match decode_no_cs(tlv_value) {
349                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
350                Err(e) => format!("{{\"error\": \"{}\"}}", e),
351            }
352        }
353        0x0001 => {
354            match decode_fabrics(tlv_value) {
355                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
356                Err(e) => format!("{{\"error\": \"{}\"}}", e),
357            }
358        }
359        0x0002 => {
360            match decode_supported_fabrics(tlv_value) {
361                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
362                Err(e) => format!("{{\"error\": \"{}\"}}", e),
363            }
364        }
365        0x0003 => {
366            match decode_commissioned_fabrics(tlv_value) {
367                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
368                Err(e) => format!("{{\"error\": \"{}\"}}", e),
369            }
370        }
371        0x0004 => {
372            match decode_trusted_root_certificates(tlv_value) {
373                Ok(value) => {
374                    // Serialize Vec<Vec<u8>> as array of hex strings
375                    let hex_array: Vec<String> = value.iter()
376                        .map(|bytes| bytes.iter()
377                            .map(|byte| format!("{:02x}", byte))
378                            .collect::<String>())
379                        .collect();
380                    serde_json::to_string(&hex_array).unwrap_or_else(|_| "null".to_string())
381                },
382                Err(e) => format!("{{\"error\": \"{}\"}}", e),
383            }
384        }
385        0x0005 => {
386            match decode_current_fabric_index(tlv_value) {
387                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
388                Err(e) => format!("{{\"error\": \"{}\"}}", e),
389            }
390        }
391        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
392    }
393}
394
395/// Get list of all attributes supported by this cluster
396///
397/// # Returns
398/// Vector of tuples containing (attribute_id, attribute_name)
399pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
400    vec![
401        (0x0000, "NOCs"),
402        (0x0001, "Fabrics"),
403        (0x0002, "SupportedFabrics"),
404        (0x0003, "CommissionedFabrics"),
405        (0x0004, "TrustedRootCertificates"),
406        (0x0005, "CurrentFabricIndex"),
407    ]
408}
409
410#[derive(Debug, serde::Serialize)]
411pub struct AttestationResponse {
412    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
413    pub attestation_elements: Option<Vec<u8>>,
414    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
415    pub attestation_signature: Option<Vec<u8>>,
416}
417
418#[derive(Debug, serde::Serialize)]
419pub struct CertificateChainResponse {
420    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
421    pub certificate: Option<Vec<u8>>,
422}
423
424#[derive(Debug, serde::Serialize)]
425pub struct CSRResponse {
426    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
427    pub nocsr_elements: Option<Vec<u8>>,
428    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
429    pub attestation_signature: Option<Vec<u8>>,
430}
431
432#[derive(Debug, serde::Serialize)]
433pub struct NOCResponse {
434    pub status_code: Option<NodeOperationalCertStatus>,
435    pub fabric_index: Option<u8>,
436    pub debug_text: Option<String>,
437}
438
439#[derive(Debug, serde::Serialize)]
440pub struct SignVIDVerificationResponse {
441    pub fabric_index: Option<u8>,
442    pub fabric_binding_version: Option<u8>,
443    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
444    pub signature: Option<Vec<u8>>,
445}
446
447// Command response decoders
448
449/// Decode AttestationResponse command response (01)
450pub fn decode_attestation_response(inp: &tlv::TlvItemValue) -> anyhow::Result<AttestationResponse> {
451    if let tlv::TlvItemValue::List(_fields) = inp {
452        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
453        Ok(AttestationResponse {
454                attestation_elements: item.get_octet_string_owned(&[0]),
455                attestation_signature: item.get_octet_string_owned(&[1]),
456        })
457    } else {
458        Err(anyhow::anyhow!("Expected struct fields"))
459    }
460}
461
462/// Decode CertificateChainResponse command response (03)
463pub fn decode_certificate_chain_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CertificateChainResponse> {
464    if let tlv::TlvItemValue::List(_fields) = inp {
465        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
466        Ok(CertificateChainResponse {
467                certificate: item.get_octet_string_owned(&[0]),
468        })
469    } else {
470        Err(anyhow::anyhow!("Expected struct fields"))
471    }
472}
473
474/// Decode CSRResponse command response (05)
475pub fn decode_csr_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CSRResponse> {
476    if let tlv::TlvItemValue::List(_fields) = inp {
477        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
478        Ok(CSRResponse {
479                nocsr_elements: item.get_octet_string_owned(&[0]),
480                attestation_signature: item.get_octet_string_owned(&[1]),
481        })
482    } else {
483        Err(anyhow::anyhow!("Expected struct fields"))
484    }
485}
486
487/// Decode NOCResponse command response (08)
488pub fn decode_noc_response(inp: &tlv::TlvItemValue) -> anyhow::Result<NOCResponse> {
489    if let tlv::TlvItemValue::List(_fields) = inp {
490        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
491        Ok(NOCResponse {
492                status_code: item.get_int(&[0]).and_then(|v| NodeOperationalCertStatus::from_u8(v as u8)),
493                fabric_index: item.get_int(&[1]).map(|v| v as u8),
494                debug_text: item.get_string_owned(&[2]),
495        })
496    } else {
497        Err(anyhow::anyhow!("Expected struct fields"))
498    }
499}
500
501/// Decode SignVIDVerificationResponse command response (0E)
502pub fn decode_sign_vid_verification_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SignVIDVerificationResponse> {
503    if let tlv::TlvItemValue::List(_fields) = inp {
504        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
505        Ok(SignVIDVerificationResponse {
506                fabric_index: item.get_int(&[0]).map(|v| v as u8),
507                fabric_binding_version: item.get_int(&[1]).map(|v| v as u8),
508                signature: item.get_octet_string_owned(&[2]),
509        })
510    } else {
511        Err(anyhow::anyhow!("Expected struct fields"))
512    }
513}
514