diff --git a/.gitmodules b/.gitmodules index ea471b67..921e575b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,11 @@ [submodule "components/esp32_bc-ur"] path = components/esp32_bc-ur url = https://github.com/Blockstream/esp32_bc-ur +[submodule "components/cUR"] + path = components/cUR + url = https://github.com/odudex/cUR + branch = main +[submodule "components/k_quirc"] + path = components/k_quirc + url = https://github.com/odudex/k_quirc + branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index 377c3e4e..cae593fc 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.16) if(DEFINED ESP_PLATFORM AND ESP_PLATFORM EQUAL 1) set(EXTRA_COMPONENT_DIRS bootloader_components/bootloader_support) + # Jade does its own payload CBOR (TinyCBOR) - only the UR transport + # layer of the cUR component is needed + set(UR_ENVELOPE_ONLY ON CACHE BOOL "" FORCE) include($ENV{IDF_PATH}/tools/cmake/project.cmake) idf_build_set_property(DEPENDENCIES_LOCK dependencies.lock.${IDF_TARGET}) project(jade) diff --git a/components/cUR b/components/cUR new file mode 160000 index 00000000..09724a01 --- /dev/null +++ b/components/cUR @@ -0,0 +1 @@ +Subproject commit 09724a010622460733e5e5d60f9505f5989b2fe8 diff --git a/components/k_quirc b/components/k_quirc new file mode 160000 index 00000000..f1887f25 --- /dev/null +++ b/components/k_quirc @@ -0,0 +1 @@ +Subproject commit f1887f25baee9603027302ef50d382509d6e977b diff --git a/libjade/CMakeLists.txt b/libjade/CMakeLists.txt index fffa9e40..e0ff255e 100644 --- a/libjade/CMakeLists.txt +++ b/libjade/CMakeLists.txt @@ -90,8 +90,8 @@ set(COMMON_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/include ${CMAKE_CURRENT_SOURCE_DIR}/../components/assets ${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32_bsdiff - ${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32-quirc - ${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32-quirc/lib + ${CMAKE_CURRENT_SOURCE_DIR}/../components/k_quirc/include + ${CMAKE_CURRENT_SOURCE_DIR}/../components/k_quirc/src ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/src ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/src/ccan @@ -105,28 +105,38 @@ set(COMMON_SRC ${IDF_PATH}/components/http_parser/http_parser.c ) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32_bc-ur - ${CMAKE_CURRENT_BINARY_DIR}/bcur) -target_include_directories(bcur PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/include) -target_link_libraries(bcur PRIVATE mbedcrypto) +# Jade does its own payload CBOR (TinyCBOR) - only the UR transport +# layer of the cUR component is needed +set(UR_ENVELOPE_ONLY ON CACHE BOOL "" FORCE) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../components/cUR + ${CMAKE_CURRENT_BINARY_DIR}/cur) # libjade.so add_library(jade SHARED ${COMMON_SRC}) -target_link_libraries(jade PRIVATE m z bcur cbor_target otpauth_migrate_target mbedtls mbedcrypto mbedx509) +target_link_libraries(jade PRIVATE m z ur cbor_target otpauth_migrate_target mbedtls mbedcrypto mbedx509) target_include_directories(jade PRIVATE ${COMMON_INCLUDES}) +# libjade.c textually #includes the k_quirc sources, so the QR decoder is +# configured here rather than by components/k_quirc/CMakeLists.txt (which only +# applies to the ESP-IDF component build). These must stay in step with the +# definitions that file sets for the firmware: without them the qr_qvga_* +# fixtures exercised a materially different recognition path from the one that +# actually ships. Attached to the source file, not a target, so any future +# target compiling libjade.c inherits them. set_source_files_properties(libjade.c PROPERTIES COMPILE_OPTIONS "-Wall;-W;-Wno-format;-Wno-unused-function;-Wno-unused-parameter;-Wno-sign-compare;-Wno-missing-field-initializers;-Wno-narrowing") +set_source_files_properties(libjade.c PROPERTIES COMPILE_DEFINITIONS + "K_QUIRC_ADAPTIVE_THRESHOLD;K_QUIRC_BILINEAR_THRESHOLD") target_compile_definitions(jade PRIVATE LIBJADE_BUILD) # libjade.a add_library(jade_static STATIC ${COMMON_SRC}) -target_link_libraries(jade_static PRIVATE m z mbedtls mbedcrypto mbedx509 bcur) +target_link_libraries(jade_static PRIVATE m z mbedtls mbedcrypto mbedx509 ur) target_include_directories(jade_static PRIVATE ${COMMON_INCLUDES}) target_compile_definitions(jade_static PRIVATE LIBJADE_BUILD) add_executable(libjade_daemon daemon.c) -target_link_libraries(libjade_daemon PRIVATE jade_static bcur cbor_target otpauth_migrate_target m z mbedtls mbedcrypto mbedx509 Threads::Threads) +target_link_libraries(libjade_daemon PRIVATE jade_static ur cbor_target otpauth_migrate_target m z mbedtls mbedcrypto mbedx509 Threads::Threads) target_include_directories(libjade_daemon PRIVATE ${COMMON_INCLUDES}) # Assets diff --git a/libjade/libjade.c b/libjade/libjade.c index 4cb988e5..c07edec8 100644 --- a/libjade/libjade.c +++ b/libjade/libjade.c @@ -66,11 +66,10 @@ #undef ESP_PLATFORM #include "components/esp32_deflate/deflate.c" // qrCode encoding/decoding -#include "components/esp32-quirc/lib/decode.c" -#include "components/esp32-quirc/lib/identify.c" -#include "components/esp32-quirc/lib/quirc.c" -#include "components/esp32-quirc/lib/version_db.c" -#include "components/esp32-quirc/openmv/collections.c" +#include "components/k_quirc/src/k_quirc.c" +#include "components/k_quirc/src/k_quirc_decode.c" +#include "components/k_quirc/src/k_quirc_identify.c" +#include "components/k_quirc/src/k_quirc_version.c" // bspatch #include "components/esp32_bsdiff/bspatch.c" diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 724e0126..f66ea87b 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -52,7 +52,7 @@ idf_component_register(SRC_DIRS "." "${usbdir}" "${wallydirs}" "${secpdir}" - PRIV_REQUIRES assets libwally-core libsodium esp32-rotary-encoder esp32-quirc bootloader_support app_update nvs_flash bt autogenlang cbor esp_netif esp32_bsdiff esp32_deflate nghttp esp32_bc-ur driver mbedtls http_parser esp_hw_support efuse esp_eth esp_http_server esp_lcd usb vfs app_trace spi_flash google-otpauth-migrate ${extra_priv_requires} + PRIV_REQUIRES assets libwally-core libsodium esp32-rotary-encoder k_quirc bootloader_support app_update nvs_flash bt autogenlang cbor esp_netif esp32_bsdiff esp32_deflate nghttp cUR driver mbedtls http_parser esp_hw_support efuse esp_eth esp_http_server esp_lcd usb vfs app_trace spi_flash google-otpauth-migrate ${extra_priv_requires} EMBED_FILES ${PROJECT_DIR}/pinserver_public_key.pub ${logo_files} ${qemu_display_file}) if(CONFIG_ETH_USE_OPENETH) diff --git a/main/bcur.c b/main/bcur.c index 0044fd9a..7f7faa8e 100644 --- a/main/bcur.c +++ b/main/bcur.c @@ -11,8 +11,8 @@ #include "utils/util.h" #include -#include -#include +#include +#include // PSBT serialisation functions bool deserialise_psbt(const uint8_t* bytes, size_t bytes_len, struct wally_psbt** psbt_out); @@ -181,25 +181,27 @@ bool bcur_parse_bip39_wrapper( // Decode bcur string bool ret = false; - uint8_t decoder[URDECODER_SIZE]; - urcreate_placement_decoder(decoder, sizeof(decoder)); - if (!urreceive_part_decoder(decoder, bcur) || !uris_success_decoder(decoder)) { + ur_decoder_t* const decoder = ur_decoder_new(); + JADE_ASSERT(decoder); + if (ur_decoder_receive_part(decoder, bcur) != UR_DECODER_OK) { JADE_LOGW("Unable to decode bcur bip39 string from single part"); goto cleanup; } - // Read the result - char const* type = NULL; - uint8_t* result = NULL; - size_t result_len = 0; - urresult_ur_decoder(decoder, &result, &result_len, &type); - if (!type || !result || !result_len || strcasecmp(BCUR_TYPE_CRYPTO_BIP39, type)) { + // Read the result - borrowed from the decoder, freed with it + // NOTE: guaranteed non-NULL and populated when the decoder state is UR_DECODER_OK + const ur_result_t* const result = ur_decoder_get_result(decoder); + JADE_ASSERT(result); + JADE_ASSERT(result->cbor_data); + JADE_ASSERT(result->cbor_len); + JADE_ASSERT(result->type); + if (strcasecmp(BCUR_TYPE_CRYPTO_BIP39, result->type)) { JADE_LOGW("Unable to decode bcur bip39 string to expected type %s", BCUR_TYPE_CRYPTO_BIP39); goto cleanup; } // Decode the cbor - if (!bcur_parse_bip39(result, result_len, mnemonic, mnemonic_len, written)) { + if (!bcur_parse_bip39(result->cbor_data, result->cbor_len, mnemonic, mnemonic_len, written)) { JADE_LOGW("Failed to parse bcur bip39 cbor message"); goto cleanup; } @@ -208,7 +210,7 @@ bool bcur_parse_bip39_wrapper( ret = true; cleanup: - urfree_placement_decoder(decoder); + ur_decoder_free(decoder); return ret; } @@ -605,6 +607,16 @@ void bcur_build_cbor_crypto_account(const script_variant_t script_variant, const } // Support scanning a bc-ur qr-code - single-frame or animated/multi-frame. +// Context passed to collect_any_bcur() via qr_data->ctx. +// NOTE: the decoder is replaced on a recoverable failure, so this struct - not +// the decoder pointer - is what the caller holds on to. +typedef struct { + ur_decoder_t* decoder; + // Set when scanning stopped because the message can never be assembled. + // The caller reports it once the camera activity has been torn down. + const char* fatal_error; +} bcur_scan_ctx_t; + // Adds a scanned bc-ur qr the bcur decoder - only returns true when the decoder is complete. // ie. collates multiple frames until the entire bc-ur data is complete. // If the qr-code scanned is not a bc-ur part, return success immediately. @@ -617,6 +629,9 @@ static bool collect_any_bcur(qr_data_t* qr_data) JADE_ASSERT(qr_data->progress_bar); JADE_ASSERT(qr_data->data[qr_data->len] == '\0'); + bcur_scan_ctx_t* const ctx = (bcur_scan_ctx_t*)qr_data->ctx; + JADE_ASSERT(ctx->decoder); + if (qr_data->len < sizeof(BCUR_PREFIX) || strncasecmp((const char*)qr_data->data, BCUR_PREFIX, sizeof(BCUR_PREFIX) - 1)) { // Not bc-ur - return immediately @@ -626,32 +641,44 @@ static bool collect_any_bcur(qr_data_t* qr_data) // The scanned data looks like a bcur code or fragment, add it to the bcur decoder // and return true only when the bcur decoder says the message is complete. - const bool processed_part = urreceive_part_decoder(qr_data->ctx, (const char*)qr_data->data); + const ur_decoder_state_t state = ur_decoder_receive_part(ctx->decoder, (const char*)qr_data->data); + + // Permanent policy failure: the message declares a sequence length or size + // this build can never assemble, so no amount of further scanning will + // complete it. Stop the camera loop and let the caller report it, rather + // than spinning indefinitely with no feedback. + // NOTE: the UI call belongs in the caller - this runs on the camera task + // with the camera activity current. + if (state == UR_DECODER_ERROR_UNSUPPORTED_SIZE) { + JADE_LOGE("bcur message exceeds the decoder's size limits - abandoning scan"); + ctx->fatal_error = "QR data too large"; + return true; + } - // On hard failure, reset the decoder - if (uris_failure_decoder(qr_data->ctx)) { + // On hard failure (terminal but unsuccessful - eg. checksum mismatch), reset the decoder + // NOTE: transient errors (eg. a misread frame) are not terminal - keep feeding parts + if (ur_decoder_state_is_terminal(state) && state != UR_DECODER_OK) { JADE_LOGE("Failure to scan bcur data - resetting the decoder"); - urfree_placement_decoder(qr_data->ctx); - urcreate_placement_decoder(qr_data->ctx, URDECODER_SIZE); + ur_decoder_free(ctx->decoder); + ctx->decoder = ur_decoder_new(); + JADE_ASSERT(ctx->decoder); + // Reset the bar so the restart is visible rather than silent. + update_progress_bar(qr_data->progress_bar, 100, 0); return false; } - // Update associated progress bar - be a bit defensive here - const bool decoded = uris_success_decoder(qr_data->ctx); - const size_t nreceived = urreceived_parts_count_decoder(qr_data->ctx); - if (processed_part && nreceived) { - // NOTE: can only call 'expected' once we have received at least one part - const size_t nexpected = urexpected_part_count_decoder(qr_data->ctx); - - // If fully decoded show full bar - but if not fully decoded - // don't show a full bar - pause at 'almost done' if required. + // Update associated progress bar using the weighted estimate, which also + // credits mixed fountain parts that have not yet resolved a pure fragment. + // If not fully decoded don't show a full bar - cap at 'almost done'. + const bool decoded = state == UR_DECODER_OK; + if (!ur_decoder_state_is_error(state)) { if (decoded) { - update_progress_bar(qr_data->progress_bar, nexpected, nexpected); - } else if (nreceived < nexpected) { - update_progress_bar(qr_data->progress_bar, nexpected, nreceived); + update_progress_bar(qr_data->progress_bar, 100, 100); + } else { + const float estimate = ur_decoder_estimated_percent_complete_weighted(ctx->decoder); + const size_t pcnt = estimate * 100.0f; + update_progress_bar(qr_data->progress_bar, 100, pcnt < 99 ? pcnt : 99); } - // else, appear to have all pieces but not fully decoded ... - // just leave progress bar showing whatever 'almost done' level. } // Return true if complete @@ -666,34 +693,46 @@ bool bcur_scan_qr(const char* prompt_text, char** output_type, uint8_t** output, JADE_INIT_OUT_PPTR(output); JADE_INIT_OUT_SIZE(output_len); - uint8_t urdecoder[URDECODER_SIZE]; - urcreate_placement_decoder(urdecoder, sizeof(urdecoder)); + bcur_scan_ctx_t scan_ctx = { .decoder = ur_decoder_new(), .fatal_error = NULL }; + JADE_ASSERT(scan_ctx.decoder); progress_bar_t progress_bar = {}; - qr_data_t qr_data = { .len = 0, .is_valid = collect_any_bcur, .ctx = urdecoder, .progress_bar = &progress_bar }; + qr_data_t qr_data = { .len = 0, .is_valid = collect_any_bcur, .ctx = &scan_ctx, .progress_bar = &progress_bar }; // Scan qr code using the bcur decoder to collate multiple frames if required - if (!jade_camera_scan_qr(&qr_data, prompt_text, QR_GUIDE_SHOW, help_url)) { + // NOTE: collect_any_bcur() may replace the decoder (on hard failure), so always + // reach it through scan_ctx rather than caching the pointer. + const bool scanned = jade_camera_scan_qr(&qr_data, prompt_text, QR_GUIDE_SHOW, help_url); + ur_decoder_t* const urdecoder = scan_ctx.decoder; + + if (scan_ctx.fatal_error) { + // Scanning was abandoned because the message can never be assembled - + // report it here, now the camera activity has gone. + await_error(scan_ctx.fatal_error); + ur_decoder_free(urdecoder); + return false; + } + + if (!scanned) { // User exited without completing scanning - urfree_placement_decoder(urdecoder); + ur_decoder_free(urdecoder); return false; } // Copy output into output params - caller takes ownership - if (uris_success_decoder(urdecoder)) { + if (ur_decoder_get_state(urdecoder) == UR_DECODER_OK) { // bcur message scanned - extract from decoder and return the payload - uint8_t* result = NULL; - size_t result_len = 0; - const char* result_type = NULL; - urresult_ur_decoder(urdecoder, &result, &result_len, &result_type); + // NOTE: the result is borrowed from the decoder, and freed with it + const ur_result_t* const result = ur_decoder_get_result(urdecoder); JADE_ASSERT(result); - JADE_ASSERT(result_len); - JADE_ASSERT(result_type); + JADE_ASSERT(result->cbor_data); + JADE_ASSERT(result->cbor_len); + JADE_ASSERT(result->type); // Copy payload and bc-ur type - *output = JADE_MALLOC_PREFER_SPIRAM(result_len + offset); - memcpy(*output + offset, result, result_len); - *output_len = result_len + offset; - *output_type = strdup(result_type); + *output = JADE_MALLOC_PREFER_SPIRAM(result->cbor_len + offset); + memcpy(*output + offset, result->cbor_data, result->cbor_len); + *output_len = result->cbor_len + offset; + *output_type = strdup(result->type); } else { // Not a bc-ur code - copy straight payload and append a nul-terminator. // Leave bc-ur type as NULL to indicate data was not a bc-ur payload. @@ -705,7 +744,7 @@ bool bcur_scan_qr(const char* prompt_text, char** output_type, uint8_t** output, } // Free the decoder and return true (as we scanned data successfully) - urfree_placement_decoder(urdecoder); + ur_decoder_free(urdecoder); return true; } @@ -715,6 +754,33 @@ bool bcur_scan_qr(const char* prompt_text, char** output_type, uint8_t** output, // Caller takes ownership of the icons returned. // NOTE Only supports qr-versions from 4 to 12 (4, 6 and 12 fit nicely on a v1 Jade screen, and // 4, 6 and 9 on the larger v2 screen (with greater scaling)). +// Number of 'pure' data fragments a payload of 'len' will split into at this +// qr version, without building an encoder. +static size_t bcur_num_pure_fragments(const size_t len, const char* bcur_type, const uint8_t qr_version) +{ + JADE_ASSERT(bcur_type); + JADE_ASSERT(qr_version >= 4); + JADE_ASSERT(qr_version <= 12); + + const uint16_t capacity = QR_ALPHANUMERIC_CAPACITY[qr_version]; + const uint16_t max_fragment_size = BCUR_MAX_FRAGMENT_SIZE(capacity, bcur_type); + JADE_ASSERT(max_fragment_size > 0); + return (len + max_fragment_size - 1) / max_fragment_size; +} + +bool bcur_can_create_qr_icons(const size_t len, const char* bcur_type, const uint8_t qr_version) +{ + JADE_ASSERT(bcur_type); + JADE_ASSERT(len); + + // A stream with more fragments than the decoder's cap can never be read + // back - by this device or any other cUR-based one - and there is no + // feedback while scanning beyond the scan simply never completing. Refuse + // to generate it. UR_MAX_SEQ_LEN comes from the decoder's own header so + // the two cannot drift apart. + return bcur_num_pure_fragments(len, bcur_type, qr_version) <= UR_MAX_SEQ_LEN && len <= UR_MAX_MESSAGE_LEN; +} + void bcur_create_qr_icons(const uint8_t* payload, const size_t len, const char* bcur_type, const uint8_t qr_version, Icon** icons, size_t* num_icons) { @@ -741,11 +807,14 @@ void bcur_create_qr_icons(const uint8_t* payload, const size_t len, const char* JADE_LOGI("BC-UR encoding payload length %u as type %s", len, bcur_type); JADE_LOGI("Targetting qr-code version %u, capacity %u (alphanumeric mode), using max fragment size %u", qr_version, qrcode_alphanumeric_capacity, bcur_max_fragment_size); - uint8_t encoder[URENCODER_SIZE]; - urcreate_placement_encoder(encoder, sizeof(encoder), bcur_type, payload, len, bcur_max_fragment_size, 0, 8); - const size_t min_num_fragments = urseqlen_encoder(encoder); // the number of 'pure' data fragments + ur_encoder_t* const encoder = ur_encoder_new(bcur_type, payload, len, bcur_max_fragment_size, 0, 8); + JADE_ASSERT(encoder); + const size_t min_num_fragments = ur_encoder_seq_len(encoder); // the number of 'pure' data fragments const size_t num_fragments = BCUR_NUM_FRAGMENTS(min_num_fragments); // add some fountain-code fragments JADE_ASSERT(num_fragments >= min_num_fragments); + // Callers must have checked bcur_can_create_qr_icons() - producing a stream + // beyond the decoder's cap would yield QRs that cannot be scanned back. + JADE_ASSERT(min_num_fragments <= UR_MAX_SEQ_LEN); JADE_LOGI("Encoded payload length %u as %u pure fragments and %u fountain-code fragments", len, min_num_fragments, num_fragments - min_num_fragments); @@ -753,11 +822,12 @@ void bcur_create_qr_icons(const uint8_t* payload, const size_t len, const char* uint8_t* qrbuffer = JADE_MALLOC(qrcode_getBufferSize(qr_version)); // Convert to 'num_fragments' qr-code icons - const bool force_uppercase = true; // fetch bcur fragment as uppercase to conform to 'alphanumeric' qr mode + // NOTE: fragments are uppercase, to conform to 'alphanumeric' qr mode Icon* const qr_icons = JADE_MALLOC(num_fragments * sizeof(Icon)); for (int ifrag = 0; ifrag < num_fragments; ++ifrag) { char* fragment = NULL; - urnext_part_encoder(encoder, force_uppercase, &fragment); + const bool have_part = ur_encoder_next_part(encoder, &fragment); + JADE_ASSERT(have_part); const size_t fragment_len = strlen(fragment); JADE_LOGI("Fragment %u, making qr-code icon with data (length: %u): %s", ifrag, fragment_len, fragment); @@ -768,15 +838,15 @@ void bcur_create_qr_icons(const uint8_t* payload, const size_t len, const char* QRCode qrcode; const int qret = qrcode_initText(&qrcode, qrbuffer, qr_version, BCUR_QR_ECC, fragment); JADE_ASSERT(qret == 0); - urfree_encoded_encoder(fragment); + free(fragment); // Convert fragment to Icon qrcode_toIcon(&qrcode, qr_icons + ifrag, QR_SCALE_FACTOR[qr_version]); } - JADE_ASSERT(uris_complete_encoder(encoder)); + JADE_ASSERT(ur_encoder_is_complete(encoder)); free(qrbuffer); - urfree_placement_encoder(encoder); + ur_encoder_free(encoder); // Return the created icons *icons = qr_icons; @@ -810,12 +880,12 @@ bool bcur_check_fragment_sizes(void) size_t max = 0; for (size_t len = maxlen - 8; len < sizeof(payload); ++len) { - uint8_t encoder[URENCODER_SIZE]; - urcreate_placement_encoder(encoder, sizeof(encoder), type, payload, len, maxlen, 0, 8); + ur_encoder_t* const encoder = ur_encoder_new(type, payload, len, maxlen, 0, 8); + JADE_ASSERT(encoder); char* fragment = NULL; - urnext_part_encoder(encoder, true, &fragment); - JADE_ASSERT(fragment); + const bool have_part = ur_encoder_next_part(encoder, &fragment); + JADE_ASSERT(have_part); const size_t fraglen = strlen(fragment); if (fraglen + 4 > capacity) { // In truth fraglen == capacity is valid, but later parts (and of larger payloads) @@ -827,8 +897,8 @@ bool bcur_check_fragment_sizes(void) if (fraglen > max) { max = fraglen; } - urfree_encoded_encoder(fragment); - urfree_placement_encoder(encoder); + free(fragment); + ur_encoder_free(encoder); } JADE_LOGI("max: %u (of target/limit %u)", max, capacity); } diff --git a/main/bcur.h b/main/bcur.h index a205f068..fd40d7b9 100644 --- a/main/bcur.h +++ b/main/bcur.h @@ -64,4 +64,9 @@ WARN_UNUSED_RESULT bool bcur_scan_qr(const char* prompt_text, char** output_type void bcur_create_qr_icons( const uint8_t* payload, size_t len, const char* bcur_type, uint8_t qr_version, Icon** icons, size_t* num_icons); +// Whether a payload of the given length can be encoded at the given qr version +// without producing more fragments than a bc-ur decoder will accept. +// Callers must check this before bcur_create_qr_icons(), which asserts. +bool bcur_can_create_qr_icons(size_t len, const char* bcur_type, uint8_t qr_version); + #endif /* BCUR_H_ */ diff --git a/main/process/mnemonic.c b/main/process/mnemonic.c index db726f90..bbe4fab6 100644 --- a/main/process/mnemonic.c +++ b/main/process/mnemonic.c @@ -19,7 +19,6 @@ #include "process_utils.h" -#include #include #define MAX_NUM_FINAL_WORDS 128 @@ -200,16 +199,25 @@ static bool mnemonic_export_qr(const char* mnemonic, bool* export_qr_verified) } // Verify QR by scanning it back + // NOTE: qr_data holds the scanned seed entropy, so it must be wiped. + // Reduce it to the two flags we need and pop before branching, so no + // exit path from the checks below can skip the wipe. qr_data_t qr_data = { .len = 0 }; + SENSITIVE_PUSH(&qr_data, sizeof(qr_data)); const bool scan_success = jade_camera_scan_qr(&qr_data, "Scan QR to verify", QR_GUIDE_SHOW, "blkstrm.com/seedqr"); - if (scan_success && qr_data.len == entropy_len && !memcmp(qr_data.data, entropy, entropy_len)) { + const bool qr_matched + = scan_success && qr_data.len == entropy_len && !memcmp(qr_data.data, entropy, entropy_len); + const bool qr_captured = qr_data.len > 0; + SENSITIVE_POP(&qr_data); + + if (qr_matched) { // QR Code scanned, and it matched expected entropy await_message("QR Code Verified"); *export_qr_verified = true; break; // done } else { - const char* question[] = { qr_data.len ? "QR code does not match" : "No QR code captured", "Retry?" }; + const char* question[] = { qr_captured ? "QR code does not match" : "No QR code captured", "Retry?" }; if (await_skipyes_activity(NULL, question, 2, true, NULL)) { // User agreed to retry, so go back to displaying qr fragments continue; diff --git a/main/qrmode.c b/main/qrmode.c index 91ac273f..b97eb646 100644 --- a/main/qrmode.c +++ b/main/qrmode.c @@ -922,7 +922,29 @@ static gui_activity_t* create_display_bcur_qr_activity(const char* message[], co // Map BCUR cbor into a series of QR-code icons Icon* icons = NULL; size_t num_icons = 0; - const uint8_t qrcode_version = qr_version_from_flags(qr_flags); + uint8_t qrcode_version = qr_version_from_flags(qr_flags); + + // A payload needing more fragments than a bc-ur decoder accepts could never + // be scanned back - the scan would simply never complete. Step up to a + // denser QR (fewer, larger fragments) rather than emit an unusable stream. + // Denser codes are harder to scan, so this is only a fallback. + if (!bcur_can_create_qr_icons(cbor_len, bcur_type, qrcode_version)) { + static const uint8_t denser[] = { QR_VER_MID, QR_VER_HIGH }; + bool fits = false; + for (size_t i = 0; i < sizeof(denser) / sizeof(denser[0]); ++i) { + if (denser[i] > qrcode_version && bcur_can_create_qr_icons(cbor_len, bcur_type, denser[i])) { + JADE_LOGW("Payload too large for qr version %u - using %u instead", qrcode_version, denser[i]); + qrcode_version = denser[i]; + fits = true; + break; + } + } + if (!fits) { + JADE_LOGE("Payload of %u bytes is too large to display as a bc-ur qr", cbor_len); + return NULL; + } + } + bcur_create_qr_icons(cbor, cbor_len, bcur_type, qrcode_version, &icons, &num_icons); // Create qr activity for those icons @@ -949,6 +971,12 @@ static void display_bcur_qr(const char* message[], const size_t message_size, co // Create show psbt activity for those icons gui_activity_t* act = create_display_bcur_qr_activity(message, message_size, bcur_type, cbor, cbor_len, qr_flags); + if (!act) { + // Too large to encode as a scannable bc-ur animation at any density + idletimer_set_min_timeout_secs(0); + await_error("Data too large for QR"); + return; + } while (true) { // Show, and await button click @@ -959,7 +987,15 @@ static void display_bcur_qr(const char* message[], const size_t message_size, co if (handle_qr_options(&qr_flags, help_url)) { // Options were updated - re-create bcur qr screen display_processing_message_activity(); - act = create_display_bcur_qr_activity(message, message_size, bcur_type, cbor, cbor_len, qr_flags); + gui_activity_t* const newact + = create_display_bcur_qr_activity(message, message_size, bcur_type, cbor, cbor_len, qr_flags); + if (newact) { + act = newact; + } else { + // New density cannot encode this payload - keep the + // existing screen rather than dropping the user out. + await_error("Data too large for QR"); + } } } else if (ev_id == BTN_QR_BRIGHTNESS) { gui_next_qrcode_color(); diff --git a/main/qrscan.c b/main/qrscan.c index e96202c9..a1ce4733 100644 --- a/main/qrscan.c +++ b/main/qrscan.c @@ -1,5 +1,5 @@ #ifndef AMALGAMATED_BUILD -#include +#include "k_quirc.h" #include #include "camera.h" @@ -7,7 +7,6 @@ #include "jade_assert.h" #include "qrscan.h" #include "sensitive.h" -#include "utils/malloc_ext.h" #include "utils/util.h" #define SCAN_MARGIN 20 @@ -18,50 +17,53 @@ static bool qr_extract_payload(qr_data_t* qr_data) { JADE_ASSERT(qr_data); JADE_ASSERT(qr_data->q); - JADE_ASSERT(qr_data->ds); qr_data->data[0] = '\0'; qr_data->len = 0; - const int count = quirc_count(qr_data->q); + const int count = k_quirc_count(qr_data->q); if (count <= 0) { return false; } JADE_LOGI("Detected %d QR codes in image.", count); // Store the first string we manage to extract - initialise to empty string. - struct quirc_data data; - SENSITIVE_PUSH(&data, sizeof(data)); + k_quirc_result_t result; + SENSITIVE_PUSH(&result, sizeof(result)); // Look for a string for (int i = 0; i < count; ++i) { - struct quirc_code code; - quirc_extract(qr_data->q, i, &code); - - const quirc_decode_error_t error_status = quirc_decode(&code, &data, qr_data->ds); - if (error_status != QUIRC_SUCCESS) { - JADE_LOGW("QUIRC error %s", quirc_strerror(error_status)); - } else if (data.data_type == QUIRC_DATA_TYPE_KANJI) { - JADE_LOGW("QUIRC unexpected data type: %d", data.data_type); - } else if (!data.payload_len) { + const k_quirc_error_t error_status = k_quirc_decode(qr_data->q, i, &result); + if (error_status != K_QUIRC_SUCCESS) { + JADE_LOGW("QUIRC error %s", k_quirc_strerror(error_status)); + } else if (!result.valid) { + JADE_LOGW("QUIRC invalid result"); + } else if (result.data.data_type & K_QUIRC_DATA_TYPE_KANJI) { + // NOTE: data_type is a bitmask of every segment mode in the symbol, + // so this must be a mask test. An equality test passed any + // mixed-mode code that ended in another mode - eg. a Kanji segment + // followed by a one-byte segment reported BYTE, and its Shift-JIS + // bytes reached the string handling below. + JADE_LOGW("QUIRC unexpected data type: %d", result.data.data_type); + } else if (!result.data.payload_len) { JADE_LOGW("QUIRC empty string"); - } else if (data.payload_len >= sizeof(qr_data->data)) { - JADE_LOGW("QUIRC data too long to handle: %u", data.payload_len); - JADE_ASSERT(data.payload_len <= sizeof(data.payload)); + } else if (result.data.payload_len >= sizeof(qr_data->data)) { + JADE_LOGW("QUIRC data too long to handle: %u", result.data.payload_len); + JADE_ASSERT(result.data.payload_len <= sizeof(result.data.payload)); } else { // The payload appears to be a nul terminated string, but the // 'payload_len' seems to be the string length not including that // terminator. // To avoid any confusion or grey areas, we copy the bytes, // and then explicitly add the nul terminator ourselves. - memcpy(qr_data->data, data.payload, data.payload_len); - qr_data->data[data.payload_len] = '\0'; - qr_data->len = data.payload_len; - SENSITIVE_POP(&data); + memcpy(qr_data->data, result.data.payload, result.data.payload_len); + qr_data->data[result.data.payload_len] = '\0'; + qr_data->len = result.data.payload_len; + SENSITIVE_POP(&result); return true; } } - SENSITIVE_POP(&data); + SENSITIVE_POP(&result); return false; } @@ -79,7 +81,7 @@ static bool qr_recognize( // Checked qr image buffer exists and is an acceptable size int quirc_width = 0, quirc_height = 0; - uint8_t* const quirc_image = quirc_begin(qr_data->q, &quirc_width, &quirc_height); + uint8_t* const quirc_image = k_quirc_begin(qr_data->q, &quirc_width, &quirc_height); JADE_ASSERT(quirc_image); JADE_ASSERT(quirc_width <= width); JADE_ASSERT(quirc_height <= height); @@ -95,7 +97,7 @@ static bool qr_recognize( memcpy(quirc_image + (y * quirc_width), data + ((y + yoffset) * width) + xoffset, quirc_width); } } - quirc_end(qr_data->q); + k_quirc_end(qr_data->q, false); // If no QR data can be recognised/extracted, return false if (!qr_extract_payload(qr_data) || !qr_data->len) { @@ -126,14 +128,12 @@ bool scan_qr(const size_t width, const size_t height, const uint8_t* data, const JADE_ASSERT(!qr_data->q); // Create the quirc structs - qr_data->q = quirc_new(); + qr_data->q = k_quirc_new(); JADE_ASSERT(qr_data->q); - qr_data->ds = JADE_MALLOC_DRAM(sizeof(struct datastream)); - JADE_ASSERT(qr_data->ds); // Also correctly size the internal image buffer since we know the size of the camera images. const uint16_t scan_width = min_u16(CAMERA_IMAGE_WIDTH, CAMERA_IMAGE_HEIGHT) - SCAN_MARGIN; - const int qret = quirc_resize(qr_data->q, scan_width, scan_width); + const int qret = k_quirc_resize(qr_data->q, scan_width, scan_width); JADE_ASSERT(qret == 0); qr_data->len = 0; @@ -143,10 +143,8 @@ bool scan_qr(const size_t width, const size_t height, const uint8_t* data, const const bool ret = qr_recognize(width, height, data, len, qr_data); // Destroy the quirc structs created above - quirc_destroy(qr_data->q); + k_quirc_destroy(qr_data->q); qr_data->q = NULL; - free(qr_data->ds); - qr_data->ds = NULL; // Any scanned qr code will be in the qr_data passed return ret && qr_data->len > 0; @@ -166,15 +164,13 @@ bool jade_camera_scan_qr( #ifdef CONFIG_HAS_CAMERA // Create the quirc structs (reused for each frame) - destroyed below JADE_ASSERT(!qr_data->q); - qr_data->q = quirc_new(); + qr_data->q = k_quirc_new(); JADE_ASSERT(qr_data->q); - qr_data->ds = JADE_MALLOC_DRAM(sizeof(struct datastream)); - JADE_ASSERT(qr_data->ds); // Also correctly size the internal image buffer since we know the size of the camera images. // This image buffer is then reused for every camera image frame processed. const uint16_t scan_width = min_u16(CAMERA_IMAGE_WIDTH, CAMERA_IMAGE_HEIGHT) - SCAN_MARGIN; - const int qret = quirc_resize(qr_data->q, scan_width, scan_width); + const int qret = k_quirc_resize(qr_data->q, scan_width, scan_width); JADE_ASSERT(qret == 0); qr_data->len = 0; @@ -188,10 +184,8 @@ bool jade_camera_scan_qr( help_url, qr_data->progress_bar); // Destroy the quirc structs created above - quirc_destroy(qr_data->q); + k_quirc_destroy(qr_data->q); qr_data->q = NULL; - free(qr_data->ds); - qr_data->ds = NULL; // Any scanned qr code will be in the qr_data passed return qr_data->len > 0; diff --git a/main/qrscan.h b/main/qrscan.h index f60d884c..65438c83 100644 --- a/main/qrscan.h +++ b/main/qrscan.h @@ -11,7 +11,7 @@ // An extracted QR code string #define QR_MAX_PAYLOAD_LENGTH 1024 -struct quirc; +struct k_quirc; typedef struct _qr_data_t qr_data_t; // Function to tell whether the extracted qr data is valid for the callers purposes @@ -32,9 +32,8 @@ struct _qr_data_t { // Any progress-bar associated with this (potentially multi-frame) scanning progress_bar_t* progress_bar; - // Cached internal quirc structs - caller should set to NULL - struct quirc* q; - struct datastream* ds; + // Cached internal quirc decoder - caller should set to NULL + struct k_quirc* q; }; #ifdef CONFIG_DEBUG_MODE diff --git a/main/selfcheck.c b/main/selfcheck.c index eb5d61eb..aefdeaa9 100644 --- a/main/selfcheck.c +++ b/main/selfcheck.c @@ -25,9 +25,9 @@ #include #include -#include -#include #include +#include +#include int register_multisig_file(const char* multisig_file, size_t multisig_file_len, const char** errmsg); @@ -478,20 +478,20 @@ bool test_multisig_files(jade_process_t* process) #define FREE_DECODER_AND_FAIL(d) \ do { \ - urfree_placement_decoder(d); \ + ur_decoder_free(d); \ FAIL(); \ } while (false) #define FREE_ENCODER_AND_FAIL(e) \ do { \ - urfree_placement_encoder(e); \ + ur_encoder_free(e); \ FAIL(); \ } while (false) #define FREE_ENCODED_PARTS(p) \ do { \ for (int i = 0; i < sizeof(p) / sizeof(p[0]); ++i) { \ - urfree_encoded_encoder(p[i]); \ + free(p[i]); \ } \ } while (false) @@ -526,119 +526,87 @@ static bool test_bcur_decode_encode(void) // 1. Try decoder { // Check decoder with a message of 2 'pure' fragments - uint8_t decoder[URDECODER_SIZE]; - urcreate_placement_decoder(decoder, sizeof(decoder)); - if (uris_success_decoder(decoder)) { + ur_decoder_t* const decoder = ur_decoder_new(); + JADE_ASSERT(decoder); + if (ur_decoder_get_state(decoder) != UR_DECODER_PROCESSING) { FREE_DECODER_AND_FAIL(decoder); } - if (uris_complete_decoder(decoder)) { + if (ur_decoder_received_parts_count(decoder) != 0) { FREE_DECODER_AND_FAIL(decoder); } - if (uris_failure_decoder(decoder)) { - FREE_DECODER_AND_FAIL(decoder); - } - if (urreceived_parts_count_decoder(decoder) != 0) { - FREE_DECODER_AND_FAIL(decoder); - } - // NOTE: Can't call urexpected_part_count_decoder() until first part processed + // NOTE: Can't call ur_decoder_expected_part_count() until first part processed - // send first qr - if (!urreceive_part_decoder(decoder, qr_part1of2)) { - FREE_DECODER_AND_FAIL(decoder); - } - if (uris_failure_decoder(decoder)) { - FREE_DECODER_AND_FAIL(decoder); - } - if (uris_success_decoder(decoder)) { - // Should NOT be complete yet + // send first qr - should be processed, but NOT complete yet + if (ur_decoder_receive_part(decoder, qr_part1of2) != UR_DECODER_PROCESSING) { FREE_DECODER_AND_FAIL(decoder); } - if (urreceived_parts_count_decoder(decoder) != 1) { + if (ur_decoder_received_parts_count(decoder) != 1) { FREE_DECODER_AND_FAIL(decoder); } - if (urexpected_part_count_decoder(decoder) != 2) { + if (ur_decoder_expected_part_count(decoder) != 2) { FREE_DECODER_AND_FAIL(decoder); } - // send first qr again - should be ignored/harmless - if (!urreceive_part_decoder(decoder, qr_part1of2)) { - FREE_DECODER_AND_FAIL(decoder); - } - if (uris_failure_decoder(decoder)) { - FREE_DECODER_AND_FAIL(decoder); - } - if (uris_success_decoder(decoder)) { - // Should NOT be complete yet + // send first qr again - should be ignored/harmless, still NOT complete + if (ur_decoder_receive_part(decoder, qr_part1of2) != UR_DECODER_PROCESSING) { FREE_DECODER_AND_FAIL(decoder); } - if (urreceived_parts_count_decoder(decoder) != 1) { + if (ur_decoder_received_parts_count(decoder) != 1) { FREE_DECODER_AND_FAIL(decoder); } - if (urexpected_part_count_decoder(decoder) != 2) { + if (ur_decoder_expected_part_count(decoder) != 2) { FREE_DECODER_AND_FAIL(decoder); } - // send second qr - if (!urreceive_part_decoder(decoder, qr_part2of2)) { + // send second qr - should now be complete + if (ur_decoder_receive_part(decoder, qr_part2of2) != UR_DECODER_OK) { FREE_DECODER_AND_FAIL(decoder); } - if (uris_failure_decoder(decoder)) { + if (ur_decoder_received_parts_count(decoder) != 2) { FREE_DECODER_AND_FAIL(decoder); } - if (!uris_success_decoder(decoder)) { - // Should now be complete - FREE_DECODER_AND_FAIL(decoder); - } - if (urreceived_parts_count_decoder(decoder) != 2) { - FREE_DECODER_AND_FAIL(decoder); - } - if (urexpected_part_count_decoder(decoder) != 2) { + if (ur_decoder_expected_part_count(decoder) != 2) { FREE_DECODER_AND_FAIL(decoder); } - // read the result - const char* type = NULL; - uint8_t* result = NULL; - size_t result_len = 0; - urresult_ur_decoder(decoder, &result, &result_len, &type); - JADE_ASSERT(type); - JADE_ASSERT(result_len); + // read the result - borrowed from the decoder, freed with it + const ur_result_t* const result = ur_decoder_get_result(decoder); JADE_ASSERT(result); - if (strncmp(expected_type, type, strlen(expected_type))) { + JADE_ASSERT(result->type); + JADE_ASSERT(result->cbor_len); + JADE_ASSERT(result->cbor_data); + if (strcmp(expected_type, result->type)) { FREE_DECODER_AND_FAIL(decoder); } - if (result_len != payload_len || memcmp(result, payload, result_len)) { + if (result->cbor_len != payload_len || memcmp(result->cbor_data, payload, result->cbor_len)) { FREE_DECODER_AND_FAIL(decoder); } - urfree_placement_decoder(decoder); + ur_decoder_free(decoder); } // 2. Try encoder { // If we encode the data, the first two parts should match the original payloads // ie. the 'pure' fragments (ie. the actual data split into two and encoded) - uint8_t encoder[URENCODER_SIZE]; - urcreate_placement_encoder( - encoder, sizeof(encoder), expected_type, payload, payload_len, encoder_max_fragment_len, 0, 10); - const bool force_uppercase = true; + ur_encoder_t* const encoder + = ur_encoder_new(expected_type, payload, payload_len, encoder_max_fragment_len, 0, 10); + JADE_ASSERT(encoder); char* parts[3] = { NULL, NULL, NULL }; - urnext_part_encoder(encoder, force_uppercase, &parts[0]); - if (!parts[0] || strncmp(parts[0], qr_part1of2, strlen(qr_part1of2))) { + if (!ur_encoder_next_part(encoder, &parts[0]) || !parts[0] || strcmp(parts[0], qr_part1of2)) { FREE_ENCODED_PARTS(parts); FREE_ENCODER_AND_FAIL(encoder); } - if (uris_complete_encoder(encoder)) { + if (ur_encoder_is_complete(encoder)) { // Should NOT be complete yet FREE_ENCODED_PARTS(parts); FREE_ENCODER_AND_FAIL(encoder); } - urnext_part_encoder(encoder, force_uppercase, &parts[1]); - if (!parts[1] || strncmp(parts[1], qr_part2of2, strlen(qr_part2of2))) { + if (!ur_encoder_next_part(encoder, &parts[1]) || !parts[1] || strcmp(parts[1], qr_part2of2)) { FREE_ENCODED_PARTS(parts); FREE_ENCODER_AND_FAIL(encoder); } - if (!uris_complete_encoder(encoder)) { + if (!ur_encoder_is_complete(encoder)) { // Should now be complete FREE_ENCODED_PARTS(parts); FREE_ENCODER_AND_FAIL(encoder); @@ -646,12 +614,15 @@ static bool test_bcur_decode_encode(void) // We can continue to generate additional parts - these are fountain-code fragments // which can stand in for any missed fragments. NOTE: the sequence-numbers appear 'overflowed'. - urnext_part_encoder(encoder, force_uppercase, &parts[2]); - if (!parts[2] || strncmp(parts[2], "UR:CRYPTO-PSBT/3-2/", strlen("UR:CRYPTO-PSBT/3-2/"))) { + // NOTE: deliberately a prefix comparison, unlike the checks above - + // only the header is fixed, the fountain-fragment body varies. The + // length check stops a header-only string from passing. + if (!ur_encoder_next_part(encoder, &parts[2]) || !parts[2] || strlen(parts[2]) <= strlen("UR:CRYPTO-PSBT/3-2/") + || strncmp(parts[2], "UR:CRYPTO-PSBT/3-2/", strlen("UR:CRYPTO-PSBT/3-2/"))) { FREE_ENCODED_PARTS(parts); FREE_ENCODER_AND_FAIL(encoder); } - urfree_placement_encoder(encoder); + ur_encoder_free(encoder); // Check fountain encoding / redundancy with fresh decoders - incl. getting a 'later' part first // Check all 2-of-3 combinations - any 2 distinct parts should be sufficient. @@ -664,46 +635,40 @@ static bool test_bcur_decode_encode(void) const size_t initial_expected_received = i == 2 ? 0 : 1; for (size_t j = 0; j < 3; ++j) { - uint8_t decoder[URDECODER_SIZE]; - urcreate_placement_decoder(decoder, sizeof(decoder)); + ur_decoder_t* const decoder = ur_decoder_new(); + JADE_ASSERT(decoder); - // Present first part - if (!urreceive_part_decoder(decoder, parts[i])) { + // Present first part - should be processed, but NOT complete yet + if (ur_decoder_receive_part(decoder, parts[i]) != UR_DECODER_PROCESSING) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (uris_success_decoder(decoder)) { - // Should NOT be complete yet + if (ur_decoder_processed_parts_count(decoder) != 1) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (urprocessed_parts_count_decoder(decoder) != 1) { + if (ur_decoder_expected_part_count(decoder) != 2) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (urexpected_part_count_decoder(decoder) != 2) { - FREE_ENCODED_PARTS(parts); - FREE_DECODER_AND_FAIL(decoder); - } - if (urreceived_parts_count_decoder(decoder) != initial_expected_received) { + if (ur_decoder_received_parts_count(decoder) != initial_expected_received) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } // Present second part - if (!urreceive_part_decoder(decoder, parts[j])) { - FREE_ENCODED_PARTS(parts); - FREE_DECODER_AND_FAIL(decoder); - } - if (urprocessed_parts_count_decoder(decoder) != 2) { + const ur_decoder_state_t state = ur_decoder_receive_part(decoder, parts[j]); + if (ur_decoder_state_is_error(state)) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (urexpected_part_count_decoder(decoder) != 2) { + // NOTE: a duplicate part is deduped before it is counted, so + // 'processed' remains 1 if the same part is presented twice + if (ur_decoder_processed_parts_count(decoder) != (i != j ? 2 : 1)) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (uris_failure_decoder(decoder)) { + if (ur_decoder_expected_part_count(decoder) != 2) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } @@ -715,43 +680,41 @@ static bool test_bcur_decode_encode(void) // the 'pure' data part is received (and can be combined with the fountain // part to generate the missing data part). if (i != j) { - if (urreceived_parts_count_decoder(decoder) != 2) { + if (ur_decoder_received_parts_count(decoder) != 2) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (!uris_success_decoder(decoder)) { + if (state != UR_DECODER_OK) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - // Check payload is as expected - const char* type = NULL; - uint8_t* result = NULL; - size_t result_len = 0; - urresult_ur_decoder(decoder, &result, &result_len, &type); - JADE_ASSERT(type); - JADE_ASSERT(result_len); + // Check payload is as expected - result borrowed from the decoder + const ur_result_t* const result = ur_decoder_get_result(decoder); JADE_ASSERT(result); - if (strncmp(expected_type, type, strlen(expected_type))) { + JADE_ASSERT(result->type); + JADE_ASSERT(result->cbor_len); + JADE_ASSERT(result->cbor_data); + if (strcmp(expected_type, result->type)) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (result_len != payload_len || memcmp(result, payload, result_len)) { + if (result->cbor_len != payload_len || memcmp(result->cbor_data, payload, result->cbor_len)) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } } else { // Same part received twice - does not increment 'received_parts' - if (urreceived_parts_count_decoder(decoder) != initial_expected_received) { + if (ur_decoder_received_parts_count(decoder) != initial_expected_received) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } - if (uris_success_decoder(decoder)) { + if (state != UR_DECODER_PROCESSING) { FREE_ENCODED_PARTS(parts); FREE_DECODER_AND_FAIL(decoder); } } - urfree_placement_decoder(decoder); + ur_decoder_free(decoder); } } FREE_ENCODED_PARTS(parts); @@ -783,28 +746,25 @@ bool test_bcur_decode_bad_cases(void) // Various bad cases - test the decoder ignores them for (size_t i = 0; i < ncases; ++i) { - uint8_t decoder[URDECODER_SIZE]; - urcreate_placement_decoder(decoder, sizeof(decoder)); + ur_decoder_t* const decoder = ur_decoder_new(); + JADE_ASSERT(decoder); - // Present first part - should be ignored and all counts - // of 'parts seen' should remain zero, and 'is complete' - // and 'is success' should be false. - if (urreceive_part_decoder(decoder, cases[i])) { - FREE_DECODER_AND_FAIL(decoder); - } - if (urprocessed_parts_count_decoder(decoder)) { + // Present first part - should be rejected with a transient (non-terminal) + // error, and all counts of 'parts seen' should remain zero. + const ur_decoder_state_t state = ur_decoder_receive_part(decoder, cases[i]); + if (!ur_decoder_state_is_error(state)) { FREE_DECODER_AND_FAIL(decoder); } - if (urreceived_parts_count_decoder(decoder)) { + if (ur_decoder_state_is_terminal(state)) { FREE_DECODER_AND_FAIL(decoder); } - if (uris_complete_decoder(decoder)) { + if (ur_decoder_processed_parts_count(decoder)) { FREE_DECODER_AND_FAIL(decoder); } - if (uris_success_decoder(decoder)) { + if (ur_decoder_received_parts_count(decoder)) { FREE_DECODER_AND_FAIL(decoder); } - urfree_placement_decoder(decoder); + ur_decoder_free(decoder); } return true; }