Refactor pn532

This commit is contained in:
2026-09-15 15:36:29 +08:00
parent 70194f98c1
commit a21e72d668
33 changed files with 524 additions and 433 deletions
+8 -8
View File
@@ -22,7 +22,7 @@ Every command and every response is one *information frame* of bytes:
```
| Field | Size | Value / meaning |
|-------|------|-----------------|
|------------|------|----------------------------------------------------------------------------------------------|
| Preamble | 1 | `0x00` (see §1.4 for length rules) |
| Start code | 2 | `0x00 0xFF` |
| LEN | 1 | Length of the *data field* = 1 (TFI) + N (payload bytes). `0x01..0xFF`. |
@@ -63,7 +63,7 @@ frame's 255-byte limit it uses an *extended* frame:
### 1.2 Frame Identifier (TFI)
| TFI | Direction |
|-----|-----------|
|--------|--------------------------------|
| `0xD4` | Host → PN532 |
| `0xD5` | PN532 → Host |
| `0x7F` | Error frame (PN532 → host, §2) |
@@ -73,7 +73,7 @@ frame's 255-byte limit it uses an *extended* frame:
The response echoes the command code **+ 1**:
| Command (host→PN532) | Response (PN532→host) |
|----------------------|----------------------|
|------------------------------|-----------------------|
| `GetFirmwareVersion` `0x02` | `0x03` |
| `SAMConfiguration` `0x14` | `0x15` |
| `InListPassiveTarget` `0x4A` | `0x4B` |
@@ -148,7 +148,7 @@ NADPresent MI Error code
Error code list:
| Code | Cause |
|------|-------|
|--------|-------------------------------------------------------------------------|
| `0x00` | OK |
| `0x01` | Time out — target did not answer |
| `0x02` | CRC error detected by the CIU |
@@ -232,7 +232,7 @@ Each command is the first payload byte (PD0) after the `0xD4` TFI. `In` = initia
`Tg` = target.
| Command | Code | Parameters |
|---------|------|-----------|
|-------------------------|--------|--------------------------------------|
| `Diagnose` | `0x00` | NumTst (1), [InParam…] |
| `GetFirmwareVersion` | `0x02` | — |
| `GetGeneralStatus` | `0x04` | — |
@@ -382,7 +382,7 @@ Response (`0x07`) with value `0x84`:
These are the payload bytes passed inside `InDataExchange` / `InCommunicateThru`.
| Operation | Card command |
|-----------|--------------|
|-------------------------|-----------------------------------------|
| Authenticate key A / B | `0x60` / `0x61` + block + UID + key (6) |
| Read block | `0x30` + block |
| Write block | `0xA0` + block + 16 data bytes |
@@ -404,7 +404,7 @@ D4 12 Flags
```
| Bit | Flag | Meaning |
|-----|------|---------|
|-----|-----------------------|--------------------------------------------------|
| 0 | `fNADUsed` | Use NAD in DEP / 14443-4 PCD |
| 1 | `fDIDUsed` | Use DID (DEP) / CID (14443-4 PCD) |
| 2 | `fAutomaticATR_RES` | Auto-generate ATR_RES in target mode |
@@ -421,7 +421,7 @@ The firmware drives a *Contactless Interface Unit* (CIU) whose commands live in
`WriteRegister`/`ReadRegister`. Listed for completeness:
| Command | Code | Action |
|---------|------|--------|
|------------------|--------|------------------------------------------------------|
| Idle | `0000` | No action; cancel current command |
| Config | `0001` | Configure CIU for FeliCa / MIFARE / NFCIP-1 |
| GenerateRandomID | `0010` | Generate 10-byte random ID |
+1 -1
View File
@@ -12,5 +12,5 @@ pub enum Command {
RequestResponse = 0x04,
ReadWithoutEncryption = 0x06,
WriteWithEncryption = 0x08,
RequestSystemCode = 0x0C
RequestSystemCode = 0x0C,
}
+1 -1
View File
@@ -16,5 +16,5 @@ pub enum Command {
Transfer = 0xB0,
Decrement = 0xC0,
Increment = 0xC1,
Store = 0xC2
Store = 0xC2,
}
+2 -2
View File
@@ -4,7 +4,7 @@ use crate::commands::*;
use crate::error::Error;
use crate::interface::Interface;
use crate::types::constants::{PN532_GPIO_P32, PN532_GPIO_P34, PN532_GPIO_VALIDATION_BIT};
use crate::{StatusCode};
use crate::StatusCode;
use core::marker::PhantomData;
use core::time::Duration;
use embedded_hal::delay::DelayNs;
@@ -53,7 +53,7 @@ where
Err(Error::UnusedPin) => {
crate::debug!("Unused pin");
Ok(())
},
}
Err(e) => Err(e),
}
}
+5 -3
View File
@@ -1,6 +1,6 @@
use core::fmt::Debug;
use bitfields::bitflag;
use crate::protocol::felica;
use bitfields::bitflag;
use core::fmt::Debug;
/// Driver-level error type, generic over the transport (I2C) error.
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
@@ -31,6 +31,8 @@ pub enum Error<E> {
InvalidBaudRate,
#[error("Unused pin")]
UnusedPin,
#[error("Error while interacting with IO Pin")]
IOPinError,
#[cfg(feature = "felica")]
#[error(transparent)]
FelicaError(#[from] felica::error::Error),
@@ -68,5 +70,5 @@ pub enum StatusCode {
MismatchUd = 0x2C,
OverCurrent = 0x2D,
NADMissing = 0x2E,
Max = 0x2F
Max = 0x2F,
}
+13 -23
View File
@@ -1,8 +1,8 @@
use crate::types::constants::*;
use crate::{Error, Interface, NoIrq, NoReset, PN532Command};
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
use embedded_hal::i2c::{I2c, Operation};
use crate::{Error, Interface, NoIrq, NoReset, PN532Command};
use crate::types::constants::*;
/// [`Interface`] implementation over a blocking `embedded_hal::i2c::I2c` bus.
///
@@ -146,13 +146,17 @@ where
/// Wait until the PN532 signals data is ready.
fn wait_ready(&mut self, timeout_ms: u16) -> Result<(), Error<I2C::Error>> {
let mut elapsed = 0u16;
loop {
match self.irq.is_low() {
Ok(true) => {
let mut status = [0u8; 1];
match self.i2c.read(PN532_I2C_ADDRESS, &mut status) {
Ok(()) if status[0] & 1 == 1 => return Ok(()),
_ => {
loop {
let Ok(is_irq_low) = self.irq.is_low() else {
return Err(Error::IOPinError);
};
if is_irq_low
&& self.i2c.read(PN532_I2C_ADDRESS, &mut status).is_ok()
&& status[0] & 1 == 1
{
return Ok(());
}
self.delay.delay_ms(1);
elapsed += 1;
if timeout_ms != 0 && elapsed >= timeout_ms {
@@ -160,17 +164,6 @@ where
}
}
}
}
Ok(false) | Err(_) => {
self.delay.delay_ms(1);
elapsed += 1;
if timeout_ms != 0 && elapsed >= timeout_ms {
return Err(Error::Timeout);
}
}
}
}
}
fn read_ack_frame(&mut self) -> Result<(), Error<I2C::Error>> {
self.wait_ready(PN532_ACK_WAIT_TIME_MS)?;
@@ -178,10 +171,7 @@ where
self.i2c
.transaction(
PN532_I2C_ADDRESS,
&mut [
Operation::Read(&mut [0]),
Operation::Read(&mut ack),
],
&mut [Operation::Read(&mut [0]), Operation::Read(&mut ack)],
)
.map_err(Error::Transport)?;
if ack == ACK {
+22 -8
View File
@@ -10,10 +10,13 @@ mod i2c;
use embedded_hal::delay::DelayNs;
pub use i2c::I2cInterface;
use embedded_hal::digital::{ErrorKind, ErrorType, InputPin, OutputPin};
use crate::error::Error;
use crate::types::constants::{
MAX_DATA_LEN, PN532_HOST_TO_PN532, PN532_PN532_TO_HOST, PN532_POSTAMBLE, PN532_PREAMBLE,
PN532_START_CODE_1, PN532_START_CODE_2,
};
use crate::PN532Command;
use crate::types::constants::{MAX_DATA_LEN, PN532_HOST_TO_PN532, PN532_PN532_TO_HOST, PN532_POSTAMBLE, PN532_PREAMBLE, PN532_START_CODE_1, PN532_START_CODE_2};
use embedded_hal::digital::{ErrorKind, ErrorType, InputPin, OutputPin};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct UnreachError;
@@ -28,7 +31,9 @@ impl embedded_hal::digital::Error for UnreachError {
#[derive(Clone, Copy, Debug, Default)]
pub struct NoReset;
impl ErrorType for NoReset { type Error = UnreachError; }
impl ErrorType for NoReset {
type Error = UnreachError;
}
impl OutputPin for NoReset {
fn set_low(&mut self) -> Result<(), Self::Error> {
@@ -44,15 +49,17 @@ impl OutputPin for NoReset {
#[derive(Clone, Copy, Debug, Default)]
pub struct NoIrq;
impl ErrorType for NoIrq { type Error = UnreachError; }
impl ErrorType for NoIrq {
type Error = UnreachError;
}
impl InputPin for NoIrq {
fn is_high(&mut self) -> Result<bool, Self::Error> {
panic!("NoIrq should not be used");
Err(UnreachError)
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
panic!("NoIrq should not be used");
Err(UnreachError)
}
}
@@ -99,7 +106,10 @@ pub trait Interface<RST: OutputPin, IRQ: InputPin, DELAY: DelayNs> {
) -> Result<usize, Error<Self::TransportError>>;
}
pub(crate) fn to_request_frame<E>(header: &[u8], body: &[u8]) -> Result<heapless::Vec<u8, MAX_DATA_LEN>, Error<E>> {
pub(crate) fn to_request_frame<E>(
header: &[u8],
body: &[u8],
) -> Result<heapless::Vec<u8, MAX_DATA_LEN>, Error<E>> {
let data_len = header.len() + body.len() + 1; // TFI + payload
if data_len > 0xFF {
return Err(Error::NoSpace);
@@ -128,7 +138,11 @@ pub(crate) fn to_request_frame<E>(header: &[u8], body: &[u8]) -> Result<heapless
Ok(frame)
}
pub(crate) fn from_response_frame<E>(command: PN532Command, frame: &[u8], buf: &mut [u8]) -> Result<usize, Error<E>> {
pub(crate) fn from_response_frame<E>(
command: PN532Command,
frame: &[u8],
buf: &mut [u8],
) -> Result<usize, Error<E>> {
if frame[0] != PN532_PREAMBLE
|| frame[1] != PN532_START_CODE_1
|| frame[2] != PN532_START_CODE_2
+2 -3
View File
@@ -46,12 +46,12 @@
extern crate alloc;
pub mod commands;
pub(crate) mod driver;
pub mod error;
pub mod interface;
pub(crate) mod driver;
pub mod protocol;
pub mod tg;
pub mod types;
pub mod protocol;
pub use commands::*;
pub use driver::*;
@@ -87,4 +87,3 @@ macro_rules! debug {
macro_rules! trace {
($($arg:tt)+) => {};
}
+30 -22
View File
@@ -1,22 +1,27 @@
use crate::types::felica::felica_lite_s::auth::{
AuthSession, CardKey, MacInput, RandomChallenge, SessionKeys,
};
use crate::types::felica::{CardSession, FelicaLiteSBlock, FelicaLiteSServiceCode};
use crate::{Interface, Pn532};
use des::cipher::{Block, BlockCipherEncrypt, Key, KeyInit};
use des::TdesEde2;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
use rand_core::Rng;
use crate::{Interface, Pn532};
use crate::types::felica::{CardSession, FelicaLiteSBlock, FelicaLiteSServiceCode};
use crate::types::felica::felica_lite_s::auth::{AuthSession, CardKey, MacInput, RandomChallenge, SessionKeys};
impl<RST, IRQ, DELAY, B, const N: usize> Pn532<RST, IRQ, DELAY, B, N>
where
RST: OutputPin,
IRQ: InputPin,
DELAY: DelayNs,
B: Interface<RST, IRQ, DELAY>
B: Interface<RST, IRQ, DELAY>,
{
pub(crate) fn set_random_challenge(&mut self, card: &CardSession, rng: &mut impl Rng)
-> Result<RandomChallenge, crate::Error<B::TransportError>>
{
/// Write value to [`FelicaLiteSBlock::RC`]
pub(crate) fn set_random_challenge(
&mut self,
card: &CardSession,
rng: &mut impl Rng,
) -> Result<RandomChallenge, crate::Error<B::TransportError>> {
let mut data = [[0u8; 16]];
rng.fill_bytes(&mut data[0]);
self.felica_write_without_encryption(
@@ -29,6 +34,7 @@ where
Ok(rc)
}
/// Get [`SessionKeys`]
pub(crate) fn get_session_keys(
&mut self,
rc: &RandomChallenge,
@@ -44,7 +50,6 @@ where
/// 2-key Triple DES-CBC MAC over `block_list` followed by `block_data`.
pub(crate) fn compute_mac(
&mut self,
session: &AuthSession,
key_bytes: [u8; 16],
block_list: &[u8; 8],
@@ -90,17 +95,14 @@ where
}
/// Compute the MAC_A value for a Write With MAC operation.
pub(crate) fn compute_write_mac(
&mut self,
session: &AuthSession,
plain: &[u8; 24],
) -> [u8; 8] {
pub(crate) fn compute_write_mac(&mut self, session: &AuthSession, plain: &[u8; 24]) -> [u8; 8] {
let mut header = [0u8; 8];
header.copy_from_slice(&plain[0..8]);
self.compute_mac(session, session.sk.as_des_write(), &header, &plain[8..24])
Self::compute_mac(session, session.sk.as_des_write(), &header, &plain[8..24])
}
pub(crate) fn check_auth(
/// Check if [`AuthSession`] is valid
pub(crate) fn check_session(
&mut self,
card: &CardSession,
session: &AuthSession,
@@ -109,14 +111,20 @@ where
self.felica_read_without_encryption(
card,
&[FelicaLiteSServiceCode::Read],
&[FelicaLiteSBlock::ID, FelicaLiteSBlock::CKV, FelicaLiteSBlock::MACA],
&mut data
&[
FelicaLiteSBlock::ID,
FelicaLiteSBlock::CKV,
FelicaLiteSBlock::MACA,
],
&mut data,
)?;
let block_list = &[FelicaLiteSBlock::ID, FelicaLiteSBlock::CKV, FelicaLiteSBlock::MACA];
let input = MacInput::new(
&data, block_list
);
let mac = self.compute_mac(
let block_list = &[
FelicaLiteSBlock::ID,
FelicaLiteSBlock::CKV,
FelicaLiteSBlock::MACA,
];
let input = MacInput::new(&data, block_list);
let mac = Self::compute_mac(
session,
session.sk.as_des(),
&input.block_list(),
@@ -1,103 +1,105 @@
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
use crate::{Error, Interface, Pn532};
use crate::types::felica::{CardSession, FelicaLiteSBlock};
use crate::types::felica::felica_lite_s::auth::{AuthSession, CardKey, CardKeyVersion};
use crate::types::felica::felica_lite_s::config::MemoryConfig;
use crate::types::felica::{CardSession, FelicaLiteSBlock};
use crate::{Error, Interface, Pn532};
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
impl<RST, IRQ, DELAY, B, const N: usize> Pn532<RST, IRQ, DELAY, B, N>
where
RST: OutputPin,
IRQ: InputPin,
DELAY: DelayNs,
B: Interface<RST, IRQ, DELAY>
B: Interface<RST, IRQ, DELAY>,
{
pub fn set_id(&mut self, card: &CardSession, arbitrary: [u8; 6], dfc: Option<[u8; 2]>) -> Result<(), Error<B::TransportError>> {
/// set value for [`FelicaLiteSBlock::ID`]
pub fn set_id(
&mut self,
card: &CardSession,
arbitrary: [u8; 6],
dfc: Option<[u8; 2]>,
) -> Result<(), Error<B::TransportError>> {
let dfc = dfc.unwrap_or_default();
let mut block = [0u8; 16];
block[8] = dfc[0];
block[9] = dfc[1];
block[10..16].copy_from_slice(&arbitrary);
self.write_block(
card,
&[FelicaLiteSBlock::ID],
&[block]
)
self.write_block(card, &[FelicaLiteSBlock::ID], &[block])
}
/// get value for [`FelicaLiteSBlock::ID`]
pub fn get_id(&mut self, card: &CardSession) -> Result<[u8; 16], Error<B::TransportError>> {
let mut block = [[0u8; 16]];
self.read_block(
card,
&[FelicaLiteSBlock::ID],
&mut block
)?;
self.read_block(card, &[FelicaLiteSBlock::ID], &mut block)?;
crate::trace!("ID block: {:02X}", block);
Ok(block[0])
}
pub fn set_ck(&mut self, card: &CardSession, key: &CardKey) -> Result<(), Error<B::TransportError>> {
self.write_block(
card,
&[FelicaLiteSBlock::CK],
&[**key]
)
/// set value for [`FelicaLiteSBlock::CK`]
pub fn set_ck(
&mut self,
card: &CardSession,
key: &CardKey,
) -> Result<(), Error<B::TransportError>> {
self.write_block(card, &[FelicaLiteSBlock::CK], &[**key])
}
pub fn set_ck_with_mac(&mut self, card: &CardSession, session: &AuthSession, key: &CardKey) -> Result<(), Error<B::TransportError>> {
self.write_with_mac(
card,
session,
FelicaLiteSBlock::CK,
&**key
)
/// set value for [`FelicaLiteSBlock::CK`] with MAC
///
/// see [`Pn532::try_authenticate`] to obtain a [`AuthSession`]
pub fn set_ck_with_mac(
&mut self,
card: &CardSession,
session: &AuthSession,
key: &CardKey,
) -> Result<(), Error<B::TransportError>> {
self.write_with_mac(card, session, FelicaLiteSBlock::CK, &**key)
}
pub fn set_ckv(&mut self, card: &CardSession, version: &CardKeyVersion) -> Result<(), Error<B::TransportError>> {
self.write_block(
card,
&[FelicaLiteSBlock::CKV],
&[version.as_block()]
)
/// set value for [`FelicaLiteSBlock::CKV`]
pub fn set_ckv(
&mut self,
card: &CardSession,
version: &CardKeyVersion,
) -> Result<(), Error<B::TransportError>> {
self.write_block(card, &[FelicaLiteSBlock::CKV], &[version.as_block()])
}
pub fn set_ckv_with_mac(&mut self, card: &CardSession, session: &AuthSession, version: &CardKeyVersion) -> Result<(), Error<B::TransportError>> {
self.write_with_mac(
card,
session,
FelicaLiteSBlock::CKV,
&version.as_block()
)
/// set value for [`FelicaLiteSBlock::CKV`] with MAC
///
/// see [`Pn532::try_authenticate`] to obtain a [`AuthSession`]
pub fn set_ckv_with_mac(
&mut self,
card: &CardSession,
session: &AuthSession,
version: &CardKeyVersion,
) -> Result<(), Error<B::TransportError>> {
self.write_with_mac(card, session, FelicaLiteSBlock::CKV, &version.as_block())
}
pub fn get_ckv(&mut self, card: &CardSession) -> Result<CardKeyVersion, Error<B::TransportError>> {
pub fn get_ckv(
&mut self,
card: &CardSession,
) -> Result<CardKeyVersion, Error<B::TransportError>> {
let mut block = [[0u8; 16]];
self.read_block(
card,
&[FelicaLiteSBlock::CKV],
&mut block
)?;
self.read_block(card, &[FelicaLiteSBlock::CKV], &mut block)?;
crate::trace!("CKV block: {:02X}", block);
Ok(CardKeyVersion::from(block[0]))
}
pub fn get_mc(&mut self, card: &CardSession) -> Result<MemoryConfig, Error<B::TransportError>> {
let mut block = [[0u8; 16]];
self.read_block(
card,
&[FelicaLiteSBlock::MemoryConfig],
&mut block
)?;
self.read_block(card, &[FelicaLiteSBlock::MemoryConfig], &mut block)?;
crate::trace!("MC block: {:02X}", block[0]);
Ok(MemoryConfig::from(block[0]))
}
pub fn set_mc(&mut self, card: &CardSession, config: MemoryConfig) -> Result<(), Error<B::TransportError>> {
pub fn set_mc(
&mut self,
card: &CardSession,
config: MemoryConfig,
) -> Result<(), Error<B::TransportError>> {
let block: [u8; 16] = config.into();
self.write_block(
card,
&[FelicaLiteSBlock::MemoryConfig],
&[block]
)
self.write_block(card, &[FelicaLiteSBlock::MemoryConfig], &[block])
}
}
+32 -27
View File
@@ -1,28 +1,32 @@
pub mod issuance;
pub mod auth;
pub mod issuance;
use crate::protocol::felica::error::Error;
use crate::types::felica::felica_lite_s::auth::{AuthSession, CardKey, MacInput};
use crate::types::felica::{AsBlock, CardSession, FelicaLiteSBlock, FelicaLiteSServiceCode};
use crate::{Interface, Pn532};
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
use rand_core::Rng;
use crate::{Interface, Pn532};
use crate::protocol::felica::error::Error;
use crate::types::felica::{AsBlock, CardSession, FelicaLiteSBlock, FelicaLiteSServiceCode};
use crate::types::felica::felica_lite_s::auth::{AuthSession, CardKey, MacInput};
impl<RST, IRQ, DELAY, B, const N: usize> Pn532<RST, IRQ, DELAY, B, N>
where
RST: OutputPin,
IRQ: InputPin,
DELAY: DelayNs,
B: Interface<RST, IRQ, DELAY>
B: Interface<RST, IRQ, DELAY>,
{
pub fn try_authenticate(&mut self, rng: &mut impl Rng, card: &CardSession, key: &CardKey)
-> Result<Option<AuthSession>, crate::Error<B::TransportError>>
{
/// Obtain a [`AuthSession`] and set [`FelicaLiteSBlock::STATE`] EXT_AUTH to 1
pub fn try_authenticate(
&mut self,
rng: &mut impl Rng,
card: &CardSession,
key: &CardKey,
) -> Result<Option<AuthSession>, crate::Error<B::TransportError>> {
let rc = self.set_random_challenge(card, rng)?;
let sk = self.get_session_keys(&rc, key)?;
let session = AuthSession::new(sk, rc);
let is_valid_auth = self.check_auth(card, &session)?;
let is_valid_auth = self.check_session(card, &session)?;
if !is_valid_auth {
return Ok(None);
@@ -31,17 +35,20 @@ where
Ok(Some(session))
}
/// Read a block with MAC
///
/// see [`Pn532::try_authenticate`] to obtain a [`AuthSession`]
pub fn read_with_mac(
&mut self,
card: &CardSession,
session: &AuthSession,
block: impl AsBlock
block: impl AsBlock,
) -> Result<[u8; 16], crate::Error<B::TransportError>> {
let mut block_data = [[0u8; 16]; 2];
let block_list = [block.as_block(), FelicaLiteSBlock::MACA as u16];
self.read_block(card, &block_list, &mut block_data)?;
let input = MacInput::new(&block_data, &block_list);
let mac = self.compute_mac(
let mac = Self::compute_mac(
session,
session.sk.as_des(),
&input.block_list(),
@@ -54,7 +61,9 @@ where
Ok(block_data[0])
}
/// Write one data block protected by a Write With MAC (manual §5.4.4).
/// Write a block with MAC
///
/// see [`Pn532::try_authenticate`] to obtain a [`AuthSession`]
pub fn write_with_mac(
&mut self,
card: &CardSession,
@@ -95,31 +104,27 @@ where
self.write_with_mac(card, session, FelicaLiteSBlock::STATE, &state)
}
/// read a raw block
///
/// see [`Pn532::read_with_mac`] to read with MAC
pub fn read_block<const U: usize>(
&mut self,
card: &CardSession,
list: &[impl AsBlock; U],
data: &mut [[u8; 16]; U]
data: &mut [[u8; 16]; U],
) -> Result<(), crate::Error<B::TransportError>> {
self.felica_read_without_encryption(
card,
&[FelicaLiteSServiceCode::Read],
list,
data
)
self.felica_read_without_encryption(card, &[FelicaLiteSServiceCode::Read], list, data)
}
/// write a raw block
///
/// see [`Pn532::write_with_mac`] to write with MAC
pub fn write_block<const U: usize>(
&mut self,
card: &CardSession,
list: &[impl AsBlock; U],
data: &[[u8; 16]; U]
data: &[[u8; 16]; U],
) -> Result<(), crate::Error<B::TransportError>> {
self.felica_write_without_encryption(
card,
&[FelicaLiteSServiceCode::Write],
list,
data
)
self.felica_write_without_encryption(card, &[FelicaLiteSServiceCode::Write], list, data)
}
}
+54 -42
View File
@@ -1,32 +1,55 @@
#[cfg(feature="felica-lite-s")]
mod felica_lite_s;
pub mod error;
#[cfg(feature = "felica-lite-s")]
mod felica_lite_s;
use error::Error as FelicaError;
use crate::commands::*;
use crate::driver::Pn532;
use crate::error::Error;
use crate::interface::Interface;
use crate::types::felica::constants::{
FELICA_READ_MAX_BLOCK_NUM, FELICA_READ_MAX_SERVICE_NUM, FELICA_WRITE_MAX_BLOCK_NUM,
FELICA_WRITE_MAX_SERVICE_NUM,
};
use crate::types::felica::*;
use crate::types::BaudRate;
use core::time::Duration;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::{InputPin, OutputPin};
use crate::types::felica::*;
use crate::types::BaudRate;
use crate::types::felica::constants::{FELICA_READ_MAX_BLOCK_NUM, FELICA_READ_MAX_SERVICE_NUM, FELICA_WRITE_MAX_BLOCK_NUM, FELICA_WRITE_MAX_SERVICE_NUM};
use error::Error as FelicaError;
impl<RST, IRQ, DELAY, B, const N: usize> Pn532<RST, IRQ, DELAY, B, N>
where
RST: OutputPin,
IRQ: InputPin,
DELAY: DelayNs,
B: Interface<RST, IRQ, DELAY>
B: Interface<RST, IRQ, DELAY>,
{
fn validate_response(
response: &[u8],
response_len: usize,
valid_len: usize,
) -> Result<(), Error<B::TransportError>> {
if response_len >= 11 && (response[9] != 0 || response[10] != 0) {
return Err(Error::FelicaError(FelicaError::Status(
response[9],
response[10],
)));
}
if response_len != valid_len {
return Err(Error::InvalidFrame);
}
Ok(())
}
/// Poll for a FeliCa card.
pub fn felica_polling(
&mut self,
config: FelicaPollingConfig
config: FelicaPollingConfig,
) -> Result<Option<FelicaPollingResponse>, Error<B::TransportError>> {
if !matches!(config.baud_rate, BaudRate::Felica212kbps | BaudRate::Felica424kbps) {
if !matches!(
config.baud_rate,
BaudRate::Felica212kbps | BaudRate::Felica424kbps
) {
return Err(Error::InvalidBaudRate);
}
let system_code = config.system_code;
@@ -113,13 +136,17 @@ where
}
/// Send a FeliCa "Request Response" command, returning the card's mode.
pub fn felica_request_response(&mut self, card: &FelicaPollingResponse) -> Result<u8, Error<B::TransportError>> {
pub fn felica_request_response(
&mut self,
card: &FelicaPollingResponse,
) -> Result<u8, Error<B::TransportError>> {
let mut cmd = [0u8; 9];
cmd[0] = FelicaCommand::RequestResponse.into_bits();
cmd[1..9].copy_from_slice(&card.idm.into_be_bytes());
let mut response = [0u8; 10];
let response_len = self.felica_send_command(&cmd, &mut response, Duration::from_millis(200))?;
let response_len =
self.felica_send_command(&cmd, &mut response, Duration::from_millis(200))?;
if response_len != 10 {
return Err(Error::InvalidFrame);
}
@@ -136,15 +163,13 @@ where
block_list: &[impl AsBlock; U],
block_data: &mut [[u8; 16]; U],
) -> Result<(), Error<B::TransportError>> {
const COMMAND_SIZE: usize =
1 // command code (0x06)
const COMMAND_SIZE: usize = 1 // command code (0x06)
+ 8 // idm
+ 1 // service length
+ 2 * FELICA_READ_MAX_SERVICE_NUM // service code list (2 * service length)
+ 1 // block length (1<=n<=4)
+ 2 * FELICA_READ_MAX_BLOCK_NUM; // block list (2n<=N<=3n), hardcoded to use 2 byte block list only
const RESPONSE_SIZE: usize =
1 // response code (0x07)
const RESPONSE_SIZE: usize = 1 // response code (0x07)
+ 8 // IDm
+ 1 // status[0]
+ 1 // status[1]
@@ -153,14 +178,14 @@ where
// validate
let num_service = service_code_list.len();
if num_service > FELICA_READ_MAX_SERVICE_NUM || U > FELICA_READ_MAX_BLOCK_NUM
{
if num_service > FELICA_READ_MAX_SERVICE_NUM || U > FELICA_READ_MAX_BLOCK_NUM {
return Err(Error::InvalidParam);
}
// command
let mut cmd = heapless::Vec::<u8, COMMAND_SIZE>::new();
cmd.push(FelicaCommand::ReadWithoutEncryption.into_bits()).unwrap();
cmd.push(FelicaCommand::ReadWithoutEncryption.into_bits())
.unwrap();
cmd.extend_from_slice(&card.idm.into_bytes()).unwrap();
cmd.push(num_service as u8).unwrap();
for sc in service_code_list {
@@ -175,25 +200,18 @@ where
// response
let mut response = [0u8; RESPONSE_SIZE];
// PMm gives the card's *processing* time only; add RF TX/RX + PN532
// overhead margin so we never abandon a command mid-flight (which would
// desync the host/PN532 framing).
let response_len = self.felica_send_command(
&cmd,
&mut response,
card.pmm.get_read_timeout(U, Some(Duration::from_millis(50)))
card.pmm
.get_read_timeout(U, Some(Duration::from_millis(50))),
)?;
crate::trace!("Read Response: {:02X}", response);
if response_len >= 11 && (response[9] != 0 || response[10] != 0) {
return Err(Error::FelicaError(FelicaError::Status(response[9], response[10])));
}
if response_len != 12 + 16 * U {
return Err(Error::InvalidFrame);
}
Self::validate_response(&response, response_len, 12 + 16 * U)?;
let mut k = 12;
for block in block_data.iter_mut().take(U) {
for block in block_data.iter_mut() {
block.copy_from_slice(&response[k..k + 16]);
k += 16;
}
@@ -208,24 +226,21 @@ where
block_list: &[impl AsBlock; U],
block_data: &[[u8; 16]; U],
) -> Result<(), Error<B::TransportError>> {
const COMMAND_SIZE: usize =
1 // command code
const COMMAND_SIZE: usize = 1 // command code
+ 8 // IDm
+ 1 // service len (m)
+ 2 * FELICA_WRITE_MAX_SERVICE_NUM // service code (2 * m)
+ 1 // block len (n)
+ 2 * FELICA_WRITE_MAX_BLOCK_NUM // block list (2n<=N<=3n) (hardcoded to 2 bytes only)
+ 16 * FELICA_WRITE_MAX_BLOCK_NUM; // block data (16 * n)
const RESPONSE_SIZE: usize =
1 // command code
const RESPONSE_SIZE: usize = 1 // command code
+ 8 // IDm
+ 1 // status[0]
+ 1; // status[1]
// validate
let num_service = service_code_list.len();
if num_service > FELICA_WRITE_MAX_SERVICE_NUM || U > FELICA_WRITE_MAX_BLOCK_NUM
{
if num_service > FELICA_WRITE_MAX_SERVICE_NUM || U > FELICA_WRITE_MAX_BLOCK_NUM {
return Err(Error::InvalidParam);
}
@@ -252,15 +267,12 @@ where
let response_len = self.felica_send_command(
&cmd,
&mut response,
card.pmm.get_write_timeout(U, Some(Duration::from_millis(50)))
card.pmm
.get_write_timeout(U, Some(Duration::from_millis(50))),
)?;
if response_len >= 11 && (response[9] != 0 || response[10] != 0) {
return Err(Error::FelicaError(FelicaError::Status(response[9], response[10])));
}
if response_len != RESPONSE_SIZE {
return Err(Error::InvalidFrame);
}
Self::validate_response(&response, response_len, RESPONSE_SIZE)?;
Ok(())
}
}
+10 -4
View File
@@ -1,9 +1,11 @@
///! WIP
use crate::commands::{PN532_COMMAND_INDATAEXCHANGE, PN532_COMMAND_INLISTPASSIVETARGET, PN532_COMMAND_INRELEASE};
use crate::commands::{
PN532_COMMAND_INDATAEXCHANGE, PN532_COMMAND_INLISTPASSIVETARGET, PN532_COMMAND_INRELEASE,
};
use crate::error::StatusCode;
use crate::{BaudRate, Error, Interface, Pn532, Uid};
impl<B: Interface, const N: usize> Pn532<B, N> {
/// Wait for an ISO14443A target and read its UID.
///
@@ -36,7 +38,9 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
let mut bytes = [0u8; 7];
bytes[..uid_len as usize].copy_from_slice(&self.buffer[6..6 + uid_len as usize]);
Ok(Some(Uid { bytes: heapless::Vec::from_array(bytes) }))
Ok(Some(Uid {
bytes: heapless::Vec::from_array(bytes),
}))
}
/// In-list a passive target (used before [`Self::in_data_exchange`]).
@@ -74,7 +78,9 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
let len = self.read_timeout(1000)?;
if len == 0 || (self.buffer[0] & 0x3F) != 0 {
return Err(Error::Status(StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap()));
return Err(Error::Status(
StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap(),
));
}
let data_len = len - 1;
+14 -5
View File
@@ -1,9 +1,12 @@
///! WIP
use crate::commands::{MIFARE_CMD_AUTH_A, MIFARE_CMD_AUTH_B, MIFARE_CMD_READ, MIFARE_CMD_WRITE, MIFARE_CMD_WRITE_ULTRALIGHT, PN532_COMMAND_INDATAEXCHANGE};
use crate::commands::{
MIFARE_CMD_AUTH_A, MIFARE_CMD_AUTH_B, MIFARE_CMD_READ, MIFARE_CMD_WRITE,
MIFARE_CMD_WRITE_ULTRALIGHT, PN532_COMMAND_INDATAEXCHANGE,
};
use crate::error::StatusCode;
use crate::{Error, Interface, Pn532};
impl<B: Interface, const N: usize> Pn532<B, N> {
/// Whether the block number is the first block of a sector.
pub fn mifare_classic_is_first_block(block: u32) -> bool {
@@ -47,7 +50,9 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
self.send(10 + uid_len)?;
let len = self.read()?;
if len == 0 || self.buffer[0] != 0x00 {
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
return Err(Error::Status(
StatusCode::from_repr(self.buffer[0]).unwrap(),
));
}
Ok(())
}
@@ -66,7 +71,9 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
let len = self.read()?;
if len < 17 || self.buffer[0] != 0x00 {
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
return Err(Error::Status(
StatusCode::from_repr(self.buffer[0]).unwrap(),
));
}
data.copy_from_slice(&self.buffer[1..17]);
Ok(())
@@ -193,7 +200,9 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
let len = self.read()?;
if len < 5 || self.buffer[0] != 0x00 {
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
return Err(Error::Status(
StatusCode::from_repr(self.buffer[0]).unwrap(),
));
}
buffer.copy_from_slice(&self.buffer[1..5]);
Ok(())
+3 -3
View File
@@ -2,7 +2,7 @@
#[cfg(feature = "felica")]
pub mod felica;
#[cfg(feature = "mifare-classic")]
mod mifare;
#[cfg(feature = "iso14443a")]
pub mod iso14443a;
pub mod iso14443a;#[cfg(feature = "mifare-classic")]
mod mifare;
+45 -5
View File
@@ -22,7 +22,7 @@ where
RST: OutputPin,
IRQ: InputPin,
DELAY: DelayNs,
B: Interface<RST, IRQ, DELAY>
B: Interface<RST, IRQ, DELAY>,
{
/// Initialize the PN532 as a target using a raw command frame.
pub fn tg_init_as_target(
@@ -44,10 +44,50 @@ where
timeout_ms: u16,
) -> Result<TargetInitStatus, Error<B::TransportError>> {
const COMMAND: [u8; 44] = [
PN532Command::TgInitAsTarget.into_bits(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0xFE,
0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, 0x06, 0x46,
0x66, 0x6D, 0x01, 0x01, 0x10, 0x00,
PN532Command::TgInitAsTarget.into_bits(),
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x01,
0xFE,
0x0F,
0xBB,
0xBA,
0xA6,
0xC9,
0x89,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xFF,
0xFF,
0x01,
0xFE,
0x0F,
0xBB,
0xBA,
0xA6,
0xC9,
0x89,
0x00,
0x00,
0x06,
0x46,
0x66,
0x6D,
0x01,
0x01,
0x10,
0x00,
];
self.tg_init_as_target(&COMMAND, timeout_ms)
}
-2
View File
@@ -26,8 +26,6 @@ pub const PN532_ACK_WAIT_TIME_MS: u16 = 10;
/// The fixed 7-bit I2C address of the PN532 (0x48 >> 1).
pub const PN532_I2C_ADDRESS: u8 = 0x48 >> 1;
pub const ACK: [u8; 6] = [0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00];
/// Maximum response data length supported (matches the PN532's 64-byte packet buffer).
@@ -18,8 +18,8 @@ impl CardKey {
let mut des_key = [0u8; 16];
for i in 0..8 {
des_key[7-i] = card_key[i];
des_key[15-i] = card_key[i + 8];
des_key[7 - i] = card_key[i];
des_key[15 - i] = card_key[i + 8];
}
des_key
@@ -1,11 +1,11 @@
use crate::types::felica::AsBlock;
use crate::types::felica::constants::FELICA_READ_MAX_BLOCK_NUM;
use crate::types::felica::AsBlock;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MacInput<const N: usize> {
block_data: [[u8; 16]; N],
block_list: [u8; N]
block_list: [u8; N],
}
impl<const N: usize> MacInput<N> {
@@ -16,13 +16,13 @@ impl<const N: usize> MacInput<N> {
if N < 2 {
panic!("block should have at least 2 blocks");
}
let mut block_nums= [0u8; N];
for (i, block) in blocks_list.iter().enumerate(){
let mut block_nums = [0u8; N];
for (i, block) in blocks_list.iter().enumerate() {
block_nums[i] = block.as_block() as u8;
}
Self {
block_data: *block_data,
block_list: block_nums
block_list: block_nums,
}
}
@@ -34,7 +34,6 @@ impl<const N: usize> MacInput<N> {
data
}
pub fn block_list(&self) -> [u8; 8] {
let mut data = [0xFFu8; 8];
for (i, block) in self.block_list.iter().enumerate() {
@@ -1,19 +1,19 @@
mod card_key;
mod mac_input;
mod rc;
mod sk;
mod mac_input;
pub use card_key::*;
pub use mac_input::*;
pub use rc::*;
pub use sk::*;
pub use mac_input::*;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[readonly::make]
pub struct AuthSession {
pub sk: SessionKeys,
pub rc: RandomChallenge
pub rc: RandomChallenge,
}
impl AuthSession {
@@ -26,8 +26,8 @@ impl RandomChallenge {
let mut des = [0u8; 16];
for i in 0..8 {
des[7-i] = rc[i];
des[15-i] = rc[i + 8];
des[7 - i] = rc[i];
des[15 - i] = rc[i + 8];
}
des
@@ -48,7 +48,12 @@ impl Display for RandomChallenge {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[cfg(feature = "alloc")]
{
write!(f, "{},{}", hex::encode_upper(self.rc1()), hex::encode_upper(self.rc2()))
write!(
f,
"{},{}",
hex::encode_upper(self.rc1()),
hex::encode_upper(self.rc2())
)
}
#[cfg(not(feature = "alloc"))]
{
@@ -1,6 +1,6 @@
use crate::types::felica::felica_lite_s::auth::RandomChallenge;
use cbc::cipher::{Block, BlockCipherEncrypt};
use des::TdesEde2;
use crate::types::felica::felica_lite_s::auth::RandomChallenge;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+3 -4
View File
@@ -1,12 +1,11 @@
///! Types for Felica Lite-S (RC-S966)
///! User Manual: https://www.sony.net/Products/felica/business/tech-support/data/fls_usmnl_1.4e.pdf
pub mod auth;
pub mod config;
use bitfields::bitflag;
use super::AsBlock;
use super::AsServiceCode;
use bitfields::bitflag;
/// Service Code
#[derive(Debug, PartialEq, Eq)]
@@ -18,7 +17,7 @@ pub enum ServiceCode {
/// FeliCa Lite read service code (read without key).
Read = 0x000B,
/// FeliCa Lite write service code (write without key).
Write = 0x0009
Write = 0x0009,
}
impl AsServiceCode for ServiceCode {
@@ -71,7 +70,7 @@ pub enum Block {
/// MAC_A block.
MACA = 0x8091,
/// STATE block (EXT_AUTH / POLL_DIS).
STATE = 0x8092
STATE = 0x8092,
}
impl AsBlock for Block {
+1 -1
View File
@@ -20,5 +20,5 @@ pub enum MobileICType {
/// 0x10~0x13
V2(u8),
/// 0x06~0x07
V1(u8)
V1(u8),
}
+2 -2
View File
@@ -1,12 +1,12 @@
use core::fmt::{Display, Formatter};
use bitfields::bitfield;
use core::fmt::{Display, Formatter};
#[bitfield([u8; 8], from_endian = little, into_endian = little)]
#[derive(PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct IDm {
pub manufacturer: [u8; 2],
pub card_id: [u8; 6]
pub card_id: [u8; 6],
}
impl Display for IDm {
+10 -10
View File
@@ -1,19 +1,19 @@
mod polling;
mod pmm;
mod idm;
pub mod constants;
#[cfg(feature = "felica-lite-s")]
pub mod felica_lite_s;
pub mod ic_type;
pub mod constants;
mod idm;
mod pmm;
mod polling;
pub use idm::*;
pub use pmm::*;
pub use polling::PollingRequestCode as FelicaPollingRequestCode;
pub use polling::PollingResponse as FelicaPollingResponse;
pub use polling::PollingConfig as FelicaPollingConfig;
pub use polling::CardSession;
#[cfg(feature = "felica-lite-s")]
pub use felica_lite_s::{Block as FelicaLiteSBlock, ServiceCode as FelicaLiteSServiceCode};
pub use idm::*;
pub use pmm::*;
pub use polling::CardSession;
pub use polling::PollingConfig as FelicaPollingConfig;
pub use polling::PollingRequestCode as FelicaPollingRequestCode;
pub use polling::PollingResponse as FelicaPollingResponse;
pub trait AsServiceCode {
fn as_service_code(&self) -> u16;
+1 -1
View File
@@ -1,6 +1,6 @@
use bitfields::bitfield;
use core::fmt::{Display, Formatter};
use core::time::Duration;
use bitfields::bitfield;
#[bitfield([u8; 8], from_endian = little, into_endian = little)]
#[derive(PartialEq, Eq)]
+12 -9
View File
@@ -1,9 +1,9 @@
use super::{FelicaPollingRequestCode, IDm, PMm};
use crate::types::BaudRate;
use alloc::string::ToString;
use bitfields::bitflag;
use core::fmt::{Display, Formatter};
use core::time::Duration;
use bitfields::bitflag;
use crate::types::BaudRate;
use super::{FelicaPollingRequestCode, IDm, PMm};
#[derive(Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -12,7 +12,7 @@ pub enum PollingRequestCode {
NoRequest = 0x00,
#[default]
SystemCode = 0x01,
CommunicationPerformance = 0x02
CommunicationPerformance = 0x02,
}
#[derive(Debug, PartialEq, Eq, Default)]
@@ -27,7 +27,6 @@ pub enum PollingTimeSlot {
_16 = 0x0F,
}
/// Result of a successful FeliCa polling request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -44,9 +43,13 @@ impl Display for PollingResponse {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let system_code = match self.system_code_response {
Some(system_code) => hex::encode_upper(system_code.to_be_bytes().to_vec()),
None => "None".to_string()
None => "None".to_string(),
};
write!(f, "IDm: {}, PMm, {}, System Code: {}", self.idm, self.pmm, system_code)
write!(
f,
"IDm: {}, PMm, {}, System Code: {}",
self.idm, self.pmm, system_code
)
}
}
@@ -55,7 +58,7 @@ impl Display for PollingResponse {
#[readonly::make]
pub struct CardSession {
pub idm: IDm,
pub pmm: PMm
pub pmm: PMm,
}
impl CardSession {
@@ -68,7 +71,7 @@ impl From<PollingResponse> for CardSession {
fn from(value: PollingResponse) -> Self {
Self {
idm: value.idm,
pmm: value.pmm
pmm: value.pmm,
}
}
}
+1 -1
View File
@@ -1,8 +1,8 @@
use bitfields::bitflag;
pub mod constants;
#[cfg(feature = "felica")]
pub mod felica;
pub mod constants;
/// Passive target baud rates supported by [`crate::Pn532::read_passive_target_id`].
#[derive(PartialEq, Eq, Debug)]