summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-01-24 12:27:54 +0100
committerChristian Grothoff <christian@grothoff.org>2020-01-24 12:27:54 +0100
commit5e1227f62cc4814284fc1b0a44b1c8f42418f6b2 (patch)
tree02fa8787d73b2fe05cc88cf478eed03454efc268
parent237dbf9290ab2198894a703149eb1a5e2347da73 (diff)
parent1e464cbc9e230895ad3cee64f83bc4452b12e767 (diff)
downloadtaler-mdb-5e1227f62cc4814284fc1b0a44b1c8f42418f6b2.tar.gz
taler-mdb-5e1227f62cc4814284fc1b0a44b1c8f42418f6b2.tar.bz2
taler-mdb-5e1227f62cc4814284fc1b0a44b1c8f42418f6b2.zip
merge
-rw-r--r--src/main.c333
-rw-r--r--taler.conf82
2 files changed, 330 insertions, 85 deletions
diff --git a/src/main.c b/src/main.c
index 3110a13..2127a7b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -23,8 +23,6 @@ along with
* @author Christian Grothoff
* @author Dominik Hofer
*
-* TODO:
-* - comment code mdb incl. doxygen struct members (hofer)
*/
#include "config.h"
#include <stdio.h>
@@ -71,7 +69,9 @@ along with
*/
#define _(s) (s)
-#define BACKEND_POLL_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+/* FIXME Adjusted Time out because low internet connection at 36C3 */
+#define BACKEND_POLL_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 5)
#define NFC_FAILURE_RETRY_FREQ GNUNET_TIME_UNIT_SECONDS
@@ -125,6 +125,7 @@ along with
/* VMC commands */
#define VMC_CMD_START 0x02
#define VMC_CMD_END 0x03
+#define VMC_CMD_RESET 0x10
/**
* Acknowledgement
@@ -135,6 +136,8 @@ along with
* Request for configuration.
*/
#define VMC_CONF 0x11
+#define VMC_READER_CONF 0x00
+#define VMC_SETUP_MAX_MIN_PRICES 0x01
/**
* Machine is polling for something.
@@ -179,6 +182,9 @@ along with
/* 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"
@@ -210,8 +216,17 @@ along with
/* Cancelled Command */
#define READER_CANCELLED "08"
-/* Unused reader commands */
+/* 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"
@@ -227,8 +242,14 @@ along with
*/
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;
};
@@ -238,10 +259,19 @@ struct MdbBlock
*/
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;
};
@@ -361,18 +391,39 @@ struct PaymentActivity
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;
/**
@@ -386,12 +437,24 @@ struct MdbHandle
*/
size_t tx_len;
+ /**
+ * Time out to wait for an acknoweledge 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
+ */
int session_running;
+ /**
+ * File descriptor to the UART device file
+ */
int uartfd;
};
@@ -434,6 +497,18 @@ struct Display
};
/**
+ * 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
@@ -588,6 +663,26 @@ static struct MdbCommand revalueApproved;
static struct MdbCommand revalueAmount;
/**
+ * Send NACK
+ */
+static struct MdbCommand readerNACK;
+
+/**
+ * Display Request for Sold Out
+ */
+static struct MdbCommand readerDisplaySoldOut;
+
+/**
+ * Display Request for Error Message
+ */
+static struct MdbCommand readerDisplayInternalError;
+
+/**
+ * Display Request for Error Message
+ */
+static struct MdbCommand readerDisplayBackendNotReachable;
+
+/**
* Terminate session.
*/
static struct MdbCommand endSession;
@@ -620,11 +715,6 @@ static char backlight_off = '0';
/**
* State for the implementation of the 'cancel' button.
*/
-struct CancelButton
-{
- int cancelbuttonfd;
-};
-
static struct CancelButton cancelButton;
/**
@@ -817,6 +907,11 @@ cleanup_payment (struct PaymentActivity *pa)
nfc_abort_command (pa->pnd);
nfc_close (pa->pnd);
}
+ if (NULL != cancelbutton_task)
+ {
+ GNUNET_SCHEDULER_cancel (cancelbutton_task);
+ cancelbutton_task = NULL;
+ }
if (NULL != pa->po)
TALER_MERCHANT_proposal_cancel (pa->po);
if (NULL != pa->cpo)
@@ -917,6 +1012,11 @@ shutdown_task (void *cls)
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);
@@ -969,7 +1069,7 @@ shutdown_task (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unable to open /gpio/unexport for cancel button\n");
}
- (void) write (cancelButton.cancelbuttonfd, "17", 2);
+ (void) write (cancelButton.cancelbuttonfd, "23", 2);
close (cancelButton.cancelbuttonfd);
}
/* free the allocated productes read from config file */
@@ -1271,7 +1371,7 @@ check_payment_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Backend request to /check-payment failed: %u\n",
http_status);
- mdb.cmd = &denyVend;
+ mdb.cmd = &readerDisplayBackendNotReachable;
run_mdb_event_loop ();
cleanup_payment (pa);
GNUNET_assert (payment_activity == pa);
@@ -1370,7 +1470,7 @@ proposal_cb (void *cls,
"Failed to setup order with backend: %u/%d\n",
http_status,
(int) ec);
- mdb.cmd = &denyVend;
+ mdb.cmd = &readerDisplayBackendNotReachable;
run_mdb_event_loop ();
cleanup_payment (pa);
GNUNET_assert (payment_activity == pa);
@@ -1456,6 +1556,7 @@ launch_payment (struct Product *product)
GNUNET_free (pa);
return NULL;
}
+ /* Start to read the button on the VM to cancel this payment */
if (0 < cancelButton.cancelbuttonfd)
start_read_cancel_button ();
return pa;
@@ -1644,12 +1745,12 @@ read_keyboard_command (void *cls)
if (((char) input) == products[i].key)
{
if ( (sold_out_enabled) &&
- (products[i].sold_out) )
+ (GNUNET_YES == products[i].sold_out) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product %s sold out, denying vend\n",
products[i].description);
- mdb.cmd = &denyVend;
+ mdb.cmd = &readerDisplaySoldOut;
run_mdb_event_loop ();
start_read_keyboard ();
return;
@@ -1664,6 +1765,11 @@ read_keyboard_command (void *cls)
start_read_keyboard ();
}
+/**
+ * @brief Read the state of the cancel button GPIO pin
+ *
+ * @param cls closure
+ */
static void
cancel_button_pressed (void *cls)
{
@@ -1673,26 +1779,35 @@ cancel_button_pressed (void *cls)
read (cancelButton.cancelbuttonfd, &value, 1);
lseek (cancelButton.cancelbuttonfd, 0, SEEK_SET);
- if ( '1' == value )
+ /* 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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Cancel button pressed, canceling current order\n");
- if (GNUNET_NO == payment_activity->paid)
+ if ('1' == value)
{
- mdb.cmd = &denyVend;
+ 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 = &denyVend;
+ }
+ 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 = GNUNET_NO;
+ }
+ run_mdb_event_loop ();
+ cleanup_payment (payment_activity);
+ payment_activity = NULL;
}
else
{
- mdb.cmd = &endSession;
- mdb.session_running = GNUNET_NO;
+ start_read_cancel_button ();
}
- run_mdb_event_loop ();
- cleanup_payment (payment_activity);
- payment_activity = NULL;
- }
- else
- {
- start_read_cancel_button ();
}
}
@@ -1735,6 +1850,9 @@ start_read_keyboard ()
NULL);
}
+/**
+ * @brief Wait for cancel button during payment activity
+ */
static void
start_read_cancel_button ()
{
@@ -1870,6 +1988,12 @@ handle_ack ()
mdb.session_running = GNUNET_NO;
mdb.cmd = &endSession;
}
+ if (&readerDisplaySoldOut == mdb.last_cmd)
+ mdb.cmd = &denyVend;
+ if (&readerDisplayInternalError == mdb.last_cmd)
+ mdb.cmd = &denyVend;
+ if (&readerDisplayBackendNotReachable == mdb.last_cmd)
+ mdb.cmd = &denyVend;
mdb.last_cmd = NULL;
/* Cause the write-task to be re-scheduled now */
if (NULL != mdb.wtask)
@@ -1891,6 +2015,8 @@ 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)
@@ -1916,6 +2042,7 @@ handle_command (const char *hex,
GNUNET_break_op (0);
return;
}
+
/* parse the first byte (cmd) and the second byte (subcmd) */
switch (cmd)
{
@@ -1944,6 +2071,34 @@ handle_command (const char *hex,
{
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 = &readerDisplayInternalError;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received command with wrong checksum `%.*s'\n",
+ (int) hex_len,
+ hex);
+ break;
+
+ }
unsigned int product;
GNUNET_break (GNUNET_YES == mdb.session_running);
@@ -1969,15 +2124,15 @@ handle_command (const char *hex,
if (product == products[i].number)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Product %u selected on NFC\n",
+ "Product %u selected on MDB\n",
product);
if ( (sold_out_enabled) &&
- (products[i].sold_out) )
+ (GNUNET_YES == products[i].sold_out) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product %s sold out, denying vend\n",
products[i].description);
- mdb.cmd = &denyVend;
+ mdb.cmd = &readerDisplaySoldOut;
return;
}
payment_activity = launch_payment (&products[i]);
@@ -2067,14 +2222,60 @@ handle_command (const char *hex,
break;
}
case VMC_CONF:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received request for configuration via MDB\n");
- mdb.cmd = &readerConfigData;
- break;
+ {
+ 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 = &readerConfigData;
+ 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;
@@ -2103,6 +2304,19 @@ handle_command (const char *hex,
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")) &&
+ (GNUNET_YES == 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 = GNUNET_NO;
+ }
+ }
break;
case VMC_READER_ENABLE:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -2324,11 +2538,23 @@ read_products (void *cls,
"description");
return;
}
+ if (sold_out_enabled)
+ {
+ if (0 == strcmp (tmpProduct.description,
+ "empty"))
+ {
+ tmpProduct.sold_out = GNUNET_YES;
+ }
+ else
+ {
+ tmpProduct.sold_out = GNUNET_NO;
+ }
+ }
if (GNUNET_OK !=
- TALER_config_get_denom (cls,
- section,
- "price",
- &tmpProduct.price))
+ TALER_config_get_amount (cls,
+ section,
+ "price",
+ &tmpProduct.price))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
@@ -2620,25 +2846,25 @@ run (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Unable to open /gpio/export for cancel button\n");
}
- (void) write (cancelButton.cancelbuttonfd, "17", 2);
+ (void) write (cancelButton.cancelbuttonfd, "23", 2);
close (cancelButton.cancelbuttonfd);
- cancelButton.cancelbuttonfd = open ("/sys/class/gpio/gpio17/direction",
+ cancelButton.cancelbuttonfd = open ("/sys/class/gpio/gpio23/direction",
O_WRONLY);
if (0 > cancelButton.cancelbuttonfd)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unable to open /gpio/gpio17/direction for cancel button\n");
+ "Unable to open /gpio/gpio23/direction for cancel button\n");
}
(void) write (cancelButton.cancelbuttonfd, "in", 2);
close (cancelButton.cancelbuttonfd);
- cancelButton.cancelbuttonfd = open ("/sys/class/gpio/gpio17/value",
+ cancelButton.cancelbuttonfd = open ("/sys/class/gpio/gpio23/value",
O_RDONLY);
if (0 > cancelButton.cancelbuttonfd)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unable to open /gpio/gpio17/value for cancel button\n");
+ "Unable to open /gpio/gpio23/value for cancel button\n");
}
@@ -2861,6 +3087,25 @@ main (int argc,
READER_REVALUE_LIMIT,
READER_REVALUE_LIMIT_AMOUNT);
+ readerNACK = setup_mdb_cmd ("Reader NACK",
+ READER_NACK,
+ NULL);
+
+ readerDisplaySoldOut = setup_mdb_cmd ("Display Sold Out",
+ READER_DISPLAY_REQUEST,
+ READER_DISPLAY_REQUEST_TIME
+ READER_DISPLAY_SOLD_OUT);
+
+ readerDisplayInternalError = setup_mdb_cmd ("Display Communication Error",
+ READER_DISPLAY_REQUEST,
+ READER_DISPLAY_REQUEST_TIME
+ READER_DISPLAY_INTERNAL_ERROR);
+
+ readerDisplayBackendNotReachable = setup_mdb_cmd (
+ "Display Backend not reachable",
+ READER_DISPLAY_REQUEST,
+ READER_DISPLAY_REQUEST_TIME
+ READER_DISPLAY_BACKEND_NOT_REACHABLE);
ret = GNUNET_PROGRAM_run (argc,
argv,
"taler-mdb",
diff --git a/taler.conf b/taler.conf
index 8f382dd..7d925de 100644
--- a/taler.conf
+++ b/taler.conf
@@ -1,9 +1,9 @@
[taler]
-currency = TESTKUDOS
+currency = EUR
[taler-mdb]
-backend-base-url = http://backend.test.taler.net/
-backend-authorization = ApiKey sandbox
+backend-base-url = http://backend.euro.taler.net/
+backend-authorization = ApiKey Sandbox
# taler url for success message (see taler documentation)
fulfillment-url = taler://fulfillment-success
# alternative url (see taler documentation)
@@ -22,149 +22,149 @@ FRAMEBUFFER_DEVICE = /dev/fb1
FRAMEBUFFER_BACKLIGHT = /sys/class/backlight/soc:backlight/brightness
#Products
+#If sold out is enabled, products with description "empty" will be registered as sold out.
#machine number 55
[product-21]
description = empty
-price = TESTKUDOS:0.1
+price = EUR:0.0
number = 46
#machine number 54
[product-20]
description = empty
-price = TESTKUDOS:0.1
+price = EUR:0.0
number = 45
#machine number 53
[product-19]
-description = empty
-price = TESTKUDOS:0.1
+description = Hackerspace Passport
+price = EUR:1.0
number = 44
#machine number 52
[product-18]
-description = empty
-price = TESTKUDOS:0.1
+description = T-Shirt M
+price = EUR:5.0
number = 43
#machine number 51
[product-17]
-description = empty
-price = TESTKUDOS:0.1
+description = T-Shirt S
+price = EUR:5.0
number = 42
#machine number 50
[product-16]
-description = empty
-price = TESTKUDOS:0.1
+description = Electronic Kit
+price = EUR:10.0
number = 41
#machine number 45
[product-15]
description = Snickers
-price = TESTKUDOS:0.1
+price = EUR:0.3
number = 36
#machine number 44
[product-14]
-description = empty
-price = TESTKUDOS:0.1
+description = Electronic Kit
+price = EUR:7.0
number = 35
#machine number 43
[product-13]
-description = Twix
-price = TESTKUDOS:0.1
+description = empty
+price = EUR:0.0
key = d
number = 34
#machine number 42
[product-12]
description = Screwdriver
-price = TESTKUDOS:0.1
+price = EUR:15.0
key = h
number = 33
#machine number 41
[product-11]
-description = empty
-price = TESTKUDOS:0.1
+description = Twix
+price = EUR:0.3
key = j
number = 32
#machine number 40
[product-10]
description = Mars
-price = TESTKUDOS:0.1
+price = EUR:0.3
key = k
-number = 30
+number = 31
#machine number 34
[product-9]
-description = empty
-price = TESTKUDOS:0.1
+description = Book GRM - Brainfuck
+price = EUR:25.0
key = l
number = 25
#machine number 30
[product-8]
-description = Meter
-price = TESTKUDOS:0.1
+description = Ruler
+price = EUR:10.0
key = w
number = 21
#machine number 25
[product-7]
-description = empty
-price = TESTKUDOS:0.1
+description = NFC TAG
+price = EUR:1.0
key = e
number = 16
#machine number 24
[product-6]
description = Knive
-price = TESTKUDOS:0.1
+price = EUR:25.0
key = r
number = 15
#machine number 23
[product-5]
-description = empty
-price = TESTKUDOS:0.1
+description = Electronic Kit
+price = EUR:5.0
key = t
number = 14
#machine number 22
[product-4]
description = empty
-price = TESTKUDOS:0.1
+price = EUR:0.0
key = z
number = 13
#machine number 21
[product-3]
description = empty
-price = TESTKUDOS:0.1
+price = EUR:0.0
key = u
number = 12
#machine number 20
[product-2]
description = Gummy bears
-price = TESTKUDOS:0.1
+price = EUR:0.3
key = i
-number = 10
+number = 11
#machine number 14
-[produt-1]
+[product-1]
description = Sword
-price = TESTKUDOS:0.1
-key = o
-number = 5
+price = EUR:0.05
+number = 5
#machine number 10
[product-0]
description = Umbrella
-price = TESTKUDOS:0.1
+price = EUR:10.0
key = p
number = 1