foxmark3 0.1.1

Send/receive Proxmark 3 commands
Documentation
//! Commands and structures for [`Proxmark::version`] requests.

use std::{fmt, time::Duration};

use bytemuck::from_bytes;
use tracing::error;

use crate::raw::{Command, request};

use super::{Error, Proxmark};

#[repr(C, packed)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Header {
    id: u32,
    section_size: u32,
    version_len: u32,
}

const SZ_HEADER: usize = size_of::<Header>();

/// Version information provided by the device firmware.
///
/// [`Proxmark::version`] requests this data.
#[derive(Debug, Clone)]
pub struct Version {
    /// The value of the ID register of the AT91C running the firmware.
    pub id: u32,
    /// The size of the program data section.
    pub section_size: u32,
    /// Arbitrary information about the device firmware. The format of the string is subject to the
    /// device firmware; `foxmark3` has no control over it.
    pub report: String,
}

impl Proxmark {
    /// Requests the device's firmware version information.
    #[tracing::instrument(skip(self), level = tracing::Level::TRACE)]
    pub fn version(&mut self) -> Result<Version, Error> {
        let resp = self
            .0
            .request_response(request::ng(Command::VERSION, []), Duration::from_secs(1))?;

        if resp.payload().len() < SZ_HEADER {
            error!(
                "payload was too short! {} < {SZ_HEADER}",
                resp.payload().len()
            );

            return Err(Error::MalformedResponse(Box::new(resp)));
        }

        let header: &Header = from_bytes(&resp.payload()[..SZ_HEADER]);
        let version_utf8 = &resp.payload()[SZ_HEADER..];

        let version_len = header.version_len as usize;
        if version_len != version_utf8.len() {
            error!(
                "self-described payload length ({version_len}) does not match what is indicated by NG header length ({})!",
                version_utf8.len()
            );

            return Err(Error::MalformedResponse(Box::new(resp)));
        }

        Ok(Version {
            id: header.id,
            section_size: header.section_size,
            report: String::from_utf8_lossy(version_utf8).into_owned(),
        })
    }
}

impl fmt::Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let max_length = self.report.lines().map(str::len).max().unwrap_or(0);

        let table_start = "======== PM3 VERSION ========";
        let table_extension = max_length.saturating_sub(table_start.len());

        f.write_str(table_start)?;
        for _ in 0..table_extension {
            f.write_str("=")?;
        }

        write!(
            f,
            concat!(
                "\n",
                " ID: 0x{:08X}\n",
                " Section size: {}\n",
                " Report:\n\n{}\n",
            ),
            self.id, self.section_size, self.report
        )?;

        for _ in 0..(table_start.len() + table_extension) {
            f.write_str("=")?;
        }

        Ok(())
    }
}