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