diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 0cfdb8d..2421f88 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -50,6 +50,10 @@ if (BUILD_TESTING)
target_link_libraries(emv_build_candidate_list_test PRIVATE emv_cardreader_emul print_helpers emv)
add_test(emv_build_candidate_list_test emv_build_candidate_list_test)
+ add_executable(emv_select_application_test emv_select_application_test.c)
+ target_link_libraries(emv_select_application_test PRIVATE emv_cardreader_emul print_helpers emv)
+ add_test(emv_select_application_test emv_select_application_test)
+
add_executable(isocodes_test isocodes_test.c)
find_package(Intl)
if(Intl_FOUND)
diff --git a/tests/emv_select_application_test.c b/tests/emv_select_application_test.c
new file mode 100644
index 0000000..023bc1e
--- /dev/null
+++ b/tests/emv_select_application_test.c
@@ -0,0 +1,576 @@
+/**
+ * @file emv_select_application_test.c
+ * @brief Unit tests for EMV application selection
+ *
+ * Copyright (c) 2024 Leon Lynch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * .
+ */
+
+#include "emv.h"
+#include "emv_cardreader_emul.h"
+#include "emv_ttl.h"
+#include "emv_tlv.h"
+#include "emv_app.h"
+#include "emv_tags.h"
+#include "emv_fields.h"
+
+#include
+#include
+#include
+
+// For debug output
+#include "emv_debug.h"
+#include "print_helpers.h"
+
+// Test data taken from test_sorted_app_priority[] in emv_build_candidate_list_test.c
+static const struct xpdu_t test_pse[] = {
+ {
+ 20, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x0E, 0x31, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, 0x00 }, // SELECT 1PAY.SYS.DDF01
+ 36, (uint8_t[]){ 0x6F, 0x20, 0x84, 0x0E, 0x31, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, 0xA5, 0x0E, 0x88, 0x01, 0x01, 0x5F, 0x2D, 0x04, 0x6E, 0x6C, 0x65, 0x6E, 0x9F, 0x11, 0x01, 0x01, 0x90, 0x00 }, // FCI
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x0C, 0x00 }, // READ RECORD 1,1
+ 72, (uint8_t[]){ 0x70, 0x44, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x05, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x35, 0x87, 0x01, 0x05, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x33, 0x87, 0x01, 0x04, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00 }, // AEF
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD 1,2
+ 58, (uint8_t[]){ 0x70, 0x36, 0x61, 0x19, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x05, 0x10, 0x10, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x38, 0x73, 0x07, 0x9F, 0x0A, 0x04, 0x00, 0x01, 0x01, 0x04, 0x61, 0x19, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x37, 0x73, 0x07, 0x9F, 0x0A, 0x04, 0x00, 0x01, 0x01, 0x04, 0x90, 0x00 }, // AEF without application priority indicator, of which one AID is not supported
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x03, 0x0C, 0x00 }, // READ RECORD 1,3
+ 72, (uint8_t[]){ 0x70, 0x44, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x01, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x31, 0x87, 0x01, 0x01, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x06, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x36, 0x87, 0x01, 0x07, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00 }, // AEF
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x04, 0x0C, 0x00 }, // READ RECORD 1,4
+ 72, (uint8_t[]){ 0x70, 0x44, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x02, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x32, 0x87, 0x01, 0x01, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x04, 0x50, 0x05, 0x41, 0x50, 0x50, 0x20, 0x34, 0x87, 0x01, 0x04, 0x73, 0x0B, 0x9F, 0x0A, 0x08, 0x00, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00 }, // AEF
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x05, 0x0C, 0x00 }, // READ RECORD 1,5
+ 2, (uint8_t[]){ 0x6A, 0x83 }, // Record not found
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app1[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x01, 0x00 }, // SELECT A0000000031001
+ 2, (uint8_t[]){ 0x6A, 0x82 }, // File or application not found
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app7[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10, 0x00 }, // SELECT A0000000041010
+ 2, (uint8_t[]){ 0x62, 0x83 }, // Selected file deactivated
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xC0, 0x00, 0x00, 0x00 }, // GET RESPONSE
+ 2, (uint8_t[]){ 0x6C, 0x61 }, // 97 bytes available
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xC0, 0x00, 0x00, 0x61 }, // GET RESPONSE Le=97
+ 51, (uint8_t[]){ 0x6F, 0x5D, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10, 0xA5, 0x52, 0x50, 0x10, 0x44, 0x45, 0x42, 0x49, 0x54, 0x20, 0x4D, 0x41, 0x53, 0x54, 0x45, 0x52, 0x43, 0x41, 0x52, 0x44, 0x9F, 0x12, 0x10, 0x44, 0x65, 0x62, 0x69, 0x74, 0x20, 0x4D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x61, 0x72, 0x64, 0x87, 0x01, 0x01, 0x9F, 0x11, 0x01, 0x01, 0x5F, 0x2D, 0x04, 0x6E, 0x6C, 0x65, 0x6E, 0xBF, 0x0C, 0x1C, 0x9F, 0x5D, 0x03, 0x01, 0x00, 0x00, 0x9F, 0x0A, 0x04, 0x00, 0x01, 0x01, 0x01, 0x9F, 0x4D, 0x02, 0x0B, 0x0A, 0x9F, 0x6E, 0x07, 0x05, 0x28, 0x00, 0x00, 0x30, 0x30, 0x00, 0x90, 0x00 }, // FCI
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app4[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x04, 0x00 }, // SELECT A0000000031004
+ 1, (uint8_t[]){ 0x00 }, // Invalid response
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app3[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0x00 }, // SELECT A0000000031003
+ 2, (uint8_t[]){ 0x6A, 0x81 }, // Function not supported
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app5[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x05, 0x00 }, // SELECT A0000000031005
+ 32, (uint8_t[]){ 0x6F, 0x1C, 0x85, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x05, 0xA5, 0x11, 0x50, 0x05, 0x56, 0x20, 0x50, 0x41, 0x59, 0x87, 0x01, 0x01, 0x5F, 0x2D, 0x04, 0x6E, 0x6C, 0x65, 0x6E, 0x90, 0x00 }, // Invalid FCI
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app2[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x02, 0x00 }, // SELECT A0000000031002
+ 32, (uint8_t[]){ 0x6F, 0x1C, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x05, 0xA5, 0x11, 0x50, 0x05, 0x56, 0x20, 0x50, 0x41, 0x59, 0x87, 0x01, 0x01, 0x5F, 0x2D, 0x04, 0x6E, 0x6C, 0x65, 0x6E, 0x90, 0x00 }, // FCI for A0000000031005
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app6[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x06, 0x00 }, // SELECT A0000000031006
+ 32, (uint8_t[]){ 0x6F, 0x1C, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x05, 0xA5, 0x11, 0x50, 0x05, 0x56, 0x20, 0x50, 0x41, 0x59, 0x87, 0x01, 0x01, 0x5F, 0x2D, 0x04, 0x6E, 0x6C, 0x65, 0x6E, 0x90, 0x00 }, // FCI for A0000000031005
+ },
+ { 0 }
+};
+
+static const struct xpdu_t test_select_app_success[] = {
+ {
+ 13, (uint8_t[]){ 0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0x00 }, // SELECT A0000000031003
+ 32, (uint8_t[]){ 0x6F, 0x1C, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0xA5, 0x11, 0x50, 0x05, 0x56, 0x20, 0x50, 0x41, 0x59, 0x87, 0x01, 0x01, 0x5F, 0x2D, 0x04, 0x6E, 0x6C, 0x65, 0x6E, 0x90, 0x00 }, // FCI
+ },
+ { 0 }
+};
+
+static bool verify_app_list_numbers(
+ const struct emv_app_list_t* app_list,
+ const unsigned int* numbers,
+ size_t numbers_len
+)
+{
+ struct emv_app_t* app;
+ size_t i;
+
+ i = 0;
+ for (app = app_list->front; app != NULL; app = app->next) {
+ if (i >= numbers_len) {
+ return false;
+ }
+
+ // Use application display name to validate sorted app order
+ char tmp[] = "APP x";
+ tmp[4] = '0' + numbers[i];
+ if (strcmp(tmp, app->display_name) != 0) {
+ return false;
+ }
+
+ ++i;
+ }
+
+ if (i != numbers_len) {
+ return false;
+ }
+
+ return true;
+}
+
+int main(void)
+{
+ int r;
+ struct emv_cardreader_emul_ctx_t emul_ctx;
+ struct emv_ttl_t ttl;
+ struct emv_tlv_list_t supported_aids = EMV_TLV_LIST_INIT;
+ struct emv_app_list_t app_list = EMV_APP_LIST_INIT;
+ struct emv_app_t* selected_app = (void*)42;
+
+ ttl.cardreader.mode = EMV_CARDREADER_MODE_APDU;
+ ttl.cardreader.ctx = &emul_ctx;
+ ttl.cardreader.trx = &emv_cardreader_emul;
+
+ // Supported applications
+ emv_tlv_list_push(&supported_aids, EMV_TAG_9F06_AID, 6, (uint8_t[]){ 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10 }, EMV_ASI_PARTIAL_MATCH); // Visa
+ emv_tlv_list_push(&supported_aids, EMV_TAG_9F06_AID, 7, (uint8_t[]){ 0xA0, 0x00, 0x00, 0x00, 0x03, 0x20, 0x10 }, EMV_ASI_EXACT_MATCH); // Visa Electron
+ emv_tlv_list_push(&supported_aids, EMV_TAG_9F06_AID, 7, (uint8_t[]){ 0xA0, 0x00, 0x00, 0x00, 0x03, 0x20, 0x20 }, EMV_ASI_EXACT_MATCH); // V Pay
+ emv_tlv_list_push(&supported_aids, EMV_TAG_9F06_AID, 6, (uint8_t[]){ 0xA0, 0x00, 0x00, 0x00, 0x04, 0x10 }, EMV_ASI_PARTIAL_MATCH); // Mastercard
+ emv_tlv_list_push(&supported_aids, EMV_TAG_9F06_AID, 6, (uint8_t[]){ 0xA0, 0x00, 0x00, 0x00, 0x04, 0x30 }, EMV_ASI_PARTIAL_MATCH); // Maestro
+
+ r = emv_debug_init(
+ EMV_DEBUG_SOURCE_ALL,
+ EMV_DEBUG_CARD,
+ &print_emv_debug
+ );
+ if (r) {
+ printf("Failed to initialise EMV debugging\n");
+ return 1;
+ }
+
+ // Prepare candidate application list to facilitate further testing
+ // See emv_build_candidate_list_test.c for thorough candidate application
+ // list testing
+ printf("\nPrepare candidate application list...\n");
+ emul_ctx.xpdu_list = test_pse;
+ emul_ctx.xpdu_current = NULL;
+ emv_app_list_clear(&app_list);
+ r = emv_build_candidate_list(
+ &ttl,
+ &supported_aids,
+ &app_list
+ );
+ if (r) {
+ fprintf(stderr, "Unexpected emv_build_candidate_list() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (emv_app_list_is_empty(&app_list)) {
+ fprintf(stderr, "Candidate list unexpectedly empty\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 1, 2, 3, 4, 5, 6, 7 }, 7)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ if (!emv_app_list_selection_is_required(&app_list)) {
+ fprintf(stderr, "Cardholder application selection unexpectedly NOT required\n");
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest selection of invalid application index...\n");
+ r = emv_select_application(&ttl, &app_list, 13, &selected_app);
+ if (r != EMV_ERROR_INVALID_PARAMETER) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 1, 2, 3, 4, 5, 6, 7 }, 7)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest selection of first application index and application not found...\n");
+ emul_ctx.xpdu_list = test_select_app1;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 0, &selected_app);
+ if (r != EMV_OUTCOME_TRY_AGAIN) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 2, 3, 4, 5, 6, 7 }, 6)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest selection of last application index and application blocked...\n");
+ emul_ctx.xpdu_list = test_select_app7;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 5, &selected_app);
+ if (r != EMV_OUTCOME_TRY_AGAIN) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 2, 3, 4, 5, 6 }, 5)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest card error during application selection...\n");
+ emul_ctx.xpdu_list = test_select_app4;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 2, &selected_app);
+ if (r != EMV_OUTCOME_CARD_ERROR) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 2, 3, 5, 6 }, 4)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest selection of application with card blocked...\n");
+ emul_ctx.xpdu_list = test_select_app3;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 1, &selected_app);
+ if (r != EMV_OUTCOME_CARD_BLOCKED) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 2, 5, 6 }, 3)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest invalid FCI during application selection...\n");
+ emul_ctx.xpdu_list = test_select_app5;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 1, &selected_app);
+ if (r != EMV_OUTCOME_TRY_AGAIN) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 2, 6 }, 2)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest DF Name mismatch during application selection...\n");
+ emul_ctx.xpdu_list = test_select_app2;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 0, &selected_app);
+ if (r != EMV_OUTCOME_TRY_AGAIN) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 6 }, 1)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ printf("\nTest DF Name mismatch during application selection and expecting empty candidate application list...\n");
+ emul_ctx.xpdu_list = test_select_app6;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 0, &selected_app);
+ if (r != EMV_OUTCOME_NOT_ACCEPTED) {
+ fprintf(stderr, "Unexpected emv_select_application() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (selected_app != NULL) {
+ fprintf(stderr, "emv_select_application() failed to zero selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ if (!emv_app_list_is_empty(&app_list)) {
+ fprintf(stderr, "Candidate list unexpectedly NOT empty\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ // Silence debugging logs for rebuilding candidate application list
+ r = emv_debug_init(
+ EMV_DEBUG_SOURCE_NONE,
+ EMV_DEBUG_NONE,
+ NULL
+ );
+ if (r) {
+ printf("Failed to initialise EMV debugging\n");
+ return 1;
+ }
+
+ // Prepare candidate application list to facilitate further testing
+ // See emv_build_candidate_list_test.c for thorough candidate application
+ // list testing
+ printf("\nPrepare candidate application list...\n");
+ emul_ctx.xpdu_list = test_pse;
+ emul_ctx.xpdu_current = NULL;
+ emv_app_list_clear(&app_list);
+ r = emv_build_candidate_list(
+ &ttl,
+ &supported_aids,
+ &app_list
+ );
+ if (r) {
+ fprintf(stderr, "Unexpected emv_build_candidate_list() result; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (emv_app_list_is_empty(&app_list)) {
+ fprintf(stderr, "Candidate list unexpectedly empty\n");
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 1, 2, 3, 4, 5, 6, 7 }, 7)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ if (!emv_app_list_selection_is_required(&app_list)) {
+ fprintf(stderr, "Cardholder application selection unexpectedly NOT required\n");
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ // Reset debuggin logs
+ r = emv_debug_init(
+ EMV_DEBUG_SOURCE_ALL,
+ EMV_DEBUG_CARD,
+ &print_emv_debug
+ );
+ if (r) {
+ printf("Failed to initialise EMV debugging\n");
+ return 1;
+ }
+
+ printf("\nTest successful application selection...\n");
+ emul_ctx.xpdu_list = test_select_app_success;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_select_application(&ttl, &app_list, 2, &selected_app);
+ if (r) {
+ fprintf(stderr, "emv_select_application() failed; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (emul_ctx.xpdu_current->c_xpdu_len != 0) {
+ fprintf(stderr, "Incomplete card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ if (!selected_app) {
+ fprintf(stderr, "emv_select_application() failed to populate selected_app\n");
+ r = 1;
+ goto exit;
+ }
+ r = emv_app_free(selected_app);
+ if (r) {
+ fprintf(stderr, "emv_app_free() failed; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r));
+ r = 1;
+ goto exit;
+ }
+ if (!verify_app_list_numbers(&app_list, (unsigned int[]){ 1, 2, 4, 5, 6, 7 }, 6)) {
+ fprintf(stderr, "Invalid remaining candidate application list\n");
+ for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+ print_emv_app(app);
+ }
+ r = 1;
+ goto exit;
+ }
+ printf("Success\n");
+
+ // Success
+ r = 0;
+ goto exit;
+
+exit:
+ emv_tlv_list_clear(&supported_aids);
+ emv_app_list_clear(&app_list);
+
+ return r;
+}