matroskin 0.1.0

whatsminer api client lib
Documentation
//! Implement `set.system.update_firmware` command
//!
//! This command initiates a firmware upgrade process. The initial JSON
//! handshake sends the command and receives a `"ready"` response from the
//! device. The actual binary firmware transfer (4-byte LE size prefix +
//! firmware data) is handled separately by [`Actor::upload_firmware`](crate::actor::Actor::upload_firmware).
//!
//! - Command: [SetSystemUpdateFirmware]
//! - ApiDoc: <https://apidoc.whatsminer.com/#api-System-update_firmware>

use crate::{auth_data::AuthData, error::Result, permission::PermissionLevel, request::Request};

/// Internal command representing the `set.system.update_firmware` operation.
///
/// Initiates the firmware upgrade handshake. Callers interact with firmware
/// upgrades through [`Actor::upload_firmware`](crate::actor::Actor::upload_firmware),
/// which executes this handshake and then streams the binary payload.
///
/// - ApiDoc: <https://apidoc.whatsminer.com/#api-System-update_firmware>
///
/// # Example
///
/// ```rust,no_run
/// use matroskin::actor::Actor;
/// use matroskin::account::Account;
/// use matroskin::password::Password;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let actor = Actor::new("10.10.10.10:4433", Account::Super, Password::Super);
///     actor.connect()?;
///
///     let file = tokio::fs::File::open("firmware.bin").await?;
///     let len = file.metadata().await?.len();
///     actor.upload_firmware(file, len).await?;
///
///     actor.shutdown()?;
///     Ok(())
/// }
/// ```
#[derive(Debug, Default, Clone, Copy)]
pub struct SetSystemUpdateFirmware;

impl SetSystemUpdateFirmware {
    pub const CMD_NAME: &'static str = "set.system.update_firmware";
    pub const MINIMAL_PERMISSIONS: PermissionLevel = PermissionLevel::All;

    pub fn encode_request(auth: AuthData) -> Result<Vec<u8>> {
        let req = Request::new(Self::CMD_NAME, Some(auth), None::<()>);
        Ok(serde_json::to_vec(&req)?)
    }
}

#[cfg(test)]
mod tests {
    use crate::account::Account;
    use crate::auth_data::AuthData;
    use crate::response::Response;

    use super::*;

    const RESPONSE_JSON: &str =
        r#"{"code":0,"when":1672281726,"msg":"ready","desc":"set.system.update_firmware"}"#;

    #[test]
    fn request_is_encrypted_without_params() {
        let auth = AuthData::for_command_name(
            SetSystemUpdateFirmware::CMD_NAME,
            Account::Super,
            "pass",
            "salt",
        )
        .unwrap();
        let bytes = SetSystemUpdateFirmware::encode_request(auth).unwrap();
        let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();

        assert_eq!(json["cmd"], "set.system.update_firmware");
        assert_eq!(json["account"], "super");
        assert!(json["ts"].as_u64().unwrap() > 0);
        assert_eq!(json["token"].as_str().unwrap().len(), 8);
        assert!(json.get("param").is_none());
    }

    #[test]
    fn deserialize_response_fixture() {
        let response: Response<String> = serde_json::from_str(RESPONSE_JSON).unwrap();
        assert!(response.code.is_success());
        assert_eq!(response.desc, "set.system.update_firmware");
        assert_eq!(response.when, 1672281726);
        assert_eq!(response.msg, "ready");
    }
}