taler-mdb

GNU Taler Extensions and Integrations
Log | Files | Refs | Submodules | README | LICENSE

commit e70905eea22e5bd5dd21607d73eeaaf3547aa677
parent d0eb9b58e045b1fe08071b471b5f335f26122878
Author: Boss Marco <bossm8@students.bfh.ch>
Date:   Tue, 10 Dec 2019 14:59:57 +0100

delete autosave

Diffstat:
Dsrc/main.c.autosave | 2599-------------------------------------------------------------------------------
1 file changed, 0 insertions(+), 2599 deletions(-)

diff --git a/src/main.c.autosave b/src/main.c.autosave @@ -1,2599 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2019 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more -details. - - You should have received a copy of the GNU General Public License -along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** -* @file main.c -* @brief main functionality of the application -* @author Boss Marco -* @author Christian Grothoff -* @author Dominik Hofer -* -* TODO: -* - comment code (Boss) -*/ -#include "config.h" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/socket.h> -#if HAVE_SYS_UN_H -#include <sys/un.h> -#endif -#if HAVE_NETINET_IN_H -#include <netinet/in.h> -#endif -#if HAVE_NETINET_IP_H -#include <netinet/ip.h> /* superset of previous */ -#endif -#include <sys/stat.h> -#include <sys/types.h> -#include <errno.h> -#include <termios.h> -#include <nfc/nfc.h> -#include <microhttpd.h> -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <taler/taler_json_lib.h> -#include <taler/taler_merchant_service.h> -#if HAVE_QRENCODE_H -#include <qrencode.h> -#endif -#include <sys/mman.h> -#include <sys/ioctl.h> -#include <fcntl.h> -/* for adafruit pitft display */ -#include <linux/fb.h> - -/* Constants */ -#define MAX_SIZE_RX_BUFFER 256 - -/* Constants */ -#define MAX_SIZE_TX_BUFFER 256 - -/** - * Disable i18n support. - */ -#define _(s) (s) - -#define BACKEND_POLL_TIMEOUT GNUNET_TIME_UNIT_MINUTES - -#define NFC_FAILURE_RETRY_FREQ GNUNET_TIME_UNIT_SECONDS - -/** - * How long do we wait at most for an ACK from MDB? - */ -#define MAX_ACK_LATENCY GNUNET_TIME_UNIT_SECONDS - -/** - * Timeout in milliseconds for libnfc operations. - */ -#define NFC_TIMEOUT 500 - -#define MAX_HTTP_RETRY_FREQ GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MILLISECONDS, 500) - -#define MAX_HTTP_RETRY_FREQ GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MILLISECONDS, 500) - -/** - * How long could it take at most for us to notify the Taler merchant - * backend to grant a refund to a user if dispensing the product - * failed? (Very conservative value here, for vending machines brewing - * coffee or something complex that could fail.) - */ -#define MAX_REFUND_DELAY GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MINUTES, 5) - -/** - * Code returned by libnfc in case of success. - */ -#define APDU_SUCCESS "\x90\x00" - -/** - * Code returned by libnfc in case Taler wallet is not installed. - */ -#define APDU_NOT_FOUND "\x6a\x82" - -/* upper and lower bounds for mifare targets uid length */ -/** - * Upper lenght of the uid for a valid MIFARE target - */ -#define UID_LEN_UPPER 7 - -/** - * Lower lenght of the uid for a valid MIFARE target - */ -#define UID_LEN_LOWER 4 - -/* Commands for communication via MDB/ICP */ -/* VMC commands */ -#define VMC_CMD_START 0x02 -#define VMC_CMD_END 0x03 - -/** - * Acknowledgement - */ -#define VMC_ACKN 0x00 - -/** - * Request for configuration. - */ -#define VMC_CONF 0x11 - -/** - * Machine is polling for something. - */ -#define VMC_POLL 0x12 - -/** - * Vending, with sub-command. - */ -#define VMC_VEND 0x13 -#define VMC_VEND_REQUEST 0x00 -#define VMC_VEND_CANCEL 0x01 -#define VMC_VEND_SUCCESS 0x02 -#define VMC_VEND_FAILURE 0x03 -#define VMC_VEND_SESSION_COMPLETE 0x04 - -/** - * Commands for the reader (our device). - */ -#define VMC_READER 0x14 -#define VMC_READER_DISABLE 0x00 -#define VMC_READER_ENABLE 0x01 -#define VMC_READER_CANCEL 0x02 - -#define VMC_REQUEST_ID 0x17 - -/** - * Out of sequence. - */ -#define VMC_OOSQ 0xB0 - -/** - * Request to retransmit last command. - */ -#define VMC_RETR 0xAA - -/* Reader commands */ - -/* Config Data */ -/* Refer to the mdb interface specifications v4.2 p.288 */ -#define READER_CONFIG "01" -#define READER_FEATURE_LEVEL "01" -#define READER_COUNTRYCODE "1978" -#define READER_SCALE_FACTOR "0A" -#define READER_DECIMAL_PLACES "02" -#define READER_MAX_RESPONSE_TIME "FF" -#define READER_MISC_OPTIONS "0C" - -/* Session Commands */ -/* Refer to the mdb interface specifications v4.2 p.131 */ -#define READER_BEGIN_SESSION "03" -#define READER_FUNDS_AVAILABLE "FFFF" -#define READER_END_SESSION "07" - -/* Vend Commands */ -/* Refer to the mdb interface specifications v4.2 p.134 */ -#define READER_VEND_APPROVE "05" -#define READER_VEND_AMOUNT "0001" -#define READER_VEND_DENIED "06" - -/* Cancelled Command */ -#define READER_CANCELLED "08" - -/* Unused reader commands */ -#define READER_DISPLAY_REQUEST "02" -#define READER_SESSION_CANCEL_REQUEST "04" -#define READER_REVALUE_APPROVED "0D" -#define READER_REVALUE_DENIED "0E" - -/** - * Datatype for mdb subcommands and data - */ -struct MdbBlock -{ - uint8_t *bin; - - size_t bin_size; -}; - - -/** - * Datatype for mdb command - */ -struct MdbCommand -{ - const char *name; - - struct MdbBlock cmd; - - struct MdbBlock data; -}; - - -/** - * Struct holding the information for a product to sell - */ -struct Product -{ - /** - * The price for the product - */ - struct TALER_Amount price; - - /** - * Description (or name) of the product - */ - char *description; - - /** - * Number of the product in the vending machine - */ - unsigned long long number; - - /** - * Key for the product (optional, needed to test the application without vending machine) - */ - char key; -}; - - -/** - * Handle for a payment - */ -struct PaymentActivity -{ - /** - * Handle to a PUT /proposal operation - */ - struct TALER_MERCHANT_ProposalOperation *po; - - /** - * Handle for a /check-payment operation. - */ - struct TALER_MERCHANT_CheckPaymentOperation *cpo; - - /** - * Order ID for pending order - */ - char *order_id; - - /** - * URI needed to pay the pending order - */ - char *taler_pay_uri; - - /** - * NFC device - */ - nfc_device *pnd; - - /** - * Target to send the data via NFC - */ - nfc_target nt; - - /** - * Current task running - */ - struct GNUNET_SCHEDULER_Task *task; - - /** - * Tasks delayed - */ - struct GNUNET_SCHEDULER_Task *delay_task; - - /** - * Payment checking delayed task - */ - struct GNUNET_SCHEDULER_Task *delay_pay_task; - - /** - * What is the price the user is paying? - */ - struct TALER_Amount amount; - - /** - * Member to see if the wallet already received a uri - * GNUNET_YES, GNUNET_NO - * If yes, tunneling can be offered to the wallet - */ - int wallet_has_uri; - - /** - * Set to #GNUNET_YES once the product has been paid - * (and we are in the process of yielding the product). - */ - int paid; -}; - - -/** - * Data structures associated with the MDB. - */ -struct MdbHandle -{ - - uint8_t rxBuffer[MAX_SIZE_RX_BUFFER]; - - uint8_t txBuffer[MAX_SIZE_TX_BUFFER]; - - struct GNUNET_SCHEDULER_Task *rtask; - - struct GNUNET_SCHEDULER_Task *wtask; - - const struct MdbCommand *cmd; - - const struct MdbCommand *last_cmd; - - size_t rx_off; - - /** - * Current write offset in @e txBuffer. - */ - size_t tx_off; - - /** - * Number of bytes in @e txBuffer with the serialized data of the - * @e last_cmd. - */ - size_t tx_len; - - struct GNUNET_TIME_Absolute ack_timeout; - - struct termios uart_opts_backup; - - int session_running; - - int uartfd; - -}; - - -/** - * Handle for the Framebuffer device - */ -struct Display -{ - /** - * File descriptor for the screen - */ - int devicefd; - - /** - * File descriptor to set backlight information - */ - int backlightfd; - - /** - * The display memory to set the pixel information - */ - uint16_t *memory; - - /** - * Original screen information - */ - struct fb_var_screeninfo orig_vinfo; - - /** - * Variable screen information (color depth ...) - */ - struct fb_var_screeninfo var_info; - - /** - * Fixed screen informtaion - */ - struct fb_fix_screeninfo fix_info; -}; - - -/** - * DLL of pending refund operations. - */ -struct Refund -{ - /** - * DLL next pointer. - */ - struct Refund *next; - - /** - * DLL prev pointer. - */ - struct Refund *prev; - - /** - * Handle to the ongoing operation. - */ - struct TALER_MERCHANT_RefundIncreaseOperation *rio; - -}; - - -/** - * DLL head of refunds. - */ -static struct Refund *refund_head; - -/** - * DLL tail of refunds. - */ -static struct Refund *refund_tail; - -/** - * NFC context used by the NFC reader - */ -static nfc_context *context; - -/** - * Global return value - */ -static int global_ret; - -/** - * Flag set to remember that we are in shutdown. - */ -static int in_shutdown; - -/** - * Refenence to the keyboard task - */ -static struct GNUNET_SCHEDULER_Task *keyboard_task; - -/** - * Curl context for communication with taler backend - */ -static struct GNUNET_CURL_Context *ctx; - -/** - * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). - */ -static struct GNUNET_CURL_RescheduleContext *rc; - -/** - * Currency read from configuration file - */ -static char *currency; - -/** - * Taler Backend url read from configuration file - */ -static char *backendBaseUrl; - -/** - * Taler fulfillment url read from configuration file - */ -static char *fulfillmentUrl; - -/** - * Fulfillment message to display after successfull payment, read from configuration file - */ -static char *fulfillmentMsg; - -/** - * Authorization for the taler backend - */ -static char *authorization; - -/** - * Handle for the payment - */ -static struct PaymentActivity *payment_activity; - -/** - * Products read from configuration file - */ -static struct Product *products; - -/** - * Amount of products - */ -static unsigned int products_length; - -/** - * Data associated with the MDB session. - */ -static struct MdbHandle mdb; - -/** - * MDB response to the request for configuration. - */ -static struct MdbCommand readerConfigData; - -/** - * Ask MDB to begin session (with "infinite" money) - */ -static struct MdbCommand beginSession; - -/** - * Refuse vending request (payment failed) - */ -static struct MdbCommand denyVend; - -/** - * Approve vending request (payment succeeded) - */ -static struct MdbCommand approveVend; - -/** - * Confirm cancellation by machine. - */ -static struct MdbCommand readerCancelled; - -/** - * Terminate session. - */ -static struct MdbCommand endSession; - -/** - * Name of the framebuffer device (i.e. /dev/fb1). - */ -static char *framebuffer_device_filename; - -/** - * Name of the backlight file of @e framebuffer_device_filename (i.e. /sys/class/backlight/soc:backlight/brightness). - */ -static char *framebuffer_backlight_filename; - -/** - * Global option '-i' to invert backlight on/off values - */ -static int backlight_invert; - -/** - * Standard backlight on value - */ -static char backlight_on = '1'; - -/** - * Standard backlight off value - */ -static char backlight_off = '0'; - -/** - * Name of the UART device with the MDB (i.e. /dev/ttyAMA0). - */ -static char *uart_device_filename; - -/** - * Global option '-d' to disable MDB set. - */ -static int disable_mdb; - -/** - * Global option '-t' to disable stdin / terminal. - */ -static int disable_tty; - -/** - * Taler wallet application identifier - */ -static const uint8_t taler_aid[] = { 0xF0, 0x00, 0x54, 0x41, 0x4c, 0x45, 0x52 }; - -/** - * NFC select file command to select wallet aid - */ -static const uint8_t select_file[] = { 0x00, 0xA4, 0x04, 0x00, 0x07 }; - -/** - * NFC put command to send data to the wallet - */ -static const uint8_t put_data[] = { 0x00, 0xDA, 0x01, 0x00, 0x7c, 0x01 }; - -#if FUTURE_FEATURES -/* tunneling */ -static const uint8_t get_data[] = { 0x00, 0xCA, 0x01, 0x00, 0x00, 0x00 }; -#endif - -/** - * Handle for the framebuffer device - */ -static struct Display qrDisplay; - - -#if HAVE_QRENCODE_H -#include <qrencode.h> - -/** - * @brief Create the QR code to pay and display it on screen - * - * @param uri what text to show in the QR code - */ -static void -show_qrcode (const char *uri) -{ - QRinput *qri; - QRcode *qrc; - unsigned int size; - char *upper; - char *base; - size_t xOff; - size_t yOff; - const char *dddash; - - if (0 > qrDisplay.devicefd) - return; /* no display, no dice */ - /* find the fourth '/' in the payto://pay/hostname/-uri */ - dddash = strchr (uri, '/'); - if (NULL != dddash) - dddash = strchr (dddash + 1, '/'); - if (NULL != dddash) - dddash = strchr (dddash + 1, '/'); - if (NULL != dddash) - dddash = strchr (dddash + 1, '/'); - if (NULL == dddash) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "taler://pay/-URI malformed: `%s'\n", - uri); - return; - } - - qri = QRinput_new2 (0, QR_ECLEVEL_L); - if (NULL == qri) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "QRinput_new2"); - return; - } -#if UPPER_SUPPORTED - /* convert all characters until the fourth '/' to upper - case. The rest _should_ be upper case in a NICE setup, - but we can't warrant it and must not touch those. */ - base = GNUNET_strndup (uri, - dddash - uri); - - GNUNET_STRINGS_utf8_toupper (base, base); - GNUNET_asprintf (&upper, - "%s%s", - base, - dddash); - GNUNET_free (base); -#else - (void) dddash; - upper = GNUNET_strdup (uri); -#endif - /* first try encoding as uppercase-only alpha-numerical - QR code (much smaller encoding); if that fails, also - try using binary encoding (in case nick contains - special characters). */ - if ( (0 != - QRinput_append (qri, - QR_MODE_AN, - strlen (upper), - (unsigned char *) upper)) && - (0 != - QRinput_append (qri, - QR_MODE_8, - strlen (upper), - (unsigned char *) upper))) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "QRinput_append"); - GNUNET_free (upper); - return; - } - GNUNET_free (upper); - qrc = QRcode_encodeInput (qri); - if (NULL == qrc) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "QRcode_encodeInput"); - QRinput_free (qri); - return; - } - - /* +8 for 4-pixels border */ - size = GNUNET_MIN (qrDisplay.var_info.xres, - qrDisplay.var_info.yres); - unsigned int nwidth = qrc->width + 8; /* 4 pixel border */ - xOff = 4 * size / nwidth; - yOff = 4 * size / nwidth; - if (qrDisplay.var_info.xres < qrDisplay.var_info.yres) - yOff += (qrDisplay.var_info.yres - qrDisplay.var_info.xres) / 2; - else - xOff += (qrDisplay.var_info.xres - qrDisplay.var_info.yres) / 2; - for (unsigned int x = 0; x < qrDisplay.var_info.xres - 2 * xOff; x++) - for (unsigned int y = 0; y < qrDisplay.var_info.yres - 2 * yOff; y++) - { - unsigned int xoff = x * nwidth / size; - unsigned int yoff = y * nwidth / size; - unsigned int off = xoff + yoff * qrc->width; - if ( (xoff >= (unsigned) qrc->width) || - (yoff >= (unsigned) qrc->width) ) - continue; - qrDisplay.memory[(y + yOff) * qrDisplay.var_info.xres + (x + xOff)] = - (0 == (qrc->data[off] & 1)) ? 0xFFFF : 0x0000; - } - - QRcode_free (qrc); - QRinput_free (qri); - - if (0 < qrDisplay.backlightfd) - (void) write (qrDisplay.backlightfd, &backlight_on, 1); -} - - -#endif - - -static void -run_mdb_event_loop (void); - - -/** - * @brief Cleanup all the data when a order has succeeded or got cancelled - * @param pa the payment activity to clean up - */ -static void -cleanup_payment (struct PaymentActivity *pa) -{ - if (NULL != pa->pnd) - { - nfc_abort_command (pa->pnd); - nfc_close (pa->pnd); - } - if (NULL != pa->po) - TALER_MERCHANT_proposal_cancel (pa->po); - if (NULL != pa->cpo) - TALER_MERCHANT_check_payment_cancel (pa->cpo); - GNUNET_CURL_gnunet_scheduler_reschedule (&rc); - if (NULL != pa->task) - GNUNET_SCHEDULER_cancel (pa->task); - if (NULL != pa->delay_task) - GNUNET_SCHEDULER_cancel (pa->delay_task); - if (NULL != pa->delay_pay_task) - GNUNET_SCHEDULER_cancel (pa->delay_pay_task); - if (NULL != pa->taler_pay_uri) - { -#if HAVE_QRENCODE_H - if (NULL != qrDisplay.memory) - memset (qrDisplay.memory, - 0xFF, - qrDisplay.var_info.xres * qrDisplay.var_info.yres - * sizeof (uint16_t)); - if (0 < qrDisplay.backlightfd) - (void) write (qrDisplay.backlightfd, &backlight_off, 1); -#endif - GNUNET_free (pa->taler_pay_uri); - } - GNUNET_free_non_null (pa->order_id); - GNUNET_free (pa); -} - - -/** - * @brief Shutdown the mdb communication tasks - */ -static void -mdb_shutdown () -{ - if (NULL != mdb.rtask) - { - GNUNET_SCHEDULER_cancel (mdb.rtask); - mdb.rtask = NULL; - } - if (NULL != mdb.wtask) - { - GNUNET_SCHEDULER_cancel (mdb.wtask); - mdb.wtask = NULL; - } - if (disable_mdb) - return; - /* restore UART */ - if (0 != tcsetattr (mdb.uartfd, - TCSAFLUSH, - &mdb.uart_opts_backup)) - { - printf ("Failed to restore uart discipline\n"); - global_ret = EXIT_FAILURE; - } - if (-1 != mdb.uartfd) - { - (void) close (mdb.uartfd); - mdb.uartfd = -1; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Shutdown complete (including MDB)\n"); -} - - -/** - * @brief Shutdown the application. - * - * @param cls closure - */ -static void -shutdown_task (void *cls) -{ - struct Refund *r; - - (void) cls; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Shutdown initiated\n"); - while (NULL != (r = refund_head)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Pending refund operation aborted due to shutdown\n"); - GNUNET_CONTAINER_DLL_remove (refund_head, - refund_tail, - r); - TALER_MERCHANT_refund_increase_cancel (r->rio); - GNUNET_free (r); - } - if (NULL != context) - { - nfc_exit (context); - context = NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "NFC down\n"); - if (NULL != payment_activity) - { - cleanup_payment (payment_activity); - payment_activity = NULL; - } - if (NULL != keyboard_task) - { - GNUNET_SCHEDULER_cancel (keyboard_task); - keyboard_task = NULL; - } - /* last ditch saying nicely goodbye to MDB */ - in_shutdown = GNUNET_YES; - mdb.cmd = &endSession; - run_mdb_event_loop (); - if (NULL != ctx) - { - GNUNET_CURL_fini (ctx); - ctx = NULL; - } - if (NULL != rc) - { - GNUNET_CURL_gnunet_rc_destroy (rc); - rc = NULL; - } - - if (NULL != qrDisplay.memory) - { - /* free the display data */ - munmap (qrDisplay.memory, - qrDisplay.fix_info.smem_len); - qrDisplay.memory = NULL; - /* reset original state */ - if (0 > ioctl (qrDisplay.devicefd, - FBIOPUT_VSCREENINFO, - &qrDisplay.orig_vinfo)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "failed to reset originial display state\n"); - } - /* close device */ - close (qrDisplay.devicefd); - qrDisplay.devicefd = -1; - if (0 < qrDisplay.backlightfd) - close (qrDisplay.backlightfd); - qrDisplay.backlightfd = -1; - } - if (NULL != products) - { - for (unsigned int i = 0; i < products_length; i++) - GNUNET_free (products[i].description); - GNUNET_array_grow (products, - products_length, - 0); - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Shutdown complete (except for MDB)\n"); -} - - -static void -check_payment_again (void *cls); - - -static void -connect_target (void *cls); - - -static void -wallet_select_aid (void *cls); - - -/** - * @brief Transmit the pay uri from taler to the wallet application via NFC - * - * @param cls closure - */ -static void -wallet_transmit_uri (void *cls) -{ - struct PaymentActivity *pa = cls; - uint8_t response[] = { 0x00, 0x00 }; - size_t slen = strlen (pa->taler_pay_uri); - uint8_t message[sizeof (put_data) + slen]; - - pa->delay_task = NULL; - memcpy (message, put_data, sizeof (put_data)); - memcpy (&message[sizeof (put_data)], pa->taler_pay_uri, slen); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Sending 'PUT DATA' command for `%s' to wallet\n", - pa->taler_pay_uri); - if (0 > nfc_initiator_transceive_bytes (pa->pnd, - message, - sizeof (message), - response, - sizeof(response), - NFC_TIMEOUT)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to send command via NFC\n"); - pa->task = GNUNET_SCHEDULER_add_now (&connect_target, - pa); - return; - } - if (0 != memcmp (response, - APDU_SUCCESS, - sizeof (response))) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "'PUT DATA' command transmission failed, return code: %x%x\n", - response[0], - response[1]); - pa->task = GNUNET_SCHEDULER_add_now (&connect_target, - pa); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "'PUT DATA' command sent successfully via NFC\n"); - pa->wallet_has_uri = GNUNET_YES; - /* FIXME: or just offer Internet service here? */ - pa->delay_task = GNUNET_SCHEDULER_add_delayed (MAX_HTTP_RETRY_FREQ, - &wallet_transmit_uri, - pa); -} - - -/** - * @brief Select the taler wallet app via NFC - * - * @param cls closure - */ -static void -wallet_select_aid (void *cls) -{ - struct PaymentActivity *pa = cls; - uint8_t response[] = { 0x00, 0x00 }; - uint8_t message[sizeof(select_file) + sizeof(taler_aid)]; - - pa->task = NULL; - memcpy (message, select_file, sizeof (select_file)); - memcpy (&message[sizeof (select_file)], taler_aid, sizeof (taler_aid)); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Trying to find Taler wallet on NFC\n"); - if (0 > nfc_initiator_transceive_bytes (pa->pnd, - message, - sizeof (message), - response, - sizeof (response), - NFC_TIMEOUT)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Failed to transceive with NFC app, trying to find another NFC client\n"); - pa->task = GNUNET_SCHEDULER_add_now (&connect_target, - pa); - return; - } - if (0 == memcmp (response, - APDU_SUCCESS, - sizeof (response))) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Taler wallet found over NFC\n"); - pa->delay_task = GNUNET_SCHEDULER_add_now (&wallet_transmit_uri, - pa); - return; - } - if (0 == memcmp (response, - APDU_NOT_FOUND, - sizeof (response))) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Taler wallet NOT found on this device\n"); - pa->task = GNUNET_SCHEDULER_add_now (&connect_target, - pa); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "AID selection failure, return code: %x%x, trying to find another NFC client\n", - response[0], - response[1]); - pa->task = GNUNET_SCHEDULER_add_delayed (NFC_FAILURE_RETRY_FREQ, - &connect_target, - pa); -} - - -/** - * @brief Connect the NFC reader with a compatible NFC target - * - * @param cls closure - */ -static void -connect_target (void *cls) -{ - struct PaymentActivity *pa = cls; - const nfc_modulation nmMifare = { - .nmt = NMT_ISO14443A, - .nbr = NBR_212, - }; - - pa->task = NULL; - pa->nt.nti.nai.szUidLen = 0; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Trying to find NFC client\n"); - if (0 > nfc_initiator_poll_target (pa->pnd, - &nmMifare, - 1, - 0x01, - 0x01, - &pa->nt)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Failed to connect to NFC target\n"); - } - else if ( (pa->nt.nti.nai.szUidLen > UID_LEN_UPPER) || - (pa->nt.nti.nai.szUidLen < UID_LEN_LOWER) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to connect, wrong NFC modulation\n"); - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Found NFC client\n"); - pa->task = GNUNET_SCHEDULER_add_now (&wallet_select_aid, - pa); - return; - } - pa->task = GNUNET_SCHEDULER_add_delayed (NFC_FAILURE_RETRY_FREQ, - &connect_target, - pa); -} - - -/** - * @brief Open the NFC reader. - * - * @param cls closure - */ -static void -open_nfc_reader (void *cls) -{ - struct PaymentActivity *pa = cls; - - pa->task = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Trying to open NFC device\n"); - pa->pnd = nfc_open (context, NULL); - if (NULL == pa->pnd) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Payment inititation: Unable to open NFC device\n"); - pa->task = GNUNET_SCHEDULER_add_delayed (NFC_FAILURE_RETRY_FREQ, - &open_nfc_reader, - pa); - return; - } - if (0 > nfc_initiator_init (pa->pnd)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to initialize NFC device: %s\n", - nfc_strerror (pa->pnd)); - cleanup_payment (pa); - GNUNET_assert (payment_activity == pa); - payment_activity = NULL; - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "NFC in operation %s / %s\n", - nfc_device_get_name (pa->pnd), - nfc_device_get_connstring (pa->pnd)); - pa->task = GNUNET_SCHEDULER_add_now (&connect_target, - pa); -} - - -static void -start_read_keyboard (void); - - -/** - * @brief Callback to process a GET /check-payment request - * - * @param cls closure - * @param http_status HTTP status code for this request - * @param obj raw response body - * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not - * settled, #GNUNET_SYSERR on error - * (note that refunded payments are returned as paid!) - * @param refunded #GNUNET_YES if there is at least on refund on this payment, - * #GNUNET_NO if refunded, #GNUNET_SYSERR or error - * @param refunded_amount amount that was refunded, NULL if there - * was no refund - * @param taler_pay_uri the URI that instructs the wallets to process - * the payment - */ -static void -check_payment_cb (void *cls, - unsigned int http_status, - const json_t *obj, - int paid, - int refunded, - struct TALER_Amount *refund_amount, - const char *taler_pay_uri) -{ - struct PaymentActivity *pa = cls; - - (void) refunded; - (void) refund_amount; - (void) obj; - GNUNET_assert (payment_activity == pa); - pa->cpo = NULL; - if (MHD_HTTP_OK != http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Backend request to /check-payment failed: %u\n", - http_status); - mdb.cmd = &denyVend; - run_mdb_event_loop (); - cleanup_payment (pa); - GNUNET_assert (payment_activity == pa); - payment_activity = NULL; - return; - } - - if (paid) - { - mdb.cmd = &approveVend; - payment_activity->paid = GNUNET_YES; - run_mdb_event_loop (); - if ((disable_mdb) && (! disable_tty)) - { - GNUNET_SCHEDULER_cancel (keyboard_task); - keyboard_task = NULL; - start_read_keyboard (); - } - return; - } - else - { - /* Start to check for payment. Note that we do this even before - we talked successfully to the wallet via NFC because we MAY show the - QR code in the future and in that case the payment may happen - anytime even before the NFC communication succeeds. */ - if ( (NULL == pa->cpo) && - (NULL == pa->delay_pay_task) ) - { - pa->delay_pay_task = GNUNET_SCHEDULER_add_delayed (MAX_HTTP_RETRY_FREQ, - &check_payment_again, - pa); - } - } - if (NULL == pa->taler_pay_uri) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Trying to talk to wallet to give it pay URI `%s'\n", - taler_pay_uri); - GNUNET_assert (NULL == pa->pnd); - pa->taler_pay_uri = GNUNET_strdup (taler_pay_uri); -#if HAVE_QRENCODE_H - show_qrcode (taler_pay_uri); -#endif - pa->task = GNUNET_SCHEDULER_add_now (&open_nfc_reader, - pa); - } -} - - -/** - * @brief Check the payment status again - * - * @param cls closure - */ -static void -check_payment_again (void *cls) -{ - struct PaymentActivity *pa = cls; - - pa->delay_pay_task = NULL; - GNUNET_assert (NULL == pa->cpo); - pa->cpo = TALER_MERCHANT_check_payment (ctx, - backendBaseUrl, - pa->order_id, - NULL /* snack machine, no Web crap */, - BACKEND_POLL_TIMEOUT, - &check_payment_cb, - pa); -} - - -/** - * @brief Callback for a PUT /order request - * - * @param cls closure - * @param http_status HTTP status code for this request - * @param ec Taler error code - * @param obj raw response body - * @param order_id order ID of the order created - */ -static void -proposal_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - const json_t *obj, - const char *order_id) -{ - (void) obj; - struct PaymentActivity *pa = cls; - - pa->po = NULL; - if (MHD_HTTP_OK != http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to setup order with backend: %u/%d\n", - http_status, - (int) ec); - mdb.cmd = &denyVend; - run_mdb_event_loop (); - cleanup_payment (pa); - GNUNET_assert (payment_activity == pa); - payment_activity = NULL; - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Backend successfully created order `%s'\n", - order_id); - pa->order_id = GNUNET_strdup (order_id); - pa->cpo = TALER_MERCHANT_check_payment (ctx, - backendBaseUrl, - pa->order_id, - NULL /* snack machine, no Web crap */, - GNUNET_TIME_UNIT_ZERO, - &check_payment_cb, - pa); -} - - -/** - * @brief Launch a new order - * - * @param product information for product to sell - * @return payment activity for the order, NULL on failure - */ -static struct PaymentActivity * -launch_payment (const struct Product *product) -{ - struct PaymentActivity *pa; - json_t *orderReq; - char *fulflmntUrl; - struct GNUNET_ShortHashCode uuid; - char *uuid_s; - - /* We need to ensure that every fulfillment URL is unique; - most easily done by adding a random nonce, as we may - not have a reliable counter or clock on the machine */ - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, - &uuid, - sizeof (uuid)); - uuid_s = GNUNET_STRINGS_data_to_string_alloc (&uuid, - sizeof (uuid)); - /* create the fulfillment url, e.g. "taler://fulfillment-success/Enjoy+your+ice+cream!"; */ - GNUNET_asprintf (&fulflmntUrl, - "%s%s%s#%s", - fulfillmentUrl, - fulfillmentMsg, - product->description, - uuid_s); - GNUNET_free (uuid_s); - /* create the json object for the order request */ - orderReq = json_pack ("{ s:s, s:o, s:s, s:o }", - "summary", product->description, - "amount", TALER_JSON_from_amount (&product->price), - "fulfillment_url", fulflmntUrl - ,"auto_refund", GNUNET_JSON_from_time_rel ( - MAX_REFUND_DELAY) - ); - GNUNET_free (fulflmntUrl); - if (NULL == orderReq) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "json_pack failed (out of memory?)\n"); - return NULL; - } - pa = GNUNET_new (struct PaymentActivity); - pa->amount = product->price; - pa->po = TALER_MERCHANT_order_put (ctx, - backendBaseUrl, - orderReq, - &proposal_cb, - pa); - json_decref (orderReq); - if (NULL == pa->po) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "TALER_MERCHANT_order_put failed (out of memory?)\n"); - GNUNET_free (pa); - return NULL; - } - return pa; -} - - -/** - * @brief Vending successful, conclude payment activity. - */ -static void -vend_success () -{ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "MDB vend success received\n"); - GNUNET_break (NULL != payment_activity); - if (NULL != payment_activity) - { - cleanup_payment (payment_activity); - payment_activity = NULL; - } -} - - -/** - * @brief Callback to process a POST /refund request - * - * @param cls closure - * @param http_status HTTP status code for this request - * @param ec taler-specific error code - * @param obj the response body - */ -static void -refund_complete_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - const json_t *obj) -{ - struct Refund *r = cls; - - r->rio = NULL; - if (MHD_HTTP_OK != http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to grant consumer refund: %u/%d\n", - http_status, - ec); - } - GNUNET_CONTAINER_DLL_remove (refund_head, - refund_tail, - r); - GNUNET_free (r); -} - - -/** - * @brief Vending failed, provide refund. - */ -static void -vend_failure () -{ - struct Refund *r; - - if (NULL == payment_activity) - { - GNUNET_break (0); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received MDB vend failure, refunding customer\n"); - mdb.cmd = &endSession; - mdb.session_running = GNUNET_NO; - r = GNUNET_new (struct Refund); - r->rio = TALER_MERCHANT_refund_increase (ctx, - backendBaseUrl, - payment_activity->order_id, - &payment_activity->amount, - "failed to dispense product", - &refund_complete_cb, - r); - if (NULL == r->rio) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to launch refund interaction with the merchant backend!\n"); - GNUNET_free (r); - } - else - { - GNUNET_CONTAINER_DLL_insert (refund_head, - refund_tail, - r); - } - if (NULL != payment_activity) - { - cleanup_payment (payment_activity); - payment_activity = NULL; - } -} - - -/** - * @brief Read the character from stdin and activate the selected task - * - * @param cls closure - */ -static void -read_keyboard_command (void *cls) -{ - (void) cls; - int input; - - keyboard_task = NULL; - input = getchar (); - if ( (EOF == input) || - ('x' == (char) input) ) - { - GNUNET_SCHEDULER_shutdown (); - return; - } - if (NULL != payment_activity) - { - switch ((char) input) - { - case 'c': - if (GNUNET_NO == payment_activity->paid) - { - mdb.cmd = &denyVend; - } - else - { - mdb.cmd = &endSession; - mdb.session_running = GNUNET_NO; - } - run_mdb_event_loop (); - cleanup_payment (payment_activity); - payment_activity = NULL; - break; - case 'a': - payment_activity->paid = GNUNET_YES; - mdb.cmd = &approveVend; - run_mdb_event_loop (); - break; - case 'n': - if (disable_mdb) - { - vend_failure (); - } - else - { - fprintf (stderr, - "Cannot fail to vend at this time, waiting for payment\n"); - } - break; - case 'y': - if (disable_mdb) - { - vend_success (); - } - else - { - fprintf (stderr, - "Cannot succeed to vend at this time, waiting for payment\n"); - } - break; - default: - fprintf (stderr, - "Unknown command `%c'\n", - input); - break; - } - start_read_keyboard (); - return; - } - if (NULL != payment_activity) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Purchase activity already pending\n"); - start_read_keyboard (); - return; - } - for (unsigned int i = 0; i < products_length; i++) - if (((char) input) == products[i].key) - { - payment_activity = launch_payment (&products[i]); - start_read_keyboard (); - return; - } - fprintf (stderr, - "Unknown command '%c'\n", - (char) input); - start_read_keyboard (); -} - - -/** - * @brief Wait for a keyboard input - */ -static void -start_read_keyboard () -{ - static struct GNUNET_DISK_FileHandle fh = { STDIN_FILENO }; - - GNUNET_assert (NULL == keyboard_task); - if (NULL == payment_activity) - { - for (unsigned int i = 0; i < products_length; i++) - printf ("'%c' to buy %s\n", - products[i].key, - products[i].description); - } - else - { - if (GNUNET_NO == payment_activity->paid) - { - printf ("'a' to fake payment for the last purchase\n" - "'c' to cancel last purchase\n"); - } - else - { - if (disable_mdb) - printf ("'y' to simulate product successfully dispensed\n" - "'n' to simulate product failed to be dispensed\n"); - } - } - printf ("'x' to quit\n"); - printf ("Waiting for keyboard input\n"); - keyboard_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, - &fh, - &read_keyboard_command, - NULL); -} - - -/** - * @brief Send data to the vmc via the uart bus - * - * @param cls closure - */ -static void -write_mdb_command (void *cls) -{ - int did_write = 0; - - (void) cls; - mdb.wtask = NULL; - - if (disable_mdb) - { - /* check if there is a cmd to send and overwrite last command */ - if (NULL == mdb.cmd) - return; - mdb.last_cmd = mdb.cmd; - mdb.cmd = NULL; - run_mdb_event_loop (); - return; - } - /* if command was sent completely send the rest */ - if (mdb.tx_off < mdb.tx_len) - { - ssize_t ret = write (mdb.uartfd, - &mdb.txBuffer[mdb.tx_off], - mdb.tx_len - mdb.tx_off); - - did_write = 1; - if (-1 == ret) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "write", - uart_device_filename); - if (in_shutdown) - mdb_shutdown (); - else - GNUNET_SCHEDULER_shutdown (); - return; - } - mdb.tx_off += ret; - /* if command was sent sucessfully start the timer for ACK timeout */ - if ( (ret > 0) && - (mdb.tx_off == mdb.tx_len) ) - mdb.ack_timeout = GNUNET_TIME_relative_to_absolute (MAX_ACK_LATENCY); - } - /* ongoing write incomplete, continue later */ - if (mdb.tx_off < mdb.tx_len) - { - run_mdb_event_loop (); - return; - } - if (NULL != mdb.last_cmd) - { - struct GNUNET_TIME_Relative del; - - /* Still waiting for ACK! -> delay write task */ - del = GNUNET_TIME_absolute_get_remaining (mdb.ack_timeout); - if (0 != del.rel_value_us) - { - if (did_write) - run_mdb_event_loop (); - else - mdb.wtask = GNUNET_SCHEDULER_add_delayed (del, - &write_mdb_command, - NULL); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "MDB failed to acknowledge command `%s' within timeout\n", - mdb.last_cmd->name); - mdb.last_cmd = NULL; - if (in_shutdown) - { - mdb_shutdown (); - return; - } - } - if (NULL == mdb.cmd) - return; - /* if there is a command to send calculate length and checksum */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Sending command `%s'\n", - mdb.cmd->name); - mdb.tx_off = 0; - mdb.tx_len = mdb.cmd->cmd.bin_size + mdb.cmd->data.bin_size + 1; - GNUNET_assert (mdb.tx_len <= sizeof (mdb.txBuffer)); - { - /* calculate checksum: sum up command and data, take just the LSB of the result */ - uint32_t chkSum = 0; - - for (size_t idx = 0; idx < mdb.cmd->cmd.bin_size; idx++) - chkSum += mdb.txBuffer[idx] = mdb.cmd->cmd.bin[idx]; - for (size_t idx = 0; idx < mdb.cmd->data.bin_size; idx++) - chkSum += mdb.txBuffer[idx + mdb.cmd->cmd.bin_size] = - mdb.cmd->data.bin[idx]; - mdb.txBuffer[mdb.cmd->cmd.bin_size + mdb.cmd->data.bin_size] = - (uint8_t) (chkSum & 0xFF); - } - mdb.last_cmd = mdb.cmd; - mdb.cmd = NULL; - run_mdb_event_loop (); -} - - -/** - * @brief MDB acknowledged the last command, proceed. - */ -static void -handle_ack () -{ - if (&beginSession == mdb.last_cmd) - mdb.session_running = GNUNET_YES; - if (&denyVend == mdb.last_cmd) - { - mdb.session_running = GNUNET_NO; - mdb.cmd = &endSession; - } - mdb.last_cmd = NULL; - /* Cause the write-task to be re-scheduled now */ - if (NULL != mdb.wtask) - { - GNUNET_SCHEDULER_cancel (mdb.wtask); - mdb.wtask = NULL; - } -} - - -/** - * @brief Parse received command from the VMC - * - * @param hex received command from VMC - * @param hex_len number of characters in @a hex - */ -static void -handle_command (const char *hex, - size_t hex_len) -{ - unsigned int cmd; - - /* if the received command is 0 or not a multiple of 2 we cannot parse it */ - if (0 == hex_len) - return; - if (0 != (hex_len % 2)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Received unexpected input `%.*s'\n", - (int) hex_len, - hex); - GNUNET_break_op (0); - return; - } - /* convert the received 2 bytes from ASCII to hex */ - if (1 != sscanf (hex, - "%2X", - &cmd)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Received non-HEX input `%.*s'\n", - (int) hex_len, - hex); - GNUNET_break_op (0); - return; - } - /* parse the first byte (cmd) and the second byte (subcmd) */ - switch (cmd) - { - case VMC_VEND: - { - unsigned int subcmd; - - GNUNET_break (GNUNET_YES == mdb.session_running); - if (4 > hex_len) - { - GNUNET_break_op (0); - return; - } - if (1 != sscanf (&hex[2], - "%2X", - &subcmd)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Received non-HEX input `%.*s'\n", - (int) hex_len - 2, - &hex[2]); - GNUNET_break_op (0); - return; - } - switch (subcmd) - { - case VMC_VEND_REQUEST: - { - unsigned int product; - - GNUNET_break (GNUNET_YES == mdb.session_running); - /* NOTE: hex[4..7] contain the price */ - if (12 > hex_len) - { - GNUNET_break_op (0); - return; - } - if (1 != sscanf (&hex[8], - "%4X", - &product)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Received non-HEX input `%.*s'\n", - (int) hex_len - 8, - &hex[8]); - GNUNET_break_op (0); - return; - } - /* compare the received product number with the defined product numbers */ - for (unsigned int i = 0; i < products_length; i++) - if (product == products[i].number) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Product %u selected on NFC\n", - product); - payment_activity = launch_payment (&products[i]); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Unknown product %u selected on MDB, denying vend\n", - product); - mdb.cmd = &denyVend; - break; - } - case VMC_VEND_SUCCESS: - GNUNET_break (GNUNET_YES == mdb.session_running); - vend_success (); - break; - case VMC_VEND_FAILURE: - { - GNUNET_break (GNUNET_YES == mdb.session_running); - vend_failure (); - break; - } - case VMC_VEND_SESSION_COMPLETE: - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received MDB session complete\n"); - mdb.session_running = GNUNET_NO; - mdb.cmd = &endSession; - if (NULL != payment_activity) - { - cleanup_payment (payment_activity); - payment_activity = NULL; - } - } - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Unknown MDB sub-command %X of command %X\n", - subcmd, - cmd); - break; - } - break; - } - case VMC_CONF: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received request for configuration via MDB\n"); - mdb.cmd = &readerConfigData; - break; - case VMC_POLL: - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Received POLL from MDB (ignored)\n"); - break; - case VMC_READER: - { - unsigned int subcmd; - - if (4 > hex_len) - { - GNUNET_break_op (0); - return; - } - if (1 != sscanf (&hex[2], - "%2X", - &subcmd)) - { - GNUNET_break_op (0); - return; - } - - switch (subcmd) - { - case VMC_READER_DISABLE: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received Reader Disable via MDB\n"); - mdb.session_running = GNUNET_NO; - if (NULL != payment_activity) - { - cleanup_payment (payment_activity); - payment_activity = NULL; - } - break; - case VMC_READER_ENABLE: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received Reader Enable via MDB\n"); - mdb.session_running = GNUNET_NO; - break; - case VMC_READER_CANCEL: - mdb.cmd = &readerCancelled; - mdb.session_running = GNUNET_NO; - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Unknown MDB sub-command %X of command %X\n", - subcmd, - cmd); - break; - } - break; - } - case VMC_REQUEST_ID: - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Received VMC request ID, no need to handle (done by HW)\n"); - break; - case VMC_ACKN: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received acknowledgement (for command `%s') from MDB\n", - (NULL != mdb.last_cmd) ? mdb.last_cmd->name : "?"); - handle_ack (); - break; - case VMC_OOSQ: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Received command out of sequence from MDB (for command `%s')\n", - (NULL != mdb.last_cmd) ? mdb.last_cmd->name : "?"); - mdb.session_running = GNUNET_NO; - if (mdb.last_cmd != &endSession) - mdb.cmd = &endSession; - else - mdb.last_cmd = NULL; - break; - case VMC_RETR: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Received request to resend previous data from MDB (previous command was `%s')\n", - (NULL != mdb.last_cmd) ? mdb.last_cmd->name : "?"); - GNUNET_break (NULL == mdb.cmd); - GNUNET_break (NULL != mdb.last_cmd); - mdb.cmd = mdb.last_cmd; - mdb.last_cmd = NULL; - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Received unknown MDB command %X\n", - cmd); - break; - } -} - - -/** - * @brief Read in the sent commands of the VMC controller - * - * @param cls closure - */ -static void -read_mdb_command (void *cls) -{ - ssize_t ret; - size_t cmdStartIdx; - size_t cmdEndIdx; - - (void) cls; - /* don't read if the mdb bus is disabled (only for testing) */ - GNUNET_assert (! disable_mdb); - mdb.rtask = NULL; - ret = read (mdb.uartfd, - &mdb.rxBuffer[mdb.rx_off], - sizeof (mdb.rxBuffer) - mdb.rx_off); - if (-1 == ret) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "read", - uart_device_filename); - GNUNET_SCHEDULER_shutdown (); - return; - } - mdb.rx_off += ret; - - while (mdb.rx_off > 0) - { - /* find begining of command */ - for (cmdStartIdx = 0; cmdStartIdx < mdb.rx_off; cmdStartIdx++) - if (VMC_CMD_START == mdb.rxBuffer[cmdStartIdx]) - break; - if (cmdStartIdx == mdb.rx_off) - { - mdb.rx_off = 0; - break; - } - /* find end of command */ - for (cmdEndIdx = cmdStartIdx; cmdEndIdx < mdb.rx_off; cmdEndIdx++) - if (VMC_CMD_END == mdb.rxBuffer[cmdEndIdx]) - break; - if (cmdEndIdx == mdb.rx_off) - { - /* check to make sure rxBuffer was big enough in principle */ - if ( (cmdStartIdx == 0) && - (mdb.rx_off == sizeof (mdb.rxBuffer)) ) - { - /* Developer: if this happens, try increasing rxBuffer! */ - GNUNET_break (0); - GNUNET_SCHEDULER_shutdown (); - return; - } - /* move cmd in buffer to the beginning of the buffer */ - memmove (mdb.rxBuffer, - &mdb.rxBuffer[cmdStartIdx], - mdb.rx_off - cmdStartIdx); - mdb.rx_off -= cmdStartIdx; - break; - } - /* if the full command was received parse it */ - handle_command ((const char *) &mdb.rxBuffer[cmdStartIdx + 1], - cmdEndIdx - cmdStartIdx - 1); - /* move the data after the processed command to the left */ - memmove (mdb.rxBuffer, - &mdb.rxBuffer[cmdEndIdx + 1], - mdb.rx_off - cmdEndIdx + 1); - mdb.rx_off -= (cmdEndIdx + 1); - } - if (in_shutdown) - { - mdb_shutdown (); - return; - } - run_mdb_event_loop (); -} - - -/** - * @brief Mdb event loop to start read and write tasks - */ -static void -run_mdb_event_loop () -{ - struct GNUNET_DISK_FileHandle fh = { mdb.uartfd }; - - /* begin session if no cmd waits for sending and no cmd is received from the VMC */ - if ( (GNUNET_NO == mdb.session_running) && - (NULL == mdb.cmd) && - (NULL == mdb.last_cmd) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Begining MDB session\n"); - mdb.cmd = &beginSession; - } - /* start write task if he doesn't exist and if there is a cmd waiting to get sent */ - if ( (NULL == mdb.wtask) && - ( (NULL != mdb.cmd) || - (in_shutdown) || - (mdb.tx_len > mdb.tx_off) ) ) - { - if (disable_mdb) - mdb.wtask = GNUNET_SCHEDULER_add_now (&write_mdb_command, - NULL); - else - mdb.wtask = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, - &fh, - &write_mdb_command, - NULL); - } - if ( (disable_mdb) && - (NULL != mdb.last_cmd) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Faking acknowledgement (for command `%s') from MDB\n", - mdb.last_cmd->name); - handle_ack (); - } - /* start read task if he doesn't exist and the mdb communication is not disabled (only for testing) */ - if ( (NULL == mdb.rtask) && - (! disable_mdb) ) - mdb.rtask = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, - &fh, - &read_mdb_command, - NULL); -} - - -/** - * @brief Read the products from the configuration file - * - * @param cls closure - * @param section section of the config file to read from - */ -static void -read_products (void *cls, - const char *section) -{ - struct Product tmpProduct; - char *tmpKey; - - if (0 != strncmp (section, - "product-", - strlen ("product-"))) - return; - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cls, - section, - "description", - &tmpProduct.description)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "description"); - return; - } - if (GNUNET_OK != - TALER_config_get_denom (cls, - section, - "price", - &tmpProduct.price)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "price"); - GNUNET_free (tmpProduct.description); - return; - } - if (0 != strcasecmp (currency, - tmpProduct.price.currency)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "price", - "currency missmatch"); - GNUNET_free (tmpProduct.description); - return; - } - if (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_string (cls, - section, - "key", - &tmpKey)) - { - tmpProduct.key = tmpKey[0]; - GNUNET_free (tmpKey); - } - else - { - /* no key */ - tmpProduct.key = '\0'; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (cls, - section, - "number", - &tmpProduct.number)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "number"); - GNUNET_free (tmpProduct.description); - return; - } - GNUNET_array_append (products, - products_length, - tmpProduct); -} - - -/** - * @brief Initialise the uart device to send mdb commands - */ -static int -mdb_init () -{ - struct termios uart_opts_raw; - - if (disable_mdb) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Running with MDB disabled!\n"); - run_mdb_event_loop (); - return GNUNET_OK; - } - /* open uart connection */ - if (0 > (mdb.uartfd = open (uart_device_filename, - O_RDWR | O_NOCTTY | O_NDELAY))) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "open", - uart_device_filename); - return GNUNET_SYSERR; - } - - if (0 != tcgetattr (mdb.uartfd, &mdb.uart_opts_backup)) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, - "tcgetattr"); - return GNUNET_SYSERR; - } - uart_opts_raw = mdb.uart_opts_backup; - - /* reset current uart discipline */ - memset (&uart_opts_raw, - 0, - sizeof(uart_opts_raw)); - - /* set baudrate */ - cfsetispeed (&uart_opts_raw, B9600); - cfsetospeed (&uart_opts_raw, B9600); - - /* set options */ - uart_opts_raw.c_cflag &= ~PARENB; - uart_opts_raw.c_cflag &= ~CSTOPB; - uart_opts_raw.c_cflag &= ~CSIZE; - uart_opts_raw.c_cflag |= CS8; - - /* 19200 bps, 8 databits, ignore cd-signal, allow reading */ - uart_opts_raw.c_cflag |= (CLOCAL | CREAD); - uart_opts_raw.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - uart_opts_raw.c_iflag = IGNPAR; - uart_opts_raw.c_oflag &= ~OPOST; - uart_opts_raw.c_cc[VMIN] = 0; - uart_opts_raw.c_cc[VTIME] = 50; - tcflush (mdb.uartfd, TCIOFLUSH); - if (0 != tcsetattr (mdb.uartfd, - TCSAFLUSH, - &uart_opts_raw)) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, - "tcsetattr"); - return GNUNET_SYSERR; - } - /* if the configuration of the uart was sucessfull start the mdb write and read tasks */ - run_mdb_event_loop (); - return GNUNET_OK; -} - - -/** - * @brief Start the application - * - * @param cls closure - * @param args arguments left - * @param cfgfile config file name - * @param cfg handle for the configuation file - */ -static void -run (void *cls, - char *const *args, - const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - (void) cls; - (void) args; - (void) cfgfile; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "taler", - "currency", - &currency)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler", - "currency"); - global_ret = EXIT_FAILURE; - return; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - "taler-mdb", - "UART_DEVICE", - &uart_device_filename)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-mdb", - "UART_DEVICE"); - uart_device_filename = GNUNET_strdup ("/dev/ttyAMA0"); - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - "taler-mdb", - "FRAMEBUFFER_DEVICE", - &framebuffer_device_filename)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-mdb", - "FRAMEBUFFER_DEVICE"); - framebuffer_device_filename = GNUNET_strdup ("/dev/fb1"); - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - "taler-mdb", - "FRAMEBUFFER_BACKLIGHT", - &framebuffer_backlight_filename)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-mdb", - "FRAMEBUFFER_BACKLIGHT"); - framebuffer_backlight_filename = GNUNET_strdup ( - "/sys/class/backlight/soc:backlight/brightness"); - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "taler-mdb", - "backend-base-url", - &backendBaseUrl)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-mdb", - "backend-base-url"); - global_ret = EXIT_FAILURE; - return; - } - { - char *auth; - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "taler-mdb", - "backend-authorization", - &auth)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-mdb", - "backend-authorization"); - global_ret = EXIT_FAILURE; - return; - } - GNUNET_asprintf (&authorization, - "%s: %s", - MHD_HTTP_HEADER_AUTHORIZATION, - auth); - GNUNET_free (auth); - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "taler-mdb", - "fulfillment-url", - &fulfillmentUrl)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-mdb", - "fulfillment-url"); - global_ret = EXIT_FAILURE; - return; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "taler-mdb", - "fulfillment-msg", - &fulfillmentMsg)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-mdb", - "fulfillment-msgcurrency"); - global_ret = EXIT_FAILURE; - return; - } - GNUNET_CONFIGURATION_iterate_sections (cfg, - &read_products, - (void *) cfg); - GNUNET_assert (NULL != products); - - GNUNET_SCHEDULER_add_shutdown (&shutdown_task, - NULL); - - /* initialize mdb */ - if (GNUNET_OK != mdb_init ()) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unable to initialize MDB (mdb_init() failed)\n"); - global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return; - } - - /* initialize nfc */ - nfc_init (&context); - if (NULL == context) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unable to initialize NFC (nfc_init() failed)\n"); - global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return; - } - - /* initialize HTTP client */ - ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, - &rc); - rc = GNUNET_CURL_gnunet_rc_create (ctx); - /* setup authorization */ - GNUNET_assert (GNUNET_OK == - GNUNET_CURL_append_header (ctx, - authorization)); - -#if HAVE_QRENCODE_H - /* open the framebuffer device */ - qrDisplay.devicefd = open (framebuffer_device_filename, - O_RDWR); - if (0 < qrDisplay.devicefd) - { - /* read information about the screen */ - ioctl (qrDisplay.devicefd, - FBIOGET_VSCREENINFO, - &qrDisplay.var_info); - - /* store current screeninfo for reset */ - qrDisplay.orig_vinfo = qrDisplay.var_info; - - if (16 != qrDisplay.var_info.bits_per_pixel) - { - /* Change variable info to 16 bit per pixel */ - qrDisplay.var_info.bits_per_pixel = 16; - if (0 > ioctl (qrDisplay.devicefd, - FBIOPUT_VSCREENINFO, - &qrDisplay.var_info)) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "ioctl(FBIOPUT_VSCREENINFO)"); - return; - } - } - - /* Get fixed screen information */ - if (0 > ioctl (qrDisplay.devicefd, - FBIOGET_FSCREENINFO, - &qrDisplay.fix_info)) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "ioctl(FBIOGET_FSCREENINFO)"); - return; - } - - /* get pointer onto frame buffer */ - qrDisplay.memory = mmap (NULL, - qrDisplay.fix_info.smem_len, - PROT_READ | PROT_WRITE, MAP_SHARED, - qrDisplay.devicefd, - 0); - - /* open backlight file to turn display backlight on and off */ - if (0 > qrDisplay.devicefd) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, - "mmap"); - return; - } - - memset (qrDisplay.memory, - 0xFF, - qrDisplay.var_info.xres * qrDisplay.var_info.yres - * sizeof (uint16_t)); - - qrDisplay.backlightfd = open ( - framebuffer_backlight_filename, O_WRONLY); - if (0 > qrDisplay.backlightfd) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, - "open", - framebuffer_backlight_filename); - } - else - { - if (backlight_invert) - { - backlight_on = '0'; - backlight_off = '1'; - } - (void) write (qrDisplay.backlightfd, &backlight_off, 1); - } - } - else - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, - "open", - framebuffer_device_filename); - } -#endif - if (! disable_tty) - start_read_keyboard (); -} - - -/** - * @brief Convert the ASCII cmd in @hex to hex and store it in the mdb data struct @blk - * - * @param *blk pointer with reference to the mdb datablock - * @param *hex pointer to the string containing the cmd data - */ -static void -parse_block (struct MdbBlock *blk, - const char *hex) -{ - if (NULL == hex) - { - blk->bin_size = 0; - blk->bin = NULL; - return; - } - blk->bin_size = strlen (hex) / 2; - blk->bin = GNUNET_malloc (blk->bin_size); - for (size_t idx = 0; idx < blk->bin_size; idx++) - { - unsigned int val; - - GNUNET_assert (1 == - sscanf (&hex[idx * 2], - "%2X", - &val)); - blk->bin[idx] = (uint8_t) val; - } -} - - -/** - * @brief Create a new mdb command - * - * @param *name pointer to the string containing the command name - * @param *cmd pointer to the string containing the command - * @param *data pointer to the string containing the command data - * @return structure of type MdbCommand holding the given information by the parameters - */ -static struct MdbCommand -setup_mdb_cmd (const char *name, - const char *cmd, - const char *data) -{ - struct MdbCommand cmdNew; - - cmdNew.name = (NULL == name) - ? "No Cmd Name Set" - : name; - parse_block (&cmdNew.cmd, cmd); - parse_block (&cmdNew.data, data); - return cmdNew; -} - - -int -main (int argc, - char*const*argv) -{ - struct termios tty_opts_backup, tty_opts_raw; - int ret; - /* the available command line options */ - struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_option_flag ('d', - "disable-mdb", - "disable all interactions with the MDB (for testing without machine)", - &disable_mdb), - GNUNET_GETOPT_option_flag ('t', - "disable-tty", - "disable all keyboard interactions (for running from systemd)", - &disable_tty), - GNUNET_GETOPT_option_flag ('i', - "backlight-invert", - "invert the backlight on/off values (standard on = 1)", - &backlight_invert), - GNUNET_GETOPT_OPTION_END - }; - int have_tty; - - if (! disable_tty) - { - have_tty = isatty (STDIN_FILENO); - if (have_tty) - { - if (0 != tcgetattr (STDIN_FILENO, &tty_opts_backup)) - fprintf (stderr, - "Failed to get terminal discipline\n"); - tty_opts_raw = tty_opts_backup; - tty_opts_raw.c_lflag &= ~(ECHO | ECHONL | ICANON); - if (0 != tcsetattr (STDIN_FILENO, TCSANOW, &tty_opts_raw)) - fprintf (stderr, - "Failed to set terminal discipline\n"); - } - } - /* make the needed commands for the communication with the vending machine controller */ - readerConfigData = setup_mdb_cmd ("Reader Config", - READER_CONFIG, - READER_FEATURE_LEVEL READER_COUNTRYCODE - READER_SCALE_FACTOR READER_DECIMAL_PLACES - READER_MAX_RESPONSE_TIME - READER_MISC_OPTIONS); - beginSession = setup_mdb_cmd ("Begin Session", - READER_BEGIN_SESSION, - READER_FUNDS_AVAILABLE); - approveVend = setup_mdb_cmd ("Approve Vend", - READER_VEND_APPROVE, - READER_VEND_AMOUNT); - readerCancelled = setup_mdb_cmd ("Confirm cancellation", - READER_CANCELLED, - NULL); - denyVend = setup_mdb_cmd ("Deny Vend", - READER_VEND_DENIED, - NULL); - endSession = setup_mdb_cmd ("End Session", - READER_END_SESSION, - NULL); - - ret = GNUNET_PROGRAM_run (argc, - argv, - "taler-mdb", - "This is an application for snack machines to pay with GNU Taler via NFC.\n", - options, - &run, - NULL); - if (! disable_tty) - { - if (have_tty) - { - /* Restore previous TTY settings */ - if (0 != tcsetattr (STDIN_FILENO, - TCSANOW, - &tty_opts_backup)) - fprintf (stderr, - "Failed to restore terminal discipline\n"); - } - } - if (GNUNET_OK != ret) - return 1; - return global_ret; -}