1# Copyright (c) 2023 Nordic Semiconductor ASA 2# 3# SPDX-License-Identifier: Apache-2.0 4from __future__ import annotations 5 6import logging 7from pathlib import Path 8 9import pytest 10from twister_harness import DeviceAdapter, MCUmgr, Shell 11from twister_harness.helpers.utils import find_in_config, match_lines, match_no_lines 12from utils import check_with_mcumgr_command, check_with_shell_command 13from west_sign_wrapper import west_sign_with_imgtool 14 15logger = logging.getLogger(__name__) 16 17 18def create_signed_image(build_dir: Path, app_build_dir: Path, version: str) -> Path: 19 image_to_test = Path(build_dir) / 'test_{}.signed.bin'.format( 20 version.replace('.', '_').replace('+', '_')) 21 origin_key_file = find_in_config( 22 Path(build_dir) / 'mcuboot' / 'zephyr' / '.config', 23 'CONFIG_BOOT_SIGNATURE_KEY_FILE' 24 ) 25 west_sign_with_imgtool( 26 build_dir=Path(app_build_dir), 27 output_bin=image_to_test, 28 key_file=Path(origin_key_file), 29 version=version 30 ) 31 assert image_to_test.is_file() 32 return image_to_test 33 34 35def get_upgrade_string_to_verify(build_dir: Path) -> str: 36 sysbuild_config = Path(build_dir) / 'zephyr' / '.config' 37 if find_in_config(sysbuild_config, 'SB_CONFIG_MCUBOOT_MODE_SWAP_USING_OFFSET'): 38 return 'Starting swap using offset algorithm' 39 return 'Starting swap using move algorithm' 40 41 42def clear_buffer(dut: DeviceAdapter) -> None: 43 disconnect = False 44 if not dut.is_device_connected(): 45 dut.connect() 46 disconnect = True 47 dut.clear_buffer() 48 if disconnect: 49 dut.disconnect() 50 51 52def test_upgrade_with_confirm(dut: DeviceAdapter, shell: Shell, mcumgr: MCUmgr): 53 """ 54 Verify that the application can be updated 55 1) Device flashed with MCUboot and an application that contains SMP server 56 2) Prepare an update of an application containing the SMP server 57 3) Upload the application update to slot 1 using mcumgr 58 4) Flag the application update in slot 1 as 'pending' by using mcumgr 'test' 59 5) Restart the device, verify that swapping process is initiated 60 6) Verify that the updated application is booted 61 7) Confirm the image using mcumgr 62 8) Restart the device, and verify that the new application is still booted 63 """ 64 logger.info('Prepare upgrade image') 65 new_version = '0.0.2+0' 66 image_to_test = create_signed_image(dut.device_config.build_dir, 67 dut.device_config.app_build_dir, new_version) 68 69 logger.info('Upload image with mcumgr') 70 dut.disconnect() 71 mcumgr.image_upload(image_to_test) 72 73 logger.info('Test uploaded APP image') 74 second_hash = mcumgr.get_hash_to_test() 75 mcumgr.image_test(second_hash) 76 clear_buffer(dut) 77 mcumgr.reset_device() 78 79 dut.connect() 80 output = dut.readlines_until('Launching primary slot application') 81 upgrade_string_to_verify = get_upgrade_string_to_verify(dut.device_config.build_dir) 82 match_lines(output, [ 83 'Swap type: test', 84 upgrade_string_to_verify 85 ]) 86 logger.info('Verify new APP is booted') 87 check_with_shell_command(shell, new_version, swap_type='test') 88 dut.disconnect() 89 check_with_mcumgr_command(mcumgr, new_version) 90 91 logger.info('Confirm the image') 92 mcumgr.image_confirm(second_hash) 93 mcumgr.reset_device() 94 95 dut.connect() 96 output = dut.readlines_until('Launching primary slot application') 97 match_no_lines(output, [ 98 upgrade_string_to_verify 99 ]) 100 logger.info('Verify new APP is still booted') 101 check_with_shell_command(shell, new_version) 102 103 104def test_upgrade_with_revert(dut: DeviceAdapter, shell: Shell, mcumgr: MCUmgr): 105 """ 106 Verify that MCUboot will roll back an image that is not confirmed 107 1) Device flashed with MCUboot and an application that contains SMP server 108 2) Prepare an update of an application containing the SMP server 109 3) Upload the application update to slot 1 using mcumgr 110 4) Flag the application update in slot 1 as 'pending' by using mcumgr 'test' 111 5) Restart the device, verify that swapping process is initiated 112 6) Verify that the updated application is booted 113 7) Reset the device without confirming the image 114 8) Verify that MCUboot reverts update 115 """ 116 origin_version = find_in_config( 117 Path(dut.device_config.app_build_dir) / 'zephyr' / '.config', 118 'CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION' 119 ) 120 logger.info('Prepare upgrade image') 121 new_version = '0.0.3+0' 122 image_to_test = create_signed_image(dut.device_config.build_dir, 123 dut.device_config.app_build_dir, new_version) 124 125 logger.info('Upload image with mcumgr') 126 dut.disconnect() 127 mcumgr.image_upload(image_to_test) 128 129 logger.info('Test uploaded APP image') 130 second_hash = mcumgr.get_hash_to_test() 131 mcumgr.image_test(second_hash) 132 clear_buffer(dut) 133 mcumgr.reset_device() 134 135 dut.connect() 136 output = dut.readlines_until('Launching primary slot application') 137 upgrade_string_to_verify = get_upgrade_string_to_verify(dut.device_config.build_dir) 138 match_lines(output, [ 139 'Swap type: test', 140 upgrade_string_to_verify 141 ]) 142 logger.info('Verify new APP is booted') 143 check_with_shell_command(shell, new_version, swap_type='test') 144 dut.disconnect() 145 check_with_mcumgr_command(mcumgr, new_version) 146 147 logger.info('Revert images') 148 mcumgr.reset_device() 149 150 dut.connect() 151 output = dut.readlines_until('Launching primary slot application') 152 match_lines(output, [ 153 'Swap type: revert', 154 upgrade_string_to_verify 155 ]) 156 logger.info('Verify that MCUboot reverts update') 157 check_with_shell_command(shell, origin_version) 158 159 160@pytest.mark.parametrize( 161 'key_file', [None, 'root-ec-p256.pem'], 162 ids=[ 163 'no_key', 164 'invalid_key' 165 ]) 166def test_upgrade_signature(dut: DeviceAdapter, shell: Shell, mcumgr: MCUmgr, key_file): 167 """ 168 Verify that the application is not updated when app is not signed or signed with invalid key 169 1) Device flashed with MCUboot and an application that contains SMP server 170 2) Prepare an update of an application containing the SMP server that has 171 been signed: 172 a) without any key 173 b) with a different key than MCUboot was compiled with 174 3) Upload the application update to slot 1 using mcumgr 175 4) Flag the application update in slot 1 as 'pending' by using mcumgr 'test' 176 5) Restart the device, verify that swap is not started 177 """ 178 if key_file: 179 origin_key_file = find_in_config( 180 Path(dut.device_config.build_dir) / 'mcuboot' / 'zephyr' / '.config', 181 'CONFIG_BOOT_SIGNATURE_KEY_FILE' 182 ).strip('"\'') 183 key_file = Path(origin_key_file).parent / key_file 184 assert key_file.is_file() 185 assert not key_file.samefile(origin_key_file) 186 image_to_test = Path(dut.device_config.build_dir) / 'test_invalid_key.bin' 187 logger.info('Sign second image with an invalid key') 188 else: 189 image_to_test = Path(dut.device_config.build_dir) / 'test_no_key.bin' 190 logger.info('Sign second imagewith no key') 191 192 west_sign_with_imgtool( 193 build_dir=Path(dut.device_config.app_build_dir), 194 output_bin=image_to_test, 195 key_file=key_file, 196 version='0.0.3+4' # must differ from the origin version, if not then hash is not updated 197 ) 198 assert image_to_test.is_file() 199 200 logger.info('Upload image with mcumgr') 201 dut.disconnect() 202 mcumgr.image_upload(image_to_test) 203 204 logger.info('Test uploaded APP image') 205 second_hash = mcumgr.get_hash_to_test() 206 mcumgr.image_test(second_hash) 207 208 logger.info('Verify that swap is not started') 209 clear_buffer(dut) 210 mcumgr.reset_device() 211 212 dut.connect() 213 output = dut.readlines_until('Launching primary slot application') 214 upgrade_string_to_verify = get_upgrade_string_to_verify(dut.device_config.build_dir) 215 match_no_lines(output, [upgrade_string_to_verify]) 216 match_lines(output, ['Image in the secondary slot is not valid']) 217