From 63780d0257a378bc90dad8e254bdace2c61fa4cf Mon Sep 17 00:00:00 2001
From: Leon Lynch <lynch.leon@gmail.com>
Date: Thu, 8 Feb 2024 23:18:44 +0100
Subject: [PATCH] Implement EMV candidate list sorting

---
 src/emv.c                             |   9 +++
 src/emv.h                             |   3 +-
 src/emv_app.c                         | 100 ++++++++++++++++++++++++++
 src/emv_app.h                         |   7 ++
 tests/emv_build_candidate_list_test.c |  70 ++++++++++++++++++
 5 files changed, 188 insertions(+), 1 deletion(-)

diff --git a/src/emv.c b/src/emv.c
index f04ab31..534c809 100644
--- a/src/emv.c
+++ b/src/emv.c
@@ -335,5 +335,14 @@ int emv_build_candidate_list(
 		return EMV_OUTCOME_NOT_ACCEPTED;
 	}
 
+	// Sort application list according to priority
+	// See EMV 4.4 Book 1, 12.4, step 4
+	r = emv_app_list_sort_priority(app_list);
+	if (r) {
+		emv_debug_trace_msg("emv_app_list_sort_priority() failed; r=%d", r);
+		emv_debug_error("Failed to sort application list; terminate session");
+		return EMV_ERROR_INTERNAL;
+	}
+
 	return 0;
 }
diff --git a/src/emv.h b/src/emv.h
index a230396..1ac256d 100644
--- a/src/emv.h
+++ b/src/emv.h
@@ -92,7 +92,8 @@ int emv_atr_parse(const void* atr, size_t atr_len);
 
 /**
  * Build candidate application list using Payment System Environment (PSE) or
- * discovery of supported AIDs
+ * discovery of supported AIDs, and then sort according to Application Priority
+ * Indicator
  * @remark See EMV 4.4 Book 1, 12.3
  *
  * @param ttl EMV Terminal Transport Layer context
diff --git a/src/emv_app.c b/src/emv_app.c
index f7e37f6..e792b5d 100644
--- a/src/emv_app.c
+++ b/src/emv_app.c
@@ -407,3 +407,103 @@ struct emv_app_t* emv_app_list_pop(struct emv_app_list_t* list)
 
 	return app;
 }
+
+static int emv_app_list_insert(
+	struct emv_app_list_t* list,
+	struct emv_app_t* pos,
+	struct emv_app_t* app
+)
+{
+	if (!emv_app_list_is_valid(list)) {
+		return -1;
+	}
+	if (!pos) {
+		return -2;
+	}
+	if (!app) {
+		return -3;
+	}
+
+	if (list->back == pos) {
+		// Insert at back of list
+		return emv_app_list_push(list, app);
+	} else if (pos->next) {
+		// Insert in middle of list
+		app->next = pos->next;
+		pos->next = app;
+	} else {
+		// Invalid list or position
+		return -4;
+	}
+
+	return 0;
+}
+
+static int emv_app_list_push_front(
+	struct emv_app_list_t* list,
+	struct emv_app_t* app
+)
+{
+	if (!emv_app_list_is_valid(list)) {
+		return -1;
+	}
+	if (!app) {
+		return -2;
+	}
+
+	if (list->front) {
+		app->next = list->front;
+		list->front = app;
+	} else {
+		list->front = app;
+		list->back = app;
+	}
+
+	return 0;
+}
+
+int emv_app_list_sort_priority(struct emv_app_list_t* list)
+{
+	int r;
+	struct emv_app_t* app;
+	struct emv_app_list_t sorted_list = EMV_APP_LIST_INIT;
+
+	if (!emv_app_list_is_valid(list)) {
+		return -1;
+	}
+
+	// Given that the app list should be very short, an insertion sort is a
+	// reasonable approach
+	while ((app = emv_app_list_pop(list))) {
+		struct emv_app_t* pos = NULL;
+
+		// Value of 1 is the highest priority
+		// See EMV 4.4 Book 1, 12.2.3, table 13
+		// However, the EMV specification does not state how an application
+		// without a priority indicator should be prioritised relative to an
+		// application with a priority indicator, and therefore this
+		// implementation chooses to favour applications with a priority
+		// indicator over those without.
+		for (struct emv_app_t* cur = sorted_list.front; cur != NULL; cur = cur->next) {
+			if (!cur->priority) {
+				break;
+			}
+			if (app->priority && app->priority < cur->priority) {
+				break;
+			}
+			pos = cur;
+		}
+		if (pos) {
+			r = emv_app_list_insert(&sorted_list, pos, app);
+		} else {
+			r = emv_app_list_push_front(&sorted_list, app);
+		}
+		if (r) {
+			emv_app_list_clear(&sorted_list);
+			return -2;
+		}
+	}
+
+	*list = sorted_list;
+	return 0;
+}
diff --git a/src/emv_app.h b/src/emv_app.h
index 48ec993..a4cefe6 100644
--- a/src/emv_app.h
+++ b/src/emv_app.h
@@ -167,6 +167,13 @@ int emv_app_list_push(struct emv_app_list_t* list, struct emv_app_t* app);
  */
 struct emv_app_t* emv_app_list_pop(struct emv_app_list_t* list);
 
+/**
+ * Sort EMV application list according to the priority field
+ * @param list EMV application list
+ * @return Zero for success. Less than zero for error.
+ */
+int emv_app_list_sort_priority(struct emv_app_list_t* list);
+
 __END_DECLS
 
 #endif
diff --git a/tests/emv_build_candidate_list_test.c b/tests/emv_build_candidate_list_test.c
index aa6836c..4c66166 100644
--- a/tests/emv_build_candidate_list_test.c
+++ b/tests/emv_build_candidate_list_test.c
@@ -28,6 +28,7 @@
 #include "emv_fields.h"
 
 #include <stdio.h>
+#include <string.h>
 
 // For debug output
 #include "emv_debug.h"
@@ -249,6 +250,34 @@ static const struct xpdu_t test_aid_multi_app_supported[] = {
 	{ 0 }
 };
 
+static const struct xpdu_t test_sorted_app_priority[] = {
+	{
+		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 }
+};
+
 int main(void)
 {
 	int r;
@@ -256,6 +285,7 @@ int main(void)
 	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;
+	size_t app_count;
 
 	ttl.cardreader.mode = EMV_CARDREADER_MODE_APDU;
 	ttl.cardreader.ctx = &emul_ctx;
@@ -539,6 +569,46 @@ int main(void)
 	}
 	printf("Success\n");
 
+	printf("\nTesting sorted app priority...\n");
+	emul_ctx.xpdu_list = test_sorted_app_priority;
+	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;
+	}
+	app_count = 0;
+	for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) {
+		print_emv_app(app);
+		++app_count;
+
+		// Use application display name to validate sorted app order
+		char tmp[] = "APP x";
+		tmp[4] = '0' + app_count;
+		if (strcmp(tmp, app->display_name) != 0) {
+			fprintf(stderr, "Invalid candidate list order\n");
+			r = 1;
+			goto exit;
+		}
+	}
+	printf("Success\n");
+
 	// Success
 	r = 0;
 	goto exit;