matc/clusters/codec/
tls_client_management.rs

1//! Matter TLV encoders and decoders for TLS Client Management Cluster
2//! Cluster ID: 0x0802
3//!
4//! This file is automatically generated from TLSClientManagement.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Import serialization helpers for octet strings
14use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16// Enum definitions
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum StatusCode {
21    /// The endpoint is already installed.
22    Endpointalreadyinstalled = 2,
23    /// No root certificate exists for this CAID.
24    Rootcertificatenotfound = 3,
25    /// No client certificate exists for this CCDID.
26    Clientcertificatenotfound = 4,
27    /// The endpoint is in use and cannot be removed.
28    Endpointinuse = 5,
29    /// Time sync has not yet occurred.
30    Invalidtime = 6,
31}
32
33impl StatusCode {
34    /// Convert from u8 value
35    pub fn from_u8(value: u8) -> Option<Self> {
36        match value {
37            2 => Some(StatusCode::Endpointalreadyinstalled),
38            3 => Some(StatusCode::Rootcertificatenotfound),
39            4 => Some(StatusCode::Clientcertificatenotfound),
40            5 => Some(StatusCode::Endpointinuse),
41            6 => Some(StatusCode::Invalidtime),
42            _ => None,
43        }
44    }
45
46    /// Convert to u8 value
47    pub fn to_u8(self) -> u8 {
48        self as u8
49    }
50}
51
52impl From<StatusCode> for u8 {
53    fn from(val: StatusCode) -> Self {
54        val as u8
55    }
56}
57
58// Struct definitions
59
60#[derive(Debug, serde::Serialize)]
61pub struct TLSEndpoint {
62    pub endpoint_id: Option<u8>,
63    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
64    pub hostname: Option<Vec<u8>>,
65    pub port: Option<u16>,
66    pub caid: Option<u8>,
67    pub ccdid: Option<u8>,
68    pub reference_count: Option<u8>,
69}
70
71// Command encoders
72
73/// Encode ProvisionEndpoint command (0x00)
74pub fn encode_provision_endpoint(hostname: Vec<u8>, port: u16, caid: u8, ccdid: Option<u8>, endpoint_id: Option<u8>) -> anyhow::Result<Vec<u8>> {
75    let tlv = tlv::TlvItemEnc {
76        tag: 0,
77        value: tlv::TlvItemValueEnc::StructInvisible(vec![
78        (0, tlv::TlvItemValueEnc::OctetString(hostname)).into(),
79        (1, tlv::TlvItemValueEnc::UInt16(port)).into(),
80        (2, tlv::TlvItemValueEnc::UInt8(caid)).into(),
81        (3, tlv::TlvItemValueEnc::UInt8(ccdid.unwrap_or(0))).into(),
82        (4, tlv::TlvItemValueEnc::UInt8(endpoint_id.unwrap_or(0))).into(),
83        ]),
84    };
85    Ok(tlv.encode()?)
86}
87
88/// Encode FindEndpoint command (0x02)
89pub fn encode_find_endpoint(endpoint_id: u8) -> anyhow::Result<Vec<u8>> {
90    let tlv = tlv::TlvItemEnc {
91        tag: 0,
92        value: tlv::TlvItemValueEnc::StructInvisible(vec![
93        (0, tlv::TlvItemValueEnc::UInt8(endpoint_id)).into(),
94        ]),
95    };
96    Ok(tlv.encode()?)
97}
98
99/// Encode RemoveEndpoint command (0x04)
100pub fn encode_remove_endpoint(endpoint_id: u8) -> anyhow::Result<Vec<u8>> {
101    let tlv = tlv::TlvItemEnc {
102        tag: 0,
103        value: tlv::TlvItemValueEnc::StructInvisible(vec![
104        (0, tlv::TlvItemValueEnc::UInt8(endpoint_id)).into(),
105        ]),
106    };
107    Ok(tlv.encode()?)
108}
109
110// Attribute decoders
111
112/// Decode MaxProvisioned attribute (0x0000)
113pub fn decode_max_provisioned(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
114    if let tlv::TlvItemValue::Int(v) = inp {
115        Ok(*v as u8)
116    } else {
117        Err(anyhow::anyhow!("Expected UInt8"))
118    }
119}
120
121/// Decode ProvisionedEndpoints attribute (0x0001)
122pub fn decode_provisioned_endpoints(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TLSEndpoint>> {
123    let mut res = Vec::new();
124    if let tlv::TlvItemValue::List(v) = inp {
125        for item in v {
126            res.push(TLSEndpoint {
127                endpoint_id: item.get_int(&[0]).map(|v| v as u8),
128                hostname: item.get_octet_string_owned(&[1]),
129                port: item.get_int(&[2]).map(|v| v as u16),
130                caid: item.get_int(&[3]).map(|v| v as u8),
131                ccdid: item.get_int(&[4]).map(|v| v as u8),
132                reference_count: item.get_int(&[5]).map(|v| v as u8),
133            });
134        }
135    }
136    Ok(res)
137}
138
139
140// JSON dispatcher function
141
142/// Decode attribute value and return as JSON string
143///
144/// # Parameters
145/// * `cluster_id` - The cluster identifier
146/// * `attribute_id` - The attribute identifier
147/// * `tlv_value` - The TLV value to decode
148///
149/// # Returns
150/// JSON string representation of the decoded value or error
151pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
152    // Verify this is the correct cluster
153    if cluster_id != 0x0802 {
154        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0802, got {}\"}}", cluster_id);
155    }
156
157    match attribute_id {
158        0x0000 => {
159            match decode_max_provisioned(tlv_value) {
160                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
161                Err(e) => format!("{{\"error\": \"{}\"}}", e),
162            }
163        }
164        0x0001 => {
165            match decode_provisioned_endpoints(tlv_value) {
166                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
167                Err(e) => format!("{{\"error\": \"{}\"}}", e),
168            }
169        }
170        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
171    }
172}
173
174/// Get list of all attributes supported by this cluster
175///
176/// # Returns
177/// Vector of tuples containing (attribute_id, attribute_name)
178pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
179    vec![
180        (0x0000, "MaxProvisioned"),
181        (0x0001, "ProvisionedEndpoints"),
182    ]
183}
184
185// Command listing
186
187pub fn get_command_list() -> Vec<(u32, &'static str)> {
188    vec![
189        (0x00, "ProvisionEndpoint"),
190        (0x02, "FindEndpoint"),
191        (0x04, "RemoveEndpoint"),
192    ]
193}
194
195pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
196    match cmd_id {
197        0x00 => Some("ProvisionEndpoint"),
198        0x02 => Some("FindEndpoint"),
199        0x04 => Some("RemoveEndpoint"),
200        _ => None,
201    }
202}
203
204pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
205    match cmd_id {
206        0x00 => Some(vec![
207            crate::clusters::codec::CommandField { tag: 0, name: "hostname", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
208            crate::clusters::codec::CommandField { tag: 1, name: "port", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
209            crate::clusters::codec::CommandField { tag: 2, name: "caid", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
210            crate::clusters::codec::CommandField { tag: 3, name: "ccdid", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: true },
211            crate::clusters::codec::CommandField { tag: 4, name: "endpoint_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: true },
212        ]),
213        0x02 => Some(vec![
214            crate::clusters::codec::CommandField { tag: 0, name: "endpoint_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
215        ]),
216        0x04 => Some(vec![
217            crate::clusters::codec::CommandField { tag: 0, name: "endpoint_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
218        ]),
219        _ => None,
220    }
221}
222
223pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
224    match cmd_id {
225        0x00 => {
226        let hostname = crate::clusters::codec::json_util::get_octstr(args, "hostname")?;
227        let port = crate::clusters::codec::json_util::get_u16(args, "port")?;
228        let caid = crate::clusters::codec::json_util::get_u8(args, "caid")?;
229        let ccdid = crate::clusters::codec::json_util::get_opt_u8(args, "ccdid")?;
230        let endpoint_id = crate::clusters::codec::json_util::get_opt_u8(args, "endpoint_id")?;
231        encode_provision_endpoint(hostname, port, caid, ccdid, endpoint_id)
232        }
233        0x02 => {
234        let endpoint_id = crate::clusters::codec::json_util::get_u8(args, "endpoint_id")?;
235        encode_find_endpoint(endpoint_id)
236        }
237        0x04 => {
238        let endpoint_id = crate::clusters::codec::json_util::get_u8(args, "endpoint_id")?;
239        encode_remove_endpoint(endpoint_id)
240        }
241        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
242    }
243}
244
245#[derive(Debug, serde::Serialize)]
246pub struct ProvisionEndpointResponse {
247    pub endpoint_id: Option<u8>,
248}
249
250#[derive(Debug, serde::Serialize)]
251pub struct FindEndpointResponse {
252    pub endpoint: Option<TLSEndpoint>,
253}
254
255// Command response decoders
256
257/// Decode ProvisionEndpointResponse command response (01)
258pub fn decode_provision_endpoint_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ProvisionEndpointResponse> {
259    if let tlv::TlvItemValue::List(_fields) = inp {
260        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
261        Ok(ProvisionEndpointResponse {
262                endpoint_id: item.get_int(&[0]).map(|v| v as u8),
263        })
264    } else {
265        Err(anyhow::anyhow!("Expected struct fields"))
266    }
267}
268
269/// Decode FindEndpointResponse command response (03)
270pub fn decode_find_endpoint_response(inp: &tlv::TlvItemValue) -> anyhow::Result<FindEndpointResponse> {
271    if let tlv::TlvItemValue::List(_fields) = inp {
272        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
273        Ok(FindEndpointResponse {
274                endpoint: {
275                    if let Some(nested_tlv) = item.get(&[0]) {
276                        if let tlv::TlvItemValue::List(_) = nested_tlv {
277                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
278                            Some(TLSEndpoint {
279                endpoint_id: nested_item.get_int(&[0]).map(|v| v as u8),
280                hostname: nested_item.get_octet_string_owned(&[1]),
281                port: nested_item.get_int(&[2]).map(|v| v as u16),
282                caid: nested_item.get_int(&[3]).map(|v| v as u8),
283                ccdid: nested_item.get_int(&[4]).map(|v| v as u8),
284                reference_count: nested_item.get_int(&[5]).map(|v| v as u8),
285                            })
286                        } else {
287                            None
288                        }
289                    } else {
290                        None
291                    }
292                },
293        })
294    } else {
295        Err(anyhow::anyhow!("Expected struct fields"))
296    }
297}
298
299// Typed facade (invokes + reads)
300
301/// Invoke `ProvisionEndpoint` command on cluster `TLS Client Management`.
302pub async fn provision_endpoint(conn: &crate::controller::Connection, endpoint: u16, hostname: Vec<u8>, port: u16, caid: u8, ccdid: Option<u8>, endpoint_id: Option<u8>) -> anyhow::Result<ProvisionEndpointResponse> {
303    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TLS_CLIENT_MANAGEMENT, crate::clusters::defs::CLUSTER_TLS_CLIENT_MANAGEMENT_CMD_ID_PROVISIONENDPOINT, &encode_provision_endpoint(hostname, port, caid, ccdid, endpoint_id)?).await?;
304    decode_provision_endpoint_response(&tlv)
305}
306
307/// Invoke `FindEndpoint` command on cluster `TLS Client Management`.
308pub async fn find_endpoint(conn: &crate::controller::Connection, endpoint: u16, endpoint_id: u8) -> anyhow::Result<FindEndpointResponse> {
309    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TLS_CLIENT_MANAGEMENT, crate::clusters::defs::CLUSTER_TLS_CLIENT_MANAGEMENT_CMD_ID_FINDENDPOINT, &encode_find_endpoint(endpoint_id)?).await?;
310    decode_find_endpoint_response(&tlv)
311}
312
313/// Invoke `RemoveEndpoint` command on cluster `TLS Client Management`.
314pub async fn remove_endpoint(conn: &crate::controller::Connection, endpoint: u16, endpoint_id: u8) -> anyhow::Result<()> {
315    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_TLS_CLIENT_MANAGEMENT, crate::clusters::defs::CLUSTER_TLS_CLIENT_MANAGEMENT_CMD_ID_REMOVEENDPOINT, &encode_remove_endpoint(endpoint_id)?).await?;
316    Ok(())
317}
318
319/// Read `MaxProvisioned` attribute from cluster `TLS Client Management`.
320pub async fn read_max_provisioned(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
321    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TLS_CLIENT_MANAGEMENT, crate::clusters::defs::CLUSTER_TLS_CLIENT_MANAGEMENT_ATTR_ID_MAXPROVISIONED).await?;
322    decode_max_provisioned(&tlv)
323}
324
325/// Read `ProvisionedEndpoints` attribute from cluster `TLS Client Management`.
326pub async fn read_provisioned_endpoints(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<TLSEndpoint>> {
327    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_TLS_CLIENT_MANAGEMENT, crate::clusters::defs::CLUSTER_TLS_CLIENT_MANAGEMENT_ATTR_ID_PROVISIONEDENDPOINTS).await?;
328    decode_provisioned_endpoints(&tlv)
329}
330