From d1429868cefa319217089f6501cbafa503d6d6ba Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 6 Dec 2024 11:34:30 +0100 Subject: [PATCH] nrf: add NFCT NDEF example. --- examples/nrf52840/src/bin/nfct.rs | 278 ++++++++++++++++++++++++++++-- 1 file changed, 261 insertions(+), 17 deletions(-) diff --git a/examples/nrf52840/src/bin/nfct.rs b/examples/nrf52840/src/bin/nfct.rs index d559d006a..0b128c3bd 100644 --- a/examples/nrf52840/src/bin/nfct.rs +++ b/examples/nrf52840/src/bin/nfct.rs @@ -1,11 +1,12 @@ #![no_std] #![no_main] -use defmt::*; +use defmt::{todo, *}; use embassy_executor::Spawner; use embassy_nrf::config::HfclkSource; use embassy_nrf::nfct::{Config as NfcConfig, NfcId, NfcT}; use embassy_nrf::{bind_interrupts, nfct}; +use iso14443_4::{Card, IsoDep}; use {defmt_rtt as _, embassy_nrf as _, panic_probe as _}; bind_interrupts!(struct Irqs { @@ -30,12 +31,28 @@ async fn main(_spawner: Spawner) { let mut buf = [0u8; 256]; + let cc = &[ + 0x00, 0x0f, /* CCEN_HI, CCEN_LOW */ + 0x20, /* VERSION */ + 0x00, 0x7f, /* MLe_HI, MLe_LOW */ + 0x00, 0x7f, /* MLc_HI, MLc_LOW */ + /* TLV */ + 0x04, 0x06, 0xe1, 0x04, 0x00, 0x7f, 0x00, 0x00, + ]; + + let ndef = &[ + 0x00, 0x10, 0xd1, 0x1, 0xc, 0x55, 0x4, 0x65, 0x6d, 0x62, 0x61, 0x73, 0x73, 0x79, 0x2e, 0x64, 0x65, 0x76, + ]; + let mut selected: &[u8] = cc; + loop { info!("activating"); nfc.activate().await; + info!("activated!"); + + let mut nfc = IsoDep::new(iso14443_3::Logger(&mut nfc)); loop { - info!("rxing"); let n = match nfc.receive(&mut buf).await { Ok(n) => n, Err(e) => { @@ -44,25 +61,51 @@ async fn main(_spawner: Spawner) { } }; let req = &buf[..n]; - info!("received frame {:02x}", req); + info!("iso-dep rx {:02x}", req); - let mut deselect = false; - let resp = match req { - [0xe0, ..] => { - info!("Got RATS, tx'ing ATS"); - &[0x06, 0x77, 0x77, 0x81, 0x02, 0x80][..] + let Ok(apdu) = Apdu::parse(req) else { + error!("apdu parse error"); + break; + }; + + info!("apdu: {:?}", apdu); + + let resp = match (apdu.cla, apdu.ins, apdu.p1, apdu.p2) { + (0, 0xa4, 4, 0) => { + info!("select app"); + &[0x90, 0x00][..] } - [0xc2] => { - info!("Got deselect!"); - deselect = true; - &[0xc2] + (0, 0xa4, 0, 12) => { + info!("select df"); + match apdu.data { + [0xe1, 0x03] => { + selected = cc; + &[0x90, 0x00][..] + } + [0xe1, 0x04] => { + selected = ndef; + &[0x90, 0x00][..] + } + _ => todo!(), // return NOT FOUND + } + } + (0, 0xb0, p1, p2) => { + info!("read"); + let offs = u16::from_be_bytes([p1 & 0xef, p2]) as usize; + let len = if apdu.le == 0 { usize::MAX } else { apdu.le as usize }; + let n = len.min(selected.len() - offs); + buf[..n].copy_from_slice(&selected[offs..][..n]); + buf[n..][..2].copy_from_slice(&[0x90, 0x00]); + &buf[..n + 2] } _ => { info!("Got unknown command!"); - &[0xFF] + &[0xFF, 0xFF] } }; + info!("iso-dep tx {:02x}", resp); + match nfc.transmit(resp).await { Ok(()) => {} Err(e) => { @@ -70,10 +113,211 @@ async fn main(_spawner: Spawner) { break; } } - - if deselect { - break; - } + } + } +} + +#[derive(Debug, Clone, defmt::Format)] +struct Apdu<'a> { + pub cla: u8, + pub ins: u8, + pub p1: u8, + pub p2: u8, + pub data: &'a [u8], + pub le: u16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)] +struct ApduParseError; + +impl<'a> Apdu<'a> { + pub fn parse(apdu: &'a [u8]) -> Result { + if apdu.len() < 4 { + return Err(ApduParseError); + } + + let (data, le) = match apdu.len() - 4 { + 0 => (&[][..], 0), + 1 => (&[][..], apdu[4]), + n if n == 1 + apdu[4] as usize && apdu[4] != 0 => (&apdu[5..][..apdu[4] as usize], 0), + n if n == 2 + apdu[4] as usize && apdu[4] != 0 => (&apdu[5..][..apdu[4] as usize], apdu[apdu.len() - 1]), + _ => return Err(ApduParseError), + }; + + Ok(Apdu { + cla: apdu[0], + ins: apdu[1], + p1: apdu[2], + p2: apdu[3], + data, + le: le as _, + }) + } +} + +mod iso14443_3 { + use core::future::Future; + + use defmt::info; + use embassy_nrf::nfct::{Error, NfcT}; + + pub trait Card { + type Error; + async fn receive(&mut self, buf: &mut [u8]) -> Result; + async fn transmit(&mut self, buf: &[u8]) -> Result<(), Self::Error>; + } + + impl<'a, T: Card> Card for &'a mut T { + type Error = T::Error; + + fn receive(&mut self, buf: &mut [u8]) -> impl Future> { + T::receive(self, buf) + } + + fn transmit(&mut self, buf: &[u8]) -> impl Future> { + T::transmit(self, buf) + } + } + + impl<'a> Card for NfcT<'a> { + type Error = Error; + + fn receive(&mut self, buf: &mut [u8]) -> impl Future> { + self.receive(buf) + } + + fn transmit(&mut self, buf: &[u8]) -> impl Future> { + self.transmit(buf) + } + } + + pub struct Logger(pub T); + + impl Card for Logger { + type Error = T::Error; + + async fn receive(&mut self, buf: &mut [u8]) -> Result { + let n = T::receive(&mut self.0, buf).await?; + info!("<- {:02x}", &buf[..n]); + Ok(n) + } + + fn transmit(&mut self, buf: &[u8]) -> impl Future> { + info!("-> {:02x}", buf); + T::transmit(&mut self.0, buf) + } + } +} + +mod iso14443_4 { + use defmt::info; + + use crate::iso14443_3; + + #[derive(defmt::Format)] + pub enum Error { + Deselected, + Protocol, + Lower(T), + } + + pub trait Card { + type Error; + async fn receive(&mut self, buf: &mut [u8]) -> Result; + async fn transmit(&mut self, buf: &[u8]) -> Result<(), Self::Error>; + } + + pub struct IsoDep { + nfc: T, + + /// Block count spin bit: 0 or 1 + block_num: u8, + + /// true if deselected. This is permanent, you must create another IsoDep + /// instance if we get selected again. + deselected: bool, + + /// last response, in case we need to retransmit. + resp: [u8; 256], + resp_len: usize, + } + + impl IsoDep { + pub fn new(nfc: T) -> Self { + Self { + nfc, + block_num: 1, + deselected: false, + resp: [0u8; 256], + resp_len: 0, + } + } + } + + impl Card for IsoDep { + type Error = Error; + + async fn receive(&mut self, buf: &mut [u8]) -> Result { + if self.deselected { + return Err(Error::Deselected); + } + + let mut temp = [0u8; 256]; + + loop { + let n = self.nfc.receive(&mut temp).await.map_err(Error::Lower)?; + assert!(n != 0); + match temp[0] { + 0x02 | 0x03 => { + self.block_num ^= 0x01; + assert!(temp[0] == 0x02 | self.block_num); + buf[..n - 1].copy_from_slice(&temp[1..n]); + return Ok(n - 1); + } + 0xb2 | 0xb3 => { + if temp[0] & 0x01 != self.block_num { + info!("Got NAK, transmitting ACK."); + let resp = &[0xA2 | self.block_num]; + self.nfc.transmit(resp).await.map_err(Error::Lower)?; + } else { + info!("Got NAK, retransmitting."); + let resp: &[u8] = &self.resp[..self.resp_len]; + self.nfc.transmit(resp).await.map_err(Error::Lower)?; + } + } + 0xe0 => { + info!("Got RATS, tx'ing ATS"); + let resp = &[0x06, 0x77, 0x77, 0x81, 0x02, 0x80]; + self.nfc.transmit(resp).await.map_err(Error::Lower)?; + } + 0xc2 => { + info!("Got deselect!"); + self.deselected = true; + let resp = &[0xC2]; + self.nfc.transmit(resp).await.map_err(Error::Lower)?; + return Err(Error::Deselected); + } + _ => { + info!("Got unknown command {:02x}!", temp[0]); + return Err(Error::Protocol); + } + }; + } + } + + async fn transmit(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + if self.deselected { + return Err(Error::Deselected); + } + + self.resp[0] = 0x02 | self.block_num; + self.resp[1..][..buf.len()].copy_from_slice(buf); + self.resp_len = 1 + buf.len(); + + let resp: &[u8] = &self.resp[..self.resp_len]; + self.nfc.transmit(resp).await.map_err(Error::Lower)?; + + Ok(()) } } }