foxmark3 0.1.1

Send/receive Proxmark 3 commands
Documentation
//! Requests which may be sent using a raw [`Proxmark`](crate::raw::Proxmark). This includes
//! [`RequestOld`], [`RequestMix`], and [`RequestNg`], which can be created using associated
//! helper functions [`old`], [`mix`], and [`ng`]. The exact type of request used depends on
//! what the firmware expects.

use std::fmt;

use bytemuck::bytes_of;
use contracts::requires;

use crate::util::BytesExt;

use super::{
    NgPayload,
    common::{
        ByteFold, CRC, Command, FrameOld, MixShim, NgPayloadInternal, NgShim, SZ_ARGS, SZ_DATA,
    },
};

/// An **OLD** frame, sent from client to device.
pub type RequestOld = FrameOld;

/// A **MIX** frame, sent from client to device.
pub type RequestMix = RequestNgFrame<MixShim>;

/// An **NG** frame, sent from client to device.
pub type RequestNg = RequestNgFrame<NgShim>;

/// An **NG** frame, sent from client to device.
#[derive(Debug, Copy, Clone)]
pub struct RequestNgFrame<D> {
    cmd: Command,
    shim: D,
}

/// Creates a request using an **OLD** frame. The payload is automatically padded with zeroes at
/// the end to fit the frame size.
///
/// # Panics
///
/// Panics if the payload exceeds a length of [`SZ_DATA`].
#[requires(payload.as_ref().len() <= SZ_DATA, "payload size exceeds frame capabilities")]
pub fn old(cmd: Command, args: [u64; 3], payload: impl AsRef<[u8]>) -> RequestOld {
    RequestOld {
        cmd,
        args,
        payload: payload.as_ref().to_buffer(),
    }
}

/// Creates a request using a **MIX** frame.
///
/// # Panics
///
/// Panics if the payload exceeds a length of [`SZ_DATA`] - [`SZ_ARGS`].
#[requires(payload.as_ref().len() <= SZ_DATA - SZ_ARGS, "payload size exceeds frame capabilities")]
pub fn mix(cmd: Command, args: [u64; 3], payload: impl AsRef<[u8]>) -> RequestMix {
    let payload = payload.as_ref();

    RequestMix {
        cmd,
        shim: MixShim {
            args,
            len: payload.len(),
            buf: payload.to_buffer(),
        },
    }
}

/// Creates a request using an **NG** frame.
///
/// # Panics
///
/// Panics if the payload exceeds a length of [`SZ_DATA`].
#[requires(payload.as_ref().len() <= SZ_DATA, "payload size exceeds frame capabilities")]
pub fn ng(cmd: Command, payload: impl AsRef<[u8]>) -> RequestNg {
    let payload = payload.as_ref();

    RequestNg {
        cmd,
        shim: NgShim {
            len: payload.len(),
            buf: payload.to_buffer(),
        },
    }
}

/// A request, which may be sent from client to the device.
///
/// This trait may never be implemented outside of `foxmark3` types. As such, this trait is subject
/// to further additions without a breaking version change.
#[allow(private_bounds)]
pub trait Request: ByteFold + fmt::Debug + Copy + Clone {
    /// The [`Command`] of this request frame.
    fn cmd(&self) -> Command;
    /// The byte data of this request frame.
    fn payload(&self) -> &[u8];
}

impl Request for RequestOld {
    fn cmd(&self) -> Command {
        self.cmd
    }

    fn payload(&self) -> &[u8] {
        &self.payload
    }
}

impl<S: NgPayload + NgPayloadInternal> Request for RequestNgFrame<S> {
    fn cmd(&self) -> Command {
        self.cmd
    }

    fn payload(&self) -> &[u8] {
        self.shim.payload()
    }
}

// [include/pm3_cmd.h:47] #define COMMANDNG_PREAMBLE_MAGIC  0x61334d50 // PM3a
const NG_REQUEST_MAGIC: u32 = 0x61_33_4d_50;
// [include/pm3_cmd.h:47] #define COMMANDNG_POSTAMBLE_MAGIC 0x3361     // a3
const NG_REQUEST_CRC_FALLBACK: u16 = 0x33_61;

// magic + cmd(u16) + length(u16) + data + crc(u16)
pub(crate) const SZ_BUF: usize = size_of_val(&NG_REQUEST_MAGIC) + 3 * size_of::<u16>() + SZ_DATA;

impl<D: NgPayloadInternal> ByteFold for RequestNgFrame<D> {
    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:170-184] within `SendCommandNG_internal`.

        let mut digest = CRC.digest();
        let mut g = |x| {
            f(x)?;
            digest.update(x);

            Ok(())
        };
        let ng_length = self.shim.ng_length().into_bits();

        g(bytes_of(&NG_REQUEST_MAGIC))?;
        g(bytes_of(&ng_length))?;
        g(bytes_of(&self.cmd))?;

        // For some reason I can't just pass g here? or &mut g?
        self.shim.try_byte_fold(|x| {
            f(x)?;
            digest.update(x);

            Ok(())
        })?;

        // f(bytes_of(&digest.finalize()))?;
        f(bytes_of(&NG_REQUEST_CRC_FALLBACK))?;

        Ok(())
    }
}