use crate::tlv;
use anyhow;
use serde_json;
use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum NetworkCommissioningStatus {
Success = 0,
Outofrange = 1,
Boundsexceeded = 2,
Networkidnotfound = 3,
Duplicatenetworkid = 4,
Networknotfound = 5,
Regulatoryerror = 6,
Authfailure = 7,
Unsupportedsecurity = 8,
Otherconnectionfailure = 9,
Ipv6failed = 10,
Ipbindfailed = 11,
Unknownerror = 12,
}
impl NetworkCommissioningStatus {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(NetworkCommissioningStatus::Success),
1 => Some(NetworkCommissioningStatus::Outofrange),
2 => Some(NetworkCommissioningStatus::Boundsexceeded),
3 => Some(NetworkCommissioningStatus::Networkidnotfound),
4 => Some(NetworkCommissioningStatus::Duplicatenetworkid),
5 => Some(NetworkCommissioningStatus::Networknotfound),
6 => Some(NetworkCommissioningStatus::Regulatoryerror),
7 => Some(NetworkCommissioningStatus::Authfailure),
8 => Some(NetworkCommissioningStatus::Unsupportedsecurity),
9 => Some(NetworkCommissioningStatus::Otherconnectionfailure),
10 => Some(NetworkCommissioningStatus::Ipv6failed),
11 => Some(NetworkCommissioningStatus::Ipbindfailed),
12 => Some(NetworkCommissioningStatus::Unknownerror),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<NetworkCommissioningStatus> for u8 {
fn from(val: NetworkCommissioningStatus) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum WiFiBand {
_2g4 = 0,
_3g65 = 1,
_5g = 2,
_6g = 3,
_60g = 4,
_1g = 5,
}
impl WiFiBand {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(WiFiBand::_2g4),
1 => Some(WiFiBand::_3g65),
2 => Some(WiFiBand::_5g),
3 => Some(WiFiBand::_6g),
4 => Some(WiFiBand::_60g),
5 => Some(WiFiBand::_1g),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<WiFiBand> for u8 {
fn from(val: WiFiBand) -> Self {
val as u8
}
}
pub type ThreadCapabilities = u8;
pub mod threadcapabilities {
pub const IS_BORDER_ROUTER_CAPABLE: u8 = 0x01;
pub const IS_ROUTER_CAPABLE: u8 = 0x02;
pub const IS_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x04;
pub const IS_FULL_THREAD_DEVICE: u8 = 0x08;
pub const IS_SYNCHRONIZED_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x10;
}
pub type WiFiSecurity = u8;
pub mod wifisecurity {
pub const UNENCRYPTED: u8 = 0x01;
pub const WEP: u8 = 0x02;
pub const WPA_PERSONAL: u8 = 0x04;
pub const WPA2_PERSONAL: u8 = 0x08;
pub const WPA3_PERSONAL: u8 = 0x10;
}
#[derive(Debug, serde::Serialize)]
pub struct NetworkInfo {
#[serde(serialize_with = "serialize_opt_bytes_as_hex")]
pub network_id: Option<Vec<u8>>,
pub connected: Option<bool>,
}
#[derive(Debug, serde::Serialize)]
pub struct ThreadInterfaceScanResult {
pub pan_id: Option<u16>,
pub extended_pan_id: Option<u64>,
pub network_name: Option<String>,
pub channel: Option<u16>,
pub version: Option<u8>,
pub extended_address: Option<u8>,
pub rssi: Option<i8>,
pub lqi: Option<u8>,
}
#[derive(Debug, serde::Serialize)]
pub struct WiFiInterfaceScanResult {
pub security: Option<WiFiSecurity>,
#[serde(serialize_with = "serialize_opt_bytes_as_hex")]
pub ssid: Option<Vec<u8>>,
#[serde(serialize_with = "serialize_opt_bytes_as_hex")]
pub bssid: Option<Vec<u8>>,
pub channel: Option<u16>,
pub wifi_band: Option<WiFiBand>,
pub rssi: Option<i8>,
}
pub fn encode_scan_networks(ssid: Option<Vec<u8>>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::OctetString(ssid.unwrap_or_default())).into(),
(1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_add_or_update_wifi_network(ssid: Vec<u8>, credentials: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::OctetString(ssid)).into(),
(1, tlv::TlvItemValueEnc::OctetString(credentials)).into(),
(2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_add_or_update_thread_network(operational_dataset: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::OctetString(operational_dataset)).into(),
(1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_remove_network(network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
(1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_connect_network(network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
(1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_reorder_network(network_id: Vec<u8>, network_index: u8, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
(1, tlv::TlvItemValueEnc::UInt8(network_index)).into(),
(2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn decode_max_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u8)
} else {
Err(anyhow::anyhow!("Expected UInt8"))
}
}
pub fn decode_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<NetworkInfo>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(NetworkInfo {
network_id: item.get_octet_string_owned(&[0]),
connected: item.get_bool(&[1]),
});
}
}
Ok(res)
}
pub fn decode_scan_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u8)
} else {
Err(anyhow::anyhow!("Expected UInt8"))
}
}
pub fn decode_connect_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u8)
} else {
Err(anyhow::anyhow!("Expected UInt8"))
}
}
pub fn decode_interface_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
if let tlv::TlvItemValue::Bool(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected Bool"))
}
}
pub fn decode_last_networking_status(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkCommissioningStatus>> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(NetworkCommissioningStatus::from_u8(*v as u8))
} else {
Ok(None)
}
}
pub fn decode_last_network_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
if let tlv::TlvItemValue::OctetString(v) = inp {
Ok(Some(v.clone()))
} else {
Ok(None)
}
}
pub fn decode_last_connect_error_value(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i32>> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(Some(*v as i32))
} else {
Ok(None)
}
}
pub fn decode_supported_wifi_bands(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<WiFiBand>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
if let tlv::TlvItemValue::Int(i) = &item.value {
if let Some(enum_val) = WiFiBand::from_u8(*i as u8) {
res.push(enum_val);
}
}
}
}
Ok(res)
}
pub fn decode_supported_thread_features(inp: &tlv::TlvItemValue) -> anyhow::Result<ThreadCapabilities> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u8)
} else {
Err(anyhow::anyhow!("Expected Integer"))
}
}
pub fn decode_thread_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u16)
} else {
Err(anyhow::anyhow!("Expected UInt16"))
}
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x0031 {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0031, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_max_networks(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_networks(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_scan_max_time_seconds(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0003 => {
match decode_connect_max_time_seconds(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0004 => {
match decode_interface_enabled(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0005 => {
match decode_last_networking_status(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0006 => {
match decode_last_network_id(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0007 => {
match decode_last_connect_error_value(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0008 => {
match decode_supported_wifi_bands(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0009 => {
match decode_supported_thread_features(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x000A => {
match decode_thread_version(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
_ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
}
}
pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
vec![
(0x0000, "MaxNetworks"),
(0x0001, "Networks"),
(0x0002, "ScanMaxTimeSeconds"),
(0x0003, "ConnectMaxTimeSeconds"),
(0x0004, "InterfaceEnabled"),
(0x0005, "LastNetworkingStatus"),
(0x0006, "LastNetworkID"),
(0x0007, "LastConnectErrorValue"),
(0x0008, "SupportedWiFiBands"),
(0x0009, "SupportedThreadFeatures"),
(0x000A, "ThreadVersion"),
]
}
#[derive(Debug, serde::Serialize)]
pub struct ScanNetworksResponse {
pub networking_status: Option<NetworkCommissioningStatus>,
pub debug_text: Option<String>,
pub wifi_scan_results: Option<Vec<WiFiInterfaceScanResult>>,
pub thread_scan_results: Option<Vec<ThreadInterfaceScanResult>>,
}
#[derive(Debug, serde::Serialize)]
pub struct NetworkConfigResponse {
pub networking_status: Option<NetworkCommissioningStatus>,
pub debug_text: Option<String>,
pub network_index: Option<u8>,
}
#[derive(Debug, serde::Serialize)]
pub struct ConnectNetworkResponse {
pub networking_status: Option<NetworkCommissioningStatus>,
pub debug_text: Option<String>,
pub error_value: Option<i32>,
}
pub fn decode_scan_networks_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ScanNetworksResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ScanNetworksResponse {
networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
debug_text: item.get_string_owned(&[1]),
wifi_scan_results: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
let mut items = Vec::new();
for list_item in l {
items.push(WiFiInterfaceScanResult {
security: list_item.get_int(&[0]).map(|v| v as u8),
ssid: list_item.get_octet_string_owned(&[1]),
bssid: list_item.get_octet_string_owned(&[2]),
channel: list_item.get_int(&[3]).map(|v| v as u16),
wifi_band: list_item.get_int(&[4]).and_then(|v| WiFiBand::from_u8(v as u8)),
rssi: list_item.get_int(&[5]).map(|v| v as i8),
});
}
Some(items)
} else {
None
}
},
thread_scan_results: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
let mut items = Vec::new();
for list_item in l {
items.push(ThreadInterfaceScanResult {
pan_id: list_item.get_int(&[0]).map(|v| v as u16),
extended_pan_id: list_item.get_int(&[1]),
network_name: list_item.get_string_owned(&[2]),
channel: list_item.get_int(&[3]).map(|v| v as u16),
version: list_item.get_int(&[4]).map(|v| v as u8),
extended_address: list_item.get_int(&[5]).map(|v| v as u8),
rssi: list_item.get_int(&[6]).map(|v| v as i8),
lqi: list_item.get_int(&[7]).map(|v| v as u8),
});
}
Some(items)
} else {
None
}
},
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_network_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<NetworkConfigResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(NetworkConfigResponse {
networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
debug_text: item.get_string_owned(&[1]),
network_index: item.get_int(&[2]).map(|v| v as u8),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_connect_network_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ConnectNetworkResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ConnectNetworkResponse {
networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
debug_text: item.get_string_owned(&[1]),
error_value: item.get_int(&[2]).map(|v| v as i32),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}