foxmark3 0.1.1

Send/receive Proxmark 3 commands
Documentation
//! Commands and structures for ISO/IEC 14443 type A chipsets.

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

use bytemuck::from_bytes;

use crate::{
    raw::{self, Command, common::NgPayload, request},
    util::SliceExt,
};

use super::{Error, Proxmark};

bitflags::bitflags! {
    struct Flags: u16 {
        const Connect = 1 << 0;
        const NoDisconnect = 1 << 1;
    }
}

/// A "unique identifier" produced by an ISO/IEC 14443-3 anticollision.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Uid {
    /// A 4 byte "single" UID.
    Single([u8; 4]),
    /// A 7 byte "double" UID.
    Double([u8; 7]),
    /// A 10 byte "triple" UID.
    Triple([u8; 10]),
}

/// The possible lengths of a [`Uid`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UidLength {
    /// A 4 byte "single".
    Single,
    /// A 7 byte "double".
    Double,
    /// A 10 byte "triple".
    Triple,
}

/// The data produced by an ISO/IEC 14443-3 anticollision.
///
/// [`Proxmark::read_iso14443a`] requests this data.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CardSelect {
    /// The "unique identifier."
    pub uid: Uid,
    /// The "answer to request."
    pub atqa: u16,
    /// The "select acknowledge."
    pub sak: u8,
}

/// An error encountered by [`Proxmark::read_iso14443a`].
#[derive(Debug, thiserror::Error)]
pub enum CardSelectError {
    /// No card was able to be read.
    #[error("failed to read a card")]
    Failure,
    /// The card employs a proprietary anticollision, which cannot be read by ISO/IEC 14443 type A.
    #[error("card uses proprietary anticollision (can't be read by ISO 14443 type A)")]
    ProprietaryAnticollision,
    /// The [`super::Proxmark`] encountered an error.
    #[error(transparent)]
    Proxmark(#[from] Error),
}

impl Proxmark {
    /// Performs ISO/IEC 14443-3 type A anticollision and returns the resulting card data.
    #[tracing::instrument(skip(self), level = tracing::Level::TRACE)]
    pub fn read_iso14443a(&mut self) -> Result<CardSelect, CardSelectError> {
        self.0
            .request(request::mix(
                Command::HF_ISO_14443A_READER,
                [(Flags::Connect | Flags::NoDisconnect).bits().into(), 0, 0],
                [],
            ))
            .map_err(Error::from)?;

        let resp = self
            .0
            .response_of(Command::ACK, Duration::from_millis(2500))
            .map_err(Error::from)?;

        if let raw::Response::Mix(mix) = resp {
            // [src/cmdhf14a.c:2596-2600]:
            //
            // 0: couldn't read
            // 1: OK, with ATS
            // 2: OK, no ATS
            // 3: proprietary Anticollision
            // 4: OK, SAK = no ATS but RATS possible (tested below)

            #[repr(C, packed)]
            #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
            struct Raw {
                uid: [u8; 10],
                uid_len: u8,
                atqa: [u8; 2],
                sak: u8,
                ats_len: u8,
                ats: [u8; 256],
            }

            match mix.shim.args[0] {
                0 => Err(CardSelectError::Failure),
                1 | 2 | 4 => {
                    if mix.shim.payload().len() != size_of::<Raw>() {
                        return Err(Error::MalformedResponse(Box::new(resp)).into());
                    }

                    let raw: &Raw = from_bytes(mix.shim.payload());

                    let uid = match raw.uid_len {
                        4 => Uid::Single(*raw.uid.take_array::<4>()),
                        7 => Uid::Double(*raw.uid.take_array::<7>()),
                        10 => Uid::Triple(*raw.uid.take_array::<10>()),
                        x => todo!("proper err for bad UID len {x}"),
                    };

                    Ok(CardSelect {
                        uid,
                        atqa: u16::from_le_bytes(raw.atqa),
                        sak: raw.sak,
                    })
                }
                3 => Err(CardSelectError::ProprietaryAnticollision),
                _ => Err(Error::MalformedResponse(Box::new(resp)).into()),
            }
        } else {
            Err(Error::MalformedResponse(Box::new(resp)).into())
        }
    }
}

impl Uid {
    /// Attempts to obtain a [`Uid`] from its raw byte representation
    #[must_use]
    pub fn from_slice(slice: &[u8]) -> Option<Self> {
        match slice.len() {
            UidLength::BYTES_SINGLE => Some(Uid::Single(unsafe {
                <[u8; UidLength::BYTES_SINGLE]>::try_from(slice).unwrap_unchecked()
            })),
            UidLength::BYTES_DOUBLE => Some(Uid::Double(unsafe {
                <[u8; UidLength::BYTES_DOUBLE]>::try_from(slice).unwrap_unchecked()
            })),
            UidLength::BYTES_TRIPLE => Some(Uid::Triple(unsafe {
                <[u8; UidLength::BYTES_TRIPLE]>::try_from(slice).unwrap_unchecked()
            })),
            _ => None,
        }
    }

    /// The [`UidLength`] of this [`Uid`].
    #[must_use]
    pub const fn len(&self) -> UidLength {
        match self {
            Uid::Single(_) => UidLength::Single,
            Uid::Double(_) => UidLength::Double,
            Uid::Triple(_) => UidLength::Triple,
        }
    }

    /// Obtains the bytes of this [`Uid`] as a slice.
    #[must_use]
    pub const fn as_slice(&self) -> &[u8] {
        match self {
            Uid::Single(x) => x.as_slice(),
            Uid::Double(x) => x.as_slice(),
            Uid::Triple(x) => x.as_slice(),
        }
    }

