diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index f2fb620f..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,8 +0,0 @@ -# Checklist - -- [ ] App update process has been followed -- [ ] Target branch is `develop` -- [ ] Application version has been bumped - - diff --git a/.github/workflows/check_version.yml b/.github/workflows/check_version.yml index 42051866..b9b512ae 100644 --- a/.github/workflows/check_version.yml +++ b/.github/workflows/check_version.yml @@ -3,6 +3,9 @@ name: Verify PRs to main on: workflow_dispatch: pull_request: + paths: + - app/** + - deps/** branches: - main - develop diff --git a/.github/workflows/guidelines_enforcer.yml b/.github/workflows/guidelines_enforcer.yml index 0b958e52..fdaf9f27 100644 --- a/.github/workflows/guidelines_enforcer.yml +++ b/.github/workflows/guidelines_enforcer.yml @@ -21,5 +21,3 @@ jobs: guidelines_enforcer: name: Call Ledger guidelines_enforcer uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 - with: - relative_app_directory: 'app' diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..1f9ca750 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,57 @@ +name: Sonarcloud + +on: + push: + branches: + - disable + pull_request: + branches: + - disable + types: [opened, synchronize, reopened] + +jobs: + build: + name: SonarQube analyze + runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder@sha256:877adc3ff619222aaf03a490d546ea9001f02faa0c6ac7c06c876c99584f9cdb + env: + SONAR_SCANNER_VERSION: 4.7.0.2747 + SONAR_SERVER_URL: "https://sonarcloud.io" + BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Download and set up sonar-scanner + env: + SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip + run: | + apt-get update -y + apt-get upgrade -y + curl -sL https://deb.nodesource.com/setup_16.x | bash - + apt-get install -y gcovr nodejs unzip + mkdir -p $HOME/.sonar + curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }} + unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/ + echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH + - name: Download and set up build-wrapper + env: + BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip + run: | + curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }} + unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/ + echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH + - name: Run build-wrapper + run: | + build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make clean all + - name: Run sonar-scanner + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/.gitignore b/.gitignore index 98709ce7..48621352 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ cmake-build-fuzz/ !\deps/nanox-secure-sdk !\deps/ledger-zxlib !\deps/tinycbor +!\deps/tinycbor-ledger !\deps/BLAKE app/src/glyphs.c diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 00000000..c5d2136c --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1,17 @@ +# Path to sources +# sonar.sources= +# sonar.exclusions= +# sonar.inclusions= + +# Path to tests +# sonar.tests= +# sonar.test.exclusions= +# sonar.test.inclusions= + +# Source encoding +# sonar.sourceEncoding= + +# Exclusions for copy-paste detection +# sonar.cpd.exclusions= +# Python version (for python projects only) +# sonar.python.version= \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a3d1917a..bd698708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,11 @@ string(APPEND CMAKE_LINKER_FLAGS " -fsanitize=address -fno-omit-frame-pointer") ############################################################## # static libs file(GLOB_RECURSE JSMN_SRC - deps/jsmn/src/jsmn.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/jsmn/src/jsmn.c + ) +file(GLOB_RECURSE TINYCBOR_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/deps/tinycbor/src/cborparser.c + ${CMAKE_CURRENT_SOURCE_DIR}/deps/tinycbor/src/cborvalidation.c ) file(GLOB_RECURSE LIB_SRC @@ -98,6 +102,7 @@ file(GLOB_RECURSE LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/app/src/formatting.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/parser_impl.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/json/json_parser.c + ${CMAKE_CURRENT_SOURCE_DIR}/app/src/cbor/cbor_parser_helper.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/tx_parser.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/tx_display.c ${CMAKE_CURRENT_SOURCE_DIR}/app/src/tx_validate.c @@ -107,6 +112,7 @@ file(GLOB_RECURSE LIB_SRC add_library(app_lib STATIC ${LIB_SRC} ${JSMN_SRC} + ${TINYCBOR_SRC} ) target_include_directories(app_lib PUBLIC @@ -115,7 +121,8 @@ target_include_directories(app_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/app/src ${CMAKE_CURRENT_SOURCE_DIR}/app/src/common ${CMAKE_CURRENT_SOURCE_DIR}/deps/ledger-zxlib/app/common - ${CMAKE_CURRENT_SOURCE_DIR}/deps/tinykeccak + ${CMAKE_CURRENT_SOURCE_DIR}/deps/tinycbor/src + ${CMAKE_CURRENT_SOURCE_DIR}/deps/tinykeccak/ ) target_link_libraries(app_lib PUBLIC) @@ -133,6 +140,7 @@ target_include_directories(unittests PRIVATE ${CONAN_INCLUDE_DIRS_FMT} ${CONAN_INCLUDE_DIRS_JSONCPP} ${CMAKE_CURRENT_SOURCE_DIR}/deps/jsmn/src + ${CMAKE_CURRENT_SOURCE_DIR}/deps/tinycbor/src ) target_link_libraries(unittests PRIVATE @@ -143,6 +151,7 @@ target_link_libraries(unittests PRIVATE add_compile_definitions(TESTVECTORS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/") add_compile_definitions(APP_TESTING=1) +add_compile_definitions(COMPILE_TEXTUAL=1) add_test(unittests ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittests) set_tests_properties(unittests PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) diff --git a/README.md b/README.md index ce72b6f1..5cc09314 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,18 @@ --- -![zondax](docs/zondax.jpg) +![zondax_light](docs/zondax_light.png#gh-light-mode-only) +![zondax_dark](docs/zondax_dark.png#gh-dark-mode-only) _Please visit our website at [zondax.ch](zondax.ch)_ +You can also visit [Zondax Hub](https://hub.zondax.ch/cosmos) to test any of the versions of the app + --- -This project contains the Cosmos app for Ledger Nano S and X. +This project contains the Cosmos app for Ledger Nano S, Nano S+, X and Stax. -- Ledger Nano S/X Cosmos app +- Ledger Nano S/S+/X/Stax Cosmos app - Specs / Documentation - C++ unit tests - Zemu tests @@ -202,5 +205,8 @@ The Makefile will build the firmware in a docker container and leave the binary ## APDU Specifications +### DISCLAIMER +Ledger NanoS does not support Cosmos Textual Mode due to memory restriction + - [APDU Protocol](docs/APDUSPEC.md) - [Transaction format](docs/TXSPEC.md) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..636d34e8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,79 @@ +# Coordinated Vulnerability Disclosure Policy + +The Cosmos ecosystem believes that strong security is a blend of highly +technical security researchers who care about security and the forward +progression of the ecosystem and the attentiveness and openness of Cosmos core +contributors to help continually secure our operations. + +> **IMPORTANT**: *DO NOT* open public issues on this repository for security +> vulnerabilities. + +## Scope + +| Scope | +|-----------------------| +| last release (tagged) | +| main branch | + +The latest **release tag** of this repository is supported for security updates +as well as the **main** branch. Security vulnerabilities should be reported if +the vulnerability can be reproduced on either one of those. + +## Reporting a Vulnerability + +| Reporting methods | +|---------------------------------------------------------------| +| [GitHub Private Vulnerability Reporting][gh-private-advisory] | +| [HackerOne bug bounty program][h1] | + +All security vulnerabilities can be reported under GitHub's [Private +vulnerability reporting][gh-private-advisory] system. This will open a private +issue for the developers. Try to fill in as much of the questions as possible. +If you are not familiar with the CVSS system for assessing vulnerabilities, just +use the Low/High/Critical severity ratings. A partially filled in report for a +critical vulnerability is still better than no report at all. + +Vulnerabilities associated with the **Go, Rust or Protobuf code** of the +repository may be eligible for a [bug bounty][h1]. Please see the bug bounty +page for more details on submissions and rewards. If you think the vulnerability +is eligible for a payout, **report on HackerOne first**. + +Vulnerabilities in services and their source codes (JavaScript, web page, Google +Workspace) are not in scope for the bug bounty program, but they are welcome to +be reported in GitHub. + +### Guidelines + +We require that all researchers: + +* Abide by this policy to disclose vulnerabilities, and avoid posting + vulnerability information in public places, including GitHub, Discord, + Telegram, and Twitter. +* Make every effort to avoid privacy violations, degradation of user experience, + disruption to production systems (including but not limited to the Cosmos + Hub), and destruction of data. +* Keep any information about vulnerabilities that you’ve discovered confidential + between yourself and the Cosmos engineering team until the issue has been + resolved and disclosed. +* Avoid posting personally identifiable information, privately or publicly. + +If you follow these guidelines when reporting an issue to us, we commit to: + +* Not pursue or support any legal action related to your research on this + vulnerability +* Work with you to understand, resolve and ultimately disclose the issue in a + timely fashion + +### More information + +* See [TIMELINE.md] for an example timeline of a disclosure. +* See [DISCLOSURE.md] to see more into the inner workings of the disclosure + process. +* See [EXAMPLES.md] for some of the examples that we are interested in for the + bug bounty program. + +[gh-private-advisory]: /../../security/advisories/new +[h1]: https://hackerone.com/cosmos +[TIMELINE.md]: https://github.com/cosmos/security/blob/main/TIMELINE.md +[DISCLOSURE.md]: https://github.com/cosmos/security/blob/main/DISCLOSURE.md +[EXAMPLES.md]: https://github.com/cosmos/security/blob/main/EXAMPLES.md diff --git a/app/Makefile b/app/Makefile index de57f0fc..53b11936 100755 --- a/app/Makefile +++ b/app/Makefile @@ -54,12 +54,6 @@ APP_LOAD_PARAMS = --curve secp256k1 $(COMMON_LOAD_PARAMS) --path $(APPPATH) include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.devices -# On zxlib v19.7.1, Makefile.devices will set a default value for APP_STACK_SIZE -# to follow the most recent nanos-secure-sdk rules we will clean APP_STACK_SIZE value -# and set a minimum value -APP_STACK_SIZE := -APP_STACK_MIN_SIZE := 1444 - $(info TARGET_NAME = [$(TARGET_NAME)]) $(info ICONNAME = [$(ICONNAME)]) @@ -67,8 +61,18 @@ ifndef ICONNAME $(error ICONNAME is not set) endif -include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.platform +# Compile textual mode for all devices excetpt Nano S, +# and define a Min stack size for Nano S with some margin +# to get an error if app grows too much +ifneq ($(TARGET_NAME),TARGET_NANOS) + DEFINES += COMPILE_TEXTUAL +endif +APP_STACK_MIN_SIZE := 1600 + +include $(CURDIR)/../deps/ledger-zxlib/makefiles/Makefile.platform +CFLAGS += -I$(MY_DIR)/../deps/tinycbor/src +APP_SOURCE_PATH += $(MY_DIR)/../deps/tinycbor-ledger APP_SOURCE_PATH += $(MY_DIR)/../deps/jsmn/src .PHONY: rust diff --git a/app/Makefile.version b/app/Makefile.version index 931edd84..e33445ec 100644 --- a/app/Makefile.version +++ b/app/Makefile.version @@ -1,6 +1,6 @@ # This is the `transaction_version` field of `Runtime` APPVERSION_M=2 # This is the `spec_version` field of `Runtime` -APPVERSION_N=34 +APPVERSION_N=35 # This is the patch version of this release -APPVERSION_P=14 +APPVERSION_P=19 diff --git a/app/glyphs/icon_stax_32.gif b/app/glyphs/icon_stax_32.gif index c33ae718..bd338188 100644 Binary files a/app/glyphs/icon_stax_32.gif and b/app/glyphs/icon_stax_32.gif differ diff --git a/app/glyphs/icon_stax_64.gif b/app/glyphs/icon_stax_64.gif index 72cef217..64dee01b 100644 Binary files a/app/glyphs/icon_stax_64.gif and b/app/glyphs/icon_stax_64.gif differ diff --git a/app/script_s2.ld b/app/script_s2.ld deleted file mode 100644 index db3150b3..00000000 --- a/app/script_s2.ld +++ /dev/null @@ -1,170 +0,0 @@ -/******************************************************************************* -* Ledger Blue - Secure firmware -* (c) 2016, 2017, 2018, 2019 Ledger -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ - -/** - * Global chip memory layout and constants - * - */ - -MEMORY -{ - DISCARD (rwx) : ORIGIN = 0xd0000000, LENGTH = 1M - - FLASH (rx) : ORIGIN = 0xc0de0000, LENGTH = 400K - DATA (r) : ORIGIN = 0xc0de0000, LENGTH = 400K - SRAM (rwx) : ORIGIN = 0xda7a0000, LENGTH = 30K -} - -PAGE_SIZE = 512; -STACK_SIZE = 8192; -END_STACK = ORIGIN(SRAM) + LENGTH(SRAM); - -ENTRY(main); - -SECTIONS -{ - /****************************************************************/ - /* This section locates the code in FLASH */ - /****************************************************************/ - - /** put text in Flash memory, VMA will be equal to LMA */ - .text : - { - /* provide start code symbol, shall be zero */ - _text = .; - _nvram_start = .; - - /* ensure main is always @ 0xC0D00000 */ - *(.boot*) - - /* place the other code and rodata defined BUT nvram variables that are displaced in a r/w area */ - *(.text*) - *(.rodata) - *(.rodata.[^N]*) /*.data.rel.ro* not here to detect invalid PIC usage */ - *(.rodata.N[^_]*) - - . = ALIGN(4); - - /* all code placed */ - _etext = .; - - . = ALIGN(PAGE_SIZE); - - _nvram_data = .; - - /* NVM data (ex-filesystem) */ - *(.bss.N_* .rodata.N_*) - - . = ALIGN(PAGE_SIZE); - _envram_data = .; - - _install_parameters = .; - _nvram_end = .; - } > FLASH = 0x00 - - .data (NOLOAD): - { - . = ALIGN(4); - - /** - * Place RAM initialized variables - */ - _data = .; - - *(vtable) - *(.data*) - - _edata = .; - - } > DISCARD /*> SRAM AT>FLASH = 0x00 */ - - .bss : - { - /** - * Place RAM uninitialized variables - */ - _bss = .; - *(.bss*) - _ebss = .; - - - /** - * Reserve stack size - */ - . = ALIGN(4); - app_stack_canary = .; - PROVIDE(app_stack_canary = .); - . += 4; - _stack_validation = .; - . = _stack_validation + STACK_SIZE; - _stack = ABSOLUTE(END_STACK) - STACK_SIZE; - PROVIDE( _stack = ABSOLUTE(END_STACK) - STACK_SIZE); - _estack = ABSOLUTE(END_STACK); - PROVIDE( _estack = ABSOLUTE(END_STACK) ); - - } > SRAM = 0x00 - - /****************************************************************/ - /* DEBUG */ - /****************************************************************/ - - /* remove the debugging information from the standard libraries */ - DEBUG (NOLOAD) : - { - libc.a ( * ) - libm.a ( * ) - libgcc.a ( * ) - *(.ARM.exidx* .gnu.linkonce.armexidx.*) - } > DISCARD - - /* Stabs debugging sections. */ - .stab 0 : { *(.stab) } - .stabstr 0 : { *(.stabstr) } - .stab.excl 0 : { *(.stab.excl) } - .stab.exclstr 0 : { *(.stab.exclstr) } - .stab.index 0 : { *(.stab.index) } - .stab.indexstr 0 : { *(.stab.indexstr) } - .comment 0 : { *(.comment) } - /* DWARF debug sections. - Symbols in the DWARF debugging sections are relative to the beginning - of the section so we begin them at 0. */ - /* DWARF 1 */ - .debug 0 : { *(.debug) } - .line 0 : { *(.line) } - /* GNU DWARF 1 extensions */ - .debug_srcinfo 0 : { *(.debug_srcinfo) } - .debug_sfnames 0 : { *(.debug_sfnames) } - /* DWARF 1.1 and DWARF 2 */ - .debug_aranges 0 : { *(.debug_aranges) } - .debug_pubnames 0 : { *(.debug_pubnames) } - /* DWARF 2 */ - .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } - .debug_abbrev 0 : { *(.debug_abbrev) } - .debug_line 0 : { *(.debug_line) } - .debug_frame 0 : { *(.debug_frame) } - .debug_str 0 : { *(.debug_str) } - .debug_loc 0 : { *(.debug_loc) } - .debug_macinfo 0 : { *(.debug_macinfo) } - /* SGI/MIPS DWARF 2 extensions */ - .debug_weaknames 0 : { *(.debug_weaknames) } - .debug_funcnames 0 : { *(.debug_funcnames) } - .debug_typenames 0 : { *(.debug_typenames) } - .debug_varnames 0 : { *(.debug_varnames) } -} - -PROVIDE(_nvram = ABSOLUTE(_nvram_start)); -PROVIDE(_envram = ABSOLUTE(_nvram_end)); \ No newline at end of file diff --git a/app/src/addr.h b/app/src/addr.h index cac4c26b..d9bbae1d 100644 --- a/app/src/addr.h +++ b/app/src/addr.h @@ -15,6 +15,7 @@ ********************************************************************************/ #pragma once +#include "zxerror.h" #ifdef __cplusplus extern "C" { @@ -29,8 +30,6 @@ zxerr_t addr_getItem(int8_t displayIdx, char *outValue, uint16_t outValueLen, uint8_t pageIdx, uint8_t *pageCount); -zxerr_t addr_to_textual(char *s, uint16_t max, const char *text, uint16_t textLen); - #ifdef __cplusplus } #endif diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index 94dcb2e0..18ca7d10 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -23,6 +23,7 @@ #include #include "view.h" +#include "view_internal.h" #include "actions.h" #include "tx.h" #include "addr.h" @@ -62,6 +63,7 @@ __Z_INLINE void handle_getversion(__Z_UNUSED volatile uint32_t *flags, volatile } __Z_INLINE uint8_t extractHRP(uint32_t rx, uint32_t offset) { + uint8_t hrp_len = 0; if (rx < offset + 1) { THROW(APDU_CODE_DATA_INVALID); } @@ -76,10 +78,15 @@ __Z_INLINE uint8_t extractHRP(uint32_t rx, uint32_t offset) { memcpy(bech32_hrp, G_io_apdu_buffer + offset + 1, bech32_hrp_len); bech32_hrp[bech32_hrp_len] = 0; // zero terminate - return bech32_hrp_len; + hrp_len = bech32_hrp_len; + return hrp_len; } __Z_INLINE void extractHDPath(uint32_t rx, uint32_t offset) { + if (rx < offset + 1) { + THROW(APDU_CODE_DATA_INVALID); + } + if ((rx - offset) < sizeof(uint32_t) * HDPATH_LEN_DEFAULT) { THROW(APDU_CODE_WRONG_LENGTH); } @@ -109,8 +116,8 @@ static void extractHDPath_HRP(uint32_t rx, uint32_t offset) { // Check if HRP was sent if ((rx - offset) > sizeof(uint32_t) * HDPATH_LEN_DEFAULT) { - extractHRP(rx, offset + sizeof(uint32_t) * HDPATH_LEN_DEFAULT); - encoding = checkChainConfig(hdPath[1], bech32_hrp, bech32_hrp_len); + uint8_t hrp_bech32_len = extractHRP(rx, offset + sizeof(uint32_t) * HDPATH_LEN_DEFAULT); + encoding = checkChainConfig(hdPath[1], bech32_hrp, hrp_bech32_len); if (encoding == UNSUPPORTED) { ZEMU_LOGF(50, "Chain config not supported for: %s\n", bech32_hrp) THROW(APDU_CODE_COMMAND_NOT_ALLOWED); @@ -125,10 +132,6 @@ static bool process_chunk(volatile uint32_t *tx, uint32_t rx) { const uint8_t payloadType = G_io_apdu_buffer[OFFSET_PAYLOAD_TYPE]; - if (G_io_apdu_buffer[OFFSET_P2] != 0) { - THROW(APDU_CODE_INVALIDP1P2); - } - if (rx < OFFSET_DATA) { THROW(APDU_CODE_WRONG_LENGTH); } @@ -139,7 +142,6 @@ static bool process_chunk(volatile uint32_t *tx, uint32_t rx) { tx_initialize(); tx_reset(); extractHDPath_HRP(rx, OFFSET_DATA); - return false; case P1_ADD: added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); @@ -187,29 +189,24 @@ __Z_INLINE void handleGetAddrSecp256K1(volatile uint32_t *flags, volatile uint32 THROW(APDU_CODE_OK); } -__Z_INLINE void handleSignSecp256K1(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { +__Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { if (!process_chunk(tx, rx)) { THROW(APDU_CODE_OK); } + // Let grab P2 value and if it's not valid, the parser should reject it + const tx_type_e sign_type = (tx_type_e) G_io_apdu_buffer[OFFSET_P2]; + if ((hdPath[1] == HDPATH_ETH_1_DEFAULT) && !app_mode_expert()) { *flags |= IO_ASYNCH_REPLY; view_custom_error_show(PIC(msg_error1),PIC(msg_error2)); THROW(APDU_CODE_DATA_INVALID); } - - // Put address in output buffer, we will use it to confirm source address - zxerr_t zxerr = app_fill_address(); - if (zxerr != zxerr_ok) { - *tx = 0; - THROW(APDU_CODE_DATA_INVALID); - } - parser_tx_obj.own_addr = (const char *) (G_io_apdu_buffer + VIEW_ADDRESS_OFFSET_SECP256K1); - const char *error_msg = tx_parse(); - + parser_tx_obj.tx_json.own_addr = (const char *) (G_io_apdu_buffer + VIEW_ADDRESS_OFFSET_SECP256K1); + const char *error_msg = tx_parse(sign_type); if (error_msg != NULL) { - int error_msg_length = strlen(error_msg); + const int error_msg_length = strnlen(error_msg, sizeof(G_io_apdu_buffer)); MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); *tx += (error_msg_length); THROW(APDU_CODE_DATA_INVALID); @@ -250,7 +247,7 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { case INS_SIGN_SECP256K1: { CHECK_PIN_VALIDATED() - handleSignSecp256K1(flags, tx, rx); + handleSign(flags, tx, rx); break; } diff --git a/app/src/cbor/cbor_parser_helper.c b/app/src/cbor/cbor_parser_helper.c new file mode 100644 index 00000000..59af1ee3 --- /dev/null +++ b/app/src/cbor/cbor_parser_helper.c @@ -0,0 +1,150 @@ +/******************************************************************************* +* (c) 2018, 2019 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ + +#include "cbor.h" +#include "cbor_parser_helper.h" +#include "parser_txdef.h" +#include +#include +#include "parser.h" + +parser_error_t parser_mapCborError(CborError err) { + switch (err) { + case CborErrorUnexpectedEOF: + return parser_cbor_unexpected_EOF; + case CborErrorMapNotSorted: + return parser_cbor_not_canonical; + case CborNoError: + return parser_ok; + default: + return parser_cbor_unexpected; + } +} + +static parser_error_t cbor_check_optFields(CborValue *data, Cbor_container *container) { + if (data == NULL || container == NULL) { + return parser_unexpected_value; + } + int key; + for (size_t i = 0; i < container->n_field; i++) { + + PARSER_ASSERT_OR_ERROR(cbor_value_is_integer(data), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_get_int(data, &key)) + CHECK_CBOR_MAP_ERR(cbor_value_advance(data)) + + switch(key) { + case INDENT_KEY_ID: { + int tmpVal = 0; + PARSER_ASSERT_OR_ERROR(cbor_value_is_integer(data), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_get_int(data, &tmpVal)) + PARSER_ASSERT_OR_ERROR((tmpVal >= 0 && tmpVal <= UINT8_MAX), parser_unexpected_value) + container->screen.indent = (uint8_t) tmpVal; + break; + } + + case EXPERT_KEY_ID: + PARSER_ASSERT_OR_ERROR(cbor_value_is_boolean(data), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_get_boolean(data, &container->screen.expert)) + break; + + default: + container->screen.indent = 0; + container->screen.expert = false; + } + CHECK_CBOR_MAP_ERR(cbor_value_advance(data)) + } + return parser_ok; +} + +static parser_error_t cbor_check_screen(CborValue *data, Cbor_container *container) { + if (data == NULL || container == NULL) { + return parser_unexpected_value; + } + + int screen_key; + //check title Key + PARSER_ASSERT_OR_ERROR(cbor_value_is_integer(data), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_get_int(data, &screen_key)) + if (screen_key != TITLE_KEY_ID) { + PARSER_ASSERT_OR_ERROR(screen_key==CONTENT_KEY_ID, parser_unexpected_type) + + // No title + container->screen.titlePtr = NULL; + container->screen.titleLen = 0; + CHECK_CBOR_MAP_ERR(cbor_value_advance(data)) + + //get content ptr + READ_STRING_PTR_SIZE(data, container->screen.contentPtr, container->screen.contentLen) + PARSER_ASSERT_OR_ERROR(container->screen.contentLen <= MAX_CONTENT_SIZE, parser_unexpected_value) + return parser_ok; + } + + CHECK_CBOR_MAP_ERR(cbor_value_advance(data)) + + //get title ptr + READ_STRING_PTR_SIZE(data, container->screen.titlePtr, container->screen.titleLen) + PARSER_ASSERT_OR_ERROR(container->screen.titleLen <= MAX_CONTENT_SIZE, parser_unexpected_value) + + CHECK_CBOR_MAP_ERR(cbor_value_advance(data)); + + //check content Key + PARSER_ASSERT_OR_ERROR(cbor_value_is_integer(data), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_get_int(data, &screen_key)) + PARSER_ASSERT_OR_ERROR(screen_key==CONTENT_KEY_ID, parser_unexpected_type) + + CHECK_CBOR_MAP_ERR(cbor_value_advance(data)) + + //get content ptr + READ_STRING_PTR_SIZE(data, container->screen.contentPtr, container->screen.contentLen) + PARSER_ASSERT_OR_ERROR(container->screen.contentLen <= MAX_CONTENT_SIZE, parser_unexpected_value) + + return parser_ok; +} + +parser_error_t cbor_get_containerInfo(CborValue *data, Cbor_container *container) { + if (data == NULL || container == NULL) { + return parser_unexpected_value; + } + + PARSER_ASSERT_OR_ERROR(!cbor_value_at_end(data), parser_unexpected_buffer_end) + CHECK_PARSER_ERR(cbor_check_screen(data, container)) + CHECK_CBOR_MAP_ERR(cbor_value_advance(data)) + + if (container->n_field > 2) { + container->n_field -= 2; + CHECK_PARSER_ERR(cbor_check_optFields(data, container)) + } else { + container->screen.indent = 0; + container->screen.expert = false; + } + + return parser_ok; +} + +parser_error_t cbor_check_expert(CborValue *data, Cbor_container *container) { + if (data == NULL || container == NULL) { + return parser_unexpected_value; + } + + if (container->n_field > 1) { + CHECK_PARSER_ERR(cbor_check_optFields(data, container)) + } else { + container->screen.indent = 0; + container->screen.expert = false; + } + + return parser_ok; +} diff --git a/app/src/cbor/cbor_parser_helper.h b/app/src/cbor/cbor_parser_helper.h new file mode 100644 index 00000000..20509f66 --- /dev/null +++ b/app/src/cbor/cbor_parser_helper.h @@ -0,0 +1,50 @@ +/******************************************************************************* +* (c) 2018, 2019 Zondax GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#include "cbor.h" +#include +#include +#include + + +parser_error_t parser_mapCborError(CborError err); + +#define CHECK_CBOR_MAP_ERR(CALL) { \ + CborError err = CALL; \ + if (err!=CborNoError) return parser_mapCborError(err);} + +#define PARSER_ASSERT_OR_ERROR(CALL, ERROR) if (!(CALL)) return ERROR; + +#define CHECK_CBOR_TYPE(type, expected) {if ((type)!=(expected)) return parser_unexpected_type;} + +#define INIT_CBOR_PARSER(c, it) \ + CborParser parser; \ + CHECK_CBOR_MAP_ERR(cbor_parser_init((c)->buffer + (c)->offset, (c)->bufferLen - (c)->offset, 0, &parser, &(it))) + +#define READ_STRING_PTR_SIZE(it, V_OUTPUT_PTR, V_OUTPUT_SIZE) \ + PARSER_ASSERT_OR_ERROR(cbor_value_is_text_string(it), parser_context_mismatch); \ + it->flags = 0x64; \ + CHECK_CBOR_MAP_ERR(cbor_value_get_text_string_chunk(it,(const char **)&V_OUTPUT_PTR, &V_OUTPUT_SIZE, NULL)); + + +typedef struct Cbor_container { + size_t n_field; + screen_arg_t screen; +} Cbor_container; + +parser_error_t cbor_get_containerInfo(CborValue *data, Cbor_container *container); +parser_error_t cbor_check_expert(CborValue *data, Cbor_container *container); diff --git a/app/src/coin.h b/app/src/coin.h index 509735ed..b938a903 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -18,7 +18,7 @@ extern "C" { #endif -#define CLA 0x55 +#define CLA 0x55u #define HDPATH_LEN_DEFAULT 5 @@ -35,6 +35,11 @@ typedef enum { addr_secp256k1 = 0, } address_kind_e; +typedef enum { + tx_json = 0, + tx_textual +} tx_type_e; + typedef enum { BECH32_COSMOS = 0, BECH32_ETH, @@ -54,8 +59,8 @@ typedef enum { // In non-expert mode, the app will convert from uatom to ATOM #define COIN_DEFAULT_DENOM_BASE "uatom" #define COIN_DEFAULT_DENOM_REPR "ATOM" -#define COIN_DEFAULT_DENOM_FACTOR 6 -#define COIN_DEFAULT_DENOM_TRIMMING 6 +#define COIN_DEFAULT_DENOM_FACTOR 6u +#define COIN_DEFAULT_DENOM_TRIMMING 6u // Coin denoms may be up to 128 characters long // https://github.com/cosmos/cosmos-sdk/blob/master/types/coin.go#L780 @@ -63,18 +68,18 @@ typedef enum { #define COIN_DENOM_MAXSIZE 129 #define COIN_AMOUNT_MAXSIZE 50 -#define COIN_MAX_CHAINID_LEN 20 -#define INDEXING_TMP_KEYSIZE 70 -#define INDEXING_TMP_VALUESIZE 70 -#define INDEXING_GROUPING_REF_TYPE_SIZE 70 -#define INDEXING_GROUPING_REF_FROM_SIZE 70 +#define COIN_MAX_CHAINID_LEN 20u +#define INDEXING_TMP_KEYSIZE 70u +#define INDEXING_TMP_VALUESIZE 70u +#define INDEXING_GROUPING_REF_TYPE_SIZE 70u +#define INDEXING_GROUPING_REF_FROM_SIZE 70u #define MENU_MAIN_APP_LINE2_SECRET "?" #define COIN_SECRET_REQUIRED_CLICKS 0 #define INS_GET_VERSION 0x00 -#define INS_SIGN_SECP256K1 0x02 -#define INS_GET_ADDR_SECP256K1 0x04 +#define INS_SIGN_SECP256K1 0x02u +#define INS_GET_ADDR_SECP256K1 0x04u #ifdef __cplusplus } diff --git a/app/src/common/parser.h b/app/src/common/parser.h index e8f69a46..c000b072 100644 --- a/app/src/common/parser.h +++ b/app/src/common/parser.h @@ -21,13 +21,24 @@ extern "C" { #endif #include "parser_impl.h" +#include "coin.h" + +#define OUTPUT_HANDLER_SIZE 600 +#define MAX_CONTENT_SIZE 550 +#define MAX_TITLE_SIZE 40 +#define PRINTABLE_TITLE_SIZE 17 +#define PRINTABLE_PAGINATED_TITLE_SIZE 10 +#define SCREEN_BREAK ":" +#define SCREEN_INDENT ">" +#define TITLE_TRUNCATE_REPLACE "---" const char *parser_getErrorDescription(parser_error_t err); //// parses a tx buffer parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, - size_t dataLen); + size_t dataLen, + parser_tx_t *tx_obj); //// verifies tx fields parser_error_t parser_validate(const parser_context_t *ctx); diff --git a/app/src/common/parser_common.h b/app/src/common/parser_common.h index ce23e1ac..95e8f9be 100644 --- a/app/src/common/parser_common.h +++ b/app/src/common/parser_common.h @@ -50,6 +50,7 @@ typedef enum { parser_unexpected_chain, parser_missing_field, parser_query_no_results, + parser_transaction_too_big, // Coin Specific parser_json_zero_tokens, parser_json_too_many_tokens, // "NOMEM: JSON string contains too many tokens" @@ -63,14 +64,17 @@ typedef enum { parser_json_missing_account_number, parser_json_missing_memo, parser_json_unexpected_error, + //cbor + parser_cbor_unexpected, + parser_cbor_unexpected_EOF, + parser_cbor_not_canonical, + // context + parser_context_mismatch, + parser_context_unexpected_size, + parser_context_invalid_chars, + parser_context_unknown_prefix, } parser_error_t; -typedef struct { - const uint8_t *buffer; - uint16_t bufferLen; - uint16_t offset; -} parser_context_t; - #ifdef __cplusplus } #endif diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 926e0129..60e1d7ab 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -25,7 +25,7 @@ #define RAM_BUFFER_SIZE 8192 #define FLASH_BUFFER_SIZE 16384 #elif defined(TARGET_NANOS) -#define RAM_BUFFER_SIZE 256 +#define RAM_BUFFER_SIZE 0 #define FLASH_BUFFER_SIZE 8192 #endif @@ -76,13 +76,24 @@ uint8_t *tx_get_buffer() static parser_tx_t tx_obj; -const char *tx_parse() +const char *tx_parse(tx_type_e type) { - MEMZERO(&tx_obj, sizeof(tx_obj)); +#if defined(COMPILE_TEXTUAL) + if (type != tx_json && type != tx_textual) { + return parser_getErrorDescription(parser_value_out_of_range); + } +#else + if (type != tx_json) { + return parser_getErrorDescription(parser_value_out_of_range); + } +#endif + MEMZERO(&tx_obj, sizeof(tx_obj)); + tx_obj.tx_type = type; uint8_t err = parser_parse(&ctx_parsed_tx, tx_get_buffer(), - tx_get_buffer_length()); + tx_get_buffer_length(), + &tx_obj); zemu_log_stack("parse|parsed"); if (err != parser_ok) @@ -91,6 +102,7 @@ const char *tx_parse() } err = parser_validate(&ctx_parsed_tx); + CHECK_APP_CANARY() if (err != parser_ok) diff --git a/app/src/common/tx.h b/app/src/common/tx.h index b84b9151..54b22b37 100644 --- a/app/src/common/tx.h +++ b/app/src/common/tx.h @@ -42,10 +42,11 @@ uint8_t *tx_get_buffer(); /// Parse message stored in transaction buffer /// This function should be called as soon as full buffer data is loaded. /// \return It returns NULL if data is valid or error message otherwise. -const char *tx_parse(); +const char *tx_parse(tx_type_e type); /// Return the number of items in the transaction zxerr_t tx_getNumItems(uint8_t *num_items); +zxerr_t tx_getTextualNumItems(uint8_t *num_items); /// Gets an specific item from the transaction (including paging) zxerr_t tx_getItem(int8_t displayIdx, diff --git a/app/src/crypto.c b/app/src/crypto.c index fc20dc2b..46079007 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -29,7 +29,7 @@ uint32_t hdPath[HDPATH_LEN_DEFAULT]; uint8_t bech32_hrp_len; char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; -address_encoding_e encoding; +address_encoding_e encoding = BECH32_COSMOS; #include "cx.h" @@ -73,15 +73,7 @@ static zxerr_t crypto_extractUncompressedPublicKey(uint8_t *pubKey, uint16_t pub __Z_INLINE zxerr_t compressPubkey(const uint8_t *pubkey, uint16_t pubkeyLen, uint8_t *output, uint16_t outputLen) { if (pubkey == NULL || output == NULL || pubkeyLen != PK_LEN_SECP256K1_UNCOMPRESSED || outputLen < PK_LEN_SECP256K1) { - return zxerr_unknown; - } - - // Format pubkey - for (int i = 0; i < 32; i++) { - output[i] = pubkey[64 - i]; - } - if ((pubkey[32] & 1) != 0) { - output[31] |= 0x80; + return zxerr_invalid_crypto_settings; } MEMCPY(output, pubkey, PK_LEN_SECP256K1); diff --git a/app/src/crypto.h b/app/src/crypto.h index 12a0ea6e..7b216290 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -33,8 +33,6 @@ extern char bech32_hrp[MAX_BECH32_HRP_LEN + 1]; extern uint8_t bech32_hrp_len; extern address_encoding_e encoding; -void crypto_set_hrp(char *p); - zxerr_t crypto_fillAddress(uint8_t *buffer, uint16_t bufferLen, uint16_t *addrResponseLen); zxerr_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, uint16_t *signatureLen); diff --git a/app/src/json/json_parser.h b/app/src/json/json_parser.h index 95c0c1db..2fd1e36e 100644 --- a/app/src/json/json_parser.h +++ b/app/src/json/json_parser.h @@ -1,5 +1,5 @@ /******************************************************************************* -* (c) 2018, 2019 Zondax GmbH +* (c) 2018 - 2023 Zondax AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/app/src/parser.c b/app/src/parser.c index c3f37f93..f0192bf9 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -24,17 +24,48 @@ #include "parser_impl.h" #include "common/parser.h" #include "coin.h" +#include "app_mode.h" +#include + +parser_error_t parser_init_context(parser_context_t *ctx, + const uint8_t *buffer, + uint16_t bufferSize) { + ctx->offset = 0; + + if (bufferSize == 0 || buffer == NULL) { + // Not available, use defaults + ctx->buffer = NULL; + ctx->bufferLen = 0; + return parser_init_context_empty; + } + + ctx->buffer = buffer; + ctx->bufferLen = bufferSize; + + return parser_ok; +} parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, - size_t dataLen) { - CHECK_PARSER_ERR(tx_display_readTx(ctx, data, dataLen)) - extraDepthLevel =false; + size_t dataLen, + parser_tx_t *tx_obj) { + + CHECK_PARSER_ERR(parser_init_context(ctx, data, dataLen)) + ctx->tx_obj = tx_obj; + if (tx_obj->tx_type == tx_textual) { + CHECK_PARSER_ERR(_read_text_tx(ctx, tx_obj)) + } else { + CHECK_PARSER_ERR(_read_json_tx(ctx, tx_obj)) + } + + extraDepthLevel = false; return parser_ok; } parser_error_t parser_validate(const parser_context_t *ctx) { - CHECK_PARSER_ERR(tx_validate(&parser_tx_obj.json)) + if (ctx->tx_obj->tx_type == tx_json) { + CHECK_PARSER_ERR(tx_validate(&parser_tx_obj.tx_json.json)) + } // Iterate through all items to check that all can be shown and are valid uint8_t numItems = 0; @@ -42,89 +73,102 @@ parser_error_t parser_validate(const parser_context_t *ctx) { char tmpKey[40]; char tmpVal[40]; - + uint8_t pageCount = 0; for (uint8_t idx = 0; idx < numItems; idx++) { - uint8_t pageCount = 0; CHECK_PARSER_ERR(parser_getItem(ctx, idx, tmpKey, sizeof(tmpKey), tmpVal, sizeof(tmpVal), 0, &pageCount)) } - return parser_ok; } -parser_error_t parser_getNumItems(const parser_context_t *ctx __attribute__((unused)), uint8_t *num_items) { +parser_error_t parser_getNumItems(const parser_context_t *ctx, uint8_t *num_items) { *num_items = 0; + if (ctx->tx_obj->tx_type == tx_textual) { + *num_items = app_mode_expert() ? (uint8_t) ctx->tx_obj->tx_text.n_containers : (uint8_t) (ctx->tx_obj->tx_text.n_containers - ctx->tx_obj->tx_text.n_expert); + return parser_ok; + } + return tx_display_numItems(num_items); } -__Z_INLINE bool_t parser_areEqual(uint16_t tokenIdx, const char *expected) { - if (parser_tx_obj.json.tokens[tokenIdx].type != JSMN_STRING) { - return bool_false; +__Z_INLINE bool parser_areEqual(uint16_t tokenIdx, const char *expected) { + if (parser_tx_obj.tx_json.json.tokens[tokenIdx].type != JSMN_STRING) { + return false; } - int32_t len = parser_tx_obj.json.tokens[tokenIdx].end - parser_tx_obj.json.tokens[tokenIdx].start; + int32_t len = parser_tx_obj.tx_json.json.tokens[tokenIdx].end - parser_tx_obj.tx_json.json.tokens[tokenIdx].start; if (len < 0) { - return bool_false; + return false; } if (strlen(expected) != (size_t) len) { - return bool_false; + return false; } - const char *p = parser_tx_obj.tx + parser_tx_obj.json.tokens[tokenIdx].start; + const char *p = parser_tx_obj.tx_json.tx + parser_tx_obj.tx_json.json.tokens[tokenIdx].start; for (int32_t i = 0; i < len; i++) { if (expected[i] != *(p + i)) { - return bool_false; + return false; } } - return bool_true; + return true; } -__Z_INLINE bool_t parser_isAmount(char *key) { +__Z_INLINE bool parser_isAmount(char *key) { if (strcmp(key, "fee/amount") == 0) { - return bool_true; + return true; } if (strcmp(key, "msgs/inputs/coins") == 0) { - return bool_true; + return true; } if (strcmp(key, "msgs/outputs/coins") == 0) { - return bool_true; + return true; } if (strcmp(key, "msgs/value/inputs/coins") == 0) { - return bool_true; + return true; } if (strcmp(key, "msgs/value/outputs/coins") == 0) { - return bool_true; + return true; } if (strcmp(key, "msgs/value/amount") == 0) { - return bool_true; + return true; } if (strcmp(key, "tip/amount") == 0) { - return bool_true; + return true; } - return bool_false; + return false; } -__Z_INLINE bool_t is_default_denom_base(const char *denom, uint8_t denom_len) { - if (tx_is_expert_mode()) { - return false; +__Z_INLINE parser_error_t is_default_denom_base(const char *denom, uint8_t denom_len, bool *is_default) { + if (is_default == NULL) { + return parser_unexpected_value; + } + + bool is_expert_or_default = false; + CHECK_PARSER_ERR(tx_is_expert_mode_or_not_default_chainid(&is_expert_or_default)) + if (is_expert_or_default) { + *is_default = false; + return parser_ok; } if (strlen(COIN_DEFAULT_DENOM_BASE) != denom_len) { - return bool_false; + *is_default = false; + return parser_ok; } - if (memcmp(denom, COIN_DEFAULT_DENOM_BASE, denom_len) == 0) - return bool_true; + if (memcmp(denom, COIN_DEFAULT_DENOM_BASE, denom_len) == 0) { + *is_default = true; + return parser_ok; + } - return bool_false; + return parser_ok; } __Z_INLINE parser_error_t parser_formatAmountItem(uint16_t amountToken, @@ -133,7 +177,7 @@ __Z_INLINE parser_error_t parser_formatAmountItem(uint16_t amountToken, *pageCount = 0; uint16_t numElements; - CHECK_PARSER_ERR(array_get_element_count(&parser_tx_obj.json, amountToken, &numElements)) + CHECK_PARSER_ERR(array_get_element_count(&parser_tx_obj.tx_json.json, amountToken, &numElements)) if (numElements == 0) { *pageCount = 1; @@ -145,7 +189,7 @@ __Z_INLINE parser_error_t parser_formatAmountItem(uint16_t amountToken, return parser_unexpected_field; } - if (parser_tx_obj.json.tokens[amountToken].type != JSMN_OBJECT) { + if (parser_tx_obj.tx_json.json.tokens[amountToken].type != JSMN_OBJECT) { return parser_unexpected_field; } @@ -165,16 +209,17 @@ __Z_INLINE parser_error_t parser_formatAmountItem(uint16_t amountToken, MEMZERO(outVal, outValLen); MEMZERO(bufferUI, sizeof(bufferUI)); - const char *amountPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 2].start; - if (parser_tx_obj.json.tokens[amountToken + 2].start < 0) { + if (parser_tx_obj.tx_json.json.tokens[amountToken + 2].start < 0 || + parser_tx_obj.tx_json.json.tokens[amountToken + 4].start < 0) { return parser_unexpected_buffer_end; } + const char *amountPtr = parser_tx_obj.tx_json.tx + parser_tx_obj.tx_json.json.tokens[amountToken + 2].start; - const int32_t amountLen = parser_tx_obj.json.tokens[amountToken + 2].end - - parser_tx_obj.json.tokens[amountToken + 2].start; - const char *denomPtr = parser_tx_obj.tx + parser_tx_obj.json.tokens[amountToken + 4].start; - const int32_t denomLen = parser_tx_obj.json.tokens[amountToken + 4].end - - parser_tx_obj.json.tokens[amountToken + 4].start; + const int32_t amountLen = parser_tx_obj.tx_json.json.tokens[amountToken + 2].end - + parser_tx_obj.tx_json.json.tokens[amountToken + 2].start; + const char *denomPtr = parser_tx_obj.tx_json.tx + parser_tx_obj.tx_json.json.tokens[amountToken + 4].start; + const int32_t denomLen = parser_tx_obj.tx_json.json.tokens[amountToken + 4].end - + parser_tx_obj.tx_json.json.tokens[amountToken + 4].start; if (denomLen <= 0 || denomLen >= COIN_DENOM_MAXSIZE) { return parser_unexpected_error; @@ -194,7 +239,9 @@ __Z_INLINE parser_error_t parser_formatAmountItem(uint16_t amountToken, snprintf(bufferUI, sizeof(bufferUI), "%s ", tmpAmount); // If denomination has been recognized format and replace - if (is_default_denom_base(denomPtr, denomLen)) { + bool is_default =false; + CHECK_PARSER_ERR(is_default_denom_base(denomPtr, denomLen, &is_default)) + if (is_default) { if (fpstr_to_str(bufferUI, sizeof(bufferUI), tmpAmount, COIN_DEFAULT_DENOM_FACTOR) != 0) { return parser_unexpected_error; } @@ -214,24 +261,24 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, ZEMU_LOGF(200, "[formatAmount] ------- pageidx %d", pageIdx) *pageCount = 0; - if (parser_tx_obj.json.tokens[amountToken].type != JSMN_ARRAY) { + if (parser_tx_obj.tx_json.json.tokens[amountToken].type != JSMN_ARRAY) { return parser_formatAmountItem(amountToken, outVal, outValLen, pageIdx, pageCount); } uint8_t totalPages = 0; - bool_t showItemSet = false; + uint8_t showItemSet = 0; uint8_t showPageIdx = pageIdx; uint16_t showItemTokenIdx = 0; uint16_t numberAmounts; - CHECK_PARSER_ERR(array_get_element_count(&parser_tx_obj.json, amountToken, &numberAmounts)) + CHECK_PARSER_ERR(array_get_element_count(&parser_tx_obj.tx_json.json, amountToken, &numberAmounts)) // Count total subpagesCount and calculate correct page and TokenIdx for (uint16_t i = 0; i < numberAmounts; i++) { uint16_t itemTokenIdx; uint8_t subpagesCount; - CHECK_PARSER_ERR(array_get_nth_element(&parser_tx_obj.json, amountToken, i, &itemTokenIdx)); + CHECK_PARSER_ERR(array_get_nth_element(&parser_tx_obj.tx_json.json, amountToken, i, &itemTokenIdx)); CHECK_PARSER_ERR(parser_formatAmountItem(itemTokenIdx, outVal, outValLen, 0, &subpagesCount)); totalPages += subpagesCount; @@ -240,7 +287,7 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, if (!showItemSet) { if (showPageIdx < subpagesCount) { - showItemSet = true; + showItemSet = 1; showItemTokenIdx = itemTokenIdx; ZEMU_LOGF(200, "[formatAmount] [%d] [SET] TokenIdx %d - PageIdx: %d", i, showItemTokenIdx, showPageIdx) @@ -264,14 +311,196 @@ __Z_INLINE parser_error_t parser_formatAmount(uint16_t amountToken, return parser_formatAmountItem(showItemTokenIdx, outVal, outValLen, showPageIdx, &dummy); } -parser_error_t parser_getItem(const parser_context_t *ctx, +#if defined(COMPILE_TEXTUAL) +__Z_INLINE parser_error_t parser_screenPrint(const parser_context_t *ctx, + Cbor_container *container, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount) { + + //verification assures that content + title < size(tmp), to be used in string manipulation + if (container->screen.titleLen > MAX_TITLE_SIZE || container->screen.contentLen > MAX_CONTENT_SIZE) { + return parser_unexpected_value; + } + MEMZERO(ctx->tx_obj->tx_text.tmpBuffer, sizeof(ctx->tx_obj->tx_text.tmpBuffer)); + char *tmp = (char*) ctx->tx_obj->tx_text.tmpBuffer; + size_t tmp_len = sizeof(ctx->tx_obj->tx_text.tmpBuffer); + char out[OUTPUT_HANDLER_SIZE] = {0}; + + // No Tittle screen + if (container->screen.titleLen == 0) { + MEMCPY(tmp, container->screen.contentPtr, container->screen.contentLen); + CHECK_PARSER_ERR(tx_display_translation(out, sizeof(out),tmp, container->screen.contentLen)) + + for (uint8_t i = 0; i < container->screen.indent; i++) { + z_str3join(out, sizeof(out), SCREEN_INDENT, ""); + } + + snprintf(outKey, outKeyLen, " "); + pageString(outVal, outValLen, out, pageIdx, pageCount); + return parser_ok; + } + + //Translate output, cpy to tmp to assure it ends in \0 + MEMZERO(tmp, tmp_len); + if(container->screen.contentPtr == NULL) { + return parser_unexpected_value; + } + MEMCPY(tmp, container->screen.contentPtr, container->screen.contentLen); + CHECK_PARSER_ERR(tx_display_translation(out, sizeof(out), tmp,container->screen.contentLen)) + + uint8_t titleLen = container->screen.titleLen + container->screen.indent; + //Title needs to be truncated, so we concat title witn content + if ((titleLen > PRINTABLE_TITLE_SIZE ) || (outValLen > 0 && ((strlen(out)/outValLen) >= 1 + && titleLen > PRINTABLE_PAGINATED_TITLE_SIZE))) { + + char key[MAX_TITLE_SIZE + 2] = {0}; + MEMCPY(key, TITLE_TRUNCATE_REPLACE, strlen(TITLE_TRUNCATE_REPLACE)); + for (uint8_t i = 0; i < container->screen.indent; i++) { + z_str3join(key, sizeof(key), SCREEN_INDENT, ""); + } + + MEMZERO(ctx->tx_obj->tx_text.tmpBuffer, sizeof(ctx->tx_obj->tx_text.tmpBuffer)); + if(container->screen.titlePtr == NULL) { + return parser_unexpected_value; + } + MEMCPY(tmp, container->screen.titlePtr, container->screen.titleLen); + MEMCPY(tmp + container->screen.titleLen,": ",2); + MEMCPY(tmp + container->screen.titleLen + 2, out, sizeof(out) - container->screen.titleLen -2); + snprintf(outKey, outKeyLen, "%s", key); + pageString(outVal, outValLen, tmp, pageIdx, pageCount); + return parser_ok; + } + + //Normal print case - Prepare title + char key[MAX_TITLE_SIZE + 2] = {0}; + if(container->screen.titlePtr == NULL) { + return parser_unexpected_value; + } + MEMCPY(key, container->screen.titlePtr, container->screen.titleLen); + for (uint8_t i = 0; i < container->screen.indent; i++) { + z_str3join(key, sizeof(key), SCREEN_INDENT, ""); + } + snprintf(outKey, outKeyLen, "%s", key); + pageString(outVal, outValLen, out, pageIdx, pageCount); + + return parser_ok; +} + +__Z_INLINE parser_error_t parser_getScreenInfo(const parser_context_t *ctx, + Cbor_container *container, + uint8_t index) { + CborValue it; + CborValue containerArray_ptr; + CborValue mapStruct_ptr; + INIT_CBOR_PARSER(ctx, mapStruct_ptr) + + PARSER_ASSERT_OR_ERROR(!cbor_value_at_end(&mapStruct_ptr), parser_unexpected_buffer_end) + PARSER_ASSERT_OR_ERROR(cbor_value_is_map(&mapStruct_ptr), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_enter_container(&mapStruct_ptr, &it)) + CHECK_CBOR_MAP_ERR(cbor_value_advance(&it)) + CHECK_CBOR_MAP_ERR(cbor_value_enter_container(&it, &containerArray_ptr)) + + for (int i = 0; i < index ; i ++) { + CHECK_CBOR_MAP_ERR(cbor_value_advance(&containerArray_ptr)) + } + + CborValue data; + CHECK_CBOR_MAP_ERR(cbor_value_get_map_length(&containerArray_ptr, &container->n_field)) + CHECK_CBOR_MAP_ERR(cbor_value_enter_container(&containerArray_ptr, &data)) + CHECK_PARSER_ERR(cbor_get_containerInfo(&data, container)) + + return parser_ok; +} + +__Z_INLINE parser_error_t parser_getNextNonExpert(const parser_context_t *ctx, + Cbor_container *container, + uint8_t displayIdx) { + + PARSER_ASSERT_OR_ERROR(displayIdx < ctx->tx_obj->tx_text.n_containers, parser_unexpected_value); + + uint8_t non_expert = 0; + for (size_t containerIdx = 0; containerIdx < ctx->tx_obj->tx_text.n_containers; containerIdx++) { + parser_getScreenInfo(ctx, container, containerIdx); + if (!container->screen.expert) { + non_expert++; + } else { + continue; + } + if (non_expert == displayIdx + 1) { + break; + } + PARSER_ASSERT_OR_ERROR(non_expert <= displayIdx, parser_unexpected_value); + } + return parser_ok; +} +#endif + +__Z_INLINE parser_error_t parser_getTextualItem(const parser_context_t *ctx, + uint8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount) { +#if !defined(COMPILE_TEXTUAL) + UNUSED(ctx); + UNUSED(displayIdx); + UNUSED(outKey); + UNUSED(outKeyLen); + UNUSED(outVal); + UNUSED(outValLen); + UNUSED(pageIdx); + UNUSED(pageCount); + return parser_value_out_of_range; +#else + *pageCount = 0; + + MEMZERO(outKey, outKeyLen); + MEMZERO(outVal, outValLen); + + uint8_t numItems; + CHECK_PARSER_ERR(parser_getNumItems(ctx, &numItems)) + PARSER_ASSERT_OR_ERROR((numItems != 0), parser_unexpected_number_items) + PARSER_ASSERT_OR_ERROR((displayIdx < numItems), parser_display_idx_out_of_range) + + CHECK_APP_CANARY() + + Cbor_container container; + container.screen.titlePtr = NULL; + container.screen.titleLen = 0; + container.screen.contentPtr = NULL; + container.screen.contentLen = 0; + container.screen.indent = 0; + container.screen.expert = false; + CHECK_PARSER_ERR(parser_getScreenInfo(ctx, &container, displayIdx)) + + // title and content can be Null depending on the screen for chain id they cant be null + if (container.screen.titlePtr != NULL && container.screen.contentPtr != NULL) { + if (!strncmp(container.screen.titlePtr, "Chain id", container.screen.titleLen)){ + if(!strncmp(container.screen.contentPtr, "0", container.screen.contentLen) || + !strncmp(container.screen.contentPtr, "1", container.screen.contentLen)) { + return parser_unexpected_chain; + } + } + } + + if (!app_mode_expert()) { + CHECK_PARSER_ERR(parser_getNextNonExpert(ctx, &container, displayIdx)) + } + + CHECK_PARSER_ERR(parser_screenPrint(ctx, &container, outKey, outKeyLen, outVal, outValLen, pageIdx, pageCount)) + + return parser_ok; +#endif +} + +__Z_INLINE parser_error_t parser_getJsonItem(const parser_context_t *ctx, uint8_t displayIdx, char *outKey, uint16_t outKeyLen, char *outVal, uint16_t outValLen, uint8_t pageIdx, uint8_t *pageCount) { - *pageCount = 0; - char tmpKey[100]; + *pageCount = 0; + char tmpKey[35] = {0}; MEMZERO(outKey, outKeyLen); MEMZERO(outVal, outValLen); @@ -284,7 +513,7 @@ parser_error_t parser_getItem(const parser_context_t *ctx, return parser_unexpected_number_items; } - if (displayIdx < 0 || displayIdx >= numItems) { + if (displayIdx >= numItems) { return parser_display_idx_out_of_range; } @@ -312,3 +541,25 @@ parser_error_t parser_getItem(const parser_context_t *ctx, return parser_ok; } + +parser_error_t parser_getItem(const parser_context_t *ctx, + uint8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount) { + + if (ctx->tx_obj->tx_type == tx_textual) { + CHECK_PARSER_ERR(parser_getTextualItem(ctx,displayIdx, + outKey, outKeyLen, + outVal, outValLen, + pageIdx, pageCount)); + + } else { + CHECK_PARSER_ERR(parser_getJsonItem(ctx,displayIdx, + outKey, outKeyLen, + outVal, outValLen, + pageIdx, pageCount)); + } + + return parser_ok; +} diff --git a/app/src/parser_impl.c b/app/src/parser_impl.c index 8f03893d..946722a8 100644 --- a/app/src/parser_impl.c +++ b/app/src/parser_impl.c @@ -15,35 +15,11 @@ ********************************************************************************/ #include "parser_impl.h" +#include "cbor.h" +#include parser_tx_t parser_tx_obj; -parser_error_t parser_init_context(parser_context_t *ctx, - const uint8_t *buffer, - uint16_t bufferSize) { - ctx->offset = 0; - - if (bufferSize == 0 || buffer == NULL) { - // Not available, use defaults - ctx->buffer = NULL; - ctx->bufferLen = 0; - return parser_init_context_empty; - } - - ctx->buffer = buffer; - ctx->bufferLen = bufferSize; - - return parser_ok; -} - -parser_error_t parser_init(parser_context_t *ctx, const uint8_t *buffer, size_t bufferSize) { - parser_error_t err = parser_init_context(ctx, buffer, bufferSize); - if (err != parser_ok) - return err; - - return err; -} - const char *parser_getErrorDescription(parser_error_t err) { switch (err) { case parser_ok: @@ -70,6 +46,8 @@ const char *parser_getErrorDescription(parser_error_t err) { return "item query returned no results"; case parser_missing_field: return "missing field"; + case parser_unexpected_type: + return "unexpected type"; ////// case parser_display_idx_out_of_range: return "display index out of range"; @@ -100,24 +78,98 @@ const char *parser_getErrorDescription(parser_error_t err) { return "JSON Missing memo"; case parser_json_unexpected_error: return "JSON Unexpected error"; + // cbor + case parser_cbor_unexpected: + return "unexpected CBOR error"; + case parser_cbor_not_canonical: + return "CBOR was not in canonical order"; + case parser_cbor_unexpected_EOF: + return "Unexpected CBOR EOF"; + // Context specific + case parser_context_mismatch: + return "context prefix is invalid"; + case parser_context_unexpected_size: + return "context unexpected size"; + case parser_context_invalid_chars: + return "context invalid chars"; + case parser_transaction_too_big: + return "Transaction is too big"; default: return "Unrecognized error code"; } } -parser_error_t _readTx(parser_context_t *c, parser_tx_t *v __attribute((unused))) { - parser_error_t err = json_parse(&parser_tx_obj.json, +parser_error_t _read_json_tx(parser_context_t *c, __Z_UNUSED parser_tx_t *v) { + parser_error_t err = json_parse(&parser_tx_obj.tx_json.json, (const char *) c->buffer, c->bufferLen); if (err != parser_ok) { return err; } - parser_tx_obj.tx = (const char *) c->buffer; - parser_tx_obj.flags.cache_valid = 0; - parser_tx_obj.filter_msg_type_count = 0; - parser_tx_obj.filter_msg_from_count = 0; + parser_tx_obj.tx_json.tx = (const char *) c->buffer; + parser_tx_obj.tx_json.flags.cache_valid = 0; + parser_tx_obj.tx_json.filter_msg_type_count = 0; + parser_tx_obj.tx_json.filter_msg_from_count = 0; + + return parser_ok; +} + +parser_error_t _read_text_tx(parser_context_t *c, parser_tx_t *v) { +#if !defined(COMPILE_TEXTUAL) + UNUSED(c); + UNUSED(v); + return parser_value_out_of_range; +#else + CborValue it; + CborValue mapStruct_ptr; + CHECK_APP_CANARY() + INIT_CBOR_PARSER(c, mapStruct_ptr) + CHECK_APP_CANARY() + + //Make sure we have a map/struct + PARSER_ASSERT_OR_ERROR(cbor_value_is_map(&mapStruct_ptr), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_enter_container(&mapStruct_ptr, &it)) + + //Make sure we have screen_key set to 1 + int screen_key = 0; + PARSER_ASSERT_OR_ERROR(cbor_value_is_integer(&it), parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_get_int(&it, &screen_key)) + PARSER_ASSERT_OR_ERROR(screen_key == 1, parser_unexpected_type) + CHECK_CBOR_MAP_ERR(cbor_value_advance(&it)) + + //Make sure we have an array of containers and check size + PARSER_ASSERT_OR_ERROR(cbor_value_is_array(&it), parser_unexpected_type) + + CHECK_CBOR_MAP_ERR(cbor_value_get_array_length(&it, &v->tx_text.n_containers)) + // Limit max fields to 255 + PARSER_ASSERT_OR_ERROR((v->tx_text.n_containers > 0 && v->tx_text.n_containers <= UINT8_MAX), parser_unexpected_number_items) + + CborValue containerArray_ptr; + CborValue data; + Cbor_container container; + + // Enter array of containers + CHECK_CBOR_MAP_ERR(cbor_value_enter_container(&it, &containerArray_ptr)) + + for (size_t i = 0; i < v->tx_text.n_containers; i++) { + MEMZERO(&container, sizeof(container)); + + CHECK_CBOR_MAP_ERR(cbor_value_get_map_length(&containerArray_ptr, &container.n_field)) + PARSER_ASSERT_OR_ERROR((container.n_field > 0 && container.n_field < 5), parser_unexpected_value) + + CHECK_CBOR_MAP_ERR(cbor_value_enter_container(&containerArray_ptr, &data)) + CHECK_PARSER_ERR(cbor_check_expert(&data, &container)) + + v->tx_text.n_expert += container.screen.expert ? 1 : 0; + CHECK_CBOR_MAP_ERR(cbor_value_advance(&containerArray_ptr)) + } + CHECK_CBOR_MAP_ERR(cbor_value_leave_container(&it, &containerArray_ptr)) + + // End of buffer does not match end of parsed data + PARSER_ASSERT_OR_ERROR(it.source.ptr == c->buffer + c->bufferLen, parser_cbor_unexpected_EOF) return parser_ok; +#endif } diff --git a/app/src/parser_impl.h b/app/src/parser_impl.h index 23d33fa9..1af7a28f 100644 --- a/app/src/parser_impl.h +++ b/app/src/parser_impl.h @@ -26,17 +26,26 @@ extern "C" { #endif typedef struct { - char str1[50]; - char str2[50]; + const uint8_t *buffer; + uint16_t bufferLen; + uint16_t offset; + parser_tx_t *tx_obj; +} parser_context_t; + +typedef struct { + const char *str1; + const char *str2; } key_subst_t; -extern parser_tx_t parser_tx_obj; +typedef struct { + char ascii_code; + char str; +} ascii_subst_t; -parser_error_t parser_init(parser_context_t *ctx, - const uint8_t *buffer, - size_t bufferSize); +extern parser_tx_t parser_tx_obj; -parser_error_t _readTx(parser_context_t *c, parser_tx_t *v); +parser_error_t _read_json_tx(parser_context_t *c, parser_tx_t *v); +parser_error_t _read_text_tx(parser_context_t *c, parser_tx_t *v); #ifdef __cplusplus } diff --git a/app/src/parser_txdef.h b/app/src/parser_txdef.h index 2c55ff66..c3c75146 100644 --- a/app/src/parser_txdef.h +++ b/app/src/parser_txdef.h @@ -21,8 +21,30 @@ extern "C" { #include #include - #include +#include "coin.h" +#include "cbor.h" + +#define MAX_NUMBER_SCREENS 50 +#define TITLE_KEY_ID 1 +#define CONTENT_KEY_ID 2 +#define INDENT_KEY_ID 3 +#define EXPERT_KEY_ID 4 + +typedef struct screen_arg_t { + char *titlePtr; + size_t titleLen; + char *contentPtr; + size_t contentLen; + uint8_t indent; + bool expert; +} screen_arg_t; + +typedef struct tx_textual_t{ + size_t n_containers; + uint8_t n_expert; + uint8_t tmpBuffer[625]; +} tx_textual_t; typedef struct { // These are internal values used for tracking the state of the query/search @@ -46,9 +68,8 @@ typedef struct { int16_t out_val_len; } tx_query_t; - - -typedef struct { +typedef struct +{ // Buffer to the original tx blob const char *tx; @@ -74,6 +95,16 @@ typedef struct { // current tx query tx_query_t query; +}tx_json_t; + + +typedef struct { + tx_type_e tx_type; + + union { + tx_json_t tx_json; + tx_textual_t tx_text; + }; } parser_tx_t; #ifdef __cplusplus diff --git a/app/src/tx_display.c b/app/src/tx_display.c index 69f7bfb9..2b414332 100644 --- a/app/src/tx_display.c +++ b/app/src/tx_display.c @@ -24,9 +24,17 @@ #include "tx_parser.h" #include "parser_impl.h" #include +#include +#include "utf8.h" #define NUM_REQUIRED_ROOT_PAGES 7 +#define ASSERT_PTR_BOUNDS(count, dstLen) \ + count++; \ + if(count > dstLen) { \ + return parser_transaction_too_big; \ + } \ + const char *get_required_root_item(root_item_e i) { switch (i) { case root_item_chain_id: @@ -93,12 +101,6 @@ typedef struct { display_cache_t display_cache; -parser_error_t tx_display_readTx(parser_context_t *ctx, const uint8_t *data, size_t dataLen) { - CHECK_PARSER_ERR(parser_init(ctx, data, dataLen)) - CHECK_PARSER_ERR(_readTx(ctx, &parser_tx_obj)) - return parser_ok; -} - __Z_INLINE parser_error_t calculate_is_default_chainid() { display_cache.is_default_chain = false; @@ -109,8 +111,8 @@ __Z_INLINE parser_error_t calculate_is_default_chainid() { INIT_QUERY_CONTEXT(outKey, sizeof(outKey), outVal, sizeof(outVal), 0, get_root_max_level(root_item_chain_id)) - parser_tx_obj.query.item_index = 0; - parser_tx_obj.query._item_index_current = 0; + parser_tx_obj.tx_json.query.item_index = 0; + parser_tx_obj.tx_json.query._item_index_current = 0; uint16_t ret_value_token_index; CHECK_PARSER_ERR(tx_traverse_find( @@ -129,6 +131,9 @@ __Z_INLINE parser_error_t calculate_is_default_chainid() { // If we don't match the default chainid, switch to expert mode display_cache.is_default_chain = true; zemu_log_stack("DEFAULT Chain "); + } else if ((outVal[0] == 0x30 || outVal[0] == 0x31) && strlen(outVal) == 1) { + zemu_log_stack("Not Allowed chain"); + return parser_unexpected_chain; } else { zemu_log_stack("Chain is NOT DEFAULT"); } @@ -137,17 +142,17 @@ __Z_INLINE parser_error_t calculate_is_default_chainid() { } __Z_INLINE bool address_matches_own(char *addr) { - if (parser_tx_obj.own_addr == NULL) { + if (parser_tx_obj.tx_json.own_addr == NULL) { return false; } - if (strcmp(parser_tx_obj.own_addr, addr) != 0) { + if (strcmp(parser_tx_obj.tx_json.own_addr, addr) != 0) { return false; } return true; } parser_error_t tx_indexRootFields() { - if (parser_tx_obj.flags.cache_valid) { + if (parser_tx_obj.tx_json.flags.cache_valid) { return parser_ok; } @@ -169,10 +174,10 @@ parser_error_t tx_indexRootFields() { MEMZERO(&reference_msg_type, sizeof(reference_msg_type)); MEMZERO(&reference_msg_from, sizeof(reference_msg_from)); - parser_tx_obj.filter_msg_type_count = 0; - parser_tx_obj.filter_msg_from_count = 0; - parser_tx_obj.flags.msg_type_grouping = 1; - parser_tx_obj.flags.msg_from_grouping = 1; + parser_tx_obj.tx_json.filter_msg_type_count = 0; + parser_tx_obj.tx_json.filter_msg_from_count = 0; + parser_tx_obj.tx_json.flags.msg_type_grouping = 1; + parser_tx_obj.tx_json.flags.msg_from_grouping = 1; // Look for all expected root items in the JSON tree // mark them as found/valid, @@ -183,7 +188,7 @@ parser_error_t tx_indexRootFields() { const char *required_root_item_key = get_required_root_item(root_item_idx); parser_error_t err = object_get_value( - &parser_tx_obj.json, + &parser_tx_obj.tx_json.json, ROOT_TOKEN_INDEX, required_root_item_key, &req_root_item_key_token_idx); @@ -204,10 +209,10 @@ parser_error_t tx_indexRootFields() { tmp_val, sizeof(tmp_val), 0, get_root_max_level(root_item_idx)) - parser_tx_obj.query.item_index = current_item_idx; - strncpy_s(parser_tx_obj.query.out_key, + parser_tx_obj.tx_json.query.item_index = current_item_idx; + strncpy_s(parser_tx_obj.tx_json.query.out_key, required_root_item_key, - parser_tx_obj.query.out_key_len); + parser_tx_obj.tx_json.query.out_key_len); uint16_t ret_value_token_index; err = tx_traverse_find(display_cache.root_item_start_token_idx[root_item_idx], &ret_value_token_index); @@ -218,15 +223,15 @@ parser_error_t tx_indexRootFields() { uint8_t pageCount; CHECK_PARSER_ERR(tx_getToken( ret_value_token_index, - parser_tx_obj.query.out_val, - parser_tx_obj.query.out_val_len, + parser_tx_obj.tx_json.query.out_val, + parser_tx_obj.tx_json.query.out_val_len, 0, &pageCount)) - ZEMU_LOGF(200, "[ZEMU] %s : %s", tmp_key, parser_tx_obj.query.out_val) + ZEMU_LOGF(200, "[ZEMU] %s : %s", tmp_key, parser_tx_obj.tx_json.query.out_val) switch (root_item_idx) { case root_item_memo: { - if (strlen(parser_tx_obj.query.out_val) == 0) { + if (strlen(parser_tx_obj.tx_json.query.out_val) == 0) { err = parser_query_no_results; continue; } @@ -238,45 +243,45 @@ parser_error_t tx_indexRootFields() { // This is indicated by `parser_tx_obj.flags.msg_type_grouping` // GROUPING: Message Type - if (parser_tx_obj.flags.msg_type_grouping && is_msg_type_field(tmp_key)) { + if (parser_tx_obj.tx_json.flags.msg_type_grouping && is_msg_type_field(tmp_key)) { // First message, initialize expected type - if (parser_tx_obj.filter_msg_type_count == 0) { + if (parser_tx_obj.tx_json.filter_msg_type_count == 0) { if (strlen(tmp_val) >= sizeof(reference_msg_type)) { return parser_unexpected_type; } snprintf(reference_msg_type, sizeof(reference_msg_type), "%s", tmp_val); - parser_tx_obj.filter_msg_type_valid_idx = current_item_idx; + parser_tx_obj.tx_json.filter_msg_type_valid_idx = current_item_idx; } if (strcmp(reference_msg_type, tmp_val) != 0) { // different values, so disable grouping - parser_tx_obj.flags.msg_type_grouping = 0; - parser_tx_obj.filter_msg_type_count = 0; + parser_tx_obj.tx_json.flags.msg_type_grouping = 0; + parser_tx_obj.tx_json.filter_msg_type_count = 0; } - parser_tx_obj.filter_msg_type_count++; + parser_tx_obj.tx_json.filter_msg_type_count++; } // GROUPING: Message From - if (parser_tx_obj.flags.msg_from_grouping && is_msg_from_field(tmp_key)) { + if (parser_tx_obj.tx_json.flags.msg_from_grouping && is_msg_from_field(tmp_key)) { // First message, initialize expected from - if (parser_tx_obj.filter_msg_from_count == 0) { + if (parser_tx_obj.tx_json.filter_msg_from_count == 0) { snprintf(reference_msg_from, sizeof(reference_msg_from), "%s", tmp_val); - parser_tx_obj.filter_msg_from_valid_idx = current_item_idx; + parser_tx_obj.tx_json.filter_msg_from_valid_idx = current_item_idx; } if (strcmp(reference_msg_from, tmp_val) != 0) { // different values, so disable grouping - parser_tx_obj.flags.msg_from_grouping = 0; - parser_tx_obj.filter_msg_from_count = 0; + parser_tx_obj.tx_json.flags.msg_from_grouping = 0; + parser_tx_obj.tx_json.filter_msg_from_count = 0; } - parser_tx_obj.filter_msg_from_count++; + parser_tx_obj.tx_json.filter_msg_from_count++; } - ZEMU_LOGF(200, "[ZEMU] %s [%d/%d]", tmp_key, parser_tx_obj.filter_msg_type_count, parser_tx_obj.filter_msg_from_count); + ZEMU_LOGF(200, "[ZEMU] %s [%d/%d]", tmp_key, parser_tx_obj.tx_json.filter_msg_type_count, parser_tx_obj.tx_json.filter_msg_from_count); break; } default: @@ -294,66 +299,91 @@ parser_error_t tx_indexRootFields() { display_cache.total_item_count += display_cache.root_item_number_subitems[root_item_idx]; } - parser_tx_obj.flags.cache_valid = 1; + parser_tx_obj.tx_json.flags.cache_valid = 1; CHECK_PARSER_ERR(calculate_is_default_chainid()) // turn off grouping if we are not in expert mode - if (tx_is_expert_mode()) { - parser_tx_obj.flags.msg_from_grouping = 0; + bool is_expert_or_default = false; + CHECK_PARSER_ERR(tx_is_expert_mode_or_not_default_chainid(&is_expert_or_default)) + if (is_expert_or_default) { + parser_tx_obj.tx_json.flags.msg_from_grouping = 0; } // check if from reference value matches the device address that will be signing - parser_tx_obj.flags.msg_from_grouping_hide_all = 0; + parser_tx_obj.tx_json.flags.msg_from_grouping_hide_all = 0; if (address_matches_own(reference_msg_from)) { - parser_tx_obj.flags.msg_from_grouping_hide_all = 1; + parser_tx_obj.tx_json.flags.msg_from_grouping_hide_all = 1; } return parser_ok; } -__Z_INLINE bool is_default_chainid() { +__Z_INLINE parser_error_t is_default_chainid(bool *is_default) { + if (is_default == NULL) { + return parser_unexpected_value; + } + CHECK_PARSER_ERR(tx_indexRootFields()) - return display_cache.is_default_chain; + *is_default = display_cache.is_default_chain; + + return parser_ok; } -bool tx_is_expert_mode() { - return app_mode_expert() || !is_default_chainid(); +parser_error_t tx_is_expert_mode_or_not_default_chainid(bool *expert_or_default) { + if (expert_or_default == NULL) { + return parser_unexpected_value; + } + + bool is_default = false; + CHECK_PARSER_ERR(is_default_chainid(&is_default)) + *expert_or_default = app_mode_expert() || !is_default; + + return parser_ok; } -__Z_INLINE uint8_t get_subitem_count(root_item_e root_item) { +__Z_INLINE parser_error_t get_subitem_count(root_item_e root_item, uint8_t *num_items) { + if (num_items == NULL) { + return parser_unexpected_value; + } + CHECK_PARSER_ERR(tx_indexRootFields()) - if (display_cache.total_item_count == 0) - return 0; + if (display_cache.total_item_count == 0) { + *num_items = 0; + return parser_ok; + } int32_t tmp_num_items = display_cache.root_item_number_subitems[root_item]; + bool is_expert_or_default = false; switch (root_item) { case root_item_chain_id: case root_item_sequence: case root_item_account_number: - if (!tx_is_expert_mode()) { + CHECK_PARSER_ERR(tx_is_expert_mode_or_not_default_chainid(&is_expert_or_default)) + if (!is_expert_or_default) { tmp_num_items = 0; } break; case root_item_msgs: { // Remove grouped items from list - if (parser_tx_obj.flags.msg_type_grouping && parser_tx_obj.filter_msg_type_count > 0) { + if (parser_tx_obj.tx_json.flags.msg_type_grouping && parser_tx_obj.tx_json.filter_msg_type_count > 0) { tmp_num_items += 1; // we leave main type - tmp_num_items -= parser_tx_obj.filter_msg_type_count; + tmp_num_items -= parser_tx_obj.tx_json.filter_msg_type_count; } - if (parser_tx_obj.flags.msg_from_grouping && parser_tx_obj.filter_msg_from_count > 0) { - if (!parser_tx_obj.flags.msg_from_grouping_hide_all) { + if (parser_tx_obj.tx_json.flags.msg_from_grouping && parser_tx_obj.tx_json.filter_msg_from_count > 0) { + if (!parser_tx_obj.tx_json.flags.msg_from_grouping_hide_all) { tmp_num_items += 1; // we leave main from } - tmp_num_items -= parser_tx_obj.filter_msg_from_count; + tmp_num_items -= parser_tx_obj.tx_json.filter_msg_from_count; } break; } case root_item_memo: break; case root_item_fee: - if (!tx_is_expert_mode()) { + CHECK_PARSER_ERR(tx_is_expert_mode_or_not_default_chainid(&is_expert_or_default)) + if (!is_expert_or_default) { tmp_num_items = 1; // Only Amount } break; @@ -363,8 +393,9 @@ __Z_INLINE uint8_t get_subitem_count(root_item_e root_item) { default: break; } + *num_items = tmp_num_items; - return tmp_num_items; + return parser_ok; } __Z_INLINE parser_error_t retrieve_tree_indexes(uint8_t display_index, root_item_e *root_item, uint8_t *subitem_index) { @@ -372,19 +403,28 @@ __Z_INLINE parser_error_t retrieve_tree_indexes(uint8_t display_index, root_item // consume indexed subpages until we get the item index in the subpage *root_item = 0; *subitem_index = 0; - while (get_subitem_count(*root_item) == 0) { + uint8_t num_items; + + CHECK_PARSER_ERR(get_subitem_count(*root_item, &num_items)); + while (num_items == 0) { (*root_item)++; + CHECK_PARSER_ERR(get_subitem_count(*root_item, &num_items)); } for (uint16_t i = 0; i < display_index; i++) { (*subitem_index)++; - const uint8_t subitem_count = get_subitem_count(*root_item); + uint8_t subitem_count = 0; + CHECK_PARSER_ERR(get_subitem_count(*root_item, &subitem_count)); if (*subitem_index >= subitem_count) { // Advance root index and skip empty items *subitem_index = 0; (*root_item)++; - while (get_subitem_count(*root_item) == 0) { + + uint8_t num_items_2 = 0; + CHECK_PARSER_ERR(get_subitem_count(*root_item, &num_items_2)); + while (num_items_2 == 0) { (*root_item)++; + CHECK_PARSER_ERR(get_subitem_count(*root_item, &num_items_2)); } } } @@ -401,8 +441,10 @@ parser_error_t tx_display_numItems(uint8_t *num_items) { CHECK_PARSER_ERR(tx_indexRootFields()) *num_items = 0; + uint8_t n_items = 0; for (root_item_e root_item = 0; root_item < NUM_REQUIRED_ROOT_PAGES; root_item++) { - *num_items += get_subitem_count(root_item); + CHECK_PARSER_ERR( get_subitem_count(root_item, &n_items)) + *num_items += n_items; } return parser_ok; @@ -417,7 +459,7 @@ parser_error_t tx_display_query(uint16_t displayIdx, uint8_t num_items; CHECK_PARSER_ERR(tx_display_numItems(&num_items)) - if (displayIdx < 0 || displayIdx >= num_items) { + if (displayIdx >= num_items) { return parser_display_idx_out_of_range; } @@ -429,8 +471,8 @@ parser_error_t tx_display_query(uint16_t displayIdx, static char tmp_val[2]; INIT_QUERY_CONTEXT(outKey, outKeyLen, tmp_val, sizeof(tmp_val), 0, get_root_max_level(root_index)) - parser_tx_obj.query.item_index = subitem_index; - parser_tx_obj.query._item_index_current = 0; + parser_tx_obj.tx_json.query.item_index = subitem_index; + parser_tx_obj.tx_json.query._item_index_current = 0; strncpy_s(outKey, get_required_root_item(root_index), outKeyLen); @@ -466,6 +508,7 @@ static const key_subst_t key_substitutions[] = { {"memo", "Memo"}, {"fee/amount", "Fee"}, {"fee/gas", "Gas"}, + {"fee/gas_limit", "Gas Limit"}, {"fee/granter", "Granter"}, {"fee/payer", "Payer"}, {"msgs/type", "Type"}, @@ -488,6 +531,7 @@ static const key_subst_t key_substitutions[] = { {"msgs/value/amount", "Amount"}, {"msgs/value/delegator_address", "Delegator"}, {"msgs/value/validator_address", "Validator"}, + {"msgs/value/withdraw_address", "Withdraw Address"}, {"msgs/value/validator_src_address", "Validator Source"}, {"msgs/value/validator_dst_address", "Validator Dest"}, {"msgs/value/description", "Description"}, @@ -496,9 +540,8 @@ static const key_subst_t key_substitutions[] = { {"msgs/value/proposal_type", "Proposal"}, {"msgs/value/proposer", "Proposer"}, {"msgs/value/title", "Title"}, - {"msgs/value/depositer", "Sender"}, + {"msgs/value/depositor", "Sender"}, {"msgs/value/proposal_id", "Proposal ID"}, - {"msgs/value/amount", "Amount"}, {"msgs/value/voter", "Description"}, {"msgs/value/option", "Option"}, }; @@ -508,12 +551,102 @@ parser_error_t tx_display_make_friendly() { // post process keys for (size_t i = 0; i < array_length(key_substitutions); i++) { - if (!strcmp(parser_tx_obj.query.out_key, key_substitutions[i].str1)) { - strncpy_s(parser_tx_obj.query.out_key, key_substitutions[i].str2, parser_tx_obj.query.out_key_len); - break; + const char* str1 = (const char*) PIC(key_substitutions[i].str1); + const char* str2 = (const char*) PIC(key_substitutions[i].str2); + const uint16_t str1Len = strlen(str1); + const uint16_t str2Len = strlen(str2); + + + const uint16_t outKeyLen = strnlen(parser_tx_obj.tx_json.query.out_key, parser_tx_obj.tx_json.query.out_key_len); + if ((outKeyLen == str1Len && strncmp(parser_tx_obj.tx_json.query.out_key, str1, str1Len) == 0) + && parser_tx_obj.tx_json.query.out_key_len >= str2Len) { + MEMZERO(parser_tx_obj.tx_json.query.out_key, parser_tx_obj.tx_json.query.out_key_len); + MEMCPY(parser_tx_obj.tx_json.query.out_key, str2, str2Len); + break; + } + } + + return parser_ok; +} + +static const ascii_subst_t ascii_substitutions[] = { + {0x07, 'a'}, {0x08, 'b'}, {0x0C, 'f'}, + {0x0A, 'n'}, {0x0D, 'r'}, {0x09, 't'}, + {0x0B, 'v'}, {0x5C, '\\'}, +}; + +parser_error_t tx_display_translation(char *dst, uint16_t dstLen, char *src, uint16_t srcLen) { + MEMZERO(dst, dstLen); + char *p = src; + uint16_t count = 0; + + while (p < src + srcLen) { + utf8_int32_t tmp_codepoint = 0; + p = utf8codepoint(p, &tmp_codepoint); + + if (tmp_codepoint < 0x0F || tmp_codepoint == 0x5C) { + bool found = false; + for (size_t i = 0; i < array_length(ascii_substitutions); i++) { + if ((char)tmp_codepoint == ascii_substitutions[i].ascii_code) { + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = '\\'; + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = ascii_substitutions[i].str; + found = true; + break; + } + } + if (!found) { + // Write out the value as a hex escape, \xNN + if (count > dstLen) { + return parser_unexpected_value; + } + snprintf(dst, 4, "\\x%.02X", tmp_codepoint); + dst += 4; + } + } else if (tmp_codepoint >= 32 && tmp_codepoint<=((int32_t) 0x7F)) { + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = (char) tmp_codepoint; + } else { + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = '\\'; + + uint8_t bytes_to_print = 8; + int32_t swapped = ZX_SWAP(tmp_codepoint); + if (tmp_codepoint > 0xFFFF) { + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = 'U'; + } else { + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = 'u'; + bytes_to_print = 4; + swapped = (swapped >> 16) & 0xFFFF; + } + + if (dstLen < (bytes_to_print + count)) { + return parser_unexpected_value; + } + + char buf[18] = {0}; + array_to_hexstr(buf, sizeof(buf), (uint8_t *) &swapped, 4); + for (int i = 0; i < bytes_to_print; i++) { + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = (buf[i] >= 'a' && buf[i] <= 'z') ? (buf[i] - 32) : buf[i]; + } + } + } + + if (src[srcLen - 1] == ' ' || src[srcLen - 1] == '@') { + if (src[dstLen - 1] + 1 > dstLen) { + return parser_unexpected_value; } + ASSERT_PTR_BOUNDS(count, dstLen); + *dst++ = '@'; } + // Terminate string + ASSERT_PTR_BOUNDS(count, dstLen); + *dst = 0; return parser_ok; } diff --git a/app/src/tx_display.h b/app/src/tx_display.h index d9ed8da7..5130a482 100644 --- a/app/src/tx_display.h +++ b/app/src/tx_display.h @@ -17,8 +17,10 @@ #pragma once #include +#include "parser_impl.h" #include #include "parser_txdef.h" +#include "coin.h" #ifdef __cplusplus extern "C" { @@ -34,7 +36,7 @@ typedef enum { root_item_tip, } root_item_e; -bool tx_is_expert_mode(); +parser_error_t tx_is_expert_mode_or_not_default_chainid(bool *expert_or_default); const char *get_required_root_item(root_item_e i); @@ -42,13 +44,11 @@ parser_error_t tx_display_query(uint16_t displayIdx, char *outKey, uint16_t outKeyLen, uint16_t *ret_value_token_index); -parser_error_t tx_display_readTx(parser_context_t *c, - const uint8_t *data, size_t dataLen); - parser_error_t tx_display_numItems(uint8_t *num_items); parser_error_t tx_display_make_friendly(); +parser_error_t tx_display_translation(char *dst, uint16_t dstLen, char *src, uint16_t srcLen); //--------------------------------------------- #ifdef __cplusplus diff --git a/app/src/tx_parser.c b/app/src/tx_parser.c index ea91d7e6..43296511 100644 --- a/app/src/tx_parser.c +++ b/app/src/tx_parser.c @@ -64,6 +64,7 @@ static const key_subst_t value_substitutions[] = { {"cosmos-sdk/MsgVote", "Vote"}, {"cosmos-sdk/MsgWithdrawDelegationReward", "Withdraw Reward"}, {"cosmos-sdk/MsgWithdrawValidatorCommission", "Withdraw Val. Commission"}, + {"cosmos-sdk/MsgSetWithdrawAddress", "Withdraw Set Address"}, {"cosmos-sdk/MsgMultiSend", "Multi Send"}, }; @@ -74,34 +75,38 @@ parser_error_t tx_getToken(uint16_t token_index, *pageCount = 0; MEMZERO(out_val, out_val_len); - const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; - const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; + const int16_t token_start = parser_tx_obj.tx_json.json.tokens[token_index].start; + const int16_t token_end = parser_tx_obj.tx_json.json.tokens[token_index].end; if (token_start > token_end) { return parser_unexpected_buffer_end; } - const char *inValue = parser_tx_obj.tx + token_start; + const char *inValue = parser_tx_obj.tx_json.tx + token_start; uint16_t inLen = token_end - token_start; // empty strings are considered the first page *pageCount = 1; if (inLen > 0) { for (uint32_t i = 0; i < array_length(value_substitutions); i++) { - const char *substStr = value_substitutions[i].str1; - const size_t substStrLen = strlen(substStr); - if (inLen == substStrLen && !MEMCMP(inValue, substStr, substStrLen)) { - inValue = value_substitutions[i].str2; - inLen = strlen(value_substitutions[i].str2); + const char* str1 = (const char*) PIC(value_substitutions[i].str1); + const char* str2 = (const char*) PIC(value_substitutions[i].str2); + const uint16_t str1Len = strlen(str1); + const uint16_t str2Len = strlen(str2); + + if (inLen == str1Len && strncmp(inValue, str1, str1Len) == 0) { + inValue = str2; + inLen = str2Len; //Extra Depth level for Multisend type - extraDepthLevel = (i == MULTISEND_KEY_IDX); + extraDepthLevel = false; + if (strstr(inValue, "Multi") != NULL) { + extraDepthLevel = true; + } break; } } - pageStringExt(out_val, out_val_len, inValue, inLen, pageIdx, pageCount); - } if (pageIdx >= *pageCount) { @@ -112,21 +117,21 @@ parser_error_t tx_getToken(uint16_t token_index, } __Z_INLINE void append_key_item(uint16_t token_index) { - if (*parser_tx_obj.query.out_key > 0) { + if (*parser_tx_obj.tx_json.query.out_key > 0) { // There is already something there, add separator - strcat_chunk_s(parser_tx_obj.query.out_key, - parser_tx_obj.query.out_key_len, + strcat_chunk_s(parser_tx_obj.tx_json.query.out_key, + parser_tx_obj.tx_json.query.out_key_len, "/", 1); } - const int16_t token_start = parser_tx_obj.json.tokens[token_index].start; - const int16_t token_end = parser_tx_obj.json.tokens[token_index].end; - const char *address_ptr = parser_tx_obj.tx + token_start; + const int16_t token_start = parser_tx_obj.tx_json.json.tokens[token_index].start; + const int16_t token_end = parser_tx_obj.tx_json.json.tokens[token_index].end; + const char *address_ptr = parser_tx_obj.tx_json.tx + token_start; const int32_t new_item_size = token_end - token_start; - strcat_chunk_s(parser_tx_obj.query.out_key, - parser_tx_obj.query.out_key_len, + strcat_chunk_s(parser_tx_obj.tx_json.query.out_key, + parser_tx_obj.tx_json.query.out_key_len, address_ptr, new_item_size); } @@ -138,31 +143,31 @@ __Z_INLINE void append_key_item(uint16_t token_index) { /////////////////////////// parser_error_t tx_traverse_find(uint16_t root_token_index, uint16_t *ret_value_token_index) { - const jsmntype_t token_type = parser_tx_obj.json.tokens[root_token_index].type; + const jsmntype_t token_type = parser_tx_obj.tx_json.json.tokens[root_token_index].type; CHECK_APP_CANARY() - if (parser_tx_obj.tx == NULL || root_token_index < 0) { + if (parser_tx_obj.tx_json.tx == NULL) { return parser_no_data; } - if (parser_tx_obj.query.max_level <= 0 || parser_tx_obj.query.max_depth <= 0 || + if (parser_tx_obj.tx_json.query.max_level <= 0 || parser_tx_obj.tx_json.query.max_depth <= 0 || token_type == JSMN_STRING || token_type == JSMN_PRIMITIVE) { const bool skipTypeField = - parser_tx_obj.flags.cache_valid && - parser_tx_obj.flags.msg_type_grouping && - is_msg_type_field(parser_tx_obj.query.out_key) && - parser_tx_obj.filter_msg_type_valid_idx != parser_tx_obj.query._item_index_current; + parser_tx_obj.tx_json.flags.cache_valid && + parser_tx_obj.tx_json.flags.msg_type_grouping && + is_msg_type_field(parser_tx_obj.tx_json.query.out_key) && + parser_tx_obj.tx_json.filter_msg_type_valid_idx != parser_tx_obj.tx_json.query._item_index_current; const bool skipFromFieldHidingRule = - parser_tx_obj.flags.msg_from_grouping_hide_all || - parser_tx_obj.filter_msg_from_valid_idx != parser_tx_obj.query._item_index_current; + parser_tx_obj.tx_json.flags.msg_from_grouping_hide_all || + parser_tx_obj.tx_json.filter_msg_from_valid_idx != parser_tx_obj.tx_json.query._item_index_current; const bool skipFromField = - parser_tx_obj.flags.cache_valid && - parser_tx_obj.flags.msg_from_grouping && - is_msg_from_field(parser_tx_obj.query.out_key) && + parser_tx_obj.tx_json.flags.cache_valid && + parser_tx_obj.tx_json.flags.msg_from_grouping && + is_msg_from_field(parser_tx_obj.tx_json.query.out_key) && skipFromFieldHidingRule; const bool skipField = skipFromField || skipTypeField; @@ -170,17 +175,17 @@ parser_error_t tx_traverse_find(uint16_t root_token_index, uint16_t *ret_value_t CHECK_APP_CANARY() // Early bail out - if (!skipField && parser_tx_obj.query._item_index_current == parser_tx_obj.query.item_index) { + if (!skipField && parser_tx_obj.tx_json.query._item_index_current == parser_tx_obj.tx_json.query.item_index) { *ret_value_token_index = root_token_index; CHECK_APP_CANARY() return parser_ok; } if (skipField) { - parser_tx_obj.query.item_index++; + parser_tx_obj.tx_json.query.item_index++; } - parser_tx_obj.query._item_index_current++; + parser_tx_obj.tx_json.query._item_index_current++; CHECK_APP_CANARY() return parser_query_no_results; } @@ -188,37 +193,37 @@ parser_error_t tx_traverse_find(uint16_t root_token_index, uint16_t *ret_value_t uint16_t el_count; parser_error_t err; - CHECK_PARSER_ERR(object_get_element_count(&parser_tx_obj.json, root_token_index, &el_count)) + CHECK_PARSER_ERR(object_get_element_count(&parser_tx_obj.tx_json.json, root_token_index, &el_count)) switch (token_type) { case JSMN_OBJECT: { - const size_t key_len = strlen(parser_tx_obj.query.out_key); + const size_t key_len = strlen(parser_tx_obj.tx_json.query.out_key); for (uint16_t i = 0; i < el_count; ++i) { uint16_t key_index; uint16_t value_index; - CHECK_PARSER_ERR(object_get_nth_key(&parser_tx_obj.json, root_token_index, i, &key_index)) - CHECK_PARSER_ERR(object_get_nth_value(&parser_tx_obj.json, root_token_index, i, &value_index)) + CHECK_PARSER_ERR(object_get_nth_key(&parser_tx_obj.tx_json.json, root_token_index, i, &key_index)) + CHECK_PARSER_ERR(object_get_nth_value(&parser_tx_obj.tx_json.json, root_token_index, i, &value_index)) // Skip writing keys if we are actually exploring to count append_key_item(key_index); CHECK_APP_CANARY() // When traversing objects both level and depth should be considered - parser_tx_obj.query.max_level--; - parser_tx_obj.query.max_depth--; + parser_tx_obj.tx_json.query.max_level--; + parser_tx_obj.tx_json.query.max_depth--; // Traverse the value, extracting subkeys err = tx_traverse_find(value_index, ret_value_token_index); CHECK_APP_CANARY() - parser_tx_obj.query.max_level++; - parser_tx_obj.query.max_depth++; + parser_tx_obj.tx_json.query.max_level++; + parser_tx_obj.tx_json.query.max_depth++; if (err == parser_ok) { return parser_ok; } - *(parser_tx_obj.query.out_key + key_len) = 0; + *(parser_tx_obj.tx_json.query.out_key + key_len) = 0; CHECK_APP_CANARY() } break; @@ -226,16 +231,16 @@ parser_error_t tx_traverse_find(uint16_t root_token_index, uint16_t *ret_value_t case JSMN_ARRAY: { for (uint16_t i = 0; i < el_count; ++i) { uint16_t element_index; - CHECK_PARSER_ERR(array_get_nth_element(&parser_tx_obj.json, + CHECK_PARSER_ERR(array_get_nth_element(&parser_tx_obj.tx_json.json, root_token_index, i, &element_index)) CHECK_APP_CANARY() // When iterating along an array, // the level does not change but we need to count the recursion - parser_tx_obj.query.max_depth--; + parser_tx_obj.tx_json.query.max_depth--; err = tx_traverse_find(element_index, ret_value_token_index); - parser_tx_obj.query.max_depth++; + parser_tx_obj.tx_json.query.max_depth++; CHECK_APP_CANARY() diff --git a/app/src/tx_parser.h b/app/src/tx_parser.h index 77dfedb2..2bebc3ad 100644 --- a/app/src/tx_parser.h +++ b/app/src/tx_parser.h @@ -30,23 +30,22 @@ extern "C" { #endif #define MAX_RECURSION_DEPTH 6 -#define MULTISEND_KEY_IDX 9 extern bool extraDepthLevel; #define INIT_QUERY_CONTEXT(_KEY, _KEY_LEN, _VAL, _VAL_LEN, _PAGE_IDX, _MAX_LEVEL) \ - parser_tx_obj.query._item_index_current = 0; \ - parser_tx_obj.query.max_depth = MAX_RECURSION_DEPTH; \ - parser_tx_obj.query.max_level = _MAX_LEVEL; \ + parser_tx_obj.tx_json.query._item_index_current = 0; \ + parser_tx_obj.tx_json.query.max_depth = MAX_RECURSION_DEPTH; \ + parser_tx_obj.tx_json.query.max_level = _MAX_LEVEL; \ \ - parser_tx_obj.query.item_index= 0; \ - parser_tx_obj.query.page_index = (_PAGE_IDX); \ + parser_tx_obj.tx_json.query.item_index= 0; \ + parser_tx_obj.tx_json.query.page_index = (_PAGE_IDX); \ \ MEMZERO(_KEY, (_KEY_LEN)); \ MEMZERO(_VAL, (_VAL_LEN)); \ - parser_tx_obj.query.out_key= _KEY; \ - parser_tx_obj.query.out_val= _VAL; \ - parser_tx_obj.query.out_key_len = (_KEY_LEN); \ - parser_tx_obj.query.out_val_len = (_VAL_LEN); + parser_tx_obj.tx_json.query.out_key= _KEY; \ + parser_tx_obj.tx_json.query.out_val= _VAL; \ + parser_tx_obj.tx_json.query.out_key_len = (_KEY_LEN); \ + parser_tx_obj.tx_json.query.out_val_len = (_VAL_LEN); parser_error_t tx_traverse_find(uint16_t root_token_index, uint16_t *ret_value_token_index); @@ -70,4 +69,3 @@ __Z_INLINE bool is_msg_from_field(char *field_name) { } #pragma clang diagnostic pop #endif - diff --git a/app/stax_icon.gif b/app/stax_icon.gif index 361019ee..bd338188 100644 Binary files a/app/stax_icon.gif and b/app/stax_icon.gif differ diff --git a/deps/nanosplus-secure-sdk b/deps/nanosplus-secure-sdk index 876b8a50..9eb46818 160000 --- a/deps/nanosplus-secure-sdk +++ b/deps/nanosplus-secure-sdk @@ -1 +1 @@ -Subproject commit 876b8a50c334ed3075e34fc3bc16b227d0c1f292 +Subproject commit 9eb46818441490bdc7a81bc6dd6c9f33d071bcbb diff --git a/deps/nanox-secure-sdk b/deps/nanox-secure-sdk index 876b8a50..9eb46818 160000 --- a/deps/nanox-secure-sdk +++ b/deps/nanox-secure-sdk @@ -1 +1 @@ -Subproject commit 876b8a50c334ed3075e34fc3bc16b227d0c1f292 +Subproject commit 9eb46818441490bdc7a81bc6dd6c9f33d071bcbb diff --git a/deps/tinycbor-ledger/LICENSE b/deps/tinycbor-ledger/LICENSE new file mode 100644 index 00000000..4aad977c --- /dev/null +++ b/deps/tinycbor-ledger/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Intel Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/deps/tinycbor-ledger/README.md b/deps/tinycbor-ledger/README.md new file mode 100644 index 00000000..a1fffa85 --- /dev/null +++ b/deps/tinycbor-ledger/README.md @@ -0,0 +1,4 @@ +# Workaround +Ledger NanoX SDK does not allow += in APP_SOURCE_PATH or INCLUDES_PATH +For this reason, in order to compile only a couple of files, we need to copy them over to this directory +Please refer to ../tinycbor for the complete source code diff --git a/deps/tinycbor-ledger/cborparser.c b/deps/tinycbor-ledger/cborparser.c new file mode 100644 index 00000000..654629bd --- /dev/null +++ b/deps/tinycbor-ledger/cborparser.c @@ -0,0 +1,1530 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE 1 +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE 1 +#endif +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include + +/** + * \defgroup CborParsing Parsing CBOR streams + * \brief Group of functions used to parse CBOR streams. + * + * TinyCBOR provides functions for pull-based stream parsing of a CBOR-encoded + * payload. The main data type for the parsing is a CborValue, which behaves + * like an iterator and can be used to extract the encoded data. It is first + * initialized with a call to cbor_parser_init() and is usually used to extract + * exactly one item, most often an array or map. + * + * Nested CborValue objects can be parsed using cbor_value_enter_container(). + * Each call to cbor_value_enter_container() must be matched by a call to + * cbor_value_leave_container(), with the exact same parameters. + * + * The example below initializes a CborParser object, begins the parsing with a + * CborValue and decodes a single integer: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * cbor_parser_init(buffer, len, 0, &parser, &value); + * cbor_value_get_int(&value, &result); + * return result; + * } + * \endcode + * + * The code above does no error checking, which means it assumes the data comes + * from a source trusted to send one properly-encoded integer. The following + * example does the exact same operation, but includes error checking and + * returns 0 on parsing failure: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * if (cbor_parser_init(buffer, len, 0, &parser, &value) != CborNoError) + * return 0; + * if (!cbor_value_is_integer(&value) || + * cbor_value_get_int(&value, &result) != CborNoError) + * return 0; + * return result; + * } + * \endcode + * + * Note, in the example above, that one can't distinguish a parsing failure + * from an encoded value of zero. Reporting a parsing error is left as an + * exercise to the reader. + * + * The code above does not execute a range-check either: it is possible that + * the value decoded from the CBOR stream encodes a number larger than what can + * be represented in a variable of type \c{int}. If detecting that case is + * important, the code should call cbor_value_get_int_checked() instead. + * + *

