taler-mdb.c (103417B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2019, 2020, 2022, 2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 FITNESS FOR 12 A PARTICULAR PURPOSE. See the GNU General Public License for more 13 details. 14 15 You should have received a copy of the GNU General Public License 16 along with 17 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file taler-mdb.c 21 * @brief runs the payment logic for a Taler-enabled snack machine 22 * @author Marco Boss 23 * @author Christian Grothoff 24 * @author Dominik Hofer 25 */ 26 #include "config.h" 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <signal.h> 31 #include <unistd.h> 32 #include <sys/socket.h> 33 #if HAVE_SYS_UN_H 34 #include <sys/un.h> 35 #endif 36 #if HAVE_NETINET_IN_H 37 #include <netinet/in.h> 38 #endif 39 #if HAVE_NETINET_IP_H 40 #include <netinet/ip.h> /* superset of previous */ 41 #endif 42 #include <sys/stat.h> 43 #include <sys/types.h> 44 #include <errno.h> 45 #include <termios.h> 46 #include <nfc/nfc.h> 47 #include <microhttpd.h> 48 #include <gnunet/gnunet_util_lib.h> 49 #include <gnunet/gnunet_json_lib.h> 50 #include <taler/taler_json_lib.h> 51 #include <taler/taler_merchant_service.h> 52 #if HAVE_QRENCODE_H 53 #include <qrencode.h> 54 #endif 55 #include <sys/mman.h> 56 #include <sys/ioctl.h> 57 #include <fcntl.h> 58 /* for adafruit pitft display */ 59 #include <linux/fb.h> 60 #include "taler_mdb_util.h" 61 62 #ifndef EXIT_NOTCONFIGURED 63 #define EXIT_NOTCONFIGURED 6 64 #endif 65 66 67 /* Constants */ 68 #define MAX_SIZE_RX_BUFFER 256 69 70 /* Constants */ 71 #define MAX_SIZE_TX_BUFFER 256 72 73 /** 74 * Disable i18n support. 75 */ 76 #define _(s) (s) 77 78 #define BACKEND_POLL_TIMEOUT GNUNET_TIME_relative_multiply ( \ 79 GNUNET_TIME_UNIT_SECONDS, 30) 80 81 /** 82 * Set payment deadline below what will work with the snack machine. 83 */ 84 #define PAY_TIMEOUT GNUNET_TIME_relative_multiply ( \ 85 GNUNET_TIME_UNIT_MINUTES, 2) 86 87 /** 88 * How long to show a transient error. 89 */ 90 #define ERR_DELAY GNUNET_TIME_relative_multiply ( \ 91 GNUNET_TIME_UNIT_SECONDS, 30) 92 93 /** 94 * How long could it take at most for us to notify the Taler merchant 95 * backend to grant a refund to a user if dispensing the product 96 * failed? (Very conservative value here, for vending machines brewing 97 * coffee or something complex that could fail.) 98 */ 99 #define MAX_REFUND_DELAY GNUNET_TIME_relative_multiply ( \ 100 GNUNET_TIME_UNIT_MINUTES, 5) 101 102 103 #define NFC_FAILURE_RETRY_FREQ GNUNET_TIME_UNIT_MINUTES 104 105 #define NFC_NOT_FOUND_RETRY_FREQ GNUNET_TIME_UNIT_SECONDS 106 107 /** 108 * How long do we wait at most for an ACK from MDB? 109 */ 110 #define MAX_ACK_LATENCY GNUNET_TIME_UNIT_SECONDS 111 112 /** 113 * Timeout in milliseconds for libnfc operations. 114 */ 115 #define NFC_TIMEOUT 500 116 117 #define MAX_HTTP_RETRY_FREQ GNUNET_TIME_relative_multiply ( \ 118 GNUNET_TIME_UNIT_MILLISECONDS, 500) 119 120 #define MAX_HTTP_RETRY_FREQ GNUNET_TIME_relative_multiply ( \ 121 GNUNET_TIME_UNIT_MILLISECONDS, 500) 122 123 /** 124 * Code returned by libnfc in case of success. 125 */ 126 #define APDU_SUCCESS "\x90\x00" 127 128 /** 129 * Code returned by libnfc in case Taler wallet is not installed. 130 */ 131 #define APDU_NOT_FOUND "\x6a\x82" 132 133 /* upper and lower bounds for mifare targets uid length */ 134 /** 135 * Upper length of the uid for a valid MIFARE target 136 */ 137 #define UID_LEN_UPPER 7 138 139 /** 140 * Lower length of the uid for a valid MIFARE target 141 */ 142 #define UID_LEN_LOWER 4 143 144 /* Commands for communication via MDB/ICP */ 145 /* VMC commands */ 146 #define VMC_CMD_START 0x02 147 #define VMC_CMD_END 0x03 148 #define VMC_CMD_RESET 0x10 149 150 /** 151 * Acknowledgement 152 */ 153 #define VMC_ACKN 0x00 154 155 /** 156 * Request for configuration. 157 */ 158 #define VMC_CONF 0x11 159 #define VMC_READER_CONF 0x00 160 #define VMC_SETUP_MAX_MIN_PRICES 0x01 161 162 /** 163 * Machine is polling for something. 164 */ 165 #define VMC_POLL 0x12 166 167 /** 168 * Vending, with sub-command. 169 */ 170 #define VMC_VEND 0x13 171 #define VMC_VEND_REQUEST 0x00 172 #define VMC_VEND_CANCEL 0x01 173 #define VMC_VEND_SUCCESS 0x02 174 #define VMC_VEND_FAILURE 0x03 175 #define VMC_VEND_SESSION_COMPLETE 0x04 176 177 /** 178 * VMC Revalue Request 179 */ 180 #define VMC_REVALUE 0x15 181 #define VMC_REVALUE_REQUEST 0x00 182 #define VMC_REVALUE_LIMIT_REQUEST 0x01 183 /** 184 * Commands for the reader (our device). 185 */ 186 #define VMC_READER 0x14 187 #define VMC_READER_DISABLE 0x00 188 #define VMC_READER_ENABLE 0x01 189 #define VMC_READER_CANCEL 0x02 190 191 #define VMC_REQUEST_ID 0x17 192 193 /** 194 * Out of sequence. 195 */ 196 #define VMC_OOSQ 0xB0 197 198 /** 199 * Request to retransmit last command. 200 */ 201 #define VMC_RETR 0xAA 202 203 /* Reader commands */ 204 205 /* Reader Not Acknowledge */ 206 #define READER_NACK "FF" 207 208 /* Config Data */ 209 /* Refer to the mdb interface specifications v4.2 p.288 */ 210 #define READER_CONFIG "01" 211 #define READER_FEATURE_LEVEL "01" 212 #define READER_COUNTRYCODE "0972" 213 #define READER_SCALE_FACTOR "0A" 214 #define READER_DECIMAL_PLACES "02" 215 #define READER_MAX_RESPONSE_TIME "07" 216 #define READER_MISC_OPTIONS "0D" 217 218 /* Session Commands */ 219 /* Refer to the mdb interface specifications v4.2 p.131 */ 220 #define READER_BEGIN_SESSION "03" 221 #define READER_FUNDS_AVAILABLE "000A" 222 #define READER_END_SESSION "07" 223 224 /* Vend Commands */ 225 /* Refer to the mdb interface specifications v4.2 p.134 */ 226 #define READER_VEND_APPROVE "05" 227 #define READER_VEND_AMOUNT "FFFE" 228 #define READER_VEND_DENIED "06" 229 230 /* Revalue */ 231 #define READER_REVALUE_APPROVED "0D" 232 #define READER_REVALUE_APPROVED "0D" 233 #define READER_REVALUE_LIMIT "0F" 234 #define READER_REVALUE_LIMIT_AMOUNT "FFFE" 235 236 /* Cancelled Command */ 237 #define READER_CANCELLED "08" 238 239 /* Display Request for Sold Out product */ 240 #define READER_DISPLAY_REQUEST "02" 241 #define READER_DISPLAY_REQUEST_TIME "32" 242 #define READER_DISPLAY_SOLD_OUT \ 243 "202020202020202050726f6475637420736f6c64206f75742020202020202020" 244 #define READER_DISPLAY_INTERNAL_ERROR \ 245 "202020496e7465726e616c204572726f72202d2054727920416761696e202020i" 246 #define READER_DISPLAY_BACKEND_NOT_REACHABLE \ 247 "20202020204261636b656e64206e6f7420726561636861626c65202020202020" 248 249 /* Unused reader commands */ 250 #define READER_SESSION_CANCEL_REQUEST "04" 251 #define READER_REVALUE_DENIED "0E" 252 253 /** 254 * How long are we willing to wait for MDB during 255 * shutdown? 256 */ 257 #define SHUTDOWN_MDB_TIMEOUT GNUNET_TIME_relative_multiply ( \ 258 GNUNET_TIME_UNIT_MILLISECONDS, 100) 259 260 /** 261 * Datatype for mdb subcommands and data 262 */ 263 struct MdbBlock 264 { 265 /** 266 * Data containing an mdb command or the data of an mdb command 267 */ 268 uint8_t *bin; 269 270 /** 271 * Size of the data referenced by *bin 272 */ 273 size_t bin_size; 274 }; 275 276 277 /** 278 * Datatype for mdb command 279 */ 280 struct MdbCommand 281 { 282 /** 283 * Name of the command for the logging 284 */ 285 const char *name; 286 287 /** 288 * Data block containing the information about the mdb command 289 */ 290 struct MdbBlock cmd; 291 292 /** 293 * Data block containing the information about the mdb command data 294 */ 295 struct MdbBlock data; 296 }; 297 298 299 /** 300 * Struct holding the information for a product to sell 301 */ 302 struct Product 303 { 304 /** 305 * The price for the product 306 */ 307 struct TALER_Amount price; 308 309 /** 310 * Description (or name) of the product 311 */ 312 char *description; 313 314 /** 315 * Authorization to header to use for this @e instance. 316 */ 317 char *auth_header; 318 319 /** 320 * Which instance should be used for billing? Full URL, replaces 321 * the default base URL for orders involving this product. NULL if 322 * we should use the #backend_base_url. 323 */ 324 char *instance; 325 326 /** 327 * Preview image to embed in the contract, NULL for 328 * no preview. Already base64 encoded. 329 */ 330 char *preview; 331 332 /** 333 * Number of the product in the vending machine 334 */ 335 unsigned long long number; 336 337 /** 338 * Set to #GNUNET_YES if this product was found 339 * to have been sold out (VEND failure). 340 */ 341 bool sold_out; 342 343 /** 344 * Key for the product (optional, needed to test the application without vending machine) 345 */ 346 char key; 347 348 }; 349 350 351 /** 352 * Handle for a payment 353 */ 354 struct PaymentActivity 355 { 356 357 /** 358 * Curl context for communication with taler backend 359 */ 360 struct GNUNET_CURL_Context *ctx; 361 362 /** 363 * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). 364 */ 365 struct GNUNET_CURL_RescheduleContext *rc; 366 367 /** 368 * Handle to a POST /orders operation 369 */ 370 struct TALER_MERCHANT_PostOrdersHandle *po; 371 372 /** 373 * Handle for a GET /private/orders/$ID operation. 374 */ 375 struct TALER_MERCHANT_OrderMerchantGetHandle *ogh; 376 377 /** 378 * The product being sold. 379 */ 380 struct Product *product; 381 382 /** 383 * Base URL for merchant interactions for this pa. 384 */ 385 const char *base_url; 386 387 /** 388 * Order ID for pending order 389 */ 390 char *order_id; 391 392 /** 393 * URI needed to pay the pending order 394 */ 395 char *taler_pay_uri; 396 397 /** 398 * NFC device 399 */ 400 nfc_device *pnd; 401 402 /** 403 * Target to send the data via NFC 404 */ 405 nfc_target nt; 406 407 /** 408 * Current task running 409 */ 410 struct GNUNET_SCHEDULER_Task *task; 411 412 /** 413 * Tasks delayed 414 */ 415 struct GNUNET_SCHEDULER_Task *delay_task; 416 417 /** 418 * Payment checking delayed task 419 */ 420 struct GNUNET_SCHEDULER_Task *delay_pay_task; 421 422 /** 423 * What is the price the user is paying? 424 */ 425 struct TALER_Amount amount; 426 427 /** 428 * Handle for our attempt to delete an ongoing order. 429 */ 430 struct TALER_MERCHANT_OrderDeleteHandle *odh; 431 432 /** 433 * Member to see if the wallet already received a uri 434 * If true, tunneling can be offered to the wallet. 435 */ 436 bool wallet_has_uri; 437 438 /** 439 * Set to true once the product has been paid 440 * (and we are in the process of yielding the product). 441 */ 442 bool paid; 443 }; 444 445 446 /** 447 * Data structures associated with the MDB. 448 */ 449 struct MdbHandle 450 { 451 452 /** 453 * Buffer to save the received data from UART 454 */ 455 uint8_t rxBuffer[MAX_SIZE_RX_BUFFER]; 456 457 /** 458 * Buffer to save the data to send via UART 459 */ 460 uint8_t txBuffer[MAX_SIZE_TX_BUFFER]; 461 462 /** 463 * Reference to scheduler task to read from UART 464 */ 465 struct GNUNET_SCHEDULER_Task *rtask; 466 467 /** 468 * Reference to scheduler task to write to UART 469 */ 470 struct GNUNET_SCHEDULER_Task *wtask; 471 472 /** 473 * Reference to the mdb cmd which will be sent next 474 */ 475 const struct MdbCommand *cmd; 476 477 /** 478 * Reference to the mdb cmd which was sent last 479 */ 480 const struct MdbCommand *last_cmd; 481 482 /** 483 * Current read offset in @e rxBuffer. 484 */ 485 size_t rx_off; 486 487 /** 488 * Current write offset in @e txBuffer. 489 */ 490 size_t tx_off; 491 492 /** 493 * Number of bytes in @e txBuffer with the serialized data of the 494 * @e last_cmd. 495 */ 496 size_t tx_len; 497 498 /** 499 * Time out to wait for an acknowledge received via the mdb bus 500 */ 501 struct GNUNET_TIME_Absolute ack_timeout; 502 503 /** 504 * Backup of the config data to restore the configuration of the UART before closing it 505 */ 506 struct termios uart_opts_backup; 507 508 /** 509 * Indicates if a vend session is running or not 510 */ 511 bool session_running; 512 513 /** 514 * File descriptor to the UART device file 515 */ 516 int uartfd; 517 518 }; 519 520 521 /** 522 * Handle for the Framebuffer device 523 */ 524 struct Display 525 { 526 /** 527 * File descriptor for the screen 528 */ 529 int devicefd; 530 531 /** 532 * File descriptor to set backlight information 533 */ 534 int backlightfd; 535 536 /** 537 * The display memory to set the pixel information 538 */ 539 uint16_t *memory; 540 541 /** 542 * Original screen information 543 */ 544 struct fb_var_screeninfo orig_vinfo; 545 546 /** 547 * Variable screen information (color depth ...) 548 */ 549 struct fb_var_screeninfo var_info; 550 551 /** 552 * Fixed screen informtaion 553 */ 554 struct fb_fix_screeninfo fix_info; 555 }; 556 557 /** 558 * Handle for the Cancel Button 559 */ 560 struct CancelButton 561 { 562 /** 563 * File descriptor to read the state of the cancel button gpio pin 564 */ 565 int cancelbuttonfd; 566 }; 567 568 569 /** 570 * DLL of pending refund operations. 571 */ 572 struct Refund 573 { 574 /** 575 * DLL next pointer. 576 */ 577 struct Refund *next; 578 579 /** 580 * DLL prev pointer. 581 */ 582 struct Refund *prev; 583 584 /** 585 * Curl context for communication with taler backend 586 */ 587 struct GNUNET_CURL_Context *ctx; 588 589 /** 590 * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). 591 */ 592 struct GNUNET_CURL_RescheduleContext *rc; 593 594 /** 595 * Handle to the ongoing operation. 596 */ 597 struct TALER_MERCHANT_OrderRefundHandle *orh; 598 599 }; 600 601 602 /** 603 * DLL head of refunds. 604 */ 605 static struct Refund *refund_head; 606 607 /** 608 * DLL tail of refunds. 609 */ 610 static struct Refund *refund_tail; 611 612 /** 613 * NFC context used by the NFC reader 614 */ 615 static nfc_context *context; 616 617 /** 618 * Global return value 619 */ 620 static int global_ret; 621 622 /** 623 * Flag set to remember that we are in shutdown. 624 */ 625 static int in_shutdown; 626 627 /** 628 * Flag set to remember that MDB needs shutdown 629 * (because we were actually able to talk to MDB). 630 */ 631 static bool mdb_active; 632 633 /** 634 * Reference to the keyboard task 635 */ 636 static struct GNUNET_SCHEDULER_Task *keyboard_task; 637 638 /** 639 * Reference to the cancel button task 640 */ 641 static struct GNUNET_SCHEDULER_Task *cancelbutton_task; 642 643 /** 644 * Task to stop showing transient errors. 645 */ 646 static struct GNUNET_SCHEDULER_Task *err_stop_task; 647 648 /** 649 * Handle to the process showing messages/advertisements 650 * while we are inactive. 651 */ 652 static struct GNUNET_OS_Process *adv_child; 653 654 /** 655 * Handle to the process showing error messages 656 * while we have one. 657 */ 658 static struct GNUNET_OS_Process *err_child; 659 660 /** 661 * Command to run when advertising is enabled. 662 * Can be NULL. 663 */ 664 static char *adv_process_command; 665 666 /** 667 * Command to run when advertising is enabled. 668 * Can be NULL. 669 */ 670 static char *err_process_command; 671 672 /** 673 * Taler Backend url read from configuration file 674 */ 675 static char *backend_base_url; 676 677 /** 678 * Fulfillment message to display after successful payment, read from configuration file 679 */ 680 static char *fulfillment_msg; 681 682 /** 683 * Should we hide a transient error when MDB is ready? 684 */ 685 static bool clear_error_on_start; 686 687 /** 688 * Handle for the payment 689 */ 690 static struct PaymentActivity *payment_activity; 691 692 /** 693 * Products read from configuration file 694 */ 695 static struct Product *products; 696 697 /** 698 * Amount of products 699 */ 700 static unsigned int products_length; 701 702 /** 703 * Data associated with the MDB session. 704 */ 705 static struct MdbHandle mdb; 706 707 /** 708 * MDB response to the request for configuration. 709 */ 710 static struct MdbCommand cmd_reader_config_data; 711 712 /** 713 * Ask MDB to begin session (with "infinite" money) 714 */ 715 static struct MdbCommand cmd_begin_session; 716 717 /** 718 * Refuse vending request (payment failed) 719 */ 720 static struct MdbCommand cmd_deny_vend; 721 722 /** 723 * Approve vending request (payment succeeded) 724 */ 725 static struct MdbCommand cmd_approve_vend; 726 727 /** 728 * Confirm cancellation by machine. 729 */ 730 static struct MdbCommand cmd_reader_cancelled; 731 732 /** 733 * Approve Revalue 734 */ 735 static struct MdbCommand cmd_revalue_approved; 736 737 /** 738 * Send Revalue Limit Amount 739 */ 740 static struct MdbCommand cmd_revalue_amount; 741 742 /** 743 * Send NACK 744 */ 745 static struct MdbCommand cmd_reader_NACK; 746 747 /** 748 * Display Request for Sold Out 749 */ 750 static struct MdbCommand cmd_reader_display_sold_out; 751 752 /** 753 * Display Request for Error Message 754 */ 755 static struct MdbCommand cmd_reader_display_internal_error; 756 757 /** 758 * Display Request for Error Message 759 */ 760 static struct MdbCommand cmd_reader_display_backend_not_reachable; 761 762 /** 763 * Terminate session. 764 */ 765 static struct MdbCommand endSession; 766 767 /** 768 * Name of the framebuffer device (i.e. /dev/fb1). 769 */ 770 static char *framebuffer_device_filename; 771 772 /** 773 * Name of the backlight file of @e framebuffer_device_filename (i.e. /sys/class/backlight/soc:backlight/brightness). 774 */ 775 static char *framebuffer_backlight_filename; 776 777 /** 778 * Global option '-i' to invert backlight on/off values 779 */ 780 static int backlight_invert; 781 782 /** 783 * Standard backlight on value 784 */ 785 static char backlight_on = '1'; 786 787 /** 788 * Standard backlight off value 789 */ 790 static char backlight_off = '0'; 791 792 /** 793 * State for the implementation of the 'cancel' button. 794 */ 795 static struct CancelButton cancel_button; 796 797 /** 798 * Name of the UART device with the MDB (i.e. /dev/ttyAMA0). 799 */ 800 static char *uart_device_filename; 801 802 /** 803 * Global option '-d' to disable MDB set. 804 */ 805 static int disable_mdb; 806 807 /** 808 * Global option '-t' to disable stdin / terminal. 809 */ 810 static int disable_tty; 811 812 /** 813 * Global option '-s' to enable sold-out detection. 814 */ 815 static int sold_out_enabled; 816 817 /** 818 * Taler wallet application identifier 819 */ 820 static const uint8_t taler_aid[] = { 0xF0, 0x00, 0x54, 0x41, 0x4c, 0x45, 0x52 }; 821 822 /** 823 * NFC select file command to select wallet aid 824 */ 825 static const uint8_t select_file[] = { 0x00, 0xA4, 0x04, 0x00, 0x07 }; 826 827 /** 828 * NFC put command to send data to the wallet 829 */ 830 static const uint8_t put_data[] = { 0x00, 0xDA, 0x01, 0x00, 0x7c, 0x01 }; 831 832 #if FUTURE_FEATURES 833 /* tunneling */ 834 static const uint8_t get_data[] = { 0x00, 0xCA, 0x01, 0x00, 0x00, 0x00 }; 835 #endif 836 837 /** 838 * Handle for the framebuffer device 839 */ 840 static struct Display qrDisplay; 841 842 843 /** 844 * Start process using the @a command command-line. 845 * 846 * @param command command to run 847 * @param ... extra arguments to pass 848 * @return process handle, NULL on failure 849 */ 850 static struct GNUNET_OS_Process * 851 start_command (const char *command, 852 ...) 853 { 854 char **argv = NULL; 855 unsigned int argc = 0; 856 char *cpy = GNUNET_strdup (command); 857 struct GNUNET_OS_Process *ret; 858 va_list ap; 859 const char *arg; 860 861 for (const char *tok = strtok (cpy, " "); 862 NULL != tok; 863 tok = strtok (NULL, " ")) 864 { 865 GNUNET_array_append (argv, 866 argc, 867 GNUNET_strdup (tok)); 868 } 869 va_start (ap, 870 command); 871 while (NULL != (arg = va_arg (ap, 872 const char *))) 873 { 874 GNUNET_array_append (argv, 875 argc, 876 GNUNET_strdup (arg)); 877 } 878 va_end (ap); 879 GNUNET_array_append (argv, 880 argc, 881 NULL); 882 ret = GNUNET_OS_start_process_vap (GNUNET_OS_INHERIT_STD_ERR, 883 NULL, 884 NULL, 885 NULL, 886 argv[0], 887 argv); 888 if (NULL == ret) 889 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 890 "Failed to launch %s\n", 891 argv[0]); 892 for (unsigned int i = 0; i<argc; i++) 893 GNUNET_free (argv[i]); 894 GNUNET_array_grow (argv, 895 argc, 896 0); 897 GNUNET_free (cpy); 898 return ret; 899 } 900 901 902 /** 903 * Stop the advertising process. 904 */ 905 static void 906 stop_advertising (void) 907 { 908 if (NULL == adv_child) 909 return; 910 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 911 "Stopping advertising\n"); 912 GNUNET_break (0 == 913 GNUNET_OS_process_kill (adv_child, 914 SIGTERM)); 915 GNUNET_break (GNUNET_OK == 916 GNUNET_OS_process_wait (adv_child)); 917 GNUNET_OS_process_destroy (adv_child); 918 adv_child = NULL; 919 } 920 921 922 /** 923 * Start the advertising process. 924 */ 925 static void 926 start_advertising (void) 927 { 928 if (NULL != err_child) 929 return; 930 stop_advertising (); /* just to be sure */ 931 if (NULL == adv_process_command) 932 return; 933 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 934 "Starting advertising\n"); 935 adv_child = start_command (adv_process_command, 936 NULL); 937 } 938 939 940 /** 941 * Stop the process showing an error. 942 */ 943 static void 944 hide_error (void) 945 { 946 if (NULL == err_child) 947 return; 948 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 949 "Hiding error\n"); 950 if (NULL != err_stop_task) 951 { 952 GNUNET_SCHEDULER_cancel (err_stop_task); 953 err_stop_task = NULL; 954 } 955 GNUNET_break (0 == 956 GNUNET_OS_process_kill (err_child, 957 SIGTERM)); 958 GNUNET_break (GNUNET_OK == 959 GNUNET_OS_process_wait (err_child)); 960 GNUNET_OS_process_destroy (err_child); 961 err_child = NULL; 962 } 963 964 965 /** 966 * Show an error using the respective error process 967 * command. 968 * 969 * @param err_type type of the error to pass to the command 970 */ 971 static void 972 show_error (const char *err_type) 973 { 974 stop_advertising (); 975 hide_error (); /* just to be sure */ 976 if (NULL == err_process_command) 977 { 978 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 979 "Cannot show error `%s'\n", 980 err_type); 981 return; 982 } 983 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 984 "Showing error `%s' using `%s'\n", 985 err_type, 986 err_process_command); 987 err_child = start_command (err_process_command, 988 err_type, 989 NULL); 990 GNUNET_break (NULL != err_child); 991 } 992 993 994 /** 995 * Task to stop the process showing an error. 996 * 997 * @param cls NULL 998 */ 999 static void 1000 do_hide_error (void *cls) 1001 { 1002 err_stop_task = NULL; 1003 hide_error (); 1004 start_advertising (); 1005 } 1006 1007 1008 /** 1009 * Briefly show a temporary error. 1010 * 1011 * @param err_type error to show 1012 */ 1013 static void 1014 temporary_error (const char *err_type) 1015 { 1016 show_error (err_type); 1017 if (NULL != err_stop_task) 1018 { 1019 GNUNET_SCHEDULER_cancel (err_stop_task); 1020 err_stop_task = NULL; 1021 } 1022 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1023 "Will hide error in %s\n", 1024 GNUNET_TIME_relative2s (ERR_DELAY, 1025 true)); 1026 err_stop_task = GNUNET_SCHEDULER_add_delayed (ERR_DELAY, 1027 &do_hide_error, 1028 NULL); 1029 } 1030 1031 1032 #if HAVE_QRENCODE_H 1033 #include <qrencode.h> 1034 1035 /** 1036 * @brief Create the QR code to pay and display it on screen 1037 * 1038 * @param uri what text to show in the QR code 1039 */ 1040 static void 1041 show_qrcode (const char *uri) 1042 { 1043 QRinput *qri; 1044 QRcode *qrc; 1045 unsigned int size; 1046 char *upper; 1047 char *base; 1048 char *ubase; 1049 size_t xOff; 1050 size_t yOff; 1051 const char *dddash; 1052 unsigned int nwidth; 1053 1054 stop_advertising (); 1055 hide_error (); 1056 if (0 > qrDisplay.devicefd) 1057 return; /* no display, no dice */ 1058 /* find the fourth '/' in the payto://pay/hostname/-uri */ 1059 dddash = strchr (uri, '/'); 1060 if (NULL != dddash) 1061 dddash = strchr (dddash + 1, '/'); 1062 if (NULL != dddash) 1063 dddash = strchr (dddash + 1, '/'); 1064 if (NULL != dddash) 1065 dddash = strchr (dddash + 1, '/'); 1066 if (NULL == dddash) 1067 { 1068 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1069 "taler://pay/-URI malformed: `%s'\n", 1070 uri); 1071 return; 1072 } 1073 1074 qri = QRinput_new2 (0, QR_ECLEVEL_L); 1075 if (NULL == qri) 1076 { 1077 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 1078 "QRinput_new2"); 1079 return; 1080 } 1081 /* convert all characters until the fourth '/' to upper 1082 case. The rest _should_ be upper case in a NICE setup, 1083 but we can't warrant it and must not touch those. */ 1084 base = GNUNET_strndup (uri, 1085 dddash - uri); 1086 1087 ubase = GNUNET_STRINGS_utf8_toupper (base); 1088 GNUNET_free (base); 1089 GNUNET_asprintf (&upper, 1090 "%s%s", 1091 ubase, 1092 dddash); 1093 GNUNET_free (ubase); 1094 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1095 "Showing QR code for `%s'\n", 1096 upper); 1097 /* first try encoding as uppercase-only alpha-numerical 1098 QR code (much smaller encoding); if that fails, also 1099 try using binary encoding (in case nick contains 1100 special characters). */ 1101 if ( (0 != 1102 QRinput_append (qri, 1103 QR_MODE_AN, 1104 strlen (upper), 1105 (unsigned char *) upper)) && 1106 (0 != 1107 QRinput_append (qri, 1108 QR_MODE_8, 1109 strlen (upper), 1110 (unsigned char *) upper))) 1111 { 1112 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 1113 "QRinput_append"); 1114 GNUNET_free (upper); 1115 return; 1116 } 1117 GNUNET_free (upper); 1118 qrc = QRcode_encodeInput (qri); 1119 if (NULL == qrc) 1120 { 1121 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 1122 "QRcode_encodeInput"); 1123 QRinput_free (qri); 1124 return; 1125 } 1126 1127 /* set QR-code border */ 1128 memset (qrDisplay.memory, 1129 0xFF, 1130 qrDisplay.var_info.xres * qrDisplay.var_info.yres 1131 * sizeof (uint16_t)); 1132 size = GNUNET_MIN (qrDisplay.var_info.xres, 1133 qrDisplay.var_info.yres); 1134 1135 nwidth = qrc->width + 8; /* +8 for 4 pixel border */ 1136 xOff = 4 * size / nwidth; 1137 yOff = 4 * size / nwidth; 1138 1139 /* calculate offset to show the code centered */ 1140 if (qrDisplay.var_info.xres < qrDisplay.var_info.yres) 1141 yOff += (qrDisplay.var_info.yres - qrDisplay.var_info.xres) / 2; 1142 else 1143 xOff += (qrDisplay.var_info.xres - qrDisplay.var_info.yres) / 2; 1144 for (unsigned int x = 0; x < qrDisplay.var_info.xres - 2 * xOff; x++) 1145 for (unsigned int y = 0; y < qrDisplay.var_info.yres - 2 * yOff; y++) 1146 { 1147 unsigned int xoff = x * nwidth / size; 1148 unsigned int yoff = y * nwidth / size; 1149 unsigned int off = xoff + yoff * qrc->width; 1150 if ( (xoff >= (unsigned) qrc->width) || 1151 (yoff >= (unsigned) qrc->width) ) 1152 continue; 1153 /* set the pixels in the display memory */ 1154 qrDisplay.memory[(y + yOff) * qrDisplay.var_info.xres + (x + xOff)] = 1155 (0 == (qrc->data[off] & 1)) ? 0xFFFF : 0x0000; 1156 } 1157 1158 QRcode_free (qrc); 1159 QRinput_free (qri); 1160 1161 /* Turn on backlight if supported */ 1162 if (0 < qrDisplay.backlightfd) 1163 (void) ! write (qrDisplay.backlightfd, 1164 &backlight_on, 1165 1); 1166 } 1167 1168 1169 #endif 1170 1171 1172 static void 1173 run_mdb_event_loop (void); 1174 1175 1176 /** 1177 * Runs asynchronous cleanup part for freeing a 1178 * payment activity. 1179 * 1180 * @param[in] cls a `struct PaymentActivity` to clean up 1181 */ 1182 static void 1183 async_pa_cleanup_job (void *cls) 1184 { 1185 struct PaymentActivity *pa = cls; 1186 1187 if (NULL != pa->ctx) 1188 GNUNET_CURL_fini (pa->ctx); 1189 if (NULL != pa->rc) 1190 GNUNET_CURL_gnunet_rc_destroy (pa->rc); 1191 GNUNET_free (pa); 1192 start_advertising (); 1193 } 1194 1195 1196 /** 1197 * @brief Cleanup all the data when a order has succeeded or got cancelled 1198 * 1199 * @param pa the payment activity to clean up 1200 */ 1201 static void 1202 cleanup_payment (struct PaymentActivity *pa); 1203 1204 1205 /** 1206 * Function called with the result of the DELETE /orders/$ID operation. 1207 * 1208 * @param cls closure with the `struct PaymentActivity *` 1209 * @param hr HTTP response details 1210 */ 1211 static void 1212 order_delete_cb ( 1213 void *cls, 1214 const struct TALER_MERCHANT_HttpResponse *hr) 1215 { 1216 struct PaymentActivity *pa = cls; 1217 1218 pa->odh = NULL; 1219 if ( (MHD_HTTP_OK != hr->http_status) && 1220 (MHD_HTTP_NO_CONTENT != hr->http_status) ) 1221 { 1222 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1223 "Failed to delete incomplete order from backend: %d/%u\n", 1224 (int) hr->http_status, 1225 (unsigned int) hr->ec); 1226 } 1227 cleanup_payment (pa); 1228 } 1229 1230 1231 /** 1232 * Clear the screen showing the QR code for the order. 1233 * 1234 * @param[in,out] pa payment activity to clear screen for 1235 */ 1236 static void 1237 clear_screen (struct PaymentActivity *pa) 1238 { 1239 if (NULL == pa->taler_pay_uri) 1240 return; 1241 #if HAVE_QRENCODE_H 1242 if (NULL != qrDisplay.memory) 1243 memset (qrDisplay.memory, 1244 0xFF, 1245 qrDisplay.var_info.xres * qrDisplay.var_info.yres 1246 * sizeof (uint16_t)); 1247 if (0 < qrDisplay.backlightfd) 1248 (void) ! write (qrDisplay.backlightfd, 1249 &backlight_off, 1250 1); 1251 #endif 1252 GNUNET_free (pa->taler_pay_uri); 1253 } 1254 1255 1256 static void 1257 cleanup_payment (struct PaymentActivity *pa) 1258 { 1259 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1260 "Cleaning up payment\n"); 1261 if ( (! pa->paid) && 1262 (NULL != pa->order_id) ) 1263 { 1264 char *oid; 1265 1266 oid = pa->order_id; 1267 pa->order_id = NULL; 1268 pa->odh = TALER_MERCHANT_order_delete ( 1269 pa->ctx, 1270 pa->base_url, 1271 oid, 1272 true, /* delete even claimed orders */ 1273 &order_delete_cb, 1274 pa); 1275 GNUNET_free (oid); 1276 return; 1277 } 1278 if (NULL != pa->odh) 1279 { 1280 TALER_MERCHANT_order_delete_cancel (pa->odh); 1281 pa->odh = NULL; 1282 } 1283 if (NULL != pa->pnd) 1284 { 1285 nfc_abort_command (pa->pnd); 1286 nfc_close (pa->pnd); 1287 pa->pnd = NULL; 1288 } 1289 if (NULL != cancelbutton_task) 1290 { 1291 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1292 "Stopping watching of the cancel button\n"); 1293 GNUNET_SCHEDULER_cancel (cancelbutton_task); 1294 cancelbutton_task = NULL; 1295 } 1296 if (NULL != pa->po) 1297 { 1298 TALER_MERCHANT_orders_post_cancel (pa->po); 1299 pa->po = NULL; 1300 } 1301 if (NULL != pa->ogh) 1302 { 1303 TALER_MERCHANT_merchant_order_get_cancel (pa->ogh); 1304 pa->ogh = NULL; 1305 } 1306 GNUNET_CURL_gnunet_scheduler_reschedule (&pa->rc); 1307 if (NULL != pa->task) 1308 { 1309 GNUNET_SCHEDULER_cancel (pa->task); 1310 pa->task = NULL; 1311 } 1312 if (NULL != pa->delay_task) 1313 { 1314 GNUNET_SCHEDULER_cancel (pa->delay_task); 1315 pa->delay_task = NULL; 1316 } 1317 if (NULL != pa->delay_pay_task) 1318 { 1319 GNUNET_SCHEDULER_cancel (pa->delay_pay_task); 1320 pa->delay_pay_task = NULL; 1321 } 1322 clear_screen (pa); 1323 GNUNET_free (pa->order_id); 1324 GNUNET_SCHEDULER_add_now (&async_pa_cleanup_job, 1325 pa); 1326 } 1327 1328 1329 /** 1330 * @brief Shutdown the mdb communication tasks 1331 */ 1332 static void 1333 mdb_shutdown (void) 1334 { 1335 if (NULL != mdb.rtask) 1336 { 1337 GNUNET_SCHEDULER_cancel (mdb.rtask); 1338 mdb.rtask = NULL; 1339 } 1340 if (NULL != mdb.wtask) 1341 { 1342 GNUNET_SCHEDULER_cancel (mdb.wtask); 1343 mdb.wtask = NULL; 1344 } 1345 hide_error (); 1346 stop_advertising (); 1347 if (disable_mdb) 1348 return; 1349 /* restore UART */ 1350 if (0 != tcsetattr (mdb.uartfd, 1351 TCSAFLUSH, 1352 &mdb.uart_opts_backup)) 1353 { 1354 printf ("Failed to restore uart discipline\n"); 1355 global_ret = EXIT_FAILURE; 1356 } 1357 if (-1 != mdb.uartfd) 1358 { 1359 GNUNET_break (0 == close (mdb.uartfd)); 1360 mdb.uartfd = -1; 1361 } 1362 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1363 "Shutdown complete (including MDB)\n"); 1364 } 1365 1366 1367 /** 1368 * @brief Shutdown the application. 1369 * 1370 * @param cls closure 1371 */ 1372 static void 1373 shutdown_task (void *cls) 1374 { 1375 struct Refund *r; 1376 1377 (void) cls; 1378 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1379 "Shutdown initiated\n"); 1380 stop_advertising (); 1381 while (NULL != (r = refund_head)) 1382 { 1383 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1384 "Pending refund operation aborted due to shutdown\n"); 1385 GNUNET_CONTAINER_DLL_remove (refund_head, 1386 refund_tail, 1387 r); 1388 TALER_MERCHANT_post_order_refund_cancel (r->orh); 1389 GNUNET_free (r); 1390 } 1391 if (NULL != context) 1392 { 1393 nfc_exit (context); 1394 context = NULL; 1395 } 1396 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1397 "NFC down\n"); 1398 if (NULL != payment_activity) 1399 { 1400 cleanup_payment (payment_activity); 1401 payment_activity = NULL; 1402 } 1403 if (NULL != cancelbutton_task) 1404 { 1405 GNUNET_SCHEDULER_cancel (cancelbutton_task); 1406 cancelbutton_task = NULL; 1407 } 1408 if (NULL != keyboard_task) 1409 { 1410 GNUNET_SCHEDULER_cancel (keyboard_task); 1411 keyboard_task = NULL; 1412 } 1413 /* last ditch saying nicely goodbye to MDB */ 1414 in_shutdown = GNUNET_YES; 1415 mdb.cmd = &endSession; 1416 if (-1 != mdb.uartfd) 1417 run_mdb_event_loop (); 1418 if ( (MAP_FAILED != qrDisplay.memory) && 1419 (NULL != qrDisplay.memory) ) 1420 { 1421 /* free the display data */ 1422 munmap (qrDisplay.memory, 1423 qrDisplay.fix_info.smem_len); 1424 qrDisplay.memory = NULL; 1425 /* reset original state */ 1426 if (0 > ioctl (qrDisplay.devicefd, 1427 FBIOPUT_VSCREENINFO, 1428 &qrDisplay.orig_vinfo)) 1429 { 1430 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1431 "failed to reset originial display state\n"); 1432 } 1433 /* close the device */ 1434 GNUNET_break (0 == close (qrDisplay.devicefd)); 1435 qrDisplay.devicefd = -1; 1436 if (0 < qrDisplay.backlightfd) 1437 GNUNET_break (0 == close (qrDisplay.backlightfd)); 1438 qrDisplay.backlightfd = -1; 1439 } 1440 if (-1 != cancel_button.cancelbuttonfd) 1441 { 1442 GNUNET_break (0 == 1443 close (cancel_button.cancelbuttonfd)); 1444 cancel_button.cancelbuttonfd = -1; 1445 } 1446 { 1447 int efd; 1448 1449 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1450 "Unexporting GPIO pin 23\n"); 1451 efd = open ("/sys/class/gpio/unexport", 1452 O_WRONLY); 1453 if (-1 == efd) 1454 { 1455 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1456 "Unable to open /gpio/unexport for cancel button\n"); 1457 } 1458 else 1459 { 1460 if (2 != write (efd, 1461 "23", 1462 2)) 1463 { 1464 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 1465 "write", 1466 "/sys/class/gpio/unexport"); 1467 1468 } 1469 GNUNET_break (0 == close (efd)); 1470 } 1471 } 1472 /* free the allocated productes read from config file */ 1473 if (NULL != products) 1474 { 1475 for (unsigned int i = 0; i < products_length; i++) 1476 { 1477 GNUNET_free (products[i].description); 1478 GNUNET_free (products[i].auth_header); 1479 GNUNET_free (products[i].instance); 1480 GNUNET_free (products[i].preview); 1481 } 1482 GNUNET_array_grow (products, 1483 products_length, 1484 0); 1485 } 1486 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1487 "Shutdown complete (except for MDB)\n"); 1488 } 1489 1490 1491 static void 1492 check_payment_again (void *cls); 1493 1494 1495 static void 1496 connect_target (void *cls); 1497 1498 1499 static void 1500 wallet_select_aid (void *cls); 1501 1502 1503 /** 1504 * @brief Transmit the pay uri from taler to the wallet application via NFC 1505 * 1506 * @param cls closure 1507 */ 1508 static void 1509 wallet_transmit_uri (void *cls) 1510 { 1511 struct PaymentActivity *pa = cls; 1512 /* response array for APDU response status word */ 1513 uint8_t response[] = { 0x00, 0x00 }; 1514 size_t slen = strlen (pa->taler_pay_uri); 1515 uint8_t message[sizeof (put_data) + slen]; 1516 1517 pa->delay_task = NULL; 1518 /* append the pay uri to the put data command */ 1519 memcpy (message, put_data, sizeof (put_data)); 1520 memcpy (&message[sizeof (put_data)], pa->taler_pay_uri, slen); 1521 /* send the put data command via nfc */ 1522 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1523 "Sending 'PUT DATA' command for `%s' to wallet\n", 1524 pa->taler_pay_uri); 1525 if (0 > nfc_initiator_transceive_bytes (pa->pnd, 1526 message, 1527 sizeof (message), 1528 response, 1529 sizeof(response), 1530 NFC_TIMEOUT)) 1531 { 1532 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1533 "Failed to send command via NFC\n"); 1534 pa->task = GNUNET_SCHEDULER_add_now (&connect_target, 1535 pa); 1536 return; 1537 } 1538 /* check if the transmission succeeded */ 1539 if (0 != memcmp (response, 1540 APDU_SUCCESS, 1541 sizeof (response))) 1542 { 1543 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1544 "'PUT DATA' command transmission failed, return code: %x%x\n", 1545 response[0], 1546 response[1]); 1547 pa->task = GNUNET_SCHEDULER_add_now (&connect_target, 1548 pa); 1549 return; 1550 } 1551 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1552 "'PUT DATA' command sent successfully via NFC\n"); 1553 pa->wallet_has_uri = true; 1554 /* FIXME: or just offer Internet service here? */ 1555 1556 /* transmit the uri again later, there can be many external failures, 1557 for example the taler wallet app was not opened and thus did not receive 1558 the data */ 1559 pa->delay_task = GNUNET_SCHEDULER_add_delayed (MAX_HTTP_RETRY_FREQ, 1560 &wallet_transmit_uri, 1561 pa); 1562 } 1563 1564 1565 /** 1566 * @brief Select the taler wallet app via NFC on the target selected with 1567 * @e connect_target() 1568 * (check if it is installed on the smartphone) 1569 * 1570 * @param cls closure 1571 */ 1572 static void 1573 wallet_select_aid (void *cls) 1574 { 1575 struct PaymentActivity *pa = cls; 1576 /* response array for APDU response status word */ 1577 uint8_t response[] = { 0x00, 0x00 }; 1578 uint8_t message[sizeof(select_file) + sizeof(taler_aid)]; 1579 1580 pa->task = NULL; 1581 /* append the taler wallet aid to the select file command */ 1582 memcpy (message, 1583 select_file, 1584 sizeof (select_file)); 1585 memcpy (&message[sizeof (select_file)], 1586 taler_aid, 1587 sizeof (taler_aid)); 1588 1589 /* send the select file command via nfc */ 1590 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1591 "Trying to find Taler wallet on NFC\n"); 1592 if (0 > nfc_initiator_transceive_bytes (pa->pnd, 1593 message, 1594 sizeof (message), 1595 response, 1596 sizeof (response), 1597 NFC_TIMEOUT)) 1598 { 1599 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1600 "Failed to transceive with NFC app, trying to find another NFC client\n"); 1601 pa->task = GNUNET_SCHEDULER_add_now (&connect_target, 1602 pa); 1603 return; 1604 } 1605 /* check if the transmission succeeded */ 1606 if (0 == memcmp (response, 1607 APDU_SUCCESS, 1608 sizeof (response))) 1609 { 1610 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1611 "Taler wallet found over NFC\n"); 1612 pa->delay_task = GNUNET_SCHEDULER_add_now (&wallet_transmit_uri, 1613 pa); 1614 return; 1615 } 1616 /* if the transmission was not successful chack if the app is available at all */ 1617 if (0 == memcmp (response, 1618 APDU_NOT_FOUND, 1619 sizeof (response))) 1620 { 1621 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1622 "Taler wallet NOT found on this device\n"); 1623 pa->task = GNUNET_SCHEDULER_add_now (&connect_target, 1624 pa); 1625 return; 1626 } 1627 /* If the upper cases did not match, there was an unknown APDU status returned */ 1628 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1629 "AID selection failure, return code: %x%x, trying to find another NFC client\n", 1630 response[0], 1631 response[1]); 1632 /* start the selection again */ 1633 pa->task = GNUNET_SCHEDULER_add_delayed (NFC_NOT_FOUND_RETRY_FREQ, 1634 &connect_target, 1635 pa); 1636 } 1637 1638 1639 /** 1640 * @brief Connect the NFC reader with a compatible NFC target 1641 * 1642 * @param cls closure 1643 */ 1644 static void 1645 connect_target (void *cls) 1646 { 1647 struct PaymentActivity *pa = cls; 1648 /* nfc modulation used */ 1649 const nfc_modulation nmMifare = { 1650 .nmt = NMT_ISO14443A, 1651 .nbr = NBR_212, 1652 }; 1653 1654 pa->task = NULL; 1655 /* set the uid len to zero (maybe it is still set from earlier selections) */ 1656 pa->nt.nti.nai.szUidLen = 0; 1657 /* poll for a fitting nfc target (we use the shortest time possible to not block the scheduler) */ 1658 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1659 "Trying to find NFC client\n"); 1660 if (0 > nfc_initiator_poll_target (pa->pnd, 1661 &nmMifare, 1662 1, 1663 0x01, 1664 0x01, 1665 &pa->nt)) 1666 { 1667 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1668 "Failed to connect to NFC target\n"); 1669 } 1670 /* if the uid length are out of bound abort */ 1671 else if ( (pa->nt.nti.nai.szUidLen > UID_LEN_UPPER) || 1672 (pa->nt.nti.nai.szUidLen < UID_LEN_LOWER) ) 1673 { 1674 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1675 "Failed to connect, wrong NFC modulation\n"); 1676 } 1677 else 1678 { 1679 /* the target was successfully selected, 1680 now we have to check if the taler wallet is installed on it */ 1681 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1682 "Found NFC client\n"); 1683 pa->task = GNUNET_SCHEDULER_add_now (&wallet_select_aid, 1684 pa); 1685 return; 1686 } 1687 /* if no target was found try again */ 1688 pa->task = GNUNET_SCHEDULER_add_delayed (NFC_NOT_FOUND_RETRY_FREQ, 1689 &connect_target, 1690 pa); 1691 } 1692 1693 1694 /** 1695 * @brief Open the NFC reader. 1696 * 1697 * @param cls closure 1698 */ 1699 static void 1700 open_nfc_reader (void *cls) 1701 { 1702 struct PaymentActivity *pa = cls; 1703 1704 pa->task = NULL; 1705 /* open the nfc reader via libnfc's open */ 1706 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1707 "Trying to open NFC device\n"); 1708 pa->pnd = nfc_open (context, NULL); 1709 if (NULL == pa->pnd) 1710 { 1711 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1712 "Payment inititation: Unable to open NFC device\n"); 1713 pa->task = GNUNET_SCHEDULER_add_delayed (NFC_FAILURE_RETRY_FREQ, 1714 &open_nfc_reader, 1715 pa); 1716 return; 1717 } 1718 /* initialize the reader as initiator */ 1719 if (0 > nfc_initiator_init (pa->pnd)) 1720 { 1721 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1722 "Failed to initialize NFC device: %s\n", 1723 nfc_strerror (pa->pnd)); 1724 cleanup_payment (pa); 1725 GNUNET_assert (payment_activity == pa); 1726 payment_activity = NULL; 1727 return; 1728 } 1729 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1730 "NFC in operation %s / %s\n", 1731 nfc_device_get_name (pa->pnd), 1732 nfc_device_get_connstring (pa->pnd)); 1733 /* the nfc reader was opened successfully, now try to find a mobile device as a target */ 1734 pa->task = GNUNET_SCHEDULER_add_now (&connect_target, 1735 pa); 1736 } 1737 1738 1739 static void 1740 start_read_keyboard (void); 1741 1742 1743 /** 1744 * @brief Callback to process a GET /check-payment request 1745 * 1746 * @param cls closure 1747 * @param osr order status response details (on success) 1748 */ 1749 static void 1750 check_payment_cb (void *cls, 1751 const struct TALER_MERCHANT_OrderStatusResponse *osr) 1752 { 1753 struct PaymentActivity *pa = cls; 1754 const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; 1755 1756 GNUNET_assert (payment_activity == pa); 1757 pa->ogh = NULL; 1758 if ( (MHD_HTTP_OK != hr->http_status) && 1759 (MHD_HTTP_GATEWAY_TIMEOUT != hr->http_status) && 1760 (MHD_HTTP_REQUEST_TIMEOUT != hr->http_status) ) 1761 { 1762 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1763 "Backend request to GET /orders/$ID failed: %u/%d\n", 1764 hr->http_status, 1765 (int) hr->ec); 1766 mdb.cmd = &cmd_reader_display_backend_not_reachable; 1767 temporary_error ("backend-unexpected-failure"); 1768 run_mdb_event_loop (); 1769 cleanup_payment (pa); 1770 GNUNET_assert (payment_activity == pa); 1771 payment_activity = NULL; 1772 return; 1773 } 1774 1775 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1776 "Backend request to GET /orders/$ID returned: %u\n", 1777 hr->http_status); 1778 if ( (MHD_HTTP_OK == hr->http_status) && 1779 (TALER_MERCHANT_OSC_PAID == osr->details.ok.status) ) 1780 { 1781 clear_screen (pa); 1782 clear_error_on_start = true; 1783 temporary_error ("dispensing"); 1784 mdb.cmd = &cmd_approve_vend; 1785 payment_activity->paid = true; 1786 run_mdb_event_loop (); 1787 if ((disable_mdb) && (! disable_tty)) 1788 { 1789 GNUNET_SCHEDULER_cancel (keyboard_task); 1790 keyboard_task = NULL; 1791 start_read_keyboard (); 1792 } 1793 return; 1794 } 1795 /* Start to check for payment. Note that we do this even before 1796 we talked successfully to the wallet via NFC because we MAY show the 1797 QR code in the future and in that case the payment may happen 1798 anytime even before the NFC communication succeeds. */ 1799 if ( (NULL == pa->ogh) && 1800 (NULL == pa->delay_pay_task) ) 1801 { 1802 pa->delay_pay_task = GNUNET_SCHEDULER_add_delayed (MAX_HTTP_RETRY_FREQ, 1803 &check_payment_again, 1804 pa); 1805 } 1806 if ( (NULL == pa->taler_pay_uri) && 1807 (MHD_HTTP_OK == hr->http_status) && 1808 (TALER_MERCHANT_OSC_UNPAID == osr->details.ok.status) ) 1809 { 1810 char *uri; 1811 1812 uri = GNUNET_strdup (osr->details.ok.details.unpaid.taler_pay_uri); 1813 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1814 "Trying to talk to wallet to give it pay URI `%s'\n", 1815 uri); 1816 GNUNET_assert (NULL == pa->pnd); 1817 pa->taler_pay_uri = uri; 1818 #if HAVE_QRENCODE_H 1819 show_qrcode (uri); 1820 #endif 1821 pa->task = GNUNET_SCHEDULER_add_now (&open_nfc_reader, 1822 pa); 1823 } 1824 } 1825 1826 1827 /** 1828 * @brief Check the payment status again 1829 * 1830 * @param cls closure 1831 */ 1832 static void 1833 check_payment_again (void *cls) 1834 { 1835 struct PaymentActivity *pa = cls; 1836 1837 pa->delay_pay_task = NULL; 1838 GNUNET_assert (NULL == pa->ogh); 1839 pa->ogh = TALER_MERCHANT_merchant_order_get (pa->ctx, 1840 pa->base_url, 1841 pa->order_id, 1842 NULL /* snack machine, no Web crap */ 1843 , 1844 BACKEND_POLL_TIMEOUT, 1845 &check_payment_cb, 1846 pa); 1847 } 1848 1849 1850 /** 1851 * @brief Callback for a POST /orders request 1852 * 1853 * @param cls closure 1854 * @param por response for this request 1855 */ 1856 static void 1857 proposal_cb (void *cls, 1858 const struct TALER_MERCHANT_PostOrdersReply *por) 1859 { 1860 struct PaymentActivity *pa = cls; 1861 1862 pa->po = NULL; 1863 if (MHD_HTTP_OK != por->hr.http_status) 1864 { 1865 /* FIXME: In the future, we may want to support MHD_HTTP_GONE 1866 explicitly and show 'product out of stock' here! */ 1867 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1868 "Failed to setup order with backend: %u/%d\n", 1869 por->hr.http_status, 1870 (int) por->hr.ec); 1871 json_dumpf (por->hr.reply, 1872 stderr, 1873 0); 1874 temporary_error ("backend-temporary-failure"); 1875 mdb.cmd = &cmd_reader_display_backend_not_reachable; 1876 run_mdb_event_loop (); 1877 cleanup_payment (pa); 1878 GNUNET_assert (payment_activity == pa); 1879 payment_activity = NULL; 1880 return; 1881 } 1882 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1883 "Backend successfully created order `%s'\n", 1884 por->details.ok.order_id); 1885 pa->order_id = GNUNET_strdup (por->details.ok.order_id); 1886 pa->ogh = TALER_MERCHANT_merchant_order_get (pa->ctx, 1887 pa->base_url, 1888 pa->order_id, 1889 NULL /* snack machine, no Web crap */ 1890 , 1891 GNUNET_TIME_UNIT_ZERO, 1892 &check_payment_cb, 1893 pa); 1894 } 1895 1896 1897 static void 1898 start_read_cancel_button (void); 1899 1900 1901 /** 1902 * @brief Launch a new order 1903 * 1904 * @param product information for product to sell 1905 * @return payment activity for the order, NULL on failure 1906 */ 1907 static struct PaymentActivity * 1908 launch_payment (struct Product *product) 1909 { 1910 static const char *uuids[1]; 1911 struct PaymentActivity *pa; 1912 json_t *orderReq; 1913 char *msg; 1914 const char *pos; 1915 1916 pos = strstr (fulfillment_msg, 1917 "${PRODUCT_DESCRIPTION}"); 1918 if (NULL != pos) 1919 { 1920 /* replace ${PRODUCT_DESCRIPTION} with the real one */ 1921 GNUNET_asprintf (&msg, 1922 "%.*s%s%s", 1923 /* first output URL until ${PRODUCT_DESCRIPTION} */ 1924 (int) (pos - fulfillment_msg), 1925 fulfillment_msg, 1926 /* replace ${PRODUCT_DESCRIPTION} with the right description */ 1927 product->description, 1928 /* append rest of original URL */ 1929 pos + strlen ("${PRODUCT_DESCRIPTION}")); 1930 } 1931 else 1932 { 1933 msg = GNUNET_strdup (fulfillment_msg); 1934 } 1935 1936 /* create the json object for the order request */ 1937 if (NULL != product->preview) 1938 { 1939 json_t *lproducts; 1940 1941 lproducts = json_array (); 1942 GNUNET_assert (NULL != lproducts); 1943 GNUNET_assert ( 1944 0 == 1945 json_array_append_new (lproducts, 1946 GNUNET_JSON_PACK ( 1947 GNUNET_JSON_pack_string ("description", 1948 product->description), 1949 GNUNET_JSON_pack_string ("image", 1950 product->preview), 1951 TALER_JSON_pack_amount ("price", 1952 &product->price), 1953 GNUNET_JSON_pack_uint64 ("quantity", 1954 1)))); 1955 orderReq = GNUNET_JSON_PACK ( 1956 GNUNET_JSON_pack_string ("summary", 1957 product->description), 1958 #if BUG 1959 GNUNET_JSON_pack_timestamp ("pay_deadline", 1960 GNUNET_TIME_relative_to_timestamp ( 1961 PAY_TIMEOUT)), 1962 #endif 1963 GNUNET_JSON_pack_array_steal ( 1964 "products", 1965 lproducts), 1966 TALER_JSON_pack_amount ("amount", 1967 &product->price), 1968 GNUNET_JSON_pack_string ("fulfillment_message", 1969 msg), 1970 GNUNET_JSON_pack_time_rel ("auto_refund", 1971 MAX_REFUND_DELAY)); 1972 } 1973 else 1974 { 1975 orderReq = GNUNET_JSON_PACK ( 1976 GNUNET_JSON_pack_string ("summary", 1977 product->description), 1978 #if BUG 1979 GNUNET_JSON_pack_timestamp ("pay_deadline", 1980 GNUNET_TIME_relative_to_timestamp ( 1981 PAY_TIMEOUT)), 1982 #endif 1983 TALER_JSON_pack_amount ("amount", 1984 &product->price), 1985 GNUNET_JSON_pack_string ("fulfillment_message", 1986 msg), 1987 GNUNET_JSON_pack_time_rel ("auto_refund", 1988 MAX_REFUND_DELAY)); 1989 } 1990 GNUNET_free (msg); 1991 if (NULL == orderReq) 1992 { 1993 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1994 "json_pack failed (out of memory?)\n"); 1995 return NULL; 1996 } 1997 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1998 "Creating order for `%s' at backend `%s'\n", 1999 product->description, 2000 (NULL == product->instance) 2001 ? backend_base_url 2002 : product->instance); 2003 pa = GNUNET_new (struct PaymentActivity); 2004 pa->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 2005 &pa->rc); 2006 pa->rc = GNUNET_CURL_gnunet_rc_create (pa->ctx); 2007 GNUNET_assert (GNUNET_OK == 2008 GNUNET_CURL_append_header (pa->ctx, 2009 product->auth_header)); 2010 pa->product = product; 2011 pa->amount = product->price; 2012 /* put the order on the merchant's backend */ 2013 pa->base_url = (NULL == product->instance) 2014 ? backend_base_url 2015 : product->instance; 2016 GNUNET_assert (NULL == pa->po); 2017 pa->po = TALER_MERCHANT_orders_post2 (pa->ctx, 2018 pa->base_url, 2019 orderReq, 2020 MAX_REFUND_DELAY, 2021 NULL, /* no payment target preference */ 2022 0, 2023 NULL, /* no inventory */ 2024 0, 2025 uuids, /* no locks */ 2026 false, /* no claim token */ 2027 &proposal_cb, 2028 pa); 2029 json_decref (orderReq); 2030 if (NULL == pa->po) 2031 { 2032 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2033 "TALER_MERCHANT_order_put failed (out of memory?)\n"); 2034 temporary_error ("internal-failure"); 2035 cleanup_payment (pa); 2036 return NULL; 2037 } 2038 /* Start to read the button on the VM to cancel this payment */ 2039 if (-1 != cancel_button.cancelbuttonfd) 2040 { 2041 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2042 "Payment started, watching for cancel button\n"); 2043 start_read_cancel_button (); 2044 } 2045 return pa; 2046 } 2047 2048 2049 /** 2050 * @brief Vending successful, conclude payment activity. 2051 */ 2052 static void 2053 vend_success (void) 2054 { 2055 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2056 "MDB vend success received\n"); 2057 GNUNET_break (NULL != payment_activity); 2058 if (NULL != payment_activity) 2059 { 2060 cleanup_payment (payment_activity); 2061 payment_activity = NULL; 2062 } 2063 if (clear_error_on_start) 2064 { 2065 hide_error (); 2066 start_advertising (); 2067 } 2068 } 2069 2070 2071 /** 2072 * Runs asynchronous cleanup part for freeing a 2073 * refund activity. 2074 * 2075 * @param[in] cls a `struct Refund` to clean up 2076 */ 2077 static void 2078 async_refund_cleanup_job (void *cls) 2079 { 2080 struct Refund *r = cls; 2081 2082 if (NULL != r->ctx) 2083 GNUNET_CURL_fini (r->ctx); 2084 if (NULL != r->rc) 2085 GNUNET_CURL_gnunet_rc_destroy (r->rc); 2086 GNUNET_free (r); 2087 } 2088 2089 2090 /** 2091 * @brief Callback to process a POST /refund request 2092 * 2093 * @param cls closure 2094 * @param rr response details 2095 */ 2096 static void 2097 refund_complete_cb (void *cls, 2098 const struct TALER_MERCHANT_RefundResponse *rr) 2099 { 2100 struct Refund *r = cls; 2101 2102 r->orh = NULL; 2103 if (MHD_HTTP_OK != rr->hr.http_status) 2104 { 2105 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2106 "Failed to grant consumer refund: %u/%d\n", 2107 rr->hr.http_status, 2108 (int) rr->hr.ec); 2109 } 2110 GNUNET_CONTAINER_DLL_remove (refund_head, 2111 refund_tail, 2112 r); 2113 GNUNET_SCHEDULER_add_now (&async_refund_cleanup_job, 2114 r); 2115 } 2116 2117 2118 /** 2119 * @brief Vending failed, provide refund. 2120 */ 2121 static void 2122 vend_failure (void) 2123 { 2124 struct Product *p; 2125 struct Refund *r; 2126 2127 if (NULL == payment_activity) 2128 { 2129 GNUNET_break (0); 2130 return; 2131 } 2132 p = payment_activity->product; 2133 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2134 "Received MDB vend failure for `%s', refunding customer\n", 2135 p->description); 2136 p->sold_out = true; 2137 mdb.cmd = &endSession; 2138 mdb.session_running = false; 2139 r = GNUNET_new (struct Refund); 2140 r->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 2141 &r->rc); 2142 r->rc = GNUNET_CURL_gnunet_rc_create (r->ctx); 2143 GNUNET_assert (GNUNET_OK == 2144 GNUNET_CURL_append_header (r->ctx, 2145 p->auth_header)); 2146 r->orh = TALER_MERCHANT_post_order_refund (r->ctx, 2147 (NULL == p->instance) 2148 ? backend_base_url 2149 : p->instance, 2150 payment_activity->order_id, 2151 &payment_activity->amount, 2152 "failed to dispense product", 2153 &refund_complete_cb, 2154 r); 2155 if (NULL == r->orh) 2156 { 2157 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2158 "Failed to launch refund interaction with the merchant backend!\n"); 2159 GNUNET_free (r); 2160 } 2161 else 2162 { 2163 GNUNET_CONTAINER_DLL_insert (refund_head, 2164 refund_tail, 2165 r); 2166 } 2167 if (NULL != payment_activity) 2168 { 2169 cleanup_payment (payment_activity); 2170 payment_activity = NULL; 2171 } 2172 } 2173 2174 2175 /** 2176 * @brief Read the character from stdin and activate the selected task 2177 * 2178 * @param cls closure 2179 */ 2180 static void 2181 read_keyboard_command (void *cls) 2182 { 2183 int input; 2184 2185 (void) cls; 2186 keyboard_task = NULL; 2187 input = getchar (); 2188 if ( (EOF == input) || 2189 ('x' == (char) input) ) 2190 { 2191 GNUNET_SCHEDULER_shutdown (); 2192 return; 2193 } 2194 if (NULL != payment_activity) 2195 { 2196 switch ((char) input) 2197 { 2198 case 'c': 2199 if (GNUNET_NO == payment_activity->paid) 2200 { 2201 mdb.cmd = &cmd_deny_vend; 2202 } 2203 else 2204 { 2205 mdb.cmd = &endSession; 2206 mdb.session_running = false; 2207 } 2208 run_mdb_event_loop (); 2209 cleanup_payment (payment_activity); 2210 payment_activity = NULL; 2211 break; 2212 case 'a': 2213 payment_activity->paid = true; 2214 mdb.cmd = &cmd_approve_vend; 2215 run_mdb_event_loop (); 2216 break; 2217 case 'n': 2218 if (disable_mdb) 2219 { 2220 vend_failure (); 2221 } 2222 else 2223 { 2224 fprintf (stderr, 2225 "Cannot fail to vend at this time, waiting for payment\n"); 2226 } 2227 break; 2228 case 'y': 2229 if (disable_mdb) 2230 { 2231 vend_success (); 2232 } 2233 else 2234 { 2235 fprintf (stderr, 2236 "Cannot succeed to vend at this time, waiting for payment\n"); 2237 } 2238 break; 2239 default: 2240 fprintf (stderr, 2241 "Unknown command `%c'\n", 2242 input); 2243 break; 2244 } 2245 start_read_keyboard (); 2246 return; 2247 } 2248 if (NULL != payment_activity) 2249 { 2250 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2251 "Purchase activity already pending\n"); 2252 start_read_keyboard (); 2253 return; 2254 } 2255 for (unsigned int i = 0; i < products_length; i++) 2256 if (((char) input) == products[i].key) 2257 { 2258 if ( (sold_out_enabled) && 2259 (products[i].sold_out) ) 2260 { 2261 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2262 "Product %s sold out, denying vend\n", 2263 products[i].description); 2264 mdb.cmd = &cmd_reader_display_sold_out; 2265 run_mdb_event_loop (); 2266 start_read_keyboard (); 2267 return; 2268 } 2269 payment_activity = launch_payment (&products[i]); 2270 start_read_keyboard (); 2271 return; 2272 } 2273 fprintf (stderr, 2274 "Unknown command '%c'\n", 2275 (char) input); 2276 start_read_keyboard (); 2277 } 2278 2279 2280 /** 2281 * @brief Read the state of the cancel button GPIO pin 2282 * 2283 * @param cls closure 2284 */ 2285 static void 2286 cancel_button_pressed (void *cls) 2287 { 2288 char value; 2289 2290 (void) cls; 2291 cancelbutton_task = NULL; 2292 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2293 "Cancel button event detected\n"); 2294 if (1 != 2295 read (cancel_button.cancelbuttonfd, 2296 &value, 2297 1)) 2298 { 2299 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 2300 "read"); 2301 start_read_cancel_button (); 2302 return; 2303 } 2304 2305 GNUNET_break (0 == lseek (cancel_button.cancelbuttonfd, 2306 0, 2307 SEEK_SET)); 2308 /* This point should only be reached when a order is pending, because 2309 * the scheduler read file gets added in the function "launch_payment". 2310 * But anyway safe check, else do nothing */ 2311 if (NULL != payment_activity) 2312 { 2313 if ('1' == value) 2314 { 2315 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2316 "Cancel button pressed, canceling current order\n"); 2317 if (GNUNET_NO == payment_activity->paid) 2318 { 2319 /* The current payment was not paid already, deny it */ 2320 mdb.cmd = &cmd_deny_vend; 2321 } 2322 else 2323 { 2324 /* The order was paid and if we know this, then it is also yielded, 2325 * just end the current session */ 2326 mdb.cmd = &endSession; 2327 mdb.session_running = false; 2328 } 2329 run_mdb_event_loop (); 2330 cleanup_payment (payment_activity); 2331 payment_activity = NULL; 2332 } 2333 else 2334 { 2335 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2336 "Cancel button spurious event (%d), looking for more\n", 2337 (int) value); 2338 start_read_cancel_button (); 2339 } 2340 } 2341 } 2342 2343 2344 /** 2345 * @brief Wait for a keyboard input 2346 */ 2347 static void 2348 start_read_keyboard (void) 2349 { 2350 static struct GNUNET_DISK_FileHandle fh = { STDIN_FILENO }; 2351 2352 GNUNET_assert (NULL == keyboard_task); 2353 if (NULL == payment_activity) 2354 { 2355 for (unsigned int i = 0; i < products_length; i++) 2356 printf ("'%c' to buy %s\n", 2357 products[i].key, 2358 products[i].description); 2359 } 2360 else 2361 { 2362 if (GNUNET_NO == payment_activity->paid) 2363 { 2364 printf ("'a' to fake payment for the last purchase\n" 2365 "'c' to cancel last purchase\n"); 2366 } 2367 else 2368 { 2369 if (disable_mdb) 2370 printf ("'y' to simulate product successfully dispensed\n" 2371 "'n' to simulate product failed to be dispensed\n"); 2372 } 2373 } 2374 printf ("'x' to quit\n"); 2375 printf ("Waiting for keyboard input\n"); 2376 keyboard_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 2377 &fh, 2378 &read_keyboard_command, 2379 NULL); 2380 } 2381 2382 2383 /** 2384 * @brief Wait for cancel button during payment activity 2385 */ 2386 static void 2387 start_read_cancel_button (void) 2388 { 2389 struct GNUNET_DISK_FileHandle fh = { 2390 cancel_button.cancelbuttonfd 2391 }; 2392 2393 if (NULL != cancelbutton_task) 2394 { 2395 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2396 "Cancel task still active (!?), not starting another one\n"); 2397 return; 2398 } 2399 cancelbutton_task = GNUNET_SCHEDULER_add_read_file ( 2400 GNUNET_TIME_UNIT_FOREVER_REL, 2401 &fh, 2402 &cancel_button_pressed, 2403 NULL); 2404 } 2405 2406 2407 /** 2408 * @brief Send data to the vmc via the uart bus 2409 * 2410 * @param cls closure 2411 */ 2412 static void 2413 write_mdb_command (void *cls) 2414 { 2415 int did_write = 0; 2416 2417 (void) cls; 2418 mdb.wtask = NULL; 2419 2420 if (disable_mdb) 2421 { 2422 /* check if there is a cmd to send and overwrite last command */ 2423 if (NULL == mdb.cmd) 2424 return; 2425 mdb.last_cmd = mdb.cmd; 2426 mdb.cmd = NULL; 2427 run_mdb_event_loop (); 2428 return; 2429 } 2430 /* if command was sent partially, send the rest */ 2431 if (mdb.tx_off < mdb.tx_len) 2432 { 2433 ssize_t ret = write (mdb.uartfd, 2434 &mdb.txBuffer[mdb.tx_off], 2435 mdb.tx_len - mdb.tx_off); 2436 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2437 "Wrote %d bytes to MDB\n", 2438 (int) ret); 2439 did_write = 1; 2440 if (-1 == ret) 2441 { 2442 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 2443 "write", 2444 uart_device_filename); 2445 if (in_shutdown) 2446 mdb_shutdown (); 2447 else 2448 GNUNET_SCHEDULER_shutdown (); 2449 return; 2450 } 2451 mdb.tx_off += ret; 2452 /* if command was sent sucessfully start the timer for ACK timeout */ 2453 if ( (ret > 0) && 2454 (mdb.tx_off == mdb.tx_len) ) 2455 mdb.ack_timeout = GNUNET_TIME_relative_to_absolute (MAX_ACK_LATENCY); 2456 } 2457 /* ongoing write incomplete, continue later */ 2458 if (mdb.tx_off < mdb.tx_len) 2459 { 2460 run_mdb_event_loop (); 2461 return; 2462 } 2463 if (NULL != mdb.last_cmd) 2464 { 2465 struct GNUNET_TIME_Relative del; 2466 2467 /* Still waiting for ACK! -> delay write task */ 2468 del = GNUNET_TIME_absolute_get_remaining (mdb.ack_timeout); 2469 if (0 != del.rel_value_us) 2470 { 2471 if (did_write) 2472 run_mdb_event_loop (); 2473 else 2474 mdb.wtask = GNUNET_SCHEDULER_add_delayed (del, 2475 &write_mdb_command, 2476 NULL); 2477 return; 2478 } 2479 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2480 "MDB failed to acknowledge command `%s' within timeout\n", 2481 mdb.last_cmd->name); 2482 mdb.last_cmd = NULL; 2483 if (in_shutdown) 2484 { 2485 mdb_shutdown (); 2486 return; 2487 } 2488 } 2489 if (NULL == mdb.cmd) 2490 return; 2491 /* if there is a command to send calculate length and checksum */ 2492 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2493 "Sending command `%s'\n", 2494 mdb.cmd->name); 2495 mdb.tx_off = 0; 2496 mdb.tx_len = mdb.cmd->cmd.bin_size + mdb.cmd->data.bin_size + 1; 2497 GNUNET_assert (mdb.tx_len <= sizeof (mdb.txBuffer)); 2498 { 2499 /* calculate checksum: sum up command and data, take just the LSB of the result */ 2500 uint32_t chkSum = 0; 2501 2502 for (size_t idx = 0; idx < mdb.cmd->cmd.bin_size; idx++) 2503 chkSum += mdb.txBuffer[idx] = mdb.cmd->cmd.bin[idx]; 2504 for (size_t idx = 0; idx < mdb.cmd->data.bin_size; idx++) 2505 chkSum += mdb.txBuffer[idx + mdb.cmd->cmd.bin_size] = 2506 mdb.cmd->data.bin[idx]; 2507 mdb.txBuffer[mdb.cmd->cmd.bin_size + mdb.cmd->data.bin_size] = 2508 (uint8_t) (chkSum & 0xFF); 2509 } 2510 mdb.last_cmd = mdb.cmd; 2511 mdb.cmd = NULL; 2512 run_mdb_event_loop (); 2513 } 2514 2515 2516 /** 2517 * @brief MDB acknowledged the last command, proceed. 2518 */ 2519 static void 2520 handle_ack () 2521 { 2522 if (&cmd_begin_session == mdb.last_cmd) 2523 mdb.session_running = true; 2524 if (&cmd_deny_vend == mdb.last_cmd) 2525 { 2526 mdb.session_running = false; 2527 mdb.cmd = &endSession; 2528 } 2529 if (&cmd_reader_display_sold_out == mdb.last_cmd) 2530 mdb.cmd = &cmd_deny_vend; 2531 if (&cmd_reader_display_internal_error == mdb.last_cmd) 2532 mdb.cmd = &cmd_deny_vend; 2533 if (&cmd_reader_display_backend_not_reachable == mdb.last_cmd) 2534 mdb.cmd = &cmd_deny_vend; 2535 mdb.last_cmd = NULL; 2536 /* Cause the write-task to be re-scheduled now */ 2537 if (NULL != mdb.wtask) 2538 { 2539 GNUNET_SCHEDULER_cancel (mdb.wtask); 2540 mdb.wtask = NULL; 2541 } 2542 } 2543 2544 2545 /** 2546 * @brief Parse received command from the VMC 2547 * 2548 * @param hex received command from VMC 2549 * @param hex_len number of characters in @a hex 2550 */ 2551 static void 2552 handle_command (const char *hex, 2553 size_t hex_len) 2554 { 2555 unsigned int cmd; 2556 unsigned int tmp = 0; 2557 uint32_t chkSum; 2558 2559 /* if the received command is 0 or not a multiple of 2 we cannot parse it */ 2560 if (0 == hex_len) 2561 return; 2562 if (0 != (hex_len % 2)) 2563 { 2564 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2565 "Received unexpected input `%.*s'\n", 2566 (int) hex_len, 2567 hex); 2568 GNUNET_break_op (0); 2569 return; 2570 } 2571 /* convert the received 2 bytes from ASCII to hex */ 2572 if (1 != sscanf (hex, 2573 "%2X", 2574 &cmd)) 2575 { 2576 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2577 "Received non-HEX input `%.*s'\n", 2578 (int) hex_len, 2579 hex); 2580 GNUNET_break_op (0); 2581 return; 2582 } 2583 2584 /* parse the first byte (cmd) and the second byte (subcmd) */ 2585 switch (cmd) 2586 { 2587 case VMC_VEND: 2588 { 2589 unsigned int subcmd; 2590 2591 if (4 > hex_len) 2592 { 2593 GNUNET_break_op (0); 2594 return; 2595 } 2596 if (1 != sscanf (&hex[2], 2597 "%2X", 2598 &subcmd)) 2599 { 2600 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2601 "Received non-HEX input `%.*s'\n", 2602 (int) hex_len - 2, 2603 &hex[2]); 2604 GNUNET_break_op (0); 2605 return; 2606 } 2607 switch (subcmd) 2608 { 2609 case VMC_VEND_REQUEST: 2610 { 2611 unsigned int product; 2612 2613 /* Calculate the checksum and check it */ 2614 chkSum = cmd; 2615 2616 for (size_t offset = 1; offset < ((hex_len / 2)); offset++) 2617 { 2618 chkSum += tmp; 2619 if (1 != sscanf (hex + (2 * offset), 2620 "%2X", 2621 &tmp)) 2622 { 2623 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2624 "Received non-HEX input `%.*s'\n", 2625 (int) hex_len, 2626 hex); 2627 GNUNET_break_op (0); 2628 return; 2629 } 2630 } 2631 if ( ((uint8_t) (chkSum & 0xFF)) != tmp) 2632 { 2633 mdb.cmd = &cmd_reader_display_internal_error; 2634 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2635 "Received command with wrong checksum `%.*s'\n", 2636 (int) hex_len, 2637 hex); 2638 temporary_error ("err-num-read-fail"); 2639 break; 2640 } 2641 2642 GNUNET_break (mdb.session_running); 2643 /* NOTE: hex[4..7] contain the price */ 2644 if (12 > hex_len) 2645 { 2646 GNUNET_break_op (0); 2647 return; 2648 } 2649 if (1 != sscanf (&hex[8], 2650 "%4X", 2651 &product)) 2652 { 2653 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2654 "Received non-HEX input `%.*s'\n", 2655 (int) hex_len - 8, 2656 &hex[8]); 2657 GNUNET_break_op (0); 2658 return; 2659 } 2660 /* compare the received product number with the defined product numbers */ 2661 for (unsigned int i = 0; i < products_length; i++) 2662 if (product == products[i].number) 2663 { 2664 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2665 "Product %u selected on MDB\n", 2666 product); 2667 if ( (sold_out_enabled) && 2668 (products[i].sold_out) ) 2669 { 2670 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2671 "Product %s sold out, denying vend\n", 2672 products[i].description); 2673 mdb.cmd = &cmd_reader_display_sold_out; 2674 temporary_error ("err-sold-out"); 2675 return; 2676 } 2677 payment_activity = launch_payment (&products[i]); 2678 if (NULL == payment_activity) 2679 vend_failure (); 2680 return; 2681 } 2682 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2683 "Unknown product %u selected on MDB, denying vend\n", 2684 product); 2685 temporary_error ("unknown-product"); 2686 mdb.cmd = &cmd_deny_vend; 2687 break; 2688 } 2689 case VMC_VEND_SUCCESS: 2690 GNUNET_break (mdb.session_running); 2691 vend_success (); 2692 break; 2693 case VMC_VEND_FAILURE: 2694 { 2695 GNUNET_break (mdb.session_running); 2696 vend_failure (); 2697 break; 2698 } 2699 case VMC_VEND_SESSION_COMPLETE: 2700 { 2701 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2702 "Received MDB session complete\n"); 2703 mdb.session_running = false; 2704 mdb.cmd = &endSession; 2705 if (NULL != payment_activity) 2706 { 2707 cleanup_payment (payment_activity); 2708 payment_activity = NULL; 2709 } 2710 } 2711 break; 2712 default: 2713 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2714 "Unknown MDB sub-command %X of command %X\n", 2715 subcmd, 2716 cmd); 2717 break; 2718 } 2719 break; 2720 } 2721 case VMC_REVALUE: 2722 { 2723 unsigned int subcmd; 2724 2725 if (4 > hex_len) 2726 { 2727 GNUNET_break_op (0); 2728 return; 2729 } 2730 if (1 != sscanf (&hex[2], 2731 "%2X", 2732 &subcmd)) 2733 { 2734 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2735 "Received non-HEX input `%.*s'\n", 2736 (int) hex_len - 2, 2737 &hex[2]); 2738 GNUNET_break_op (0); 2739 return; 2740 } 2741 switch (subcmd) 2742 { 2743 case VMC_REVALUE_REQUEST: 2744 { 2745 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2746 "Received request for revalue via MDB\n"); 2747 mdb.cmd = &cmd_revalue_approved; 2748 break; 2749 } 2750 case VMC_REVALUE_LIMIT_REQUEST: 2751 { 2752 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2753 "Received request for revalue limit amount via MDB\n"); 2754 mdb.cmd = &cmd_revalue_amount; 2755 break; 2756 } 2757 default: 2758 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2759 "Unknown MDB sub-command %X of command %X\n", 2760 subcmd, 2761 cmd); 2762 break; 2763 } 2764 break; 2765 } 2766 case VMC_CONF: 2767 { 2768 unsigned int subcmd; 2769 2770 if (4 > hex_len) 2771 { 2772 GNUNET_break_op (0); 2773 return; 2774 } 2775 if (1 != sscanf (&hex[2], 2776 "%2X", 2777 &subcmd)) 2778 { 2779 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2780 "Received non-HEX input `%.*s'\n", 2781 (int) hex_len - 2, 2782 &hex[2]); 2783 GNUNET_break_op (0); 2784 return; 2785 } 2786 switch (subcmd) 2787 { 2788 case VMC_READER_CONF: 2789 { 2790 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2791 "Received request for configuration via MDB\n"); 2792 mdb.cmd = &cmd_reader_config_data; 2793 break; 2794 2795 } 2796 case VMC_SETUP_MAX_MIN_PRICES: 2797 { 2798 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2799 "Received max and min prices via MDB\n"); 2800 break; 2801 } 2802 default: 2803 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2804 "Unknown MDB sub-command %X of command %X\n", 2805 subcmd, 2806 cmd); 2807 break; 2808 } 2809 break; 2810 } 2811 case VMC_POLL: 2812 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2813 "Received POLL from MDB (ignored)\n"); 2814 break; 2815 case VMC_CMD_RESET: 2816 { 2817 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2818 "Received RESET from MDB (ignored)\n"); 2819 break; 2820 } 2821 case VMC_READER: 2822 { 2823 unsigned int subcmd; 2824 2825 if (4 > hex_len) 2826 { 2827 GNUNET_break_op (0); 2828 return; 2829 } 2830 if (1 != sscanf (&hex[2], 2831 "%2X", 2832 &subcmd)) 2833 { 2834 GNUNET_break_op (0); 2835 return; 2836 } 2837 2838 switch (subcmd) 2839 { 2840 case VMC_READER_DISABLE: 2841 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2842 "Received Reader Disable via MDB\n"); 2843 mdb.session_running = false; 2844 if (NULL != payment_activity) 2845 { 2846 cleanup_payment (payment_activity); 2847 payment_activity = NULL; 2848 } 2849 for (unsigned int i = 0; i < products_length; i++) 2850 { 2851 if ( (sold_out_enabled) && 2852 (0 != strcmp (products[i].description, 2853 "empty")) && 2854 (products[i].sold_out) ) 2855 { 2856 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2857 "Resetting sold out state of product %s\n", 2858 products[i].description); 2859 products[i].sold_out = false; 2860 } 2861 } 2862 break; 2863 case VMC_READER_ENABLE: 2864 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2865 "Received Reader Enable via MDB\n"); 2866 mdb.session_running = false; 2867 break; 2868 case VMC_READER_CANCEL: 2869 mdb.cmd = &cmd_reader_cancelled; 2870 mdb.session_running = false; 2871 break; 2872 default: 2873 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2874 "Unknown MDB sub-command %X of command %X\n", 2875 subcmd, 2876 cmd); 2877 break; 2878 } 2879 break; 2880 } 2881 case VMC_REQUEST_ID: 2882 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 2883 "Received VMC request ID, no need to handle (done by HW)\n"); 2884 break; 2885 case VMC_ACKN: 2886 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2887 "Received acknowledgement (for command `%s') from MDB\n", 2888 (NULL != mdb.last_cmd) ? mdb.last_cmd->name : "?"); 2889 handle_ack (); 2890 break; 2891 case VMC_OOSQ: 2892 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2893 "Received command out of sequence from MDB (for command `%s')\n", 2894 (NULL != mdb.last_cmd) ? mdb.last_cmd->name : "?"); 2895 mdb.session_running = false; 2896 if (mdb.last_cmd != &endSession) 2897 mdb.cmd = &endSession; 2898 else 2899 mdb.last_cmd = NULL; 2900 break; 2901 case VMC_RETR: 2902 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2903 "Received request to resend previous data from MDB (previous command was `%s')\n", 2904 (NULL != mdb.last_cmd) ? mdb.last_cmd->name : "?"); 2905 GNUNET_break (NULL == mdb.cmd); 2906 GNUNET_break (NULL != mdb.last_cmd); 2907 mdb.cmd = mdb.last_cmd; 2908 mdb.last_cmd = NULL; 2909 break; 2910 default: 2911 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2912 "Received unknown MDB command %X\n", 2913 cmd); 2914 break; 2915 } 2916 } 2917 2918 2919 /** 2920 * @brief Read in the sent commands of the VMC controller 2921 * 2922 * @param cls closure 2923 */ 2924 static void 2925 read_mdb_command (void *cls) 2926 { 2927 ssize_t ret; 2928 size_t cmdStartIdx; 2929 size_t cmdEndIdx; 2930 2931 (void) cls; 2932 /* don't read if the mdb bus is disabled (only for testing) */ 2933 GNUNET_assert (! disable_mdb); 2934 mdb.rtask = NULL; 2935 ret = read (mdb.uartfd, 2936 &mdb.rxBuffer[mdb.rx_off], 2937 sizeof (mdb.rxBuffer) - mdb.rx_off); 2938 if (-1 == ret) 2939 { 2940 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 2941 "read", 2942 uart_device_filename); 2943 GNUNET_SCHEDULER_shutdown (); 2944 return; 2945 } 2946 mdb.rx_off += ret; 2947 2948 while (mdb.rx_off > 0) 2949 { 2950 mdb_active = true; 2951 /* find beginning of command */ 2952 for (cmdStartIdx = 0; cmdStartIdx < mdb.rx_off; cmdStartIdx++) 2953 if (VMC_CMD_START == mdb.rxBuffer[cmdStartIdx]) 2954 break; 2955 if (cmdStartIdx == mdb.rx_off) 2956 { 2957 mdb.rx_off = 0; 2958 break; 2959 } 2960 /* find end of command */ 2961 for (cmdEndIdx = cmdStartIdx; cmdEndIdx < mdb.rx_off; cmdEndIdx++) 2962 if (VMC_CMD_END == mdb.rxBuffer[cmdEndIdx]) 2963 break; 2964 if (cmdEndIdx == mdb.rx_off) 2965 { 2966 /* check to make sure rxBuffer was big enough in principle */ 2967 if ( (cmdStartIdx == 0) && 2968 (mdb.rx_off == sizeof (mdb.rxBuffer)) ) 2969 { 2970 /* Developer: if this happens, try increasing rxBuffer! */ 2971 GNUNET_break (0); 2972 GNUNET_SCHEDULER_shutdown (); 2973 return; 2974 } 2975 /* move cmd in buffer to the beginning of the buffer */ 2976 memmove (mdb.rxBuffer, 2977 &mdb.rxBuffer[cmdStartIdx], 2978 mdb.rx_off - cmdStartIdx); 2979 mdb.rx_off -= cmdStartIdx; 2980 break; 2981 } 2982 /* if the full command was received parse it */ 2983 handle_command ((const char *) &mdb.rxBuffer[cmdStartIdx + 1], 2984 cmdEndIdx - cmdStartIdx - 1); 2985 /* move the data after the processed command to the left */ 2986 memmove (mdb.rxBuffer, 2987 &mdb.rxBuffer[cmdEndIdx + 1], 2988 mdb.rx_off - cmdEndIdx + 1); 2989 mdb.rx_off -= (cmdEndIdx + 1); 2990 } 2991 if (in_shutdown) 2992 { 2993 mdb_shutdown (); 2994 return; 2995 } 2996 run_mdb_event_loop (); 2997 } 2998 2999 3000 /** 3001 * @brief Mdb event loop to start read and write tasks 3002 */ 3003 static void 3004 run_mdb_event_loop (void) 3005 { 3006 struct GNUNET_DISK_FileHandle fh = { mdb.uartfd }; 3007 3008 /* begin session if no cmd waits for sending and no cmd is received from the VMC */ 3009 if ( (! mdb.session_running) && 3010 (NULL == mdb.cmd) && 3011 (NULL == mdb.last_cmd) ) 3012 { 3013 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 3014 "Beginning MDB session\n"); 3015 mdb.cmd = &cmd_begin_session; 3016 } 3017 /* start write task if he doesn't exist and if there is a cmd waiting to get sent */ 3018 if ( (NULL == mdb.wtask) && 3019 ( (NULL != mdb.cmd) || 3020 (in_shutdown) || 3021 (mdb.tx_len > mdb.tx_off) ) ) 3022 { 3023 if (disable_mdb || ! mdb_active) 3024 mdb.wtask = GNUNET_SCHEDULER_add_now (&write_mdb_command, 3025 NULL); 3026 else 3027 mdb.wtask = GNUNET_SCHEDULER_add_write_file ((in_shutdown) 3028 ? SHUTDOWN_MDB_TIMEOUT 3029 : 3030 GNUNET_TIME_UNIT_FOREVER_REL, 3031 &fh, 3032 &write_mdb_command, 3033 NULL); 3034 } 3035 if ( (disable_mdb) && 3036 (NULL != mdb.last_cmd) ) 3037 { 3038 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 3039 "Faking acknowledgement (for command `%s') from MDB\n", 3040 mdb.last_cmd->name); 3041 handle_ack (); 3042 } 3043 /* start read task if he doesn't exist and the mdb communication is not disabled (only for testing) */ 3044 if ( (NULL == mdb.rtask) && 3045 (! disable_mdb) ) 3046 mdb.rtask = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 3047 &fh, 3048 &read_mdb_command, 3049 NULL); 3050 } 3051 3052 3053 /** 3054 * @brief Read the products from the configuration file 3055 * 3056 * @param cls closure in this case the name of the configuration file to read from 3057 * @param section section of the config file to read from 3058 */ 3059 static void 3060 read_products (void *cls, 3061 const char *section) 3062 { 3063 const struct GNUNET_CONFIGURATION_Handle *cfg = cls; 3064 struct Product tmpProduct = { 3065 .sold_out = false 3066 }; 3067 char *tmpKey; 3068 char *thumbnail_fn; 3069 3070 /* if the current section is not a product skip it */ 3071 if (0 != strncmp (section, 3072 "product-", 3073 strlen ("product-"))) 3074 return; 3075 /* the current section is a product, parse its specifications and store it in a temporary product */ 3076 if (GNUNET_OK != 3077 GNUNET_CONFIGURATION_get_value_string (cfg, 3078 section, 3079 "DESCRIPTION", 3080 &tmpProduct.description)) 3081 { 3082 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3083 section, 3084 "DESCRIPTION"); 3085 return; 3086 } 3087 if (sold_out_enabled) 3088 { 3089 if (0 == strcmp (tmpProduct.description, 3090 "empty")) 3091 { 3092 tmpProduct.sold_out = true; 3093 } 3094 else 3095 { 3096 tmpProduct.sold_out = false; 3097 } 3098 } 3099 if (GNUNET_OK != 3100 TALER_config_get_amount (cfg, 3101 section, 3102 "price", 3103 &tmpProduct.price)) 3104 { 3105 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3106 section, 3107 "price"); 3108 GNUNET_free (tmpProduct.description); 3109 return; 3110 } 3111 if (GNUNET_OK == 3112 GNUNET_CONFIGURATION_get_value_string (cfg, 3113 section, 3114 "KEY", 3115 &tmpKey)) 3116 { 3117 tmpProduct.key = tmpKey[0]; 3118 GNUNET_free (tmpKey); 3119 } 3120 else 3121 { 3122 /* no key */ 3123 tmpProduct.key = '\0'; 3124 } 3125 if (GNUNET_OK != 3126 GNUNET_CONFIGURATION_get_value_string (cfg, 3127 section, 3128 "INSTANCE", 3129 &tmpProduct.instance)) 3130 tmpProduct.instance = NULL; 3131 tmpProduct.preview = NULL; 3132 if (GNUNET_OK == 3133 GNUNET_CONFIGURATION_get_value_string (cfg, 3134 section, 3135 "THUMBNAIL", 3136 &thumbnail_fn)) 3137 { 3138 struct GNUNET_DISK_FileHandle *fh; 3139 3140 fh = GNUNET_DISK_file_open (thumbnail_fn, 3141 GNUNET_DISK_OPEN_READ, 3142 GNUNET_DISK_PERM_NONE); 3143 if (NULL == fh) 3144 { 3145 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3146 "Could not open thumbnail `%s'\n", 3147 thumbnail_fn); 3148 } 3149 else 3150 { 3151 off_t flen; 3152 3153 if (GNUNET_OK != 3154 GNUNET_DISK_file_handle_size (fh, 3155 &flen)) 3156 { 3157 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 3158 "stat", 3159 thumbnail_fn); 3160 } 3161 else 3162 { 3163 void *thumb; 3164 size_t len; 3165 ssize_t ret; 3166 3167 len = (size_t) flen; 3168 thumb = GNUNET_malloc (len); 3169 ret = GNUNET_DISK_file_read (fh, 3170 thumb, 3171 len); 3172 if ( (ret < 0) || 3173 (len != ((size_t) ret)) ) 3174 { 3175 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 3176 "read", 3177 thumbnail_fn); 3178 } 3179 else 3180 { 3181 const char *mime_type = ""; 3182 const char *ext; 3183 char *base64; 3184 3185 ext = strrchr (thumbnail_fn, '.'); 3186 if (NULL != ext) 3187 { 3188 static const struct 3189 { 3190 const char *ext; 3191 const char *mime; 3192 } mimes[] = { 3193 { ".png", "image/png" }, 3194 { ".jpg", "image/jpeg" }, 3195 { ".jpeg", "image/jpeg" }, 3196 { ".svg", "image/svg" }, 3197 { NULL, NULL } 3198 }; 3199 3200 for (unsigned int i = 0; NULL != mimes[i].ext; i++) 3201 if (0 == strcasecmp (mimes[i].ext, 3202 ext)) 3203 { 3204 mime_type = mimes[i].mime; 3205 break; 3206 } 3207 } 3208 (void) GNUNET_STRINGS_base64_encode (thumb, 3209 len, 3210 &base64); 3211 GNUNET_asprintf (&tmpProduct.preview, 3212 "data:%s;base64,%s", 3213 mime_type, 3214 base64); 3215 GNUNET_free (base64); 3216 } 3217 GNUNET_free (thumb); 3218 } 3219 GNUNET_break (GNUNET_OK == 3220 GNUNET_DISK_file_close (fh)); 3221 } 3222 GNUNET_free (thumbnail_fn); 3223 } 3224 if (GNUNET_OK != 3225 GNUNET_CONFIGURATION_get_value_number (cfg, 3226 section, 3227 "NUMBER", 3228 &tmpProduct.number)) 3229 { 3230 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3231 section, 3232 "NUMBER"); 3233 GNUNET_free (tmpProduct.description); 3234 GNUNET_free (tmpProduct.instance); 3235 GNUNET_free (tmpProduct.preview); 3236 return; 3237 } 3238 3239 { 3240 char *auth; 3241 3242 if ( (GNUNET_OK != 3243 GNUNET_CONFIGURATION_get_value_string (cfg, 3244 section, 3245 "BACKEND_AUTHORIZATION", 3246 &auth)) && 3247 (GNUNET_OK != 3248 GNUNET_CONFIGURATION_get_value_string (cfg, 3249 "taler-mdb", 3250 "BACKEND_AUTHORIZATION", 3251 &auth)) ) 3252 { 3253 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3254 section, 3255 "BACKEND_AUTHORIZATION"); 3256 GNUNET_free (tmpProduct.description); 3257 GNUNET_free (tmpProduct.instance); 3258 GNUNET_free (tmpProduct.preview); 3259 return; 3260 } 3261 GNUNET_asprintf (&tmpProduct.auth_header, 3262 "%s: %s", 3263 MHD_HTTP_HEADER_AUTHORIZATION, 3264 auth); 3265 GNUNET_free (auth); 3266 } 3267 /* append the temporary product to the existing products */ 3268 GNUNET_array_append (products, 3269 products_length, 3270 tmpProduct); 3271 } 3272 3273 3274 /** 3275 * @brief Initialise the uart device to send mdb commands 3276 * 3277 * @return #GNUNET_OK on success 3278 */ 3279 static enum GNUNET_GenericReturnValue 3280 mdb_init (void) 3281 { 3282 struct termios uart_opts_raw; 3283 3284 if (disable_mdb) 3285 { 3286 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 3287 "Running with MDB disabled!\n"); 3288 run_mdb_event_loop (); 3289 return GNUNET_OK; 3290 } 3291 /* open uart connection */ 3292 if (0 > (mdb.uartfd = open (uart_device_filename, 3293 O_RDWR | O_NOCTTY | O_NDELAY))) 3294 { 3295 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 3296 "open", 3297 uart_device_filename); 3298 return GNUNET_SYSERR; 3299 } 3300 3301 if (0 != tcgetattr (mdb.uartfd, 3302 &mdb.uart_opts_backup)) 3303 { 3304 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 3305 "tcgetattr"); 3306 return GNUNET_SYSERR; 3307 } 3308 uart_opts_raw = mdb.uart_opts_backup; 3309 3310 /* reset current uart discipline */ 3311 memset (&uart_opts_raw, 3312 0, 3313 sizeof(uart_opts_raw)); 3314 3315 /* set baudrate */ 3316 cfsetispeed (&uart_opts_raw, B9600); 3317 cfsetospeed (&uart_opts_raw, B9600); 3318 3319 /* set options */ 3320 uart_opts_raw.c_cflag &= ~PARENB; 3321 uart_opts_raw.c_cflag &= ~CSTOPB; 3322 uart_opts_raw.c_cflag &= ~CSIZE; 3323 uart_opts_raw.c_cflag |= CS8; 3324 3325 /* 19200 bps, 8 databits, ignore cd-signal, allow reading */ 3326 uart_opts_raw.c_cflag |= (CLOCAL | CREAD); 3327 uart_opts_raw.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); 3328 uart_opts_raw.c_iflag = IGNPAR; 3329 uart_opts_raw.c_oflag &= ~OPOST; 3330 uart_opts_raw.c_cc[VMIN] = 0; 3331 uart_opts_raw.c_cc[VTIME] = 50; 3332 tcflush (mdb.uartfd, TCIOFLUSH); 3333 if (0 != tcsetattr (mdb.uartfd, 3334 TCSAFLUSH, 3335 &uart_opts_raw)) 3336 { 3337 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 3338 "tcsetattr"); 3339 return GNUNET_SYSERR; 3340 } 3341 /* if the configuration of the uart was successful start the mdb write and read tasks */ 3342 run_mdb_event_loop (); 3343 return GNUNET_OK; 3344 } 3345 3346 3347 /** 3348 * @brief Start the application 3349 * 3350 * @param cls closure 3351 * @param args arguments left 3352 * @param cfgfile config file name 3353 * @param cfg handle for the configuration 3354 */ 3355 static void 3356 run (void *cls, 3357 char *const *args, 3358 const char *cfgfile, 3359 const struct GNUNET_CONFIGURATION_Handle *cfg) 3360 { 3361 (void) cls; 3362 (void) args; 3363 (void) cfgfile; 3364 3365 /* parse the devices, if no config entry is found, a standard is used */ 3366 if (GNUNET_OK != 3367 GNUNET_CONFIGURATION_get_value_filename (cfg, 3368 "taler-mdb", 3369 "UART_DEVICE", 3370 &uart_device_filename)) 3371 { 3372 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3373 "taler-mdb", 3374 "UART_DEVICE"); 3375 uart_device_filename = GNUNET_strdup ("/dev/ttyAMA0"); 3376 } 3377 if (GNUNET_OK != 3378 GNUNET_CONFIGURATION_get_value_filename (cfg, 3379 "taler-mdb", 3380 "FRAMEBUFFER_DEVICE", 3381 &framebuffer_device_filename)) 3382 { 3383 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 3384 "taler-mdb", 3385 "FRAMEBUFFER_DEVICE"); 3386 framebuffer_device_filename = GNUNET_strdup ("/dev/fb1"); 3387 } 3388 if (GNUNET_OK != 3389 GNUNET_CONFIGURATION_get_value_filename (cfg, 3390 "taler-mdb", 3391 "FRAMEBUFFER_BACKLIGHT", 3392 &framebuffer_backlight_filename)) 3393 { 3394 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3395 "taler-mdb", 3396 "FRAMEBUFFER_BACKLIGHT"); 3397 framebuffer_backlight_filename = GNUNET_strdup ( 3398 "/sys/class/backlight/soc:backlight/brightness"); 3399 } 3400 /* parse the taler configurations */ 3401 if (GNUNET_OK != 3402 GNUNET_CONFIGURATION_get_value_string (cfg, 3403 "taler-mdb", 3404 "BACKEND_BASE_URL", 3405 &backend_base_url)) 3406 { 3407 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3408 "taler-mdb", 3409 "BACKEND_BASE_URL"); 3410 global_ret = EXIT_FAILURE; 3411 return; 3412 } 3413 (void) GNUNET_CONFIGURATION_get_value_string (cfg, 3414 "taler-mdb", 3415 "ADVERTISEMENT_COMMAND", 3416 &adv_process_command); 3417 (void) GNUNET_CONFIGURATION_get_value_string (cfg, 3418 "taler-mdb", 3419 "FAIL_COMMAND", 3420 &err_process_command); 3421 if (GNUNET_OK != 3422 GNUNET_CONFIGURATION_get_value_string (cfg, 3423 "taler-mdb", 3424 "FULFILLMENT_MSG", 3425 &fulfillment_msg)) 3426 { 3427 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 3428 "taler-mdb", 3429 "FULFILLMENT_MSG"); 3430 global_ret = EXIT_FAILURE; 3431 return; 3432 } 3433 3434 /* parse the products */ 3435 GNUNET_CONFIGURATION_iterate_sections (cfg, 3436 &read_products, 3437 (void *) cfg); 3438 if (NULL == products) 3439 { 3440 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3441 "No valid products configured\n"); 3442 global_ret = EXIT_NOTCONFIGURED; 3443 return; 3444 } 3445 3446 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 3447 NULL); 3448 3449 /* initialize mdb */ 3450 if (GNUNET_OK != mdb_init ()) 3451 { 3452 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3453 "Unable to initialize MDB (mdb_init() failed)\n"); 3454 global_ret = EXIT_FAILURE; 3455 GNUNET_SCHEDULER_shutdown (); 3456 return; 3457 } 3458 3459 /* initialize nfc */ 3460 nfc_init (&context); 3461 if (NULL == context) 3462 { 3463 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 3464 "Unable to initialize NFC (nfc_init() failed)\n"); 3465 global_ret = EXIT_FAILURE; 3466 GNUNET_SCHEDULER_shutdown (); 3467 return; 3468 } 3469 3470 /* open gpio pin for cancel button */ 3471 { 3472 int efd; 3473 3474 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 3475 "Exporting GPIO pin 23\n"); 3476 efd = open ("/sys/class/gpio/export", 3477 O_WRONLY); 3478 if (-1 == efd) 3479 { 3480 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 3481 "Unable to open /gpio/export for cancel button\n"); 3482 } 3483 else 3484 { 3485 if (2 != write (efd, 3486 "23", 3487 2)) 3488 { 3489 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 3490 "write", 3491 "/sys/class/gpio/export"); 3492 } 3493 GNUNET_assert (0 == close (efd)); 3494 } 3495 } 3496 3497 /* set direction: input */ 3498 { 3499 int dfd; 3500 3501 dfd = open ("/sys/class/gpio/gpio23/direction", 3502 O_WRONLY); 3503 if (-1 == dfd) 3504 { 3505 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 3506 "Unable to open /gpio/gpio23/direction for cancel button\n"); 3507 } 3508 else 3509 { 3510 if (2 != write (dfd, 3511 "in", 3512 2)) 3513 { 3514 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 3515 "write", 3516 "/sys/class/gpio/gpio23/direction"); 3517 } 3518 GNUNET_assert (0 == close (dfd)); 3519 } 3520 } 3521 3522 { 3523 /* actually open fd for reading the state */ 3524 cancel_button.cancelbuttonfd = open ("/sys/class/gpio/gpio23/value", 3525 O_RDONLY); 3526 if (-1 == cancel_button.cancelbuttonfd) 3527 { 3528 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 3529 "Unable to open /gpio/gpio23/value for cancel button\n"); 3530 } 3531 } 3532 3533 3534 #if HAVE_QRENCODE_H 3535 /* open the framebuffer device */ 3536 qrDisplay.devicefd = open (framebuffer_device_filename, 3537 O_RDWR); 3538 if (-1 != qrDisplay.devicefd) 3539 { 3540 /* read information about the screen */ 3541 ioctl (qrDisplay.devicefd, 3542 FBIOGET_VSCREENINFO, 3543 &qrDisplay.var_info); 3544 3545 /* store current screeninfo for reset */ 3546 qrDisplay.orig_vinfo = qrDisplay.var_info; 3547 3548 if (16 != qrDisplay.var_info.bits_per_pixel) 3549 { 3550 /* Change variable info to 16 bit per pixel */ 3551 qrDisplay.var_info.bits_per_pixel = 16; 3552 if (0 > ioctl (qrDisplay.devicefd, 3553 FBIOPUT_VSCREENINFO, 3554 &qrDisplay.var_info)) 3555 { 3556 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 3557 "ioctl(FBIOPUT_VSCREENINFO)"); 3558 return; 3559 } 3560 } 3561 3562 /* Get fixed screen information */ 3563 if (0 > ioctl (qrDisplay.devicefd, 3564 FBIOGET_FSCREENINFO, 3565 &qrDisplay.fix_info)) 3566 { 3567 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 3568 "ioctl(FBIOGET_FSCREENINFO)"); 3569 return; 3570 } 3571 3572 /* get pointer onto frame buffer */ 3573 qrDisplay.memory = mmap (NULL, 3574 qrDisplay.fix_info.smem_len, 3575 PROT_READ | PROT_WRITE, MAP_SHARED, 3576 qrDisplay.devicefd, 3577 0); 3578 if (MAP_FAILED == qrDisplay.memory) 3579 { 3580 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 3581 "mmap"); 3582 return; 3583 } 3584 3585 /* set the screen to white */ 3586 memset (qrDisplay.memory, 3587 0xFF, 3588 qrDisplay.var_info.xres * qrDisplay.var_info.yres 3589 * sizeof (uint16_t)); 3590 3591 /* open backlight file to turn display backlight on and off */ 3592 qrDisplay.backlightfd = open ( 3593 framebuffer_backlight_filename, O_WRONLY); 3594 if (0 > qrDisplay.backlightfd) 3595 { 3596 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 3597 "open", 3598 framebuffer_backlight_filename); 3599 } 3600 else 3601 { 3602 /* if '-i' flag was set, invert the on and off values */ 3603 if (backlight_invert) 3604 { 3605 backlight_on = '0'; 3606 backlight_off = '1'; 3607 } 3608 /* turn off the backlight */ 3609 (void) ! write (qrDisplay.backlightfd, 3610 &backlight_off, 3611 1); 3612 } 3613 } 3614 else 3615 { 3616 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, 3617 "open", 3618 framebuffer_device_filename); 3619 } 3620 #endif 3621 start_advertising (); 3622 if (! disable_tty) 3623 start_read_keyboard (); 3624 } 3625 3626 3627 /** 3628 * @brief Convert the ASCII cmd in @hex to hex and store it in the mdb data struct @blk 3629 * 3630 * @param *blk pointer with reference to the mdb datablock 3631 * @param *hex pointer to the string containing the cmd data 3632 */ 3633 static void 3634 parse_block (struct MdbBlock *blk, 3635 const char *hex) 3636 { 3637 if (NULL == hex) 3638 { 3639 blk->bin_size = 0; 3640 blk->bin = NULL; 3641 return; 3642 } 3643 blk->bin_size = strlen (hex) / 2; 3644 blk->bin = GNUNET_malloc (blk->bin_size); 3645 for (size_t idx = 0; idx < blk->bin_size; idx++) 3646 { 3647 unsigned int val; 3648 3649 GNUNET_assert (1 == 3650 sscanf (&hex[idx * 2], 3651 "%2X", 3652 &val)); 3653 blk->bin[idx] = (uint8_t) val; 3654 } 3655 } 3656 3657 3658 /** 3659 * @brief Create a new mdb command 3660 * 3661 * @param *name pointer to the string containing the command name 3662 * @param *cmd pointer to the string containing the command 3663 * @param *data pointer to the string containing the command data 3664 * @return structure of type MdbCommand holding the given information by the parameters 3665 */ 3666 static struct MdbCommand 3667 setup_mdb_cmd (const char *name, 3668 const char *cmd, 3669 const char *data) 3670 { 3671 struct MdbCommand cmdNew; 3672 3673 cmdNew.name = (NULL == name) 3674 ? "No Cmd Name Set" 3675 : name; 3676 parse_block (&cmdNew.cmd, cmd); 3677 parse_block (&cmdNew.data, data); 3678 return cmdNew; 3679 } 3680 3681 3682 int 3683 main (int argc, 3684 char*const*argv) 3685 { 3686 struct termios tty_opts_backup; 3687 struct termios tty_opts_raw; 3688 int have_tty; 3689 int ret; 3690 struct GNUNET_GETOPT_CommandLineOption options[] = { 3691 GNUNET_GETOPT_option_flag ('d', 3692 "disable-mdb", 3693 "disable all interactions with the MDB (for testing without machine)", 3694 &disable_mdb), 3695 GNUNET_GETOPT_option_flag ('i', 3696 "backlight-invert", 3697 "invert the backlight on/off values (standard on = 1)", 3698 &backlight_invert), 3699 GNUNET_GETOPT_option_flag ('s', 3700 "enable-soldout", 3701 "enable detection of sold-out products, preventing vend operations of the respective product until the process is restarted", 3702 &sold_out_enabled), 3703 GNUNET_GETOPT_option_flag ('t', 3704 "disable-tty", 3705 "disable all keyboard interactions (for running from systemd)", 3706 &disable_tty), 3707 GNUNET_GETOPT_OPTION_END 3708 }; 3709 3710 if (! disable_tty) 3711 { 3712 have_tty = isatty (STDIN_FILENO); 3713 if (have_tty) 3714 { 3715 if (0 != tcgetattr (STDIN_FILENO, &tty_opts_backup)) 3716 fprintf (stderr, 3717 "Failed to get terminal discipline\n"); 3718 tty_opts_raw = tty_opts_backup; 3719 tty_opts_raw.c_lflag &= ~(ECHO | ECHONL | ICANON); 3720 if (0 != tcsetattr (STDIN_FILENO, TCSANOW, &tty_opts_raw)) 3721 fprintf (stderr, 3722 "Failed to set terminal discipline\n"); 3723 } 3724 } 3725 /* make the needed commands for the communication with the vending machine controller */ 3726 cmd_reader_config_data = setup_mdb_cmd ("Reader Config", 3727 READER_CONFIG, 3728 READER_FEATURE_LEVEL 3729 READER_COUNTRYCODE 3730 READER_SCALE_FACTOR 3731 READER_DECIMAL_PLACES 3732 READER_MAX_RESPONSE_TIME 3733 READER_MISC_OPTIONS); 3734 cmd_begin_session = setup_mdb_cmd ("Begin Session", 3735 READER_BEGIN_SESSION, 3736 READER_FUNDS_AVAILABLE); 3737 cmd_approve_vend = setup_mdb_cmd ("Approve Vend", 3738 READER_VEND_APPROVE, 3739 READER_VEND_AMOUNT); 3740 cmd_reader_cancelled = setup_mdb_cmd ("Confirm cancellation", 3741 READER_CANCELLED, 3742 NULL); 3743 cmd_deny_vend = setup_mdb_cmd ("Deny Vend", 3744 READER_VEND_DENIED, 3745 NULL); 3746 endSession = setup_mdb_cmd ("End Session", 3747 READER_END_SESSION, 3748 NULL); 3749 cmd_revalue_approved = setup_mdb_cmd ("Reader Approve Revalue", 3750 READER_REVALUE_APPROVED, 3751 NULL); 3752 3753 cmd_revalue_amount = setup_mdb_cmd ("Send Revalue Limit Amount", 3754 READER_REVALUE_LIMIT, 3755 READER_REVALUE_LIMIT_AMOUNT); 3756 3757 cmd_reader_NACK = setup_mdb_cmd ("Reader NACK", 3758 READER_NACK, 3759 NULL); 3760 3761 cmd_reader_display_sold_out = setup_mdb_cmd ("Display Sold Out", 3762 READER_DISPLAY_REQUEST, 3763 READER_DISPLAY_REQUEST_TIME 3764 READER_DISPLAY_SOLD_OUT); 3765 3766 cmd_reader_display_internal_error = setup_mdb_cmd ( 3767 "Display Communication Error", 3768 READER_DISPLAY_REQUEST, 3769 READER_DISPLAY_REQUEST_TIME 3770 READER_DISPLAY_INTERNAL_ERROR); 3771 3772 cmd_reader_display_backend_not_reachable = setup_mdb_cmd ( 3773 "Display Backend not reachable", 3774 READER_DISPLAY_REQUEST, 3775 READER_DISPLAY_REQUEST_TIME 3776 READER_DISPLAY_BACKEND_NOT_REACHABLE); 3777 ret = GNUNET_PROGRAM_run (TALER_MDB_project_data (), 3778 argc, 3779 argv, 3780 "taler-mdb", 3781 "This is an application for snack machines to pay with GNU Taler via NFC.\n", 3782 options, 3783 &run, 3784 NULL); 3785 if (! disable_tty) 3786 { 3787 if (have_tty) 3788 { 3789 /* Restore previous TTY settings */ 3790 if (0 != tcsetattr (STDIN_FILENO, 3791 TCSANOW, 3792 &tty_opts_backup)) 3793 fprintf (stderr, 3794 "Failed to restore terminal discipline\n"); 3795 } 3796 } 3797 if (GNUNET_NO == ret) 3798 return 0; 3799 if (GNUNET_OK != ret) 3800 return 1; 3801 return global_ret; 3802 }