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:
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;
+}