Skip to content

Commit

Permalink
[nrfconnect] Generate Nordic-specific OTA image header (#15248)
Browse files Browse the repository at this point in the history
* [nrfconnect] Generate Nordic-specific OTA image header

Generate Nordic-specific OTA image header describing
contents of the OTA image using a TLV-based structure.

* [nrfconnect] Parse OTA image content header.

* Fix bugs

* Address code review comments

* Update docs

* Restyled by prettier-markdown

Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
Damian-Nordic and restyled-commits authored Feb 27, 2022
1 parent 2aab9b8 commit 4668421
Show file tree
Hide file tree
Showing 12 changed files with 533 additions and 22 deletions.
28 changes: 25 additions & 3 deletions config/nrfconnect/chip-module/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ if (CONFIG_CHIP)
include(ExternalProject)
include(../../zephyr/ota-image.cmake)
include(../../zephyr/zephyr-util.cmake)
include(ota-content-header.cmake)

# ==============================================================================
# Declare configuration variables and define constants
Expand Down Expand Up @@ -308,11 +309,32 @@ add_dependencies(chip chip-gn)
# ==============================================================================

if (CONFIG_CHIP_OTA_IMAGE_BUILD)
if (CONFIG_CHIP_OTA_IMAGE_INCLUDE_MCUBOOT)
list(APPEND CHIP_IMAGE_TYPES "mcuboot")
list(APPEND CHIP_IMAGE_PATHS "${APPLICATION_BINARY_DIR}/mcuboot/zephyr/zephyr.bin")
endif()

list(APPEND CHIP_IMAGE_TYPES "app_mcuboot")
list(APPEND CHIP_IMAGE_PATHS "${PROJECT_BINARY_DIR}/app_update.bin")

if (CONFIG_NRF53_UPGRADE_NETWORK_CORE)
list(APPEND CHIP_IMAGE_TYPES "net_mcuboot")
list(APPEND CHIP_IMAGE_PATHS "${PROJECT_BINARY_DIR}/net_core_app_update.bin")
endif()

chip_ota_content_header(chip-ota-content-header
FILE_TYPES ${CHIP_IMAGE_TYPES}
FILE_PATHS ${CHIP_IMAGE_PATHS}
OUTPUT_FILE ${PROJECT_BINARY_DIR}/${CONFIG_CHIP_OTA_IMAGE_FILE_NAME}.content
)

chip_ota_image(chip-ota-image
INPUT_FILES ${PROJECT_BINARY_DIR}/app_update.bin
OUTPUT_FILE ${PROJECT_BINARY_DIR}/matter_ota.bin
INPUT_FILES ${PROJECT_BINARY_DIR}/${CONFIG_CHIP_OTA_IMAGE_FILE_NAME}.content ${CHIP_IMAGE_PATHS}
OUTPUT_FILE ${PROJECT_BINARY_DIR}/${CONFIG_CHIP_OTA_IMAGE_FILE_NAME}
)
add_dependencies(chip-ota-image mcuboot_sign_target)

add_dependencies(chip-ota-content-header mcuboot_sign_target)
add_dependencies(chip-ota-image chip-ota-content-header)
endif()

endif() # CONFIG_CHIP
122 changes: 122 additions & 0 deletions config/nrfconnect/chip-module/make_ota_content_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env python3

#
# Copyright (c) 2022 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""
Utility for creating a Matter OTA content header that describes a list of files
included in a Matter OTA image. The header format is specific to the nRF Connect
platform.
Usage example:
./make_ota_content_header.py \
--file mcuboot mcuboot/zephyr/zephyr.bin \
--file app_mcuboot zephyr/app_update.bin \
out.header
"""

import argparse
import os
import struct
import sys
from enum import IntEnum

sys.path.insert(0, os.path.join(os.path.dirname(
__file__), '../../../src/controller/python'))
from chip.tlv import TLVWriter, uint # noqa: E402


# Context-specific tags describing members of the top-level header TLV structure
class HeaderTag(IntEnum):
FILE_INFO_LIST = 0,


# Context-specific tags describing members of the file info TLV structure
class FileInfoTag(IntEnum):
FILE_ID = 0,
FILE_SIZE = 1,


# File identifiers for all the supported file types
FILE_IDS = dict(
mcuboot=0,
app_mcuboot=1,
net_mcuboot=2,
)


def generate_header_tlv(file_infos: list):
"""
Generate TLV structure describing OTA image contents:
Header ::=
[0] FileInfoList: [
[0]:
[0] FileId: <file_id>
[1] FileSize: <file_size>
[1]:
[0] FileId: <file_id>
[1] FileSize: <file_size>
...
]
"""

writer = TLVWriter()
writer.put(None, {
HeaderTag.FILE_INFO_LIST: [{
FileInfoTag.FILE_ID: uint(file_id),
FileInfoTag.FILE_SIZE: uint(file_size),
} for file_id, file_size in file_infos]
})

return writer.encoding


def generate_header(file_infos: list, output_file: str):
"""
Generate OTA image content header and save it to file
"""

header_tlv = generate_header_tlv(file_infos)
header = struct.pack('<I', len(header_tlv)) + header_tlv

with open(output_file, 'wb') as file:
file.write(header)


def main():
parser = argparse.ArgumentParser(
description='nRF Connect Matter OTA image content header utility',
fromfile_prefix_chars='@')

file_types = '{' + ','.join(FILE_IDS.keys()) + '}'
parser.add_argument('--file', required=True, nargs=2, action='append',
metavar=(file_types, 'path'),
help='File included in Matter OTA image')
parser.add_argument('output_file', help='Path to output header file')

args = parser.parse_args()

file_infos = [(FILE_IDS[type], os.path.getsize(path))
for type, path in args.file]

generate_header(file_infos, args.output_file)


if __name__ == "__main__":
main()
64 changes: 64 additions & 0 deletions config/nrfconnect/chip-module/ota-content-header.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#
# Copyright (c) 2022 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

find_package(Python3 REQUIRED)

#
# Create CMake target for building a Matter OTA content header that describes a list of files
# included in a Matter OTA image. The header format is specific to the nRF Connect platform.
#
# Required arguments:
# FILE_TYPES type, [type...] - list of file types that identify files included in the image.
# Currently recognized file types are:
# * mcuboot - MCUBoot bootloader image in the raw binary format
# * app_mcuboot - application core image in the MCUBoot-defined format
# * net_mcuboot - network core image in the MCUBoot-defined format
# FILE_PATHS path, [path...] - list of paths to files included in the image
# OUTPUT_FILE path - file path to write the newly created Matter OTA content header
#
function(chip_ota_content_header TARGET_NAME)
cmake_parse_arguments(ARG "" "OUTPUT_FILE" "FILE_TYPES;FILE_PATHS" ${ARGN})

if (NOT ARG_FILE_TYPES OR NOT ARG_FILE_PATHS OR NOT ARG_OUTPUT_FILE)
message(FATAL_ERROR "All FILE_TYPES, FILE_PATHS and OUTPUT_FILE arguments must be specified")
endif()

# Prepare make_ota_content_header.py argument list
set(SCRIPT_ARGS)

foreach(file_info IN ZIP_LISTS ARG_FILE_TYPES ARG_FILE_PATHS)
list(APPEND SCRIPT_ARGS
--file
"${file_info_0}"
"${file_info_1}"
)
endforeach()

list(APPEND SCRIPT_ARGS ${ARG_OUTPUT_FILE})

# Convert the argument list to multi-line string
string(REPLACE ";" "\n" SCRIPT_ARGS "${SCRIPT_ARGS}")

# Pass the argument list via file to avoid hitting Windows command-line length limit
file(GENERATE
OUTPUT ${ARG_OUTPUT_FILE}.args
CONTENT ${SCRIPT_ARGS}
)

add_custom_target(${TARGET_NAME} ALL
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/make_ota_content_header.py @${ARG_OUTPUT_FILE}.args
)
endfunction()
24 changes: 20 additions & 4 deletions config/zephyr/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,30 @@ config APP_LINK_WITH_CHIP
config CHIP_OTA_IMAGE_BUILD
bool "Generate OTA image"
help
Enable building OTA (Over-the-air update) image.
Enable building Matter OTA (Over-the-air update) image.

if CHIP_OTA_IMAGE_BUILD

config CHIP_OTA_IMAGE_FILE_NAME
string "OTA image file name"
default "matter.ota"
help
File name of the generated Matter OTA image.

config CHIP_OTA_IMAGE_EXTRA_ARGS
string "OTA image creator extra arguments"
depends on CHIP_OTA_IMAGE_BUILD
help
This option allows one to pass optional arguments to the
ota_image_tool.py script, used for building OTA image.
This option allows one to pass optional arguments to the ota_image_tool.py
script, used for building OTA image.

config CHIP_OTA_IMAGE_INCLUDE_MCUBOOT
bool "Include MCUBoot in OTA image"
depends on BOOTLOADER_MCUBOOT
help
Include MCUBoot bootloader image in the generated Matter OTA image. This
option can be enabled if the device supports the bootloader update.

endif

module = MATTER
module-str = Matter
Expand Down
4 changes: 2 additions & 2 deletions config/zephyr/ota-image.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ function(chip_ota_image TARGET_NAME)

# Pass the argument list via file to avoid hitting Windows command-line length limit
file(GENERATE
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/args-ota-image.tmp
OUTPUT ${ARG_OUTPUT_FILE}.args
CONTENT ${OTA_ARGS}
)

add_custom_target(${TARGET_NAME} ALL
COMMAND ${Python3_EXECUTABLE} ${CHIP_ROOT}/src/app/ota_image_tool.py create @${CMAKE_CURRENT_BINARY_DIR}/args-ota-image.tmp
COMMAND ${Python3_EXECUTABLE} ${CHIP_ROOT}/src/app/ota_image_tool.py create @${ARG_OUTPUT_FILE}.args
)
endfunction()
34 changes: 24 additions & 10 deletions docs/guides/nrfconnect_examples_software_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ To test the DFU over Matter, you need to complete the following steps:

$ scripts/examples/gn_build_example.sh examples/chip-tool out/chiptool 'chip_mdns="platform"'

4. Run OTA Provider application with _app_update.bin_ replaced with the path to
the new firmware image which you wish to provide to the Matter device:
4. Run OTA Provider application with _matter.ota_ replaced with the path to the
Matter OTA image which you wish to provide to the Matter device. Note that
the Matter OTA image is, by default, generated at _zephyr/matter.ota_ in the
example's build directory:

$ out/provider/chip-ota-provider-app -f app_update.bin
$ out/provider/chip-ota-provider-app -f matter.ota

Keep the application running and use another terminal for the remaining
steps.
Expand All @@ -56,16 +58,28 @@ To test the DFU over Matter, you need to complete the following steps:
Operational Dataset. It can be retrieved from the OTBR in case you have
changed the default network settings when forming the network.

$ ./out/chiptool/chip-tool pairing ble-thread 2 hex:000300000f02081111111122222222051000112233445566778899aabbccddeeff01021234 20202021 3840
$ ./out/chiptool/chip-tool pairing ble-thread 2 hex:000300000f02081111111122222222051000112233445566778899aabbccddeeff01021234 20202021 3840

8. Configure the Matter device with the default OTA Provider by running the
following command. The last two arguments are Requestor Node Id and
Requestor Endpoint Id, respectively:

$ ./out/chiptool/chip-tool otasoftwareupdaterequestor write default-ota-providers '[{"fabricIndex": 1, "providerNodeID": 1, "endpoint": 0}]' 2 0

9. Configure the OTA Provider with the access control list (ACL) that grants
_Operate_ privileges to all nodes in the fabric. This is necessary to allow
the nodes to send cluster commands to the OTA Provider:

$ ./out/chiptool/chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [112233], "targets": null}, {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": null, "targets": null}]' 1 0

10. Initiate the DFU procedure in one of the following ways:

8. Initiate the DFU procedure in one of the following ways:

- If you have built the device firmware with `-DCONFIG_CHIP_LIB_SHELL=y`
option, which enables Matter shell commands, run the following command
on the device shell. The numeric arguments are Fabric Index, Provider
Node Id and Provider Endpoint Id, respectively.
on the device shell:

$ matter ota query 1 1 0
$ matter ota query

- Otherwise, use chip-tool to send the Announce OTA Provider command to
the device. The numeric arguments are Provider Node Id, Provider Vendor
Expand All @@ -77,8 +91,8 @@ To test the DFU over Matter, you need to complete the following steps:
Once the device is made aware of the OTA Provider node, it automatically
queries the OTA Provider for a new firmware image.

9. When the firmware image download is complete, reboot the device to apply the
update.
11. When the firmware image download is complete, the device is automatically
rebooted to apply the update.

## Device Firmware Upgrade over Bluetooth LE using smartphone

Expand Down
6 changes: 3 additions & 3 deletions src/lib/shell/commands/Ota.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ Shell::Engine sSubShell;
CHIP_ERROR QueryImageHandler(int argc, char ** argv)
{
VerifyOrReturnError(GetRequestorInstance() != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(argc == 3, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(argc == 0, CHIP_ERROR_INVALID_ARGUMENT);
PlatformMgr().ScheduleWork([](intptr_t) { GetRequestorInstance()->TriggerImmediateQuery(); });
return CHIP_NO_ERROR;
}

CHIP_ERROR ApplyImageHandler(int argc, char ** argv)
{
VerifyOrReturnError(GetRequestorInstance() != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(argc == 3, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(argc == 0, CHIP_ERROR_INVALID_ARGUMENT);
PlatformMgr().ScheduleWork([](intptr_t) { GetRequestorInstance()->ApplyUpdate(); });
return CHIP_NO_ERROR;
}

CHIP_ERROR NotifyImageHandler(int argc, char ** argv)
{
VerifyOrReturnError(GetRequestorInstance() != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(argc == 4, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(argc == 1, CHIP_ERROR_INVALID_ARGUMENT);

const intptr_t version = static_cast<intptr_t>(strtoul(argv[0], nullptr, 10));

Expand Down
2 changes: 2 additions & 0 deletions src/platform/nrfconnect/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ static_library("nrfconnect") {

if (chip_enable_ota_requestor) {
sources += [
"OTAImageContentHeader.cpp",
"OTAImageContentHeader.h",
"OTAImageProcessorImpl.cpp",
"OTAImageProcessorImpl.h",
]
Expand Down
Loading

0 comments on commit 4668421

Please sign in to comment.