@@ -53,6 +53,8 @@ static void emv_txn_load_config(struct emv_ctx_t* emv);
53
53
// argp option keys
54
54
enum emv_tool_param_t {
55
55
EMV_TOOL_PARAM_ODA = -255 , // Negative value to avoid short options
56
+ EMV_TOOL_PARAM_TXN_DATE ,
57
+ EMV_TOOL_PARAM_TXN_TIME ,
56
58
EMV_TOOL_PARAM_TXN_TYPE ,
57
59
EMV_TOOL_PARAM_TXN_AMOUNT ,
58
60
EMV_TOOL_PARAM_TXN_AMOUNT_OTHER ,
@@ -70,6 +72,8 @@ static struct argp_option argp_options[] = {
70
72
{ "oda" , EMV_TOOL_PARAM_ODA , "SDA,DDA,CDA" , 0 , "Comma separated list of supported Offline Data Authentication (ODA) methods. Default is SDA,DDA,CDA." },
71
73
72
74
{ NULL , 0 , NULL , 0 , "Transaction parameters" , 2 },
75
+ { "txn-date" , EMV_TOOL_PARAM_TXN_DATE , "YYYY-MM-DD" , 0 , "Transaction date (YYYY-MM-DD). Default is current date." },
76
+ { "txn-time" , EMV_TOOL_PARAM_TXN_TIME , "hh:mm:ss" , 0 , "Transaction time (hh:mm:ss). Default is current time." },
73
77
{ "txn-type" , EMV_TOOL_PARAM_TXN_TYPE , "VALUE" , 0 , "Transaction type (two numeric digits, according to ISO 8583:1987 Processing Code)" },
74
78
{ "txn-amount" , EMV_TOOL_PARAM_TXN_AMOUNT , "AMOUNT" , 0 , "Transaction amount (without decimal separator)" },
75
79
{ "txn-amount-other" , EMV_TOOL_PARAM_TXN_AMOUNT_OTHER , "AMOUNT" , 0 , "Secondary transaction amount associated with cashback (without decimal separator)" },
@@ -103,6 +107,8 @@ static uint8_t term_caps_sec =
103
107
EMV_TERM_CAPS_SECURITY_CDA ;
104
108
105
109
// Transaction parameters
110
+ static uint8_t txn_date [3 ] = { 0xFF }; // Default is current date
111
+ static uint8_t txn_time [3 ] = { 0xFF }; // Default is current time
106
112
static uint8_t txn_type = EMV_TRANSACTION_TYPE_GOODS_AND_SERVICES ;
107
113
static uint32_t txn_amount = 0 ;
108
114
static uint32_t txn_amount_other = 0 ;
@@ -160,6 +166,56 @@ static error_t argp_parser_helper(int key, char* arg, struct argp_state* state)
160
166
return 0 ;
161
167
}
162
168
169
+ case EMV_TOOL_PARAM_TXN_DATE : {
170
+ int r ;
171
+ int year ;
172
+ int year_short ;
173
+ int month ;
174
+ int day ;
175
+ r = sscanf (arg , "%4d-%2d-%2d" , & year , & month , & day );
176
+ if (r != 3 ) {
177
+ argp_error (state , "Transaction date (--txn-date) argument must be YYYY-MM-DD" );
178
+ }
179
+ if (year < 1950 || year > 2049 ||
180
+ month < 1 || month > 12 ||
181
+ day < 1 || day > 31
182
+ ) {
183
+ argp_error (state , "Transaction date (--txn-date) argument must contain a valid date" );
184
+ }
185
+ if (year < 2000 ) { // See EMV 4.4 Book 4, 6.7.3
186
+ year_short = year - 1900 ;
187
+ } else {
188
+ year_short = year - 2000 ;
189
+ }
190
+ txn_date [0 ] = ((year_short / 10 ) << 4 ) | (year_short % 10 );
191
+ txn_date [1 ] = ((month / 10 ) << 4 ) | (month % 10 );
192
+ txn_date [2 ] = ((day / 10 ) << 4 ) | (day % 10 );
193
+
194
+ return 0 ;
195
+ }
196
+
197
+ case EMV_TOOL_PARAM_TXN_TIME : {
198
+ int r ;
199
+ int hours ;
200
+ int minutes ;
201
+ int seconds ;
202
+ r = sscanf (arg , "%2d:%2d:%2d" , & hours , & minutes , & seconds );
203
+ if (r != 3 ) {
204
+ argp_error (state , "Transaction time (--txn-time) argument must be hh:mm:ss" );
205
+ }
206
+ if (hours < 0 || hours > 23 ||
207
+ minutes < 0 || minutes > 59 ||
208
+ seconds < 0 || seconds > 59
209
+ ) {
210
+ argp_error (state , "Transaction time (--txn-time) argument must contain a valid time" );
211
+ }
212
+ txn_time [0 ] = ((hours / 10 ) << 4 ) | (hours % 10 );
213
+ txn_time [1 ] = ((minutes / 10 ) << 4 ) | (minutes % 10 );
214
+ txn_time [2 ] = ((seconds / 10 ) << 4 ) | (seconds % 10 );
215
+
216
+ return 0 ;
217
+ }
218
+
163
219
case EMV_TOOL_PARAM_TXN_TYPE : {
164
220
size_t arg_len = strlen (arg );
165
221
unsigned long value ;
@@ -584,25 +640,29 @@ static void emv_txn_load_params(struct emv_ctx_t* emv, uint32_t txn_seq_cnt, uin
584
640
{
585
641
time_t t = time (NULL );
586
642
struct tm * tm = localtime (& t );
587
- uint8_t emv_date [3 ];
588
- uint8_t emv_time [3 ];
589
- int date_offset = 0 ;
590
643
uint8_t buf [6 ];
591
644
592
645
// Transaction sequence counter
593
646
// See EMV 4.4 Book 4, 6.5.5
594
647
emv_tlv_list_push (& emv -> params , EMV_TAG_9F41_TRANSACTION_SEQUENCE_COUNTER , 4 , emv_uint_to_format_n (txn_seq_cnt , buf , 4 ), 0 );
595
648
596
- // Current date and time
597
- tm -> tm_year += date_offset ; // Useful for expired test cards
598
- emv_date [0 ] = (((tm -> tm_year / 10 ) % 10 ) << 4 ) | (tm -> tm_year % 10 );
599
- emv_date [1 ] = (((tm -> tm_mon + 1 ) / 10 ) << 4 ) | ((tm -> tm_mon + 1 ) % 10 );
600
- emv_date [2 ] = ((tm -> tm_mday / 10 ) << 4 ) | (tm -> tm_mday % 10 );
601
- emv_time [0 ] = ((tm -> tm_hour / 10 ) << 4 ) | (tm -> tm_hour % 10 );
602
- emv_time [1 ] = ((tm -> tm_min / 10 ) << 4 ) | (tm -> tm_min % 10 );
603
- emv_time [2 ] = ((tm -> tm_sec / 10 ) << 4 ) | (tm -> tm_sec % 10 );
604
- emv_tlv_list_push (& emv -> params , EMV_TAG_9A_TRANSACTION_DATE , 3 , emv_date , 0 );
605
- emv_tlv_list_push (& emv -> params , EMV_TAG_9F21_TRANSACTION_TIME , 3 , emv_time , 0 );
649
+ // Transaction date
650
+ if (txn_date [0 ] == 0xFF ) {
651
+ // Use current date
652
+ txn_date [0 ] = (((tm -> tm_year / 10 ) % 10 ) << 4 ) | (tm -> tm_year % 10 );
653
+ txn_date [1 ] = (((tm -> tm_mon + 1 ) / 10 ) << 4 ) | ((tm -> tm_mon + 1 ) % 10 );
654
+ txn_date [2 ] = ((tm -> tm_mday / 10 ) << 4 ) | (tm -> tm_mday % 10 );
655
+ }
656
+ emv_tlv_list_push (& emv -> params , EMV_TAG_9A_TRANSACTION_DATE , 3 , txn_date , 0 );
657
+
658
+ // Transaction time
659
+ if (txn_time [0 ] == 0xFF ) {
660
+ // Use current time
661
+ txn_time [0 ] = ((tm -> tm_hour / 10 ) << 4 ) | (tm -> tm_hour % 10 );
662
+ txn_time [1 ] = ((tm -> tm_min / 10 ) << 4 ) | (tm -> tm_min % 10 );
663
+ txn_time [2 ] = ((tm -> tm_sec / 10 ) << 4 ) | (tm -> tm_sec % 10 );
664
+ }
665
+ emv_tlv_list_push (& emv -> params , EMV_TAG_9F21_TRANSACTION_TIME , 3 , txn_time , 0 );
606
666
607
667
// Transaction currency
608
668
emv_tlv_list_push (& emv -> params , EMV_TAG_5F2A_TRANSACTION_CURRENCY_CODE , 2 , (uint8_t []){ 0x09 , 0x78 }, 0 ); // Euro (978)
0 commit comments