Files
MeshCore/src/helpers/ota/OtaStoreSdNrf52.cpp
T

725 lines
22 KiB
C++

#include "OtaStoreSdNrf52.h"
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
#include <Arduino.h>
#include <SdFat.h>
#include <SPI.h>
#include <new>
#include <stdlib.h>
#include <string.h>
#include "MeshCore.h"
#include "OtaBootloaderUpdate.h"
#include "OtaByteIO.h"
#include "OtaFlashLayout_nrf52.h"
#include "OtaSdAuthRecord.h"
#include "OtaSdBootToken.h"
#include "OtaSelf.h"
#include "OtaSdSpiBusNrf52.h"
#include "flash/flash_nrf5x.h"
#ifndef OTA_SD_SCK_MHZ
#define OTA_SD_SCK_MHZ 8
#endif
#if defined(OTA_RAK15002_SD_STORE)
// The mounted FAT/exFAT volume and both retained file handles (staging and
// archive) are lazy heap objects, not linked globals. Bind their total plus
// allocator metadata to the runtime RAM policy's separate 2 KiB allowance.
static_assert(sizeof(mesh::ota::OtaSdFs) + 2u * sizeof(mesh::ota::OtaSdFile) + 3u * 32u <= 2048u,
"RAK15002 FAT volume/file allocations exceed the RAM allowance");
#endif
namespace mesh {
namespace ota {
namespace {
static void clear_sd_auth_record() {
volatile uint8_t* const retained =
reinterpret_cast<volatile uint8_t*>(MOTA_SD_AUTH_ADDR);
for (uint32_t i = 0; i < MOTA_SD_AUTH_LEN; ++i) retained[i] = 0;
__DMB();
__DSB();
}
#if !defined(OTA_SEEDER_ONLY)
static bool publish_sd_auth_record(const uint8_t record[MOTA_SD_AUTH_LEN]) {
volatile uint8_t* const retained =
reinterpret_cast<volatile uint8_t*>(MOTA_SD_AUTH_ADDR);
// Invalidate magic first, then publish the body and magic last. A reset at any
// intermediate instruction therefore leaves a record OTAFIX rejects.
for (uint32_t i = 0; i < sizeof(MOTA_SD_AUTH_MAGIC); ++i) retained[i] = 0;
__DMB();
for (uint32_t i = sizeof(MOTA_SD_AUTH_MAGIC); i < MOTA_SD_AUTH_LEN; ++i)
retained[i] = record[i];
__DMB();
for (uint32_t i = 0; i < sizeof(MOTA_SD_AUTH_MAGIC); ++i) retained[i] = record[i];
__DMB();
__DSB();
for (uint32_t i = 0; i < MOTA_SD_AUTH_LEN; ++i)
if (retained[i] != record[i]) return false;
return true;
}
#endif
} // namespace
const char* const OtaStoreSdNrf52::PATH = "/meshcore-ota.mota";
OtaStoreSdNrf52::OtaStoreSdNrf52()
#if !defined(OTA_RAK15002_SD_STORE)
: _sd(new (std::nothrow) OtaSdFs()), _file(new (std::nothrow) OtaSdFile())
#endif
{
// If an earlier reset did not enter OTAFIX, stale authorization must not arm a
// later unrelated reboot. OTAFIX runs before application constructors and
// consumes a valid record on the intended apply boot.
clear_sd_auth_record();
}
OtaStoreSdNrf52::~OtaStoreSdNrf52() {
resetStoreState();
if (_file) delete _file;
if (_sd) delete _sd;
releaseBusClaim();
}
bool OtaStoreSdNrf52::acquireBus() {
#if defined(OTA_RAK15002_SD_STORE)
if (!ota_rak15002_sd_card_present()) { fail("SD card absent"); return false; }
if (!rak4631_try_acquire_spi(RakSharedSpiOwner::Sd)) {
fail("WisBlock SPI owned by Ethernet or NOR"); return false;
}
_bus_owned = true;
if (!_sd) _sd = new (std::nothrow) OtaSdFs();
if (!_file) _file = new (std::nothrow) OtaSdFile();
if (!_sd || !_file) {
fail("SD allocation failed"); releaseBusClaim(); return false;
}
#endif
return true;
}
bool OtaStoreSdNrf52::busAccessAllowed() const {
#if defined(OTA_RAK15002_SD_STORE)
return _bus_owned && rak4631_spi_owner() == RakSharedSpiOwner::Sd
&& ota_rak15002_sd_card_present();
#else
return true;
#endif
}
void OtaStoreSdNrf52::releaseBusClaim() {
#if defined(OTA_RAK15002_SD_STORE)
if (!_bus_owned) return;
if (_bus_started) {
ota_sd_spi_bus().end();
// Leave the powered, unswitched card deselected, and stop driving the
// shared clock/data pads before another peripheral claims them.
digitalWrite(OTA_SD_CS_PIN, HIGH);
pinMode(OTA_SD_CS_PIN, OUTPUT);
pinMode(3, INPUT); pinMode(29, INPUT); pinMode(30, INPUT);
_bus_started = false;
}
rak4631_release_spi(RakSharedSpiOwner::Sd);
_bus_owned = false;
#endif
}
bool OtaStoreSdNrf52::releaseBus() {
if (_total) { fail("SD staged update is pinned"); return false; }
resetStoreState();
releaseBusClaim();
return true;
}
void OtaStoreSdNrf52::fail(const char* message) {
strncpy(_error, message ? message : "SD error", sizeof(_error) - 1);
_error[sizeof(_error) - 1] = 0;
}
void OtaStoreSdNrf52::resetStoreState() {
_total = 0;
_first_sector = 0;
_allocated_sectors = 0;
_planned_bootloader = false;
#if defined(OTA_RAK15002_SD_STORE)
// Ownership persists if a card is physically removed. Cleanup still owns
// the controller and can release it, but no other device may steal it.
const bool access = _bus_owned && rak4631_spi_owner() == RakSharedSpiOwner::Sd;
#else
const bool access = true;
#endif
if (access && _file && *_file) _file->close();
if (access && _sd) _sd->end();
_mounted = false;
}
bool OtaStoreSdNrf52::beginCardOnly() {
_error[0] = 0;
if (!acquireBus()) return false;
resetStoreState();
ota_sd_prepare_chip_select();
#if defined(OTA_RAK15002_SD_STORE)
_bus_started = true;
#endif
if (!_sd || !_file ||
!_sd->cardBegin(SdSpiConfig(OTA_SD_CS_PIN, DEDICATED_SPI,
SD_SCK_MHZ(OTA_SD_SCK_MHZ), &ota_sd_spi_bus()))) {
fail("SD card init failed");
resetStoreState(); releaseBusClaim();
return false;
}
return true;
}
bool OtaStoreSdNrf52::mount() {
if (_mounted) return busAccessAllowed();
_error[0] = 0;
if (!acquireBus()) return false;
ota_sd_prepare_chip_select();
#if defined(OTA_RAK15002_SD_STORE)
_bus_started = true;
#endif
if (!_sd || !_file || !_sd->begin(SdSpiConfig(OTA_SD_CS_PIN, DEDICATED_SPI,
SD_SCK_MHZ(OTA_SD_SCK_MHZ), &ota_sd_spi_bus()))) {
fail("SD mount failed");
resetStoreState(); releaseBusClaim();
return false;
}
_mounted = true;
return true;
}
bool OtaStoreSdNrf52::formatCard(MainBoard& board) {
clear_sd_auth_record();
if (!beginCardOnly()) return false;
board.serviceWatchdog();
if (!_sd->format()) {
fail("SD format failed");
_sd->end();
releaseBusClaim();
return false;
}
board.serviceWatchdog();
if (!_sd->volumeBegin()) {
fail("SD remount after format failed");
_sd->end();
releaseBusClaim();
return false;
}
_mounted = true;
board.serviceWatchdog();
return true;
}
bool OtaStoreSdNrf52::eraseCard(MainBoard& board) {
clear_sd_auth_record();
if (!beginCardOnly()) return false;
SdCard* card = _sd->card();
const uint32_t sector_count = card ? card->sectorCount() : 0;
if (!card || sector_count == 0) {
fail("SD sector count failed");
_sd->end();
releaseBusClaim();
return false;
}
// Match SdFat's formatter example: bounded erase ranges avoid cards that
// reject or time out on a single whole-device erase command. Servicing the
// board watchdog between ranges keeps the intentionally long operation from
// looking like a wedged main loop.
const uint32_t erase_sectors = 262144UL;
uint32_t first_sector = 0;
while (first_sector < sector_count) {
const uint32_t remaining = sector_count - first_sector;
const uint32_t count = remaining < erase_sectors ? remaining : erase_sectors;
const uint32_t last_sector = first_sector + count - 1;
board.serviceWatchdog();
if (!card->erase(first_sector, last_sector)) {
fail("SD erase failed");
_sd->end();
releaseBusClaim();
return false;
}
first_sector += count;
}
board.serviceWatchdog();
if (!card->syncDevice()) {
fail("SD erase sync failed");
_sd->end();
releaseBusClaim();
return false;
}
_sd->end();
releaseBusClaim();
return true;
}
bool OtaStoreSdNrf52::getSpace(MainBoard& board, uint64_t& used_bytes,
uint64_t& free_bytes) {
used_bytes = 0;
free_bytes = 0;
_error[0] = 0;
if (!mount()) return false;
board.serviceWatchdog();
const uint32_t cluster_count = _sd->clusterCount();
const uint32_t free_clusters = _sd->freeClusterCount();
const uint32_t bytes_per_cluster = _sd->bytesPerCluster();
board.serviceWatchdog();
if (cluster_count == 0 || bytes_per_cluster == 0 ||
free_clusters > cluster_count) {
fail("SD space query failed");
return false;
}
free_bytes = (uint64_t)free_clusters * bytes_per_cluster;
used_bytes = (uint64_t)(cluster_count - free_clusters) * bytes_per_cluster;
return true;
}
namespace {
static const uint16_t SD_LIST_PAGE_SIZE = 2;
static const size_t SD_LIST_NAME_CAP = 256; // FAT/exFAT long-name maximum plus NUL
static const size_t SD_LIST_SHOWN_PATH = 50; // bounded CLI line; long paths get an explicit leading "..."
struct SdListState {
SdListState(MainBoard& b, uint32_t f) : board(b), first(f) {}
MainBoard& board;
uint32_t first;
uint32_t total = 0;
char body[132] = {0};
size_t used = 0;
char* path = nullptr;
size_t path_len = 0;
size_t path_cap = 0;
bool failed = false;
};
static void compact_file_size(char* out, size_t cap, uint64_t bytes) {
static const char units[] = {'B', 'K', 'M', 'G', 'T'};
uint8_t unit = 0;
uint64_t whole = bytes;
while (whole >= 1024 && unit < sizeof(units) - 1) { whole /= 1024; unit++; }
snprintf(out, cap, "%lu%c", (unsigned long)whole, units[unit]);
}
static bool ensure_list_path(SdListState& state, size_t need) {
if (need <= state.path_cap) return true;
size_t cap = state.path_cap ? state.path_cap : 256;
while (cap < need) {
if (cap > SIZE_MAX / 2) { state.failed = true; return false; }
cap *= 2;
}
char* grown = static_cast<char*>(realloc(state.path, cap));
if (!grown) { state.failed = true; return false; }
state.path = grown;
state.path_cap = cap;
return true;
}
static bool append_sd_list_entry(SdListState& state, const char* path, uint64_t bytes) {
char size[12];
compact_file_size(size, sizeof(size), bytes);
const size_t path_len = strlen(path);
const char* shown = path;
char shortened[SD_LIST_SHOWN_PATH + 1];
if (path_len > SD_LIST_SHOWN_PATH) {
memcpy(shortened, "...", 3);
memcpy(shortened + 3, path + path_len - (SD_LIST_SHOWN_PATH - 3), SD_LIST_SHOWN_PATH - 3);
shortened[SD_LIST_SHOWN_PATH] = 0;
shown = shortened;
}
size_t remain = sizeof(state.body) - state.used;
int n = snprintf(state.body + state.used, remain, "\n %s %s", shown, size);
if (n <= 0 || (size_t)n >= remain) { state.failed = true; return false; }
state.used += (size_t)n;
return true;
}
static bool scan_sd_files(OtaSdFile& dir, SdListState& state) {
OtaSdFile file;
while (file.openNext(&dir, O_RDONLY)) {
state.board.serviceWatchdog();
const size_t parent_len = state.path_len;
if (parent_len > SIZE_MAX - SD_LIST_NAME_CAP - 2 ||
!ensure_list_path(state, parent_len + SD_LIST_NAME_CAP + 2)) {
file.close();
return false;
}
size_t name_at = parent_len;
if (name_at == 0 || state.path[name_at - 1] != '/') state.path[name_at++] = '/';
state.path[name_at] = 0;
file.getName(state.path + name_at, SD_LIST_NAME_CAP);
size_t name_len = strlen(state.path + name_at);
if (name_len == 0) { state.failed = true; file.close(); return false; }
state.path_len = name_at + name_len;
if (file.isDir()) {
if (!scan_sd_files(file, state)) {
state.path_len = parent_len;
state.path[parent_len] = 0;
file.close();
return false;
}
} else {
uint32_t index = state.total++;
if (index >= state.first && index < state.first + SD_LIST_PAGE_SIZE) {
if (!append_sd_list_entry(state, state.path, file.fileSize())) {
state.path_len = parent_len;
state.path[parent_len] = 0;
file.close();
return false;
}
}
}
state.path_len = parent_len;
state.path[parent_len] = 0;
file.close();
}
return true;
}
} // namespace
bool OtaStoreSdNrf52::listFiles(MainBoard& board, uint16_t page,
char* reply, size_t cap) {
if (!reply || cap == 0) return false;
if (page == 0) page = 1;
_error[0] = 0;
if (!mount()) return false;
OtaSdFile root = _sd->open("/", O_RDONLY);
if (!root || !root.isDir()) { fail("SD root directory open failed"); return false; }
SdListState state(board, (uint32_t)(page - 1) * SD_LIST_PAGE_SIZE);
bool scanned = ensure_list_path(state, 2) && scan_sd_files(root, state);
root.close();
free(state.path);
if (!scanned || state.failed) { fail("SD file listing failed"); return false; }
uint32_t pages = (state.total + SD_LIST_PAGE_SIZE - 1) / SD_LIST_PAGE_SIZE;
if (pages == 0) {
snprintf(reply, cap, "> SD files: empty");
} else if (page > pages) {
snprintf(reply, cap, "Error: SD file page %u out of range (1-%lu)",
(unsigned)page, (unsigned long)pages);
} else {
snprintf(reply, cap, "> SD files %u/%lu (%lu):%s",
(unsigned)page, (unsigned long)pages,
(unsigned long)state.total, state.body);
}
return true;
}
uint32_t OtaStoreSdNrf52::capacity() const {
if (!busAccessAllowed() || !_mounted || !_sd || !_sd->card()) return 0;
uint64_t bytes = (uint64_t)_sd->card()->sectorCount() * MOTA_SD_AUTH_SECTOR_SIZE;
return bytes > UINT32_MAX ? UINT32_MAX : (uint32_t)bytes;
}
#if defined(OTA_SEEDER_ONLY)
// The source-only context uses OtaStoreRam<1> for install fetches and this
// object only owns the FAT volume shared with OtaCacheSdNrf52. Its archive
// has its own file/read/write methods. Do not retain unreachable installer
// vtable code (or permit accidental staging/approval) in a Companion image.
bool OtaStoreSdNrf52::plan_layout(bool, uint32_t, uint32_t, uint32_t, bool) {
fail("source-only SD archive cannot stage/apply"); return false;
}
bool OtaStoreSdNrf52::begin(uint32_t) {
fail("source-only SD archive cannot stage/apply"); return false;
}
bool OtaStoreSdNrf52::set_meta_size(uint32_t) { return false; }
bool OtaStoreSdNrf52::write(uint32_t, const uint8_t*, uint32_t) { return false; }
bool OtaStoreSdNrf52::read(uint32_t, uint8_t*, uint32_t) const { return false; }
bool OtaStoreSdNrf52::finalize() { return false; }
void OtaStoreSdNrf52::checkpoint() {}
bool OtaStoreSdNrf52::reopen() { return false; }
bool OtaStoreSdNrf52::discard() { return false; }
void OtaStoreSdNrf52::clear() {}
bool OtaStoreSdNrf52::approve_for_bootloader(const uint8_t[32], const uint8_t[32]) {
fail("source-only SD archive cannot stage/apply"); return false;
}
#else
bool OtaStoreSdNrf52::locate_file() {
if (!busAccessAllowed()) return false;
Sector_t first = 0, last = 0;
if (!_file || !_file->contiguousRange(&first, &last) || last < first) {
fail("OTA file is not contiguous");
return false;
}
uint64_t need = ((uint64_t)_total + MOTA_SD_AUTH_SECTOR_SIZE - 1) /
MOTA_SD_AUTH_SECTOR_SIZE;
uint64_t available = (uint64_t)last - first + 1;
const uint32_t card_sectors = _sd->card() ? _sd->card()->sectorCount() : 0;
if (need == 0 || need > available || first == 0 || card_sectors == 0 ||
first >= card_sectors || need > card_sectors - first) {
fail("OTA file sector range is invalid");
return false;
}
_first_sector = (uint32_t)first;
_allocated_sectors = (uint32_t)need;
return true;
}
bool OtaStoreSdNrf52::plan_layout(bool, uint32_t image_size,
uint32_t, uint32_t payload_size,
bool is_bootloader) {
_planned_bootloader = false;
if (is_bootloader) {
#if defined(OTA_SD_BOOTLOADER_UPDATE)
if (ota_bootloader_image_geometry_valid(image_size, payload_size)) {
_planned_bootloader = true;
return true;
}
fail("bootloader package geometry mismatch");
#else
fail("bootloader update is not enabled for this SD target");
#endif
return false;
}
const uint32_t app_base = mota_nrf52_app_base();
if (image_size == 0 || payload_size == 0 || app_base >= MOTA_NRF52_APP_END ||
image_size > MOTA_NRF52_APP_END - app_base) {
fail("image exceeds nRF52 application region");
return false;
}
return true;
}
bool OtaStoreSdNrf52::begin(uint32_t total_size) {
_total = 0;
_first_sector = 0;
_allocated_sectors = 0;
clear_sd_auth_record();
if (total_size < 13 || !mount()) return false;
if (*_file) _file->close();
_sd->remove(PATH);
if (!_file->open(PATH, O_RDWR | O_CREAT | O_EXCL)) {
fail("SD OTA file create failed");
return false;
}
if (!_file->preAllocate(total_size)) {
fail("SD lacks contiguous space for update");
_file->close();
_sd->remove(PATH);
return false;
}
_total = total_size;
if (!locate_file()) {
_total = 0;
_file->close();
_sd->remove(PATH);
return false;
}
return true;
}
bool OtaStoreSdNrf52::set_meta_size(uint32_t meta_bytes) {
if (!_total || meta_bytes > _total - 5) return false;
uint8_t erased[512];
memset(erased, 0xFF, sizeof(erased));
for (uint32_t off = 0; off < meta_bytes; ) {
uint32_t n = meta_bytes - off;
if (n > sizeof(erased)) n = sizeof(erased);
if (!write(off, erased, n)) return false;
off += n;
}
return true;
}
bool OtaStoreSdNrf52::write(uint32_t offset, const uint8_t* data, uint32_t len) {
if (!busAccessAllowed() || !_file || !*_file || (uint64_t)offset + len > _total || !_file->seekSet(offset) ||
_file->write(data, len) != len) {
fail("SD OTA write failed");
return false;
}
return true;
}
bool OtaStoreSdNrf52::read(uint32_t offset, uint8_t* buf, uint32_t len) const {
if (!busAccessAllowed() || !_file || !*_file || (uint64_t)offset + len > _total || !_file->seekSet(offset) ||
_file->read(buf, len) != (int)len) {
return false;
}
return true;
}
bool OtaStoreSdNrf52::finalize() {
if (!busAccessAllowed()) return false;
SdCard* const card = _sd ? _sd->card() : nullptr;
if (!_file || !*_file || !card || !_file->sync() || !card->syncDevice()) {
fail("SD OTA sync failed");
return false;
}
return locate_file();
}
void OtaStoreSdNrf52::checkpoint() {
if (busAccessAllowed() && _file && *_file) {
_file->sync();
if (_sd->card()) _sd->card()->syncDevice();
}
}
bool OtaStoreSdNrf52::reopen() {
_total = 0;
_planned_bootloader = false;
if (!mount()) return false;
if (*_file) _file->close();
if (!_file->open(PATH, O_RDWR)) return false;
uint8_t hdr[8];
if (_file->fileSize() < 13 || !_file->seekSet(0) ||
_file->read(hdr, sizeof(hdr)) != (int)sizeof(hdr) ||
memcmp(hdr, MOTA_MAGIC, 4) != 0) {
_file->close();
return false;
}
uint32_t total = rd_u32le(hdr + 4);
if (total < 13 || total != _file->fileSize()) {
_file->close();
return false;
}
_total = total;
uint8_t manifest[MOTA_MFL];
MotaManifest parsed;
if (total < 8u + sizeof(manifest) + 5u || !_file->seekSet(8u) ||
_file->read(manifest, sizeof(manifest)) != (int)sizeof(manifest) ||
!mota_parse_manifest(manifest, sizeof(manifest), parsed)) {
_total = 0;
_file->close();
return false;
}
if (parsed.is_bootloader()) {
#if defined(OTA_SD_BOOTLOADER_UPDATE)
const uint64_t payload_off = 8u + sizeof(manifest) +
(uint64_t)parsed.block_count * 4u;
if (!ota_bootloader_image_geometry_valid(parsed.image_size, parsed.payload_size) ||
payload_off + parsed.payload_size + 5u != total) {
_total = 0;
_file->close();
return false;
}
_planned_bootloader = true;
#else
_total = 0;
_file->close();
return false;
#endif
}
if (!locate_file()) {
_total = 0;
_file->close();
return false;
}
return true;
}
bool OtaStoreSdNrf52::discard() {
_total = 0;
_planned_bootloader = false;
_first_sector = 0;
_allocated_sectors = 0;
clear_sd_auth_record();
if (!mount()) return false;
if (_file && *_file) _file->close();
if (_sd->exists(PATH) && !_sd->remove(PATH)) {
fail("SD OTA file removal failed");
return false;
}
if (_sd->exists(PATH)) {
fail("SD OTA file still exists after removal");
return false;
}
SdCard* const card = _sd->card();
if (card && !card->syncDevice()) {
fail("SD OTA removal sync failed");
return false;
}
return true;
}
void OtaStoreSdNrf52::clear() { (void)discard(); }
bool OtaStoreSdNrf52::approve_for_bootloader(
const uint8_t expected_boot_image_hash[32],
const uint8_t authorized_container_hash[32]) {
if (!_total || !finalize()) return false;
if (!authorized_container_hash) {
fail("missing authenticated SD container hash");
return false;
}
SdCard* const card = _sd ? _sd->card() : nullptr;
const uint32_t card_sectors = card ? card->sectorCount() : 0;
#if defined(OTA_SD_BOOTLOADER_UPDATE)
if (_planned_bootloader) {
SelfFwInfo fi;
if (!expected_boot_image_hash ||
_total != MOTA_NRF52_BOOT_CONTAINER_SIZE ||
!ota_self_firmware(fi) ||
!ota_bootloader_scratch_headroom_valid(
fi.valid, mota_nrf52_app_base(), fi.image_len,
OTA_BOOT_SCRATCH_START) ||
!ota_bootloader_live_bank_preserves_scratch(
mota_nrf52_app_base(), fi.image_len, OTA_BOOT_SCRATCH_START)) {
fail("running firmware/settings do not preserve boot scratch");
return false;
}
}
#endif
if (!write(8 + MOTA_OFF_APPROVAL, APPROVAL_YES, sizeof(APPROVAL_YES)) ||
!_file->sync() || !card || !card->syncDevice()) {
fail("SD approval write failed");
return false;
}
#if defined(OTA_SD_BOOTLOADER_UPDATE)
if (_planned_bootloader) {
uint8_t token[MOTA_SD_BOOT_TOKEN_LEN];
mota_sd_boot_token_encode(token, _total, expected_boot_image_hash);
// This page is outside the hash-valid live app and below InternalFS. The
// token is temporary: OTAFIX validates it against the current SD bytes,
// then consumes the page as the first bootloader scratch page.
flash_nrf5x_flush();
if (!flash_nrf5x_erase(OTA_BOOT_SCRATCH_START) ||
flash_nrf5x_write(OTA_BOOT_SCRATCH_START, token, sizeof(token)) < 0) {
fail("SD bootloader token flash write failed");
return false;
}
flash_nrf5x_flush();
if (memcmp((const void*)(uintptr_t)OTA_BOOT_SCRATCH_START,
token, sizeof(token)) != 0) {
fail("SD bootloader token flash verify failed");
return false;
}
}
#endif
uint8_t auth[MOTA_SD_AUTH_LEN];
const uint8_t purpose = _planned_bootloader
? MOTA_SD_AUTH_PURPOSE_BOOTLOADER : MOTA_SD_AUTH_PURPOSE_APP;
const uint8_t format = _planned_bootloader ? MOTA_BOOT_FORMAT_VER : MOTA_APP_FORMAT_VER;
if (!mota_sd_auth_encode(auth, purpose, format, _first_sector,
_allocated_sectors, _total, card_sectors,
authorized_container_hash) ||
!publish_sd_auth_record(auth)) {
clear_sd_auth_record();
fail("SD retained authorization publish failed");
return false;
}
return true;
}
#endif // OTA_SEEDER_ONLY
} // namespace ota
} // namespace mesh
#endif