diff --git a/.github/workflows/build_rust_sdk.yml b/.github/workflows/build_rust_sdk.yml
index 2eb48df3f..54a4ee6dd 100644
--- a/.github/workflows/build_rust_sdk.yml
+++ b/.github/workflows/build_rust_sdk.yml
@@ -18,7 +18,6 @@ jobs:
uses: actions/checkout@v4
with:
path: c_sdk
- ref: master
- name: Clone Rust SDK
uses: actions/checkout@v4
with:
diff --git a/.github/workflows/check_clang_static_analyzer.yml b/.github/workflows/check_clang_static_analyzer.yml
index 8eea72f42..213541bda 100644
--- a/.github/workflows/check_clang_static_analyzer.yml
+++ b/.github/workflows/check_clang_static_analyzer.yml
@@ -38,7 +38,7 @@ jobs:
make scan-build -j ENABLE_SDK_WERROR=1 DEBUG=${{ matrix.debug }}
- name: Upload scan result
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: failure()
with:
name: scan-build
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index 155dc7cd0..5986a0e85 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -52,7 +52,7 @@ jobs:
genhtml coverage.info -o coverage
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: code-coverage
path: unit-tests/lib_*/coverage
diff --git a/Makefile.defines b/Makefile.defines
index 9b83c46cd..b66338d2a 100644
--- a/Makefile.defines
+++ b/Makefile.defines
@@ -20,7 +20,7 @@ __MAKEFILE_DEFINES__ := 1
include $(BOLOS_SDK)/Makefile.target
-API_LEVEL := 0
+API_LEVEL := 22
# APPNAME exposed to the app as a CFLAG because it might contain spaces
CFLAGS += -DAPPNAME=\"$(APPNAME)\"
@@ -50,23 +50,23 @@ endif
SYSROOT = $(shell $(GCCPATH)arm-none-eabi-gcc -print-sysroot)
ifeq ($(SYSROOT),)
-CFLAGS += -I/usr/include
-else
-CFLAGS += --sysroot="$(SYSROOT)"
+ # path for Debian-based systems
+ SYSROOT = /usr/lib/arm-none-eabi
endif
+CFLAGS += --sysroot="$(SYSROOT)"
-# optimization and debug levels
+# optimization and debugging levels
ifneq ($(DEBUG),0)
-OPTI_LVL = -Og
-OPTI_ALVL = $(OPTI_LVL)
-DBG_LVL = -g3
+ OPTI_LVL = g
+ # any higher won't work with LLVM >= 14
+ # will be fixed in LLVM 20 : https://github.com/llvm/llvm-project/pull/116956
+ DBG_LVL = 1
else
-OPTI_LVL = -Oz
-OPTI_ALVL = -Os # assembler does not handle -Oz, use -Os instead
-DBG_LVL = -g0
+ OPTI_LVL = z
+ DBG_LVL = 0
endif
+CFLAGS += -O$(OPTI_LVL) -g$(DBG_LVL)
-CFLAGS += $(OPTI_LVL) $(DBG_LVL)
CFLAGS += -fomit-frame-pointer -momit-leaf-frame-pointer
CFLAGS += -fno-common -mlittle-endian
@@ -91,9 +91,6 @@ CFLAGS += -fropi
CFLAGS += -fno-jump-tables # avoid jump tables for switch to avoid problems with invalid PIC access
CFLAGS += -nostdlib -nodefaultlibs
-AFLAGS += $(OPTI_ALVL) $(DBG_LVL) -fno-common
-
-LDFLAGS += $(OPTI_LVL) $(DBG_LVL)
LDFLAGS += -fomit-frame-pointer
LDFLAGS += -Wall
LDFLAGS += -fno-common -ffunction-sections -fdata-sections -fwhole-program
@@ -103,7 +100,7 @@ LDFLAGS += -Wl,--gc-sections -Wl,-Map,$(DBG_DIR)/app.map
ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOS))
CFLAGS += --target=armv6m-none-eabi -mthumb
CFLAGS += -mcpu=cortex-m0plus -mthumb -mtune=cortex-m0plus
-AFLAGS += -mcpu=cortex-m0plus -mtune=cortex-m0plus -mthumb
+AFLAGS += -mcpu=cortex-m0plus -mthumb
LDFLAGS += -mcpu=cortex-m0plus -mthumb
LDFLAGS += -nostartfiles --specs=nano.specs
endif
@@ -117,7 +114,7 @@ LDFLAGS += -mtune=cortex-m0plus -mlittle-endian -mcpu=cortex-m0plus
LDFLAGS += -nostdlib -nodefaultlibs #-nostartfiles
LDFLAGS += -mno-movt
LDFLAGS += -L$(BOLOS_SDK)/arch/st33/lib/
-AFLAGS += -mcpu=cortex-m0plus -mtune=cortex-m0plus -mthumb
+AFLAGS += -mcpu=cortex-m0plus -mthumb
endif
ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_STAX TARGET_FLEX TARGET_NANOS2))
diff --git a/Makefile.rules_generic b/Makefile.rules_generic
index a3170dccd..bf4fded01 100644
--- a/Makefile.rules_generic
+++ b/Makefile.rules_generic
@@ -92,11 +92,11 @@ $(OBJ_DIR)/app/%.o: $(APP_DIR)/%.c $(BUILD_DEPENDENCIES) prepare
$(OBJ_DIR)/app/%.o: $(APP_DIR)/%.s $(BUILD_DEPENDENCIES) prepare
@echo "[AS] $@"
- $(L)$(call as_cmdline,$(INCLUDES_PATH), $(DEFINES),$<,$@)
+ $(L)$(call as_cmdline,$(INCLUDES_PATH),$<,$@)
$(OBJ_DIR)/app/%.o: $(APP_DIR)/%.S $(BUILD_DEPENDENCIES) prepare
@echo "[AS] $@"
- $(L)$(call as_cmdline,$(INCLUDES_PATH), $(DEFINES),$<,$@)
+ $(L)$(call as_cmdline,$(INCLUDES_PATH),$<,$@)
# SDK files targets
$(OBJ_DIR)/sdk/%.o: $(BOLOS_SDK)/%.c $(BUILD_DEPENDENCIES) prepare
@@ -105,11 +105,11 @@ $(OBJ_DIR)/sdk/%.o: $(BOLOS_SDK)/%.c $(BUILD_DEPENDENCIES) prepare
$(OBJ_DIR)/sdk/%.o: $(BOLOS_SDK)/%.s $(BUILD_DEPENDENCIES) prepare
@echo "[AS] $@"
- $(L)$(call as_cmdline,$(INCLUDES_PATH), $(DEFINES),$<,$@)
+ $(L)$(call as_cmdline,$(INCLUDES_PATH),$<,$@)
$(OBJ_DIR)/sdk/%.o: $(BOLOS_SDK)/%.S $(BUILD_DEPENDENCIES) prepare
@echo "[AS] $@"
- $(L)$(call as_cmdline,$(INCLUDES_PATH), $(DEFINES),$<,$@)
+ $(L)$(call as_cmdline,$(INCLUDES_PATH),$<,$@)
# Generic targets
$(OBJ_DIR)/%.o: %.c $(BUILD_DEPENDENCIES) prepare
@@ -118,11 +118,11 @@ $(OBJ_DIR)/%.o: %.c $(BUILD_DEPENDENCIES) prepare
$(OBJ_DIR)/%.o: %.s $(BUILD_DEPENDENCIES) prepare
@echo "[AS] $@"
- $(L)$(call as_cmdline,$(INCLUDES_PATH), $(DEFINES),$<,$@)
+ $(L)$(call as_cmdline,$(INCLUDES_PATH),$<,$@)
$(OBJ_DIR)/%.o: %.S $(BUILD_DEPENDENCIES) prepare
@echo "[AS] $@"
- $(L)$(call as_cmdline,$(INCLUDES_PATH), $(DEFINES),$<,$@)
+ $(L)$(call as_cmdline,$(INCLUDES_PATH),$<,$@)
ifeq (,$(filter $(DEFINES),BOLOS_OS_UPGRADER_APP))
ifneq ($(SCRIPT_LD),)
@@ -197,7 +197,7 @@ endif
# dependency files are generated along the object file
cc_cmdline = $(CC) -c $(CFLAGS) -MMD -MT $(4) -MF $(subst $(OBJ_DIR), $(DEP_DIR), $(addsuffix .d, $(basename $(4)))) $(addprefix -D,$(2)) $(addprefix -I,$(1)) -o $(4) $(3)
-as_cmdline = $(AS) -c $(AFLAGS) $(addprefix -D,$(2)) $(addprefix -I,$(1)) -o $(4) $(3)
+as_cmdline = $(AS) -c $(AFLAGS) $(addprefix -I,$(1)) -o $(3) $(2)
### END GCC COMPILER RULES
diff --git a/Makefile.standard_app b/Makefile.standard_app
index 2cee343d9..95c02823a 100644
--- a/Makefile.standard_app
+++ b/Makefile.standard_app
@@ -218,8 +218,11 @@ APP_FLAGS_APP_LOAD_PARAMS = $(shell printf '0x%x' $$(( $(STANDARD_APP_FLAGS) + $
# COMPILER SETTINGS #
#####################################################################
CC := $(CLANGPATH)clang
-AS := $(GCCPATH)arm-none-eabi-gcc
-LD := $(GCCPATH)arm-none-eabi-gcc
+AS := $(CLANGPATH)clang
+LD := $(CLANGPATH)clang
+ifeq ($(AS),$(CLANGPATH)clang)
+ AFLAGS += --target=arm-arm-none-eabi
+endif
LDLIBS += -lm -lgcc -lc
#####################################################################
diff --git a/README.md b/README.md
index 31608c075..61538a176 100644
--- a/README.md
+++ b/README.md
@@ -55,34 +55,23 @@ In short, to build an app for an OS, you should:
## About API_LEVEL branches
-This list the API_LEVEL branches with their purposed (corresponding OS) and state if they should still be patched or not (OS not “active” anymore).
-OS release candidates are only kept in the list when a corresponding OS release production might be released on the same API_LEVEL.
-
-| Name | Related OS | Active
-|---------|-------------------------------------------------------------------------------------------------------------------------------|-------------------
-| LNS |
nanos_2.1.0 | :heavy_check_mark:
-| 1 | nanox_2.1.0
nanos+_1.1.0 | :x:
-| 2 | only rc releases | :x:
-| 3 | only rc releases | :x:
-| 4 | only rc releases | :x:
-| 5 | nanox_2.2.{0, 1, 2, 3}
nanos+ 1.1.1 | :heavy_check_mark:
-| 6 | only rc releases | :x:
-| 7 / 7.1 | only rc releases | :x:
-| 7.2 | only rc releases
(not compatible due
to font update) | :x:
-| 8 | stax_1.0.0 | :x:
-| 9 | only rc releases | :x:
-| 10 | stax_1.1.0 | :x:
-| 11 | stax_1.2.0
stax_1.2.1 | :x:
-| 12 | nanox_2.3.0-rc2
nanos+_1.2.0-rc2 | :x:
-| 13 | stax_1.3.0 | :x:
-| 14 | only rc releases | :x:
-| 15 | stax_1.4.0 | :heavy_check_mark:
-| 16 | europa_0.1.0-re1 | :x:
-| 17 | europa_0.1.0-re2 | :x:
-| 18 | europa_0.1.0-re3
flex_0.2.0-rc1
flex_0.2.0-rc2
nanox_2.3.0-rc5
nanos+_1.2.0-rc5
nanos+_1.2.0 | :heavy_check_mark:
-| 19 | flex_1.0.0-rc1
flex_1.0.0-rc2
flex_1.0.0
flex_1.0.1
| :x:
-| 20 | stax_1.5.0-rc1
flex_1.1.0-rc1 | :x:
-| 21 | stax_1.5.0-rc2
flex_1.1.0-rc2
stax_1.5.0
flex_1.1.0
flex_1.1.1
| :heavy_check_mark:
+This list the main API_LEVEL branches with their purposed (corresponding OS) and state if they should still be patched or not (OS not “active” anymore).
+
+The full mapping of API_LEVEL branches, including OS release candidates, is available [here](api_levels.json).
+
+| Name | Related OS | Active |
+| ---- | -------------------------------------------------- | ------------------ |
+| LNS |
nanos_2.1.0 | :heavy_check_mark: |
+| 1 | nanox_2.1.0
nanos+_1.1.0 | :x: |
+| 5 | nanox_2.2.{0, 1, 2, 3}
nanos+ 1.1.1 | :heavy_check_mark: |
+| 8 | stax_1.0.0 | :x: |
+| 10 | stax_1.1.0 | :x: |
+| 11 | stax_1.2.0
stax_1.2.1 | :x: |
+| 13 | stax_1.3.0 | :x: |
+| 15 | stax_1.4.0 | :x: |
+| 18 | nanos+_1.2.0 | :heavy_check_mark: |
+| 19 | flex_1.0.0
flex_1.0.1
| :x: |
+| 21 | stax_1.5.0
flex_1.1.0
flex_1.1.1
| :heavy_check_mark: |
### Cherry-picking process:
diff --git a/api_levels.json b/api_levels.json
new file mode 100644
index 000000000..8dffb911a
--- /dev/null
+++ b/api_levels.json
@@ -0,0 +1,98 @@
+{
+ "LNS": [
+ "nanos_2.1.0"
+ ],
+ "1": [
+ "nanox_2.1.0",
+ "nanos+_1.1.0"
+ ],
+ "2": [
+ "only rc releases"
+ ],
+ "3": [
+ "only rc releases"
+ ],
+ "4": [
+ "only rc releases"
+ ],
+ "5": [
+ "nanox_2.2.0",
+ "nanox_2.2.1",
+ "nanox_2.2.2",
+ "nanox_2.2.3",
+ "nanos+_1.1.1"
+ ],
+ "6": [
+ "only rc releases"
+ ],
+ "7": [
+ "only rc releases"
+ ],
+ "7.1": [
+ "only rc releases"
+ ],
+ "7.2": [
+ "only rc releases"
+ ],
+ "8": [
+ "stax_1.0.0"
+ ],
+ "9": [
+ "only rc releases"
+ ],
+ "10": [
+ "stax_1.1.0"
+ ],
+ "11": [
+ "stax_1.2.0",
+ "stax_1.2.1"
+ ],
+ "12": [
+ "nanox_2.3.0-rc2",
+ "nanos+_1.2.0-rc2"
+ ],
+ "13": [
+ "stax_1.3.0"
+ ],
+ "14": [
+ "only rc releases"
+ ],
+ "15": [
+ "stax_1.4.0"
+ ],
+ "16": [
+ "europa_0.1.0-re1"
+ ],
+ "17": [
+ "europa_0.1.0-re2"
+ ],
+ "18": [
+ "europa_0.1.0-re3",
+ "flex_0.2.0-rc1",
+ "flex_0.2.0-rc2",
+ "nanox_2.3.0-rc5",
+ "nanos+_1.2.0-rc5",
+ "nanos+_1.2.0"
+ ],
+ "19": [
+ "flex_1.0.0-rc1",
+ "flex_1.0.0-rc2",
+ "flex_1.0.0",
+ "flex_1.0.1"
+ ],
+ "20": [
+ "stax_1.5.0-rc1",
+ "flex_1.1.0-rc1"
+ ],
+ "21": [
+ "stax_1.5.0-rc2",
+ "flex_1.1.0-rc2",
+ "stax_1.5.0",
+ "flex_1.1.0",
+ "flex_1.1.1",
+ "nanos+_1.3.0-rc1",
+ "nanox_2.4.0-rc1",
+ "stax_1.6.0-rc1",
+ "flex_1.2.0-rc1"
+ ]
+}
diff --git a/doc/Doxyfile b/doc/Doxyfile
index ac9121848..56bc0322c 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -263,12 +263,6 @@ TAB_SIZE = 4
ALIASES =
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST =
-
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@@ -1182,13 +1176,6 @@ CLANG_DATABASE_PATH =
ALPHABETICAL_INDEX = YES
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX = 5
-
# In case all classes in a project start with a common prefix, all classes will
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# can be used to specify a prefix (or a list of prefixes) that should be ignored
diff --git a/doc/mainpage.dox b/doc/mainpage.dox
index 5d5943464..2bbc66517 100644
--- a/doc/mainpage.dox
+++ b/doc/mainpage.dox
@@ -3,7 +3,7 @@
@section mainpage_intro Introduction
This documentation describes the different interfaces of Embedded SDK,
-for \b Nano X, \b Nano S+ and \b Stax applications development.
+for \b Nano X, \b Nano S+ \b Stax and \b Flex applications development.
@section ble_presentation BlueTooth Low-Energy Interface
@@ -16,7 +16,7 @@ The @subpage cxng_mainpage page contains all information necessary to understand
@section nbgl_presentation Screen drawing API
The @subpage nbgl_mainpage page contains all information necessary to understand the Graphical Library for
-\b Stax, \b NanoX and \b NanoS+ applications development.
+\b Stax, \b Flex, \b NanoX and \b NanoS+ applications development.
@section nfc_presentation NFC Interface
@@ -50,7 +50,7 @@ The @subpage ux_mainpage page contains all information necessary to user the Use
@subsection ux_nbgl_presentation User eXperience on Applications using \b NBGL
The @subpage ux_nbgl_mainpage page contains all information necessary to user the User eXperience for
-\b Stax, \b NanoX and \b NanoS+ applications development using \b NBGL (most recent ones).
+\b Stax, \b Flex, \b NanoX and \b NanoS+ applications development using \b NBGL (most recent ones).
*/
diff --git a/include/cx_stubs.h b/include/cx_stubs.h
index b0d5a5e7e..40ec737dd 100644
--- a/include/cx_stubs.h
+++ b/include/cx_stubs.h
@@ -145,3 +145,4 @@
#define _NR_cx_cmac_start 0x8d
#define _NR_cx_cmac_update 0x8e
#define _NR_cx_cmac_finish 0x8f
+#define _NR_cx_aes_siv_reset 0x90
diff --git a/include/os_app.h b/include/os_app.h
index 3668ed3a4..76b29f571 100644
--- a/include/os_app.h
+++ b/include/os_app.h
@@ -28,6 +28,9 @@ typedef void (*appmain_t)(void);
// application slot description
typedef struct application_s {
+ // special flags for this application
+ uint64_t flags;
+
// nvram start address for this application (to check overlap when loading, and mpu lock)
unsigned char *nvram_begin;
// nvram stop address (exclusive) for this application (to check overlap when loading, and mpu
@@ -38,9 +41,6 @@ typedef struct application_s {
// into Thumb code
appmain_t main;
- // special flags for this application
- uint64_t flags;
-
// Memory organization: [ code (RX) |alignpage| data (RW) |alignpage| install params (R) ]
// length of the code section of the application (RX)
diff --git a/include/os_nvm.h b/include/os_nvm.h
index 3f1811bda..8fd5e370f 100644
--- a/include/os_nvm.h
+++ b/include/os_nvm.h
@@ -23,7 +23,7 @@ SYSCALL void nvm_erase(void *dst_adr PLENGTH(len), unsigned int len);
SUDOCALL void nvm_write_page(unsigned int page_adr, bool force);
// erase a nvm page at given address, only callable by the privileged APIs
-SUDOCALL void nvm_erase_page(unsigned int page_adr);
+SUDOCALL unsigned int nvm_erase_page(unsigned int page_adr);
// any application can wipe the global pin, global seed, user's keys
// disabled for security reasons // SYSCALL void
diff --git a/include/ox_aes.h b/include/ox_aes.h
index f09cf751d..2ee7b87f8 100644
--- a/include/ox_aes.h
+++ b/include/ox_aes.h
@@ -99,7 +99,7 @@ cx_aes_set_key_hw(const cx_aes_key_t *key PLENGTH(sizeof(cx_aes_key_t)), uint32_
/**
* @brief Resets the AES context.
*/
-SYSCALL void cx_aes_reset_hw(void);
+SYSCALL cx_err_t cx_aes_reset_hw(void);
/**
* @brief Encrypts or decrypts a block with AES.
diff --git a/lib_cxng/cx.export b/lib_cxng/cx.export
index 19d47e726..b935b75ff 100644
--- a/lib_cxng/cx.export
+++ b/lib_cxng/cx.export
@@ -148,3 +148,4 @@ cx_cipher_reset
cx_cmac_start
cx_cmac_update
cx_cmac_finish
+cx_aes_siv_reset
diff --git a/lib_cxng/include/lcx_aes_siv.h b/lib_cxng/include/lcx_aes_siv.h
index 06905dc69..b4db323b2 100644
--- a/lib_cxng/include/lcx_aes_siv.h
+++ b/lib_cxng/include/lcx_aes_siv.h
@@ -55,6 +55,17 @@ typedef struct _cx_aes_siv_context {
*/
WARN_UNUSED_RESULT cx_err_t cx_aes_siv_init(cx_aes_siv_context_t *ctx);
+/**
+ * @brief Reset the AES-SIV context and HW used.
+ * @details The cipher context must be initialized beforehand.
+ * This function must be called after calls to #cx_aes_siv_finish or
+ * #cx_aes_siv_update.
+ *
+ * @param[in] ctx Pointer to the AES-SIV context.
+ * @return Error code.
+ */
+WARN_UNUSED_RESULT cx_err_t cx_aes_siv_reset(cx_aes_siv_context_t *ctx);
+
/**
* @brief Sets the key to compute AES-SIV.
*
diff --git a/lib_cxng/src/cx_aes.c b/lib_cxng/src/cx_aes.c
index 338f5386c..49dd20cdd 100644
--- a/lib_cxng/src/cx_aes.c
+++ b/lib_cxng/src/cx_aes.c
@@ -28,21 +28,24 @@ cx_err_t cx_aes_init_key_no_throw(const uint8_t *raw_key, size_t key_len, cx_aes
cx_err_t cx_aes_enc_block(const cx_aes_key_t *key, const uint8_t *inblock, uint8_t *outblock)
{
cx_err_t error;
+ cx_err_t err_reset = CX_INTERNAL_ERROR;
CX_CHECK(cx_aes_set_key_hw(key, CX_ENCRYPT));
CX_CHECK(cx_aes_block_hw(inblock, outblock));
- cx_aes_reset_hw();
end:
- return error;
+ err_reset = cx_aes_reset_hw();
+ return error == CX_OK ? err_reset : error;
}
cx_err_t cx_aes_dec_block(const cx_aes_key_t *key, const uint8_t *inblock, uint8_t *outblock)
{
cx_err_t error;
+ cx_err_t err_reset = CX_INTERNAL_ERROR;
+ CX_CHECK(cx_aes_set_key_hw(key, CX_ENCRYPT));
CX_CHECK(cx_aes_set_key_hw(key, CX_DECRYPT));
CX_CHECK(cx_aes_block_hw(inblock, outblock));
- cx_aes_reset_hw();
end:
- return error;
+ err_reset = cx_aes_reset_hw();
+ return error == CX_OK ? err_reset : error;
}
cx_err_t cx_aes_iv_no_throw(const cx_aes_key_t *key,
diff --git a/lib_cxng/src/cx_aes_siv.c b/lib_cxng/src/cx_aes_siv.c
index ed106d3dd..e7d8fd762 100644
--- a/lib_cxng/src/cx_aes_siv.c
+++ b/lib_cxng/src/cx_aes_siv.c
@@ -22,6 +22,19 @@ cx_err_t cx_aes_siv_init(cx_aes_siv_context_t *ctx)
return error;
}
+cx_err_t cx_aes_siv_reset(cx_aes_siv_context_t *ctx)
+{
+ cx_err_t error = CX_INVALID_PARAMETER_VALUE;
+ if (ctx->cipher_ctx == NULL) {
+ goto end;
+ }
+ CX_CHECK(ctx->cipher_ctx->cipher_info->base->ctx_reset());
+ cx_cipher_reset(ctx->cipher_ctx);
+
+end:
+ return error;
+}
+
cx_err_t cx_aes_siv_set_key(cx_aes_siv_context_t *ctx, const uint8_t *key, size_t key_bitlen)
{
// AES SIV uses two keys of either 128, 192 or 256 bits each
@@ -156,12 +169,14 @@ cx_err_t cx_aes_siv_encrypt(cx_aes_siv_context_t *ctx,
uint8_t *tag)
{
cx_err_t error;
+ cx_err_t err_reset = CX_INTERNAL_ERROR;
CX_CHECK(cx_aes_siv_start(ctx, CX_ENCRYPT, NULL, 0));
CX_CHECK(cx_aes_siv_update_aad(ctx, aad, aad_len));
CX_CHECK(cx_aes_siv_finish(ctx, input, in_len, tag));
CX_CHECK(cx_aes_siv_update(ctx, input, output, in_len));
end:
- return error;
+ err_reset = cx_aes_siv_reset(ctx);
+ return error == CX_OK ? err_reset : error;
}
cx_err_t cx_aes_siv_decrypt(cx_aes_siv_context_t *ctx,
@@ -173,14 +188,16 @@ cx_err_t cx_aes_siv_decrypt(cx_aes_siv_context_t *ctx,
uint8_t *tag)
{
cx_err_t error;
+ cx_err_t err_reset = CX_INTERNAL_ERROR;
CX_CHECK(cx_aes_siv_start(ctx, CX_DECRYPT, tag, CX_AES_BLOCK_SIZE));
CX_CHECK(cx_aes_siv_update(ctx, input, output, in_len));
- cx_cipher_reset(ctx->cipher_ctx);
+ CX_CHECK(cx_aes_siv_reset(ctx));
CX_CHECK(cx_aes_siv_update_aad(ctx, aad, aad_len));
CX_CHECK(cx_aes_siv_finish(ctx, output, in_len, tag));
end:
- return error;
+ err_reset = cx_aes_siv_reset(ctx);
+ return error == CX_OK ? err_reset : error;
}
#endif // HAVE_AES && HAVE_CMAC
diff --git a/lib_cxng/src/cx_cipher.h b/lib_cxng/src/cx_cipher.h
index 88eddd8a6..7b8741a25 100644
--- a/lib_cxng/src/cx_cipher.h
+++ b/lib_cxng/src/cx_cipher.h
@@ -31,7 +31,7 @@ extern const cx_cipher_info_t cx_aes_256_info;
/** HW support */
WARN_UNUSED_RESULT cx_err_t cx_aes_set_key_hw(const cx_aes_key_t *keys, uint32_t mode);
WARN_UNUSED_RESULT cx_err_t cx_aes_block_hw(const uint8_t *inblock, uint8_t *outblock);
-void cx_aes_reset_hw(void);
+WARN_UNUSED_RESULT cx_err_t cx_aes_reset_hw(void);
#endif // HAVE_AES
#endif /* CX_CIPHER_H */
diff --git a/lib_nbgl/doc/mainpage_nanos.dox b/lib_nbgl/doc/mainpage_nanos.dox
index 5cb0e4afc..0d4b564a5 100644
--- a/lib_nbgl/doc/mainpage_nanos.dox
+++ b/lib_nbgl/doc/mainpage_nanos.dox
@@ -1,7 +1,7 @@
#ifndef HAVE_SE_TOUCH
-/** @page nbgl_mainpage New BOLOS Graphic API for Nanos
+/** @page nbgl_mainpage New BOLOS Graphic API for Nano devices (Nano X and Nano S+)
-@section mainpage_intro Introduction
+@section nbgl_mainpage_intro Introduction
This documentation describes the different interfaces of NBGL, the library that
is targeted to be integrated in Nano products (Nano X and Nano S+).
@@ -23,15 +23,11 @@ This is the most preferred API level to write an embedded Application.
The @subpage nbgl_app_use_case_nanos page contains all information necessary to understand
and use \b NBGL Application Use cases API
-@subsection nbgl_flow_level_api_1 Flow API
-The @subpage nbgl_flow page contains all information necessary to understand
-and use \b NBGL Flow API, managing a scenario flow, made of one or several steps
-
-@subsection nbgl_step_level_api_1 Step API
+@subsection nbgl_step_level_api_1 High-Level API
The @subpage nbgl_step page contains all information necessary to understand
-and use \b NBGL Step API, managing the page(s) of a scenario step
+and use \b NBGL High-Level API, using predefined steps
-@subsection nbgl_mid_level_api_1 Complex objects (layout) API
+@subsection nbgl_mid_level_api_1 Mid-Level API
The @subpage nbgl_layout page contains all information necessary to understand
and use \b NBGL Mid-Level API, using complex objects
@@ -48,6 +44,7 @@ In order to reduce the footprint (RAM+Flash), some features are only activated i
Define | Meaning | Default state
------------- | ------------- | -------------
+\b NBGL_STEP | Activate High-Level API | Active in SDK, and in OS
\b NBGL_USE_CASE | Activate Use Case API | Active in SDK, not in OS
\b NBGL_KEYBOARD | Activate Keyboard object | Active in OS, not in SDK
\b NBGL_KEYPAD | Activate Keypad object | Active in OS, not in SDK
diff --git a/lib_nbgl/doc/nbgl_layout.dox b/lib_nbgl/doc/nbgl_layout.dox
index 608d61b8f..d57f134cb 100644
--- a/lib_nbgl/doc/nbgl_layout.dox
+++ b/lib_nbgl/doc/nbgl_layout.dox
@@ -50,10 +50,10 @@ For example 1:
@code
nbgl_layoutDescription_t layoutDescription = {
- .modal = false, // not modal (so on plane 0)
- .onActionCallback = &myActionCallback, // generic callback for all controls
- .onTapText = NULL, // no "tapable" main container
- .ticker.callback = NULL // no ticker
+ .modal = false, // not modal (so on plane 0)
+ .onActionCallback = &myActionCallback, // generic callback for all controls
+ .onTapText = NULL, // no "tapable" main container
+ .ticker.callback = NULL // no ticker
};
// create layout
nbgl_layout_t *layout = nbgl_layoutGet(&layoutDescription);
@@ -63,11 +63,11 @@ For example 2:
@code
nbgl_layoutDescription_t layoutDescription = {
- .modal = false, // not modal (so on plane 0)
- .onActionCallback = &myActionCallback, // generic callback for all controls
- .onTapText = "Tap to continue", // A "tapable" main container is necessary, with this text
- .onTapToken = TAP_TOKEN, // token to be used when main container is touched
- .ticker.callback = NULL // no ticker
+ .modal = false, // not modal (so on plane 0)
+ .onActionCallback = &myActionCallback, // generic callback for all controls
+ .onTapText = "Tap to continue", // A "tapable" main container is necessary, with this text
+ .onTapToken = TAP_TOKEN, // token to be used when main container is touched
+ .ticker.callback = NULL // no ticker
};
/* create layout */
nbgl_layout_t *layout = nbgl_layoutGet(&layoutDescription);
@@ -665,7 +665,7 @@ void app_keyboard(void) {
Some rare screens require displaying a keypad at the bottom of the page, to enter digits.
-The digits to enter are usually a PIN code, so enter or to confirm.
+The digits to enter are usually a PIN code, to enter or to confirm.
Here are some example of these pages in Bolos UX:
diff --git a/lib_nbgl/doc/nbgl_layout_nanos.dox b/lib_nbgl/doc/nbgl_layout_nanos.dox
index 7b8b22c46..64531bca2 100644
--- a/lib_nbgl/doc/nbgl_layout_nanos.dox
+++ b/lib_nbgl/doc/nbgl_layout_nanos.dox
@@ -40,9 +40,9 @@ For example:
@code
nbgl_layoutDescription_t layoutDescription = {
- .modal = false, // not modal (so on plane 0)
- .onActionCallback = &myActionCallback, // generic callback for all controls
- .ticker.callback = NULL // no ticker
+ .modal = false, // not modal (so on plane 0)
+ .onActionCallback = &myActionCallback, // generic callback for all controls
+ .ticker.callback = NULL // no ticker
};
// create layout
nbgl_layout_t *layout = nbgl_layoutGet(&layoutDescription);
@@ -187,22 +187,22 @@ static void keyboardCallback(char touchedKey) {
void app_keyboard(void) {
nbgl_layoutDescription_t layoutDescription = {
- .modal = false,
+ .modal = false,
.onActionCallback = NULL
};
nbgl_layoutKbd_t kbdInfo = {
- .callback = keyboardCallback,
- .keyMask = 1<<26, // no masked letter but masked backspace
- .lettersOnly = true // only letters are allowed
+ .callback = keyboardCallback,
+ .keyMask = 1<<26, // no masked letter but masked backspace
+ .lettersOnly = true // only letters are allowed
};
nbgl_layoutCenteredInfo_t centeredInfo = {
.text1 = descriptionTxt,
.text2 = NULL,
- .icon = NULL,
+ .icon = NULL,
.onTop = true
};
nbgl_layoutNavigation_t navInfo = {
- .direction = HORIZONTAL_NAV,
+ .direction = HORIZONTAL_NAV,
.indication = LEFT_ARROW | RIGHT_ARROW
};
int status;
@@ -261,19 +261,26 @@ used to modify the active keys of an existing keypad (backspace and validate key
@note at creation time, backspace and validate keys are inactive
-@subsubsection keypad_sub_section_2 Adding/Updating hidden digits area
+@subsubsection keypad_sub_section_2 Adding/Updating keypad content
-This object, displayed on top of the keypad, consists in up to 8 discs (plain or empty) corresponding to
-hidden entered/not entered digits.
+This object consists in:
+- an optional title
+- either:
+ - up to 12 discs (invisible or visible) corresponding to hidden entered/not entered digits.
+ - or up to 12 digits if not hidden
The parameters to configure this object are:
+- The optional title (NULL if not used)
+- A boolean to indicate whether digits are hidden or not
- The number of total digits to be displayed (all digits are considered as "not entered")
+- The digits to be displayed if not hidden
-The API to insert such an object is @ref nbgl_layoutAddHiddenDigits().
+The API to insert such an object is @ref nbgl_layoutAddKeypadContent().
-This function returns a positive integer (if successful) to be used as an index in @ref nbgl_layoutUpdateHiddenDigits() function,
-used to modify the number of entered digits.
+This function returns a positive integer (if successful).
+
+The @ref nbgl_layoutUpdateKeypadContent() function can be used to modify the number of entered digits or the digits.
@section refresh_layout Refreshing screen
@@ -286,7 +293,7 @@ It will only refresh the rectangle part of the screen having changed (with objec
@section callbacks Control actions management
-Some controls, like a touchable bar, or a choice by radio buttons, can be interacted with thanks to the Touchscreen.
+Some controls can be interacted with thanks to the buttons.
The developer can subscribe to these events by providing an action callback in @ref nbgl_layoutGet(), with @ref nbgl_layoutTouchCallback_t prototype.
@@ -294,10 +301,10 @@ The first parameter (*token*) of this function is a token provided along with th
The second parameter (*index*) is only used for some types of complex objects:
-- **Navigation bar**: in this case, *index* gives the index of the new active page, when navigating.
+- **Bar List choices**: in this case, *index* gives the index of the selected bar.
- **Radio button choices**: in this case, *index* gives the index of the selected choice.
-- **Switches**: in this case, if *index* is 0 it means OFF, otherwise it means ON.
-- **Choice buttons**: in this case, if *index* is 0, it means top-button (choice 1), otherwise it means bottom-buttom (choice 2)
+- **Switches**: in this case, *index* is 0: it is up to the Apps to know what to do here.
+- **Info buttons**: in this case, *index* is 0 (useless)
@subsection release Releasing a layout
diff --git a/lib_nbgl/doc/nbgl_page.dox b/lib_nbgl/doc/nbgl_page.dox
index bee008ef8..568bb9c5a 100644
--- a/lib_nbgl/doc/nbgl_page.dox
+++ b/lib_nbgl/doc/nbgl_page.dox
@@ -1,5 +1,5 @@
#ifdef NBGL_PAGE
-/** @page nbgl_page Predefined pages API
+/** @page nbgl_page Predefined Pages API
@section nbgl_page_intro Introduction
This chapter describes briefly the high-level API of Advanced BOLOS Graphic Library.
diff --git a/lib_nbgl/doc/nbgl_step.dox b/lib_nbgl/doc/nbgl_step.dox
index 4b5838878..ebc228391 100644
--- a/lib_nbgl/doc/nbgl_step.dox
+++ b/lib_nbgl/doc/nbgl_step.dox
@@ -1,5 +1,5 @@
#ifdef NBGL_STEP
-/** @page nbgl_step Step API
+/** @page nbgl_step Predefined Step API
@section nbgl_step_intro Introduction
This chapter describes briefly the high-level API of Advanced BOLOS Graphic Library.
diff --git a/lib_nbgl/doc/nbgl_use_case.dox b/lib_nbgl/doc/nbgl_use_case.dox
index 5035fd80a..9fc4e8e5b 100644
--- a/lib_nbgl/doc/nbgl_use_case.dox
+++ b/lib_nbgl/doc/nbgl_use_case.dox
@@ -50,16 +50,19 @@ A few APIs are available to draw typical Use-Cases, such as:
- @ref nbgl_useCaseReview() to draw the pages of a regular coin transaction review, when all info are available from the beginning (see @subpage use_case_review)
- @ref nbgl_useCaseReviewLight() to draw the pages of a transaction review with a simple button confirmation, when all info are available from the beginning (see @subpage use_case_review_light)
- @ref nbgl_useCaseReviewStreamingStart() to draw the pages of a regular coin transaction review, when all info are not available from the beginning (see @subpage use_case_review_streaming)
+- for reviews with a warning prolog:
+ - @ref nbgl_useCaseReviewWithWarning() to draw the pages of a regular coin transaction review, when all info are available from the beginning, but with an identified risk requiring a warning prolog (see @subpage use_case_review_with_warning)
+ - @ref nbgl_useCaseReviewStreamingWithWarningStart() to draw the pages of a regular coin transaction review, when all info are not available from the beginning, but with an identified risk requiring a warning prolog (see @subpage use_case_review_with_warning and @subpage use_case_review_streaming)
- for address verification:
- @ref nbgl_useCaseAddressReview() to draw an address confirmation page, with a possibility to see it as QR Code and some extra tag/value pairs (see @subpage use_case_addr_review)
- for keypad:
- @ref nbgl_useCaseKeypadPIN() to draw a default keypad implementation with hidden digits (see @subpage use_case_keypad)
- @ref nbgl_useCaseKeypadDigits() to draw a default keypad implementation, showing digits (see @subpage use_case_keypad)
+- for generic navigable content:
+ - @ref nbgl_useCaseNavigableContent() to draw a level of generic content navigable pages
Some APIs have also been kept for backward compatibility, and for some rare cases:
-- for Settings:
- - @ref nbgl_useCaseSettings() to draw a level of settings pages
- for most used reviews:
- @ref nbgl_useCaseReviewStart() to draw the cover page of a review (initial page, without data)
- @ref nbgl_useCaseStaticReview() to draw the data pages of a regular review, when all info are available from the beginning (all pages but the cover one)
@@ -179,11 +182,14 @@ Here is the code to display the example picture (and a status page for confirmat
@code
static void confirmationCallback(void) {
// draw a status screen which continues by returning to appMain
- nbgl_useCaseStatus("Transaction rejected",false,appMain);
+ nbgl_useCaseStatus("Transaction rejected", false, appMain);
}
void onRejectTransaction(void) {
- nbgl_useCaseConfirm("Reject transaction?","Yes, Reject","Go back to transaction",confirmationCallback);
+ nbgl_useCaseConfirm("Reject transaction?",
+ "Yes, Reject",
+ "Go back to transaction",
+ confirmationCallback);
}
@endcode
@@ -213,7 +219,8 @@ void onRejectTransaction(void) {
nbgl_useCaseChoice(&C_key_64px,
"Delete all credentials?",
"This includes all saved\ncredentials from all websites\nand services.",
- "Yes, erase","No, don't erase",
+ "Yes, erase",
+ "No, don't erase",
onChoice);
}
@endcode
@@ -226,12 +233,12 @@ A status is a transient page, without control, to display during a short time, f
The @ref nbgl_useCaseStatus() function enables to create such a page, with the following arguments:
- a message string to set in middle of page
-- a boolean to indicate if true, that the message is drawn in a Ledger style (with corners)
-- a quit callback, called when quit timer times out (or the page is "tapped")
+- a boolean to indicate if true, that the message is drawn in a Ledger style (with corners) and select the icon
+- a quit callback, called when timer times out (or the page is "tapped")
-If it's a success status, a "success" tune will be automatically played.
+If it's a success status, a *success* tune will be automatically played.
-@subsection use_case_review_status Pre-defined status Use Case
+@subsection use_case_review_status Pre-defined review status Use Case
\image{inline} html UseCase-Review-Status.png "caption" height=300
@@ -239,9 +246,9 @@ Similar as @subpage use_case_status, this is used to display transient page, wit
The @ref nbgl_useCaseReviewStatus() function enables to create such a page, with the following arguments:
- a type of status (with predefined message)
-- a quit callback, called when quit timer times out (or the page is "tapped")
+- a quit callback, called when timer times out (or the page is "tapped")
-If it's a success status, a "success" tune will be automatically played.
+If it's a success status, a *success* tune will be automatically played.
@subsection use_case_review Review Use Case
@@ -249,13 +256,12 @@ If it's a success status, a "success" tune will be automatically played.
In most cases, the developer may know all tag/value pairs of a transaction when it is submitted.
-In this case, what we call a "static" review can be used. The number of pages is computed automatically and pages can be navigated forward
-and backward.
+Thus, the number of pages is computed automatically and pages can be navigated forward and backward.
-In case of a tag/value pair too long to be fully displayed, the "more" button will be automatically drawn and its handling
+In case of a tag/value pair too long to be fully displayed, the *more* button will be automatically drawn and its handling
automatically performed by NBGL by building a detailed modal view.
-When the user taps on "Reject" in any page, a confirmation page is automatically drawned to let user confirm that he rejects
+When the user taps on *Reject* in any page, a confirmation page is automatically drawned to let user confirm that he rejects
the transaction. In this case, the given callback is called and it's up to app's developer to call @ref nbgl_useCaseReviewStatus(), as in
case of long-press.
@@ -289,15 +295,15 @@ static void onReviewResult(bool confirm) {
}
}
-void staticReview(void) {
- // static review, providing the whole list of pairs
+void startReview(void) {
+ // standard review, providing the whole list of pairs
nbgl_useCaseReview(TYPE_TRANSACTION, // type of operation
- &pairList, // list of tag/value pairs
- coinIcon, // icon of the coin
- "Review transaction\nto send coin", // title of the first page
- NULL, // sub-title of the first page
- "Sign transaction to\nsend coin?", // title of the last page
- onReviewResult); // callback on result of the review
+ &pairList, // list of tag/value pairs
+ coinIcon, // icon of the coin
+ "Review transaction\nto send coin", // title of the first page
+ NULL, // sub-title of the first page
+ "Sign transaction to\nsend coin?", // title of the last page
+ onReviewResult); // callback on result of the review
}
@endcode
@@ -351,15 +357,15 @@ static nbgl_layoutTagValue_t* getPair(uint8_t index) {
return &pair;
}
-void staticReview(void) {
- // static review, providing the whole list of pairs
+void startReview(void) {
+ // standard review, providing the whole list of pairs
nbgl_useCaseReview(TYPE_TRANSACTION, // type of operation
- &pairList, // list of tag/value pairs
- coinIcon, // icon of the coin
- "Review transaction\nto send coin", // title of the first page
- NULL, // sub-title of the first page
- "Sign transaction to\nsend coin?", // title of the last page
- onReviewResult); // callback on result of the review
+ &pairList, // list of tag/value pairs
+ coinIcon, // icon of the coin
+ "Review transaction\nto send coin", // title of the first page
+ NULL, // sub-title of the first page
+ "Sign transaction to\nsend coin?", // title of the last page
+ onReviewResult); // callback on result of the review
}
@endcode
@@ -380,13 +386,13 @@ The API to initiate the display of the series of pages is @ref nbgl_useCaseRevie
In some cases, the application cannot know all tag/value pairs of a transaction when the review is started.
-In this case, what we call a "streaming" review can be used. The pages to display for each "stream are computed automatically and pages can be navigated forward
-and backward (only within a "stream" for backward).
+In this case, what we call a *streaming* review can be used. The pages to display for each *stream* are computed automatically and pages can be navigated forward
+and backward (only within a *stream* for backward).
-In case of a tag/value pair too long to be fully displayed, the "more" button will be automatically drawn and its handling
+In case of a tag/value pair too long to be fully displayed, the *more* button will be automatically drawn and its handling
automatically performed by NBGL by building a detailed modal view.
-When the user taps on "Reject" in any page, a confirmation page is automatically drawned to let user confirm that he rejects
+When the user taps on *Reject* in any page, a confirmation page is automatically drawned to let user confirm that he rejects
the transaction. In this case, the given callback is called and it's up to app's developer to call @ref nbgl_useCaseReviewStatus(), as in
case of long-press.
@@ -394,7 +400,7 @@ The API to initiate the display of the series of pages is @ref nbgl_useCaseRevie
- the type of operation to review (transaction, message or generic operation)
- the texts/icon to use in presentation page
-- a callback with one boolean parameter.
+- a callback with one boolean parameter:
- If this parameter is *false*, it means that the transaction is rejected.
- If this parameter is *true*, it means that NBGL is waiting for new data, sent with @ref nbgl_useCaseReviewStreamingContinue() or @ref nbgl_useCaseReviewStreamingContinueExt()
@@ -402,11 +408,11 @@ As long as there are new tag/value pairs to send, the API to call is either @ref
@ref nbgl_useCaseReviewStreamingContinue(), providing:
- the list of tag/value pairs (or a callback to get them one by one)
-- a callback with one boolean parameter.
+- a callback with one boolean parameter:
- If this parameter is *false*, it means that the transaction is rejected.
- If this parameter is *true*, it means that NBGL is waiting for new data, to be sent with @ref nbgl_useCaseReviewStreamingContinue() or @ref nbgl_useCaseReviewStreamingContinueExt()
-When there is no more data to senf, the API to call is either @ref nbgl_useCaseReviewStreamingFinish(), providing:
+When there is no more data to send, the API to call is @ref nbgl_useCaseReviewStreamingFinish(), providing:
- the title to use for last page (with long-press button)
- a callback called when the long press button on last page or reject confirmation is used. The callback's param is *true* for confirmation, *false* for rejection.
@@ -445,13 +451,178 @@ static void onTransactionContinue(bool askMore)
}
}
-void staticReview(void) {
+void startReview(void) {
// initiate the streaming review
nbgl_useCaseReviewStreamingStart(TYPE_TRANSACTION,
- &C_ic_asset_cardano_64, // icon on first and last page
- "Review transaction", // title of the first page
- NULL, // sub-title of the first page
- onTransactionContinue); // callback to reject or ask more data
+ &C_ic_asset_cardano_64, // icon on first and last page
+ "Review transaction", // title of the first page
+ NULL, // sub-title of the first page
+ onTransactionContinue); // callback to reject or ask more data
+}
+@endcode
+
+@subsection use_case_review_with_warning Review with warning Use Case
+
+\image{inline} html UseCase-Review-With-Warning.png "caption" height=500
+
+The review itself behaves like in @subpage use_case_review. The main differences are:
+
+- The review itself is preceded by a warning page offering the possibility to cancel the review ("Back to safety") or to start it ("Continue anyway")
+- In the first and last pages of the actual review, a top-right button offers the possibility to get more information about the warning
+
+The API to initiate the display of the series of pages is @ref nbgl_useCaseReviewWithWarning(), providing:
+
+- the type of operation to review (transaction, message or generic operation)
+- the list of tag/value pairs (or a callback to get them one by one)
+- the texts/icon to use in presentation page and in last page
+- the configuration to use for the warning (see @ref nbgl_warning_t structure)
+- a callback called when the long press button on last page or reject confirmation is used. The callback's param is *true* for confirmation, *false* for rejection.
+
+@note the recommended configuration for warning is the predefined one. In this case, one just has to fill the *predefinedSet* field of @ref nbgl_warning_t with the appropriate warning
+causes (bitfield) and the *reportProvider* field with the name of the 3rd party providing the Web3Checks report, if necessary.
+
+Here is the code to display something similar to example picture:
+
+@code
+// 4 pairs of tag/value to display
+static nbgl_layoutTagValue_t pairs[4];
+
+static const nbgl_contentTagValueList_t pairList = {
+ .nbMaxLinesForValue = 0,
+ .nbPairs = 4,
+ .pairs = (nbgl_layoutTagValue_t*)pairs
+};
+
+// warning description (cannot be in call stack)
+static nbgl_warning_t warningDesc;
+
+// called when long press button on 3rd page is long-touched or when reject footer is touched
+static void onReviewResult(bool confirm) {
+ // display a status page and go back to main
+ if (confirm) {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, appMain);
+ }
+ else {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_REJECTED, appMain);
+ }
+}
+
+void staticReview(void) {
+ warningDesc.predefinedSet = (1 << W3C_LOSING_SWAP_WARN) | (1 << BLIND_SIGNING_WARN);
+ warningDesc.reportProvider = "Blockaid";
+
+ // static review, providing the whole list of pairs
+ nbgl_useCaseReviewWithWarning(TYPE_TRANSACTION, // type of operation
+ &pairList, // list of tag/value pairs
+ coinIcon, // icon of the coin
+ "Review transaction\nto send coin", // title of the first page
+ NULL, // sub-title of the first page
+ "Sign transaction to\nsend coin?", // title of the last page
+ NULL, // no tip-box in first page of review
+ warningDesc, // description of warning causes
+ onReviewResult); // callback on result of the review
+}
+@endcode
+
+Here is another version of the example code, not using predefined text:
+
+@code
+// 4 pairs of tag/value to display
+static nbgl_layoutTagValue_t pairs[4];
+
+static const nbgl_contentTagValueList_t pairList = {
+ .nbMaxLinesForValue = 0,
+ .nbPairs = 4,
+ .pairs = (nbgl_layoutTagValue_t*)pairs
+};
+
+// icons for first level warning details
+static const nbgl_icon_details_t *barListIcons[] = {&WARNING_ICON, &INFO_I_ICON};
+
+// first level warning details
+static const char *const barListTexts[] = {"Blind signing", "Risk detected"};
+static const char *const barListSubTexts[] = {"This transaction cannot be fully decoded.", "Web3 Checks found a risk:\nLosing swap"};
+// second level warning details in prolog
+static const struct nbgl_warningDetails_s barListIntroDetails[] = {
+ {.title = "Back",
+ .type = QRCODE_WARNING,
+ .qrCode = {.url = "ledger.com/e8", .text1 = "ledger.com/e8", .text2 = "Scan to learn about the risks of blind signing.", .centered = true}},
+ {.title = "Back",
+ .type = QRCODE_WARNING,
+ .qrCode = {.url = "url.com/od24xz", .text1 = "url.com/od24xz", .text2 = "Scan to view the risk report from Blockaid.", .centered = true}}
+};
+
+// second level warning details in review
+static const struct nbgl_warningDetails_s barListIntroDetails[] = {
+ {.title = "Back",
+ .type = CENTERED_INFO_WARNING,
+ .centeredInfo = {.icon = &C_Warning_64px, .title = "Blind signing", .description = "This transaction’s details are not fully verifiable. If you sign it, you could lose all your assets.\n\nLearn about blind signing:
+ledger.com/e8"}},
+ {.title = "Back",
+ .type = CENTERED_INFO_WARNING,
+ .centeredInfo = {.icon = &C_Warning_64px, .title = "Risk detected", .description = "This transaction was scanned as risky by Web3 Checks.\n\nView full Blockaid report:url.com/od24xz"}}
+};
+
+// info in main warning page
+static const nbgl_contentCenter_t warningInfo = {
+ .icon = &C_Warning_64px,
+ .title = "Dangerous transaction",
+ .description = "This transaction cannot be fully decoded, and was marked risky by Web3 Checks."
+};
+
+// first level warning details in prolog
+static const nbgl_warningDetails_t warningIntroDetails = {
+ .title = "Security report",
+ .type = BAR_LIST_WARNING,
+ .barList.nbBars = 2,
+ .barList.icons = barListIcons,
+ .texts = barListTexts,
+ .subTexts = barListSubTexts,
+ .barList.details = barListIntroDetails
+};
+
+// first level warning details in review (when pressing top-right from first and last pages)
+static const nbgl_warningDetails_t warningReviewDetails = {
+ .title = "Security report",
+ .type = BAR_LIST_WARNING,
+ .barList.nbBars = 2,
+ .barList.icons = barListIcons,
+ .texts = barListTexts,
+ .subTexts = barListSubTexts,
+ .barList.details = barListReviewDetails
+};
+
+// warning description (cannot be in call stack)
+static const nbgl_warning_t warningDesc = {
+ .info = &warningInfo,
+ .introTopRightIcon = &PRIVACY_ICON,
+ .reviewTopRightIcon = &WARNING_ICON,
+ .introDetails = &warningIntroDetails,
+ .reviewDetails = &warningReviewDetails
+};
+
+// called when long press button on 3rd page is long-touched or when reject footer is touched
+static void onReviewResult(bool confirm) {
+ // display a status page and go back to main
+ if (confirm) {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, appMain);
+ }
+ else {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_REJECTED, appMain);
+ }
+}
+
+void staticReview(void) {
+ // static review, providing the whole list of pairs
+ nbgl_useCaseReviewWithWarning(TYPE_TRANSACTION, // type of operation
+ &pairList, // list of tag/value pairs
+ coinIcon, // icon of the coin
+ "Review transaction\nto send coin", // title of the first page
+ NULL, // sub-title of the first page
+ "Sign transaction to\nsend coin?", // title of the last page
+ NULL, // no tip-box in first page of review
+ warningDesc, // description of warning causes
+ onReviewResult); // callback on result of the review
}
@endcode
@@ -485,10 +656,10 @@ static const nbgl_contentTagValueList_t pairList = {
// called when either confirm button or reject token is called
static void displayAddressCallback(bool confirm) {
if (confirm) {
- nbgl_useCaseStatus("ADDRESS\nVERIFIED",true,app_fullEthereum);
+ nbgl_useCaseReviewStatus(STATUS_TYPE_ADDRESS_VERIFIED, app_fullEthereum);
}
else {
- nbgl_useCaseStatus("Address rejected",false,app_fullEthereum);
+ nbgl_useCaseReviewStatus(STATUS_TYPE_ADDRESS_REJECTED, app_fullEthereum);
}
}
@@ -497,7 +668,8 @@ void app_ethereumVerifyAddress(void) {
&pairList
myAppIcon,
"Verify MyCoin\naddress",
- NULL,"Cancel",
+ NULL,
+ "Cancel",
appMain);
}
@endcode
@@ -506,12 +678,9 @@ void app_ethereumVerifyAddress(void) {
\image{inline} html UseCase-Spinner.png "caption" height=300
-When an address needs to be confirmed, it can be displayed in a Address Confirmation Use Case, at first as simple page with
-the raw address (as text) and a black button/Footer pair to choose to confirm or reject the address.
+This Use Case is simply to display a static *waiting page*
-An extra button under the raw address enables to open a modal page to see the address as a QR code.
-
-The @ref nbgl_useCaseSpinner() function enables to create such a page, with the following parameters:
+The @ref nbgl_useCaseSpinner() function enables to create such a page, without any parameters.
@subsection use_case_keypad Keypad Use Case
@@ -559,13 +728,13 @@ static void pinentry_cb(int token, uint8_t index) {
void ui_menu_pinentry_display(unsigned int value) {
// Draw the keypad
nbgl_useCaseKeypadPIN("Enter User PIN",
- 6,
- 12,
- TOKEN_PIN_ENTRY_BACK,
- false,
- TUNE_TAP_CASUAL,
- validate_pin,
- pinentry_cb);
+ 6,
+ 12,
+ TOKEN_PIN_ENTRY_BACK,
+ false,
+ TUNE_TAP_CASUAL,
+ validate_pin,
+ pinentry_cb);
}
@endcode
@@ -574,6 +743,5 @@ void ui_menu_pinentry_display(unsigned int value) {
After having drawn graphic objects in **framebuffer**, all functions of this API automatically refresh the screen.
So no need to call @ref nbgl_refresh().
-
*/
#endif // HAVE_SE_TOUCH
diff --git a/lib_nbgl/doc/nbgl_use_case_nanos.dox b/lib_nbgl/doc/nbgl_use_case_nanos.dox
index 88b18a20f..734c0f19c 100644
--- a/lib_nbgl/doc/nbgl_use_case_nanos.dox
+++ b/lib_nbgl/doc/nbgl_use_case_nanos.dox
@@ -15,7 +15,7 @@ A full description of each predefined use-case can be found in this document
@section nbgl_use_case_concepts Concepts
-This layer uses the Step API described in @ref nbgl_step, but offers to developer more than a single step.
+This layer uses the high-level API described in @ref nbgl_step, but offers to developer more than a single step.
The goal is to simplify the usage of NBGL, but also to offer a better homogeneity across applications, by pushing
developers to use common API for common use-cases.
@@ -25,354 +25,548 @@ for end-users, getting more and more familiar with the user experience of applic
@subsection nbgl_use_case_example_1 Example 1: transaction review
-\image{inline} html UseCase-Nano-Review1.png "caption" width=1000
+\image{inline} html UseCase-Nano-Review-ex.png "caption" width=1000
In this example, a transaction review consists in 5 successive pages, and can be seen as a use-case
-@subsection nbgl_use_case_example_2 Example 2: about pages
+@subsection nbgl_use_case_example_2 Example 2: Settings pages
-\image{inline} html UseCase-Nano-About1.png "caption" width=800
+\image{inline} html UseCase-Nano-Settings-ex.png "caption" width=750
-In this example, the settings/about (accessed from "Settings"/"About" in Home, single level) consists in 4 pages, and can be seen as another use-case.
+In this example, the settings (accessed from *Settings* in Home, single level) consists in 3 pages, and can be seen as another use-case.
@section nbgl_use_cases Use Cases
A few APIs are available to draw typical Use-Cases, such as:
-- for Home Screen:
- - @ref nbgl_useCaseHome() to draw the home screen of an application (see @subpage use_case_home)
-- for Settings:
- - @ref nbgl_useCaseSettings() to draw a level of settings pages (see @subpage use_case_settings)
-- for most used reviews:
- - @ref nbgl_useCaseStaticReview() to draw the pages of a regular review, when all info are available from the beginning (see @subpage use_case_static_review)
- - @ref nbgl_useCaseRegularReview() to draw the pages of a regular review (all pages but the cover one) (see @subpage use_case_regular_review)
+- For Home Screen & Settings:
+ - @ref nbgl_useCaseHomeAndSettings() to draw the home page and settings/info pages (see @subpage use_case_home_settings)
+- For Individual pages:
+ - @ref nbgl_useCaseConfirm() to draw a typical confirmation page, for example when rejecting a transaction (see @subpage use_case_confirm)
+ - @ref nbgl_useCaseChoice() to draw a typical dual choice page (see @subpage use_case_choice)
+ - @ref nbgl_useCaseStatus() to draw a transient status page, without control, for example when a transaction is successfully signed (see @subpage use_case_status)
+ - @ref nbgl_useCaseSpinner() to draw an infinite spinner page (see @subpage use_case_spinner)
+- For most used reviews:
+ - @ref nbgl_useCaseReview() to draw the pages of a regular coin transaction review, when all info are available from the beginning (see @subpage use_case_review)
+ - @ref nbgl_useCaseReviewLight() to draw the pages of a transaction review with a simple button confirmation, when all info are available from the beginning (see @subpage use_case_review_light)
+ - @ref nbgl_useCaseReviewStreamingStart() to draw the pages of a regular coin transaction review, when all info are not available from the beginning (see @subpage use_case_review_streaming)
+- For address verification:
+ - @ref nbgl_useCaseAddressReview() to draw an address confirmation page, with a possibility to see some extra tag/value pairs (see @subpage use_case_addr_review)
+- For keypad:
+ - @ref nbgl_useCaseKeypadPIN() to draw a default keypad implementation with hidden digits (see @subpage use_case_keypad)
+ - @ref nbgl_useCaseKeypadDigits() to draw a default keypad implementation, showing digits (see @subpage use_case_keypad)
+- For generic navigable content:
+ - @ref nbgl_useCaseNavigableContent() to draw a level of generic content navigable pages
+
+@subsection use_case_home_settings Home & Settings screen Use Case
+
+\image{inline} html UseCase-Nano-Home.png "caption" width=1000
+
+Ledger would like all application to have the same layout for home screen and settings/info, so the @ref nbgl_useCaseHomeAndSettings() function enables to
+create such a set of page, the configurable parameters being:
+
+- The application name (appName)
+- The application icon (appIcon)
+- The tagline, a text under app name (if NULL, it will be " is ready")
+- The callback when touching *quit application* button
+- The settings pages description
+- The info pages description
-@subsection use_case_home Home screen Use Case
+@code
+extern const nbgl_icon_details_t *app_icon;
-\image{inline} html UseCase-Nano-Home.png "caption" width=800
+enum {
+ SWITCH1_TOKEN = FIRST_USER_TOKEN,
+ SWITCH2_TOKEN
+};
-Ledger would like all application to have the same home screen, so the @ref nbgl_useCaseHome() function enables to
-create such a page, the only configurable parameters being:
+static const nbgl_layoutSwitch_t switches[] = {
+ {.initState = false,
+ .text = "Dummy 1",
+ .subText = "Allow dummy 1\nin transactions",
+ .token = SWITCH1_TOKEN},
+ {.initState = true,
+ .text = "Dummy 2",
+ .subText = "Allow dummy 2\nin transactions",
+ .token = SWITCH2_TOKEN}
+};
+static const char *infoTypes[] = {"Version", "Developer"};
+static const char *infoContents[] = {"1.1.0", "Ledger"};
+
+// function called in case of action on switches
+static void controlsCallback(int token, uint8_t index, int page) {
+ if (token == SWITCH1_TOKEN) {
+ if (index == 0) {
+ // deactivate something related with Dummy1
+ }
+ else {
+ // activate something related with Dummy1
+ }
+ }
+ else if (token == SWITCH2_TOKEN) {
+ if (index == 0) {
+ // deactivate something related with Dummy2
+ }
+ else {
+ // activate something related with Dummy2
+ }
+ }
+}
-- the application name
-- the application icon
-- the application version
-- the tagline, which is the text of the first page (if NULL, it will be the .")
-- the callbacks when touching *quit* or *about* buttons
-- the type of *about* button (about or settings)
+static const nbgl_content_t contentsList = {
+ .content.switchesList.nbSwitches = 2,
+ .content.switchesList.switches = switches,
+ .type = SWITCHES_LIST,
+ .contentActionCallback = controlsCallback
+};
-@code
-extern const nbgl_icon_details_t *myAppIcon;
+static const nbgl_genericContents_t settingContents = {
+ .contentsList = &contentsList,
+ .nbContents = 1
+};
+static const nbgl_contentInfoList_t infosList = {
+ .nbInfos = 2,
+ .infoTypes = infoTypes,
+ .infoContents = infoContents
+};
-void onAbout(void) {
- // draw settings page here
-}
void onQuit(void) {
// exit app here
}
void appMain(void) {
- nbgl_useCaseHome("MyApp",
- &myAppIcon,
- "1.0", // App version
- NULL, // no tagline
- false, // with settings button
- onAbout,
- onQuit);
+ nbgl_useCaseHomeAndSettings("NBGL Tests",
+ app_icon,
+ NULL, // use default tag line
+ INIT_HOME_PAGE, // start at home page
+ &esettingContents, // description of settings
+ &infosList, // description of app info
+ NULL, // no action button on home screen
+ onQuit); // when quitting
}
@endcode
+@subsubsection use_case_home_settings_with_action Home & Settings screen with action button Use Case
-@subsection use_case_settings Settings Use Case
-
-\image{inline} html UseCase-Nano-About1.png "caption" width=800
-
-Usually settings (or information) of an application consists in a list of pages, each page containing either:
+\image{inline} html UseCase-Nano-Settings-Action.png "caption" width=1000
-- a static information (version, developer)
-- a changeable settings (boolean, list of values)
+For some rare applications, one may need an action button in the Home screen, to perform either:
-The API to initiate the display of the series of pages is @ref nbgl_useCaseSettings(), providing:
+- The main action of the Application
+- Or an side-action, as to display an address
-- the page in which to start
-- the number of pages
-- a callback called when the "Back" page is double-pressed
-- a navigation callback called when left or right buttons are pressed (and also to fill the initial page), to retrieve
-the content of the page
-- a last callback called when a page is selected (double-pressed)
+The \b action argument of @ref nbgl_useCaseHomeAndSettings() can be used for that. This structure (@ref nbgl_homeAction_t)
+enables to specify:
-Here is the source code to display the example of settings:
+- A text
+- An optional icon
+- A function to be called when the buttons are pressed
-@code
-typedef struct {
- const char *text; ///< main text for the switch
- nbgl_state_t initState; ///< initial state of the switch
-} Switch_t;
-
-/**********************
- * STATIC VARIABLES
- **********************/
-static Switch_t switches[] = {
- {
- .initState = false,
- .text = "Blind signing",
- },
- {
- .initState = true,
- .text = "Debug",
- },
- {
- .initState = true,
- .text = "Nonce",
- },
-};
+@subsection use_case_confirm Confirmation Use Case
-static bool navCallback(uint8_t page, nbgl_pageContent_t *content)
-{
- if (page < 3) {
- content->text = switches[page].text;
- content->subText = switches[page].initState ? "Enabled" : "Disabled";
- }
- else {
- return false;
- }
- return true;
-}
+\image{inline} html UseCase-Nano-Confirm.png "caption" width=750
-static void actionCallback(uint8_t page) {
- switches[page].initState = !switches[page].initState;
- nbgl_useCaseSettings(page, 3, app_fullEthereum, navCallback, actionCallback);
-}
+A confirmation use-case consists in 3 modal pages sequence:
-void myAppSettings(void) {
- // draw the settings Use Case (3 pages), starting at page 0
- nbgl_useCaseSettings(0, // init page
- 3, // number of pages
- appMain, // "Back" callback
- navCallback, // navigation callback, to get content of pages
- actionCallback // action callback, when a page is selected
- );
-}
-@endcode
+- 1st page containing a fixed icon, a configurable message
+- 2nd page to confirm (pressing both buttons)
+- 3rd page to reject (pressing both buttons)
+The @ref nbgl_useCaseConfirm() function enables to create such a page.
-@subsection use_case_regular_review Regular Review Use Case
+The *callback* argument is called upon confirmation.
-\image{inline} html UseCase-Nano-Review1.png "caption" width=1000
+When rejected, this modal screen is simply dismissed, revealing the previous page on background.
-Usually transaction/message reviews consist in a sequence of pages, each page containing:
+Here is the code to display the example picture (and a status page for confirmation)
-- one tag/value pair page (sometimes with a multi-pages value)
-- two selectable pages at the end, to accept or reject the transaction
+@code
+static void confirmationCallback(void) {
+ // draw a status screen which continues by returning to appMain
+}
-To navigate between pages, right and left buttons can be pressed. And the last two pages can be selected with a double-press.
+void onRejectTransaction(void) {
+ nbgl_useCaseConfirm("Do you confirm\nThis message?",
+ "Confirm",
+ "Cancel",
+ confirmationCallback);
+}
+@endcode
-The API to initiate the display of the series of pages is @ref nbgl_useCaseRegularReview(), providing:
+@subsection use_case_choice Choice Use Case
-- the page in which to start (usually 0)
-- the number of pages (if unknown, set to 0)
-- a navigation callback called when right or left button is pressed, to retrieve
-the content of the previous/next page. It is also called to fill the initial page.
+\image{inline} html UseCase-Nano-Choice.png "caption" width=800
-Here is the code to display something similar to the example picture:
+A choice use-case consists in a 4-page sequence:
-@code
-// 2 pairs of tag/value to display
-static nbgl_layoutTagValue_t pairs[2] = {
- {
- .item = "Address",
- .value = "0xEAEAEAEAEAEAEAEAE"
- },
- {
- .item = "Amount",
- .value = "BOL 0.666"
- }
-};
+- 1st page containing a configurable icon and a configurable title
+- 2nd page containing a configurable message (text and subText)
+- 3rd page to confirm, with a configurable message (pressing both buttons)
+- 4th page to reject, with a configurable message (preesing both buttons)
-static void onApprove(void) {
- // confirm transaction
+The @ref nbgl_useCaseChoice() function enables to create such a page.
- // and go back to main
- appMain();
-}
+The *callback* argument is called when a choice is done. Its argument is a boolean which is *true* upon confirmation, else *false*.
-static void onReject(void) {
- // reject transaction
+Here is the code to display the example picture
- // and go back to main
- appMain();
-}
-
-// called to get the content of the given page
-static bool navCallback(uint8_t page, nbgl_pageContent_t *content) {
- memset(content, 0, sizeof(nbgl_pageContent_t));
- if (page == 0) {
- // the first page is used to display the title of the review
- content->text = "Review\ntransaction";
- content->icon = &C_icon_eye;
- }
- else if (page < 3) {
- // the 2 next pages contain the two pairs of tag/values
- content->text = pairs[page - 1].item;
- content->subText = pairs[page - 1].value;
- }
- else if (page == 3) {
- // this page is for approval
- content->text = "Approve";
- content->icon = &C_icon_validate;
- content->callback = onApprove;
- }
- else if (page == 4) {
- // this page is for rejection
- content->text = "Reject";
- content->icon = &C_icon_crossmark;
- content->callback = onReject;
+@code
+static void onChoice(bool confirm) {
+ if (confirm) {
+ // do something
}
else {
- return false;
+ // do something
}
- // valid page so return true
- return true;
}
-void startReview(void) {
- // review on 3 pages, starting at 0
- nbgl_useCaseRegularReview(0, // start at first page
- 5, // number of pages
- navCallback);
+void onRejectTransaction(void) {
+ nbgl_useCaseChoice(&C_icon_warning,
+ "Dummy 2",
+ "Are you sure to\nallow dummy 2\nin transactions?",
+ "I understand, confirm",
+ "Cancel",
+ onChoice);
}
@endcode
-@subsection use_case_static_review Static Review Use Case
+@subsection use_case_status Status Use Case
+
+\image{inline} html UseCase-Nano-Status.png "caption" width=600
+
+A status is a transient page, without control, to display during a short time, for example when a transaction is successfully signed.
+The @ref nbgl_useCaseStatus() function enables to create such a page, with the following arguments:
+
+- A message string to set in middle of page
+- A boolean to indicate if true (to determine the icon)
+- A quit callback, called when timer times out (or both buttons are pressed)
-\image{inline} html UseCase-Nano-Review1.png "caption" width=1000
+@subsection use_case_review_status Pre-defined review status Use Case
-In some cases, the developer may know all tag/value pairs of a transaction when it is submitted.
-And he may want to use a more standardized transaction review Use Case, with less interaction.
+\image{inline} html UseCase-Nano-Review-Status.png "caption" width=600
-In this case, what we call a "static" review can be used. It is similar to a regular review with known number of pages in terms of
-presentation and interactions with end-users, but much easier to use for developer.
+Similar as @subpage use_case_status, this is used to display transient page, without control, during a short time, for example when a transaction is successfully signed.
+The @ref nbgl_useCaseReviewStatus() function enables to create such a page, with the following arguments:
-Indeed, in this case, NBGL automatically computes the number of pages, and the pairs to draw in each page.
-In case of a tag/value pair too long to be fully displayed, it is split in a multi-pages steps.
+- A type of status (with predefined message and icon)
+- A quit callback, called when timer times out (or both buttons are pressed)
-The API to initiate the display of the series of pages is @ref nbgl_useCaseStaticReview(), providing:
+@subsection use_case_review Review Use Case
-- the list of tag/value pairs (or a callback to get them one by one)
-- the text to use in first page
-- an action callback called when a page's content is selectable (double-pressed)
-- a callback called when the "accept" (with param equal to \b true) or "reject" (with param equal to \b false) last two pages is selected
+\image{inline} html UseCase-Nano-Review.png "caption" height=300
+
+In most cases, the developer may know all tag/value pairs of a transaction when it is submitted.
+
+Thus, the number of pages is computed automatically and pages can be navigated forward and backward.
+
+@note In case of a tag/value pair too long to be fully displayed, the value is automatically split on successive pages, adding an indication `(n/m)` in the name.
+
+At the end of the review flow, the user has 2 pages to *Approve* or *Reject* the transaction.
+Finally, the given callback is called and it's up to app's developer to call @ref nbgl_useCaseReviewStatus().
+
+The API to initiate the display of the series of pages is @ref nbgl_useCaseReview(), providing:
+
+- The type of operation to review (*transaction*, *message* or generic *operation*)
+- The title and icon for the presentation page
+- The list of tag/value pairs (or a callback to get them one by one)
+- A callback called when the review is *Approved* or *Rejected*. The callback's param is *true* for approval, *false* for rejection.
Here is the code to display something similar to example picture:
@code
-// 2 pairs of tag/value to display
-static nbgl_layoutTagValue_t pairs[2] = {
- {
- .item = "Address",
- .value = "0xEAEAEAEAEAEAEAEAE"
- },
- {
- .item = "Amount",
- .value = "BOL 0.666"
- }
-};
+extern const nbgl_icon_details_t *app_icon;
+
+// pairs of tag/value to display
+static nbgl_layoutTagValue_t pairs[2];
-static nbgl_contentTagValueList_t pairList = {
- .nbMaxLinesForValue = 0,
+static const nbgl_contentTagValueList_t pairList = {
.nbPairs = 2,
.pairs = (nbgl_layoutTagValue_t*)pairs
};
-// called when "Approve" or "Reject" page is selected (double-press)
-static void reviewChoice(bool confirm) {
+// called when review is approved or rejected
+static void review_choice(bool confirm) {
+ // display a status page and go back to main
if (confirm) {
- // confirm transaction
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, appMain);
}
else {
- // reject transaction
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_REJECTED, appMain);
}
- // go back to main
- appMain();
}
-void startStaticReview(void) {
- // static review, providing the whole list of pairs
- nbgl_useCaseStaticReview(&pairList,
- &C_icon_eye,
- "Review\ntransaction",
- "Approve",
- "Reject",
- reviewChoice);
+void startReview(void) {
+ // Setup data to display
+ pairs[0].item = "Amount";
+ pairs[0].value = "NBT 0.99";
+ pairs[1].item = "Address";
+ pairs[1].value = "0x1234567890";
+
+ // standard review, providing the whole list of pairs
+ nbgl_useCaseReview(TYPE_TRANSACTION, // type of operation
+ &pairList, // list of tag/value pairs
+ app_icon, // icon of the coin
+ "Review transaction\nto send NBT", // title of the first page
+ NULL, // sub-title of the first page
+ NULL, // title of the last page
+ review_choice); // callback on result of the review
}
@endcode
Here is another version of the example code, using a callback mechanism to get tag/value pairs:
@code
+extern const nbgl_icon_details_t *app_icon;
+
// common tag/value pair to return
static nbgl_layoutTagValue_t pair;
static nbgl_layoutTagValue_t* getPair(uint8_t index);
-static nbgl_contentTagValueList_t pairList = {
- .nbMaxLinesForValue = 0,
- .nbPairs = 5,
+static const nbgl_contentTagValueList_t pairList = {
+ .nbPairs = 2,
.pairs = NULL, // to indicate that callback should be used
.callback = getPair,
- .startIndex = 0
};
-// called when "Approve" or "Reject" page is selected (double-press)
-static void reviewChoice(bool confirm) {
+// called when review is approved or rejected
+static void review_choice(bool confirm) {
+ // display a status page and go back to main
if (confirm) {
- // confirm transaction
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, appMain);
}
else {
- // reject transaction
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_REJECTED, appMain);
}
- // go back to main
- appMain();
}
// function called by NBGL to get the pair indexed by "index"
static nbgl_layoutTagValue_t* getPair(uint8_t index) {
switch (index) {
case 0:
- pair.item = "To";
- pair.value = "0x123456";
- break;
- case 1:
- pair.item = "From";
- pair.value = "0x654321";
- break;
- case 2:
pair.item = "Amount";
- pair.value = "1.2345 BTC";
+ pair.value = "NBT 0.99";
break;
- case 3:
- pair.item = "Fees";
- pair.value = "0.1 BTC";
+ case 1:
+ pair.item = "Address";
+ pair.value = "0x1234567890";
break;
}
return &pair;
}
-void startStaticReview(void) {
- // static review, providing the description of pairs
- nbgl_useCaseStaticReview(&pairList,
- &C_icon_eye,
- "Review\ntransaction",
- "Approve",
- "Reject",
- reviewChoice);
+void startReview(void) {
+ // standard review, providing the whole list of pairs
+ nbgl_useCaseReview(TYPE_TRANSACTION, // type of operation
+ &pairList, // list of tag/value pairs
+ app_icon, // icon of the coin
+ "Review transaction\nto send NBT", // title of the first page
+ NULL, // sub-title of the first page
+ NULL, // title of the last page
+ review_choice); // callback on result of the review
+}
+@endcode
+
+@subsection use_case_review_light Light review Use Case
+
+Not really useful on Nano devices, and thus, directly mapped to @subpage use_case_review.
+
+@subsection use_case_review_streaming Streaming review Use Case
+
+\image{inline} html UseCase-Nano-Streaming.png "caption" width=1000
+
+In some cases, the application cannot know all tag/value pairs of a transaction when the review is started.
+
+In this case, what we call a *streaming* review can be used. The pages to display for each *stream* are computed automatically
+and pages can be navigated forward and backward.
+
+In case of a tag/value pair too long to be fully displayed, the value is automatically split on
+successive pages, adding an indication `(n/m)` in the name.
+
+At the end of the review flow, the user has 2 pages to *Approve* or *Reject* the transaction.
+Finally, the given callback is called and it's up to app's developer to call @ref nbgl_useCaseReviewStatus().
+
+The API to initiate the display of the series of pages is @ref nbgl_useCaseReviewStreamingStart(), providing:
+
+- The type of operation to review (transaction, message or generic operation)
+- The title and icon for the presentation page
+- A callback with one boolean parameter:
+ - If this parameter is *false*, it means that the transaction is rejected.
+ - If this parameter is *true*, it means that NBGL is waiting for new data, sent with @ref nbgl_useCaseReviewStreamingContinue()
+
+As long as there are new tag/value pairs to send, the API to call is @ref nbgl_useCaseReviewStreamingContinue(), providing:
+
+- The list of tag/value pairs (or a callback to get them one by one)
+- A callback with one boolean parameter:
+ - If this parameter is *false*, it means that the transaction is rejected.
+ - If this parameter is *true*, it means that NBGL is waiting for new data, to be sent with @ref nbgl_useCaseReviewStreamingContinue()
+
+@note Considering *skip* is not available on Nano device, @ref nbgl_useCaseReviewStreamingContinueExt() is useless and directly mapped to @ref nbgl_useCaseReviewStreamingContinue()
+
+When there is no more data to send, the API to call is @ref nbgl_useCaseReviewStreamingFinish(), providing:
+
+- A callback called when the review is *Approved* or *Rejected*. The callback's param is *true* for approval, *false* for rejection.
+
+Here is the code to display something similar to example picture:
+
+@code
+extern const nbgl_icon_details_t *app_icon;
+
+// called when long press button on last page is long-touched or when reject footer is touched
+static void onReviewResult(bool confirm) {
+ // display a status page and go back to main
+ if (confirm) {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_SIGNED, appMain);
+ }
+ else {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_TRANSACTION_REJECTED, appMain);
+ }
+}
+
+static void onTransactionContinue(bool askMore)
+{
+ if (askMore) {
+ nbgl_contentTagValueList_t pairsToSend;
+ // try to get more data
+ if (moreDataToSend(&pairsToSend)) {
+ nbgl_useCaseReviewStreamingContinue(&pairsToSend, onTransactionContinue);
+ }
+ else {
+ // all data sent, so finish
+ nbgl_useCaseReviewStreamingFinish("Sign transaction?", // title on last page
+ onReviewResult); // callback to handle reject/accept
+ }
+ }
+ else {
+ onReviewResult(false);
+ }
+}
+
+void startReview(void) {
+ // initiate the streaming review
+ nbgl_useCaseReviewStreamingStart(TYPE_TRANSACTION,
+ app_icon, // icon on first page
+ "Review transaction\nto send NBT", // title of the first page
+ NULL, // sub-title
+ onTransactionContinue); // callback to reject or ask more data
+}
+@endcode
+
+@subsection use_case_addr_review Address Review Use Case
+
+\image{inline} html UseCase-Nano-AddressReview.png "caption" width=1500
+
+When an address needs to be confirmed, it can be displayed in a Address Review Use Case.
+
+After a title page, a second page is displayed with the raw address (as text).
+
+Moreover, if extra information need to be displayed, in the form of tags/values, they are show in successive pages.
+
+@note In case of a tag/value pair too long to be fully displayed, the value is automatically split on successive pages, adding an indication `(n/m)` in the name.
+
+
+At the end of the review flow, the user has 2 pages to *Approve* or *Reject* the review.
+Finally, the given callback is called and it's up to app's developer to call @ref nbgl_useCaseReviewStatus().
+
+The @ref nbgl_useCaseAddressReview() function enables to create such a set of pages, with the following parameters:
+
+- The address to confirm (NULL terminated string)
+- A callback called when the review is *Approved* or *Rejected*
+- The optional list of extra tag/value pairs
+
+Here is the code to display something similar to example picture:
+
+@code
+// 2 pairs of tag/value to display in second page
+static nbgl_layoutTagValue_t pairs[2];
+
+static const nbgl_contentTagValueList_t pairList = {
+ .nbPairs = 2,
+ .pairs = (nbgl_layoutTagValue_t*)pairs
+};
+
+// called when either confirm button or reject token is called
+static void review_choice(bool confirm) {
+ if (confirm) {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_ADDRESS_VERIFIED, ui_main);
+ }
+ else {
+ nbgl_useCaseReviewStatus(STATUS_TYPE_ADDRESS_REJECTED, ui_main);
+ }
+}
+
+void app_ethereumVerifyAddress(void) {
+ // Setup data to display
+ pairs[0].item = "Type address";
+ pairs[0].value = "dummy type";
+ pairs[1].item = "Sub address";
+ pairs[1].value = "dummy sub address";
+
+ // Setup list
+ pairList.nbMaxLinesForValue = 0;
+ pairList.nbPairs = 2;
+ pairList.pairs = pairs;
+
+ nbgl_useCaseAddressReview(
+ "5A8FgbMkmG2e3J41sBdjvjaBUyz8qHohsQcGtRf63qEUTMBvmA45fpp5pSacMdSg7A3b71RejLzB8EkGbfjp5PELVHCRUaE",
+ &pairList,
+ &ICON_APP,
+ "Verify NBT address",
+ NULL,
+ review_choice);
}
@endcode
@subsection use_case_spinner Spinner Use Case
-This Use Case is simply to display a static "waiting page"
+\image{inline} html UseCase-Nano-Spinner.png "caption" width=300
+
+This Use Case is simply to display a static *waiting page*
-The @ref nbgl_useCaseSpinner() function enables to create such a page.
+The @ref nbgl_useCaseSpinner() function enables to create such a page, without any parameters.
+
+@subsection use_case_keypad Keypad Use Case
+
+\image{inline} html UseCase-Nano-Keypad.png "caption" height=350
+
+We have here 2 different variants, allowing to \b show or \b hide the entered digits.
+
+When a pincode is requested, a default keypad can be displayed, with hidden digits.
+
+The @ref nbgl_useCaseKeypadPIN() function enables to create such page, with the following parameters:
+
+- A title
+- The min and max lengths
+- A boolean to request a *shuffled* keypad
+- Callbacks for navigation and pin validation
+
+The other variant, where digits don't need to be hidden is @ref nbgl_useCaseKeypadDigits();
+it takes the same parameters.
+
+@note The \em backspace and \em validate buttons will be shown or hidden automatically.
+@note The \em backspace button is also used as a *cancel* button, when no digits are selected.
+@note The max nb of supported digits is \b 12.
+
+Here is the code to display something similar to example picture:
+
+@code
+static void validate_pin(const uint8_t *pinentry, uint8_t length) {
+ // Code to validate the entered pin code
+}
+
+static void quit_cb() {
+ // Back to the main app menu
+}
+
+void ui_menu_pinentry_display(unsigned int value) {
+ // Draw the keypad
+ nbgl_useCaseKeypadPIN("PIN Keypad (5555)",
+ 4,
+ 4,
+ false,
+ validate_pin,
+ quit_cb);
+}
+@endcode
@section use_case_refresh_page Refreshing screen
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-About1.png b/lib_nbgl/doc/resources/UseCase-Nano-About1.png
deleted file mode 100755
index 86ffb0442..000000000
Binary files a/lib_nbgl/doc/resources/UseCase-Nano-About1.png and /dev/null differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-AddressReview.png b/lib_nbgl/doc/resources/UseCase-Nano-AddressReview.png
new file mode 100644
index 000000000..5726cb4c3
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-AddressReview.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Choice.png b/lib_nbgl/doc/resources/UseCase-Nano-Choice.png
new file mode 100644
index 000000000..7f1d32e98
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Choice.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Confirm.png b/lib_nbgl/doc/resources/UseCase-Nano-Confirm.png
new file mode 100644
index 000000000..332c13ac3
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Confirm.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Home.png b/lib_nbgl/doc/resources/UseCase-Nano-Home.png
old mode 100755
new mode 100644
index 981cbd26b..227a9d80a
Binary files a/lib_nbgl/doc/resources/UseCase-Nano-Home.png and b/lib_nbgl/doc/resources/UseCase-Nano-Home.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Keypad.png b/lib_nbgl/doc/resources/UseCase-Nano-Keypad.png
new file mode 100644
index 000000000..2ba4bdd63
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Keypad.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Review-Status.png b/lib_nbgl/doc/resources/UseCase-Nano-Review-Status.png
new file mode 100644
index 000000000..462cf63e7
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Review-Status.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Review-ex.png b/lib_nbgl/doc/resources/UseCase-Nano-Review-ex.png
new file mode 100644
index 000000000..09aa91f55
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Review-ex.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Review.png b/lib_nbgl/doc/resources/UseCase-Nano-Review.png
new file mode 100644
index 000000000..b8d86bdcb
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Review.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Review1.png b/lib_nbgl/doc/resources/UseCase-Nano-Review1.png
deleted file mode 100755
index f0afae8ac..000000000
Binary files a/lib_nbgl/doc/resources/UseCase-Nano-Review1.png and /dev/null differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Settings-Action.png b/lib_nbgl/doc/resources/UseCase-Nano-Settings-Action.png
new file mode 100644
index 000000000..b96ff40f8
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Settings-Action.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Settings-ex.png b/lib_nbgl/doc/resources/UseCase-Nano-Settings-ex.png
new file mode 100644
index 000000000..06b5b6f10
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Settings-ex.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Spinner.png b/lib_nbgl/doc/resources/UseCase-Nano-Spinner.png
new file mode 100644
index 000000000..85826dccd
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Spinner.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Status.png b/lib_nbgl/doc/resources/UseCase-Nano-Status.png
new file mode 100644
index 000000000..e2fc0ce6c
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Status.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Nano-Streaming.png b/lib_nbgl/doc/resources/UseCase-Nano-Streaming.png
new file mode 100644
index 000000000..b6df3b5fe
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Nano-Streaming.png differ
diff --git a/lib_nbgl/doc/resources/UseCase-Review-With-Warning.png b/lib_nbgl/doc/resources/UseCase-Review-With-Warning.png
new file mode 100755
index 000000000..7829ed476
Binary files /dev/null and b/lib_nbgl/doc/resources/UseCase-Review-With-Warning.png differ
diff --git a/lib_nbgl/glyphs/32px/Exclamation_32px.png b/lib_nbgl/glyphs/32px/Exclamation_32px.png
new file mode 100755
index 000000000..1eee85008
Binary files /dev/null and b/lib_nbgl/glyphs/32px/Exclamation_32px.png differ
diff --git a/lib_nbgl/glyphs/32px/Privacy_32px.png b/lib_nbgl/glyphs/32px/Privacy_32px.png
new file mode 100755
index 000000000..700f60a21
Binary files /dev/null and b/lib_nbgl/glyphs/32px/Privacy_32px.png differ
diff --git a/lib_nbgl/glyphs/40px/Exclamation_40px.png b/lib_nbgl/glyphs/40px/Exclamation_40px.png
new file mode 100755
index 000000000..37f8b56a1
Binary files /dev/null and b/lib_nbgl/glyphs/40px/Exclamation_40px.png differ
diff --git a/lib_nbgl/glyphs/40px/Important_Circle_40px.png b/lib_nbgl/glyphs/40px/Important_Circle_40px.png
new file mode 100755
index 000000000..5037b98d5
Binary files /dev/null and b/lib_nbgl/glyphs/40px/Important_Circle_40px.png differ
diff --git a/lib_nbgl/glyphs/40px/Privacy_40px.png b/lib_nbgl/glyphs/40px/Privacy_40px.png
new file mode 100755
index 000000000..1664a2088
Binary files /dev/null and b/lib_nbgl/glyphs/40px/Privacy_40px.png differ
diff --git a/lib_nbgl/include/nbgl_content.h b/lib_nbgl/include/nbgl_content.h
index 5dcbb47d2..47ad63ca9 100644
--- a/lib_nbgl/include/nbgl_content.h
+++ b/lib_nbgl/include/nbgl_content.h
@@ -123,8 +123,10 @@ typedef struct {
*
*/
typedef enum {
- ENS_ALIAS = 0, ///< alias comes from ENS
- ADDRESS_BOOK_ALIAS ///< alias comes from Address Book
+ NO_ALIAS_TYPE = 0,
+ ENS_ALIAS, ///< alias comes from ENS
+ ADDRESS_BOOK_ALIAS, ///< alias comes from Address Book
+ QR_CODE_ALIAS ///< alias is an address to be displayed as a QR Code
} nbgl_contentValueAliasType_t;
/**
@@ -134,7 +136,9 @@ typedef enum {
typedef struct {
const char *fullValue; ///< full string of the value when used as an alias
const char *explanation; ///< string displayed in gray, explaing where the alias comes from
- ///< if NULL, a default explanation is provided, depending of the type
+ ///< only used if aliasType is @ref NO_ALIAS_TYPE
+ const char *title; ///< if not NULL and aliasType is @ref QR_CODE_ALIAS, is used as title of
+ ///< the QR Code
nbgl_contentValueAliasType_t aliasType; ///< type of alias
} nbgl_contentValueExt_t;
@@ -264,7 +268,14 @@ typedef struct nbgl_pageSwitchesList_s {
typedef struct {
const char *const *infoTypes; ///< array of types of infos (in black/bold)
const char *const *infoContents; ///< array of contents of infos (in black)
- uint8_t nbInfos; ///< number of elements in infoTypes and infoContents array
+ const nbgl_contentValueExt_t
+ *infoExtensions; ///< if not NULL, gives additional info on type field
+ ///< any {0} element of this array is considered as invalid
+ uint8_t nbInfos; ///< number of elements in infoTypes and infoContents array
+ uint8_t token; ///< token to use with extensions, if withExtensions is true and infoExtensions
+ ///< is not NULL
+ bool withExtensions; /// if set to TRUE and if infoExtensions is not NULL, use this
+ /// infoExtensions field
} nbgl_contentInfoList_t;
/**
diff --git a/lib_nbgl/include/nbgl_flow.h b/lib_nbgl/include/nbgl_flow.h
index 142e3b2bd..3143d7846 100644
--- a/lib_nbgl/include/nbgl_flow.h
+++ b/lib_nbgl/include/nbgl_flow.h
@@ -51,7 +51,22 @@ typedef struct nbgl_stepDesc_s {
#endif // HAVE_LANGUAGE_PACK
} nbgl_stepDesc_t;
-typedef nbgl_stepDesc_t nbgl_pageContent_t;
+/**
+ * @brief This structure contains data to build a page in multi-pages mode (@ref
+ * nbgl_pageDrawGenericContent)
+ */
+typedef struct nbgl_pageContent_s {
+ nbgl_contentType_t type; ///< type of page content in the following union
+ union {
+ nbgl_contentCenteredInfo_t centeredInfo; ///< @ref CENTERED_INFO type
+ nbgl_contentInfoButton_t infoButton; ///< @ref INFO_BUTTON type
+ nbgl_contentTagValueList_t tagValueList; ///< @ref TAG_VALUE_LIST type
+ nbgl_contentSwitchesList_t switchesList; ///< @ref SWITCHES_LIST type
+ nbgl_contentInfoList_t infosList; ///< @ref INFOS_LIST type
+ nbgl_contentRadioChoice_t choicesList; ///< @ref CHOICES_LIST type
+ nbgl_contentBarsList_t barsList; ///< @ref BARS_LIST type
+ };
+} nbgl_pageContent_t;
/**********************
* GLOBAL PROTOTYPES
diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h
index 0fd7dca6d..3a18e93b3 100644
--- a/lib_nbgl/include/nbgl_layout.h
+++ b/lib_nbgl/include/nbgl_layout.h
@@ -593,6 +593,11 @@ int nbgl_layoutAddTopRightButton(nbgl_layout_t *layout,
int nbgl_layoutAddTouchableBar(nbgl_layout_t *layout, const nbgl_layoutBar_t *barLayout);
int nbgl_layoutAddSwitch(nbgl_layout_t *layout, const nbgl_layoutSwitch_t *switchLayout);
int nbgl_layoutAddText(nbgl_layout_t *layout, const char *text, const char *subText);
+int nbgl_layoutAddTextWithAlias(nbgl_layout_t *layout,
+ const char *text,
+ const char *subText,
+ uint8_t token,
+ uint8_t index);
int nbgl_layoutAddSubHeaderText(nbgl_layout_t *layout, const char *text);
int nbgl_layoutAddRadioChoice(nbgl_layout_t *layout, const nbgl_layoutRadioChoice_t *choices);
int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info);
@@ -738,6 +743,14 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout,
bool enableBackspace);
int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits);
int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t nbActive);
+int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout,
+ bool hidden,
+ uint8_t nbDigits,
+ const char *text);
+int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout,
+ bool hidden,
+ uint8_t nbActiveDigits,
+ const char *text);
#endif // HAVE_SE_TOUCH
#endif // NBGL_KEYPAD
diff --git a/lib_nbgl/include/nbgl_obj.h b/lib_nbgl/include/nbgl_obj.h
index 75f0c516b..d4d730795 100644
--- a/lib_nbgl/include/nbgl_obj.h
+++ b/lib_nbgl/include/nbgl_obj.h
@@ -56,11 +56,11 @@ extern "C" {
#else
#define KEYPAD_KEY_HEIGHT 104
#endif
-#define KEYPAD_MAX_DIGITS 12
#else // HAVE_SE_TOUCH
#define KEYPAD_WIDTH 114
#define KEYPAD_HEIGHT 18
#endif // HAVE_SE_TOUCH
+#define KEYPAD_MAX_DIGITS 12
#ifdef HAVE_SE_TOUCH
// external margin in pixels
@@ -127,6 +127,9 @@ extern "C" {
#define QRCODE_ICON C_QRCode_32px
#define MINI_PUSH_ICON C_Mini_Push_32px
#define WARNING_ICON C_Warning_32px
+#define ROUND_WARN_ICON C_Important_Circle_32px
+#define PRIVACY_ICON C_Privacy_32px
+#define EXCLAMATION_ICON C_Exclamation_32px
#else // TARGET_STAX
#define SPACE_ICON C_Space_40px
#define BACKSPACE_ICON C_Erase_40px
@@ -146,6 +149,9 @@ extern "C" {
#define QRCODE_ICON C_QRCode_40px
#define MINI_PUSH_ICON C_Mini_Push_40px
#define WARNING_ICON C_Warning_40px
+#define ROUND_WARN_ICON C_Important_Circle_40px
+#define PRIVACY_ICON C_Privacy_40px
+#define EXCLAMATION_ICON C_Exclamation_40px
#endif // TARGET_STAX
// For backward compatibility, to be remove later
@@ -195,7 +201,7 @@ typedef enum {
* @brief prototype of function to be called when a button event is received by an object (TODO:
* change to screen?)
* @param obj the concerned object
- * @param buttonState event on buttons
+ * @param buttonEvent event on buttons
*/
typedef void (*nbgl_buttonCallback_t)(void *obj, nbgl_buttonEvent_t buttonEvent);
diff --git a/lib_nbgl/include/nbgl_step.h b/lib_nbgl/include/nbgl_step.h
index 38fdd27a0..10a50e2e7 100644
--- a/lib_nbgl/include/nbgl_step.h
+++ b/lib_nbgl/include/nbgl_step.h
@@ -54,6 +54,7 @@ typedef void (*nbgl_stepMenuListCallback_t)(uint8_t choiceIndex);
/**
* @brief prototype of function to be called when buttons are touched on a screen
+ * @param stepCtx context returned by the nbgl_stepDrawXXX function
* @param event type of button event
*/
typedef void (*nbgl_stepButtonCallback_t)(nbgl_step_t stepCtx, nbgl_buttonEvent_t event);
diff --git a/lib_nbgl/include/nbgl_types.h b/lib_nbgl/include/nbgl_types.h
index c994d0632..d39f30123 100644
--- a/lib_nbgl/include/nbgl_types.h
+++ b/lib_nbgl/include/nbgl_types.h
@@ -309,8 +309,6 @@ typedef enum {
* Same as POST_REFRESH_FORCE_POWER_ON, but with pipeline enabled.
* When using pipeline, refreshes are faster, but some constraints must be respected: successive
* draws & refreshes areas must not overlap.
- * If overlapping is needed, the function @ref nbgl_driver_waitPipeline() must be called, in order
- * to wait for pipelined operations to finish.
*/
typedef enum nbgl_post_refresh_t {
POST_REFRESH_FORCE_POWER_OFF, ///< Force screen power off after refresh
diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h
index 3d56a73f1..136209aa5 100644
--- a/lib_nbgl/include/nbgl_use_case.h
+++ b/lib_nbgl/include/nbgl_use_case.h
@@ -130,10 +130,10 @@ typedef void (*nbgl_actionCallback_t)(uint8_t page);
/**
* @brief prototype of pin validation callback function
- * @param content pin value
- * @param length pin length
+ * @param pin pin value
+ * @param pinLen pin length
*/
-typedef void (*nbgl_pinValidCallback_t)(const uint8_t *content, uint8_t page);
+typedef void (*nbgl_pinValidCallback_t)(const uint8_t *pin, uint8_t pinLen);
/**
* @brief prototype of content navigation callback function
@@ -184,10 +184,91 @@ typedef struct {
const char *modalTitle; ///< title given to modal window displayed when tip-box is touched
nbgl_contentType_t type; ///< type of page content in the following union
union {
- const nbgl_contentInfoList_t infos; ///< infos pairs displayed in modal.
+ nbgl_contentInfoList_t infos; ///< infos pairs displayed in modal.
};
} nbgl_tipBox_t;
+/**
+ * @brief The different types of warning page contents
+ *
+ */
+typedef enum {
+ CENTERED_INFO_WARNING = 0, ///< Centered info
+ QRCODE_WARNING, ///< QR Code
+ BAR_LIST_WARNING ///< list of touchable bars, to display sub-pages
+} nbgl_warningDetailsType_t;
+
+/**
+ * @brief The necessary parameters to build a list of touchable bars, to display sub-pages
+ *
+ */
+typedef struct {
+ uint8_t nbBars; ///< number of touchable bars
+ const char *const *texts; ///< array of texts for each bar (nbBars items, in black/bold)
+ const char *const *subTexts; ///< array of texts for each bar (nbBars items, in black)
+ const nbgl_icon_details_t **icons; ///< array of icons for each bar (nbBars items)
+ const struct nbgl_warningDetails_s
+ *details; ///< array of nbBars structures giving what to display when each bar is touched.
+} nbgl_warningBarList_t;
+
+/**
+ * @brief The necessary parameters to build the page(s) displayed when the top-right button is
+ * touched in intro page (before review)
+ *
+ */
+typedef struct nbgl_warningDetails_s {
+ const char *title; ///< text of the page (used to go back)
+ nbgl_warningDetailsType_t
+ type; ///< type of content in the page, determining what to use in the following union
+ union {
+ nbgl_contentCenter_t
+ centeredInfo; ///< centered info, if type == @ref CENTERED_INFO_WARNING
+#ifdef NBGL_QRCODE
+ nbgl_layoutQRCode_t qrCode; ///< QR code, if type == @ref QRCODE_WARNING
+#endif // NBGL_QRCODE
+ nbgl_warningBarList_t barList; ///< touchable bars list, if type == @ref BAR_LIST_WARNING
+ };
+} nbgl_warningDetails_t;
+
+/**
+ * @brief The different types of pre-defined warnings
+ *
+ */
+typedef enum {
+ BLIND_SIGNING_WARN = 0, ///< Blind signing
+ W3C_ISSUE_WARN, ///< Web3 Checks issue
+ W3C_LOSING_SWAP_WARN, ///< Web3 Checks: Losing Swap risk
+ W3C_THREAT_DETECTED_WARN, ///< Web3 Checks: Thread detexted, malicious (know drainer)
+ NB_WARNING_TYPES
+} nbgl_warningType_t;
+
+/**
+ * @brief The necessary parameters to build a warning page preceding a review.
+ * One can either use `predefinedSet` when the warnings are already supported in @ref
+ * nbgl_warningType_t list, or use `introDetails` or `reviewDetails` to configure manually the
+ * warning pages
+ * @note it's also used to build the modal pages displayed when touching the top-right button in the
+ * review's first page
+ *
+ */
+typedef struct {
+ uint32_t predefinedSet; ///< bitfield of pre-defined warnings, to be taken in @ref
+ ///< nbgl_warningType_t set it to 0 if not using pre-defined warnings
+ const char *reportProvider; ///< name of the security report provider, used in some strings
+ const nbgl_warningDetails_t
+ *introDetails; ///< details displayed when top-right button is touched in intro page
+ ///< (before review) if using pre-defined configuration, set to NULL,
+ const nbgl_warningDetails_t
+ *reviewDetails; ///< details displayed when top-right button is touched in first/last page
+ ///< of review if using pre-defined configuration, set to NULL
+ const nbgl_contentCenter_t
+ *info; ///< parameters to build the intro warning page, if not using pre-defined
+ const nbgl_icon_details_t
+ *introTopRightIcon; ///< icon to use in the intro warning page, if not using pre-defined
+ const nbgl_icon_details_t *reviewTopRightIcon; ///< icon to use in the first/last page of
+ ///< review, if not using pre-defined
+} nbgl_warning_t;
+
/**
* @brief The different types of operation to review
*
@@ -265,7 +346,15 @@ void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationT
const char *finishTitle,
const nbgl_tipBox_t *tipBox,
nbgl_choiceCallback_t choiceCallback);
-
+void nbgl_useCaseReviewWithWarning(nbgl_operationType_t operationType,
+ const nbgl_contentTagValueList_t *tagValueList,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const char *finishTitle,
+ const nbgl_tipBox_t *tipBox,
+ const nbgl_warning_t *warning,
+ nbgl_choiceCallback_t choiceCallback);
void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType,
const nbgl_contentTagValueList_t *tagValueList,
const nbgl_icon_details_t *icon,
@@ -305,6 +394,13 @@ void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t ope
const char *reviewSubTitle,
nbgl_choiceCallback_t choiceCallback);
+void nbgl_useCaseReviewStreamingWithWarningStart(nbgl_operationType_t operationType,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const nbgl_warning_t *warning,
+ nbgl_choiceCallback_t choiceCallback);
+
void nbgl_useCaseReviewStreamingContinueExt(const nbgl_contentTagValueList_t *tagValueList,
nbgl_choiceCallback_t choiceCallback,
nbgl_callback_t skipCallback);
@@ -324,6 +420,12 @@ void nbgl_useCaseGenericConfiguration(const char *title,
const nbgl_genericContents_t *contents,
nbgl_callback_t quitCallback);
+void nbgl_useCaseGenericSettings(const char *appName,
+ uint8_t initPage,
+ const nbgl_genericContents_t *settingContents,
+ const nbgl_contentInfoList_t *infosList,
+ nbgl_callback_t quitCallback);
+
void nbgl_useCaseChoice(const nbgl_icon_details_t *icon,
const char *message,
const char *subMessage,
@@ -333,9 +435,20 @@ void nbgl_useCaseChoice(const nbgl_icon_details_t *icon,
void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback);
+void nbgl_useCaseConfirm(const char *message,
+ const char *subMessage,
+ const char *confirmText,
+ const char *rejectText,
+ nbgl_callback_t callback);
void nbgl_useCaseSpinner(const char *text);
-#ifdef HAVE_SE_TOUCH
+void nbgl_useCaseNavigableContent(const char *title,
+ uint8_t initPage,
+ uint8_t nbPages,
+ nbgl_callback_t quitCallback,
+ nbgl_navCallback_t navCallback,
+ nbgl_layoutTouchCallback_t controlsCallback);
+
// utils
uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
const nbgl_contentTagValueList_t *tagValueList,
@@ -364,62 +477,56 @@ uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoice
bool withNav);
uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *tagValueList);
+#ifdef HAVE_SE_TOUCH
// use case drawing
-void nbgl_useCaseHome(const char *appName,
- const nbgl_icon_details_t *appIcon,
- const char *tagline,
- bool withSettings,
- nbgl_callback_t topRightCallback,
- nbgl_callback_t quitCallback);
-void nbgl_useCaseHomeExt(const char *appName,
- const nbgl_icon_details_t *appIcon,
- const char *tagline,
- bool withSettings,
- const char *actionButtonText,
- nbgl_callback_t actionCallback,
- nbgl_callback_t topRightCallback,
- nbgl_callback_t quitCallback);
-void nbgl_useCaseSettings(const char *settingsTitle,
- uint8_t initPage,
- uint8_t nbPages,
- bool touchableTitle,
- nbgl_callback_t quitCallback,
- nbgl_navCallback_t navCallback,
- nbgl_layoutTouchCallback_t controlsCallback);
-void nbgl_useCaseGenericSettings(const char *appName,
- uint8_t initPage,
- const nbgl_genericContents_t *settingContents,
- const nbgl_contentInfoList_t *infosList,
- nbgl_callback_t quitCallback);
-void nbgl_useCaseConfirm(const char *message,
- const char *subMessage,
- const char *confirmText,
- const char *rejectText,
- nbgl_callback_t callback);
-void nbgl_useCaseReviewStart(const nbgl_icon_details_t *icon,
- const char *reviewTitle,
- const char *reviewSubTitle,
- const char *rejectText,
- nbgl_callback_t continueCallback,
- nbgl_callback_t rejectCallback);
-void nbgl_useCaseRegularReview(uint8_t initPage,
- uint8_t nbPages,
- const char *rejectText,
- nbgl_layoutTouchCallback_t buttonCallback,
- nbgl_navCallback_t navCallback,
- nbgl_choiceCallback_t choiceCallback);
-void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList,
- const nbgl_pageInfoLongPress_t *infoLongPress,
- const char *rejectText,
- nbgl_choiceCallback_t callback);
-void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList,
- const nbgl_pageInfoLongPress_t *infoLongPress,
- const char *rejectText,
- nbgl_choiceCallback_t callback);
-void nbgl_useCaseAddressConfirmation(const char *address, nbgl_choiceCallback_t callback);
-void nbgl_useCaseAddressConfirmationExt(const char *address,
- nbgl_choiceCallback_t callback,
- const nbgl_contentTagValueList_t *tagValueList);
+DEPRECATED void nbgl_useCaseHome(const char *appName,
+ const nbgl_icon_details_t *appIcon,
+ const char *tagline,
+ bool withSettings,
+ nbgl_callback_t topRightCallback,
+ nbgl_callback_t quitCallback);
+DEPRECATED void nbgl_useCaseHomeExt(const char *appName,
+ const nbgl_icon_details_t *appIcon,
+ const char *tagline,
+ bool withSettings,
+ const char *actionButtonText,
+ nbgl_callback_t actionCallback,
+ nbgl_callback_t topRightCallback,
+ nbgl_callback_t quitCallback);
+DEPRECATED void nbgl_useCaseSettings(const char *settingsTitle,
+ uint8_t initPage,
+ uint8_t nbPages,
+ bool touchableTitle,
+ nbgl_callback_t quitCallback,
+ nbgl_navCallback_t navCallback,
+ nbgl_layoutTouchCallback_t controlsCallback);
+void nbgl_useCaseReviewStart(const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const char *rejectText,
+ nbgl_callback_t continueCallback,
+ nbgl_callback_t rejectCallback);
+void nbgl_useCaseRegularReview(uint8_t initPage,
+ uint8_t nbPages,
+ const char *rejectText,
+ nbgl_layoutTouchCallback_t buttonCallback,
+ nbgl_navCallback_t navCallback,
+ nbgl_choiceCallback_t choiceCallback);
+void nbgl_useCaseStaticReview(const nbgl_contentTagValueList_t *tagValueList,
+ const nbgl_pageInfoLongPress_t *infoLongPress,
+ const char *rejectText,
+ nbgl_choiceCallback_t callback);
+void nbgl_useCaseStaticReviewLight(const nbgl_contentTagValueList_t *tagValueList,
+ const nbgl_pageInfoLongPress_t *infoLongPress,
+ const char *rejectText,
+ nbgl_choiceCallback_t callback);
+
+DEPRECATED void nbgl_useCaseAddressConfirmationExt(const char *address,
+ nbgl_choiceCallback_t callback,
+ const nbgl_contentTagValueList_t *tagValueList);
+#define nbgl_useCaseAddressConfirmation(__address, __callback) \
+ nbgl_useCaseAddressConfirmationExt(__address, __callback, NULL)
+
#ifdef NBGL_KEYPAD
void nbgl_useCaseKeypadDigits(const char *title,
uint8_t minDigits,
@@ -438,6 +545,22 @@ void nbgl_useCaseKeypadPIN(const char *title,
nbgl_pinValidCallback_t validatePinCallback,
nbgl_layoutTouchCallback_t actionCallback);
#endif // NBGL_KEYPAD
+
+#else // HAVE_SE_TOUCH
+#ifdef NBGL_KEYPAD
+void nbgl_useCaseKeypadDigits(const char *title,
+ uint8_t minDigits,
+ uint8_t maxDigits,
+ bool shuffled,
+ nbgl_pinValidCallback_t validatePinCallback,
+ nbgl_callback_t backCallbackk);
+void nbgl_useCaseKeypadPIN(const char *title,
+ uint8_t minDigits,
+ uint8_t maxDigits,
+ bool shuffled,
+ nbgl_pinValidCallback_t validatePinCallback,
+ nbgl_callback_t backCallback);
+#endif // NBGL_KEYPAD
#endif // HAVE_SE_TOUCH
#ifdef __cplusplus
diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c
index 179716f86..6d0907d0d 100644
--- a/lib_nbgl/src/nbgl_layout.c
+++ b/lib_nbgl/src/nbgl_layout.c
@@ -878,6 +878,110 @@ static nbgl_container_t *addContentCenter(nbgl_layoutInternal_t *layoutInt,
return container;
}
+static int addText(nbgl_layout_t *layout,
+ const char *text,
+ const char *subText,
+ uint8_t token,
+ uint8_t index,
+ bool withAlias)
+{
+ nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout;
+ nbgl_container_t *container;
+ nbgl_text_area_t *textArea;
+ nbgl_text_area_t *subTextArea;
+ uint16_t fullHeight = 0;
+
+ if (layout == NULL) {
+ return -1;
+ }
+ container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer);
+
+ // get container children
+ container->children = nbgl_containerPoolGet(withAlias ? 3 : 2, layoutInt->layer);
+ container->obj.area.width = AVAILABLE_WIDTH;
+
+ if (text != NULL) {
+ textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer);
+
+ textArea->textColor = BLACK;
+ textArea->text = PIC(text);
+ textArea->textAlignment = MID_LEFT;
+ textArea->fontId = SMALL_BOLD_FONT;
+ textArea->style = NO_STYLE;
+ textArea->wrapping = true;
+ textArea->obj.alignment = NO_ALIGNMENT;
+ textArea->obj.alignmentMarginY = PRE_TEXT_MARGIN;
+ textArea->obj.area.width = container->obj.area.width;
+ if (withAlias == true) {
+ textArea->obj.area.width -= 12 + MINI_PUSH_ICON.width;
+ }
+ textArea->obj.area.height = nbgl_getTextHeightInWidth(
+ textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping);
+ fullHeight += textArea->obj.area.height + textArea->obj.alignmentMarginY;
+ container->children[container->nbChildren] = (nbgl_obj_t *) textArea;
+ container->nbChildren++;
+ if (withAlias == true) {
+ nbgl_image_t *image = (nbgl_image_t *) nbgl_objPoolGet(IMAGE, layoutInt->layer);
+ // the whole container is touchable
+ layoutObj_t *obj
+ = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) container, token, TUNE_TAP_CASUAL);
+ obj->index = index;
+ image->foregroundColor = BLACK;
+ image->buffer = &PUSH_ICON;
+ image->obj.alignment = RIGHT_TOP;
+ image->obj.alignmentMarginX = 12;
+ image->obj.alignTo = (nbgl_obj_t *) textArea;
+ container->obj.touchMask = (1 << TOUCHED);
+ container->obj.touchId = VALUE_BUTTON_1_ID + index;
+
+ container->children[container->nbChildren] = (nbgl_obj_t *) image;
+ container->nbChildren++;
+ }
+ }
+ if (subText != NULL) {
+ subTextArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer);
+ subTextArea->textColor = BLACK;
+ subTextArea->text = PIC(subText);
+ subTextArea->fontId = SMALL_REGULAR_FONT;
+ subTextArea->style = NO_STYLE;
+ subTextArea->wrapping = true;
+ subTextArea->obj.area.width = container->obj.area.width;
+ subTextArea->obj.area.height = nbgl_getTextHeightInWidth(subTextArea->fontId,
+ subTextArea->text,
+ subTextArea->obj.area.width,
+ subTextArea->wrapping);
+ subTextArea->textAlignment = MID_LEFT;
+ subTextArea->obj.alignment = NO_ALIGNMENT;
+ if (text != NULL) {
+ subTextArea->obj.alignmentMarginY = TEXT_SUBTEXT_MARGIN;
+ fullHeight += POST_SUBTEXT_MARGIN; // under the subText
+ }
+ else {
+#ifdef TARGET_STAX
+ subTextArea->obj.alignmentMarginY = BORDER_MARGIN;
+ fullHeight += BORDER_MARGIN;
+#else // TARGET_STAX
+ subTextArea->obj.alignmentMarginY = 26;
+ fullHeight += 26; // under the subText
+#endif // TARGET_STAX
+ }
+ container->children[container->nbChildren] = (nbgl_obj_t *) subTextArea;
+ container->nbChildren++;
+ fullHeight += subTextArea->obj.area.height + subTextArea->obj.alignmentMarginY;
+ }
+ else {
+ fullHeight += PRE_TEXT_MARGIN;
+ }
+ container->obj.area.height = fullHeight;
+ container->layout = VERTICAL;
+ container->obj.alignmentMarginX = BORDER_MARGIN;
+ container->obj.alignment = NO_ALIGNMENT;
+ // set this new obj as child of main container
+ layoutAddObject(layoutInt, (nbgl_obj_t *) container);
+
+ return container->obj.area.height;
+}
+
/**********************
* GLOBAL FUNCTIONS
**********************/
@@ -1187,7 +1291,7 @@ int nbgl_layoutAddSwitch(nbgl_layout_t *layout, const nbgl_layoutSwitch_t *switc
}
/**
- * @brief Creates an area with given text and sub text (in gray)
+ * @brief Creates an area with given text (in bold) and sub text (in regular)
*
* @param layout the current layout
* @param text main text (in small bold font), optional
@@ -1196,82 +1300,29 @@ int nbgl_layoutAddSwitch(nbgl_layout_t *layout, const nbgl_layoutSwitch_t *switc
*/
int nbgl_layoutAddText(nbgl_layout_t *layout, const char *text, const char *subText)
{
- nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout;
- nbgl_container_t *container;
- nbgl_text_area_t *textArea;
- nbgl_text_area_t *subTextArea;
- uint16_t fullHeight = 0;
-
LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddText():\n");
- if (layout == NULL) {
- return -1;
- }
- container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer);
-
- // get container children
- container->children = nbgl_containerPoolGet(2, layoutInt->layer);
- container->obj.area.width = AVAILABLE_WIDTH;
-
- if (text != NULL) {
- textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer);
-
- textArea->textColor = BLACK;
- textArea->text = PIC(text);
- textArea->textAlignment = MID_LEFT;
- textArea->fontId = SMALL_BOLD_FONT;
- textArea->style = NO_STYLE;
- textArea->wrapping = true;
- textArea->obj.alignment = NO_ALIGNMENT;
- textArea->obj.alignmentMarginY = PRE_TEXT_MARGIN;
- textArea->obj.area.width = container->obj.area.width;
- textArea->obj.area.height = nbgl_getTextHeightInWidth(
- textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping);
- fullHeight += textArea->obj.area.height + textArea->obj.alignmentMarginY;
- container->children[container->nbChildren] = (nbgl_obj_t *) textArea;
- container->nbChildren++;
- }
- if (subText != NULL) {
- subTextArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer);
- subTextArea->textColor = BLACK;
- subTextArea->text = PIC(subText);
- subTextArea->fontId = SMALL_REGULAR_FONT;
- subTextArea->style = NO_STYLE;
- subTextArea->wrapping = true;
- subTextArea->obj.area.width = container->obj.area.width;
- subTextArea->obj.area.height = nbgl_getTextHeightInWidth(subTextArea->fontId,
- subTextArea->text,
- subTextArea->obj.area.width,
- subTextArea->wrapping);
- subTextArea->textAlignment = MID_LEFT;
- subTextArea->obj.alignment = NO_ALIGNMENT;
- if (text != NULL) {
- subTextArea->obj.alignmentMarginY = TEXT_SUBTEXT_MARGIN;
- fullHeight += POST_SUBTEXT_MARGIN; // under the subText
- }
- else {
-#ifdef TARGET_STAX
- subTextArea->obj.alignmentMarginY = BORDER_MARGIN;
- fullHeight += BORDER_MARGIN;
-#else // TARGET_STAX
- subTextArea->obj.alignmentMarginY = 26;
- fullHeight += 26; // under the subText
-#endif // TARGET_STAX
- }
- container->children[container->nbChildren] = (nbgl_obj_t *) subTextArea;
- container->nbChildren++;
- fullHeight += subTextArea->obj.area.height + subTextArea->obj.alignmentMarginY;
- }
- else {
- fullHeight += PRE_TEXT_MARGIN;
- }
- container->obj.area.height = fullHeight;
- container->layout = VERTICAL;
- container->obj.alignmentMarginX = BORDER_MARGIN;
- container->obj.alignment = NO_ALIGNMENT;
- // set this new obj as child of main container
- layoutAddObject(layoutInt, (nbgl_obj_t *) container);
+ return addText(layout, text, subText, 0, 0, false);
+}
- return container->obj.area.height;
+/**
+ * @brief Creates an area with given text (in bold) and sub text (in regular), with a
+ * > icon on right of text to activate an action when touched, with the given token
+ *
+ * @param layout the current layout
+ * @param text main text (in small bold font), optional
+ * @param subText description under main text (in small regular font), optional
+ * @param token token to use in callback when > icon is touched
+ * @param index index to use in callback when > icon is touched
+ * @return height of the control if OK
+ */
+int nbgl_layoutAddTextWithAlias(nbgl_layout_t *layout,
+ const char *text,
+ const char *subText,
+ uint8_t token,
+ uint8_t index)
+{
+ LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddTextWithAlias():\n");
+ return addText(layout, text, subText, token, index, true);
}
/**
diff --git a/lib_nbgl/src/nbgl_layout_keypad_nanos.c b/lib_nbgl/src/nbgl_layout_keypad_nanos.c
index 877a00b3f..24b29e8b0 100644
--- a/lib_nbgl/src/nbgl_layout_keypad_nanos.c
+++ b/lib_nbgl/src/nbgl_layout_keypad_nanos.c
@@ -53,7 +53,6 @@
* @param layout the current layout
* @param callback function called when any of the key is touched
* @param text text to use as title for the keypad
- * @param textId ID of the text to use as title for the keypad
* @param shuffled if set to true, digits are shuffled in keypad
* @return the index of keypad in layout, to use in @ref nbgl_layoutUpdateKeypad()
*/
@@ -136,8 +135,8 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout,
// if validate key is enabled and was not, select it directly
keypad->selectedKey = 11;
}
- else {
- // otherwise let the draw function pick a new selected
+ // Shuffle if selected key was not backspace or if the last pin entry has been deleted
+ else if ((keypad->selectedKey != 0) || (keypad->enableBackspace && !enableBackspace)) {
keypad->selectedKey = 0xFF;
}
keypad->enableValidate = enableValidate;
@@ -160,109 +159,188 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout,
* @return the index of digits set, to use in @ref nbgl_layoutUpdateHiddenDigits()
*/
int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits)
+{
+ return nbgl_layoutAddKeypadContent(layout, true, nbDigits, NULL);
+}
+
+/**
+ * @brief Updates an existing set of hidden digits, with the given configuration
+ *
+ * @param layout the current layout
+ * @param index index returned by @ref nbgl_layoutAddHiddenDigits()
+ * @param nbActive number of "active" digits (represented by discs instead of circles)
+ * @return >=0 if OK
+ */
+int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t nbActive)
+{
+ UNUSED(index);
+ if (nbgl_layoutUpdateKeypadContent(layout, true, nbActive, NULL) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * @brief Adds an area with a title and a placeholder for hidden digits on top of a keypad, to
+ * represent the entered digits as small discs.
+ *
+ * @note It must be the only object added in the layout, after the keypad itself
+ *
+ * @param layout the current layout
+ * @param hidden if set to true, digits appear as discs, otherwise as visible digits (given in text
+ * param)
+ * @param nbDigits number of digits to be displayed (only used if hidden is true)
+ * @param text only used if hidden is false
+ * @return the height of this area, if no error, < 0 otherwise
+ */
+int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout,
+ bool hidden,
+ uint8_t nbDigits,
+ const char *text)
{
nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout;
nbgl_container_t *container;
+ nbgl_text_area_t *textArea;
- LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddHiddenDigits():\n");
+ LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddKeypadContent():\n");
if (layout == NULL) {
return -1;
}
- // create a container, invisible or bordered
- container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer);
- container->nbChildren = nbDigits;
- container->children = nbgl_containerPoolGet(container->nbChildren, layoutInt->layer);
- // 1 pixel between each icon (knowing that the effective bullets are 8px large)
- container->obj.area.width = nbDigits * C_pin_bullet_empty.width + (nbDigits - 1);
- container->obj.area.height = C_pin_bullet_empty.height;
- // distance from top to digits is fixed to 24 px
- container->obj.alignmentMarginY = 24;
- container->obj.alignTo = NULL;
- container->obj.alignment = TOP_MIDDLE;
-
- // set this new container as child of the main container
- layoutAddObject(layoutInt, (nbgl_obj_t *) container);
-
- // create children of the container, as images (empty circles)
- nbgl_objPoolGetArray(IMAGE, nbDigits, layoutInt->layer, (nbgl_obj_t **) container->children);
- for (int i = 0; i < nbDigits; i++) {
- nbgl_image_t *image = (nbgl_image_t *) container->children[i];
- image->buffer = &C_pin_bullet_empty;
- image->foregroundColor = WHITE;
- if (i > 0) {
- image->obj.alignment = MID_RIGHT;
- image->obj.alignTo = (nbgl_obj_t *) container->children[i - 1];
- image->obj.alignmentMarginX = 1;
- }
- else {
- image->obj.alignment = NO_ALIGNMENT;
+ if (hidden) {
+ // create a container, invisible
+ container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer);
+ container->nbChildren = nbDigits;
+ container->children = nbgl_containerPoolGet(container->nbChildren, layoutInt->layer);
+ // 1 pixel between each icon (knowing that the effective bullets are 8px large)
+ container->obj.area.width = nbDigits * C_pin_bullet_empty.width + (nbDigits - 1);
+ container->obj.area.height = C_pin_bullet_empty.height;
+ // distance from top to digits is fixed to 24 px
+ container->obj.alignmentMarginY = 24;
+ container->obj.alignTo = NULL;
+ container->obj.alignment = TOP_MIDDLE;
+
+ // set this new container as child of the main container
+ layoutAddObject(layoutInt, (nbgl_obj_t *) container);
+
+ // create children of the container, as images (empty circles)
+ nbgl_objPoolGetArray(
+ IMAGE, nbDigits, layoutInt->layer, (nbgl_obj_t **) container->children);
+ for (int i = 0; i < nbDigits; i++) {
+ nbgl_image_t *image = (nbgl_image_t *) container->children[i];
+ image->buffer = &C_pin_bullet_empty;
+ image->foregroundColor = WHITE;
+ if (i > 0) {
+ image->obj.alignment = MID_RIGHT;
+ image->obj.alignTo = (nbgl_obj_t *) container->children[i - 1];
+ image->obj.alignmentMarginX = 1;
+ }
+ else {
+ image->obj.alignment = NO_ALIGNMENT;
+ }
}
}
- // return index of container to be modified later on
- return (layoutInt->nbChildren - 1);
+ else {
+ // create text area
+ textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer);
+ textArea->textColor = WHITE;
+ textArea->text = text;
+ textArea->textAlignment = CENTER;
+ textArea->fontId = BAGL_FONT_OPEN_SANS_EXTRABOLD_11px_1bpp;
+ textArea->obj.area.width = AVAILABLE_WIDTH;
+ textArea->obj.area.height = nbgl_getFontLineHeight(textArea->fontId);
+ textArea->autoHideLongLine = true;
+ // distance from top to digits is fixed to 20 px
+ textArea->obj.alignmentMarginY = 20;
+ textArea->obj.alignTo = NULL;
+ textArea->obj.alignment = TOP_MIDDLE;
+ // set this new text area as child of the main container
+ layoutAddObject(layoutInt, (nbgl_obj_t *) textArea);
+ }
+
+ // return 0
+ return 0;
}
/**
* @brief Updates an existing set of hidden digits, with the given configuration
*
* @param layout the current layout
- * @param index index returned by @ref nbgl_layoutAddHiddenDigits()
- * @param nbActive number of "active" digits (represented by discs instead of circles)
+ * @param hidden if set to true, digits appear as discs, otherwise as visible digits (given in text
+ * param)
+ * @param nbActiveDigits number of "active" digits (represented by discs instead of circles) (only
+ * used if hidden is true)
+ * @param text only used if hidden is false
+ *
* @return >=0 if OK
*/
-int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t nbActive)
+int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout,
+ bool hidden,
+ uint8_t nbActiveDigits,
+ const char *text)
{
nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout;
nbgl_container_t *container;
nbgl_image_t *image;
- LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateHiddenDigits(): nbActive = %d\n", nbActive);
+ LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateHiddenDigits(): nbActive = %d\n", nbActiveDigits);
if (layout == NULL) {
return -1;
}
- // get container
- container = (nbgl_container_t *) layoutInt->children[index];
- // sanity check
- if ((container == NULL) || (container->obj.type != CONTAINER)) {
- return -1;
- }
- if (nbActive > container->nbChildren) {
- return -1;
- }
- if (nbActive == 0) {
- // deactivate the first digit
- image = (nbgl_image_t *) container->children[0];
- if ((image == NULL) || (image->obj.type != IMAGE)) {
+ if (hidden) {
+ // get container (3rd child of main container)
+ container = (nbgl_container_t *) layoutInt->children[2];
+ // sanity check
+ if ((container == NULL) || (container->obj.type != CONTAINER)) {
return -1;
}
- image->buffer = &C_pin_bullet_empty;
- }
- else {
- image = (nbgl_image_t *) container->children[nbActive - 1];
- if ((image == NULL) || (image->obj.type != IMAGE)) {
+ if (nbActiveDigits > container->nbChildren) {
return -1;
}
- // if the last "active" is already active, it means that we are decreasing the number of
- // active otherwise we are increasing it
- if (image->buffer == &C_pin_bullet_filled) {
- // all digits are already active
- if (nbActive == container->nbChildren) {
- return 0;
+ if (nbActiveDigits == 0) {
+ // deactivate the first digit
+ image = (nbgl_image_t *) container->children[0];
+ if ((image == NULL) || (image->obj.type != IMAGE)) {
+ return -1;
}
- // deactivate the next digit
- image = (nbgl_image_t *) container->children[nbActive];
image->buffer = &C_pin_bullet_empty;
}
else {
- image->buffer = &C_pin_bullet_filled;
+ image = (nbgl_image_t *) container->children[nbActiveDigits - 1];
+ if ((image == NULL) || (image->obj.type != IMAGE)) {
+ return -1;
+ }
+ // if the last "active" is already active, it means that we are decreasing the number of
+ // active otherwise we are increasing it
+ if (image->buffer == &C_pin_bullet_filled) {
+ // all digits are already active
+ if (nbActiveDigits == container->nbChildren) {
+ return 0;
+ }
+ // deactivate the next digit
+ image = (nbgl_image_t *) container->children[nbActiveDigits];
+ image->buffer = &C_pin_bullet_empty;
+ }
+ else {
+ image->buffer = &C_pin_bullet_filled;
+ }
}
- }
- nbgl_objDraw((nbgl_obj_t *) image);
+ nbgl_objDraw((nbgl_obj_t *) image);
+ }
+ else {
+ // update main text area (second child of the main container)
+ nbgl_text_area_t *textArea = (nbgl_text_area_t *) layoutInt->children[2];
+ if ((textArea == NULL) || (textArea->obj.type != TEXT_AREA)) {
+ return -1;
+ }
+ textArea->text = text;
+ nbgl_objDraw((nbgl_obj_t *) textArea);
+ }
return 0;
}
+
#endif // NBGL_KEYPAD
#endif // HAVE_SE_TOUCH
diff --git a/lib_nbgl/src/nbgl_obj_keypad_nanos.c b/lib_nbgl/src/nbgl_obj_keypad_nanos.c
index 75dd229d8..20f98414e 100644
--- a/lib_nbgl/src/nbgl_obj_keypad_nanos.c
+++ b/lib_nbgl/src/nbgl_obj_keypad_nanos.c
@@ -165,24 +165,8 @@ static void keypadInitSelected(nbgl_keypad_t *keypad)
keypad->selectedKey = 1 + INIT_DIGIT_VALUE;
}
else {
- uint8_t nbChoices = 10;
- uint8_t random;
- if (keypad->enableBackspace) {
- nbChoices++;
- }
- if (keypad->enableValidate) {
- nbChoices++;
- }
- random = cx_rng_u32_range(0, nbChoices);
- if (random < 10) {
- keypad->selectedKey = 1 + random;
- }
- else if (random == 10) {
- keypad->selectedKey = 0;
- }
- else if (random == 11) {
- keypad->selectedKey = 11;
- }
+ // Exclude backspace and validate keys ([0,11]), shuffle only digits
+ keypad->selectedKey = cx_rng_u32_range(1, 11);
}
}
diff --git a/lib_nbgl/src/nbgl_page.c b/lib_nbgl/src/nbgl_page.c
index 25f0a2a05..f0e037f8b 100644
--- a/lib_nbgl/src/nbgl_page.c
+++ b/lib_nbgl/src/nbgl_page.c
@@ -199,8 +199,22 @@ static void addContent(nbgl_pageContent_t *content,
case INFOS_LIST: {
uint8_t i;
for (i = 0; i < content->infosList.nbInfos; i++) {
- availableHeight -= nbgl_layoutAddText(
- layout, content->infosList.infoTypes[i], content->infosList.infoContents[i]);
+ // if the extension is valid for this index, use a Text with Alias
+ if ((content->infosList.withExtensions == true)
+ && (content->infosList.infoExtensions != NULL)
+ && (content->infosList.infoExtensions[i].fullValue != NULL)) {
+ availableHeight
+ -= nbgl_layoutAddTextWithAlias(layout,
+ content->infosList.infoTypes[i],
+ content->infosList.infoContents[i],
+ content->infosList.token,
+ i);
+ }
+ else {
+ availableHeight -= nbgl_layoutAddText(layout,
+ content->infosList.infoTypes[i],
+ content->infosList.infoContents[i]);
+ }
// do not draw a separation line if too low in the container
if (availableHeight > 10) {
nbgl_layoutAddSeparationLine(layout);
diff --git a/lib_nbgl/src/nbgl_step.c b/lib_nbgl/src/nbgl_step.c
index 774fb0a6a..95e4f9118 100644
--- a/lib_nbgl/src/nbgl_step.c
+++ b/lib_nbgl/src/nbgl_step.c
@@ -251,6 +251,10 @@ static void displayTextPage(StepContext_t *ctx, uint8_t textPage)
BAGL_FONT_OPEN_SANS_REGULAR_11px_1bpp, txt, AVAILABLE_WIDTH, nbLines, &len, true);
// memorize next position to save processing
ctx->textContext.nextPageStart = txt + len;
+ // if the next char is '\n', skip it to avoid starting with an empty line
+ if (*ctx->textContext.nextPageStart == '\n') {
+ ctx->textContext.nextPageStart++;
+ }
}
else {
ctx->textContext.nextPageStart = NULL;
@@ -309,7 +313,9 @@ static void actionCallback(nbgl_layout_t *layout, nbgl_buttonEvent_t event)
}
else if ((ctx->textContext.pos == LAST_STEP)
|| (ctx->textContext.pos == NEITHER_FIRST_NOR_LAST_STEP)) {
- ctx->textContext.onActionCallback((nbgl_step_t) ctx, event);
+ if (ctx->textContext.onActionCallback != NULL) {
+ ctx->textContext.onActionCallback((nbgl_step_t) ctx, event);
+ }
}
}
else if (event == BUTTON_RIGHT_PRESSED) {
@@ -319,11 +325,15 @@ static void actionCallback(nbgl_layout_t *layout, nbgl_buttonEvent_t event)
}
else if ((ctx->textContext.pos == FIRST_STEP)
|| (ctx->textContext.pos == NEITHER_FIRST_NOR_LAST_STEP)) {
- ctx->textContext.onActionCallback((nbgl_step_t) ctx, event);
+ if (ctx->textContext.onActionCallback != NULL) {
+ ctx->textContext.onActionCallback((nbgl_step_t) ctx, event);
+ }
}
}
else if (event == BUTTON_BOTH_PRESSED) {
- ctx->textContext.onActionCallback((nbgl_step_t) ctx, event);
+ if (ctx->textContext.onActionCallback != NULL) {
+ ctx->textContext.onActionCallback((nbgl_step_t) ctx, event);
+ }
}
}
@@ -526,9 +536,12 @@ nbgl_step_t nbgl_stepDrawCenteredInfo(nbgl_stepPosition_t pos,
// initialize context (already set to 0 by getFreeContext())
ctx->textContext.onActionCallback = onActionCallback;
if (ticker) {
- ctx->ticker.tickerCallback = ticker->tickerCallback;
- ctx->ticker.tickerIntervale = ticker->tickerIntervale;
- ctx->ticker.tickerValue = ticker->tickerValue;
+ ctx->ticker.tickerCallback = ticker->tickerCallback;
+ ctx->ticker.tickerIntervale = ticker->tickerIntervale;
+ ctx->ticker.tickerValue = ticker->tickerValue;
+ layoutDescription.ticker.tickerCallback = ticker->tickerCallback;
+ layoutDescription.ticker.tickerIntervale = ticker->tickerIntervale;
+ layoutDescription.ticker.tickerValue = ticker->tickerValue;
}
ctx->textContext.nbPages = 1;
diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c
index bb77a2e50..8beba798f 100644
--- a/lib_nbgl/src/nbgl_use_case.c
+++ b/lib_nbgl/src/nbgl_use_case.c
@@ -16,7 +16,6 @@
#include "os_pic.h"
#include "os_print.h"
#include "os_helpers.h"
-#include "decorators.h"
/*********************
* DEFINES
@@ -49,9 +48,18 @@
#define QRCODE_REDUCED_ADDR_LEN 128
// macros to ease access to shared contexts
-#define keypadContext sharedContext.keypad
-#define addressConfirmationContext sharedContext.addressConfirmation
-#define blindSigningContext sharedContext.blindSigning
+#define keypadContext sharedContext.keypad
+#define reviewWithWarnCtx sharedContext.reviewWithWarning
+
+/* max length of the string displaying the description of the Web3 Checks report */
+#define W3C_DESCRIPTION_MAX_LEN 128
+
+/**
+ * @brief This is to use in @ref nbgl_operationType_t when the operation is concerned by an internal
+ * warning This is used to indicate a warning with a top-right button in review first & last page
+ *
+ */
+#define RISKY_OPERATION (1 << 6)
/**********************
* TYPEDEFS
@@ -70,8 +78,15 @@ enum {
CONFIRM_TOKEN,
REJECT_TOKEN,
VALUE_ALIAS_TOKEN,
+ INFO_ALIAS_TOKEN,
BLIND_WARNING_TOKEN,
- TIP_BOX_TOKEN
+ WARNING_BUTTON_TOKEN,
+ TIP_BOX_TOKEN,
+ WARNING_CHOICE_TOKEN,
+ DISMISS_QR_TOKEN,
+ DISMISS_WARNING_TOKEN,
+ FIRST_WARN_BAR_TOKEN,
+ LAST_WARN_BAR_TOKEN = (FIRST_WARN_BAR_TOKEN + NB_WARNING_TYPES - 1),
};
typedef enum {
@@ -92,7 +107,7 @@ typedef struct DetailsContext_s {
typedef struct AddressConfirmationContext_s {
nbgl_layoutTagValue_t tagValuePair;
- nbgl_layout_t modalLayout;
+ nbgl_layout_t *modalLayout;
} AddressConfirmationContext_t;
#ifdef NBGL_KEYPAD
@@ -115,18 +130,21 @@ typedef struct BlindSigningContext_s {
const char *reviewSubTitle;
const char *finishTitle;
const nbgl_tipBox_t *tipBox;
+ const nbgl_warning_t *warning;
nbgl_choiceCallback_t choiceCallback;
nbgl_layout_t *layoutCtx;
-} BlindSigningContext_t;
+ nbgl_layout_t *modalLayout;
+ uint8_t securityReportLevel; // level 1 is the first level of menus
+ bool isIntro; // set to true during intro (before actual review)
+} ReviewWithWarningContext_t;
// this union is intended to save RAM for context storage
-// indeed, these three contexts cannot happen simultaneously
+// indeed, these 2 contexts cannot happen simultaneously
typedef union {
#ifdef NBGL_KEYPAD
KeypadContext_t keypad;
#endif
- AddressConfirmationContext_t addressConfirmation;
- BlindSigningContext_t blindSigning;
+ ReviewWithWarningContext_t reviewWithWarning;
} SharedContext_t;
typedef struct {
@@ -144,7 +162,7 @@ typedef struct {
nbgl_contentTagValueCallback_t
currentCallback; // to be used to retrieve the pairs with value alias
- nbgl_layout_t modalLayout;
+ nbgl_layout_t *modalLayout;
} GenericContext_t;
typedef struct {
@@ -176,12 +194,18 @@ typedef union {
nbgl_reviewStreamingContext_t reviewStreaming;
} nbgl_BundleNavContext_t;
+typedef struct {
+ const nbgl_icon_details_t *icon;
+ const char *text;
+ const char *subText;
+} SecurityReportItem_t;
+
/**********************
* STATIC VARIABLES
**********************/
// char buffers to build some strings
-static char appDescription[APP_DESCRIPTION_MAX_LEN];
+static char tmpString[W3C_DESCRIPTION_MAX_LEN];
// multi-purposes callbacks
static nbgl_callback_t onQuit;
@@ -217,6 +241,9 @@ static DetailsContext_t detailsContext;
// multi-purpose context shared for non-concurrent usages
static SharedContext_t sharedContext;
+// context for address review
+static AddressConfirmationContext_t addressConfirmationContext;
+
// contexts for generic navigation
static GenericContext_t genericContext;
static nbgl_content_t
@@ -241,6 +268,25 @@ static const uint8_t nbMaxElementsPerContentType[] = {
5, // BARS_LIST (computed dynamically)
};
+static const SecurityReportItem_t securityReportItems[NB_WARNING_TYPES] = {
+ [BLIND_SIGNING_WARN] = {.icon = &WARNING_ICON,
+ .text = "Blind signing",
+ .subText = "This transaction cannot be fully decoded." },
+ [W3C_ISSUE_WARN] = {.icon = &ROUND_WARN_ICON,
+ .text = "Web3 Checks issue",
+ .subText = "Web3 Checks could not verify this transaction." },
+ [W3C_LOSING_SWAP_WARN] = {.icon = &ROUND_WARN_ICON,
+ .text = "Risk detected",
+ .subText = "Web3 Checks found a risk:\nLosing swap" },
+ [W3C_THREAT_DETECTED_WARN]
+ = {.icon = &ROUND_WARN_ICON,
+ .text = "Threat detected",
+ .subText = "This transaction was scanned as malicious by Web3 Checks."}
+};
+
+// configuration of warning when using @ref nbgl_useCaseReviewBlindSigning()
+static const nbgl_warning_t blindSigningWarning = {.predefinedSet = (1 << BLIND_SIGNING_WARN)};
+
#ifdef NBGL_QRCODE
/* buffer to store reduced address under QR Code */
static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
@@ -251,15 +297,15 @@ static char reducedAddress[QRCODE_REDUCED_ADDR_LEN];
**********************/
static void displayReviewPage(uint8_t page, bool forceFullRefresh);
static void displayDetailsPage(uint8_t page, bool forceFullRefresh);
-static void displayFullValuePage(const nbgl_contentTagValue_t *pair);
-static void displayBlindWarning(void);
+static void displayFullValuePage(const char *backText,
+ const char *aliasText,
+ const nbgl_contentValueExt_t *extension);
static void displayTipBoxModal(void);
static void displaySettingsPage(uint8_t page, bool forceFullRefresh);
static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh);
static void pageCallback(int token, uint8_t index);
#ifdef NBGL_QRCODE
static void displayAddressQRCode(void);
-static void addressLayoutTouchCallbackQR(int token, uint8_t index);
#endif // NBGL_QRCODE
static void modalLayoutTouchCallback(int token, uint8_t index);
static void displaySkipWarning(void);
@@ -269,7 +315,9 @@ static void bundleNavStartSettingsAtPage(uint8_t initSettingPage);
static void bundleNavStartSettings(void);
static void bundleNavReviewStreamingChoice(bool confirm);
-static void blindSigningWarning(void);
+static void displaySecurityReport(uint32_t set);
+static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details);
+static void displayInitialWarning(void);
static void useCaseReview(nbgl_operationType_t operationType,
const nbgl_contentTagValueList_t *tagValueList,
const nbgl_icon_details_t *icon,
@@ -444,6 +492,14 @@ static const char *getRejectReviewText(nbgl_operationType_t operationType)
static void pageModalCallback(int token, uint8_t index)
{
LOG_DEBUG(USE_CASE_LOGGER, "pageModalCallback, token = %d, index = %d\n", token, index);
+
+ if (token == INFO_ALIAS_TOKEN) {
+ // the icon next to info type alias has been touched
+ displayFullValuePage(tipBoxInfoList.infoTypes[index],
+ tipBoxInfoList.infoContents[index],
+ &tipBoxInfoList.infoExtensions[index]);
+ return;
+ }
nbgl_pageRelease(modalPageContext);
modalPageContext = NULL;
if (token == NAV_TOKEN) {
@@ -580,10 +636,29 @@ static void pageCallback(int token, uint8_t index)
else {
pair = genericContext.currentCallback(genericContext.currentElementIdx + index);
}
- displayFullValuePage(pair);
+ displayFullValuePage(pair->item, pair->value, pair->extension);
}
else if (token == BLIND_WARNING_TOKEN) {
- displayBlindWarning();
+ reviewWithWarnCtx.isIntro = false;
+ reviewWithWarnCtx.warning = NULL;
+ displaySecurityReport(1 << BLIND_SIGNING_WARN);
+ }
+ else if (token == WARNING_BUTTON_TOKEN) {
+ // top-right button, display the security modal
+ reviewWithWarnCtx.securityReportLevel = 1;
+ // if preset is used
+ if (reviewWithWarnCtx.warning->predefinedSet) {
+ displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
+ }
+ else {
+ // use customized warning
+ if (reviewWithWarnCtx.isIntro) {
+ displayCustomizedSecurityReport(reviewWithWarnCtx.warning->introDetails);
+ }
+ else {
+ displayCustomizedSecurityReport(reviewWithWarnCtx.warning->reviewDetails);
+ }
+ }
}
else if (token == TIP_BOX_TOKEN) {
displayTipBoxModal();
@@ -714,7 +789,9 @@ static const nbgl_content_t *genericContextComputeNextPageParams(uint8_t
// Handle case where the content to be displayed is in the next content
// In such case start at element index 0.
- if (nextElementIdx >= genericContext.currentContentElementNb) {
+ // If currentContentElementNb == 0, means not initialized, so skip
+ if ((nextElementIdx >= genericContext.currentContentElementNb)
+ && (genericContext.currentContentElementNb > 0)) {
nextContentIdx += 1;
nextElementIdx = 0;
}
@@ -958,16 +1035,24 @@ static bool genericContextPreparePageContent(const nbgl_content_t *p_content,
bool isFirstOrLastPage
= ((p_content->type == CENTERED_INFO) || (p_content->type == EXTENDED_CENTER))
|| (p_content->type == INFO_LONG_PRESS);
- bool isStreamingNavAndBlindOperation
+ nbgl_operationType_t operationType
= (navType == STREAMING_NAV)
- && (bundleNavContext.reviewStreaming.operationType & BLIND_OPERATION);
- bool isGenericNavAndBlindOperation
- = (navType == GENERIC_NAV) && (bundleNavContext.review.operationType & BLIND_OPERATION);
+ ? bundleNavContext.reviewStreaming.operationType
+ : ((navType == GENERIC_NAV) ? bundleNavContext.review.operationType : 0);
- // if first or last page of review and blind operation, add the warning top-right button
- if (isFirstOrLastPage && (isStreamingNavAndBlindOperation || isGenericNavAndBlindOperation)) {
- pageContent->topRightIcon = &WARNING_ICON;
- pageContent->topRightToken = BLIND_WARNING_TOKEN;
+ // if first or last page of review and blind/risky operation, add the warning top-right button
+ if (isFirstOrLastPage && (operationType & (BLIND_OPERATION | RISKY_OPERATION))) {
+ // if issue is only Web3Checks issue, use '!' icon
+ if ((operationType & RISKY_OPERATION)
+ && (reviewWithWarnCtx.warning->predefinedSet == (1 << W3C_ISSUE_WARN))) {
+ pageContent->topRightIcon = &EXCLAMATION_ICON;
+ }
+ else {
+ pageContent->topRightIcon = &WARNING_ICON;
+ }
+
+ pageContent->topRightToken
+ = (operationType & BLIND_OPERATION) ? BLIND_WARNING_TOKEN : WARNING_BUTTON_TOKEN;
}
return true;
@@ -1133,7 +1218,9 @@ static void displayDetailsPage(uint8_t detailsPage, bool forceFullRefresh)
}
// function used to display the content of a full value, when touching an alias of a tag/value pair
-static void displayFullValuePage(const nbgl_contentTagValue_t *pair)
+static void displayFullValuePage(const char *backText,
+ const char *aliasText,
+ const nbgl_contentValueExt_t *extension)
{
nbgl_layoutDescription_t layoutDescription = {.modal = true,
.withLeftBorder = true,
@@ -1143,60 +1230,38 @@ static void displayFullValuePage(const nbgl_contentTagValue_t *pair)
.separationLine = false,
.backAndText.token = 0,
.backAndText.tuneId = TUNE_TAP_CASUAL,
- .backAndText.text = PIC(pair->item)};
- const char *info;
- genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
+ .backAndText.text = PIC(backText)};
+ genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
// add header with the tag part of the pair, to go back
nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
- // add full value text
- if (pair->extension->explanation == NULL) {
- if (pair->extension->aliasType == ENS_ALIAS) {
+ // add either QR Code or full value text
+ if (extension->aliasType == QR_CODE_ALIAS) {
+#ifdef NBGL_QRCODE
+ nbgl_layoutQRCode_t qrCode
+ = {.url = extension->fullValue,
+ .text1 = (extension->title != NULL) ? extension->title : extension->fullValue,
+ .text2 = extension->explanation,
+ .centered = true,
+ .offsetY = 0};
+
+ nbgl_layoutAddQRCode(genericContext.modalLayout, &qrCode);
+#endif // NBGL_QRCODE
+ }
+ else {
+ const char *info;
+ // add full value text
+ if (extension->aliasType == ENS_ALIAS) {
info = "ENS names are resolved by Ledger backend.";
}
- else if (pair->extension->aliasType == ADDRESS_BOOK_ALIAS) {
+ else if (extension->aliasType == ADDRESS_BOOK_ALIAS) {
info = "This account label comes from your Address Book in Ledger Live.";
}
else {
- info = "";
+ info = extension->explanation;
}
+ nbgl_layoutAddTextContent(
+ genericContext.modalLayout, aliasText, extension->fullValue, info);
}
- else {
- info = pair->extension->explanation;
- }
- nbgl_layoutAddTextContent(
- genericContext.modalLayout, pair->value, pair->extension->fullValue, info);
-
- // draw & refresh
- nbgl_layoutDraw(genericContext.modalLayout);
- nbgl_refresh();
-}
-
-// function used to display the modal warning when touching the alert symbol of a blind review
-static void displayBlindWarning(void)
-{
- nbgl_layoutDescription_t layoutDescription = {.modal = true,
- .withLeftBorder = true,
- .onActionCallback = &modalLayoutTouchCallback,
- .tapActionText = NULL};
- nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
- .separationLine = false,
- .backAndText.token = 0,
- .backAndText.tuneId = TUNE_TAP_CASUAL,
- .backAndText.text = NULL};
- nbgl_layoutCenteredInfo_t centeredInfo
- = {.icon = NULL, .text3 = NULL, .style = LARGE_CASE_INFO, .offsetY = 0, .onTop = false};
- centeredInfo.text1 = "Security risk detected";
- centeredInfo.text2
- = "This transaction or message cannot be decoded fully. If you choose to sign, you could "
- "be authorizing malicious actions that can drain your wallet.\n\n"
- "Learn more: ledger.com/e8";
-
- genericContext.modalLayout = nbgl_layoutGet(&layoutDescription);
- // add header with the tag part of the pair, to go back
- nbgl_layoutAddHeader(genericContext.modalLayout, &headerDesc);
- // add full value text
- nbgl_layoutAddCenteredInfo(genericContext.modalLayout, ¢eredInfo);
-
// draw & refresh
nbgl_layoutDraw(genericContext.modalLayout);
nbgl_refresh();
@@ -1215,13 +1280,17 @@ static void displayTipBoxModal(void)
.navWithButtons.quitText = NULL,
.progressIndicator = false,
.tuneId = TUNE_TAP_CASUAL};
- nbgl_pageContent_t content = {.type = INFOS_LIST,
- .topRightIcon = NULL,
- .infosList.nbInfos = tipBoxInfoList.nbInfos,
- .infosList.infoTypes = tipBoxInfoList.infoTypes,
- .infosList.infoContents = tipBoxInfoList.infoContents,
- .title = tipBoxModalTitle,
- .titleToken = QUIT_TOKEN};
+ nbgl_pageContent_t content = {.type = INFOS_LIST,
+ .topRightIcon = NULL,
+ .infosList.nbInfos = tipBoxInfoList.nbInfos,
+ .infosList.withExtensions = tipBoxInfoList.withExtensions,
+ .infosList.infoTypes = tipBoxInfoList.infoTypes,
+ .infosList.infoContents = tipBoxInfoList.infoContents,
+ .infosList.infoExtensions = tipBoxInfoList.infoExtensions,
+ .infosList.token = INFO_ALIAS_TOKEN,
+ .title = tipBoxModalTitle,
+ .titleToken = QUIT_TOKEN,
+ .tuneId = TUNE_TAP_CASUAL};
if (modalPageContext != NULL) {
nbgl_pageRelease(modalPageContext);
@@ -1237,7 +1306,7 @@ static void displayAddressQRCode(void)
// display the address as QR Code
nbgl_layoutDescription_t layoutDescription = {.modal = true,
.withLeftBorder = true,
- .onActionCallback = &addressLayoutTouchCallbackQR,
+ .onActionCallback = &modalLayoutTouchCallback,
.tapActionText = NULL};
nbgl_layoutHeader_t headerDesc = {
.type = HEADER_EMPTY, .separationLine = false, .emptySpace.height = SMALL_CENTERING_HEADER};
@@ -1269,38 +1338,128 @@ static void displayAddressQRCode(void)
nbgl_layoutAddQRCode(addressConfirmationContext.modalLayout, &qrCode);
- nbgl_layoutAddFooter(addressConfirmationContext.modalLayout, "Close", 0, TUNE_TAP_CASUAL);
+ nbgl_layoutAddFooter(
+ addressConfirmationContext.modalLayout, "Close", DISMISS_QR_TOKEN, TUNE_TAP_CASUAL);
nbgl_layoutDraw(addressConfirmationContext.modalLayout);
nbgl_refresh();
}
-// called when quit button is touched on Address verification page
-static void addressLayoutTouchCallbackQR(int token, uint8_t index)
-{
- UNUSED(token);
- UNUSED(index);
-
- // dismiss modal
- nbgl_layoutRelease(addressConfirmationContext.modalLayout);
- addressConfirmationContext.modalLayout = NULL;
- nbgl_screenRedraw();
- nbgl_refresh();
-}
#endif // NBGL_QRCODE
// called when header is touched on modal page, to dismiss it
static void modalLayoutTouchCallback(int token, uint8_t index)
{
- UNUSED(token);
UNUSED(index);
-
- // dismiss modal
- nbgl_layoutRelease(genericContext.modalLayout);
- genericContext.modalLayout = NULL;
- nbgl_screenRedraw();
+ if (token == DISMISS_QR_TOKEN) {
+ // dismiss modal
+ nbgl_layoutRelease(addressConfirmationContext.modalLayout);
+ addressConfirmationContext.modalLayout = NULL;
+ nbgl_screenRedraw();
+ }
+ else if (token == DISMISS_WARNING_TOKEN) {
+ // dismiss modal
+ nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
+ // if already at first level, simply redraw the background
+ if (reviewWithWarnCtx.securityReportLevel <= 1) {
+ reviewWithWarnCtx.modalLayout = NULL;
+ nbgl_screenRedraw();
+ }
+ else {
+ // We are at level 2 of warning modal, so re-display the level 1
+ reviewWithWarnCtx.securityReportLevel = 1;
+ // if preset is used
+ if (reviewWithWarnCtx.warning->predefinedSet) {
+ displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
+ }
+ else {
+ // use customized warning
+ const nbgl_warningDetails_t *details
+ = (reviewWithWarnCtx.isIntro) ? reviewWithWarnCtx.warning->introDetails
+ : reviewWithWarnCtx.warning->reviewDetails;
+ displayCustomizedSecurityReport(details);
+ }
+ return;
+ }
+ }
+ else if ((token >= FIRST_WARN_BAR_TOKEN) && (token <= LAST_WARN_BAR_TOKEN)) {
+ // dismiss modal before creating a new one
+ nbgl_layoutRelease(reviewWithWarnCtx.modalLayout);
+ reviewWithWarnCtx.securityReportLevel = 2;
+ // if preset is used
+ if (reviewWithWarnCtx.warning->predefinedSet) {
+ displaySecurityReport(1 << (token - FIRST_WARN_BAR_TOKEN));
+ }
+ else {
+ // use customized warning
+ const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
+ ? reviewWithWarnCtx.warning->introDetails
+ : reviewWithWarnCtx.warning->reviewDetails;
+ if (details->type == BAR_LIST_WARNING) {
+ displayCustomizedSecurityReport(
+ &details->barList.details[token - FIRST_WARN_BAR_TOKEN]);
+ }
+ }
+ return;
+ }
+ else {
+ // dismiss modal
+ nbgl_layoutRelease(genericContext.modalLayout);
+ genericContext.modalLayout = NULL;
+ nbgl_screenRedraw();
+ }
nbgl_refresh();
}
+// called when item are touched in layout
+static void layoutTouchCallback(int token, uint8_t index)
+{
+ // choice buttons in initial warning page
+ if (token == WARNING_CHOICE_TOKEN) {
+ if (index == 0) { // top button to exit
+ reviewWithWarnCtx.choiceCallback(false);
+ }
+ else { // bottom button to continue to review
+ reviewWithWarnCtx.isIntro = false;
+ if (reviewWithWarnCtx.isStreaming) {
+ useCaseReviewStreamingStart(reviewWithWarnCtx.operationType,
+ reviewWithWarnCtx.icon,
+ reviewWithWarnCtx.reviewTitle,
+ reviewWithWarnCtx.reviewSubTitle,
+ reviewWithWarnCtx.choiceCallback,
+ false);
+ }
+ else {
+ useCaseReview(reviewWithWarnCtx.operationType,
+ reviewWithWarnCtx.tagValueList,
+ reviewWithWarnCtx.icon,
+ reviewWithWarnCtx.reviewTitle,
+ reviewWithWarnCtx.reviewSubTitle,
+ reviewWithWarnCtx.finishTitle,
+ reviewWithWarnCtx.tipBox,
+ reviewWithWarnCtx.choiceCallback,
+ false,
+ false);
+ }
+ }
+ }
+ // top-right button in initial warning page
+ else if (token == WARNING_BUTTON_TOKEN) {
+ // start at first level of security report
+ reviewWithWarnCtx.securityReportLevel = 1;
+ // if preset is used
+ if (reviewWithWarnCtx.warning->predefinedSet) {
+ displaySecurityReport(reviewWithWarnCtx.warning->predefinedSet);
+ }
+ else {
+ // use customized warning
+ const nbgl_warningDetails_t *details = (reviewWithWarnCtx.isIntro)
+ ? reviewWithWarnCtx.warning->introDetails
+ : reviewWithWarnCtx.warning->reviewDetails;
+ displayCustomizedSecurityReport(details);
+ }
+ }
+}
+
// called when skip button is touched in footer, during forward only review
static void displaySkipWarning(void)
{
@@ -1640,7 +1799,7 @@ static void bundleNavReviewAskRejectionConfirmation(nbgl_operationType_t operati
const char *title;
const char *confirmText;
// clear skip and blind bits
- operationType &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION);
+ operationType &= ~(SKIPPABLE_OPERATION | BLIND_OPERATION | RISKY_OPERATION);
if (operationType == TYPE_TRANSACTION) {
title = "Reject transaction?";
confirmText = "Go back to transaction";
@@ -1689,51 +1848,311 @@ static void bundleNavReviewStreamingChoice(bool confirm)
}
}
-// function called when the warning page of Blind Signing review buttons are pressed
-static void blindSigningWarningCallback(bool confirm)
+// function used to display the security level page in modal
+// it can be either a single page with text or QR code, or a list of touchable bars leading
+// to single pages
+static void displaySecurityReport(uint32_t set)
{
- if (confirm) { // top button to exit
- blindSigningContext.choiceCallback(false);
+ nbgl_layoutDescription_t layoutDescription = {.modal = true,
+ .withLeftBorder = true,
+ .onActionCallback = modalLayoutTouchCallback,
+ .tapActionText = NULL};
+ nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
+ .separationLine = true,
+ .backAndText.tuneId = TUNE_TAP_CASUAL,
+ .backAndText.token = DISMISS_WARNING_TOKEN};
+ nbgl_layoutFooter_t footerDesc
+ = {.type = FOOTER_EMPTY, .separationLine = false, .emptySpace.height = 0};
+ uint8_t i;
+ uint8_t nbWarnings = 0;
+ const char *provider;
+
+ reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
+
+ // count the number of warnings
+ for (i = 0; i < NB_WARNING_TYPES; i++) {
+ if (set & (1 << i)) {
+ nbWarnings++;
+ }
+ }
+
+ if ((nbWarnings > 1) && (reviewWithWarnCtx.securityReportLevel == 1)) {
+ // if more than one warning warning, so use a list of touchable bars
+ for (i = 0; i < NB_WARNING_TYPES; i++) {
+ if (reviewWithWarnCtx.warning->predefinedSet & (1 << i)) {
+ nbgl_layoutBar_t bar;
+ bar.text = securityReportItems[i].text;
+ bar.subText = securityReportItems[i].subText;
+ bar.iconRight = &PUSH_ICON;
+ bar.iconLeft = securityReportItems[i].icon;
+ bar.token = FIRST_WARN_BAR_TOKEN + i;
+ bar.tuneId = TUNE_TAP_CASUAL;
+ bar.large = false;
+ bar.inactive = false;
+ nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
+ nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
+ }
+ }
+ headerDesc.backAndText.text = "Security report";
+ nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
+ nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
+ nbgl_refresh();
+ return;
+ }
+ if (reviewWithWarnCtx.warning && reviewWithWarnCtx.warning->reportProvider) {
+ provider = reviewWithWarnCtx.warning->reportProvider;
+ }
+ else {
+ provider = "[unknown]";
}
- else { // bottom button to continue to review
- if (blindSigningContext.isStreaming) {
- useCaseReviewStreamingStart(blindSigningContext.operationType,
- blindSigningContext.icon,
- blindSigningContext.reviewTitle,
- blindSigningContext.reviewSubTitle,
- blindSigningContext.choiceCallback,
- false);
+ if (set & (1 << BLIND_SIGNING_WARN)) {
+ if (reviewWithWarnCtx.isIntro) {
+#ifdef NBGL_QRCODE
+ // display a QR Code if in intro
+ nbgl_layoutQRCode_t qrCode
+ = {.url = "ledger.com/e8",
+ .text1 = "ledger.com/e8",
+ .text2 = "Scan to learn about the risks of blind signing.",
+ .centered = true,
+ .offsetY = 0};
+ nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
+ footerDesc.emptySpace.height = 24;
+#endif // NBGL_QRCODE
+ headerDesc.backAndText.text = "Blind signing report";
}
else {
- useCaseReview(blindSigningContext.operationType,
- blindSigningContext.tagValueList,
- blindSigningContext.icon,
- blindSigningContext.reviewTitle,
- blindSigningContext.reviewSubTitle,
- blindSigningContext.finishTitle,
- blindSigningContext.tipBox,
- blindSigningContext.choiceCallback,
- false,
- false);
+ // display a centered if in review
+ nbgl_contentCenter_t info = {0};
+ info.icon = &C_Warning_64px;
+ info.title = "Blind Signing";
+ info.description
+ = "This transaction's details are not fully verifiable. If you sign it, you could "
+ "lose all your assets.\n\n"
+ "Learn about blind signing:\nledger.com/e8";
+ nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
+ footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER;
+ headerDesc.separationLine = false;
+ }
+ }
+ else if (set & (1 << W3C_ISSUE_WARN)) {
+ // if W3 Checks issue, display a centered info
+ nbgl_contentCenter_t info = {0};
+ info.icon = &C_Important_Circle_64px;
+ info.title = "Web3 Checks could not verify this message";
+ info.description = "An issue prevented Web3 Checks from running.\nGet help: ledger.com/e11";
+ nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
+ footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER;
+ headerDesc.separationLine = false;
+ }
+ else if (set & (1 << W3C_THREAT_DETECTED_WARN)) {
+ if (reviewWithWarnCtx.isIntro) {
+#ifdef NBGL_QRCODE
+ // display a QR Code if in intro
+ nbgl_layoutQRCode_t qrCode = {.url = "url.com/od24xz",
+ .text1 = "url.com/od24xz",
+ .text2 = tmpString,
+ .centered = true,
+ .offsetY = 0};
+ snprintf(tmpString,
+ W3C_DESCRIPTION_MAX_LEN,
+ "Scan to view the threat report from %s.",
+ provider);
+ nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
+ footerDesc.emptySpace.height = 24;
+#endif // NBGL_QRCODE
+ headerDesc.backAndText.text = "Web3 Checks threat report";
}
+ else {
+ // display a centered if in review
+ nbgl_contentCenter_t info = {0};
+ info.icon = &C_Warning_64px;
+ info.title = "Threat detected";
+ info.smallTitle = "Known drainer contract";
+ info.description = tmpString;
+ snprintf(tmpString,
+ W3C_DESCRIPTION_MAX_LEN,
+ "This transaction was scanned as malicious by Web3 Checks.\n\nView full %s "
+ "report:\nurl.com/od24xz",
+ provider);
+ nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
+ footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER;
+ headerDesc.separationLine = false;
+ }
+ }
+ else if (set & (1 << W3C_LOSING_SWAP_WARN)) {
+ if (reviewWithWarnCtx.isIntro) {
+#ifdef NBGL_QRCODE
+ // display a QR Code if in intro
+ nbgl_layoutQRCode_t qrCode = {.url = "url.com/od24xz",
+ .text1 = "url.com/od24xz",
+ .text2 = tmpString,
+ .centered = true,
+ .offsetY = 0};
+ snprintf(tmpString,
+ W3C_DESCRIPTION_MAX_LEN,
+ "Scan to view the risk report from %s.",
+ provider);
+ nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &qrCode);
+ footerDesc.emptySpace.height = 24;
+#endif // NBGL_QRCODE
+ headerDesc.backAndText.text = "Web3 Checks risk report";
+ }
+ else {
+ // display a centered if in review
+ nbgl_contentCenter_t info = {0};
+ info.icon = &C_Warning_64px;
+ info.title = "Risk detected";
+ info.smallTitle = "Losing swap";
+ info.description = tmpString;
+ snprintf(tmpString,
+ W3C_DESCRIPTION_MAX_LEN,
+ "This transaction was scanned as risky by Web3 Checks.\n\n"
+ "View full %s report:\\nurl.com/od24xz",
+ provider);
+ nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &info);
+ footerDesc.emptySpace.height = MEDIUM_CENTERING_HEADER;
+ headerDesc.separationLine = false;
+ }
+ }
+ nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
+ if (footerDesc.emptySpace.height > 0) {
+ nbgl_layoutAddExtendedFooter(reviewWithWarnCtx.modalLayout, &footerDesc);
+ }
+ nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
+ nbgl_refresh();
+}
+
+// function used to display the security level page in modal
+// it can be either a single page with text or QR code, or a list of touchable bars leading
+// to single pages
+static void displayCustomizedSecurityReport(const nbgl_warningDetails_t *details)
+{
+ nbgl_layoutDescription_t layoutDescription = {.modal = true,
+ .withLeftBorder = true,
+ .onActionCallback = modalLayoutTouchCallback,
+ .tapActionText = NULL};
+ nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT,
+ .separationLine = true,
+ .backAndText.tuneId = TUNE_TAP_CASUAL,
+ .backAndText.token = DISMISS_WARNING_TOKEN};
+ uint8_t i;
+
+ reviewWithWarnCtx.modalLayout = nbgl_layoutGet(&layoutDescription);
+ headerDesc.backAndText.text = details->title;
+ nbgl_layoutAddHeader(reviewWithWarnCtx.modalLayout, &headerDesc);
+ if (details->type == BAR_LIST_WARNING) {
+ // if more than one warning warning, so use a list of touchable bars
+ for (i = 0; i < details->barList.nbBars; i++) {
+ nbgl_layoutBar_t bar;
+ bar.text = details->barList.texts[i];
+ bar.subText = details->barList.subTexts[i];
+ bar.iconRight = &PUSH_ICON;
+ bar.iconLeft = details->barList.icons[i];
+ bar.token = FIRST_WARN_BAR_TOKEN + i;
+ bar.tuneId = TUNE_TAP_CASUAL;
+ bar.large = false;
+ bar.inactive = false;
+ nbgl_layoutAddTouchableBar(reviewWithWarnCtx.modalLayout, &bar);
+ nbgl_layoutAddSeparationLine(reviewWithWarnCtx.modalLayout);
+ }
+ }
+ else if (details->type == QRCODE_WARNING) {
+#ifdef NBGL_QRCODE
+ // display a QR Code
+ nbgl_layoutAddQRCode(reviewWithWarnCtx.modalLayout, &details->qrCode);
+#endif // NBGL_QRCODE
+ headerDesc.backAndText.text = details->title;
}
+ else if (details->type == CENTERED_INFO_WARNING) {
+ // display a centered info
+ nbgl_layoutAddContentCenter(reviewWithWarnCtx.modalLayout, &details->centeredInfo);
+ headerDesc.separationLine = false;
+ }
+ nbgl_layoutDraw(reviewWithWarnCtx.modalLayout);
+ nbgl_refresh();
}
-// function used to display the warning page when starting a Bling Signing review
-static void blindSigningWarning(void)
+// function used to display the initial warning page when starting a "review with warning"
+static void displayInitialWarning(void)
{
// Play notification sound
#ifdef HAVE_PIEZO_SOUND
io_seproxyhal_play_tune(TUNE_LOOK_AT_ME);
#endif // HAVE_PIEZO_SOUND
- nbgl_useCaseChoice(&C_Warning_64px,
- "Blind signing ahead",
- "The details of this transaction or message are not fully verifiable. If "
- "you sign it, you could lose all "
- "your assets.",
- "Back to safety",
- "Continue anyway",
- blindSigningWarningCallback);
+ nbgl_layoutDescription_t layoutDescription;
+ nbgl_layoutChoiceButtons_t buttonsInfo = {.bottomText = "Continue anyway",
+ .token = WARNING_CHOICE_TOKEN,
+ .topText = "Back to safety",
+ .style = ROUNDED_AND_FOOTER_STYLE,
+ .tuneId = TUNE_TAP_CASUAL};
+ nbgl_layoutHeader_t headerDesc = {.type = HEADER_EMPTY,
+ .separationLine = false,
+ .emptySpace.height = MEDIUM_CENTERING_HEADER};
+
+ reviewWithWarnCtx.isIntro = true;
+
+ layoutDescription.modal = false;
+ layoutDescription.withLeftBorder = true;
+
+ layoutDescription.onActionCallback = layoutTouchCallback;
+ layoutDescription.tapActionText = NULL;
+
+ layoutDescription.ticker.tickerCallback = NULL;
+ reviewWithWarnCtx.layoutCtx = nbgl_layoutGet(&layoutDescription);
+
+ nbgl_layoutAddHeader(reviewWithWarnCtx.layoutCtx, &headerDesc);
+ // do not display top-right icon if only Web3 Checks issue
+ if (reviewWithWarnCtx.warning->predefinedSet != (1 << W3C_ISSUE_WARN)) {
+ if (reviewWithWarnCtx.warning->predefinedSet != 0) {
+ nbgl_layoutAddTopRightButton(
+ reviewWithWarnCtx.layoutCtx, &PRIVACY_ICON, WARNING_BUTTON_TOKEN, TUNE_TAP_CASUAL);
+ }
+ else if (reviewWithWarnCtx.warning->introTopRightIcon != NULL) {
+ nbgl_layoutAddTopRightButton(reviewWithWarnCtx.layoutCtx,
+ reviewWithWarnCtx.warning->introTopRightIcon,
+ WARNING_BUTTON_TOKEN,
+ TUNE_TAP_CASUAL);
+ }
+ }
+ // add button and footer on bottom
+ nbgl_layoutAddChoiceButtons(reviewWithWarnCtx.layoutCtx, &buttonsInfo);
+ // add main content
+ // if predefined content is configured, use it preferably
+ if (reviewWithWarnCtx.warning->predefinedSet != 0) {
+ nbgl_contentCenter_t info = {0};
+ info.icon = &C_Warning_64px;
+ if (reviewWithWarnCtx.warning->predefinedSet == (1 << BLIND_SIGNING_WARN)) {
+ info.title = "Blind signing ahead";
+ info.description
+ = "The details of this transaction or message are not fully verifiable. If "
+ "you sign it, you could lose all "
+ "your assets.";
+ }
+ else if (reviewWithWarnCtx.warning->predefinedSet == (1 << W3C_ISSUE_WARN)) {
+ info.icon = &C_Important_Circle_64px;
+ info.title = "Web3 Checks could not verify this message";
+ info.description
+ = "An issue prevented Web3 Checks from running.\nGet help: ledger.com/e11";
+ }
+ else if (reviewWithWarnCtx.warning->predefinedSet == (1 << W3C_THREAT_DETECTED_WARN)) {
+ info.title = "Threat detected";
+ info.smallTitle = "Known drainer contract";
+ info.description = "This transaction was scanned as malicious by Web3 Checks.";
+ }
+ else {
+ info.title = "Dangerous transaction";
+ info.description
+ = "This transaction cannot be fully decoded, and was not verified by Web3 Checks.";
+ }
+ nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, &info);
+ }
+ else if (reviewWithWarnCtx.warning->info != NULL) {
+ // if no predefined content, use custom one
+ nbgl_layoutAddContentCenter(reviewWithWarnCtx.layoutCtx, reviewWithWarnCtx.warning->info);
+ }
+
+ nbgl_layoutDraw(reviewWithWarnCtx.layoutCtx);
+ nbgl_refresh();
}
// function to factorize code for all simple reviews
@@ -1768,6 +2187,9 @@ static void useCaseReview(nbgl_operationType_t operationType,
prepareReviewFirstPage(
&STARTING_CONTENT.content.extendedCenter.contentCenter, icon, reviewTitle, reviewSubTitle);
if (tipBox != NULL) {
+ // do not display "Swipe to review" if a tip-box is displayed
+ STARTING_CONTENT.content.extendedCenter.contentCenter.subText = NULL;
+
STARTING_CONTENT.content.extendedCenter.tipBox.icon = tipBox->icon;
STARTING_CONTENT.content.extendedCenter.tipBox.text = tipBox->text;
STARTING_CONTENT.content.extendedCenter.tipBox.token = TIP_BOX_TOKEN;
@@ -1775,7 +2197,11 @@ static void useCaseReview(nbgl_operationType_t operationType,
tipBoxModalTitle = tipBox->modalTitle;
// the only supported type yet is @ref INFOS_LIST
if (tipBox->type == INFOS_LIST) {
- memcpy(&tipBoxInfoList, &tipBox->infos, sizeof(nbgl_contentInfoList_t));
+ tipBoxInfoList.nbInfos = tipBox->infos.nbInfos;
+ tipBoxInfoList.withExtensions = tipBox->infos.withExtensions;
+ tipBoxInfoList.infoTypes = PIC(tipBox->infos.infoTypes);
+ tipBoxInfoList.infoContents = PIC(tipBox->infos.infoContents);
+ tipBoxInfoList.infoExtensions = PIC(tipBox->infos.infoExtensions);
}
}
@@ -1911,12 +2337,12 @@ static void useCaseHomeExt(const char *appName,
}
if (tagline == NULL) {
if (strlen(appName) > MAX_APP_NAME_FOR_SDK_TAGLINE) {
- snprintf(appDescription,
+ snprintf(tmpString,
APP_DESCRIPTION_MAX_LEN,
"This app enables signing\ntransactions on its network.");
}
else {
- snprintf(appDescription,
+ snprintf(tmpString,
APP_DESCRIPTION_MAX_LEN,
"%s %s\n%s",
TAGLINE_PART1,
@@ -1926,16 +2352,15 @@ static void useCaseHomeExt(const char *appName,
// If there is more than 3 lines, it means the appName was split, so we put it on the next
// line
- if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, appDescription, AVAILABLE_WIDTH, false)
- > 3) {
- snprintf(appDescription,
+ if (nbgl_getTextNbLinesInWidth(SMALL_REGULAR_FONT, tmpString, AVAILABLE_WIDTH, false) > 3) {
+ snprintf(tmpString,
APP_DESCRIPTION_MAX_LEN,
"%s\n%s %s",
TAGLINE_PART1,
appName,
TAGLINE_PART2);
}
- info.centeredInfo.text2 = appDescription;
+ info.centeredInfo.text2 = tmpString;
}
else {
info.centeredInfo.text2 = tagline;
@@ -2330,29 +2755,30 @@ uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *
* @deprecated
* See #nbgl_useCaseHomeAndSettings
*/
-DEPRECATED void nbgl_useCaseHome(const char *appName,
- const nbgl_icon_details_t *appIcon,
- const char *tagline,
- bool withSettings,
- nbgl_callback_t topRightCallback,
- nbgl_callback_t quitCallback)
-{
- nbgl_useCaseHomeExt(
- appName, appIcon, tagline, withSettings, NULL, NULL, topRightCallback, quitCallback);
+void nbgl_useCaseHome(const char *appName,
+ const nbgl_icon_details_t *appIcon,
+ const char *tagline,
+ bool withSettings,
+ nbgl_callback_t topRightCallback,
+ nbgl_callback_t quitCallback)
+{
+ nbgl_homeAction_t homeAction = {0};
+ useCaseHomeExt(
+ appName, appIcon, tagline, withSettings, &homeAction, topRightCallback, quitCallback);
}
/**
* @deprecated
* See #nbgl_useCaseHomeAndSettings
*/
-DEPRECATED void nbgl_useCaseHomeExt(const char *appName,
- const nbgl_icon_details_t *appIcon,
- const char *tagline,
- bool withSettings,
- const char *actionButtonText,
- nbgl_callback_t actionCallback,
- nbgl_callback_t topRightCallback,
- nbgl_callback_t quitCallback)
+void nbgl_useCaseHomeExt(const char *appName,
+ const nbgl_icon_details_t *appIcon,
+ const char *tagline,
+ bool withSettings,
+ const char *actionButtonText,
+ nbgl_callback_t actionCallback,
+ nbgl_callback_t topRightCallback,
+ nbgl_callback_t quitCallback)
{
nbgl_homeAction_t homeAction = {.callback = actionCallback,
.icon = NULL,
@@ -2364,28 +2790,25 @@ DEPRECATED void nbgl_useCaseHomeExt(const char *appName,
}
/**
- * @brief Draws the settings pages of an app with as many pages as given
- * For each page, the given navCallback will be called to get the content. Only 'type' and
- * union has to be set in this content
+ * @brief Initiates the drawing a set of pages of generic content, with a touchable header (usually
+ * to go back or to an upper level) For each page (including the first one), the given 'navCallback'
+ * will be called to get the content. Only 'type' and union has to be set in this content.
*
- * @param title string to set in touchable title
+ * @param title string to set in touchable title (header)
* @param initPage page on which to start [0->(nbPages-1)]
* @param nbPages number of pages
- * @param touchable unused, it is always on
- * @param quitCallback callback called when quit button (or title) is pressed
+ * @param quitCallback callback called title is pressed
* @param navCallback callback called when navigation arrows are pressed
* @param controlsCallback callback called when any controls in the settings (radios, switches) is
* called (the tokens must be >= @ref FIRST_USER_TOKEN)
*/
-void nbgl_useCaseSettings(const char *title,
- uint8_t initPage,
- uint8_t nbPages,
- bool touchable,
- nbgl_callback_t quitCallback,
- nbgl_navCallback_t navCallback,
- nbgl_layoutTouchCallback_t controlsCallback)
+void nbgl_useCaseNavigableContent(const char *title,
+ uint8_t initPage,
+ uint8_t nbPages,
+ nbgl_callback_t quitCallback,
+ nbgl_navCallback_t navCallback,
+ nbgl_layoutTouchCallback_t controlsCallback)
{
- UNUSED(touchable);
reset_callbacks();
// memorize context
@@ -2401,6 +2824,24 @@ void nbgl_useCaseSettings(const char *title,
displaySettingsPage(initPage, true);
}
+/**
+ * @deprecated
+ * See #nbgl_useCaseHomeAndSettings if used in a 'Settings' context, or nbgl_useCaseNavigableContent
+ * otherwise
+ */
+void nbgl_useCaseSettings(const char *title,
+ uint8_t initPage,
+ uint8_t nbPages,
+ bool touchable,
+ nbgl_callback_t quitCallback,
+ nbgl_navCallback_t navCallback,
+ nbgl_layoutTouchCallback_t controlsCallback)
+{
+ UNUSED(touchable);
+ nbgl_useCaseNavigableContent(
+ title, initPage, nbPages, quitCallback, navCallback, controlsCallback);
+}
+
/**
* @brief Draws the settings pages of an app with automatic pagination depending on content
* to be displayed that is passed through settingContents and infosList
@@ -2990,20 +3431,64 @@ void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationT
const nbgl_tipBox_t *tipBox,
nbgl_choiceCallback_t choiceCallback)
{
- memset(&blindSigningContext, 0, sizeof(blindSigningContext));
-
- blindSigningContext.isStreaming = false;
- blindSigningContext.operationType = operationType | BLIND_OPERATION;
- blindSigningContext.tagValueList = tagValueList;
- blindSigningContext.icon = icon;
- blindSigningContext.reviewTitle = reviewTitle;
- blindSigningContext.reviewSubTitle = reviewSubTitle;
- blindSigningContext.finishTitle = finishTitle;
- blindSigningContext.tipBox = tipBox;
- blindSigningContext.choiceCallback = choiceCallback;
+ nbgl_useCaseReviewWithWarning(operationType,
+ tagValueList,
+ icon,
+ reviewTitle,
+ reviewSubTitle,
+ finishTitle,
+ tipBox,
+ &blindSigningWarning,
+ choiceCallback);
+}
- blindSigningWarning();
+/**
+ * @brief Draws a flow of pages of a review requiring a warning page before the review.
+ * Moreover, the first and last pages of review display a top-right button, that displays more
+ * information about the warnings
+ *
+ * Navigation operates with either swipe or navigation
+ * keys at bottom right. The last page contains a long-press button with the given finishTitle and
+ * the given icon.
+ * @note All tag/value pairs are provided in the API and the number of pages is automatically
+ * computed, the last page being a long press one
+ *
+ * @param operationType type of operation (Operation, Transaction, Message)
+ * @param tagValueList list of tag/value pairs
+ * @param icon icon used on first and last review page
+ * @param reviewTitle string used in the first review page
+ * @param reviewSubTitle string to set under reviewTitle (can be NULL)
+ * @param finishTitle string used in the last review page
+ * @param tipBox parameter to build a tip-box and necessary modal (can be NULL)
+ * @param warning structure to build the initial warning page (cannot be NULL)
+ * @param choiceCallback callback called when operation is accepted (param is true) or rejected
+ * (param is false)
+ */
+void nbgl_useCaseReviewWithWarning(nbgl_operationType_t operationType,
+ const nbgl_contentTagValueList_t *tagValueList,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const char *finishTitle,
+ const nbgl_tipBox_t *tipBox,
+ const nbgl_warning_t *warning,
+ nbgl_choiceCallback_t choiceCallback)
+{
+ memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
+ reviewWithWarnCtx.isStreaming = false;
+ reviewWithWarnCtx.operationType = operationType | RISKY_OPERATION;
+ reviewWithWarnCtx.tagValueList = tagValueList;
+ reviewWithWarnCtx.icon = icon;
+ reviewWithWarnCtx.reviewTitle = reviewTitle;
+ reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
+ reviewWithWarnCtx.finishTitle = finishTitle;
+ reviewWithWarnCtx.tipBox = tipBox;
+ reviewWithWarnCtx.warning = warning;
+ reviewWithWarnCtx.choiceCallback = choiceCallback;
+
+ displayInitialWarning();
}
+
/**
* @brief Draws a flow of pages of a light review. Navigation operates with either swipe or
* navigation keys at bottom right. The last page contains a button/footer with the given
@@ -3117,16 +3602,43 @@ void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t ope
const char *reviewSubTitle,
nbgl_choiceCallback_t choiceCallback)
{
- memset(&blindSigningContext, 0, sizeof(blindSigningContext));
+ nbgl_useCaseReviewStreamingWithWarningStart(
+ operationType, icon, reviewTitle, reviewSubTitle, &blindSigningWarning, choiceCallback);
+}
+
+/**
+ * @brief Start drawing the flow of pages of a blind-signing review. The review is preceded by a
+ * warning page
+ * @note This should be followed by calls to @ref nbgl_useCaseReviewStreamingContinue and finally
+ * to
+ * @ref nbgl_useCaseReviewStreamingFinish.
+ *
+ * @param operationType type of operation (Operation, Transaction, Message)
+ * @param icon icon used on first and last review page
+ * @param reviewTitle string used in the first review page
+ * @param reviewSubTitle string to set under reviewTitle (can be NULL)
+ * @param warning structure to build the initial warning page (cannot be NULL)
+ * @param choiceCallback callback called when more operation data are needed (param is true) or
+ * operation is rejected (param is false)
+ */
+void nbgl_useCaseReviewStreamingWithWarningStart(nbgl_operationType_t operationType,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const nbgl_warning_t *warning,
+ nbgl_choiceCallback_t choiceCallback)
+{
+ memset(&reviewWithWarnCtx, 0, sizeof(reviewWithWarnCtx));
- blindSigningContext.isStreaming = true;
- blindSigningContext.operationType = operationType | BLIND_OPERATION;
- blindSigningContext.icon = icon;
- blindSigningContext.reviewTitle = reviewTitle;
- blindSigningContext.reviewSubTitle = reviewSubTitle;
- blindSigningContext.choiceCallback = choiceCallback;
+ reviewWithWarnCtx.isStreaming = true;
+ reviewWithWarnCtx.operationType = operationType | RISKY_OPERATION;
+ reviewWithWarnCtx.icon = icon;
+ reviewWithWarnCtx.reviewTitle = reviewTitle;
+ reviewWithWarnCtx.reviewSubTitle = reviewSubTitle;
+ reviewWithWarnCtx.choiceCallback = choiceCallback;
+ reviewWithWarnCtx.warning = warning;
- blindSigningWarning();
+ displayInitialWarning();
}
/**
@@ -3245,18 +3757,9 @@ void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
* @deprecated
* See #nbgl_useCaseAddressReview
*/
-DEPRECATED void nbgl_useCaseAddressConfirmation(const char *address, nbgl_choiceCallback_t callback)
-{
- nbgl_useCaseAddressConfirmationExt(address, callback, NULL);
-}
-
-/**
- * @deprecated
- * See #nbgl_useCaseAddressReview
- */
-DEPRECATED void nbgl_useCaseAddressConfirmationExt(const char *address,
- nbgl_choiceCallback_t callback,
- const nbgl_contentTagValueList_t *tagValueList)
+void nbgl_useCaseAddressConfirmationExt(const char *address,
+ nbgl_choiceCallback_t callback,
+ const nbgl_contentTagValueList_t *tagValueList)
{
reset_callbacks();
memset(&genericContext, 0, sizeof(genericContext));
diff --git a/lib_nbgl/src/nbgl_use_case_nanos.c b/lib_nbgl/src/nbgl_use_case_nanos.c
index 0b821c461..ab30fddfe 100644
--- a/lib_nbgl/src/nbgl_use_case_nanos.c
+++ b/lib_nbgl/src/nbgl_use_case_nanos.c
@@ -20,6 +20,8 @@
/*********************
* DEFINES
*********************/
+#define WITH_HORIZONTAL_CHOICES_LIST
+#define WITH_HORIZONTAL_BARS_LIST
/**********************
* TYPEDEFS
@@ -30,6 +32,7 @@ typedef struct ReviewContext_s {
const nbgl_contentTagValueList_t *tagValueList;
const nbgl_icon_details_t *icon;
const char *reviewTitle;
+ const char *reviewSubTitle;
const char *address; // for address confirmation review
} ReviewContext_t;
@@ -42,26 +45,67 @@ typedef struct ChoiceContext_s {
nbgl_choiceCallback_t onChoice;
} ChoiceContext_t;
+typedef struct ConfirmContext_s {
+ const char *message;
+ const char *subMessage;
+ const char *confirmText;
+ const char *cancelText;
+ nbgl_callback_t onConfirm;
+ nbgl_step_t currentStep;
+} ConfirmContext_t;
+
+typedef struct ContentContext_s {
+ const char *title; // For CHOICES_LIST /BARS_LIST
+ nbgl_genericContents_t genericContents;
+ const char *rejectText;
+ nbgl_layoutTouchCallback_t controlsCallback;
+ nbgl_callback_t quitCallback;
+} ContentContext_t;
+
typedef struct HomeContext_s {
const char *appName;
const nbgl_icon_details_t *appIcon;
const char *tagline;
const nbgl_genericContents_t *settingContents;
const nbgl_contentInfoList_t *infosList;
+ const nbgl_homeAction_t *homeAction;
nbgl_callback_t quitCallback;
} HomeContext_t;
+#ifdef NBGL_KEYPAD
+typedef struct KeypadContext_s {
+ uint8_t pinEntry[8];
+ uint8_t pinLen;
+ uint8_t pinMinDigits;
+ uint8_t pinMaxDigits;
+ nbgl_layout_t *layoutCtx;
+ bool hidden;
+ uint8_t keypadIndex;
+ nbgl_pinValidCallback_t validatePin;
+ nbgl_callback_t backCallback;
+} KeypadContext_t;
+#endif
+
typedef enum {
NONE_USE_CASE,
+ SPINNER_USE_CASE,
REVIEW_USE_CASE,
+ GENERIC_REVIEW_USE_CASE,
+ REVIEW_BLIND_SIGN_USE_CASE,
ADDRESS_REVIEW_USE_CASE,
+ STREAMING_BLIND_SIGN_START_REVIEW_USE_CASE,
STREAMING_START_REVIEW_USE_CASE,
STREAMING_CONTINUE_REVIEW_USE_CASE,
STREAMING_FINISH_REVIEW_USE_CASE,
CHOICE_USE_CASE,
+ STATUS_USE_CASE,
+ CONFIRM_USE_CASE,
+ KEYPAD_USE_CASE,
HOME_USE_CASE,
INFO_USE_CASE,
SETTINGS_USE_CASE,
+ GENERIC_SETTINGS,
+ CONTENT_USE_CASE,
} ContextType_t;
typedef struct UseCaseContext_s {
@@ -71,9 +115,14 @@ typedef struct UseCaseContext_s {
nbgl_stepCallback_t
stepCallback; ///< if not NULL, function to be called on "double-key" action
union {
- ReviewContext_t review;
- ChoiceContext_t choice;
- HomeContext_t home;
+ ReviewContext_t review;
+ ChoiceContext_t choice;
+ ConfirmContext_t confirm;
+ HomeContext_t home;
+ ContentContext_t content;
+#ifdef NBGL_KEYPAD
+ KeypadContext_t keypad;
+#endif
};
} UseCaseContext_t;
@@ -91,22 +140,36 @@ static void displayHomePage(nbgl_stepPosition_t pos);
static void displayInfoPage(nbgl_stepPosition_t pos);
static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state);
static void displayChoicePage(nbgl_stepPosition_t pos);
+static void displayConfirm(nbgl_stepPosition_t pos);
+static void displayContent(nbgl_stepPosition_t pos, bool toogle_state);
+static void displaySpinner(const char *text);
static void startUseCaseHome(void);
static void startUseCaseInfo(void);
static void startUseCaseSettings(void);
static void startUseCaseSettingsAtPage(uint8_t initSettingPage);
+static void startUseCaseContent(void);
+
+static void statusTickerCallback(void);
// Simple helper to get the number of elements inside a nbgl_content_t
static uint8_t getContentNbElement(const nbgl_content_t *content)
{
switch (content->type) {
+ case CENTERED_INFO:
+ return 1;
+ case INFO_BUTTON:
+ return 1;
case TAG_VALUE_LIST:
return content->content.tagValueList.nbPairs;
case SWITCHES_LIST:
return content->content.switchesList.nbSwitches;
case INFOS_LIST:
return content->content.infosList.nbInfos;
+ case CHOICES_LIST:
+ return content->content.choicesList.nbChoices;
+ case BARS_LIST:
+ return content->content.barsList.nbBars;
default:
return 0;
}
@@ -124,6 +187,10 @@ static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *gener
}
if (genericContents->callbackCallNeeded) {
+ if (content == NULL) {
+ LOG_DEBUG(USE_CASE_LOGGER, "Invalid content variable\n");
+ return NULL;
+ }
// Retrieve content through callback, but first memset the content.
memset(content, 0, sizeof(nbgl_content_t));
genericContents->contentGetterCallback(contentIdx, content);
@@ -137,15 +204,28 @@ static const nbgl_content_t *getContentAtIdx(const nbgl_genericContents_t *gener
// Helper to retrieve the content inside a nbgl_genericContents_t using
// either the contentsList or using the contentGetterCallback
-static const nbgl_content_t *getContentElemAtIdx(const nbgl_genericContents_t *genericContents,
- uint8_t elemIdx,
- uint8_t *elemContentIdx,
- nbgl_content_t *content)
+static const nbgl_content_t *getContentElemAtIdx(uint8_t elemIdx,
+ uint8_t *elemContentIdx,
+ nbgl_content_t *content)
{
- const nbgl_content_t *p_content;
- uint8_t nbPages = 0;
- uint8_t elemNbPages = 0;
-
+ const nbgl_genericContents_t *genericContents = NULL;
+ const nbgl_content_t *p_content = NULL;
+ uint8_t nbPages = 0;
+ uint8_t elemNbPages = 0;
+
+ switch (context.type) {
+ case SETTINGS_USE_CASE:
+ case HOME_USE_CASE:
+ case GENERIC_SETTINGS:
+ genericContents = context.home.settingContents;
+ break;
+ case CONTENT_USE_CASE:
+ case GENERIC_REVIEW_USE_CASE:
+ genericContents = &context.content.genericContents;
+ break;
+ default:
+ return NULL;
+ }
for (int i = 0; i < genericContents->nbContents; i++) {
p_content = getContentAtIdx(genericContents, i, content);
elemNbPages = getContentNbElement(p_content);
@@ -159,6 +239,80 @@ static const nbgl_content_t *getContentElemAtIdx(const nbgl_genericContents_t *g
return p_content;
}
+static const char *getChoiceName(uint8_t choiceIndex)
+{
+ uint8_t elemIdx;
+ uint8_t nbValues;
+ const nbgl_content_t *p_content = NULL;
+ nbgl_content_t content = {0};
+ nbgl_contentRadioChoice_t *contentChoices = NULL;
+ nbgl_contentBarsList_t *contentBars = NULL;
+ char **names = NULL;
+
+ p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
+ if (p_content == NULL) {
+ return NULL;
+ }
+ switch (p_content->type) {
+ case CHOICES_LIST:
+ contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
+ names = (char **) PIC(contentChoices->names);
+ nbValues = contentChoices->nbChoices;
+ break;
+ case BARS_LIST:
+ contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
+ names = (char **) PIC(contentBars->barTexts);
+ nbValues = contentBars->nbBars;
+ break;
+ default:
+ // Not supported as vertical MenuList
+ return NULL;
+ }
+ if (choiceIndex >= nbValues) {
+ // Last item is always "Back" button
+ return "Back";
+ }
+ return (const char *) PIC(names[choiceIndex]);
+}
+
+static void onChoiceSelected(uint8_t choiceIndex)
+{
+ uint8_t elemIdx;
+ uint8_t token = 255;
+ const nbgl_content_t *p_content = NULL;
+ nbgl_content_t content = {0};
+ nbgl_contentRadioChoice_t *contentChoices = NULL;
+ nbgl_contentBarsList_t *contentBars = NULL;
+
+ p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
+ if (p_content == NULL) {
+ return;
+ }
+ switch (p_content->type) {
+ case CHOICES_LIST:
+ contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
+ if (choiceIndex < contentChoices->nbChoices) {
+ token = contentChoices->token;
+ }
+ break;
+ case BARS_LIST:
+ contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
+ if (choiceIndex < contentBars->nbBars) {
+ token = contentBars->tokens[choiceIndex];
+ }
+ break;
+ default:
+ // Not supported as vertical MenuList
+ break;
+ }
+ if ((token != 255) && (context.content.controlsCallback != NULL)) {
+ context.content.controlsCallback(token, 0);
+ }
+ else if (context.content.quitCallback != NULL) {
+ context.content.quitCallback();
+ }
+}
+
static void getPairData(const nbgl_contentTagValueList_t *tagValueList,
uint8_t index,
const char **item,
@@ -204,42 +358,118 @@ static void onChoiceReject(void)
}
}
-static void onSettingsAction(void)
+static void onConfirmAccept(void)
+{
+ if (context.confirm.currentStep) {
+ nbgl_stepRelease(context.confirm.currentStep);
+ }
+ if (context.confirm.onConfirm) {
+ context.confirm.onConfirm();
+ }
+}
+
+static void onConfirmReject(void)
{
- nbgl_content_t content;
- uint8_t elemIdx;
+ if (context.confirm.currentStep) {
+ nbgl_stepRelease(context.confirm.currentStep);
+ nbgl_screenRedraw();
+ nbgl_refresh();
+ }
+}
- const nbgl_content_t *p_content = getContentElemAtIdx(
- context.home.settingContents, context.currentPage, &elemIdx, &content);
+static void onSwitchAction(void)
+{
+ const nbgl_contentSwitch_t *contentSwitch = NULL;
+ const nbgl_content_t *p_content = NULL;
+ nbgl_content_t content = {0};
+ uint8_t elemIdx;
- switch (p_content->type) {
- case SWITCHES_LIST: {
- const nbgl_contentSwitch_t *contentSwitch = &((const nbgl_contentSwitch_t *) PIC(
- p_content->content.switchesList.switches))[elemIdx];
- nbgl_state_t state = (contentSwitch->initState == ON_STATE) ? OFF_STATE : ON_STATE;
+ p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
+ if ((p_content == NULL) || (p_content->type != SWITCHES_LIST)) {
+ return;
+ }
+ contentSwitch
+ = &((const nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
+ switch (context.type) {
+ case SETTINGS_USE_CASE:
+ case HOME_USE_CASE:
+ case GENERIC_SETTINGS:
displaySettingsPage(FORWARD_DIRECTION, true);
- if (p_content->contentActionCallback != NULL) {
- nbgl_contentActionCallback_t onContentAction
- = PIC(p_content->contentActionCallback);
- onContentAction(contentSwitch->token, state, context.currentPage);
- }
break;
- }
+ case CONTENT_USE_CASE:
+ case GENERIC_REVIEW_USE_CASE:
+ displayContent(FORWARD_DIRECTION, true);
+ break;
default:
break;
}
+ if (p_content->contentActionCallback != NULL) {
+ nbgl_contentActionCallback_t actionCallback = PIC(p_content->contentActionCallback);
+ actionCallback(contentSwitch->token, 0, context.currentPage);
+ }
+ else if (context.content.controlsCallback != NULL) {
+ context.content.controlsCallback(contentSwitch->token, 0);
+ }
}
static void drawStep(nbgl_stepPosition_t pos,
const nbgl_icon_details_t *icon,
const char *txt,
const char *subTxt,
- nbgl_stepButtonCallback_t onActionCallback)
+ nbgl_stepButtonCallback_t onActionCallback,
+ bool modal)
{
+ uint8_t elemIdx;
+ nbgl_step_t newStep = NULL;
+ const nbgl_content_t *p_content = NULL;
+ nbgl_content_t content = {0};
+ nbgl_contentRadioChoice_t *contentChoices = NULL;
+ nbgl_contentBarsList_t *contentBars = NULL;
+ nbgl_screenTickerConfiguration_t *p_ticker = NULL;
+ nbgl_layoutMenuList_t list = {0};
+ nbgl_screenTickerConfiguration_t ticker = {
+ .tickerCallback = PIC(statusTickerCallback),
+ .tickerIntervale = 0, // not periodic
+ .tickerValue = 3000 // 3 seconds
+ };
+
pos |= GET_POS_OF_STEP(context.currentPage, context.nbPages);
- if (icon == NULL) {
- nbgl_stepDrawText(pos, onActionCallback, NULL, txt, subTxt, BOLD_TEXT1_INFO, false);
+ if ((context.type == STATUS_USE_CASE) || (context.type == SPINNER_USE_CASE)) {
+ p_ticker = &ticker;
+ }
+ if ((context.type == CONFIRM_USE_CASE) && (context.confirm.currentStep != NULL)) {
+ nbgl_stepRelease(context.confirm.currentStep);
+ }
+
+ if (txt == NULL) {
+ p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
+ if (p_content) {
+ switch (p_content->type) {
+ case CHOICES_LIST:
+ contentChoices
+ = ((nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList));
+ list.nbChoices = contentChoices->nbChoices + 1; // For Back button
+ list.selectedChoice = contentChoices->initChoice;
+ list.callback = getChoiceName;
+ newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
+ break;
+ case BARS_LIST:
+ contentBars = ((nbgl_contentBarsList_t *) PIC(&p_content->content.barsList));
+ list.nbChoices = contentBars->nbBars + 1; // For Back button
+ list.selectedChoice = 0;
+ list.callback = getChoiceName;
+ newStep = nbgl_stepDrawMenuList(onChoiceSelected, p_ticker, &list, modal);
+ break;
+ default:
+ // Not supported as vertical MenuList
+ break;
+ }
+ }
+ }
+ else if (icon == NULL) {
+ newStep = nbgl_stepDrawText(
+ pos, onActionCallback, p_ticker, txt, subTxt, BOLD_TEXT1_INFO, modal);
}
else {
nbgl_layoutCenteredInfo_t info;
@@ -248,12 +478,21 @@ static void drawStep(nbgl_stepPosition_t pos,
info.text2 = subTxt;
info.onTop = false;
info.style = BOLD_TEXT1_INFO;
- nbgl_stepDrawCenteredInfo(pos, onActionCallback, NULL, &info, false);
+ newStep = nbgl_stepDrawCenteredInfo(pos, onActionCallback, p_ticker, &info, modal);
+ }
+ if (context.type == CONFIRM_USE_CASE) {
+ context.confirm.currentStep = newStep;
}
}
static bool buttonGenericCallback(nbgl_buttonEvent_t event, nbgl_stepPosition_t *pos)
{
+ uint8_t elemIdx;
+ uint8_t token = 0;
+ uint8_t index = 0;
+ const nbgl_content_t *p_content = NULL;
+ nbgl_content_t content = {0};
+
if (event == BUTTON_LEFT_PRESSED) {
if (context.currentPage > 0) {
context.currentPage--;
@@ -275,8 +514,43 @@ static bool buttonGenericCallback(nbgl_buttonEvent_t event, nbgl_stepPosition_t
*pos = FORWARD_DIRECTION;
}
else {
- if ((event == BUTTON_BOTH_PRESSED) && (context.stepCallback != NULL)) {
- context.stepCallback();
+ if (event == BUTTON_BOTH_PRESSED) {
+ if (context.stepCallback != NULL) {
+ context.stepCallback();
+ }
+ else if ((context.type == CONTENT_USE_CASE)
+ || (context.type == GENERIC_REVIEW_USE_CASE)) {
+ p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
+ if (p_content != NULL) {
+ switch (p_content->type) {
+ case CENTERED_INFO:
+ // No associated callback
+ return false;
+ case INFO_BUTTON:
+ token = p_content->content.infoButton.buttonToken;
+ break;
+ case SWITCHES_LIST:
+ token = p_content->content.switchesList.switches->token;
+ break;
+ case BARS_LIST:
+ token = p_content->content.barsList.tokens[context.currentPage];
+ break;
+ case CHOICES_LIST:
+ token = p_content->content.choicesList.token;
+ index = context.currentPage;
+ break;
+ default:
+ break;
+ }
+
+ if ((p_content) && (p_content->contentActionCallback != NULL)) {
+ p_content->contentActionCallback(token, 0, context.currentPage);
+ }
+ else if (context.content.controlsCallback != NULL) {
+ context.content.controlsCallback(token, index);
+ }
+ }
+ }
}
return false;
}
@@ -355,6 +629,18 @@ static void genericChoiceCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
displayChoicePage(pos);
}
+static void genericConfirmCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
+{
+ UNUSED(stepCtx);
+ nbgl_stepPosition_t pos;
+
+ if (!buttonGenericCallback(event, &pos)) {
+ return;
+ }
+
+ displayConfirm(pos);
+}
+
static void statusButtonCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
{
UNUSED(stepCtx);
@@ -365,6 +651,18 @@ static void statusButtonCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
}
}
+static void contentCallback(nbgl_step_t stepCtx, nbgl_buttonEvent_t event)
+{
+ UNUSED(stepCtx);
+ nbgl_stepPosition_t pos;
+
+ if (!buttonGenericCallback(event, &pos)) {
+ return;
+ }
+
+ displayContent(pos, false);
+}
+
// callback used for timeout
static void statusTickerCallback(void)
{
@@ -376,85 +674,171 @@ static void statusTickerCallback(void)
// function used to display the current page in review
static void displayReviewPage(nbgl_stepPosition_t pos)
{
- const char *text = NULL;
- const char *subText = NULL;
- const nbgl_icon_details_t *icon = NULL;
+ uint8_t reviewPages = 0;
+ uint8_t finalPages = 0;
+ uint8_t pairIndex = 0;
+ const char *text = NULL;
+ const char *subText = NULL;
+ const nbgl_icon_details_t *icon = NULL;
+ uint8_t currentIndex = 0;
+ uint8_t warnIndex = 255;
+ uint8_t titleIndex = 255;
+ uint8_t subIndex = 255;
+ uint8_t approveIndex = 255;
+ uint8_t rejectIndex = 255;
context.stepCallback = NULL;
- if (context.currentPage == 0) { // title page
- icon = context.review.icon;
- text = context.review.reviewTitle;
+ // Determine the 1st page to display tag/values
+ if (context.type == REVIEW_BLIND_SIGN_USE_CASE) {
+ // Warning page to display
+ warnIndex = currentIndex++;
+ reviewPages++;
}
- else if (context.currentPage == (context.nbPages - 2)) { // accept page
- icon = &C_icon_validate_14;
- text = "Approve";
- context.stepCallback = onReviewAccept;
+ // Title page to display
+ titleIndex = currentIndex++;
+ reviewPages++;
+ if (context.review.reviewSubTitle) {
+ // subtitle page to display
+ subIndex = currentIndex++;
+ reviewPages++;
}
- else if (context.currentPage == (context.nbPages - 1)) { // reject page
- icon = &C_icon_crossmark;
- text = "Reject";
- context.stepCallback = onReviewReject;
+ approveIndex = context.nbPages - 2;
+ rejectIndex = context.nbPages - 1;
+ finalPages = approveIndex;
+
+ // Determine which page to display
+ if (context.currentPage >= finalPages) {
+ if (context.currentPage == approveIndex) {
+ // Approve page
+ icon = &C_icon_validate_14;
+ text = "Approve";
+ context.stepCallback = onReviewAccept;
+ }
+ else if (context.currentPage == rejectIndex) {
+ // Reject page
+ icon = &C_icon_crossmark;
+ text = "Reject";
+ context.stepCallback = onReviewReject;
+ }
+ }
+ else if (context.currentPage < reviewPages) {
+ if (context.currentPage == warnIndex) {
+ // Blind Signing Warning page
+ icon = &C_icon_warning;
+ text = "Blind\nsigning";
+ }
+ else if (context.currentPage == titleIndex) {
+ // Title page
+ icon = context.review.icon;
+ text = context.review.reviewTitle;
+ }
+ else if (context.currentPage == subIndex) {
+ // SubTitle page
+ text = context.review.reviewSubTitle;
+ }
}
- else if ((context.review.address != NULL)
- && (context.currentPage == 1)) { // address confirmation and 2nd page
+ else if ((context.review.address != NULL) && (context.currentPage == reviewPages)) {
+ // address confirmation and 2nd page
text = "Address";
subText = context.review.address;
}
else {
- uint8_t pairIndex = (context.review.address != NULL) ? (context.currentPage - 2)
- : (context.currentPage - 1);
+ pairIndex = context.currentPage - reviewPages;
+ if (context.review.address != NULL) {
+ pairIndex--;
+ }
getPairData(context.review.tagValueList, pairIndex, &text, &subText);
}
- drawStep(pos, icon, text, subText, reviewCallback);
+ drawStep(pos, icon, text, subText, reviewCallback, false);
nbgl_refresh();
}
// function used to display the current page in review
static void displayStreamingReviewPage(nbgl_stepPosition_t pos)
{
- const char *text = NULL;
- const char *subText = NULL;
- const nbgl_icon_details_t *icon = NULL;
+ const char *text = NULL;
+ const char *subText = NULL;
+ const nbgl_icon_details_t *icon = NULL;
+ uint8_t reviewPages = 0;
+ uint8_t warnIndex = 255;
+ uint8_t titleIndex = 255;
+ uint8_t subIndex = 255;
context.stepCallback = NULL;
- if (context.type == STREAMING_START_REVIEW_USE_CASE) {
- if (context.currentPage == 0) { // title page
- icon = context.review.icon;
- text = context.review.reviewTitle;
- }
- else {
- nbgl_useCaseSpinner("Processing");
- onReviewAccept();
- return;
- }
- }
- else if (context.type == STREAMING_CONTINUE_REVIEW_USE_CASE) {
- if (context.currentPage < context.review.tagValueList->nbPairs) {
+ switch (context.type) {
+ case STREAMING_START_REVIEW_USE_CASE:
+ case STREAMING_BLIND_SIGN_START_REVIEW_USE_CASE:
+ if (context.type == STREAMING_START_REVIEW_USE_CASE) {
+ // Title page to display
+ titleIndex = reviewPages++;
+ if (context.review.reviewSubTitle) {
+ // subtitle page to display
+ subIndex = reviewPages++;
+ }
+ }
+ else {
+ // warning page to display
+ warnIndex = reviewPages++;
+ // Title page to display
+ titleIndex = reviewPages++;
+ if (context.review.reviewSubTitle) {
+ // subtitle page to display
+ subIndex = reviewPages++;
+ }
+ }
+ // Determine which page to display
+ if (context.currentPage >= reviewPages) {
+ displaySpinner("Processing");
+ onReviewAccept();
+ return;
+ }
+ // header page(s)
+ if (context.currentPage == warnIndex) {
+ // warning page
+ icon = &C_icon_warning;
+ text = "Blind\nsigning";
+ }
+ else if (context.currentPage == titleIndex) {
+ // title page
+ icon = context.review.icon;
+ text = context.review.reviewTitle;
+ }
+ else if (context.currentPage == subIndex) {
+ // subtitle page
+ text = context.review.reviewSubTitle;
+ }
+ break;
+
+ case STREAMING_CONTINUE_REVIEW_USE_CASE:
+ if (context.currentPage >= context.review.tagValueList->nbPairs) {
+ displaySpinner("Processing");
+ onReviewAccept();
+ return;
+ }
getPairData(context.review.tagValueList, context.currentPage, &text, &subText);
- }
- else {
- nbgl_useCaseSpinner("Processing");
- onReviewAccept();
- return;
- }
- }
- else {
- if (context.currentPage == 0) { // accept page
- icon = &C_icon_validate_14;
- text = "Approve";
- context.stepCallback = onReviewAccept;
- }
- else { // reject page
- icon = &C_icon_crossmark;
- text = "Reject";
- context.stepCallback = onReviewReject;
- }
+ break;
+
+ case STREAMING_FINISH_REVIEW_USE_CASE:
+ default:
+ if (context.currentPage == 0) {
+ // accept page
+ icon = &C_icon_validate_14;
+ text = "Approve";
+ context.stepCallback = onReviewAccept;
+ }
+ else {
+ // reject page
+ icon = &C_icon_crossmark;
+ text = "Reject";
+ context.stepCallback = onReviewReject;
+ }
+ break;
}
- drawStep(pos, icon, text, subText, streamingReviewCallback);
+ drawStep(pos, icon, text, subText, streamingReviewCallback, false);
nbgl_refresh();
}
@@ -479,10 +863,95 @@ static void displayInfoPage(nbgl_stepPosition_t pos)
context.stepCallback = startUseCaseHome;
}
- drawStep(pos, icon, text, subText, infoCallback);
+ drawStep(pos, icon, text, subText, infoCallback, false);
nbgl_refresh();
}
+// function used to get the current page content
+static void getContentPage(bool toogle_state,
+ const char **text,
+ const char **subText,
+ const nbgl_icon_details_t **icon)
+{
+ static char fullText[75];
+ uint8_t elemIdx;
+ nbgl_state_t state = OFF_STATE;
+ const nbgl_content_t *p_content = NULL;
+ nbgl_content_t content = {0};
+ nbgl_contentSwitch_t *contentSwitch = NULL;
+#ifdef WITH_HORIZONTAL_CHOICES_LIST
+ nbgl_contentRadioChoice_t *contentChoices = NULL;
+ char **names = NULL;
+#endif
+
+ p_content = getContentElemAtIdx(context.currentPage, &elemIdx, &content);
+ if (p_content == NULL) {
+ return;
+ }
+ switch (p_content->type) {
+ case CENTERED_INFO:
+ *text = PIC(p_content->content.centeredInfo.text1);
+ *subText = PIC(p_content->content.centeredInfo.text2);
+ break;
+ case INFO_BUTTON:
+ *icon = PIC(p_content->content.infoButton.icon);
+ *text = PIC(p_content->content.infoButton.text);
+ *subText = PIC(p_content->content.infoButton.buttonText);
+ break;
+ case TAG_VALUE_LIST:
+ getPairData(&p_content->content.tagValueList, elemIdx, text, subText);
+ break;
+ case SWITCHES_LIST:
+ contentSwitch = &(
+ (nbgl_contentSwitch_t *) PIC(p_content->content.switchesList.switches))[elemIdx];
+ *text = contentSwitch->text;
+ state = contentSwitch->initState;
+ if (toogle_state) {
+ state = (state == ON_STATE) ? OFF_STATE : ON_STATE;
+ }
+ if (state == ON_STATE) {
+ snprintf(fullText, sizeof(fullText), "%s\nEnabled", contentSwitch->subText);
+ }
+ else {
+ snprintf(fullText, sizeof(fullText), "%s\nDisabled", contentSwitch->subText);
+ }
+ context.stepCallback = onSwitchAction;
+ *subText = fullText;
+ break;
+ case INFOS_LIST:
+ *text = ((const char *const *) PIC(p_content->content.infosList.infoTypes))[elemIdx];
+ *subText
+ = ((const char *const *) PIC(p_content->content.infosList.infoContents))[elemIdx];
+ break;
+ case CHOICES_LIST:
+#ifdef WITH_HORIZONTAL_CHOICES_LIST
+ if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
+ *text = PIC(context.content.title);
+ *subText = PIC(p_content->content.choicesList.names[elemIdx]);
+ }
+ else {
+ contentChoices = (nbgl_contentRadioChoice_t *) PIC(&p_content->content.choicesList);
+ names = (char **) PIC(contentChoices->names);
+ *text = (const char *) PIC(names[elemIdx]);
+ }
+#endif
+ break;
+ case BARS_LIST:
+#ifdef WITH_HORIZONTAL_BARS_LIST
+ if ((context.type == CONTENT_USE_CASE) && (context.content.title != NULL)) {
+ *text = PIC(context.content.title);
+ *subText = PIC(p_content->content.barsList.barTexts[elemIdx]);
+ }
+ else {
+ *text = PIC(p_content->content.barsList.barTexts[elemIdx]);
+ }
+#endif
+ break;
+ default:
+ break;
+ }
+}
+
// function used to display the current page in settings
static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state)
{
@@ -493,68 +962,56 @@ static void displaySettingsPage(nbgl_stepPosition_t pos, bool toogle_state)
context.stepCallback = NULL;
if (context.currentPage < (context.nbPages - 1)) {
- nbgl_content_t nbgl_content;
- uint8_t elemIdx;
-
- const nbgl_content_t *p_nbgl_content = getContentElemAtIdx(
- context.home.settingContents, context.currentPage, &elemIdx, &nbgl_content);
-
- switch (p_nbgl_content->type) {
- case TAG_VALUE_LIST:
- getPairData(&p_nbgl_content->content.tagValueList, elemIdx, &text, &subText);
- break;
- case SWITCHES_LIST: {
- const nbgl_contentSwitch_t *contentSwitch = &((const nbgl_contentSwitch_t *) PIC(
- p_nbgl_content->content.switchesList.switches))[elemIdx];
- text = contentSwitch->text;
- // switch subtext is ignored
- nbgl_state_t state = contentSwitch->initState;
- if (toogle_state) {
- state = (state == ON_STATE) ? OFF_STATE : ON_STATE;
- }
- if (state == ON_STATE) {
- subText = "Enabled";
- }
- else {
- subText = "Disabled";
- }
- context.stepCallback = onSettingsAction;
- break;
- }
- case INFOS_LIST:
- text = ((const char *const *) PIC(
- p_nbgl_content->content.infosList.infoTypes))[elemIdx];
- subText = ((const char *const *) PIC(
- p_nbgl_content->content.infosList.infoContents))[elemIdx];
- break;
- default:
- break;
- }
+ getContentPage(toogle_state, &text, &subText, &icon);
}
else { // last page is for quit
- icon = &C_icon_back_x;
- text = "Back";
- context.stepCallback = startUseCaseHome;
+ icon = &C_icon_back_x;
+ text = "Back";
+ if (context.type == GENERIC_SETTINGS) {
+ context.stepCallback = context.home.quitCallback;
+ }
+ else {
+ context.stepCallback = startUseCaseHome;
+ }
}
- drawStep(pos, icon, text, subText, settingsCallback);
+ drawStep(pos, icon, text, subText, settingsCallback, false);
nbgl_refresh();
}
static void startUseCaseHome(void)
{
- if (context.type == SETTINGS_USE_CASE) {
- context.currentPage = 1;
+ int8_t addPages = 0;
+ if (context.home.homeAction) {
+ // Action page index
+ addPages++;
}
- else if (context.type == INFO_USE_CASE) {
- context.currentPage = 2;
- }
- else {
- context.currentPage = 0;
+ switch (context.type) {
+ case SETTINGS_USE_CASE:
+ // Settings page index
+ context.currentPage = 1 + addPages;
+ break;
+ case INFO_USE_CASE:
+ // Info page index
+ context.currentPage = 2 + addPages;
+ break;
+ default:
+ // Home page index
+ context.currentPage = 0;
+ break;
}
- context.type = HOME_USE_CASE;
- context.nbPages = 4;
+ context.type = HOME_USE_CASE;
+ context.nbPages = 2; // Home + Quit
+ if (context.home.settingContents) {
+ context.nbPages++;
+ }
+ if (context.home.infosList) {
+ context.nbPages++;
+ }
+ if (context.home.homeAction) {
+ context.nbPages += addPages;
+ }
displayHomePage(FORWARD_DIRECTION);
}
@@ -569,10 +1026,13 @@ static void startUseCaseInfo(void)
static void startUseCaseSettingsAtPage(uint8_t initSettingPage)
{
- nbgl_content_t content;
- const nbgl_content_t *p_content;
+ nbgl_content_t content = {0};
+ const nbgl_content_t *p_content = NULL;
- context.type = SETTINGS_USE_CASE;
+ if (context.type == 0) {
+ // Not yet init, it is not a GENERIC_SETTINGS
+ context.type = SETTINGS_USE_CASE;
+ }
context.nbPages = 1; // For back screen
for (int i = 0; i < context.home.settingContents->nbContents; i++) {
p_content = getContentAtIdx(context.home.settingContents, i, &content);
@@ -588,70 +1048,84 @@ static void startUseCaseSettings(void)
startUseCaseSettingsAtPage(0);
}
+static void startUseCaseContent(void)
+{
+ uint8_t contentIdx = 0;
+ const nbgl_content_t *p_content = NULL;
+ nbgl_content_t content = {0};
+
+ context.nbPages = 1; // Quit
+
+ for (contentIdx = 0; contentIdx < context.content.genericContents.nbContents; contentIdx++) {
+ p_content = getContentAtIdx(&context.content.genericContents, contentIdx, &content);
+ context.nbPages += getContentNbElement(p_content);
+ }
+
+ displayContent(FORWARD_DIRECTION, false);
+}
+
// function used to display the current page in home
static void displayHomePage(nbgl_stepPosition_t pos)
{
- const char *text = NULL;
- const char *subText = NULL;
- const nbgl_icon_details_t *icon = NULL;
+ const char *text = NULL;
+ const char *subText = NULL;
+ const nbgl_icon_details_t *icon = NULL;
+ uint8_t currentIndex = 0;
+ uint8_t homeIndex = 255;
+ uint8_t actionIndex = 255;
+ uint8_t settingsIndex = 255;
+ uint8_t infoIndex = 255;
context.stepCallback = NULL;
- // Handle case where there is no settings
- if (context.home.settingContents == NULL && context.currentPage == 1) {
- if (pos & BACKWARD_DIRECTION) {
- context.currentPage -= 1;
- }
- else {
- context.currentPage += 1;
- if (context.home.infosList == NULL) {
- context.currentPage += 1;
- }
- }
+ // Determine which pages are present
+ homeIndex = currentIndex++;
+ if (context.home.homeAction) {
+ actionIndex = currentIndex++;
+ }
+ if (context.home.settingContents) {
+ settingsIndex = currentIndex++;
+ }
+ if (context.home.infosList) {
+ infoIndex = currentIndex++;
}
- // Handle case where there is no info
- if (context.home.infosList == NULL && context.currentPage == 2) {
- if (pos & BACKWARD_DIRECTION) {
- context.currentPage -= 1;
- if (context.home.settingContents == NULL) {
- context.currentPage -= 1;
- }
+ if (context.currentPage == homeIndex) {
+ // Home page
+ icon = context.home.appIcon;
+ if (context.home.tagline != NULL) {
+ text = context.home.tagline;
}
else {
- context.currentPage += 1;
+ text = context.home.appName;
+ subText = "is ready";
}
}
-
- switch (context.currentPage) {
- case 0:
- icon = context.home.appIcon;
- if (context.home.tagline != NULL) {
- text = context.home.tagline;
- }
- else {
- text = context.home.appName;
- subText = "is ready";
- }
- break;
- case 1:
- icon = &C_icon_coggle;
- text = "Settings";
- context.stepCallback = startUseCaseSettings;
- break;
- case 2:
- icon = &C_icon_certificate;
- text = "About";
- context.stepCallback = startUseCaseInfo;
- break;
- default:
- icon = &C_icon_dashboard_x;
- text = "Quit";
- context.stepCallback = context.home.quitCallback;
- break;
+ else if (context.currentPage == actionIndex) {
+ // Action page
+ icon = context.home.homeAction->icon;
+ text = PIC(context.home.homeAction->text);
+ context.stepCallback = context.home.homeAction->callback;
+ }
+ else if (context.currentPage == settingsIndex) {
+ // Settings page
+ icon = &C_icon_coggle;
+ text = "Settings";
+ context.stepCallback = startUseCaseSettings;
+ }
+ else if (context.currentPage == infoIndex) {
+ // About page
+ icon = &C_icon_certificate;
+ text = "About";
+ context.stepCallback = startUseCaseInfo;
+ }
+ else {
+ icon = &C_icon_dashboard_x;
+ text = "Quit";
+ context.stepCallback = context.home.quitCallback;
}
- drawStep(pos, icon, text, subText, homeCallback);
+ drawStep(pos, icon, text, subText, homeCallback, false);
nbgl_refresh();
}
@@ -700,15 +1174,437 @@ static void displayChoicePage(nbgl_stepPosition_t pos)
context.stepCallback = onChoiceReject;
}
- drawStep(pos, icon, text, subText, genericChoiceCallback);
+ drawStep(pos, icon, text, subText, genericChoiceCallback, false);
nbgl_refresh();
}
-/**********************
- * GLOBAL FUNCTIONS
- **********************/
-
-/**
+// function used to display the Confirm page
+static void displayConfirm(nbgl_stepPosition_t pos)
+{
+ const char *text = NULL;
+ const char *subText = NULL;
+ const nbgl_icon_details_t *icon = NULL;
+
+ context.stepCallback = NULL;
+ switch (context.currentPage) {
+ case 0:
+ // title page
+ text = context.confirm.message;
+ subText = context.confirm.subMessage;
+ break;
+ case 1:
+ // confirm page
+ icon = &C_icon_validate_14;
+ text = context.confirm.confirmText;
+ context.stepCallback = onConfirmAccept;
+ break;
+ case 2:
+ // cancel page
+ icon = &C_icon_crossmark;
+ text = context.confirm.cancelText;
+ context.stepCallback = onConfirmReject;
+ break;
+ }
+
+ drawStep(pos, icon, text, subText, genericConfirmCallback, true);
+ nbgl_refresh();
+}
+
+// function used to display the current navigable content
+static void displayContent(nbgl_stepPosition_t pos, bool toogle_state)
+{
+ const char *text = NULL;
+ const char *subText = NULL;
+ const nbgl_icon_details_t *icon = NULL;
+
+ context.stepCallback = NULL;
+
+ if (context.currentPage < (context.nbPages - 1)) {
+ getContentPage(toogle_state, &text, &subText, &icon);
+ }
+ else { // last page is for quit
+ if (context.content.rejectText) {
+ text = context.content.rejectText;
+ }
+ else {
+ text = "Back";
+ }
+ if (context.type == GENERIC_REVIEW_USE_CASE) {
+ icon = &C_icon_crossmark;
+ }
+ else {
+ icon = &C_icon_back_x;
+ }
+ context.stepCallback = context.content.quitCallback;
+ }
+
+ drawStep(pos, icon, text, subText, contentCallback, false);
+ nbgl_refresh();
+}
+
+static void displaySpinner(const char *text)
+{
+ drawStep(SINGLE_STEP, &C_icon_processing, text, NULL, NULL, false);
+ nbgl_refresh();
+}
+
+// function to factorize code for all simple reviews
+static void useCaseReview(ContextType_t type,
+ const nbgl_contentTagValueList_t *tagValueList,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const char *finishTitle,
+ nbgl_choiceCallback_t choiceCallback)
+{
+ UNUSED(finishTitle); // TODO dedicated screen for it?
+
+ memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = type;
+ context.review.tagValueList = tagValueList;
+ context.review.reviewTitle = reviewTitle;
+ context.review.reviewSubTitle = reviewSubTitle;
+ context.review.icon = icon;
+ context.review.onChoice = choiceCallback;
+ context.currentPage = 0;
+ // 1 page for title and 2 pages at the end for accept/reject
+ context.nbPages = tagValueList->nbPairs + 3;
+ if (type == REVIEW_BLIND_SIGN_USE_CASE) {
+ context.nbPages++; // 1 page for warning
+ }
+ if (reviewSubTitle) {
+ context.nbPages++; // 1 page for subtitle page
+ }
+
+ displayReviewPage(FORWARD_DIRECTION);
+}
+
+#ifdef NBGL_KEYPAD
+static void setPinCodeText(void)
+{
+ bool enableValidate = false;
+ bool enableBackspace = true;
+
+ // pin can be validated when min digits is entered
+ enableValidate = (context.keypad.pinLen >= context.keypad.pinMinDigits);
+ // backspace is disabled when no digit is entered and back vallback is not provided
+ enableBackspace = (context.keypad.pinLen > 0) || (context.keypad.backCallback != NULL);
+ nbgl_layoutUpdateKeypadContent(context.keypad.layoutCtx,
+ context.keypad.hidden,
+ context.keypad.pinLen,
+ (const char *) context.keypad.pinEntry);
+ nbgl_layoutUpdateKeypad(
+ context.keypad.layoutCtx, context.keypad.keypadIndex, enableValidate, enableBackspace);
+ nbgl_layoutDraw(context.keypad.layoutCtx);
+ nbgl_refresh();
+}
+
+// called when a key is touched on the keypad
+static void keypadCallback(char touchedKey)
+{
+ switch (touchedKey) {
+ case BACKSPACE_KEY:
+ if (context.keypad.pinLen > 0) {
+ context.keypad.pinLen--;
+ context.keypad.pinEntry[context.keypad.pinLen] = 0;
+ }
+ else if (context.keypad.backCallback != NULL) {
+ context.keypad.backCallback();
+ break;
+ }
+ setPinCodeText();
+ break;
+
+ case VALIDATE_KEY:
+ context.keypad.validatePin(context.keypad.pinEntry, context.keypad.pinLen);
+ break;
+
+ default:
+ if ((touchedKey >= 0x30) && (touchedKey < 0x40)) {
+ if (context.keypad.pinLen < context.keypad.pinMaxDigits) {
+ context.keypad.pinEntry[context.keypad.pinLen] = touchedKey;
+ context.keypad.pinLen++;
+ }
+ setPinCodeText();
+ }
+ break;
+ }
+}
+
+// called to create a keypad, with either hidden or visible digits
+static void keypadGenericUseCase(const char *title,
+ uint8_t minDigits,
+ uint8_t maxDigits,
+ bool shuffled,
+ bool hidden,
+ nbgl_pinValidCallback_t validatePinCallback,
+ nbgl_callback_t backCallback)
+{
+ nbgl_layoutDescription_t layoutDescription = {0};
+ int status = -1;
+
+ // reset the keypad context
+ memset(&context, 0, sizeof(KeypadContext_t));
+ context.type = KEYPAD_USE_CASE;
+ context.currentPage = 0;
+ context.nbPages = 1;
+ context.keypad.validatePin = validatePinCallback;
+ context.keypad.backCallback = backCallback;
+ context.keypad.pinMinDigits = minDigits;
+ context.keypad.pinMaxDigits = maxDigits;
+ context.keypad.hidden = hidden;
+ context.keypad.layoutCtx = nbgl_layoutGet(&layoutDescription);
+
+ // add keypad
+ status = nbgl_layoutAddKeypad(context.keypad.layoutCtx, keypadCallback, title, shuffled);
+ if (status < 0) {
+ return;
+ }
+ context.keypad.keypadIndex = status;
+ // add digits
+ status = nbgl_layoutAddKeypadContent(context.keypad.layoutCtx, hidden, maxDigits, "");
+ if (status < 0) {
+ return;
+ }
+
+ nbgl_layoutDraw(context.keypad.layoutCtx);
+ if (context.keypad.backCallback != NULL) {
+ // force backspace to be visible at first digit, to be used as quit
+ nbgl_layoutUpdateKeypad(context.keypad.layoutCtx, context.keypad.keypadIndex, false, true);
+ }
+ nbgl_refresh();
+}
+#endif // NBGL_KEYPAD
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**
+ * @brief with Nano Screen, only a single tag/value pair is displayable in a page
+ *
+ * @param nbPairs unused
+ * @param tagValueList unused
+ * @param startIndex unused
+ * @param requireSpecificDisplay (output) set to true if the tag/value needs a specific display:
+ * - centeredInfo flag is enabled
+ * - the tag/value doesn't fit in a page
+ * @return the number of tag/value pairs fitting in a page
+ */
+uint8_t nbgl_useCaseGetNbTagValuesInPage(uint8_t nbPairs,
+ const nbgl_contentTagValueList_t *tagValueList,
+ uint8_t startIndex,
+ bool *requireSpecificDisplay)
+{
+ UNUSED(nbPairs);
+ UNUSED(tagValueList);
+ UNUSED(startIndex);
+ *requireSpecificDisplay = true;
+ return 1;
+}
+
+/**
+ * @brief with Nano Screen, only a single tag/value pair is displayable in a page
+ *
+ * @param nbPairs unused
+ * @param tagValueList unused
+ * @param startIndex unused
+ * @param isSkippable unused
+ * @param requireSpecificDisplay (output) set to true if the tag/value needs a specific display:
+ * - centeredInfo flag is enabled
+ * - the tag/value doesn't fit in a page
+ * @return the number of tag/value pairs fitting in a page
+ */
+uint8_t nbgl_useCaseGetNbTagValuesInPageExt(uint8_t nbPairs,
+ const nbgl_contentTagValueList_t *tagValueList,
+ uint8_t startIndex,
+ bool isSkippable,
+ bool *requireSpecificDisplay)
+{
+ UNUSED(nbPairs);
+ UNUSED(tagValueList);
+ UNUSED(startIndex);
+ UNUSED(isSkippable);
+ *requireSpecificDisplay = true;
+ return 1;
+}
+
+/**
+ * @brief with Nano Screen, only a single info is displayable in a page
+ *
+ * @param nbInfos unused
+ * @param infosList unused
+ * @param startIndex unused
+ * @return the number of infos fitting in a page
+ */
+uint8_t nbgl_useCaseGetNbInfosInPage(uint8_t nbInfos,
+ const nbgl_contentInfoList_t *infosList,
+ uint8_t startIndex,
+ bool withNav)
+{
+ UNUSED(nbInfos);
+ UNUSED(infosList);
+ UNUSED(startIndex);
+ UNUSED(withNav);
+ return 1;
+}
+
+/**
+ * @brief with Nano Screen, only a single switch is displayable in a page
+ *
+ * @param nbSwitches unused
+ * @param switchesList unused
+ * @param startIndex unused
+ * @return the number of switches fitting in a page
+ */
+uint8_t nbgl_useCaseGetNbSwitchesInPage(uint8_t nbSwitches,
+ const nbgl_contentSwitchesList_t *switchesList,
+ uint8_t startIndex,
+ bool withNav)
+{
+ UNUSED(nbSwitches);
+ UNUSED(switchesList);
+ UNUSED(startIndex);
+ UNUSED(withNav);
+ return 1;
+}
+
+/**
+ * @brief with Nano Screen, only a single bar is displayable in a page
+ *
+ * @param nbBars unused
+ * @param barsList unused
+ * @param startIndex unused
+ * @return the number of bars fitting in a page
+ */
+uint8_t nbgl_useCaseGetNbBarsInPage(uint8_t nbBars,
+ const nbgl_contentBarsList_t *barsList,
+ uint8_t startIndex,
+ bool withNav)
+{
+ UNUSED(nbBars);
+ UNUSED(barsList);
+ UNUSED(startIndex);
+ UNUSED(withNav);
+ return 1;
+}
+
+/**
+ * @brief with Nano Screen, only a single radio choice displayable in a page
+ *
+ * @param nbChoices unused
+ * @param choicesList unused
+ * @param startIndex unused
+ * @return the number of radio choices fitting in a page
+ */
+uint8_t nbgl_useCaseGetNbChoicesInPage(uint8_t nbChoices,
+ const nbgl_contentRadioChoice_t *choicesList,
+ uint8_t startIndex,
+ bool withNav)
+{
+ UNUSED(nbChoices);
+ UNUSED(choicesList);
+ UNUSED(startIndex);
+ UNUSED(withNav);
+ return 1;
+}
+
+/**
+ * @brief computes the number of pages necessary to display the given list of tag/value pairs
+ *
+ * @param tagValueList list of tag/value pairs
+ * @return the number of pages necessary to display the given list of tag/value pairs
+ */
+uint8_t nbgl_useCaseGetNbPagesForTagValueList(const nbgl_contentTagValueList_t *tagValueList)
+{
+ uint8_t nbPages = 0;
+ uint8_t nbPairs = tagValueList->nbPairs;
+ uint8_t nbPairsInPage;
+ uint8_t i = 0;
+ bool flag;
+
+ while (i < tagValueList->nbPairs) {
+ // upper margin
+ nbPairsInPage = nbgl_useCaseGetNbTagValuesInPageExt(nbPairs, tagValueList, i, false, &flag);
+ i += nbPairsInPage;
+ nbPairs -= nbPairsInPage;
+ nbPages++;
+ }
+ return nbPages;
+}
+
+/**
+ * @brief Initiates the drawing a set of pages of generic content, with a touchable header (usually
+ * to go back or to an upper level) For each page (including the first one), the given 'navCallback'
+ * will be called to get the content. Only 'type' and union has to be set in this content.
+ *
+ * @param title string to set in touchable title (header)
+ * @param initPage page on which to start [0->(nbPages-1)]
+ * @param nbPages number of pages
+ * @param quitCallback callback called title is pressed
+ * @param navCallback callback called when navigation arrows are pressed
+ * @param controlsCallback callback called when any controls in the settings (radios, switches) is
+ * called (the tokens must be >= @ref FIRST_USER_TOKEN)
+ */
+void nbgl_useCaseNavigableContent(const char *title,
+ uint8_t initPage,
+ uint8_t nbPages,
+ nbgl_callback_t quitCallback,
+ nbgl_navCallback_t navCallback,
+ nbgl_layoutTouchCallback_t controlsCallback)
+{
+ nbgl_pageContent_t pageContent = {0};
+ static nbgl_content_t contentsList = {0};
+
+ if (initPage >= nbPages) {
+ return;
+ }
+ // Use Callback to get the page content
+ if (navCallback(initPage, &pageContent) == false) {
+ return;
+ }
+ memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = CONTENT_USE_CASE;
+ context.content.quitCallback = quitCallback;
+ context.content.controlsCallback = controlsCallback;
+ context.content.genericContents.callbackCallNeeded = false;
+ context.content.genericContents.nbContents = nbPages;
+
+ contentsList.type = pageContent.type;
+ switch (pageContent.type) {
+ case CENTERED_INFO:
+ contentsList.content.centeredInfo = pageContent.centeredInfo;
+ break;
+ case INFO_BUTTON:
+ contentsList.content.infoButton = pageContent.infoButton;
+ break;
+ case TAG_VALUE_LIST:
+ contentsList.content.tagValueList = pageContent.tagValueList;
+ break;
+ case SWITCHES_LIST:
+ contentsList.content.switchesList = pageContent.switchesList;
+ break;
+ case INFOS_LIST:
+ contentsList.content.infosList = pageContent.infosList;
+ break;
+ case CHOICES_LIST:
+ contentsList.content.choicesList = pageContent.choicesList;
+ context.content.title = title;
+ context.currentPage = pageContent.choicesList.initChoice;
+ break;
+ case BARS_LIST:
+ contentsList.content.barsList = pageContent.barsList;
+ context.content.title = title;
+ break;
+ default:
+ break;
+ }
+ context.content.genericContents.contentsList = (const nbgl_content_t *) &contentsList;
+
+ startUseCaseContent();
+}
+
+/**
* @brief Draws the extended version of home page of an app (page on which we land when launching it
* from dashboard) with automatic support of setting display.
*
@@ -731,14 +1627,13 @@ void nbgl_useCaseHomeAndSettings(const char *appName,
const nbgl_homeAction_t *action,
nbgl_callback_t quitCallback)
{
- UNUSED(action); // TODO support it at some point?
-
memset(&context, 0, sizeof(UseCaseContext_t));
context.home.appName = appName;
context.home.appIcon = appIcon;
context.home.tagline = tagline;
context.home.settingContents = PIC(settingContents);
context.home.infosList = PIC(infosList);
+ context.home.homeAction = action;
context.home.quitCallback = quitCallback;
if (initSettingPage != INIT_HOME_PAGE) {
@@ -749,6 +1644,53 @@ void nbgl_useCaseHomeAndSettings(const char *appName,
}
}
+/**
+ * @brief Draws the settings pages of an app with automatic pagination depending on content
+ * to be displayed that is passed through settingContents and infosList
+ *
+ * @param appName string to use as title
+ * @param initPage page on which to start, can be != 0 if you want to display a specific page
+ * after a setting confirmation change or something. Then the value should be taken from the
+ * nbgl_contentActionCallback_t callback call.
+ * @param settingContents contents to be displayed
+ * @param infosList infos to be displayed (version, license, developer, ...)
+ * @param quitCallback callback called when quit button (or title) is pressed
+ */
+void nbgl_useCaseGenericSettings(const char *appName,
+ uint8_t initPage,
+ const nbgl_genericContents_t *settingContents,
+ const nbgl_contentInfoList_t *infosList,
+ nbgl_callback_t quitCallback)
+{
+ memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = GENERIC_SETTINGS;
+ context.home.appName = appName;
+ context.home.settingContents = PIC(settingContents);
+ context.home.infosList = PIC(infosList);
+ context.home.quitCallback = quitCallback;
+
+ startUseCaseSettingsAtPage(initPage);
+}
+
+/**
+ * @brief Draws a set of pages with automatic pagination depending on content
+ * to be displayed that is passed through contents.
+ *
+ * @param title string to use as title
+ * @param initPage page on which to start, can be != 0 if you want to display a specific page
+ * after a confirmation change or something. Then the value should be taken from the
+ * nbgl_contentActionCallback_t callback call.
+ * @param contents contents to be displayed
+ * @param quitCallback callback called when quit button (or title) is pressed
+ */
+void nbgl_useCaseGenericConfiguration(const char *title,
+ uint8_t initPage,
+ const nbgl_genericContents_t *contents,
+ nbgl_callback_t quitCallback)
+{
+ nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback);
+}
+
/**
* @brief Draws a flow of pages of a review.
* @note All tag/value pairs are provided in the API and the number of pages is automatically
@@ -771,21 +1713,93 @@ void nbgl_useCaseReview(nbgl_operationType_t operationType,
const char *finishTitle,
nbgl_choiceCallback_t choiceCallback)
{
- UNUSED(operationType); // TODO adapt accept and reject text depending on this value?
- UNUSED(reviewSubTitle); // TODO dedicated screen for it?
- UNUSED(finishTitle); // TODO dedicated screen for it?
+ UNUSED(operationType); // TODO adapt accept and reject text depending on this value?
+
+ useCaseReview(REVIEW_USE_CASE,
+ tagValueList,
+ icon,
+ reviewTitle,
+ reviewSubTitle,
+ finishTitle,
+ choiceCallback);
+}
- memset(&context, 0, sizeof(UseCaseContext_t));
- context.type = REVIEW_USE_CASE;
- context.review.tagValueList = tagValueList;
- context.review.reviewTitle = reviewTitle;
- context.review.icon = icon;
- context.review.onChoice = choiceCallback;
- context.currentPage = 0;
- // + 3 because 1 page for title and 2 pages at the end for accept/reject
- context.nbPages = tagValueList->nbPairs + 3;
+/**
+ * @brief Draws a flow of pages of a review. Navigation operates with either swipe or navigation
+ * keys at bottom right. The last page contains a long-press button with the given finishTitle and
+ * the given icon.
+ * @note All tag/value pairs are provided in the API and the number of pages is automatically
+ * computed, the last page being a long press one
+ *
+ * @param operationType type of operation (Operation, Transaction, Message)
+ * @param tagValueList list of tag/value pairs
+ * @param icon icon used on first and last review page
+ * @param reviewTitle string used in the first review page
+ * @param reviewSubTitle string to set under reviewTitle (can be NULL)
+ * @param finishTitle string used in the last review page
+ * @param dummy inconsistent parameter on Nano devices (ignored)
+ * @param choiceCallback callback called when operation is accepted (param is true) or rejected
+ * (param is false)
+ */
+void nbgl_useCaseAdvancedReview(nbgl_operationType_t operationType,
+ const nbgl_contentTagValueList_t *tagValueList,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const char *finishTitle,
+ const nbgl_tipBox_t *dummy,
+ nbgl_choiceCallback_t choiceCallback)
+{
+ UNUSED(operationType); // TODO adapt accept and reject text depending on this value?
+ UNUSED(dummy);
+
+ useCaseReview(REVIEW_USE_CASE,
+ tagValueList,
+ icon,
+ reviewTitle,
+ reviewSubTitle,
+ finishTitle,
+ choiceCallback);
+}
- displayReviewPage(FORWARD_DIRECTION);
+/**
+ * @brief Draws a flow of pages of a blind-signing review. The review is preceded by a warning page
+ *
+ * Navigation operates with either swipe or navigation
+ * keys at bottom right. The last page contains a long-press button with the given finishTitle and
+ * the given icon.
+ * @note All tag/value pairs are provided in the API and the number of pages is automatically
+ * computed, the last page being a long press one
+ *
+ * @param operationType type of operation (Operation, Transaction, Message)
+ * @param tagValueList list of tag/value pairs
+ * @param icon icon used on first and last review page
+ * @param reviewTitle string used in the first review page
+ * @param reviewSubTitle string to set under reviewTitle (can be NULL)
+ * @param finishTitle string used in the last review page
+ * @param dummy inconsistent parameter on Nano devices (ignored)
+ * @param choiceCallback callback called when operation is accepted (param is true) or rejected
+ * (param is false)
+ */
+void nbgl_useCaseReviewBlindSigning(nbgl_operationType_t operationType,
+ const nbgl_contentTagValueList_t *tagValueList,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ const char *finishTitle,
+ const nbgl_tipBox_t *dummy,
+ nbgl_choiceCallback_t choiceCallback)
+{
+ UNUSED(operationType); // TODO adapt accept and reject text depending on this value?
+ UNUSED(dummy);
+
+ useCaseReview(REVIEW_BLIND_SIGN_USE_CASE,
+ tagValueList,
+ icon,
+ reviewTitle,
+ reviewSubTitle,
+ finishTitle,
+ choiceCallback);
}
/**
@@ -842,27 +1856,52 @@ void nbgl_useCaseAddressReview(const char *address,
const char *reviewSubTitle,
nbgl_choiceCallback_t choiceCallback)
{
- UNUSED(reviewSubTitle); // TODO dedicated screen for it?
-
memset(&context, 0, sizeof(UseCaseContext_t));
- context.type = ADDRESS_REVIEW_USE_CASE;
- context.review.address = address;
- context.review.reviewTitle = reviewTitle;
- context.review.icon = icon;
- context.review.onChoice = choiceCallback;
- context.currentPage = 0;
+ context.type = ADDRESS_REVIEW_USE_CASE;
+ context.review.address = address;
+ context.review.reviewTitle = reviewTitle;
+ context.review.reviewSubTitle = reviewSubTitle;
+ context.review.icon = icon;
+ context.review.onChoice = choiceCallback;
+ context.currentPage = 0;
// + 4 because 1 page for title, 1 for address and 2 pages at the end for approve/reject
context.nbPages = 4;
if (additionalTagValueList) {
- memcpy(&context.review.tagValueList,
- additionalTagValueList,
- sizeof(nbgl_contentTagValueList_t));
+ context.review.tagValueList = PIC(additionalTagValueList);
context.nbPages += additionalTagValueList->nbPairs;
}
displayReviewPage(FORWARD_DIRECTION);
}
+/**
+ * @brief Draws a flow of pages of a review with automatic pagination depending on content
+ * to be displayed that is passed through contents.
+ *
+ * @param contents contents to be displayed
+ * @param rejectText text to use in footer
+ * @param rejectCallback callback called when reject is pressed
+ */
+void nbgl_useCaseGenericReview(const nbgl_genericContents_t *contents,
+ const char *rejectText,
+ nbgl_callback_t rejectCallback)
+{
+ memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = GENERIC_REVIEW_USE_CASE;
+ context.content.rejectText = rejectText;
+ context.content.quitCallback = rejectCallback;
+ context.content.genericContents.nbContents = contents->nbContents;
+ context.content.genericContents.callbackCallNeeded = contents->callbackCallNeeded;
+ if (contents->callbackCallNeeded) {
+ context.content.genericContents.contentGetterCallback = contents->contentGetterCallback;
+ }
+ else {
+ context.content.genericContents.contentsList = PIC(contents->contentsList);
+ }
+
+ startUseCaseContent();
+}
+
/**
* @brief Draws a transient (3s) status page, either of success or failure, with the given message
*
@@ -872,22 +1911,16 @@ void nbgl_useCaseAddressReview(const char *address,
*/
void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t quitCallback)
{
- UNUSED(isSuccess); // TODO add icon depending on isSuccess?
+ const nbgl_icon_details_t *icon = NULL;
memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = STATUS_USE_CASE;
context.stepCallback = quitCallback;
context.currentPage = 0;
context.nbPages = 1;
- nbgl_screenTickerConfiguration_t ticker = {
- .tickerCallback = PIC(statusTickerCallback),
- .tickerIntervale = 0, // not periodic
- .tickerValue = 3000 // 3 seconds
- };
-
- nbgl_stepDrawText(
- SINGLE_STEP, statusButtonCallback, &ticker, message, NULL, BOLD_TEXT1_INFO, false);
- nbgl_refresh();
+ icon = isSuccess ? &C_icon_validate_14 : &C_icon_crossmark;
+ drawStep(SINGLE_STEP, icon, message, NULL, statusButtonCallback, false);
}
/**
@@ -931,7 +1964,7 @@ void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType,
isSuccess = true;
break;
case STATUS_TYPE_ADDRESS_REJECTED:
- msg = "Verification\ncancelled";
+ msg = "Address verification cancelled";
isSuccess = false;
break;
default:
@@ -958,16 +1991,49 @@ void nbgl_useCaseReviewStreamingStart(nbgl_operationType_t operationType,
const char *reviewSubTitle,
nbgl_choiceCallback_t choiceCallback)
{
- UNUSED(operationType); // TODO adapt accept and reject text depending on this value?
- UNUSED(reviewSubTitle); // TODO dedicated screen for it?
+ UNUSED(operationType); // TODO adapt accept and reject text depending on this value?
memset(&context, 0, sizeof(UseCaseContext_t));
- context.type = STREAMING_START_REVIEW_USE_CASE;
- context.review.reviewTitle = reviewTitle;
- context.review.icon = icon;
- context.review.onChoice = choiceCallback;
- context.currentPage = 0;
- context.nbPages = 1 + 1; // Start page + trick for review continue
+ context.type = STREAMING_START_REVIEW_USE_CASE;
+ context.review.reviewTitle = reviewTitle;
+ context.review.reviewSubTitle = reviewSubTitle;
+ context.review.icon = icon;
+ context.review.onChoice = choiceCallback;
+ context.currentPage = 0;
+ context.nbPages = 2; // Start page + trick for review continue
+
+ displayStreamingReviewPage(FORWARD_DIRECTION);
+}
+
+/**
+ * @brief Start drawing the flow of pages of a blind-signing review. The review is preceded by a
+ * warning page
+ * @note This should be followed by calls to nbgl_useCaseReviewStreamingContinue and finally to
+ * nbgl_useCaseReviewStreamingFinish.
+ *
+ * @param operationType type of operation (Operation, Transaction, Message)
+ * @param icon icon used on first and last review page
+ * @param reviewTitle string used in the first review page
+ * @param reviewSubTitle string to set under reviewTitle (can be NULL)
+ * @param choiceCallback callback called when more operation data are needed (param is true) or
+ * operation is rejected (param is false)
+ */
+void nbgl_useCaseReviewStreamingBlindSigningStart(nbgl_operationType_t operationType,
+ const nbgl_icon_details_t *icon,
+ const char *reviewTitle,
+ const char *reviewSubTitle,
+ nbgl_choiceCallback_t choiceCallback)
+{
+ UNUSED(operationType); // TODO adapt accept and reject text depending on this value?
+
+ memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = STREAMING_BLIND_SIGN_START_REVIEW_USE_CASE;
+ context.review.reviewTitle = reviewTitle;
+ context.review.reviewSubTitle = reviewSubTitle;
+ context.review.icon = icon;
+ context.review.onChoice = choiceCallback;
+ context.currentPage = 0;
+ context.nbPages = 3; // Warning + Start page + trick for review continue
displayStreamingReviewPage(FORWARD_DIRECTION);
}
@@ -1036,8 +2102,12 @@ void nbgl_useCaseReviewStreamingFinish(const char *finishTitle,
*/
void nbgl_useCaseSpinner(const char *text)
{
- drawStep(SINGLE_STEP, &C_icon_processing, text, NULL, NULL);
- nbgl_refresh();
+ memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = SPINNER_USE_CASE;
+ context.currentPage = 0;
+ context.nbPages = 1;
+
+ displaySpinner(text);
}
void nbgl_useCaseChoice(const nbgl_icon_details_t *icon,
@@ -1061,5 +2131,96 @@ void nbgl_useCaseChoice(const nbgl_icon_details_t *icon,
displayChoicePage(FORWARD_DIRECTION);
};
+/**
+ * @brief Draws a page to confirm or not an action, described in a centered info (with info icon),
+ * thanks to a button and a footer at the bottom of the page. The given callback is called if the
+ * button is touched. If the footer is touched, the page is only "dismissed"
+ * @note This page is displayed as a modal (so the content of the previous page will be visible when
+ * dismissed).
+ *
+ * @param message string to set in center of page (32px)
+ * @param subMessage string to set under message (24px) (can be NULL)
+ * @param confirmText string to set in button, to confirm
+ * @param cancelText string to set in footer, to reject
+ * @param callback callback called when confirmation button is touched
+ */
+void nbgl_useCaseConfirm(const char *message,
+ const char *subMessage,
+ const char *confirmText,
+ const char *cancelText,
+ nbgl_callback_t callback)
+{
+ memset(&context, 0, sizeof(UseCaseContext_t));
+ context.type = CONFIRM_USE_CASE;
+ context.confirm.message = message;
+ context.confirm.subMessage = subMessage;
+ context.confirm.confirmText = confirmText;
+ context.confirm.cancelText = cancelText;
+ context.confirm.onConfirm = callback;
+ context.currentPage = 0;
+ context.nbPages = 1 + 2; // 2 pages at the end for confirm/cancel
+
+ displayConfirm(FORWARD_DIRECTION);
+}
+
+#ifdef NBGL_KEYPAD
+/**
+ * @brief draws a standard keypad modal page with visible digits. It contains
+ * - a navigation bar at the top
+ * - a title for the pin code
+ * - a visible digit entry
+ * - the keypad at the bottom
+ *
+ * @note callbacks allow to control the behavior.
+ * backspace and validation button are shown/hidden automatically
+ *
+ * @param title string to set in pin code title
+ * @param minDigits pin minimum number of digits
+ * @param maxDigits maximum number of digits to be displayed
+ * @param shuffled if set to true, digits are shuffled in keypad
+ * @param validatePinCallback function calledto validate the pin code
+ * @param backCallback callback called on backspace is "pressed" in first digit
+ */
+void nbgl_useCaseKeypadDigits(const char *title,
+ uint8_t minDigits,
+ uint8_t maxDigits,
+ bool shuffled,
+ nbgl_pinValidCallback_t validatePinCallback,
+ nbgl_callback_t backCallback)
+{
+ keypadGenericUseCase(
+ title, minDigits, maxDigits, shuffled, false, validatePinCallback, backCallback);
+}
+
+/**
+ * @brief draws a standard keypad modal page with hidden digits. It contains
+ * - a navigation bar at the top
+ * - a title for the pin code
+ * - a hidden digit entry
+ * - the keypad at the bottom
+ *
+ * @note callbacks allow to control the behavior.
+ * backspace and validation button are shown/hidden automatically
+ *
+ * @param title string to set in pin code title
+ * @param minDigits pin minimum number of digits
+ * @param maxDigits maximum number of digits to be displayed
+ * @param backToken token used with actionCallback (0 if unused))
+ * @param shuffled if set to true, digits are shuffled in keypad
+ * @param validatePinCallback function calledto validate the pin code
+ * @param backCallback callback called on backspace is "pressed" in first digit
+ */
+void nbgl_useCaseKeypadPIN(const char *title,
+ uint8_t minDigits,
+ uint8_t maxDigits,
+ bool shuffled,
+ nbgl_pinValidCallback_t validatePinCallback,
+ nbgl_callback_t backCallback)
+{
+ keypadGenericUseCase(
+ title, minDigits, maxDigits, shuffled, true, validatePinCallback, backCallback);
+}
+#endif // NBGL_KEYPAD
+
#endif // HAVE_SE_TOUCH
#endif // NBGL_USE_CASE
diff --git a/lib_standard_app/main.c b/lib_standard_app/main.c
index a04b360e2..fc0b0fbb5 100644
--- a/lib_standard_app/main.c
+++ b/lib_standard_app/main.c
@@ -46,8 +46,10 @@ WEAK void common_app_init(void)
io_seproxyhal_init();
+#ifdef HAVE_IO_USB
USB_power(0);
USB_power(1);
+#endif
#ifdef HAVE_BLE
BLE_power(0, NULL);
@@ -83,7 +85,9 @@ WEAK void standalone_app_main(void)
// - the NanoX goes on battery power and display the lock screen
// - the user plug the NanoX instead of entering its pin
// - the device is frozen, battery should be removed
+#ifdef HAVE_IO_USB
USB_power(0);
+#endif
#ifdef HAVE_BLE
BLE_power(0, NULL);
#endif
diff --git a/src/cx_stubs.S b/src/cx_stubs.S
index 49d1ade68..c4ab57a23 100644
--- a/src/cx_stubs.S
+++ b/src/cx_stubs.S
@@ -160,6 +160,7 @@ CX_TRAMPOLINE _NR_cx_cipher_reset cx_cipher_reset
CX_TRAMPOLINE _NR_cx_cmac_start cx_cmac_start
CX_TRAMPOLINE _NR_cx_cmac_update cx_cmac_update
CX_TRAMPOLINE _NR_cx_cmac_finish cx_cmac_finish
+CX_TRAMPOLINE _NR_cx_aes_siv_reset cx_aes_siv_reset
.thumb_func
cx_trampoline_helper:
diff --git a/src/syscalls.c b/src/syscalls.c
index 076b95ece..3e8b5edc3 100644
--- a/src/syscalls.c
+++ b/src/syscalls.c
@@ -252,11 +252,11 @@ cx_err_t cx_aes_set_key_hw(const cx_aes_key_t *keys, uint32_t mode)
return SVC_cx_call(SYSCALL_cx_aes_set_key_hw_ID, parameters);
}
-void cx_aes_reset_hw(void)
+cx_err_t cx_aes_reset_hw(void)
{
unsigned int parameters[2];
parameters[1] = 0;
- SVC_cx_call(SYSCALL_cx_aes_reset_hw_ID, parameters);
+ return SVC_cx_call(SYSCALL_cx_aes_reset_hw_ID, parameters);
}
cx_err_t cx_aes_block_hw(const unsigned char *inblock, unsigned char *outblock)
@@ -1835,13 +1835,12 @@ void nvm_write_page(unsigned int page_adr, bool force)
return;
}
-void nvm_erase_page(unsigned int page_adr)
+unsigned int nvm_erase_page(unsigned int page_adr)
{
unsigned int parameters[2];
parameters[0] = (unsigned int) page_adr;
parameters[1] = 0;
- SVC_Call(SYSCALL_nvm_erase_page_ID, parameters);
- return;
+ return (unsigned int) SVC_Call(SYSCALL_nvm_erase_page_ID, parameters);
}
try_context_t *try_context_get(void)
diff --git a/unit-tests/lib_nbgl/CMakeLists.txt b/unit-tests/lib_nbgl/CMakeLists.txt
index 815bc2a08..5ee877e25 100644
--- a/unit-tests/lib_nbgl/CMakeLists.txt
+++ b/unit-tests/lib_nbgl/CMakeLists.txt
@@ -78,7 +78,7 @@ add_library(nbgl_obj SHARED ../../lib_nbgl/src/nbgl_obj.c)
target_link_libraries(test_nbgl_fonts PUBLIC cmocka gcov nbgl_fonts nbgl_stubs)
target_link_libraries(test_nbgl_obj_pool PUBLIC cmocka gcov nbgl_obj_pool)
-target_link_libraries(test_nbgl_screen PUBLIC cmocka gcov nbgl_screen nbgl_obj_pool)
+target_link_libraries(test_nbgl_screen PUBLIC cmocka gcov nbgl_screen nbgl_obj_pool nbgl_stubs)
target_link_libraries(test_nbgl_obj PUBLIC cmocka gcov nbgl_obj nbgl_screen nbgl_obj_pool nbgl_fonts nbgl_stubs)
add_test(test_nbgl_fonts test_nbgl_fonts)
diff --git a/unit-tests/lib_nbgl/nbgl_stubs.c b/unit-tests/lib_nbgl/nbgl_stubs.c
index c9913caf8..9605d7215 100644
--- a/unit-tests/lib_nbgl/nbgl_stubs.c
+++ b/unit-tests/lib_nbgl/nbgl_stubs.c
@@ -8,7 +8,9 @@
#include
#include
#include "nbgl_fonts.h"
+#include "nbgl_touch.h"
#include "ux_loc.h"
+#include "os_helpers.h"
#include "os_task.h"
void fetch_language_packs(void);
diff --git a/unit-tests/lib_nbgl/test_nbgl_screen.c b/unit-tests/lib_nbgl/test_nbgl_screen.c
index 3ef09bff4..8f542b207 100644
--- a/unit-tests/lib_nbgl/test_nbgl_screen.c
+++ b/unit-tests/lib_nbgl/test_nbgl_screen.c
@@ -9,6 +9,7 @@
#include
#include "nbgl_screen.h"
#include "nbgl_debug.h"
+#include "nbgl_touch.h"
#include "ux_loc.h"
#include "os_task.h"
@@ -46,6 +47,15 @@ void nbgl_objDraw(nbgl_obj_t *obj)
UNUSED(obj);
}
+void nbgl_touchHandler(bool fromUx,
+ nbgl_touchStatePosition_t *touchStatePosition,
+ uint32_t currentTime)
+{
+ UNUSED(fromUx);
+ UNUSED(touchStatePosition);
+ UNUSED(currentTime);
+}
+
static void test_push_pop(void **state __attribute__((unused)))
{
nbgl_obj_t **elements0, **elements1, **elements2, **elements3;