From f81404b768799ea0954ce14c4a2fbc695e8aaa92 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Fri, 19 Apr 2019 10:21:59 +0200 Subject: [PATCH] Improved generic UI --- .gitignore | 4 + Makefile | 75 ++++-- glyphs/icon_crossmark.gif | Bin 0 -> 1131 bytes glyphs/icon_eye.gif | Bin 0 -> 1130 bytes glyphs/icon_validate_14.gif | Bin 0 -> 1125 bytes icon.gif => nanos_icon.gif | Bin nanox_icon.gif | Bin 0 -> 73 bytes script.ld | 2 +- src/app_main.c | 137 +++++----- src/app_main.h | 2 +- src/glyphs.c | 51 ---- src/glyphs.h | 42 --- src/lib/json_parser.c | 48 ++-- src/lib/json_parser.h | 18 +- src/lib/transaction.c | 17 +- src/lib/transaction.h | 3 +- src/lib/transaction_parser.c | 489 ----------------------------------- src/lib/transaction_parser.h | 95 ------- src/lib/tx_display.c | 190 ++++++++++++++ src/lib/tx_display.h | 65 +++++ src/lib/tx_parser.c | 170 ++++++++++++ src/lib/tx_parser.h | 86 ++++++ src/lib/tx_validate.c | 162 ++++++++++++ src/lib/tx_validate.h | 34 +++ src/view.c | 162 ++++++++---- src/view.h | 22 +- src/view_common.c | 138 +++++----- src/view_common.h | 63 +++-- src/view_conf.c | 47 ++-- src/view_expl.c | 121 ++++++--- 30 files changed, 1225 insertions(+), 1018 deletions(-) create mode 100644 glyphs/icon_crossmark.gif create mode 100644 glyphs/icon_eye.gif create mode 100644 glyphs/icon_validate_14.gif rename icon.gif => nanos_icon.gif (100%) create mode 100644 nanox_icon.gif delete mode 100644 src/glyphs.c delete mode 100644 src/glyphs.h delete mode 100644 src/lib/transaction_parser.c delete mode 100644 src/lib/transaction_parser.h create mode 100644 src/lib/tx_display.c create mode 100644 src/lib/tx_display.h create mode 100644 src/lib/tx_parser.c create mode 100644 src/lib/tx_parser.h create mode 100644 src/lib/tx_validate.c create mode 100644 src/lib/tx_validate.h diff --git a/.gitignore b/.gitignore index 1f037415..eaf7e6bd 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,7 @@ debug/app\.asm bin/app\.hex bin/app\.elf + +src/glyphs\.h + +src/glyphs\.c diff --git a/Makefile b/Makefile index a6b35a4f..a14e22da 100755 --- a/Makefile +++ b/Makefile @@ -19,51 +19,76 @@ ifeq ($(BOLOS_SDK),) $(error BOLOS_SDK is not set) endif - -dummy_submodules := $(shell git submodule update --init --recursive) - -SCRIPT_LD:=$(CURDIR)/script.ld - include $(BOLOS_SDK)/Makefile.defines # Main app configuration APPNAME = "Cosmos" APPVERSION_M=1 -APPVERSION_N=3 -APPVERSION_P=2 +APPVERSION_N=5 +APPVERSION_P=0 + +APP_LOAD_PARAMS = --appFlags 0x200 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" + +ifeq ($(TARGET_NAME),TARGET_NANOS) +SCRIPT_LD:=$(CURDIR)/script.ld +ICONNAME:=$(CURDIR)/nanos_icon.gif +endif -APP_LOAD_PARAMS = --appFlags 0x00 --delete $(COMMON_LOAD_PARAMS) --path "44'/118'" -ICONNAME=$(CURDIR)/icon.gif +ifeq ($(TARGET_NAME),TARGET_NANOX) +ICONNAME:=$(CURDIR)/nanox_icon.gif +endif + +ifndef ICONNAME +$(error ICONNAME is not set) +endif + +all: default ############ # Platform DEFINES += UNUSED\(x\)=\(void\)x +DEFINES += PRINTF\(...\)= APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) DEFINES += APPVERSION=\"$(APPVERSION)\" -DEFINES += OS_IO_SEPROXYHAL IO_SEPROXYHAL_BUFFER_SIZE_B=128 -DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU - +DEFINES += OS_IO_SEPROXYHAL DEFINES += HAVE_BAGL HAVE_SPRINTF -DEFINES += PRINTF\(...\)= DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=7 IO_HID_EP_LENGTH=64 HAVE_USB_APDU DEFINES += LEDGER_MAJOR_VERSION=$(APPVERSION_M) LEDGER_MINOR_VERSION=$(APPVERSION_N) LEDGER_PATCH_VERSION=$(APPVERSION_P) -SDK_SOURCE_PATH += lib_u2f DEFINES += HAVE_U2F HAVE_IO_U2F -DEFINES += USB_SEGMENT_SIZE=64 DEFINES += U2F_PROXY_MAGIC=\"CSM\" +DEFINES += USB_SEGMENT_SIZE=64 DEFINES += U2F_MAX_MESSAGE_SIZE=264 #257+5+2 - DEFINES += HAVE_BOLOS_APP_STACK_CANARY -DEFINES += LEDGER_SPECIFIC + +ifeq ($(TARGET_NAME),TARGET_NANOX) +DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 + +DEFINES += HAVE_GLO096 +DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 +DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature +DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX +DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX +DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX + +DEFINES += HAVE_UX_FLOW + +#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl +SDK_SOURCE_PATH += lib_ux +else +# Assume Nano S +DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 +endif + +# X specific #Feature temporarily disabled +DEFINES += LEDGER_SPECIFIC #DEFINES += TESTING_ENABLED -#DEFINES += FEATURE_ED25519 # Compiler, assembler, and linker @@ -92,17 +117,19 @@ AS := $(GCCPATH)arm-none-eabi-gcc AFLAGS += LD := $(GCCPATH)arm-none-eabi-gcc -LDFLAGS += -O3 -s +LDFLAGS += -O3 -Os LDLIBS += -lm -lgcc -lc ########################## +include $(BOLOS_SDK)/Makefile.glyphs APP_SOURCE_PATH += src deps/jsmn/src deps/ledger-zxlib/include deps/ledger-zxlib/src SDK_SOURCE_PATH += lib_stusb lib_u2f lib_stusb_impl -#include $(BOLOS_SDK)/Makefile.glyphs - -all: default +ifeq ($(TARGET_NAME),TARGET_NANOX) +#SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl +SDK_SOURCE_PATH += lib_ux +endif load: python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) @@ -114,12 +141,10 @@ package: ./pkgdemo.sh ${APPNAME} ${APPVERSION} ${ICONNAME} # Import generic rules from the SDK - include $(BOLOS_SDK)/Makefile.rules #add dependency on custom makefile filename -dep/%.d: %.c Makefile.genericwallet - +dep/%.d: %.c Makefile listvariants: @echo VARIANTS COIN cosmos diff --git a/glyphs/icon_crossmark.gif b/glyphs/icon_crossmark.gif new file mode 100644 index 0000000000000000000000000000000000000000..2dcf9d9eebd7c09dddb2ab4b90cbda83c06b6ecd GIT binary patch literal 1131 zcmZ?wbhEHbh+i z#(Mch>H3D2mX`VkM*2oZxP`3=Lf^oD9vKom`v^ z4IGV}EX^$3V0vBhlS^|`^GaZPQxJMxaOwpmh};68%`T}$nPsUdZbkXI3Sf_0W#V>= zBTn<6dQ)(_#R;cgeV}9XLD7p8-7q0w8Uiuli5JL$C;!wuV45!iCT_<6|Nj2@{p;tC z@87({PcxqRv3h4bgmo;iK$ z#u35cm<%;FYmM&SmXyJnS^XAT( zJ!|HS>C>i8nLKIYg#NzXp6;&Bj`p_Jmgc6$hWfhNn(C^`it@73lH#Jmg8aPPob0U3 zjP$hBl;otug!s7FnCPg;i14t`kl>)e0DnJUA8#*D4|g|LU=%qz*xT9KSX)_In46iJ z7#kTH=@}C@U!{$jiyfNJ~jdh>MAe2nz`c@bmHVaC32Tu(PqUFf%a% zONgH=z;c3tK?g*D$_@r5g`R%S3EQ{LsG1P=G9yPZ?_TW!_O_JIo#INXvMrLH1~M>M F0{{d^jh+i z#(Mch>H3D2mX`VkM*2oZxP`3=Lf^oD9vKom`v^ z4IGV}EX^$3V0vBhlS^|`^GaZPQxJOHaOwpmh};68%`T}$nPsUdZbkXI3Sf_0W#V>= zGfwlMdQ)(_#RaEceV}9XLD7p8-7q0w8Uiuli5JL$C;!wuV45!iCT_<6|Nj2@{p;tC z@87({PcxqRv3h4bgmo;iK$ z#u35cm<%;FYmM&SmXyJnS^XAT( zJ!|HS>C>i8nLKIYg#NzXp6;&Bj`p_Jmgc6$hWfhNn(C^`it@73lH#Jmg8aPPob0U3 zjP$hBl;otug!s7FnCPg;i14t`kl>)e0DnJUA8#*D4|g|LV6Zqk*xT9KSX)_In46iJ z7#kTH=@}C@U!{$jiyfNJ~jdh>MAe2nz`c@bmHVaC32Tu(PqUFf%a% zONgH=z;c3tK?g*D$_@r5`JVojvmYFo*YYB?T-40^%iq9xH)NNlPJ66kronvNgOR}+ E02G{#(EtDd literal 0 HcmV?d00001 diff --git a/glyphs/icon_validate_14.gif b/glyphs/icon_validate_14.gif new file mode 100644 index 0000000000000000000000000000000000000000..ccb5cabe38d16ff857d540b64742e4b8346a6949 GIT binary patch literal 1125 zcmZ?wbhEHbh+i z#(Mch>H3D2mX`VkM*2oZx= z0Z#LvdQ)(_#So`neV}9XLD7p8-7q0w8Uiuli5JL$C;!wuV45!iCT_<6|Nj2@{p;tC z@87({PcxqRv3h4bgmo;iK$ z#u35cm<%;FYmM&SmXyJnS^XAT( zJ!|HS>C>i8nLKIYg#NzXp6;&Bj`p_Jmgc6$hWfhNn(C^`it@73lH#Jmg8aPPob0U3 zjP$hBl;otug!s7FnCPg;i14t`kl>)e0DnJUA8#*D4|g|L7iT9&2YWkP8*3{|3v)A5 z6JsMo1ARSR9c?X54RtkD6=fww1$jAH8EGj=32`w|5n&-g0e(JS9&Rp94t6$H7G@?! zU #include #include -#include +#include +#include + +#include "lib/transaction.h" +#include "lib/tx_display.h" +#include "view.h" +#include "signature.h" #ifdef TESTING_ENABLED // Generate using always the same private data @@ -38,6 +40,13 @@ const uint8_t privateKeyDataTest[] = { }; #endif +#if defined(TARGET_NANOX) +#define IS_UX_ALLOWED (G_ux_params.len != BOLOS_UX_IGNORE && G_ux_params.len != BOLOS_UX_CONTINUE) +#else +#define IS_UX_ALLOWED (ux.params.len != BOLOS_UX_IGNORE && ux.params.len != BOLOS_UX_CONTINUE) +#endif + + uint8_t bip32_depth; uint32_t bip32_path[10]; sigtype_t current_sigtype; @@ -98,8 +107,7 @@ unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { return 0; // nothing received from the master so far (it's a tx // transaction) } else { - return io_seproxyhal_spi_recv(G_io_apdu_buffer, - sizeof(G_io_apdu_buffer), 0); + return io_seproxyhal_spi_recv(G_io_apdu_buffer, sizeof(G_io_apdu_buffer), 0); } default: @@ -201,32 +209,33 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx, bool getBip32) { //region View Transaction Handlers -int tx_getData( - char *title, int max_title_length, - char *key, int max_key_length, - char *value, int max_value_length, - int page_index, - int chunk_index, - int *page_count_out, - int *chunk_count_out) { +int16_t tx_getData(char *title, int16_t max_title_length, + char *key, int16_t max_key_length, + char *value, int16_t max_value_length, + int16_t page_index, + int16_t chunk_index, + int16_t *page_count_out, + int16_t *chunk_count_out) { + *page_count_out = tx_display_num_pages(); + *chunk_count_out = 0; + + if (*page_count_out > 0) { + switch (current_sigtype) { + case SECP256K1: + snprintf(title, max_title_length, "SECP256K1 %02d/%02d", page_index + 1, *page_count_out); + break; + default: + snprintf(title, max_title_length, "INVALID!"); + break; + } - *page_count_out = transaction_get_display_pages(); + INIT_QUERY(key, max_key_length, value, max_value_length, chunk_index) + *chunk_count_out = tx_display_get_item(page_index); - switch (current_sigtype) { - case SECP256K1: - snprintf(title, max_title_length, "SECP256K1 %02d/%02d", page_index + 1, *page_count_out); - break; - default: - snprintf(title, max_title_length, "INVALID!"); - break; + tx_display_make_friendly(); } - *chunk_count_out = transaction_get_display_key_value( - key, max_key_length, - value, max_value_length, - page_index, chunk_index); - - return 0; + return *chunk_count_out; } void tx_accept_sign() { @@ -262,11 +271,11 @@ void tx_accept_sign() { if (result == 1) { set_code(G_io_apdu_buffer, length, APDU_CODE_OK); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); - view_display_signing_success(); + view_idle(0); } else { set_code(G_io_apdu_buffer, length, APDU_CODE_SIGN_VERIFY_ERROR); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length + 2); - view_display_signing_error(); + view_idle(0); } } @@ -297,16 +306,18 @@ void get_pk_compressed(uint8_t *pkc) { memcpy(pkc, publicKey.W, PK_COMPRESSED_LEN); } -int addr_getData(char *title, int max_title_length, - char *key, int max_key_length, - char *value, int max_value_length, - int page_index, - int chunk_index, - int *page_count_out, - int *chunk_count_out) { +int16_t addr_getData(char *title, int16_t max_title_length, + char *key, int16_t max_key_length, + char *value, int16_t max_value_length, + int16_t page_index, + int16_t chunk_index, + int16_t *page_count_out, + int16_t *chunk_count_out) { - *page_count_out = 0x7FFFFFFF; - *chunk_count_out = 1; + if (page_count_out) + *page_count_out = 1; + if (chunk_count_out) + *chunk_count_out = 1; snprintf(title, max_title_length, "Account %d", bip32_path[2] & 0x7FFFFFF); snprintf(key, max_key_length, "index %d", page_index); @@ -327,14 +338,19 @@ int addr_getData(char *title, int max_title_length, } void addr_accept() { - int pos = 0; // Send pubkey - get_pk_compressed(G_io_apdu_buffer + pos); - pos += PK_COMPRESSED_LEN; + uint8_t *pk = G_io_apdu_buffer; + get_pk_compressed(pk); + int pos = PK_COMPRESSED_LEN; - // Send bech32 addr - strcpy((char *) (G_io_apdu_buffer + pos), (char *) viewctl_DataValue); - pos += strlen((char *) viewctl_DataValue); + // Convert pubkey to bech32 address + const char *bech32_out = (char*)(G_io_apdu_buffer + pos); + uint8_t hashed_pk[CX_RIPEMD160_SIZE]; + cx_hash_sha256(pk, PK_COMPRESSED_LEN, pk, CX_SHA256_SIZE); + ripemd160_32(hashed_pk, pk); + + bech32EncodeFromBytes( bech32_out, bech32_hrp, hashed_pk, CX_RIPEMD160_SIZE); + pos += strlen(bech32_out); set_code(G_io_apdu_buffer, pos, APDU_CODE_OK); io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, pos + 2); @@ -366,8 +382,6 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { switch (G_io_apdu_buffer[OFFSET_INS]) { case INS_GET_VERSION: { - unsigned int UX_ALLOWED = (ux.params.len != BOLOS_UX_IGNORE && ux.params.len != BOLOS_UX_CONTINUE); - #ifdef TESTING_ENABLED G_io_apdu_buffer[0] = 0xFF; #else @@ -376,7 +390,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { G_io_apdu_buffer[1] = LEDGER_MAJOR_VERSION; G_io_apdu_buffer[2] = LEDGER_MINOR_VERSION; G_io_apdu_buffer[3] = LEDGER_PATCH_VERSION; - G_io_apdu_buffer[4] = !UX_ALLOWED; + G_io_apdu_buffer[4] = !IS_UX_ALLOWED; *tx += 5; THROW(APDU_CODE_OK); @@ -403,27 +417,6 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { break; } - case INS_SHOW_ADDR_SECP256K1: { - // Parse arguments - if (!extractHRP(&bech32_hrp_len, bech32_hrp, rx, OFFSET_DATA)) { - THROW(APDU_CODE_DATA_INVALID); - } - - if (!extractBip32(&bip32_depth, bip32_path, rx, OFFSET_DATA + bech32_hrp_len + 1)) { - THROW(APDU_CODE_DATA_INVALID); - } - - if (!validateCosmosPath(bip32_depth, bip32_path)) { - THROW(APDU_CODE_DATA_INVALID); - } - - view_set_handlers(addr_getData, NULL, NULL); - view_addr_show(bip32_path[4] & 0x7FFFFFF); - - *flags |= IO_ASYNCH_REPLY; - break; - } - case INS_GET_ADDR_SECP256K1: { // Parse arguments if (!extractHRP(&bech32_hrp_len, bech32_hrp, rx, OFFSET_DATA)) { @@ -458,6 +451,8 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { THROW(APDU_CODE_BAD_KEY_HANDLE); } + tx_display_index_root(); + view_set_handlers(tx_getData, tx_accept_sign, tx_reject); view_tx_show(0); diff --git a/src/app_main.h b/src/app_main.h index ce867b52..cd5a8e6a 100644 --- a/src/app_main.h +++ b/src/app_main.h @@ -31,7 +31,7 @@ #define INS_GET_VERSION 0 #define INS_PUBLIC_KEY_SECP256K1 1 // It will be deprecated in the near future #define INS_SIGN_SECP256K1 2 -#define INS_SHOW_ADDR_SECP256K1 3 +//#define INS_SHOW_ADDR_SECP256K1 3 DEPRECATED #define INS_GET_ADDR_SECP256K1 4 #ifdef TESTING_ENABLED diff --git a/src/glyphs.c b/src/glyphs.c deleted file mode 100644 index c1334285..00000000 --- a/src/glyphs.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "glyphs.h" - -unsigned int const C_icon_app_colors[] - = { - 0x00000000, - 0x00ffffff, - }; - -unsigned char const C_icon_app_bitmap[] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xe4, 0x1f, 0xe0, 0x8f, 0xf1, 0xc7, 0xe0, 0x67, 0xe4, - 0x27, 0xe6, 0x07, 0xe3, 0x8f, 0xf1, 0x07, 0xf8, 0x27, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -}; - -#ifdef OS_IO_SEPROXYHAL -#include "os_io_seproxyhal.h" -const bagl_icon_details_t C_icon_app = { GLYPH_icon_app_WIDTH, GLYPH_icon_app_HEIGHT, 1, C_icon_app_colors, C_icon_app_bitmap }; -#endif // OS_IO_SEPROXYHAL - -#include "glyphs.h" - -unsigned int const C_icon_back_colors[] - = { - 0x00000000, - 0x00ffffff, - }; - -unsigned char const C_icon_back_bitmap[] = { - 0xe0, 0x01, 0xfe, 0xc1, 0xfd, 0x38, 0x7f, 0x06, 0xdf, 0x81, 0xff, 0xc4, 0x7f, 0xf3, 0xff, 0xbc, - 0x1f, 0xe7, 0xe7, 0xf1, 0x3f, 0xf8, 0x07, 0x78, 0x00,}; - -#ifdef OS_IO_SEPROXYHAL -#include "os_io_seproxyhal.h" -const bagl_icon_details_t C_icon_back = { GLYPH_icon_back_WIDTH, GLYPH_icon_back_HEIGHT, 1, C_icon_back_colors, C_icon_back_bitmap }; -#endif // OS_IO_SEPROXYHAL - -#include "glyphs.h" - -unsigned int const C_icon_dashboard_colors[] - = { - 0x00000000, - 0x00ffffff, - }; - -unsigned char const C_icon_dashboard_bitmap[] = { - 0xe0, 0x01, 0xfe, 0xc1, 0xff, 0x38, 0x70, 0x06, 0xd8, 0x79, 0x7e, 0x9e, 0x9f, 0xe7, 0xe7, 0xb9, - 0x01, 0xe6, 0xc0, 0xf1, 0x3f, 0xf8, 0x07, 0x78, 0x00,}; - -#ifdef OS_IO_SEPROXYHAL -#include "os_io_seproxyhal.h" -const bagl_icon_details_t C_icon_dashboard = { GLYPH_icon_dashboard_WIDTH, GLYPH_icon_dashboard_HEIGHT, 1, C_icon_dashboard_colors, C_icon_dashboard_bitmap }; -#endif // OS_IO_SEPROXYHAL diff --git a/src/glyphs.h b/src/glyphs.h deleted file mode 100644 index 5db4cd66..00000000 --- a/src/glyphs.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef GLYPH_icon_app_BPP -#define GLYPH_icon_app_WIDTH 16 -#define GLYPH_icon_app_HEIGHT 16 -#define GLYPH_icon_app_BPP 1 -extern -unsigned int const C_icon_app_colors[]; -extern -unsigned char const C_icon_app_bitmap[]; -#ifdef OS_IO_SEPROXYHAL -#include "os_io_seproxyhal.h" -extern -const bagl_icon_details_t C_icon_app; -#endif // GLYPH_icon_app_BPP -#endif // OS_IO_SEPROXYHAL -#ifndef GLYPH_icon_back_BPP -#define GLYPH_icon_back_WIDTH 14 -#define GLYPH_icon_back_HEIGHT 14 -#define GLYPH_icon_back_BPP 1 -extern -unsigned int const C_icon_back_colors[]; -extern -unsigned char const C_icon_back_bitmap[]; -#ifdef OS_IO_SEPROXYHAL -#include "os_io_seproxyhal.h" -extern -const bagl_icon_details_t C_icon_back; -#endif // GLYPH_icon_back_BPP -#endif // OS_IO_SEPROXYHAL -#ifndef GLYPH_icon_dashboard_BPP -#define GLYPH_icon_dashboard_WIDTH 14 -#define GLYPH_icon_dashboard_HEIGHT 14 -#define GLYPH_icon_dashboard_BPP 1 -extern -unsigned int const C_icon_dashboard_colors[]; -extern -unsigned char const C_icon_dashboard_bitmap[]; -#ifdef OS_IO_SEPROXYHAL -#include "os_io_seproxyhal.h" -extern -const bagl_icon_details_t C_icon_dashboard; -#endif // GLYPH_icon_dashboard_BPP -#endif // OS_IO_SEPROXYHAL diff --git a/src/lib/json_parser.c b/src/lib/json_parser.c index bc86f1de..5874d887 100644 --- a/src/lib/json_parser.c +++ b/src/lib/json_parser.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,22 +17,25 @@ #include #include "json_parser.h" +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) + #include "os.h" + #define EQUALS(_P, _Q, _LEN) (os_memcmp( PIC(_P), PIC(_Q), (_LEN))==0) +#else + #define EQUALS(_P, _Q, _LEN) (memcmp( (_P), (_Q), (_LEN))==0) +#endif + void reset_parsed_json(parsed_json_t *parser_data) { memset(parser_data, 0, sizeof(parsed_json_t)); } -const char *json_parse( - parsed_json_t *parsed_json, - const char *transaction) { +const char *json_parse(parsed_json_t *parsed_json, const char *transaction) { return json_parse_s(parsed_json, transaction, strlen(transaction)); } -const char *json_parse_s( - parsed_json_t *parsed_json, - const char *transaction, - uint16_t transaction_length) { - +const char *json_parse_s(parsed_json_t *parsed_json, + const char *transaction, + uint16_t transaction_length) { jsmn_parser parser; jsmn_init(&parser); @@ -220,17 +223,17 @@ int16_t object_get_value(uint16_t object_token_index, return -1; } - size_t length = strlen(key_name); - jsmntok_t object_token = parsed_transaction->Tokens[object_token_index]; + const jsmntok_t object_token = parsed_transaction->Tokens[object_token_index]; + int token_index = object_token_index; int prev_element_end = object_token.start; token_index++; - while (true) { - if (token_index >= parsed_transaction->NumberOfTokens) { - break; - } - jsmntok_t key_token = parsed_transaction->Tokens[token_index++]; - jsmntok_t value_token = parsed_transaction->Tokens[token_index]; + + while (token_index < parsed_transaction->NumberOfTokens) { + const jsmntok_t key_token = parsed_transaction->Tokens[token_index]; + token_index++; + const jsmntok_t value_token = parsed_transaction->Tokens[token_index]; + if (key_token.start > object_token.end) { break; } @@ -238,12 +241,13 @@ int16_t object_get_value(uint16_t object_token_index, continue; } prev_element_end = value_token.end; - char *cmper = (char *) (transaction + key_token.start); - size_t cmper_l = (size_t) (key_token.end - key_token.start); - if (memcmp(key_name, cmper, length) == 0 && cmper_l == length) { - return token_index; + + if ( ((uint16_t) strlen(key_name)) == (key_token.end - key_token.start)) { + if (EQUALS(key_name, transaction + key_token.start, key_token.end - key_token.start)) { + return token_index; + } } } return -1; -} \ No newline at end of file +} diff --git a/src/lib/json_parser.h b/src/lib/json_parser.h index 63656677..3a5dc971 100644 --- a/src/lib/json_parser.h +++ b/src/lib/json_parser.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,9 +49,7 @@ typedef struct { uint16_t max_chars_per_key_line; uint16_t max_chars_per_value_line; const char *tx; - - // cached values - int16_t num_pages; + uint8_t cache_valid; } parsing_context_t; //--------------------------------------------- @@ -62,18 +60,16 @@ typedef struct { /// \param transaction /// \param transaction_length /// \return Error message -const char *json_parse_s( - parsed_json_t *parsed_json, - const char *transaction, - uint16_t transaction_length); +const char *json_parse_s(parsed_json_t *parsed_json, + const char *transaction, + uint16_t transaction_length); /// Parse json to create a token representation /// \param parsed_json /// \param transaction /// \return Error message -const char *json_parse( - parsed_json_t *parsed_json, - const char *transaction); +const char *json_parse(parsed_json_t *parsed_json, + const char *transaction); /// Get the number of elements in the array /// \param array_token_index diff --git a/src/lib/transaction.c b/src/lib/transaction.c index d4fe6bb3..8676c70b 100644 --- a/src/lib/transaction.c +++ b/src/lib/transaction.c @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,14 @@ ********************************************************************************/ #include "transaction.h" -#include "../view.h" #include "apdu_codes.h" -#include "json_parser.h" #include "buffering.h" +#include "json_parser.h" +#include "tx_validate.h" +#include "tx_parser.h" + +// TODO: Remove this dependency +#include "../view.h" // Ram #define RAM_BUFFER_SIZE 416 @@ -30,9 +34,15 @@ typedef struct { uint8_t buffer[FLASH_BUFFER_SIZE]; } storage_t; +#if defined(TARGET_NANOS) storage_t N_appdata_impl __attribute__ ((aligned(64))); #define N_appdata (*(storage_t *)PIC(&N_appdata_impl)) +#elif defined(TARGET_NANOX) +storage_t const N_appdata_impl __attribute__ ((aligned(64))); +#define N_appdata (*(volatile storage_t *)PIC(&N_appdata_impl)) +#endif + parsed_json_t parsed_transaction; void update_ram(buffer_state_t *buffer, uint8_t *data, int size) { @@ -91,6 +101,5 @@ const char* transaction_parse() { context.parsed_tx = &parsed_transaction; set_parsing_context(context); - set_copy_delegate(&os_memmove); return NULL; } diff --git a/src/lib/transaction.h b/src/lib/transaction.h index a42c3196..0ccf023b 100644 --- a/src/lib/transaction.h +++ b/src/lib/transaction.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018 ZondaX GmbH +* (c) ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ ********************************************************************************/ #pragma once -#include "transaction_parser.h" #include "os.h" void transaction_initialize(); diff --git a/src/lib/transaction_parser.c b/src/lib/transaction_parser.c deleted file mode 100644 index 244752cf..00000000 --- a/src/lib/transaction_parser.c +++ /dev/null @@ -1,489 +0,0 @@ -/******************************************************************************* -* (c) 2018 ZondaX GmbH -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ - -#include -#include -#include "transaction_parser.h" -#include "json_parser.h" - -#define MAX_RECURSION_DEPTH 3 -#define MAX_TREE_LEVEL 2 - -//--------------------------------------------- - -const char whitespaces[] = { - 0x20,// space ' ' - 0x0c, // form_feed '\f' - 0x0a, // line_feed, '\n' - 0x0d, // carriage_return, '\r' - 0x09, // horizontal_tab, '\t' - 0x0b // vertical_tab, '\v' -}; - -//--------------------------------------------- - -int16_t msgs_total_pages = 0; -int16_t msgs_array_elements = 0; - -//--------------------------------------------- - -copy_delegate copy_fct = NULL; // Decoupled so we can handle both RAM and NVRAM with similar code -parsing_context_t parsing_context; - -void set_copy_delegate(copy_delegate delegate) { - copy_fct = delegate; -} - -void set_parsing_context(parsing_context_t context) { - parsing_context = context; - - // reset cached values - parsing_context.num_pages = -1; -} - -void strcat_s(char *dst, uint16_t dst_max, const char *src, uint16_t src_size) { - char *p = dst; - *(p + dst_max - 1) = 0; // last character terminates with zero - - const uint16_t prev_size = strlen(dst); - p += prev_size; - uint16_t space_left = dst_max - prev_size; - - if (src_size > space_left) { - src_size = space_left - 1; - } - - if (src_size > 0) { - // Check bounds - copy_fct(p, src, src_size); - } - - // terminate - *(p + src_size) = 0; -} - -//-------------------------------------- -// Transaction parsing helper functions -//-------------------------------------- -int16_t update(char *out, const int16_t out_len, const int16_t token_index, uint16_t chunk_to_display) { - - const int16_t token_start = parsing_context.parsed_tx->Tokens[token_index].start; - const int16_t token_end = parsing_context.parsed_tx->Tokens[token_index].end; - const int16_t token_len = token_end - token_start; - - int16_t num_chunks = (token_len / (out_len - 1)) + 1; - if (token_len > 0 && (token_len % (out_len - 1) == 0)) - num_chunks--; - - out[0] = '\0'; // flush - if (chunk_to_display < num_chunks) { - const int16_t chunk_start = token_start + chunk_to_display * (out_len - 1); - int16_t chunk_len = token_end - chunk_start; - - if (chunk_len < 0) { - return -1; - } - - if (chunk_len > out_len - 1) { - chunk_len = out_len - 1; - } - copy_fct(out, parsing_context.tx + chunk_start, chunk_len); - out[chunk_len] = 0; - } - - return num_chunks; -} - -///// Update value characters from json transaction read from the token_index element. -///// Value is only updated if current_item_index (which is incremented internally) matches item_index_to_display -///// If value is updated, we also update view_scrolling_total_size to value string length. -int16_t retrieve_value(display_context_params_t *p, int16_t token_index) { - if (p->item_index == p->item_index_to_display) { - return update(p->value, p->value_length, token_index, p->chunk_index); - } - - p->item_index++; - return -1; -} - -///// Update key characters from json transaction read from the token_index element. -void append_key_item(display_context_params_t *p, int16_t token_index) { - if (*p->key > 0) { - // There is already something there, add separator - strcat_s(p->key, p->key_length, "/", 1); - } - - const int16_t token_start = parsing_context.parsed_tx->Tokens[token_index].start; - const int16_t token_end = parsing_context.parsed_tx->Tokens[token_index].end; - const char *address_ptr = parsing_context.tx + token_start; - const int16_t new_item_size = token_end - token_start; - - strcat_s(p->key, p->key_length, address_ptr, new_item_size); -} - -int16_t display_arbitrary_item_inner(display_context_params_t *p, int16_t token_index, uint8_t level, uint8_t depth) { -// if level == 2 -// show value as json-encoded string -// else -// switch typeof(json) { -// case object: -// for (key, value) in object: -// show key -// display(value, level + 1) -// case array: -// for element in array: -// display(element, level + 1) -// otherwise: -// show value as json-encoded string -// } - - const jsmntype_t token_type = parsing_context.parsed_tx->Tokens[token_index].type; - - if (level >= MAX_TREE_LEVEL || - depth >= MAX_RECURSION_DEPTH || - token_type == JSMN_STRING || - token_type == JSMN_PRIMITIVE) { - // Early bail out - return retrieve_value(p, token_index); - } - - const int16_t el_count = object_get_element_count(token_index, parsing_context.parsed_tx); - const int16_t key_len = strlen(p->key); - - switch (token_type) { - case JSMN_OBJECT: { - for (int16_t i = 0; i < el_count; ++i) { - int16_t key_index = object_get_nth_key(token_index, i, parsing_context.parsed_tx); - int16_t value_index = object_get_nth_value(token_index, i, parsing_context.parsed_tx); - - *(p->key + key_len) = 0; - if (p->item_index_to_display != -1) { - append_key_item(p, key_index); - } - - int16_t found = display_arbitrary_item_inner(p, value_index, level + 1, depth + 1); - if (found != -1) { - return found; - } - } - break; - } - case JSMN_ARRAY: { - for (int16_t i = 0; i < el_count; ++i) { - int16_t element_index = array_get_nth_element(token_index, i, parsing_context.parsed_tx); - - int16_t found = display_arbitrary_item_inner(p, element_index, level, depth + 1); - if (found != -1) { - return found; - } - } - break; - } - default: - break; - } - - // Not found yet, continue parsing - return -1; -} - -/// Returns number of pages that we'll have for the recursive parsing of a single msg json blob. -/// \param token_index -/// \return -int16_t display_get_arbitrary_items_count(int16_t token_index) { - display_context_params_t params; - - char dummy[1]; - params.item_index_to_display = -1; // set value that wont be found - params.key = dummy; - params.value = dummy; - params.key_length = 0; - params.value_length = 0; - params.item_index = 0; - params.chunk_index = 0; - - const int8_t start_level = 0; - const int8_t start_depth = 0; - - // To count items we need to traverse the whole tree - display_arbitrary_item_inner(¶ms, token_index, start_level, start_depth); - return params.item_index; -} - -int16_t display_arbitrary_item(int16_t item_index_to_display, - char *key, int16_t key_length, - char *value, int16_t value_length, - int16_t token_index, int16_t chunk_index) { - int16_t current_item_index = 0; - - display_context_params_t params; - params.item_index_to_display = item_index_to_display; - params.key = key; - params.value = value; - params.key_length = key_length; - params.value_length = value_length; - params.item_index = current_item_index; - params.chunk_index = chunk_index; - - params.key[0] = 0; - params.value[0] = 0; - - const int8_t start_level = 0; - const int8_t start_depth = 0; - - return display_arbitrary_item_inner(¶ms, token_index, start_level, start_depth); -} - -int16_t transaction_get_display_key_value(char *key, int16_t max_key_length, - char *value, int16_t max_value_length, - int16_t page_index, int16_t chunk_index) { - const int16_t non_msg_pages_count = 5; - if (page_index >= 0 && page_index < non_msg_pages_count) { - const char *key_name; - switch (page_index) { - case 0: - key_name = "chain_id"; - break; - case 1: - key_name = "account_number"; - break; - case 2: - key_name = "sequence"; - break; - case 3: - key_name = "fee"; - break; - case 4: - key_name = "memo"; - break; - default: - key_name = "????"; - } - - strcpy(key, key_name); - int16_t token_index = object_get_value(ROOT_TOKEN_INDEX, - key_name, - parsing_context.parsed_tx, - parsing_context.tx); - - return update(value, max_value_length, token_index, chunk_index); - } - - int16_t msgs_page_to_display = page_index - non_msg_pages_count; - int16_t subpage_to_display = msgs_page_to_display; - - // FIXME: cache? - transaction_get_display_pages(); - - if (msgs_page_to_display < msgs_total_pages) { - int16_t msgs_array_token_index = object_get_value( - ROOT_TOKEN_INDEX, - "msgs", - parsing_context.parsed_tx, - parsing_context.tx); - - int16_t total = 0; - int16_t msgs_array_index = 0; - int16_t msgs_token_index = 0; - - for (int16_t i = 0; i < msgs_array_elements; i++) { - int16_t token_index_of_msg = array_get_nth_element(msgs_array_token_index, i, - parsing_context.parsed_tx); - int16_t count = display_get_arbitrary_items_count(token_index_of_msg); - total += count; - if (msgs_page_to_display < total) { - msgs_token_index = token_index_of_msg; - msgs_array_index = i; - break; - } - subpage_to_display -= count; - } - - snprintf(key, max_key_length, "msgs_%d/", msgs_array_index + 1); - int16_t prefix_len = strlen(key); - return display_arbitrary_item(subpage_to_display, - key + prefix_len, - max_key_length - prefix_len, - value, - max_value_length, - msgs_token_index, - chunk_index); - } - - return -1; -} - -int16_t transaction_get_display_pages() { - if (parsing_context.num_pages > 0) { - // return cached value - return parsing_context.num_pages; - } - - int16_t msgs_token_index = object_get_value(ROOT_TOKEN_INDEX, - "msgs", - parsing_context.parsed_tx, - parsing_context.tx); - - msgs_array_elements = array_get_element_count(msgs_token_index, parsing_context.parsed_tx); - msgs_total_pages = 0; - - for (int16_t i = 0; i < msgs_array_elements; i++) { - int16_t token_index_of_msg = array_get_nth_element(msgs_token_index, i, parsing_context.parsed_tx); - msgs_total_pages += display_get_arbitrary_items_count(token_index_of_msg); - } - - parsing_context.num_pages = msgs_total_pages + 5; - return parsing_context.num_pages; -} - -int8_t is_space(char c) { - for (unsigned int i = 0; i < sizeof(whitespaces); i++) { - if (whitespaces[i] == c) { - return 1; - } - } - return 0; -} - -int8_t contains_whitespace(parsed_json_t *parsed_transaction, - const char *transaction) { - - int start = 0; - int last_element_index = parsed_transaction->Tokens[0].end; - - // Starting at token 1 because token 0 contains full tx - for (int i = 1; i < parsed_transaction->NumberOfTokens; i++) { - if (parsed_transaction->Tokens[i].type != JSMN_UNDEFINED) { - int end = parsed_transaction->Tokens[i].start; - for (int j = start; j < end; j++) { - if (is_space(transaction[j]) == 1) { - return 1; - } - } - start = parsed_transaction->Tokens[i].end + 1; - } else { - return 0; - } - } - while (start <= last_element_index && transaction[start] != '\0') { - if (is_space(transaction[start]) == 1) { - return 1; - } - start++; - } - return 0; -} - -int8_t is_sorted(int16_t first_index, - int16_t second_index, - parsed_json_t *parsed_transaction, - const char *transaction) { -#if DEBUG_SORTING - char first[256]; - char second[256]; - - int size = parsed_tx->Tokens[first_index].end - parsed_tx->Tokens[first_index].start; - strncpy(first, tx + parsed_tx->Tokens[first_index].start, size); - first[size] = '\0'; - size = parsed_tx->Tokens[second_index].end - parsed_tx->Tokens[second_index].start; - strncpy(second, tx + parsed_tx->Tokens[second_index].start, size); - second[size] = '\0'; -#endif - - if (strcmp( - transaction + parsed_transaction->Tokens[first_index].start, - transaction + parsed_transaction->Tokens[second_index].start) <= 0) { - return 1; - } - return 0; -} - -int8_t dictionaries_sorted(parsed_json_t *parsed_transaction, - const char *transaction) { - for (int i = 0; i < parsed_transaction->NumberOfTokens; i++) { - if (parsed_transaction->Tokens[i].type == JSMN_OBJECT) { - - int count = object_get_element_count(i, parsed_transaction); - if (count > 1) { - int prev_token_index = object_get_nth_key(i, 0, parsed_transaction); - for (int j = 1; j < count; j++) { - int next_token_index = object_get_nth_key(i, j, parsed_transaction); - if (!is_sorted(prev_token_index, next_token_index, parsed_transaction, transaction)) { - return 0; - } - prev_token_index = next_token_index; - } - } - } - } - return 1; -} - -const char *json_validate(parsed_json_t *parsed_transaction, - const char *transaction) { - - if (contains_whitespace(parsed_transaction, transaction) == 1) { - return "JSON Contains whitespace in the corpus"; - } - - if (dictionaries_sorted(parsed_transaction, transaction) != 1) { - return "JSON Dictionaries are not sorted"; - } - - if (object_get_value(0, - "chain_id", - parsed_transaction, - transaction) == -1) { - return "JSON Missing chain_id"; - } - - if (object_get_value(0, - "sequence", - parsed_transaction, - transaction) == -1) { - return "JSON Missing sequence"; - } - - if (object_get_value(0, - "fee", - parsed_transaction, - transaction) == -1) { - return "JSON Missing fee"; - } - - if (object_get_value(0, - "msgs", - parsed_transaction, - transaction) == -1) { - return "JSON Missing msgs"; - } - - if (object_get_value(0, - "account_number", - parsed_transaction, - transaction) == -1) { - return "JSON Missing account_number"; - } - - if (object_get_value(0, - "memo", - parsed_transaction, - transaction) == -1) { - return "JSON Missing memo"; - } - - return NULL; -} \ No newline at end of file diff --git a/src/lib/transaction_parser.h b/src/lib/transaction_parser.h deleted file mode 100644 index 6131731f..00000000 --- a/src/lib/transaction_parser.h +++ /dev/null @@ -1,95 +0,0 @@ -/******************************************************************************* -* (c) 2018 ZondaX GmbH -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ - -#pragma once - -#include "json_parser.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// used to reduce stack size usage in recursive calls -typedef struct { - int16_t item_index_to_display; - char *key; - char *value; - uint8_t max_level; - int16_t key_length; - int16_t value_length; - int16_t item_index; - int16_t chunk_index; -} display_context_params_t; - -/// Helper function that gets key and value by parsing -/// `msgs` blob basd on TXSpec - -/// \param item_index_to_display, index of the recursive parsing logic -/// \param key, an array that will be filled with key string -/// \param key_length, size of the key array -/// \param value, an array that will be filled with value string -/// \param value_length, size of the value array -/// \param token_index, index of the token that points to a single msg json element -/// \param chunk_index, [optional] value is split into chunks if it's very long, here we specify which chunk we should use -/// \return number of chunks or -1 if it was not possible to find the item -int16_t display_arbitrary_item(int16_t item_index_to_display, - char *key, - int16_t key_length, - char *value, - int16_t value_length, - int16_t token_index, - int16_t chunk_index); - -/// This is the main function called from ledger that updates key and value strings -/// that are going to be displayed in the UI. -/// \param key, an array that will be filled with key string -/// \param max_key_length, size of the key array -/// \param value, an array that will be filled with value string -/// \param max_value_length, size of the value array -/// \param page_index, index of the UI page for which key and value will be returned -/// \param chunk_index, [optional] value is split into chunks if it's very long, here we specify which chunk we should use -/// \return number of chunks or -1 if it was not possible to find the items -int16_t transaction_get_display_key_value(char *key, - int16_t max_key_length, - char *value, - int16_t max_value_length, - int16_t page_index, - int16_t chunk_index); - -/// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) -/// \return number of pages (msg pages + 5 required) -int16_t transaction_get_display_pages(); - -/// Validate json transaction -/// \param parsed_transacton -/// \param transaction -/// \return -const char *json_validate(parsed_json_t *parsed_transaction, - const char *transaction); - -//--------------------------------------------- -// Delegates - -typedef void(*copy_delegate)(void *dst, const void *source, size_t size); -void set_copy_delegate(copy_delegate delegate); -void set_parsing_context(parsing_context_t context); - -//--------------------------------------------- - -#ifdef __cplusplus -} -#endif diff --git a/src/lib/tx_display.c b/src/lib/tx_display.c new file mode 100644 index 00000000..8838145a --- /dev/null +++ b/src/lib/tx_display.c @@ -0,0 +1,190 @@ +/******************************************************************************* +* (c) ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "tx_parser.h" +#include "tx_display.h" +#include "json_parser.h" + +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) +#include "os.h" +#else +#define PIC(X) X +#endif + +// Required pages +// FIXME: the required root items have been moved to a function due to PIC issues. Refactor and fix +const char *get_required_root_item(uint8_t i) { + switch(i) { + case 0: return "chain_id"; + case 1: return "account_number"; + case 2: return "sequence"; + case 3: return "fee"; + case 4: return "memo"; + case 5: return "msgs"; + default: return "?"; + } +} + +//const char *root_required_items[NUM_REQUIRED_ROOT_PAGES] = { +// "chain_id", +// "account_number", +// "sequence", +// "fee", +// "memo", +// "msgs" +//}; + +static const uint16_t root_max_level[NUM_REQUIRED_ROOT_PAGES] = { + 2, // "chain_id", + 2, // "account_number", + 2, // "sequence", + 1, // "fee", + 2, // "memo" + 2, // "msgs" +}; + +static const key_subst_t key_substitutions[NUM_KEY_SUBSTITUTIONS] = { + {"chain_id", "Chain ID"}, + {"account_number", "Account"}, + {"sequence", "Sequence"}, + {"memo", "Memo"}, + {"fee/amount", "Fee"}, + {"fee/gas", "Gas"}, + {"msgs/inputs/address", "Source Address"}, + {"msgs/inputs/coins", "Source Coins"}, + {"msgs/outputs/address", "Dest Address"}, + {"msgs/outputs/coins", "Dest Coins"}, +// + {"msgs/description", "Description"}, + {"msgs/initial_deposit/amount", "Deposit Amount"}, + {"msgs/initial_deposit/denom", "Deposit Denom"}, + {"msgs/proposal_type", "Proposal"}, + {"msgs/proposer", "Proposer"}, + {"msgs/title", "Title"}, +}; + +#define STRNCPY_S(DST, SRC, DST_SIZE) \ + strncpy(DST, SRC, DST_SIZE - 1); \ + DST[DST_SIZE - 1] = 0; + +display_cache_t display_cache; + +display_cache_t *tx_display_cache() { + return &display_cache; +} + +void tx_display_index_root() { + if (parsing_context.cache_valid) { + return; + } + + // Clear values + display_cache.num_pages = 0; + memset(display_cache.num_subpages, 0, NUM_REQUIRED_ROOT_PAGES); + memset(display_cache.subroot_start_token, TX_TOKEN_NOT_FOUND, NUM_REQUIRED_ROOT_PAGES); + + // Calculate pages + int8_t found = 0; + for (int8_t idx = 0; idx < NUM_REQUIRED_ROOT_PAGES; idx++) { + const int16_t subroot_token_idx = object_get_value(ROOT_TOKEN_INDEX, + get_required_root_item(idx), + parsing_context.parsed_tx, + parsing_context.tx); + if (subroot_token_idx < 0) { + break; + } + + display_cache.num_subpages[idx] = 0; + display_cache.subroot_start_token[idx] = subroot_token_idx; + + char tmp_key[2]; + char tmp_val[2]; + INIT_QUERY_CONTEXT(tmp_key, sizeof(tmp_key), tmp_val, sizeof(tmp_val), 0, root_max_level[idx]) + STRNCPY_S(tx_ctx.query.out_key, get_required_root_item(idx), tx_ctx.query.out_key_len); + tx_ctx.max_depth = MAX_RECURSION_DEPTH; + tx_ctx.query.item_index = 0; + + found = 0; + while (found >= 0) { + tx_ctx.item_index_current = 0; + found = tx_traverse(subroot_token_idx); + + if (found >= 0) { + display_cache.num_subpages[idx]++; + tx_ctx.query.item_index++; + } + }; + display_cache.num_pages += display_cache.num_subpages[idx]; + + if (display_cache.num_subpages[idx] == 0) { + break; + } + } + + parsing_context.cache_valid = 1; +} + +int16_t tx_display_num_pages() { + tx_display_index_root(); + return display_cache.num_pages; +} + +// This function assumes that the tx_ctx has been set properly +int16_t tx_display_get_item(uint16_t page_index) { + if (!parsing_context.cache_valid) { + return ERR_MUST_INDEX_FIRST; + } + + // TODO: Verify it has been properly set? + tx_ctx.query.out_key[0] = 0; + tx_ctx.query.out_val[0] = 0; + if (page_index < 0 || page_index >= display_cache.num_pages) { + return -1; + } + + tx_ctx.query.item_index = 0; + uint16_t root_index = 0; + for (uint16_t i = 0; i < page_index; i++) { + tx_ctx.query.item_index++; + if (tx_ctx.query.item_index >= display_cache.num_subpages[root_index]) { + tx_ctx.query.item_index = 0; + root_index++; + } + } + + tx_ctx.item_index_current = 0; + tx_ctx.max_level = root_max_level[root_index]; + tx_ctx.max_depth = MAX_RECURSION_DEPTH; + + STRNCPY_S(tx_ctx.query.out_key, get_required_root_item(root_index), tx_ctx.query.out_key_len); + + int16_t ret = tx_traverse(display_cache.subroot_start_token[root_index]); + + return ret; +} + +void tx_display_make_friendly() { + // post process keys + for (int8_t i = 0; i < NUM_KEY_SUBSTITUTIONS; i++) { + if (!strcmp(tx_ctx.query.out_key, key_substitutions[i].str1)) { + STRNCPY_S(tx_ctx.query.out_key, key_substitutions[i].str2, tx_ctx.query.out_key_len); + break; + } + } +} + diff --git a/src/lib/tx_display.h b/src/lib/tx_display.h new file mode 100644 index 00000000..736e0aba --- /dev/null +++ b/src/lib/tx_display.h @@ -0,0 +1,65 @@ +/******************************************************************************* +* (c) ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once +#include + +#include "json_parser.h" +#include "tx_parser.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ERR_MUST_INDEX_FIRST -2 + +#define NUM_REQUIRED_ROOT_PAGES 6 +#define NUM_KEY_SUBSTITUTIONS 16 + +typedef struct { + char str1[50]; + char str2[50]; +} key_subst_t; + +typedef struct { + int16_t num_pages; + int16_t subroot_start_token[NUM_REQUIRED_ROOT_PAGES]; + uint8_t num_subpages[NUM_REQUIRED_ROOT_PAGES]; +} display_cache_t; + +// This is only used for testing purposes +display_cache_t *tx_display_cache(); + +// This must be run before accessing items +void tx_display_index_root(); + +/// This is the main function called from ledger that updates key and value strings +/// that are going to be displayed in the UI. +/// This function assumes that the tx_ctx has been properly set +int16_t tx_display_get_item(uint16_t page_index); + +/// Return number of UI pages that we'll have for the current json transaction (only if the tx is valid) +/// \return number of pages (msg pages + 5 required) +int16_t tx_display_num_pages(); + +/// Apply postprocessing rules to key and values +void tx_display_make_friendly(); + +//--------------------------------------------- + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/tx_parser.c b/src/lib/tx_parser.c new file mode 100644 index 00000000..fdbd6ab1 --- /dev/null +++ b/src/lib/tx_parser.c @@ -0,0 +1,170 @@ +/******************************************************************************* +* (c) ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "tx_parser.h" +#include "json_parser.h" + +#if defined(TARGET_NANOX) || defined(TARGET_NANOS) +#define COPYFUNC os_memmove +#else +#define COPYFUNC memcpy +#endif + +// Global context to save memory / stack space in recursive calls +parsing_context_t parsing_context; +tx_context_t tx_ctx; + +void set_parsing_context(parsing_context_t context) { + parsing_context = context; + // reset cached values + parsing_context.cache_valid = false; +} + +// strcat but source does not need to be terminated (a chunk from a bigger string is concatenated) +// dst_max is measured in bytes including the space for NULL termination +// src_size does not include NULL termination +__always_inline void strcat_chunk_s(char *dst, uint16_t dst_max, const char *src_chunk, uint16_t src_chunk_size) { + *(dst + dst_max - 1) = 0; // last character terminates with zero in case we go beyond bounds + const uint16_t prev_size = strlen(dst); + + uint16_t space_left = dst_max - prev_size - 1; // -1 because requires termination + + if (src_chunk_size > space_left) { + src_chunk_size = space_left; + } + + if (src_chunk_size > 0) { + // Check bounds + COPYFUNC(dst + prev_size, src_chunk, src_chunk_size); + // terminate + *(dst + prev_size + src_chunk_size) = 0; + } +} + +__always_inline int16_t tx_get_value(const int16_t token_index) { + + const int16_t token_start = parsing_context.parsed_tx->Tokens[token_index].start; + const int16_t token_end = parsing_context.parsed_tx->Tokens[token_index].end; + const int16_t token_len = token_end - token_start; + + int16_t num_chunks = (token_len / (tx_ctx.query.out_val_len - 1)) + 1; + if (token_len > 0 && (token_len % (tx_ctx.query.out_val_len - 1) == 0)) + num_chunks--; + + tx_ctx.query.out_val[0] = '\0'; // flush + if (tx_ctx.query.chunk_index >= num_chunks) { + return TX_TOKEN_NOT_FOUND; + } + + const int16_t chunk_start = token_start + tx_ctx.query.chunk_index * (tx_ctx.query.out_val_len - 1); + int16_t chunk_len = token_end - chunk_start; + + if (chunk_len < 0) { + return TX_TOKEN_NOT_FOUND; + } + + if (chunk_len > tx_ctx.query.out_val_len - 1) { + chunk_len = tx_ctx.query.out_val_len - 1; + } + COPYFUNC(tx_ctx.query.out_val, parsing_context.tx + chunk_start, chunk_len); + tx_ctx.query.out_val[chunk_len] = 0; + + return num_chunks; +} + +///// Update key characters from json transaction read from the token_index element. +__always_inline void append_key_item(int16_t token_index) { + if (*tx_ctx.query.out_key > 0) { + // There is already something there, add separator + strcat_chunk_s(tx_ctx.query.out_key, tx_ctx.query.out_key_len, "/", 1); + } + + const int16_t token_start = parsing_context.parsed_tx->Tokens[token_index].start; + const int16_t token_end = parsing_context.parsed_tx->Tokens[token_index].end; + const char *address_ptr = parsing_context.tx + token_start; + const int16_t new_item_size = token_end - token_start; + + strcat_chunk_s(tx_ctx.query.out_key, tx_ctx.query.out_key_len, address_ptr, new_item_size); +} + +int16_t tx_traverse(int16_t root_token_index) { + const jsmntype_t token_type = parsing_context.parsed_tx->Tokens[root_token_index].type; + + if (tx_ctx.max_level <= 0 || tx_ctx.max_depth <= 0 || + token_type == JSMN_STRING || + token_type == JSMN_PRIMITIVE) { + + // Early bail out + if (tx_ctx.item_index_current == tx_ctx.query.item_index) { + return tx_get_value(root_token_index); + } + tx_ctx.item_index_current++; + return TX_TOKEN_NOT_FOUND; + } + + const int16_t el_count = object_get_element_count(root_token_index, parsing_context.parsed_tx); + int16_t num_chunks = TX_TOKEN_NOT_FOUND; + + switch (token_type) { + case JSMN_OBJECT: { + const int16_t key_len = strlen(tx_ctx.query.out_key); + for (int16_t i = 0; i < el_count; ++i) { + const int16_t key_index = object_get_nth_key(root_token_index, i, parsing_context.parsed_tx); + const int16_t value_index = object_get_nth_value(root_token_index, i, parsing_context.parsed_tx); + + // Skip writing keys if we are actually exploring to count + if (tx_ctx.query.item_index != TX_TOKEN_NOT_FOUND) { + append_key_item(key_index); + } + + // When traversing objects both level and depth should be considered + tx_ctx.max_level--; + tx_ctx.max_depth--; + num_chunks = tx_traverse(value_index); // Traverse the value, extracting subkeys + tx_ctx.max_level++; + tx_ctx.max_depth++; + + if (num_chunks != TX_TOKEN_NOT_FOUND) { + break; + } + + *(tx_ctx.query.out_key + key_len) = 0; + } + break; + } + case JSMN_ARRAY: { + for (int16_t i = 0; i < el_count; ++i) { + const int16_t element_index = array_get_nth_element(root_token_index, i, parsing_context.parsed_tx); + + // When iterating along an array, the level does not change but we need to count the recursion + tx_ctx.max_depth--; + num_chunks = tx_traverse(element_index); + tx_ctx.max_depth++; + + if (num_chunks != TX_TOKEN_NOT_FOUND) { + break; + } + } + break; + } + default: + break; + } + + return num_chunks; +} diff --git a/src/lib/tx_parser.h b/src/lib/tx_parser.h new file mode 100644 index 00000000..3bf3cead --- /dev/null +++ b/src/lib/tx_parser.h @@ -0,0 +1,86 @@ +/******************************************************************************* +* (c) ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#include "json_parser.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TX_TOKEN_NOT_FOUND (-1) + +#define MAX_RECURSION_DEPTH 6 + +typedef struct { + int16_t item_index; // ?? + int16_t chunk_index; // ?? + + char *out_key; // ?? + int16_t out_key_len; // ?? + char *out_val; // ?? + int16_t out_val_len; // ?? +} tx_query_t; + +// used to reduce stack size usage in recursive calls +typedef struct { + tx_query_t query; // ?? + int16_t item_index_current; // ?? + uint8_t max_level; + uint8_t max_depth; +} tx_context_t; + +extern parsing_context_t parsing_context; +extern tx_context_t tx_ctx; + +#define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX, _MAX_LEVEL) \ + INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ + tx_ctx.item_index_current = 0; \ + tx_ctx.max_depth = MAX_RECURSION_DEPTH; \ + tx_ctx.max_level = _MAX_LEVEL; + +#define INIT_QUERY(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _CHUNK_IDX) \ + _KEY[0] = 0; \ + _VAL[0] = 0; \ + tx_ctx.query.out_key=_KEY; \ + tx_ctx.query.out_val=_VAL; \ + tx_ctx.query.out_key_len = _KEY_LEN; \ + tx_ctx.query.out_val_len = _VAL_LEN; \ + tx_ctx.query.item_index= 0; \ + tx_ctx.query.chunk_index = _CHUNK_IDX; + +/// Validate json transaction +/// \param parsed_transacton +/// \param transaction +/// \return +const char *json_validate(parsed_json_t *parsed_transaction, const char *transaction); + +// Traverses transaction data and fills tx_context +// \return -1 if the item was not found or the number of available chunks for this item +int16_t tx_traverse(int16_t root_token_index); + +// Retrieves the value for the corresponding token index. If the value goes beyond val_len, the chunk_idx will be used +int16_t tx_get_value(int16_t token_index); + +void set_parsing_context(parsing_context_t context); + +//--------------------------------------------- + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/tx_validate.c b/src/lib/tx_validate.c new file mode 100644 index 00000000..8cabe23c --- /dev/null +++ b/src/lib/tx_validate.c @@ -0,0 +1,162 @@ +/******************************************************************************* +* (c) ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include +#include +#include "tx_parser.h" +#include "json_parser.h" + +const char whitespaces[] = { + 0x20,// space ' ' + 0x0c, // form_feed '\f' + 0x0a, // line_feed, '\n' + 0x0d, // carriage_return, '\r' + 0x09, // horizontal_tab, '\t' + 0x0b // vertical_tab, '\v' +}; + +int8_t is_space(char c) { + for (uint16_t i = 0; i < sizeof(whitespaces); i++) { + if (whitespaces[i] == c) { + return 1; + } + } + return 0; +} + +int8_t contains_whitespace(parsed_json_t *parsed_transaction, const char *transaction) { + int start = 0; + const int last_element_index = parsed_transaction->Tokens[0].end; + + // Starting at token 1 because token 0 contains full tx + for (int i = 1; i < parsed_transaction->NumberOfTokens; i++) { + if (parsed_transaction->Tokens[i].type != JSMN_UNDEFINED) { + const int end = parsed_transaction->Tokens[i].start; + for (int j = start; j < end; j++) { + if (is_space(transaction[j]) == 1) { + return 1; + } + } + start = parsed_transaction->Tokens[i].end + 1; + } else { + return 0; + } + } + while (start <= last_element_index && transaction[start] != '\0') { + if (is_space(transaction[start])) { + return 1; + } + start++; + } + return 0; +} + +int8_t is_sorted(int16_t first_index, int16_t second_index, + parsed_json_t *parsed_transaction, + const char *transaction) { +#if DEBUG_SORTING + char first[256]; + char second[256]; + + int size = parsed_tx->Tokens[first_index].end - parsed_tx->Tokens[first_index].start; + strncpy(first, tx + parsed_tx->Tokens[first_index].start, size); + first[size] = '\0'; + size = parsed_tx->Tokens[second_index].end - parsed_tx->Tokens[second_index].start; + strncpy(second, tx + parsed_tx->Tokens[second_index].start, size); + second[size] = '\0'; +#endif + + if (strcmp(transaction + parsed_transaction->Tokens[first_index].start, + transaction + parsed_transaction->Tokens[second_index].start) <= 0) { + return 1; + } + return 0; +} + +int8_t dictionaries_sorted(parsed_json_t *parsed_transaction, + const char *transaction) { + for (int i = 0; i < parsed_transaction->NumberOfTokens; i++) { + if (parsed_transaction->Tokens[i].type == JSMN_OBJECT) { + + const int count = object_get_element_count(i, parsed_transaction); + if (count > 1) { + int prev_token_index = object_get_nth_key(i, 0, parsed_transaction); + for (int j = 1; j < count; j++) { + int next_token_index = object_get_nth_key(i, j, parsed_transaction); + if (!is_sorted(prev_token_index, next_token_index, parsed_transaction, transaction)) { + return 0; + } + prev_token_index = next_token_index; + } + } + } + } + return 1; +} + +const char *json_validate(parsed_json_t *parsed_transaction, const char *transaction) { + if (contains_whitespace(parsed_transaction, transaction) == 1) { + return "JSON Contains whitespace in the corpus"; + } + + if (dictionaries_sorted(parsed_transaction, transaction) != 1) { + return "JSON Dictionaries are not sorted"; + } + + if (object_get_value(0, + "chain_id", + parsed_transaction, + transaction) == -1) { + return "JSON Missing chain_id"; + } + + if (object_get_value(0, + "sequence", + parsed_transaction, + transaction) == -1) { + return "JSON Missing sequence"; + } + + if (object_get_value(0, + "fee", + parsed_transaction, + transaction) == -1) { + return "JSON Missing fee"; + } + + if (object_get_value(0, + "msgs", + parsed_transaction, + transaction) == -1) { + return "JSON Missing msgs"; + } + + if (object_get_value(0, + "account_number", + parsed_transaction, + transaction) == -1) { + return "JSON Missing account_number"; + } + + if (object_get_value(0, + "memo", + parsed_transaction, + transaction) == -1) { + return "JSON Missing memo"; + } + + return NULL; +} diff --git a/src/lib/tx_validate.h b/src/lib/tx_validate.h new file mode 100644 index 00000000..73e598f4 --- /dev/null +++ b/src/lib/tx_validate.h @@ -0,0 +1,34 @@ +/******************************************************************************* +* (c) ZondaX GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#pragma once + +#include "json_parser.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Validate json transaction +/// \param parsed_transacton +/// \param transaction +/// \return +const char *json_validate(parsed_json_t *parsed_transaction, const char *transaction); + +#ifdef __cplusplus +} +#endif diff --git a/src/view.c b/src/view.c index e8cb3be7..7cf0d34e 100644 --- a/src/view.c +++ b/src/view.c @@ -1,6 +1,6 @@ /******************************************************************************* * (c) 2016 Ledger -* (c) 2018 ZondaX GmbH +* (c) 2018, 2019 ZondaX GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,14 +27,67 @@ #include #include -#define TRUE 1 -#define FALSE 0 +viewctl_delegate_getData ehGetData = NULL; +viewctl_delegate_accept ehAccept = NULL; +viewctl_delegate_reject ehReject = NULL; -ux_state_t ux; -enum UI_STATE view_uiState; +void accept(unsigned int unused); void reject(unsigned int unused); +#if defined(TARGET_NANOX) + +#include "ux.h" +ux_state_t G_ux; +bolos_ux_params_t G_ux_params; + +union { + struct { + char account[40]; + char index[40]; + char bech32[200]; + } addr; +} view; + +#ifdef TESTING_ENABLED +UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, "Tendermint", "Cosmos (TEST)", }); +#else +UX_FLOW_DEF_NOCB(ux_idle_flow_1_step, pbb, { &C_icon_app, "Tendermint", "Cosmos", }); +#endif +UX_FLOW_DEF_NOCB(ux_idle_flow_2_step, bn, { "Version", APPVERSION, }); +UX_FLOW_DEF_VALID(ux_idle_flow_3_step, pb, os_sched_exit(-1), { &C_icon_dashboard, "Quit",}); +const ux_flow_step_t *const ux_idle_flow [] = { + &ux_idle_flow_1_step, + &ux_idle_flow_2_step, + &ux_idle_flow_3_step, + FLOW_END_STEP, +}; + +UX_FLOW_DEF_VALID(ux_tx_flow_1_step, pbb, view_tx_show(0), { &C_icon_eye, "Review", "Transaction" }); +UX_FLOW_DEF_VALID(ux_tx_flow_2_step, pbb, view_sign_transaction(0), { &C_icon_validate_14, "Sign", "Transaction" }); +UX_FLOW_DEF_VALID(ux_tx_flow_3_step, pbb, reject(0), { &C_icon_crossmark, "Reject", "Transaction" }); +const ux_flow_step_t *const ux_tx_flow [] = { + &ux_tx_flow_1_step, + &ux_tx_flow_2_step, + &ux_tx_flow_3_step, + FLOW_END_STEP, +}; + +UX_FLOW_DEF_NOCB(ux_addr_flow_1_step, bnn, { "Address Request", view.addr.account, view.addr.index}); +UX_FLOW_DEF_NOCB(ux_addr_flow_2_step, bnnn_paging, { .title = "Address", .text = view.addr.bech32 }); +UX_FLOW_DEF_VALID(ux_addr_flow_3_step, pb, accept(0), { &C_icon_validate_14, "Reply", }); +UX_FLOW_DEF_VALID(ux_addr_flow_4_step, pb, reject(0), { &C_icon_crossmark, "Reject", }); +const ux_flow_step_t *const ux_addr_flow [] = { + &ux_addr_flow_1_step, + &ux_addr_flow_2_step, + &ux_addr_flow_3_step, + &ux_addr_flow_4_step, + FLOW_END_STEP, +}; +#else +// Nano S +ux_state_t ux; + //------ View elements const ux_menu_entry_t menu_main[]; const ux_menu_entry_t menu_about[]; @@ -42,7 +95,7 @@ const ux_menu_entry_t menu_about[]; const ux_menu_entry_t menu_transaction_info[] = { {NULL, view_tx_show, 0, NULL, "View transaction", NULL, 0, 0}, {NULL, view_sign_transaction, 0, NULL, "Sign transaction", NULL, 0, 0}, - {NULL, reject, 0, &C_icon_back, "Reject", NULL, 60, 40}, + {NULL, reject, 0, &C_icon_crossmark, "Reject", NULL, 60, 40}, UX_MENU_END }; @@ -63,34 +116,34 @@ const ux_menu_entry_t menu_about[] = { {menu_main, NULL, 2, &C_icon_back, "Back", NULL, 61, 40}, UX_MENU_END }; -//------ View elements +#endif -//------ Event handlers -viewctl_delegate_getData ehGetData = NULL; -viewctl_delegate_accept ehAccept = NULL; -viewctl_delegate_reject ehReject = NULL; +//////////////////////////////// +//////////////////////////////// +//////////////////////////////// -void view_set_handlers(viewctl_delegate_getData func_getData, - viewctl_delegate_accept func_accept, - viewctl_delegate_reject func_reject) { - ehGetData = func_getData; - ehAccept = func_accept; - ehReject = func_reject; +void view_init(void) { + UX_INIT(); } -// ------ Event handlers - -void io_seproxyhal_display(const bagl_element_t *element) { - io_seproxyhal_display_default((bagl_element_t *) element); +void view_idle(unsigned int ignored) { +#if defined(TARGET_NANOS) + UX_MENU_DISPLAY(0, menu_main, NULL); +#elif defined(TARGET_NANOX) + if(G_ux.stack_count == 0) { + ux_stack_push(); + } + ux_flow_init(0, ux_idle_flow, NULL); +#endif } void view_tx_show(unsigned int start_page) { if (ehGetData == NULL) { return; } - viewexpl_start(start_page, ehGetData, NULL, view_display_tx_menu); + } void view_addr_exit(unsigned int unused) { @@ -100,57 +153,68 @@ void view_addr_exit(unsigned int unused) { view_idle(0); } -void view_addr_show(unsigned int start_page) { - viewexpl_start(start_page, - ehGetData, // update - NULL, // ready - view_addr_exit // exit - ); -} - void view_addr_confirm(unsigned int start_page) { +#if defined(TARGET_NANOS) viewconf_start(start_page, ehGetData, // update NULL, // ready NULL, // exit ehAccept, ehReject); -} +#elif defined(TARGET_NANOX) + // Retrieve data + ehGetData(view.addr.account, sizeof(view.addr.account), + view.addr.index, sizeof(view.addr.index), + view.addr.bech32, sizeof(view.addr.bech32), + start_page, 0, 0, 0); + + if(G_ux.stack_count == 0) { + ux_stack_push(); + } -///////////////////////////////// + ux_flow_init(0, ux_addr_flow, NULL); +#endif +} void view_sign_transaction(unsigned int unused) { - UNUSED(unused); - if (ehAccept != NULL) { - ehAccept(); - } + accept(unused); } void reject(unsigned int unused) { + UNUSED(unused); if (ehReject != NULL) { ehReject(); } } -void view_init(void) { - UX_INIT(); - view_uiState = UI_IDLE; +void accept(unsigned int unused) { + UNUSED(unused); + if (ehAccept != NULL) { + ehAccept(); + } } -void view_idle(unsigned int ignored) { - view_uiState = UI_IDLE; - UX_MENU_DISPLAY(0, menu_main, NULL); -} +void view_display_tx_menu(unsigned int unused) { + UNUSED(unused); -void view_display_tx_menu(unsigned int ignored) { - view_uiState = UI_TRANSACTION; +#if defined(TARGET_NANOS) UX_MENU_DISPLAY(0, menu_transaction_info, NULL); +#elif defined(TARGET_NANOX) + if(G_ux.stack_count == 0) { + ux_stack_push(); + } + ux_flow_init(0, ux_tx_flow, NULL); +#endif } -void view_display_signing_success() { - view_idle(0); +void view_set_handlers(viewctl_delegate_getData func_getData, + viewctl_delegate_accept func_accept, + viewctl_delegate_reject func_reject) { + ehGetData = func_getData; + ehAccept = func_accept; + ehReject = func_reject; } -void view_display_signing_error() { - view_idle(0); +void io_seproxyhal_display(const bagl_element_t *element) { + io_seproxyhal_display_default((bagl_element_t *) element); } diff --git a/src/view.h b/src/view.h index a141ec07..b2db46a5 100644 --- a/src/view.h +++ b/src/view.h @@ -22,13 +22,6 @@ #include "view_expl.h" #include "view_conf.h" -enum UI_STATE { - UI_IDLE, - UI_TRANSACTION -}; - -extern enum UI_STATE view_uiState; - //------ Event handlers /// view_set_handlers void view_set_handlers(viewctl_delegate_getData func_getData, @@ -51,14 +44,13 @@ void view_tx_show(unsigned int start_page); /// view_sign_transaction void view_sign_transaction(unsigned int start_page); -/// view_addr_show -void view_addr_show(unsigned int start_page); - /// view_addr_confirm void view_addr_confirm(unsigned int start_page); -/// view_display_signing_success -void view_display_signing_success(); - -/// view_display_signing_error -void view_display_signing_error(); +int view_tx_get_data(char *title, int max_title_length, + char *key, int max_key_length, + char *value, int max_value_length, + int page_index, + int chunk_index, + int *page_count_out, + int *chunk_count_out); diff --git a/src/view_common.c b/src/view_common.c index a874bb96..9e5ac870 100644 --- a/src/view_common.c +++ b/src/view_common.c @@ -29,17 +29,9 @@ void viewctl_display_page(); -enum UI_DISPLAY_MODE viewctl_scrolling_mode; - -volatile char viewctl_DataKey[MAX_CHARS_PER_KEY_LINE]; -volatile char viewctl_DataValue[MAX_CHARS_PER_VALUE_LINE]; -volatile char viewctl_Title[MAX_SCREEN_LINE_WIDTH]; const char *dblClickInfo = "DBL-CLICK TO VIEW"; -int viewctl_DetailsCurrentPage; -int viewctl_DetailsPageCount; -int viewctl_ChunksIndex; -int viewctl_ChunksCount; +viewctl_s viewctl; viewctl_delegate_getData viewctl_ehGetData = NULL; viewctl_delegate_ready viewctl_ehReady = NULL; @@ -53,22 +45,21 @@ void viewctl_start(int start_page, viewctl_delegate_ready ehReady, viewctl_delegate_exit ehExit, viewctl_delegate_display_ux func_display_ux) { - + // set handlers viewctl_ehGetData = func_getData; viewctl_ehReady = ehReady; viewctl_ehExit = ehExit; viewctl_display_ux = func_display_ux; - viewctl_scrolling_mode = PENDING; - viewctl_DetailsCurrentPage = start_page; - - viewctl_ChunksIndex = 0; - viewctl_ChunksCount = 1; + // initialize variables + viewctl.scrolling_mode = PENDING; + viewctl.detailsCurrentPage = start_page; + viewctl.chunksIndex = 0; + viewctl.chunksCount = 1; viewctl_display_page(); - if (viewctl_ehReady != NULL) { - ehReady(0); + viewctl_ehReady(0); } } @@ -83,31 +74,28 @@ const bagl_element_t *ui_view_info_prepro(const bagl_element_t *element) { case 0x02: UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); break; - case 0x03: - UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); - break; } return element; } void submenu_left() { - viewctl_ChunksIndex--; - viewctl_scrolling_mode = PENDING; + viewctl.chunksIndex--; + viewctl.scrolling_mode = PENDING; viewctl_display_page(); } void submenu_right() { - viewctl_ChunksIndex++; - viewctl_scrolling_mode = PENDING; + viewctl.chunksIndex++; + viewctl.scrolling_mode = PENDING; viewctl_display_page(); } void menu_left() { - viewctl_scrolling_mode = PENDING; - viewctl_ChunksIndex = 0; - viewctl_ChunksCount = 1; - if (viewctl_DetailsCurrentPage > 0) { - viewctl_DetailsCurrentPage--; + viewctl.scrolling_mode = PENDING; + viewctl.chunksIndex = 0; + viewctl.chunksCount = 1; + if (viewctl.detailsCurrentPage > 0) { + viewctl.detailsCurrentPage--; viewctl_display_page(); } else { viewctl_ehExit(0); @@ -115,11 +103,11 @@ void menu_left() { } void menu_right() { - viewctl_scrolling_mode = PENDING; - viewctl_ChunksIndex = 0; - viewctl_ChunksCount = 1; - if (viewctl_DetailsCurrentPage < viewctl_DetailsPageCount - 1) { - viewctl_DetailsCurrentPage++; + viewctl.scrolling_mode = PENDING; + viewctl.chunksIndex = 0; + viewctl.chunksCount = 1; + if (viewctl.detailsCurrentPage < viewctl.detailsPageCount - 1) { + viewctl.detailsCurrentPage++; viewctl_display_page(); } else { viewctl_ehExit(0); @@ -127,9 +115,9 @@ void menu_right() { } void viewctl_crop_key() { - int offset = strlen((char *) viewctl_DataKey) - MAX_SCREEN_LINE_WIDTH; + int offset = strlen((char *) viewctl.dataKey) - MAX_SCREEN_LINE_WIDTH; if (offset > 0) { - char *start = (char *) viewctl_DataKey; + char *start = (char *) viewctl.dataKey; for (;;) { *start = start[offset]; if (*start++ == '\0') @@ -143,56 +131,68 @@ void viewctl_display_page() { return; } + strcpy(viewctl.title, "?"); + strcpy(viewctl.dataKey, "?"); + strcpy(viewctl.dataValue, "?"); + // Read key and value strings from json viewctl_ehGetData( - (char *) viewctl_Title, - sizeof(viewctl_Title), - (char *) viewctl_DataKey, - sizeof(viewctl_DataKey), - (char *) viewctl_DataValue, - sizeof(viewctl_DataValue), - viewctl_DetailsCurrentPage, - viewctl_ChunksIndex, - &viewctl_DetailsPageCount, - &viewctl_ChunksCount); - - // If value is very long, we split it into chunks - // and add chunk index/count information at the end of the key - if (viewctl_ChunksCount > 1) { - int position = strlen((char *) viewctl_DataKey); - snprintf((char *) viewctl_DataKey + position, - sizeof(viewctl_DataKey) - position, - " %02d/%02d", - viewctl_ChunksIndex + 1, - viewctl_ChunksCount); + (char *) viewctl.title, sizeof(viewctl.title), + (char *) viewctl.dataKey, sizeof(viewctl.dataKey), + (char *) viewctl.dataValue, sizeof(viewctl.dataValue), + viewctl.detailsCurrentPage, viewctl.chunksIndex, + &viewctl.detailsPageCount, &viewctl.chunksCount); + + // fix possible utf8 issues + asciify((char *) viewctl.title); + asciify((char *) viewctl.dataKey); + asciify((char *) viewctl.dataValue); + + if (viewctl.chunksCount > 0) { + // If value is very long, we split it into chunks + // and add chunk index/count information at the end of the key + if (viewctl.chunksCount > 1) { + int position = strlen((char *) viewctl.dataKey); + snprintf((char *) viewctl.dataKey + position, sizeof(viewctl.dataKey) - position, + " %d/%d", viewctl.chunksIndex + 1, viewctl.chunksCount); + } + +#if defined(TARGET_NANOX) + const int dataValueLen = strlen(viewctl.dataValue); + + int offset = 0; + for (int i=0; i MAX_SCREEN_LINE_WIDTH) { - int value_length = strlen((char *) viewctl_DataValue); + viewctl.scrolling_mode = VALUE_SCROLLING; + if (strlen((char *) viewctl.dataKey) > MAX_SCREEN_LINE_WIDTH) { + int value_length = strlen((char *) viewctl.dataValue); if (value_length > MAX_SCREEN_LINE_WIDTH) { - strcpy((char *) viewctl_DataValue, "DBL-CLICK FOR VALUE"); - viewctl_scrolling_mode = KEY_SCROLLING_NO_VALUE; + strcpy((char *) viewctl.dataValue, "DBL-CLICK FOR VALUE"); + viewctl.scrolling_mode = KEY_SCROLLING_NO_VALUE; } else { - viewctl_scrolling_mode = KEY_SCROLLING_SHORT_VALUE; + viewctl.scrolling_mode = KEY_SCROLLING_SHORT_VALUE; } } } default: break; } - - // fix possible utf8 issues - asciify((char *) viewctl_Title); - asciify((char *) viewctl_DataKey); - asciify((char *) viewctl_DataValue); +#endif + } viewctl_display_ux(); } diff --git a/src/view_common.h b/src/view_common.h index 7f2b5c25..24541f60 100644 --- a/src/view_common.h +++ b/src/view_common.h @@ -16,18 +16,15 @@ ********************************************************************************/ #pragma once +#include + #include "os.h" #include "cx.h" #define MAX_CHARS_TITLE 32 #define MAX_CHARS_PER_KEY_LINE 64 #define MAX_CHARS_PER_VALUE_LINE 192 -#define MAX_SCREEN_LINE_WIDTH 22 - -extern volatile char viewctl_DataKey[MAX_CHARS_PER_KEY_LINE]; -extern volatile char viewctl_DataValue[MAX_CHARS_PER_VALUE_LINE]; -extern volatile char viewctl_Title[MAX_SCREEN_LINE_WIDTH]; -extern enum UI_DISPLAY_MODE viewctl_scrolling_mode; +#define MAX_SCREEN_LINE_WIDTH 19 enum UI_DISPLAY_MODE { VALUE_SCROLLING, @@ -36,27 +33,45 @@ enum UI_DISPLAY_MODE { PENDING }; -// Index of the currently displayed page -extern int viewctl_DetailsCurrentPage; -// Total number of displayable pages -extern int viewctl_DetailsPageCount; +#if defined(TARGET_NANOX) +#define MAX_SCREEN_NUM_LINES 4 +#endif + +typedef struct { + enum UI_DISPLAY_MODE scrolling_mode; + // Index of the currently displayed page + int16_t detailsCurrentPage; + // Total number of displayable pages + int16_t detailsPageCount; + + // When data goes beyond the limit, it will be split in chunks that + // that spread over several pages + // Index of currently displayed value chunk + int16_t chunksIndex; + // Total number of displayable value chunks + int16_t chunksCount; + + // DATA + char dataKey[MAX_CHARS_PER_KEY_LINE]; + char dataValue[MAX_CHARS_PER_VALUE_LINE]; + char title[MAX_SCREEN_LINE_WIDTH]; + +#if defined(TARGET_NANOX) + char dataValueChunk[MAX_SCREEN_NUM_LINES][MAX_SCREEN_LINE_WIDTH+1]; +#endif +} viewctl_s; -// Below data is used to help split long messages that are scrolled -// into smaller chunks so they fit the memory buffer -// Index of currently displayed value chunk -extern int viewctl_ChunksIndex; -// Total number of displayable value chunks -extern int viewctl_ChunksCount; +extern viewctl_s viewctl; // Delegate to update contents -typedef int (*viewctl_delegate_getData)( - char *title, int max_title_length, - char *key, int max_key_length, - char *value, int max_value_length, - int page_index, - int chunk_index, - int *page_count_out, - int *chunk_count_out); +typedef int16_t (*viewctl_delegate_getData)( + char *title, int16_t max_title_length, + char *key, int16_t max_key_length, + char *value, int16_t max_value_length, + int16_t page_index, + int16_t chunk_index, + int16_t *page_count_out, + int16_t *chunk_count_out); // Delegate to handle exit view event typedef void (*viewctl_delegate_exit)(unsigned int ignored); diff --git a/src/view_conf.c b/src/view_conf.c index f021f2f1..22c29fcc 100644 --- a/src/view_conf.c +++ b/src/view_conf.c @@ -30,34 +30,44 @@ viewctl_delegate_accept viewctl_ehAccept = NULL; viewctl_delegate_reject viewctl_ehReject = NULL; +#ifdef TARGET_NANOX +void viewconf_start(int start_page, + viewctl_delegate_getData func_update, + viewctl_delegate_ready func_ready, + viewctl_delegate_exit func_exit, + viewctl_delegate_accept func_accept, + viewctl_delegate_reject func_reject){} + +#else static const bagl_element_t viewconf_bagl_valuescrolling[] = { - UI_FillRectangle(0, 0, 0, 128, 32, 0x000000, 0xFFFFFF), - UI_Icon(0, 0, 0, 7, 7, BAGL_GLYPH_ICON_CROSS), - UI_Icon(0, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), - UI_LabelLine(1, 0, 8, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_Title), - UI_LabelLine(1, 0, 19, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataKey), - UI_LabelLineScrolling(2, 16, 30, 96, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataValue), + UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), + UI_Icon(UIID_ICONLEFT, 0, 0, 7, 7, BAGL_GLYPH_ICON_CROSS), + UI_Icon(UIID_ICONRIGHT, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), + + UI_LabelLine(UIID_LABEL + 0, 0, 8, 128, 11, UI_WHITE, UI_BLACK, (const char *) viewctl.title), + UI_LabelLine(UIID_LABEL + 1, 0, 19, 128, 11, UI_WHITE, UI_BLACK, (const char *) viewctl.dataKey), + UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 30, 96, 11, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), }; static const bagl_element_t viewconf_bagl_keyscrolling[] = { - UI_FillRectangle(0, 0, 0, 128, 32, 0x000000, 0xFFFFFF), - UI_Icon(0, 0, 0, 7, 7, BAGL_GLYPH_ICON_CROSS), - UI_Icon(0, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), - UI_LabelLine(1, 0, 8, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_Title), - UI_LabelLine(1, 0, 30, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataValue), - UI_LabelLineScrolling(2, 16, 19, 96, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataKey), + UI_FillRectangle(0, 0, 0, UI_SCREEN_WIDTH, UI_SCREEN_HEIGHT, 0x000000, 0xFFFFFF), + UI_Icon(UIID_ICONLEFT, 0, 0, 7, 7, BAGL_GLYPH_ICON_CROSS), + UI_Icon(UIID_ICONRIGHT, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_CHECK), + + UI_LabelLine(UIID_LABEL + 0, 0, 8, 128, 11, UI_WHITE, 0x000000, (const char *) viewctl.title), + UI_LabelLine(UIID_LABEL + 1, 0, 30, 128, 11, UI_WHITE, 0x000000, (const char *) viewctl.dataValue), + UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 19, 96, 11, UI_WHITE, 0x000000, (const char *) viewctl.dataKey), }; const bagl_element_t *viewconf_bagl_prepro(const bagl_element_t *element) { - switch (element->component.userid) { - case 0x01: + case UIID_ICONLEFT: UX_CALLBACK_SET_INTERVAL(2000); break; - case 0x02: - UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); + case UIID_ICONRIGHT: + UX_CALLBACK_SET_INTERVAL(2000); break; - case 0x03: + case UIID_LABELSCROLL: UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); break; } @@ -90,7 +100,7 @@ static unsigned int viewconf_bagl_valuescrolling_button( } void viewconf_display_ux() { - if (viewctl_scrolling_mode == VALUE_SCROLLING) { + if (viewctl.scrolling_mode == VALUE_SCROLLING) { UX_DISPLAY(viewconf_bagl_valuescrolling, viewconf_bagl_prepro); } else { UX_DISPLAY(viewconf_bagl_keyscrolling, viewconf_bagl_prepro); @@ -107,3 +117,4 @@ void viewconf_start(int start_page, viewctl_ehReject = func_reject; viewctl_start(start_page, func_update, func_ready, func_exit, viewconf_display_ux); } +#endif diff --git a/src/view_expl.c b/src/view_expl.c index 723baecc..8d2ce9df 100644 --- a/src/view_expl.c +++ b/src/view_expl.c @@ -27,47 +27,90 @@ #include #include -static const bagl_element_t viewexpl_bagl_valuescrolling[] = { - UI_FillRectangle(0, 0, 0, 128, 32, 0x000000, 0xFFFFFF), - UI_Icon(0, 0, 0, 7, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(0, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_RIGHT), - UI_LabelLine(1, 0, 8, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_Title), - UI_LabelLine(1, 0, 19, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataKey), - UI_LabelLineScrolling(2, 16, 30, 96, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataValue), +#if defined(TARGET_NANOX) +static const bagl_element_t viewexpl_bagl[] = { + UI_BACKGROUND_LEFT_RIGHT_ICONS, + UI_LabelLine(UIID_LABEL+0, 0, 9 + UI_11PX * 0, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.title), + UI_LabelLine(UIID_LABEL+1, 0, 9 + UI_11PX * 1, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataKey), + UI_LabelLine(UIID_LABEL+2, 0, 9 + UI_11PX * 2, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[0]), + UI_LabelLine(UIID_LABEL+3, 0, 9 + UI_11PX * 3, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[1]), + UI_LabelLine(UIID_LABEL+4, 0, 9 + UI_11PX * 4, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[2]), + UI_LabelLine(UIID_LABEL+5, 0, 9 + UI_11PX * 5, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValueChunk[3]), }; -static const bagl_element_t viewexpl_bagl_keyscrolling[] = { - UI_FillRectangle(0, 0, 0, 128, 32, 0x000000, 0xFFFFFF), - UI_Icon(0, 0, 0, 7, 7, BAGL_GLYPH_ICON_LEFT), - UI_Icon(0, 128 - 7, 0, 7, 7, BAGL_GLYPH_ICON_RIGHT), - UI_LabelLine(1, 0, 8, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_Title), - UI_LabelLine(1, 0, 30, 128, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataValue), - UI_LabelLineScrolling(2, 16, 19, 96, 11, 0xFFFFFF, 0x000000, (const char *) viewctl_DataKey), -}; +static unsigned int viewexpl_bagl_button( + unsigned int button_mask, + unsigned int button_mask_counter) { + switch (button_mask) { + // Press both left and right to switch to value scrolling + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: { + if (viewctl.scrolling_mode == KEY_SCROLLING_NO_VALUE) { + viewctl_display_page(); + } else { + viewctl_ehExit(0); + } + break; + } -const bagl_element_t *viewexpl_bagl_prepro(const bagl_element_t *element) { + // Press left to progress to the previous element + case BUTTON_EVT_RELEASED | BUTTON_LEFT: { + if (viewctl.chunksIndex > 0) { + submenu_left(); + } else { + menu_left(); + } + break; + } - switch (element->component.userid) { - case 0x01: - UX_CALLBACK_SET_INTERVAL(2000); + // Hold left to progress to the previous element quickly + // It also steps out from the value chunk page view mode + case BUTTON_EVT_FAST | BUTTON_LEFT: { + menu_left(); break; - case 0x02: - UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); + } + + // Press right to progress to the next element + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: { + if (viewctl.chunksIndex < viewctl.chunksCount - 1) { + submenu_right(); + } else { + menu_right(); + } break; - case 0x03: - UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); + } + + // Hold right to progress to the next element quickly + // It also steps out from the value chunk page view mode + case BUTTON_EVT_FAST | BUTTON_RIGHT: { + menu_right(); break; + } } - return element; + return 0; } +#elif defined(TARGET_NANOS) +static const bagl_element_t viewexpl_bagl_valuescrolling[] = { + UI_BACKGROUND_LEFT_RIGHT_ICONS, + UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.title), + UI_LabelLine(UIID_LABEL + 1, 0, 19, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataKey), + UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 30, 96, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), +}; + +static const bagl_element_t viewexpl_bagl_keyscrolling[] = { + UI_BACKGROUND_LEFT_RIGHT_ICONS, + UI_LabelLine(UIID_LABEL + 0, 0, 8, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.title), + UI_LabelLine(UIID_LABEL + 1, 0, 30, UI_SCREEN_WIDTH, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataValue), + UI_LabelLineScrolling(UIID_LABELSCROLL, 16, 19, 96, UI_11PX, UI_WHITE, UI_BLACK, (const char *) viewctl.dataKey), +}; + static unsigned int viewexpl_bagl_keyscrolling_button( unsigned int button_mask, unsigned int button_mask_counter) { switch (button_mask) { // Press both left and right to switch to value scrolling case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: { - if (viewctl_scrolling_mode == KEY_SCROLLING_NO_VALUE) { + if (viewctl.scrolling_mode == KEY_SCROLLING_NO_VALUE) { viewctl_display_page(); } else { viewctl_ehExit(0); @@ -77,7 +120,7 @@ static unsigned int viewexpl_bagl_keyscrolling_button( // Press left to progress to the previous element case BUTTON_EVT_RELEASED | BUTTON_LEFT: { - if (viewctl_ChunksIndex > 0) { + if (viewctl.chunksIndex > 0) { submenu_left(); } else { menu_left(); @@ -94,7 +137,7 @@ static unsigned int viewexpl_bagl_keyscrolling_button( // Press right to progress to the next element case BUTTON_EVT_RELEASED | BUTTON_RIGHT: { - if (viewctl_ChunksIndex < viewctl_ChunksCount - 1) { + if (viewctl.chunksIndex < viewctl.chunksCount - 1) { submenu_right(); } else { menu_right(); @@ -118,18 +161,38 @@ static unsigned int viewexpl_bagl_valuescrolling_button( return viewexpl_bagl_keyscrolling_button(button_mask, button_mask_counter); } +#endif + +const bagl_element_t *viewexpl_bagl_prepro(const bagl_element_t *element) { + switch (element->component.userid) { + case UIID_ICONLEFT: + UX_CALLBACK_SET_INTERVAL(2000); + break; + case UIID_ICONRIGHT: + UX_CALLBACK_SET_INTERVAL(2000); + break; + case UIID_LABELSCROLL: + UX_CALLBACK_SET_INTERVAL(MAX(3000, 1000 + bagl_label_roundtrip_duration_ms(element, 7))); + break; + } + return element; +} + void viewexpl_display_ux() { - if (viewctl_scrolling_mode == VALUE_SCROLLING) { +#if defined(TARGET_NANOX) + UX_DISPLAY(viewexpl_bagl, viewexpl_bagl_prepro); +#else + if (viewctl.scrolling_mode == VALUE_SCROLLING) { UX_DISPLAY(viewexpl_bagl_valuescrolling, viewexpl_bagl_prepro); } else { UX_DISPLAY(viewexpl_bagl_keyscrolling, viewexpl_bagl_prepro); } +#endif } void viewexpl_start(int start_page, viewctl_delegate_getData ehUpdate, viewctl_delegate_ready ehReady, viewctl_delegate_exit ehExit) { - viewctl_start(start_page, ehUpdate, ehReady, ehExit, viewexpl_display_ux); }