can-utils-rs 0.4.1

A pure Rust CLI utility for managing and manipulating CAN interfaces and sending or printing pretty CAN data.
Documentation
use anyhow::{Result, anyhow, bail};

#[derive(Debug, Clone)]
pub struct SendFrame {
    pub id: u32,
    pub data: Vec<u8>,
}

impl SendFrame {
    pub fn format_compact(&self) -> String {
        let payload = self
            .data
            .iter()
            .map(|b| format!("{:02X}", b))
            .collect::<String>();

        format!("{:03X}#{}", self.id, payload)
    }

    pub fn format_spaced(&self) -> String {
        if self.data.is_empty() {
            return format!("{:03X}#", self.id);
        }

        let payload = self
            .data
            .iter()
            .map(|b| format!("{:02X}", b))
            .collect::<Vec<_>>()
            .join(" ");

        format!("{:03X}#{}", self.id, payload)
    }
}

pub fn parse_can_id(input: &str) -> Result<u32> {
    let trimmed = input.trim();

    if trimmed.is_empty() {
        bail!("CAN ID cannot be empty");
    }

    let id = u32::from_str_radix(trimmed, 16)
        .map_err(|_| anyhow!("CAN ID must be valid hexadecimal"))?;

    if id > 0x7FF {
        bail!("CAN ID must be <= 7FF for standard CAN");
    }

    Ok(id)
}

pub fn parse_can_data(input: &str) -> Result<Vec<u8>> {
    let trimmed = input.trim();

    if trimmed.is_empty() {
        return Ok(Vec::new());
    }

    let parts: Vec<&str> = trimmed.split_whitespace().collect();

    if parts.len() > 8 {
        bail!("CAN data may contain at most 8 bytes");
    }

    let mut data = Vec::with_capacity(parts.len());

    for part in parts {
        if part.len() != 2 {
            bail!("Each byte must be exactly 2 hexadecimal characters");
        }

        let byte =
            u8::from_str_radix(part, 16).map_err(|_| anyhow!("Invalid hex byte '{}'", part))?;

        data.push(byte);
    }

    Ok(data)
}