summaryrefslogtreecommitdiff
path: root/src/taler-mdb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/taler-mdb.c')
-rw-r--r--src/taler-mdb.c3522
1 files changed, 3522 insertions, 0 deletions
diff --git a/src/taler-mdb.c b/src/taler-mdb.c
new file mode 100644
index 0000000..f0b1529
--- /dev/null
+++ b/src/taler-mdb.c
@@ -0,0 +1,3522 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2020, 2022 Taler Systems SA
+
+ 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 runs the payment logic for a Taler-enabled snack machine
+* @author Marco Boss
+* @author Christian Grothoff
+* @author Dominik Hofer
+*
+*/
+#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>
+
+#ifndef EXIT_NOTCONFIGURED
+#define EXIT_NOTCONFIGURED 6
+#endif
+
+
+/* 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_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * Set payment deadline below what will work with the snack machine.
+ */
+#define PAY_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 2)
+
+/**
+ * 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)
+
+
+#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)
+
+/**
+ * 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 length of the uid for a valid MIFARE target
+ */
+#define UID_LEN_UPPER 7
+
+/**
+ * Lower length 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
+#define VMC_CMD_RESET 0x10
+
+/**
+ * Acknowledgement
+ */
+#define VMC_ACKN 0x00
+
+/**
+ * Request for configuration.
+ */
+#define VMC_CONF 0x11
+#define VMC_READER_CONF 0x00
+#define VMC_SETUP_MAX_MIN_PRICES 0x01
+
+/**
+ * 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
+
+/**
+ * VMC Revalue Request
+ */
+#define VMC_REVALUE 0x15
+#define VMC_REVALUE_REQUEST 0x00
+#define VMC_REVALUE_LIMIT_REQUEST 0x01
+/**
+ * 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 */
+
+/* Reader Not Acknowledge */
+#define READER_NACK "FF"
+
+/* Config Data */
+/* Refer to the mdb interface specifications v4.2 p.288 */
+#define READER_CONFIG "01"
+#define READER_FEATURE_LEVEL "01"
+#define READER_COUNTRYCODE "0972"
+#define READER_SCALE_FACTOR "0A"
+#define READER_DECIMAL_PLACES "02"
+#define READER_MAX_RESPONSE_TIME "07"
+#define READER_MISC_OPTIONS "0D"
+
+/* Session Commands */
+/* Refer to the mdb interface specifications v4.2 p.131 */
+#define READER_BEGIN_SESSION "03"
+#define READER_FUNDS_AVAILABLE "000A"
+#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 "FFFE"
+#define READER_VEND_DENIED "06"
+
+/* Revalue */
+#define READER_REVALUE_APPROVED "0D"
+#define READER_REVALUE_APPROVED "0D"
+#define READER_REVALUE_LIMIT "0F"
+#define READER_REVALUE_LIMIT_AMOUNT "FFFE"
+
+/* Cancelled Command */
+#define READER_CANCELLED "08"
+
+/* Display Request for Sold Out product */
+#define READER_DISPLAY_REQUEST "02"
+#define READER_DISPLAY_REQUEST_TIME "32"
+#define READER_DISPLAY_SOLD_OUT \
+ "202020202020202050726f6475637420736f6c64206f75742020202020202020"
+#define READER_DISPLAY_INTERNAL_ERROR \
+ "202020496e7465726e616c204572726f72202d2054727920416761696e202020i"
+#define READER_DISPLAY_BACKEND_NOT_REACHABLE \
+ "20202020204261636b656e64206e6f7420726561636861626c65202020202020"
+
+/* Unused reader commands */
+#define READER_SESSION_CANCEL_REQUEST "04"
+#define READER_REVALUE_DENIED "0E"
+
+/**
+ * How long are we willing to wait for MDB during
+ * shutdown?
+ */
+#define SHUTDOWN_MDB_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MILLISECONDS, 100)
+
+/**
+ * Datatype for mdb subcommands and data
+ */
+struct MdbBlock
+{
+ /**
+ * Data containing a mdb command or the data of an mdb command
+ */
+ uint8_t *bin;
+
+ /**
+ * Size of the data referenced by *bin
+ */
+ size_t bin_size;
+};
+
+
+/**
+ * Datatype for mdb command
+ */
+struct MdbCommand
+{
+ /**
+ * Name of the command for the logging
+ */
+ const char *name;
+
+ /**
+ * Data block containing the information about the mdb command
+ */
+ struct MdbBlock cmd;
+
+ /**
+ * Data block containing the information about the mdb command data
+ */
+ 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;
+
+ /**
+ * Authorization to header to use for this @e instance.
+ */
+ char *auth_header;
+
+ /**
+ * Which instance should be used for billing? Full URL, replaces
+ * the default base URL for orders involving this product. NULL if
+ * we should use the #backend_base_url.
+ */
+ char *instance;
+
+ /**
+ * Preview image to embed in the contract, NULL for
+ * no preview. Already base64 encoded.
+ */
+ char *preview;
+
+ /**
+ * Number of the product in the vending machine
+ */
+ unsigned long long number;
+
+ /**
+ * Set to #GNUNET_YES if this product was found
+ * to have been sold out (VEND failure).
+ */
+ bool sold_out;
+
+ /**
+ * Key for the product (optional, needed to test the application without vending machine)
+ */
+ char key;
+
+};
+
+
+/**
+ * Handle for a payment
+ */
+struct PaymentActivity
+{
+
+ /**
+ * Curl context for communication with taler backend
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule().
+ */
+ struct GNUNET_CURL_RescheduleContext *rc;
+
+ /**
+ * Handle to a POST /orders operation
+ */
+ struct TALER_MERCHANT_PostOrdersHandle *po;
+
+ /**
+ * Handle for a GET /private/orders/$ID operation.
+ */
+ struct TALER_MERCHANT_OrderMerchantGetHandle *ogh;
+
+ /**
+ * The product being sold.
+ */
+ struct Product *product;
+
+ /**
+ * Base URL for merchant interactions for this pa.
+ */
+ const char *base_url;
+
+ /**
+ * 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;
+
+ /**
+ * Handle for our attempt to delete an ongoing order.
+ */
+ struct TALER_MERCHANT_OrderDeleteHandle *odh;
+
+ /**
+ * Member to see if the wallet already received a uri
+ * If true, tunneling can be offered to the wallet.
+ */
+ bool wallet_has_uri;
+
+ /**
+ * Set to true once the product has been paid
+ * (and we are in the process of yielding the product).
+ */
+ bool paid;
+};
+
+
+/**
+ * Data structures associated with the MDB.
+ */
+struct MdbHandle
+{
+
+ /**
+ * Buffer to save the received data from UART
+ */
+ uint8_t rxBuffer[MAX_SIZE_RX_BUFFER];
+
+ /**
+ * Buffer to save the data to send via UART
+ */
+ uint8_t txBuffer[MAX_SIZE_TX_BUFFER];
+
+ /**
+ * Reference to scheduler task to read from UART
+ */
+ struct GNUNET_SCHEDULER_Task *rtask;
+
+ /**
+ * Reference to scheduler task to write to UART
+ */
+ struct GNUNET_SCHEDULER_Task *wtask;
+
+ /**
+ * Reference to the mdb cmd which will be sent next
+ */
+ const struct MdbCommand *cmd;
+
+ /**
+ * Reference to the mdb cmd which was sent last
+ */
+ const struct MdbCommand *last_cmd;
+
+ /**
+ * Current read offset in @e rxBuffer.
+ */
+ 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;
+
+ /**
+ * Time out to wait for an acknowledge received via the mdb bus
+ */
+ struct GNUNET_TIME_Absolute ack_timeout;
+
+ /**
+ * Backup of the config data to restore the configuration of the UART before closing it
+ */
+ struct termios uart_opts_backup;
+
+ /**
+ * Indicates if a vend session is running or not
+ */
+ bool session_running;
+
+ /**
+ * File descriptor to the UART device file
+ */
+ 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;
+};
+
+/**
+ * Handle for the Cancel Button
+ */
+struct CancelButton
+{
+ /**
+ * File descriptor to read the state of the cancel button gpio pin
+ */
+ int cancelbuttonfd;
+};
+
+
+/**
+ * DLL of pending refund operations.
+ */
+struct Refund
+{
+ /**
+ * DLL next pointer.
+ */
+ struct Refund *next;
+
+ /**
+ * DLL prev pointer.
+ */
+ struct Refund *prev;
+
+ /**
+ * Curl context for communication with taler backend
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule().
+ */
+ struct GNUNET_CURL_RescheduleContext *rc;
+
+ /**
+ * Handle to the ongoing operation.
+ */
+ struct TALER_MERCHANT_OrderRefundHandle *orh;
+
+};
+
+
+/**
+ * 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;
+
+/**
+ * Flag set to remember that MDB needs shutdown
+ * (because we were actually able to talk to MDB).
+ */
+static bool mdb_active;
+
+/**
+ * Reference to the keyboard task
+ */
+static struct GNUNET_SCHEDULER_Task *keyboard_task;
+
+/**
+ * Reference to the cancel button task
+ */
+static struct GNUNET_SCHEDULER_Task *cancelbutton_task;
+
+/**
+ * Taler Backend url read from configuration file
+ */
+static char *backend_base_url;
+
+/**
+ * Fulfillment message to display after successful payment, read from configuration file
+ */
+static char *fulfillment_msg;
+
+/**
+ * ESSID of a WLAN network offered by the snack system, or NULL.
+ */
+static char *essid;
+
+/**
+ * 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 cmd_reader_config_data;
+
+/**
+ * Ask MDB to begin session (with "infinite" money)
+ */
+static struct MdbCommand cmd_begin_session;
+
+/**
+ * Refuse vending request (payment failed)
+ */
+static struct MdbCommand cmd_deny_vend;
+
+/**
+ * Approve vending request (payment succeeded)
+ */
+static struct MdbCommand cmd_approve_vend;
+
+/**
+ * Confirm cancellation by machine.
+ */
+static struct MdbCommand cmd_reader_cancelled;
+
+/**
+ * Approve Revalue
+ */
+static struct MdbCommand cmd_revalue_approved;
+
+/**
+ * Send Revalue Limit Amount
+ */
+static struct MdbCommand cmd_revalue_amount;
+
+/**
+ * Send NACK
+ */
+static struct MdbCommand cmd_reader_NACK;
+
+/**
+ * Display Request for Sold Out
+ */
+static struct MdbCommand cmd_reader_display_sold_out;
+
+/**
+ * Display Request for Error Message
+ */
+static struct MdbCommand cmd_reader_display_internal_error;
+
+/**
+ * Display Request for Error Message
+ */
+static struct MdbCommand cmd_reader_display_backend_not_reachable;
+
+/**
+ * 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';
+
+/**
+ * State for the implementation of the 'cancel' button.
+ */
+static struct CancelButton cancel_button;
+
+/**
+ * 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;
+
+/**
+ * Global option '-s' to enable sold-out detection.
+ */
+static int sold_out_enabled;
+
+/**
+ * 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;
+ }
+ /* 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);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Showing QR code for `%s'\n",
+ upper);
+ /* 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;
+ }
+
+ /* set QR-code border */
+ size = GNUNET_MIN (qrDisplay.var_info.xres,
+ qrDisplay.var_info.yres);
+ unsigned int nwidth = qrc->width + 8; /* +8 for 4 pixel border */
+ xOff = 4 * size / nwidth;
+ yOff = 4 * size / nwidth;
+ /* calculate offset to show the code centered */
+ 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;
+ /* set the pixels in the display memory */
+ qrDisplay.memory[(y + yOff) * qrDisplay.var_info.xres + (x + xOff)] =
+ (0 == (qrc->data[off] & 1)) ? 0xFFFF : 0x0000;
+ }
+
+ QRcode_free (qrc);
+ QRinput_free (qri);
+
+ /* turn on backlight if supported */
+ if (0 < qrDisplay.backlightfd)
+ (void) ! write (qrDisplay.backlightfd,
+ &backlight_on,
+ 1);
+}
+
+
+#endif
+
+
+static void
+run_mdb_event_loop (void);
+
+
+/**
+ * Runs asynchronous cleanup part for freeing a
+ * payment activity.
+ *
+ * @param[in] cls a `struct PaymentActivity` to clean up
+ */
+static void
+async_pa_cleanup_job (void *cls)
+{
+ struct PaymentActivity *pa = cls;
+
+ if (NULL != pa->ctx)
+ GNUNET_CURL_fini (pa->ctx);
+ if (NULL != pa->rc)
+ GNUNET_CURL_gnunet_rc_destroy (pa->rc);
+ GNUNET_free (pa);
+}
+
+
+/**
+ * @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);
+
+
+/**
+ * Function called with the result of the DELETE /orders/$ID operation.
+ *
+ * @param cls closure with the `struct PaymentActivity *`
+ * @param hr HTTP response details
+ */
+static void
+order_delete_cb (
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PaymentActivity *pa = cls;
+
+ pa->odh = NULL;
+ if ( (MHD_HTTP_OK != hr->http_status) &&
+ (MHD_HTTP_NO_CONTENT != hr->http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to delete incomplete order from backend: %d/%u\n",
+ (int) hr->http_status,
+ (unsigned int) hr->ec);
+ }
+ cleanup_payment (pa);
+}
+
+
+static void
+cleanup_payment (struct PaymentActivity *pa)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cleaning up payment\n");
+ if ( (! pa->paid) &&
+ (NULL != pa->order_id) )
+ {
+ char *oid;
+
+ oid = pa->order_id;
+ pa->order_id = NULL;
+ pa->odh = TALER_MERCHANT_order_delete (
+ pa->ctx,
+ pa->base_url,
+ oid,
+ true, /* delete even claimed orders */
+ &order_delete_cb,
+ pa);
+ GNUNET_free (oid);
+ return;
+ }
+ if (NULL != pa->odh)
+ TALER_MERCHANT_order_delete_cancel (pa->odh);
+ if (NULL != pa->pnd)
+ {
+ nfc_abort_command (pa->pnd);
+ nfc_close (pa->pnd);
+ }
+ if (NULL != cancelbutton_task)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Stopping watching of the cancel button\n");
+ GNUNET_SCHEDULER_cancel (cancelbutton_task);
+ cancelbutton_task = NULL;
+ }
+ if (NULL != pa->po)
+ TALER_MERCHANT_orders_post_cancel (pa->po);
+ if (NULL != pa->ogh)
+ TALER_MERCHANT_merchant_order_get_cancel (pa->ogh);
+ GNUNET_CURL_gnunet_scheduler_reschedule (&pa->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 (pa->order_id);
+ GNUNET_SCHEDULER_add_now (&async_pa_cleanup_job,
+ pa);
+}
+
+
+/**
+ * @brief Shutdown the mdb communication tasks
+ */
+static void
+mdb_shutdown (void)
+{
+ 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)
+ {
+ GNUNET_break (0 == 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_post_order_refund_cancel (r->orh);
+ 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 != cancelbutton_task)
+ {
+ GNUNET_SCHEDULER_cancel (cancelbutton_task);
+ cancelbutton_task = 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;
+ if (-1 != mdb.uartfd)
+ run_mdb_event_loop ();
+ if ( (MAP_FAILED != qrDisplay.memory) &&
+ (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 the device */
+ GNUNET_break (0 == close (qrDisplay.devicefd));
+ qrDisplay.devicefd = -1;
+ if (0 < qrDisplay.backlightfd)
+ GNUNET_break (0 == close (qrDisplay.backlightfd));
+ qrDisplay.backlightfd = -1;
+ }
+ if (-1 != cancel_button.cancelbuttonfd)
+ {
+ GNUNET_break (0 ==
+ close (cancel_button.cancelbuttonfd));
+ cancel_button.cancelbuttonfd = -1;
+ }
+ {
+ int efd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Unexporting GPIO pin 23\n");
+ efd = open ("/sys/class/gpio/unexport",
+ O_WRONLY);
+ if (-1 == efd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unable to open /gpio/unexport for cancel button\n");
+ }
+ else
+ {
+ if (2 != write (efd,
+ "23",
+ 2))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "write",
+ "/sys/class/gpio/unexport");
+
+ }
+ GNUNET_break (0 == close (efd));
+ }
+ }
+ /* free the allocated productes read from config file */
+ if (NULL != products)
+ {
+ for (unsigned int i = 0; i < products_length; i++)
+ {
+ GNUNET_free (products[i].description);
+ GNUNET_free (products[i].auth_header);
+ GNUNET_free (products[i].instance);
+ GNUNET_free (products[i].preview);
+ }
+ 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;
+ /* response array for APDU response status word */
+ uint8_t response[] = { 0x00, 0x00 };
+ size_t slen = strlen (pa->taler_pay_uri);
+ uint8_t message[sizeof (put_data) + slen];
+
+ pa->delay_task = NULL;
+ /* append the pay uri to the put data command */
+ memcpy (message, put_data, sizeof (put_data));
+ memcpy (&message[sizeof (put_data)], pa->taler_pay_uri, slen);
+ /* send the put data command via nfc */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "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;
+ }
+ /* check if the transmission succeeded */
+ 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 = true;
+ /* FIXME: or just offer Internet service here? */
+
+ /* transmit the uri again later, there can be many external failures,
+ for example the taler wallet app was not opened and thus did not receive
+ the data */
+ pa->delay_task = GNUNET_SCHEDULER_add_delayed (MAX_HTTP_RETRY_FREQ,
+ &wallet_transmit_uri,
+ pa);
+}
+
+
+/**
+ * @brief Select the taler wallet app via NFC on the target selected with
+ * @e connect_target()
+ * (check if it is installed on the smartphone)
+ *
+ * @param cls closure
+ */
+static void
+wallet_select_aid (void *cls)
+{
+ struct PaymentActivity *pa = cls;
+ /* response array for APDU response status word */
+ uint8_t response[] = { 0x00, 0x00 };
+ uint8_t message[sizeof(select_file) + sizeof(taler_aid)];
+
+ pa->task = NULL;
+ /* append the taler wallet aid to the select file command */
+ memcpy (message, select_file, sizeof (select_file));
+ memcpy (&message[sizeof (select_file)], taler_aid, sizeof (taler_aid));
+
+ /* send the select file command via nfc */
+ 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;
+ }
+ /* check if the transmission succeeded */
+ 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 the transmission was not successful chack if the app is available at all */
+ 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;
+ }
+ /* If the upper cases did not match, there was an unknown APDU status returned */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "AID selection failure, return code: %x%x, trying to find another NFC client\n",
+ response[0],
+ response[1]);
+ /* start the selection again */
+ 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;
+ /* nfc modulation used */
+ const nfc_modulation nmMifare = {
+ .nmt = NMT_ISO14443A,
+ .nbr = NBR_212,
+ };
+
+ pa->task = NULL;
+ /* set the uid len to zero (maybe it is still set from earlier selections) */
+ pa->nt.nti.nai.szUidLen = 0;
+ /* poll for a fitting nfc target (we use the shortest time possible to not block the scheduler) */
+ 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");
+ }
+ /* if the uid length are out of bound abort */
+ 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
+ {
+ /* the target was successfully selected,
+ now we have to check if the taler wallet is installed on it */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found NFC client\n");
+ pa->task = GNUNET_SCHEDULER_add_now (&wallet_select_aid,
+ pa);
+ return;
+ }
+ /* if no target was found try again */
+ 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;
+ /* open the nfc reader via libnfc's open */
+ 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;
+ }
+ /* initialize the reader as initiator */
+ 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));
+ /* the nfc reader was opened successfully, now try to find a mobile device as a target */
+ 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 osr order status response details (on success)
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_OrderStatusResponse *osr)
+{
+ struct PaymentActivity *pa = cls;
+ const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
+ char *uri;
+
+ GNUNET_assert (payment_activity == pa);
+ pa->ogh = NULL;
+ if ( (MHD_HTTP_OK != hr->http_status) &&
+ (MHD_HTTP_GATEWAY_TIMEOUT != hr->http_status) &&
+ (MHD_HTTP_REQUEST_TIMEOUT != hr->http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Backend request to /check-payment failed: %u/%d\n",
+ hr->http_status,
+ (int) hr->ec);
+ mdb.cmd = &cmd_reader_display_backend_not_reachable;
+ run_mdb_event_loop ();
+ cleanup_payment (pa);
+ GNUNET_assert (payment_activity == pa);
+ payment_activity = NULL;
+ return;
+ }
+
+ if ( (MHD_HTTP_OK != hr->http_status) &&
+ (TALER_MERCHANT_OSC_PAID == osr->details.success.status) )
+ {
+ mdb.cmd = &cmd_approve_vend;
+ payment_activity->paid = true;
+ run_mdb_event_loop ();
+ if ((disable_mdb) && (! disable_tty))
+ {
+ GNUNET_SCHEDULER_cancel (keyboard_task);
+ keyboard_task = NULL;
+ start_read_keyboard ();
+ }
+ return;
+ }
+ /* 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->ogh) &&
+ (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) &&
+ (MHD_HTTP_OK == hr->http_status) &&
+ (TALER_MERCHANT_OSC_UNPAID == osr->details.success.status) )
+ {
+ if (NULL == essid)
+ uri = GNUNET_strdup (osr->details.success.details.unpaid.taler_pay_uri);
+ else
+ GNUNET_asprintf (&uri,
+ "%s#%s",
+ osr->details.success.details.unpaid.taler_pay_uri,
+ essid);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to talk to wallet to give it pay URI `%s'\n",
+ uri);
+ GNUNET_assert (NULL == pa->pnd);
+ pa->taler_pay_uri = uri;
+#if HAVE_QRENCODE_H
+ show_qrcode (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->ogh);
+ pa->ogh = TALER_MERCHANT_merchant_order_get (pa->ctx,
+ pa->base_url,
+ pa->order_id,
+ NULL /* snack machine, no Web crap */,
+ false,
+ BACKEND_POLL_TIMEOUT,
+ &check_payment_cb,
+ pa);
+}
+
+
+/**
+ * @brief Callback for a POST /orders request
+ *
+ * @param cls closure
+ * @param por response for this request
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct PaymentActivity *pa = cls;
+
+ pa->po = NULL;
+ if (MHD_HTTP_OK != por->hr.http_status)
+ {
+ /* FIXME: In the future, we may want to support MHD_HTTP_GONE
+ explicitly and show 'product out of stock' here! */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to setup order with backend: %u/%d\n",
+ por->hr.http_status,
+ (int) por->hr.ec);
+ json_dumpf (por->hr.reply,
+ stderr,
+ 0);
+ mdb.cmd = &cmd_reader_display_backend_not_reachable;
+ 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",
+ por->details.ok.order_id);
+ pa->order_id = GNUNET_strdup (por->details.ok.order_id);
+ pa->ogh = TALER_MERCHANT_merchant_order_get (pa->ctx,
+ pa->base_url,
+ pa->order_id,
+ NULL /* snack machine, no Web crap */,
+ false,
+ GNUNET_TIME_UNIT_ZERO,
+ &check_payment_cb,
+ pa);
+}
+
+
+static void
+start_read_cancel_button (void);
+
+
+/**
+ * @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 (struct Product *product)
+{
+ struct PaymentActivity *pa;
+ json_t *orderReq;
+ char *msg;
+ const char *pos;
+
+ pos = strstr (fulfillment_msg,
+ "${PRODUCT_DESCRIPTION}");
+ if (NULL != pos)
+ {
+ /* replace ${PRODUCT_DESCRIPTION} with the real one */
+ GNUNET_asprintf (&msg,
+ "%.*s%s%s",
+ /* first output URL until ${PRODUCT_DESCRIPTION} */
+ (int) (pos - fulfillment_msg),
+ fulfillment_msg,
+ /* replace ${PRODUCT_DESCRIPTION} with the right description */
+ product->description,
+ /* append rest of original URL */
+ pos + strlen ("${PRODUCT_DESCRIPTION}"));
+ }
+ else
+ {
+ msg = GNUNET_strdup (fulfillment_msg);
+ }
+
+ /* create the json object for the order request */
+ if (NULL != product->preview)
+ {
+ json_t *products;
+
+ products = json_array ();
+ GNUNET_assert (NULL != products);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (products,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("description",
+ product->description),
+ GNUNET_JSON_pack_string ("image",
+ product->preview),
+ TALER_JSON_pack_amount ("price",
+ &product->price),
+ GNUNET_JSON_pack_uint64 ("quantity",
+ 1))));
+ orderReq = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("summary",
+ product->description),
+#if BUG
+ GNUNET_JSON_pack_timestamp ("pay_deadline",
+ GNUNET_TIME_relative_to_timestamp (
+ PAY_TIMEOUT)),
+#endif
+ GNUNET_JSON_pack_array_steal (
+ "products",
+ products),
+ TALER_JSON_pack_amount ("amount",
+ &product->price),
+ GNUNET_JSON_pack_string ("fulfillment_message",
+ msg),
+ GNUNET_JSON_pack_time_rel ("auto_refund",
+ MAX_REFUND_DELAY));
+ }
+ else
+ {
+ orderReq = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("summary",
+ product->description),
+#if BUG
+ GNUNET_JSON_pack_timestamp ("pay_deadline",
+ GNUNET_TIME_relative_to_timestamp (
+ PAY_TIMEOUT)),
+#endif
+ TALER_JSON_pack_amount ("amount",
+ &product->price),
+ GNUNET_JSON_pack_string ("fulfillment_message",
+ msg),
+ GNUNET_JSON_pack_time_rel ("auto_refund",
+ MAX_REFUND_DELAY));
+ }
+ GNUNET_free (msg);
+ if (NULL == orderReq)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "json_pack failed (out of memory?)\n");
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Creating order for `%s' at backend `%s'\n",
+ product->description,
+ (NULL == product->instance)
+ ? backend_base_url
+ : product->instance);
+ pa = GNUNET_new (struct PaymentActivity);
+ pa->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &pa->rc);
+ pa->rc = GNUNET_CURL_gnunet_rc_create (pa->ctx);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CURL_append_header (pa->ctx,
+ product->auth_header));
+ pa->product = product;
+ pa->amount = product->price;
+ /* put the order on the merchant's backend */
+ pa->base_url = (NULL == product->instance)
+ ? backend_base_url
+ : product->instance;
+ pa->po = TALER_MERCHANT_orders_post2 (pa->ctx,
+ pa->base_url,
+ orderReq,
+ MAX_REFUND_DELAY,
+ NULL, /* no payment target preference */
+ 0,
+ NULL, /* no inventory */
+ 0,
+ NULL, /* no locks */
+ false, /* no claim token */
+ &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");
+ cleanup_payment (pa);
+ return NULL;
+ }
+ /* Start to read the button on the VM to cancel this payment */
+ if (-1 != cancel_button.cancelbuttonfd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment started, watching for cancel button\n");
+ start_read_cancel_button ();
+ }
+ return pa;
+}
+
+
+/**
+ * @brief Vending successful, conclude payment activity.
+ */
+static void
+vend_success (void)
+{
+ 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;
+ }
+}
+
+
+/**
+ * Runs asynchronous cleanup part for freeing a
+ * refund activity.
+ *
+ * @param[in] cls a `struct Refund` to clean up
+ */
+static void
+async_refund_cleanup_job (void *cls)
+{
+ struct Refund *r = cls;
+
+ if (NULL != r->ctx)
+ GNUNET_CURL_fini (r->ctx);
+ if (NULL != r->rc)
+ GNUNET_CURL_gnunet_rc_destroy (r->rc);
+ GNUNET_free (r);
+}
+
+
+/**
+ * @brief Callback to process a POST /refund request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param refund_uri the refund uri offered to the wallet
+ * @param h_contract hash of the contract a Browser may need to authorize
+ * obtaining the HTTP response.
+ */
+static void
+refund_complete_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const char *refund_uri,
+ const struct TALER_PrivateContractHashP *h_contract)
+{
+ struct Refund *r = cls;
+
+ (void) refund_uri;
+ r->orh = NULL;
+ if (MHD_HTTP_OK != hr->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to grant consumer refund: %u/%d\n",
+ hr->http_status,
+ (int) hr->ec);
+ }
+ GNUNET_CONTAINER_DLL_remove (refund_head,
+ refund_tail,
+ r);
+ GNUNET_SCHEDULER_add_now (&async_refund_cleanup_job,
+ r);
+}
+
+
+/**
+ * @brief Vending failed, provide refund.
+ */
+static void
+vend_failure (void)
+{
+ struct Product *p;
+ struct Refund *r;
+
+ if (NULL == payment_activity)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ p = payment_activity->product;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received MDB vend failure for `%s', refunding customer\n",
+ p->description);
+ p->sold_out = true;
+ mdb.cmd = &endSession;
+ mdb.session_running = false;
+ r = GNUNET_new (struct Refund);
+ r->ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &r->rc);
+ r->rc = GNUNET_CURL_gnunet_rc_create (r->ctx);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CURL_append_header (r->ctx,
+ p->auth_header));
+ r->orh = TALER_MERCHANT_post_order_refund (r->ctx,
+ (NULL == p->instance)
+ ? backend_base_url
+ : p->instance,
+ payment_activity->order_id,
+ &payment_activity->amount,
+ "failed to dispense product",
+ &refund_complete_cb,
+ r);
+ if (NULL == r->orh)
+ {
+ 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 = &cmd_deny_vend;
+ }
+ else
+ {
+ mdb.cmd = &endSession;
+ mdb.session_running = false;
+ }
+ run_mdb_event_loop ();
+ cleanup_payment (payment_activity);
+ payment_activity = NULL;
+ break;
+ case 'a':
+ payment_activity->paid = true;
+ mdb.cmd = &cmd_approve_vend;
+ 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)
+ {
+ if ( (sold_out_enabled) &&
+ (products[i].sold_out) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Product %s sold out, denying vend\n",
+ products[i].description);
+ mdb.cmd = &cmd_reader_display_sold_out;
+ run_mdb_event_loop ();
+ start_read_keyboard ();
+ return;
+ }
+ payment_activity = launch_payment (&products[i]);
+ start_read_keyboard ();
+ return;
+ }
+ fprintf (stderr,
+ "Unknown command '%c'\n",
+ (char) input);
+ start_read_keyboard ();
+}
+
+
+/**
+ * @brief Read the state of the cancel button GPIO pin
+ *
+ * @param cls closure
+ */
+static void
+cancel_button_pressed (void *cls)
+{
+ (void) cls;
+ char value;
+
+ cancelbutton_task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Cancel button event detected\n");
+ if (1 !=
+ read (cancel_button.cancelbuttonfd,
+ &value,
+ 1))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "read");
+ start_read_cancel_button ();
+ return;
+ }
+
+ GNUNET_break (0 == lseek (cancel_button.cancelbuttonfd,
+ 0,
+ SEEK_SET));
+ /* This point should only be reached when a order is pending, because
+ * the scheduler read file gets added in the function "launch_payment".
+ * But anyway safe check, else do nothing */
+ if (NULL != payment_activity)
+ {
+ if ('1' == value)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancel button pressed, canceling current order\n");
+ if (GNUNET_NO == payment_activity->paid)
+ {
+ /* The current payment was not paid already, deny it */
+ mdb.cmd = &cmd_deny_vend;
+ }
+ else
+ {
+ /* The order was paid and if we know this, then it is also yielded,
+ * just end the current session */
+ mdb.cmd = &endSession;
+ mdb.session_running = false;
+ }
+ run_mdb_event_loop ();
+ cleanup_payment (payment_activity);
+ payment_activity = NULL;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Cancel button spurious event (%d), looking for more\n",
+ (int) value);
+ start_read_cancel_button ();
+ }
+ }
+}
+
+
+/**
+ * @brief Wait for a keyboard input
+ */
+static void
+start_read_keyboard (void)
+{
+ 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 Wait for cancel button during payment activity
+ */
+static void
+start_read_cancel_button (void)
+{
+ struct GNUNET_DISK_FileHandle fh = {
+ cancel_button.cancelbuttonfd
+ };
+
+ if (NULL != cancelbutton_task)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancel task still active (!?), not starting another one\n");
+ return;
+ }
+ cancelbutton_task = GNUNET_SCHEDULER_add_read_file (
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &fh,
+ &cancel_button_pressed,
+ 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 partially, 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);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Wrote %d bytes to MDB\n",
+ (int) ret);
+ 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 (&cmd_begin_session == mdb.last_cmd)
+ mdb.session_running = true;
+ if (&cmd_deny_vend == mdb.last_cmd)
+ {
+ mdb.session_running = false;
+ mdb.cmd = &endSession;
+ }
+ if (&cmd_reader_display_sold_out == mdb.last_cmd)
+ mdb.cmd = &cmd_deny_vend;
+ if (&cmd_reader_display_internal_error == mdb.last_cmd)
+ mdb.cmd = &cmd_deny_vend;
+ if (&cmd_reader_display_backend_not_reachable == mdb.last_cmd)
+ mdb.cmd = &cmd_deny_vend;
+ 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;
+ unsigned int tmp = 0;
+ uint32_t chkSum;
+
+ /* 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;
+
+ 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:
+ {
+
+ /* Calculate the checksum and check it */
+ chkSum = cmd;
+
+ for (size_t offset = 1; offset < ((hex_len / 2)); offset++)
+ {
+ chkSum += tmp;
+ if (1 != sscanf (hex + (2 * offset),
+ "%2X",
+ &tmp))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received non-HEX input `%.*s'\n",
+ (int) hex_len,
+ hex);
+ GNUNET_break_op (0);
+ return;
+ }
+ }
+ if ( ((uint8_t) (chkSum & 0xFF)) != tmp)
+ {
+ mdb.cmd = &cmd_reader_display_internal_error;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received command with wrong checksum `%.*s'\n",
+ (int) hex_len,
+ hex);
+ break;
+
+ }
+ unsigned int product;
+
+ GNUNET_break (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 MDB\n",
+ product);
+ if ( (sold_out_enabled) &&
+ (products[i].sold_out) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Product %s sold out, denying vend\n",
+ products[i].description);
+ mdb.cmd = &cmd_reader_display_sold_out;
+ return;
+ }
+ payment_activity = launch_payment (&products[i]);
+ if (NULL == payment_activity)
+ vend_failure ();
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unknown product %u selected on MDB, denying vend\n",
+ product);
+ mdb.cmd = &cmd_deny_vend;
+ break;
+ }
+ case VMC_VEND_SUCCESS:
+ GNUNET_break (mdb.session_running);
+ vend_success ();
+ break;
+ case VMC_VEND_FAILURE:
+ {
+ GNUNET_break (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 = false;
+ 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_REVALUE:
+ {
+ unsigned int subcmd;
+
+ 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_REVALUE_REQUEST:
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request for revalue via MDB\n");
+ mdb.cmd = &cmd_revalue_approved;
+ break;
+ }
+ case VMC_REVALUE_LIMIT_REQUEST:
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request for revalue limit amount via MDB\n");
+ mdb.cmd = &cmd_revalue_amount;
+ break;
+ }
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unknown MDB sub-command %X of command %X\n",
+ subcmd,
+ cmd);
+ break;
+ }
+ break;
+ }
+ case VMC_CONF:
+ {
+ unsigned int subcmd;
+
+ 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_READER_CONF:
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request for configuration via MDB\n");
+ mdb.cmd = &cmd_reader_config_data;
+ break;
+
+ }
+ case VMC_SETUP_MAX_MIN_PRICES:
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received max and min prices via MDB\n");
+ break;
+ }
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unknown MDB sub-command %X of command %X\n",
+ subcmd,
+ cmd);
+ break;
+ }
+ break;
+ }
+ case VMC_POLL:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received POLL from MDB (ignored)\n");
+ break;
+ case VMC_CMD_RESET:
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received RESET 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 = false;
+ if (NULL != payment_activity)
+ {
+ cleanup_payment (payment_activity);
+ payment_activity = NULL;
+ }
+ for (unsigned int i = 0; i < products_length; i++)
+ {
+ if ( (sold_out_enabled) &&
+ (0 != strcmp (products[i].description,
+ "empty")) &&
+ (products[i].sold_out) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resetting sold out state of product %s\n",
+ products[i].description);
+ products[i].sold_out = false;
+ }
+ }
+ break;
+ case VMC_READER_ENABLE:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received Reader Enable via MDB\n");
+ mdb.session_running = false;
+ break;
+ case VMC_READER_CANCEL:
+ mdb.cmd = &cmd_reader_cancelled;
+ mdb.session_running = false;
+ 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 = false;
+ 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)
+ {
+ mdb_active = true;
+ /* find beginning 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 (void)
+{
+ struct GNUNET_DISK_FileHandle fh = { mdb.uartfd };
+
+ /* begin session if no cmd waits for sending and no cmd is received from the VMC */
+ if ( (! mdb.session_running) &&
+ (NULL == mdb.cmd) &&
+ (NULL == mdb.last_cmd) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Beginning MDB session\n");
+ mdb.cmd = &cmd_begin_session;
+ }
+ /* 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_active)
+ mdb.wtask = GNUNET_SCHEDULER_add_now (&write_mdb_command,
+ NULL);
+ else
+ mdb.wtask = GNUNET_SCHEDULER_add_write_file ((in_shutdown)
+ ? SHUTDOWN_MDB_TIMEOUT
+ :
+ 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 in this case the name of the configuration file to read from
+ * @param section section of the config file to read from
+ */
+static void
+read_products (void *cls,
+ const char *section)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct Product tmpProduct;
+ char *tmpKey;
+ char *thumbnail_fn;
+
+ /* if the current section is not a product skip it */
+ if (0 != strncmp (section,
+ "product-",
+ strlen ("product-")))
+ return;
+ /* the current section is a product, parse its specifications and store it in a temporary product */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "description",
+ &tmpProduct.description))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "description");
+ return;
+ }
+ if (sold_out_enabled)
+ {
+ if (0 == strcmp (tmpProduct.description,
+ "empty"))
+ {
+ tmpProduct.sold_out = true;
+ }
+ else
+ {
+ tmpProduct.sold_out = false;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ section,
+ "price",
+ &tmpProduct.price))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "price");
+ GNUNET_free (tmpProduct.description);
+ return;
+ }
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "key",
+ &tmpKey))
+ {
+ tmpProduct.key = tmpKey[0];
+ GNUNET_free (tmpKey);
+ }
+ else
+ {
+ /* no key */
+ tmpProduct.key = '\0';
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "instance",
+ &tmpProduct.instance))
+ tmpProduct.instance = NULL;
+ tmpProduct.preview = NULL;
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "thumbnail",
+ &thumbnail_fn))
+ {
+ struct GNUNET_DISK_FileHandle *fh;
+
+ fh = GNUNET_DISK_file_open (thumbnail_fn,
+ GNUNET_DISK_OPEN_READ,
+ GNUNET_DISK_PERM_NONE);
+ if (NULL == fh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not open thumbnail `%s'\n",
+ thumbnail_fn);
+ }
+ else
+ {
+ off_t flen;
+
+ if (GNUNET_OK !=
+ GNUNET_DISK_file_handle_size (fh,
+ &flen))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "stat",
+ thumbnail_fn);
+ }
+ else
+ {
+ void *thumb;
+ size_t len;
+ ssize_t ret;
+
+ len = (size_t) flen;
+ thumb = GNUNET_malloc (len);
+ ret = GNUNET_DISK_file_read (fh,
+ thumb,
+ len);
+ if ( (ret < 0) ||
+ (len != ((size_t) ret)) )
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "read",
+ thumbnail_fn);
+ }
+ else
+ {
+ const char *mime_type = "";
+ const char *ext;
+ char *base64;
+
+ ext = strrchr (thumbnail_fn, '.');
+ if (NULL != ext)
+ {
+ static const struct
+ {
+ const char *ext;
+ const char *mime;
+ } mimes[] = {
+ { ".png", "image/png" },
+ { ".jpg", "image/jpeg" },
+ { ".jpeg", "image/jpeg" },
+ { ".svg", "image/svg" },
+ { NULL, NULL }
+ };
+
+ for (unsigned int i = 0; NULL != mimes[i].ext; i++)
+ if (0 == strcasecmp (mimes[i].ext,
+ ext))
+ {
+ mime_type = mimes[i].mime;
+ break;
+ }
+ }
+ (void) GNUNET_STRINGS_base64_encode (thumb,
+ len,
+ &base64);
+ GNUNET_asprintf (&tmpProduct.preview,
+ "data:%s;base64,%s",
+ mime_type,
+ base64);
+ GNUNET_free (base64);
+ }
+ GNUNET_free (thumb);
+ }
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_file_close (fh));
+ }
+ GNUNET_free (thumbnail_fn);
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ section,
+ "number",
+ &tmpProduct.number))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "number");
+ GNUNET_free (tmpProduct.description);
+ GNUNET_free (tmpProduct.instance);
+ GNUNET_free (tmpProduct.preview);
+ return;
+ }
+
+ {
+ char *auth;
+
+ if ( (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "BACKEND-AUTHORIZATION",
+ &auth)) &&
+ (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler-mdb",
+ "BACKEND-AUTHORIZATION",
+ &auth)) )
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "BACKEND-AUTHORIZATION");
+ GNUNET_free (tmpProduct.description);
+ GNUNET_free (tmpProduct.instance);
+ GNUNET_free (tmpProduct.preview);
+ return;
+ }
+ GNUNET_asprintf (&tmpProduct.auth_header,
+ "%s: %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ auth);
+ GNUNET_free (auth);
+ }
+ /* append the temporary product to the existing products */
+ GNUNET_array_append (products,
+ products_length,
+ tmpProduct);
+}
+
+
+/**
+ * @brief Initialise the uart device to send mdb commands
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+mdb_init (void)
+{
+ 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 successful 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 configuration file
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+
+ /* parse the devices, if no config entry is found, a standard is used */
+ 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");
+ }
+ /* parse the taler configurations */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler-mdb",
+ "backend-base-url",
+ &backend_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler-mdb",
+ "backend-base-url");
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ {
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler-mdb",
+ "essid",
+ &essid))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No ESSID specified, will not advertise WLAN\n");
+ }
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler-mdb",
+ "fulfillment-msg",
+ &fulfillment_msg))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler-mdb",
+ "fulfillment-msg");
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+
+ /* parse the products */
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &read_products,
+ (void *) cfg);
+ if (NULL == products)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No valid products configured\n");
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+
+ 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_WARNING,
+ "Unable to initialize NFC (nfc_init() failed)\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ /* open gpio pin for cancel button */
+ {
+ int efd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exporting GPIO pin 23\n");
+ efd = open ("/sys/class/gpio/export",
+ O_WRONLY);
+ if (-1 == efd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unable to open /gpio/export for cancel button\n");
+ }
+ else
+ {
+ if (2 != write (efd,
+ "23",
+ 2))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "write",
+ "/sys/class/gpio/export");
+ }
+ GNUNET_assert (0 == close (efd));
+ }
+ }
+
+ /* set direction: input */
+ {
+ int dfd;
+
+ dfd = open ("/sys/class/gpio/gpio23/direction",
+ O_WRONLY);
+ if (-1 == dfd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unable to open /gpio/gpio23/direction for cancel button\n");
+ }
+ else
+ {
+ if (2 != write (dfd,
+ "in",
+ 2))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "write",
+ "/sys/class/gpio/gpio23/direction");
+ }
+ }
+ GNUNET_assert (0 == close (dfd));
+ }
+
+ {
+ /* actually open fd for reading the state */
+ cancel_button.cancelbuttonfd = open ("/sys/class/gpio/gpio23/value",
+ O_RDONLY);
+ if (-1 == cancel_button.cancelbuttonfd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unable to open /gpio/gpio23/value for cancel button\n");
+ }
+ }
+
+
+#if HAVE_QRENCODE_H
+ /* open the framebuffer device */
+ qrDisplay.devicefd = open (framebuffer_device_filename,
+ O_RDWR);
+ if (-1 != 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);
+ if (MAP_FAILED == qrDisplay.memory)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "mmap");
+ return;
+ }
+
+ /* set the screen to white */
+ memset (qrDisplay.memory,
+ 0xFF,
+ qrDisplay.var_info.xres * qrDisplay.var_info.yres
+ * sizeof (uint16_t));
+
+ /* open backlight file to turn display backlight on and off */
+ 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 '-i' flag was set, invert the on and off values */
+ if (backlight_invert)
+ {
+ backlight_on = '0';
+ backlight_off = '1';
+ }
+ /* turn off the backlight */
+ (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 have_tty;
+ 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 ('s',
+ "enable-soldout",
+ "enable detection of sold-out products, preventing vend operations of the respective product until the process is restarted",
+ &sold_out_enabled),
+ 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
+ };
+
+ 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 */
+ cmd_reader_config_data = 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);
+ cmd_begin_session = setup_mdb_cmd ("Begin Session",
+ READER_BEGIN_SESSION,
+ READER_FUNDS_AVAILABLE);
+ cmd_approve_vend = setup_mdb_cmd ("Approve Vend",
+ READER_VEND_APPROVE,
+ READER_VEND_AMOUNT);
+ cmd_reader_cancelled = setup_mdb_cmd ("Confirm cancellation",
+ READER_CANCELLED,
+ NULL);
+ cmd_deny_vend = setup_mdb_cmd ("Deny Vend",
+ READER_VEND_DENIED,
+ NULL);
+ endSession = setup_mdb_cmd ("End Session",
+ READER_END_SESSION,
+ NULL);
+ cmd_revalue_approved = setup_mdb_cmd ("Reader Approve Revalue",
+ READER_REVALUE_APPROVED,
+ NULL);
+
+ cmd_revalue_amount = setup_mdb_cmd ("Send Revalue Limit Amount",
+ READER_REVALUE_LIMIT,
+ READER_REVALUE_LIMIT_AMOUNT);
+
+ cmd_reader_NACK = setup_mdb_cmd ("Reader NACK",
+ READER_NACK,
+ NULL);
+
+ cmd_reader_display_sold_out = setup_mdb_cmd ("Display Sold Out",
+ READER_DISPLAY_REQUEST,
+ READER_DISPLAY_REQUEST_TIME
+ READER_DISPLAY_SOLD_OUT);
+
+ cmd_reader_display_internal_error = setup_mdb_cmd (
+ "Display Communication Error",
+ READER_DISPLAY_REQUEST,
+ READER_DISPLAY_REQUEST_TIME
+ READER_DISPLAY_INTERNAL_ERROR);
+
+ cmd_reader_display_backend_not_reachable = setup_mdb_cmd (
+ "Display Backend not reachable",
+ READER_DISPLAY_REQUEST,
+ READER_DISPLAY_REQUEST_TIME
+ READER_DISPLAY_BACKEND_NOT_REACHABLE);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_get_utf8_args (argc, argv,
+ &argc, &argv))
+ return 4;
+ 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);
+ GNUNET_free_nz ((void *) argv);
+ 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_NO == ret)
+ return 0;
+ if (GNUNET_OK != ret)
+ return 1;
+ return global_ret;
+}