Skip to content

Commit

Permalink
Implement EMV candidate list sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
leonlynch committed Feb 8, 2024
1 parent 9edf967 commit 63780d0
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/emv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
3 changes: 2 additions & 1 deletion src/emv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 100 additions & 0 deletions src/emv_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 7 additions & 0 deletions src/emv_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
70 changes: 70 additions & 0 deletions tests/emv_build_candidate_list_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "emv_fields.h"

#include <stdio.h>
#include <string.h>

// For debug output
#include "emv_debug.h"
Expand Down Expand Up @@ -249,13 +250,42 @@ 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;
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;
size_t app_count;

ttl.cardreader.mode = EMV_CARDREADER_MODE_APDU;
ttl.cardreader.ctx = &emul_ctx;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 63780d0

Please sign in to comment.