Skip to content
126 changes: 96 additions & 30 deletions core/embed/io/nrf/stm32u5/nrf_update.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,14 @@
#define IMAGE_HASH_LEN 32
#define IMAGE_TLV_SHA256 0x10

struct image_version {
uint8_t iv_major;
uint8_t iv_minor;
uint16_t iv_revision;
uint32_t iv_build_num;
} __packed;

struct image_header {
uint32_t ih_magic;
uint32_t ih_load_addr;
uint16_t ih_hdr_size; /* Size of image header (bytes). */
uint16_t ih_protect_tlv_size; /* Size of protected TLV area (bytes). */
uint32_t ih_img_size; /* Does not include header. */
uint32_t ih_flags; /* IMAGE_F_[...]. */
struct image_version ih_ver;
nrf_app_version_t ih_ver;
uint32_t _pad1;
} __packed;

Expand All @@ -56,11 +49,11 @@ struct image_header {
*
* @param binary_ptr pointer to the binary image
* @param out_hash Buffer of at least IMAGE_HASH_LEN bytes to receive the hash
* @return 0 on success, or a negative errno on failure
* @return "true" on success, "false" on failure
*/
static int read_image_sha256(const uint8_t *binary_ptr, size_t binary_size,
uint8_t out_hash[IMAGE_HASH_LEN]) {
int rc;
static bool read_image_sha256(const uint8_t *binary_ptr, size_t binary_size,
uint8_t out_hash[IMAGE_HASH_LEN]) {
bool ret;

/* Read header to get image_size and hdr_size */
struct image_header *hdr = (struct image_header *)binary_ptr;
Expand All @@ -77,7 +70,7 @@ static int read_image_sha256(const uint8_t *binary_ptr, size_t binary_size,
uint16_t tlv_hdr[2];

if (off + sizeof(tlv_hdr) > binary_size) {
rc = -1; // Not enough data for TLV header
ret = false; // Not enough data for TLV header
break;
}

Expand All @@ -87,45 +80,118 @@ static int read_image_sha256(const uint8_t *binary_ptr, size_t binary_size,
uint16_t len = tlv_hdr[1];

if (off + sizeof(tlv_hdr) + len > binary_size) {
rc = -1; // Not enough data for TLV value
ret = false; // Not enough data for TLV value
break;
}

if (type == IMAGE_TLV_SHA256) {
if (len != IMAGE_HASH_LEN) {
rc = -1;
ret = false;
} else {
memcpy(out_hash, binary_ptr + off + sizeof(tlv_hdr), IMAGE_HASH_LEN);
rc = 0;
ret = true;
}
break;
}

off += sizeof(tlv_hdr) + len;
}

return rc;
return ret;
}

/**
* Read the image version from the image header.
*
* @param image_ptr pointer to the binary image
* @param out_version Pointer to nrf_app_version_t to receive the version
* @return "true" on success, "false" on failure
*/
static bool image_version_read(const uint8_t *image_ptr,
nrf_app_version_t *out_version) {
struct image_header *hdr = (struct image_header *)image_ptr;

if (image_ptr == NULL || out_version == NULL) {
return false;
}

memcpy(out_version, &hdr->ih_ver, sizeof(nrf_app_version_t));

return true;
}

/**
* Read the image version from the nRF MCUboot via SMP serial recovery.
*
* @param out_version Pointer to nrf_app_version_t to receive the version
* @return "true" on success, "false" on failure
*/
static bool nrf_smp_version_get(nrf_app_version_t *out_version) {
bool ret = false;

nrf_reboot_to_bootloader();
nrf_set_dfu_mode(true);

if (smp_image_version_get(out_version)) {
// Success - version string provided via SMP has been decoded and stored
// within "out_version" variable
ret = true;
}

nrf_reboot();
nrf_set_dfu_mode(false);

return ret;
}

/**
* Comparison of two image versions.
*
* @param v1 Pointer to first nrf_app_version_t
* @param v2 Pointer to second nrf_app_version_t
* @return 0 when equal, 1 when v1 is greater, -1 when v2 is greater
*/
static int version_cmp(const nrf_app_version_t *v1,
const nrf_app_version_t *v2) {
if (v1->major != v2->major) {
return (v1->major < v2->major) ? -1 : 1;
}
if (v1->minor != v2->minor) {
return (v1->minor < v2->minor) ? -1 : 1;
}
if (v1->revision != v2->revision) {
return (v1->revision < v2->revision) ? -1 : 1;
}
if (v1->build_num != v2->build_num) {
return (v1->build_num < v2->build_num) ? -1 : 1;
}
return 0;
}

bool nrf_update_required(const uint8_t *image_ptr, size_t image_len) {
nrf_info_t info = {0};
for (int i = 0; i < 3; i++) {
nrf_info_t info;
uint8_t expected_hash[SHA256_DIGEST_LENGTH];

uint16_t try_cntr = 0;
while (!nrf_get_info(&info)) {
nrf_reboot();
systick_delay_ms(500);
try_cntr++;
if (try_cntr > 3) {
// Assuming corrupted image, but we could also check comm with MCUboot
return true;
if (nrf_get_info(&info) == true &&
read_image_sha256(image_ptr, image_len, expected_hash) == true) {
return memcmp(info.hash, expected_hash, SHA256_DIGEST_LENGTH) != 0;
}
}

uint8_t expected_hash[SHA256_DIGEST_LENGTH] = {0};
// Can't communicate with the App via SPI, trying SMP serial recovery over
// UART to nRF MCUboot
nrf_app_version_t smp_version, image_version;

if (nrf_smp_version_get(&smp_version) == true &&
image_version_read(image_ptr, &image_version) == true) {
return version_cmp(&image_version, &smp_version) > 0;
}

read_image_sha256(image_ptr, image_len, expected_hash);
systick_delay_ms(100); // TODO: is it necessary?
}

return memcmp(info.hash, expected_hash, SHA256_DIGEST_LENGTH) != 0;
// Assuming corrupted image, force update
return true;
}

bool nrf_update(const uint8_t *image_ptr, size_t image_len) {
Expand Down
84 changes: 84 additions & 0 deletions core/embed/rust/rust_smp.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,96 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <trezor_types.h>

/**
* @brief Parsed nRF application version in format
* "major.minor.revision[.build_num]".
*
* Matches MCUboot image header layout (8 bytes total).
* If the optional build number is absent it defaults to 0.
*/
typedef struct __packed {
uint8_t major; /**< Major version (0..255). */
uint8_t minor; /**< Minor version (0..255). */
uint16_t revision; /**< Revision (0..65535). */
uint32_t build_num; /**< Optional build number (0..4294967295). */
} nrf_app_version_t;

/**
* @brief Send an SMP Echo request with a small text payload.
*
* @param text Pointer to ASCII data (not null-terminated).
* @param text_len Number of bytes to send.
* @return true on successful send and valid response, false otherwise.
*
* @note Length is bounded by underlying SMP MTU; oversized input fails.
*/
bool smp_echo(const char* text, uint8_t text_len);

/**
* @brief Issue an SMP Reset request to the remote nRF device.
*
* Sends a reset command and does not wait for the device to come back.
* @return void
*/
void smp_reset(void);

/**
* @brief Retrieve and parse the active nRF application version via SMP.
*
* Performs an Image State read, extracts the version string, and fills
* the provided structure with numeric fields.
*
* @param out Pointer to nrf_app_version_t to be filled (must be valid).
* @return true on success, false on failure (communication or parse error).
*
* @warning Fails if SMP channel not acquired or version format invalid.
*/
bool smp_image_version_get(nrf_app_version_t* out);

/**
* @brief Feed a received transport byte into the SMP RX state machine.
*
* Call for each byte arriving from the nRF link (i.e. UART).
* Assembles frames and dispatches completed SMP responses internally.
*
* @param byte Received raw byte.
* @return void
*/
void smp_process_rx_byte(uint8_t byte);

/**
* @brief Upload an MCUboot image to the nRF device over SMP.
*
* Streams the binary image followed by hash metadata (if required) using
* SMP upload semantics.
*
* @param data Pointer to image buffer.
* @param len Size of image buffer in bytes.
* @param image_hash Pointer to hash bytes (may be NULL if not used).
* @param image_hash_len Length of hash (e.g. 32 for SHA-256).
* @return true if upload completed successfully, false on error.
*
* @note Caller must ensure image fits partition and hash matches expected size.
*/
bool smp_upload_app_image(const uint8_t* data, size_t len,
const uint8_t* image_hash, size_t image_hash_len);
40 changes: 39 additions & 1 deletion core/embed/rust/src/smp/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{echo, process_rx_byte, reset, upload};
use super::{echo, image_info, process_rx_byte, reset, upload};

use crate::util::from_c_array;

Expand All @@ -14,6 +14,44 @@ extern "C" fn smp_reset() {
reset::send();
}

#[repr(C, packed)]
pub struct NrfAppVersion {
pub major: u8,
pub minor: u8,
pub revision: u16,
pub build_num: u32,
}

/// Get the nRF app version as parsed integer components.
///
/// Sends an SMP request to the nRF device to retrieve the active application
/// image version, then parses the version string into individual numeric
/// components.
///
/// # Arguments
/// * `out` - Pointer to NrfAppVersion structure to be filled. Must not be NULL.
///
/// # Returns
/// `true` if version was successfully retrieved and parsed, `false` otherwise.
#[no_mangle]
extern "C" fn smp_image_version_get(out: *mut NrfAppVersion) -> bool {
if out.is_null() {
return false;
}
match image_info::get_version_numbers() {
Some(v) => {
unsafe {
(*out).major = v.major;
(*out).minor = v.minor;
(*out).revision = v.revision;
(*out).build_num = v.build_num;
}
true
}
None => false,
}
}

#[no_mangle]
extern "C" fn smp_upload_app_image(
data: *const cty::uint8_t,
Expand Down
Loading
Loading