feat(applet): canonical CBOR uint/bstr/tstr encoders (M2B.4)
This commit is contained in:
@@ -360,6 +360,150 @@ class StructuralCborTest {
|
||||
assertEquals(4, span);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Canonical encoder tests (M2B.4) — uint / bstr-header / tstr-header
|
||||
// writers used by M2D.2 (DeviceResponseBuilder).
|
||||
//
|
||||
// Canonical CBOR (RFC 8949 §4.2.1): shortest argument size that fits the
|
||||
// value. Boundary at each size step (0..23 immediate, 24..0xff one-byte,
|
||||
// 0x100..0xffff two-byte, 0x10000..0xffffffff four-byte).
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void encodeUint_zeroImmediate() {
|
||||
// 0 fits in the immediate range — single header byte 0x00.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeUint(0, out, (short) 0);
|
||||
assertEquals(1, n);
|
||||
assertArrayPrefix(out, "00", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeUint_twentyThreeImmediate() {
|
||||
// 23 is the largest immediate value — header byte 0x17.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeUint(23, out, (short) 0);
|
||||
assertEquals(1, n);
|
||||
assertArrayPrefix(out, "17", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeUint_twentyFourOneByteBoundary() {
|
||||
// 24 is the lower boundary of the 1-byte argument form — 0x18 0x18.
|
||||
// Canonical rule forbids encoding 24 as 0x19 0x00 0x18 etc.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeUint(24, out, (short) 0);
|
||||
assertEquals(2, n);
|
||||
assertArrayPrefix(out, "1818", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeUint_twoByteBoundary() {
|
||||
// 0x100 = 256 is the lower boundary of the 2-byte argument form.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeUint(0x100, out, (short) 0);
|
||||
assertEquals(3, n);
|
||||
assertArrayPrefix(out, "190100", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeUint_fourByteBoundary() {
|
||||
// 0x10000 = 65536 is the lower boundary of the 4-byte argument form.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeUint(0x10000, out, (short) 0);
|
||||
assertEquals(5, n);
|
||||
assertArrayPrefix(out, "1a00010000", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeUint_negativeRejected() {
|
||||
// Aliro mdoc CBOR never uses negative integers (those are major type 1).
|
||||
byte[] out = new byte[8];
|
||||
ISOException ex = assertThrows(ISOException.class,
|
||||
() -> StructuralCbor.encodeUint(-1, out, (short) 0));
|
||||
assertEquals(ISO7816.SW_DATA_INVALID, ex.getReason() & 0xFFFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeBstrHeader_zero() {
|
||||
// bstr len 0 — header byte 0x40 (major type 2 | additional info 0).
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeBstrHeader((short) 0, out, (short) 0);
|
||||
assertEquals(1, n);
|
||||
assertArrayPrefix(out, "40", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeBstrHeader_twentyThreeImmediate() {
|
||||
// Largest immediate-length bstr header — 0x57.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeBstrHeader((short) 23, out, (short) 0);
|
||||
assertEquals(1, n);
|
||||
assertArrayPrefix(out, "57", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeBstrHeader_twentyFourOneByteBoundary() {
|
||||
// 24 forces into 1-byte argument form — 0x58 0x18.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeBstrHeader((short) 24, out, (short) 0);
|
||||
assertEquals(2, n);
|
||||
assertArrayPrefix(out, "5818", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeBstrHeader_twoByteBoundary() {
|
||||
// 0x100 = 256 forces into 2-byte argument form — 0x59 0x01 0x00.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeBstrHeader((short) 0x100, out, (short) 0);
|
||||
assertEquals(3, n);
|
||||
assertArrayPrefix(out, "590100", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeBstrHeader_negativeLenRejected() {
|
||||
byte[] out = new byte[8];
|
||||
ISOException ex = assertThrows(ISOException.class,
|
||||
() -> StructuralCbor.encodeBstrHeader((short) -1, out, (short) 0));
|
||||
assertEquals(ISO7816.SW_DATA_INVALID, ex.getReason() & 0xFFFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeTstrHeader_zero() {
|
||||
// tstr len 0 — header byte 0x60 (major type 3 | additional info 0).
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeTstrHeader((short) 0, out, (short) 0);
|
||||
assertEquals(1, n);
|
||||
assertArrayPrefix(out, "60", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeTstrHeader_twentyFourOneByteBoundary() {
|
||||
// 24 forces into 1-byte argument form — 0x78 0x18.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeTstrHeader((short) 24, out, (short) 0);
|
||||
assertEquals(2, n);
|
||||
assertArrayPrefix(out, "7818", n);
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeTstrHeader_twoByteBoundary() {
|
||||
// 0x100 forces into 2-byte argument form — 0x79 0x01 0x00.
|
||||
byte[] out = new byte[8];
|
||||
short n = StructuralCbor.encodeTstrHeader((short) 0x100, out, (short) 0);
|
||||
assertEquals(3, n);
|
||||
assertArrayPrefix(out, "790100", n);
|
||||
}
|
||||
|
||||
/** Helper: assert {@code out[0..n)} equals the hex-decoded expected bytes. */
|
||||
private static void assertArrayPrefix(byte[] out, String expectedHex, short n) {
|
||||
byte[] expected = hex(expectedHex);
|
||||
assertEquals(expected.length, n, "expected " + expectedHex + " (" + expected.length + " bytes)");
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
assertEquals(expected[i], out[i], "byte " + i + " mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] hex(String s) {
|
||||
s = s.replaceAll("\\s+", "");
|
||||
byte[] out = new byte[s.length() / 2];
|
||||
|
||||
Reference in New Issue
Block a user