diff --git a/build/gcc-tools.mk b/build/gcc-tools.mk index d67f64eb1a..4d4e03129c 100644 --- a/build/gcc-tools.mk +++ b/build/gcc-tools.mk @@ -14,7 +14,7 @@ endif # C compiler flags CFLAGS += -g3 -m64 -O$(GCC_OPTIMIZE) -gdwarf-2 -CFLAGS += -Wno-unused-local-typedefs -Wno-pragmas +CFLAGS += -Wno-unused-local-typedefs -Wno-pragmas -Wno-overloaded-virtual ASFLAGS += -g3 diff --git a/proto_defs/shared b/proto_defs/shared index e17a68ad87..abd3f527b6 160000 --- a/proto_defs/shared +++ b/proto_defs/shared @@ -1 +1 @@ -Subproject commit e17a68ad87ebb916d6a254c3afb7f2f288bf0015 +Subproject commit abd3f527b6e968d02a3aecf242f55c06eecee137 diff --git a/proto_defs/src/cloud/describe.pb.h b/proto_defs/src/cloud/describe.pb.h index ca5eb833b6..1e6ea2e361 100644 --- a/proto_defs/src/cloud/describe.pb.h +++ b/proto_defs/src/cloud/describe.pb.h @@ -45,11 +45,6 @@ typedef enum _particle_cloud_FirmwareModuleValidityFlag { } particle_cloud_FirmwareModuleValidityFlag; /* Struct definitions */ -typedef struct _particle_cloud_FirmwareModuleAsset { - pb_callback_t hash; /* /< SHA-256 hash */ - pb_callback_t name; /* /< Asset name */ -} particle_cloud_FirmwareModuleAsset; - /* * System describe. */ typedef struct _particle_cloud_SystemDescribe { @@ -73,8 +68,16 @@ typedef struct _particle_cloud_FirmwareModule { pb_callback_t hash; /* /< SHA-256 hash */ pb_callback_t dependencies; /* /< Module dependencies */ pb_callback_t asset_dependencies; /* /< Asset dependencies */ + uint32_t size; /* /< Actual module size */ } particle_cloud_FirmwareModule; +typedef struct _particle_cloud_FirmwareModuleAsset { + pb_callback_t hash; /* /< SHA-256 hash */ + pb_callback_t name; /* /< Asset name */ + uint32_t size; /* /< Asset size */ + uint32_t storage_size; /* /< Asset storage size (taking into account compression and metadata) */ +} particle_cloud_FirmwareModuleAsset; + /* * Firmware module dependency. */ typedef struct _particle_cloud_FirmwareModuleDependency { @@ -104,17 +107,15 @@ extern "C" { /* Initializer values for message structs */ #define particle_cloud_FirmwareModuleDependency_init_default {_particle_cloud_FirmwareModuleType_MIN, 0, 0} -#define particle_cloud_FirmwareModuleAsset_init_default {{{NULL}, NULL}, {{NULL}, NULL}} -#define particle_cloud_FirmwareModule_init_default {_particle_cloud_FirmwareModuleType_MIN, 0, 0, _particle_cloud_FirmwareModuleStore_MIN, 0, 0, 0, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} +#define particle_cloud_FirmwareModuleAsset_init_default {{{NULL}, NULL}, {{NULL}, NULL}, 0, 0} +#define particle_cloud_FirmwareModule_init_default {_particle_cloud_FirmwareModuleType_MIN, 0, 0, _particle_cloud_FirmwareModuleStore_MIN, 0, 0, 0, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0} #define particle_cloud_SystemDescribe_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} #define particle_cloud_FirmwareModuleDependency_init_zero {_particle_cloud_FirmwareModuleType_MIN, 0, 0} -#define particle_cloud_FirmwareModuleAsset_init_zero {{{NULL}, NULL}, {{NULL}, NULL}} -#define particle_cloud_FirmwareModule_init_zero {_particle_cloud_FirmwareModuleType_MIN, 0, 0, _particle_cloud_FirmwareModuleStore_MIN, 0, 0, 0, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} +#define particle_cloud_FirmwareModuleAsset_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, 0, 0} +#define particle_cloud_FirmwareModule_init_zero {_particle_cloud_FirmwareModuleType_MIN, 0, 0, _particle_cloud_FirmwareModuleStore_MIN, 0, 0, 0, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0} #define particle_cloud_SystemDescribe_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}} /* Field tags (for use in manual encoding/decoding) */ -#define particle_cloud_FirmwareModuleAsset_hash_tag 1 -#define particle_cloud_FirmwareModuleAsset_name_tag 2 #define particle_cloud_SystemDescribe_firmware_modules_tag 1 #define particle_cloud_SystemDescribe_imei_tag 2 #define particle_cloud_SystemDescribe_iccid_tag 3 @@ -130,6 +131,11 @@ extern "C" { #define particle_cloud_FirmwareModule_hash_tag 8 #define particle_cloud_FirmwareModule_dependencies_tag 9 #define particle_cloud_FirmwareModule_asset_dependencies_tag 10 +#define particle_cloud_FirmwareModule_size_tag 11 +#define particle_cloud_FirmwareModuleAsset_hash_tag 1 +#define particle_cloud_FirmwareModuleAsset_name_tag 2 +#define particle_cloud_FirmwareModuleAsset_size_tag 3 +#define particle_cloud_FirmwareModuleAsset_storage_size_tag 4 #define particle_cloud_FirmwareModuleDependency_type_tag 1 #define particle_cloud_FirmwareModuleDependency_index_tag 2 #define particle_cloud_FirmwareModuleDependency_version_tag 3 @@ -144,7 +150,9 @@ X(a, STATIC, SINGULAR, UINT32, version, 3) #define particle_cloud_FirmwareModuleAsset_FIELDLIST(X, a) \ X(a, CALLBACK, SINGULAR, BYTES, hash, 1) \ -X(a, CALLBACK, SINGULAR, STRING, name, 2) +X(a, CALLBACK, SINGULAR, STRING, name, 2) \ +X(a, STATIC, SINGULAR, UINT32, size, 3) \ +X(a, STATIC, SINGULAR, UINT32, storage_size, 4) #define particle_cloud_FirmwareModuleAsset_CALLBACK pb_default_field_callback #define particle_cloud_FirmwareModuleAsset_DEFAULT NULL @@ -158,7 +166,8 @@ X(a, STATIC, SINGULAR, FIXED32, checked_flags, 6) \ X(a, STATIC, SINGULAR, FIXED32, passed_flags, 7) \ X(a, CALLBACK, OPTIONAL, BYTES, hash, 8) \ X(a, CALLBACK, REPEATED, MESSAGE, dependencies, 9) \ -X(a, CALLBACK, REPEATED, MESSAGE, asset_dependencies, 10) +X(a, CALLBACK, REPEATED, MESSAGE, asset_dependencies, 10) \ +X(a, STATIC, SINGULAR, UINT32, size, 11) #define particle_cloud_FirmwareModule_CALLBACK pb_default_field_callback #define particle_cloud_FirmwareModule_DEFAULT NULL #define particle_cloud_FirmwareModule_dependencies_MSGTYPE particle_cloud_FirmwareModuleDependency diff --git a/proto_defs/src/control/storage.pb.c b/proto_defs/src/control/storage.pb.c index cf96996e8a..e956424ef4 100644 --- a/proto_defs/src/control/storage.pb.c +++ b/proto_defs/src/control/storage.pb.c @@ -81,6 +81,12 @@ PB_BIND(particle_ctrl_GetModuleInfoReply_Dependency, particle_ctrl_GetModuleInfo PB_BIND(particle_ctrl_GetModuleInfoReply_Module, particle_ctrl_GetModuleInfoReply_Module, AUTO) +PB_BIND(particle_ctrl_GetAssetInfoRequest, particle_ctrl_GetAssetInfoRequest, AUTO) + + +PB_BIND(particle_ctrl_GetAssetInfoReply, particle_ctrl_GetAssetInfoReply, AUTO) + + diff --git a/proto_defs/src/control/storage.pb.h b/proto_defs/src/control/storage.pb.h index d7c0664ba9..a3d53c346b 100644 --- a/proto_defs/src/control/storage.pb.h +++ b/proto_defs/src/control/storage.pb.h @@ -5,6 +5,7 @@ #define PB_PARTICLE_CTRL_CONTROL_STORAGE_PB_H_INCLUDED #include #include "control/extensions.pb.h" +#include "cloud/describe.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -93,7 +94,17 @@ typedef struct _particle_ctrl_FirmwareUpdateDataRequest { pb_callback_t data; } particle_ctrl_FirmwareUpdateDataRequest; +typedef struct _particle_ctrl_GetAssetInfoReply { + pb_callback_t available; + pb_callback_t required; +} particle_ctrl_GetAssetInfoReply; + +typedef struct _particle_ctrl_GetAssetInfoRequest { + char dummy_field; +} particle_ctrl_GetAssetInfoRequest; + typedef struct _particle_ctrl_GetModuleInfoReply { + pb_callback_t modules_deprecated; pb_callback_t modules; } particle_ctrl_GetModuleInfoReply; @@ -145,13 +156,16 @@ typedef struct _particle_ctrl_GetModuleInfoReply_Module { pb_callback_t dependencies; } particle_ctrl_GetModuleInfoReply_Module; +/* * + Get asset info. */ typedef struct _particle_ctrl_GetSectionDataSizeReply { uint32_t size; } particle_ctrl_GetSectionDataSizeReply; typedef struct _particle_ctrl_GetSectionDataSizeRequest { uint32_t storage; /* Firmware modules */ - uint32_t section; + /* TODO: move these messages to common place */ + uint32_t section; /* /< Firmware modules */ } particle_ctrl_GetSectionDataSizeRequest; typedef struct _particle_ctrl_ReadSectionDataRequest { @@ -239,9 +253,11 @@ extern "C" { #define particle_ctrl_GetSectionDataSizeRequest_init_default {0, 0} #define particle_ctrl_GetSectionDataSizeReply_init_default {0} #define particle_ctrl_GetModuleInfoRequest_init_default {0} -#define particle_ctrl_GetModuleInfoReply_init_default {{{NULL}, NULL}} +#define particle_ctrl_GetModuleInfoReply_init_default {{{NULL}, NULL}, {{NULL}, NULL}} #define particle_ctrl_GetModuleInfoReply_Dependency_init_default {_particle_ctrl_FirmwareModuleType_MIN, 0, 0} #define particle_ctrl_GetModuleInfoReply_Module_init_default {_particle_ctrl_FirmwareModuleType_MIN, 0, 0, 0, 0, {{NULL}, NULL}} +#define particle_ctrl_GetAssetInfoRequest_init_default {0} +#define particle_ctrl_GetAssetInfoReply_init_default {{{NULL}, NULL}, {{NULL}, NULL}} #define particle_ctrl_StartFirmwareUpdateRequest_init_zero {0, _particle_ctrl_FileFormat_MIN} #define particle_ctrl_StartFirmwareUpdateReply_init_zero {0} #define particle_ctrl_FinishFirmwareUpdateRequest_init_zero {0} @@ -264,14 +280,19 @@ extern "C" { #define particle_ctrl_GetSectionDataSizeRequest_init_zero {0, 0} #define particle_ctrl_GetSectionDataSizeReply_init_zero {0} #define particle_ctrl_GetModuleInfoRequest_init_zero {0} -#define particle_ctrl_GetModuleInfoReply_init_zero {{{NULL}, NULL}} +#define particle_ctrl_GetModuleInfoReply_init_zero {{{NULL}, NULL}, {{NULL}, NULL}} #define particle_ctrl_GetModuleInfoReply_Dependency_init_zero {_particle_ctrl_FirmwareModuleType_MIN, 0, 0} #define particle_ctrl_GetModuleInfoReply_Module_init_zero {_particle_ctrl_FirmwareModuleType_MIN, 0, 0, 0, 0, {{NULL}, NULL}} +#define particle_ctrl_GetAssetInfoRequest_init_zero {0} +#define particle_ctrl_GetAssetInfoReply_init_zero {{{NULL}, NULL}, {{NULL}, NULL}} /* Field tags (for use in manual encoding/decoding) */ #define particle_ctrl_DescribeStorageReply_storage_tag 1 #define particle_ctrl_FirmwareUpdateDataRequest_data_tag 1 -#define particle_ctrl_GetModuleInfoReply_modules_tag 1 +#define particle_ctrl_GetAssetInfoReply_available_tag 1 +#define particle_ctrl_GetAssetInfoReply_required_tag 2 +#define particle_ctrl_GetModuleInfoReply_modules_deprecated_tag 1 +#define particle_ctrl_GetModuleInfoReply_modules_tag 2 #define particle_ctrl_ReadSectionDataReply_data_tag 1 #define particle_ctrl_ClearSectionDataRequest_storage_tag 1 #define particle_ctrl_ClearSectionDataRequest_section_tag 2 @@ -439,10 +460,12 @@ X(a, STATIC, SINGULAR, UINT32, size, 1) #define particle_ctrl_GetModuleInfoRequest_DEFAULT NULL #define particle_ctrl_GetModuleInfoReply_FIELDLIST(X, a) \ -X(a, CALLBACK, REPEATED, MESSAGE, modules, 1) +X(a, CALLBACK, REPEATED, MESSAGE, modules_deprecated, 1) \ +X(a, CALLBACK, REPEATED, MESSAGE, modules, 2) #define particle_ctrl_GetModuleInfoReply_CALLBACK pb_default_field_callback #define particle_ctrl_GetModuleInfoReply_DEFAULT NULL -#define particle_ctrl_GetModuleInfoReply_modules_MSGTYPE particle_ctrl_GetModuleInfoReply_Module +#define particle_ctrl_GetModuleInfoReply_modules_deprecated_MSGTYPE particle_ctrl_GetModuleInfoReply_Module +#define particle_ctrl_GetModuleInfoReply_modules_MSGTYPE particle_cloud_FirmwareModule #define particle_ctrl_GetModuleInfoReply_Dependency_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UENUM, type, 1) \ @@ -462,6 +485,19 @@ X(a, CALLBACK, REPEATED, MESSAGE, dependencies, 6) #define particle_ctrl_GetModuleInfoReply_Module_DEFAULT NULL #define particle_ctrl_GetModuleInfoReply_Module_dependencies_MSGTYPE particle_ctrl_GetModuleInfoReply_Dependency +#define particle_ctrl_GetAssetInfoRequest_FIELDLIST(X, a) \ + +#define particle_ctrl_GetAssetInfoRequest_CALLBACK NULL +#define particle_ctrl_GetAssetInfoRequest_DEFAULT NULL + +#define particle_ctrl_GetAssetInfoReply_FIELDLIST(X, a) \ +X(a, CALLBACK, REPEATED, MESSAGE, available, 1) \ +X(a, CALLBACK, REPEATED, MESSAGE, required, 2) +#define particle_ctrl_GetAssetInfoReply_CALLBACK pb_default_field_callback +#define particle_ctrl_GetAssetInfoReply_DEFAULT NULL +#define particle_ctrl_GetAssetInfoReply_available_MSGTYPE particle_cloud_FirmwareModuleAsset +#define particle_ctrl_GetAssetInfoReply_required_MSGTYPE particle_cloud_FirmwareModuleAsset + extern const pb_msgdesc_t particle_ctrl_StartFirmwareUpdateRequest_msg; extern const pb_msgdesc_t particle_ctrl_StartFirmwareUpdateReply_msg; extern const pb_msgdesc_t particle_ctrl_FinishFirmwareUpdateRequest_msg; @@ -487,6 +523,8 @@ extern const pb_msgdesc_t particle_ctrl_GetModuleInfoRequest_msg; extern const pb_msgdesc_t particle_ctrl_GetModuleInfoReply_msg; extern const pb_msgdesc_t particle_ctrl_GetModuleInfoReply_Dependency_msg; extern const pb_msgdesc_t particle_ctrl_GetModuleInfoReply_Module_msg; +extern const pb_msgdesc_t particle_ctrl_GetAssetInfoRequest_msg; +extern const pb_msgdesc_t particle_ctrl_GetAssetInfoReply_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define particle_ctrl_StartFirmwareUpdateRequest_fields &particle_ctrl_StartFirmwareUpdateRequest_msg @@ -514,6 +552,8 @@ extern const pb_msgdesc_t particle_ctrl_GetModuleInfoReply_Module_msg; #define particle_ctrl_GetModuleInfoReply_fields &particle_ctrl_GetModuleInfoReply_msg #define particle_ctrl_GetModuleInfoReply_Dependency_fields &particle_ctrl_GetModuleInfoReply_Dependency_msg #define particle_ctrl_GetModuleInfoReply_Module_fields &particle_ctrl_GetModuleInfoReply_Module_msg +#define particle_ctrl_GetAssetInfoRequest_fields &particle_ctrl_GetAssetInfoRequest_msg +#define particle_ctrl_GetAssetInfoReply_fields &particle_ctrl_GetAssetInfoReply_msg /* Maximum encoded size of messages (where known) */ /* particle_ctrl_FirmwareUpdateDataRequest_size depends on runtime parameters */ @@ -523,6 +563,7 @@ extern const pb_msgdesc_t particle_ctrl_GetModuleInfoReply_Module_msg; /* particle_ctrl_WriteSectionDataRequest_size depends on runtime parameters */ /* particle_ctrl_GetModuleInfoReply_size depends on runtime parameters */ /* particle_ctrl_GetModuleInfoReply_Module_size depends on runtime parameters */ +/* particle_ctrl_GetAssetInfoReply_size depends on runtime parameters */ #define particle_ctrl_CancelFirmwareUpdateReply_size 0 #define particle_ctrl_CancelFirmwareUpdateRequest_size 0 #define particle_ctrl_ClearSectionDataReply_size 0 @@ -533,6 +574,7 @@ extern const pb_msgdesc_t particle_ctrl_GetModuleInfoReply_Module_msg; #define particle_ctrl_FinishFirmwareUpdateReply_size 0 #define particle_ctrl_FinishFirmwareUpdateRequest_size 2 #define particle_ctrl_FirmwareUpdateDataReply_size 0 +#define particle_ctrl_GetAssetInfoRequest_size 0 #define particle_ctrl_GetModuleInfoReply_Dependency_size 14 #define particle_ctrl_GetModuleInfoRequest_size 0 #define particle_ctrl_GetSectionDataSizeReply_size 6 diff --git a/services/inc/storage_streams.h b/services/inc/storage_streams.h index 5b896d5d9d..2488d1db59 100644 --- a/services/inc/storage_streams.h +++ b/services/inc/storage_streams.h @@ -162,6 +162,7 @@ class FileInputStream : public InputStream { } int peek(char* data, size_t size) override { + size_t originalSize = size; if (!remaining_) { return SYSTEM_ERROR_END_OF_STREAM; } @@ -170,6 +171,9 @@ class FileInputStream : public InputStream { fs::FsLock lock(fs_); CHECK_FS(lfs_file_seek(&fs_->instance, &file_, offset_, LFS_SEEK_SET)); size = CHECK_FS(lfs_file_read(&fs_->instance, &file_, data, size)); + if (size == 0 && originalSize > 0) { + return SYSTEM_ERROR_NOT_ENOUGH_DATA; + } return size; } @@ -277,11 +281,16 @@ class InflatorStream: public InputStream { } int seek(size_t offset) override { - CHECK_TRUE(offset == 0 || (offset >= offset_ && offset <= inflatedSize_), SYSTEM_ERROR_NOT_ALLOWED); + CHECK_TRUE(offset == 0 || (offset >= offset_ && offset <= inflatedSize_) || (offset < offset_ && (offset_ - offset) <= posInChunk_), SYSTEM_ERROR_NOT_ALLOWED); if (offset == 0) { return rewind(); - } else { + } else if (offset >= offset_) { return skip(offset - offset_); + } else { + auto diff = offset_ - offset; + offset_ -= diff; + posInChunk_ -= diff; + return offset_; } } @@ -339,6 +348,9 @@ class InflatorStream: public InputStream { CHECK(r); compressedPos += n; compressedStream_->skip(n); + if (n == 0 && availForRead() <= 0) { + break; + } } while (compressedPos < compressedChunk && r != INFLATE_HAS_MORE_OUTPUT); if (r == INFLATE_HAS_MORE_OUTPUT && availForRead() > 0) { break; @@ -427,6 +439,7 @@ class ProxyInputStream : public InputStream { // offset, storageId_, address_, remaining_, offset_); CHECK_TRUE(offset <= size_, SYSTEM_ERROR_NOT_ENOUGH_DATA); offset_ = CHECK(stream_->seek(baseOffset_ + offset)); + offset_ -= baseOffset_; return offset_; } diff --git a/system/inc/asset_manager.h b/system/inc/asset_manager.h index be8468df7c..1fca8a59f1 100644 --- a/system/inc/asset_manager.h +++ b/system/inc/asset_manager.h @@ -128,6 +128,7 @@ class AssetManager { const Vector& requiredAssets() const; const Vector& availableAssets() const; + Vector availableAndRequiredAssets() const; Vector missingAssets() const; Vector unusedAssets() const; diff --git a/system/inc/system_control.h b/system/inc/system_control.h index 2c602e48ba..f8ff39ce22 100644 --- a/system/inc/system_control.h +++ b/system/inc/system_control.h @@ -61,6 +61,7 @@ typedef enum ctrl_request_type { CTRL_REQUEST_SET_STARTUP_MODE = 75, CTRL_REQUEST_LOG_CONFIG = 80, CTRL_REQUEST_GET_MODULE_INFO = 90, + CTRL_REQUEST_GET_ASSET_INFO = 91, CTRL_REQUEST_DIAGNOSTIC_INFO = 100, // CTRL_REQUEST_WIFI_SET_ANTENNA = 110, // CTRL_REQUEST_WIFI_GET_ANTENNA = 111, diff --git a/system/inc/system_info_encoding.h b/system/inc/system_info_encoding.h new file mode 100644 index 0000000000..b7a691588e --- /dev/null +++ b/system/inc/system_info_encoding.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include "hal_platform.h" + +#if HAL_PLATFORM_PROTOBUF + +#include +#include + +#include "system_info.h" +#include "enumflags.h" + +#if HAL_PLATFORM_ASSETS +#include "asset_manager.h" +#endif // HAL_PLATFORM_ASSETS + +namespace particle { +namespace system { + +class EncodeFirmwareModules { +public: + enum class Flag { + NONE = 0, + FORCE_SHA = 0x01, + SYSTEM_INFO_CLOUD = 0x02 + }; + using Flags = EnumFlags; + + explicit EncodeFirmwareModules(pb_callback_t* cb, Flags flags = Flags(Flag::NONE)); + ~EncodeFirmwareModules(); + + hal_system_info_t* sysInfo() { + return &sysInfo_; + } + +private: + hal_system_info_t sysInfo_; + Flags flags_; +}; + +#if HAL_PLATFORM_ASSETS + +class EncodeAssets { +public: + explicit EncodeAssets(pb_callback_t* cb, const Vector& assets); + ~EncodeAssets() = default; +private: + Vector assets_; +}; + +#endif // HAL_PLATFORM_ASSETS + + +} } // particle::system + +#endif // HAL_PLATFORM_PROTOBUF diff --git a/system/src/asset_manager.cpp b/system/src/asset_manager.cpp index 6174961a7c..c8b3fc3115 100644 --- a/system/src/asset_manager.cpp +++ b/system/src/asset_manager.cpp @@ -31,6 +31,7 @@ #include "scope_guard.h" #include "system_cache.h" #include "system_defs.h" +#include "ota_module.h" namespace particle { @@ -38,6 +39,14 @@ namespace { const size_t FREE_BLOCKS_REQUIRED = 16; +bool boundsMatches(const hal_module_t* module) { + auto bounds = find_module_bounds(module->info.module_function, module->info.module_index, module_mcu_target(&module->info)); + if (!bounds) { + return false; + } + return !memcmp(&bounds, &module->bounds, sizeof(bounds)); +} + // TODO: move to using streams as well? int parseAssetDependencies(Vector& assets, hal_storage_id storageId, uintptr_t start, uintptr_t end) { for (uintptr_t offset = start; offset < end;) { @@ -127,6 +136,16 @@ const Vector& AssetManager::availableAssets() const { return availableAssets_; } +Vector AssetManager::availableAndRequiredAssets() const { + Vector res; + for (const auto& asset: availableAssets_) { + if (requiredAssets_.contains(asset)) { + res.append(asset); + } + } + return res; +} + Vector AssetManager::missingAssets() const { Vector missing; for (const auto& asset: requiredAssets()) { @@ -168,15 +187,32 @@ int AssetManager::requiredAssetsForModule(const hal_module_t* module, Vectorinfo.flags & MODULE_INFO_FLAG_PREFIX_EXTENSIONS) { // There are extensions in the prefix uintptr_t extensionsStart = (uintptr_t)module->info.module_start_address + module->module_info_offset + sizeof(module_info_t); - CHECK(parseAssetDependencies(assets, storageId, extensionsStart, (uintptr_t)module->info.module_end_address)); + uintptr_t extensionsEnd = (uintptr_t)module->info.module_end_address; + if (!boundsMatches(module)) { + extensionsStart -= (uintptr_t)module->info.module_start_address; + extensionsStart += (uintptr_t)module->bounds.start_address; + extensionsEnd -= (uintptr_t)module->info.module_start_address; + extensionsEnd += (uintptr_t)module->bounds.start_address; + } + CHECK(parseAssetDependencies(assets, storageId, extensionsStart, extensionsEnd)); } // Suffix module_info_suffix_base_t suffix = {}; uintptr_t suffixStart = (uintptr_t)module->info.module_end_address - sizeof(module_info_suffix_base_t); + if (!boundsMatches(module)) { + suffixStart -= (uintptr_t)module->info.module_start_address; + suffixStart += (uintptr_t)module->bounds.start_address; + } CHECK(hal_storage_read(storageId, suffixStart, (uint8_t*)&suffix, sizeof(suffix))); if (suffix.size > sizeof(module_info_suffix_base_t) + sizeof(module_info_extension_t) * 2) { // There are some additional extensions in suffix uintptr_t extensionsStart = (uintptr_t)module->info.module_end_address - suffix.size; + if (!boundsMatches(module)) { + extensionsStart -= (uintptr_t)module->info.module_start_address; + extensionsStart += (uintptr_t)module->bounds.start_address; + suffixStart -= (uintptr_t)module->info.module_start_address; + suffixStart += (uintptr_t)module->bounds.start_address; + } CHECK(parseAssetDependencies(assets, storageId, extensionsStart, suffixStart)); } @@ -230,7 +266,7 @@ int AssetManager::parseAvailableAssets() { if (r) { LOG(WARN, "Failed to open asset %s", info.name); } else { - reader.validate(false /* full */); + reader.validate(); } if (reader.isValid() && reader.size() == info.size) { if (!assets.append(reader.asset())) { @@ -281,6 +317,11 @@ int AssetManager::storeAsset(const hal_module_t* module) { CHECK_TRUE(fs, SYSTEM_ERROR_FILE); fs::FsLock lock(fs); + // Unmount and remount in order to invalidate access to any of the currently opened assets + filesystem_unmount(fs); + availableAssets_.clear(); + CHECK(filesystem_mount(fs)); + lfs_file_t file = {}; lfs_remove(&fs->instance, info.name().c_str()); CHECK_FS(lfs_file_open(&fs->instance, &file, info.name().c_str(), LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND)); diff --git a/system/src/asset_manager_api.cpp b/system/src/asset_manager_api.cpp index bb4fc68067..095c6b6c2b 100644 --- a/system/src/asset_manager_api.cpp +++ b/system/src/asset_manager_api.cpp @@ -53,7 +53,7 @@ int asset_manager_get_info(asset_manager_info* info, void* reserved) { auto internal = new Info(); CHECK_TRUE(internal, SYSTEM_ERROR_NO_MEMORY); internal->required = AssetManager::instance().requiredAssets(); - internal->available = AssetManager::instance().availableAssets(); + internal->available = AssetManager::instance().availableAndRequiredAssets(); info->internal = (void*)internal; diff --git a/system/src/control/storage.cpp b/system/src/control/storage.cpp index 30fab30afe..d9438eaa99 100644 --- a/system/src/control/storage.cpp +++ b/system/src/control/storage.cpp @@ -37,6 +37,7 @@ #include "control/storage.pb.h" #include +#include "system_info_encoding.h" #define PB(_name) particle_ctrl_##_name @@ -80,25 +81,6 @@ void firmwareUpdateCompletionHandler(int result, void* data) { g_update.reset(); } -PB(FirmwareModuleType) moduleFunctionToPb(module_function_t func) { - switch (func) { - case MODULE_FUNCTION_BOOTLOADER: - return PB(FirmwareModuleType_BOOTLOADER); - case MODULE_FUNCTION_MONO_FIRMWARE: - return PB(FirmwareModuleType_MONO_FIRMWARE); - case MODULE_FUNCTION_SYSTEM_PART: - return PB(FirmwareModuleType_SYSTEM_PART); - case MODULE_FUNCTION_USER_PART: - return PB(FirmwareModuleType_USER_PART); - case MODULE_FUNCTION_NCP_FIRMWARE: - return PB(FirmwareModuleType_NCP_FIRMWARE); - case MODULE_FUNCTION_RADIO_STACK: - return PB(FirmwareModuleType_RADIO_STACK); - default: - return PB(FirmwareModuleType_INVALID_FIRMWARE_MODULE); - } -} - } // namespace int startFirmwareUpdateRequest(ctrl_request* req) { @@ -191,74 +173,24 @@ int firmwareUpdateDataRequest(ctrl_request* req) { } int getModuleInfo(ctrl_request* req) { - hal_system_info_t info = {}; - info.size = sizeof(info); - info.flags = HAL_SYSTEM_INFO_FLAGS_CLOUD; - HAL_System_Info(&info, true /* construct */, nullptr); - SCOPE_GUARD({ - HAL_System_Info(&info, false, nullptr); - }); PB(GetModuleInfoReply) pbRep = {}; - pbRep.modules.arg = &info; - pbRep.modules.funcs.encode = [](pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { - const auto info = (const hal_system_info_t*)*arg; - for (size_t i = 0; i < info->module_count; ++i) { - const hal_module_t& module = info->modules[i]; - if (module.bounds.store != MODULE_STORE_MAIN) { - continue; - } - const auto type = moduleFunctionToPb((module_function_t)module.info.module_function); - if (type == PB(FirmwareModuleType_INVALID_FIRMWARE_MODULE)) { - continue; - } - PB(GetModuleInfoReply_Module) pbModule = {}; - pbModule.type = type; - pbModule.index = module.info.module_index; - pbModule.version = module.info.module_version; - pbModule.size = (uintptr_t)module.info.module_end_address - (uintptr_t)module.info.module_start_address; - unsigned valid = 0; - if (!(module.validity_result & MODULE_VALIDATION_INTEGRITY)) { - valid |= PB(FirmwareModuleValidityFlag_INTEGRITY_CHECK_FAILED); - } - if (!(module.validity_result & MODULE_VALIDATION_DEPENDENCIES)) { - valid |= PB(FirmwareModuleValidityFlag_DEPENDENCY_CHECK_FAILED); - } - pbModule.validity = valid; - pbModule.dependencies.arg = const_cast(&module.info); - pbModule.dependencies.funcs.encode = [](pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { - const auto info = (const module_info_t*)*arg; - for (unsigned i = 0; i < 2; ++i) { - const module_dependency_t& dep = (i == 0) ? info->dependency : info->dependency2; - const auto type = moduleFunctionToPb((module_function_t)dep.module_function); - if (type == PB(FirmwareModuleType_INVALID_FIRMWARE_MODULE)) { - continue; - } - PB(GetModuleInfoReply_Dependency) pbDep = {}; - pbDep.type = type; - pbDep.index = dep.module_index; - pbDep.version = dep.module_version; - if (!pb_encode_tag_for_field(strm, field)) { - return false; - } - if (!pb_encode_submessage(strm, PB(GetModuleInfoReply_Dependency_fields), &pbDep)) { - return false; - } - } - return true; - }; - if (!pb_encode_tag_for_field(strm, field)) { - return false; - } - if (!pb_encode_submessage(strm, PB(GetModuleInfoReply_Module_fields), &pbModule)) { - return false; - } - } - return true; - }; + using namespace particle::system; + EncodeFirmwareModules modules(&pbRep.modules, EncodeFirmwareModules::Flag::FORCE_SHA); CHECK(encodeReplyMessage(req, PB(GetModuleInfoReply_fields), &pbRep)); return 0; } +#if HAL_PLATFORM_ASSETS +int getAssetInfo(ctrl_request* req) { + PB(GetAssetInfoReply) pbRep = {}; + using namespace particle::system; + EncodeAssets assetsAvailable(&pbRep.available, AssetManager::instance().availableAssets()); + EncodeAssets assetsRequired(&pbRep.required, AssetManager::instance().requiredAssets()); + CHECK(encodeReplyMessage(req, PB(GetAssetInfoReply_fields), &pbRep)); + return 0; +} +#endif // HAL_PLATFORM_ASSETS + } // namespace particle::control } // namespace particle diff --git a/system/src/control/storage.h b/system/src/control/storage.h index c12a3b15ed..8fb188eb85 100644 --- a/system/src/control/storage.h +++ b/system/src/control/storage.h @@ -30,6 +30,10 @@ int firmwareUpdateDataRequest(ctrl_request* req); int getModuleInfo(ctrl_request* req); +#if HAL_PLATFORM_ASSETS +int getAssetInfo(ctrl_request* req); +#endif // HAL_PLATFORM_ASSETS + } // namespace particle::control } // namespace particle diff --git a/system/src/system_control_internal.cpp b/system/src/system_control_internal.cpp index 102cf81b15..5b7d1c1c0a 100644 --- a/system/src/system_control_internal.cpp +++ b/system/src/system_control_internal.cpp @@ -224,6 +224,12 @@ void SystemControl::processRequest(ctrl_request* req, ControlRequestChannel* /* setResult(req, control::getModuleInfo(req)); break; } +#if HAL_PLATFORM_ASSETS + case CTRL_REQUEST_GET_ASSET_INFO: { + setResult(req, control::getAssetInfo(req)); + break; + } +#endif // HAL_PLATFORM_ASSETS case CTRL_REQUEST_DIAGNOSTIC_INFO: { if (req->request_size > 0) { // TODO: Querying a part of the diagnostic data is not supported diff --git a/system/src/system_info.cpp b/system/src/system_info.cpp index 9fa059eafc..13870c944f 100644 --- a/system/src/system_info.cpp +++ b/system/src/system_info.cpp @@ -17,6 +17,7 @@ #include "system_info.h" #include "system_cloud_internal.h" +#include "system_info_encoding.h" #include "check.h" #include "scope_guard.h" #include "bytes2hexbuf.h" @@ -313,70 +314,8 @@ class PbAppenderStream: public pb_ostream_t { void* ctx_; }; -PB(FirmwareModuleType) moduleFunctionToPb(module_function_t func) { - switch (func) { - case MODULE_FUNCTION_RESOURCE: - return PB(FirmwareModuleType_RESOURCE_MODULE); - case MODULE_FUNCTION_BOOTLOADER: - return PB(FirmwareModuleType_BOOTLOADER_MODULE); - case MODULE_FUNCTION_MONO_FIRMWARE: - return PB(FirmwareModuleType_MONO_FIRMWARE_MODULE); - case MODULE_FUNCTION_SYSTEM_PART: - return PB(FirmwareModuleType_SYSTEM_PART_MODULE); - case MODULE_FUNCTION_USER_PART: - return PB(FirmwareModuleType_USER_PART_MODULE); - case MODULE_FUNCTION_SETTINGS: - return PB(FirmwareModuleType_SETTINGS_MODULE); - case MODULE_FUNCTION_NCP_FIRMWARE: - return PB(FirmwareModuleType_NCP_FIRMWARE_MODULE); - case MODULE_FUNCTION_RADIO_STACK: - return PB(FirmwareModuleType_RADIO_STACK_MODULE); - case MODULE_FUNCTION_ASSET: - return PB(FirmwareModuleType_ASSET_MODULE); - default: - return PB(FirmwareModuleType_INVALID_MODULE); - } -} - -PB(FirmwareModuleStore) moduleStoreToPb(module_store_t store) { - switch (store) { - case MODULE_STORE_FACTORY: - return PB(FirmwareModuleStore_FACTORY_MODULE_STORE); - case MODULE_STORE_BACKUP: - return PB(FirmwareModuleStore_BACKUP_MODULE_STORE); - case MODULE_STORE_SCRATCHPAD: - return PB(FirmwareModuleStore_SCRATCHPAD_MODULE_STORE); - case MODULE_STORE_MAIN: - default: - return PB(FirmwareModuleStore_MAIN_MODULE_STORE); - } -} - #endif // HAL_PLATFORM_PROTOBUF -#if HAL_PLATFORM_ASSETS - -bool encodeAssetDependencies(pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { - auto assets = (const spark::Vector*)*arg; - for (const auto& asset: *assets) { - PB(FirmwareModuleAsset) pbModuleAsset = {}; - EncodedString pbName(&pbModuleAsset.name); - EncodedString pbHash(&pbModuleAsset.hash); - // TODO: hash type - pbName.data = asset.name().c_str(); - pbName.size = asset.name().length(); - - pbHash.data = asset.hash().hash().data(); - pbHash.size = asset.hash().hash().size(); - if (!pb_encode_tag_for_field(strm, field) || !pb_encode_submessage(strm, &PB(FirmwareModuleAsset_msg), &pbModuleAsset)) { - return false; - } - } - return true; -} - -#endif // HAL_PLATFORM_ASSETS - } // anonymous bool module_info_to_json(AppendJson& json, const hal_module_t* module, uint32_t flags) @@ -530,23 +469,16 @@ bool system_app_info(appender_fn appender, void* append_data, void* reserved) { #if HAL_PLATFORM_PROTOBUF bool system_module_info_pb(appender_fn appender, void* append_data, void* reserved) { - hal_system_info_t sysInfo = {}; - sysInfo.size = sizeof(sysInfo); - sysInfo.flags = HAL_SYSTEM_INFO_FLAGS_CLOUD; - int r = system_info_get_unstable(&sysInfo, 0, nullptr); - if (r != 0) { - return false; - } - SCOPE_GUARD({ - system_info_free_unstable(&sysInfo, nullptr); - }); PB(SystemDescribe) pbDesc = {}; + // Firmware modules + EncodeFirmwareModules modules(&pbDesc.firmware_modules, EncodeFirmwareModules::Flag::SYSTEM_INFO_CLOUD); + // IMEI, ICCID, modem firmware version EncodedString pbImei(&pbDesc.imei); EncodedString pbIccid(&pbDesc.iccid); EncodedString pbModemFwVer(&pbDesc.modem_firmware_version); - for (unsigned i = 0; i < sysInfo.key_value_count; ++i) { - const auto& keyVal = sysInfo.key_values[i]; + for (unsigned i = 0; i < modules.sysInfo()->key_value_count; ++i) { + const auto& keyVal = modules.sysInfo()->key_values[i]; if (strcmp(keyVal.key, "imei") == 0) { pbImei.data = keyVal.value; pbImei.size = strlen(keyVal.value); @@ -558,70 +490,8 @@ bool system_module_info_pb(appender_fn appender, void* append_data, void* reserv pbModemFwVer.size = strlen(keyVal.value); } } - // Firmware modules - pbDesc.firmware_modules.arg = &sysInfo; - pbDesc.firmware_modules.funcs.encode = [](pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { - auto sysInfo = (const hal_system_info_t*)*arg; - for (unsigned i = 0; i < sysInfo->module_count; ++i) { - const auto& module = sysInfo->modules[i]; - if (!is_module_function_valid((module_function_t)module.info.module_function)) { - continue; - } - if (module.bounds.store == MODULE_STORE_FACTORY && (module.validity_result & MODULE_VALIDATION_INTEGRITY) == 0) { - continue; // See system_info_to_json() - } - PB(FirmwareModule) pbModule = {}; - pbModule.type = moduleFunctionToPb((module_function_t)module.info.module_function); - pbModule.index = module.info.module_index; - pbModule.version = module.info.module_version; - pbModule.store = moduleStoreToPb((module_store_t)module.bounds.store); - pbModule.max_size = module.bounds.maximum_size; - pbModule.checked_flags = module.validity_checked; - pbModule.passed_flags = module.validity_result; - EncodedString pbHash(&pbModule.hash); - if (module.info.module_function == MODULE_FUNCTION_USER_PART) { - pbHash.data = (const char*)module.suffix.sha; - pbHash.size = sizeof(module.suffix.sha); - } - pbModule.dependencies.arg = (void*)&module; // arg is a non-const pointer - pbModule.dependencies.funcs.encode = [](pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { - auto module = (const hal_module_t*)*arg; - for (unsigned i = 0; i < 2; ++i) { - const auto& dep = (i == 0) ? module->info.dependency : module->info.dependency2; - PB(FirmwareModuleDependency) pbDep = {}; - if (!is_module_function_valid((module_function_t)dep.module_function)) { - continue; - } - pbDep.type = moduleFunctionToPb((module_function_t)dep.module_function); - pbDep.index = dep.module_index; - pbDep.version = dep.module_version; - if (!pb_encode_tag_for_field(strm, field) || !pb_encode_submessage(strm, &PB(FirmwareModuleDependency_msg), &pbDep)) { - return false; - } - } - return true; - }; #if HAL_PLATFORM_ASSETS - spark::Vector assets; - if ((module.info.module_function == MODULE_FUNCTION_USER_PART) && (module.validity_result & MODULE_VALIDATION_INTEGRITY)) { - if (!AssetManager::requiredAssetsForModule(&module, assets) && assets.size() > 0) { - pbModule.asset_dependencies.arg = (void*)&assets; - pbModule.asset_dependencies.funcs.encode = encodeAssetDependencies; - } - } -#endif // HAL_PLATFORM_ASSETS - if (!pb_encode_tag_for_field(strm, field) || !pb_encode_submessage(strm, &PB(FirmwareModule_msg), &pbModule)) { - return false; - } - } - return true; - }; -#if HAL_PLATFORM_ASSETS - auto availableAssets = AssetManager::instance().availableAssets(); - if (availableAssets.size() > 0) { - pbDesc.assets.arg = (void*)&availableAssets; - pbDesc.assets.funcs.encode = encodeAssetDependencies; - } + EncodeAssets assets(&pbDesc.assets, AssetManager::instance().availableAssets()); #endif // HAL_PLATFORM_ASSETS PbAppenderStream strm(appender, append_data); return pb_encode(&strm, &PB(SystemDescribe_msg), &pbDesc); diff --git a/system/src/system_info_encoding.cpp b/system/src/system_info_encoding.cpp new file mode 100644 index 0000000000..74c07ce850 --- /dev/null +++ b/system/src/system_info_encoding.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2023 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "system_info_encoding.h" + +#if HAL_PLATFORM_PROTOBUF + +#include "control/common.h" +#include "cloud/describe.pb.h" + +#define PB(name) particle_cloud_##name + +using namespace particle; +using namespace particle::system; +using particle::control::common::EncodedString; + +namespace { + +PB(FirmwareModuleType) moduleFunctionToPb(module_function_t func) { + switch (func) { + case MODULE_FUNCTION_RESOURCE: + return PB(FirmwareModuleType_RESOURCE_MODULE); + case MODULE_FUNCTION_BOOTLOADER: + return PB(FirmwareModuleType_BOOTLOADER_MODULE); + case MODULE_FUNCTION_MONO_FIRMWARE: + return PB(FirmwareModuleType_MONO_FIRMWARE_MODULE); + case MODULE_FUNCTION_SYSTEM_PART: + return PB(FirmwareModuleType_SYSTEM_PART_MODULE); + case MODULE_FUNCTION_USER_PART: + return PB(FirmwareModuleType_USER_PART_MODULE); + case MODULE_FUNCTION_SETTINGS: + return PB(FirmwareModuleType_SETTINGS_MODULE); + case MODULE_FUNCTION_NCP_FIRMWARE: + return PB(FirmwareModuleType_NCP_FIRMWARE_MODULE); + case MODULE_FUNCTION_RADIO_STACK: + return PB(FirmwareModuleType_RADIO_STACK_MODULE); + case MODULE_FUNCTION_ASSET: + return PB(FirmwareModuleType_ASSET_MODULE); + default: + return PB(FirmwareModuleType_INVALID_MODULE); + } +} + +PB(FirmwareModuleStore) moduleStoreToPb(module_store_t store) { + switch (store) { + case MODULE_STORE_FACTORY: + return PB(FirmwareModuleStore_FACTORY_MODULE_STORE); + case MODULE_STORE_BACKUP: + return PB(FirmwareModuleStore_BACKUP_MODULE_STORE); + case MODULE_STORE_SCRATCHPAD: + return PB(FirmwareModuleStore_SCRATCHPAD_MODULE_STORE); + case MODULE_STORE_MAIN: + default: + return PB(FirmwareModuleStore_MAIN_MODULE_STORE); + } +} + +#if HAL_PLATFORM_ASSETS + +bool encodeAssetDependencies(pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { + auto assets = (const spark::Vector*)*arg; + for (const auto& asset: *assets) { + PB(FirmwareModuleAsset) pbModuleAsset = {}; + EncodedString pbName(&pbModuleAsset.name); + EncodedString pbHash(&pbModuleAsset.hash); + // TODO: hash type + pbName.data = asset.name().c_str(); + pbName.size = asset.name().length(); + + pbHash.data = asset.hash().hash().data(); + pbHash.size = asset.hash().hash().size(); + + pbModuleAsset.size = asset.size(); + pbModuleAsset.storage_size = asset.storageSize(); + + if (!pb_encode_tag_for_field(strm, field) || !pb_encode_submessage(strm, &PB(FirmwareModuleAsset_msg), &pbModuleAsset)) { + return false; + } + } + return true; +} + +#endif // HAL_PLATFORM_ASSETS + +} // anonymous + +#if HAL_PLATFORM_PROTOBUF + +EncodeFirmwareModules::EncodeFirmwareModules(pb_callback_t* cb, Flags flags) + : sysInfo_{}, + flags_(flags) { + sysInfo_.size = sizeof(sysInfo_); + sysInfo_.flags = (flags & Flag::SYSTEM_INFO_CLOUD) ? HAL_SYSTEM_INFO_FLAGS_CLOUD : 0; + system_info_get_unstable(&sysInfo_, 0, nullptr); + + cb->arg = this; + cb->funcs.encode = [](pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { + const auto self = (const EncodeFirmwareModules*)*arg; + for (unsigned i = 0; i < self->sysInfo_.module_count; ++i) { + const auto& module = self->sysInfo_.modules[i]; + if (!is_module_function_valid((module_function_t)module.info.module_function)) { + continue; + } + // If sending a describe, skip factory modules entirely just in case + if (module.bounds.store == MODULE_STORE_FACTORY && (self->flags_ & Flag::SYSTEM_INFO_CLOUD)) { + continue; + } + // if (module.bounds.store == MODULE_STORE_FACTORY && (module.validity_result & MODULE_VALIDATION_INTEGRITY) == 0) { + // continue; // See system_info_to_json() + // } + PB(FirmwareModule) pbModule = {}; + pbModule.type = moduleFunctionToPb((module_function_t)module.info.module_function); + pbModule.index = module.info.module_index; + pbModule.version = module.info.module_version; + pbModule.store = moduleStoreToPb((module_store_t)module.bounds.store); + pbModule.max_size = module.bounds.maximum_size; + pbModule.size = (uintptr_t)module.info.module_end_address - (uintptr_t)module.info.module_start_address; + if (pbModule.size > 0) { + pbModule.size += sizeof(uint32_t) /* CRC */; + } + pbModule.checked_flags = module.validity_checked; + pbModule.passed_flags = module.validity_result; + EncodedString pbHash(&pbModule.hash); + if (module.info.module_function == MODULE_FUNCTION_USER_PART || (self->flags_ & Flag::FORCE_SHA)) { + pbHash.data = (const char*)module.suffix.sha; + pbHash.size = sizeof(module.suffix.sha); + } + pbModule.dependencies.arg = (void*)&module; // arg is a non-const pointer + pbModule.dependencies.funcs.encode = [](pb_ostream_t* strm, const pb_field_iter_t* field, void* const* arg) { + auto module = (const hal_module_t*)*arg; + for (unsigned i = 0; i < 2; ++i) { + const auto& dep = (i == 0) ? module->info.dependency : module->info.dependency2; + PB(FirmwareModuleDependency) pbDep = {}; + if (!is_module_function_valid((module_function_t)dep.module_function)) { + continue; + } + pbDep.type = moduleFunctionToPb((module_function_t)dep.module_function); + pbDep.index = dep.module_index; + pbDep.version = dep.module_version; + if (!pb_encode_tag_for_field(strm, field) || !pb_encode_submessage(strm, &PB(FirmwareModuleDependency_msg), &pbDep)) { + return false; + } + } + return true; + }; +#if HAL_PLATFORM_ASSETS + spark::Vector assets; + if ((module.info.module_function == MODULE_FUNCTION_USER_PART) && (module.validity_result & MODULE_VALIDATION_INTEGRITY)) { + if (!AssetManager::requiredAssetsForModule(&module, assets) && assets.size() > 0) { + pbModule.asset_dependencies.arg = (void*)&assets; + pbModule.asset_dependencies.funcs.encode = encodeAssetDependencies; + } + } +#endif // HAL_PLATFORM_ASSETS + if (!pb_encode_tag_for_field(strm, field) || !pb_encode_submessage(strm, &PB(FirmwareModule_msg), &pbModule)) { + return false; + } + } + return true; + }; +} + +EncodeFirmwareModules::~EncodeFirmwareModules() { + system_info_free_unstable(&sysInfo_, nullptr); +} + +#endif // HAL_PLATFORM_PROTOBUF + +#if HAL_PLATFORM_ASSETS + +EncodeAssets::EncodeAssets(pb_callback_t* cb, const Vector& assets) + : assets_(assets) { + cb->arg = (void*)&this->assets_; + cb->funcs.encode = encodeAssetDependencies; +} +#endif // HAL_PLATFORM_ASSETS + +#endif // HAL_PLATFORM_PROTOBUF \ No newline at end of file diff --git a/user/tests/integration/ota/assets/assets.cpp b/user/tests/integration/ota/assets/assets.cpp index 2cd37d8191..cac6cec1a8 100644 --- a/user/tests/integration/ota/assets/assets.cpp +++ b/user/tests/integration/ota/assets/assets.cpp @@ -10,6 +10,8 @@ namespace { volatile int updateResult = firmware_update_failed; +ApplicationAsset testAsset; + struct ReportAsset { ApplicationAsset asset; uint32_t crc = 0; @@ -58,7 +60,7 @@ uint16_t getProductVersion() { // so, need to read from the actual suffix/extension. STARTUP(spark_protocol_set_product_firmware_version(spark_protocol_instance(), getProductVersion())); -int readAndCalculateAssetCrc(ApplicationAsset& asset, uint32_t* outCrc, bool resetAndSkip) { +int readAndCalculateAssetCrc(ApplicationAsset& asset, uint32_t* outCrc, bool resetAndSkip, bool seekBackwards = false) { uint32_t crc = 0; SCOPE_GUARD({ asset.reset(); @@ -70,16 +72,33 @@ int readAndCalculateAssetCrc(ApplicationAsset& asset, uint32_t* outCrc, bool res size_t pos = 0; while (asset.available() > 0) { size_t r = CHECK(asset.read(buf, sizeof(buf))); + if (seekBackwards && pos == 0) { + char tmp; + CHECK(asset.seek(r - 1)); + CHECK(asset.read(&tmp, sizeof(tmp))); + CHECK_TRUE(tmp == buf[r - 1], SYSTEM_ERROR_BAD_DATA); + } pos += r; crc = particle::softCrc32((const uint8_t*)buf, r, &crc); // This call is here so that test runner readMailbox() requests // are processed in a timely manner and we don't lose any messages. Particle.process(); - if (resetAndSkip && asset.available() <= size / 2) { + if (resetAndSkip && pos >= size / 2) { asset.reset(); CHECK(asset.skip(pos)); resetAndSkip = false; } + if (seekBackwards && pos >= size / 2) { + seekBackwards = false; + CHECK(asset.skip(size - 1)); + CHECK(asset.seek(pos - r)); + char buf1[256]; + size_t reread = CHECK(asset.read(buf1, sizeof(buf1))); + CHECK_TRUE(!memcmp(buf, buf1, std::min(r, reread)), SYSTEM_ERROR_BAD_DATA); + } + if (pos > size) { + return SYSTEM_ERROR_OUT_OF_RANGE; + } } *outCrc = crc; return 0; @@ -128,7 +147,7 @@ size_t serializeAssetReportAsJson(char* buf, size_t size, spark::Vector= 1 * 60 * 1000) { + break; + } + Particle.process(); + delay(100); + } + + size_t size = testAsset.size(); + char tmp; + int error = 0; + for (size_t i = 0; i < size - 1; i++) { + // This should error out eventually + auto r = testAsset.read(&tmp, sizeof(tmp)); + if (r < 0) { + error = r; + break; + } + } + assertLess(error, 0); + + assertNotEqual((int)updateResult, (int)firmware_update_failed); + assertNotEqual((int)updateResult, (int)SYSTEM_ERROR_OTA); + + assertEqual(0, pushMailbox(MailboxEntry().type(MailboxEntry::Type::RESET_PENDING), 5000)); +} + +test(06_ad_hoc_ota_asset_repeat_complete) { + Particle.connect(); + assertTrue(waitFor(Particle.connected, 10 * 60 * 1000)); + assertEqual(0, validateAndReportAssets()); + assertTrue(hookExecuted); + assertTrue(hookAssets == System.assetsAvailable()); +} + +test(07_ad_hoc_ota_restore) { updateResult = firmware_update_failed; Particle.disconnect(); System.assetsHandled(); @@ -238,7 +317,7 @@ test(04_ad_hoc_ota_restore) { System.enableReset(); } -test(05_product_ota_start) { +test(08_product_ota_start) { assertFalse(hookExecuted); assertEqual(0, hookAssets.size()); assertEqual(0, System.assetsRequired().size()); @@ -258,7 +337,7 @@ test(05_product_ota_start) { System.assetsHandled(); } -test(06_product_ota_wait) { +test(09_product_ota_wait) { SCOPE_GUARD({ System.off(all_events); System.enableReset(); @@ -279,7 +358,7 @@ test(06_product_ota_wait) { assertEqual(0, pushMailbox(MailboxEntry().type(MailboxEntry::Type::SAFE_MODE_PENDING), 5000)); } -test(07_product_ota_complete) { +test(10_product_ota_complete) { Particle.connect(); assertTrue(waitFor(Particle.connected, 10 * 60 * 1000)); assertEqual(0, validateAndReportAssets()); @@ -287,23 +366,23 @@ test(07_product_ota_complete) { assertTrue(hookAssets == System.assetsAvailable()); } -test(08_product_ota_complete_handled) { +test(11_product_ota_complete_handled) { System.assetsHandled(true); assertEqual(0, pushMailbox(MailboxEntry().type(MailboxEntry::Type::RESET_PENDING), 5000)); System.reset(); } -test(09_assets_handled_hook) { +test(12_assets_handled_hook) { assertFalse(hookExecuted); assertEqual(0, hookAssets.size()); assertNotEqual(0, System.assetsAvailable().size()); } -test(10_assets_read_skip_reset) { +test(13_assets_read_skip_reset) { assertEqual(0, validateAndReportAssets(0, true /* resetAndSkip */)); } -test(11_assets_available_after_eof_reports_zero) { +test(14_assets_available_after_eof_reports_zero) { auto asset = System.assetsAvailable()[0]; char buf[256]; while (asset.available()) { @@ -318,7 +397,7 @@ test(11_assets_available_after_eof_reports_zero) { asset.reset(); } -test(12_assets_read_using_filesystem) { +test(15_assets_read_using_filesystem) { #if HAL_PLATFORM_INFLATE_USE_FILESYSTEM const size_t FREE_MEM_TO_LEAVE = 30 * 1024; auto freeMem = System.freeMemory(); @@ -333,6 +412,103 @@ test(12_assets_read_using_filesystem) { #endif // HAL_PLATFORM_INFLATE_USE_FILESYSTEM } +test(16_assets_add_extra_asset_start) { + System.disableReset(); + System.on(firmware_update, [](system_event_t ev, int data, void* context) { + updateResult = data; + }); + updateResult = SYSTEM_ERROR_OTA; + System.assetsHandled(); + Particle.connect(); + assertTrue(waitFor(Particle.connected, 5 * 60 * 1000)); +} + +test(17_assets_add_extra_asset_wait) { + SCOPE_GUARD({ + System.off(all_events); + System.enableReset(); + }); + + for (auto start = millis(); millis() - start <= 5 * 60 * 1000;) { + if (updateResult == firmware_update_complete || updateResult == firmware_update_failed || updateResult == firmware_update_pending) { + break; + } else if (updateResult == SYSTEM_ERROR_OTA && millis() - start >= 1 * 60 * 1000) { + break; + } + Particle.process(); + delay(100); + } + + assertNotEqual((int)updateResult, (int)firmware_update_failed); + assertNotEqual((int)updateResult, (int)SYSTEM_ERROR_OTA); + assertEqual(0, pushMailbox(MailboxEntry().type(MailboxEntry::Type::RESET_PENDING), 5000)); +} + +test(18_assets_add_extra_asset_complete) { + Particle.connect(); + assertTrue(waitFor(Particle.connected, 10 * 60 * 1000)); + assertEqual(0, validateAndReportAssets()); + assertTrue(hookExecuted); + assertTrue(hookAssets == System.assetsAvailable()); +} + +test(19_assets_factory_image_asset_dependencies) { +#if HAL_PLATFORM_NRF52840 // FIXME for RTL872x-based platforms + hal_system_info_t info = {}; + info.size = sizeof(info); + int r = system_info_get_unstable(&info, 0 /* flags */, nullptr /* reserved */); + assertEqual(r, 0); + SCOPE_GUARD({ + system_info_free_unstable(&info, nullptr /* reserved */); + }); + + const hal_module_t* app = nullptr; + for (size_t i = 0; i < info.module_count; ++i) { + const auto& module = info.modules[i]; + if (module.info.module_function == MODULE_FUNCTION_USER_PART && module.bounds.store == MODULE_STORE_MAIN) { + if (!app || module.info.module_index > app->info.module_index) { + app = &module; + } + } + } + + assertTrue(app); + + auto storageId = HAL_STORAGE_ID_INVALID; + if (app->bounds.location == MODULE_BOUNDS_LOC_INTERNAL_FLASH) { + storageId = HAL_STORAGE_ID_INTERNAL_FLASH; + } else if (app->bounds.location == MODULE_BOUNDS_LOC_EXTERNAL_FLASH) { + storageId = HAL_STORAGE_ID_EXTERNAL_FLASH; + } + assertNotEqual(storageId, HAL_STORAGE_ID_INVALID); + + // Erase entire Factory Module + assertEqual(hal_storage_erase(HAL_STORAGE_ID_EXTERNAL_FLASH, EXTERNAL_FLASH_FAC_ADDRESS, EXTERNAL_FLASH_FAC_LENGTH), EXTERNAL_FLASH_FAC_LENGTH); + + char tmp[256]; + size_t totalSize = (uintptr_t)app->info.module_end_address - (uintptr_t)app->info.module_start_address + sizeof(uint32_t) /* CRC32 */; + for (size_t i = 0; i < totalSize;) { + auto toRead = std::min(sizeof(tmp), totalSize - i); + assertEqual((int)toRead, hal_storage_read(storageId, (uintptr_t)app->info.module_start_address + i, (uint8_t*)tmp, toRead)); + assertEqual((int)toRead, hal_storage_write(HAL_STORAGE_ID_EXTERNAL_FLASH, EXTERNAL_FLASH_FAC_ADDRESS + i, (const uint8_t*)tmp, toRead)); + i += toRead; + } + // We'll validate on JS side +#else +#endif // HAL_PLATFORM_NRF52840 +} + +test(20_assets_factory_remove) { +#if HAL_PLATFORM_NRF52840 // FIXME for RTL872x-based platforms + // Erase entire Factory Module + assertEqual(hal_storage_erase(HAL_STORAGE_ID_EXTERNAL_FLASH, EXTERNAL_FLASH_FAC_ADDRESS, EXTERNAL_FLASH_FAC_LENGTH), EXTERNAL_FLASH_FAC_LENGTH); +#endif // HAL_PLATFORM_NRF52840 +} + +test(21_seek_backwards) { + assertEqual(0, validateAndReportAssets(0, /* resetAndSkip */ false, /* seekBackwards */ true)); +} + test(99_product_ota_restore) { // Just in case System.enableReset(); diff --git a/user/tests/integration/ota/assets/assets.spec.js b/user/tests/integration/ota/assets/assets.spec.js index dd27e88e9e..d3dabd486e 100644 --- a/user/tests/integration/ota/assets/assets.spec.js +++ b/user/tests/integration/ota/assets/assets.spec.js @@ -218,6 +218,45 @@ function cloudReportedToReport(rep) { }}); } +async function queryDeviceAssets() { + const usbDevice = await device.getUsbDevice(); + const assets = await usbDevice.getAssetInfo(); + assets.required = assets.required.map((val) => { return { + name: val.name, + size: 0, + storageSize: 0, + valid: true, + readable: true, + hash: val.hash, + crc: 0, + error: 0 + }}); + assets.available = assets.available.map((val) => { return { + name: val.name, + size: val.size, + storageSize: val.storageSize, + valid: true, + readable: true, + hash: val.hash, + crc: 0, + error: 0 + }}); + const modules = await usbDevice.getFirmwareModuleInfo(); + const app = modules.find((val) => val.type === 'USER_PART' && val.store === 'MAIN' && val.assetDependencies.length > 0); + if (app) { + assets.requiredApp = app.assetDependencies.map((val) => { return { + name: val.name, + size: 0, + storageSize: 0, + valid: true, + readable: true, + hash: val.hash, + crc: 0, + error: 0 + }}); + } + return assets; +} async function generateProductVersion() { if (productVersion) { @@ -299,16 +338,47 @@ test('03_ad_hoc_ota_complete', async function() { const deviceReported = JSON.parse(device.mailBox.pop().d); const cloudReported = cloudReportedToReport(await waitForAssets(5 * 60 * 1000)); const local = generatedAssetsToReport(); + console.log(local); + console.log(deviceReported); + expect(deviceReported.available).to.deep.equal(local); + expect(deviceReported.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(cloudReported).excludingEvery(['hash', 'crc', 'size', 'storageSize']).to.deep.equal(local); + const queried = await queryDeviceAssets(); + expect(queried.available).excludingEvery(['crc']).to.deep.equal(local); + expect(queried.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(queried.requiredApp).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); +}); + +test('04_ad_hoc_ota_asset_repeat_start', async function() { + const asset = assets[0]; + const module = await createAssetModule(asset.data, asset.name, { compress: true }); + await flash(this, module, asset.name); +}); + +test('05_ad_hoc_ota_asset_repeat_wait', async function() { +}); + +test('06_ad_hoc_ota_asset_repeat_complete', async function() { + const deviceReported = JSON.parse(device.mailBox.pop().d); + const cloudReported = cloudReportedToReport(await waitForAssets(5 * 60 * 1000)); + const local = generatedAssetsToReport(); + deviceReported.available.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); + cloudReported.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); expect(deviceReported.available).to.deep.equal(local); expect(deviceReported.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); expect(cloudReported).excludingEvery(['hash', 'crc', 'size', 'storageSize']).to.deep.equal(local); + const queried = await queryDeviceAssets(); + queried.available.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); + expect(queried.available).excludingEvery(['crc']).to.deep.equal(local); + expect(queried.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(queried.requiredApp).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); }); -test('04_ad_hoc_ota_restore', async function() { +test('07_ad_hoc_ota_restore', async function() { await device.flash(device.testAppBinFile); }); -test('05_product_ota_start', async function() { +test('08_product_ota_start', async function() { // Regenerate await generateAssets(); await uploadProductFirmware(false /* removeOnly */); @@ -320,38 +390,42 @@ test('05_product_ota_start', async function() { expect(Number(dev.body.firmware_version)).to.not.equal(Number(productVersion)); }); -test('06_product_ota_wait', async function() { +test('09_product_ota_wait', async function() { }); -test('07_product_ota_complete', async function() { +test('10_product_ota_complete', async function() { const deviceReported = JSON.parse(device.mailBox.shift().d); const cloudReported = cloudReportedToReport(await waitForAssets(5 * 60 * 1000)); const local = generatedAssetsToReport(); expect(deviceReported.available).to.deep.equal(local); expect(deviceReported.required).excludingEvery(['crc', 'size', 'readable', 'storageSize']).to.deep.equal(local); expect(cloudReported).excludingEvery(['hash', 'crc', 'size', 'storageSize']).to.deep.equal(local); + const queried = await queryDeviceAssets(); + expect(queried.available).excludingEvery(['crc']).to.deep.equal(local); + expect(queried.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(queried.requiredApp).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); }); -test('08_product_ota_complete_handled', async function() { +test('11_product_ota_complete_handled', async function() { }); -test('09_assets_handled_hook', async function() { +test('12_assets_handled_hook', async function() { }); -test('10_assets_read_skip_reset', async function() { +test('13_assets_read_skip_reset', async function() { const deviceReported = JSON.parse(device.mailBox.shift().d); const local = generatedAssetsToReport(); expect(deviceReported.available).to.deep.equal(local); expect(deviceReported.required).excludingEvery(['crc', 'size', 'readable', 'storageSize']).to.deep.equal(local); }); -test('11_assets_available_after_eof_reports_zero', async function() { +test('14_assets_available_after_eof_reports_zero', async function() { }); -test('12_assets_read_using_filesystem', async function() { +test('15_assets_read_using_filesystem', async function() { const deviceReported = JSON.parse(device.mailBox.shift().d); const local = generatedAssetsToReport(); console.dir(deviceReported, { depth: null }); @@ -360,6 +434,74 @@ test('12_assets_read_using_filesystem', async function() { expect(deviceReported.required).excludingEvery(['crc', 'size', 'readable', 'storageSize']).to.deep.equal(local); }); +test('16_assets_add_extra_asset_start', async function() { + const name = `assetx_${timestamp}.bin`; + const asset = await generateAsset(1024, name); + assets.push(asset); + const module = await createAssetModule(asset.data, asset.name, { compress: true }); + await flash(this, module, asset.name); +}); + +test('17_assets_add_extra_asset_wait', async function() { + +}); + +test('18_assets_add_extra_asset_complete', async function() { + const deviceReported = JSON.parse(device.mailBox.pop().d); + const cloudReported = cloudReportedToReport(await waitForAssets(5 * 60 * 1000)); + const localWithExtra = generatedAssetsToReport(); + // Remove extra asset + const extra = assets.pop(); + const local = generatedAssetsToReport(); + assets.push(extra); + const queried = await queryDeviceAssets(); + deviceReported.available.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); + cloudReported.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); + expect(deviceReported.available).to.deep.equal(local); + expect(deviceReported.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(cloudReported).excludingEvery(['hash', 'crc', 'size', 'storageSize']).to.deep.equal(local); + expect(queried.available).excludingEvery(['crc']).to.deep.equal(localWithExtra); + expect(queried.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(queried.requiredApp).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); +}); + +test('19_assets_factory_image_asset_dependencies', async function() { + if (!device.platform.tags.includes('nrf52840')) { + // FIXME for rtl872x-based platforms + return; + } + + const usbDevice = await device.getUsbDevice(); + const modules = await usbDevice.getFirmwareModuleInfo(); + const app = modules.find((val) => val.type === 'USER_PART' && val.store === 'MAIN'); + const factory = modules.find((val) => val.type === 'USER_PART' && val.store === 'FACTORY'); + console.dir(app, { depth: null }); + console.dir(factory, { depth: null }); + expect(app).to.not.be.undefined; + expect(factory).to.not.be.undefined; + expect(app).excluding('store').to.deep.equal(factory); +}); + +test('20_assets_factory_remove', async function() { + +}); + +test('21_seek_backwards', async function() { + const deviceReported = JSON.parse(device.mailBox.pop().d); + const localWithExtra = generatedAssetsToReport(); + // Remove extra asset + const extra = assets.pop(); + const local = generatedAssetsToReport(); + assets.push(extra); + const queried = await queryDeviceAssets(); + deviceReported.available.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); + expect(deviceReported.available).to.deep.equal(local); + expect(deviceReported.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(queried.available).excludingEvery(['crc']).to.deep.equal(localWithExtra); + expect(queried.required).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); + expect(queried.requiredApp).excludingEvery(['crc', 'size', 'storageSize', 'readable']).to.deep.equal(local); +}); + test('99_product_ota_restore', async function() { await checkDeviceInProductAndMarkAsDevelopment(); await device.flash(device.testAppBinFile); diff --git a/wiring/inc/spark_wiring_asset.h b/wiring/inc/spark_wiring_asset.h index 689860976f..88967267b5 100644 --- a/wiring/inc/spark_wiring_asset.h +++ b/wiring/inc/spark_wiring_asset.h @@ -154,6 +154,14 @@ class ApplicationAsset: public Stream { */ virtual int skip(size_t size); + /** + * Seek to absolute position `pos`. + * + * @param pos Absolute position to seek to. + * @return int Resulting absolute position or `system_error_t` error code. + */ + int seek(size_t pos); + /** * No-op, conforming to `Stream` interface. * diff --git a/wiring/src/spark_wiring_asset.cpp b/wiring/src/spark_wiring_asset.cpp index 814a899354..0beb42412e 100644 --- a/wiring/src/spark_wiring_asset.cpp +++ b/wiring/src/spark_wiring_asset.cpp @@ -127,6 +127,17 @@ int ApplicationAsset::skip(size_t size) { return asset_manager_skip(data_->stream, size, nullptr); } +int ApplicationAsset::seek(size_t pos) { + CHECK(prepareForReading()); + int r = asset_manager_seek(data_->stream, pos, nullptr); + // Seeking backwards might require rewind and seek + if (r < 0) { + CHECK(asset_manager_seek(data_->stream, 0, nullptr)); + r = asset_manager_seek(data_->stream, pos, nullptr); + } + return r; +} + void ApplicationAsset::flush() { return; }