Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assets OTA #2668

Merged
merged 70 commits into from Aug 15, 2023
Merged

Assets OTA #2668

merged 70 commits into from Aug 15, 2023

Conversation

avtolstoy
Copy link
Member

@avtolstoy avtolstoy commented Jul 11, 2023

Description

Assets OTA

TODO

  1. Automated test for assets OTA
  2. system describe communication unit tests are broken due to migration from JSON to protobuf

Steps to Test

P2 / Photon 2

  1. Update device to latest available Device OS (5.4.1)
    1.1. The easiest is device-os-flash --all-devices 5.4.1
  2. Build/flash bootloader
    2.2. (bootloader) $ make PLATFORM=p2 COMPILE_LTO=n clean all
    2.3. Flash bootloader device-os-flash --all-devices $DEVICE_OS_DIR/build/target/bootloader/platform-32-m/bootloader.bin
  3. Build/flash Device OS and application in whichever convenient way (e.g. Workbench with deviceOS@source)

Gen 3 (nRF52840 platforms)

  1. Update device to latest available Device OS (5.4.1)
    1.1. The easiest is device-os-flash --all-devices 5.4.1
  2. Build/flash bootloader
    2.2. (bootloader) $ make PLATFORM=boron COMPILE_LTO=n clean all
    2.3. Flash bootloader device-os-flash --all-devices $DEVICE_OS_DIR/build/target/bootloader/platform-13-m/bootloader.bin
  3. Build/flash Device OS and application in whichever convenient way (e.g. Workbench with deviceOS@source)

Example App

See app/assets.

References

N/A


Completeness

  • User is totes amazing for contributing!
  • Contributor has signed CLA (Info here)
  • Problem and Solution clearly stated
  • Run unit/integration/application tests on device
  • Added documentation
  • Added to CHANGELOG.md after merging (add links to docs and issues)

@@ -49,6 +49,11 @@ ASFLAGS += -I$(COMMON_BUILD)/arm/startup
ASFLAGS += -Wa,--defsym -Wa,SPARK_INIT_STARTUP=0
ASFLAGS += -D__STACKSIZE__=$(MAIN_STACK_SIZE) -D__STACK_SIZE=$(MAIN_STACK_SIZE)

