Skip to content

Commit

Permalink
Add timing to reading operation; fix reading type 4 in multiple chunk…
Browse files Browse the repository at this point in the history
…s of 256+
  • Loading branch information
martinpaljak committed Oct 26, 2023
1 parent 7179c65 commit c7ed04a
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 31 deletions.
1 change: 0 additions & 1 deletion src/main/java/pro/javacard/nfc4pc/CLIOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

public abstract class CLIOptions {
Expand Down
40 changes: 29 additions & 11 deletions src/main/java/pro/javacard/nfc4pc/NDEF.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,25 +150,43 @@ static Optional<byte[]> getType4(APDUBIBO bibo) {
// Capabilities
ResponseAPDU read = bibo.transceive(new CommandAPDU(0x00, 0xb0, 0x00, 0x00, 0x0F));

// We always use short APDU-s
int maxReadSize = getShort(read.getData(), (short) 3);
if (maxReadSize > 0x100) {
log.warn("Max read size is {}, limiting to 256", maxReadSize);
maxReadSize = 0x100;
}

// This DOES include the 2 byte header
int payloadSize = getShort(read.getData(), (short) 11);

ResponseAPDU selectDATA = bibo.transceive(new CommandAPDU(0x00, 0xA4, 0x00, 0x0C, HexUtils.hex2bin("e104")));
if (selectDATA.getSW() == 0x9000) {
ResponseAPDU len = bibo.transceive(new CommandAPDU(0x00, 0xb0, 0x00, 0x00, 0x02));
int reportedLen = getShort(len.getData(), (short) 0);
if ((reportedLen + 2) != payloadSize) {
log.error("Warning: payload length mismatch");
}
if (len.getSW() == 0x9000) {
final byte[] payload;
if (reportedLen > maxReadSize) { // XXX: assumes that not that big
byte[] chunk1 = bibo.transceive(new CommandAPDU(0x00, 0xb0, 0x00, 0x02, maxReadSize)).getData();
byte[] chunk2 = bibo.transceive(new CommandAPDU(0x00, 0xb0, 0x00, maxReadSize + 2, reportedLen - maxReadSize)).getData();
payload = concatenate(chunk1, chunk2);
} else {
payload = bibo.transceive(new CommandAPDU(0x00, 0xb0, 0x00, 0x02, reportedLen)).getData();
// 2 byte header contains the payload length AFTER the header
int reportedLen = getShort(len.getData(), (short) 0);
if ((reportedLen + 2) != payloadSize) {
log.error("Warning: payload length mismatch");
}

ByteArrayOutputStream bos = new ByteArrayOutputStream();
int offset;
int i;
// Limit up to 10 reads
for (offset = 2, i = 0; offset < payloadSize && i < 10; i++) {
int left = payloadSize - offset;

ResponseAPDU readResponse = bibo.transceive(new CommandAPDU(0x00, 0xb0, offset >> 8, offset, Math.min(left, maxReadSize)));
if (readResponse.getSW() != 0x9000) {
log.error("Read returned: {}", readResponse.getSW());
return Optional.empty();
}
byte[] chunk = readResponse.getData();
offset += chunk.length;
bos.writeBytes(chunk);
}
byte[] payload = bos.toByteArray();
log.info("Payload: " + HexUtils.bin2hex(payload));
return Optional.of(payload);
}
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/pro/javacard/nfc4pc/NFC4PC.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.concurrent.ScheduledFuture;
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/pro/javacard/nfc4pc/NFCReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import javax.smartcardio.CardTerminal;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -43,7 +44,7 @@ public NFCReader(TapProcessor processor) {
}

// This is a fun exercise, we have a thread per reader and use the thread name for logging as well as reader access.
private void onReaderThread(String name, Runnable r) {
private void onReaderThread(String name, Runnable r) {
readerThreads.computeIfAbsent(name, (n) -> Executors.newSingleThreadExecutor(new NamedReaderThreadFactory(n))).submit(r);
}

Expand Down Expand Up @@ -110,6 +111,8 @@ private void tryToRead() {
c.beginExclusive(); // Use locking, as this is short read
// get UID
APDUBIBO b = new APDUBIBO(CardBIBO.wrap(c));

long start = System.currentTimeMillis();
var uid = NDEF.getUID(b);
if (uid.isEmpty()) {
log.info("No UID, assuming not a supported contactless reader/device");
Expand All @@ -118,20 +121,22 @@ private void tryToRead() {
}
// Type 2 > Type 4
var url = NDEF.getType2(b).or(() -> NDEF.getType4(b));
Duration readtime = Duration.ofMillis(System.currentTimeMillis() - start);


String location = null;
if (url.isPresent()) {
try {
// TODO: detect unknown payload. TODO: warn if smart poster
location = NDEF.msg2url(url.get());
processor.onNFCTap(new NFCTapData(n, uid.get(), URI.create(location), null));
processor.onNFCTap(new NFCTapData(n, uid.get(), URI.create(location), readtime, null));
} catch (IllegalArgumentException e) {
processor.onNFCTap(new NFCTapData(n, uid.get(), e));
return;
//notifyUser(n, "Could not parse message etc");
}
} else {
processor.onNFCTap(new NFCTapData(n, uid.get(), null, null));
processor.onNFCTap(new NFCTapData(n, uid.get(), null, readtime, null));
}
} catch (BIBOException e) {
// TODO: notify exclusively opened readers ?
Expand Down
11 changes: 6 additions & 5 deletions src/main/java/pro/javacard/nfc4pc/NFCTapData.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@
import apdu4j.core.HexUtils;

import java.net.URI;
import java.time.Duration;

public record NFCTapData(String reader, byte[] uid, URI url, Exception error) {
public record NFCTapData(String reader, byte[] uid, URI url, Duration readtime, Exception error) {
public NFCTapData(String reader, Exception error) {
this(reader, null, null, error);
this(reader, null, null, null, error);
}

public NFCTapData(String reader, byte[] uid, Exception error) {
this(reader, uid, null, error);
this(reader, uid, null, null, error);
}

public NFCTapData(String reader, byte[] uid, URI url) {
this(reader, uid, url, null);
this(reader, uid, url, null, null);
}

@Override
public String toString() {
return String.format("TapData[reader=%s, uid=%s, url=%s, error=%s]", reader, HexUtils.bin2hex(uid), url, error);
return String.format("TapData[reader=%s, uid=%s, url=%s, readtime=%dms, error=%s]", reader, HexUtils.bin2hex(uid), url, readtime.toMillis(), error);
}
}
12 changes: 3 additions & 9 deletions src/test/java/pro/javacard/nfc4pc/MiscTest.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package pro.javacard.nfc4pc;

import apdu4j.core.HexUtils;
import com.payneteasy.tlv.BerTlvParser;
import com.payneteasy.tlv.BerTlvs;
import org.junit.jupiter.api.Test;

public class MiscTest {

@Test
public void testSomething() {
byte[] v = HexUtils.hex2bin("0312D1010E55046B7962657270756E6B2E6E6574FE000000000000000000000000000000000000000000000000000000000000000000000000000000");
BerTlvParser parser = new BerTlvParser();
BerTlvs result = parser.parse(v);
System.out.println(result);
public void nothing() throws Exception {

}
}

0 comments on commit c7ed04a

Please sign in to comment.