blob: 13b1895fac6acf7ad7fa52b2592bad310eacb6e4 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2023 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.
#
'''This file should contain a way to generate custom OTA payloads.
The format of the custom payload is the following:
| Total size of TLVs | TLV1 | ... | TLVn |
The OTA payload can then be used to generate an OTA image file, which
will be parsed by the OTA image processor. The total size of TLVs is
needed as input for a TLVReader.
Currently, this script only supports Certification Declaration update,
but it could be modified to support all factory data fields.
'''
import argparse
import logging
import os
import sys
import ota_image_tool
from chip.tlv import TLVWriter
from custom import CertDeclaration, DacCert, DacPKey, PaiCert
from default import InputArgument
from generate import set_logger
sys.path.insert(0, os.path.join(
os.path.dirname(__file__), '../factory_data_generator'))
sys.path.insert(0, os.path.join(
os.path.dirname(__file__), '../../../../src/controller/python'))
sys.path.insert(0, os.path.join(
os.path.dirname(__file__), '../../../../src/app/'))
OTA_FACTORY_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_factory_tlv_temp.bin")
OTA_APP_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_app_tlv_temp.bin")
class TAG:
APPLICATION = 1
BOOTLOADER = 2
FACTORY_DATA = 3
def generate_header(tag: int, length: int):
header = bytearray(tag.to_bytes(4, "little"))
header += bytearray(length.to_bytes(4, "little"))
return header
def generate_factory_data(args: object):
"""
Generate custom OTA payload from InputArgument derived objects. The payload is
written in a temporary file that will be appended to args.input_files.
"""
fields = dict()
if args.dac_key is not None:
args.dac_key.generate_private_key(args.dac_key_password)
data = [obj for key, obj in vars(args).items() if isinstance(obj, InputArgument)]
for arg in sorted(data, key=lambda x: x.key()):
fields.update({arg.key(): arg.encode()})
if fields:
writer = TLVWriter()
writer.put(None, fields)
payload = generate_header(TAG.FACTORY_DATA, len(writer.encoding))
payload += writer.encoding
with open(OTA_FACTORY_TLV_TEMP, "wb") as _handle:
_handle.write(payload)
logging.info(f"Factory data payload size: {len(payload)}")
def generate_app(args: object):
version = args.app_version.to_bytes(4, "little")
versionStr = bytearray(args.app_version_str, "ascii") + bytearray(64 - len(args.app_version_str))
buildDate = bytearray(args.app_build_date, "ascii") + bytearray(64 - len(args.app_build_date))
descriptor = version + versionStr + buildDate
file_size = os.path.getsize(args.app_input_file)
payload = generate_header(TAG.APPLICATION, len(descriptor) + file_size) + descriptor
with open(OTA_APP_TLV_TEMP, "wb") as _handle:
_handle.write(payload)
logging.info(f"Application payload size: {len(payload)}")
def generate_bootloader(args: object):
# TODO
pass
def show_payload(args: object):
"""
Parse and present OTA custom payload in human-readable form.
"""
# TODO: implement to show current TLVs
pass
def create_image(args: object):
ota_image_tool.validate_header_attributes(args)
input_files = list()
if args.factory_data:
generate_factory_data(args)
input_files += [OTA_FACTORY_TLV_TEMP]
if args.app_input_file is not None:
generate_app(args)
input_files += [OTA_APP_TLV_TEMP, args.app_input_file]
print(input_files)
args.input_files = input_files
ota_image_tool.generate_image(args)
if args.factory_data:
os.remove(OTA_FACTORY_TLV_TEMP)
if args.app_input_file is not None:
os.remove(OTA_APP_TLV_TEMP)
def main():
"""
This function is a modified version of ota_image_tool.py main function.
The wrapper version defines a new set of args, which are used to generate
TLV data that will be embedded in the final OTA payload.
"""
def any_base_int(s): return int(s, 0)
set_logger()
parser = argparse.ArgumentParser(
description='Matter OTA (Over-the-air update) image utility', fromfile_prefix_chars='@')
subcommands = parser.add_subparsers(
dest='subcommand', title='valid subcommands', required=True)
create_parser = subcommands.add_parser('create', help='Create OTA image')
create_parser.add_argument('-v', '--vendor-id', type=any_base_int,
required=True, help='Vendor ID')
create_parser.add_argument('-p', '--product-id', type=any_base_int,
required=True, help='Product ID')
create_parser.add_argument('-vn', '--version', type=any_base_int,
required=True, help='Software version (numeric)')
create_parser.add_argument('-vs', '--version-str', required=True,
help='Software version (string)')
create_parser.add_argument('-da', '--digest-algorithm', choices=ota_image_tool.DIGEST_ALL_ALGORITHMS,
required=True, help='Digest algorithm')
create_parser.add_argument('-mi', '--min-version', type=any_base_int,
help='Minimum software version that can be updated to this image')
create_parser.add_argument('-ma', '--max-version', type=any_base_int,
help='Maximum software version that can be updated to this image')
create_parser.add_argument(
'-rn', '--release-notes', help='Release note URL')
create_parser.add_argument('input_files', nargs='*',
help='Path to input image payload file')
create_parser.add_argument('output_file', help='Path to output image file')
create_parser.add_argument('-app', '--app-input-file',
help='Path to input application image payload file')
create_parser.add_argument('--app-version', type=any_base_int,
help='Application Software version (numeric)')
create_parser.add_argument('--app-version-str', type=str,
help='Application Software version (string)')
create_parser.add_argument('--app-build-date', type=str,
help='Application build date (string)')
create_parser.add_argument('-bl', '--bootloader-input-file',
help='Path to input bootloader image payload file')
create_parser.add_argument('--bl-version', type=any_base_int,
help='Bootloader Software version (numeric)')
create_parser.add_argument('--bl-version-str', type=str,
help='Bootloader Software version (string)')
create_parser.add_argument('--bl-build-date', type=str,
help='Bootloader build date (string)')
create_parser.add_argument('--bl-load-addr', type=any_base_int,
help='Bootloader load address (numeric)')
# Factory data specific arguments. Will be used to generate the TLV payload.
create_parser.add_argument('-fd', '--factory-data', action='store_true',
help='If found, enable factory data payload generation.')
create_parser.add_argument("--cert_declaration", type=CertDeclaration,
help="[path] Path to Certification Declaration in DER format")
create_parser.add_argument("--dac_cert", type=DacCert,
help="[path] Path to DAC certificate in DER format")
create_parser.add_argument("--dac_key", type=DacPKey,
help="[path] Path to DAC key in DER format")
create_parser.add_argument("--dac_key_password", type=str,
help="[path] Password to decode DAC Key if available")
create_parser.add_argument("--pai_cert", type=PaiCert,
help="[path] Path to PAI certificate in DER format")
show_parser = subcommands.add_parser('show', help='Show OTA image info')
show_parser.add_argument('image_file', help='Path to OTA image file')
extract_tool = subcommands.add_parser('extract', help='Remove the OTA header from an image file')
extract_tool.add_argument('image_file', help='Path to OTA image file with header')
extract_tool.add_argument('output_file', help='Path to put the output file (no header)')
change_tool = subcommands.add_parser('change_header', help='Change the specified values in the header')
change_tool.add_argument('-v', '--vendor-id', type=any_base_int,
help='Vendor ID')
change_tool.add_argument('-p', '--product-id', type=any_base_int,
help='Product ID')
change_tool.add_argument('-vn', '--version', type=any_base_int,
help='Software version (numeric)')
change_tool.add_argument('-vs', '--version-str',
help='Software version (string)')
change_tool.add_argument('-da', '--digest-algorithm', choices=ota_image_tool.DIGEST_ALL_ALGORITHMS,
help='Digest algorithm')
change_tool.add_argument('-mi', '--min-version', type=any_base_int,
help='Minimum software version that can be updated to this image')
change_tool.add_argument('-ma', '--max-version', type=any_base_int,
help='Maximum software version that can be updated to this image')
change_tool.add_argument(
'-rn', '--release-notes', help='Release note URL')
change_tool.add_argument('image_file',
help='Path to input OTA file')
change_tool.add_argument('output_file', help='Path to output OTA file')
args = parser.parse_args()
if args.subcommand == 'create':
create_image(args)
elif args.subcommand == 'show':
ota_image_tool.show_header(args)
show_payload(args)
elif args.subcommand == 'extract':
ota_image_tool.remove_header(args)
elif args.subcommand == 'change_header':
ota_image_tool.update_header_args(args)
if __name__ == "__main__":
main()