Skip to content

Commit 1b5379c

Browse files
committed
feat: replace old mifare classic data reader with ntag reader
1 parent 9722388 commit 1b5379c

File tree

7 files changed

+68
-116
lines changed

7 files changed

+68
-116
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ tokio-io-timeout = "1.2.0"
2929
thiserror = "1.0.37"
3030
futures-util = "0.3.24"
3131
glob = "0.3.0"
32+
hex = "0.4.3"
3233

3334
[profile.release]
3435
strip = "debuginfo"

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@
44

55
Bloop box client written in Rust with Tokio.
66

7+
## NFC Tag Support
8+
9+
While for reading UIDs any NFC tag supporting Iso14443a with a baud rate of 106 is supported, it is recommended to use
10+
tags with a 7-byte UID. Tags with shorter UIDs will be padded with zeroes, while tags with longer UIDs will be
11+
truncated.
12+
13+
When it comes to config tags though you have to use either NTAG 213, 215 or 216. Other NTAG formats may work but are
14+
not tested.
15+
716
## LED Status Codes
817

18+
The status RGB LED will display the current status of the Bloop Box. If no user interaction is required, you'll get a
19+
static light, otherwise a blinking one.
20+
921
### Static
1022

1123
- Green: Ready to read attendee tags

src/nfc/reader.rs

Lines changed: 39 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,17 @@ use nfc1::target_info::TargetInfo;
33
use nfc1::BaudRate::Baud106;
44
use nfc1::Error::Timeout as TimeoutError;
55
use nfc1::ModulationType::Iso14443a;
6+
use nfc1::Property::EasyFraming;
67
use nfc1::{Modulation, Property, Timeout};
78

89
use crate::nfc::ndef::{parse_ndef_text_record, NdefMessageParser};
910

10-
pub type Uid = [u8; 4];
11-
12-
#[derive(Copy, Clone)]
13-
struct AuthOption {
14-
key_type: u8,
15-
key: [u8; 6],
16-
}
11+
pub type Uid = [u8; 7];
1712

1813
pub struct NfcReader<'a> {
1914
device: nfc1::Device<'a>,
2015
}
2116

