implement MAC auth for felica
This commit is contained in:
@@ -5,12 +5,6 @@ linker = "/home/fromost/.rustup/toolchains/esp/xtensa-esp-elf/esp-15.2.0_2025092
|
|||||||
[env]
|
[env]
|
||||||
DEFMT_LOG = "info,lora=trace,eeprom24x=debug,pn532=trace"
|
DEFMT_LOG = "info,lora=trace,eeprom24x=debug,pn532=trace"
|
||||||
|
|
||||||
DEV_EUI = "000000DD44F30FAE"
|
|
||||||
APP_KEY = "00000000000000000000000040000000"
|
|
||||||
APP_EUI = "0000000000000040"
|
|
||||||
|
|
||||||
CARD_KEY = "00000000000000000000000000000000"
|
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-C", "link-arg=-nostartfiles",
|
"-C", "link-arg=-nostartfiles",
|
||||||
|
|||||||
@@ -26,3 +26,5 @@ Cargo.lock
|
|||||||
|
|
||||||
# Ignore .DS_Store file in mac
|
# Ignore .DS_Store file in mac
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
|
|
||||||
|
**/*.env
|
||||||
@@ -60,6 +60,7 @@ crossbeam = { version = "0.8.4", default-features = false, features = ["alloc"]
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
patch-crate = "0.1.13"
|
patch-crate = "0.1.13"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
|
||||||
[package.metadata.patch]
|
[package.metadata.patch]
|
||||||
crates = ["lorawan-device"]
|
crates = ["lorawan-device"]
|
||||||
|
|||||||
@@ -2,6 +2,17 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-changed=Cargo.toml");
|
println!("cargo:rerun-if-changed=Cargo.toml");
|
||||||
patch_crate::run().expect("Failed while patching");
|
patch_crate::run().expect("Failed while patching");
|
||||||
|
|
||||||
|
let iter = dotenvy::from_path_iter(".env")
|
||||||
|
.expect("failed to read .env");
|
||||||
|
|
||||||
|
for item in iter {
|
||||||
|
let (key, value) = item.expect("invalid .env entry");
|
||||||
|
|
||||||
|
println!("cargo::rustc-env={key}={value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo::rerun-if-changed=.env");
|
||||||
|
|
||||||
linker_be_nice();
|
linker_be_nice();
|
||||||
println!("cargo:rustc-link-arg=-Tdefmt.x");
|
println!("cargo:rustc-link-arg=-Tdefmt.x");
|
||||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ cbc = { version = "~0.2", default-features = false, features = ["block-padding"]
|
|||||||
heapless = "~0.9"
|
heapless = "~0.9"
|
||||||
thiserror = { version = "~2", default-features = false }
|
thiserror = { version = "~2", default-features = false }
|
||||||
bitfields = "3.1.0"
|
bitfields = "3.1.0"
|
||||||
|
rand_core = "0.10.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
+4
-34
@@ -107,41 +107,11 @@ pub const PN532_GPIO_P33: u8 = 3;
|
|||||||
pub const PN532_GPIO_P34: u8 = 4;
|
pub const PN532_GPIO_P34: u8 = 4;
|
||||||
pub const PN532_GPIO_P35: u8 = 5;
|
pub const PN532_GPIO_P35: u8 = 5;
|
||||||
|
|
||||||
// FeliCa limits.
|
pub const FELICA_READ_MAX_SERVICE_NUM: usize = 3;
|
||||||
pub const fn felica_read_max_service_num() -> usize {
|
pub const FELICA_READ_MAX_BLOCK_NUM: usize = 3;
|
||||||
if cfg!(feature = "felica-lite-s") {
|
|
||||||
8
|
|
||||||
} else {
|
|
||||||
16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn felica_read_max_block_num() -> usize {
|
|
||||||
if cfg!(feature = "felica-lite-s") {
|
|
||||||
4
|
|
||||||
} else {
|
|
||||||
12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn felica_write_max_service_num() -> usize {
|
|
||||||
if cfg!(feature = "felica-lite-s") {
|
|
||||||
4
|
|
||||||
} else {
|
|
||||||
16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn felica_write_max_block_num() -> usize {
|
|
||||||
if cfg!(feature = "felica-lite-s") {
|
|
||||||
2
|
|
||||||
} else {
|
|
||||||
10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const FELICA_READ_MAX_SERVICE_NUM: usize = felica_read_max_service_num();
|
pub const FELICA_WRITE_MAX_SERVICE_NUM: usize = 3;
|
||||||
pub const FELICA_READ_MAX_BLOCK_NUM: usize = felica_read_max_block_num();
|
pub const FELICA_WRITE_MAX_BLOCK_NUM: usize = 3;
|
||||||
|
|
||||||
pub const FELICA_WRITE_MAX_SERVICE_NUM: usize = felica_write_max_service_num();
|
|
||||||
pub const FELICA_WRITE_MAX_BLOCK_NUM: usize = felica_write_max_block_num();
|
|
||||||
|
|
||||||
pub const FELICA_REQ_SERVICE_MAX_NODE_NUM: usize = 32;
|
pub const FELICA_REQ_SERVICE_MAX_NODE_NUM: usize = 32;
|
||||||
|
|
||||||
|
|||||||
+4
-7
@@ -15,15 +15,12 @@ pub struct Uid {
|
|||||||
impl core::fmt::Display for Uid {
|
impl core::fmt::Display for Uid {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
return write!(f, "{}", hex::encode(&self.bytes));
|
{
|
||||||
|
write!(f, "{}", hex::encode(&self.bytes))
|
||||||
|
}
|
||||||
#[cfg(not(feature = "alloc"))]
|
#[cfg(not(feature = "alloc"))]
|
||||||
{
|
{
|
||||||
let mut buf: heapless::Vec<u8, 14> = heapless::Vec::new();
|
write!(f, "{:?}", self)
|
||||||
hex::encode_to_slice(&self.bytes, &mut buf)
|
|
||||||
.map_err(|_| core::fmt::Error)?;
|
|
||||||
let s = heapless::String::from_utf8(buf)
|
|
||||||
.map_err(|_| core::fmt::Error)?;
|
|
||||||
write!(f, "{}", s)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use bitfields::bitflag;
|
use bitfields::bitflag;
|
||||||
use defmt::Debug2Format;
|
use defmt::Debug2Format;
|
||||||
|
use crate::felica;
|
||||||
|
|
||||||
/// Driver-level error type, generic over the transport (I2C) error.
|
/// Driver-level error type, generic over the transport (I2C) error.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||||
@@ -29,8 +30,8 @@ pub enum Error<E> {
|
|||||||
#[error("Invalid baud rate")]
|
#[error("Invalid baud rate")]
|
||||||
InvalidBaudRate,
|
InvalidBaudRate,
|
||||||
#[cfg(feature = "felica")]
|
#[cfg(feature = "felica")]
|
||||||
#[error("Felica error")]
|
#[error(transparent)]
|
||||||
FelicaError
|
FelicaError(#[from] felica::error::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
#[cfg(feature = "defmt")]
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Status error: sf1:{0}, sf2:{1}")]
|
||||||
|
Status(u8, u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
impl defmt::Format for Error {
|
||||||
|
fn format(&self, f: defmt::Formatter) {
|
||||||
|
defmt::write!(f, "{}", defmt::Display2Format(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
use des::cipher::{Block, BlockCipherEncrypt, Key, KeyInit};
|
||||||
|
use des::TdesEde2;
|
||||||
|
use rand_core::Rng;
|
||||||
|
use crate::{Interface, Pn532};
|
||||||
|
use crate::felica::types::{FelicaLiteSBlock, FelicaLiteSServiceCode, IDm, PMm};
|
||||||
|
|
||||||
|
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||||
|
pub fn set_random_challenge(&mut self, card: &IDm, pmm: &PMm, rng: &mut impl Rng)
|
||||||
|
-> Result<([u8; 8], [u8; 8]), crate::Error<B::TransportError>>
|
||||||
|
{
|
||||||
|
let mut data = [[0u8; 16]];
|
||||||
|
rng.fill_bytes(&mut data[0]);
|
||||||
|
self.felica_write_without_encryption(
|
||||||
|
card, pmm,
|
||||||
|
&[FelicaLiteSServiceCode::Write],
|
||||||
|
&[FelicaLiteSBlock::RC],
|
||||||
|
&data,
|
||||||
|
)?;
|
||||||
|
let rc1 = data[0][0..8].try_into().unwrap();
|
||||||
|
let rc2 = data[0][8..16].try_into().unwrap();
|
||||||
|
Ok((rc1, rc2))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_session_keys(
|
||||||
|
&mut self,
|
||||||
|
rc: &[u8; 16],
|
||||||
|
card_key: &[u8; 16],
|
||||||
|
) -> Result<([u8; 8], [u8; 8]), crate::Error<B::TransportError>> {
|
||||||
|
// FeliCa byte order:
|
||||||
|
//
|
||||||
|
// CK1 = CK[0..8]
|
||||||
|
// CK2 = CK[8..16]
|
||||||
|
//
|
||||||
|
// The DES interface receives each half reversed.
|
||||||
|
let mut des_key = [0u8; 16];
|
||||||
|
|
||||||
|
des_key[0..8].copy_from_slice(&{
|
||||||
|
let mut x = [0u8; 8];
|
||||||
|
x.copy_from_slice(&card_key[0..8]);
|
||||||
|
x.reverse();
|
||||||
|
x
|
||||||
|
});
|
||||||
|
|
||||||
|
des_key[8..16].copy_from_slice(&{
|
||||||
|
let mut x = [0u8; 8];
|
||||||
|
x.copy_from_slice(&card_key[8..16]);
|
||||||
|
x.reverse();
|
||||||
|
x
|
||||||
|
});
|
||||||
|
|
||||||
|
// RC1 || RC2, with each 8-byte half reversed.
|
||||||
|
let mut des_rc = [0u8; 16];
|
||||||
|
|
||||||
|
des_rc[0..8].copy_from_slice(&{
|
||||||
|
let mut x = [0u8; 8];
|
||||||
|
x.copy_from_slice(&rc[0..8]);
|
||||||
|
x.reverse();
|
||||||
|
x
|
||||||
|
});
|
||||||
|
|
||||||
|
des_rc[8..16].copy_from_slice(&{
|
||||||
|
let mut x = [0u8; 8];
|
||||||
|
x.copy_from_slice(&rc[8..16]);
|
||||||
|
x.reverse();
|
||||||
|
x
|
||||||
|
});
|
||||||
|
|
||||||
|
let key = Key::<TdesEde2>::try_from(des_key).unwrap();
|
||||||
|
let cipher = TdesEde2::new(&key);
|
||||||
|
|
||||||
|
// 2-key 3DES-CBC, IV = 0.
|
||||||
|
let mut state = Block::<TdesEde2>::default();
|
||||||
|
|
||||||
|
// RC1
|
||||||
|
for i in 0..8 {
|
||||||
|
state[i] ^= des_rc[i];
|
||||||
|
}
|
||||||
|
cipher.encrypt_block(&mut state);
|
||||||
|
|
||||||
|
let sk1_des: [u8; 8] = *state.as_array().unwrap();
|
||||||
|
|
||||||
|
// RC2
|
||||||
|
for i in 0..8 {
|
||||||
|
state[i] ^= des_rc[8 + i];
|
||||||
|
}
|
||||||
|
cipher.encrypt_block(&mut state);
|
||||||
|
|
||||||
|
let sk2_des: [u8; 8] = *state.as_array().unwrap();
|
||||||
|
|
||||||
|
// Convert the DES-side values back to FeliCa byte order.
|
||||||
|
let mut sk1 = sk1_des;
|
||||||
|
sk1.reverse();
|
||||||
|
|
||||||
|
let mut sk2 = sk2_des;
|
||||||
|
sk2.reverse();
|
||||||
|
|
||||||
|
crate::debug!("RC: {:02X}", rc);
|
||||||
|
crate::debug!("CK DES key: {:02X}", des_key);
|
||||||
|
crate::debug!("RC DES input: {:02X}", des_rc);
|
||||||
|
crate::debug!("SK1 DES: {:02X}", sk1_des);
|
||||||
|
crate::debug!("SK1: {:02X}", sk1);
|
||||||
|
crate::debug!("SK2 DES: {:02X}", sk2_des);
|
||||||
|
crate::debug!("SK2: {:02X}", sk2);
|
||||||
|
|
||||||
|
Ok((sk1, sk2))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_plain_text(&mut self, card: &IDm, pmm: &PMm)
|
||||||
|
-> Result<([u8; 56], [u8; 8]), crate::Error<B::TransportError>>
|
||||||
|
{
|
||||||
|
let mut data = [[0u8; 16]; 3];
|
||||||
|
self.felica_read_without_encryption(
|
||||||
|
card, pmm,
|
||||||
|
&[FelicaLiteSServiceCode::Read],
|
||||||
|
&[FelicaLiteSBlock::ID, FelicaLiteSBlock::CKV, FelicaLiteSBlock::MACA],
|
||||||
|
&mut data
|
||||||
|
)?;
|
||||||
|
let id = data[0];
|
||||||
|
let ckv = data[1];
|
||||||
|
let maca = data[2];
|
||||||
|
let mut data = [0u8; 56];
|
||||||
|
data[0] = FelicaLiteSBlock::ID as u8;
|
||||||
|
data[1] = 0x00;
|
||||||
|
data[2] = FelicaLiteSBlock::CKV as u8;
|
||||||
|
data[3] = 0x00;
|
||||||
|
data[4] = FelicaLiteSBlock::MACA as u8;
|
||||||
|
data[5] = 0x00;
|
||||||
|
data[6] = 0xFF;
|
||||||
|
data[7] = 0xFF;
|
||||||
|
data[8..24].copy_from_slice(&id);
|
||||||
|
data[24..40].copy_from_slice(&ckv);
|
||||||
|
data[40..56].copy_from_slice(&maca);
|
||||||
|
crate::debug!("{} {}", maca[0..8], maca[8..16]);
|
||||||
|
let maca: [u8; 8] = maca[0..8].try_into().unwrap();
|
||||||
|
Ok((data, maca))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_mac(
|
||||||
|
&mut self,
|
||||||
|
card: &IDm,
|
||||||
|
pmm: &PMm,
|
||||||
|
sk1: &[u8; 8],
|
||||||
|
sk2: &[u8; 8],
|
||||||
|
iv: &[u8; 8],
|
||||||
|
) -> Result<bool, crate::Error<B::TransportError>> {
|
||||||
|
let (data, mac_a) = self.get_plain_text(card, pmm)?;
|
||||||
|
|
||||||
|
// data layout from get_plain_text():
|
||||||
|
//
|
||||||
|
// [0..8] = block list
|
||||||
|
// [8..24] = ID block
|
||||||
|
// [24..40] = CKV block
|
||||||
|
// [40..56] = MAC_A block (NOT used in MAC calculation)
|
||||||
|
|
||||||
|
let block_list = &data[0..8];
|
||||||
|
let block_data = &data[8..40];
|
||||||
|
|
||||||
|
// FeliCa Lite-S uses the session keys with their byte order reversed
|
||||||
|
// at the DES interface.
|
||||||
|
let mut key_bytes = [0u8; 16];
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
key_bytes[i] = sk1[7 - i];
|
||||||
|
key_bytes[8 + i] = sk2[7 - i];
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = Key::<TdesEde2>::try_from(key_bytes).unwrap();
|
||||||
|
let cipher = TdesEde2::new(&key);
|
||||||
|
|
||||||
|
// RC1 is also byte-reversed at the DES interface.
|
||||||
|
let mut state_bytes = *iv;
|
||||||
|
state_bytes.reverse();
|
||||||
|
|
||||||
|
// The initial plaintext for MAC_A is the block-number list.
|
||||||
|
// Byte order is reversed before the DES operation.
|
||||||
|
let mut first = [0u8; 8];
|
||||||
|
first.copy_from_slice(block_list);
|
||||||
|
first.reverse();
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
state_bytes[i] ^= first[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = Block::<TdesEde2>::try_from(state_bytes).unwrap();
|
||||||
|
cipher.encrypt_block(&mut state);
|
||||||
|
|
||||||
|
crate::debug!(
|
||||||
|
"MAC block list: {:02X}",
|
||||||
|
block_list
|
||||||
|
);
|
||||||
|
|
||||||
|
crate::debug!(
|
||||||
|
"MAC first input: {:02X}",
|
||||||
|
state_bytes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process ID and CKV.
|
||||||
|
//
|
||||||
|
// Each 8-byte piece is byte-reversed before entering the
|
||||||
|
// 2-key 3DES CBC operation.
|
||||||
|
for chunk in block_data.chunks_exact(8) {
|
||||||
|
let mut input = [0u8; 8];
|
||||||
|
input.copy_from_slice(chunk);
|
||||||
|
input.reverse();
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
input[i] ^= state[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::debug!("MAC input: {:02X}", input);
|
||||||
|
|
||||||
|
let mut block = Block::<TdesEde2>::try_from(input).unwrap();
|
||||||
|
cipher.encrypt_block(&mut block);
|
||||||
|
|
||||||
|
state = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the final result back to FeliCa byte order.
|
||||||
|
let mut mac: [u8; 8] = *state.as_array().unwrap();
|
||||||
|
mac.reverse();
|
||||||
|
|
||||||
|
crate::debug!("mac: {:02X}, mac_a: {:02X}", mac, mac_a);
|
||||||
|
|
||||||
|
Ok(mac == mac_a[0..8])
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
-57
@@ -1,14 +1,12 @@
|
|||||||
mod types;
|
pub mod types;
|
||||||
|
#[cfg(feature="felica-lite-s")]
|
||||||
pub(crate) use types::AsBlock;
|
mod felica_lite_s;
|
||||||
pub use types::Block as FelicaBlock;
|
pub mod error;
|
||||||
pub use types::PollingRequestCode as FelicaPollingRequestCode;
|
|
||||||
pub use types::PollingResponse as FelicaPollingResponse;
|
|
||||||
pub use types::ServiceCode as FelicaServiceCode;
|
|
||||||
|
|
||||||
|
use error::Error as FelicaError;
|
||||||
use crate::commands::*;
|
use crate::commands::*;
|
||||||
use crate::driver::Pn532;
|
use crate::driver::Pn532;
|
||||||
use crate::error::{Error, StatusCode};
|
use crate::error::Error;
|
||||||
use crate::interface::Interface;
|
use crate::interface::Interface;
|
||||||
use crate::BaudRate;
|
use crate::BaudRate;
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
@@ -20,9 +18,9 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
system_code: Option<u16>,
|
system_code: Option<u16>,
|
||||||
baud_rate: BaudRate,
|
baud_rate: BaudRate,
|
||||||
request_code: PollingRequestCode,
|
request_code: FelicaPollingRequestCode,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<Option<PollingResponse>, Error<B::TransportError>> {
|
) -> Result<Option<FelicaPollingResponse>, Error<B::TransportError>> {
|
||||||
if !matches!(baud_rate, BaudRate::Felica212kbps | BaudRate::Felica424kbps) {
|
if !matches!(baud_rate, BaudRate::Felica212kbps | BaudRate::Felica424kbps) {
|
||||||
return Err(Error::InvalidBaudRate);
|
return Err(Error::InvalidBaudRate);
|
||||||
}
|
}
|
||||||
@@ -57,6 +55,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut idm = [0u8; 8];
|
let mut idm = [0u8; 8];
|
||||||
|
|
||||||
idm.copy_from_slice(&self.buffer[4..12]);
|
idm.copy_from_slice(&self.buffer[4..12]);
|
||||||
let idm = IDm::from(idm);
|
let idm = IDm::from(idm);
|
||||||
|
|
||||||
@@ -70,7 +69,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(PollingResponse {
|
Ok(Some(FelicaPollingResponse {
|
||||||
idm,
|
idm,
|
||||||
pmm,
|
pmm,
|
||||||
system_code_response,
|
system_code_response,
|
||||||
@@ -108,49 +107,8 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
Ok(response_len)
|
Ok(response_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a FeliCa "Request Service" command.
|
|
||||||
pub fn felica_request_service(
|
|
||||||
&mut self,
|
|
||||||
card: &IDm,
|
|
||||||
node_code_list: &[u16],
|
|
||||||
key_versions: &mut [u16],
|
|
||||||
) -> Result<(), Error<B::TransportError>> {
|
|
||||||
let num_node = node_code_list.len();
|
|
||||||
if num_node > FELICA_REQ_SERVICE_MAX_NODE_NUM || num_node > key_versions.len() {
|
|
||||||
return Err(Error::InvalidParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM];
|
|
||||||
let mut j = 0;
|
|
||||||
cmd[j] = FELICA_CMD_REQUEST_SERVICE;
|
|
||||||
j += 1;
|
|
||||||
cmd[j..j + 8].copy_from_slice(&card.into_be_bytes());
|
|
||||||
j += 8;
|
|
||||||
cmd[j] = num_node as u8;
|
|
||||||
j += 1;
|
|
||||||
for &code in node_code_list {
|
|
||||||
cmd[j] = code as u8;
|
|
||||||
cmd[j + 1] = (code >> 8) as u8;
|
|
||||||
j += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut response = [0u8; 10 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM];
|
|
||||||
let response_len = self.felica_send_command(
|
|
||||||
&cmd[..j],
|
|
||||||
&mut response,
|
|
||||||
Duration::from_millis(200)
|
|
||||||
)?;
|
|
||||||
if response_len != 10 + 2 * num_node {
|
|
||||||
return Err(Error::InvalidFrame);
|
|
||||||
}
|
|
||||||
for (i, kv) in key_versions.iter_mut().enumerate().take(num_node) {
|
|
||||||
*kv = u16::from_le_bytes([response[10 + i * 2], response[10 + i * 2 + 1]]);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a FeliCa "Request Response" command, returning the card's mode.
|
/// Send a FeliCa "Request Response" command, returning the card's mode.
|
||||||
pub fn felica_request_response(&mut self, card: &PollingResponse) -> Result<u8, Error<B::TransportError>> {
|
pub fn felica_request_response(&mut self, card: &FelicaPollingResponse) -> Result<u8, Error<B::TransportError>> {
|
||||||
let mut cmd = [0u8; 9];
|
let mut cmd = [0u8; 9];
|
||||||
cmd[0] = FELICA_CMD_REQUEST_RESPONSE;
|
cmd[0] = FELICA_CMD_REQUEST_RESPONSE;
|
||||||
cmd[1..9].copy_from_slice(&card.idm.into_be_bytes());
|
cmd[1..9].copy_from_slice(&card.idm.into_be_bytes());
|
||||||
@@ -202,7 +160,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
// command
|
// command
|
||||||
let mut cmd = heapless::Vec::<u8, COMMAND_SIZE>::new();
|
let mut cmd = heapless::Vec::<u8, COMMAND_SIZE>::new();
|
||||||
cmd.push(FELICA_CMD_READ_WITHOUT_ENCRYPTION).unwrap();
|
cmd.push(FELICA_CMD_READ_WITHOUT_ENCRYPTION).unwrap();
|
||||||
cmd.extend_from_slice(&card.into_be_bytes()).unwrap();
|
cmd.extend_from_slice(&card.into_bytes()).unwrap();
|
||||||
cmd.push(num_service as u8).unwrap();
|
cmd.push(num_service as u8).unwrap();
|
||||||
for sc in service_code_list {
|
for sc in service_code_list {
|
||||||
let sc: [u8; 2] = sc.as_service_code().to_le_bytes();
|
let sc: [u8; 2] = sc.as_service_code().to_le_bytes();
|
||||||
@@ -224,11 +182,12 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
&mut response,
|
&mut response,
|
||||||
pmm.get_read_timeout(num_block, Some(Duration::from_millis(50)))
|
pmm.get_read_timeout(num_block, Some(Duration::from_millis(50)))
|
||||||
)?;
|
)?;
|
||||||
|
crate::trace!("Read Response: {:02X}", response);
|
||||||
if response_len != 12 + 16 * num_block {
|
if response_len != 12 + 16 * num_block {
|
||||||
return Err(Error::InvalidFrame);
|
return Err(Error::InvalidFrame);
|
||||||
}
|
}
|
||||||
if response[9] != 0 || response[10] != 0 {
|
if response[9] != 0 || response[10] != 0 {
|
||||||
return Err(Error::Status(StatusCode::from_bits(response[9])));
|
return Err(Error::FelicaError(FelicaError::Status(response[9], response[10])));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut k = 12;
|
let mut k = 12;
|
||||||
@@ -275,7 +234,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
// command
|
// command
|
||||||
let mut cmd = heapless::Vec::<u8, COMMAND_SIZE>::new();
|
let mut cmd = heapless::Vec::<u8, COMMAND_SIZE>::new();
|
||||||
cmd.push(FELICA_CMD_WRITE_WITHOUT_ENCRYPTION).unwrap();
|
cmd.push(FELICA_CMD_WRITE_WITHOUT_ENCRYPTION).unwrap();
|
||||||
cmd.extend_from_slice(&card.into_be_bytes()).unwrap();
|
cmd.extend_from_slice(&card.into_bytes()).unwrap();
|
||||||
cmd.push(num_service as u8).unwrap();
|
cmd.push(num_service as u8).unwrap();
|
||||||
for sc in service_code_list {
|
for sc in service_code_list {
|
||||||
let sc = sc.as_service_code().to_le_bytes();
|
let sc = sc.as_service_code().to_le_bytes();
|
||||||
@@ -301,7 +260,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
return Err(Error::InvalidFrame);
|
return Err(Error::InvalidFrame);
|
||||||
}
|
}
|
||||||
if response[9] != 0 || response[10] != 0 {
|
if response[9] != 0 || response[10] != 0 {
|
||||||
return Err(Error::Status(StatusCode::from_bits(response[9])));
|
return Err(Error::FelicaError(FelicaError::Status(response[9], response[10])));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -309,6 +268,7 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
/// Send a FeliCa "Request System Code" command.
|
/// Send a FeliCa "Request System Code" command.
|
||||||
///
|
///
|
||||||
/// Returns the number of system codes written to `system_code_list`.
|
/// Returns the number of system codes written to `system_code_list`.
|
||||||
|
#[cfg(not(feature = "felica-lite-s"))]
|
||||||
pub fn felica_request_system_code(
|
pub fn felica_request_system_code(
|
||||||
&mut self,
|
&mut self,
|
||||||
card: &IDm,
|
card: &IDm,
|
||||||
@@ -337,4 +297,46 @@ impl<B: Interface, const N: usize> Pn532<B, N> {
|
|||||||
}
|
}
|
||||||
Ok(num_system_code)
|
Ok(num_system_code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send a FeliCa "Request Service" command.
|
||||||
|
#[cfg(not(feature = "felica-lite-s"))]
|
||||||
|
pub fn felica_request_service(
|
||||||
|
&mut self,
|
||||||
|
card: &IDm,
|
||||||
|
node_code_list: &[u16],
|
||||||
|
key_versions: &mut [u16],
|
||||||
|
) -> Result<(), Error<B::TransportError>> {
|
||||||
|
let num_node = node_code_list.len();
|
||||||
|
if num_node > FELICA_REQ_SERVICE_MAX_NODE_NUM || num_node > key_versions.len() {
|
||||||
|
return Err(Error::InvalidParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM];
|
||||||
|
let mut j = 0;
|
||||||
|
cmd[j] = FELICA_CMD_REQUEST_SERVICE;
|
||||||
|
j += 1;
|
||||||
|
cmd[j..j + 8].copy_from_slice(&card.into_be_bytes());
|
||||||
|
j += 8;
|
||||||
|
cmd[j] = num_node as u8;
|
||||||
|
j += 1;
|
||||||
|
for &code in node_code_list {
|
||||||
|
cmd[j] = code as u8;
|
||||||
|
cmd[j + 1] = (code >> 8) as u8;
|
||||||
|
j += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut response = [0u8; 10 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM];
|
||||||
|
let response_len = self.felica_send_command(
|
||||||
|
&cmd[..j],
|
||||||
|
&mut response,
|
||||||
|
Duration::from_millis(200)
|
||||||
|
)?;
|
||||||
|
if response_len != 10 + 2 * num_node {
|
||||||
|
return Err(Error::InvalidFrame);
|
||||||
|
}
|
||||||
|
for (i, kv) in key_versions.iter_mut().enumerate().take(num_node) {
|
||||||
|
*kv = u16::from_le_bytes([response[10 + i * 2], response[10 + i * 2 + 1]]);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// use core::fmt::{Display, Formatter};
|
||||||
|
// use bitfields::bitfield;
|
||||||
|
//
|
||||||
|
// #[bitfield([u8; 16])]
|
||||||
|
// #[derive(Eq, PartialEq)]
|
||||||
|
// pub struct ChallengeKey {
|
||||||
|
// pub ck1: [u8; 8],
|
||||||
|
// pub ck2: [u8; 8],
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl ChallengeKey {
|
||||||
|
// pub fn try_from_block(block: &[u8; 16]) -> Result<Self, <[u8; 8] as TryFrom<&[u8]>>::Error> {
|
||||||
|
// let mut ck1: [u8; 8] = block[0..8].try_into()?;
|
||||||
|
// ck1.reverse();
|
||||||
|
// let mut ck2: [u8; 8] = block[8..16].try_into()?;
|
||||||
|
// ck2.reverse();
|
||||||
|
// let mut instance = Self::new();
|
||||||
|
// instance.set_ck1(ck1);
|
||||||
|
// instance.set_ck2(ck2);
|
||||||
|
// Ok(instance)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl Display for ChallengeKey {
|
||||||
|
// fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||||
|
// #[cfg(feature = "alloc")]
|
||||||
|
// {
|
||||||
|
// let ck1 = hex::encode(self.ck1());
|
||||||
|
// let ck2 = hex::encode(self.ck2());
|
||||||
|
// write!(f, "{ck1},{ck2}")
|
||||||
|
// }
|
||||||
|
// #[cfg(not(feature = "alloc"))]
|
||||||
|
// {
|
||||||
|
// write!(f, "{:?}", self)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[cfg(feature = "defmt")]
|
||||||
|
// impl defmt::Format for ChallengeKey {
|
||||||
|
// fn format(&self, fmt: defmt::Formatter) {
|
||||||
|
// defmt::write!(fmt, "{}", defmt::Display2Format(self))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use bitfields::bitfield;
|
||||||
|
|
||||||
|
#[bitfield([u8; 16])]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct SubstractionRegisterBlock {
|
||||||
|
pub reg_a: [u8; 4],
|
||||||
|
pub reg_b: [u8; 4],
|
||||||
|
pub reg_c: [u8; 8],
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
pub mod block;
|
||||||
|
pub mod auth;
|
||||||
|
|
||||||
|
///! Types for Felica Lite-S (RC-S966)
|
||||||
|
///! User Manual: https://www.sony.net/Products/felica/business/tech-support/data/fls_usmnl_1.4e.pdf
|
||||||
|
|
||||||
|
use bitfields::bitflag;
|
||||||
|
use super::AsBlock;
|
||||||
|
use super::AsServiceCode;
|
||||||
|
|
||||||
|
/// Service Code
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitflag(u16)]
|
||||||
|
pub enum ServiceCode {
|
||||||
|
#[base]
|
||||||
|
Unknown = 0,
|
||||||
|
/// FeliCa Lite read service code (read without key).
|
||||||
|
Read = 0x000B,
|
||||||
|
/// FeliCa Lite write service code (write without key).
|
||||||
|
Write = 0x0009
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsServiceCode for ServiceCode {
|
||||||
|
fn as_service_code(&self) -> u16 {
|
||||||
|
*self as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_service_code(val: u16) -> Self {
|
||||||
|
Self::from_bits(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FeliCa Lite system block numbers.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitflag(u16)]
|
||||||
|
pub enum Block {
|
||||||
|
#[base]
|
||||||
|
Unknown = 0,
|
||||||
|
PAD0 = 0x8000,
|
||||||
|
PAD1 = 0x8001,
|
||||||
|
PAD2 = 0x8002,
|
||||||
|
PAD3 = 0x8003,
|
||||||
|
PAD4 = 0x8004,
|
||||||
|
PAD5 = 0x8005,
|
||||||
|
PAD6 = 0x8006,
|
||||||
|
PAD7 = 0x8007,
|
||||||
|
PAD8 = 0x8008,
|
||||||
|
PAD9 = 0x8009,
|
||||||
|
PAD10 = 0x800A,
|
||||||
|
PAD11 = 0x800B,
|
||||||
|
PAD12 = 0x800C,
|
||||||
|
PAD13 = 0x800D,
|
||||||
|
/// Subtraction Register Block
|
||||||
|
REG = 0x800E,
|
||||||
|
/// Random challenge block.
|
||||||
|
RC = 0x8080,
|
||||||
|
/// MAC block.
|
||||||
|
MAC = 0x8081,
|
||||||
|
/// ID block.
|
||||||
|
ID = 0x8082,
|
||||||
|
/// Card key version block.
|
||||||
|
CKV = 0x8086,
|
||||||
|
/// Card key block.
|
||||||
|
CK= 0x8087,
|
||||||
|
/// Memory configuration block.
|
||||||
|
MemoryConfig = 0x8088,
|
||||||
|
/// Write counter block.
|
||||||
|
WriteCounter = 0x8090,
|
||||||
|
/// MAC_A block.
|
||||||
|
MACA = 0x8091
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsBlock for Block {
|
||||||
|
fn as_block(&self) -> u16 {
|
||||||
|
*self as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use bitfields::bitflag;
|
||||||
|
|
||||||
|
/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitflag(u8)]
|
||||||
|
pub enum CardICType {
|
||||||
|
#[base]
|
||||||
|
Unknown = 0x00,
|
||||||
|
S140 = 0x50,
|
||||||
|
SA40_2P = 0x51,
|
||||||
|
SA41_2C = 0x52,
|
||||||
|
SA21_2 = 0x46,
|
||||||
|
SA20_2 = 0x45,
|
||||||
|
SA20_1 = 0x44,
|
||||||
|
SA01_2 = 0x35,
|
||||||
|
SA00_1 = 0x32,
|
||||||
|
S962 = 0x20,
|
||||||
|
S960 = 0x0D,
|
||||||
|
S953 = 0x09,
|
||||||
|
S952 = 0x08,
|
||||||
|
S915 = 0x01,
|
||||||
|
S982 = 0xF1,
|
||||||
|
S978F = 0xF0,
|
||||||
|
S967LiteS = 0xF2,
|
||||||
|
S967Plug = 0xE1,
|
||||||
|
S967NFC = 0xFF,
|
||||||
|
S926 = 0xE0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum MobileICType {
|
||||||
|
/// 0x14~0x1F
|
||||||
|
V3(u8),
|
||||||
|
/// 0x10~0x13
|
||||||
|
V2(u8),
|
||||||
|
/// 0x06~0x07
|
||||||
|
V1(u8)
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use bitfields::bitfield;
|
use bitfields::bitfield;
|
||||||
|
|
||||||
#[bitfield([u8; 8])]
|
#[bitfield([u8; 8], from_endian = big, into_endian = big)]
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct IDm {
|
pub struct IDm {
|
||||||
pub manufacturer: u8,
|
pub manufacturer: [u8; 2],
|
||||||
pub card_id: [u8; 7]
|
pub card_id: [u8; 6]
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,29 @@
|
|||||||
mod polling;
|
mod polling;
|
||||||
mod pmm;
|
mod pmm;
|
||||||
mod idm;
|
mod idm;
|
||||||
|
#[cfg(feature = "felica-lite-s")]
|
||||||
|
pub mod felica_lite_s;
|
||||||
|
pub mod ic_type;
|
||||||
|
|
||||||
use bitfields::bitflag;
|
|
||||||
pub use idm::*;
|
pub use idm::*;
|
||||||
pub use pmm::*;
|
pub use pmm::*;
|
||||||
pub use polling::*;
|
pub use polling::PollingRequestCode as FelicaPollingRequestCode;
|
||||||
|
pub use polling::PollingResponse as FelicaPollingResponse;
|
||||||
|
#[cfg(feature = "felica-lite-s")]
|
||||||
|
pub use felica_lite_s::{Block as FelicaLiteSBlock, ServiceCode as FelicaLiteSServiceCode};
|
||||||
|
|
||||||
pub trait AsServiceCode {
|
pub trait AsServiceCode {
|
||||||
fn as_service_code(&self) -> u16;
|
fn as_service_code(&self) -> u16;
|
||||||
fn from_service_code(val: u16) -> Self;
|
fn from_service_code(val: u16) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
pub trait AsBlock {
|
||||||
#[bitflag(u16)]
|
fn as_block(&self) -> u16;
|
||||||
pub enum ServiceCode {
|
|
||||||
#[base]
|
|
||||||
Unknown = 0,
|
|
||||||
/// FeliCa Lite read service code (read without key).
|
|
||||||
Read = 0x000B,
|
|
||||||
/// FeliCa Lite write service code (write without key).
|
|
||||||
Write = 0x0009
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsServiceCode for ServiceCode {
|
impl AsBlock for u16 {
|
||||||
fn as_service_code(&self) -> u16 {
|
fn as_block(&self) -> u16 {
|
||||||
*self as u16
|
*self
|
||||||
}
|
|
||||||
|
|
||||||
fn from_service_code(val: u16) -> Self {
|
|
||||||
Self::from_bits(val)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,80 +36,3 @@ impl AsServiceCode for u16 {
|
|||||||
val
|
val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AsBlock {
|
|
||||||
fn as_block(&self) -> u16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FeliCa Lite system block numbers.
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitflag(u16)]
|
|
||||||
pub enum Block {
|
|
||||||
#[base]
|
|
||||||
Unknown = 0,
|
|
||||||
/// Random challenge block.
|
|
||||||
RC = 0x8080,
|
|
||||||
/// MAC block.
|
|
||||||
MAC = 0x8081,
|
|
||||||
/// ID block.
|
|
||||||
ID = 0x8082,
|
|
||||||
/// Card key version block.
|
|
||||||
CKV = 0x8086,
|
|
||||||
/// Card key block.
|
|
||||||
CK= 0x8087,
|
|
||||||
/// Memory configuration block.
|
|
||||||
MemoryConfig = 0x8088,
|
|
||||||
/// Write counter block.
|
|
||||||
WriteCounter = 0x8090,
|
|
||||||
/// MAC_A block.
|
|
||||||
MACA = 0x8091
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsBlock for Block {
|
|
||||||
fn as_block(&self) -> u16 {
|
|
||||||
*self as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsBlock for u16 {
|
|
||||||
fn as_block(&self) -> u16 {
|
|
||||||
// A 2-byte block list element is `0x80 | block_number`: the MSB marks a
|
|
||||||
// 2-byte element (access mode 0 = standard area, service list order 0).
|
|
||||||
*self | 0x8000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[bitflag(u8)]
|
|
||||||
pub enum CardICType {
|
|
||||||
#[base]
|
|
||||||
Unknown = 0x00,
|
|
||||||
S140 = 0x50,
|
|
||||||
SA40_2P = 0x51,
|
|
||||||
SA41_2C = 0x52,
|
|
||||||
SA21_2 = 0x46,
|
|
||||||
SA20_2 = 0x45,
|
|
||||||
SA20_1 = 0x44,
|
|
||||||
SA01_2 = 0x35,
|
|
||||||
SA00_1 = 0x32,
|
|
||||||
S962 = 0x20,
|
|
||||||
S960 = 0x0D,
|
|
||||||
S953 = 0x09,
|
|
||||||
S952 = 0x08,
|
|
||||||
S915 = 0x01,
|
|
||||||
S982 = 0xF1,
|
|
||||||
S978F = 0xF0,
|
|
||||||
S967LiteS = 0xF2,
|
|
||||||
S967Plug = 0xE1,
|
|
||||||
S967NFC = 0xFF,
|
|
||||||
S926 = 0xE0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum MobileICType {
|
|
||||||
V3(u8), // 0x14~0x1F
|
|
||||||
V2(u8), // 0x10~0x13
|
|
||||||
V1(u8) // 0x06~0x07
|
|
||||||
}
|
|
||||||
+2
-1
@@ -63,7 +63,8 @@ pub use error::*;
|
|||||||
pub use interface::*;
|
pub use interface::*;
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
#[cfg(feature = "defmt")]
|
||||||
use defmt::debug;
|
#[allow(unused_imports)]
|
||||||
|
use defmt::{error, warn, info, debug, trace};
|
||||||
|
|
||||||
#[cfg(not(feature = "defmt"))]
|
#[cfg(not(feature = "defmt"))]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
use crate::types::{LoraPublisher, SharedI2c};
|
use crate::types::{LoraPublisher, SharedI2c};
|
||||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
|
||||||
use defmt::Debug2Format;
|
use defmt::Debug2Format;
|
||||||
use embassy_sync::once_lock::OnceLock;
|
use embassy_sync::once_lock::OnceLock;
|
||||||
use embassy_time::{Delay, Timer};
|
use embassy_time::{Delay, Timer};
|
||||||
use esp_hal::gpio;
|
use esp_hal::gpio;
|
||||||
use esp_hal::rng::Trng;
|
use esp_hal::rng::Trng;
|
||||||
use lorawan_device::RngCore;
|
use pn532::felica::types::{FelicaPollingRequestCode};
|
||||||
use pn532::felica::{FelicaBlock, FelicaPollingRequestCode, FelicaServiceCode};
|
|
||||||
use pn532::{BaudRate, Pn532};
|
use pn532::{BaudRate, Pn532};
|
||||||
|
|
||||||
static CARD_KEY: OnceLock<heapless::Vec<u8, 16>> = OnceLock::new();
|
static CARD_KEY: OnceLock<[u8; 16]> = OnceLock::new();
|
||||||
|
const CARD_KEY_STR: &str = env!("CARD_KEY");
|
||||||
|
|
||||||
fn get_card_key() -> &'static heapless::Vec<u8, 16> {
|
fn get_card_key() -> &'static [u8; 16] {
|
||||||
CARD_KEY.get_or_init(|| {
|
CARD_KEY.get_or_init(|| {
|
||||||
let hex_string = env!("CARD_KEY");
|
|
||||||
let mut buffer = [0u8; 16];
|
let mut buffer = [0u8; 16];
|
||||||
hex::decode_to_slice(hex_string, &mut buffer).unwrap();
|
hex::decode_to_slice(CARD_KEY_STR, &mut buffer).unwrap();
|
||||||
heapless::Vec::from_array(buffer)
|
buffer
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,19 +39,17 @@ impl<'a> CardReader<'a> {
|
|||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn start_card_reader(
|
pub async fn start_card_reader(
|
||||||
mut card_reader: CardReader<'static>,
|
mut card_reader: CardReader<'static>,
|
||||||
mut publisher: LoraPublisher<'static>,
|
_publisher: LoraPublisher<'static>,
|
||||||
mut trng: Trng
|
mut trng: Trng
|
||||||
) -> () {
|
) -> () {
|
||||||
let card_key = get_card_key();
|
let card_key = get_card_key();
|
||||||
let mut rc = [0u8; 16];
|
|
||||||
trng.fill_bytes(&mut rc);
|
|
||||||
defmt::trace!("card_key: {}", hex::encode_upper(card_key));
|
defmt::trace!("card_key: {}", hex::encode_upper(card_key));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
Timer::after_millis(50).await;
|
Timer::after_millis(50).await;
|
||||||
let Some(response) = (match card_reader.driver.felica_polling(
|
let Some(response) = (match card_reader.driver.felica_polling(
|
||||||
None,
|
None,
|
||||||
BaudRate::Felica424kbps,
|
BaudRate::Felica212kbps,
|
||||||
FelicaPollingRequestCode::SystemCode,
|
FelicaPollingRequestCode::SystemCode,
|
||||||
core::time::Duration::from_secs(1),
|
core::time::Duration::from_secs(1),
|
||||||
) {
|
) {
|
||||||
@@ -68,24 +64,56 @@ pub async fn start_card_reader(
|
|||||||
};
|
};
|
||||||
defmt::info!("Polling card: {}", Debug2Format(&response));
|
defmt::info!("Polling card: {}", Debug2Format(&response));
|
||||||
|
|
||||||
let mut data = [[0u8; 16]; 3];
|
// if let Err(e) = card_reader.driver.felica_write_without_encryption(
|
||||||
// user blocks 0x0000..0x000D via the raw u16 AsBlock impl; a single read
|
// &response.idm, &response.pmm,
|
||||||
// is capped at 3 blocks by the 64-byte packet buffer (response = 14 + 16n).
|
// &[FelicaLiteSServiceCode::Write],
|
||||||
let blocks: [u16; 3] = [0x0000, 0x0001, 0x0002];
|
// &[FelicaLiteSBlock::CK],
|
||||||
match card_reader.driver.felica_read_without_encryption(
|
// &[*card_key]
|
||||||
|
// ) {
|
||||||
|
// defmt::error!("card_reader write ck error: {}", e);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
let (rc1, rc2) = match card_reader.driver.set_random_challenge(
|
||||||
&response.idm,
|
&response.idm,
|
||||||
&response.pmm,
|
&response.pmm,
|
||||||
&[FelicaServiceCode::Read],
|
&mut trng
|
||||||
&blocks,
|
|
||||||
&mut data,
|
|
||||||
) {
|
) {
|
||||||
Ok(_) => {},
|
Ok(rc) => rc,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
defmt::error!("unable to read blocks: {}", e);
|
defmt::error!("card_reader rc error: {}", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
defmt::debug!("Blocks: {:?}", data);
|
defmt::info!("RC: rc1: {:02X}, rc2: {:02X}", rc1, rc2);
|
||||||
|
|
||||||
|
let mut rc = [0u8; 16];
|
||||||
|
rc[..8].copy_from_slice(&rc1);
|
||||||
|
rc[8..].copy_from_slice(&rc2);
|
||||||
|
|
||||||
|
let (sk1, sk2) = match card_reader.driver.get_session_keys(&rc, card_key) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
defmt::error!("card_reader sk error: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
defmt::info!("sk1: {:02X} sk2: {:02X}", sk1, sk2);
|
||||||
|
|
||||||
|
let mac = match card_reader.driver.check_mac(
|
||||||
|
&response.idm,
|
||||||
|
&response.pmm,
|
||||||
|
&sk1,
|
||||||
|
&sk2,
|
||||||
|
&rc1
|
||||||
|
) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
defmt::error!("card_driver mac check error: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
defmt::info!("mac check: {}", mac);
|
||||||
|
|
||||||
match card_reader.driver.release() {
|
match card_reader.driver.release() {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ use crate::types::{LoraPayload, LoraPublisher, LoraSubscriber, SharedAsyncSpi};
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
|
||||||
use defmt::Formatter;
|
use defmt::Formatter;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
|
||||||
use embassy_sync::pubsub::{Publisher, Subscriber};
|
|
||||||
use embassy_time::{Delay, Duration, Instant, Timer};
|
use embassy_time::{Delay, Duration, Instant, Timer};
|
||||||
use esp_hal::gpio;
|
use esp_hal::gpio;
|
||||||
use esp_hal::rng::Rng;
|
use esp_hal::rng::Rng;
|
||||||
|
|||||||
Reference in New Issue
Block a user