taler-mdb

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

commit 90adc8d43444b097a7f0608671238d1d930a4e6d
parent 57de4d50cb0dfdc391386a7383a82dcfb47fbb38
Author: Boss Marco <bossm8@students.bfh.ch>
Date:   Tue, 10 Dec 2019 16:29:57 +0100

Merge branch 'master' of git+ssh://git.taler.net/taler-mdb

Diffstat:
Mconfigure.ac | 6+++++-
Msrc/Makefile.am | 12++++++++++++
Msrc/main.c | 53++++++++++++++++++++++++++++++++++++++++++++++++++---
Asrc/qr-show.c | 425+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 492 insertions(+), 4 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -96,9 +96,13 @@ AC_ARG_WITH(qrencode, AS_IF([test "$qrencode" != 1], [ +AM_CONDITIONAL(HAVE_QR, false) QR_LIBS="" QR_CFLAGS="" -]) +], +[AM_CONDITIONAL(HAVE_QR, true)]) + + AC_SUBST(QR_CFLAGS) AC_SUBST(QR_LIBS) diff --git a/src/Makefile.am b/src/Makefile.am @@ -1,6 +1,10 @@ # This Makefile.am is in the public domain bin_PROGRAMS = taler-mdb +if HAVE_QR +bin_PROGRAMS += qr-show +endif + if USE_COVERAGE AM_CFLAGS = --coverage -O0 XLIB = -lgcov @@ -21,6 +25,14 @@ taler_mdb_LDADD = \ $(XLIB) +qr_show_SOURCES = \ + qr-show.c +qr_show_LDADD = \ + -lgnunetutil \ + @QR_LIBS@ \ + $(XLIB) + + if HAVE_LIBCURL taler_mdb_LDADD += -lcurl else diff --git a/src/main.c b/src/main.c @@ -249,9 +249,16 @@ struct Product unsigned long long number; /** + * Set to #GNUNET_YES if this product was found + * to have been sold out (VEND failure). + */ + int sold_out; + + /** * Key for the product (optional, needed to test the application without vending machine) */ char key; + }; @@ -271,6 +278,11 @@ struct PaymentActivity struct TALER_MERCHANT_CheckPaymentOperation *cpo; /** + * The product being sold. + */ + struct Product *product; + + /** * Order ID for pending order */ char *order_id; @@ -583,6 +595,11 @@ static int disable_mdb; 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 }; @@ -1320,7 +1337,7 @@ proposal_cb (void *cls, * @return payment activity for the order, NULL on failure */ static struct PaymentActivity * -launch_payment (const struct Product *product) +launch_payment (struct Product *product) { struct PaymentActivity *pa; json_t *orderReq; @@ -1360,6 +1377,7 @@ launch_payment (const struct Product *product) return NULL; } pa = GNUNET_new (struct PaymentActivity); + pa->product = product; pa->amount = product->price; /* put the order on the merchant's backend */ pa->po = TALER_MERCHANT_order_put (ctx, @@ -1412,6 +1430,7 @@ refund_complete_cb (void *cls, { struct Refund *r = cls; + (void) obj; r->rio = NULL; if (MHD_HTTP_OK != http_status) { @@ -1433,6 +1452,7 @@ refund_complete_cb (void *cls, static void vend_failure () { + struct Product *p; struct Refund *r; if (NULL == payment_activity) @@ -1440,8 +1460,11 @@ vend_failure () GNUNET_break (0); return; } + p = payment_activity->product; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received MDB vend failure, refunding customer\n"); + "Received MDB vend failure for `%s', refunding customer\n", + p->description); + p->sold_out = GNUNET_YES; mdb.cmd = &endSession; mdb.session_running = GNUNET_NO; r = GNUNET_new (struct Refund); @@ -1555,6 +1578,17 @@ read_keyboard_command (void *cls) 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 = &denyVend; + run_mdb_event_loop (); + start_read_keyboard (); + return; + } payment_activity = launch_payment (&products[i]); start_read_keyboard (); return; @@ -1702,7 +1736,7 @@ write_mdb_command (void *cls) 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.cmd->data.bin[idx]; mdb.txBuffer[mdb.cmd->cmd.bin_size + mdb.cmd->data.bin_size] = (uint8_t) (chkSum & 0xFF); } @@ -1826,6 +1860,15 @@ handle_command (const char *hex, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Product %u selected on NFC\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 = &denyVend; + return; + } payment_activity = launch_payment (&products[i]); return; } @@ -2568,6 +2611,10 @@ main (int argc, "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)", diff --git a/src/qr-show.c b/src/qr-show.c @@ -0,0 +1,425 @@ +/* + 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 qr-show.c +* @brief shows a QR code on the display for a defined amount of time +* @author Boss Marco +* @author Christian Grothoff +*/ +#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 <qrencode.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <fcntl.h> +/* for adafruit pitft display */ +#include <linux/fb.h> + +/** + * Disable i18n support. + */ +#define _(s) (s) + +/** + * 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; +}; + + +/** + * Next program to launch. + */ +static char *const *arg_next; + +static struct GNUNET_TIME_Relative delay; + +/** + * Refenence to the delay task + */ +static struct GNUNET_SCHEDULER_Task *delay_task; + +/** + * 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; + +static int backlight_invert; +static char backlight_on = '1'; +static char backlight_off = '0'; + +/** + * Handle for the framebuffer device + */ +static struct Display qrDisplay; + + +/** + * @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; + size_t xOff; + size_t yOff; + + if (0 > qrDisplay.devicefd) + return; /* no display, no dice */ + qri = QRinput_new2 (0, QR_ECLEVEL_L); + if (NULL == qri) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "QRinput_new2"); + return; + } + /* 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 (uri), + (unsigned char *) uri)) && + (0 != + QRinput_append (qri, + QR_MODE_8, + strlen (uri), + (unsigned char *) uri))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "QRinput_append"); + return; + } + 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); +} + + +static void +stop_task (void *cls) +{ + (void) cls; + delay_task = NULL; + GNUNET_SCHEDULER_shutdown (); +} + + +static void +shutdown_task (void *cls) +{ + (void) cls; + if (NULL != delay_task) + { + GNUNET_SCHEDULER_cancel (delay_task); + delay_task = NULL; + } + 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); + 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; + } +} + + +/** + * @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) cfgfile; + + if (NULL == args[0]) + return; + arg_next = args + 1; + 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"); + } + /* 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); + } + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); + show_qrcode (args[0]); + delay_task = GNUNET_SCHEDULER_add_delayed (delay, + &stop_task, + NULL); +} + + +int +main (int argc, + char*const*argv) +{ + int ret; + /* the available command line options */ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_relative_time ('d', + "delay", + "DELAY", + "how long should we display the text before continuing", + &delay), + GNUNET_GETOPT_OPTION_END + }; + + ret = GNUNET_PROGRAM_run (argc, + argv, + "qr-show", + "This is an application to show a QR code for a defined period of time before starting another program.\n", + options, + &run, + NULL); + if (GNUNET_OK != ret) + return 1; + if ( (NULL == arg_next) || + (NULL == arg_next[0]) ) + return 0; + execvp (arg_next[0], + arg_next); + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "execvp", + arg_next[0]); + return 1; +}