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