diff --git a/src/main/java/pro/javacard/nfc4pc/CLIOptions.java b/src/main/java/pro/javacard/nfc4pc/CLIOptions.java index 9aa50d5..745b264 100644 --- a/src/main/java/pro/javacard/nfc4pc/CLIOptions.java +++ b/src/main/java/pro/javacard/nfc4pc/CLIOptions.java @@ -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 { diff --git a/src/main/java/pro/javacard/nfc4pc/NDEF.java b/src/main/java/pro/javacard/nfc4pc/NDEF.java index b34a7f8..ac14923 100644 --- a/src/main/java/pro/javacard/nfc4pc/NDEF.java +++ b/src/main/java/pro/javacard/nfc4pc/NDEF.java @@ -150,25 +150,43 @@ static Optional 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); } diff --git a/src/main/java/pro/javacard/nfc4pc/NFC4PC.java b/src/main/java/pro/javacard/nfc4pc/NFC4PC.java index 7d6361e..c2ab8f6 100644 --- a/src/main/java/pro/javacard/nfc4pc/NFC4PC.java +++ b/src/main/java/pro/javacard/nfc4pc/NFC4PC.java @@ -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; diff --git a/src/main/java/pro/javacard/nfc4pc/NFCReader.java b/src/main/java/pro/javacard/nfc4pc/NFCReader.java index ea07275..7314deb 100644 --- a/src/main/java/pro/javacard/nfc4pc/NFCReader.java +++ b/src/main/java/pro/javacard/nfc4pc/NFCReader.java @@ -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; @@ -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); } @@ -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"); @@ -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 ? diff --git a/src/main/java/pro/javacard/nfc4pc/NFCTapData.java b/src/main/java/pro/javacard/nfc4pc/NFCTapData.java index 4415ba2..b47470f 100644 --- a/src/main/java/pro/javacard/nfc4pc/NFCTapData.java +++ b/src/main/java/pro/javacard/nfc4pc/NFCTapData.java @@ -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); } } diff --git a/src/test/java/pro/javacard/nfc4pc/MiscTest.java b/src/test/java/pro/javacard/nfc4pc/MiscTest.java index eab9ee9..96e2643 100644 --- a/src/test/java/pro/javacard/nfc4pc/MiscTest.java +++ b/src/test/java/pro/javacard/nfc4pc/MiscTest.java @@ -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 { + } } +