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;
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Uid {
Single([u8; 4]),
Double([u8; 7]),
Triple([u8; 10]),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UidLength {
Single,
Double,
Triple,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CardSelect {
pub uid: Uid,
pub atqa: u16,
pub sak: u8,
}
#[derive(Debug, thiserror::Error)]
pub enum CardSelectError {
#[error("failed to read a card")]
Failure,
#[error("card uses proprietary anticollision (can't be read by ISO 14443 type A)")]
ProprietaryAnticollision,
#[error(transparent)]
Proxmark(#[from] Error),
}
impl Proxmark {
#[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 {
#[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 {
#[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,
}
}
#[must_use]
pub const fn len(&self) -> UidLength {
match self {
Uid::Single(_) => UidLength::Single,
Uid::Double(_) => UidLength::Double,
Uid::Triple(_) => UidLength::Triple,
}
}
#[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(),
}
}
#[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 {
pub const MIN: Self = Self::Single;
pub const MAX: Self = Self::Triple;
pub const ALL: [Self; 3] = [Self::Single, Self::Double, Self::Triple];
pub const BYTES_SINGLE: usize = 4;
pub const BYTES_DOUBLE: usize = 7;
pub const BYTES_TRIPLE: usize = 10;
#[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,
}
}
#[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()
}
}
}