From 022b9c4f288b237d16fe5f8fc9e3e27848194eb0 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Tue, 21 Jun 2022 15:28:09 +0200 Subject: [PATCH] add metadata parser/encoder --- .../im/status/keycard/applet/Metadata.java | 106 ++++++++++++++++++ .../im/status/keycard/applet/TinyBERTLV.java | 76 +++++++++---- 2 files changed, 162 insertions(+), 20 deletions(-) create mode 100644 lib/src/main/java/im/status/keycard/applet/Metadata.java diff --git a/lib/src/main/java/im/status/keycard/applet/Metadata.java b/lib/src/main/java/im/status/keycard/applet/Metadata.java new file mode 100644 index 0000000..eafd785 --- /dev/null +++ b/lib/src/main/java/im/status/keycard/applet/Metadata.java @@ -0,0 +1,106 @@ +package im.status.keycard.applet; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; +import java.util.SortedSet; +import java.util.TreeSet; + +public class Metadata { + private String cardName; + private SortedSet wallets; + + public static Metadata fromData(byte[] data) { + int version = (data[0] & 0xe0) >> 5; + + if (version != 1) { + throw new RuntimeException("Invalid version"); + } + + int namelen = (data[0] & 0x1f); + int off = 1; + + String cardName = new String(data, off, namelen, Charset.forName("US-ASCII")); + off += namelen; + + SortedSet set = new TreeSet<>(); + + while(off < data.length) { + int[] start = TinyBERTLV.readNum(data, off); + int[] count = TinyBERTLV.readNum(data, start[1]); + off = count[1]; + long s = start[0] & 0xffffffffl; + buildRange(set, s, (s + count[0])); + } + + return new Metadata(cardName, set); + } + + private static void buildRange(SortedSet set, long start, long end) { + for (long i = start; i <= end; i++) { + set.add(i); + } + } + + Metadata(String cardName, SortedSet wallets) { + this.cardName = cardName; + this.wallets = wallets; + } + + public Metadata(String cardName) { + this(cardName, new TreeSet<>()); + } + + public String getCardName() { + return cardName; + } + + public void setCardName(String cardName) { + if (cardName.length() > 20) { + throw new IllegalArgumentException("card name too long"); + } + + this.cardName = cardName; + } + + public SortedSet getWallets() { + return wallets; + } + + public void addWallet(long w) { + this.wallets.add(w); + } + + public void removeWallet(long w) { + this.wallets.remove(w); + } + + public byte[] toByteArray() { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + byte[] name = this.cardName.getBytes(Charset.forName("US-ASCII")); + os.write(0x20 | name.length); + os.write(name, 0, name.length); + + if (wallets.isEmpty()) { + return os.toByteArray(); + } + + long start = wallets.first(); + int len = 0; + + for (Long w : wallets.tailSet(start + 1)) { + if (w == (start + len + 1)) { + len++; + } else { + TinyBERTLV.writeNum(os, (int) start); + TinyBERTLV.writeNum(os, len); + len = 0; + start = w; + } + } + + TinyBERTLV.writeNum(os, (int) start); + TinyBERTLV.writeNum(os, len); + + return os.toByteArray(); + } +} diff --git a/lib/src/main/java/im/status/keycard/applet/TinyBERTLV.java b/lib/src/main/java/im/status/keycard/applet/TinyBERTLV.java index 01cd33e..41e5183 100644 --- a/lib/src/main/java/im/status/keycard/applet/TinyBERTLV.java +++ b/lib/src/main/java/im/status/keycard/applet/TinyBERTLV.java @@ -1,5 +1,6 @@ package im.status.keycard.applet; +import java.io.ByteArrayOutputStream; import java.util.Arrays; /** @@ -14,6 +15,57 @@ public class TinyBERTLV { private byte[] buffer; private int pos; + public static int[] readNum(byte[] buf, int off) { + int len = buf[off++] & 0xff; + int lenlen = 0; + + if ((len & 0x80) == 0x80) { + lenlen = len & 0x7f; + len = readVal(buf, off, lenlen); + } + + return new int[] {len, off + lenlen}; + } + + public static int readVal(byte[] val, int off, int len) { + switch (len) { + case 1: + return val[off] & 0xff; + case 2: + return ((val[off] & 0xff) << 8) | (val[off+1] & 0xff); + case 3: + return ((val[off] & 0xff) << 16) | ((val[off+1] & 0xff) << 8) | (val[off+2] & 0xff); + case 4: + return ((val[off] & 0xff) << 24) | ((val[off+1] & 0xff) << 16) | ((val[off+2] & 0xff) << 8) | (val[off+3] & 0xff); + default: + throw new IllegalArgumentException("Integers of length " + len + " are unsupported"); + } + } + + public static void writeNum(ByteArrayOutputStream os, int len) { + if ((len & 0xff000000) != 0) { + os.write(0x84); + os.write((len & 0xff000000) >> 24); + os.write((len & 0x00ff0000) >> 16); + os.write((len & 0x0000ff00) >> 8); + os.write(len & 0x000000ff); + } else if ((len & 0x00ff0000) != 0) { + os.write(0x83); + os.write((len & 0x00ff0000) >> 16); + os.write((len & 0x0000ff00) >> 8); + os.write(len & 0x000000ff); + } else if ((len & 0x0000ff00) != 0) { + os.write(0x82); + os.write((len & 0x0000ff00) >> 8); + os.write(len & 0x000000ff); + } else if ((len & 0x00000080) != 0) { + os.write(0x81); + os.write(len & 0x000000ff); + } else { + os.write(len); + } + } + public TinyBERTLV(byte[] buffer) { this.buffer = buffer; this.pos = 0; @@ -64,19 +116,7 @@ public class TinyBERTLV { */ public int readInt() throws IllegalArgumentException { byte[] val = readPrimitive(TLV_INT); - - switch (val.length) { - case 1: - return val[0] & 0xff; - case 2: - return ((val[0] & 0xff) << 8) | (val[1] & 0xff); - case 3: - return ((val[0] & 0xff) << 16) | ((val[1] & 0xff) << 8) | (val[2] & 0xff); - case 4: - return ((val[0] & 0xff) << 24) | ((val[1] & 0xff) << 16) | ((val[2] & 0xff) << 8) | (val[3] & 0xff); - default: - throw new IllegalArgumentException("Integers of length " + val.length + " are unsupported"); - } + return TinyBERTLV.readVal(val, 0, val.length); } /** @@ -104,13 +144,9 @@ public class TinyBERTLV { * @return the tag */ public int readLength() { - int len = buffer[pos++] & 0xff; - - if (len == 0x81) { - len = buffer[pos++] & 0xff; - } - - return len; + int[] len = TinyBERTLV.readNum(buffer, pos); + pos = len[1]; + return len[0]; } private void checkTag(int expected, int actual) throws IllegalArgumentException {