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>();
#[derive(Debug, Clone)]
pub struct Version {
pub id: u32,
pub section_size: u32,
pub report: String,
}
impl Proxmark {
#[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(())
}
}