root/usr/src/test/os-tests/tests/uccid/atrparse.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2019, Joyent, Inc.
 */

/*
 * Verify that we can parse various forms of ATR data and detect invalid data.
 */

#include <err.h>
#include <stdlib.h>

#include <atr.h>

typedef struct atr_vals {
} atr_vals_t;

typedef struct atr_test {
        const char *ar_test;
        uint8_t ar_len;
        uint8_t ar_buf[64];
        atr_parsecode_t ar_retval;
        /* Everything after this is data from the ATR */
        atr_protocol_t ar_sup;
        atr_protocol_t ar_def;
        boolean_t ar_neg;
        uint8_t ar_fi;
        uint8_t ar_di;
        atr_convention_t ar_conv;
        uint8_t ar_guard;
        atr_clock_stop_t ar_stop;
        /* These will be checked based on sup prot */
        uint8_t ar_t0_wi;
        atr_t1_checksum_t  ar_t1_cksum;
        uint8_t ar_t1_bwi;
        uint8_t ar_t1_cwi;
        uint8_t ar_t1_ifsc;
} atr_test_t;

atr_test_t atr_tests[] = {
        { "zero-length data", 0, { 0 }, ATR_CODE_TOO_SHORT },
        { "No T0", 1, { 0x3f }, ATR_CODE_TOO_SHORT },
        { "Too much data", 34, { 0 }, ATR_CODE_TOO_LONG },
        { "Overrun T0 (1)", 2, { 0x3b, 0x10 }, ATR_CODE_OVERRUN },
        { "Overrun T0 (2)", 2, { 0x3b, 0x80 }, ATR_CODE_OVERRUN },
        { "Overrun T0 (3)", 2, { 0x3b, 0x01 }, ATR_CODE_OVERRUN },
        { "Overrun T0 (4)", 2, { 0x3b, 0x11 }, ATR_CODE_OVERRUN },
        { "Overrun T0 (5)", 2, { 0x3b, 0xff }, ATR_CODE_OVERRUN },
        { "Overrun TD1", 3, { 0x3b, 0x80, 0x10 }, ATR_CODE_OVERRUN },
        { "Overrun TD2", 4, { 0x3b, 0x80, 0x80, 0x10 }, ATR_CODE_OVERRUN },
        { "Overrun TD", 33, { 0x3b, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
            0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
            0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
            0x80, 0x80, 0x80 }, ATR_CODE_OVERRUN },
        { "T0 w/ T=15 and no cksum", 5, { 0x3b, 0x80, 0x80, 0x1f, 0x00 },
            ATR_CODE_OVERRUN },
        { "Bad TS (1)", 2, { 0x3a, 0x00 }, ATR_CODE_INVALID_TS },
        { "Bad TS (2)", 2, { 0xff, 0x00 }, ATR_CODE_INVALID_TS },
        { "T0 w/ T=15 and bad cksum", 6, { 0x3b, 0x80, 0x80, 0x1f, 0x00, 0x00 },
            ATR_CODE_CHECKSUM_ERROR },
        { "T0 w/ T=15 and bad cksum (make sure no TS)", 6,
            { 0x3b, 0x80, 0x80, 0x1f, 0x00, 0x24 },
            ATR_CODE_CHECKSUM_ERROR },
        { "T=15 in TD1", 4, { 0x3b, 0x80, 0x0f, 0x8f }, ATR_CODE_INVALID_TD1 },
        {
                .ar_test = "Minimal T0 Direct",
                .ar_len = 2,
                .ar_buf = { 0x3b, 0x00 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "Minimal T0 Inverse",
                .ar_len = 2,
                .ar_buf = { 0x3f, 0x00 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_INVERSE,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 Fi/Di (1)",
                .ar_len = 3,
                .ar_buf = { 0x3b, 0x10, 0x24 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 2,
                .ar_di = 4,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 Fi/Di (2)",
                .ar_len = 3,
                .ar_buf = { 0x3b, 0x10, 0x93 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 9,
                .ar_di = 3,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 Ignore deprecated TB1",
                .ar_len = 3,
                .ar_buf = { 0x3b, 0x20, 0x42 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 Ignore deprecated TB2",
                .ar_len = 4,
                .ar_buf = { 0x3b, 0x80, 0x20, 0x42 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 Ignore deprecated TB1/TB2",
                .ar_len = 5,
                .ar_buf = { 0x3b, 0xa0, 0x55, 0x20, 0x42 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 Encode TC1",
                .ar_len = 3,
                .ar_buf = { 0x3b, 0x40, 0x23 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0x23,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 TA2 says neg",
                .ar_len = 4,
                .ar_buf = { 0x3b, 0x80, 0x10, 0x00 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 TA2 says not neg",
                .ar_len = 4,
                .ar_buf = { 0x3b, 0x80, 0x10, 0x80 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_FALSE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 TA2 says not neg, honor Fi/Di",
                .ar_len = 5,
                .ar_buf = { 0x3b, 0x90, 0x24, 0x10, 0x80 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_FALSE,
                .ar_fi = 2,
                .ar_di = 4,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 TA2 says not neg, don't honor Fi/Di",
                .ar_len = 5,
                .ar_buf = { 0x3b, 0x90, 0x24, 0x10, 0x90 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_FALSE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 TC2 set",
                .ar_len = 4,
                .ar_buf = { 0x3b, 0x80, 0x40, 0x35 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 0x35,
        }, {
                .ar_test = "T0 T15 empty (requires checksum)",
                .ar_len = 5,
                .ar_buf = { 0x3b, 0x80, 0x80, 0x0f, 0x0f },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 T15 Clock Stop (1)",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0x07, 0x18 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 T15 Clock Stop (2)",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0x47, 0x58 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_LOW,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 T15 Clock Stop (3)",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0x87, 0x98 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_HI,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 T15 Clock Stop (4)",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0xc7, 0xd8 },
                .ar_sup = ATR_P_T0,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_BOTH,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "T0 with random prots",
                .ar_len = 7,
                .ar_buf = { 0x3b, 0x80, 0x84, 0x85, 0x88, 0x0f, 0x06 },
                .ar_sup = ATR_P_T0,
                /*
                 * This comes from the fact that TD1 is T=4 and that isn't
                 * supported in the system.
                 */
                .ar_def = ATR_P_NONE,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
        }, {
                .ar_test = "Actual ATR (1, Yubikey4)",
                .ar_len = 18,
                .ar_buf = { 0x3b, 0xf8, 0x13, 0x00, 0x00, 0x81, 0x31, 0xfe,
                    0x15, 0x59, 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x34,
                    0xd4 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 3,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 1,
                .ar_t1_cwi = 5,
                .ar_t1_ifsc = 254
        }, {
                .ar_test = "Actual ATR (2)",
                .ar_len = 19,
                .ar_buf = { 0x3b, 0xf9, 0x18, 0x00, 0x00, 0x81, 0x31, 0xfe,
                    0x45, 0x4a, 0x32, 0x44, 0x30, 0x38, 0x31, 0x5f, 0x50, 0x56,
                    0xb6 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 8,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 5,
                .ar_t1_ifsc = 254
        }, {
                .ar_test = "Actual ATR (3)",
                .ar_len = 22,
                .ar_buf = { 0x3b, 0xfc, 0x18, 0x00, 0x00, 0x81, 0x31, 0x80,
                    0x45, 0x90, 0x67, 0x46, 0x4a, 0x00, 0x64, 0x16, 0x6, 0xf2,
                    0x72, 0x7e, 0x00, 0xe0 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 8,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 5,
                .ar_t1_ifsc = 128
        }, {
                .ar_test = "Minimal T=1",
                .ar_len = 4,
                .ar_buf = { 0x3b, 0x80, 0x01, 0x81 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=1 Fi/Di",
                .ar_len = 5,
                .ar_buf = { 0x3b, 0x90, 0x34, 0x01, 0xa5 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 3,
                .ar_di = 4,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=1 TA2 says neg, T=1 def",
                .ar_len = 5,
                .ar_buf = { 0x3b, 0x80, 0x11, 0x11, 0x80 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=0, T=1 TA2 says neg, T=0 def",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x90, 0x10, 0x01, 0x01 },
                .ar_sup = ATR_P_T0 | ATR_P_T1,
                .ar_def = ATR_P_T0,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=0, T=1 TA2 says neg, T=1 def",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x90, 0x11, 0x01, 0x00 },
                .ar_sup = ATR_P_T0 | ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=0, T=1 TA2 says not neg, T=0 def",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x90, 0x90, 0x01, 0x81 },
                .ar_sup = ATR_P_T0 | ATR_P_T1,
                .ar_def = ATR_P_T0,
                .ar_neg = B_FALSE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=0, T=1 TA2 says not neg, T=1 def",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x90, 0x81, 0x01, 0x90 },
                .ar_sup = ATR_P_T0 | ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_FALSE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t0_wi = 10,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=1, BWI/CWI",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x81, 0x21, 0x59, 0x79 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 5,
                .ar_t1_cwi = 9,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=1, IFSC",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x81, 0x11, 0x49, 0x59 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 73
        }, {
                .ar_test = "T=1, Checksum (LRC)",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x81, 0x41, 0x00, 0x40 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }, {
                .ar_test = "T=1, Checksum (CRC)",
                .ar_len = 6,
                .ar_buf = { 0x3b, 0x80, 0x81, 0x41, 0x01, 0x41 },
                .ar_sup = ATR_P_T1,
                .ar_def = ATR_P_T1,
                .ar_neg = B_TRUE,
                .ar_fi = 1,
                .ar_di = 1,
                .ar_conv = ATR_CONVENTION_DIRECT,
                .ar_guard = 0,
                .ar_stop = ATR_CLOCK_STOP_NONE,
                .ar_t1_cksum = ATR_T1_CHECKSUM_CRC,
                .ar_t1_bwi = 4,
                .ar_t1_cwi = 13,
                .ar_t1_ifsc = 32
        }
};

static void
atr_parse_failed(atr_test_t *test, const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        (void) fprintf(stderr, "Test \"%s\" failed: ", test->ar_test);
        (void) vfprintf(stderr, fmt, ap);
        (void) fprintf(stderr, "\n");
        va_end(ap);
}

static uint_t
atr_parse_one(atr_data_t *data, atr_test_t *test)
{
        uint_t err = 0;
        atr_parsecode_t ret;
        atr_protocol_t sup, def;
        boolean_t neg;
        uint8_t fi, di, guard;
        atr_convention_t conv;
        atr_clock_stop_t stop;

        ret = atr_parse(test->ar_buf, test->ar_len, data);
        if (ret != test->ar_retval) {
                atr_parse_failed(test, "found unexpected return "
                    "value: %u (%s), expected: %u", ret, atr_strerror(ret),
                    test->ar_retval);
                return (1);
        }

        /* Don't test anything else if it's not OK */
        if (ret != ATR_CODE_OK)
                return (0);

        sup = atr_supported_protocols(data);
        def = atr_default_protocol(data);
        neg = atr_params_negotiable(data);
        fi = atr_fi_index(data);
        di = atr_di_index(data);
        conv = atr_convention(data);
        guard = atr_extra_guardtime(data);
        stop = atr_clock_stop(data);

        if (sup != test->ar_sup) {
                atr_parse_failed(test, "Found mismatched supported "
                    "protocols: %u, expected: %u", sup, test->ar_sup);
                err++;
        }

        if (def != test->ar_def) {
                atr_parse_failed(test, "Found mismatched default "
                    "protocols: %u, expected: %u", def, test->ar_def);
                err++;
        }

        if (neg != test->ar_neg) {
                atr_parse_failed(test, "Found mismatched negotiable bit: "
                    "%u, expected %u", neg, test->ar_neg);
                err++;
        }

        if (fi != test->ar_fi) {
                atr_parse_failed(test, "Found mismatched fi index: "
                    "%u, expected: %u", fi, test->ar_fi);
                err++;
        }

        if (di != test->ar_di) {
                atr_parse_failed(test, "Found mismatched di index: "
                    "%u, expected: %u", di, test->ar_di);
                err++;
        }

        if (conv != test->ar_conv) {
                atr_parse_failed(test, "Found mismatched TS convention: "
                    "%u, expected: %u", conv, test->ar_conv);
                err++;
        }

        if (guard != test->ar_guard) {
                atr_parse_failed(test, "Found mismatched extra guardtime: "
                    "%u, expected: %u", guard, test->ar_guard);
                err++;
        }

        if (stop != test->ar_stop) {
                atr_parse_failed(test, "Found mismatched clock stop: "
                    "%u, expected: %u", stop, test->ar_stop);
                err++;
        }

        if ((sup & ATR_P_T0) != 0) {
                uint8_t wi;

                wi = atr_t0_wi(data);
                if (wi != test->ar_t0_wi) {
                        atr_parse_failed(test, "Found mismatched T0 wi: "
                            "%u, expected: %u", wi, test->ar_t0_wi);
                        err++;
                }
        }

        if ((sup & ATR_P_T1) != 0) {
                atr_t1_checksum_t cksum;
                uint8_t bwi, cwi, ifsc;

                cksum = atr_t1_checksum(data);
                bwi = atr_t1_bwi(data);
                cwi = atr_t1_cwi(data);
                ifsc = atr_t1_ifsc(data);

                if (cksum != test->ar_t1_cksum) {
                        atr_parse_failed(test, "Found mistmatched T1 checksum: "
                            "%u, expected: %u", cksum, test->ar_t1_cksum);
                        err++;
                }

                if (bwi != test->ar_t1_bwi) {
                        atr_parse_failed(test, "Found mistmatched T1 bwi: "
                            "%u, expected: %u", bwi, test->ar_t1_bwi);
                        err++;
                }

                if (cwi != test->ar_t1_cwi) {
                        atr_parse_failed(test, "Found mistmatched T1 cwi: "
                            "%u, expected: %u", cwi, test->ar_t1_cwi);
                        err++;
                }

                if (ifsc != test->ar_t1_ifsc) {
                        atr_parse_failed(test, "Found mistmatched T1 ifsc: "
                            "%u, expected: %u", ifsc, test->ar_t1_ifsc);
                        err++;
                }
        }

        if (err > 0) {
                atr_data_dump(data, stderr);
                return (1);
        }

        return (0);
}

int
main(void)
{
        uint_t i;
        uint_t errs = 0;
        atr_data_t *data;

        data = atr_data_alloc();
        if (data == NULL) {
                errx(EXIT_FAILURE, "failed to allocate atr_data_t");
        }

        for (i = 0; i < sizeof (atr_tests) / sizeof (atr_test_t); i++) {
                atr_data_reset(data);
                errs += atr_parse_one(data, &atr_tests[i]);
        }

        atr_data_free(data);

        if (errs != 0) {
                warnx("%d test(s) failed", errs);
        }
        return (errs != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}