Skip to main content

matc/clusters/codec/
network_commissioning_cluster.rs

1//! Matter TLV encoders and decoders for Network Commissioning Cluster
2//! Cluster ID: 0x0031
3//!
4//! This file is automatically generated from NetworkCommissioningCluster.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 NetworkCommissioningStatus {
21    /// OK, no error
22    Success = 0,
23    /// Value Outside Range
24    Outofrange = 1,
25    /// A collection would exceed its size limit
26    Boundsexceeded = 2,
27    /// The NetworkID is not among the collection of added networks
28    Networkidnotfound = 3,
29    /// The NetworkID is already among the collection of added networks
30    Duplicatenetworkid = 4,
31    /// Cannot find AP: SSID Not found
32    Networknotfound = 5,
33    /// Cannot find AP: Mismatch on band/channels/regulatory domain / 2.4GHz vs 5GHz
34    Regulatoryerror = 6,
35    /// Cannot associate due to authentication failure
36    Authfailure = 7,
37    /// Cannot associate due to unsupported security mode
38    Unsupportedsecurity = 8,
39    /// Other association failure
40    Otherconnectionfailure = 9,
41    /// Failure to generate an IPv6 address
42    Ipv6failed = 10,
43    /// Failure to bind Wi-Fi +<->+ IP interfaces
44    Ipbindfailed = 11,
45    /// Unknown error
46    Unknownerror = 12,
47}
48
49impl NetworkCommissioningStatus {
50    /// Convert from u8 value
51    pub fn from_u8(value: u8) -> Option<Self> {
52        match value {
53            0 => Some(NetworkCommissioningStatus::Success),
54            1 => Some(NetworkCommissioningStatus::Outofrange),
55            2 => Some(NetworkCommissioningStatus::Boundsexceeded),
56            3 => Some(NetworkCommissioningStatus::Networkidnotfound),
57            4 => Some(NetworkCommissioningStatus::Duplicatenetworkid),
58            5 => Some(NetworkCommissioningStatus::Networknotfound),
59            6 => Some(NetworkCommissioningStatus::Regulatoryerror),
60            7 => Some(NetworkCommissioningStatus::Authfailure),
61            8 => Some(NetworkCommissioningStatus::Unsupportedsecurity),
62            9 => Some(NetworkCommissioningStatus::Otherconnectionfailure),
63            10 => Some(NetworkCommissioningStatus::Ipv6failed),
64            11 => Some(NetworkCommissioningStatus::Ipbindfailed),
65            12 => Some(NetworkCommissioningStatus::Unknownerror),
66            _ => None,
67        }
68    }
69
70    /// Convert to u8 value
71    pub fn to_u8(self) -> u8 {
72        self as u8
73    }
74}
75
76impl From<NetworkCommissioningStatus> for u8 {
77    fn from(val: NetworkCommissioningStatus) -> Self {
78        val as u8
79    }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
83#[repr(u8)]
84pub enum WiFiBand {
85    /// 2.4GHz - 2.401GHz to 2.495GHz (802.11b/g/n/ax)
86    _2g4 = 0,
87    /// 3.65GHz - 3.655GHz to 3.695GHz (802.11y)
88    _3g65 = 1,
89    /// 5GHz - 5.150GHz to 5.895GHz (802.11a/n/ac/ax)
90    _5g = 2,
91    /// 6GHz - 5.925GHz to 7.125GHz (802.11ax / Wi-Fi 6E)
92    _6g = 3,
93    /// 60GHz - 57.24GHz to 70.20GHz (802.11ad/ay)
94    _60g = 4,
95    /// Sub-1GHz - 755MHz to 931MHz (802.11ah)
96    _1g = 5,
97}
98
99impl WiFiBand {
100    /// Convert from u8 value
101    pub fn from_u8(value: u8) -> Option<Self> {
102        match value {
103            0 => Some(WiFiBand::_2g4),
104            1 => Some(WiFiBand::_3g65),
105            2 => Some(WiFiBand::_5g),
106            3 => Some(WiFiBand::_6g),
107            4 => Some(WiFiBand::_60g),
108            5 => Some(WiFiBand::_1g),
109            _ => None,
110        }
111    }
112
113    /// Convert to u8 value
114    pub fn to_u8(self) -> u8 {
115        self as u8
116    }
117}
118
119impl From<WiFiBand> for u8 {
120    fn from(val: WiFiBand) -> Self {
121        val as u8
122    }
123}
124
125// Bitmap definitions
126
127/// ThreadCapabilities bitmap type
128pub type ThreadCapabilities = u8;
129
130/// Constants for ThreadCapabilities
131pub mod threadcapabilities {
132    /// Thread Border Router functionality is present
133    pub const IS_BORDER_ROUTER_CAPABLE: u8 = 0x01;
134    /// Router mode is supported (interface could be in router or REED mode)
135    pub const IS_ROUTER_CAPABLE: u8 = 0x02;
136    /// Sleepy end-device mode is supported
137    pub const IS_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x04;
138    /// Device is a full Thread device (opposite of Minimal Thread Device)
139    pub const IS_FULL_THREAD_DEVICE: u8 = 0x08;
140    /// Synchronized sleepy end-device mode is supported
141    pub const IS_SYNCHRONIZED_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x10;
142}
143
144/// WiFiSecurity bitmap type
145pub type WiFiSecurity = u8;
146
147/// Constants for WiFiSecurity
148pub mod wifisecurity {
149    /// Supports unencrypted Wi-Fi
150    pub const UNENCRYPTED: u8 = 0x01;
151    /// Supports Wi-Fi using WEP security
152    pub const WEP: u8 = 0x02;
153    /// Supports Wi-Fi using WPA-Personal security
154    pub const WPA_PERSONAL: u8 = 0x04;
155    /// Supports Wi-Fi using WPA2-Personal security
156    pub const WPA2_PERSONAL: u8 = 0x08;
157    /// Supports Wi-Fi using WPA3-Personal security
158    pub const WPA3_PERSONAL: u8 = 0x10;
159    /// Supports Wi-Fi using Per-Device Credentials
160    pub const WPA3_MATTER_PDC: u8 = 0x20;
161}
162
163// Struct definitions
164
165#[derive(Debug, serde::Serialize)]
166pub struct NetworkInfo {
167    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
168    pub network_id: Option<Vec<u8>>,
169    pub connected: Option<bool>,
170    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
171    pub network_identifier: Option<Vec<u8>>,
172    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
173    pub client_identifier: Option<Vec<u8>>,
174}
175
176#[derive(Debug, serde::Serialize)]
177pub struct ThreadInterfaceScanResult {
178    pub pan_id: Option<u16>,
179    pub extended_pan_id: Option<u64>,
180    pub network_name: Option<String>,
181    pub channel: Option<u16>,
182    pub version: Option<u8>,
183    pub extended_address: Option<u8>,
184    pub rssi: Option<i8>,
185    pub lqi: Option<u8>,
186}
187
188#[derive(Debug, serde::Serialize)]
189pub struct WiFiInterfaceScanResult {
190    pub security: Option<WiFiSecurity>,
191    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
192    pub ssid: Option<Vec<u8>>,
193    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
194    pub bssid: Option<Vec<u8>>,
195    pub channel: Option<u16>,
196    pub wifi_band: Option<WiFiBand>,
197    pub rssi: Option<i8>,
198}
199
200// Command encoders
201
202/// Encode ScanNetworks command (0x00)
203pub fn encode_scan_networks(ssid: Option<Vec<u8>>, breadcrumb: Option<u64>) -> anyhow::Result<Vec<u8>> {
204    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
205    tlv_fields.push((0, tlv::TlvItemValueEnc::OctetString(ssid.unwrap_or_default())).into());
206    if let Some(x) = breadcrumb { tlv_fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
207    let tlv = tlv::TlvItemEnc {
208        tag: 0,
209        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
210    };
211    Ok(tlv.encode()?)
212}
213
214/// Encode AddOrUpdateWiFiNetwork command (0x02)
215pub fn encode_add_or_update_wifi_network(ssid: Vec<u8>, credentials: Vec<u8>, breadcrumb: Option<u64>, network_identity: Option<Vec<u8>>, client_identifier: Option<Vec<u8>>, possession_nonce: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
216    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
217    tlv_fields.push((0, tlv::TlvItemValueEnc::OctetString(ssid)).into());
218    tlv_fields.push((1, tlv::TlvItemValueEnc::OctetString(credentials)).into());
219    if let Some(x) = breadcrumb { tlv_fields.push((2, tlv::TlvItemValueEnc::UInt64(x)).into()); }
220    if let Some(x) = network_identity { tlv_fields.push((3, tlv::TlvItemValueEnc::OctetString(x)).into()); }
221    if let Some(x) = client_identifier { tlv_fields.push((4, tlv::TlvItemValueEnc::OctetString(x)).into()); }
222    if let Some(x) = possession_nonce { tlv_fields.push((5, tlv::TlvItemValueEnc::OctetString(x)).into()); }
223    let tlv = tlv::TlvItemEnc {
224        tag: 0,
225        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
226    };
227    Ok(tlv.encode()?)
228}
229
230/// Encode AddOrUpdateThreadNetwork command (0x03)
231pub fn encode_add_or_update_thread_network(operational_dataset: Vec<u8>, breadcrumb: Option<u64>) -> anyhow::Result<Vec<u8>> {
232    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
233    tlv_fields.push((0, tlv::TlvItemValueEnc::OctetString(operational_dataset)).into());
234    if let Some(x) = breadcrumb { tlv_fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
235    let tlv = tlv::TlvItemEnc {
236        tag: 0,
237        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
238    };
239    Ok(tlv.encode()?)
240}
241
242/// Encode RemoveNetwork command (0x04)
243pub fn encode_remove_network(network_id: Vec<u8>, breadcrumb: Option<u64>) -> anyhow::Result<Vec<u8>> {
244    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
245    tlv_fields.push((0, tlv::TlvItemValueEnc::OctetString(network_id)).into());
246    if let Some(x) = breadcrumb { tlv_fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
247    let tlv = tlv::TlvItemEnc {
248        tag: 0,
249        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
250    };
251    Ok(tlv.encode()?)
252}
253
254/// Encode ConnectNetwork command (0x06)
255pub fn encode_connect_network(network_id: Vec<u8>, breadcrumb: Option<u64>) -> anyhow::Result<Vec<u8>> {
256    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
257    tlv_fields.push((0, tlv::TlvItemValueEnc::OctetString(network_id)).into());
258    if let Some(x) = breadcrumb { tlv_fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
259    let tlv = tlv::TlvItemEnc {
260        tag: 0,
261        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
262    };
263    Ok(tlv.encode()?)
264}
265
266/// Encode ReorderNetwork command (0x08)
267pub fn encode_reorder_network(network_id: Vec<u8>, network_index: u8, breadcrumb: Option<u64>) -> anyhow::Result<Vec<u8>> {
268    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
269    tlv_fields.push((0, tlv::TlvItemValueEnc::OctetString(network_id)).into());
270    tlv_fields.push((1, tlv::TlvItemValueEnc::UInt8(network_index)).into());
271    if let Some(x) = breadcrumb { tlv_fields.push((2, tlv::TlvItemValueEnc::UInt64(x)).into()); }
272    let tlv = tlv::TlvItemEnc {
273        tag: 0,
274        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
275    };
276    Ok(tlv.encode()?)
277}
278
279/// Encode QueryIdentity command (0x09)
280pub fn encode_query_identity(key_identifier: Vec<u8>, possession_nonce: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
281    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
282    tlv_fields.push((0, tlv::TlvItemValueEnc::OctetString(key_identifier)).into());
283    if let Some(x) = possession_nonce { tlv_fields.push((1, tlv::TlvItemValueEnc::OctetString(x)).into()); }
284    let tlv = tlv::TlvItemEnc {
285        tag: 0,
286        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
287    };
288    Ok(tlv.encode()?)
289}
290
291// Attribute decoders
292
293/// Decode MaxNetworks attribute (0x0000)
294pub fn decode_max_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
295    if let tlv::TlvItemValue::Int(v) = inp {
296        Ok(*v as u8)
297    } else {
298        Err(anyhow::anyhow!("Expected UInt8"))
299    }
300}
301
302/// Decode Networks attribute (0x0001)
303pub fn decode_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<NetworkInfo>> {
304    let mut res = Vec::new();
305    if let tlv::TlvItemValue::List(v) = inp {
306        for item in v {
307            res.push(NetworkInfo {
308                network_id: item.get_octet_string_owned(&[0]),
309                connected: item.get_bool(&[1]),
310                network_identifier: item.get_octet_string_owned(&[2]),
311                client_identifier: item.get_octet_string_owned(&[3]),
312            });
313        }
314    }
315    Ok(res)
316}
317
318/// Decode ScanMaxTimeSeconds attribute (0x0002)
319pub fn decode_scan_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
320    if let tlv::TlvItemValue::Int(v) = inp {
321        Ok(*v as u8)
322    } else {
323        Err(anyhow::anyhow!("Expected UInt8"))
324    }
325}
326
327/// Decode ConnectMaxTimeSeconds attribute (0x0003)
328pub fn decode_connect_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
329    if let tlv::TlvItemValue::Int(v) = inp {
330        Ok(*v as u8)
331    } else {
332        Err(anyhow::anyhow!("Expected UInt8"))
333    }
334}
335
336/// Decode InterfaceEnabled attribute (0x0004)
337pub fn decode_interface_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
338    if let tlv::TlvItemValue::Bool(v) = inp {
339        Ok(*v)
340    } else {
341        Err(anyhow::anyhow!("Expected Bool"))
342    }
343}
344
345/// Decode LastNetworkingStatus attribute (0x0005)
346pub fn decode_last_networking_status(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkCommissioningStatus>> {
347    if let tlv::TlvItemValue::Int(v) = inp {
348        Ok(NetworkCommissioningStatus::from_u8(*v as u8))
349    } else {
350        Ok(None)
351    }
352}
353
354/// Decode LastNetworkID attribute (0x0006)
355pub fn decode_last_network_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
356    if let tlv::TlvItemValue::OctetString(v) = inp {
357        Ok(Some(v.clone()))
358    } else {
359        Ok(None)
360    }
361}
362
363/// Decode LastConnectErrorValue attribute (0x0007)
364pub fn decode_last_connect_error_value(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i32>> {
365    if let tlv::TlvItemValue::Int(v) = inp {
366        Ok(Some(*v as i32))
367    } else {
368        Ok(None)
369    }
370}
371
372/// Decode SupportedWiFiBands attribute (0x0008)
373pub fn decode_supported_wifi_bands(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<WiFiBand>> {
374    let mut res = Vec::new();
375    if let tlv::TlvItemValue::List(v) = inp {
376        for item in v {
377            if let tlv::TlvItemValue::Int(i) = &item.value {
378                if let Some(enum_val) = WiFiBand::from_u8(*i as u8) {
379                    res.push(enum_val);
380                }
381            }
382        }
383    }
384    Ok(res)
385}
386
387/// Decode SupportedThreadFeatures attribute (0x0009)
388pub fn decode_supported_thread_features(inp: &tlv::TlvItemValue) -> anyhow::Result<ThreadCapabilities> {
389    if let tlv::TlvItemValue::Int(v) = inp {
390        Ok(*v as u8)
391    } else {
392        Err(anyhow::anyhow!("Expected Integer"))
393    }
394}
395
396/// Decode ThreadVersion attribute (0x000A)
397pub fn decode_thread_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
398    if let tlv::TlvItemValue::Int(v) = inp {
399        Ok(*v as u16)
400    } else {
401        Err(anyhow::anyhow!("Expected UInt16"))
402    }
403}
404
405
406// JSON dispatcher function
407
408/// Decode attribute value and return as JSON string
409///
410/// # Parameters
411/// * `cluster_id` - The cluster identifier
412/// * `attribute_id` - The attribute identifier
413/// * `tlv_value` - The TLV value to decode
414///
415/// # Returns
416/// JSON string representation of the decoded value or error
417pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
418    // Verify this is the correct cluster
419    if cluster_id != 0x0031 {
420        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0031, got {}\"}}", cluster_id);
421    }
422
423    match attribute_id {
424        0x0000 => {
425            match decode_max_networks(tlv_value) {
426                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
427                Err(e) => format!("{{\"error\": \"{}\"}}", e),
428            }
429        }
430        0x0001 => {
431            match decode_networks(tlv_value) {
432                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
433                Err(e) => format!("{{\"error\": \"{}\"}}", e),
434            }
435        }
436        0x0002 => {
437            match decode_scan_max_time_seconds(tlv_value) {
438                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
439                Err(e) => format!("{{\"error\": \"{}\"}}", e),
440            }
441        }
442        0x0003 => {
443            match decode_connect_max_time_seconds(tlv_value) {
444                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
445                Err(e) => format!("{{\"error\": \"{}\"}}", e),
446            }
447        }
448        0x0004 => {
449            match decode_interface_enabled(tlv_value) {
450                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
451                Err(e) => format!("{{\"error\": \"{}\"}}", e),
452            }
453        }
454        0x0005 => {
455            match decode_last_networking_status(tlv_value) {
456                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
457                Err(e) => format!("{{\"error\": \"{}\"}}", e),
458            }
459        }
460        0x0006 => {
461            match decode_last_network_id(tlv_value) {
462                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
463                Err(e) => format!("{{\"error\": \"{}\"}}", e),
464            }
465        }
466        0x0007 => {
467            match decode_last_connect_error_value(tlv_value) {
468                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
469                Err(e) => format!("{{\"error\": \"{}\"}}", e),
470            }
471        }
472        0x0008 => {
473            match decode_supported_wifi_bands(tlv_value) {
474                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
475                Err(e) => format!("{{\"error\": \"{}\"}}", e),
476            }
477        }
478        0x0009 => {
479            match decode_supported_thread_features(tlv_value) {
480                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
481                Err(e) => format!("{{\"error\": \"{}\"}}", e),
482            }
483        }
484        0x000A => {
485            match decode_thread_version(tlv_value) {
486                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
487                Err(e) => format!("{{\"error\": \"{}\"}}", e),
488            }
489        }
490        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
491    }
492}
493
494/// Get list of all attributes supported by this cluster
495///
496/// # Returns
497/// Vector of tuples containing (attribute_id, attribute_name)
498pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
499    vec![
500        (0x0000, "MaxNetworks"),
501        (0x0001, "Networks"),
502        (0x0002, "ScanMaxTimeSeconds"),
503        (0x0003, "ConnectMaxTimeSeconds"),
504        (0x0004, "InterfaceEnabled"),
505        (0x0005, "LastNetworkingStatus"),
506        (0x0006, "LastNetworkID"),
507        (0x0007, "LastConnectErrorValue"),
508        (0x0008, "SupportedWiFiBands"),
509        (0x0009, "SupportedThreadFeatures"),
510        (0x000A, "ThreadVersion"),
511    ]
512}
513
514// Command listing
515
516pub fn get_command_list() -> Vec<(u32, &'static str)> {
517    vec![
518        (0x00, "ScanNetworks"),
519        (0x02, "AddOrUpdateWiFiNetwork"),
520        (0x03, "AddOrUpdateThreadNetwork"),
521        (0x04, "RemoveNetwork"),
522        (0x06, "ConnectNetwork"),
523        (0x08, "ReorderNetwork"),
524        (0x09, "QueryIdentity"),
525    ]
526}
527
528pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
529    match cmd_id {
530        0x00 => Some("ScanNetworks"),
531        0x02 => Some("AddOrUpdateWiFiNetwork"),
532        0x03 => Some("AddOrUpdateThreadNetwork"),
533        0x04 => Some("RemoveNetwork"),
534        0x06 => Some("ConnectNetwork"),
535        0x08 => Some("ReorderNetwork"),
536        0x09 => Some("QueryIdentity"),
537        _ => None,
538    }
539}
540
541pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
542    match cmd_id {
543        0x00 => Some(vec![
544            crate::clusters::codec::CommandField { tag: 0, name: "ssid", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: true },
545            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
546        ]),
547        0x02 => Some(vec![
548            crate::clusters::codec::CommandField { tag: 0, name: "ssid", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
549            crate::clusters::codec::CommandField { tag: 1, name: "credentials", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
550            crate::clusters::codec::CommandField { tag: 2, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
551            crate::clusters::codec::CommandField { tag: 3, name: "network_identity", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
552            crate::clusters::codec::CommandField { tag: 4, name: "client_identifier", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
553            crate::clusters::codec::CommandField { tag: 5, name: "possession_nonce", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
554        ]),
555        0x03 => Some(vec![
556            crate::clusters::codec::CommandField { tag: 0, name: "operational_dataset", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
557            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
558        ]),
559        0x04 => Some(vec![
560            crate::clusters::codec::CommandField { tag: 0, name: "network_id", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
561            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
562        ]),
563        0x06 => Some(vec![
564            crate::clusters::codec::CommandField { tag: 0, name: "network_id", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
565            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
566        ]),
567        0x08 => Some(vec![
568            crate::clusters::codec::CommandField { tag: 0, name: "network_id", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
569            crate::clusters::codec::CommandField { tag: 1, name: "network_index", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: false },
570            crate::clusters::codec::CommandField { tag: 2, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
571        ]),
572        0x09 => Some(vec![
573            crate::clusters::codec::CommandField { tag: 0, name: "key_identifier", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
574            crate::clusters::codec::CommandField { tag: 1, name: "possession_nonce", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
575        ]),
576        _ => None,
577    }
578}
579
580pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
581    match cmd_id {
582        0x00 => {
583        let ssid = crate::clusters::codec::json_util::get_opt_octstr(args, "ssid")?;
584        let breadcrumb = crate::clusters::codec::json_util::get_opt_u64(args, "breadcrumb")?;
585        encode_scan_networks(ssid, breadcrumb)
586        }
587        0x02 => {
588        let ssid = crate::clusters::codec::json_util::get_octstr(args, "ssid")?;
589        let credentials = crate::clusters::codec::json_util::get_octstr(args, "credentials")?;
590        let breadcrumb = crate::clusters::codec::json_util::get_opt_u64(args, "breadcrumb")?;
591        let network_identity = crate::clusters::codec::json_util::get_opt_octstr(args, "network_identity")?;
592        let client_identifier = crate::clusters::codec::json_util::get_opt_octstr(args, "client_identifier")?;
593        let possession_nonce = crate::clusters::codec::json_util::get_opt_octstr(args, "possession_nonce")?;
594        encode_add_or_update_wifi_network(ssid, credentials, breadcrumb, network_identity, client_identifier, possession_nonce)
595        }
596        0x03 => {
597        let operational_dataset = crate::clusters::codec::json_util::get_octstr(args, "operational_dataset")?;
598        let breadcrumb = crate::clusters::codec::json_util::get_opt_u64(args, "breadcrumb")?;
599        encode_add_or_update_thread_network(operational_dataset, breadcrumb)
600        }
601        0x04 => {
602        let network_id = crate::clusters::codec::json_util::get_octstr(args, "network_id")?;
603        let breadcrumb = crate::clusters::codec::json_util::get_opt_u64(args, "breadcrumb")?;
604        encode_remove_network(network_id, breadcrumb)
605        }
606        0x06 => {
607        let network_id = crate::clusters::codec::json_util::get_octstr(args, "network_id")?;
608        let breadcrumb = crate::clusters::codec::json_util::get_opt_u64(args, "breadcrumb")?;
609        encode_connect_network(network_id, breadcrumb)
610        }
611        0x08 => {
612        let network_id = crate::clusters::codec::json_util::get_octstr(args, "network_id")?;
613        let network_index = crate::clusters::codec::json_util::get_u8(args, "network_index")?;
614        let breadcrumb = crate::clusters::codec::json_util::get_opt_u64(args, "breadcrumb")?;
615        encode_reorder_network(network_id, network_index, breadcrumb)
616        }
617        0x09 => {
618        let key_identifier = crate::clusters::codec::json_util::get_octstr(args, "key_identifier")?;
619        let possession_nonce = crate::clusters::codec::json_util::get_opt_octstr(args, "possession_nonce")?;
620        encode_query_identity(key_identifier, possession_nonce)
621        }
622        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
623    }
624}
625
626#[derive(Debug, serde::Serialize)]
627pub struct ScanNetworksResponse {
628    pub networking_status: Option<NetworkCommissioningStatus>,
629    pub debug_text: Option<String>,
630    pub wifi_scan_results: Option<Vec<WiFiInterfaceScanResult>>,
631    pub thread_scan_results: Option<Vec<ThreadInterfaceScanResult>>,
632}
633
634#[derive(Debug, serde::Serialize)]
635pub struct NetworkConfigResponse {
636    pub networking_status: Option<NetworkCommissioningStatus>,
637    pub debug_text: Option<String>,
638    pub network_index: Option<u8>,
639    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
640    pub client_identity: Option<Vec<u8>>,
641    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
642    pub possession_signature: Option<Vec<u8>>,
643}
644
645#[derive(Debug, serde::Serialize)]
646pub struct ConnectNetworkResponse {
647    pub networking_status: Option<NetworkCommissioningStatus>,
648    pub debug_text: Option<String>,
649    pub error_value: Option<i32>,
650}
651
652#[derive(Debug, serde::Serialize)]
653pub struct QueryIdentityResponse {
654    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
655    pub identity: Option<Vec<u8>>,
656    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
657    pub possession_signature: Option<Vec<u8>>,
658}
659
660// Command response decoders
661
662/// Decode ScanNetworksResponse command response (01)
663pub fn decode_scan_networks_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ScanNetworksResponse> {
664    if let tlv::TlvItemValue::List(_fields) = inp {
665        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
666        Ok(ScanNetworksResponse {
667                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
668                debug_text: item.get_string_owned(&[1]),
669                wifi_scan_results: {
670                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
671                        let mut items = Vec::new();
672                        for list_item in l {
673                            items.push(WiFiInterfaceScanResult {
674                security: list_item.get_int(&[0]).map(|v| v as u8),
675                ssid: list_item.get_octet_string_owned(&[1]),
676                bssid: list_item.get_octet_string_owned(&[2]),
677                channel: list_item.get_int(&[3]).map(|v| v as u16),
678                wifi_band: list_item.get_int(&[4]).and_then(|v| WiFiBand::from_u8(v as u8)),
679                rssi: list_item.get_int(&[5]).map(|v| v as i8),
680                            });
681                        }
682                        Some(items)
683                    } else {
684                        None
685                    }
686                },
687                thread_scan_results: {
688                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
689                        let mut items = Vec::new();
690                        for list_item in l {
691                            items.push(ThreadInterfaceScanResult {
692                pan_id: list_item.get_int(&[0]).map(|v| v as u16),
693                extended_pan_id: list_item.get_int(&[1]),
694                network_name: list_item.get_string_owned(&[2]),
695                channel: list_item.get_int(&[3]).map(|v| v as u16),
696                version: list_item.get_int(&[4]).map(|v| v as u8),
697                extended_address: list_item.get_int(&[5]).map(|v| v as u8),
698                rssi: list_item.get_int(&[6]).map(|v| v as i8),
699                lqi: list_item.get_int(&[7]).map(|v| v as u8),
700                            });
701                        }
702                        Some(items)
703                    } else {
704                        None
705                    }
706                },
707        })
708    } else {
709        Err(anyhow::anyhow!("Expected struct fields"))
710    }
711}
712
713/// Decode NetworkConfigResponse command response (05)
714pub fn decode_network_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<NetworkConfigResponse> {
715    if let tlv::TlvItemValue::List(_fields) = inp {
716        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
717        Ok(NetworkConfigResponse {
718                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
719                debug_text: item.get_string_owned(&[1]),
720                network_index: item.get_int(&[2]).map(|v| v as u8),
721                client_identity: item.get_octet_string_owned(&[3]),
722                possession_signature: item.get_octet_string_owned(&[4]),
723        })
724    } else {
725        Err(anyhow::anyhow!("Expected struct fields"))
726    }
727}
728
729/// Decode ConnectNetworkResponse command response (07)
730pub fn decode_connect_network_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ConnectNetworkResponse> {
731    if let tlv::TlvItemValue::List(_fields) = inp {
732        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
733        Ok(ConnectNetworkResponse {
734                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
735                debug_text: item.get_string_owned(&[1]),
736                error_value: item.get_int(&[2]).map(|v| v as i32),
737        })
738    } else {
739        Err(anyhow::anyhow!("Expected struct fields"))
740    }
741}
742
743/// Decode QueryIdentityResponse command response (0A)
744pub fn decode_query_identity_response(inp: &tlv::TlvItemValue) -> anyhow::Result<QueryIdentityResponse> {
745    if let tlv::TlvItemValue::List(_fields) = inp {
746        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
747        Ok(QueryIdentityResponse {
748                identity: item.get_octet_string_owned(&[0]),
749                possession_signature: item.get_octet_string_owned(&[1]),
750        })
751    } else {
752        Err(anyhow::anyhow!("Expected struct fields"))
753    }
754}
755
756// Typed facade (invokes + reads)
757
758/// Invoke `ScanNetworks` command on cluster `Network Commissioning`.
759pub async fn scan_networks(conn: &crate::controller::Connection, endpoint: u16, ssid: Option<Vec<u8>>, breadcrumb: Option<u64>) -> anyhow::Result<ScanNetworksResponse> {
760    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_SCANNETWORKS, &encode_scan_networks(ssid, breadcrumb)?).await?;
761    decode_scan_networks_response(&tlv)
762}
763
764/// Invoke `AddOrUpdateWiFiNetwork` command on cluster `Network Commissioning`.
765pub async fn add_or_update_wifi_network(conn: &crate::controller::Connection, endpoint: u16, ssid: Vec<u8>, credentials: Vec<u8>, breadcrumb: Option<u64>, network_identity: Option<Vec<u8>>, client_identifier: Option<Vec<u8>>, possession_nonce: Option<Vec<u8>>) -> anyhow::Result<NetworkConfigResponse> {
766    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_ADDORUPDATEWIFINETWORK, &encode_add_or_update_wifi_network(ssid, credentials, breadcrumb, network_identity, client_identifier, possession_nonce)?).await?;
767    decode_network_config_response(&tlv)
768}
769
770/// Invoke `AddOrUpdateThreadNetwork` command on cluster `Network Commissioning`.
771pub async fn add_or_update_thread_network(conn: &crate::controller::Connection, endpoint: u16, operational_dataset: Vec<u8>, breadcrumb: Option<u64>) -> anyhow::Result<NetworkConfigResponse> {
772    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_ADDORUPDATETHREADNETWORK, &encode_add_or_update_thread_network(operational_dataset, breadcrumb)?).await?;
773    decode_network_config_response(&tlv)
774}
775
776/// Invoke `RemoveNetwork` command on cluster `Network Commissioning`.
777pub async fn remove_network(conn: &crate::controller::Connection, endpoint: u16, network_id: Vec<u8>, breadcrumb: Option<u64>) -> anyhow::Result<NetworkConfigResponse> {
778    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_REMOVENETWORK, &encode_remove_network(network_id, breadcrumb)?).await?;
779    decode_network_config_response(&tlv)
780}
781
782/// Invoke `ConnectNetwork` command on cluster `Network Commissioning`.
783pub async fn connect_network(conn: &crate::controller::Connection, endpoint: u16, network_id: Vec<u8>, breadcrumb: Option<u64>) -> anyhow::Result<ConnectNetworkResponse> {
784    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_CONNECTNETWORK, &encode_connect_network(network_id, breadcrumb)?).await?;
785    decode_connect_network_response(&tlv)
786}
787
788/// Invoke `ReorderNetwork` command on cluster `Network Commissioning`.
789pub async fn reorder_network(conn: &crate::controller::Connection, endpoint: u16, network_id: Vec<u8>, network_index: u8, breadcrumb: Option<u64>) -> anyhow::Result<NetworkConfigResponse> {
790    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_REORDERNETWORK, &encode_reorder_network(network_id, network_index, breadcrumb)?).await?;
791    decode_network_config_response(&tlv)
792}
793
794/// Invoke `QueryIdentity` command on cluster `Network Commissioning`.
795pub async fn query_identity(conn: &crate::controller::Connection, endpoint: u16, key_identifier: Vec<u8>, possession_nonce: Option<Vec<u8>>) -> anyhow::Result<QueryIdentityResponse> {
796    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_QUERYIDENTITY, &encode_query_identity(key_identifier, possession_nonce)?).await?;
797    decode_query_identity_response(&tlv)
798}
799
800/// Read `MaxNetworks` attribute from cluster `Network Commissioning`.
801pub async fn read_max_networks(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
802    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_MAXNETWORKS).await?;
803    decode_max_networks(&tlv)
804}
805
806/// Read `Networks` attribute from cluster `Network Commissioning`.
807pub async fn read_networks(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<NetworkInfo>> {
808    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_NETWORKS).await?;
809    decode_networks(&tlv)
810}
811
812/// Read `ScanMaxTimeSeconds` attribute from cluster `Network Commissioning`.
813pub async fn read_scan_max_time_seconds(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
814    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_SCANMAXTIMESECONDS).await?;
815    decode_scan_max_time_seconds(&tlv)
816}
817
818/// Read `ConnectMaxTimeSeconds` attribute from cluster `Network Commissioning`.
819pub async fn read_connect_max_time_seconds(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
820    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_CONNECTMAXTIMESECONDS).await?;
821    decode_connect_max_time_seconds(&tlv)
822}
823
824/// Read `InterfaceEnabled` attribute from cluster `Network Commissioning`.
825pub async fn read_interface_enabled(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
826    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_INTERFACEENABLED).await?;
827    decode_interface_enabled(&tlv)
828}
829
830/// Read `LastNetworkingStatus` attribute from cluster `Network Commissioning`.
831pub async fn read_last_networking_status(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<NetworkCommissioningStatus>> {
832    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_LASTNETWORKINGSTATUS).await?;
833    decode_last_networking_status(&tlv)
834}
835
836/// Read `LastNetworkID` attribute from cluster `Network Commissioning`.
837pub async fn read_last_network_id(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Vec<u8>>> {
838    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_LASTNETWORKID).await?;
839    decode_last_network_id(&tlv)
840}
841
842/// Read `LastConnectErrorValue` attribute from cluster `Network Commissioning`.
843pub async fn read_last_connect_error_value(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<i32>> {
844    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_LASTCONNECTERRORVALUE).await?;
845    decode_last_connect_error_value(&tlv)
846}
847
848/// Read `SupportedWiFiBands` attribute from cluster `Network Commissioning`.
849pub async fn read_supported_wifi_bands(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<WiFiBand>> {
850    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_SUPPORTEDWIFIBANDS).await?;
851    decode_supported_wifi_bands(&tlv)
852}
853
854/// Read `SupportedThreadFeatures` attribute from cluster `Network Commissioning`.
855pub async fn read_supported_thread_features(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ThreadCapabilities> {
856    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_SUPPORTEDTHREADFEATURES).await?;
857    decode_supported_thread_features(&tlv)
858}
859
860/// Read `ThreadVersion` attribute from cluster `Network Commissioning`.
861pub async fn read_thread_version(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
862    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_THREADVERSION).await?;
863    decode_thread_version(&tlv)
864}
865