foxmark3 0.1.1

Send/receive Proxmark 3 commands
Documentation
//! Device interface which allows execution of commands. This is an abstraction upon
//! [`crate::raw`].

pub mod capabilities;
pub mod iclass;
pub mod iso14443a;
pub mod version;

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

use tracing::{Level, error, instrument};

use crate::raw::{
    self, Command,
    common::SZ_DATA,
    device::{self, DirtyError},
    request,
};

#[cfg(target_os = "linux")]
use crate::raw::device::FindError;

pub use crate::raw::find_path;

/// Creates a new [`Proxmark`] from its serial port path.
pub fn new<'a>(path: impl Into<std::borrow::Cow<'a, str>>) -> Result<Proxmark, DirtyError> {
    raw::new(path).map(Proxmark)
}

/// Finds the only Proxmark3 connected. This is a convenience function for:
///
/// ```no_run
/// # fn main() -> Result<(), FindError> {
/// foxmark3::new(foxmark3::raw::find_path()?)?
/// # ;
/// # Ok(())
/// # }
/// ```
#[cfg(target_os = "linux")]
pub fn find() -> Result<Proxmark, FindError> {
    let ret = new(find_path()?)?;
    Ok(ret)
}

/// A Proxmark3 device capable of sending/receiving client commands.
#[must_use]
#[derive(Debug)]
pub struct Proxmark(raw::Proxmark);

/// An error produced by a [`Proxmark`].
#[must_use]
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// The device returned an unexpected response.
    #[error("response was malformed: {0:?}")]
    MalformedResponse(Box<raw::Response>),
    /// The underlying [`raw::Proxmark`] produced an error.
    #[error(transparent)]
    Raw(#[from] raw::device::Error),
}

impl Proxmark {
    /// Upconverts a [`raw::Proxmark`] to a command [`Proxmark`].
    ///
    /// # Safety
    ///
    /// The [`raw::Proxmark`] must not be executing any command at the time of upconversion,
    /// otherwise the behavior of functions on the returned [`Proxmark`] is undefined.
    pub unsafe fn from_raw(proxmark: raw::Proxmark) -> Self {
        Self(proxmark)
    }

    /// Yields the [`raw::Proxmark`] capable of sending/receiving **frames**, rather than commands.
    pub fn into_raw(self) -> raw::Proxmark {
        self.0
    }

    /// Pings the device with 512 bytes of arbitrary data, and returns the round-trip time.
    #[instrument(skip(self), level = Level::TRACE)]
    pub fn ping(&mut self) -> Result<Duration, Error> {
        // 113 is prime, so this should be a sufficient source of psuedo-psuedo-"randomness"
        #[allow(clippy::cast_possible_truncation)]
        let payload: [u8; SZ_DATA] = core::array::from_fn(|i| ((i * 113) % 256) as u8);

        let start = Instant::now();
        let resp = self
            .0
            .request_response(request::ng(Command::PING, payload), Duration::from_secs(1))?;
        let end = Instant::now();

        if resp.payload() == payload {
            Ok(end - start)
        } else {
            Err(Error::MalformedResponse(Box::new(resp)))
        }
    }

    /// Queries the status of the device. The format of the string returned is subject to the
    /// firmware of the device; `foxmark3` has no control over it.
    #[instrument(skip(self), level = Level::TRACE)]
    pub fn status(&mut self) -> Result<String, Error> {
        self.0
            .request(request::ng(Command::STATUS, []))
            .map_err(Error::from)?;

        let ret = self.0.debug_disable(|p| {
            let mut acc = String::new();

            loop {
                let resp = p.response(Duration::from_secs(2)).map_err(Error::from)?;

                match resp.cmd() {
                    Command::STATUS => break Ok(acc),
                    Command::DEBUG_PRINT_STRINGS => {
                        let text = String::from_utf8_lossy(&resp.payload()[2..]);

                        acc.push_str(&text);
                        acc.push('\n');
                    }
                    Command::DOWNLOADED_BIGBUF => {} // Ignore, these are for the speed
                    // test
                    cmd => {
                        error!(?cmd, "invalid command for status report");

                        break Err(Error::MalformedResponse(Box::new(resp)));
                    }
                }
            }
        })?;

        Ok(ret)
    }
}

impl From<device::RequestError> for Error {
    fn from(value: device::RequestError) -> Self {
        Error::Raw(value.into())
    }
}

impl From<device::ResponseError> for Error {
    fn from(value: device::ResponseError) -> Self {
        Error::Raw(value.into())
    }
}