add metadata parser/encoder

This commit is contained in:
Michele Balistreri 2022-06-21 15:28:09 +02:00
parent a39924aba3
commit 022b9c4f28
2 changed files with 162 additions and 20 deletions

View File

@ -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<Long> 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<Long> 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<Long> set, long start, long end) {
for (long i = start; i <= end; i++) {
set.add(i);
}
}
Metadata(String cardName, SortedSet<Long> 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<Long> 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();
}
}

View File

@ -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 {