matc 0.1.2

Matter protocol library (controller side)
Documentation
//! Matter TLV encoders and decoders for OTA Software Update Requestor Cluster
//! Cluster ID: 0x002A
//!
//! This file is automatically generated from OTARequestor.xml

use crate::tlv;
use anyhow;
use serde_json;


// Enum definitions

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum AnnouncementReason {
    /// An OTA Provider is announcing its presence.
    Simpleannouncement = 0,
    /// An OTA Provider is announcing, either to a single Node or to a group of Nodes, that a new Software Image MAY be available.
    Updateavailable = 1,
    /// An OTA Provider is announcing, either to a single Node or to a group of Nodes, that a new Software Image MAY be available, which contains an update that needs to be applied urgently.
    Urgentupdateavailable = 2,
}

impl AnnouncementReason {
    /// Convert from u8 value
    pub fn from_u8(value: u8) -> Option<Self> {
        match value {
            0 => Some(AnnouncementReason::Simpleannouncement),
            1 => Some(AnnouncementReason::Updateavailable),
            2 => Some(AnnouncementReason::Urgentupdateavailable),
            _ => None,
        }
    }

    /// Convert to u8 value
    pub fn to_u8(self) -> u8 {
        self as u8
    }
}

impl From<AnnouncementReason> for u8 {
    fn from(val: AnnouncementReason) -> Self {
        val as u8
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ChangeReason {
    /// The reason for a state change is unknown.
    Unknown = 0,
    /// The reason for a state change is the success of a prior operation.
    Success = 1,
    /// The reason for a state change is the failure of a prior operation.
    Failure = 2,
    /// The reason for a state change is a time-out.
    Timeout = 3,
    /// The reason for a state change is a request by the OTA Provider to wait.
    Delaybyprovider = 4,
}

impl ChangeReason {
    /// Convert from u8 value
    pub fn from_u8(value: u8) -> Option<Self> {
        match value {
            0 => Some(ChangeReason::Unknown),
            1 => Some(ChangeReason::Success),
            2 => Some(ChangeReason::Failure),
            3 => Some(ChangeReason::Timeout),
            4 => Some(ChangeReason::Delaybyprovider),
            _ => None,
        }
    }

    /// Convert to u8 value
    pub fn to_u8(self) -> u8 {
        self as u8
    }
}

impl From<ChangeReason> for u8 {
    fn from(val: ChangeReason) -> Self {
        val as u8
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum UpdateState {
    /// Current state is not yet determined.
    Unknown = 0,
    /// Indicate a Node not yet in the process of software update.
    Idle = 1,
    /// Indicate a Node in the process of querying an OTA Provider.
    Querying = 2,
    /// Indicate a Node waiting after a Busy response.
    Delayedonquery = 3,
    /// Indicate a Node currently in the process of downloading a software update.
    Downloading = 4,
    /// Indicate a Node currently in the process of verifying and applying a software update.
    Applying = 5,
    /// Indicate a Node waiting caused by AwaitNextAction response.
    Delayedonapply = 6,
    /// Indicate a Node in the process of recovering to a previous version.
    Rollingback = 7,
    /// Indicate a Node is capable of user consent.
    Delayedonuserconsent = 8,
}

impl UpdateState {
    /// Convert from u8 value
    pub fn from_u8(value: u8) -> Option<Self> {
        match value {
            0 => Some(UpdateState::Unknown),
            1 => Some(UpdateState::Idle),
            2 => Some(UpdateState::Querying),
            3 => Some(UpdateState::Delayedonquery),
            4 => Some(UpdateState::Downloading),
            5 => Some(UpdateState::Applying),
            6 => Some(UpdateState::Delayedonapply),
            7 => Some(UpdateState::Rollingback),
            8 => Some(UpdateState::Delayedonuserconsent),
            _ => None,
        }
    }

    /// Convert to u8 value
    pub fn to_u8(self) -> u8 {
        self as u8
    }
}

impl From<UpdateState> for u8 {
    fn from(val: UpdateState) -> Self {
        val as u8
    }
}

// Struct definitions

#[derive(Debug, serde::Serialize)]
pub struct ProviderLocation {
    pub provider_node_id: Option<u64>,
    pub endpoint: Option<u16>,
}

// Command encoders

/// Encode AnnounceOTAProvider command (0x00)
pub fn encode_announce_ota_provider(provider_node_id: u64, vendor_id: u16, announcement_reason: AnnouncementReason, metadata_for_node: Vec<u8>, endpoint: u16) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::UInt64(provider_node_id)).into(),
        (1, tlv::TlvItemValueEnc::UInt16(vendor_id)).into(),
        (2, tlv::TlvItemValueEnc::UInt8(announcement_reason.to_u8())).into(),
        (3, tlv::TlvItemValueEnc::OctetString(metadata_for_node)).into(),
        (4, tlv::TlvItemValueEnc::UInt16(endpoint)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

// Attribute decoders

/// Decode DefaultOTAProviders attribute (0x0000)
pub fn decode_default_ota_providers(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ProviderLocation>> {
    let mut res = Vec::new();
    if let tlv::TlvItemValue::List(v) = inp {
        for item in v {
            res.push(ProviderLocation {
                provider_node_id: item.get_int(&[1]),
                endpoint: item.get_int(&[2]).map(|v| v as u16),
            });
        }
    }
    Ok(res)
}

/// Decode UpdatePossible attribute (0x0001)
pub fn decode_update_possible(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
    if let tlv::TlvItemValue::Bool(v) = inp {
        Ok(*v)
    } else {
        Err(anyhow::anyhow!("Expected Bool"))
    }
}

/// Decode UpdateState attribute (0x0002)
pub fn decode_update_state(inp: &tlv::TlvItemValue) -> anyhow::Result<UpdateState> {
    if let tlv::TlvItemValue::Int(v) = inp {
        UpdateState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
    } else {
        Err(anyhow::anyhow!("Expected Integer"))
    }
}

/// Decode UpdateStateProgress attribute (0x0003)
pub fn decode_update_state_progress(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(Some(*v as u8))
    } else {
        Ok(None)
    }
}


// JSON dispatcher function

/// Decode attribute value and return as JSON string
///
/// # Parameters
/// * `cluster_id` - The cluster identifier
/// * `attribute_id` - The attribute identifier
/// * `tlv_value` - The TLV value to decode
///
/// # Returns
/// JSON string representation of the decoded value or error
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
    // Verify this is the correct cluster
    if cluster_id != 0x002A {
        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x002A, got {}\"}}", cluster_id);
    }

    match attribute_id {
        0x0000 => {
            match decode_default_ota_providers(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0001 => {
            match decode_update_possible(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0002 => {
            match decode_update_state(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0003 => {
            match decode_update_state_progress(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),
    }
}

/// Get list of all attributes supported by this cluster
///
/// # Returns
/// Vector of tuples containing (attribute_id, attribute_name)
pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
    vec![
        (0x0000, "DefaultOTAProviders"),
        (0x0001, "UpdatePossible"),
        (0x0002, "UpdateState"),
        (0x0003, "UpdateStateProgress"),
    ]
}

#[derive(Debug, serde::Serialize)]
pub struct StateTransitionEvent {
    pub previous_state: Option<UpdateState>,
    pub new_state: Option<UpdateState>,
    pub reason: Option<ChangeReason>,
    pub target_software_version: Option<u32>,
}

#[derive(Debug, serde::Serialize)]
pub struct VersionAppliedEvent {
    pub software_version: Option<u32>,
    pub product_id: Option<u16>,
}

#[derive(Debug, serde::Serialize)]
pub struct DownloadErrorEvent {
    pub software_version: Option<u32>,
    pub bytes_downloaded: Option<u64>,
    pub progress_percent: Option<u8>,
    pub platform_code: Option<i64>,
}

// Event decoders

/// Decode StateTransition event (0x00, priority: info)
pub fn decode_state_transition_event(inp: &tlv::TlvItemValue) -> anyhow::Result<StateTransitionEvent> {
    if let tlv::TlvItemValue::List(_fields) = inp {
        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
        Ok(StateTransitionEvent {
                                previous_state: item.get_int(&[0]).and_then(|v| UpdateState::from_u8(v as u8)),
                                new_state: item.get_int(&[1]).and_then(|v| UpdateState::from_u8(v as u8)),
                                reason: item.get_int(&[2]).and_then(|v| ChangeReason::from_u8(v as u8)),
                                target_software_version: item.get_int(&[3]).map(|v| v as u32),
        })
    } else {
        Err(anyhow::anyhow!("Expected struct fields"))
    }
}

/// Decode VersionApplied event (0x01, priority: critical)
pub fn decode_version_applied_event(inp: &tlv::TlvItemValue) -> anyhow::Result<VersionAppliedEvent> {
    if let tlv::TlvItemValue::List(_fields) = inp {
        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
        Ok(VersionAppliedEvent {
                                software_version: item.get_int(&[0]).map(|v| v as u32),
                                product_id: item.get_int(&[1]).map(|v| v as u16),
        })
    } else {
        Err(anyhow::anyhow!("Expected struct fields"))
    }
}

/// Decode DownloadError event (0x02, priority: info)
pub fn decode_download_error_event(inp: &tlv::TlvItemValue) -> anyhow::Result<DownloadErrorEvent> {
    if let tlv::TlvItemValue::List(_fields) = inp {
        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
        Ok(DownloadErrorEvent {
                                software_version: item.get_int(&[0]).map(|v| v as u32),
                                bytes_downloaded: item.get_int(&[1]),
                                progress_percent: item.get_int(&[2]).map(|v| v as u8),
                                platform_code: item.get_int(&[3]).map(|v| v as i64),
        })
    } else {
        Err(anyhow::anyhow!("Expected struct fields"))
    }
}