foxmark3 0.1.0

Send/receive Proxmark 3 commands
Documentation
//! Data packets sent between the client (on computer) and the device (the Proxmark3).
//!
//! Requests are sent from the client to the device, whereas responses are sent from the device to
//! the client.

use std::fmt;

use bytemuck::bytes_of;

// [include/pm3_cmd.h:28] #define PM3_CMD_DATA_SIZE 512
/// The size of the byte buffer in all frame types (**OLD**/**MIX**/**NG**).
///
/// - For **NG** frames, this corresponds to the maximum payload size.
/// - For **MIX** frames, since the byte buffer also contains the frame arguments, this corresponds
///   to the maximum payload size plus [`SZ_ARGS`].
/// - For **OLD** frames, this corresponds to the payload size.
pub const SZ_DATA: usize = 512;

// [include/pm3_cmd.h:29] #define PM3_CMD_DATA_SIZE_MIX ( PM3_CMD_DATA_SIZE - 3 * sizeof(uint64_t) )
/// The size of arguments in a **MIX** frame.
pub const SZ_ARGS: usize = size_of::<[u64; 3]>();

// [client/src/comms.c:180] compute_crc(CRC_14443_A, (uint8_t *)&txBufferNG, sizeof(PacketCommandNGPreamble) + len, &first, &second);
pub(super) const CRC: crc::Crc<u16> = crc::Crc::<u16>::new(&crc::CRC_16_ISO_IEC_14443_3_A);

/// A command code sent to/from the device.
///
/// A minimal set of commands supported by Proxmark3 firmware are pre-defined as constants. For
/// other command codes, consider using [`Command::from`] with the `u16` command code defined in
/// the [original header file](https://github.com/Proxmark/proxmark3/blob/118eca80df79f9bb6604f896faafeb2fd425893e/include/usb_cmd.h#L60)
#[derive(
    Copy,
    Clone,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    bytemuck::Pod,
    bytemuck::Zeroable,
    derive_more::From,
    derive_more::Into,
)]
#[repr(C)]
pub struct Command(u16);

macro_rules! commands {
    ($($name_camel:ident, $name_snake:ident = $value:literal),+ $(,)?) => {
        impl Command {
            $(
                #[doc = concat!("Command code for ", stringify!($name_snake))]
                pub const $name_snake: Command = Command($value);
            )+

            /// The name of this command. If the command code is not defined as a constant on
            /// [`Command`], [`None`] is returned.
            pub fn name(&self) -> Option<&'static str> {
                match self.0 {
                    $(
                        $value => Some(stringify!($name_snake)),
                    )+
                        _ => None

                }
            }
        }
    };
}

// [pm3_cmd.h]
commands! {
    Ack, ACK = 0x00FF,

    DebugPrintStrings, DEBUG_PRINT_STRINGS = 0x0100,
    DebugPrintIntegers, DEBUG_PRINT_INTEGERS = 0x0101,
    Version, VERSION = 0x0107,
    Status, STATUS = 0x0108,
    Ping, PING = 0x0109,
    Capabilities, CAPABILITIES = 0x0112,
    QuitSession, QUIT_SESSION = 0x0113,
    Wtx, WTX = 0x0116,

    DownloadedBigBuf, DOWNLOADED_BIGBUF = 0x0208,

    HfIso14443aReader, HF_ISO_14443A_READER = 0x0385,
    HfIClassReader, HF_ICLASS_READER = 0x0394,

}

impl fmt::Debug for Command {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(name) = self.name() {
            write!(f, "{name}")?;
        } else {
            write!(f, "????")?;
        }

        write!(f, "(0x{:04X})", self.0)?;

        Ok(())
    }
}

#[bitfield_struct::bitfield(u16)]
pub(super) struct NgLength {
    #[bits(15)]
    pub length: u16,
    pub ng: bool,
}

/// An inverse of [`std::io::Write`]
pub(super) trait ByteFold {
    fn byte_fold<F>(&self, mut f: F)
    where
        F: FnMut(&[u8]),
    {
        let _ = self.try_byte_fold(|bytes| {
            f(bytes);
            Ok::<(), ()>(())
        });
    }

    fn try_byte_fold<F, E>(&self, f: F) -> Result<(), E>
    where
        F: FnMut(&[u8]) -> Result<(), E>;