# Tracker
ifeq ($(PLATFORM_ID),26)
USE_PRINTF_FLOAT ?= n
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change for customer applications using printf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I have a workaround for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on the workaround? I tried Serial.printlnf("pi=%f", M_PI); on a Tracker with 5.4.1 and I got the expected pi=3.141593 but with this branch, I get pi=

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the workaround because Tracker Edge/Monitor Edge uses a lot of floats and doubles

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly in the jsmn JSON writer but also logging of various sensor data and GPS

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to re-iterate: this is a temporary change and will be resolved (restored and some other stuff thrown away) before this PR is merged. This is failing a lot of our automated tests, so won't be unnoticed.

} else if (c == 'h') {
LOG(INFO, "Hook executed=%d", hookExecuted);
if (hookExecuted) {
LOG(INFO, "%u ssets captured in hook:", hookAssets.size());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'A'

int read() override;
int peek() override;
// Not present in Stream
virtual int read(char* buffer, size_t size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience, can we add an uint8_t overload virtual int read(uint8_t* buffer, size_t size);?

@mrlambchop mrlambchop self-requested a review July 30, 2023 21:28
@mrlambchop mrlambchop marked this pull request as ready for review July 30, 2023 21:29
Copy link
Member

@XuGuohui XuGuohui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job 👍 ! This is a huge PR and I just had a brief review. The main problem I found is we use memory allocation in many places without checking the result. This is a potential risk. I'll go though the asset OTA process as well.

hal/src/argon/hal_platform_config.h Outdated Show resolved Hide resolved
platform/MCU/rtl872x/src/flash_mal.c Show resolved Hide resolved
platform/MCU/rtl872x/src/flash_mal.c Show resolved Hide resolved
platform/MCU/rtl872x/src/flash_mal.c Show resolved Hide resolved
services/inc/storage_streams.h Show resolved Hide resolved
system/src/asset_manager_api.cpp Outdated Show resolved Hide resolved
system/src/system_cloud_internal.cpp Show resolved Hide resolved
system/src/system_info.cpp Show resolved Hide resolved
wiring/src/spark_wiring_system.cpp Outdated Show resolved Hide resolved
wiring/src/spark_wiring_system.cpp Outdated Show resolved Hide resolved
namespace particle {

class ApplicationAsset: public Stream {
public:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doxygen comments please for public methods here

static spark::Vector<ApplicationAsset> assetsAvailable();
static int assetsHandled(bool state = true);

static int onAssetsOta(OnAssetsOtaCallback cb, void* context = nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doxygen comments for public methods please

@@ -48,9 +48,9 @@ const module_bounds_t module_km0_part1 = {

// OTA region, to be updated.
module_bounds_t module_ota = {
.maximum_size = 0x200000, //2M
.maximum_size = 0x180000, // 1.5MB
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get these values from the linker script?

struct asset_manager_stream;
typedef struct asset_manager_stream asset_manager_stream;

typedef enum asset_manager_consumer_state {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please comment these cases about what the do or represent


LOG(INFO, "prefix func=%d flags=%x start=%x end=%x compressed=%d", prefix.module_function, prefix.flags, prefix.module_start_address, prefix.module_end_address, compressed);

// Comperssion header
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Compression"

@avtolstoy avtolstoy force-pushed the feature/third-party-ota branch 2 times, most recently from 42e7dad to 5f86de1 Compare August 2, 2023 10:23
Copy link
Member

@technobly technobly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works great! :shipit:

@scott-brust scott-brust modified the milestones: 5.5.0, 5.5.0-rc.1 Aug 7, 2023
Copy link
Member

@sergeuz sergeuz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting the comments I have so far. Will continue looking into this tomorrow.

(*ctx)->buf_size = bufSize;
(*ctx)->output = output;
(*ctx)->user_data = user_data;
inflate_reset(*ctx);
CHECK(inflate_reset(*ctx));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context will be leaked if inflate_reset fails.

}

int inflate_reset_impl(inflate_ctx* ctx) {
#if HAL_PLATFORM_INFLATE_USE_FILESYSTEM
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case when HAL_PLATFORM_INFLATE_USE_FILESYSTEM is 1 but the entire buffer is allocated in RAM doesn't seem to be handled by this function.

uintptr_t productDataStart = (uintptr_t)desc.info.module_end_address - sizeof(module_info_suffix_base_t) - sizeof(module_info_product_data_ext_t);
CHECK(hal_storage_read(storageId, productDataStart, (uint8_t*)&product, sizeof(product)));

return product.version;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -81,28 +81,41 @@ extern "C" {
# define EXTERNAL_FLASH_RESERVED1_XIP_ADDRESS (EXTERNAL_FLASH_RESERVED1_ADDRESS + EXTERNAL_FLASH_XIP_BASE)
# define EXTERNAL_FLASH_RESERVED1_LENGTH (2048*1024)
# define EXTERNAL_FLASH_FAC_ADDRESS (EXTERNAL_FLASH_RESERVED1_ADDRESS + EXTERNAL_FLASH_RESERVED1_LENGTH)
# define EXTERNAL_FLASH_ASSET_STORAGE_FIRST_PAGE ((EXTERNAL_FLASH_RESERVED1_XIP_ADDRESS) / sFLASH_PAGESIZE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is EXTERNAL_FLASH_RESERVED1_XIP_ADDRESS and not EXTERNAL_FLASH_RESERVED1_ADDRESS used here?


class Buffer {
public:
Buffer(size_t size = 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making the constructor explicit and removing implicit conversion operators to avoid unintended conversions.

return !memcmp(buffer_.get(), other.buffer_.get(), size());
}
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way it's implemented now, the operator will return false if both buffers are empty.

#pragma once

#undef LOG_COMPILE_TIME_LEVEL
#define LOG_COMPILE_TIME_LEVEL LOG_LEVEL_NONE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be overriding the logging settings in header files.

isOpen_(false) {
fs_ = filesystem_get_instance(instance, nullptr);
open(filename);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider only providing a default constructor and exposing the open() method so that the possible error when opening the file can be properly handled by the calling code.

Copy link
Member Author

@avtolstoy avtolstoy Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will leave this one as-is just for simplicity of not having to carry around filename (see AssetReader usage as well). UGH Fixed 😄

CHECK_TRUE(data, SYSTEM_ERROR_INVALID_ARGUMENT);
size = std::min(size, remaining_);
fs::FsLock lock(fs_);
CHECK(lfs_file_seek(&fs_->instance, &file_, offset_, LFS_SEEK_SET));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure CHECK_FS is used with LittleFS functions here and in other places in this PR.

Copy link
Member

@sergeuz sergeuz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job. I don't think I found anything major, although I must admit I don't fully follow the logic where a file is used to store the decompression buffer. Hopefully that is well covered by tests.

offset_(0),
error_(0) {

error_ = inflate_create(&inflate_, nullptr, [](const char* data, size_t size, void* ctx) -> int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach to error handling is tedious and prone to bugs. In this class specifically, error_ is only checked in rewind() so the calling code will highly likely miss the error occurred when creating a decompressor context in the constructor. Consider separating the initialization logic in a checkable method such as init().

}
}
return availForRead();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty cool that this class doesn't require an intermediate buffer.

@@ -0,0 +1,158 @@
/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header file seems to be internal to the system. If so, consider moving it to system/src as e.g. asset_manager_internal.h.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also required by hal unfortunately (ota_flash_hal.cpp)

Buffer buf(hash_.size() * 2 + 1);
if (buf.size()) {
memset(buf.data(), 0, buf.size());
bytes2hexbuf_lower_case((const uint8_t*)hash_.data(), hash_.size(), buf.data());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just in case, there's a safer alternative that always null-terminates the output buffer.

* @param reserved Reserved (NULL)
* @return 0 on success or `system_error_t` error code
*/
int asset_manager_free_info(asset_manager_info* info, void* reserved);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use void as the return type of destructor-like functions where possible as it's often not straightforward to handle errors during deinitialization in the calling code. If it does make sense to return an error from such a function, it should be documented what happens to the instance being deinitialized when an error occurs.

Same here.

CHECK_TRUE(prefix.module_function == MODULE_FUNCTION_ASSET, SYSTEM_ERROR_BAD_DATA);
CHECK_TRUE(prefix.flags & MODULE_INFO_FLAG_DROP_MODULE_INFO, SYSTEM_ERROR_BAD_DATA);
CHECK_FALSE(prefix.flags & MODULE_INFO_FLAG_PREFIX_EXTENSIONS, SYSTEM_ERROR_BAD_DATA);
CHECK_TRUE(moduleSize == ((uintptr_t)prefix.module_end_address - (uintptr_t)prefix.module_start_address + sizeof(uint32_t)), SYSTEM_ERROR_BAD_DATA);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment that sizeof(uint32_t) is the CRC.

info->internal = (void*)internal;

bool ok = false;
SCOPE_GUARD({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have NAMED_SCOPE_GUARD that can be dismissed.

delete internal;
}
if (info->required) {
free(info->required);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

free() is used to free the memory allocated with new.

Same here.

internal->required = AssetManager::instance().requiredAssets();
internal->available = AssetManager::instance().availableAssets();

info->internal = (void*)internal;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This internal info doesn't seem to be used by any of the API calls. If so, let's remove it and save on memory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the storage for at the very least hash and name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, didn't notice that 👍

}

inline bool AssetHash::operator==(const AssetHash& other) const {
return (type_ == other.type_ && hash_ == other.hash_);
Copy link
Member

@sergeuz sergeuz Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that the asset name is not taken into account here? It's worth adding a comment if so. Oops, this class is just an asset's hash.

@avtolstoy avtolstoy requested a review from sergeuz August 10, 2023 16:34
@avtolstoy avtolstoy merged commit 684713e into develop Aug 15, 2023
1 of 11 checks passed
@avtolstoy avtolstoy deleted the feature/third-party-ota branch August 15, 2023 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants