Skip to content

Commit 611b03a

Browse files
feat: split MIFARE Classic into 1K and 4K protocols (#32)
--------- Co-authored-by: Andrei Cristea <andrei.cristea019@gmail.com>
1 parent 9928fbf commit 611b03a

6 files changed

Lines changed: 76 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [2.6.1] - 2026-02-06
10+
### Changed
11+
- Split `MIFARE_CLASSIC` into two distinct protocols in `PcscCardCommunicationProtocol`:
12+
- `MIFARE_CLASSIC_1K` with ATR pattern `3B8F8001804F0CA000000306030001.*` (card type 0001)
13+
- `MIFARE_CLASSIC_4K` with ATR pattern `3B8F8001804F0CA000000306030002.*` (card type 0002)
14+
- Added `MIFARE_CLASSIC_1K` and `MIFARE_CLASSIC_4K` to the default enabled protocols in `PcscPluginAdapter`.
15+
- Normalize logging.
16+
### Upgraded
17+
- `keyple-common-java-api` from `2.0.1` to `2.0.2`
18+
- `keyple-plugin-java-api` from `2.3.1` to `2.3.2`
19+
- `keyple-util-java-lib` from `2.4.0` to `2.4.1`
20+
- `slf4j-api` from `1.7.32` to `1.7.36` (compileOnly)
21+
922
## [2.6.0] - 2025-12-12
1023
### Added
1124
- Added support for NXP MIFARE Classic (1K, 4K variants) in `PcscCardCommunicationProtocol` with ATR pattern
@@ -134,7 +147,8 @@ This is the initial release.
134147
It follows the extraction of Keyple 1.0 components contained in the `eclipse-keyple/keyple-java` repository to dedicated repositories.
135148
It also brings many major API changes.
136149

137-
[unreleased]: https://github.com/eclipse-keyple/keyple-plugin-pcsc-java-lib/compare/2.6.0...HEAD
150+
[unreleased]: https://github.com/eclipse-keyple/keyple-plugin-pcsc-java-lib/compare/2.6.1...HEAD
151+
[2.6.1]: https://github.com/eclipse-keyple/keyple-plugin-pcsc-java-lib/compare/2.6.0...2.6.1
138152
[2.6.0]: https://github.com/eclipse-keyple/keyple-plugin-pcsc-java-lib/compare/2.5.3...2.6.0
139153
[2.5.3]: https://github.com/eclipse-keyple/keyple-plugin-pcsc-java-lib/compare/2.5.2...2.5.3
140154
[2.5.2]: https://github.com/eclipse-keyple/keyple-plugin-pcsc-java-lib/compare/2.5.1...2.5.2

build.gradle.kts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ plugins {
1414
///////////////////////////////////////////////////////////////////////////////
1515

1616
dependencies {
17-
implementation("org.eclipse.keyple:keyple-common-java-api:2.0.1")
18-
implementation("org.eclipse.keyple:keyple-plugin-java-api:2.3.1")
19-
implementation("org.eclipse.keyple:keyple-util-java-lib:2.4.0")
17+
implementation("org.eclipse.keyple:keyple-common-java-api:2.0.2")
18+
implementation("org.eclipse.keyple:keyple-plugin-java-api:2.3.2")
19+
implementation("org.eclipse.keyple:keyple-util-java-lib:2.4.1")
2020
implementation("net.java.dev.jna:jna:5.15.0")
21-
implementation("org.slf4j:slf4j-api:1.7.32")
21+
compileOnly("org.slf4j:slf4j-api:1.7.36")
2222
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
2323
}
2424

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
group = org.eclipse.keyple
33
title = Keyple Plugin PCSC Java Lib
44
description = Keyple add-on to manage PC/SC readers
5-
version = 2.6.0-SNAPSHOT
5+
version = 2.6.1-SNAPSHOT
66

77
# Java Configuration
88
javaSourceLevel = 1.8

src/main/java/org/eclipse/keyple/plugin/pcsc/PcscCardCommunicationProtocol.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,38 @@ public enum PcscCardCommunicationProtocol {
6868
INNOVATRON_B_PRIME("3B8.8001(80)?5A0A.*"),
6969

7070
/**
71-
* NXP MIFARE Classic technologies (1K, 4K variants).
71+
* NXP MIFARE Classic 1K technology.
7272
*
7373
* <p>According to PC/SC Part 3 Supplemental Document:
7474
*
7575
* <ul>
7676
* <li>Initial bytes: 3B8F8001804F0CA0000003
7777
* <li>Card protocol: 0603 (ISO 14443 A part 3)
78-
* <li>Card type: Variable (depends on memory capacity)
78+
* <li>Card type: 0001 (1K variant)
7979
* </ul>
8080
*
81-
* <p>Default rule = <b>{@code 3B8F8001804F0CA00000030603000(1|2).*}</b>
81+
* <p>Default rule = <b>{@code 3B8F8001804F0CA000000306030001.*}</b>
8282
*
8383
* @since 2.6.0
8484
*/
85-
MIFARE_CLASSIC("3B8F8001804F0CA00000030603000(1|2).*"),
85+
MIFARE_CLASSIC_1K("3B8F8001804F0CA000000306030001.*"),
86+
87+
/**
88+
* NXP MIFARE Classic 4K technology.
89+
*
90+
* <p>According to PC/SC Part 3 Supplemental Document:
91+
*
92+
* <ul>
93+
* <li>Initial bytes: 3B8F8001804F0CA0000003
94+
* <li>Card protocol: 0603 (ISO 14443 A part 3)
95+
* <li>Card type: 0002 (4K variant)
96+
* </ul>
97+
*
98+
* <p>Default rule = <b>{@code 3B8F8001804F0CA000000306030002.*}</b>
99+
*
100+
* @since 2.6.0
101+
*/
102+
MIFARE_CLASSIC_4K("3B8F8001804F0CA000000306030002.*"),
86103

87104
/**
88105
* NXP MIFARE Ultralight technologies.

src/main/java/org/eclipse/keyple/plugin/pcsc/PcscPluginAdapter.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ final class PcscPluginAdapter implements PcscPlugin, ObservablePluginSpi {
6161
protocolRulesMap.put(
6262
PcscCardCommunicationProtocol.MIFARE_ULTRALIGHT.name(),
6363
PcscCardCommunicationProtocol.MIFARE_ULTRALIGHT.getDefaultRule());
64+
protocolRulesMap.put(
65+
PcscCardCommunicationProtocol.MIFARE_CLASSIC_1K.name(),
66+
PcscCardCommunicationProtocol.MIFARE_CLASSIC_1K.getDefaultRule());
67+
protocolRulesMap.put(
68+
PcscCardCommunicationProtocol.MIFARE_CLASSIC_4K.name(),
69+
PcscCardCommunicationProtocol.MIFARE_CLASSIC_4K.getDefaultRule());
6470
protocolRulesMap.put(
6571
PcscCardCommunicationProtocol.ST25_SRT512.name(),
6672
PcscCardCommunicationProtocol.ST25_SRT512.getDefaultRule());
@@ -137,13 +143,13 @@ public int getMonitoringCycleDuration() {
137143
public Set<String> searchAvailableReaderNames() throws PluginIOException {
138144
Set<String> readerNames = new HashSet<>();
139145
if (logger.isTraceEnabled()) {
140-
logger.trace("Plugin [{}]: search available reader", getName());
146+
logger.trace("Searching available reader names");
141147
}
142148
for (CardTerminal terminal : getCardTerminalList()) {
143149
readerNames.add(terminal.getName());
144150
}
145151
if (logger.isTraceEnabled()) {
146-
logger.trace("Plugin [{}]: readers found: {}", getName(), JsonUtil.toJson(readerNames));
152+
logger.trace("Readers found [names={}]", JsonUtil.toJson(readerNames));
147153
}
148154
return readerNames;
149155
}
@@ -166,12 +172,14 @@ public String getName() {
166172
@Override
167173
public Set<ReaderSpi> searchAvailableReaders() throws PluginIOException {
168174
Set<ReaderSpi> readerSpis = new HashSet<>();
169-
logger.info("Plugin [{}]: search available readers", getName());
175+
if (logger.isTraceEnabled()) {
176+
logger.trace("Searching available readers");
177+
}
170178
for (CardTerminal terminal : getCardTerminalList()) {
171179
readerSpis.add(createReader(terminal));
172180
}
173181
for (ReaderSpi readerSpi : readerSpis) {
174-
logger.info("Plugin [{}]: reader found: [{}]", getName(), readerSpi.getName());
182+
logger.info("Reader found [name={}]", readerSpi.getName());
175183
}
176184
return readerSpis;
177185
}
@@ -205,14 +213,14 @@ private List<CardTerminal> getCardTerminalList() throws PluginIOException {
205213
return terminals.list();
206214
} catch (Exception e) {
207215
if (e.getMessage().contains("SCARD_E_NO_READERS_AVAILABLE")) {
208-
logger.error("Plugin [{}]: no reader available", getName());
216+
logger.error("No reader available");
209217
} else if (e.getMessage().contains("SCARD_E_NO_SERVICE")
210218
|| e.getMessage().contains("SCARD_E_SERVICE_STOPPED")) {
211-
logger.error("Plugin [{}]: no smart card service error", getName());
219+
logger.error("No running smart card service");
212220
// the CardTerminals object is no more valid
213221
isCardTerminalsInitialized = false;
214222
} else if (e.getMessage().contains("SCARD_F_COMM_ERROR")) {
215-
logger.error("Plugin [{}]: reader communication error", getName());
223+
logger.error("Reader communication error");
216224
} else {
217225
throw new PluginIOException("Could not access terminals list", e);
218226
}

src/main/java/org/eclipse/keyple/plugin/pcsc/PcscReaderAdapter.java

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private CardTerminal createMonitoringTerminal(String terminalName) {
107107
if (t.getName().equals(terminalName)) {
108108
if (logger.isDebugEnabled()) {
109109
logger.debug(
110-
"Reader [{}]: created separate monitoring context for improved Linux compatibility",
110+
"[{}] creating separate monitoring context for improved Linux compatibility",
111111
terminalName);
112112
}
113113
return t;
@@ -116,14 +116,14 @@ private CardTerminal createMonitoringTerminal(String terminalName) {
116116

117117
// Terminal not found in new context, fall back to same terminal
118118
logger.warn(
119-
"Reader [{}]: could not find terminal in separate context, using shared context (may cause issues on Linux)",
119+
"[{}] could not find terminal in separate context, using shared context (may cause issues on Linux)",
120120
terminalName);
121121
return communicationTerminal;
122122

123123
} catch (Exception e) {
124124
// Failed to create separate context, fall back to same terminal
125125
logger.warn(
126-
"Reader [{}]: could not create separate monitoring context ({}), using shared context (may cause issues on Linux)",
126+
"[{}] could not create separate monitoring context [reason={}], using shared context (may cause issues on Linux)",
127127
terminalName,
128128
e.getMessage());
129129
return communicationTerminal;
@@ -140,7 +140,7 @@ public void waitForCardInsertion() throws TaskCanceledException, ReaderIOExcepti
140140

141141
if (logger.isTraceEnabled()) {
142142
logger.trace(
143-
"Reader [{}]: start waiting card insertion (loop latency: {} ms)",
143+
"[{}] start waiting card insertion (loop latency: {} ms)...",
144144
getName(),
145145
cardMonitoringCycleDuration);
146146
}
@@ -153,7 +153,7 @@ public void waitForCardInsertion() throws TaskCanceledException, ReaderIOExcepti
153153
if (monitoringTerminal.waitForCardPresent(cardMonitoringCycleDuration)) {
154154
// card inserted
155155
if (logger.isTraceEnabled()) {
156-
logger.trace("Reader [{}]: card inserted", getName());
156+
logger.trace("[{}] card inserted", getName());
157157
}
158158
return;
159159
}
@@ -162,7 +162,7 @@ public void waitForCardInsertion() throws TaskCanceledException, ReaderIOExcepti
162162
}
163163
}
164164
if (logger.isTraceEnabled()) {
165-
logger.trace("Reader [{}]: waiting card insertion stopped", getName());
165+
logger.trace("[{}] waiting card insertion stopped", getName());
166166
}
167167
} catch (CardException e) {
168168
// here, it is a communication failure with the reader
@@ -201,8 +201,7 @@ public boolean isProtocolSupported(String readerProtocol) {
201201
@Override
202202
public void activateProtocol(String readerProtocol) {
203203
if (logger.isTraceEnabled()) {
204-
logger.trace(
205-
"Reader [{}]: activating protocol [{}] takes no action", getName(), readerProtocol);
204+
logger.trace("[{}] activating protocol [{}] takes no action", getName(), readerProtocol);
206205
}
207206
}
208207

@@ -214,8 +213,7 @@ public void activateProtocol(String readerProtocol) {
214213
@Override
215214
public void deactivateProtocol(String readerProtocol) {
216215
if (logger.isTraceEnabled()) {
217-
logger.trace(
218-
"Reader [{}]: de-activating protocol [{}] takes no action", getName(), readerProtocol);
216+
logger.trace("[{}] de-activating protocol [{}] takes no action", getName(), readerProtocol);
219217
}
220218
}
221219

@@ -280,18 +278,17 @@ public void openPhysicalChannel() throws ReaderIOException, CardIOException {
280278
/* init of the card physical channel: if not yet established, opening of a new physical channel */
281279
try {
282280
if (logger.isDebugEnabled()) {
283-
logger.debug(
284-
"Reader [{}]: open card physical channel for protocol [{}]", getName(), protocol);
281+
logger.debug("[{}] opening card physical channel for protocol [{}]", getName(), protocol);
285282
}
286283
card = this.communicationTerminal.connect(protocol);
287284
if (isModeExclusive) {
288285
card.beginExclusive();
289286
if (logger.isDebugEnabled()) {
290-
logger.debug("Reader [{}]: open card physical channel in exclusive mode", getName());
287+
logger.debug("[{}] card physical channel opened in EXCLUSIVE mode", getName());
291288
}
292289
} else {
293290
if (logger.isDebugEnabled()) {
294-
logger.debug("Reader [{}]: open card physical channel in shared mode", getName());
291+
logger.debug("[{}] card physical channel opened in SHARED mode", getName());
295292
}
296293
}
297294
channel = card.getBasicChannel();
@@ -524,7 +521,7 @@ public void stopCardPresenceMonitoringDuringProcessing() {
524521
@Override
525522
public void waitForCardRemoval() throws ReaderIOException, TaskCanceledException {
526523
if (logger.isTraceEnabled()) {
527-
logger.trace("Reader [{}]: start waiting card removal)", name);
524+
logger.trace("[{}] start waiting card removal)", name);
528525
}
529526
loopWaitCardRemoval.set(true);
530527
try {
@@ -537,14 +534,15 @@ public void waitForCardRemoval() throws ReaderIOException, TaskCanceledException
537534
try {
538535
disconnect();
539536
} catch (Exception e) {
540-
logger.warn("Error while disconnecting card during card removal: {}", e.getMessage());
537+
logger.warn(
538+
"[{}] error while disconnecting card during card removal: {}", name, e.getMessage());
541539
}
542540
}
543541
if (logger.isTraceEnabled()) {
544542
if (!loopWaitCardRemoval.get()) {
545-
logger.trace("Reader [{}]: waiting card removal stopped", name);
543+
logger.trace("[{}] waiting card removal stopped", name);
546544
} else {
547-
logger.trace("Reader [{}]: card removed", name);
545+
logger.trace("[{}] card removed", name);
548546
}
549547
}
550548
if (!loopWaitCardRemoval.get()) {
@@ -606,7 +604,7 @@ public void stopWaitForCardRemoval() {
606604
@Override
607605
public PcscReader setSharingMode(SharingMode sharingMode) {
608606
Assert.getInstance().notNull(sharingMode, "sharingMode");
609-
logger.info("Reader [{}]: set sharing mode to [{}]", getName(), sharingMode.name());
607+
logger.info("[{}] set sharing mode [newValue={}]", getName(), sharingMode.name());
610608
if (sharingMode == SharingMode.SHARED) {
611609
// if a card is present, change the mode immediately
612610
if (card != null) {
@@ -630,7 +628,7 @@ public PcscReader setSharingMode(SharingMode sharingMode) {
630628
*/
631629
@Override
632630
public PcscReader setContactless(boolean contactless) {
633-
logger.info("Reader [{}]: set contactless type to [{}]", getName(), contactless);
631+
logger.info("[{}] set contactless type [newValue={}]", getName(), contactless);
634632
this.isContactless = contactless;
635633
return this;
636634
}
@@ -644,7 +642,7 @@ public PcscReader setContactless(boolean contactless) {
644642
public PcscReader setIsoProtocol(IsoProtocol isoProtocol) {
645643
Assert.getInstance().notNull(isoProtocol, "isoProtocol");
646644
logger.info(
647-
"Reader [{}]: set ISO protocol to [{}] ({})",
645+
"[{}] set ISO protocol [protocolName={}, newValue={}]",
648646
getName(),
649647
isoProtocol.name(),
650648
isoProtocol.getValue());
@@ -660,7 +658,7 @@ public PcscReader setIsoProtocol(IsoProtocol isoProtocol) {
660658
@Override
661659
public PcscReader setDisconnectionMode(DisconnectionMode disconnectionMode) {
662660
Assert.getInstance().notNull(disconnectionMode, "disconnectionMode");
663-
logger.info("Reader [{}]: set disconnection mode to [{}]", getName(), disconnectionMode.name());
661+
logger.info("[{}] set disconnection mode [newValue={}]", getName(), disconnectionMode.name());
664662
this.disconnectionMode = disconnectionMode;
665663
return this;
666664
}

0 commit comments

Comments
 (0)