    fn byte_fold_to<'a>(&self, buf: &'a mut [u8]) -> &'a mut [u8] {
        let mut head = 0;

        self.byte_fold(|bytes| {
            buf[head..][..bytes.len()].copy_from_slice(bytes);
            head += bytes.len();
        });

        &mut buf[..head]
    }
}

pub(super) trait NgPayloadInternal: ByteFold + fmt::Debug + Copy + Clone {
    fn ng_length(&self) -> NgLength;
}

/// An **OLD** frame. May be sent in either direction.
#[derive(Debug, Copy, Clone)]
pub struct FrameOld {
    /// The command code of this frame. The corresponds to the action the device should take,
    /// whether idle or mid-command but awaiting further instruction.
    pub cmd: Command,
    /// The "arguments" of this frame. The use of field, including what values are valid, differs depending on the associated [`FrameOld::cmd`] and state of the device.
    pub args: [u64; 3],
    /// The payload of this frame. The use of field, including what values are valid, differs depending on the associated [`FrameOld::cmd`] and state of the device.
    pub payload: [u8; SZ_DATA],
}

impl ByteFold for FrameOld {
    fn try_byte_fold<F, E>(&self, mut f: F) -> Result<(), E>
    where
        F: FnMut(&[u8]) -> Result<(), E>,
    {
        // Mirrors the logic from [client/src/comms.c:94-106] within `SendCommandOLD`.

        f(bytes_of(&self.cmd))?;
        f(bytes_of(&self.args))?;
        f(&self.payload)?;

        Ok(())
    }
}

/// A datum which can be used with a [`RequestNgFrame`](crate::raw::request::RequestNgFrame)/[`ResponseNgFrame`](crate::raw::response::ResponseNgFrame).
pub trait NgPayload {
    /// The data of the payload, whose exact size depends on implementation.
    #[must_use]
    fn payload(&self) -> &[u8];
}

/// An [`NgPayload`] whose use results in a [`RequestMix`](crate::raw::request::RequestMix)/[`ResponseMix`](crate::raw::response::ResponseMix).
#[derive(Copy, Clone)]
pub struct MixShim {
    /// The "arguments" of this frame. The use of field, including what values are valid, differs depending on the frame command and state of the device.
    pub args: [u64; 3],
    pub(super) len: usize,
    pub(super) buf: [u8; SZ_DATA - SZ_ARGS],
}

impl fmt::Debug for MixShim {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("MixShim")
            .field("args", &self.args)
            .field("payload", &self.payload())
            .finish_non_exhaustive()
    }
}

impl NgPayload for MixShim {
    fn payload(&self) -> &[u8] {
        &self.buf[..self.len]
    }
}

impl ByteFold for MixShim {
    fn try_byte_fold<F, E>(&self, mut f: F) -> Result<(), E>
    where
        F: FnMut(&[u8]) -> Result<(), E>,
    {
        f(bytes_of(&self.args))?;
        f(self.payload())?;

        Ok(())
    }
}

impl NgPayloadInternal for MixShim {
    fn ng_length(&self) -> NgLength {
        NgLength::new().with_ng(false).with_length(
            (SZ_ARGS + self.len)
                .try_into()
                .expect("payload length within u16"),
        )
    }
}

/// An [`NgPayload`] whose use results in a [`RequestNg`](crate::raw::request::RequestNg)/[`ResponseNg`](crate::raw::response::ResponseNg).
#[derive(Copy, Clone)]
pub struct NgShim {
    pub(super) len: usize,
    pub(super) buf: [u8; SZ_DATA],
}

impl fmt::Debug for NgShim {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("NgShim")
            .field("payload", &self.payload())
            .finish_non_exhaustive()
    }
}

impl NgPayload for NgShim {
    fn payload(&self) -> &[u8] {
        &self.buf[..self.len]
    }
}

impl ByteFold for NgShim {
    fn try_byte_fold<F, E>(&self, mut f: F) -> Result<(), E>
    where
        F: FnMut(&[u8]) -> Result<(), E>,
    {
        f(self.payload())
    }
}

impl NgPayloadInternal for NgShim {
    fn ng_length(&self) -> NgLength {
        NgLength::new()
            .with_ng(true)
            .with_length(self.len.try_into().expect("payload length within u16"))
    }
}