Memory and parsing constraints

+ * + * TinyCBOR is designed to run with little memory and with minimal overhead. + * Except where otherwise noted, the parser functions always run on constant + * time (O(1)), do not recurse and never allocate memory (thus, stack usage is + * bounded and is O(1)). + * + *

Error handling and preconditions

+ * + * All functions operating on a CborValue return a CborError condition, with + * CborNoError standing for the normal situation in which no parsing error + * occurred. All functions may return parsing errors in case the stream cannot + * be decoded properly, be it due to corrupted data or due to reaching the end + * of the input buffer. + * + * Error conditions must not be ignored. All decoder functions have undefined + * behavior if called after an error has been reported, and may crash. + * + * Some functions are also documented to have preconditions, like + * cbor_value_get_int() requiring that the input be an integral value. + * Violation of preconditions also results in undefined behavior and the + * program may crash. + */ + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \struct CborValue + * + * This type contains one value parsed from the CBOR stream. Each CborValue + * behaves as an iterator in a StAX-style parser. + * + * \if privatedocs + * Implementation details: the CborValue contains these fields: + * \list + * \li ptr: pointer to the actual data + * \li flags: flags from the decoder + * \li extra: partially decoded integer value (0, 1 or 2 bytes) + * \li remaining: remaining items in this collection after this item or UINT32_MAX if length is unknown + * \endlist + * \endif + */ + +static uint64_t extract_number_and_advance(CborValue *it) +{ + /* This function is only called after we've verified that the number + * here is valid, so we can just use _cbor_value_extract_int64_helper. */ + uint8_t descriptor; + uint64_t v = _cbor_value_extract_int64_helper(it); + + read_bytes_unchecked(it, &descriptor, 0, 1); + descriptor &= SmallValueMask; + + size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); + advance_bytes(it, bytesNeeded + 1); + + return v; +} + +static bool is_fixed_type(uint8_t type) +{ + return type != CborTextStringType && type != CborByteStringType && type != CborArrayType && + type != CborMapType; +} + +static CborError preparse_value(CborValue *it) +{ + enum { + /* flags to keep */ + FlagsToKeep = CborIteratorFlag_ContainerIsMap | CborIteratorFlag_NextIsMapKey + }; + uint8_t descriptor; + + /* are we at the end? */ + it->type = CborInvalidType; + it->flags &= FlagsToKeep; + if (!read_bytes(it, &descriptor, 0, 1)) + return CborErrorUnexpectedEOF; + + uint8_t type = descriptor & MajorTypeMask; + it->type = type; + it->extra = (descriptor &= SmallValueMask); + + if (descriptor > Value64Bit) { + if (unlikely(descriptor != IndefiniteLength)) + return type == CborSimpleType ? CborErrorUnknownType : CborErrorIllegalNumber; + if (likely(!is_fixed_type(type))) { + /* special case */ + it->flags |= CborIteratorFlag_UnknownLength; + it->type = type; + return CborNoError; + } + return type == CborSimpleType ? CborErrorUnexpectedBreak : CborErrorIllegalNumber; + } + + size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); + + if (bytesNeeded) { + if (!can_read_bytes(it, bytesNeeded + 1)) + return CborErrorUnexpectedEOF; + + it->extra = 0; + + /* read up to 16 bits into it->extra */ + if (bytesNeeded == 1) { + uint8_t extra; + read_bytes_unchecked(it, &extra, 1, bytesNeeded); + it->extra = extra; + } else if (bytesNeeded == 2) { + read_bytes_unchecked(it, &it->extra, 1, bytesNeeded); + it->extra = cbor_ntohs(it->extra); + } else { + cbor_static_assert(CborIteratorFlag_IntegerValueTooLarge == (Value32Bit & 3)); + cbor_static_assert((CborIteratorFlag_IntegerValueIs64Bit | + CborIteratorFlag_IntegerValueTooLarge) == (Value64Bit & 3)); + it->flags |= (descriptor & 3); + } + } + + uint8_t majortype = type >> MajorTypeShift; + if (majortype == NegativeIntegerType) { + it->flags |= CborIteratorFlag_NegativeInteger; + it->type = CborIntegerType; + } else if (majortype == SimpleTypesType) { + switch (descriptor) { + case FalseValue: + it->extra = false; + it->type = CborBooleanType; + break; + + case SinglePrecisionFloat: + case DoublePrecisionFloat: + it->flags |= CborIteratorFlag_IntegerValueTooLarge; + __attribute__((fallthrough)); + + case TrueValue: + case NullValue: + case UndefinedValue: + case HalfPrecisionFloat: + read_bytes_unchecked(it, &it->type, 0, 1); + break; + + case SimpleTypeInNextByte: +#ifndef CBOR_PARSER_NO_STRICT_CHECKS + if (unlikely(it->extra < 32)) { + it->type = CborInvalidType; + return CborErrorIllegalSimpleType; + } +#endif + break; + + case 28: + case 29: + case 30: + case Break: + cbor_assert(false); /* these conditions can't be reached */ + return CborErrorUnexpectedBreak; + } + } + + return CborNoError; +} + +static CborError preparse_next_value_nodecrement(CborValue *it) +{ + uint8_t byte; + if (it->remaining == UINT32_MAX && read_bytes(it, &byte, 0, 1) && byte == (uint8_t)BreakByte) { + /* end of map or array */ + if ((it->flags & CborIteratorFlag_ContainerIsMap && it->flags & CborIteratorFlag_NextIsMapKey) + || it->type == CborTagType) { + /* but we weren't expecting it! */ + return CborErrorUnexpectedBreak; + } + it->type = CborInvalidType; + it->remaining = 0; + it->flags |= CborIteratorFlag_UnknownLength; /* leave_container must consume the Break */ + return CborNoError; + } + + return preparse_value(it); +} + +static CborError preparse_next_value(CborValue *it) +{ + /* tags don't count towards item totals or whether we've successfully + * read a map's key or value */ + bool itemCounts = it->type != CborTagType; + + if (it->remaining != UINT32_MAX) { + if (itemCounts && --it->remaining == 0) { + it->type = CborInvalidType; + it->flags &= ~CborIteratorFlag_UnknownLength; /* no Break to consume */ + return CborNoError; + } + } + if (itemCounts) { + /* toggle the flag indicating whether this was a map key */ + it->flags ^= CborIteratorFlag_NextIsMapKey; + } + return preparse_next_value_nodecrement(it); +} + +static CborError advance_internal(CborValue *it) +{ + uint64_t length = extract_number_and_advance(it); + + if (it->type == CborByteStringType || it->type == CborTextStringType) { + cbor_assert(length == (size_t)length); + cbor_assert((it->flags & CborIteratorFlag_UnknownLength) == 0); + advance_bytes(it, length); + } + + return preparse_next_value(it); +} + +/** \internal + * + * Decodes the CBOR integer value when it is larger than the 16 bits available + * in value->extra. This function requires that value->flags have the + * CborIteratorFlag_IntegerValueTooLarge flag set. + * + * This function is also used to extract single- and double-precision floating + * point values (SinglePrecisionFloat == Value32Bit and DoublePrecisionFloat == + * Value64Bit). + */ +uint64_t _cbor_value_decode_int64_internal(const CborValue *value) +{ + cbor_assert(value->flags & CborIteratorFlag_IntegerValueTooLarge || + value->type == CborFloatType || value->type == CborDoubleType); + if (value->flags & CborIteratorFlag_IntegerValueIs64Bit) + return read_uint64(value, 1); + + return read_uint32(value, 1); +} + +/** + * Initializes the CBOR parser for parsing \a size bytes beginning at \a + * buffer. Parsing will use flags set in \a flags. The iterator to the first + * element is returned in \a it. + * + * The \a parser structure needs to remain valid throughout the decoding + * process. It is not thread-safe to share one CborParser among multiple + * threads iterating at the same time, but the object can be copied so multiple + * threads can iterate. + */ +CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, CborParser *parser, CborValue *it) +{ + memset(parser, 0, sizeof(*parser)); + parser->source.end = buffer + size; + parser->flags = (enum CborParserGlobalFlags)flags; + it->parser = parser; + it->source.ptr = buffer; + it->remaining = 1; /* there's one type altogether, usually an array or map */ + it->flags = 0; + return preparse_value(it); +} + +CborError cbor_parser_init_reader(const struct CborParserOperations *ops, CborParser *parser, CborValue *it, void *token) +{ + memset(parser, 0, sizeof(*parser)); + parser->source.ops = ops; + parser->flags = CborParserFlag_ExternalSource; + it->parser = parser; + it->source.token = token; + it->remaining = 1; + return preparse_value(it); +} + +/** + * \fn bool cbor_value_at_end(const CborValue *it) + * + * Returns true if \a it has reached the end of the iteration, usually when + * advancing after the last item in an array or map. + * + * In the case of the outermost CborValue object, this function returns true + * after decoding a single element. A pointer to the first byte of the + * remaining data (if any) can be obtained with cbor_value_get_next_byte(). + * + * \sa cbor_value_advance(), cbor_value_is_valid(), cbor_value_get_next_byte() + */ + +/** + * \fn const uint8_t *cbor_value_get_next_byte(const CborValue *it) + * + * Returns a pointer to the next byte that would be decoded if this CborValue + * object were advanced. + * + * This function is useful if cbor_value_at_end() returns true for the + * outermost CborValue: the pointer returned is the first byte of the data + * remaining in the buffer, if any. Code can decide whether to begin decoding a + * new CBOR data stream from this point, or parse some other data appended to + * the same buffer. + * + * This function may be used even after a parsing error. If that occurred, + * then this function returns a pointer to where the parsing error occurred. + * Note that the error recovery is not precise and the pointer may not indicate + * the exact byte containing bad data. + * + * This function makes sense only when using a linear buffer (that is, when the + * parser is initialize by cbor_parser_init()). If using an external source, + * this function may return garbage; instead, consult the external source itself + * to find out more details about the presence of more data. + * + * \sa cbor_value_at_end() + */ + +CborError cbor_value_reparse(CborValue *it) +{ + if (it->flags & CborIteratorFlag_IteratingStringChunks) + return CborNoError; + return preparse_next_value_nodecrement(it); +} + +/** + * \fn bool cbor_value_is_valid(const CborValue *it) + * + * Returns true if the iterator \a it contains a valid value. Invalid iterators + * happen when iteration reaches the end of a container (see \ref + * cbor_value_at_end()) or when a search function resulted in no matches. + * + * \sa cbor_value_advance(), cbor_value_at_end(), cbor_value_get_type() + */ + +/** + * Performs a basic validation of the CBOR stream pointed by \a it and returns + * the error it found. If no error was found, it returns CborNoError and the + * application can iterate over the items with certainty that no other errors + * will appear during parsing. + * + * A basic validation checks for: + * \list + * \li absence of undefined additional information bytes; + * \li well-formedness of all numbers, lengths, and simple values; + * \li string contents match reported sizes; + * \li arrays and maps contain the number of elements they are reported to have; + * \endlist + * + * For further checks, see cbor_value_validate(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance(). + * + * \sa cbor_value_validate(), cbor_value_advance() + */ +CborError cbor_value_validate_basic(const CborValue *it) +{ + CborValue value = *it; + return cbor_value_advance(&value); +} + +/** + * Advances the CBOR value \a it by one fixed-size position. Fixed-size types + * are: integers, tags, simple types (including boolean, null and undefined + * values) and floating point types. + * + * If the type is not of fixed size, this function has undefined behavior. Code + * must be sure that the current type is one of the fixed-size types before + * calling this function. This function is provided because it can guarantee + * that it runs in constant time (O(1)). + * + * If the caller is not able to determine whether the type is fixed or not, code + * can use the cbor_value_advance() function instead. + * + * \sa cbor_value_at_end(), cbor_value_advance(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance_fixed(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + cbor_assert(is_fixed_type(it->type)); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_internal(it); +} + +static CborError advance_recursive(CborValue *it, int nestingLevel) +{ + CborError err; + CborValue recursed; + + if (is_fixed_type(it->type)) + return advance_internal(it); + + if (!cbor_value_is_container(it)) { + size_t len = SIZE_MAX; + return _cbor_value_copy_string(it, NULL, &len, it); + } + + /* map or array */ + if (nestingLevel == 0) + return CborErrorNestingTooDeep; + + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; + while (!cbor_value_at_end(&recursed)) { + err = advance_recursive(&recursed, nestingLevel - 1); + if (err) + return err; + } + return cbor_value_leave_container(it, &recursed); +} + + +/** + * Advances the CBOR value \a it by one element, skipping over containers. + * Unlike cbor_value_advance_fixed(), this function can be called on a CBOR + * value of any type. However, if the type is a container (map or array) or a + * string with a chunked payload, this function will not run in constant time + * and will recurse into itself (it will run on O(n) time for the number of + * elements or chunks and will use O(n) memory for the number of nested + * containers). + * + * The number of recursions can be limited at compile time to avoid stack + * exhaustion in constrained systems. + * + * \sa cbor_value_at_end(), cbor_value_advance_fixed(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_recursive(it, CBOR_PARSER_MAX_RECURSIONS); +} + +/** + * \fn bool cbor_value_is_tag(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR tag. + * + * \sa cbor_value_get_tag(), cbor_value_skip_tag() + */ + +/** + * \fn CborError cbor_value_get_tag(const CborValue *value, CborTag *result) + * + * Retrieves the CBOR tag value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a CBOR tag value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_tag is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_tag() + */ + +/** + * Advances the CBOR value \a it until it no longer points to a tag. If \a it is + * already not pointing to a tag, then this function returns it unchanged. + * + * This function does not run in constant time: it will run on O(n) for n being + * the number of tags. It does use constant memory (O(1) memory requirements). + * + * \sa cbor_value_advance_fixed(), cbor_value_advance() + */ +CborError cbor_value_skip_tag(CborValue *it) +{ + while (cbor_value_is_tag(it)) { + CborError err = cbor_value_advance_fixed(it); + if (err) + return err; + } + return CborNoError; +} + +/** + * \fn bool cbor_value_is_container(const CborValue *it) + * + * Returns true if the \a it value is a container and requires recursion in + * order to decode (maps and arrays), false otherwise. + */ + +/** + * Creates a CborValue iterator pointing to the first element of the container + * represented by \a it and saves it in \a recursed. The \a it container object + * needs to be kept and passed again to cbor_value_leave_container() in order + * to continue iterating past this container. + * + * The \a it CborValue iterator must point to a container. + * + * \sa cbor_value_is_container(), cbor_value_leave_container(), cbor_value_advance() + */ +CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed) +{ + cbor_static_assert(CborIteratorFlag_ContainerIsMap == (CborMapType & ~CborArrayType)); + cbor_assert(cbor_value_is_container(it)); + *recursed = *it; + + if (it->flags & CborIteratorFlag_UnknownLength) { + recursed->remaining = UINT32_MAX; + advance_bytes(recursed, 1); + } else { + uint64_t len = extract_number_and_advance(recursed); + + recursed->remaining = (uint32_t)len; + if (recursed->remaining != len || len == UINT32_MAX) { + /* back track the pointer to indicate where the error occurred */ + copy_current_position(recursed, it); + return CborErrorDataTooLarge; + } + if (recursed->type == CborMapType) { + /* maps have keys and values, so we need to multiply by 2 */ + if (recursed->remaining > UINT32_MAX / 2) { + /* back track the pointer to indicate where the error occurred */ + copy_current_position(recursed, it); + return CborErrorDataTooLarge; + } + recursed->remaining *= 2; + } + if (len == 0) { + /* the case of the empty container */ + recursed->type = CborInvalidType; + return CborNoError; + } + } + recursed->flags = (recursed->type & CborIteratorFlag_ContainerIsMap); + return preparse_next_value_nodecrement(recursed); +} + +/** + * Updates \a it to point to the next element after the container. The \a + * recursed object needs to point to the element obtained either by advancing + * the last element of the container (via cbor_value_advance(), + * cbor_value_advance_fixed(), a nested cbor_value_leave_container(), or the \c + * next pointer from cbor_value_copy_string() or cbor_value_dup_string()). + * + * The \a it and \a recursed parameters must be the exact same as passed to + * cbor_value_enter_container(). + * + * \sa cbor_value_enter_container(), cbor_value_at_end() + */ +CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed) +{ + cbor_assert(cbor_value_is_container(it)); + cbor_assert(recursed->type == CborInvalidType); + + copy_current_position(it, recursed); + if (recursed->flags & CborIteratorFlag_UnknownLength) + advance_bytes(it, 1); + return preparse_next_value(it); +} + + +/** + * \fn CborType cbor_value_get_type(const CborValue *value) + * + * Returns the type of the CBOR value that the iterator \a value points to. If + * \a value does not point to a valid value, this function returns \ref + * CborInvalidType. + * + * TinyCBOR also provides functions to test directly if a given CborValue object + * is of a given type, like cbor_value_is_text_string() and cbor_value_is_null(). + * + * \sa cbor_value_is_valid() + */ + +/** + * \fn bool cbor_value_is_null(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR null type. + * + * \sa cbor_value_is_valid(), cbor_value_is_undefined() + */ + +/** + * \fn bool cbor_value_is_undefined(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR undefined type. + * + * \sa cbor_value_is_valid(), cbor_value_is_null() + */ + +/** + * \fn bool cbor_value_is_boolean(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR boolean + * type (true or false). + * + * \sa cbor_value_is_valid(), cbor_value_get_boolean() + */ + +/** + * \fn CborError cbor_value_get_boolean(const CborValue *value, bool *result) + * + * Retrieves the boolean value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a boolean value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_boolean is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_boolean() + */ + +/** + * \fn bool cbor_value_is_simple_type(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR Simple Type + * type (other than true, false, null and undefined). + * + * \sa cbor_value_is_valid(), cbor_value_get_simple_type() + */ + +/** + * \fn CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) + * + * Retrieves the CBOR Simple Type value that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a simple_type + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_simple_type is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_simple_type() + */ + +/** + * \fn bool cbor_value_is_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR integer + * type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_uint64, cbor_value_get_raw_integer + */ + +/** + * \fn bool cbor_value_is_unsigned_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR unsigned + * integer type (positive values or zero). + * + * \sa cbor_value_is_valid(), cbor_value_get_uint64() + */ + +/** + * \fn bool cbor_value_is_negative_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR negative + * integer type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_raw_integer + */ + +/** + * \fn CborError cbor_value_get_int(const CborValue *value, int *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int} are silently truncated to fit. Use + * cbor_value_get_int_checked() if that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_int64(const CborValue *value, int64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int64_t} are silently truncated to fit. Use + * cbor_value_get_int64_checked() that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an unsigned integer + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_unsigned_integer is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_unsigned_integer() + */ + +/** + * \fn CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * This function is provided because CBOR negative integers can assume values + * that cannot be represented with normal 64-bit integer variables. + * + * If the integer is unsigned (that is, if cbor_value_is_unsigned_integer() + * returns true), then \a result will contain the actual value. If the integer + * is negative, then \a result will contain the absolute value of that integer, + * minus one. That is, \c {actual = -result - 1}. On architectures using two's + * complement for representation of negative integers, it is equivalent to say + * that \a result will contain the bitwise negation of the actual value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int64(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use either + * cbor_value_get_uint64() (if the number is positive) or + * cbor_value_get_raw_integer(). + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64() + */ +CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * The range for int64_t is -2^63 to 2^63-1 (int64_t is required to be + * two's complement, C11 7.20.1.1 paragraph 3), which in CBOR is + * represented the same way, differing only on the "sign bit" (the major + * type). + */ + + if (unlikely(v > (uint64_t)INT64_MAX)) + return CborErrorDataTooLarge; + + *result = v; + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use one of the other integer + * functions to obtain the value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64(), + * cbor_value_get_uint64(), cbor_value_get_int64_checked(), cbor_value_get_raw_integer() + */ +CborError cbor_value_get_int_checked(const CborValue *value, int *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * But we can convert from signed to unsigned without fault (paragraph 2). + * + * The range for int is implementation-defined and int is not guaranteed to use + * two's complement representation (although int32_t is). + */ + + if (value->flags & CborIteratorFlag_NegativeInteger) { + if (unlikely(v > (unsigned) -(INT_MIN + 1))) + return CborErrorDataTooLarge; + + *result = (int)v; + *result = -*result - 1; + } else { + if (unlikely(v > (uint64_t)INT_MAX)) + return CborErrorDataTooLarge; + + *result = (int)v; + } + return CborNoError; + +} + +/** + * \fn bool cbor_value_is_length_known(const CborValue *value) + * + * Returns true if the length of this type is known without calculation. That + * is, if the length of this CBOR string, map or array is encoded in the data + * stream, this function returns true. If the length is not encoded, it returns + * false. + * + * If the length is known, code can call cbor_value_get_string_length(), + * cbor_value_get_array_length() or cbor_value_get_map_length() to obtain the + * length. If the length is not known but is necessary, code can use the + * cbor_value_calculate_string_length() function (no equivalent function is + * provided for maps and arrays). + */ + +/** + * \fn bool cbor_value_is_text_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR text strings are UTF-8 encoded and usually contain + * human-readable text. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_text_string(), cbor_value_dup_text_string() + */ + +/** + * \fn bool cbor_value_is_byte_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR byte + * string. CBOR byte strings are binary data with no specified encoding or + * format. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_byte_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_get_string_length(const CborValue *value, size_t *length) + * + * Extracts the length of the byte or text string that \a value points to and + * stores it in \a result. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * If the length of this string is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * If the length of the string is required but the length was not encoded, use + * cbor_value_calculate_string_length(), but note that that function does not + * run in constant time. + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known(), cbor_value_calculate_string_length() + */ + +/** + * Calculates the length of the byte or text string that \a value points to and + * stores it in \a len. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * This function is different from cbor_value_get_string_length() in that it + * calculates the length even for strings sent in chunks. For that reason, this + * function may not run in constant time (it will run in O(n) time on the + * number of chunks). It does use constant memory (O(1)). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_get_string_length(), cbor_value_copy_text_string(), cbor_value_copy_byte_string(), cbor_value_is_length_known() + */ +CborError cbor_value_calculate_string_length(const CborValue *value, size_t *len) +{ + *len = SIZE_MAX; + return _cbor_value_copy_string(value, NULL, len, NULL); +} + +CborError _cbor_value_begin_string_iteration(CborValue *it) +{ + it->flags |= CborIteratorFlag_IteratingStringChunks | + CborIteratorFlag_BeforeFirstStringChunk; + if (!cbor_value_is_length_known(it)) { + /* chunked string: we're before the first chunk; + * advance to the first chunk */ + advance_bytes(it, 1); + } + + return CborNoError; +} + +CborError _cbor_value_finish_string_iteration(CborValue *it) +{ + if (!cbor_value_is_length_known(it)) + advance_bytes(it, 1); /* skip the Break */ + + return preparse_next_value(it); +} + +static CborError get_string_chunk_size(const CborValue *it, size_t *offset, size_t *len) +{ + uint8_t descriptor; + size_t bytesNeeded = 1; + + if (cbor_value_is_length_known(it) && (it->flags & CborIteratorFlag_BeforeFirstStringChunk) == 0) + return CborErrorNoMoreStringChunks; + + /* are we at the end? */ + if (!read_bytes(it, &descriptor, 0, 1)) + return CborErrorUnexpectedEOF; + + if (descriptor == BreakByte) + return CborErrorNoMoreStringChunks; + if ((descriptor & MajorTypeMask) != it->type) + return CborErrorIllegalType; + + /* find the string length */ + descriptor &= SmallValueMask; + if (descriptor < Value8Bit) { + *len = descriptor; + } else if (unlikely(descriptor > Value64Bit)) { + return CborErrorIllegalNumber; + } else { + uint64_t val; + bytesNeeded = (size_t)(1 << (descriptor - Value8Bit)); + if (!can_read_bytes(it, 1 + bytesNeeded)) + return CborErrorUnexpectedEOF; + + if (descriptor <= Value16Bit) { + if (descriptor == Value16Bit) + val = read_uint16(it, 1); + else + val = read_uint8(it, 1); + } else { + if (descriptor == Value32Bit) + val = read_uint32(it, 1); + else + val = read_uint64(it, 1); + } + + *len = val; + if (*len != val) + return CborErrorDataTooLarge; + + ++bytesNeeded; + } + + *offset = bytesNeeded; + return CborNoError; +} + +CborError _cbor_value_get_string_chunk_size(const CborValue *value, size_t *len) +{ + size_t offset; + return get_string_chunk_size(value, &offset, len); +} + +static CborError get_string_chunk(CborValue *it, const void **bufferptr, size_t *len) +{ + size_t offset; + CborError err = get_string_chunk_size(it, &offset, len); + if (err) + return err; + + /* we're good, transfer the string now */ + err = transfer_string(it, bufferptr, offset, *len); + if (err) + return err; + + /* we've iterated at least once */ + it->flags &= ~CborIteratorFlag_BeforeFirstStringChunk; + return CborNoError; +} + +/** + * \fn CborError cbor_value_get_text_string_chunk(const CborValue *value, const char **bufferptr, size_t *len, CborValue *next) + * + * Extracts one text string chunk pointed to by \a value and stores a pointer + * to the data in \a buffer and the size in \a len, which must not be null. If + * no more chunks are available, then \a bufferptr will be set to null. This + * function may be used to iterate over any string without causing its contents + * to be copied to a separate buffer, like the convenience function + * cbor_value_copy_text_string() does. + * + * It is designed to be used in code like: + * + * \code + * if (cbor_value_is_text_string(value)) { + * char *ptr; + * size_t len; + * while (1) { + * err = cbor_value_get_text_string_chunk(value, &ptr, &len, &value)); + * if (err) return err; + * if (ptr == NULL) return CborNoError; + * consume(ptr, len); + * } + * } + * \endcode + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. During iteration, the pointer must only be passed back + * again to this function; passing it to any other function in this library + * results in undefined behavior. If there are no more chunks to be read from + * \a value, then \a next will be set to the next item after this string; if \a + * value points to the last item, then \a next will be invalid. + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_caculate_string_length(), cbor_value_get_byte_string_chunk() + */ + +/** + * \fn CborError cbor_value_get_byte_string_chunk(const CborValue *value, const char **bufferptr, size_t *len, CborValue *next) + * + * Extracts one byte string chunk pointed to by \a value and stores a pointer + * to the data in \a buffer and the size in \a len, which must not be null. If + * no more chunks are available, then \a bufferptr will be set to null. This + * function may be used to iterate over any string without causing its contents + * to be copied to a separate buffer, like the convenience function + * cbor_value_copy_byte_string() does. + * + * It is designed to be used in code like: + * + * \code + * if (cbor_value_is_byte_string(value)) { + * char *ptr; + * size_t len; + * while (1) { + * err = cbor_value_get_byte_string_chunk(value, &ptr, &len, &value)); + * if (err) return err; + * if (ptr == NULL) return CborNoError; + * consume(ptr, len); + * } + * } + * \endcode + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. During iteration, the pointer must only be passed back + * again to this function; passing it to any other function in this library + * results in undefined behavior. If there are no more chunks to be read from + * \a value, then \a next will be set to the next item after this string; if \a + * value points to the last item, then \a next will be invalid. + * + * \sa cbor_value_dup_byte_string(), cbor_value_copy_byte_string(), cbor_value_caculate_string_length(), cbor_value_get_text_string_chunk() + */ + +CborError _cbor_value_get_string_chunk(const CborValue *value, const void **bufferptr, + size_t *len, CborValue *next) +{ + CborValue tmp; + if (!next) + next = &tmp; + *next = *value; + return get_string_chunk(next, bufferptr, len); +} + +/* We return uintptr_t so that we can pass memcpy directly as the iteration + * function. The choice is to optimize for memcpy, which is used in the base + * parser API (cbor_value_copy_string), while memcmp is used in convenience API + * only. */ +typedef uintptr_t (*IterateFunction)(char *, const uint8_t *, size_t); + +static uintptr_t iterate_noop(char *dest, const uint8_t *src, size_t len) +{ + (void)dest; + (void)src; + (void)len; + return true; +} + +static uintptr_t iterate_memcmp(char *s1, const uint8_t *s2, size_t len) +{ + return memcmp(s1, (const char *)s2, len) == 0; +} + +static uintptr_t iterate_memcpy(char *dest, const uint8_t *src, size_t len) +{ + return (uintptr_t)memcpy(dest, src, len); +} + +static CborError iterate_string_chunks(const CborValue *value, char *buffer, size_t *buflen, + bool *result, CborValue *next, IterateFunction func) +{ + CborError err; + CborValue tmp; + size_t total = 0; + const void *ptr; + + cbor_assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + if (!next) + next = &tmp; + *next = *value; + *result = true; + + err = _cbor_value_begin_string_iteration(next); + if (err) + return err; + + while (1) { + size_t newTotal; + size_t chunkLen; + err = get_string_chunk(next, &ptr, &chunkLen); + if (err == CborErrorNoMoreStringChunks) + break; + if (err) + return err; + + if (unlikely(add_check_overflow(total, chunkLen, &newTotal))) + return CborErrorDataTooLarge; + + if (*result && *buflen >= newTotal) + *result = !!func(buffer == NULL ? buffer : buffer + total, (const uint8_t *)ptr, chunkLen); + else + *result = false; + + total = newTotal; + } + + /* is there enough room for the ending NUL byte? */ + if (*result && *buflen > total) { + uint8_t nul[] = { 0 }; + *result = !!func(buffer == NULL ? buffer : buffer + total, nul, 1); + } + *buflen = total; + return _cbor_value_finish_string_iteration(next); +} + +/** + * \fn CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed to by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of text strings. That byte is + * not included in the returned value of \c{*buflen}. If there was no space for + * the terminating null, no error is returned, so callers must check the value + * of *buflen after the call, before relying on the '\0'; if it has not been + * changed by the call, there is no '\0'-termination on the buffer's contents. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_get_text_string_chunk() cbor_value_dup_text_string(), cbor_value_copy_byte_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +/** + * \fn CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of null-terminated strings. + * That byte is not included in the returned value of \c{*buflen}. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_get_byte_string_chunk(), cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next) +{ + bool copied_all; + CborError err = iterate_string_chunks(value, (char*)buffer, buflen, &copied_all, next, + buffer ? iterate_memcpy : iterate_noop); + return err ? err : + copied_all ? CborNoError : CborErrorOutOfMemory; +} + +/** + * Compares the entry \a value with the string \a string and stores the result + * in \a result. If the value is different from \a string \a result will + * contain \c false. + * + * The entry at \a value may be a tagged string. If \a value is not a string or + * a tagged string, the comparison result will be false. + * + * CBOR requires text strings to be encoded in UTF-8, but this function does + * not validate either the strings in the stream or the string \a string to be + * matched. Moreover, comparison is done on strict codepoint comparison, + * without any Unicode normalization. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_skip_tag(), cbor_value_copy_text_string() + */ +CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result) +{ + size_t len; + CborValue copy = *value; + CborError err = cbor_value_skip_tag(©); + if (err) + return err; + if (!cbor_value_is_text_string(©)) { + *result = false; + return CborNoError; + } + + len = strlen(string); + return iterate_string_chunks(©, CONST_CAST(char *, string), &len, result, NULL, iterate_memcmp); +} + +/** + * \fn bool cbor_value_is_array(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR array. + * + * \sa cbor_value_is_valid(), cbor_value_is_map() + */ + +/** + * \fn CborError cbor_value_get_array_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR array that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a CBOR array, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_array is recommended. + * + * If the length of this array is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * \fn bool cbor_value_is_map(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR map. + * + * \sa cbor_value_is_valid(), cbor_value_is_array() + */ + +/** + * \fn CborError cbor_value_get_map_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR map that \a value points to and stores it in + * \a result. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the length of this map is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * Attempts to find the value in map \a map that corresponds to the text string + * entry \a string. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the item is found, it is stored in \a result. If no item is found + * matching the key, then \a result will contain an element of type \ref + * CborInvalidType. Matching is performed using + * cbor_value_text_string_equals(), so tagged strings will also match. + * + * This function has a time complexity of O(n) where n is the number of + * elements in the map to be searched. In addition, this function is has O(n) + * memory requirement based on the number of nested containers (maps or arrays) + * found as elements of this map. + * + * \sa cbor_value_is_valid(), cbor_value_text_string_equals(), cbor_value_advance() + */ +CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element) +{ + CborError err; + size_t len = strlen(string); + cbor_assert(cbor_value_is_map(map)); + err = cbor_value_enter_container(map, element); + if (err) + goto error; + + while (!cbor_value_at_end(element)) { + /* find the non-tag so we can compare */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + if (cbor_value_is_text_string(element)) { + bool equals; + size_t dummyLen = len; + err = iterate_string_chunks(element, CONST_CAST(char *, string), &dummyLen, + &equals, element, iterate_memcmp); + if (err) + goto error; + if (equals) + return preparse_value(element); + } else { + /* skip this key */ + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* skip this value */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* not found */ + element->type = CborInvalidType; + return CborNoError; + +error: + element->type = CborInvalidType; + return err; +} + +/** + * \fn bool cbor_value_is_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (32-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_float(const CborValue *value, float *result) + * + * Retrieves the CBOR single-precision floating point (32-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a single-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_float is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_float(), cbor_value_get_double() + */ + +/** + * \fn bool cbor_value_is_double(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * double-precision floating point (64-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_float(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_double(const CborValue *value, float *result) + * + * Retrieves the CBOR double-precision floating point (64-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a double-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_double is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_double(), cbor_value_get_float() + */ + +/** + * \fn bool cbor_value_is_half_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (16-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_float() + */ + +/** + * \fn CborError cbor_value_get_half_float(const CborValue *value, void *result) + * + * Retrieves the CBOR half-precision floating point (16-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a half-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_half_float is recommended. + * + * Note: since the C language does not have a standard type for half-precision + * floating point, this function takes a \c{void *} as a parameter for the + * storage area, which must be at least 16 bits wide. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_half_float_as_float(), cbor_value_get_float() + */ + +/** @} */ diff --git a/deps/tinycbor-ledger/cborvalidation.c b/deps/tinycbor-ledger/cborvalidation.c new file mode 100644 index 00000000..ae02864f --- /dev/null +++ b/deps/tinycbor-ledger/cborvalidation.c @@ -0,0 +1,657 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "utf8_p.h" + +#include + +#ifndef CBOR_NO_FLOATING_POINT +# include +# include +#endif + + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \enum CborValidationFlags + * The CborValidationFlags enum contains flags that control the validation of a + * CBOR stream. + * + * \value CborValidateBasic Validates only the syntactic correctedness of the stream. + * \value CborValidateCanonical Validates that the stream is in canonical format, according to + * RFC 7049 section 3.9. + * \value CborValidateStrictMode Performs strict validation, according to RFC 7049 section 3.10. + * \value CborValidateStrictest Attempt to perform the strictest validation we know of. + * + * \value CborValidateShortestIntegrals (Canonical) Validate that integral numbers and lengths are + * enconded in their shortest form possible. + * \value CborValidateShortestFloatingPoint (Canonical) Validate that floating-point numbers are encoded + * in their shortest form possible. + * \value CborValidateShortestNumbers (Canonical) Validate both integral and floating-point numbers + * are in their shortest form possible. + * \value CborValidateNoIndeterminateLength (Canonical) Validate that no string, array or map uses + * indeterminate length encoding. + * \value CborValidateMapIsSorted (Canonical & Strict mode) Validate that map keys appear in + * sorted order. + * \value CborValidateMapKeysAreUnique (Strict mode) Validate that map keys are unique. + * \value CborValidateTagUse (Strict mode) Validate that known tags are used with the + * correct types. This does not validate that the content of + * those types is syntactically correct. For example, this + * option validates that tag 1 (DateTimeString) is used with + * a Text String, but it does not validate that the string is + * a valid date/time representation. + * \value CborValidateUtf8 (Strict mode) Validate that text strings are appropriately + * encoded in UTF-8. + * \value CborValidateMapKeysAreString Validate that all map keys are text strings. + * \value CborValidateNoUndefined Validate that no elements of type "undefined" are present. + * \value CborValidateNoTags Validate that no tags are used. + * \value CborValidateFiniteFloatingPoint Validate that all floating point numbers are finite (no NaN or + * infinities are allowed). + * \value CborValidateCompleteData Validate that the stream is complete and there is no more data + * in the buffer. + * \value CborValidateNoUnknownSimpleTypesSA Validate that all Standards Action simple types are registered + * with IANA. + * \value CborValidateNoUnknownSimpleTypes Validate that all simple types used are registered with IANA. + * \value CborValidateNoUnknownTagsSA Validate that all Standard Actions tags are registered with IANA. + * \value CborValidateNoUnknownTagsSR Validate that all Standard Actions and Specification Required tags + * are registered with IANA (see below for limitations). + * \value CborValidateNoUnkonwnTags Validate that all tags are registered with IANA + * (see below for limitations). + * + * \par Simple type registry + * The CBOR specification requires that registration for use of the first 19 + * simple types must be done by way of Standards Action. The rest of the simple + * types only require a specification. The official list can be obtained from + * https://www.iana.org/assignments/cbor-simple-values/cbor-simple-values.xhtml. + * + * \par + * There are no registered simple types recognized by this release of TinyCBOR + * (beyond those defined by RFC 7049). + * + * \par Tag registry + * The CBOR specification requires that registration for use of the first 23 + * tags must be done by way of Standards Action. The next up to tag 255 only + * require a specification. Finally, all other tags can be registered on a + * first-come-first-serve basis. The official list can be ontained from + * https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml. + * + * \par + * Given the variability of this list, TinyCBOR cannot recognize all tags + * registered with IANA. Instead, the implementation only recognizes tags + * that are backed by an RFC. + * + * \par + * These are the tags known to the current TinyCBOR release: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TagData ItemSemantics
0UTF-8 text stringStandard date/time string
1integerEpoch-based date/time
2byte stringPositive bignum
3byte stringNegative bignum
4arrayDecimal fraction
5arrayBigfloat
16arrayCOSE Single Recipient Encrypted Data Object (RFC 8152)
17arrayCOSE Mac w/o Recipients Object (RFC 8152)
18arrayCOSE Single Signer Data Object (RFC 8162)
21byte string, array, mapExpected conversion to base64url encoding
22byte string, array, mapExpected conversion to base64 encoding
23byte string, array, mapExpected conversion to base16 encoding
24byte stringEncoded CBOR data item
32UTF-8 text stringURI
33UTF-8 text stringbase64url
34UTF-8 text stringbase64
35UTF-8 text stringRegular expression
36UTF-8 text stringMIME message
96arrayCOSE Encrypted Data Object (RFC 8152)
97arrayCOSE MACed Data Object (RFC 8152)
98arrayCOSE Signed Data Object (RFC 8152)
55799anySelf-describe CBOR
+ */ + +struct KnownTagData { uint32_t tag; uint32_t types; }; +static const struct KnownTagData knownTagData[] = { + { 0, (uint32_t)CborTextStringType }, + { 1, (uint32_t)(CborIntegerType+1) }, + { 2, (uint32_t)CborByteStringType }, + { 3, (uint32_t)CborByteStringType }, + { 4, (uint32_t)CborArrayType }, + { 5, (uint32_t)CborArrayType }, + { 16, (uint32_t)CborArrayType }, + { 17, (uint32_t)CborArrayType }, + { 18, (uint32_t)CborArrayType }, + { 21, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 22, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 23, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 24, (uint32_t)CborByteStringType }, + { 32, (uint32_t)CborTextStringType }, + { 33, (uint32_t)CborTextStringType }, + { 34, (uint32_t)CborTextStringType }, + { 35, (uint32_t)CborTextStringType }, + { 36, (uint32_t)CborTextStringType }, + { 96, (uint32_t)CborArrayType }, + { 97, (uint32_t)CborArrayType }, + { 98, (uint32_t)CborArrayType }, + { 55799, 0U } +}; + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft); + +static inline CborError validate_utf8_string(const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + const uint8_t * const end = buffer + n; + while (buffer < end) { + uint32_t uc = get_utf8(&buffer, end); + if (uc == ~0U) + return CborErrorInvalidUtf8TextString; + } + return CborNoError; +} + +static inline CborError validate_simple_type(uint8_t simple_type, uint32_t flags) +{ + /* At current time, all known simple types are those from RFC 7049, + * which are parsed by the parser into different CBOR types. + * That means that if we've got here, the type is unknown */ + if (simple_type < 32) + return (flags & CborValidateNoUnknownSimpleTypesSA) ? CborErrorUnknownSimpleType : CborNoError; + return (flags & CborValidateNoUnknownSimpleTypes) == CborValidateNoUnknownSimpleTypes ? + CborErrorUnknownSimpleType : CborNoError; +} + +static inline CborError validate_number(const CborValue *it, CborType type, uint32_t flags) +{ + CborError err = CborNoError; + size_t bytesUsed, bytesNeeded; + uint64_t value; + + if ((flags & CborValidateShortestIntegrals) == 0) + return err; + if (type >= CborHalfFloatType && type <= CborDoubleType) + return err; /* checked elsewhere */ + + err = extract_number_checked(it, &value, &bytesUsed); + if (err) + return err; + + bytesNeeded = 0; + if (value >= Value8Bit) + ++bytesNeeded; + if (value > 0xffU) + ++bytesNeeded; + if (value > 0xffffU) + bytesNeeded += 2; + if (value > 0xffffffffU) + bytesNeeded += 4; + if (bytesNeeded < bytesUsed) + return CborErrorOverlongEncoding; + return CborNoError; +} + +static inline CborError validate_tag(CborValue *it, CborTag tag, uint32_t flags, int recursionLeft) +{ + CborType type = cbor_value_get_type(it); + const size_t knownTagCount = sizeof(knownTagData) / sizeof(knownTagData[0]); + const struct KnownTagData *tagData = knownTagData; + const struct KnownTagData * const knownTagDataEnd = knownTagData + knownTagCount; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + if (flags & CborValidateNoTags) + return CborErrorExcludedType; + + /* find the tag data, if any */ + for ( ; tagData != knownTagDataEnd; ++tagData) { + if (tagData->tag < tag) + continue; + if (tagData->tag > tag) + tagData = NULL; + break; + } + if (tagData == knownTagDataEnd) + tagData = NULL; + + if (flags & CborValidateNoUnknownTags && !tagData) { + /* tag not found */ + if (flags & CborValidateNoUnknownTagsSA && tag < 24) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTagsSR) == CborValidateNoUnknownTagsSR && tag < 256) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTags) == CborValidateNoUnknownTags) + return CborErrorUnknownTag; + } + + if (flags & CborValidateTagUse && tagData && tagData->types) { + uint32_t allowedTypes = tagData->types; + + /* correct Integer so it's not zero */ + if (type == CborIntegerType) + type = (CborType)(type + 1); + + while (allowedTypes) { + if ((uint8_t)(allowedTypes & 0xff) == type) + break; + allowedTypes >>= 8; + } + if (!allowedTypes) + return CborErrorInappropriateTagForType; + } + + return validate_value(it, flags, recursionLeft); +} + +#ifndef CBOR_NO_FLOATING_POINT +static inline CborError validate_floating_point(CborValue *it, CborType type, uint32_t flags) +{ + CborError err; + int r; + double val; + float valf = 0.0f; + uint16_t valf16 = 0x7c01; /* dummy value, an infinity */ + + if (type != CborDoubleType) { + if (type == CborFloatType) { + err = cbor_value_get_float(it, &valf); + val = valf; + } else { +# ifdef CBOR_NO_HALF_FLOAT_TYPE + (void)valf16; + return CborErrorUnsupportedType; +# else + err = cbor_value_get_half_float(it, &valf16); + val = decode_half(valf16); +# endif + } + } else { + err = cbor_value_get_double(it, &val); + } + cbor_assert(err == CborNoError); /* can't fail */ + + r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) { + if (flags & CborValidateFiniteFloatingPoint) + return CborErrorExcludedValue; + if (flags & CborValidateShortestFloatingPoint) { + if (type == CborDoubleType) + return CborErrorOverlongEncoding; +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) + return CborErrorOverlongEncoding; + if (r == FP_NAN && valf16 != 0x7e00) + return CborErrorImproperValue; + if (r == FP_INFINITE && valf16 != 0x7c00 && valf16 != 0xfc00) + return CborErrorImproperValue; +# endif + } + } + + if (flags & CborValidateShortestFloatingPoint && type > CborHalfFloatType) { + if (type == CborDoubleType) { + valf = (float)val; + if ((double)valf == val) + return CborErrorOverlongEncoding; + } +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) { + valf16 = encode_half(valf); + if (valf == decode_half(valf16)) + return CborErrorOverlongEncoding; + } +# endif + } + + return CborNoError; +} +#endif + +static CborError validate_container(CborValue *it, int containerType, uint32_t flags, int recursionLeft) +{ + CborError err; + const uint8_t *previous = NULL; + const uint8_t *previous_end = NULL; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + + while (!cbor_value_at_end(it)) { + const uint8_t *current = cbor_value_get_next_byte(it); + + if (containerType == CborMapType) { + if (flags & CborValidateMapKeysAreString) { + CborType type = cbor_value_get_type(it); + if (type == CborTagType) { + /* skip the tags */ + CborValue copy = *it; + err = cbor_value_skip_tag(©); + if (err) + return err; + type = cbor_value_get_type(©); + } + if (type != CborTextStringType) + return CborErrorMapKeyNotString; + } + } + + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + + if (containerType != CborMapType) + continue; + + if (flags & CborValidateMapIsSorted) { + if (it->parser->flags & CborParserFlag_ExternalSource) + return CborErrorUnimplementedValidation; + if (previous) { + size_t bytelen1 = (size_t)(previous_end - previous); + size_t bytelen2 = (size_t)(cbor_value_get_next_byte(it) - current); + int r = memcmp(previous, current, bytelen1 <= bytelen2 ? bytelen1 : bytelen2); + + if (r == 0 && bytelen1 != bytelen2) + r = bytelen1 < bytelen2 ? -1 : +1; + if (r > 0) + return CborErrorMapNotSorted; + if (r == 0 && (flags & CborValidateMapKeysAreUnique) == CborValidateMapKeysAreUnique) + return CborErrorMapKeysNotUnique; + } + + previous = current; + previous_end = cbor_value_get_next_byte(it); + } + + /* map: that was the key, so get the value */ + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + } + return CborNoError; +} + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft) +{ + CborError err; + CborType type = cbor_value_get_type(it); + + if (cbor_value_is_length_known(it)) { + err = validate_number(it, type, flags); + if (err) + return err; + } else { + if (flags & CborValidateNoIndeterminateLength) + return CborErrorUnknownLength; + } + + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (!err) + err = validate_container(&recursed, type, flags, recursionLeft - 1); + if (err) { + copy_current_position(it, &recursed); + return err; + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; + return CborNoError; + } + + case CborIntegerType: { + uint64_t val; + err = cbor_value_get_raw_integer(it, &val); + cbor_assert(err == CborNoError); /* can't fail */ + + break; + } + + case CborByteStringType: + case CborTextStringType: { + size_t n = 0; + const void *ptr; + + err = cbor_value_begin_string_iteration(it); + if (err) + return err; + + while (1) { + CborValue next; + err = _cbor_value_get_string_chunk(it, &ptr, &n, &next); + if (!err) { + err = validate_number(it, type, flags); + if (err) + return err; + } + + *it = next; + if (err == CborErrorNoMoreStringChunks) + return cbor_value_finish_string_iteration(it); + if (err) + return err; + + if (type == CborTextStringType && flags & CborValidateUtf8) { + err = validate_utf8_string(ptr, n); + if (err) + return err; + } + } + + return CborNoError; + } + + case CborTagType: { + CborTag tag; + err = cbor_value_get_tag(it, &tag); + cbor_assert(err == CborNoError); /* can't fail */ + + err = cbor_value_advance_fixed(it); + if (err) + return err; + err = validate_tag(it, tag, flags, recursionLeft - 1); + if (err) + return err; + + return CborNoError; + } + + case CborSimpleType: { + uint8_t simple_type; + err = cbor_value_get_simple_type(it, &simple_type); + cbor_assert(err == CborNoError); /* can't fail */ + err = validate_simple_type(simple_type, flags); + if (err) + return err; + break; + } + + case CborNullType: + case CborBooleanType: + break; + + case CborUndefinedType: + if (flags & CborValidateNoUndefined) + return CborErrorExcludedType; + break; + + case CborHalfFloatType: + case CborFloatType: + case CborDoubleType: { +#ifdef CBOR_NO_FLOATING_POINT + return CborErrorUnsupportedType; +#else + err = validate_floating_point(it, type, flags); + if (err) + return err; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + } + + case CborInvalidType: + return CborErrorUnknownType; + } + + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * Performs a full validation, controlled by the \a flags options, of the CBOR + * stream pointed by \a it and returns the error it found. If no error was + * found, it returns CborNoError and the application can iterate over the items + * with certainty that no errors will appear during parsing. + * + * If \a flags is CborValidateBasic, the result should be the same as + * cbor_value_validate_basic(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance() and cbor_value_validate_basic(). + * + * \sa CborValidationFlags, cbor_value_validate_basic(), cbor_value_advance() + */ +CborError cbor_value_validate(const CborValue *it, uint32_t flags) +{ + CborValue value = *it; + CborError err = validate_value(&value, flags, CBOR_PARSER_MAX_RECURSIONS); + if (err) + return err; + if (flags & CborValidateCompleteData && can_read_bytes(&value, 1)) + return CborErrorGarbageAtEnd; + return CborNoError; +} + +/** + * @} + */ diff --git a/deps/tinycbor/.appveyor.yml b/deps/tinycbor/.appveyor.yml new file mode 100644 index 00000000..ef47797e --- /dev/null +++ b/deps/tinycbor/.appveyor.yml @@ -0,0 +1,28 @@ +version: 0.6-build-{build} +pull_requests: + do_not_increment_build_number: true +image: +- Visual Studio 2017 +- Visual Studio 2019 +install: +- cmd: >- + if /i "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86) & (set QTDIR=C:\Qt\5.13\msvc2017) + + if /i "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" (call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64) & (set QTDIR=C:\Qt\6.1\msvc2019_64) + + set path=%PATH%;%QTDIR%\bin +build_script: +- cmd: >- + nmake -f Makefile.nmake -nologo CFLAGS="-W3 -Os -MDd" + + cd tests + + qmake CONFIG-=release CONFIG+=debug + + nmake -nologo -s +test_script: +- cmd: >- + nmake -s -nologo TESTARGS=-silent check +artifacts: +- path: lib\tinycbor.lib +deploy: off diff --git a/deps/tinycbor/.gitattributes b/deps/tinycbor/.gitattributes new file mode 100644 index 00000000..76ed2567 --- /dev/null +++ b/deps/tinycbor/.gitattributes @@ -0,0 +1,4 @@ +.tag export-subst +.gitignore export-ignore +.gitattributes export-ignore +.appveyor.yml text diff --git a/deps/tinycbor/.gitignore b/deps/tinycbor/.gitignore new file mode 100644 index 00000000..3272de33 --- /dev/null +++ b/deps/tinycbor/.gitignore @@ -0,0 +1,81 @@ +# Frequent generated files +callgrind.out.* +pcviewer.cfg +*~ +*.a +*.la +*.core +*.d +*.dylib +*.moc +*.o +*.obj +*.orig +*.swp +*.rej +*.so +*.so.* +*.pbxuser +*.mode1 +*.mode1v3 +*_pch.h.cpp +*_resource.rc +.#* +*.*# +core +.qmake.cache +.qmake.stash +.qmake.vars +.device.vars +tags +.DS_Store +*.debug +Makefile* +*.prl +*.app +*.pro.user* +*.qmlproject.user* +*.gcov +*.gcda +*.gcno +*.flc +.*.swp +tinycbor.pc + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.exe.embed.manifest +*.exe_manifest.rc +*.exe_manifest.res + +# MinGW generated files +*.Debug +*.Release + +# INTEGRITY generated files +*.gpj +*.int +*.ael +*.dla +*.dnm +*.dep +*.map + +bin +doc +lib +src/cjson +src/doxygen.log +!/Makefile +.config diff --git a/deps/tinycbor/.tag b/deps/tinycbor/.tag new file mode 100644 index 00000000..6828f88d --- /dev/null +++ b/deps/tinycbor/.tag @@ -0,0 +1 @@ +$Format:%H$ diff --git a/deps/tinycbor/.travis.yml b/deps/tinycbor/.travis.yml new file mode 100644 index 00000000..4df21cb7 --- /dev/null +++ b/deps/tinycbor/.travis.yml @@ -0,0 +1,92 @@ +env: + - BUILD_DOCS=false +jobs: + include: + - # only build docs on main + if: branch = main + env: BUILD_DOCS=true + +language: cpp +matrix: + include: + - os: linux + dist: xenial + addons: + apt: + sources: + - sourceline: 'ppa:beineri/opt-qt-5.12.1-xenial' + packages: + - qt512base valgrind + - doxygen + env: + - QMAKESPEC=linux-g++ + - EVAL="CC=gcc && CXX=g++" + - CFLAGS="-Os" + - LDFLAGS="-Wl,--no-undefined -lm" + - QMAKEFLAGS="-config release" + - QT_NO_CPU_FEATURE=rdrnd + - os: linux + dist: xenial + addons: + apt: + sources: + - sourceline: 'ppa:beineri/opt-qt-5.12.1-xenial' + packages: + - qt512base + env: + - QMAKESPEC=linux-clang + - EVAL="CC=clang && CXX=clang++" + - CFLAGS="-Oz" + - LDFLAGS="-Wl,--no-undefined -lm" + - QMAKEFLAGS="-config release" + - MAKEFLAGS=-s + - TESTARGS=-silent + - os: linux + dist: xenial + env: + - QMAKESPEC=linux-gcc-freestanding + - EVAL="CXX=false" + - CFLAGS="-ffreestanding -Os" + - LDFLAGS="-Wl,--no-undefined -lm" + - os: linux + dist: xenial + env: + - QMAKESPEC=linux-gcc-no-math + - EVAL="CXX=false && touch src/math.h src/float.h" + - CFLAGS="-ffreestanding -DCBOR_NO_FLOATING_POINT -Os" + - LDFLAGS="-Wl,--no-undefined" + - LDLIBS="" + - os: osx + env: + - QMAKESPEC=macx-clang + - CFLAGS="-Oz" + - QMAKEFLAGS="-config debug" + - MAKEFLAGS=-s + - TESTARGS=-silent + - PATH=/usr/local/opt/qt5/bin:$PATH +install: + - if [ "${TRAVIS_OS_NAME}" != "linux" ]; then + brew update; + brew install qt5; + fi +script: + - PATH=`echo /opt/qt*/bin`:$PATH + - eval "$EVAL" + - make -s -f Makefile.configure configure | tee .config + - make -k + CFLAGS="$CFLAGS -march=native -g1 -Wall -Wextra -Werror" + CPPFLAGS="-DNDEBUG -DCBOR_ENCODER_WRITER_CONTROL=-1 -DCBOR_PARSER_READER_CONTROL=-1" + lib/libtinycbor.a + - size lib/libtinycbor.a | tee sizes + - make -s clean + - make -k + CFLAGS="$CFLAGS -O0 -g" + LDFLAGS="$LDFLAGS" ${LDLIBS+LDLIBS="$LDLIBS"} + - grep -q freestanding-pass .config || make + QMAKEFLAGS="$QMAKEFLAGS QMAKE_CXX=$CXX" + tests/Makefile + - grep -q freestanding-pass .config || + (cd tests && make TESTARGS=-silent check -k + TESTRUNNER=`which valgrind 2>/dev/null`) + - make -s clean + - ! [ $BUILD_DOCS ] || ./scripts/update-docs.sh diff --git a/deps/tinycbor/Doxyfile b/deps/tinycbor/Doxyfile new file mode 100644 index 00000000..a7263c2f --- /dev/null +++ b/deps/tinycbor/Doxyfile @@ -0,0 +1,49 @@ +PROJECT_NAME = "TinyCBOR $(VERSION) API" +OUTPUT_DIRECTORY = ../doc +ABBREVIATE_BRIEF = +SHORT_NAMES = YES +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = YES +TAB_SIZE = 8 +ALIASES = "value=\arg \c" +OPTIMIZE_OUTPUT_FOR_C = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +SHOW_USED_FILES = NO +WARN_IF_UNDOCUMENTED = NO +WARN_LOGFILE = doxygen.log +INPUT = . +FILE_PATTERNS = *.h \ + *.c \ + *.dox +EXCLUDE_PATTERNS = *_p.h +STRIP_CODE_COMMENTS = NO +REFERENCED_BY_RELATION = YES +IGNORE_PREFIX = cbor_ \ + Cbor +HTML_TIMESTAMP = NO +GENERATE_HTMLHELP = YES +GENERATE_CHI = YES +BINARY_TOC = YES +TOC_EXPAND = YES +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +SEARCHENGINE = NO +GENERATE_LATEX = NO +COMPACT_LATEX = YES +MACRO_EXPANSION = YES +PREDEFINED = DOXYGEN \ + CBOR_INLINE_API= +CLASS_DIAGRAMS = NO +CLASS_GRAPH = NO +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO +GRAPHICAL_HIERARCHY = NO +DIRECTORY_GRAPH = NO diff --git a/deps/tinycbor/LICENSE b/deps/tinycbor/LICENSE new file mode 100644 index 00000000..4aad977c --- /dev/null +++ b/deps/tinycbor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Intel Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/deps/tinycbor/Makefile b/deps/tinycbor/Makefile new file mode 100644 index 00000000..3135d70f --- /dev/null +++ b/deps/tinycbor/Makefile @@ -0,0 +1,256 @@ +# Variables: +prefix = /usr/local +exec_prefix = $(prefix) +bindir = $(exec_prefix)/bin +libdir = $(exec_prefix)/lib +includedir = $(prefix)/include +pkgconfigdir = $(libdir)/pkgconfig + +CFLAGS = -Wall -Wextra +LDFLAGS_GCSECTIONS = -Wl,--gc-sections +LDFLAGS += $(if $(gc_sections-pass),$(LDFLAGS_GCSECTIONS)) +LDLIBS = -lm + +GIT_ARCHIVE = git archive --prefix="$(PACKAGE)/" -9 +INSTALL = install +INSTALL_DATA = $(INSTALL) -m 644 +INSTALL_PROGRAM = $(INSTALL) -m 755 +QMAKE = qmake +MKDIR = mkdir -p +RMDIR = rmdir +SED = sed + +# Our sources +TINYCBOR_HEADERS = src/cbor.h src/cborjson.h src/tinycbor-version.h +TINYCBOR_FREESTANDING_SOURCES = \ + src/cborerrorstrings.c \ + src/cborencoder.c \ + src/cborencoder_close_container_checked.c \ + src/cborencoder_float.c \ + src/cborparser.c \ + src/cborparser_float.c \ + src/cborpretty.c \ +# +CBORDUMP_SOURCES = tools/cbordump/cbordump.c + +BUILD_SHARED = $(shell file -L /bin/sh 2>/dev/null | grep -q ELF && echo 1) +BUILD_STATIC = 1 + +ifneq ($(BUILD_STATIC),1) +ifneq ($(BUILD_SHARED),1) + $(error error: BUILD_STATIC and BUILD_SHARED can not be both disabled) +endif +endif + +INSTALL_TARGETS += $(bindir)/cbordump +ifeq ($(BUILD_SHARED),1) +BINLIBRARY=lib/libtinycbor.so +INSTALL_TARGETS += $(libdir)/libtinycbor.so.$(VERSION) +endif +ifeq ($(BUILD_STATIC),1) +BINLIBRARY=lib/libtinycbor.a +INSTALL_TARGETS += $(libdir)/libtinycbor.a +endif +INSTALL_TARGETS += $(pkgconfigdir)/tinycbor.pc +INSTALL_TARGETS += $(TINYCBOR_HEADERS:src/%=$(includedir)/tinycbor/%) + +# setup VPATH +MAKEFILE := $(lastword $(MAKEFILE_LIST)) +SRCDIR := $(dir $(MAKEFILE)) +VPATH = $(SRCDIR):$(SRCDIR)/src + +# Our version +GIT_DIR := $(strip $(shell git -C $(SRCDIR). rev-parse --git-dir 2> /dev/null)) +VERSION = $(shell cat $(SRCDIR)VERSION) +SOVERSION = $(shell cut -f1-2 -d. $(SRCDIR)VERSION) +PACKAGE = tinycbor-$(VERSION) + +# Check that QMAKE is Qt 5 +ifeq ($(origin QMAKE),file) + check_qmake = $(strip $(shell $(1) -query QT_VERSION 2>/dev/null | cut -b1)) + ifneq ($(call check_qmake,$(QMAKE)),5) + QMAKE := qmake -qt5 + ifneq ($(call check_qmake,$(QMAKE)),5) + QMAKE := qmake-qt5 + ifneq ($(call check_qmake,$(QMAKE)),5) + QMAKE := @echo >&2 $(MAKEFILE): Cannot find a Qt 5 qmake; false + endif + endif + endif +endif + +-include .config + +ifeq ($(wildcard .config),) + $(info .config file not yet created) +endif + +ifeq ($(freestanding-pass),1) +TINYCBOR_SOURCES = $(TINYCBOR_FREESTANDING_SOURCES) +else +TINYCBOR_SOURCES = \ + $(TINYCBOR_FREESTANDING_SOURCES) \ + src/cborparser_dup_string.c \ + src/cborpretty_stdio.c \ + src/cbortojson.c \ + src/cborvalidation.c \ +# +# if open_memstream is unavailable on the system, try to implement our own +# version using funopen or fopencookie +ifeq ($(open_memstream-pass),) + ifeq ($(funopen-pass)$(fopencookie-pass),) + CFLAGS += -DWITHOUT_OPEN_MEMSTREAM + ifeq ($(wildcard .config),.config) + $(warning warning: funopen and fopencookie unavailable, open_memstream can not be implemented and conversion to JSON will not work properly!) + endif + else + TINYCBOR_SOURCES += src/open_memstream.c + endif +endif +endif + +# json2cbor depends on an external library (cjson) +ifneq ($(cjson-pass)$(system-cjson-pass),) + JSON2CBOR_SOURCES = tools/json2cbor/json2cbor.c + INSTALL_TARGETS += $(bindir)/json2cbor + ifeq ($(system-cjson-pass),1) + LDFLAGS_CJSON = -lcjson + else + JSON2CBOR_SOURCES += src/cjson/cJSON.c + json2cbor_CCFLAGS = -I$(SRCDIR)src/cjson + endif +endif + +# Rules +all: .config \ + $(if $(subst 0,,$(BUILD_STATIC)),lib/libtinycbor.a) \ + $(if $(subst 0,,$(BUILD_SHARED)),lib/libtinycbor.so) \ + $(if $(freestanding-pass),,bin/cbordump) \ + tinycbor.pc +all: $(if $(JSON2CBOR_SOURCES),bin/json2cbor) +check: tests/Makefile | $(BINLIBRARY) + $(MAKE) -C tests check +silentcheck: | $(BINLIBRARY) + TESTARGS=-silent $(MAKE) -f $(MAKEFILE) -s check +configure: .config +.config: Makefile.configure + $(MAKE) -f $(SRCDIR)Makefile.configure OUT='$@' configure + +lib/libtinycbor-freestanding.a: $(TINYCBOR_FREESTANDING_SOURCES:.c=.o) + @$(MKDIR) -p lib + $(AR) cqs $@ $^ + +lib/libtinycbor.a: $(TINYCBOR_SOURCES:.c=.o) + @$(MKDIR) -p lib + $(AR) cqs $@ $^ + +lib/libtinycbor.so: $(TINYCBOR_SOURCES:.c=.pic.o) + @$(MKDIR) -p lib + $(CC) -shared -Wl,-soname,libtinycbor.so.$(SOVERSION) -o lib/libtinycbor.so.$(VERSION) $(LDFLAGS) $^ $(LDLIBS) + cd lib ; ln -sf libtinycbor.so.$(VERSION) libtinycbor.so ; ln -sf libtinycbor.so.$(VERSION) libtinycbor.so.$(SOVERSION) + +bin/cbordump: $(CBORDUMP_SOURCES:.c=.o) $(BINLIBRARY) + @$(MKDIR) -p bin + $(CC) -o $@ $(LDFLAGS) $^ $(LDLIBS) + +bin/json2cbor: $(JSON2CBOR_SOURCES:.c=.o) $(BINLIBRARY) + @$(MKDIR) -p bin + $(CC) -o $@ $(LDFLAGS) $^ $(LDFLAGS_CJSON) $(LDLIBS) + +tinycbor.pc: tinycbor.pc.in + $(SED) > $@ < $< \ + -e 's,@prefix@,$(prefix),' \ + -e 's,@exec_prefix@,$(exec_prefix),' \ + -e 's,@libdir@,$(libdir),' \ + -e 's,@includedir@,$(includedir),' \ + -e 's,@version@,$(VERSION),' + +tests/Makefile: tests/tests.pro + $(QMAKE) $(QMAKEFLAGS) -o $@ $< + +$(PACKAGE).tar.gz: | .git + GIT_DIR=$(SRCDIR).git $(GIT_ARCHIVE) --format=tar.gz -o "$(PACKAGE).tar.gz" HEAD +$(PACKAGE).zip: | .git + GIT_DIR=$(SRCDIR).git $(GIT_ARCHIVE) --format=zip -o "$(PACKAGE).zip" HEAD + +$(DESTDIR)$(libdir)/%: lib/% + $(INSTALL) -d $(@D) + $(INSTALL_DATA) $< $@ +$(DESTDIR)$(bindir)/%: bin/% + $(INSTALL) -d $(@D) + $(INSTALL_PROGRAM) $< $@ +$(DESTDIR)$(pkgconfigdir)/%: % + $(INSTALL) -d $(@D) + $(INSTALL_DATA) $< $@ +$(DESTDIR)$(includedir)/tinycbor/%: src/% + $(INSTALL) -d $(@D) + $(INSTALL_DATA) $< $@ + +install-strip: + $(MAKE) -f $(MAKEFILE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install + +install: $(INSTALL_TARGETS:%=$(DESTDIR)%) +ifeq ($(BUILD_SHARED),1) + ln -sf libtinycbor.so.$(VERSION) $(DESTDIR)$(libdir)/libtinycbor.so + ln -sf libtinycbor.so.$(VERSION) $(DESTDIR)$(libdir)/libtinycbor.so.$(SOVERSION) +endif + +uninstall: + $(RM) $(INSTALL_TARGETS:%=$(DESTDIR)%) + $(RM) $(DESTDIR)$(libdir)/libtinycbor.so + $(RM) $(DESTDIR)$(libdir)/libtinycbor.so.$(SOVERSION) + +mostlyclean: + $(RM) $(TINYCBOR_SOURCES:.c=.o) + $(RM) $(TINYCBOR_SOURCES:.c=.pic.o) + $(RM) $(CBORDUMP_SOURCES:.c=.o) + +clean: mostlyclean + $(RM) bin/cbordump + $(RM) bin/json2cbor + $(RM) lib/libtinycbor.a + $(RM) lib/libtinycbor-freestanding.a + $(RM) tinycbor.pc + $(RM) lib/libtinycbor.so* + test -e tests/Makefile && $(MAKE) -C tests clean || : + +distclean: clean + test -e tests/Makefile && $(MAKE) -C tests distclean || : + +docs: + cd $(SRCDIR)src && VERSION=$(VERSION) doxygen $(SRCDIR)/../Doxyfile + +dist: $(PACKAGE).tar.gz $(PACKAGE).zip +distcheck: .git + -$(RM) -r $${TMPDIR-/tmp}/tinycbor-distcheck + GIT_DIR=$(SRCDIR).git git archive --prefix=tinycbor-distcheck/ --format=tar HEAD | tar -xf - -C $${TMPDIR-/tmp} + cd $${TMPDIR-/tmp}/tinycbor-distcheck && $(MAKE) silentcheck + $(RM) -r $${TMPDIR-/tmp}/tinycbor-distcheck + +tag: distcheck + @cd $(SRCDIR). && perl scripts/maketag.pl + +.PHONY: all check silentcheck configure install uninstall +.PHONY: mostlyclean clean distclean +.PHONY: docs dist distcheck release +.SECONDARY: + +cflags := $(CPPFLAGS) -I$(SRCDIR)src +cflags += -std=gnu99 $(CFLAGS) + +ifneq ($(DISABLE_WERROR),1) +cflags += \ + -Werror=discarded-qualifiers \ + -Werror=incompatible-pointer-types \ + -Werror=implicit-function-declaration \ + -Werror=int-conversion +endif + +%.o: %.c + @test -d $(@D) || $(MKDIR) $(@D) + $(CC) $(cflags) $($(basename $(notdir $@))_CCFLAGS) -c -o $@ $< +%.pic.o: %.c + @test -d $(@D) || $(MKDIR) $(@D) + $(CC) $(cflags) -fPIC $($(basename $(notdir $@))_CCFLAGS) -c -o $@ $< + +-include src/*.d diff --git a/deps/tinycbor/README b/deps/tinycbor/README new file mode 100644 index 00000000..167efa06 --- /dev/null +++ b/deps/tinycbor/README @@ -0,0 +1,13 @@ +Concise Binary Object Representation (CBOR) Library +--------------------------------------------------- + +To build TinyCBOR: + + make + +If you want to change the compiler or pass extra compiler flags: + + make CC=clang CFLAGS="-m32 -Oz" LDFLAGS="-m32" + +Documentation: https://intel.github.io/tinycbor/current/ + diff --git a/deps/tinycbor/TODO b/deps/tinycbor/TODO new file mode 100644 index 00000000..e9103ee6 --- /dev/null +++ b/deps/tinycbor/TODO @@ -0,0 +1,25 @@ +==== To Do list for libcbor ==== +=== General === +* API review +* Benchmark +* Write examples +** Simple decoder +** Decoder to JSON +** Windowed encoding/decoding (limited memory) + +=== Encoder === +* Write API docs +* Add API for creating indeterminate-length arrays and maps +* Add API for creating indeterminate-length strings +* Add API for relaxing doubles to floats and to integers +* Add length-checking of the sub-containers (#ifndef CBOR_ENCODER_NO_USER_CHECK) +* Decide how to indicate number of bytes needed +** Suggestion: return negative number from the functions + +=== Decoder === +* Write functions not yet implemented +* Add API for stream-decoding strings +* Add API for checking known tags and simple types +* (unlikely) Add API for checking the pairing of a tag and the tagged type +* Write tests for error conditions +* Fuzzy-test the decoder diff --git a/deps/tinycbor/VERSION b/deps/tinycbor/VERSION new file mode 100644 index 00000000..a918a2aa --- /dev/null +++ b/deps/tinycbor/VERSION @@ -0,0 +1 @@ +0.6.0 diff --git a/deps/tinycbor/examples/examples.pro b/deps/tinycbor/examples/examples.pro new file mode 100644 index 00000000..22071ac3 --- /dev/null +++ b/deps/tinycbor/examples/examples.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = simplereader.pro diff --git a/deps/tinycbor/examples/simplereader.c b/deps/tinycbor/examples/simplereader.c new file mode 100644 index 00000000..0612ba44 --- /dev/null +++ b/deps/tinycbor/examples/simplereader.c @@ -0,0 +1,185 @@ +#include "../src/cbor.h" + +#include +#include +#include +#include +#include + +static uint8_t *readfile(const char *fname, size_t *size) +{ + struct stat st; + FILE *f = fopen(fname, "rb"); + if (!f) + return NULL; + if (fstat(fileno(f), &st) == -1) + return NULL; + uint8_t *buf = malloc(st.st_size); + if (buf == NULL) + return NULL; + *size = fread(buf, st.st_size, 1, f) == 1 ? st.st_size : 0; + fclose(f); + return buf; +} + +static void indent(int nestingLevel) +{ + while (nestingLevel--) + printf(" "); +} + +static void dumpbytes(const uint8_t *buf, size_t len) +{ + printf("\""); + while (len--) + printf("\\x%02X", *buf++); + printf("\""); +} + +static CborError dumprecursive(CborValue *it, int nestingLevel) +{ + while (!cbor_value_at_end(it)) { + CborError err; + CborType type = cbor_value_get_type(it); + + indent(nestingLevel); + switch (type) { + case CborArrayType: + case CborMapType: { + // recursive type + CborValue recursed; + assert(cbor_value_is_container(it)); + puts(type == CborArrayType ? "Array[" : "Map["); + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; // parse error + err = dumprecursive(&recursed, nestingLevel + 1); + if (err) + return err; // parse error + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; // parse error + indent(nestingLevel); + puts("]"); + continue; + } + + case CborIntegerType: { + int64_t val; + cbor_value_get_int64(it, &val); // can't fail + printf("%lld\n", (long long)val); + break; + } + + case CborByteStringType: { + uint8_t *buf; + size_t n; + err = cbor_value_dup_byte_string(it, &buf, &n, it); + if (err) + return err; // parse error + dumpbytes(buf, n); + puts(""); + free(buf); + continue; + } + + case CborTextStringType: { + char *buf; + size_t n; + err = cbor_value_dup_text_string(it, &buf, &n, it); + if (err) + return err; // parse error + printf("\"%s\"\n", buf); + free(buf); + continue; + } + + case CborTagType: { + CborTag tag; + cbor_value_get_tag(it, &tag); // can't fail + printf("Tag(%lld)\n", (long long)tag); + break; + } + + case CborSimpleType: { + uint8_t type; + cbor_value_get_simple_type(it, &type); // can't fail + printf("simple(%u)\n", type); + break; + } + + case CborNullType: + puts("null"); + break; + + case CborUndefinedType: + puts("undefined"); + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); // can't fail + puts(val ? "true" : "false"); + break; + } + + case CborDoubleType: { + double val; + if (false) { + float f; + case CborFloatType: + cbor_value_get_float(it, &f); + val = f; + } else { + cbor_value_get_double(it, &val); + } + printf("%g\n", val); + break; + } + case CborHalfFloatType: { + uint16_t val; + cbor_value_get_half_float(it, &val); + printf("__f16(%04x)\n", val); + break; + } + + case CborInvalidType: + assert(false); // can't happen + break; + } + + err = cbor_value_advance_fixed(it); + if (err) + return err; + } + return CborNoError; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + puts("simplereader "); + return 1; + } + + size_t length; + uint8_t *buf = readfile(argv[1], &length); + if (!buf) { + perror("readfile"); + return 1; + } + + CborParser parser; + CborValue it; + CborError err = cbor_parser_init(buf, length, 0, &parser, &it); + if (!err) + err = dumprecursive(&it, 0); + free(buf); + + if (err) { + fprintf(stderr, "CBOR parsing failure at offset %ld: %s\n", + it.ptr - buf, cbor_error_string(err)); + return 1; + } + return 0; +} diff --git a/deps/tinycbor/examples/simplereader.pro b/deps/tinycbor/examples/simplereader.pro new file mode 100644 index 00000000..07fdc6ac --- /dev/null +++ b/deps/tinycbor/examples/simplereader.pro @@ -0,0 +1,3 @@ +CONFIG -= qt +SOURCES = simplereader.c +include(../src/src.pri) diff --git a/deps/tinycbor/scripts/maketag.pl b/deps/tinycbor/scripts/maketag.pl new file mode 100644 index 00000000..5b1a8b79 --- /dev/null +++ b/deps/tinycbor/scripts/maketag.pl @@ -0,0 +1,91 @@ +#!perl +use strict; +sub run(@) { + open PROC, "-|", @_ or die("Cannot run $_[0]: $!"); + my @out; + while () { + chomp; + push @out, $_; + } + close PROC; + return @out; +} + +my @tags = run("git", "tag"); +my @v = run("git", "show", "HEAD:VERSION"); +my $v = $v[0]; + +my $tagfile = ".git/TAG_EDITMSG"; +open TAGFILE, ">", $tagfile + or die("Cannot create file for editing tag message: $!"); +select TAGFILE; +print "TinyCBOR release $v\n"; +print "\n"; +print "# Write something nice about this release here\n"; + +# Do we have a commit template? +my @result = run("git", "config", "--get", "commit.template"); +if (scalar @result) { + open TEMPLATE, "<", $result[0]; + map { print $_; }