    /// Obtains the bytes of this [`Uid`] as a mutable slice.
    #[must_use]
    pub const fn as_mut_slice(&mut self) -> &mut [u8] {
        match self {
            Uid::Single(x) => x.as_mut_slice(),
            Uid::Double(x) => x.as_mut_slice(),
            Uid::Triple(x) => x.as_mut_slice(),
        }
    }
}

impl AsRef<[u8]> for Uid {
    fn as_ref(&self) -> &[u8] {
        self.as_slice()
    }
}

impl AsMut<[u8]> for Uid {
    fn as_mut(&mut self) -> &mut [u8] {
        self.as_mut_slice()
    }
}

impl UidLength {
    /// The smallest [`UidLength`].
    pub const MIN: Self = Self::Single;
    /// The largest [`UidLength`].
    pub const MAX: Self = Self::Triple;
    /// All possible [`UidLength`]s.
    pub const ALL: [Self; 3] = [Self::Single, Self::Double, Self::Triple];

    /// The number of bytes corresponding to a [`UidLength::Single`].
    pub const BYTES_SINGLE: usize = 4;
    /// The number of bytes corresponding to a [`UidLength::Double`].
    pub const BYTES_DOUBLE: usize = 7;
    /// The number of bytes corresponding to a [`UidLength::Triple`].
    pub const BYTES_TRIPLE: usize = 10;

    /// Attempts to convert a number of bytes into a [`UidLength`]
    #[must_use]
    pub const fn from_bytes(bytes: usize) -> Option<Self> {
        match bytes {
            UidLength::BYTES_SINGLE => Some(UidLength::Single),
            UidLength::BYTES_DOUBLE => Some(UidLength::Double),
            UidLength::BYTES_TRIPLE => Some(UidLength::Triple),
            _ => None,
        }
    }

    /// Provides the number of bytes corresponding to a [`Uid`] of this [`UidLength`]
    #[must_use]
    pub const fn bytes(&self) -> usize {
        match self {
            UidLength::Single => UidLength::BYTES_SINGLE,
            UidLength::Double => UidLength::BYTES_DOUBLE,
            UidLength::Triple => UidLength::BYTES_TRIPLE,
        }
    }
}

impl Display for Uid {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let slice = self.as_slice();

        write!(f, "{:02x}", slice[0])?;
        for b in &slice[1..] {
            write!(f, " {b:02x}")?;
        }

        Ok(())
    }
}

impl Display for CardSelect {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let CardSelect { uid, atqa, sak } = self;
        let [a0, a1] = u16::to_be_bytes(*atqa);

        write!(f, "[{uid} | {a0:02x} {a1:02x} | {sak:02x}]")
    }
}

#[cfg(feature = "sqlx")]
mod sqlx {
    use sqlx::{Database, Decode, Encode, Type, encode::IsNull, error::BoxDynError};

    use super::{Uid, UidLength};

    impl<DB> Type<DB> for Uid
    where
        DB: Database,
        [u8]: Type<DB>,
    {
        fn type_info() -> <DB as Database>::TypeInfo {
            <[u8]>::type_info()
        }
    }

    impl<DB> Type<DB> for UidLength
    where
        DB: Database,
        i8: Type<DB>,
    {
        fn type_info() -> <DB as Database>::TypeInfo {
            <i8>::type_info()
        }
    }

    impl<'r, DB> Decode<'r, DB> for Uid
    where
        DB: Database,
        &'r [u8]: Decode<'r, DB>,
    {
        fn decode(value: <DB as sqlx::Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
            let slice = <&[u8]>::decode(value)?;
            let ret = Uid::from_slice(slice).ok_or_else(|| {
                format!(
                    "blob has invalid length for ISO 14443A UID: {} bytes",
                    slice.len()
                )
            })?;

            Ok(ret)
        }
    }

    impl<'q, DB> Encode<'q, DB> for Uid
    where
        DB: Database,
        Vec<u8>: Encode<'q, DB>,
    {
        fn encode(
            self,
            buf: &mut <DB as Database>::ArgumentBuffer<'q>,
        ) -> Result<IsNull, BoxDynError>
        where
            Self: Sized,
        {
            self.as_slice().to_vec().encode(buf)
        }

        fn encode_by_ref(
            &self,
            buf: &mut <DB as Database>::ArgumentBuffer<'q>,
        ) -> Result<IsNull, BoxDynError> {
            (*self).encode(buf)
        }

        fn size_hint(&self) -> usize {
            self.as_slice().len()
        }
    }

    impl<'r, DB> Decode<'r, DB> for UidLength
    where
        DB: Database,
        i8: Decode<'r, DB>,
    {
        fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
            let byte = <i8>::decode(value)?;
            let ret = UidLength::from_bytes(byte as usize).ok_or_else(|| {
                format!("integer has invalid value for ISO 14443A UID length: {byte}")
            })?;

            Ok(ret)
        }
    }

    impl<DB> Encode<'_, DB> for UidLength
    where
        DB: Database,
        for<'q> i8: Encode<'q, DB>,
    {
        fn encode_by_ref(
            &self,
            buf: &mut <DB as Database>::ArgumentBuffer<'_>,
        ) -> Result<IsNull, BoxDynError> {
            (self.bytes() as i8).encode(buf)
        }

        fn size_hint(&self) -> usize {
            (self.bytes() as i8).size_hint()
        }
    }
}