22-
const KEY_TYPE_A: u8 = 0x60;
23-
const KEY_TYPE_B: u8 = 0x61;
24-
const DEFAULT_KEY_A: [u8; 6] = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
25-
const DEFAULT_KEY_B: [u8; 6] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
26-
27-
const AUTH_OPTIONS: [AuthOption; 4] = [
28-
AuthOption {
29-
key_type: KEY_TYPE_A,
30-
key: DEFAULT_KEY_A,
31-
},
32-
AuthOption {
33-
key_type: KEY_TYPE_A,
34-
key: DEFAULT_KEY_B,
35-
},
36-
AuthOption {
37-
key_type: KEY_TYPE_B,
38-
key: DEFAULT_KEY_A,
39-
},
40-
AuthOption {
41-
key_type: KEY_TYPE_B,
42-
key: DEFAULT_KEY_B,
43-
},
44-
];
45-
4617
impl<'a> NfcReader<'a> {
4718
pub fn new(device: nfc1::Device<'a>) -> Self {
4819
Self { device }
@@ -70,43 +41,33 @@ impl<'a> NfcReader<'a> {
7041
return None;
7142
}
7243

73-
Some(info.uid[0..4].try_into().unwrap())
44+
Some(info.uid[0..7].try_into().unwrap())
7445
}
7546

7647
pub fn check_for_release(&mut self) -> bool {
7748
self.device.initiator_target_is_present_any().is_err()
7849
}
7950

80-
pub fn read_first_plain_text_ndef_record(&mut self, uid: &Uid) -> Result<String> {
51+
pub fn read_first_plain_text_ndef_record(&mut self) -> Result<String> {
8152
let mut ndef_message_parser = NdefMessageParser::new();
82-
let mut maybe_auth_option: Option<AuthOption> = None;
83-
84-
'sector: for sector in 1..16 {
85-
match maybe_auth_option {
86-
Some(auth_option) => {
87-
self.authenticate_block(
88-
sector * 4 + 3,
89-
auth_option.key_type,
90-
&auth_option.key,
91-
uid,
92-
)?;
93-
}
94-
None => {
95-
let auth_option = self.try_authenticate_block(sector * 4 + 3, uid)?;
96-
maybe_auth_option = Some(auth_option);
97-
}
98-
}
9953

100-
for block in 0..3 {
101-
let block_data = self.read_block(sector * 4 + block)?;
102-
ndef_message_parser.add_data(&block_data);
54+
self.check_version()?;
55+
self.device.set_property_bool(EasyFraming, true)?;
56+
let capabilities = self.read_block(3)?;
10357

104-
if ndef_message_parser.is_done() {
105-
break 'sector;
106-
}
58+
let total_bytes = (capabilities[2] as u32) * 8;
59+
let total_pages = total_bytes / 4;
60+
let total_quads = (total_pages / 4) as u8;
61+
62+
for quad in 1..total_quads + 1 {
63+
let quad_data = self.read_block(4 * quad)?;
64+
ndef_message_parser.add_data(&quad_data);
65+
66+
if ndef_message_parser.is_done() {
67+
break;
10768
}
10869

109-
if !ndef_message_parser.has_started() {
70+
if quad == 2 && !ndef_message_parser.has_started() {
11071
return Err(anyhow!("No NDEF message found in first sector"));
11172
}
11273
}
@@ -119,61 +80,33 @@ impl<'a> NfcReader<'a> {
11980
record.text()
12081
}
12182

122-
fn read_block(&mut self, block_number: u8) -> Result<[u8; 16]> {
123-
let packet: [u8; 2] = [0x30, block_number];
124-
125-
let response = self
83+
fn check_version(&mut self) -> Result<()> {
84+
self.device.set_property_bool(EasyFraming, false)?;
85+
let version = self
12686
.device
127-
.initiator_transceive_bytes(&packet, 16, Timeout::Default)?;
128-
Ok(response[..].try_into()?)
129-
}
87+
.initiator_transceive_bytes(&[0x60], 8, Timeout::Default)?;
13088

131-
fn try_authenticate_block(&mut self, block_number: u8, uid: &[u8; 4]) -> Result<AuthOption> {
132-
for auth_option in AUTH_OPTIONS {
133-
let auth_result =
134-
self.authenticate_block(block_number, auth_option.key_type, &auth_option.key, uid);
135-
136-
match auth_result {
137-
Ok(()) => {
138-
return Ok(auth_option);
139-
}
140-
Err(_) => {
141-
self.device.initiator_select_passive_target(&Modulation {
142-
modulation_type: Iso14443a,
143-
baud_rate: Baud106,
144-
})?;
145-
}
146-
}
89+
if version[0..6] != [0x00, 0x04, 0x04, 0x02, 0x01, 0x00] {
90+
return Err(anyhow!("Version mismatch, unsupported tag"));
14791
}
14892

149-
Err(anyhow!("No key matched sector"))
150-
}
151-
152-
fn authenticate_block(
153-
&mut self,
154-
block_number: u8,
155-
key_type: u8,
156-
key: &[u8; 6],
157-
uid: &[u8; 4],
158-
) -> Result<()> {
159-
let packet: [u8; 12] = [
160-
key_type,
161-
block_number,
162-
key[0],
163-
key[1],
164-
key[2],
165-
key[3],
166-
key[4],
167-
key[5],
168-
uid[0],
169-
uid[1],
170-
uid[2],
171-
uid[3],
172-
];
93+
if ![0x0f, 0x11, 0x13].contains(&version[6]) {
94+
return Err(anyhow!("Version mismatch, unsupported subtype of tag"));
95+
}
17396

174-
self.device
175-
.initiator_transceive_bytes(&packet, 0, Timeout::Default)?;
97+
if version[7] != 0x03 {
98+
return Err(anyhow!("Version mismatch, unsupported protocol"));
99+
}
176100

177101
Ok(())
178102
}
103+
104+
fn read_block(&mut self, block_number: u8) -> Result<[u8; 16]> {
105+
let packet: [u8; 2] = [0x30, block_number];
106+
107+
let response = self
108+
.device
109+
.initiator_transceive_bytes(&packet, 16, Timeout::Default)?;
110+
Ok(response[..].try_into()?)
111+
}
179112
}

src/nfc/thread.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ pub enum NfcCommand {
1414
cancel_rx: oneshot::Receiver<()>,
1515
},
1616
Read {
17-
uid: Uid,
1817
responder: oneshot::Sender<Option<String>>,
1918
},
2019
Release {
@@ -51,8 +50,8 @@ pub fn start_nfc_listener(mut nfc_rx: mpsc::Receiver<NfcCommand>) {
5150

5251
responder.send(uid).unwrap();
5352
}
54-
Read { uid, responder } => {
55-
let result = nfc_reader.read_first_plain_text_ndef_record(&uid);
53+
Read { responder } => {
54+
let result = nfc_reader.read_first_plain_text_ndef_record();
5655

5756
match result {
5857
Ok(value) => responder.send(Some(value)).unwrap(),

src/subsystems/config_manager.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::path::{Path, PathBuf};
22

3+
use crate::nfc::reader::Uid;
34
use anyhow::{Error, Result};
45
use log::info;
56
use serde::{Deserialize, Serialize};
@@ -25,18 +26,18 @@ pub struct VolumeConfig {
2526

2627
#[derive(Clone, Deserialize, Serialize)]
2728
pub struct Config {
28-
pub config_uids: Vec<[u8; 4]>,
29+
pub config_uids: Vec<Uid>,
2930
pub connection: Option<ConnectionConfig>,
3031
pub volume: VolumeConfig,
3132
}
3233

3334
#[derive(Debug)]
3435
pub enum ConfigCommand {
3536
GetConfigUids {
36-
responder: oneshot::Sender<Vec<[u8; 4]>>,
37+
responder: oneshot::Sender<Vec<Uid>>,
3738
},
3839
SetConfigUids {
39-
config_uids: Vec<[u8; 4]>,
40+
config_uids: Vec<Uid>,
4041
responder: oneshot::Sender<()>,
4142
},
4243
GetVolume {

src/subsystems/controller.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ impl Controller {
105105
self.audio_player.send(PlayerCommand::PlayConfirm { done: done_tx }).await?;
106106
done_rx.await?;
107107

108-
let filename = format!("{:x?}.mp3", achievement_id);
108+
let filename = format!("{}.mp3", hex::encode(achievement_id));
109109
let path = self.cache_path.join(&filename);
110110

111111
if metadata(&path).await.is_err() {
@@ -184,12 +184,11 @@ impl Controller {
184184
async fn process_config_command(
185185
&mut self,
186186
uid: Uid,
187-
config_uids: &mut Vec<[u8; 4]>,
187+
config_uids: &mut Vec<Uid>,
188188
nfc: mpsc::Sender<NfcCommand>,
189189
) -> Result<()> {
190190
let (value_tx, value_rx) = oneshot::channel();
191191
nfc.send(NfcCommand::Read {
192-
uid,
193192
responder: value_tx,
194193
})
195194
.await?;
@@ -275,7 +274,7 @@ impl Controller {
275274

276275
async fn add_config_uid(
277276
&mut self,
278-
config_uids: &mut Vec<[u8; 4]>,
277+
config_uids: &mut Vec<Uid>,
279278
nfc: mpsc::Sender<NfcCommand>,
280279
) -> Result<()> {
281280
self.led.send(LedState::Blink { color: MAGENTA }).await?;

0 commit comments

Comments
 (0)