package com.dangerousthings.aliro; import org.junit.jupiter.api.Test; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests for {@link DeviceResponseBuilder} — emits the mdoc DeviceResponse * (ISO 18013-5 §8.3.2.1.2.2 / Aliro Table 8-22) carrying a cached Access * Document at {@code documents[0].issuerSigned.issuerAuth}. * *
The reference {@link #DEVICE_RESPONSE_HEX} was generated against the * real Access Document at {@code /home/work/aliro-trust/access_document.bin} * via a manual canonical-CBOR builder (NOT {@code cbor2.dumps(canonical=True)} * on the whole structure — that would re-encode the inner COSE_Sign1 and the * applet doesn't re-encode it; the AD bytes are embedded verbatim). See the * M2D.2 plan for the generator script. * *
Canonical key ordering at each map level (RFC 8949 §4.2.1: sort by * encoded-key-length then lexicographically): *
Uses {@link StructuralCbor#elementSpan} to walk — that's the same * tooling M2D.3 will use on the receiving side, so a passing structural * walk here also confirms the output is parseable by our own parser. */ @Test void buildDeviceResponse_shape_matchesAliroTable8_22() { byte[] out = new byte[ACCESS_DOC_HEX.length + 128]; short written = DeviceResponseBuilder.build( ACCESS_DOC_HEX, (short) 0, (short) ACCESS_DOC_HEX.length, out, (short) 0); byte[] scratch = new byte[4]; // Whole-response span must equal `written` — no trailing garbage. short span = StructuralCbor.elementSpan( out, (short) 0, written, scratch, (short) 0); assertEquals(written, span, "DeviceResponse span must consume exactly the written bytes"); // Top-level: map(3) — major type 5, additional info 3. assertEquals((byte) 0xA3, out[0], "top-level must be a 3-entry CBOR map (0xA3)"); // Locate the AD inside the output and confirm it's byte-identical. // Brute-force scan for the AD's first byte (0x84 — COSE_Sign1 array // header) and verify the run matches. Only one such run should exist // because the wrapper itself doesn't contain that byte sequence. int adOffset = indexOf(out, 0, written, ACCESS_DOC_HEX); assertEquals(true, adOffset >= 0, "Access Document bytes must appear verbatim somewhere in the output"); assertArrayEquals( ACCESS_DOC_HEX, Arrays.copyOfRange(out, adOffset, adOffset + ACCESS_DOC_HEX.length), "Embedded Access Document must be byte-identical to the input"); } /** Naive byte-array indexOf — sufficient for the small test buffers. */ private static int indexOf(byte[] haystack, int from, int to, byte[] needle) { outer: for (int i = from; i <= to - needle.length; i++) { for (int j = 0; j < needle.length; j++) { if (haystack[i + j] != needle[j]) continue outer; } return i; } return -1; } private static byte[] hex(String s) { s = s.replaceAll("\\s+", ""); byte[] out = new byte[s.length() / 2]; for (int i = 0; i < out.length; i++) { out[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16); } return out; } }