diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-01-24 12:27:54 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-01-24 12:27:54 +0100 |
commit | 5e1227f62cc4814284fc1b0a44b1c8f42418f6b2 (patch) | |
tree | 02fa8787d73b2fe05cc88cf478eed03454efc268 | |
parent | 237dbf9290ab2198894a703149eb1a5e2347da73 (diff) | |
parent | 1e464cbc9e230895ad3cee64f83bc4452b12e767 (diff) | |
download | taler-mdb-5e1227f62cc4814284fc1b0a44b1c8f42418f6b2.tar.gz taler-mdb-5e1227f62cc4814284fc1b0a44b1c8f42418f6b2.tar.bz2 taler-mdb-5e1227f62cc4814284fc1b0a44b1c8f42418f6b2.zip |
merge
-rw-r--r-- | src/main.c | 333 | ||||
-rw-r--r-- | taler.conf | 82 |
2 files changed, 330 insertions, 85 deletions
@@ -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", @@ -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 |