matroskin 0.1.0

whatsminer api client lib
Documentation
//! User permission model for WhatsMiner devices.
//!
//! The device stores per-account command permissions, returned by `get.device.info`
//! in the `miner.permission` field as a whitespace-separated string like:
//!
//! `"super=255 user1=0 user2=1 user3=2"`
//!
//! - `255` means full access (Super).
//! - `0` means Read-Only (no write commands allowed).
//! - `1` means Read-Write (normal level).
//! - `2` means Advanced (power level).

use crate::account::Account;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;

/// Permission level for an account.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PermissionLevel {
    /// Read-only access (value `0`). No write commands allowed.
    ReadOnly = 0,
    /// Read-write access (value `1`). Standard set of commands.
    ReadWrite = 1,
    /// Advanced access (value `2`). Power-user commands.
    Advanced = 2,
    /// Full access (value `255`). All commands allowed.
    All = 255,
}

impl PermissionLevel {
    /// Returns the raw numeric value.
    pub fn value(&self) -> u8 {
        *self as u8
    }
}

impl fmt::Display for PermissionLevel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.value())
    }
}

impl From<u8> for PermissionLevel {
    fn from(v: u8) -> Self {
        match v {
            255 => PermissionLevel::All,
            1 => PermissionLevel::ReadWrite,
            2 => PermissionLevel::Advanced,
            _ => PermissionLevel::ReadOnly,
        }
    }
}

impl Serialize for PermissionLevel {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_u8(self.value())
    }
}

struct PermissionLevelVisitor;

impl<'de> Visitor<'de> for PermissionLevelVisitor {
    type Value = PermissionLevel;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a permission level (0, 1, 2, or 255)")
    }

    fn visit_u8<E>(self, value: u8) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(PermissionLevel::from(value))
    }

    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        if value <= 255 {
            Ok(PermissionLevel::from(value as u8))
        } else {
            Err(E::custom(format!(
                "permission level out of range: {}",
                value
            )))
        }
    }

    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        value
            .parse::<u8>()
            .map(PermissionLevel::from)
            .map_err(|_| E::custom(format!("invalid permission level string: {}", value)))
    }
}

impl<'de> Deserialize<'de> for PermissionLevel {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_any(PermissionLevelVisitor)
    }
}

/// Device-level command permission for a specific account.
///
/// Parsed from the `miner.permission` field of `get.device.info`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Permission(pub PermissionLevel);

impl Permission {
    /// Full access (255)
    pub const ALL: Self = Self(PermissionLevel::All);
    /// Read-only access (0)
    pub const READ_ONLY: Self = Self(PermissionLevel::ReadOnly);

    /// Returns `true` if this permission allows execution of the given command level.
    pub fn can_execute(&self, required_level: PermissionLevel) -> bool {
        self.0 >= required_level
    }

    /// Returns `true` if this permission grants no write access.
    pub fn is_read_only(&self) -> bool {
        self.0 == PermissionLevel::ReadOnly
    }

    /// Parse the permission for a specific account from the device's
    /// `miner.permission` string.
    ///
    /// # Format
    ///
    /// `"super=255 user1=0 user2=1 user3=2"`
    pub fn parse_for_account(permission_str: &str, account: Account) -> Self {
        let key = account.as_ref();
        for entry in permission_str.split_whitespace() {
            if let Some((user, value)) = entry.split_once('=')
                && user == key
                && let Ok(v) = value.parse::<u8>()
            {
                return Permission(PermissionLevel::from(v));
            }
        }
        Permission::READ_ONLY
    }
}

impl From<PermissionLevel> for Permission {
    fn from(level: PermissionLevel) -> Self {
        Permission(level)
    }
}

impl Default for Permission {
    fn default() -> Self {
        Permission::READ_ONLY
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    const PERMISSION_STR: &str = "super=255 user1=0 user2=1 user3=2";

    #[test]
    fn parse_super_gets_all() {
        let perm = Permission::parse_for_account(PERMISSION_STR, Account::Super);
        assert_eq!(perm, Permission::ALL);
        assert!(perm.can_execute(PermissionLevel::All));
        assert!(perm.can_execute(PermissionLevel::Advanced));
    }

    #[test]
    fn parse_user1_read_only() {
        let perm = Permission::parse_for_account(PERMISSION_STR, Account::User1);
        assert_eq!(perm, Permission::READ_ONLY);
        assert!(perm.is_read_only());
        assert!(!perm.can_execute(PermissionLevel::ReadWrite));
    }

    #[test]
    fn parse_user2_read_write() {
        let perm = Permission::parse_for_account(PERMISSION_STR, Account::User2);
        assert_eq!(perm.0, PermissionLevel::ReadWrite);
        assert!(perm.can_execute(PermissionLevel::ReadWrite));
        assert!(!perm.can_execute(PermissionLevel::Advanced));
    }

    #[test]
    fn parse_user3_advanced() {
        let perm = Permission::parse_for_account(PERMISSION_STR, Account::User3);
        assert_eq!(perm.0, PermissionLevel::Advanced);
        assert!(perm.can_execute(PermissionLevel::Advanced));
        assert!(!perm.can_execute(PermissionLevel::All));
    }

    #[test]
    fn parse_missing_account_defaults_to_read_only() {
        let perm = Permission::parse_for_account("super=255", Account::User1);
        assert_eq!(perm, Permission::READ_ONLY);
    }

    #[test]
    fn serialize_permission_level() {
        let level = PermissionLevel::Advanced;
        let json = serde_json::to_string(&level).unwrap();
        assert_eq!(json, "2");
    }

    #[test]
    fn deserialize_permission_level() {
        let level: PermissionLevel = serde_json::from_str("2").unwrap();
        assert_eq!(level, PermissionLevel::Advanced);

        let level: PermissionLevel = serde_json::from_str("\"255\"").unwrap();
        assert_eq!(level, PermissionLevel::All);
    }
}