diff --git a/.cargo/config.toml b/.cargo/config.toml index 0b9a9ef..a5c1398 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,7 +3,7 @@ runner = "espflash flash --monitor -B 921600 --chip esp32s3 --log-format defmt" linker = "/home/fromost/.rustup/toolchains/esp/xtensa-esp-elf/esp-15.2.0_20250920/xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc" [env] -DEFMT_LOG = "info,lora=trace,eeprom24x=debug,pn532=trace" +DEFMT_LOG = "info,lora=trace,eeprom24x=debug,pn532=debug" [build] rustflags = [ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..83f9a5e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.pdf filter=lfs, diff=lfs merge=lfs -text \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8adb403 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 2b99cc2..bbde586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,21 +15,21 @@ test = false debug = [] [dependencies] -esp-hal = { version = "~1.1", features = ["defmt", "esp32s3", "unstable"] } -esp-rtos = { version = "0.3.0", features = [ +esp-hal = { version = "~1.2", features = ["defmt", "esp32s3", "unstable"] } +esp-rtos = { version = "0.4.0", features = [ "defmt", "embassy", "esp-alloc", "esp32s3", ] } -esp-alloc = { version = "0.10.0", features = ["defmt"] } -esp-backtrace = { version = "0.19.0", features = [ +esp-alloc = { version = "0.11.0", features = ["defmt"] } +esp-backtrace = { version = "0.20.0", features = [ "defmt", "esp32s3", "panic-handler", ] } -esp-println = { version = "0.17.0", features = ["defmt-espflash", "esp32s3"] } -esp-bootloader-esp-idf = { version = "0.5.0", features = ["defmt", "esp32s3"] } +esp-println = { version = "0.18.0", features = ["defmt-espflash", "esp32s3"] } +esp-bootloader-esp-idf = { version = "0.6.0", features = ["defmt", "esp32s3"] } embassy-executor = { version = "0.10.0", features = ["defmt"] } embassy-time = { version = "0.5.0", features = ["defmt", "generic-queue-8", "defmt-timestamp-uptime"] } diff --git a/build.rs b/build.rs index a7f6f8b..d4a692c 100644 --- a/build.rs +++ b/build.rs @@ -2,10 +2,10 @@ fn main() { println!("cargo:rerun-if-changed=Cargo.toml"); patch_crate::run().expect("Failed while patching"); - let iter = dotenvy::from_path_iter(".env") + let environment = dotenvy::from_path_iter(".env") .expect("failed to read .env"); - for item in iter { + for item in environment { let (key, value) = item.expect("invalid .env entry"); println!("cargo::rustc-env={key}={value}"); diff --git a/pn532/datasheets/felica_lite_s.pdf b/pn532/datasheets/felica_lite_s.pdf new file mode 100644 index 0000000..c03148c Binary files /dev/null and b/pn532/datasheets/felica_lite_s.pdf differ diff --git a/pn532/src/driver.rs b/pn532/src/driver.rs index 163c891..68eb676 100644 --- a/pn532/src/driver.rs +++ b/pn532/src/driver.rs @@ -1,14 +1,14 @@ //! High-level PN532 command driver. -use core::marker::PhantomData; use crate::commands::*; use crate::error::Error; use crate::interface::Interface; -use crate::{NoIrq, NoReset, StatusCode}; +use crate::types::constants::{PN532_GPIO_P32, PN532_GPIO_P34, PN532_GPIO_VALIDATION_BIT}; +use crate::{StatusCode}; +use core::marker::PhantomData; use core::time::Duration; use embedded_hal::delay::DelayNs; use embedded_hal::digital::{InputPin, OutputPin}; -use crate::types::constants::{PN532_GPIO_P32, PN532_GPIO_P34, PN532_GPIO_VALIDATION_BIT}; /// Driver for the NXP PN532 NFC controller. pub struct Pn532 @@ -16,7 +16,7 @@ where RST: OutputPin, IRQ: InputPin, DELAY: DelayNs, - B: Interface + B: Interface, { pub(crate) interface: B, pub(crate) in_listed_tag: u8, @@ -31,7 +31,7 @@ where RST: OutputPin, IRQ: InputPin, DELAY: DelayNs, - B: Interface + B: Interface, { /// Create a driver over the given transport interface. pub fn new(interface: B) -> Self { @@ -45,19 +45,24 @@ where } } - /// Initialise the PN532: pulse the reset pin (if provided) and wait for it + /// Initialize the PN532: pulse the reset pin (if provided) and wait for it /// to become ready. pub fn begin(&mut self) -> Result<(), Error> { - if let Err(Error::UnusedPin) = self.interface.begin() { - crate::debug!("No reset pin") + match self.interface.begin() { + Ok(_) => Ok(()), + Err(Error::UnusedPin) => { + crate::debug!("Unused pin"); + Ok(()) + }, + Err(e) => Err(e), } - Ok(()) } // -- internal helpers --------------------------------------------------- pub(crate) fn send(&mut self, header_len: usize) -> Result<(), Error> { - self.interface.write_command(&self.buffer[..header_len], &[]) + self.interface + .write_command(&self.buffer[..header_len], &[]) } pub(crate) fn send_with_body( @@ -73,8 +78,12 @@ where self.interface.read_response(&mut self.buffer, 1000) } - pub(crate) fn read_timeout(&mut self, timeout: Duration) -> Result> { - self.interface.read_response(&mut self.buffer, timeout.as_millis() as u16) + pub(crate) fn read_timeout( + &mut self, + timeout: Duration, + ) -> Result> { + self.interface + .read_response(&mut self.buffer, timeout.as_millis() as u16) } // -- generic PN532 functions ------------------------------------------- @@ -120,10 +129,10 @@ where } /// Set the PN532's GPIO pins (see the PN532 user manual for valid pins). - pub fn write_gpio(&mut self, pinstate: u8) -> Result<(), Error> { - let pinstate = pinstate | (1 << PN532_GPIO_P32) | (1 << PN532_GPIO_P34); + pub fn write_gpio(&mut self, pin_state: u8) -> Result<(), Error> { + let pin_state = pin_state | (1 << PN532_GPIO_P32) | (1 << PN532_GPIO_P34); self.buffer[0] = PN532Command::WriteGpio.into_bits(); - self.buffer[1] = PN532_GPIO_VALIDATION_BIT | pinstate; + self.buffer[1] = PN532_GPIO_VALIDATION_BIT | pin_state; self.buffer[2] = 0x00; self.send(3)?; let len = self.read()?; @@ -180,7 +189,7 @@ where self.read()?; Ok(()) } - + /// return error if current buffer contains an error code pub(crate) fn validate_buffer(&self, len: usize) -> Result<(), Error> { if len == 0 || (self.buffer[0] & 0x3F) != 0 { @@ -190,8 +199,6 @@ where } } - - /// Release the card. pub fn release(&mut self) -> Result<(), Error> { self.buffer[0] = PN532Command::InRelease.into_bits(); diff --git a/pn532/src/interface/i2c.rs b/pn532/src/interface/i2c.rs index b53b3b8..1bb3113 100644 --- a/pn532/src/interface/i2c.rs +++ b/pn532/src/interface/i2c.rs @@ -118,11 +118,7 @@ where buf: &mut [u8], timeout_ms: u16, ) -> Result> { - // Wait for the PN532 to signal data is ready. self.wait_ready(timeout_ms)?; - - // Read the full response frame in a single transaction, stripping the - // RDY byte. Frame layout: PREAMBLE(3) + LEN + LCS + TFI + CMD + payload + DCS + POSTAMBLE. let frame_len = buf.len() + 9; if frame_len > MAX_DATA_LEN + 10 { return Err(Error::NoSpace); @@ -148,18 +144,11 @@ where IRQ: InputPin, { /// Wait until the PN532 signals data is ready. - /// - /// With an IRQ pin this blocks until the pin goes low; without one it polls - /// the I2C status byte. Either way the status byte is consumed, so the next - /// read returns the frame itself. fn wait_ready(&mut self, timeout_ms: u16) -> Result<(), Error> { let mut elapsed = 0u16; loop { match self.irq.is_low() { Ok(true) => { - // IRQ asserted (active-low): data is ready. Poll the status - // byte to confirm and consume it so the next read gets the - // frame itself. let mut status = [0u8; 1]; match self.i2c.read(PN532_I2C_ADDRESS, &mut status) { Ok(()) if status[0] & 1 == 1 => return Ok(()), @@ -173,8 +162,6 @@ where } } Ok(false) | Err(_) => { - // IRQ connected but not asserted yet (or a read error): - // keep waiting. self.delay.delay_ms(1); elapsed += 1; if timeout_ms != 0 && elapsed >= timeout_ms { diff --git a/pn532/src/protocol/felica/felica_lite_s/auth.rs b/pn532/src/protocol/felica/felica_lite_s/auth.rs index 48f981d..f07c8ff 100644 --- a/pn532/src/protocol/felica/felica_lite_s/auth.rs +++ b/pn532/src/protocol/felica/felica_lite_s/auth.rs @@ -40,66 +40,39 @@ where let cipher = TdesEde2::new(&key); Ok(SessionKeys::new(&cipher, &rc)) - - // crate::trace!("RC: {:02X}", rc); - // crate::trace!("CK DES key: {:02X}", des_key); - // crate::trace!("RC DES input: {:02X}", des_rc); - // crate::trace!("SK1 DES: {:02X}", sk1_des); - // crate::trace!("SK1: {:02X}", sk1); - // crate::trace!("SK2 DES: {:02X}", sk2_des); - // crate::trace!("SK2: {:02X}", sk2); } - pub(crate) fn compute_mac( + /// 2-key Triple DES-CBC MAC over `block_list` followed by `block_data`. + pub(crate) fn compute_mac( &mut self, session: &AuthSession, - input: &MacInput + key_bytes: [u8; 16], + block_list: &[u8; 8], + block_data: &[u8], ) -> [u8; 8] { - // 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 = &input.block_list(); - let block_data = &input.block_data(); - - // FeliCa Lite-S uses the session keys with their byte order reversed - // at the DES interface. - let key_bytes = session.sk.as_des(); - let key = Key::::try_from(key_bytes).unwrap(); let cipher = TdesEde2::new(&key); - // RC1 is also byte-reversed at the DES interface. let mut state_bytes = session.rc.rc1(); 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(); + + let mut block_list = block_list.clone(); + block_list.reverse(); for i in 0..8 { - state_bytes[i] ^= first[i]; + state_bytes[i] ^= block_list[i]; } - let mut state = Block::::try_from(state_bytes).unwrap(); - cipher.encrypt_block(&mut state); - - // Process ID and CKV. - // - // Each 8-byte piece is byte-reversed before entering the - // 2-key 3DES CBC operation. + let mut state_block = Block::::try_from(state_bytes).unwrap(); + cipher.encrypt_block(&mut state_block); + 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]; + input[i] ^= state_block[i]; } crate::trace!("MAC input: {:02X}", input); @@ -107,16 +80,26 @@ where let mut block = Block::::try_from(input).unwrap(); cipher.encrypt_block(&mut block); - state = block; + state_block = block; } - // Reverse the final result back to FeliCa byte order. - let mut mac: [u8; 8] = state.0; + let mut mac: [u8; 8] = state_block.0; mac.reverse(); - crate::trace!("mac: {:02X}, maca: {:02X}", mac, input.maca()); + crate::trace!("mac: {:02X}", mac); mac } + /// 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] { + 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]) + } + pub(crate) fn check_auth( &mut self, card: &CardSession, @@ -133,7 +116,12 @@ where let input = MacInput::new( &data, block_list ); - let mac = self.compute_mac(session, &input); + let mac = self.compute_mac( + session, + session.sk.as_des(), + &input.block_list(), + &input.block_data(), + ); Ok(mac == input.maca()) } } \ No newline at end of file diff --git a/pn532/src/protocol/felica/felica_lite_s/issuance.rs b/pn532/src/protocol/felica/felica_lite_s/issuance.rs index 6a57731..e94fab5 100644 --- a/pn532/src/protocol/felica/felica_lite_s/issuance.rs +++ b/pn532/src/protocol/felica/felica_lite_s/issuance.rs @@ -1,6 +1,9 @@ use embedded_hal::delay::DelayNs; use embedded_hal::digital::{InputPin, OutputPin}; -use crate::{Interface, Pn532}; +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; impl Pn532 where @@ -9,7 +12,92 @@ where DELAY: DelayNs, B: Interface { - pub fn setup_first_issuance(&mut self) { - todo!() + pub fn set_id(&mut self, card: &CardSession, arbitrary: [u8; 6], dfc: Option<[u8; 2]>) -> Result<(), Error> { + 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] + ) + } + + pub fn get_id(&mut self, card: &CardSession) -> Result<[u8; 16], Error> { + let mut block = [[0u8; 16]]; + 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> { + self.write_block( + card, + &[FelicaLiteSBlock::CK], + &[**key] + ) + } + + pub fn set_ck_with_mac(&mut self, card: &CardSession, session: &AuthSession, key: &CardKey) -> Result<(), Error> { + self.write_with_mac( + card, + session, + FelicaLiteSBlock::CK, + &**key + ) + } + + pub fn set_ckv(&mut self, card: &CardSession, version: &CardKeyVersion) -> Result<(), Error> { + 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> { + self.write_with_mac( + card, + session, + FelicaLiteSBlock::CKV, + &version.as_block() + ) + } + + pub fn get_ckv(&mut self, card: &CardSession) -> Result> { + let mut block = [[0u8; 16]]; + 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> { + let mut block = [[0u8; 16]]; + 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> { + let block: [u8; 16] = config.into(); + self.write_block( + card, + &[FelicaLiteSBlock::MemoryConfig], + &[block] + ) } } \ No newline at end of file diff --git a/pn532/src/protocol/felica/felica_lite_s/mod.rs b/pn532/src/protocol/felica/felica_lite_s/mod.rs index 3309ecf..2eaf293 100644 --- a/pn532/src/protocol/felica/felica_lite_s/mod.rs +++ b/pn532/src/protocol/felica/felica_lite_s/mod.rs @@ -23,11 +23,12 @@ where let sk = self.get_session_keys(&rc, key)?; let session = AuthSession::new(sk, rc); let is_valid_auth = self.check_auth(card, &session)?; - if is_valid_auth { - Ok(Some(session)) - } else { - Ok(None) + + if !is_valid_auth { + return Ok(None); } + self.try_external_authentication(card, &session)?; + Ok(Some(session)) } pub fn read_with_mac( @@ -40,7 +41,12 @@ where 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(session, &input); + let mac = self.compute_mac( + session, + session.sk.as_des(), + &input.block_list(), + &input.block_data(), + ); if mac != input.maca() { return Err(crate::Error::FelicaError(Error::MACMismatch)); } @@ -48,6 +54,47 @@ where Ok(block_data[0]) } + /// Write one data block protected by a Write With MAC (manual §5.4.4). + pub fn write_with_mac( + &mut self, + card: &CardSession, + session: &AuthSession, + block: impl AsBlock, + data: &[u8; 16], + ) -> Result<(), crate::Error> { + let mut wcnt_block = [[0u8; 16]]; + self.read_block(card, &[FelicaLiteSBlock::WCNT], &mut wcnt_block)?; + let mut wcnt = [0u8; 3]; + wcnt.copy_from_slice(&wcnt_block[0][0..3]); + + let mut plain = [0u8; 24]; + plain[0..3].copy_from_slice(&wcnt); + plain[4] = block.as_block() as u8; + plain[6] = FelicaLiteSBlock::MACA.as_block() as u8; + plain[8..24].copy_from_slice(data); + + let mac = self.compute_write_mac(session, &plain); + + let mut mac_a = [0u8; 16]; + mac_a[0..8].copy_from_slice(&mac); + mac_a[8..11].copy_from_slice(&wcnt); + + let block_list = [block.as_block(), FelicaLiteSBlock::MACA as u16]; + let block_data = [*data, mac_a]; + self.write_block(card, &block_list, &block_data) + } + + /// Complete External Authentication (manual §5.4.2, Figure 5-10). + pub(crate) fn try_external_authentication( + &mut self, + card: &CardSession, + session: &AuthSession, + ) -> Result<(), crate::Error> { + let mut state = [0u8; 16]; + state[0] = 0x01; // EXT_AUTH: after authentication + self.write_with_mac(card, session, FelicaLiteSBlock::STATE, &state) + } + pub fn read_block( &mut self, card: &CardSession, diff --git a/pn532/src/protocol/felica/mod.rs b/pn532/src/protocol/felica/mod.rs index 1e4e0a9..fc44910 100644 --- a/pn532/src/protocol/felica/mod.rs +++ b/pn532/src/protocol/felica/mod.rs @@ -184,12 +184,13 @@ where 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); } - if response[9] != 0 || response[10] != 0 { - return Err(Error::FelicaError(FelicaError::Status(response[9], response[10]))); - } let mut k = 12; for block in block_data.iter_mut().take(U) { @@ -253,12 +254,13 @@ where &mut response, 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); } - if response[9] != 0 || response[10] != 0 { - return Err(Error::FelicaError(FelicaError::Status(response[9], response[10]))); - } Ok(()) } } diff --git a/pn532/src/types/felica/felica_lite_s/auth/card_key.rs b/pn532/src/types/felica/felica_lite_s/auth/card_key.rs index 4394979..09d75d5 100644 --- a/pn532/src/types/felica/felica_lite_s/auth/card_key.rs +++ b/pn532/src/types/felica/felica_lite_s/auth/card_key.rs @@ -3,7 +3,7 @@ use core::ops::Deref; #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct CardKey(pub [u8; 16]); +pub struct CardKey([u8; 16]); impl CardKey { /// FeliCa byte order: @@ -50,4 +50,41 @@ impl Display for CardKey { write!(f, "{:?}", self.0) } } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CardKeyVersion([u8; 2]); + +impl CardKeyVersion { + pub fn new(version: u16) -> Self { + let bytes = version.to_be_bytes(); + CardKeyVersion(bytes) + } + + pub fn as_block(&self) -> [u8; 16] { + let mut block = [0u8; 16]; + block[0] = self.0[0]; + block[1] = self.0[1]; + block + } +} + +impl From<[u8; 16]> for CardKeyVersion { + fn from(val: [u8; 16]) -> Self { + CardKeyVersion([val[0], val[1]]) + } +} + +impl Display for CardKeyVersion { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + #[cfg(feature = "alloc")] + { + write!(f, "{}", hex::encode_upper(self.0)) + } + #[cfg(not(feature = "alloc"))] + { + write!(f, "{:?}", self.0) + } + } } \ No newline at end of file diff --git a/pn532/src/types/felica/felica_lite_s/auth/sk.rs b/pn532/src/types/felica/felica_lite_s/auth/sk.rs index 0a3e1cc..ef1d8cd 100644 --- a/pn532/src/types/felica/felica_lite_s/auth/sk.rs +++ b/pn532/src/types/felica/felica_lite_s/auth/sk.rs @@ -41,13 +41,23 @@ impl SessionKeys { } pub fn as_des(&self) -> [u8; 16] { + Self::key_des(&self.sk1, &self.sk2) + } + + /// Session keys in DES byte order with the halves swapped (`SK2 || SK1`), + /// as required by the data-write MAC generation. + pub fn as_des_write(&self) -> [u8; 16] { + Self::key_des(&self.sk2, &self.sk1) + } + + fn key_des(first: &[u8; 8], second: &[u8; 8]) -> [u8; 16] { let mut key_bytes = [0u8; 16]; for i in 0..8 { - key_bytes[i] = self.sk1[7 - i]; - key_bytes[8 + i] = self.sk2[7 - i]; + key_bytes[i] = first[7 - i]; + key_bytes[8 + i] = second[7 - i]; } - + key_bytes } diff --git a/pn532/src/types/felica/felica_lite_s/config/access_level.rs b/pn532/src/types/felica/felica_lite_s/config/access_level.rs new file mode 100644 index 0000000..b331e9b --- /dev/null +++ b/pn532/src/types/felica/felica_lite_s/config/access_level.rs @@ -0,0 +1,10 @@ +use bitfields::bitflag; + +#[bitflag(u8)] +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AccessLevel { + ReadOnly = 0, + #[base] + ReadWrite = 1, +} \ No newline at end of file diff --git a/pn532/src/types/felica/felica_lite_s/config/mod.rs b/pn532/src/types/felica/felica_lite_s/config/mod.rs new file mode 100644 index 0000000..f962038 --- /dev/null +++ b/pn532/src/types/felica/felica_lite_s/config/mod.rs @@ -0,0 +1,185 @@ +mod access_level; +use access_level::AccessLevel; + +use bitfields::bitfield; + +#[bitfield([u8; 4], from_endian = little, into_endian = little)] +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Access { + #[bits(16)] + pub scratch_pad: ScratchPadAccessLevel, + /// 0xFF: ReadWrite + /// Other than 0xFF: Readonly + #[bits(8, default = 0)] + pub system_block: u8, + #[bits(1)] + pub is_ck_ckv_rewritable: bool, + #[bits(7)] + _reserved: u8, +} + +#[bitfield([u8; 7], from_endian = little, into_endian = little)] +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Authentication { + #[bits(16)] + pub scratch_pad_read: ScratchPadAuth, + #[bits(16)] + pub scratch_pad_write: ScratchPadAuth, + #[bits(16)] + pub scratch_pad_mac_write: ScratchPadAuth, + #[bits(1, default = false)] + pub state_with_mac: bool, + #[bits(7, default = 0)] + _reserved_state: u8, +} + +#[bitfield(u16)] +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScratchPadAccessLevel { + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad0: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad1: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad2: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad3: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad4: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad5: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad6: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad7: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad8: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad9: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad10: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad11: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad12: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub pad13: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub reg: AccessLevel, + #[bits(1, default = AccessLevel::ReadWrite)] + pub mc: AccessLevel, +} + +#[bitfield(u16)] +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScratchPadAuth { + #[bits(1, default = false)] + pub pad0: bool, + #[bits(1, default = false)] + pub pad1: bool, + #[bits(1, default = false)] + pub pad2: bool, + #[bits(1, default = false)] + pub pad3: bool, + #[bits(1, default = false)] + pub pad4: bool, + #[bits(1, default = false)] + pub pad5: bool, + #[bits(1, default = false)] + pub pad6: bool, + #[bits(1, default = false)] + pub pad7: bool, + #[bits(1, default = false)] + pub pad8: bool, + #[bits(1, default = false)] + pub pad9: bool, + #[bits(1, default = false)] + pub pad10: bool, + #[bits(1, default = false)] + pub pad11: bool, + #[bits(1, default = false)] + pub pad12: bool, + #[bits(1, default = false)] + pub pad13: bool, + #[bits(1, default = false)] + pub reg: bool, + #[bits(1, default = false)] + _reserved: bool, +} + +#[bitfield(u8)] +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RFParameter { + #[bits(3, default = 0x07)] + pub parameter: u8, + #[bits(5, default = 0x1F)] + _reserved: u8, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct MemoryConfig { + pub access: Access, + pub rf: RFParameter, + pub auth: Authentication, + /// 0x01: compatible + /// 0x00: incompatible + /// 0x02~0xFF: prohibited + pub ndef: u8, + _reserved: [u8; 3], +} + +impl Default for MemoryConfig { + fn default() -> Self { + Self { + access: Access::from([0xFF, 0xFF, 0xFF, 0x00]), + rf: RFParameter::default(), + ndef: 0x00, + auth: Authentication::default(), + _reserved: [0u8; 3], + } + } +} + +impl Into<[u8; 16]> for MemoryConfig { + fn into(self) -> [u8; 16] { + let mut block = [0u8; 16]; + + let access = self.access.into_bytes(); + + block[0] = access[0]; + block[1] = access[1]; + block[2] = access[2]; + block[3] = self.ndef; + block[4] = self.rf.into_bits(); + block[5] = access[3]; + + block[6..13].copy_from_slice(&self.auth.into_bytes()); + + block + } +} + +impl From<[u8; 16]> for MemoryConfig { + fn from(value: [u8; 16]) -> Self { + let access = Access::from([value[0], value[1], value[2], value[5]]); + + let rf = RFParameter::from(value[4]); + + let auth: [u8; 7] = value[6..13].try_into().unwrap(); + let auth = Authentication::from(auth); + + Self { + access, + rf, + auth, + ndef: value[3], + _reserved: value[13..16].try_into().unwrap(), + } + } +} diff --git a/pn532/src/types/felica/felica_lite_s/mod.rs b/pn532/src/types/felica/felica_lite_s/mod.rs index b07e0f5..d88de8c 100644 --- a/pn532/src/types/felica/felica_lite_s/mod.rs +++ b/pn532/src/types/felica/felica_lite_s/mod.rs @@ -2,9 +2,10 @@ ///! 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, IDm, PMm}; +use super::AsBlock; use super::AsServiceCode; /// Service Code @@ -68,7 +69,9 @@ pub enum Block { /// Write counter block. WCNT = 0x8090, /// MAC_A block. - MACA = 0x8091 + MACA = 0x8091, + /// STATE block (EXT_AUTH / POLL_DIS). + STATE = 0x8092 } impl AsBlock for Block { diff --git a/pn532/src/types/felica/ic_type.rs b/pn532/src/types/felica/ic_type.rs index ca01699..c0474d4 100644 --- a/pn532/src/types/felica/ic_type.rs +++ b/pn532/src/types/felica/ic_type.rs @@ -7,25 +7,8 @@ use bitfields::bitflag; 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 + S966 = 0xF1, + S965 = 0xF0, } /// https://www.sony.co.jp/en/Products/felica/business/tech-support/list.html diff --git a/pn532/src/types/felica/idm.rs b/pn532/src/types/felica/idm.rs index 52440db..970ab2f 100644 --- a/pn532/src/types/felica/idm.rs +++ b/pn532/src/types/felica/idm.rs @@ -1,7 +1,7 @@ use core::fmt::{Display, Formatter}; use bitfields::bitfield; -#[bitfield([u8; 8], from_endian = big, into_endian = big)] +#[bitfield([u8; 8], from_endian = little, into_endian = little)] #[derive(PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct IDm { diff --git a/pn532/src/types/felica/pmm.rs b/pn532/src/types/felica/pmm.rs index 64ee2d9..a480df6 100644 --- a/pn532/src/types/felica/pmm.rs +++ b/pn532/src/types/felica/pmm.rs @@ -2,7 +2,7 @@ use core::fmt::{Display, Formatter}; use core::time::Duration; use bitfields::bitfield; -#[bitfield([u8; 8])] +#[bitfield([u8; 8], from_endian = little, into_endian = little)] #[derive(PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PMm { diff --git a/src/board.rs b/src/board.rs index d16d327..76b4b60 100644 --- a/src/board.rs +++ b/src/board.rs @@ -13,7 +13,6 @@ use embassy_sync::pubsub::PubSubChannel; use embassy_time::{Duration, Timer}; use esp_hal::gpio::{InputConfig, Level, Pull}; use esp_hal::i2c::master::I2c; -use esp_hal::interrupt::software::SoftwareInterruptControl; use esp_hal::peripherals::Peripherals; use esp_hal::rng::{Trng, TrngSource}; use esp_hal::spi::master::{Config, Spi}; @@ -22,6 +21,7 @@ use esp_hal::{gpio, Async, Blocking}; use static_cell::StaticCell; pub(crate) static LED_STATE: AtomicBool = AtomicBool::new(false); +static TRNG_SOURCE: StaticCell = StaticCell::new(); pub struct Board { spawner: Spawner, @@ -29,16 +29,19 @@ pub struct Board { clock: Clock<'static>, storage: RomStorage<'static>, card_reader: CardReader<'static>, - trng: Trng + trng: Trng, } impl Board { - pub async fn new(spawner: Spawner, peripherals: Peripherals) -> Result { + pub async fn new( + spawner: Spawner, + peripherals: Peripherals, + ) -> Result { let timer_group = TimerGroup::new(peripherals.TIMG0); - let sw_ctrl = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); - esp_rtos::start(timer_group.timer0, sw_ctrl.software_interrupt0); + esp_rtos::start(timer_group.timer0, peripherals.FROM_CPU_INTR0); - static SPI_BUS: StaticCell>> = StaticCell::new(); + static SPI_BUS: StaticCell>> = + StaticCell::new(); let spi = Spi::new(peripherals.SPI2, Config::default())? .into_async() .with_sck(peripherals.GPIO9) @@ -46,21 +49,22 @@ impl Board { .with_miso(peripherals.GPIO11); let spi_bus = SPI_BUS.init(Mutex::new(spi)); - static I2C_BUS: StaticCell>>> = StaticCell::new(); - let async_i2c = I2c::new( - peripherals.I2C0, - esp_hal::i2c::master::Config::default() - )? + static I2C_BUS: StaticCell>>> = + StaticCell::new(); + let async_i2c = I2c::new(peripherals.I2C0, esp_hal::i2c::master::Config::default())? .with_sda(peripherals.GPIO21) .with_scl(peripherals.GPIO19); let i2c_bus = I2C_BUS.init(BlockingMutex::new(RefCell::new(async_i2c))); - - let irq = gpio::Input::new(peripherals.GPIO6, InputConfig::default().with_pull(Pull::Up)); + + let irq = gpio::Input::new( + peripherals.GPIO6, + InputConfig::default().with_pull(Pull::Up), + ); let reset = gpio::Output::new(peripherals.GPIO5, Level::High, Default::default()); let card_reader = CardReader::new(SharedI2c::new(i2c_bus), irq, reset); - - let vext = gpio::Output::new(peripherals.GPIO36, Level::Low, Default::default()); + let lora = { + let vext = gpio::Output::new(peripherals.GPIO36, Level::Low, Default::default()); let reset = gpio::Output::new(peripherals.GPIO12, Level::Low, Default::default()); let busy = gpio::Input::new(peripherals.GPIO13, InputConfig::default()); let dio1 = gpio::Input::new(peripherals.GPIO14, InputConfig::default()); @@ -70,62 +74,57 @@ impl Board { Lora::new(spi_device, reset, busy, dio1, vext).await? }; let clock = Clock::new(SharedI2c::new(i2c_bus), 0x68)?; - + let led = gpio::Output::new(peripherals.GPIO35, Level::Low, Default::default()); spawner.spawn(led_task(led)?); - - let storage = RomStorage::new( - SharedI2c::new(i2c_bus) - ); - + + let storage = RomStorage::new(SharedI2c::new(i2c_bus)); + let trng_source = TrngSource::new(peripherals.RNG, peripherals.ADC1); - core::mem::forget(trng_source); + TRNG_SOURCE.init(trng_source); let trng = Trng::try_new()?; - - let instance = Self { + + let instance = Self { spawner, lora, clock, storage, card_reader, - trng + trng, }; Ok(instance) } pub async fn start(mut self) -> Result<(), crate::error::Error> { + static LORA_CHANNEL: StaticCell = StaticCell::new(); + let lora_channel = LORA_CHANNEL.init(PubSubChannel::new()); + self.card_reader.init()?; self.lora.try_join().await?; defmt::info!("Board started"); LED_STATE.store(true, Ordering::Relaxed); - + let time = self.lora.get_time(self.clock.get_datetime()?).await?; let datetime = self.clock.update_time(time)?; let app_data = AppData { - last_boot_time: datetime + last_boot_time: datetime, }; defmt::debug!("stored app data: {}", self.storage.get_data::()?); self.storage.write_data(&app_data).await?; - static LORA_CHANNEL: StaticCell = StaticCell::new(); - let lora_channel = LORA_CHANNEL.init(PubSubChannel::new()); - - self.spawner.spawn( - components::card_reader::start_card_reader( - self.card_reader, - lora_channel.publisher()?, - self.trng.clone() - )? - ); - self.spawner.spawn( - components::lora::start_send_data_task( - self.lora, - lora_channel.subscriber()? - )? - ); - self.spawner.spawn( - components::lora::start_heartbeat_task(lora_channel.publisher()?)? - ); + self.spawner + .spawn(components::card_reader::start_card_reader( + self.card_reader, + lora_channel.publisher()?, + self.trng.clone(), + )?); + self.spawner.spawn(components::lora::start_send_data_task( + self.lora, + lora_channel.subscriber()?, + )?); + self.spawner.spawn(components::lora::start_heartbeat_task( + lora_channel.publisher()?, + )?); loop { Timer::after_millis(100).await; @@ -143,4 +142,4 @@ async fn led_task(mut led: gpio::Output<'static>) { } Timer::after(Duration::from_millis(50)).await; } -} \ No newline at end of file +} diff --git a/src/components/card_reader.rs b/src/components/card_reader.rs index 4dd940a..036eea8 100644 --- a/src/components/card_reader.rs +++ b/src/components/card_reader.rs @@ -4,10 +4,10 @@ use embassy_sync::once_lock::OnceLock; use embassy_time::{Delay, Timer}; use esp_hal::gpio; use esp_hal::rng::Trng; -use pn532::types::felica::{CardSession, FelicaLiteSBlock, FelicaPollingRequestCode}; -use pn532::{Pn532}; -use pn532::types::felica::felica_lite_s::auth::CardKey; -use pn532::types::BaudRate; +use pn532::types::felica::felica_lite_s::auth::{CardKey, CardKeyVersion}; +use pn532::types::felica::felica_lite_s::config::{MemoryConfig, RFParameter}; +use pn532::types::felica::{CardSession, FelicaLiteSBlock}; +use pn532::Pn532; static CARD_KEY: OnceLock = OnceLock::new(); const CARD_KEY_STR: &str = env!("CARD_KEY"); @@ -16,24 +16,30 @@ fn get_card_key() -> &'static CardKey { CARD_KEY.get_or_init(|| { let mut buffer = [0u8; 16]; hex::decode_to_slice(CARD_KEY_STR, &mut buffer).unwrap(); - CardKey(buffer) + CardKey::from(buffer) }) } pub struct CardReader<'a> { - driver: Pn532, gpio::Input<'a>, Delay, pn532::I2cInterface, Delay, gpio::Output<'a>, gpio::Input<'a>>> + driver: Pn532< + gpio::Output<'a>, + gpio::Input<'a>, + Delay, + pn532::I2cInterface, Delay, gpio::Output<'a>, gpio::Input<'a>>, + >, } impl<'a> CardReader<'a> { pub fn new(i2c: SharedI2c<'a>, irq: gpio::Input<'a>, reset: gpio::Output<'a>) -> Self { Self { - driver: Pn532::new(pn532::I2cInterface::with_reset_irq(i2c, Delay, reset, irq)) + driver: Pn532::new(pn532::I2cInterface::with_reset_irq(i2c, Delay, reset, irq)), } } - + pub fn init(&mut self) -> Result<(), crate::error::Error> { self.driver.begin()?; self.driver.sam_config()?; + self.driver.set_passive_activation_retries(5)?; Ok(()) } } @@ -42,16 +48,29 @@ impl<'a> CardReader<'a> { pub async fn start_card_reader( mut card_reader: CardReader<'static>, _publisher: LoraPublisher<'static>, - mut trng: Trng + mut trng: Trng, ) -> () { + loop { + match _start_card_reader(&mut card_reader, &_publisher, &mut trng).await { + Ok(_) => {} + Err(e) => { + defmt::error!("Error while reading card: {}", e); + } + } + } +} + +async fn _start_card_reader( + card_reader: &mut CardReader<'static>, + _publisher: &LoraPublisher<'static>, + trng: &mut Trng, +) -> Result<(), crate::error::Error> { let card_key = get_card_key(); defmt::trace!("card_key: {}", card_key); - + let driver = &mut card_reader.driver; loop { - Timer::after_millis(50).await; - let Some(response) = (match card_reader.driver.felica_polling( - Default::default() - ) { + Timer::after_millis(10).await; + let Some(response) = (match driver.felica_polling(Default::default()) { Ok(v) => v, Err(e) => { defmt::error!("card_reader driver error: {}", e); @@ -62,8 +81,9 @@ pub async fn start_card_reader( continue; }; defmt::info!("Polling card: {}", Debug2Format(&response)); + defmt::debug!("IDm: {}", response.idm.into_bytes()); let card = CardSession::from(response); - + // if let Err(e) = card_reader.driver.felica_write_without_encryption( // &response.idm, &response.pmm, // &[FelicaLiteSServiceCode::Write], @@ -73,32 +93,64 @@ pub async fn start_card_reader( // defmt::error!("card_reader write ck error: {}", e); // continue; // } + let id = driver.get_id(&card)?; + defmt::debug!("ID: {}", id); + + if let Some(card_id) = option_env!("CARD_ID") { + let mut id = [0u8; 6]; + hex::decode_to_slice(card_id,&mut id)?; + driver.set_id(&card, id, None)?; + } + + let mut mc = driver.get_mc(&card)?; + defmt::info!("Config: {}", mc); + mc.access.set_system_block(0x00); + mc.ndef = 0x00; + mc.rf = RFParameter::from(0xFF); + mc.access.set_is_ck_ckv_rewritable(true); + + let mut read_auth = mc.auth.scratch_pad_read(); + read_auth.set_pad0(true); + mc.auth.set_scratch_pad_read(read_auth); + + let mut write_auth = mc.auth.scratch_pad_write(); + write_auth.set_pad0(true); + mc.auth.set_scratch_pad_write(write_auth); + + let mut mac_auth = mc.auth.scratch_pad_mac_write(); + mac_auth.set_pad0(true); + mc.auth.set_scratch_pad_mac_write(mac_auth); - let auth_result = card_reader.driver.try_authenticate( - &mut trng, &card, card_key - ); - let session = match auth_result { + let block: [u8; 16] = mc.into(); + defmt::debug!("1st insurance Config: {}", block); + driver.set_mc(&card, mc)?; + + let session = match driver.try_authenticate(trng, &card, card_key) { Ok(Some(sk)) => sk, Ok(None) => { defmt::warn!("card key is invalid"); continue; - }, + } Err(e) => { defmt::error!("card_reader auth error: {}", e); continue; } }; - let read_result = card_reader.driver.read_with_mac(&card, &session, FelicaLiteSBlock::PAD0); - let _read_data = match read_result { + + driver.set_ck_with_mac(&card, &session, &card_key)?; + driver.set_ckv_with_mac(&card, &session, &CardKeyVersion::new(1))?; + driver.get_ckv(&card)?; + + let _read_data = match driver.read_with_mac(&card, &session, FelicaLiteSBlock::PAD0) { Ok(d) => d, Err(e) => { defmt::error!("card_reader read with mac error: {}", e); continue; } }; - - match card_reader.driver.release() { - Ok(_) => {}, + + match driver.release() { + Ok(_) => {} Err(e) => { defmt::error!("card_reader release error: {}", e); continue; @@ -107,9 +159,9 @@ pub async fn start_card_reader( // InRelease switches the RF field off when no target remains // (UM0701-02 §7.3.11), so re-enable it before the next poll. - if let Err(e) = card_reader.driver.set_rf_field(0x00, 0x01) { + if let Err(e) = driver.set_rf_field(0x00, 0x01) { defmt::error!("card_reader RF field error: {}", e); } Timer::after_millis(200).await; } -} \ No newline at end of file +} diff --git a/src/components/rom_storage.rs b/src/components/rom_storage.rs index f1eb784..480e1c2 100644 --- a/src/components/rom_storage.rs +++ b/src/components/rom_storage.rs @@ -7,7 +7,7 @@ use embassy_time::{Duration, Timer}; use serde::de::DeserializeOwned; use serde::Serialize; -const ADDR_SIZE: usize = 256; +const BLOCK_SIZE: usize = 256; #[derive(thiserror::Error, Debug)] pub(crate) enum StorageError { @@ -44,7 +44,7 @@ impl<'d> RomStorage<'d> { Ok(addr.clone()) } else { let size = self.addr_table.len(); - let addr = size as u32 * ADDR_SIZE as u32; + let addr = size as u32 * BLOCK_SIZE as u32; if addr >= u16::MAX as u32 { return Err(StorageError::OutOfAddress) } @@ -58,7 +58,7 @@ impl<'d> RomStorage<'d> { let addr = self.get_or_insert_addr::()?; let bytes = postcard::to_allocvec(data)?; let bytes_len = bytes.len(); - if bytes_len > ADDR_SIZE { + if bytes_len > BLOCK_SIZE { return Err(StorageError::StorageOverSize(bytes_len).into()); } for (i, chunk) in bytes.chunks(32).enumerate() { @@ -74,7 +74,7 @@ impl<'d> RomStorage<'d> { pub fn get_data(&mut self) -> Result { let addr = self.get_or_insert_addr::()?; - let mut bytes = [0u8; ADDR_SIZE]; + let mut bytes = [0u8; BLOCK_SIZE]; self.driver.read_data(addr, &mut bytes) .map_err(|e| StorageError::EEPROMError(e))?; let data = postcard::from_bytes(&bytes)?;