diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..8d273061 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: local + hooks: + - id: copyright-year + name: Check this year's Copyright + entry: python tools/checker_copyright_year.py + language: system diff --git a/.pylintrc b/.pylintrc index ee96c0bb..2bab2bf9 100644 --- a/.pylintrc +++ b/.pylintrc @@ -3,7 +3,7 @@ # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. -extension-pkg-whitelist= +extension-pkg-whitelist=hid # Add files or directories to the blacklist. They should be base names, not # paths. diff --git a/README.md b/README.md index b0c74547..39802b3e 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,10 @@ Architecure - SB - Secure Boot File [module](https://spsdk.readthedocs.io/en/latest/api/sbfile.html). - MBI - Master Boot Image [module](https://spsdk.readthedocs.io/en/latest/api/image.html). - Crypto - Cryptography [module](https://spsdk.readthedocs.io/en/latest/api/crypto.html). - - DAT - Debug Authentication [module](https://spsdk.readthedocs.io/en/latest/api/dat.html). - **Protocol Layer** packs or unpacks messages and images into a protocol defined by the required device counterpart. - BL Host [module](https://spsdk.readthedocs.io/en/latest/api/mboot.html). - SDP Host [module](https://spsdk.readthedocs.io/en/latest/api/sdp.html). - - Debug Mailbox - **Communication Layer** links SPSDK and connected devices. @@ -65,14 +63,22 @@ Usage - See [examples](examples) directory - See [application](spsdk/apps) directory +--- +**i.Mx RT 1050** + +To run examples using i.MX RT 1050 you need to download a flashloader: +- Go to: https://www.nxp.com/webapp/sps/download/license.jsp?colCode=IMX-RT1050-FLASHLOADER +- Review the license agreement, download and unzip the package +- Convert the elf file into bin (For this operation you need to have MCUXpresso IDE, IAR or Keil) + - run ```python tools\flashloader_converter.py --elf-path --ide-type --ide-path 3.5 and <3.9 interpreter, old version 2.x is not supported ! -- requirements.txt - - list of requirements for running SPSDK core + apps -- requirements-develop.txt - - requirements needed for development (running tests, checking coding style) -- docs/requirements.txt - - requirements needed for generating docs +The core dependencies are included in requirements.txt file. + +The dependencies for the development and testing are included in requirements-develop.txt. diff --git a/SW_Content_Register_SPSDK.txt b/SW_Content_Register_SPSDK.txt index 24f08da8..e189cb8c 100644 --- a/SW_Content_Register_SPSDK.txt +++ b/SW_Content_Register_SPSDK.txt @@ -1,6 +1,6 @@ NXP Software Content Register -Package: NXP SPSDK 1.2.0 +Package: NXP SPSDK 1.3.0 Outgoing License: BSD-3-Clause License Files: LICENSE Type of content: Source code diff --git a/codecheck.cmd b/codecheck.cmd index 2b3871cc..86716141 100644 --- a/codecheck.cmd +++ b/codecheck.cmd @@ -17,11 +17,10 @@ IF EXIST venv\Scripts\activate.bat call venv\Scripts\activate.bat IF EXIST %CD%\reports\htmlcov DEL %CD%\reports\htmlcov /Q pytest --cov=spsdk --cov-report=term --cov-report=html:reports/htmlcov/ --cov-branch --cov-report=xml:reports/coverage.xml . @rem WITH DURATIONS pytest --durations=0 --cov=spsdk --cov-report=term --cov-report=html:reports/htmlcov/ --cov-branch --cov-report=xml:reports/coverage.xml --log-cli-level=WARN . -@if errorlevel 1 goto END +@if errorlevel 1 echo "<<<### TESTS FAILED #################################################################>>>" @rem ------------------------------------------------------- @rem mypy: python typing tests (modules tested: spsdk + examples) -mypy spsdk > %CD%\reports\mypy.txt -@rem mypy spsdk examples >%CD%\reports\mypy.txt +mypy spsdk examples >%CD%\reports\mypy.txt @if errorlevel 1 echo "<<<### MYPY PROBLEM DETECTED ########################################################>>>" @rem ------------------------------------------------------- @rem pylint (spsdk module only, errors only) @@ -46,4 +45,3 @@ radon cc --min C spsdk > %CD%\reports\radonC.txt @rem gitcov (coverage of changed files) python tools\gitcov.py --verbose --coverage-report reports\coverage.xml @if %errorlevel% gtr 0 echo "<<<### GIT-COV ERROR DETECTED #################################################>>>" -:END \ No newline at end of file diff --git a/docs/_static/images/SPSDK-Architecture.png b/docs/_static/images/SPSDK-Architecture.png index 787bcd7c..34e6aedf 100644 Binary files a/docs/_static/images/SPSDK-Architecture.png and b/docs/_static/images/SPSDK-Architecture.png differ diff --git a/docs/api/crypto.rst b/docs/api/crypto.rst index f8793967..7d364f76 100644 --- a/docs/api/crypto.rst +++ b/docs/api/crypto.rst @@ -45,4 +45,3 @@ Crypto module loading helper functions :members: :undoc-members: :show-inheritance: - diff --git a/docs/api/dat.rst b/docs/api/dat.rst index 1236e219..ceebcf2f 100644 --- a/docs/api/dat.rst +++ b/docs/api/dat.rst @@ -3,6 +3,13 @@ Debug Authentication API Debug Authentication module provides debugging for securely provisioned devices. +DAT module general information +-------------------------------- +.. automodule:: spsdk.dat + :members: + :undoc-members: + :show-inheritance: + Module for generating debug credentials ---------------------------------------- @@ -49,4 +56,12 @@ Module with common utils for DAT module .. automodule:: spsdk.dat.utils :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + + +Module with the shadow registers control DAT support file +---------------------------------------------------------- +.. automodule:: spsdk.dat.shadow_regs + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/debuggers.rst b/docs/api/debuggers.rst new file mode 100644 index 00000000..a75b087c --- /dev/null +++ b/docs/api/debuggers.rst @@ -0,0 +1,37 @@ +Debuggers API +=============== + +Debuggers module provides wrappers for various types of debuggers. + + +Module for DebugMailbox Debug probes support +----------------------------------------------- +.. automodule:: spsdk.debuggers.debug_probe + :members: + :special-members: DebugProbe + :undoc-members: + :show-inheritance: + + +Module for DebugMailbox Jlink Debug probes support +---------------------------------------------------- +.. automodule:: spsdk.debuggers.debug_probe_jlink + :members: + :undoc-members: + :show-inheritance: + + +Module for DebugMailbox Pemicro Debug probes support +----------------------------------------------------- +.. automodule:: spsdk.debuggers.debug_probe_pemicro + :members: + :undoc-members: + :show-inheritance: + + +Module for DebugMailbox PyOCD Debug probes support +----------------------------------------------------- +.. automodule:: spsdk.debuggers.debug_probe_pyocd + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst deleted file mode 100644 index b7f96772..00000000 --- a/docs/api/exceptions.rst +++ /dev/null @@ -1,17 +0,0 @@ -Exceptions -========== - -.. automodule:: spsdk.exceptions - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: spsdk.mboot.exceptions - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: spsdk.sdp.exceptions - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/image.rst b/docs/api/image.rst index 01cef6bb..0b7c64d6 100644 --- a/docs/api/image.rst +++ b/docs/api/image.rst @@ -56,3 +56,25 @@ TrustZone :show-inheritance: +Support for BEE encryption for RT10xx devices +---------------------------------------------- +.. automodule:: spsdk.image.bee + :members: + :undoc-members: + :show-inheritance: + + +Support for KeyStore used in MasterBootImage +--------------------------------------------- +.. automodule:: spsdk.image.keystore + :members: + :undoc-members: + :show-inheritance: + + +Master Boot Image +------------------- +.. automodule:: spsdk.image.mbimg + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/docs/api/mboot.rst b/docs/api/mboot.rst index 07ec3de0..259a5f72 100644 --- a/docs/api/mboot.rst +++ b/docs/api/mboot.rst @@ -49,5 +49,9 @@ MBoot ErrorCodes :undoc-members: :show-inheritance: - - +MBoot Exceptions +----------------- +.. automodule:: spsdk.mboot.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/pfr.rst b/docs/api/pfr.rst index 4ce8a38b..747d8314 100644 --- a/docs/api/pfr.rst +++ b/docs/api/pfr.rst @@ -13,4 +13,17 @@ Protected Flash Region :special-members: CMPA, CFPA, BaseConfigArea :exclude-members: CONFIG_DIR :undoc-members: + :show-inheritance: + + +Protected Flash Region Checker for brick-conditions +---------------------------------------------------- +.. automodule:: spsdk.pfr.processor + :members: + :exclude-members: MyTransformer + :undoc-members: + :show-inheritance: +.. automodule:: spsdk.pfr.translator + :members: + :undoc-members: :show-inheritance: \ No newline at end of file diff --git a/docs/api/sbfile.rst b/docs/api/sbfile.rst index 7d7bc140..08875eed 100644 --- a/docs/api/sbfile.rst +++ b/docs/api/sbfile.rst @@ -3,37 +3,57 @@ SBFile Module API Module implementing SBFile. - SBFile Commands --------------- - .. automodule:: spsdk.sbfile.commands :members: :undoc-members: :show-inheritance: + SBFile Headers --------------- - .. automodule:: spsdk.sbfile.headers :members: :undoc-members: :show-inheritance: + SBFile Segments --------------- - .. automodule:: spsdk.sbfile.sections :members: :undoc-members: :show-inheritance: + SBFile Images --------------- - .. automodule:: spsdk.sbfile.images :members: :undoc-members: :show-inheritance: +SBFile Version 3.1. +-------------------- +.. automodule:: spsdk.sbfile.sb31 + :members: + :undoc-members: + :show-inheritance: + + +SBFile Version 3.1. functions +------------------------------- +.. automodule:: spsdk.sbfile.sb31.functions + :members: + :undoc-members: + :show-inheritance: + + +SBFile Version 3.1. images +---------------------------- +.. automodule:: spsdk.sbfile.sb31.images + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/sdp.rst b/docs/api/sdp.rst index 7f2b168c..5b8c0019 100644 --- a/docs/api/sdp.rst +++ b/docs/api/sdp.rst @@ -42,4 +42,12 @@ SDP Error/Status codes .. automodule:: spsdk.sdp.error_codes :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + + +SDP Exceptions +---------------- +.. automodule:: spsdk.sdp.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/utils.rst b/docs/api/utils.rst index 82af296c..8f68bf93 100644 --- a/docs/api/utils.rst +++ b/docs/api/utils.rst @@ -34,4 +34,38 @@ Serial Proxy .. automodule:: spsdk.utils.serial_proxy :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + + +USB Filter +------------- + +.. automodule:: spsdk.utils.usbfilter + :members: + :undoc-members: + :show-inheritance: + + +Registers descriptions with support for XML files +-------------------------------------------------- + +.. automodule:: spsdk.utils.registers + :members: + :undoc-members: + :show-inheritance: + + +USB Device Scanner +--------------------- +.. automodule:: spsdk.utils.nxpdevscan + :members: + :undoc-members: + :show-inheritance: + + +Device description +-------------------- +.. automodule:: spsdk.utils.devicedescription + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/apps/overview.rst b/docs/apps/overview.rst index 2f455b24..27d8939b 100644 --- a/docs/apps/overview.rst +++ b/docs/apps/overview.rst @@ -14,15 +14,54 @@ Options and flags for each application and their respective sub-commands are ava List of applications ==================== -nxpkeygen +blhost +------ + +The blhost application is a utility for communication with bootloader on target. + +It allows user to: + +- apply configuration block at internal memory address to memory with ID +- program one word of OCOTP Field. +- read one word of OCOTP Field +- erase region of the flash +- erase all flash according to memory id +- fill memory with pattern +- get bootloader-specific property +- write/read memory +- reset the device +- generate the Key Blob for a given DEK +- receive SB file +- load a boot image to the device +- key provisioning +- execute application at address +- apply configuration block at internal memory address +- invoke code + +For complete list run: ``blhost --help`` + + +elftosb --------- -The nxpkeygen application allows user to: +The tool for generating TrustZone, MasterBootImage and SecureBinary images. -- generate RSA/ECC key pairs (private and public) with various key's attributes -- generate debug credential files based on YAML configuration file +- generate TrustZone +- generate MasterBootImage +- generate SecureBinary + +For complete list run: ``elftosb --help`` + + +nxpcertgen +------------ + +The nxpcertgen application allows user to generate the self-signed x.509 certificate +with properties given in json configuration file. The certificates are self-signed +and support only BasicConstrains (ca, path_length). + +For complete list of operations run: ``nxpcertgen --help`` -For complete list of operations run: ``nxpkeygen --help`` nxpdebugmbox ------------ @@ -37,6 +76,25 @@ The nxpkeygen application allows user to: For complete list of operations run: ``nxpdebugmbox --help`` +nxpdevscan +----------- + +The nxpdevscan application allows user to list all connected USB and UART devices. + +For complete list of operations run: ``nxpdevscan --help`` + + +nxpkeygen +---------- + +The nxpkeygen application allows user to: + +- generate RSA/ECC key pairs (private and public) with various key's attributes +- generate debug credential files based on YAML configuration file + +For complete list of operations run: ``nxpkeygen --help`` + + pfr ---- @@ -90,33 +148,18 @@ Note: THIS IS AN EXPERIMENTAL UTILITY! USE WITH CAUTION !!! For complete list of operations run: ``sdphosts --help`` -blhost ------- - -The blhost application is a utility for communication with bootloader on target. +shadowregs +----------- +The shadowreg application is a utility for Shadow Registers controlling. It allows user to: -- apply configuration block at internal memory address to memory with ID -- program one word of OCOTP Field. -- read one word of OCOTP Field -- erase region of the flash -- fill memory with pattern -- get bootloader-specific property -- write/read memory -- reset the device -- generate the Key Blob for a given DEK - -For complete list run: ``blhost --help`` +- save current state of shadow registers to YML file +- load new state of shadow registers from YML file into microcontroller +- print all shadow registers including theirs current values +- print the current value of one shadow register +- set a value of one shadow register defined by parameter +- reset connected device +- print a list of supported devices - -elftosb ---------- - -The tool for generating TrustZone, MasterBootImage and SecureBinary images. - -- generate TrustZone -- generate MasterBootImage -- generate SecureBinary - -For complete list run: ``elftosb --help`` +For complete list of operations run: ``shadowregs --help`` diff --git a/docs/conf.py b/docs/conf.py index f696ec38..013245c7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,10 @@ # -- General configuration --------------------------------------------------- master_doc = 'index' source_suffix = ['.rst', '.md'] -autodoc_mock_imports = ['hidapi'] +autodoc_mock_imports = ['hidapi', 'hid'] + +autoclass_content = 'both' + # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom diff --git a/docs/index.rst b/docs/index.rst index adc8962e..456b176b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,12 +15,12 @@ api/crypto api/dat + api/debuggers api/image api/mboot api/pfr api/sbfile api/sdp - api/exceptions api/utils diff --git a/docs/spsdk.md b/docs/spsdk.md index 531b07df..78fd4d8e 100644 --- a/docs/spsdk.md +++ b/docs/spsdk.md @@ -12,6 +12,7 @@ foundation from quick customer prototyping up to production deployment. Is follo - **Crypto** - Support for key's and certificate's operations - **DAT** - Covering functionality of `debug authentication` tool +- **Debuggers** - Support for DebugMailbox Debug probes - **Image** - Covering functionality of `srktool`, `dcdgen`, `mkimage` and other similar tools - **MBoot** - Covering functionality of `blhost` tool - **PFR** - Support for configuration of Protected Flash Region areas (CMPA, CFPA) diff --git a/examples/__init__.py b/examples/__init__.py index 58b023d6..35187d6d 100644 --- a/examples/__init__.py +++ b/examples/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2021 NXP # # SPDX-License-Identifier: BSD-3-Clause diff --git a/examples/crypto/__init__.py b/examples/crypto/__init__.py index d8872d12..0249fd6b 100644 --- a/examples/crypto/__init__.py +++ b/examples/crypto/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2021 NXP # # SPDX-License-Identifier: BSD-3-Clause diff --git a/examples/crypto/chain_of_certificates.py b/examples/crypto/chain_of_certificates.py index 0b487580..61f94ca2 100644 --- a/examples/crypto/chain_of_certificates.py +++ b/examples/crypto/chain_of_certificates.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -14,7 +14,7 @@ from os import path from spsdk.crypto import generate_certificate, save_crypto_item, \ - load_private_key, x509, Encoding, load_public_key + load_private_key, x509, Encoding, load_public_key, RSAPublicKey, RSAPrivateKey, generate_name_struct # Certificates' structure @@ -27,20 +27,6 @@ # chain_crt2 -def gen_name_struct(name: str) -> x509.Name: - """Set the issuer/subject distinguished name. - - :param name: name of issuer/subject - :return: ordered list of attributes of certificate - """ - return x509.Name([ - x509.NameAttribute(x509.oid.NameOID.COUNTRY_NAME, "CZ"), - x509.NameAttribute(x509.oid.NameOID.STATE_OR_PROVINCE_NAME, "RpR"), - x509.NameAttribute(x509.oid.NameOID.LOCALITY_NAME, "1maje"), - x509.NameAttribute(x509.oid.NameOID.ORGANIZATION_NAME, name) - ]) - - # pylint: disable=too-many-locals def main() -> None: """Main function.""" @@ -49,9 +35,11 @@ def main() -> None: os.makedirs(data_dir, exist_ok=True) # load private key from data folder private_key_2048_ca = load_private_key(path.join(data_dir, "ca_privatekey_rsa2048.pem")) + assert isinstance(private_key_2048_ca, RSAPrivateKey) # load associated public key public_key_2048_ca = load_public_key(path.join(data_dir, "ca_publickey_rsa2048.pem")) - subject = issuer = gen_name_struct("first") + assert isinstance(public_key_2048_ca, RSAPublicKey) + subject = issuer = generate_name_struct("first", "CZ") # generate CA certificate (self-signed certificate) ca_cert = generate_certificate(subject=subject, issuer=issuer, subject_public_key=public_key_2048_ca, issuer_private_key=private_key_2048_ca, serial_number=0x1, @@ -62,8 +50,9 @@ def main() -> None: print("The CA Certificate was created in der and pem format.") # Create first chain certificate signed by private key of the CA certificate - subject_crt1 = gen_name_struct("second") + subject_crt1 = generate_name_struct("second", "CZ") public_key_2048_subject = load_public_key(path.join(data_dir, "crt_publickey_rsa2048.pem")) + assert isinstance(public_key_2048_subject, RSAPublicKey) crt1 = generate_certificate(subject=subject_crt1, issuer=issuer, subject_public_key=public_key_2048_subject, issuer_private_key=private_key_2048_ca, serial_number=0x3cc30000babadeda, if_ca=False, duration=20 * 365) @@ -73,9 +62,11 @@ def main() -> None: print("The first chain certificate (signed by CA certificate) was created in der and pem format.") # First chain certificate signed by private key of the CA certificate - subject_crt2 = gen_name_struct("third") + subject_crt2 = generate_name_struct("third", "CZ") private_key_2048_subject_1 = load_private_key(path.join(data_dir, "chain_privatekey_rsa2048.pem")) + assert isinstance(private_key_2048_subject_1, RSAPrivateKey) public_key_2048_subject_1 = load_public_key(path.join(data_dir, "chain_publickey_rsa2048.pem")) + assert isinstance(public_key_2048_subject_1, RSAPublicKey) crt1 = generate_certificate(subject=subject_crt2, issuer=issuer, subject_public_key=public_key_2048_subject_1, issuer_private_key=private_key_2048_ca, serial_number=0x2, if_ca=True, duration=20 * 365, path_length=3) @@ -85,9 +76,10 @@ def main() -> None: print("The first chain certificate (signed by CA certificate) was created in der and pem format.") # Create first chain certificate signed by private key of first certificate - subject_crt3 = gen_name_struct("fourth") + subject_crt3 = generate_name_struct("fourth", "CZ") issuer_crt3 = subject_crt2 public_key_2048_subject_2 = load_public_key(path.join(data_dir, "chain_crt2_publickey_rsa2048.pem")) + assert isinstance(public_key_2048_subject_2, RSAPublicKey) crt1 = generate_certificate(subject=subject_crt3, issuer=issuer_crt3, subject_public_key=public_key_2048_subject_2, issuer_private_key=private_key_2048_subject_1, serial_number=0x3cc30000babadeda, if_ca=False, duration=20 * 365) diff --git a/examples/dat/hsm/sahsm.py b/examples/dat/hsm/sahsm.py index e502e9a0..ae404c0f 100644 --- a/examples/dat/hsm/sahsm.py +++ b/examples/dat/hsm/sahsm.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -13,11 +13,11 @@ from spsdk import crypto -app = Flask(__name__) #pylint: disable=invalid-name +APP = Flask(__name__) THIS_DIR = os.path.dirname(__file__) -@app.route('/signer/', methods=['GET']) +@APP.route('/signer/', methods=['GET']) def signer(num: int) -> dict: """Route (API) that performing the signing. @@ -27,6 +27,9 @@ def signer(num: int) -> dict: private_key_file = os.path.join(THIS_DIR, f"k{num}_cert0_2048.pem") private_key = crypto.load_private_key(private_key_file) + # in this example we assume RSA keys + assert isinstance(private_key, crypto.RSAPrivateKey) + data_to_sign = base64.b64decode(request.args['data']) signature = private_key.sign( data=data_to_sign, @@ -37,4 +40,4 @@ def signer(num: int) -> dict: return jsonify({'signature': data.decode('utf-8')}) if __name__ == "__main__": - app.run(debug=True) + APP.run(debug=True) diff --git a/examples/lpc55xx.py b/examples/lpc55xx.py index 2fe37c4e..bf412681 100644 --- a/examples/lpc55xx.py +++ b/examples/lpc55xx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -12,8 +12,8 @@ from binascii import unhexlify from spsdk.mboot import McuBoot, McuBootCommandError, StatusCode, scan_usb -from spsdk.sbfile import BootImageV20 -from spsdk.sbfile import BootSectionV2, CmdErase, CmdLoad, CmdReset +from spsdk.sbfile.images import BootImageV20, BootSectionV2 +from spsdk.sbfile.commands import CmdErase, CmdLoad, CmdReset # Uncomment for printing debug messages # import logging diff --git a/examples/sbfile.py b/examples/sbfile.py index 8efae0e9..baa8467a 100644 --- a/examples/sbfile.py +++ b/examples/sbfile.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -14,9 +14,9 @@ import os from binascii import unhexlify -from spsdk.sbfile import BootImageV20, BootImageV21, Certificate, SBV2xAdvancedParams -from spsdk.sbfile import BootSectionV2, CmdErase, CmdLoad, CmdReset -from spsdk.utils.crypto import CertBlockV2, KeyBlob, Otfad +from spsdk.sbfile.images import BootImageV20, BootImageV21, SBV2xAdvancedParams, BootSectionV2 +from spsdk.sbfile.commands import CmdErase, CmdLoad, CmdReset +from spsdk.utils.crypto import CertBlockV2, KeyBlob, Otfad, Certificate THIS_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/mypy.ini b/mypy.ini index 522b6b1b..4ff4483a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,69 +1,3 @@ [mypy] disallow_untyped_defs = True - -; There are disbled until SPSDK-41 is resolved -; problems stems from the class redefinition for Win/Linux -[mypy-spsdk.mboot.interfaces.usb] -ignore_errors = True -[mypy-spsdk.sdp.interfaces.usb] -ignore_errors = True - -[mypy-crccheck.*] -ignore_missing_imports = True - -[mypy-asn1crypto] -ignore_missing_imports = True - -[mypy-oscrypto.*] -ignore_missing_imports = True - -[mypy-cryptography.*] -ignore_missing_imports = True - -[mypy-serial.*] -ignore_missing_imports = True - -[mypy-click_option_group.*] -ignore_missing_imports = True - -[mypy-hexdump.*] -ignore_missing_imports = True - -[mypy-construct.*] -ignore_missing_imports = True - -[mypy-pywinusb.*] -ignore_missing_imports = True - -[mypy-usb.*] -ignore_missing_imports = True - -[mypy-pylink.*] -ignore_missing_imports = True - -[mypy-munch.*] -ignore_missing_imports = True - -[mypy-pyocd-pemicro.*] -ignore_missing_imports = True - -[mypy-pypemicro.*] -ignore_missing_imports = True - -[mypy-pyocd.*] -ignore_missing_imports = True - -[mypy-colorama.*] -ignore_missing_imports = True - -[mypy-prettytable.*] -ignore_missing_imports = True - -[mypy-jmespath.*] -ignore_missing_imports = True - -[mypy-astunparse.*] -ignore_missing_imports = True - -[mypy-commentjson.*] ignore_missing_imports = True diff --git a/release_notes.txt b/release_notes.txt index eaf37e96..25e218bb 100644 --- a/release_notes.txt +++ b/release_notes.txt @@ -1,7 +1,7 @@ Release Notes for Secure Provisioning SDK ========================================== -Version: 1.2 -Date: 11-December-2020 +Version: 1.3 +Date: 05-March-2021 Secure Provisioning SDK (SPSDK) is unified, reliable and easy to use SW library working across NXP MCU portfolio providing strong foundation from quick customer prototyping up to production deployment. The library allows the user @@ -9,24 +9,32 @@ to connect and communicate with a device, configure the device, prepare, downloa operations. It is delivered in a form of python library and command line applications. Features: -- support for Niobe4 Analog devices -- extend support for Niobe4 Mini, Nano -- PFRC - console script for searching for brick-conditions in PFR settings -- custom HSM support -- sdpshost cli utility using sdpshost communication protocol -- remote signing for Debug Credential -- added command read-register into sdphost cli -- dynamic plugin support -- MCU Link Debugger support -- [PFR] added CMAC-based seal -- [PFR] load Root of Trust from elf2sb configuration file - +- support creation of SB version 3.1 (for N4Analog) +- elftosb application based on legacy elf2sb supporting SB 3.1 support +- nxpdevscan - application for connected USB, UART devices discovery +- shadowregs - application for shadow registers management using DebugProbe +- support USB path argument in blhost/sdphost (all supported OS) +- nxpcertgen cli application (basicConstrains, self-signed) +- extend blhost commands: + - flash-erase-all + - call + - load-image + - execute + - key-provisioning + - receive-sb-file +- extend blhost commands' options: + - configure-memory now allows usage of internal memory + - extend error code in output + - add parameters lock/nolock into efuse-program-once command + - add key selector option to generate-key-blob command + - add nolock/lock selector to efuse-program-once command + - add hexdata option to write-memory command Supported devices: ================== -- LPC55S69, LPC55S28 (Niobe4) -- LPC55S06 (Niobe 4 Nano) -- LPC55S16 (Niobe4 Mini) +- LPC55S6x, LPC55S2x (Niobe4) +- LPC55S0x (Niobe 4 Nano) +- LPC55S1x (Niobe4 Mini) - i.MX RT105x, RT106x - i.MX RT595S, RT685S @@ -60,6 +68,19 @@ Revision History: - included support for debuggers - utility (nxpkeygen) for generating debug credential files and corresponding keys +1.2 +- support for Niobe4 Analog devices +- extend support for Niobe4 Mini, Nano +- PFRC - console script for searching for brick-conditions in PFR settings +- custom HSM support +- sdpshost cli utility using sdpshost communication protocol +- remote signing for Debug Credential +- added command read-register into sdphost cli +- dynamic plugin support +- MCU Link Debugger support +- [PFR] added CMAC-based seal +- [PFR] load Root of Trust from elf2sb configuration file + Licence: ========= BSD-3 License diff --git a/requirements-develop.txt b/requirements-develop.txt index adcb0b09..ff182efe 100644 --- a/requirements-develop.txt +++ b/requirements-develop.txt @@ -14,3 +14,5 @@ pylint==2.4.4 pydocstyle # cli executables pyinstaller +# developement/CI tools +pre-commit diff --git a/requirements.txt b/requirements.txt index 0c1da4f9..f9fd5e79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,22 @@ -commentjson==0.9.0 -jmespath==0.10.0 -astunparse==1.6.3 -bitstring==3.1.6 -click==7.0 -click-option-group==0.3.0 -construct==2.10.56 -crccheck==0.6 -hexdump==3.3 -asn1crypto==1.2.0 -cryptography==3.3.2 -oscrypto==1.2.0 -pycryptodome==3.9.8 -pyserial==3.4 -hidapi==0.10.1 -jinja2>2.11.0 - -pylink-square>=0.8.0 -pypemicro -pyocd-pemicro -pyocd==0.28.3 -munch==2.5.0 +commentjson>=0.9,<1 +jmespath<=0.10.0 +astunparse>=1.6,<2 +bitstring>=3.1,<3.2 +click>=7.0,<8 +click-option-group>=0.3.0,<0.6 +construct~=2.10 +crccheck>=0.6,<2 +hexdump~=3.3 +asn1crypto>=1.2,<2 +cryptography>=3.3,<3.4.4 +oscrypto~=1.2 +pycryptodome>=3.9.3,<4 +pyserial>=3.1,<4 +hidapi~=0.10 +jinja2>=2.11,<3 +ruamel.yaml>=0.16.8,<0.17 +pylink-square>=0.8.2,<0.9 +pypemicro>=0.1.5,<0.2 +pyocd-pemicro>=1.0.2,<2 +pyocd>=0.28.3,<0.29 +munch<2.5.1 diff --git a/setup.py b/setup.py index 9158df8e..f7eec086 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -89,7 +89,10 @@ def sanitize_version(version_str: str) -> str: 'sdpshost=spsdk.apps.sdpshost:safe_main', 'spsdk=spsdk.apps.spsdk_apps:safe_main', 'nxpkeygen=spsdk.apps.nxpkeygen:safe_main', - 'nxpdebugmbox=spsdk.apps.nxpdebugmbox:safe_main' + 'nxpdebugmbox=spsdk.apps.nxpdebugmbox:safe_main', + 'nxpcertgen=spsdk.apps.nxpcertgen:safe_main', + 'nxpdevscan=spsdk.apps.nxpdevscan:safe_main', + 'shadowregs=spsdk.apps.shadowregs:safe_main' ], }, ) diff --git a/spsdk/__version__.py b/spsdk/__version__.py index d01a98ec..701c0a00 100644 --- a/spsdk/__version__.py +++ b/spsdk/__version__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -10,4 +10,4 @@ Having the version in a separate file makes it easier to share it with setup.py """ -__version__ = "0.3.1" +__version__ = "1.3.0" diff --git a/spsdk/apps/README.md b/spsdk/apps/README.md index 8a2a3266..dbca2a87 100644 --- a/spsdk/apps/README.md +++ b/spsdk/apps/README.md @@ -4,10 +4,17 @@ After installing SPSDK, several applications are present directly on PATH as exe - [spsdk](spsdk_apps.py) - entry point for all available applications. - [blhost](blhost.py) - console script for MBoot module. -- [sdphost](sdphost.py) - console script for SDP module. +- [elftosb](elftosb.py) - utility for generating TrustZone, MasterBootImage and SecureBinary images. +- [nxpcertgen](nxpcertgen.py) - utility for generating the self-signed x.509 certificate. +- [nxpdebugmbox](nxpdebugmbox.py) - utility for performing the Debug Authentication. +- [nxpdevscan](nxpdscan.py) - utility for listing all connected NXP USB and UART devices. +- [nxpkeygen](nxpkeygen.py) - utility for generating RSA/ECC key pairs and debug credential files based on YAML configuration file. - [pfr](pfr.py) - simple utility for creation and analysis of protected regions - CMPA and CFPA. -- [nxpkeygen](nxpkeygen.py) - utility for generating RSA/ECC key pairs and debug credential files based on YAML configuration file -- [nxpdebugmbox](nxpdebugmbox.py)- utility for performing the Debug Authentication +- [pfrc](pfrc.py) - simple utility for search of brick-conditions in PFR settings. +- [sdphost](sdphost.py) - console script for SDP module. +- [sdpshost](sdpshost.py) - console script for SDPS module. +- [shadowregs](shadowregs.py) - utility for Shadow Registers controlling. + `` spsdk --help`` - lists all available commands. diff --git a/spsdk/apps/__init__.py b/spsdk/apps/__init__.py index fd45f97e..859b7edb 100644 --- a/spsdk/apps/__init__.py +++ b/spsdk/apps/__init__.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause -"""This module contains various applications relivered with SPSDK.""" +"""This module contains various applications delivered with SPSDK.""" diff --git a/spsdk/apps/blhost.py b/spsdk/apps/blhost.py index 0134c0f4..c60a565d 100644 --- a/spsdk/apps/blhost.py +++ b/spsdk/apps/blhost.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -16,14 +16,37 @@ from click_option_group import MutuallyExclusiveOptionGroup, optgroup from spsdk import __version__ as spsdk_version, SPSDKError -from spsdk.apps.utils import INT, get_interface, format_raw_data, catch_spsdk_error, parse_file_and_size -from spsdk.mboot import McuBoot, StatusCode, parse_property_value +from spsdk.apps.blhost_helper import parse_property_tag +from spsdk.apps.utils import ( + INT, get_interface, format_raw_data, catch_spsdk_error, + parse_file_and_size, parse_hex_data +) +from spsdk.mboot import McuBoot, StatusCode, parse_property_value, GenerateKeyBlobSelect @click.group() @optgroup.group('Interface configuration', cls=MutuallyExclusiveOptionGroup) @optgroup.option('-p', '--port', metavar='COM[,speed]', help='Serial port') -@optgroup.option('-u', '--usb', metavar='PID,VID', help='USB device\'s PID:VID') +@optgroup.option('-u', '--usb', metavar='PID,VID', help=""" +USB device identifier. Following formats are supported: +, or , device/instance path, device name. +vid: hex or dec string; e.g. 0x0AB12, 43794. + +vid/pid: hex or dec string; e.g. 0x0AB12:0x123, 1:3451 + +device name: use 'dscan' utility to list supported device names. + +path - OS specific string. +Windows: +'device instance path' in device manager under Windows OS. + +Linux specific: +Use 'Bus' and 'Device' ID observed using 'lsusb' in #' form; e.g. '3#2'. + +Mac specific: +Use device name and location ID from 'System report' in ' ' +form. e.g. 'SE Blank RT Family @14100000' +""") @click.option('-j', '--json', 'use_json', is_flag=True, help='Use JSON output') @click.option('-v', '--verbose', 'log_level', flag_value=logging.INFO, help='Display more verbose output') @click.option('-d', '--debug', 'log_level', flag_value=logging.DEBUG, help='Display debugging info') @@ -34,6 +57,17 @@ def main(ctx: click.Context, port: str, usb: str, use_json: bool, log_level: int, timeout: int) -> int: """Utility for communication with bootloader on target.""" logging.basicConfig(level=log_level or logging.WARNING) + + # print help for get-property if property tag is 0 or 'list-properties' + if ctx.invoked_subcommand == 'get-property': + args = click.get_os_args() + # running this via pytest changes the args to a single arg, in that case skip + if len(args) > 1 and 'get-property' in args: + tag_str = args[args.index('get-property') + 1] + if parse_property_tag(tag_str) == 0: + click.echo(ctx.command.commands['get-property'].help) # type: ignore + ctx.exit(0) + # if --help is provided anywhere on commandline, skip interface lookup and display help message if not '--help' in click.get_os_args(): ctx.obj = { @@ -45,6 +79,22 @@ def main(ctx: click.Context, port: str, usb: str, use_json: bool, log_level: int return 0 +@main.command() +@click.argument('address', type=INT(), required=True) +@click.argument('argument', type=INT(), required=True) +@click.pass_context +def call(ctx: click.Context, address: int, argument: int) -> None: + """Invoke code that the ADDRESS, passing single ARGUMENT to it. + + \b + ADDRESS - function code address + ARGUMENT - argument for the function + """ + with McuBoot(ctx.obj['interface']) as mboot: + mboot.call(address, argument) + display_output([], mboot.status_code, ctx.obj['use_json']) + + @main.command() @click.argument('memory_id', type=INT(), required=True) @click.argument('address', type=INT(), required=True) @@ -64,14 +114,17 @@ def configure_memory(ctx: click.Context, address: int, memory_id: int) -> None: @main.command() @click.argument('address', type=INT(), required=True) @click.argument('data', type=INT(base=16), required=True) +@click.argument('lock', metavar='[nolock/lock]', type=click.Choice(['nolock', 'lock']), default='nolock') @click.pass_context -def efuse_program_once(ctx: click.Context, address: int, data: int) -> None: +def efuse_program_once(ctx: click.Context, address: int, data: int, lock: str) -> None: """Program one word of OCOTP Field. \b ADDRESS - address of OTP word, not the shadowed memory address. DATA - hex digits without prefix '0x'. """ + if lock == 'lock': + address = address | (1 << 24) with McuBoot(ctx.obj['interface']) as mboot: response = mboot.efuse_program_once(address, data) display_output([response], mboot.status_code, ctx.obj['use_json']) @@ -88,7 +141,10 @@ def efuse_read_once(ctx: click.Context, address: int) -> None: """ with McuBoot(ctx.obj['interface']) as mboot: response = mboot.efuse_read_once(address) - display_output([4, response], mboot.status_code, ctx.obj['use_json']) + display_output( + None if response is None else [4, response], + mboot.status_code, ctx.obj['use_json'] + ) @main.command() @@ -127,6 +183,20 @@ def flash_erase_region(ctx: click.Context, address: int, byte_count: int, memory display_output([], mboot.status_code, ctx.obj['use_json']) +@main.command() +@click.argument('memory_id', type=int, required=False, default=0) +@click.pass_context +def flash_erase_all(ctx: click.Context, memory_id: int) -> None: + """Erase all flash according to [memory_id], excluding protected regions. + + \b + MEMORY_ID - id of memory to erase (default: 0) + """ + with McuBoot(ctx.obj['interface']) as mboot: + mboot.flash_erase_all(memory_id) + display_output([], mboot.status_code, ctx.obj['use_json']) + + @main.command() @click.argument('address', type=INT(), required=True) @click.argument('byte_count', type=INT(), required=True) @@ -151,22 +221,72 @@ def fill_memory(ctx: click.Context, address: int, byte_count: int, @main.command() -@click.argument('property_tag', type=int, default=1, required=True) +@click.argument('boot_file', metavar='FILE', type=click.File('rb')) +@click.pass_context +def load_image(ctx: click.Context, boot_file: click.File) -> None: + """Load a boot image to the device. + + \b + FILE - boot file to load + """ + data = boot_file.read() # type: ignore + with McuBoot(ctx.obj['interface']) as mboot: + mboot.load_image(data) + display_output([], mboot.status_code, ctx.obj['use_json']) + + +@main.command() +@click.argument('property_tag', type=str, required=True) @click.argument('index', type=int, default=0) @click.pass_context -def get_property(ctx: click.Context, property_tag: int, index: int) -> None: +def get_property(ctx: click.Context, property_tag: str, index: int) -> None: """Get bootloader-specific property. \b - PROPERTY_TAG - number represeting the requested property + PROPERTY_TAG - number or name represeting the requested property MEMORY_ID - id/index of the memory (default: 0) + \b + Available properties: + 0 or 'list-properties' List all properties + 1 or 'current-version' Bootloader version + 2 or 'available-peripherals' Available peripherals + 3 or 'flash-start-address' Start of program flash, is required + 4 or 'flash-size-in-bytes' Size of program flash, is required + 5 or 'flash-sector-size' Size of flash sector, is required + 6 or 'flash-block-count' Blocks in flash array, is required + 7 or 'available-commands' Available commands + 8 or 'check-status' Check Status, is required + 9 or 'reserved' + 10 or 'verify-writes' Verify Writes flag + 11 or 'max-packet-size' Max supported packet size + 12 or 'reserved-regions' Reserved regions + 13 or 'reserved' + 14 or 'ram-start-address' Start of RAM, is required + 15 or 'ram-size-in-bytes' Size of RAM, is required + 16 or 'system-device-id' System device identification + 17 or 'security-state' Flash security state + 18 or 'unique-device-id' Unique device identification + 19 or 'flash-fac-support' FAC support flag + 20 or 'flash-access-segment-size' FAC segment size + 21 or 'flash-access-segment-count' FAC segment count + 22 or 'flash-read-margin' Read margin level of program flash + 23 or 'qspi/otfad-init-status' QuadSpi initialization status + 24 or 'target-version' Target version + 25 or 'external-memory-attributes' External memory attributes, is required + 26 or 'reliable-update-status' Reliable update status + 27 or 'flash-page-size' Flash page size, is required + 28 or 'irq-notify-pin' Interrupt notifier pin + 29 or 'ffr-keystore_update-opt' FFR key store update option + + Note: Not all properties are available for all devices. """ + property_tag_int = parse_property_tag(property_tag) with McuBoot(ctx.obj['interface']) as mboot: - response = mboot.get_property(property_tag, index=index) # type: ignore - assert response, f"Error reading property {property_tag}" + response = mboot.get_property(property_tag_int, index=index) # type: ignore + property_text = str(parse_property_value(property_tag_int, response)) if response else None display_output( response, mboot.status_code, ctx.obj['use_json'], - parse_property_value(property_tag, response) + property_text ) @@ -189,18 +309,21 @@ def read_memory(ctx: click.Context, address: int, byte_count: int, """ with McuBoot(ctx.obj['interface']) as mboot: response = mboot.read_memory(address, byte_count, memory_id) - assert response, "Error reading memory" - if out_file: - out_file.write(response) # type: ignore - else: - click.echo(format_raw_data(response, use_hexdump=use_hexdump)) + + if response: + if out_file: + out_file.write(response) # type: ignore + else: + click.echo(format_raw_data(response, use_hexdump=use_hexdump)) display_output( - [len(response)], mboot.status_code, ctx.obj['use_json'], - f"Read {len(response)} of {byte_count} bytes." + [len(response) if response else 0], + mboot.status_code, ctx.obj['use_json'], + f"Read {len(response) if response else 0} of {byte_count} bytes." ) + @main.command() @click.argument('sb_file', metavar='FILE', type=click.File('rb'), required=True) @click.pass_context @@ -212,9 +335,8 @@ def receive_sb_file(ctx: click.Context, sb_file: click.File) -> None: """ with McuBoot(ctx.obj['interface']) as mboot: data = sb_file.read() # type: ignore - write_response = mboot.receive_sb_file(data) - assert write_response, f"Error sending SB file." - display_output([write_response], mboot.status_code, ctx.obj['use_json']) + mboot.receive_sb_file(data) + display_output([], mboot.status_code, ctx.obj['use_json']) @main.command() @@ -228,42 +350,61 @@ def reset(ctx: click.Context) -> None: @main.command() @click.argument('address', type=INT(), required=True) -@click.argument('in_file', metavar='FILE', type=click.File('rb'), required=True) +@click.argument('data_source', metavar='FILE[,BYTE_COUNT] | {{HEX-DATA}}', type=str, required=True) @click.argument('memory_id', type=int, required=False, default=0) @click.pass_context -def write_memory(ctx: click.Context, address: int, in_file: click.File, memory_id: int) -> None: +def write_memory(ctx: click.Context, address: int, data_source: str, memory_id: int) -> None: """Write memory. \b ADDRESS - starting address FILE - write content of this file + BYTE_COUNT - if specified, load only first BYTE_COUNT number of bytes from file + HEX-DATA - string of hex values: {{112233}}, {{11 22 33}} MEMORY_ID - id of memory to read from (default: 0) """ + try: + data = parse_hex_data(data_source) + except SPSDKError: + file_path, size = parse_file_and_size(data_source) + with open(file_path, 'rb') as f: + data = f.read(size) + with McuBoot(ctx.obj['interface']) as mboot: - data = in_file.read() # type: ignore - write_response = mboot.write_memory(address, data, memory_id) - assert write_response, f"Error writing memory addr={address:#0x} memory_id={memory_id}" - display_output([write_response], mboot.status_code, ctx.obj['use_json']) + response = mboot.write_memory(address, data, memory_id) + display_output([len(data)] if response else None, mboot.status_code, ctx.obj['use_json']) @main.command() @click.argument('dek_file', type=click.File('rb'), required=True) @click.argument('blob_file', type=click.File('wb'), required=True) +@click.argument('key_sel', metavar='[KEY_SEL]', + type=click.Choice(['0', '1', '2', '3', 'OPTMK', 'ZMK', 'CMK']), default='0') @click.pass_context -def generate_key_blob(ctx: click.Context, dek_file: click.File, blob_file: click.File) -> None: +def generate_key_blob(ctx: click.Context, dek_file: click.File, blob_file: click.File, key_sel: str) -> None: """Generate the Key Blob for a given DEK. \b DEK_FILE - the file with the binary DEK key BLOB_FILE - the generated file with binary key blob + KEY_SEL - select the BKEK used to wrap the BK and generate the blob. + For devices with SNVS, valid options of [key_sel] are + 0, 1 or OTPMK: OTPMK from FUSE or OTP(default), + 2 or ZMK: ZMK from SNVS, + 3 or CMK: CMK from SNVS, + For devices without SNVS, this option will be ignored. """ with McuBoot(ctx.obj['interface']) as mboot: data = dek_file.read() # type: ignore - write_response = mboot.generate_key_blob(data) - if not write_response: - raise SPSDKError(f"Error generating key blob") - blob_file.write(write_response) # type: ignore - display_output([mboot.status_code, len(write_response)], mboot.status_code, ctx.obj['use_json']) + key_sel_int = int(key_sel) if key_sel.isnumeric() else GenerateKeyBlobSelect.get(key_sel) + assert isinstance(key_sel_int, int) + write_response = mboot.generate_key_blob(data, key_sel=key_sel_int) + display_output( + [mboot.status_code, len(write_response)] if write_response else None, + mboot.status_code, ctx.obj['use_json'] + ) + if write_response: + blob_file.write(write_response) # type: ignore @@ -279,13 +420,11 @@ def enroll(ctx: click.Context) -> None: """Key provisioning enroll.""" with McuBoot(ctx.obj['interface']) as mboot: response = mboot.kp_enroll() - if not response: - raise SPSDKError(f"Error enrolling the device") display_output([], mboot.status_code, ctx.obj['use_json']) @key_provisioning.command(name='set_user_key') -@click.argument('key_type', metavar='TYPE', type=int, required=True) +@click.argument('key_type', metavar='TYPE', type=INT(), required=True) @click.argument('file_and_size', metavar='FILE[,SIZE]', type=str, required=True) @click.pass_context def set_user_key(ctx: click.Context, key_type: int, file_and_size: str) -> None: @@ -306,8 +445,6 @@ def set_user_key(ctx: click.Context, key_type: int, file_and_size: str) -> None: with McuBoot(ctx.obj['interface']) as mboot: response = mboot.kp_set_user_key(key_type=key_type, key_data=key_data) # type: ignore - if not response: - raise SPSDKError("Error sending key to the device") display_output([], mboot.status_code, ctx.obj['use_json']) @@ -387,13 +524,15 @@ def read_key_store(ctx: click.Context, key_store_file: click.File) -> None: """ with McuBoot(ctx.obj['interface']) as mboot: response = mboot.kp_read_key_store() - if not response: - raise SPSDKError('Error reading key store') - key_store_file.write(response) # type: ignore - display_output([len(response)], mboot.status_code, ctx.obj['use_json']) + display_output( + [len(response)] if response else None, + mboot.status_code, ctx.obj['use_json'] + ) + if response: + key_store_file.write(response) # type: ignore -def display_output(response: list, status_code: int, use_json: bool = False, +def display_output(response: list = None, status_code: int = 0, use_json: bool = False, extra_output: str = None) -> None: """Display response and status code. @@ -420,10 +559,9 @@ def display_output(response: list, status_code: int, use_json: bool = False, print(json.dumps(data, indent=3)) else: print(f'Response status = {decode_status_code(status_code)}') - if status_code != 0: - return - for i, word in enumerate(response): - print(f'Response word {i + 1} = {word} ({word:#x})') + if isinstance(response, list): + for i, word in enumerate(response): + print(f'Response word {i + 1} = {word} ({word:#x})') if extra_output: print(extra_output) @@ -436,11 +574,12 @@ def decode_status_code(status_code: int) -> str: :return: String representation :rtype: str """ - return f"{status_code} ({status_code:#x}) {StatusCode.desc(status_code)}." + return (f"{status_code} ({status_code:#x}) " + f"{StatusCode.desc(status_code, f'Unknown error code ({status_code})')}.") @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/blhost_helper.py b/spsdk/apps/blhost_helper.py new file mode 100644 index 00000000..e4f22b01 --- /dev/null +++ b/spsdk/apps/blhost_helper.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Helper module for blhost application.""" + +PROPERTIES_NAMES = { + 'list-properties': 0, + 'current-version': 1, + 'available-peripherals': 2, + 'flash-start-address': 3, + 'flash-size-in-bytes': 4, + 'flash-sector-size': 5, + 'flash-block-count': 6, + 'available-commands': 7, + 'check-status': 8, + 'reserved': 9, + 'verify-writes': 10, + 'max-packet-size': 11, + 'reserved-regions': 12, + 'reserved': 13, + 'ram-start-address': 14, + 'ram-size-in-bytes': 15, + 'system-device-id': 16, + 'security-state': 17, + 'unique-device-id': 18, + 'flash-fac-support': 19, + 'flash-access-segment-size': 20, + 'flash-access-segment-count': 21, + 'flash-read-margin': 22, + 'qspi/otfad-init-status': 23, + 'target-version': 24, + 'external-memory-attributes': 25, + 'reliable-update-status': 26, + 'flash-page-size': 27, + 'irq-notify-pin': 28, + 'ffr-keystore_update-opt': 29, +} + +def parse_property_tag(property_tag: str) -> int: + """Convert the property as name or stringified number into integer. + + :param property_tag: Name or number of the property tag + :return: Property integer tag + """ + try: + return int(property_tag, 0) + except: + pass + try: + return PROPERTIES_NAMES[property_tag] + except: + pass + return 0xFF diff --git a/spsdk/apps/elftosb.py b/spsdk/apps/elftosb.py index bfd6a840..ba7b680f 100644 --- a/spsdk/apps/elftosb.py +++ b/spsdk/apps/elftosb.py @@ -1,28 +1,28 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Console script for Elf2SB.""" import sys +from datetime import datetime import click import commentjson as json from click_option_group import RequiredMutuallyExclusiveOptionGroup, optgroup -from Crypto.PublicKey import ECC from spsdk import __version__ as spsdk_version from spsdk.apps import elftosb_helper from spsdk.apps.utils import catch_spsdk_error -from spsdk.crypto import ( - load_private_key, load_certificate, SignatureProvider, - EllipticCurvePrivateKeyWithSerialization -) +from spsdk.crypto import SignatureProvider from spsdk.image import MasterBootImageN4Analog, MasterBootImageType, TrustZone -from spsdk.utils.misc import load_binary, load_file, write_file -from spsdk.utils.crypto import CertBlockV3 +from spsdk.sbfile.sb31.images import (SecureBinary31Commands, + SecureBinary31Header) +from spsdk.utils.crypto import CertBlockV31 +from spsdk.utils.crypto.backend_internal import internal_backend +from spsdk.utils.misc import load_binary, load_text, write_file SUPPORTED_FAMILIES = ['lpc55s3x'] @@ -33,8 +33,7 @@ def generate_trustzone_binary(tzm_conf: click.File) -> None: config = elftosb_helper.TrustZoneConfig(config_data) trustzone = TrustZone.custom(family=config.family, revision=config.revision, customizations=config.presets) tz_data = trustzone.export() - with open(config.output_file, 'wb') as f: - f.write(tz_data) + write_file(tz_data, config.output_file, mode="wb") def _get_trustzone(config: elftosb_helper.MasterBootImageConfig) -> TrustZone: @@ -42,7 +41,7 @@ def _get_trustzone(config: elftosb_helper.MasterBootImageConfig) -> TrustZone: if not config.trustzone_preset_file: return TrustZone.disabled() try: - tz_config_data = json.loads(load_file(config.trustzone_preset_file)) + tz_config_data = json.loads(load_text(config.trustzone_preset_file)) tz_config = elftosb_helper.TrustZoneConfig(tz_config_data) return TrustZone.custom( family=tz_config.family, revision=tz_config.revision, customizations=tz_config.presets @@ -66,6 +65,33 @@ def _get_master_boot_image_type(config: elftosb_helper.MasterBootImageConfig) -> return sb3_image_types[image_type] +def _get_cert_block_v31(config: elftosb_helper.CertificateBlockConfig) -> CertBlockV31: + root_certs = [ + load_binary(cert_file) for cert_file in config.root_certs # type: ignore + ] + user_data = None + if config.use_isk and config.isk_sign_data_path: + user_data = load_binary(config.isk_sign_data_path) + isk_private_key = None + if config.use_isk: + assert config.main_root_private_key_file + isk_private_key = load_binary(config.main_root_private_key_file) + isk_cert = None + if config.use_isk: + assert config.isk_certificate + isk_cert = load_binary(config.isk_certificate) + + cert_block = CertBlockV31( + root_certs=root_certs, + used_root_cert=config.main_root_cert_id, + user_data=user_data, + constraints=config.isk_constraint, + isk_cert=isk_cert, ca_flag=not config.use_isk, + isk_private_key=isk_private_key, + ) + return cert_block + + def generate_master_boot_image(image_conf: click.File) -> None: """Generate MasterBootImage from json configuration file.""" config_data = json.load(image_conf) @@ -81,36 +107,13 @@ def generate_master_boot_image(image_conf: click.File) -> None: signature_provider = None if MasterBootImageType.is_signed(image_type): cert_config = elftosb_helper.CertificateBlockConfig(config_data) - root_certs = [ - load_binary(cert_file) for cert_file in cert_config.root_certs # type: ignore - ] - user_data = None - if cert_config.isk_sign_data_path: - user_data = load_binary(cert_config.isk_sign_data_path) - isk_private_key = None - if cert_config.isk_private_key_file: - isk_private_key = load_private_key(cert_config.isk_private_key_file) - assert isinstance(isk_private_key, EllipticCurvePrivateKeyWithSerialization) - - isk_cert = None - if cert_config.isk_certificate: - cert_data = load_binary(cert_config.isk_certificate) - isk_cert = ECC.import_key(cert_data) - - ca_flag = not cert_config.use_isk - cert_block = CertBlockV3( - root_certs=root_certs, ca_flag=ca_flag, - used_root_cert=cert_config.main_root_cert_id, constraints=cert_config.isk_constraint, - isk_private_key=isk_private_key, isk_cert=isk_cert, # type: ignore - user_data=user_data - ) + cert_block = _get_cert_block_v31(cert_config) if cert_config.use_isk: signing_private_key_path = cert_config.isk_private_key_file else: signing_private_key_path = cert_config.main_root_private_key_file signature_provider = SignatureProvider.create(f'type=file;file_path={signing_private_key_path}') - assert config.master_boot_output_file mbi = MasterBootImageN4Analog( app=app, load_addr=load_addr, image_type=image_type, trust_zone=trustzone, dual_boot_version=dual_boot_version, @@ -125,7 +128,62 @@ def generate_master_boot_image(image_conf: click.File) -> None: def generate_secure_binary(container_conf: click.File) -> None: """Geneate SecureBinary image from json configuration file.""" - raise NotImplementedError() + config_data = json.load(container_conf) + config = elftosb_helper.SB31Config(config_data) + timestamp = config.timestamp + if timestamp is None: + # in our case, timestamp is the number of seconds since "Jan 1, 2000" + timestamp = int((datetime.now() - datetime(2000, 1, 1)).total_seconds()) + if isinstance(timestamp, str): + timestamp = int(timestamp, 0) + + final_data = bytes() + assert isinstance(config.main_curve_name, str) +# COMMANDS + pck = None + if config.is_encrypted: + assert isinstance(config.container_keyblob_enc_key_path, str) + pck = bytes.fromhex(load_text(config.container_keyblob_enc_key_path)) + sb_cmd_block = SecureBinary31Commands( + curve_name=config.main_curve_name, is_encrypted=config.is_encrypted, + kdk_access_rights=config.kdk_access_rights, + pck=pck, timestamp=timestamp, + ) + commands = elftosb_helper.get_cmd_from_json(config) + sb_cmd_block.set_commands(commands) + + commands_data = sb_cmd_block.export() + +# CERTIFICATE BLOCK + cert_block = _get_cert_block_v31(config) + data_cb = cert_block.export() + +# SB FILE HEADER + sb_header = SecureBinary31Header( + firmware_version=config.firmware_version, description=config.description, + curve_name=config.main_curve_name, timestamp=timestamp, is_nxp_container=config.is_nxp_container + ) + sb_header.block_count = sb_cmd_block.block_count + sb_header.image_total_length += len(sb_cmd_block.final_hash) + len(data_cb) + # TODO: use proper signature len calculation + sb_header.image_total_length += 2 * len(sb_cmd_block.final_hash) + sb_header_data = sb_header.export() + final_data += sb_header_data + +# HASH OF PREVIOUS BLOCK + final_data += sb_cmd_block.final_hash + final_data += data_cb + +# SIGNATURE + assert isinstance(config.main_signing_key, str) + private_key_data = load_binary(config.main_signing_key) + data_to_sign = final_data + signature = internal_backend.ecc_sign(private_key_data, data_to_sign) + assert internal_backend.ecc_verify(private_key_data, signature, data_to_sign) + final_data += signature + final_data += commands_data + + write_file(final_data, config.container_output, mode='wb') @click.command() @@ -155,7 +213,7 @@ def main(chip_family: str, image_conf: click.File, container_conf: click.File, t @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/elftosb_helper.py b/spsdk/apps/elftosb_helper.py index b94915c1..893e964d 100644 --- a/spsdk/apps/elftosb_helper.py +++ b/spsdk/apps/elftosb_helper.py @@ -1,13 +1,21 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for parsing original elf2sb configuration files.""" # pylint: disable=too-few-public-methods,too-many-instance-attributes +import struct +from typing import List + +from spsdk import SPSDKError +from spsdk.sbfile.sb31 import commands +from spsdk.utils.misc import load_binary + + class RootOfTrustInfo: """Filters out Root Of Trust information given to elf2sb application.""" @@ -56,6 +64,12 @@ def __init__(self, config_data: dict) -> None: self.isk_certificate_curve = config_data.get('iskCertificateEllipticCurve') self.isk_sign_data_path = config_data.get('signCertData') + self.main_signing_key = self.main_root_private_key_file + self.main_curve_name = self.root_certificate_curve + if self.use_isk: + self.main_signing_key = self.isk_private_key_file + self.main_curve_name = self.isk_certificate_curve + class MasterBootImageConfig(CertificateBlockConfig): """Configuration object for MasterBootImage.""" @@ -77,7 +91,7 @@ def __init__(self, config_data: dict) -> None: assert self.dual_boot_version self.dual_boot_version = int(self.dual_boot_version, 0) self.firmware_version = int(config_data.get('firmwareVersion', '1'), 0) - self.master_boot_output_file = config_data.get('masterBootOutputFile') + self.master_boot_output_file = config_data['masterBootOutputFile'] class SB31Config(CertificateBlockConfig): @@ -93,6 +107,132 @@ def __init__(self, config_data: dict) -> None: self.description = config_data.get('description') self.kdk_access_rights = config_data.get('kdkAccessRights', 0) self.container_configuration_word = config_data.get('containerConfigurationWord', 0) - self.firmware_version = config_data.get('firmwareVersion') + self.firmware_version = int(config_data.get('firmwareVersion', '1'), 0) self.sb3_block_output = config_data.get('sb3BlockOutput', False) - self.commands = config_data.get('commands') + self.commands = config_data['commands'] + self.container_output = config_data['containerOutputFile'] + self.is_encrypted = config_data.get('isEncrypted', True) + self.timestamp = config_data.get('timestamp') + + +def _erase_cmd_handler(cmd_args: dict) -> commands.CmdErase: + address = int(cmd_args['address'], 0) + length = int(cmd_args['size'], 0) + memory_id = int(cmd_args.get('memoryId', '0'), 0) + return commands.CmdErase(address=address, length=length, memory_id=memory_id) + +def _load_key_blob_handler(cmd_args: dict) -> commands.CmdLoadKeyBlob: + data = load_binary(cmd_args['file']) + offset = int(cmd_args['offset'], 0) + key_wrap_name = cmd_args['wrappingKeyId'] + key_wrap_id = commands.CmdLoadKeyBlob.KeyWraps[key_wrap_name] + return commands.CmdLoadKeyBlob(offset=offset, data=data, key_wrap_id=key_wrap_id) + +def _program_fuses(cmd_args: dict) -> commands.CmdProgFuses: + address = int(cmd_args['address'], 0) + fuses = [int(fuse, 0) for fuse in cmd_args['values'].split(',')] + data = struct.pack(f"<{len(fuses)}L", *fuses) + return commands.CmdProgFuses(address=address, data=data) + +def _program_ifr(cmd_args: dict) -> commands.CmdProgIfr: + address = int(cmd_args['address'], 0) + data = load_binary(cmd_args['file']) + return commands.CmdProgIfr(address=address, data=data) + +def _call(cmd_args: dict) -> commands.CmdCall: + address = int(cmd_args['address'], 0) + return commands.CmdCall(address=address) + +def _execute(cmd_args: dict) -> commands.CmdExecute: + address = int(cmd_args['address'], 0) + return commands.CmdExecute(address=address) + +def _configure_memory(cmd_args: dict) -> commands.CmdConfigureMemory: + memory_id = int(cmd_args['memoryId'], 0) + return commands.CmdConfigureMemory(address=int(cmd_args['configAddress'], 0), memory_id=memory_id) + +def _fill_memory(cmd_args: dict) -> commands.CmdFillMemory: + address = int(cmd_args['address'], 0) + length = int(cmd_args['size'], 0) + pattern = int(cmd_args['pattern'], 0) + return commands.CmdFillMemory(address=address, length=length, pattern=pattern) + +def _copy(cmd_args: dict) -> commands.CmdCopy: + address = int(cmd_args['addressFrom'], 0) + length = int(cmd_args['size'], 0) + destination_address = int(cmd_args['addressTo'], 0) + memory_id_from = int(cmd_args['memoryIdFrom'], 0) + memory_id_to = int(cmd_args['memoryIdTo'], 0) + return commands.CmdCopy( + address=address, length=length, destination_address=destination_address, + memory_id_from=memory_id_from, memory_id_to=memory_id_to + ) + +def _check_fw_version(cmd_args: dict) -> commands.CmdFwVersionCheck: + value = int(cmd_args['value'], 0) + counter_id_str = cmd_args['counterId'] + counter_id = commands.CmdFwVersionCheck.COUNTER_ID[counter_id_str] + return commands.CmdFwVersionCheck(value=value, counter_id=counter_id) + +def _load(cmd_args: dict) -> commands.CmdLoadBase: + authentication = cmd_args.get('authentication') + address = int(cmd_args['address'], 0) + memory_id = int(cmd_args.get('memoryId', '0'), 0) + if authentication == "hashlocking": + data = load_binary(cmd_args['file']) + return commands.CmdLoadHashLocking(address=address, data=data, memory_id=memory_id) + if authentication == "cmac": + data = load_binary(cmd_args['file']) + return commands.CmdLoadCmac(address=address, data=data, memory_id=memory_id) + # general non-authenticated load command + if cmd_args.get('file'): + data = load_binary(cmd_args['file']) + return commands.CmdLoad(address=address, data=data, memory_id=memory_id) + if cmd_args.get('values'): + values = [int(s, 0) for s in cmd_args['values'].split(',')] + data = struct.pack(f'<{len(values)}L', *values) + return commands.CmdLoad(address=address, data=data, memory_id=memory_id) + raise SPSDKError(f"Unsupported LOAD command args: {cmd_args}") + + +_CMD_PARSER_HANDLERS = { + "erase": _erase_cmd_handler, + "load": _load, + "execute": _execute, + "call": _call, + "programFuses": _program_fuses, + "programIFR": _program_ifr, + "copy": _copy, + "loadKeyBlob": _load_key_blob_handler, + "configureMemory": _configure_memory, + "fillMemory": _fill_memory, + "checkFwVersion": _check_fw_version, +} + +def get_cmd_from_dict(cmd_dict: dict) -> commands.BaseCmd: + """Process command description into a command object. + + :param cmd_dict: Command description from json config file + :return: Command object + :raises SPSDKError: Unknown command + """ + cmd_dict_copy = cmd_dict.copy() + cmd_name, cmd_args = cmd_dict_copy.popitem() + try: + parse_handler = _CMD_PARSER_HANDLERS[cmd_name] + command = parse_handler(cmd_args) + return command + except KeyError: + raise SPSDKError(f"Unknown command name: {cmd_name}") + + +def get_cmd_from_json(config: SB31Config) -> List[commands.BaseCmd]: + """Parse commands from config files. + + :param config: Config file object + :return: List of command objects + """ + results = [] + for command in config.commands: + results.append(get_cmd_from_dict(command)) + return results diff --git a/spsdk/apps/nxpcertgen.py b/spsdk/apps/nxpcertgen.py new file mode 100644 index 00000000..e29909d3 --- /dev/null +++ b/spsdk/apps/nxpcertgen.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +"""NXP Certificate Generator.""" +import json +import logging +import sys + +import click + +from spsdk.apps.utils import catch_spsdk_error +from spsdk.crypto import generate_certificate, load_private_key, load_public_key, save_crypto_item +from spsdk.crypto import x509 + +logger = logging.getLogger(__name__) + + +class CertificateParametersConfig: + """Configuration object for creating the certificate.""" + def __init__(self, config_data: dict) -> None: + """Initialize cert_config from json config data.""" + self.issuer_private_key = config_data['issuer_private_key'] + self.subject_public_key = config_data['subject_public_key'] + self.serial_number = config_data['serial_number'] + self.duration = config_data['duration'] + self.BasicConstrains_ca = config_data['extensions']['BASIC_CONSTRAINTS']['ca'] + self.BasicConstrains_path_length = config_data['extensions']['BASIC_CONSTRAINTS']['path_length'] + self.issuer_name = generate_name(config_data['issuer']) + self.subject_name = generate_name(config_data['subject']) + + +def generate_name(config_data: dict) -> x509.Name: + """Set the issuer/subject distinguished attribute's.""" + attributes = [ + x509.NameAttribute(getattr(x509.NameOID, key), value) + for key, value in config_data.items() + ] + return x509.Name(attributes) + + +@click.command() +@click.option('-j', '--json-conf', type=click.File('r'), + help='Path to json configuration file containing the parameters for certificate.') +@click.option('-c', '--cert-path', type=click.Path(), help='Path where certificate will be stored.') +def main(json_conf: click.File, cert_path: str) -> None: + """Utility for certificate generation.""" + logger.info("Generating Certificate...") + logger.info("Loading configuration from json file...") + + json_content = json.load(json_conf) # type: ignore + cert_config = CertificateParametersConfig(json_content) + + priv_key = load_private_key(cert_config.issuer_private_key) + pub_key = load_public_key(cert_config.subject_public_key) + + certificate = generate_certificate(subject=cert_config.subject_name, issuer=cert_config.issuer_name, + subject_public_key=pub_key, + issuer_private_key=priv_key, + serial_number=cert_config.serial_number, + duration=cert_config.duration, + if_ca=cert_config.BasicConstrains_ca, + path_length=cert_config.BasicConstrains_path_length) + logger.info("Saving the generated certificate to the specified path...") + save_crypto_item(certificate, cert_path) + logger.info("Certificate generated successfully...") + + +@catch_spsdk_error +def safe_main() -> None: + """Call the main function.""" + sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter + + +if __name__ == "__main__": + safe_main() # pragma: no cover diff --git a/spsdk/apps/nxpdebugmbox.py b/spsdk/apps/nxpdebugmbox.py index 29a7efc9..96767217 100644 --- a/spsdk/apps/nxpdebugmbox.py +++ b/spsdk/apps/nxpdebugmbox.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -11,7 +11,7 @@ import struct import sys -from typing import List +from typing import List, Dict import click from spsdk import __version__ as spsdk_version @@ -20,13 +20,38 @@ DebugAuthenticateResponse, dm_commands) from spsdk.dat.debug_mailbox import DebugMailbox from spsdk.debuggers.utils import DebugProbeUtils -from spsdk.debuggers.debug_probe import DebugProbeError +from spsdk.exceptions import SPSDKError logger = logging.getLogger("DebugMBox") LOG_LEVEL_NAMES = [name.lower() for name in logging._nameToLevel] PROTOCOL_VERSIONS = ['1.0', '1.1', '2.0', '2.1', '2.2'] +def _open_debugmbox(pass_obj: Dict) -> DebugMailbox: + """Method opens DebugMailbox object based on input arguments. + + :param pass_obj: Input dictionary with arguments. + :return: Active DebugMailbox object. + :raise SPSDKError: Raised with any kind of problems with debug probe. + """ + interface = pass_obj['interface'] + serial_no = pass_obj['serial_no'] + debug_probe_params = pass_obj['debug_probe_params'] + timing = pass_obj['timing'] + reset = pass_obj['reset'] + + debug_probes = DebugProbeUtils.get_connected_probes(interface=interface, + hardware_id=serial_no, + user_params=debug_probe_params) + selected_probe = debug_probes.select_probe() + debug_probe = selected_probe.get_probe(debug_probe_params) + debug_probe.open() + + return DebugMailbox( + debug_probe=debug_probe, + reset=reset, + moredelay=timing + ) @click.group() @click.option('-i', '--interface') @@ -52,36 +77,22 @@ def main(ctx: click.Context, interface: str, protocol: str, log_level: str, timi logging.basicConfig(level=log_level.upper()) logger.setLevel(level=log_level.upper()) - if '--help' not in click.get_os_args(): - # Get the Debug probe object - try: - probe_user_params = {} - for par in debug_probe_option: - if par.count("=") != 1: - logger.warning(f"Invalid -o parameter {par}!") - else: - par_splitted = par.split("=") - probe_user_params[par_splitted[0]] = par_splitted[1] - - debug_probes = DebugProbeUtils.get_connected_probes(interface=interface, - hardware_id=serial_no, - user_params=probe_user_params) - selected_probe = debug_probes.select_probe() - debug_probe = DebugProbeUtils.get_probe(interface=selected_probe.interface, - hardware_id=selected_probe.hardware_id, - user_params=probe_user_params) - debug_probe.open() - - ctx.obj = { - 'protocol': protocol, - 'debug_mailbox': - DebugMailbox( - debug_probe=debug_probe, reset=reset, moredelay=timing - ), - } - - except DebugProbeError as exc: - logger.error(str(exc)) + probe_user_params = {} + for par in debug_probe_option: + if par.count("=") != 1: + raise SPSDKError(f"Invalid -o parameter {par}!") + + par_splitted = par.split("=") + probe_user_params[par_splitted[0]] = par_splitted[1] + + ctx.obj = { + 'protocol': protocol, + 'interface': interface, + 'serial_no': serial_no, + 'debug_probe_params': probe_user_params, + 'timing': timing, + 'reset': reset, + } return 0 @@ -96,7 +107,7 @@ def auth(pass_obj: dict, beacon: int, certificate: str, key: str, force: bool) - """Perform the Debug Authentication.""" try: logger.info("Starting Debug Authentication") - mail_box = pass_obj['debug_mailbox'] + mail_box = _open_debugmbox(pass_obj) with open(certificate, 'rb') as f: debug_cred_data = f.read() debug_cred = DebugCredential.parse(debug_cred_data) @@ -129,7 +140,7 @@ def auth(pass_obj: dict, beacon: int, certificate: str, key: str, force: bool) - def start(pass_obj: dict) -> None: """Start DebugMailBox.""" try: - dm_commands.StartDebugMailbox(dm=pass_obj['debug_mailbox']).run() + dm_commands.StartDebugMailbox(dm=_open_debugmbox(pass_obj)).run() logger.info("Start Debug Mailbox successful") except: logger.error("Start Debug Mailbox failed!") @@ -140,7 +151,7 @@ def start(pass_obj: dict) -> None: def exit(pass_obj: dict) -> None: """Exit DebugMailBox.""" try: - dm_commands.ExitDebugMailbox(dm=pass_obj['debug_mailbox']).run() + dm_commands.ExitDebugMailbox(dm=_open_debugmbox(pass_obj)).run() logger.info("Exit Debug Mailbox successful") except: logger.error("Exit Debug Mailbox failed!") @@ -151,7 +162,7 @@ def exit(pass_obj: dict) -> None: def erase(pass_obj: dict) -> None: """Erase Flash.""" try: - dm_commands.EraseFlash(dm=pass_obj['debug_mailbox']).run() + dm_commands.EraseFlash(dm=_open_debugmbox(pass_obj)).run() logger.info("Mass flash erase successful") except: logger.error("Mass flash erase failed!") @@ -162,7 +173,7 @@ def erase(pass_obj: dict) -> None: def famode(pass_obj: dict) -> None: """Set Fault Analysis Mode.""" try: - dm_commands.SetFaultAnalysisMode(dm=pass_obj['debug_mailbox']).run() + dm_commands.SetFaultAnalysisMode(dm=_open_debugmbox(pass_obj)).run() logger.info("Set fault analysis mode successful") except: logger.error("Set fault analysis mode failed!") @@ -174,14 +185,14 @@ def famode(pass_obj: dict) -> None: def ispmode(pass_obj: dict, mode: int) -> None: """Enter ISP Mode.""" try: - dm_commands.EnterISPMode(dm=pass_obj['debug_mailbox']).run([mode]) + dm_commands.EnterISPMode(dm=_open_debugmbox(pass_obj)).run([mode]) logger.info("ISP mode entered successfully!") except: logger.error("Entering into ISP mode failed!") @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/nxpdevscan.py b/spsdk/apps/nxpdevscan.py new file mode 100644 index 00000000..a5c765cd --- /dev/null +++ b/spsdk/apps/nxpdevscan.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2020-2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +"""NXP USB Device Scanner.""" + +import sys + +from typing import IO + +import click + +import spsdk.utils.nxpdevscan as nxpdevscan + +from spsdk.apps.utils import catch_spsdk_error + + +@click.command() +@click.option('-e', '--extend-vids', multiple=True, default=[], help="VID in hex to extend search.") +@click.option('-o', '--out', default='-', type=click.File('w')) +def main(extend_vids: str, out: IO[str]) -> None: + """Utility listing all connected NXP USB and UART devices.""" + additional_vids = [int(vid, 16) for vid in extend_vids] + + nxp_devices = nxpdevscan.search_nxp_usb_devices(additional_vids) + if out.name == '': + click.echo(8*"-" + " Connected NXP USB Devices " + 8*"-" + "\n", out) + for nxp_dev in nxp_devices: + click.echo(nxp_dev.info(), out) + click.echo('', out) + + nxp_devices = nxpdevscan.search_nxp_uart_devices() + if out.name == '': + click.echo(8*"-" + " Connected NXP UART Devices " + 8*"-" + "\n", out) + for nxp_dev in nxp_devices: + click.echo(nxp_dev.info(), out) + click.echo('', out) + +@catch_spsdk_error +def safe_main() -> None: + """Call the main function.""" + sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter + + +if __name__ == "__main__": + safe_main() # pragma: no cover diff --git a/spsdk/apps/nxpkeygen.py b/spsdk/apps/nxpkeygen.py index 750912b4..0cb828db 100644 --- a/spsdk/apps/nxpkeygen.py +++ b/spsdk/apps/nxpkeygen.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -121,7 +121,7 @@ def check_file_exists(path: str, force_overwrite: bool = False) -> bool: # type 'unencrypted.') @click.argument('path', type=click.Path(file_okay=True)) @click.option('--force', is_flag=True, default=False, - help="Force overwritting of an existing file. Create destination folder, if doesn't exist already.") + help="Force overwriting of an existing file. Create destination folder, if doesn't exist already.") @click.pass_context def genkey(ctx: click.Context, path: str, password: str, force: bool) -> None: """Generate key pair for RoT or DCK. @@ -158,9 +158,9 @@ def genkey(ctx: click.Context, path: str, password: str, force: bool) -> None: @click.option('-e', '--elf2sb-config', type=click.File('r'), required=False, help='Specify Root Of Trust from configuration file used by elf2sb tool') @click.option('--force', is_flag=True, default=False, - help="Force overwritting of an existing file. Create destination folder, if doesn't exist already.") + help="Force overwriting of an existing file. Create destination folder, if doesn't exist already.") @click.option('--plugin', type=click.Path(exists=True, file_okay=True), required=False, - help='External python file contaning a custom SignatureProvider implementation.') + help='External python file containing a custom SignatureProvider implementation.') @click.argument('dc_file_path', metavar='PATH', type=click.Path(file_okay=True)) @click.pass_context def gendc(ctx: click.Context, plugin: click.Path, dc_file_path: str, config: click.File, @@ -205,7 +205,7 @@ def gendc(ctx: click.Context, plugin: click.Path, dc_file_path: str, config: cli @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/pfr.py b/spsdk/apps/pfr.py index 7a002190..2d4e6725 100644 --- a/spsdk/apps/pfr.py +++ b/spsdk/apps/pfr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -230,7 +230,7 @@ def info(device: str, revision: str, area: str, output: click.Path, open_result: @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/pfrc.py b/spsdk/apps/pfrc.py index bccc5839..28801da5 100644 --- a/spsdk/apps/pfrc.py +++ b/spsdk/apps/pfrc.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause -"""Console script for MBoot module aka BLHost.""" +"""Console script for pfrc (Utility to search for brick-conditions in PFR settings).""" import json import logging import os @@ -58,7 +58,7 @@ def main(cmpa_config: click.File, cfpa_config: click.File, rules_file: click.Fil @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/sdphost.py b/spsdk/apps/sdphost.py index 4d640d83..03c3bc45 100644 --- a/spsdk/apps/sdphost.py +++ b/spsdk/apps/sdphost.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -24,7 +24,26 @@ @click.group() @optgroup.group('Interface configuration', cls=MutuallyExclusiveOptionGroup) @optgroup.option('-p', '--port', help='Serial port') -@optgroup.option('-u', '--usb', help='USB device\'s VID:PID in hex format') +@optgroup.option('-u', '--usb', help=""" +USB device identifier. Following formats are supported: +, or , device/instance path, device name. +vid: hex or dec string; e.g. 0x0AB12, 43794. + +vid/pid: hex or dec string; e.g. 0x0AB12:0x123, 1:3451 + +device name: use 'dscan' utility to list supported device names. + +path - OS specific string. +Windows: +'device instance path' in device manager under Windows OS. + +Linux specific: +Use 'Bus' and 'Device' ID observed using 'lsusb' in #' form; e.g. '3#2'. + +Mac specific: +Use device name and location ID from 'System report' in ' ' +form. e.g. 'SE Blank RT Family @14100000' +""") @click.option('-j', '--json', 'use_json', is_flag=True, help='Use JSON output') @click.option('-v', '--verbose', 'log_level', flag_value=logging.INFO, help='Display more verbose output') @click.option('-d', '--debug', 'log_level', flag_value=logging.DEBUG, help='Display debugging info') @@ -161,7 +180,7 @@ def decode_status_code(status_code: int = None) -> str: @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/sdpshost.py b/spsdk/apps/sdpshost.py index e21c93ac..44dfe588 100644 --- a/spsdk/apps/sdpshost.py +++ b/spsdk/apps/sdpshost.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause -"""Console script for SDP module aka SDPHost.""" +"""Console script for SDPS module aka SDPSHost.""" import logging import sys @@ -65,7 +65,7 @@ def write_file(ctx: click.Context, bin_file: click.File) -> None: @catch_spsdk_error -def safe_main() -> int: +def safe_main() -> None: """Call the main function.""" sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter diff --git a/spsdk/apps/shadowregs.py b/spsdk/apps/shadowregs.py new file mode 100644 index 00000000..cb1da307 --- /dev/null +++ b/spsdk/apps/shadowregs.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Main Debug Authentication Tool application.""" + +import logging + +import sys +import os + +from typing import List, Dict + +import click + +from spsdk import __version__ as spsdk_version +from spsdk.dat import ShadowRegisters +from spsdk.dat.shadow_regs import enable_debug +from spsdk.debuggers.utils import DebugProbeUtils +from spsdk.exceptions import SPSDKError +from spsdk.utils.registers import RegConfig, RegsRegister +from spsdk.apps.utils import catch_spsdk_error +from spsdk import SPSDK_DATA_FOLDER + +logger = logging.getLogger("ShadowRegs") + +LOG_LEVEL_NAMES = [name.lower() for name in logging._nameToLevel] + +CONFIG_DIR = os.path.join(SPSDK_DATA_FOLDER, "shadow_regs") +CONFIG_FILE = "database.json" + +def _open_registers(pass_obj: Dict) -> ShadowRegisters: + """Method opens Registers object based on input arguments. + + :param pass_obj: Input dictionary with arguments. + :return: Active Registers object. + :raise SPSDKError: Raised with any kind of problems with debug probe. + """ + config_file = pass_obj['config_file'] + interface = pass_obj['interface'] + serial_no = pass_obj['serial_no'] + debug_probe_params = pass_obj['debug_probe_params'] + device = pass_obj['device'] + + if device not in RegConfig.devices(config_file): + raise SPSDKError("Invalid or none device parameter(-dev). Use 'listdevs' command to get supported devices.") + + regs_cfg = RegConfig(config_file) + + try: + debug_probes = DebugProbeUtils.get_connected_probes(interface=interface, + hardware_id=serial_no, + user_params=debug_probe_params) + selected_probe = debug_probes.select_probe() + debug_probe = selected_probe.get_probe(debug_probe_params) + debug_probe.open() + if not enable_debug(debug_probe): + raise SPSDKError("Cannot enable debug interface") + + debug_probe.enable_memory_interface() + except SPSDKError as exc: + raise SPSDKError(f"Error with opening debug probe: ({str(exc)})") + + return ShadowRegisters( + debug_probe=debug_probe, + config=regs_cfg, + device=device + ) + +@click.group() +@click.option('-i', '--interface', + help="The interface allow specify to use only one debug probe interface" + " like: 'PyOCD', 'jlink' or 'pemicro'") +@click.option('-d', '--debug', 'log_level', metavar='LEVEL', default='error', + help=f'Set the level of system logging output. ' + f'Available options are: {", ".join(LOG_LEVEL_NAMES)}', + type=click.Choice(LOG_LEVEL_NAMES)) +@click.option('-s', '--serial-no', help="Serial number of debug probe to avoid select menu after startup.") +@click.option('-dev', '--device', type=str, + help="The connected device - to list supported devices use 'listdevs' command.") +@click.option('-o', '--debug-probe-option', multiple=True, help="This option could be used " + "multiply to setup non-standard option for debug probe.") +@click.version_option(spsdk_version, '-v', '--version') +@click.help_option('--help') +@click.pass_context +def main(ctx: click.Context, interface: str, log_level: str, + serial_no: str, debug_probe_option: List[str], device: str) -> int: + """NXP Shadow Registers control Tool.""" + logging.basicConfig(level=log_level.upper()) + logger.setLevel(level=log_level.upper()) + + config_filename = os.path.join(CONFIG_DIR, CONFIG_FILE) + + probe_user_params = {} + for par in debug_probe_option: + if par.count("=") != 1: + raise SPSDKError(f"Invalid -o parameter {par}!") + else: + par_splitted = par.split("=") + probe_user_params[par_splitted[0]] = par_splitted[1] + + ctx.obj = { + 'config_file': config_filename, + 'interface': interface, + 'serial_no': serial_no, + 'debug_probe_params': probe_user_params, + 'device': device + } + + return 0 + +# Enable / Disable debug +@main.command() +@click.option('-f', '--filename', default="sr_config.yml", + help="The name of file used to save the current configuration." + " Default name is 'sr_config'. The extension is always '*.yml'.") +@click.option('-r', '--raw', is_flag=True, default=False, + help="The stored configuration will include also the computed fields " + "and anti-pole registers.") +@click.pass_obj +def saveconfig(pass_obj: dict, filename: str = "sr_config.yml", raw: bool = False) -> None: + """Save current state of shadow registers to YML file.""" + try: + shadow_regs: ShadowRegisters = _open_registers(pass_obj) + shadow_regs.reload_registers() + shadow_regs.create_yml_config(filename, raw) + click.echo(f"The Shadow registers has been saved into {filename} YAML file") + except SPSDKError as exc: + raise SPSDKError(f"Save configuration of Shadow registers failed! ({str(exc)})") + +@main.command() +@click.option('-f', '--filename', default="sr_config.yml", + help="The name of file used to load a new configuration." + " Default name is 'sr_config'. The extension is always '*.yml'.") +@click.option('-r', '--raw', is_flag=True, default=False, + help="In loaded configuration will accepted also the computed fields " + "and anti-pole registers.") +@click.pass_obj +def loadconfig(pass_obj: dict, filename: str = "sr_config.yml", raw: bool = False) -> None: + """Load new state of shadow registers from YML file into microcontroller.""" + try: + shadow_regs: ShadowRegisters = _open_registers(pass_obj) + shadow_regs.load_yml_config(filename, raw) + shadow_regs.sets_all_registers() + click.echo(f"The Shadow registers has been loaded by configuration in {filename} YAML file") + except SPSDKError as exc: + raise SPSDKError(f"Load configuration of Shadow registers failed ({str(exc)})!") + +@main.command() +@click.option('-r', '--rich', is_flag=True, default=False, help="Enables rich format of printed output.") +@click.pass_obj +def printregs(pass_obj: dict, rich: bool = False) -> None: + """Print all Shadow registers including theirs current values. + + In case of needed more information, there is also provided rich format of print. + """ + try: + shadow_regs: ShadowRegisters = _open_registers(pass_obj) + shadow_regs.reload_registers() + + for reg in shadow_regs.regs.registers: + click.echo(f"Register Name: {reg.name}") + click.echo(f"Register value: {reg.get_hex_value()}") + if rich: + click.echo(f"Register description: {reg.description}") + address = shadow_regs.offset + reg.offset + click.echo(f"Register address: 0x{address:08X}") + click.echo(f"Register width: {reg.width} bits") + click.echo() + except SPSDKError as exc: + raise SPSDKError(f"Print of Shadow registers failed! ({str(exc)})") + +@main.command() +@click.option('-r', '--reg', type=str, help="The name of register to be read.") +@click.pass_obj +def getreg(pass_obj: dict, reg: str) -> None: + """The command prints the current value of one shadow register.""" + shadow_regs: ShadowRegisters = _open_registers(pass_obj) + try: + register: RegsRegister = shadow_regs.regs.find_reg(reg) + shadow_regs.reload_register(register) + click.echo(f"Value of {reg} is: {register.get_hex_value()}") + except SPSDKError as exc: + raise SPSDKError(f"Getting Shadow register failed! ({str(exc)})") + +@main.command() +@click.option('-r', '--reg', type=str, help="The name of register to be set.") +@click.option('-v', '--reg_val', type=str, help="The new value of register in hex format.") +@click.pass_obj +def setreg(pass_obj: dict, reg: str, reg_val: str) -> None: + """The command sets a value of one shadow register defined by parameter.""" + shadow_regs: ShadowRegisters = _open_registers(pass_obj) + try: + shadow_regs.set_register(reg, reg_val) + click.echo(f"The Shadow register {reg} has been set to {reg_val} value") + except SPSDKError as exc: + raise SPSDKError(f"Setting Shadow register failed! ({str(exc)})") + +@main.command() +@click.pass_obj +def reset(pass_obj: dict) -> None: + """The command resets connected device.""" + shadow_regs: ShadowRegisters = _open_registers(pass_obj) + shadow_regs._probe.reset() + click.echo(f"The target has been reset.") + + +@main.command() +@click.pass_obj +def listdevs(pass_obj: dict) -> None: + """The command prints a list of supported devices.""" + config_filename = pass_obj['config_file'] + for ix, device in enumerate(RegConfig.devices(config_filename)): + click.echo(f"{ix:03}: {device}") + +@catch_spsdk_error +def safe_main() -> None: + """Safe main method.""" + sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter + +if __name__ == "__main__": + safe_main() diff --git a/spsdk/apps/spsdk_apps.py b/spsdk/apps/spsdk_apps.py index 3c1e5b51..0e0dea39 100644 --- a/spsdk/apps/spsdk_apps.py +++ b/spsdk/apps/spsdk_apps.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -18,11 +18,15 @@ from spsdk import __version__ as spsdk_version from .blhost import main as blhost_main from .elftosb import main as elftosb_main +from .nxpcertgen import main as nxpcertgen_main from .nxpdebugmbox import main as nxpdebugmbox_main +from .nxpdevscan import main as nxpdevscan_main from .nxpkeygen import main as nxpkeygen_main from .pfr import main as pfr_main +from .pfrc import main as pfrc_main from .sdphost import main as sdphost_main from .sdpshost import main as sdpshost_main +from .shadowregs import main as shadowregs_main from .utils import catch_spsdk_error @@ -34,13 +38,16 @@ def main() -> int: main.add_command(blhost_main, name='blhost') +main.add_command(elftosb_main, name='elftosb') +main.add_command(nxpcertgen_main, name='nxpcertgen') +main.add_command(nxpdebugmbox_main, name='nxpdebugmbox') +main.add_command(nxpdevscan_main, name='nxpdscan') +main.add_command(nxpkeygen_main, name='nxpkeygen') +main.add_command(pfr_main, name='pfr') +main.add_command(pfrc_main, name='pfrc') main.add_command(sdphost_main, name='sdphost') main.add_command(sdpshost_main, name='sdpshost') -main.add_command(pfr_main, name='pfr') -main.add_command(nxpkeygen_main, name='nxpkeygen') -main.add_command(nxpdebugmbox_main, name='nxpdebugmbox') -main.add_command(elftosb_main, name='elftosb') - +main.add_command(shadowregs_main, name='shadowreg') @catch_spsdk_error def safe_main() -> Any: diff --git a/spsdk/apps/utils.py b/spsdk/apps/utils.py index 1aaa076f..d8f3ad0e 100644 --- a/spsdk/apps/utils.py +++ b/spsdk/apps/utils.py @@ -1,15 +1,16 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for general utilities used by applications.""" +import re import sys from functools import wraps -from typing import Union, Any, Callable, Tuple, Optional +from typing import Union, Any, Callable, Tuple import click import hexdump @@ -140,8 +141,8 @@ def wrapper(*args: tuple, **kwargs: dict) -> Any: except (AssertionError, SPSDKError) as spsdk_exc: click.echo(f"ERROR:{spsdk_exc}") sys.exit(2) - except Exception as base_exc: # pylint: disable=W0703 - click.echo(f"GENERAL ERROR:{base_exc}") + except Exception as base_exc: # pylint: disable=broad-except + click.echo(f"GENERAL ERROR: {type(base_exc).__name__}: {base_exc}") sys.exit(3) return wrapper @@ -160,3 +161,28 @@ def parse_file_and_size(file_and_size: str) -> Tuple[str, int]: file_path = file_and_size file_size = -1 return file_path, file_size + + +def parse_hex_data(hex_data: str) -> bytes: + """Parse hex-data into bytes. + + :param hex_data: input hex-data, e.g: {{1122}}, {{11 22}} + :raises SPSDKError: Failure to parse given input + :return: data parsed from input + """ + hex_data = hex_data.replace(' ', '') + if not hex_data.startswith('{{') or not hex_data.endswith('}}'): + raise SPSDKError("Incorrectly formated hex-data: Need to start with {{ and end with }}") + + hex_data = hex_data.replace('{{', '').replace('}}', '') + if len(hex_data) % 2: + raise SPSDKError("Incorrectly formated hex-data: Need to have even number of characters") + if not re.fullmatch(r"[0-9a-fA-F]*", hex_data): + raise SPSDKError("Incorrect hex-data: Need to have valid hex string") + + str_parts = [hex_data[i: i+2] for i in range(0, len(hex_data), 2)] + byte_pieces = [int(part, 16) for part in str_parts] + result = bytes(byte_pieces) + if not result: + raise SPSDKError("Incorrect hex-data: Unable to get any data") + return bytes(byte_pieces) diff --git a/spsdk/crypto/__init__.py b/spsdk/crypto/__init__.py index 8d52fa32..eea773d0 100644 --- a/spsdk/crypto/__init__.py +++ b/spsdk/crypto/__init__.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for crypto operations (certificate and key management). -Moreover it includes SignatureProvider as an Interface for all potential signature providers. +Moreover, it includes SignatureProvider as an Interface for all potential signature providers. It provides following functionality: @@ -50,9 +50,13 @@ from cryptography.hazmat.primitives.asymmetric.rsa import * # type: ignore from cryptography.hazmat.primitives.serialization import * import cryptography.hazmat.primitives.asymmetric.utils as utils_cryptography + from cryptography.x509 import * -from cryptography.x509.oid import * -from cryptography.x509.base import * +from cryptography.x509 import ( + AuthorityInformationAccessOID, CRLEntryExtensionOID, + CertificatePoliciesOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, + ObjectIdentifier, SignatureAlgorithmOID +) from .certificate_management import * from .keys_management import * diff --git a/spsdk/crypto/certificate_management.py b/spsdk/crypto/certificate_management.py index 0d40790e..839f7f1f 100644 --- a/spsdk/crypto/certificate_management.py +++ b/spsdk/crypto/certificate_management.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for certificate management (generating certificate, validating certificate, chains).""" @@ -9,13 +9,15 @@ from datetime import datetime, timedelta from typing import Union, List +from spsdk.apps.utils import catch_spsdk_error from spsdk.crypto import x509, InvalidSignature, Encoding, default_backend, \ hashes, RSAPrivateKey, RSAPublicKey, ExtensionOID, \ - Certificate, CertificateSigningRequest, padding + Certificate, CertificateSigningRequest, padding, EllipticCurvePublicKey, EllipticCurvePrivateKey -def generate_certificate(subject: x509.Name, issuer: x509.Name, subject_public_key: RSAPublicKey, - issuer_private_key: RSAPrivateKey, serial_number: int = None, +def generate_certificate(subject: x509.Name, issuer: x509.Name, + subject_public_key: Union[EllipticCurvePublicKey, RSAPublicKey], + issuer_private_key: Union[EllipticCurvePrivateKey, RSAPrivateKey], serial_number: int = None, if_ca: bool = True, duration: int = 3650, path_length: int = 2) -> Certificate: """Generate certificate. @@ -123,3 +125,16 @@ def convert_certificate_into_bytes(certificate: Certificate, encoding: Encoding """ assert isinstance(certificate, Certificate), "The input is not a Certificate" return certificate.public_bytes(encoding) + + +def generate_name_struct(common_name: str, country: str) -> x509.Name: + """Set the issuer/subject distinguished name. + + :param common_name: string representing name + :param country: string representing country + :return: ordered list of attributes of certificate + """ + return x509.Name([ + x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, common_name), + x509.NameAttribute(x509.oid.NameOID.COUNTRY_NAME, country) + ]) diff --git a/spsdk/crypto/loaders.py b/spsdk/crypto/loaders.py index e5f03325..0face7f9 100644 --- a/spsdk/crypto/loaders.py +++ b/spsdk/crypto/loaders.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Loading methods for keys/certificates/CSR.""" @@ -34,7 +34,7 @@ def solve(key_data: bytes) -> PrivateKey: :param key_data: given private keys data :return: loaded private key """ - return { + return { # type: ignore Encoding.PEM: load_pem_private_key, Encoding.DER: load_der_private_key }[real_encoding](key_data, password, default_backend()) @@ -57,7 +57,7 @@ def solve(key_data: bytes) -> PublicKey: :param key_data: given public keys data :return: loaded public key """ - return { + return { # type: ignore Encoding.PEM: load_pem_public_key, Encoding.DER: load_der_public_key }[real_encoding](key_data, default_backend()) @@ -80,7 +80,7 @@ def solve(certificate_data: bytes) -> Certificate: :param certificate_data: given certificate data :return: loaded certificate """ - return { + return { # type: ignore Encoding.PEM: load_pem_x509_certificate, Encoding.DER: load_der_x509_certificate }[real_encoding](certificate_data, default_backend()) diff --git a/spsdk/dat/__init__.py b/spsdk/dat/__init__.py index 87a3a623..436f53f8 100644 --- a/spsdk/dat/__init__.py +++ b/spsdk/dat/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -10,3 +10,4 @@ from .debug_credential import DebugCredential from .dac_packet import DebugAuthenticationChallenge from .dar_packet import DebugAuthenticateResponse +from .shadow_regs import ShadowRegisters diff --git a/spsdk/dat/dar_packet.py b/spsdk/dat/dar_packet.py index 7f5c639c..ba6a55d0 100644 --- a/spsdk/dat/dar_packet.py +++ b/spsdk/dat/dar_packet.py @@ -24,7 +24,7 @@ def __init__(self, debug_credential: DebugCredential, auth_beacon: int, dac: DebugAuthenticationChallenge, path_dck_private: str) -> None: """Initialize the DebugAuthenticateResponse object. - :param debug_credential:the path, where the dc is store + :param debug_credential: the path, where the dc is store :param auth_beacon: authentication beacon value :param dac: the path, where the dac is store :param path_dck_private: the path, where the dck private key is store diff --git a/spsdk/dat/debug_mailbox.py b/spsdk/dat/debug_mailbox.py index 195bcb4f..1f1e73a8 100644 --- a/spsdk/dat/debug_mailbox.py +++ b/spsdk/dat/debug_mailbox.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for NXP SPDK DebugMailbox support.""" @@ -41,7 +41,7 @@ def __init__(self, debug_probe: DebugProbe, reset: bool = True, moredelay: float # reset line of the chip, or set the CHIP_RESET_REQ (This can be done at the # same time as setting the RESYNCH_REQ bit). - logger.debug(f"No reset mode: {self.reset!r}") + logger.debug(f"Reset mode: {self.reset!r}") if self.reset: self.debug_probe.dbgmlbx_reg_write( addr=self.registers.CSW.address, @@ -71,7 +71,6 @@ def __init__(self, debug_probe: DebugProbe, reset: bool = True, moredelay: float if retries == 0: retries = 20 raise IOError("TransferTimeoutError limit exceeded!") - # if isinstance(e, TransferTimeoutError): sleep(0.05) diff --git a/spsdk/dat/dm_commands.py b/spsdk/dat/dm_commands.py index c77f79eb..ab073c73 100644 --- a/spsdk/dat/dm_commands.py +++ b/spsdk/dat/dm_commands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -116,6 +116,11 @@ def __init__(self, dm: DebugMailbox) -> None: """Initialize.""" super(SetFaultAnalysisMode, self).__init__(dm, id=6, name='SET_FA_MODE') +class StartDebugSession(DebugMailboxCommand): + """Class for StartDebugSession.""" + def __init__(self, dm: DebugMailbox) -> None: + """Initialize.""" + super(StartDebugSession, self).__init__(dm, id=7, name='START_DBG_SESSION') class DebugAuthenticationStart(DebugMailboxCommand): """Class for DebugAuthenticationStart.""" diff --git a/spsdk/dat/shadow_regs.py b/spsdk/dat/shadow_regs.py new file mode 100644 index 00000000..a21f509e --- /dev/null +++ b/spsdk/dat/shadow_regs.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2020-2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""The shadow registers control DAT support file.""" +import logging +from typing import Any +import math + +from ruamel.yaml import YAML +import ruamel.yaml + +from spsdk.utils.registers import Registers, RegsRegister, RegConfig, BitfieldNotFound, value_to_bytes +from spsdk.dat.dm_commands import StartDebugSession + +from spsdk.exceptions import SPSDKError +from spsdk.debuggers.debug_probe import (DebugProbe, DebugProbeError) +from spsdk.dat.debug_mailbox import DebugMailbox + +logger = logging.getLogger(__name__) + +class IoVerificationError(SPSDKError): + """The error during wrie verification - exception for use with SPSDK.""" + +class ShadowRegisters(): + """SPSDK support to control the shadow registers.""" + + def __init__(self, debug_probe: DebugProbe, config: RegConfig, device: str, revision: str = "latest") -> None: + """Initialization of Shadow register class.""" + self._probe = debug_probe + self.config = config + self.device = device + self.offset = int(self.config.get_address(self.device, remove_underscore=True), 16) + + self.regs = Registers(self.device) + rev = revision if revision != "latest" else config.get_latest_revision(self.device) + self.regs.load_registers_from_xml(config.get_data_file(self.device, rev)) + + def _write_shadow_reg(self, addr: int, data: int, verify: int = True) -> None: + """The function write a shadow register. + + The funstion writes shadow register in to MCU and verify the write if requested. + + param addr: Shadow register address. + param data: Shadow register data to write. + param verify: If True the write is read back and compare, otherwise no check is done + raises IoVerificationError + """ + self._probe.mem_reg_write(addr, data) + + if verify: + readback = self._probe.mem_reg_read(addr) + if readback != data: + raise IoVerificationError(f"The written data 0x{data:08X} to 0x{addr:08X} address are invalid.") + + def reload_registers(self) -> None: + """Reload all the values in managed registers.""" + for reg in self.regs.registers: + self.reload_register(reg) + + def sets_all_registers(self) -> None: + """Update all shadow registers in target by local values.""" + for reg in self.regs.registers: + self.set_register(reg.name, reg.get_value()) + + def reload_register(self, reg: RegsRegister) -> None: + """Reload the value in requested register. + + :param reg: The register to reload from the HW. + """ + reg.set_value(self.get_register(reg.name)) + + @staticmethod + def _reverse_bytes_in_longs(arr: bytearray) -> bytearray: + """The function reverse byte order in longs from input bytes. + + param arr: Input array. + :return: New array with reversed bytes. + :raises ValueError: Raises when invalid value is in input. + """ + arr_len = len(arr) + if arr_len % 4 != 0: + raise ValueError("The input array is not in modulo 4!") + + result = bytearray() + + for x in range(arr_len): + word = bytearray(arr[x*4:x*4+4]) + word.reverse() + result.extend(word) + return result + + def set_register(self, reg_name: str, data: Any) -> None: + """The function sets the value of the specified register. + + param reg: The register name. + param data: The new data to be stored to shadow register. + raises DebugProbeError: The debug probe is not specified. + """ + if self._probe is None: + raise DebugProbeError("There is no debug probe.") + + try: + reg = self.regs.find_reg(reg_name) + value = value_to_bytes(data) + + start_address = self.offset + reg.offset + width = reg.width + + if width < len(value) * 8: + raise SPSDKError(f"Invalid length of data for shadow register write.") + + if width < 32: + width = 32 + + data_alligned = bytearray(math.ceil(width / 8)) + data_alligned[len(data_alligned) - len(value) : len(data_alligned)] = value + + if reg.reverse: + data_alligned = self._reverse_bytes_in_longs(data_alligned) + + if width == 32: + self._write_shadow_reg(start_address, int.from_bytes(data_alligned[:4], "big")) + else: + end_address = start_address + math.ceil(width / 8) + addresses = range(start_address, end_address, 4) + + i = 0 + for addr in addresses: + self._write_shadow_reg(addr, int.from_bytes(data_alligned[i:i+4], "big")) + i += 4 + + reg.set_value(value) + + except SPSDKError as exc: + raise SPSDKError(f"The get shadow register failed({str(exc)}).") + + def get_register(self, reg_name: str) -> bytes: + """The function returns value of the requested register. + + param reg: The register name. + return: The value of requested register in bytes + raises DebugProbeError: The debug probe is not specified. + """ + if self._probe is None: + raise DebugProbeError("There is no debug probe.") + + result = bytearray() + try: + reg = self.regs.find_reg(reg_name) + + start_address = self.offset + reg.offset + width = reg.width + + if width < 32: + width = 32 + + if width == 32: + result.extend(self._probe.mem_reg_read(start_address).to_bytes(4, "big")) + else: + end_address = start_address + math.ceil(width / 8) + addresses = range(start_address, end_address, 4) + + for addr in addresses: + result.extend(self._probe.mem_reg_read(addr).to_bytes(4, "big")) + + if reg.reverse: + result = self._reverse_bytes_in_longs(result) + + except SPSDKError as exc: + raise SPSDKError(f"The get shadow register failed({str(exc)}).") + + return result + + def create_yml_config(self, file_name: str, raw: bool = False) -> None: + """The function creates the configuration YML file. + + :param file_name: The file_name (without extension) of stored configuration. + :param raw: Raw output of configuration (including computed fields and anti-pole registers) + """ + CM = ruamel.yaml.comments.CommentedMap # defaults to block style + + antipole_regs = self.config.get_antipole_regs(self.device) + computed_fields = self.config.get_computed_fields(self.device) + + yaml = YAML() + yaml.indent(sequence=4, offset=2) + data = CM() + data["registers"] = CM() + + for reg in self.regs.registers: + if not raw and reg.name in antipole_regs.values(): + continue + reg_yml = CM() + reg_yml.yaml_set_start_comment("Reg Description:" + reg.description) + reg_yml.insert(1, "name", reg.name, comment="The name of the register") + data["registers"][reg.name] = reg_yml + if len(reg.get_bitfields()) > 0: + btf_yml = CM() + reg_yml["bitfields"] = btf_yml + for i, bitf in enumerate(reg.get_bitfields()): + if not raw and reg.name in computed_fields.keys() and bitf.name in computed_fields[reg.name].keys(): + continue + possible_values = "" + if bitf.has_enums(): + # print the comments as a hint of possible values + possible_values = f", (Possible values: {', '.join(bitf.get_enum_names())})" + btf_yml.insert(i, + bitf.name, + bitf.get_enum_value(), + comment=f"The width: {bitf.width} bits{possible_values}") + else: + reg_yml.insert(2, "value", reg.get_hex_value(), comment="The value of the register") + + with open(file_name, "w") as out_file: + yaml.dump(data, out_file) + + def load_yml_config(self, file_name: str, raw: bool = False) -> None: + """The function loads the configuration from YML file. + + :param file_name: The file_name (without extension) of stored configuration. + :param raw: Raw input of configuration (including computed fields and anti-pole registers) + :raise SPSDKError: When the configuration file not found. + """ + antipole_regs = self.config.get_antipole_regs(self.device) + computed_fields = self.config.get_computed_fields(self.device) + try: + with open(file_name, "r") as yml_config_file: + yaml = YAML() + yaml.indent(sequence=4, offset=2) + data = yaml.load(yml_config_file) + except FileNotFoundError: + raise SPSDKError("File with YML configuration doesn't exists.") + + for reg in data["registers"].keys(): + if not raw and reg in antipole_regs.values(): + continue + if reg not in self.regs.get_reg_names(): + continue + #The loaded register is our + if "value" in data["registers"][reg].keys(): + val = data['registers'][reg]['value'] + val = val.replace("0x", "") + self.regs.find_reg(reg).set_value(bytes.fromhex(val)) + elif "bitfields" in data["registers"][reg].keys(): + for bitf_name in data["registers"][reg]["bitfields"]: + try: + self.regs.find_reg(reg).find_bitfield(bitf_name) + except BitfieldNotFound: + continue + if not raw and reg in computed_fields.keys() and bitf_name in computed_fields[reg].keys(): + continue + bitf = self.regs.find_reg(reg).find_bitfield(bitf_name) + if bitf.has_enums(): + #solve the bitfields store in enums string + bitf.set_enum_value(data["registers"][reg]["bitfields"][bitf_name]) + else: + #load bitfield data + bitf.set_value(int(data["registers"][reg]["bitfields"][bitf_name])) + else: + logger.error(f"There are no data for {reg} register.") + + if not raw and reg in computed_fields.keys(): + # Check the computed fields + for field in computed_fields[reg].keys(): + val = self.regs.find_reg(reg).get_value() + if hasattr(self, computed_fields[reg][field]): + method = getattr(self, computed_fields[reg][field], None) + computed_val = method(val) + self.regs.find_reg(reg).set_value(computed_val) + else: + raise SPSDKError(f"The '{computed_fields[reg][field]}' compute function doesn't exists.") + + if not raw and reg in antipole_regs.keys(): + #Write also anti-pole value + val = self.regs.find_reg(reg).get_value() + self.regs.find_reg(antipole_regs[reg]).set_value(self.antipolize_reg(val)) + + logger.debug(f"The register {reg} has been loaded from configuration.") + + @staticmethod + def antipolize_reg(val: bytes) -> bytes: + """Antipolize given register value. + + :param val: Input register value. + :return: Antipolized value. + """ + newval = [0]*len(val) + for i, val_byte in enumerate(val): + newval[i] = val_byte ^ 0xFF + return bytes(newval) + + # CRC8 - ITU + @staticmethod + def crc_update(data: bytes, crc: int = 0, is_final: bool = True) -> int: + """The function compute the CRC8 ITU method from given bytes. + + :param data: Input data to compute CRC. + :param crc: The seed for CRC. + :param is_final: The flag the the function should retrn final result. + :return: The CRC result. + """ + k = 0 + data_len = len(data) + while data_len != 0: + data_len -= 1 + c = data[k] + k += 1 + for i in range(8): + bit = (crc & 0x80) != 0 + if (c & (0x80>>i)) != 0: + bit = not bit + crc <<= 1 + if bit: + crc ^= 0x07 + crc &= 0xff + if is_final: + return (crc & 0xff) ^ 0x55 + else: + return crc & 0xff + + + def comalg_dcfg_cc_socu_crc8(self, val: bytes) -> bytes: + """Function that creates the crc for DCFG_CC_SOCU. + + :param val: Input DCFG_CC_SOCU Value. + :return: Returns the value of DCFG_CC_SOCU with computed CRC8 field. + """ + ret = [0]*4 + ret[0:3] = val[0:3] + input = bytearray(val[0:3]) + input.reverse() + ret[3] = self.crc_update(input) + return bytes(ret) + + def comalg_dcfg_cc_socu_rsvd(self, val: bytes) -> bytes: + """Function fill up the DCFG_CC_SOCU RSVD filed by 0x40 to satisfy MCU needs. + + :param val: Input DCFG_CC_SOCU Value. + :return: Returns the value of DCFG_CC_SOCU with computed CRC8 field. + """ + new_val = bytearray(val) + new_val[0] &= ~0xFE + new_val[0] |= 0x40 + return new_val + + def comalg_do_nothig(self, val: bytes) -> bytes: + """Function that do nothing. + + :param val: Input Value. + :return: Returns same value as it get. + """ + return val + +def enable_debug(probe: DebugProbe, ap_mem: int = 0) -> bool: + """Function that enables debug access ports on devices with debug mailbox. + + :param probe: Initialized debug probe. + :param ap_mem: Index of Debug access port for memory interface. + :return: True if debug port is enabled, False otherwise + :raises SPSDKError: Unlock method failed. + """ + debug_enabled = False + try: + def test_ahb_access(ap_mem: int) -> bool: + logger.debug("step T.1: Activate the correct AP") + probe.coresight_reg_write(access_port=False, addr=2*4, data=ap_mem) + + logger.debug("step T.2: Set the AP access size and address mode") + probe.coresight_reg_write(access_port=True, + addr=probe.get_coresight_ap_address(ap_mem, 0*4), + data=0x22000012) + + logger.debug("step T.3: Set the initial AHB address to access") + probe.coresight_reg_write(access_port=True, + addr=probe.get_coresight_ap_address(ap_mem, 1*4), + data=0xE000ED00) + + logger.debug("step T.4: Access the memory system at that address") + try: + chip_id = probe.coresight_reg_read(access_port=True, + addr=probe.get_coresight_ap_address(ap_mem, 3*4)) + logger.debug(f"ChipID={chip_id:08X}") + except DebugProbeError: + chip_id = 0xFFFFFFFF + logger.debug(f"ChipID can't be read") + + # Check if the device is locked + return chip_id not in (0xFFFFFFFF, 0) + + logger.debug("step 3: Check if AHB is enabled") + + if not test_ahb_access(ap_mem): + logger.debug("Locked Device. Launching unlock sequence.") + + # Start debug mailbox system + dbg_mlbx = DebugMailbox(debug_probe=probe) + StartDebugSession(dm=dbg_mlbx).run() + + # Recheck the AHB access + if test_ahb_access(ap_mem): + logger.debug(f"Access granted") + debug_enabled = True + else: + logger.debug(f"Enable debug operation failed!") + else: + logger.debug("Unlocked Device") + debug_enabled = True + + except AttributeError as exc: + raise SPSDKError(f"Invalid input parameters({str(exc)})") + + except DebugProbeError as exc: + raise SPSDKError(f"Can't unlock device ({str(exc)})") + + return debug_enabled diff --git a/spsdk/dat/utils.py b/spsdk/dat/utils.py index 24df0c27..5eb18338 100644 --- a/spsdk/dat/utils.py +++ b/spsdk/dat/utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -38,7 +38,7 @@ def ecc_public_numbers_to_bytes(public_numbers: crypto.EllipticCurvePublicNumber """ x = public_numbers.x y = public_numbers.y - length = length or math.ceil(x.bit_length() // 8) + length = length or math.ceil(x.bit_length() / 8) x_bytes = x.to_bytes(length, 'big') y_bytes = y.to_bytes(length, 'big') return x_bytes + y_bytes diff --git a/tests/mcu_examples/data/rt102x/exec_hab_audit.c b/spsdk/data/cpu_data/exec_hab_audit_rt1020.c similarity index 100% rename from tests/mcu_examples/data/rt102x/exec_hab_audit.c rename to spsdk/data/cpu_data/exec_hab_audit_rt1020.c diff --git a/tests/mcu_examples/data/rt105x/exec_hab_audit.c b/spsdk/data/cpu_data/exec_hab_audit_rt1050.c similarity index 100% rename from tests/mcu_examples/data/rt105x/exec_hab_audit.c rename to spsdk/data/cpu_data/exec_hab_audit_rt1050.c diff --git a/tests/mcu_examples/data/rt106x/exec_hab_audit.c b/spsdk/data/cpu_data/exec_hab_audit_rt1060.c similarity index 100% rename from tests/mcu_examples/data/rt106x/exec_hab_audit.c rename to spsdk/data/cpu_data/exec_hab_audit_rt1060.c diff --git a/spsdk/data/cpu_data/rt1020_exec_hab_audit.bin b/spsdk/data/cpu_data/rt1020_exec_hab_audit.bin new file mode 100644 index 00000000..c0349a66 Binary files /dev/null and b/spsdk/data/cpu_data/rt1020_exec_hab_audit.bin differ diff --git a/tests/mcu_examples/data/rt105x/exec_hab_audit.bin b/spsdk/data/cpu_data/rt1050_exec_hab_audit.bin similarity index 100% rename from tests/mcu_examples/data/rt105x/exec_hab_audit.bin rename to spsdk/data/cpu_data/rt1050_exec_hab_audit.bin diff --git a/tests/mcu_examples/data/rt106x/exec_hab_audit.bin b/spsdk/data/cpu_data/rt1060_exec_hab_audit.bin similarity index 100% rename from tests/mcu_examples/data/rt106x/exec_hab_audit.bin rename to spsdk/data/cpu_data/rt1060_exec_hab_audit.bin diff --git a/spsdk/data/pfr/cmpa/niobe4_a1.xml b/spsdk/data/pfr/cmpa/niobe4_a1.xml index 7fe4c824..d59e675d 100644 --- a/spsdk/data/pfr/cmpa/niobe4_a1.xml +++ b/spsdk/data/pfr/cmpa/niobe4_a1.xml @@ -210,16 +210,16 @@ - - + + + + + - + - - - diff --git a/spsdk/data/pfr/cmpa/niobe4mini_a1.xml b/spsdk/data/pfr/cmpa/niobe4mini_a1.xml index ffd8863e..e0cde15a 100644 --- a/spsdk/data/pfr/cmpa/niobe4mini_a1.xml +++ b/spsdk/data/pfr/cmpa/niobe4mini_a1.xml @@ -221,16 +221,16 @@ - - + + + + + - + - - - @@ -295,13 +295,13 @@ - + - + diff --git a/spsdk/data/pfr/cmpa/niobe4nano_a1.xml b/spsdk/data/pfr/cmpa/niobe4nano_a1.xml index 644ada23..632200e1 100644 --- a/spsdk/data/pfr/cmpa/niobe4nano_a1.xml +++ b/spsdk/data/pfr/cmpa/niobe4nano_a1.xml @@ -295,13 +295,13 @@ - + - + diff --git a/spsdk/data/shadow_regs/database.json b/spsdk/data/shadow_regs/database.json new file mode 100644 index 00000000..c7e74c93 --- /dev/null +++ b/spsdk/data/shadow_regs/database.json @@ -0,0 +1,32 @@ +{ + "devices": { + "imxrt595": { + "revisions": { + "b0": "imxrt595_b0.xml" + }, + "latest": "b0", + "address": "0x4013_0000", + "inverted_regs": { + "DCFG_CC_SOCU": "DCFG_CC_SOCU_AP" + }, + "computed_fields": { + "DCFG_CC_SOCU": {"RSVD": "comalg_dcfg_cc_socu_rsvd", "CRC8": "comalg_dcfg_cc_socu_crc8"}, + "SEC_BOOT_CFG[5]": {"RESERVED": "comalg_do_nothig"} + } + }, + "imxrt685": { + "revisions": { + "b0": "imxrt685_b0.xml" + }, + "latest": "b0", + "address": "0x4013_0000", + "inverted_regs": { + "DCFG_CC_SOCU": "DCFG_CC_SOCU_AP" + }, + "computed_fields": { + "DCFG_CC_SOCU": {"CRC8": "comalg_dcfg_cc_socu_crc8"}, + "SEC_BOOT_CFG[5]": {"RESERVED": "comalg_do_nothig"} + } + } + } +} diff --git a/spsdk/data/shadow_regs/imxrt595_b0.xml b/spsdk/data/shadow_regs/imxrt595_b0.xml new file mode 100644 index 00000000..a4f881e1 --- /dev/null +++ b/spsdk/data/shadow_regs/imxrt595_b0.xml @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spsdk/data/shadow_regs/imxrt685_b0.xml b/spsdk/data/shadow_regs/imxrt685_b0.xml new file mode 100644 index 00000000..0246c65c --- /dev/null +++ b/spsdk/data/shadow_regs/imxrt685_b0.xml @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spsdk/debuggers/__init__.py b/spsdk/debuggers/__init__.py index 55cb98da..c7a4d447 100644 --- a/spsdk/debuggers/__init__.py +++ b/spsdk/debuggers/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause diff --git a/spsdk/debuggers/debug_probe.py b/spsdk/debuggers/debug_probe.py index bc539eb5..fabd2c6e 100644 --- a/spsdk/debuggers/debug_probe.py +++ b/spsdk/debuggers/debug_probe.py @@ -1,35 +1,15 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for DebugMailbox Debug probes support.""" -from typing import List, Dict, Any +from typing import Dict from spsdk.exceptions import SPSDKError -class ProbeDescription(): - """NamedTuple for DAT record of debug probe description.""" - interface: str - hardware_id: str - description: str - probe: "DebugProbe" - - def __init__(self, interface: str, hardware_id: str, description: str, probe: Any) -> None: - """Initialization of Debug probe dscription class. - - param interface: Probe Interface. - param hardware_id: Probe Hardware ID(Identification). - param description: Probe Text description. - param probe: Probe name of the class. - """ - self.interface = interface - self.hardware_id = hardware_id - self.description = description - self.probe = probe - class DebugProbeError(SPSDKError): """The general issue with debug probe exception for use with SPSDK.""" @@ -45,11 +25,18 @@ class DebugProbeTransferError(DebugProbeError): class DebugProbeNotOpenError(DebugProbeError): """The debug probe is not opened exception for use with SPSDK.""" +class DebugProbeMemoryInterfaceAPNotFoundError(DebugProbeError): + """The target doesn't have memory interface access port exception for use with SPSDK.""" + +class DebugProbeMemoryInterfaceNotEnabled(DebugProbeError): + """The target doesn't have memory interface enabled exception for use with SPSDK.""" + class DebugProbe(): """Abstraction class to define SPSDK debug probes interface.""" # Constants to detect the debug mailbox access port APBANKSEL = 0x000000f0 + APADDR = 0x00ffffff APSEL = 0xff000000 APSEL_SHIFT = 24 APSEL_APBANKSEL = APSEL | APBANKSEL @@ -62,15 +49,17 @@ def __init__(self, hardware_id: str, user_params: Dict = None) -> None: """ self.hardware_id = hardware_id self.user_params = user_params + self.enabled_memory_interface = True self.dbgmlbx_ap_ix = -1 @classmethod - def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> List[ProbeDescription]: + def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> list: """Functions returns the list of all connected probes in system. There is option to look for just for one debug porbe defined by its hardware ID. - :param hardware_id: None to list all probes, otherwice the the only probe with matching - hardware id is listed. + + :param hardware_id: None to list all probes, otherwice the the only probe with + matching hardware id is listed. :param user_params: The user params dictionary :return: ProbeDescription :raises NotImplementedError: The get_connected_probes is NOT implemented @@ -82,6 +71,7 @@ def debug_mailbox_access_port(self) -> int: """Returns debug mailbox access port. In case that the access port is not detected or selected, it returns value less than zero. + :return: Index of Debug MailBox Access port. """ return self.dbgmlbx_ap_ix @@ -91,6 +81,7 @@ def debug_mailbox_access_port(self, value: int) -> None: """Force the debug mailbox access port. For special cases it could be used the forcing of the debug mailbox access port index. + :param value: Forced value of Debug Mailbox Access port. """ self.dbgmlbx_ap_ix = value @@ -102,14 +93,24 @@ def open(self) -> None: General opening function for SPSDK library to support various DEBUG PROBES. The function is used to initialize the connection to target and enable using debug probe for DAT purposes. + :raises NotImplementedError: The open is NOT implemented """ raise NotImplementedError + def enable_memory_interface(self) -> None: + """Debug probe enabling memory interface. + + General memory interface enabling method (it should be called after open method) for SPSDK library + to support various DEBUG PROBES. The function is used to initialize the target memory interface + and enable using memory access of target over debug probe. + """ + def close(self) -> None: """Debug probe close. This is general closing function for SPSDK library to support various DEBUG PROBES. + :raises NotImplementedError: The close is NOT implemented """ raise NotImplementedError @@ -118,6 +119,7 @@ def dbgmlbx_reg_read(self, addr: int = 0) -> int: """Read debug mailbox access port register. This is read debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :return: The read value of addressed register (4 bytes) :raises NotImplementedError: The dbgmlbx_reg_read is NOT implemented @@ -128,12 +130,84 @@ def dbgmlbx_reg_write(self, addr: int = 0, data: int = 0) -> None: """Write debug mailbox access port register. This is write debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :param data: the data to be written into register :raises NotImplementedError: The dbgmlbx_reg_write is NOT implemented """ raise NotImplementedError + def mem_reg_read(self, addr: int = 0) -> int: + """Read 32-bit register in memory space of MCU. + + This is read 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises NotImplementedError: The mem_reg_read is NOT implemented + """ + raise NotImplementedError + + def mem_reg_write(self, addr: int = 0, data: int = 0) -> None: + """Write 32-bit register in memory space of MCU. + + This is write 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :param data: the data to be written into register + :raises NotImplementedError: The mem_reg_write is NOT implemented + """ + raise NotImplementedError + + @classmethod + def get_coresight_ap_address(cls, access_port: int, address: int) -> int: + """Return computed address of coresight access port register. + + :param access_port: Index of access port 0-255. + :param address: Register address. + :return: Coresight address. + :raises ValueError: In case of invalid value. + """ + if access_port > 255: + raise ValueError + + return access_port << cls.APSEL_SHIFT | address + + def coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: + """Read coresight register. + + It reads coresight register function for SPSDK library to support various DEBUG PROBES. + + :param access_port: if True, the Access Port (AP) register will be read(defau1lt), otherwise the Debug Port + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises NotImplementedError: The coresight_reg_read is NOT implemented + """ + raise NotImplementedError + + def coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: + """Write coresight register. + + It writes coresight register function for SPSDK library to support various DEBUG PROBES. + + :param access_port: if True, the Access Port (AP) register will be write(default), otherwise the Debug Port + :param addr: the register address + :param data: the data to be written into register + :raises NotImplementedError: The coresight_reg_read is NOT implemented + """ + raise NotImplementedError + + def reset(self) -> None: + """Reset a target. + + It resets a target. + + :raises NotImplementedError: The coresight_reg_read is NOT implemented + """ + raise NotImplementedError + def __del__(self) -> None: """General Debug Probe 'END' event handler.""" self.close() diff --git a/spsdk/debuggers/debug_probe_jlink.py b/spsdk/debuggers/debug_probe_jlink.py index a0ec096b..c61e39bd 100644 --- a/spsdk/debuggers/debug_probe_jlink.py +++ b/spsdk/debuggers/debug_probe_jlink.py @@ -1,30 +1,36 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for DebugMailbox PyLink Debug probes support.""" import logging from time import sleep -from typing import List, Dict +from typing import Dict import pylink import pylink.protocols.swd as swd from pylink.errors import JLinkException from .debug_probe import (DebugProbe, - ProbeDescription, DebugProbeTransferError, DebugProbeNotOpenError, - DebugProbeError) + DebugProbeError, + DebugProbeMemoryInterfaceNotEnabled) logger = logging.getLogger(__name__) -logger.setLevel(logging.CRITICAL) JLINK_LOGGER = logger.getChild("PyLink") +def set_logger(level: int) -> None: + """Sets the log level for this module. + param level: Requested level. + """ + logger.setLevel(level) + +set_logger(logging.ERROR) class DebugProbePyLink(DebugProbe): """Class to define PyLink package interface for NXP SPSDK.""" @@ -49,7 +55,11 @@ def __init__(self, hardware_id: str, user_params: Dict = None) -> None: """ super().__init__(hardware_id, user_params) + set_logger(logging.root.level) + + self.enabled_memory_interface = False self.pylink = None + self.last_accessed_ap = -1 # Use coresight_read/write API - True (default) # or the original swd interface- False (this did not work properly) @@ -58,17 +68,18 @@ def __init__(self, hardware_id: str, user_params: Dict = None) -> None: logger.debug(f"The SPSDK PyLink Interface has been initialized") @classmethod - def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> List[ProbeDescription]: + def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> list: """Get all connected probes over PyLink. This functions returns the list of all connected probes in system by PyLink package. + :param hardware_id: None to list all probes, otherwise the the only probe with matching hardware id is listed. :param user_params: The user params dictionary :return: probe_description """ #TODO fix problems with cyclic import - from .utils import DebugProbes + from .utils import DebugProbes, ProbeDescription jlink = DebugProbePyLink.get_jlink_lib() @@ -88,8 +99,7 @@ def open(self) -> None: The PyLink opening function for SPSDK library to support various DEBUG PROBES. The function is used to initialize the connection to target and enable using debug probe for DAT purposes. - :raises ProbeNotFoundError: The probe has not found - :raises DebugMailBoxAPNotFoundError: The debug mailbox access port NOT found + :raises DebugProbeError: The PyLink cannot establish communication with target """ try: @@ -120,6 +130,24 @@ def open(self) -> None: except JLinkException as exc: raise DebugProbeError(f"PyLink cannot establish communication with target({str(exc)}).") + def enable_memory_interface(self) -> None: + """Debug probe enabling memory interface. + + General memory interface enabling method (it should be called after open method) for SPSDK library + to support various DEBUG PROBES. The function is used to initialize the target memory interface + and enable using memory access of target over debug probe. + + :raises DebugProbeNotOpenError: The PyLink probe is NOT opened + :raises DebugProbeError: Error with connection to target. + """ + if self.pylink is None: + raise DebugProbeNotOpenError("The PyLink debug probe is not opened yet") + try: + self.pylink.connect(chip_name="Cortex-M33") + self.enabled_memory_interface = True + except JLinkException as exc: + raise DebugProbeError(f"PyLink cannot establish connection with target({str(exc)}).") + def close(self) -> None: """Close PyLink interface. @@ -132,26 +160,78 @@ def dbgmlbx_reg_read(self, addr: int = 0) -> int: """Read debug mailbox access port register. This is read debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :return: The read value of addressed register (4 bytes) - :raises NotImplementedError: The dbgmlbx_reg_read is NOT implemented """ - return self._coresight_reg_read(addr=addr) + return self.coresight_reg_read(addr=addr | (self.dbgmlbx_ap_ix << self.APSEL_SHIFT)) def dbgmlbx_reg_write(self, addr: int = 0, data: int = 0) -> None: """Write debug mailbox access port register. This is write debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :param data: the data to be written into register - :raises NotImplementedError: The dbgmlbx_reg_write is NOT implemented """ - self._coresight_reg_write(addr=addr, data=data) + self.coresight_reg_write(addr=addr | (self.dbgmlbx_ap_ix << self.APSEL_SHIFT), data=data) + + def mem_reg_read(self, addr: int = 0) -> int: + """Read 32-bit register in memory space of MCU. + + This is read 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises DebugProbeNotOpenError: The PyLink probe is NOT opened + :raises DebugProbeMemoryInterfaceNotEnabled: The PyLink is using just CoreSight access. + """ + if self.pylink is None: + raise DebugProbeNotOpenError("The PyLink debug probe is not opened yet") + + if not self.enabled_memory_interface: + raise DebugProbeMemoryInterfaceNotEnabled("Memory interface is not enabled over J-Link.") + + self.last_accessed_ap = -1 + reg = [0] + try: + reg = self.pylink.memory_read32(addr=addr, num_words=1) + except JLinkException as exc: + logger.error(f"Failed read memory({str(exc)}).") + return reg[0] - def _coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: + + def mem_reg_write(self, addr: int = 0, data: int = 0) -> None: + """Write 32-bit register in memory space of MCU. + + This is write 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :param data: the data to be written into register + :raises DebugProbeNotOpenError: The PyLink probe is NOT opened + :raises DebugProbeMemoryInterfaceNotEnabled: The PyLink is using just CoreSight access. + """ + if self.pylink is None: + raise DebugProbeNotOpenError("The PyLink debug probe is not opened yet") + + if not self.enabled_memory_interface: + raise DebugProbeMemoryInterfaceNotEnabled("Memory interface is not enabled over J-Link.") + + self.last_accessed_ap = -1 + try: + data_list = list() + data_list.append(data) + self.pylink.memory_write32(addr=addr, data=data_list) + except JLinkException as exc: + logger.error(f"Failed write memory({str(exc)}).") + + def coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: """Read coresight register over PyLink interface. The PyLink read coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be read(defau1lt), otherwise the Debug Port :param addr: the register address :return: The read value of addressed register (4 bytes) @@ -163,6 +243,12 @@ def _coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: raise DebugProbeNotOpenError("The PyLink debug probe is not opened yet") try: + if access_port: + req_ap = (self.APSEL & addr) >> self.APSEL_SHIFT + if self.last_accessed_ap != req_ap: + self._select_ap(req_ap) + self.last_accessed_ap = req_ap + if not self.use_coresight_rw: request = swd.ReadRequest(addr // 4, ap=access_port) response = request.send(self.pylink) @@ -177,10 +263,11 @@ def _coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: except JLinkException as exc: raise DebugProbeTransferError(f"The Coresight read operation failed({str(exc)}).") - def _coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: + def coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: """Write coresight register over PyLink interface. The PyLink write coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be write(default), otherwise the Debug Port :param addr: the register address :param data: the data to be written into register @@ -191,6 +278,12 @@ def _coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: in raise DebugProbeNotOpenError("The PyLink debug probe is not opened yet") try: + if access_port: + req_ap = (self.APSEL & addr) >> self.APSEL_SHIFT + if self.last_accessed_ap != req_ap: + self._select_ap(req_ap) + self.last_accessed_ap = req_ap + if not self.use_coresight_rw: request = swd.WriteRequest(addr // 4, data=data, ap=access_port) response = request.send(self.pylink) @@ -202,18 +295,31 @@ def _coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: in except JLinkException as exc: raise DebugProbeTransferError(f"The Coresight write operation failed({str(exc)}).") + def reset(self) -> None: + """Reset a target. + + It resets a target. + + :raises DebugProbeNotOpenError: The PyLink probe is NOT opened + """ + if self.pylink is None: + raise DebugProbeNotOpenError("The PyLink debug probe is not opened yet") + + self.pylink.reset() + def _select_ap(self, ap_ix: int, address: int = 0) -> None: """Helper function to selct the access port in DP. :param ap_ix: requested Access port index. :param address: requested address. """ - self._coresight_reg_write(access_port=False, addr=0x08, data=(address | (ap_ix << 24))) + self.coresight_reg_write(access_port=False, addr=0x08, data=(address | (ap_ix << 24))) def _get_dmbox_ap(self) -> int: """Search for Debug Mailbox Access Point. This is helper function to find and return the debug mailbox access port index. + :return: Debug MailBox Access Port Index if found, otherwise -1 :raises DebugProbeNotOpenError: The Segger JLink probe is NOT opened """ @@ -228,7 +334,7 @@ def _get_dmbox_ap(self) -> int: for access_port_ix in range(256): try: self._select_ap(ap_ix=access_port_ix, address=0x000000F0) - ret = self._coresight_reg_read(addr=idr_address) + ret = self.coresight_reg_read(addr=idr_address) if ret == idr_expected: logger.debug(f"Found debug mailbox ix:{access_port_ix}") diff --git a/spsdk/debuggers/debug_probe_pemicro.py b/spsdk/debuggers/debug_probe_pemicro.py index b5e9140e..cc4eaf18 100644 --- a/spsdk/debuggers/debug_probe_pemicro.py +++ b/spsdk/debuggers/debug_probe_pemicro.py @@ -1,22 +1,24 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for DebugMailbox Pemicro Debug probes support.""" import logging -from typing import List, Dict, Optional +from typing import Dict, Optional from pypemicro import PyPemicro, PEMicroException, PEMicroInterfaces +from spsdk.exceptions import SPSDKError + from .debug_probe import (DebugProbe, - ProbeDescription, DebugProbeTransferError, DebugProbeNotOpenError, DebugProbeError) + logger = logging.getLogger(__name__) logger.setLevel(logging.CRITICAL) PEMICRO_LOGGER = logger.getChild("PyPemicro") @@ -43,21 +45,23 @@ def __init__(self, hardware_id: str, user_params: Dict = None) -> None: super().__init__(hardware_id, user_params) self.pemicro: Optional[PyPemicro] = None + self.last_access_memory = False logger.debug(f"The SPSDK Pemicro Interface has been initialized") @classmethod - def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> List[ProbeDescription]: + def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> list: """Get all connected probes over Pemicro. This functions returns the list of all connected probes in system by Pemicro package. + :param hardware_id: None to list all probes, otherwice the the only probe with matching hardware id is listed. :param user_params: The user params dictionary :return: probe_description """ #TODO fix problems with cyclic import - from .utils import DebugProbes + from .utils import DebugProbes, ProbeDescription pemicro = DebugProbePemicro.get_pemicro_lib() @@ -77,8 +81,7 @@ def open(self) -> None: The Pemicro opening function for SPSDK library to support various DEBUG PROBES. The function is used to initialize the connection to target and enable using debug probe for DAT purposes. - :raises ProbeNotFoundError: The probe has not found - :raises DebugMailBoxAPNotFoundError: The debug mailbox access port NOT found + :raises DebugProbeError: The Pemicro cannot establish communication with target """ try: @@ -110,55 +113,105 @@ def close(self) -> None: if self.pemicro: self.pemicro.close() + def mem_reg_read(self, addr: int = 0) -> int: + """Read 32-bit register in memory space of MCU. + + This is read 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises DebugProbeNotOpenError: The Pemicro probe is NOT opened + :raises SPSDKError: The Pemicro probe has failed during read operation + """ + if self.pemicro is None: + raise DebugProbeNotOpenError("The Pemicro debug probe is not opened yet") + + self.last_access_memory = True + reg = 0 + try: + reg = self.pemicro.read_32bit(addr) + except PEMicroException as exc: + logger.error(f"Failed read memory({str(exc)}).") + raise SPSDKError(str(exc)) + return reg + + def mem_reg_write(self, addr: int = 0, data: int = 0) -> None: + """Write 32-bit register in memory space of MCU. + + This is write 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :param data: the data to be written into register + :raises DebugProbeNotOpenError: The Pemicro probe is NOT opened + :raises SPSDKError: The Pemicro probe has failed during write operation + """ + if self.pemicro is None: + raise DebugProbeNotOpenError("The Pemicro debug probe is not opened yet") + + self.last_access_memory = True + try: + self.pemicro.write_32bit(address=addr, data=data) + except PEMicroException as exc: + logger.error(f"Failed write memory({str(exc)}).") + raise SPSDKError(str(exc)) + def dbgmlbx_reg_read(self, addr: int = 0) -> int: """Read debug mailbox access port register. This is read debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :return: The read value of addressed register (4 bytes) :raises NotImplementedError: The dbgmlbx_reg_read is NOT implemented """ - return self._coresight_reg_read(addr=addr) + return self.coresight_reg_read(addr=addr | (self.dbgmlbx_ap_ix << self.APSEL_SHIFT)) def dbgmlbx_reg_write(self, addr: int = 0, data: int = 0) -> None: """Write debug mailbox access port register. This is write debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :param data: the data to be written into register :raises NotImplementedError: The dbgmlbx_reg_write is NOT implemented """ - self._coresight_reg_write(addr=addr, data=data) + self.coresight_reg_write(addr=addr | (self.dbgmlbx_ap_ix << self.APSEL_SHIFT), data=data) - def _coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: + def coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: """Read coresight register over Pemicro interface. The Pemicro read coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be read(default), otherwise the Debug Port :param addr: the register address :return: The read value of addressed register (4 bytes) :raises DebugProbeTransferError: The IO operation failed :raises DebugProbeNotOpenError: The Pemicro probe is NOT opened - """ if self.pemicro is None: raise DebugProbeNotOpenError("The Pemicro debug probe is not opened yet") try: + if self.last_access_memory: + self.last_access_memory = False + if access_port: - addr_ap = addr | ((self.dbgmlbx_ap_ix << self.APSEL_SHIFT) & self.APSEL_APBANKSEL) - ret = self.pemicro.read_ap_register(apselect=self.dbgmlbx_ap_ix, - addr=addr_ap) + ap_ix = (addr & self.APSEL_APBANKSEL) >> self.APSEL_SHIFT + ret = self.pemicro.read_ap_register(apselect=ap_ix, + addr=addr) else: ret = self.pemicro.read_dp_register(addr=addr) return ret except PEMicroException as exc: raise DebugProbeTransferError(f"The Coresight read operation failed({str(exc)}).") - def _coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: + def coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: """Write coresight register over Pemicro interface. The Pemicro write coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be write(default), otherwise the Debug Port :param addr: the register address :param data: the data to be written into register @@ -169,10 +222,13 @@ def _coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: in raise DebugProbeNotOpenError("The Pemicro debug probe is not opened yet") try: + if self.last_access_memory: + self.last_access_memory = False + if access_port: - addr_ap = addr | ((self.dbgmlbx_ap_ix << self.APSEL_SHIFT) & self.APSEL_APBANKSEL) - self.pemicro.write_ap_register(apselect=self.dbgmlbx_ap_ix, - addr=addr_ap, + ap_ix = (addr & self.APSEL_APBANKSEL) >> self.APSEL_SHIFT + self.pemicro.write_ap_register(apselect=ap_ix, + addr=addr, value=data) else: self.pemicro.write_dp_register(addr=addr, value=data) @@ -180,10 +236,27 @@ def _coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: in except PEMicroException as exc: raise DebugProbeTransferError(f"The Coresight write operation failed({str(exc)}).") + def reset(self) -> None: + """Reset a target. + + It resets a target. + + :raises DebugProbeNotOpenError: The Pemicro debug probe is not opened yet + """ + if self.pemicro is None: + raise DebugProbeNotOpenError("The Pemicro debug probe is not opened yet") + + try: + self.pemicro.reset_target() + except PEMicroException as exc: + logger.warning(f"The reset sequence occured some errors.") + self.pemicro.control_reset_line(assert_reset=False) + def _get_dmbox_ap(self) -> int: """Search for Debug Mailbox Access Point. This is helper function to find and return the debug mailbox access port index. + :return: Debug MailBox Access Port Index if found, otherwise -1 :raises DebugProbeNotOpenError: The PEMicro probe is NOT opened """ diff --git a/spsdk/debuggers/debug_probe_pyocd.py b/spsdk/debuggers/debug_probe_pyocd.py index 83292147..f1ae333f 100644 --- a/spsdk/debuggers/debug_probe_pyocd.py +++ b/spsdk/debuggers/debug_probe_pyocd.py @@ -1,41 +1,59 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for DebugMailbox PyOCD Debug probes support.""" import logging -from typing import List, Dict, Any +from typing import Dict, Any + -import pyocd import pylink import six +from pylink.errors import JLinkException + +import pyocd from pyocd.core.helpers import ConnectHelper from pyocd.core.exceptions import Error as PyOCDError from pyocd.probe.jlink_probe import JLinkProbe from pyocd.coresight import dap +from pyocd.coresight.ap import MEM_AP from pyocd.utility.sequencer import CallSequence from pyocd.coresight.discovery import ADIVersion, ADIv5Discovery, ADIv6Discovery from pyocd.probe.debug_probe import DebugProbe as PyOCDDebugProbe -from pylink.errors import JLinkException from .debug_probe import (DebugProbe, - ProbeDescription, ProbeNotFoundError, DebugMailBoxAPNotFoundError, DebugProbeTransferError, DebugProbeNotOpenError, - DebugProbeError) + DebugProbeError, + DebugProbeMemoryInterfaceAPNotFoundError) + logger = logging.getLogger(__name__) logger.setLevel(logging.CRITICAL) -logging.getLogger('pyocd.board.board').setLevel(logging.CRITICAL) -logging.getLogger('pyocd.core.coresight_target').setLevel(logging.CRITICAL) -logging.getLogger('pyocd.probe.common').setLevel(logging.CRITICAL) + +def set_logger(level: int) -> None: + """Sets the log level for this module. + + param level: Requested level. + """ + logger.setLevel(level) + + + logging.getLogger('pyocd.board.board').setLevel(logging.CRITICAL) + logging.getLogger('pyocd.core.coresight_target').setLevel(level) + logging.getLogger('pyocd.probe.common').setLevel(level) + logging.getLogger('pyocd.utility').setLevel(level) + logging.getLogger('pyocd.core').setLevel(level) + logging.getLogger('pyocd.coresight').setLevel(level) + +set_logger(logging.CRITICAL) class DebugProbePyOCD(DebugProbe): """Class to define PyOCD package interface for NXP SPSDK.""" @@ -47,22 +65,28 @@ def __init__(self, hardware_id: str, user_params: Dict = None) -> None: """ super().__init__(hardware_id, user_params) + set_logger(logging.root.level) + self.pyocd_session = None - self.di = None + self.dbgmlbx_ap_ix = -1 + self.mem_ap_ix = -1 + self.dbgmlbx_ap = None + self.mem_ap = None logger.debug(f"The SPSDK PyOCD Interface has been initialized") @classmethod - def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> List[ProbeDescription]: + def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> list: """Get all connected probes over PyOCD. This functions returns the list of all connected probes in system by PyOCD package. + :param hardware_id: None to list all probes, otherwice the the only probe with matching hardware id is listed. :param user_params: The user params dictionary :return: probe_description """ - from .utils import DebugProbes + from .utils import DebugProbes, ProbeDescription probes = DebugProbes() connected_probes = ConnectHelper.get_all_connected_probes(blocking=False, unique_id=hardware_id) @@ -80,6 +104,7 @@ def open(self) -> None: The PyOCD opening function for SPSDK library to support various DEBUG PROBES. The function is used to initialize the connection to target and enable using debug probe for DAT purposes. + :raises ProbeNotFoundError: The probe has not found :raises DebugMailBoxAPNotFoundError: The debug mailbox access port NOT found :raises DebugProbeError: The PyOCD cannot establish communication with target @@ -95,9 +120,11 @@ def open(self) -> None: logger.info(f"PyOCD connected via {self.pyocd_session.probe.product_name} probe.") except PyOCDError: raise DebugProbeError("PyOCD cannot establish communication with target.") - self.di = self._get_dmbox_ap() - # TODO move this functionality into higher level of code - if self.di is None: + self.dbgmlbx_ap = self._get_dmbox_ap() + self.mem_ap = self._get_mem_ap() + if self.mem_ap is None: + logger.warning("The memory interface not found - probably locked device") + if self.dbgmlbx_ap is None: raise DebugMailBoxAPNotFoundError("No debug mail box access point available!") def close(self) -> None: @@ -108,60 +135,159 @@ def close(self) -> None: if self.pyocd_session: self.pyocd_session.close() + def mem_reg_read(self, addr: int = 0) -> int: + """Read 32-bit register in memory space of MCU. + + This is read 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises DebugProbeMemoryInterfaceAPNotFoundError: The device doesn't content memory interface + """ + if self.mem_ap is None: + raise DebugProbeMemoryInterfaceAPNotFoundError + + reg = 0 + try: + reg = self.mem_ap.read32(addr=addr) + except PyOCDError as exc: + logger.error(f"Failed read memory({str(exc)}).") + return reg + + def mem_reg_write(self, addr: int = 0, data: int = 0) -> None: + """Write 32-bit register in memory space of MCU. + + This is write 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + + :param addr: the register address + :param data: the data to be written into register + :raises DebugProbeMemoryInterfaceAPNotFoundError: The device doesn't content memory interface + """ + if self.mem_ap is None: + raise DebugProbeMemoryInterfaceAPNotFoundError + + try: + self.mem_ap.write32(addr=addr, value=data) + except PyOCDError as exc: + logger.error(f"Failed write memory({str(exc)}).") + def dbgmlbx_reg_read(self, addr: int = 0) -> int: """Read debug mailbox access port register. This is read debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :return: The read value of addressed register (4 bytes) - :raises NotImplementedError: The dbgmlbx_reg_read is NOT implemented + :raises DebugMailBoxAPNotFoundError: The dbgmlbx_reg_read is NOT implemented + :raises DebugProbeTransferError: The dbgmlbx_reg_read ends with data transfer error """ - return self._coresight_reg_read(addr=addr) + if self.dbgmlbx_ap is None: + raise DebugMailBoxAPNotFoundError("No debug mail box access point available!") + try: + return self.dbgmlbx_ap.read_reg(self.APADDR & addr) + except: + raise DebugProbeTransferError("The Coresight read operation failed") def dbgmlbx_reg_write(self, addr: int = 0, data: int = 0) -> None: """Write debug mailbox access port register. This is write debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address :param data: the data to be written into register - :raises NotImplementedError: The dbgmlbx_reg_write is NOT implemented + :raises DebugMailBoxAPNotFoundError: The dbgmlbx_reg_write is NOT implemented + :raises DebugProbeTransferError: The dbgmlbx_reg_write ends with data transfer error """ - self._coresight_reg_write(addr=addr, data=data) + if self.dbgmlbx_ap is None: + raise DebugMailBoxAPNotFoundError("No debug mail box access point available!") + try: + self.dbgmlbx_ap.write_reg(addr=self.APADDR & addr, data=data) + except: + raise DebugProbeTransferError("The Coresight write operation failed") + + def reset(self) -> None: + """Reset a target. - def _coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: + It resets a target. + + :raises DebugProbeNotOpenError: The PyOCD debug probe is not opened yet + """ + if self.pyocd_session is None: + raise DebugProbeNotOpenError("The PyOCD debug probe is not opened yet") + self.pyocd_session.target.reset() + + def _get_ap_by_ix(self, index: int) -> Any: + """Function returns the AP PyoCD object by index if exists. + + :param index: Index of requested access port class. + :return: Access port class, by its IX + :raises DebugProbeNotOpenError: The PyOCD probe is NOT opened + :raises DebugProbeError: There is not active access port for specified index. + """ + if self.pyocd_session is None: + raise DebugProbeNotOpenError("The PyOCD debug probe is not opened yet") + for access_port in self.pyocd_session.target.aps.values(): + if access_port.address.apsel == index: + return access_port + + raise DebugProbeError(f"The accees port {index} is not present.") + + def _get_ap_by_addr(self, addr: int) -> Any: + """Function returns the AP PyoCD object by address if exists. + + :param addr: The access port address. + :return: The Access port object. + :raises DebugProbeNotOpenError: The PyOCD probe is NOT opened + """ + if self.pyocd_session is None: + raise DebugProbeNotOpenError("The PyOCD debug probe is not opened yet") + ap_sel = (addr & self.APSEL) >> self.APSEL_SHIFT + + return self._get_ap_by_ix(ap_sel) + + def coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: """Read coresight register over PyOCD interface. The PyOCD read coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be read(default), otherwise the Debug Port :param addr: the register address :return: The read value of addressed register (4 bytes) :raises DebugProbeTransferError: The IO operation failed :raises DebugProbeNotOpenError: The PyOCD probe is NOT opened - """ - if self.di is None: + if self.pyocd_session is None: raise DebugProbeNotOpenError("The PyOCD debug probe is not opened yet") - try: - return self.di.read_reg(addr) + if access_port: + access_p = self._get_ap_by_addr(addr) + return access_p.read_reg(self.APADDR & addr) + else: + return self.pyocd_session.target.dp.read_dp(addr) except: raise DebugProbeTransferError("The Coresight read operation failed") - def _coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: + def coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: """Write coresight register over PyOCD interface. The PyOCD write coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be write(default), otherwise the Debug Port :param addr: the register address :param data: the data to be written into register :raises DebugProbeTransferError: The IO operation failed :raises DebugProbeNotOpenError: The PyOCD probe is NOT opened """ - if self.di is None: + if self.pyocd_session is None: raise DebugProbeNotOpenError("The PyOCD debug probe is not opened yet") try: - self.di.write_reg(addr, data) - + if access_port: + access_p = self._get_ap_by_addr(addr) + access_p.write_reg(self.APADDR & addr, data) + else: + self.pyocd_session.target.dp.write_dp(addr, data) except: raise DebugProbeTransferError("The Coresight write operation failed") @@ -169,6 +295,7 @@ def _get_dmbox_ap(self) -> Any: """Search for Debug Mailbox Access Point. This is helper function to find and return the debug mailbox access port. + :return: Debug MailBox Access Port :raises DebugProbeNotOpenError: The PyOCD probe is NOT opened """ @@ -189,6 +316,29 @@ def _get_dmbox_ap(self) -> Any: return None + def _get_mem_ap(self) -> Any: + """Search for Memory Interface Access Point. + + This is helper function to find and return the memory interface access port. + + :return: Memory Interface Access Port + :raises DebugProbeNotOpenError: The PyOCD probe is NOT opened + """ + if self.pyocd_session is None: + raise DebugProbeNotOpenError("The PyOCD debug probe is not opened yet") + + for access_port in self.pyocd_session.target.aps.values(): + if self.mem_ap_ix >= 0: + if access_port.address.apsel == self.mem_ap_ix: + return access_port + else: + if isinstance(access_port, MEM_AP): + logger.debug(f"Found Memory interface access port {access_port.short_description}") + self.mem_ap_ix = access_port.address.apsel + return access_port + + return None + # pylint: disable=unused-argument def will_init_target(self, target: Any, init_sequence: 'CallSequence') -> None: """Initialize target. @@ -219,6 +369,7 @@ def dp_init_sequence(self) -> CallSequence: This function allows miss the Connect action for J-LINK probes, because the J-Link DLL do some additional unwanted actions that are not welcomed by DAT. + :return: Debug Port initialization call sequence :raises DebugProbeNotOpenError: The PyOCD probe is NOT opened """ diff --git a/spsdk/debuggers/utils.py b/spsdk/debuggers/utils.py index 62488e52..cc73eda8 100644 --- a/spsdk/debuggers/utils.py +++ b/spsdk/debuggers/utils.py @@ -1,20 +1,21 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for DebugMailbox Debug probes support.""" -from typing import Iterable, List, Dict, Any +import logging +from typing import Dict, Any, Type import prettytable import colorama # Import all supported debug probe classes -from .debug_probe_pyocd import DebugProbePyOCD -from .debug_probe_jlink import DebugProbePyLink -from .debug_probe_pemicro import DebugProbePemicro -from .debug_probe import ProbeNotFoundError, DebugProbe, ProbeDescription +from spsdk.debuggers.debug_probe_pyocd import DebugProbePyOCD +from spsdk.debuggers.debug_probe_jlink import DebugProbePyLink +from spsdk.debuggers.debug_probe_pemicro import DebugProbePemicro +from spsdk.debuggers.debug_probe import DebugProbeError, ProbeNotFoundError, DebugProbe PROBES = { "pyocd": DebugProbePyOCD, @@ -22,6 +23,32 @@ "pemicro": DebugProbePemicro, } +logger = logging.getLogger(__name__) + +class ProbeDescription(): + """NamedTuple for DAT record of debug probe description.""" + + def __init__(self, interface: str, hardware_id: str, description: str, probe: Type[DebugProbe]) -> None: + """Initialization of Debug probe dscription class. + + param interface: Probe Interface. + param hardware_id: Probe Hardware ID(Identification). + param description: Probe Text description. + param probe: Probe name of the class. + """ + self.interface = interface + self.hardware_id = hardware_id + self.description = description + self.probe = probe + + def get_probe(self, user_params: Dict = None) -> Any: + """Get instance of probe. + + :param user_params: The dictionary with optional user parameters + :return: Instance of described probe. + """ + return self.probe(hardware_id=self.hardware_id, user_params=user_params) + class DebugProbes(list): """Helper class for debug probe selection. This class accepts only ProbeDescription object.""" @@ -29,52 +56,24 @@ def append(self, item: ProbeDescription) -> None: """Overriding build-in function by check the type. :param item: ProbeDestription item. - :raises ValueError: Invalid input types has been used. + :raises TypeError: Invalid input types has been used. """ if isinstance(item, ProbeDescription): super(DebugProbes, self).append(item) else: - raise ValueError('The list accepts only ProbeDescription object') + raise TypeError('The list accepts only ProbeDescription object') def insert(self, index: int, item: ProbeDescription) -> None: """Overriding build-in function by check the type. :param item: ProbeDestription item. :param index: Index in list to insert. - :raises ValueError: Invalid input types has been used. + :raises TypeError: Invalid input types has been used. """ if isinstance(item, ProbeDescription): super(DebugProbes, self).insert(index, item) else: - raise ValueError('The list accepts only ProbeDescription object') - - def __add__(self, item: List[Any]) -> List[Any]: - """Overriding build-in function by check the type. - - :param item: ProbeDestription item. - :return: This List - :raises ValueError: Invalid input types has been used. - """ - if isinstance(item, ProbeDescription): - super(DebugProbes, self).__add__(item) - else: - raise ValueError('The list accepts only ProbeDescription object') - - return self - - def __iadd__(self, item: Iterable[Any]) -> Any: - """Overriding build-in function by check the type. - - :param item: ProbeDestription item. - :return: This List - :raises ValueError: Invalid input types has been used. - """ - if isinstance(item, ProbeDescription): - super(DebugProbes, self).__iadd__(item) - else: - raise ValueError('The list accepts only ProbeDescription object') - - return self + raise TypeError('The list accepts only ProbeDescription object') def select_probe(self, silent: bool = False) -> ProbeDescription: """Perform Probe selection. @@ -147,7 +146,10 @@ def get_connected_probes(interface: str = None, hardware_id: str = None, user_pa probes = DebugProbes() for probe_key in PROBES: if (interface is None) or (interface.lower() == probe_key): - probes.extend(PROBES[probe_key].get_connected_probes(hardware_id, user_params)) + try: + probes.extend(PROBES[probe_key].get_connected_probes(hardware_id, user_params)) + except DebugProbeError as exc: + logger.warning(f"The {probe_key} debug probe support is not ready({str(exc)}).") return probes diff --git a/spsdk/image/bee.py b/spsdk/image/bee.py index 997282d5..791a9dec 100644 --- a/spsdk/image/bee.py +++ b/spsdk/image/bee.py @@ -62,8 +62,7 @@ def update(self) -> None: def validate(self) -> None: """Validates the configuration of the instance. - :raise AssertionError: if configuration is invalid. - It is recommended to call the method before export and after parsing + It is recommended to call the method before export and after parsing. """ def export(self) -> bytes: @@ -118,16 +117,13 @@ def info(self) -> str: return f'FAC(start={hex(self.start_addr)}, length={hex(self.length)}, protected_level={self.protected_level})' def validate(self) -> None: - """Validates the configuration of the instance. - - :raise AssertionError: if configuration is invalid. - """ + """Validates the configuration of the instance.""" assert (self.start_addr & _ENCR_BLOCK_ADDR_MASK == 0) and (self.length & _ENCR_BLOCK_ADDR_MASK == 0) assert 0 <= self.protected_level <= 3 assert 0 <= self.start_addr < self.end_addr <= 0xFFFFFFFF def export(self) -> bytes: - """:return:binary representation of the region (serialization).""" + """Exports the binary representation.""" result = super().export() return result + pack(self._struct_format(), self.start_addr, self.end_addr, self.protected_level, b'\x00' * 20) @@ -356,7 +352,7 @@ def validate(self) -> None: assert len(self.kib_iv) == self._KEY_LEN def export(self) -> bytes: - """:return:binary representation of the region (serialization).""" + """Exports binary representation of the region (serialization).""" result = super().export() return result + pack(self._struct_format(), self.kib_key, self.kib_iv) @@ -453,7 +449,7 @@ def export(self, dbg_info: DebugInfo = DebugInfo.disabled()) -> bytes: """Serialization to binary representation. :param dbg_info: instance allowing to provide debug info about exported data - :return:binary representation of the region (serialization). + :return: binary representation of the region (serialization). """ result = super().export() # KIB diff --git a/spsdk/image/commands.py b/spsdk/image/commands.py index ef613238..1a050c0f 100644 --- a/spsdk/image/commands.py +++ b/spsdk/image/commands.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -26,6 +26,8 @@ ######################################################################################################################## # Enums ######################################################################################################################## +from .. import SPSDKError + class EnumWriteOps(Enum): """Enum definition for 'flags' control flags in 'par' parameter of Write Data command.""" @@ -792,7 +794,7 @@ class CmdUnlockCAAM(CmdUnlockAbstract): def __init__(self, features: int = 0): """Initialize. - :param features: mask of FEATURE_UNLOCK_ constants, defaults to 0 + :param features: mask of FEATURE_UNLOCK_x constants, defaults to 0 """ super().__init__(EnumEngine.CAAM, features) @@ -836,7 +838,7 @@ class CmdUnlockOCOTP(CmdUnlockAbstract): def __init__(self, features: int = 0, uid: int = 0): """Initialize. - :param features: mask of FEATURE_UNLOCK_ constants, defaults to 0 + :param features: mask of FEATURE_UNLOCK_x constants, defaults to 0 :param uid: Unique ID required by some engine/feature combinations """ super().__init__(EnumEngine.OCOTP, features, uid=uid) @@ -1123,7 +1125,7 @@ def parse(cls, data: bytes, offset: int = 0) -> CmdBase: SignatureOrMAC = Union[MAC, Signature] -class ExpectedSignatureOrMACError(Exception): +class ExpectedSignatureOrMACError(SPSDKError): """CmdAuthData additional data block: expected Signature or MAC object.""" @@ -1223,7 +1225,7 @@ def cmd_data_reference(self, value: SignatureOrMAC) -> None: elif self.sig_format == EnumCertFormat.CMS: assert isinstance(value, Signature) else: - raise ExpectedSignatureOrMACError + raise ExpectedSignatureOrMACError() self._signature = value def parse_cmd_data(self, data: bytes, offset: int) -> SignatureOrMAC: @@ -1241,7 +1243,7 @@ def parse_cmd_data(self, data: bytes, offset: int) -> SignatureOrMAC: if header.tag == SegTag.SIG: self._signature = Signature.parse(data, offset) return self._signature - raise ExpectedSignatureOrMACError(header.tag) + raise ExpectedSignatureOrMACError(f'TAG = {header.tag}') @property def signature(self) -> Optional[SignatureOrMAC]: diff --git a/spsdk/image/hab_audit_log.py b/spsdk/image/hab_audit_log.py index 873492dd..8841d504 100644 --- a/spsdk/image/hab_audit_log.py +++ b/spsdk/image/hab_audit_log.py @@ -1,18 +1,74 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Utility allowing parsing of HAB audit log data.""" -from typing import List, Type +import os +from enum import Enum as PyEnum from struct import unpack_from +from typing import List, Type, Optional +from spsdk.mboot import McuBoot, PropertyTag from spsdk.utils.easy_enum import Enum -from .header import CmdTag +from spsdk.utils.misc import load_binary from .commands import parse_command +from .header import CmdTag + +# Absolute path, where the executable hab audit code is located. +CPU_DATA_SUB_DIR = os.path.join(os.path.dirname(__file__), "data", "cpu_data") + + +# pylint: disable=too-few-public-methods +class CpuInfo: + """Cpu specific information, necessary for hw tests (HAB log reading).""" + + def __init__(self, cpu_name: str, hab_audit_bin: str, bin_base_address: int, bin_start_address: int): + """Constructor of CpuInfo class.""" + # name of the supported cpus (for example rt1020, rt1050,...) + self.cpu_name = cpu_name + # name of the hab audit executable file + self.hab_audit_bin = hab_audit_bin + # base address of the hab audit executable file + self.bin_base_address = bin_base_address + # start address of the hab audit executable file + self.bin_start_address = bin_start_address + + +# pylint: disable=no-member +class CpuData(PyEnum): + """Data for all supported cpus.""" + MIMXRT1020 = CpuInfo(cpu_name='rt1020', hab_audit_bin='rt1020_exec_hab_audit.bin', bin_base_address=0x20200000, + bin_start_address=0x20200358) + + MIMXRT1050 = CpuInfo(cpu_name='rt1050', hab_audit_bin='rt1050_exec_hab_audit.bin', bin_base_address=0x20018000, + bin_start_address=0x20018378) + + MIMXRT1060 = CpuInfo(cpu_name='rt1060', hab_audit_bin='rt1060_exec_hab_audit.bin', bin_base_address=0x20018000, + bin_start_address=0x200183A4) + + @property + def cpu_name(self) -> str: + """:return: name of the cpu.""" + return self.value.cpu_name + + @property + def bin(self) -> str: + """:return: name of the hab audit binary.""" + return self.value.hab_audit_bin + + @property + def base_address(self) -> int: + """:return: base address of the hab audit bin.""" + return self.value.bin_base_address + + @property + def start_address(self) -> int: + """:return: start address of the hab audit bin.""" + return self.value.bin_start_address class HabConfig(Enum): @@ -24,7 +80,7 @@ class HabConfig(Enum): class HabState(Enum): """HAB state definitions.""" - HAB_STATE_INITIAL = (0x33, 'Initialising state(transitory)') + HAB_STATE_INITIAL = (0x33, 'Initializing state(transitory)') HAB_STATE_CHECK = (0x55, 'Check state(non - secure)') HAB_STATE_NONSECURE = (0x66, 'Non - secure state') HAB_STATE_TRUSTED = (0x99, 'Trusted state') @@ -103,6 +159,50 @@ class HabEngine(Enum): HAB_ENG_SW = (0xff, 'Software engine') +def check_reserved_regions(log_addr: int, reserved_regions: list = None) -> bool: + """Checks if the address of the log is not in conflict with CPU reserved regions. + + :param log_addr: address of the RAM, where we want to store hab log + :param reserved_regions: list with reserved regions + :return: True if the address of the log is not in conflict, otherwise return False + """ + if reserved_regions is None: + return True + + while not len(reserved_regions) % 2 and len(reserved_regions) != 0: + # region_min and region_max determine one reserved region + region_max = reserved_regions.pop() + region_min = reserved_regions.pop() + # check conflict + if region_min <= log_addr <= region_max: + print(f"Conflict log address: - [ {hex(log_addr)} ] in region:" + f" {hex(region_min)} - {hex(region_max)}") + return False + return True + + +def get_hab_log_info(hab_log: Optional[bytes]) -> bool: + """Gets information about hab log. + + It detects if the hab log is empty, invalid (4x 0xff) or prints out + valid hab log status. + :param hab_log: Log with data to test. It can be None. + :return: False when flashloader is not accessible or problem with + hab log occurred, otherwise return True. + """ + if hab_log is None: + print('Problem during Hab log reading. Hab log is empty.') + return False + if hab_log[0:4] == b'\xFF\xFF\xFF\xFF': + print('Flash not accessible or application entry out of expected value') + return False + + # first three bytes are HAB status, config and state + for line in parse_hab_log(hab_log[0], hab_log[1], hab_log[2], hab_log[4:]): + print(line) + return True + + def get_hab_enum_descr(enum_cls: Type[Enum], value: int) -> str: """Converts integer value into description of the enumeration value. @@ -158,5 +258,52 @@ def parse_hab_log(hab_sts: int, hab_cfg: int, hab_state: int, data: bytes) -> Li pass offset += leng - return result + + +def hab_audit_xip_app(cpu_data: CpuData, mboot: McuBoot, read_log_only: bool) -> Optional[bytes]: + """Authenticate the application in external FLASH. + + The function loads application into RAM and invokes its function, that authenticates the application and read the + HAB log. Then the HAB log is downloaded and parsed and printed to stdout. + :param cpu_data: target cpu data + :param mboot: running flashloader + :param read_log_only: true to read HAB log without invoking authentication; False to authenticate and read-log + It is recommended to call the function firstly with parameter `True` and second time with parameter False to + see the difference. + :return: bytes contains result of the hab log, otherwise returns None when an error occurred + """ + # check if the flashloader is running (not None) + assert mboot, "Flashloader is not running" + + # get CPU data dir, hab_audit_base and hab_audit_start + assert cpu_data, "Can not read the log, because given cpu data were not provided." + cpu_data_bin_dir = cpu_data.bin + evk_exec_hab_audit_base = cpu_data.base_address + evk_exec_hab_audit_start = cpu_data.start_address + + # get main directory in absolute format + main_dir_absolute = os.path.dirname(__file__) + # get hab_audit_executable bin file directory + exec_hab_audit_path = os.path.join(os.path.dirname(main_dir_absolute), "data", "cpu_data", cpu_data_bin_dir) + if not os.path.isfile(exec_hab_audit_path): + print('\nHAB logger not supported for the processor') + return None + + # get executable file, that will be loaded into RAM + exec_hab_audit_code = load_binary(exec_hab_audit_path) + # find address of the buffer in RAM, where the HAB LOG will be stored + log_addr = evk_exec_hab_audit_base + exec_hab_audit_code.find(b'\xA5\x5A\x11\x22\x33\x44\x55\x66') + assert log_addr > evk_exec_hab_audit_base + # check if the executable binary is in collision with reserved region + reserved_regions = mboot.get_property(PropertyTag.RESERVED_REGIONS) + + # check conflict between hab log address and any of reserved regions + # we need 2 values (min and max) - that is why %2 is used + assert check_reserved_regions(log_addr, reserved_regions), \ + f"Log address is in conflict with reserved regions" + assert mboot.write_memory(evk_exec_hab_audit_base, exec_hab_audit_code, 0) + assert mboot.call(evk_exec_hab_audit_start | 1, 0 if read_log_only else 1) + + log = mboot.read_memory(log_addr, 100, 0) + return log diff --git a/spsdk/image/images.py b/spsdk/image/images.py index 5a2a0099..9d877e22 100644 --- a/spsdk/image/images.py +++ b/spsdk/image/images.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause diff --git a/spsdk/image/mbimg.py b/spsdk/image/mbimg.py index 87696348..da709e3f 100644 --- a/spsdk/image/mbimg.py +++ b/spsdk/image/mbimg.py @@ -298,16 +298,16 @@ def __init__(self, app: Union[bytes, bytearray], :param app: input image (binary) :param load_addr: address in RAM, where 'RAM' image will be copied; - for XIP images address, where the image is located in FLASH memory + for XIP images address, where the image is located in FLASH memory :param image_type: type of the master boot image :param trust_zone: TrustZone instance; None to use default settings (TrustZone enabled) :param app_table: optional table with additional images; None if no additional images needed :param cert_block: block of certificates; None for unsigned image :param priv_key_pem_data: private key to sign the image, decrypted binary data in PEM format :param hmac_key: optional key for HMAC generation (either binary ot HEX string; 32 bytes); - None if HMAC is not in the image - If key_store.key_source == KeySourceType.KEYSTORE, this is a user-key from key-store - If key_store.key_source == KeySourceType.OTP, this is a master-key burned in OTP + None if HMAC is not in the image + If key_store.key_source == KeySourceType.KEYSTORE, this is a user-key from key-store + If key_store.key_source == KeySourceType.OTP, this is a master-key burned in OTP :param key_store: optional key store binary content; None if key store is not in the image :param enable_hw_user_mode_keys: flag for controlling secure hardware key bus. If true, then it is possible to access keys on hardware secure bus from non-secure application, else non-secure application will read zeros. diff --git a/spsdk/image/misc.py b/spsdk/image/misc.py index dffebb84..82a7b070 100644 --- a/spsdk/image/misc.py +++ b/spsdk/image/misc.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -13,14 +13,15 @@ from typing import Union from .header import Header +from .. import SPSDKError -class RawDataException(Exception): +class RawDataException(SPSDKError): """Raw data read failed.""" class StreamReadFailed(RawDataException): - """read_raw_data could not read stream.""" + """Read_raw_data could not read stream.""" class NotEnoughBytesException(RawDataException): diff --git a/spsdk/image/secret.py b/spsdk/image/secret.py index 3d6a5489..b0023090 100644 --- a/spsdk/image/secret.py +++ b/spsdk/image/secret.py @@ -2,12 +2,12 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Commands and responses used by SDP module.""" - +import math from hashlib import sha256 from struct import pack, unpack_from, unpack from typing import List, Optional, Union, Any, Iterator @@ -19,6 +19,7 @@ from spsdk.utils.misc import DebugInfo from .header import SegTag, Header from .misc import modulus_fmt, hexdump_fmt +from .. import SPSDKError class EnumAlgorithm(Enum): @@ -415,7 +416,7 @@ def parse(cls, data: bytes, offset: int = 0) -> 'MAC': return cls(header.param, nonce_bytes, mac_bytes, data[offset: offset + header.length - (Header.SIZE + 4)]) -class SRKException(Exception): +class SRKException(SPSDKError): """SRK table processing exceptions.""" @@ -477,10 +478,10 @@ def parse(cls, data: bytes, offset: int = 0) -> 'SrkItem': if header.tag == EnumSRK.KEY_PUBLIC: if header.param == EnumAlgorithm.PKCS1: return SrkItemRSA.parse(data, offset) - raise NotImplementedSRKPublicKeyType(header.param) + raise NotImplementedSRKPublicKeyType(f'{header.param}') if header.tag == EnumSRK.KEY_HASH: return SrkItemHash.parse(data, offset) - raise NotImplementedSRKItem(header.tag, header.param) + raise NotImplementedSRKItem(f'TAG = {header.tag}, PARAM = {header.param}') @classmethod def from_certificate(cls, cert: Certificate) -> 'SrkItem': @@ -489,7 +490,7 @@ def from_certificate(cls, cert: Certificate) -> 'SrkItem': public_key = cert.public_key() if isinstance(public_key, rsa.RSAPublicKey): return SrkItemRSA.from_certificate(cert) - raise NotImplementedSRKCertificate(cert) + raise NotImplementedSRKCertificate() class SrkItemHash(SrkItem): @@ -566,7 +567,7 @@ def parse(cls, data: bytes, offset: int = 0) -> 'SrkItemHash': if header.param == EnumAlgorithm.SHA256: digest = rest[:sha256().digest_size] return cls(EnumAlgorithm.SHA256, digest) - raise NotImplementedSRKItem(header.tag, header.param) + raise NotImplementedSRKItem(f'TAG = {header.tag}, PARAM = {header.param}') @@ -672,10 +673,11 @@ def from_certificate(cls, cert: Certificate) -> 'SrkItemRSA': flag = 0 try: key_usage = cert.extensions.get_extension_for_class(KeyUsage) + assert isinstance(key_usage.value, KeyUsage) + if key_usage.value.key_cert_sign: + flag = 0x80 except ExtensionNotFound: pass - if key_usage.value.key_cert_sign: - flag = 0x80 if isinstance(cert.public_key(), rsa.RSAPublicKey): public_key = cert.public_key() @@ -683,17 +685,13 @@ def from_certificate(cls, cert: Certificate) -> 'SrkItemRSA': pub_key_numbers = public_key.public_numbers() assert isinstance(pub_key_numbers, rsa.RSAPublicNumbers) # get modulus and exponent of public key since we are RSA - modulus_len = pub_key_numbers.n.bit_length() // 8 - if pub_key_numbers.n.bit_length() % 8: - modulus_len += 1 - exponent_len = pub_key_numbers.e.bit_length() // 8 - if pub_key_numbers.e.bit_length() % 8: - exponent_len += 1 + modulus_len = math.ceil(pub_key_numbers.n.bit_length() / 8) + exponent_len = math.ceil(pub_key_numbers.e.bit_length() / 8) modulus = pub_key_numbers.n.to_bytes(modulus_len, "big") exponent = pub_key_numbers.e.to_bytes(exponent_len, "big") return cls(modulus, exponent, flag) - raise NotImplementedSRKCertificate(cert) + raise NotImplementedSRKCertificate() class SrkTable(BaseClass): diff --git a/spsdk/image/segments.py b/spsdk/image/segments.py index 4786d3cd..a0051e24 100644 --- a/spsdk/image/segments.py +++ b/spsdk/image/segments.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Segments within image module.""" @@ -592,12 +592,12 @@ def __repr__(self) -> str: def info(self) -> str: """String representation of the SegIVT2.""" msg = "" - msg += " format version : 0x{:02X}\n".format(self.version) - msg += " IVT start address: 0x{:08X}\n".format(self.ivt_address) - msg += " BDT start address: 0x{:08X}\n".format(self.bdt_address) - msg += " DCD start address: 0x{:08X}\n".format(self.dcd_address) - msg += " APP entry point : 0x{:08X}\n".format(self.app_address) - msg += " CSF start address: 0x{:08X}\n".format(self.csf_address) + msg += f" Format version : {_format_ivt_item(self.version, digit_count=2)}\n" + msg += f" IVT start address: {_format_ivt_item(self.ivt_address)}\n" + msg += f" BDT start address: {_format_ivt_item(self.bdt_address)}\n" + msg += f" DCD start address: {_format_ivt_item(self.dcd_address)}\n" + msg += f" APP entry point : {_format_ivt_item(self.app_address)}\n" + msg += f" CSF start address: {_format_ivt_item(self.csf_address)}\n" msg += "\n" return msg @@ -1333,12 +1333,12 @@ def __repr__(self) -> str: def info(self) -> str: """String representation of the SegIVT3a.""" msg = "" - msg += " VER: {}\n".format(self.version) - msg += " IVT: 0x{:08X}\n".format(self.ivt_address) - msg += " BDT: 0x{:08X}\n".format(self.bdt_address) - msg += " DCD: 0x{:08X}\n".format(self.dcd_address) - msg += " CSF: 0x{:08X}\n".format(self.csf_address) - msg += " NEXT: 0x{:08X}\n".format(self.next) + msg += f" Format version : {_format_ivt_item(self.version, digit_count=2)}\n" + msg += f" IVT start address: {_format_ivt_item(self.ivt_address)}\n" + msg += f" BDT start address: {_format_ivt_item(self.bdt_address)}\n" + msg += f" DCD start address: {_format_ivt_item(self.dcd_address)}\n" + msg += f" CSF start address: {_format_ivt_item(self.csf_address)}\n" + msg += f" NEXT address : {_format_ivt_item(self.next)}\n" msg += "\n" return msg @@ -1430,11 +1430,11 @@ def __repr__(self) -> str: def info(self) -> str: """String representation of the SegIVT3b.""" msg = "" - msg += " IVT: 0x{:08X}\n".format(self.ivt_address) - msg += " BDT: 0x{:08X}\n".format(self.bdt_address) - msg += " DCD: 0x{:08X}\n".format(self.dcd_address) - msg += " SCD: 0x{:08X}\n".format(self.scd_address) - msg += " CSF: 0x{:08X}\n".format(self.csf_address) + msg += f" IVT start address: {_format_ivt_item(self.ivt_address)}\n" + msg += f" BDT start address: {_format_ivt_item(self.bdt_address)}\n" + msg += f" DCD start address: {_format_ivt_item(self.dcd_address)}\n" + msg += f" CSF start address: {_format_ivt_item(self.csf_address)}\n" + msg += f" SCD start address: {_format_ivt_item(self.scd_address)}\n" msg += "\n" return msg @@ -2089,3 +2089,17 @@ def parse(cls, data: bytes) -> 'SegBIC1': obj.validate() return obj + + +def _format_ivt_item(item_address: int, digit_count: int = 8) -> str: + """Formats 'item_address' to hex or None if address is 0. + + If provided item address is not 0, the result will be in format + '0x' + leading zeros + number in HEX format + If provided number is 0, function returns 'None' + + :param item_address: Address if IVT item + :param digit_count: Number of digits to , defaults to 8 + :return: Formated number + """ + return f"{item_address:#0{digit_count + 2}x}" if item_address else "none" diff --git a/spsdk/mboot/__init__.py b/spsdk/mboot/__init__.py index 2d544149..8e0ecddf 100644 --- a/spsdk/mboot/__init__.py +++ b/spsdk/mboot/__init__.py @@ -2,18 +2,18 @@ # -*- coding: UTF-8 -*- # # Copyright 2016-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module implementing communication with the MCU Bootloader.""" from .mcuboot import McuBoot -from .commands import CommandTag, KeyProvUserKeyType +from .commands import CommandTag, KeyProvUserKeyType, GenerateKeyBlobSelect from .memories import ExtMemPropTags, ExtMemId from .properties import PropertyTag, PeripheryTag, Version, parse_property_value from .interfaces import scan_usb -from .exceptions import McuBootError, McuBootCommandError, McuBootConnectionError +from .exceptions import McuBootError, McuBootCommandError, McuBootConnectionError, McuBootDataAbortError from .error_codes import StatusCode __all__ = [ diff --git a/spsdk/mboot/commands.py b/spsdk/mboot/commands.py index 15f906f0..e809c770 100644 --- a/spsdk/mboot/commands.py +++ b/spsdk/mboot/commands.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2016-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -22,6 +22,7 @@ class CommandTag(Enum): """McuBoot Commands.""" + NO_COMMAND = (0x00, 'NoCommand', 'No Command') FLASH_ERASE_ALL = (0x01, 'FlashEraseAll', 'Erase Complete Flash') FLASH_ERASE_REGION = (0x02, 'FlashEraseRegion', 'Erase Flash Region') READ_MEMORY = (0x03, 'ReadMemory', 'Read Memory') @@ -83,6 +84,20 @@ class KeyProvUserKeyType(Enum): UDS = (12, "TODO description") # LPC55Sxx and RTxxx +class GenerateKeyBlobSelect(Enum): + """Key selector for the generate-key-blob function. + + For devices with SNVS, valid options of [key_sel] are + 0, 1 or OTPMK: OTPMK from FUSE or OTP(default), + 2 or ZMK: ZMK from SNVS, + 3 or CMK: CMK from SNVS, + For devices without SNVS, this option will be ignored. + """ + OPTMK = (0, "OPTMK", "OTPMK from FUSE or OTP(default)") + ZMK = (2, "ZMK", "ZMK from SNVS") + CMK = (3, "CMK", "CMK from SNVS") + + ######################################################################################################################## # McuBoot Command and Response packet classes ######################################################################################################################## @@ -202,6 +217,7 @@ def __init__(self, header: CmdHeader, raw_data: bytes) -> None: assert isinstance(raw_data, (bytes, bytearray)) self.header = header self.raw_data = raw_data + self.status, = unpack_from(" bool: return isinstance(obj, CmdResponse) and vars(obj) == vars(self) @@ -334,7 +350,7 @@ def info(self) -> str: return f"Tag={tag}, Status={status}, Length={self.length}" -def parse_cmd_response(data: bytes, offset: int = 0) -> Any: +def parse_cmd_response(data: bytes, offset: int = 0) -> CmdResponse: """Parse command response. :param data: Input data in bytes diff --git a/spsdk/mboot/error_codes.py b/spsdk/mboot/error_codes.py index c7955615..f393e12c 100644 --- a/spsdk/mboot/error_codes.py +++ b/spsdk/mboot/error_codes.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2016-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -35,11 +35,22 @@ class StatusCode(Enum): FLASH_COMMAND_FAILURE = (105, 'FlashCommandFailure', 'FLASH Driver: Command Failure') FLASH_UNKNOWN_PROPERTY = (106, 'FlashUnknownProperty', 'FLASH Driver: Unknown Property') FLASH_REGION_EXECUTE_ONLY = (108, 'FlashRegionExecuteOnly', 'FLASH Driver: Region Execute Only') - FLASH_EXEC_IN_RAM_NOT_READY = (109, 'FlashExecuteInRamFunctionNotReady', - 'FLASH Driver: Execute In RAM Function Not Ready') + FLASH_EXEC_IN_RAM_NOT_READY = ( + 109, 'FlashExecuteInRamFunctionNotReady', 'FLASH Driver: Execute In RAM Function Not Ready') FLASH_COMMAND_NOT_SUPPORTED = (111, 'FlashCommandNotSupported', 'FLASH Driver: Command Not Supported') FLASH_OUT_OF_DATE_CFPA_PAGE = (132, 'FlashOutOfDateCfpaPage', 'FLASH Driver: Out Of Date CFPA Page') - + FLASH_BLANK_IFR_PAGE_DATA = (133, 'FlashBlankIfrPageData', 'FLASH Driver: Blank IFR Page Data') + FLASH_ENCRYPTED_REGIONS_ERASE_NOT_DONE_AT_ONCE = ( + 134, 'FlashEncryptedRegionsEraseNotDoneAtOnce', 'FLASH Driver: Encrypted Regions Erase Not Done At Once') + FLASH_PROGRAM_VERIFICATION_NOT_ALLOWED = ( + 135, 'FlashProgramVerificationNotAllowed', 'FLASH Driver: Program Verification Not Allowed') + FLASH_HASH_CHECK_ERROR = (136, 'FlashHashCheckError', 'FLASH Driver: Hash Check Error') + FLASH_SEALED_FFR_REGION = (137, 'FlashSealedFfrRegion', 'FLASH Driver: Sealed FFR Region') + FLASH_FFR_REGION_WRITE_BROKEN = (138, 'FlashFfrRegionWriteBroken', 'FLASH Driver: FFR Region Write Broken') + FLASH_NMPA_UPDATE_NOT_ALLOWED = (139, 'FlashNmpaUpdateNotAllowed', 'FLASH Driver: NMPA Update Not Allowed') + FLASH_CMPA_CFG_DIRECT_ERASE_NOT_ALLOWED = ( + 140, 'FlashCmpaCfgDirectEraseNotAllowed', 'FLASH Driver: CMPA Cfg Direct Erase Not Allowed') + FLASH_FFR_BANK_IS_LOCKED = (141, 'FlashFfrBankIsLocked', 'FLASH Driver: FFR Bank Is Locked') # I2C driver errors. I2C_SLAVE_TX_UNDERRUN = (200, 'I2cSlaveTxUnderrun', 'I2C Driver: Slave Tx Underrun') I2C_SLAVE_RX_OVERRUN = (201, 'I2cSlaveRxOverrun', 'I2C Driver: Slave Rx Overrun') @@ -103,6 +114,21 @@ class StatusCode(Enum): MEMORY_WRITE_FAILED = (10202, 'MemoryWriteFailed', 'Memory Write Failed') MEMORY_CUMULATIVE_WRITE = (10203, 'MemoryCumulativeWrite', 'Memory Cumulative Write') MEMORY_NOT_CONFIGURED = (10205, 'MemoryNotConfigured', 'Memory Not Configured') + MEMORY_APP_OVERLAP_WITH_EXECUTE_ONLY_REGION = ( + 10204, 'MemoryAppOverlapWithExecuteOnlyRegion', 'Memory App Overlap with exec region') + MEMORY_NOT_CONFIGURED = (10205, 'MemoryNotConfigured', 'Memory Not Configured') + MEMORY_ALIGNMENT_ERROR = (10206, 'MemoryAlignmentError', 'Memory Alignment Error') + MEMORY_VERIFY_FAILED = (10207, 'MemoryVerifyFailed', 'Memory Verify Failed') + MEMORY_WRITE_PROTECTED = (10208, 'MemoryWriteProtected', 'Memory Write Protected') + MEMORY_ADDRESS_ERROR = (10209, 'MemoryAddressError', 'Memory Address Error') + MEMORY_BLANK_CHECK_FAILED = (10210, 'MemoryBlankCheckFailed', 'Memory Black Check Failed') + MEMORY_BLANK_PAGE_READ_DISALLOWED = ( + 10211, 'MemoryBlankPageReadDisallowed', 'Memory Blank Page Read Disallowed') + MEMORY_PROTECTED_PAGE_READ_DISALLOWED = ( + 10212, 'MemoryProtectedPageReadDisallowed', 'Memory Protected Page Read Disallowed') + MEMORY_FFR_SPEC_REGION_WRITE_BROKEN = ( + 10213, 'MemoryFfrSpecRegionWriteBroken', 'Memory FFR Spec Region Write Broken') + MEMORY_UNSUPPORTED_COMMAND = (10214, 'MemoryUnsupportedCommand', 'Memory Unsupported Command') # Property store errors. UNKNOWN_PROPERTY = (10300, 'UnknownProperty', 'Unknown Property') @@ -237,6 +263,16 @@ class StatusCode(Enum): SPIFINOR_COMMAND_FAILURE = (22006, 'SPIFINOR_CommandFailure', 'SPIFINOR: Command Failure') SPIFINOR_SFDP_NOT_FOUND = (22007, 'SPIFINOR_SFDP_NotFound', 'SPIFINOR: SFDP Not Found') + # OTP statuses. + OTP_INVALID_ADDRESS = (52801, 'OTP_InvalidAddress', 'OTD: Invalid OTP address') + OTP_PROGRAM_FAIL = (52802, 'OTP_ProgrammingFail', 'OTD: Programming failed') + OTP_CRC_FAIL = (52803, 'OTP_CRCFail', 'OTP: CRC check failed') + OTP_ERROR = (52804, 'OTP_Error', 'OTP: Error happened during OTP operation') + OTP_ECC_CRC_FAIL = (52805, 'OTP_EccCheckFail', 'OTP: ECC check failed during OTP operation') + OTP_LOCKED = (52806, 'OTP_FieldLocked', 'OTP: Field is locked when programming') + OTP_TIMEOUT = (52807, 'OTP_Timeout', 'OTP: Operation timed out') + OTP_CRC_CHECK_PASS = (52808, 'OTP_CRCCheckPass', 'OTP: CRC check passed') + # FlexSPI statuses. FLEXSPI_SEQUENCE_EXECUTION_TIMEOUT = ( 70000, 'FLEXSPI_SequenceExecutionTimeout', 'FLEXSPI: Sequence Execution Timeout') diff --git a/spsdk/mboot/exceptions.py b/spsdk/mboot/exceptions.py index 8c30540b..8a876940 100644 --- a/spsdk/mboot/exceptions.py +++ b/spsdk/mboot/exceptions.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2016-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -40,6 +40,10 @@ def __str__(self) -> str: return self.fmt.format(cmd_name=self.cmd_name, description=self.description) +class McuBootDataAbortError(McuBootError): + """MBoot Module: Data phase aborted by sender.""" + fmt = 'Mboot: Data aborted by sender' + class McuBootConnectionError(McuBootError): """MBoot Module: Connection Exception.""" fmt = 'MBoot: Connection issue -> {description}' diff --git a/spsdk/mboot/interfaces/base.py b/spsdk/mboot/interfaces/base.py index 61094d3f..9bbf473c 100644 --- a/spsdk/mboot/interfaces/base.py +++ b/spsdk/mboot/interfaces/base.py @@ -1,14 +1,16 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright (c) 2019-2020 NXP +# Copyright (c) 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause -"""Module for functionality shared accross all MBoot interfaces.""" +"""Module for functionality shared across all MBoot interfaces.""" from abc import ABC -from typing import Any +from typing import Any, Union + +from spsdk.mboot.commands import CmdResponse class Interface(ABC): @@ -36,7 +38,7 @@ def open(self) -> None: def close(self) -> None: """Close the interface.""" - def read(self) -> Any: + def read(self) -> Union[CmdResponse, bytes]: """Read data from the device.""" def write(self, packet: Any) -> None: diff --git a/spsdk/mboot/interfaces/uart.py b/spsdk/mboot/interfaces/uart.py index 14bc0e7a..6f23d950 100644 --- a/spsdk/mboot/interfaces/uart.py +++ b/spsdk/mboot/interfaces/uart.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2016-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -17,7 +17,7 @@ from serial import Serial, SerialException from serial.tools.list_ports import comports -from spsdk.mboot.exceptions import McuBootConnectionError +from spsdk.mboot.exceptions import McuBootConnectionError, McuBootDataAbortError from spsdk.mboot.commands import CmdPacket, CmdResponse, parse_cmd_response from spsdk.utils.easy_enum import Enum @@ -60,8 +60,8 @@ def _check_port(port: str, baudrate: int, timeout: int) -> Optional[Interface]: interface.open() interface.close() return interface - except (AssertionError, SerialException) as e: - logger.error(str(e)) + except (AssertionError, SerialException, TimeoutError) as e: + logger.debug(f"{type(e).__name__}: {e}") return None @@ -142,7 +142,7 @@ def __init__(self, port: str = None, baudrate: int = 57600, timeout: int = 5000) :type timeout: int, optional """ super().__init__() - self.device = Serial(port=port, timeout=timeout // 1000, baudrate=baudrate) + self.device = Serial(port=port, timeout=timeout / 1000, baudrate=baudrate) self.close() self.protocol_version = None self.options = None @@ -169,10 +169,14 @@ def read(self) -> Union[CmdResponse, bytes]: :return: read data :rtype: Union[spsdk.mboot.commands.CmdResponse, bytes] + :raises McuBootDataAbortError: Indicates data transmission abort """ _, frame_type = self._read_frame_header() length = to_int(self._read(2)) crc = to_int(self._read(2)) + if not length: + self._send_ack() + raise McuBootDataAbortError() data = self._read(length) self._send_ack() calculated_crc = self._calc_frame_crc(data, frame_type) @@ -200,11 +204,12 @@ def _read(self, length: int) -> bytes: """Read 'length' amount for bytes from device. :param length: Number of bytes to read - :type length: int :return: Data read from the device - :rtype: bytes + :raises TimeoutError: Time-out """ data = self.device.read(length) + if not data: + raise TimeoutError() logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") return data @@ -241,12 +246,15 @@ def _read_frame_header(self, expected_frame_type: int = None) -> Tuple[int, int] :param expected_frame_type: Check if the frame_type is exactly as expected :return: Tuple of integers representing frame header and frame type + :raises McuBootDataAbortError: Target sens Data Abort frame :raises AssertionError: Unexpected frame header or frame type (if specified) """ header = to_int(self._read(1)) assert header == self.FRAME_START_BYTE, \ f"Received invalid frame header '{header:#X}' expected '{self.FRAME_START_BYTE:#X}'" frame_type = to_int(self._read(1)) + if frame_type == FPType.ABORT: + raise McuBootDataAbortError() if expected_frame_type: assert frame_type == expected_frame_type, \ f"received invalid ACK '{frame_type:#X}' expected '{expected_frame_type:#X}'" @@ -276,9 +284,10 @@ def _calc_frame_crc(self, data: bytes, frame_type: int) -> int: return calc_crc(crc_data) def ping(self) -> None: - """Ping the target device, retreive protocol version. + """Ping the target device, retrieve protocol version. :raises AssertionError: If the target device doesn't respond to ping + :raises McuBootConnectionError: If the start frame is not received """ ping = struct.pack(' List[Interface]: +def scan_usb(device_name: str = None) -> Sequence[Interface]: """Scan connected USB devices. :param device_name: see USBDeviceFilter classes constructor for usb_id specification @@ -79,7 +71,6 @@ def scan_usb(device_name: str = None) -> List[Interface]: ######################################################################################################################## class RawHid(Interface): """Base class for OS specific RAW HID Interface classes.""" - @property def name(self) -> str: """Get the name of the device. @@ -97,14 +88,15 @@ def is_opened(self) -> bool: :return: True if device is open, False othervise. """ - return self._opened + return self.device is not None and self._opened def __init__(self) -> None: """Initialize the USB interface object.""" + super().__init__() self._opened = False self.vid = 0 self.pid = 0 - self.sn = "" + self.serial_number = "" self.vendor_name = "" self.product_name = "" self.interface_number = 0 @@ -132,13 +124,12 @@ def _decode_report(raw_data: bytes) -> Union[CmdResponse, bytes]: :param raw_data: Data received :type raw_data: bytes :return: CmdResponse object or data read - :raises McuBootConnectionError: Transaction aborted by target + :raises McuBootDataAbortError: Transaction aborted by target """ logger.debug(f"IN [{len(raw_data)}]: {', '.join(f'{b:02X}' for b in raw_data)}") report_id, _, plen = unpack_from('<2BH', raw_data) if plen == 0: - logger.debug("Received an abort package") - raise McuBootConnectionError('Transaction aborted') + raise McuBootDataAbortError() data = raw_data[4: 4 + plen] if report_id == REPORT_ID['CMD_IN']: return parse_cmd_response(data) @@ -152,26 +143,36 @@ def open(self) -> None: """Open the interface.""" logger.debug("Open Interface") try: + assert self.device self.device.open_path(self.path) self._opened = True except OSError: - raise McuBootConnectionError(f"Unable to open device VIP={self.vid} PID={self.pid} SN='{self.sn}'") + raise McuBootConnectionError( + f"Unable to open device VIP={self.vid} PID={self.pid} SN='{self.serial_number}'" + ) def close(self) -> None: """Close the interface.""" logging.debug("Close Interface") try: + assert self.device self.device.close() self._opened = False except OSError: - raise McuBootConnectionError(f"Unable to close device VIP={self.vid} PID={self.pid} SN='{self.sn}'") + raise McuBootConnectionError( + f"Unable to close device VIP={self.vid} PID={self.pid} SN='{self.serial_number}'" + ) def write(self, packet: Union[CmdPacket, bytes]) -> None: """Write data on the OUT endpoint associated to the HID interfaces. :param packet: Data to send :raises ValueError: Raises an error if packet type is incorrect + :raises McuBootConnectionError: Raises an error if device is not openned for writing """ + if not self.is_opened: + raise McuBootConnectionError(f"Device is openned for writing") + if isinstance(packet, CmdPacket): report_id = REPORT_ID['CMD_OUT'] data = packet.to_bytes(padding=False) @@ -182,21 +183,28 @@ def write(self, packet: Union[CmdPacket, bytes]) -> None: raise ValueError("Packet has to be either 'CmdPacket' or 'bytes'") raw_data = self._encode_report(report_id, data) + assert self.device self.device.write(raw_data) def read(self) -> Union[CmdResponse, bytes]: """Read data on the IN endpoint associated to the HID interface. :return: Return CmdResponse object. + :raises McuBootConnectionError: Raises an error if device is not openned for reading """ + if not self.is_opened: + raise McuBootConnectionError(f"Device is not openned for reading") + + assert self.device raw_data = self.device.read(1024, self.timeout) # NOTE: uncomment the following when using KBoot/Flashloader v2.1 and older + # import platform # if platform.system() == "Linux": # raw_data += self.device.read(1024, self.timeout) return self._decode_report(bytes(raw_data)) @staticmethod - def enumerate(usb_device_filter: USBDeviceFilter) -> List[Interface]: + def enumerate(usb_device_filter: USBDeviceFilter) -> Sequence[Interface]: """Get list of all connected devices which matches PyUSB.vid and PyUSB.pid. :param usb_device_filter: USBDeviceFilter object diff --git a/spsdk/mboot/mcuboot.py b/spsdk/mboot/mcuboot.py index 28842ae7..9fc0c1a7 100644 --- a/spsdk/mboot/mcuboot.py +++ b/spsdk/mboot/mcuboot.py @@ -2,20 +2,23 @@ # -*- coding: UTF-8 -*- # # Copyright 2016-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause -"""Module for comunication with the bootloader.""" +"""Module for communication with the bootloader.""" import logging import time from types import TracebackType from typing import Any, Dict, List, Optional, Sequence, Union, Type -from .commands import CommandTag, KeyProvOperation, KeyProvUserKeyType, CmdPacket, GenericResponse +from .commands import ( + CmdResponse, CommandTag, KeyProvOperation, KeyProvUserKeyType, + CmdPacket, GenericResponse, GenerateKeyBlobSelect +) from .error_codes import StatusCode -from .exceptions import McuBootCommandError, McuBootConnectionError, SPSDKError +from .exceptions import McuBootCommandError, McuBootConnectionError, SPSDKError, McuBootDataAbortError from .interfaces import Interface from .memories import ExtMemPropTags, ExtMemId from .properties import PropertyTag, Version, parse_property_value @@ -84,6 +87,7 @@ def _process_cmd(self, cmd_packet: CmdPacket) -> Any: logger.debug('RX-PACKET: No Response, Timeout Error !') raise McuBootConnectionError("No Response from Device") + assert isinstance(response, CmdResponse) logger.debug(f'RX-PACKET: {response.info()}') self._status_code = response.status @@ -92,12 +96,11 @@ def _process_cmd(self, cmd_packet: CmdPacket) -> Any: return response - def _read_data(self, cmd_tag: int, length: int, timeout: int = 1000) -> bytes: + def _read_data(self, cmd_tag: int, length: int) -> bytes: """Read data from device. :param cmd_tag: Tag indicating the read command. :param length: Length of data to read - :param timeout: Timeout, defaults to 1000 :raises McuBootConnectionError: Timeout error or a problem opening the interface :raises McuBootCommandError: Error during command execution on the target :return: Data read from the device @@ -111,6 +114,9 @@ def _read_data(self, cmd_tag: int, length: int, timeout: int = 1000) -> bytes: while True: try: response = self._device.read() + except McuBootDataAbortError as e: + logger.info(f'RX: {e}') + response = self._device.read() except TimeoutError: self._status_code = StatusCode.NO_RESPONSE logger.debug('RX: No Response, Timeout Error !') @@ -148,27 +154,36 @@ def _send_data(self, cmd_tag: int, data: List[bytes]) -> bool: logger.info('TX: Device Disconnected') raise McuBootConnectionError('Device Disconnected !') + total_sent = 0 + # this difference is applicable for load-image and program-aeskey commands + expect_response = (cmd_tag != CommandTag.NO_COMMAND) try: for data_chunk in data: self._device.write(data_chunk) - response = self._device.read() + total_sent += len(data_chunk) + if expect_response: + response = self._device.read() except TimeoutError: self._status_code = StatusCode.NO_RESPONSE logger.debug('RX: No Response, Timeout Error !') raise McuBootConnectionError("No Response from Device") - except SPSDKError: - response = self._device.read() - - logger.debug(f'RX-PACKET: {response.info()}') - self._status_code = response.status - if response.status != StatusCode.SUCCESS: - status_info = StatusCode.get(self._status_code, f'0x{self._status_code:08X}') - logger.debug(f"CMD: Send Error, {status_info}") - if self._cmd_exception: - raise McuBootCommandError(CommandTag.name(cmd_tag), response.status) - return False + except SPSDKError as e: + logger.info(f'RX: {e}') + if expect_response: + response = self._device.read() - logger.info(f"CMD: Successfully Send {sum(len(chunk) for chunk in data)} Bytes") + if expect_response: + assert isinstance(response, CmdResponse) + logger.debug(f'RX-PACKET: {response.info()}') + self._status_code = response.status + if response.status != StatusCode.SUCCESS: + status_info = StatusCode.get(self._status_code, f'0x{self._status_code:08X}') + logger.debug(f"CMD: Send Error, {status_info}") + if self._cmd_exception: + raise McuBootCommandError(CommandTag.name(cmd_tag), response.status) + return False + + logger.info(f"CMD: Successfully Send {total_sent} out of {sum(len(chunk) for chunk in data)} Bytes") return True def _split_data(self, data: bytes) -> List[bytes]: @@ -182,6 +197,7 @@ def _split_data(self, data: bytes) -> List[bytes]: packet_size_property = self.get_property(prop_tag=PropertyTag.MAX_PACKET_SIZE) assert packet_size_property, "Unable to get MAX PACKET SIZE" max_packet_size = packet_size_property[0] + logger.info(f"CMD: Max Packet Size = {max_packet_size}") return [ data[i:i + max_packet_size] for i in range(0, len(data), max_packet_size) ] @@ -328,21 +344,14 @@ def _get_ext_memories(self) -> list: except McuBootCommandError: values = None - if not values: + if not values: # pragma: no cover # corner-cases are currently untestable without HW if self._status_code == StatusCode.UNKNOWN_PROPERTY: - # No external memories are supported by current device. break - if self._status_code == StatusCode.INVALID_ARGUMENT: - # Current memory type is not supported by the device, skip to next external memory. - continue - - if self._status_code == StatusCode.QSPI_NOT_CONFIGURED: - # QSPI0 is not supported, skip to next external memory. - continue - - if self._status_code == StatusCode.MEMORY_NOT_CONFIGURED: - # Un-configured external memory, skip to next external memory. + if self._status_code in [ + StatusCode.INVALID_ARGUMENT, + StatusCode.QSPI_NOT_CONFIGURED, StatusCode.MEMORY_NOT_CONFIGURED + ]: continue assert self._status_code != StatusCode.SUCCESS # Other Error @@ -407,7 +416,6 @@ def flash_erase_all(self, mem_id: int = 0) -> bool: logger.info(f"CMD: FlashEraseAll(mem_id={mem_id})") cmd_packet = CmdPacket(CommandTag.FLASH_ERASE_ALL, 0, mem_id) response = self._process_cmd(cmd_packet) - assert isinstance(cmd_packet, GenericResponse) return response.status == StatusCode.SUCCESS def flash_erase_region(self, address: int, length: int, mem_id: int = 0) -> bool: @@ -484,7 +492,7 @@ def get_property(self, prop_tag: PropertyTag, index: int = 0) -> Optional[List[i :param index: External memory ID or internal memory region index (depends on property type) :return: list integers representing the property; None in case no response from device """ - logger.info(f"CMD: GetProperty({PropertyTag.name(prop_tag)!r}, index={index!r})") + logger.info(f"CMD: GetProperty({PropertyTag.name(prop_tag, 'UNKNOWN')!r}, index={index!r})") cmd_packet = CmdPacket(CommandTag.GET_PROPERTY, 0, prop_tag, index) cmd_response = self._process_cmd(cmd_packet) return cmd_response.values if cmd_response.status == StatusCode.SUCCESS else None @@ -544,8 +552,8 @@ def reset(self, timeout: int = 2000, reopen: bool = True) -> bool: :param timeout: The maximal waiting time in [ms] for reopen connection :param reopen: True for reopen connection after HW reset else False :return: False in case of any problem; True otherwise - :raise ValueError: if reopen is not supported - :raise McuBootConnectionError: Failure to reopen the device + :raises ValueError: if reopen is not supported + :raises McuBootConnectionError: Failure to reopen the device """ logger.info('CMD: Reset MCU') cmd_packet = CmdPacket(CommandTag.RESET, 0) @@ -636,14 +644,14 @@ def flash_read_resource(self, address: int, length: int, option: int = 1) -> Opt return self._read_data(CommandTag.FLASH_READ_RESOURCE, cmd_response.length) return None - def configure_memory(self, address: int, mem_id: ExtMemId) -> bool: + def configure_memory(self, address: int, mem_id: int) -> bool: """Configure memory. :param address: The address in memory where are locating configuration data - :param mem_id: External memory ID + :param mem_id: Memory ID :return: False in case of any problem; True otherwise """ - logger.info(f"CMD: ConfigureMemory({ExtMemId.name(mem_id)}, address=0x{address:08X})") + logger.info(f"CMD: ConfigureMemory({mem_id}, address=0x{address:08X})") cmd_packet = CmdPacket(CommandTag.CONFIGURE_MEMORY, 0, mem_id, address) return self._process_cmd(cmd_packet).status == StatusCode.SUCCESS @@ -657,21 +665,24 @@ def reliable_update(self, address: int) -> bool: cmd_packet = CmdPacket(CommandTag.RELIABLE_UPDATE, 0, address) return self._process_cmd(cmd_packet).status == StatusCode.SUCCESS - def generate_key_blob(self, dek_data: bytes, count: int = 72) -> Optional[bytes]: + def generate_key_blob( + self, dek_data: bytes, key_sel: int = GenerateKeyBlobSelect.OPTMK, count: int = 72 + ) -> Optional[bytes]: """Generate Key Blob. :param dek_data: Data Encryption Key as bytes + :param key_sel: select the BKEK used to wrap the BK (default: OPTMK/FUSES) :param count: Key blob count (default: 72 - AES128bit) :return: Key blob; None in case of an failure """ - logger.info(f"CMD: GenerateKeyBlob(dek_len={len(dek_data)}, count={count})") + logger.info(f"CMD: GenerateKeyBlob(dek_len={len(dek_data)}, key_sel={key_sel}, count={count})") data_chunks = self._split_data(data=dek_data) - cmd_response = self._process_cmd(CmdPacket(CommandTag.GENERATE_KEY_BLOB, 1, 0, len(dek_data), 0)) + cmd_response = self._process_cmd(CmdPacket(CommandTag.GENERATE_KEY_BLOB, 1, key_sel, len(dek_data), 0)) if cmd_response.status != StatusCode.SUCCESS: return None if not self._send_data(CommandTag.GENERATE_KEY_BLOB, data_chunks): return None - cmd_response = self._process_cmd(CmdPacket(CommandTag.GENERATE_KEY_BLOB, 0, 0, count, 1)) + cmd_response = self._process_cmd(CmdPacket(CommandTag.GENERATE_KEY_BLOB, 0, key_sel, count, 1)) if cmd_response.status == StatusCode.SUCCESS: return self._read_data(CommandTag.GENERATE_KEY_BLOB, cmd_response.length) return None @@ -754,3 +765,15 @@ def kp_read_key_store(self) -> Optional[bytes]: if cmd_response.status == StatusCode.SUCCESS: return self._read_data(CommandTag.KEY_PROVISIONING, cmd_response.length) return None + + def load_image(self, data: bytes) -> bool: + """Load a boot image to the device. + + :param data: boot image + :return: False in case of any problem; True otherwise + """ + logger.info(f"CMD: LoadImage(length={len(data)})") + data_chunks = self._split_data(data) + # there's no command in this case + self._status_code = StatusCode.SUCCESS + return self._send_data(CommandTag.NO_COMMAND, data_chunks) diff --git a/spsdk/mboot/properties.py b/spsdk/mboot/properties.py index 8a2f514c..4c6a3be6 100644 --- a/spsdk/mboot/properties.py +++ b/spsdk/mboot/properties.py @@ -2,14 +2,14 @@ # -*- coding: UTF-8 -*- # # Copyright 2016-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Helper module for more human-friendly interpretation of the target device properties.""" -from typing import Any, List, Tuple, Type, Union +from typing import Any, List, Optional, Tuple, Type, Union from spsdk.utils.easy_enum import Enum @@ -156,7 +156,7 @@ class PropertyTag(Enum): RAM_START_ADDRESS = (0x0E, 'RamStartAddress', 'RAM Start Address') RAM_SIZE = (0x0F, 'RamSize', 'RAM Size') SYSTEM_DEVICE_IDENT = (0x10, 'SystemDeviceIdent', 'System Device Identification') - FLASH_SECURITY_STATE = (0x11, 'FlashSecurityState', 'Flash Security State') + FLASH_SECURITY_STATE = (0x11, 'FlashSecurityState', 'Security State') UNIQUE_DEVICE_IDENT = (0x12, 'UniqueDeviceIdent', 'Unique Device Identification') FLASH_FAC_SUPPORT = (0x13, 'FlashFacSupport', 'Flash Fac. Support') FLASH_ACCESS_SEGMENT_SIZE = (0x14, 'FlashAccessSegmentSize', 'Flash Access Segment Size') @@ -421,7 +421,10 @@ class AvailableCommandsValue(PropertyValueBase): @property def tags(self) -> List[str]: """List of tags representing Available commands.""" - return [tag_value for _, tag_value, _ in CommandTag if (1 << tag_value) & self.value] # type: ignore + return [ + tag_value for _, tag_value, _ in CommandTag # type: ignore + if tag_value > 0 and (1 << tag_value - 1) & self.value # type: ignore + ] def __init__(self, tag: int, raw_values: List[int]) -> None: """Initialize the AvailableCommands-based property object. @@ -433,11 +436,14 @@ def __init__(self, tag: int, raw_values: List[int]) -> None: self.value = raw_values[0] def __contains__(self, item: int) -> bool: - return isinstance(item, int) and bool((1 << item) & self.value) + return isinstance(item, int) and bool((1 << item - 1) & self.value) def to_str(self) -> str: """Get stringified property representation.""" - return [name for name, value, _ in CommandTag if (1 << value) & self.value] # type: ignore + return [ + name for name, value, _ in CommandTag # type: ignore + if value > 0 and (1 << value - 1) & self.value # type: ignore + ] class IrqNotifierPinValue(PropertyValueBase): @@ -567,8 +573,8 @@ def to_str(self) -> str: 'kwargs': {'str_format': 'hex'}}, PropertyTag.FLASH_SECURITY_STATE: { 'class': BoolValue, - 'kwargs': {'true_values': (0x00000000, 0x5AA55AA5), 'true_string': 'Unlocked', - 'false_values': (0x00000001, 0xC33CC33C), 'false_string': 'Locked'}}, + 'kwargs': {'true_values': (0x00000000, 0x5AA55AA5), 'true_string': 'UNSECURE', + 'false_values': (0x00000001, 0xC33CC33C), 'false_string': 'SECURE'}}, PropertyTag.UNIQUE_DEVICE_IDENT: { 'class': DeviceUidValue, 'kwargs': {}}, @@ -608,7 +614,7 @@ def to_str(self) -> str: } -def parse_property_value(property_tag: int, raw_values: list, ext_mem_id: int = None) -> Any: +def parse_property_value(property_tag: int, raw_values: list, ext_mem_id: int = None) -> Optional[PropertyValueBase]: """Parse the property value received from the device. :param property_tag: Tag representing the property diff --git a/spsdk/pfr/processor.py b/spsdk/pfr/processor.py index 2c3af76c..07ca3cab 100644 --- a/spsdk/pfr/processor.py +++ b/spsdk/pfr/processor.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -44,6 +44,7 @@ class Processor: Processor is responsible for processing condition - parsing the condition string (lookup) - calling translator for individual keys (registers) + Translator is responsible for looking up values for given keys """ diff --git a/spsdk/pfr/translator.py b/spsdk/pfr/translator.py index 00282801..0beca434 100644 --- a/spsdk/pfr/translator.py +++ b/spsdk/pfr/translator.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause diff --git a/spsdk/sbfile/__init__.py b/spsdk/sbfile/__init__.py index 9bdfac0b..9fc9429a 100644 --- a/spsdk/sbfile/__init__.py +++ b/spsdk/sbfile/__init__.py @@ -1,51 +1,8 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module implementing SBFile.""" - -from spsdk.mboot import ExtMemId -from spsdk.utils.crypto import crypto_backend, Certificate -from .commands import CmdNop, CmdErase, CmdLoad, CmdFill, CmdJump, CmdCall, CmdReset, CmdMemEnable, CmdProg, \ - CmdKeyStoreBackup, CmdKeyStoreRestore -from .commands import VersionCheckType, CmdVersionCheck -from .images import BootImageV20, BootImageV21, SBV2xAdvancedParams -from .misc import BcdVersion3 -from .sb1.headers import SecureBootFlagsV1 -from .sb1.images import SecureBootV1 -from .sb1.sections import BootSectionV1 -from .sections import BootSectionV2, CertSectionV2, CertBlockV2 - -__all__ = [ - # images - 'BootImageV20', - 'BootImageV21', - # sections - 'BootSectionV2', - 'CertSectionV2', - 'CertBlockV2', - # commands - 'CmdNop', - 'CmdErase', - 'CmdLoad', - 'CmdFill', - 'CmdJump', - 'CmdCall', - 'CmdReset', - 'CmdMemEnable', - 'CmdProg', - 'CmdVersionCheck', - 'CmdKeyStoreBackup', - 'CmdKeyStoreRestore', - # other types and enums - 'SBV2xAdvancedParams', - 'BcdVersion3', - 'Certificate', - 'VersionCheckType', - 'ExtMemId', - # functions - 'crypto_backend', -] diff --git a/spsdk/sbfile/sb31/__init__.py b/spsdk/sbfile/sb31/__init__.py index d7b7787a..c56b86f4 100644 --- a/spsdk/sbfile/sb31/__init__.py +++ b/spsdk/sbfile/sb31/__init__.py @@ -4,7 +4,7 @@ # Copyright 2019-2020 NXP # # SPDX-License-Identifier: BSD-3-Clause -"""SB3 module of sbfile.""" +"""SB31 module of sbfile.""" from spsdk.sbfile.sb31.commands import CmdErase, CmdLoad, CmdExecute, CmdCall, CmdProgFuses, CmdProgIfr, \ CmdSectionHeader, CmdLoadCmac, CmdLoadHashLocking, CmdCopy, CmdFillMemory, parse_command, CmdLoadKeyBlob, \ diff --git a/spsdk/sbfile/sb31/commands.py b/spsdk/sbfile/sb31/commands.py index 72a7aeb8..deaed663 100644 --- a/spsdk/sbfile/sb31/commands.py +++ b/spsdk/sbfile/sb31/commands.py @@ -1,75 +1,207 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for creation commands.""" +from abc import abstractmethod from struct import pack, unpack_from, calcsize -from typing import Mapping, Type, List +from typing import Mapping, Type, List, Tuple from spsdk.sbfile.sb31.constants import EnumCmdTag -from spsdk.sbfile.sb31.functions import BaseCmd, MainCmd from spsdk.utils.misc import align_block +from spsdk.utils.easy_enum import Enum + +######################################################################################################################## +# Main Class +######################################################################################################################## + +class MainCmd: + """Functions for creating cmd intended for inheritance.""" + + def __eq__(self, obj: object) -> bool: + """Comparison of values.""" + return isinstance(obj, self.__class__) and vars(obj) == vars(self) + + def __str__(self) -> str: + """Get info of command.""" + return self.info() + + @abstractmethod + def info(self) -> str: + """Get info of command.""" + raise NotImplementedError("Info must be implemented in the derived class.") + + def export(self) -> bytes: + """Export command as bytes.""" + raise NotImplementedError("Export must be implemented in the derived class.") + + @classmethod + def parse(cls, data: bytes, offset: int = 0) -> object: + """Parse command from bytes array.""" + raise NotImplementedError("Parse must be implemented in the derived class.") ######################################################################################################################## -# Commands Classes version 3.1 +# Base Command Class ######################################################################################################################## -class CmdErase(BaseCmd): - """Erase given address range.""" - def __init__(self, address: int, length: int, memory_id: int = 0) -> None: - """Constructor for command. +class BaseCmd(MainCmd): + """Functions for creating cmd intended for inheritance.""" + FORMAT = "<4L" + SIZE = calcsize(FORMAT) + TAG = 0x55aaaa55 - :param address:Input address + @property + def address(self) -> int: + """Get address.""" + return self._address + + @address.setter + def address(self, value: int) -> None: + """Set address.""" + assert 0x00000000 <= value <= 0xFFFFFFFF + self._address = value + + @property + def length(self) -> int: + """Get length.""" + return self._length + + @length.setter + def length(self, value: int) -> None: + """Set value.""" + assert 0x00000000 <= value <= 0xFFFFFFFF + self._length = value + + def __init__(self, address: int, length: int, cmd_tag: int = EnumCmdTag.NONE) -> None: + """Constructor for Commands header. + + :param address: Input address :param length: Input length - :param memory_id: Memory ID + :param cmd_tag: Command tag """ - super().__init__(cmd_tag=EnumCmdTag.ERASE, address=address, length=length) - self.memory_id = memory_id + self._address = address + self._length = length + self.cmd_tag = cmd_tag def info(self) -> str: """Get info of command.""" - return f"ERASE: Address=0x{self.address:08X}, Length={self.length}, Memory ID={self.memory_id}" + raise NotImplementedError("Info must be implemented in the derived class.") + + def export(self) -> bytes: + """Export command as bytes.""" + return pack(self.FORMAT, self.TAG, self.address, self.length, self.cmd_tag) + + @classmethod + def header_parse(cls, cmd_tag: int, data: bytes, offset: int = 0) -> Tuple[int, int]: + """Parse header command from bytes array. + + :param data: Input data as bytes array + :param offset: The offset of input data + :param cmd_tag: Information about command tag + :raises ValueError: Raise if tag is not equal to required TAG + :raises ValueError: Raise if cmd is not equal EnumCmdTag + :return: Tuple + """ + tag, address, length, cmd = unpack_from(cls.FORMAT, data, offset) + if tag != cls.TAG: + raise ValueError("TAG is not valid.") + if cmd != cmd_tag: + raise ValueError("Values are not same.") + return address, length + +######################################################################################################################## +# Commands Classes version 3.1 +######################################################################################################################## +class CmdLoadBase(BaseCmd): + """Base class for commands loading data.""" + HAS_MEMORY_ID_BLOCK = True + + def __init__(self, cmd_tag: int, address: int, data: bytes, memory_id: int = 0) -> None: + """Constructor for command. + + :param cmd_tag: Command tag for the derived class + :param address: Address for the load command + :param data: Data to load + :param memory_id: Memory ID + """ + super().__init__(address=address, length=len(data), cmd_tag=cmd_tag) + self.memory_id = memory_id + self.data = data def export(self) -> bytes: """Export command as bytes.""" data = super().export() - data += pack("<4L", self.memory_id, 0, 0, 0) + if self.HAS_MEMORY_ID_BLOCK: + data += pack("<4L", self.memory_id, 0, 0, 0) + data += self.data + data = align_block(data, alignment=16) return data + def info(self) -> str: + """Get info about the load command.""" + msg = f"{EnumCmdTag.name(self.cmd_tag)}: " + if self.HAS_MEMORY_ID_BLOCK: + msg += f"Address=0x{self.address:08X}, Length={self.length}, Memory ID={self.memory_id}" + else: + msg += f"Address=0x{self.address:08X}, Length={self.length}" + return msg + + @classmethod - def parse(cls, data: bytes, offset: int = 0) -> "CmdErase": + def _extract_data(cls, data: bytes, offset: int = 0) -> Tuple[int, int, bytes, int, int]: + tag, address, length, cmd = unpack_from(cls.FORMAT, data) + memory_id = 0 + if tag != cls.TAG: + raise ValueError(f"Invalid TAG, expected: {cls.TAG}") + offset += BaseCmd.SIZE + if cls.HAS_MEMORY_ID_BLOCK: + memory_id, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset) + assert pad0 == pad1 == pad2 == 0 + offset += 16 + load_data = data[offset: offset + length] + return address, length, load_data, cmd, memory_id + + @classmethod + def parse(cls, data: bytes, offset: int = 0) -> "CmdLoadBase": """Parse command from bytes array. :param data: Input data as bytes array :param offset: The offset of input data - :return: CmdErase + :return: CmdLoad + :raises ValueError: Invalid cmd_tag was found """ - address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.ERASE) - memory_id, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset+16) - assert pad0 == pad1 == pad2 == 0 - return cls(address=address, length=length, memory_id=memory_id) - + address, _, data, cmd_tag, memory_id = cls._extract_data(data, offset) + if cmd_tag not in [ + EnumCmdTag.LOAD, EnumCmdTag.LOAD_CMAC, EnumCmdTag.LOAD_HASH_LOCKING, + EnumCmdTag.LOAD_KEY_BLOB, EnumCmdTag.PROGRAM_FUSES, + EnumCmdTag.PROGRAM_IFR + ]: + raise ValueError(f"Invalid cmd_tag found: {cmd_tag}") + if cls == CmdLoadBase: + return cls(cmd_tag=cmd_tag, address=address, data=data, memory_id=memory_id) + # pylint: disable=no-value-for-parameter + return cls(address=address, data=data, memory_id=memory_id) # type: ignore -class CmdLoad(BaseCmd): - """Data to write follows the range header.""" +class CmdErase(BaseCmd): + """Erase given address range. The erase will be rounded up to the sector size.""" def __init__(self, address: int, length: int, memory_id: int = 0) -> None: """Constructor for command. - :param address:Input address + :param address: Input address :param length: Input length :param memory_id: Memory ID """ - super().__init__(cmd_tag=EnumCmdTag.LOAD, address=address, length=length) + super().__init__(cmd_tag=EnumCmdTag.ERASE, address=address, length=length) self.memory_id = memory_id def info(self) -> str: """Get info of command.""" - return f"LOAD: Address=0x{self.address:08X}, Length={self.length}, Memory ID={self.memory_id}" + return f"ERASE: Address=0x{self.address:08X}, Length={self.length}, Memory ID={self.memory_id}" def export(self) -> bytes: """Export command as bytes.""" @@ -78,19 +210,32 @@ def export(self) -> bytes: return data @classmethod - def parse(cls, data: bytes, offset: int = 0) -> "CmdLoad": + def parse(cls, data: bytes, offset: int = 0) -> "CmdErase": """Parse command from bytes array. :param data: Input data as bytes array :param offset: The offset of input data - :return: CmdLoad + :return: CmdErase """ - address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.LOAD) + address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.ERASE) memory_id, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset+16) assert pad0 == pad1 == pad2 == 0 return cls(address=address, length=length, memory_id=memory_id) +class CmdLoad(CmdLoadBase): + """Data to write follows the range header.""" + + def __init__(self, address: int, data: bytes, memory_id: int = 0) -> None: + """Constructor for command. + + :param address: Address for the load command + :param data: Data to load + :param memory_id: Memory ID + """ + super().__init__(cmd_tag=EnumCmdTag.LOAD, address=address, data=data, memory_id=memory_id) + + class CmdExecute(BaseCmd): """Address will be the jump-to address.""" @@ -143,40 +288,34 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdCall": return cls(address=address) -class CmdProgFuses(BaseCmd): +class CmdProgFuses(CmdLoadBase): """Address will be address of fuse register.""" - @property - def data(self) -> List[int]: - """Get data.""" - return self._data - - @data.setter - def data(self, value: List[int]) -> None: - """Set data.""" - assert isinstance(value, list) - self._data = value - self._length = len(self._data) - - def __init__(self, address: int, data: List[int]) -> None: + HAS_MEMORY_ID_BLOCK = False + + def __init__(self, address: int, data: bytes) -> None: """Constructor for Command. :param address: Input address - :param data: Input values + :param data: Input data """ - super().__init__(cmd_tag=EnumCmdTag.PROGRAM_FUSES, address=address, length=len(data)) - self.data = data + super().__init__(cmd_tag=EnumCmdTag.PROGRAM_FUSES, address=address, data=data) + self.length //= 4 - def info(self) -> str: - """Get info of command.""" - return f"PROGRAM_FUSES: Address=0x{self.address:08X}, Values={self.data}" - - def export(self) -> bytes: - """Export command as bytes.""" - data = super().export() - for value in self.data: - data += pack(" Tuple[int, int, bytes, int, int]: + tag, address, length, cmd = unpack_from(cls.FORMAT, data) + length *= 4 + memory_id = 0 + if tag != cls.TAG: + raise ValueError(f"Invalid TAG, expected: {cls.TAG}") + offset += BaseCmd.SIZE + if cls.HAS_MEMORY_ID_BLOCK: + memory_id, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset) + assert pad0 == pad1 == pad2 == 0 + offset += 16 + load_data = data[offset: offset + length] + return address, length, load_data, cmd, memory_id @classmethod def parse(cls, data: bytes, offset: int = 0) -> "CmdProgFuses": @@ -186,46 +325,24 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdProgFuses": :param offset: The offset of input data :return: CmdProgFuses """ - address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.PROGRAM_FUSES) - values = [] - for i in range(length): - values.append(unpack_from(" bytes: - """Get data.""" - return self._data - - @data.setter - def data(self, value: bytes) -> None: - """Set data.""" - assert isinstance(value, bytes) - self._data = value - self._length = len(self._data) - - def __init__(self, data: bytes, address: int) -> None: + HAS_MEMORY_ID_BLOCK = False + + def __init__(self, address: int, data: bytes) -> None: """Constructor for Command. :param address: Input address :param data: Input data as bytes array """ - super().__init__(cmd_tag=EnumCmdTag.PROGRAM_IFR, address=address, length=len(data)) - self.data = data - - def info(self) -> str: - """Get info of command.""" - return f"PROGRAM_IFR: Address=0x{self.address:08X}, DataLen={len(self.data)}" - - def export(self) -> bytes: - """Export command as bytes.""" - data = super().export() - data += bytes(self.data) - return data + super().__init__( + cmd_tag=EnumCmdTag.PROGRAM_IFR, address=address, data=data + ) @classmethod def parse(cls, data: bytes, offset: int = 0) -> "CmdProgIfr": @@ -233,49 +350,23 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdProgIfr": :param data: Input data as bytes array :param offset: The offset of input data - :return: CmdProgIfr + :return: CmdProgFuses """ - address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.PROGRAM_IFR) - offset += BaseCmd.SIZE - load_data = data[offset: offset + length] - return cls(address=address, data=load_data) + address, _, data, _, _ = cls._extract_data(data=data, offset=offset) + return cls(address=address, data=data) -class CmdLoadCmac(BaseCmd): +class CmdLoadCmac(CmdLoadBase): """Load cmac. ROM is calculating cmac from loaded data.""" - def __init__(self, address: int, length: int, memory_id: int = 0) -> None: + def __init__(self, address: int, data: bytes, memory_id: int = 0) -> None: """Constructor for command. - :param address:Input address - :param length: Input length + :param address: Address for the load command + :param data: Data to load :param memory_id: Memory ID """ - super().__init__(cmd_tag=EnumCmdTag.LOAD_CMAC, address=address, length=length) - self.memory_id = memory_id - - def info(self) -> str: - """Get info of command.""" - return f"LOAD_CMAC: Address=0x{self.address:08X}, Length={self.length}, Memory ID={self.memory_id}" - - def export(self) -> bytes: - """Export command as bytes.""" - data = super().export() - data += pack("<4L", self.memory_id, 0, 0, 0) - return data - - @classmethod - def parse(cls, data: bytes, offset: int = 0) -> "CmdLoadCmac": - """Parse command from bytes array. - - :param data: Input data as bytes array - :param offset: The offset of input data - :return: CmdLoadCmac - """ - address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.LOAD_CMAC) - memory_id, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset+16) - assert pad0 == pad1 == pad2 == 0 - return cls(address=address, length=length, memory_id=memory_id) + super().__init__(cmd_tag=EnumCmdTag.LOAD_CMAC, address=address, data=data, memory_id=memory_id) class CmdCopy(BaseCmd): @@ -290,7 +381,7 @@ def __init__(self, ) -> None: """Constructor for command. - :param address:Input address + :param address: Input address :param length: Input length :param destination_address: Destination address :param memory_id_from: Memory ID @@ -330,51 +421,37 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdCopy": ) -class CmdLoadHashLocking(BaseCmd): +class CmdLoadHashLocking(CmdLoadBase): """Load hash. ROM is calculating hash.""" - def __init__(self, address: int, length: int, memory_id: int = 0) -> None: + def __init__(self, address: int, data: bytes, memory_id: int = 0) -> None: """Constructor for command. - :param address:Input address - :param length: Input length + :param address: Address for the load command + :param data: Data to load :param memory_id: Memory ID """ - super().__init__(cmd_tag=EnumCmdTag.LOAD_HASH_LOCKING, address=address, length=length) - self.memory_id = memory_id - - def info(self) -> str: - """Get info of command.""" - return f"LOAD_HASH_LOCKING: Address=0x{self.address:08X}, Length={self.length}, Memory ID={self.memory_id}" + super().__init__( + cmd_tag=EnumCmdTag.LOAD_HASH_LOCKING, address=address, + data=data, memory_id=memory_id + ) def export(self) -> bytes: """Export command as bytes.""" data = super().export() - data += pack("<4L", self.memory_id, 0, 0, 0) + data += bytes(64) return data - @classmethod - def parse(cls, data: bytes, offset: int = 0) -> "CmdLoadHashLocking": - """Parse command from bytes array. - - :param data: Input data as bytes array - :param offset: The offset of input data - :return: CmdCopy - """ - address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.LOAD_HASH_LOCKING) - memory_id, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset+16) - assert pad0 == pad1 == pad2 == 0 - return cls(address=address, length=length, memory_id=memory_id) - - class CmdLoadKeyBlob(BaseCmd): """Load key blob.""" FORMAT = " None: + def __init__(self, offset: int, data: bytes, key_wrap_id: int) -> None: """Constructor for command. :param offset: Input offset @@ -390,7 +467,7 @@ def info(self) -> str: return f"LOAD_KEY_BLOB: Offset=0x{self.address:08X}, Length={self.length}, Key wrap ID={self.key_wrap_id}" def export(self) -> bytes: - """Export command header as bytes array.""" + """Export command as bytes.""" result_data = pack(self.FORMAT, self.TAG, self.address, self.key_wrap_id, self.length, self.cmd_tag) result_data += self.data @@ -427,8 +504,8 @@ def info(self) -> str: return f"CONFIGURE_MEMORY: Address=0x{self.address:08X}, Memory ID={self.memory_id}" def export(self) -> bytes: - """Export command header as bytes array.""" - return pack(self.FORMAT, self.TAG, self.address, self.memory_id, self.cmd_tag) + """Export command as bytes.""" + return pack(self.FORMAT, self.TAG, self.memory_id, self.address, self.cmd_tag) @classmethod def parse(cls, data: bytes, offset: int = 0) -> "CmdConfigureMemory": @@ -438,7 +515,7 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdConfigureMemory": :param offset: The offset of input data :return: CmdConfigureMemory """ - address, memory_id = cls.header_parse( + memory_id, address = cls.header_parse( cmd_tag=EnumCmdTag.CONFIGURE_MEMORY, data=data, offset=offset ) return cls(address=address, memory_id=memory_id) @@ -447,24 +524,24 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdConfigureMemory": class CmdFillMemory(BaseCmd): """Fill memory range by pattern.""" - def __init__(self, address: int, length: int, memory_id: int = 0) -> None: + def __init__(self, address: int, length: int, pattern: int) -> None: """Constructor for command. - :param address:Input address + :param address: Input address :param length: Input length - :param memory_id: Memory ID + :param pattern: Pattern for fill memory with """ super().__init__(cmd_tag=EnumCmdTag.FILL_MEMORY, address=address, length=length) - self.memory_id = memory_id + self.pattern = pattern def info(self) -> str: """Get info of command.""" - return f"FILL_MEMORY: Address=0x{self.address:08X}, Length={self.length}, Memory ID={self.memory_id}" + return f"FILL_MEMORY: Address=0x{self.address:08X}, Length={self.length}, PATTERN={hex(self.pattern)}" def export(self) -> bytes: """Export command as bytes.""" data = super().export() - data += pack("<4L", self.memory_id, 0, 0, 0) + data += pack("<4L", self.pattern, 0, 0, 0) return data @classmethod @@ -476,16 +553,23 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdFillMemory": :return: CmdErase """ address, length = cls.header_parse(data=data, offset=offset, cmd_tag=EnumCmdTag.FILL_MEMORY) - memory_id, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset + 16) + pattern, pad0, pad1, pad2 = unpack_from("<4L", data, offset=offset + 16) assert pad0 == pad1 == pad2 == 0 - return cls(address=address, length=length, memory_id=memory_id) + return cls(address=address, length=length, pattern=pattern) class CmdFwVersionCheck(BaseCmd): """Check counter value with stored value, if values are not same, SB file is rejected.""" - NONSECURE = 1 - SECURE = 2 + class COUNTER_ID(Enum): + """Counter IDs used by the CmdFwVersionCheck command.""" + NONE = (0, 'none') + NONSECURE = (1, 'nonsecure') + SECURE = (2, 'secure') + RADIO = (3, 'radio') + SNT = (4, 'snt') + BOOTLOADER = (3, 'bootloader') + def __init__(self, value: int, counter_id: int) -> None: """Constructor for command. @@ -502,7 +586,7 @@ def info(self) -> str: return f"FW_VERSION_CHECK: Value={self.value}, Counter ID={self.counter_id}" def export(self) -> bytes: - """Export command header as bytes array.""" + """Export command as bytes.""" return pack(self.FORMAT, self.TAG, self.value, self.counter_id, self.cmd_tag) @classmethod @@ -524,7 +608,7 @@ class CmdSectionHeader(MainCmd): FORMAT = "<4L" SIZE = calcsize(FORMAT) - def __init__(self, section_uid: int = 0, section_type: int = 0, length: int = 0) -> None: + def __init__(self, length: int, section_uid: int = 1, section_type: int = 1) -> None: """Constructor for Commands section. :param section_uid: Input uid @@ -555,11 +639,25 @@ def parse(cls, data: bytes, offset: int = 0) -> "CmdSectionHeader": """ if calcsize(cls.FORMAT) > len(data) - offset: raise ValueError("FORMAT is bigger than length of the data without offset!") - obj = cls() - (obj.section_uid, obj.section_type, obj.length, obj._pad) = unpack_from(cls.FORMAT, data, offset) - return obj - - + section_uid, section_type, length, _ = unpack_from(cls.FORMAT, data, offset) + return cls(section_uid=section_uid, section_type=section_type, length=length) + + +TAG_TO_CLASS: Mapping[int, Type[BaseCmd]] = { + EnumCmdTag.ERASE: CmdErase, + EnumCmdTag.LOAD: CmdLoad, + EnumCmdTag.EXECUTE: CmdExecute, + EnumCmdTag.CALL: CmdCall, + EnumCmdTag.PROGRAM_FUSES: CmdProgFuses, + EnumCmdTag.PROGRAM_IFR: CmdProgIfr, + EnumCmdTag.LOAD_CMAC: CmdLoadCmac, + EnumCmdTag.COPY: CmdCopy, + EnumCmdTag.LOAD_HASH_LOCKING: CmdLoadHashLocking, + EnumCmdTag.LOAD_KEY_BLOB: CmdLoadKeyBlob, + EnumCmdTag.CONFIGURE_MEMORY: CmdConfigureMemory, + EnumCmdTag.FILL_MEMORY: CmdFillMemory, + EnumCmdTag.FW_VERSION_CHECK: CmdFwVersionCheck +} ######################################################################################################################## # Command parser from raw data ######################################################################################################################## @@ -571,25 +669,10 @@ def parse_command(data: bytes, offset: int = 0) -> object: :raises ValueError: Raise when tag is not in cmd_class :return: object """ - cmd_class: Mapping[int, Type[BaseCmd]] = { - EnumCmdTag.ERASE: CmdErase, - EnumCmdTag.LOAD: CmdLoad, - EnumCmdTag.EXECUTE: CmdExecute, - EnumCmdTag.CALL: CmdCall, - EnumCmdTag.PROGRAM_FUSES: CmdProgFuses, - EnumCmdTag.PROGRAM_IFR: CmdProgIfr, - EnumCmdTag.LOAD_CMAC: CmdLoadCmac, - EnumCmdTag.COPY: CmdCopy, - EnumCmdTag.LOAD_HASH_LOCKING: CmdLoadHashLocking, - EnumCmdTag.LOAD_KEY_BLOB: CmdLoadKeyBlob, - EnumCmdTag.CONFIGURE_MEMORY: CmdConfigureMemory, - EnumCmdTag.FILL_MEMORY: CmdFillMemory, - EnumCmdTag.FW_VERSION_CHECK: CmdFwVersionCheck - } # verify that first 4 bytes of frame are 55aaaa55 tag = unpack_from(" None: + """Initialize the KeyDerivator. + + :param pck: Part Common Key, base user key for all key derivations + :param timestamp: Timestamp used for creating the KeyDerivationKey + :param key_length: Requested key length after derivation (128/256bits) + :param kdk_access_rights: KeyDerivationKey access rights + """ + self.pck = pck + self.key_length = key_length + self.kdk_access_rights = kdk_access_rights + self.timestamp = timestamp + self.kdk = self._derive_kdk() + + def _derive_kdk(self) -> bytes: + """Derive the KeyDerivationKey from PCK and timestamp.""" + return derive_kdk(self.pck, self.timestamp, self.key_length, self.kdk_access_rights) + + def get_block_key(self, block_number: int) -> bytes: + """Derive key for particular block.""" + return derive_block_key(self.kdk, block_number, self.key_length, self.kdk_access_rights) + + +def derive_block_key(kdk: bytes, block_number: int, key_length: int, kdk_access_rights: int) -> bytes: + """Derive encryption AES key for given block. + + :param kdk: Key Derivation Key + :param block_number: Block number + :param key_length: Required key length (128/256) + :param kdk_access_rights: Key Derivation Key access rights (0-3) + :return: AES key for given block + """ + return _derive_key( + key=kdk, derivation_constant=block_number, kdk_access_rights=kdk_access_rights, + key_length=key_length, mode=KeyDerivationMode.BLK + ) + +def derive_kdk(pck: bytes, timestamp: int, key_length: int, kdk_access_rights: int) -> bytes: + """Derive the Key Derivation Key. + + :param pck: Part Common Key + :param timestamp: Timestamp for KDK derivation + :param key_length: Requested key length (128/256b) + :param kdk_access_rights: KDK access rights (0-3) + :return: Key Derivation Key + """ + return _derive_key( + key=pck, derivation_constant=timestamp, kdk_access_rights=kdk_access_rights, + key_length=key_length, mode=KeyDerivationMode.KDK + ) + + +def _derive_key( + key: bytes, derivation_constant: int, kdk_access_rights: int, + mode: int, key_length: int +) -> bytes: + """Derive new AES key from the provided key. + + :param key: Base (original) key + :param derivation_constant: Derivation constant for key derivation + :param kdk_access_rights: Key Derivation Key access rights (0-3) + :param mode: Mode of derivation (1/2; see `KeyDerivationMode`) + :param key_length: Requested key length (128/256b) + :return: New (derived) AES key + """ + # use partial to save typing later on + derivation_data = functools.partial( + _get_key_derivation_data, + derivation_constant=derivation_constant, + kdk_access_rights=kdk_access_rights, + mode=mode, key_length=key_length + ) + + result = internal_backend.cmac(data=derivation_data(iteration=1), key=key) + if key_length == 256: + result += internal_backend.cmac(data=derivation_data(iteration=2), key=key) + return result + + +def _get_key_derivation_data( + derivation_constant: int, kdk_access_rights: int, + mode: int, key_length: int, iteration: int +) -> bytes: + """Generate data for AES key derivation. + + :param derivation_constant: Number for the key derivation + :param kdk_access_rights: KeyDerivationKey access rights (0-3) + :param mode: Mode for key derivation (1/2, see: `KeyDerivationMode`) + :param key_length: Requested key length (128/256b) + :param iteration: Iteration of the key derivation + :return: Data used for key derivation + :raises AssertionError: Some of the arguments are incorrect. + """ + assert mode in KeyDerivationMode.tags() + assert kdk_access_rights in [0, 1, 2, 3] + assert key_length in [128, 256] + + label = int.to_bytes(derivation_constant, length=12, byteorder='little') + context = bytes(8) + context += int.to_bytes(kdk_access_rights << 6, length=1, byteorder='big') + context += b'\x01' if mode == KeyDerivationMode.KDK else b'\x10' + context += bytes(1) + key_option = 0x20 if key_length == 128 else 0x21 + context += int.to_bytes(key_option, length=1, byteorder='big') + length = int.to_bytes(key_length, length=4, byteorder='big') + i = int.to_bytes(iteration, length=4, byteorder='big') + result = label + context + length + i + return result def add_leading_zeros(byte_data: bytes, return_size: int) -> bytes: @@ -35,95 +151,3 @@ def add_trailing_zeros(byte_data: bytes, return_size: int) -> bytes: size_of_zeros = return_size - len(byte_data) byte_data_with_padding = byte_data + bytes("\x00" * size_of_zeros, "utf8") return byte_data_with_padding - - -class MainCmd: - """Functions for creating cmd intended for inheritance.""" - - def __eq__(self, obj: object) -> bool: - """Comparison of values.""" - return isinstance(obj, self.__class__) and vars(obj) == vars(self) - - def __str__(self) -> str: - """Get info of command.""" - return self.info() - - @abstractmethod - def info(self) -> str: - """Get info of command.""" - raise NotImplementedError("Info must be implemented in the derived class.") - - def export(self) -> bytes: - """Export command as bytes.""" - raise NotImplementedError("Export must be implemented in the derived class.") - - @classmethod - def parse(cls, data: bytes, offset: int = 0) -> object: - """Parse command from bytes array.""" - raise NotImplementedError("Parse must be implemented in the derived class.") - - -class BaseCmd(MainCmd): - """Functions for creating cmd intended for inheritance.""" - FORMAT = "<4L" - SIZE = calcsize(FORMAT) - TAG = 0x55aaaa55 - - @property - def address(self) -> int: - """Get address.""" - return self._address - - @address.setter - def address(self, value: int) -> None: - """Set address.""" - assert 0x00000000 <= value <= 0xFFFFFFFF - self._address = value - - @property - def length(self) -> int: - """Get length.""" - return self._length - - @length.setter - def length(self, value: int) -> None: - """Set value.""" - assert 0x00000000 <= value <= 0xFFFFFFFF - self._length = value - - def __init__(self, address: int, length: int, cmd_tag: int = EnumCmdTag.NONE) -> None: - """Constructor for Commands header. - - :param address: Input address - :param length: Input length - :param cmd_tag: Command tag - """ - self._address = address - self._length = length - self.cmd_tag = cmd_tag - - def info(self) -> str: - """Get info of command.""" - raise NotImplementedError("Info must be implemented in the derived class.") - - def export(self) -> bytes: - """Export command header as bytes array.""" - return pack(self.FORMAT, self.TAG, self.address, self.length, self.cmd_tag) - - @classmethod - def header_parse(cls, cmd_tag: int, data: bytes, offset: int = 0) -> Tuple[int, int]: - """Parse header command from bytes array. - - :param data: Input data as bytes array - :param offset: The offset of input data - :param cmd_tag: Information about command tag - :raises ValueError: Raise if tag is not equal to required TAG - :raises ValueError: Raise if cmd is not equal EnumCmdTag - :return: Tuple - """ - tag, address, length, cmd = unpack_from(cls.FORMAT, data, offset) - if tag != cls.TAG: - raise ValueError("TAG is not valid.") - if cmd != cmd_tag: - raise ValueError("Values are not same.") - return address, length diff --git a/spsdk/sbfile/sb31/images.py b/spsdk/sbfile/sb31/images.py new file mode 100644 index 00000000..84559cd8 --- /dev/null +++ b/spsdk/sbfile/sb31/images.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Module used for generation SecureBinary V3.1.""" +from datetime import datetime +from typing import List, Sequence +from struct import pack, unpack_from, calcsize + +from spsdk import SPSDKError +from spsdk.sbfile.sb31.functions import KeyDerivator +from spsdk.utils.misc import align_block +from spsdk.utils.crypto.abstract import BaseClass +from spsdk.sbfile.sb31.commands import BaseCmd, CmdSectionHeader +from spsdk.utils.crypto.backend_internal import internal_backend + + +######################################################################################################################## +# Secure Boot Image Class (Version 3.1) +######################################################################################################################## +class SecureBinary31Header(BaseClass): + """Header of the SecureBinary V3.1.""" + HEADER_FORMAT = "<4s2H3LQ4L16s" + HEADER_SIZE = calcsize(HEADER_FORMAT) + MAGIC = b"sbv3" + FORMAT_VERSION = "3.1" + DESCRIPTION_LENGTH = 16 + + def __init__( + self, firmware_version: int, curve_name: str, + description: str = None, timestamp: int = None, + is_nxp_container: bool = False, flags: int = 0, + ) -> None: + """Initialize the SecureBinary V3.1 Header. + + :param firmware_version: Firmaware version (must be bigger than current CMPA record) + :param curve_name: Name of the ECC curve used for Secure binary (secp256r1/secp384r1) + :param description: Custom description up to 16 characters long, defaults to None + :param timestamp: Timestap (number of seconds since Jan 1st, 200), if None use current time + :param is_nxp_container: NXP provisioning SB file, defaults to False + :param flags: Flags for SB file (currently un-used), defaults to 0 + """ + self.flags = flags + self.block_count = 0 + self.curve_name = curve_name + self.block_size = self.calculate_block_size() + self.image_type = 7 if is_nxp_container else 6 + self.firmware_version = firmware_version + self.timestamp = timestamp or int(datetime.now().timestamp()) + self.image_total_length = self.HEADER_SIZE + self.cert_block_offset = self.calculate_cert_block_offset() + self.description = self._adjust_description(description) + + def _adjust_description(self, description: str = None) -> bytes: + """Format the description.""" + if not description: + return bytes(self.DESCRIPTION_LENGTH) + desc = bytes(description, encoding='ascii') + desc = desc[:self.DESCRIPTION_LENGTH] + desc += bytes(self.DESCRIPTION_LENGTH - len(desc)) + return desc + + def calculate_cert_block_offset(self) -> int: + """Calculate the offset to the Certification block.""" + fixed_offset = 1 * 8 + 9 * 4 + 16 + if self.curve_name == 'secp256r1': + return fixed_offset + 32 + if self.curve_name == 'secp384r1': + return fixed_offset + 48 + raise SPSDKError(f"Invalid curve name: {self.curve_name}") + + def calculate_block_size(self) -> int: + """Calculate the the data block size.""" + fixed_block_size = 4 + 256 + if self.curve_name == 'secp256r1': + return fixed_block_size + 32 + if self.curve_name == 'secp384r1': + return fixed_block_size + 48 + raise SPSDKError(f"Invalid curve name: {self.curve_name}") + + def info(self) -> str: + """Get info of SB v31 as a string.""" + info = str() + info += f" Magic: {self.MAGIC.decode('ascii')}\n" + info += f" Version: {self.FORMAT_VERSION}\n" + info += f" Flags: 0x{self.flags:04X}\n" + info += f" Block count: {self.block_count}\n" + info += f" Block size: {self.block_size}\n" + info += f" Firmware version: {self.firmware_version}\n" + info += f" Image type: {self.image_type}\n" + info += f" Timestamp: {self.timestamp}\n" + info += f" Total length of image: {self.image_total_length}\n" + info += f" Certificate block offset: {self.cert_block_offset}\n" + info += f" Description: {self.description.decode('ascii')}\n" + return info + + def export(self) -> bytes: + """Serialize the SB file to bytes.""" + major_format_version, minor_format_version = [int(v) for v in self.FORMAT_VERSION.split(".")] + return pack( + self.HEADER_FORMAT, + self.MAGIC, + minor_format_version, major_format_version, + self.flags, + self.block_count, + self.block_size, + self.timestamp, + self.firmware_version, + self.image_total_length, + self.image_type, + self.cert_block_offset, + self.description) + + @classmethod + def parse(cls, data: bytes, offset: int = 0) -> "SecureBinary31Header": + """Parse binary data into SecureBinary31Header. + + :raises NotImplementedError: Not yet implemented + """ + raise NotImplementedError() + + +class SecureBinary31Commands(BaseClass): + """Blob containing SB3.1 commands.""" + DATA_CHUNK_LENGTH = 256 + + def __init__( + self, curve_name: str, is_encrypted: bool = True, + pck: bytes = None, timestamp: int = None, kdk_access_rights: int = None + ) -> None: + """Initialize container for SB3.1 commands. + + :param curve_name: Name of the ECC curve used for Secure binary (secp256r1/secp384r1) + :param is_encrypted: Indicate whether commands should be encrypted or not, defaults to True + :param pck: Part Common Key (needed if `is_encrypted` is True), defaults to None + :param timestamp: Timestamp used for encryption (needed if `is_encrypted` is True), defaults to None + :param kdk_access_rights: Key Derivation Key access rights (needed if `is_encrypted` is True), defaults to None + :raises SPSDKError: Key derivation arguments are not provided if `is_encrypted` is True + """ + super().__init__() + self.curve_name = curve_name + self.hash_type = self._get_hash_type(curve_name) + self.is_encrypted = is_encrypted + self.block_count = 0 + self.final_hash = bytes(self._get_hash_length(curve_name)) + self.commands: List[BaseCmd] = [] + self.key_derivator = None + if is_encrypted: + if not (pck and timestamp and kdk_access_rights): + raise SPSDKError("PCK, timeout or kdk_access_rights are not defined.") + self.key_derivator = KeyDerivator( + pck=pck, timestamp=timestamp, + key_length=self._get_key_length(curve_name), + kdk_access_rights=kdk_access_rights + ) + + def _get_hash_length(self, curve_name: str) -> int: + return { + 'secp256r1': 32, + 'secp384r1': 48 + }[curve_name] + + def _get_key_length(self, curve_name: str) -> int: + return { + 'secp256r1': 128, + 'secp384r1': 256 + }[curve_name] + + def _get_hash_type(self, curve_name: str) -> str: + return { + 'secp256r1': 'sha256', + 'secp384r1': 'sha384' + }[curve_name] + + def add_command(self, command: BaseCmd) -> None: + """Add SB3.1 command.""" + self.commands.append(command) + + def set_commands(self, commands: List[BaseCmd]) -> None: + """Set all SB3.1 commands at once.""" + self.commands = commands.copy() + + def export(self) -> bytes: + """Export commands as bytes.""" + commands_bytes = b''.join([command.export() for command in self.commands]) + section_header = CmdSectionHeader(length=len(commands_bytes)) + total = section_header.export() + commands_bytes + + data_blocks = [ + total[i : i + self.DATA_CHUNK_LENGTH] + for i in range(0, len(total), self.DATA_CHUNK_LENGTH) + ] + data_blocks[-1] = align_block(data_blocks[-1], alignment=self.DATA_CHUNK_LENGTH) + self.block_count = len(data_blocks) + + processed_blocks = [ + self._process_block(block_number, block_data) + for block_number, block_data in reversed(list(enumerate(data_blocks, start=1))) + ] + final_data = b''.join(reversed(processed_blocks)) + return final_data + + def _process_block(self, block_number: int, block_data: bytes) -> bytes: + """Process single block.""" + if self.is_encrypted: + assert self.key_derivator + block_key = self.key_derivator.get_block_key(block_number) + encrypted_block = internal_backend.aes_cbc_encrypt(block_key, block_data) + else: + encrypted_block = block_data + + full_block = pack( + f" str: + """Get string information for commands in the container.""" + info = str() + info += f"COMMANDS:\n" + info += f"Number of commands: {len(self.commands)}\n" + for command in self.commands: + info += f" {command.info()}\n" + return info + + @classmethod + def parse(cls, data: bytes, offset: int = 0) -> "SecureBinary31Commands": + """Parse binary data into SecureBinary31Commands. + + :raises NotImplementedError: Not yet implemented + """ + raise NotImplementedError() diff --git a/spsdk/sdp/hab_logs.py b/spsdk/sdp/hab_logs.py deleted file mode 100644 index ca083b95..00000000 --- a/spsdk/sdp/hab_logs.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Module that allows human-friendly interpretation of HAB logs.""" - -import struct - -from .error_codes import HabStatusInfo, HabErrorReason - - -######################################################################################################################## -# i.MX6 HAB Log Parser -######################################################################################################################## -def parse_mx6_log(data: bytes) -> str: - """Parses the HAB log data for i.MX6 devices. - - :param data: Data retrieved from the device - :return: String representation of the data - """ - log_single_desc = { - 0x00010000: "BOOTMODE - Internal Fuse", - 0x00010001: "BOOTMODE - Serial Bootloader", - 0x00010002: "BOOTMODE - Internal/Override", - 0x00010003: "BOOTMODE - Test Mode", - 0x00020000: "Security Mode - Fab", - 0x00020033: "Security Mode - Return", - 0x000200F0: "Security Mode - Open", - 0x000200CC: "Security Mode - Closed", - 0x00030000: "DIR_BT_DIS = 0", - 0x00030001: "DIR_BT_DIS = 1", - 0x00040000: "BT_FUSE_SEL = 0", - 0x00040001: "BT_FUSE_SEL = 1", - 0x00050000: "Primary Image Selected", - 0x00050001: "Secondary Image Selected", - 0x00060000: "NAND Boot", - 0x00060001: "USDHC Boot", - 0x00060002: "SATA Boot", - 0x00060003: "I2C Boot", - 0x00060004: "ECSPI Boot", - 0x00060005: "NOR Boot", - 0x00060006: "ONENAND Boot", - 0x00060007: "QSPI Boot", - 0x00061003: "Recovery Mode I2C", - 0x00061004: "Recovery Mode ECSPI", - 0x00061FFF: "Recovery Mode NONE", - 0x00062001: "MFG Mode USDHC", - 0x00070000: "Device INIT Call", - 0x000700F0: "Device INIT Pass", - 0x00070033: "Device INIT Fail", - 0x000800F0: "Device READ Data Pass", - 0x00080033: "Device READ Data Fail", - 0x000A00F0: "Plugin Image Pass", - 0x000A0033: "Plugin Image Fail", - 0x000C0000: "Serial Downloader Entry", - 0x000E0000: "ROMCP Patch" - } - - log_double_desc = { - 0x00080000: "Device READ Data Call", - 0x00090000: "HAB Authentication Status Code:", - 0x000A0000: "Plugin Image Call", - 0x000B0000: "Program Image Call", - 0x000D0000: "Serial Downloader Call" - } - - ret_msg = '' - log_loop = 0 - while log_loop < 64: - log_value = struct.unpack_from('I', data, log_loop * 4)[0] - - if log_value == 0x0: - break - - if log_value in log_single_desc: - ret_msg += " %02d. (0x%08X) -> %s\n" % (log_loop, log_value, log_single_desc[log_value]) - # TODO remove unused code> if log_value & 0xffff0000 == 0x00060000: boot_type = log_value & 0xff - elif log_value in log_double_desc: - ret_msg += " %02d. (0x%08X) -> %s\n" % (log_loop, log_value, log_double_desc[log_value]) - log_loop += 1 - log_data = struct.unpack_from('I', data, log_loop * 4)[0] - if log_value == 0x00090000: - ret_msg += " %02d. (0x%08X) -> HAB Status Code: 0x%02X %s\n" % \ - (log_loop, log_data, log_data & 0xff, HabStatusInfo.desc(log_data & 0xff)) - ret_msg += " HAB Reason Code: 0x%02X %s\n" % \ - ((log_data >> 8) & 0xff, HabErrorReason.desc((log_data >> 8) & 0xff)) - else: - ret_msg += " %02d. (0x%08X) -> Address: 0x%08X\n" % (log_loop, log_data, log_data) - else: - ret_msg += " Log Buffer Code not found\n" - - log_loop += 1 - - return ret_msg - - -######################################################################################################################## -# i.MX7 HAB Log Parser -######################################################################################################################## -def parse_mx7_log(data: bytes) -> str: - """Parses the HAB log data for i.MX7 devices. - - :param data: Data retrieved from the device - :return: String representation of the data - """ - log_all_desc = { - 0x10: "BOOTMODE - Internal Fuse", - 0x11: "BOOTMODE - Serial Bootloader ", - 0x12: "BOOTMODE - Internal/Override ", - 0x13: "BOOTMODE - Test Mode ", - 0x20: "Security Mode - Fab ", - 0x21: "Security Mode - Return ", - 0x22: "Security Mode - Open ", - 0x23: "Security Mode - Closed ", - 0x30: "DIR_BT_DIS = 0 ", - 0x31: "DIR_BT_DIS = 1 ", - 0x40: "BT_FUSE_SEL = 0 ", - 0x41: "BT_FUSE_SEL = 1 ", - 0x50: "Primary Image Selected ", - 0x51: "Secondary Image Selected ", - 0x60: "NAND Boot ", - 0x61: "USDHC Boot ", - 0x62: "SATA Boot ", - 0x63: "I2C Boot ", - 0x64: "ECSPI Boot ", - 0x65: "NOR Boot ", - 0x66: "ONENAND Boot ", - 0x67: "QSPI Boot ", - 0x70: "Recovery Mode I2C ", - 0x71: "Recovery Mode ECSPI ", - 0x72: "Recovery Mode NONE ", - 0x73: "MFG Mode USDHC ", - 0xB1: "Plugin Image Pass ", - 0xBF: "Plugin Image Fail ", - 0xD0: "Serial Downloader Entry ", - 0xE0: "ROMCP Patch ", - 0x80: "Device INIT Call ", - 0x81: "Device INIT Pass ", - 0x91: "Device READ Data Pass ", - 0xA0: "HAB Authentication Status Code: ", - 0x90: "Device READ Data Call ", - 0xB0: "Plugin Image Call ", - 0xC0: "Program Image Call ", - 0xD1: "Serial Downloader Call ", - 0x8F: "Device INIT Fail ", - 0x9F: "Device READ Data Fail " - } - - log_error_desc = { - 0x8F: "Device INIT Fail ", - 0x9F: "Device READ Data Fail ", - 0xBF: "Plugin Image Fail " - } - - log_tick_desc = { - 0x80: "Device INIT Call ", - 0x81: "Device INIT Pass ", - 0x8F: "Device INIT Fail ", - 0x91: "Device READ Data Pass ", - 0x9F: "Device READ Data Fail ", - 0xB0: "Plugin Image Call ", - 0xC0: "Program Image Call " - } - - log_address_desc = { - 0x90: "Device READ Data Call ", - 0xB0: "Plugin Image Call ", - 0xC0: "Program Image Call ", - 0xD1: "Serial Downloader Call " - } - - log_hab_desc = { - 0xA0: "HAB Authentication Status Code " - } - - ret_msg = '' - log_loop = 0 - while log_loop < 64: - log_value_full = struct.unpack_from('I', data, log_loop * 4)[0] - log_value = (log_value_full >> 24) & 0xff - - if log_value == 0x0: - break - - if log_value in log_all_desc: - ret_msg += " %02d. (0x%08X) -> %s\n" % (log_loop, log_value_full, log_all_desc[log_value]) - else: - ret_msg += " %02d. Log Buffer Code not found\n" - if log_value in log_address_desc: - log_loop += 1 - log_data = struct.unpack_from('I', data, log_loop * 4)[0] - ret_msg += " %02d. (0x%08X) -> Address: 0x%08X\n" % (log_loop, log_data, log_data) - if log_value in log_hab_desc: - log_loop += 1 - log_data = struct.unpack_from('I', data, log_loop * 4)[0] - ret_msg += " %02d. (0x%08X) -> HAB Status Code: 0x%02X %s\n" % \ - (log_loop, log_data, log_data & 0xff, HabStatusInfo.desc(log_data & 0xff)) - ret_msg += " HAB Reason Code: 0x%02X %s\n" % \ - ((log_data >> 8) & 0xff, HabErrorReason.desc((log_data >> 8) & 0xff)) - if log_value in log_error_desc: - ret_msg += " Error Code: 0x%06X\n" % (log_value_full & 0xffffff) - if log_value in log_tick_desc: - log_loop += 1 - log_data = struct.unpack_from('I', data, log_loop * 4)[0] - ret_msg += " %02d. (0x%08X) -> Tick: 0x%08X\n" % (log_loop, log_data, log_data) - - log_loop = log_loop + 1 - - return ret_msg - - -######################################################################################################################## -# i.MXRT HAB Log Parser -######################################################################################################################## -def parse_mxrt_log(_data: bytes) -> str: - """Parses the HAB log data for i.MX RT devices. - - Function is not implemented yet. - - :param _data: Data retrieved from the device - :return: String representation of the data - """ - raise NotImplementedError() diff --git a/spsdk/sdp/interfaces/uart.py b/spsdk/sdp/interfaces/uart.py index a110f392..d4421018 100644 --- a/spsdk/sdp/interfaces/uart.py +++ b/spsdk/sdp/interfaces/uart.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for serial communication with a target device using SDP protocol.""" @@ -79,7 +79,7 @@ def __init__(self, port: str = None, timeout: int = 5000, baudrate: int = 115200 :type timeout: int, optional """ super().__init__() - self.device = Serial(port=port, timeout=timeout // 1000, baudrate=baudrate) + self.device = Serial(port=port, timeout=timeout / 1000, baudrate=baudrate) self.expect_status = True def open(self) -> None: diff --git a/spsdk/sdp/interfaces/usb.py b/spsdk/sdp/interfaces/usb.py index dcc97b46..c7883fad 100644 --- a/spsdk/sdp/interfaces/usb.py +++ b/spsdk/sdp/interfaces/usb.py @@ -2,22 +2,15 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause - """Module for USB communication with a terget using SDP protocol.""" -# This violation is suppressed due to differences in Win/Linux implementation of USB -# pylint: disable=E1101 - -import collections import logging -import os import platform -from time import time -from typing import List, Tuple, Union +from typing import Sequence, Tuple, Union import hid @@ -29,10 +22,10 @@ logger = logging.getLogger('SDP:USB') +# import os # os.environ['PYUSB_DEBUG'] = 'debug' # os.environ['PYUSB_LOG_FILENAME'] = 'usb.log' - HID_REPORT = { # name | id | length 'CMD': (0x01, 1024, False), @@ -53,16 +46,12 @@ 'MX7SD': (0x15A2, 0x0076), 'MX7ULP': (0x1FC9, 0x0126), 'VYBRID': (0x15A2, 0x006A), - 'MXRT20': (0x1FC9, 0x0130), 'MXRT50': (0x1FC9, 0x0130), 'MXRT60': (0x1FC9, 0x0135), - 'MX8MQ': (0x1FC9, 0x012B), - 'MX8QXP-A0': (0x1FC9, 0x007D), 'MX8QM-A0': (0x1FC9, 0x0129), - 'MX8QXP': (0x1FC9, 0x012F), 'MX8QM': (0x1FC9, 0x0129), 'MX815': (0x1FC9, 0x013E), @@ -70,7 +59,7 @@ } -def scan_usb(device_name: str = None) -> List[Interface]: +def scan_usb(device_name: str = None) -> Sequence[Interface]: """Scan connected USB devices. Return a list of all devices found. :param device_name: see USBDeviceFilter classes constructor for usb_id specification @@ -85,7 +74,6 @@ def scan_usb(device_name: str = None) -> List[Interface]: ######################################################################################################################## class RawHid(Interface): """Base class for OS specific RAW HID Interface classes.""" - @property def name(self) -> str: """Get the name of the device. @@ -103,14 +91,14 @@ def is_opened(self) -> bool: :return: True if device is open, False othervise. """ - return self._opened + return self.device is not None and self._opened def __init__(self) -> None: """Initialize the USB interface object.""" self._opened = False self.vid = 0 self.pid = 0 - self.sn = "" + self.serial_number = "" self.vendor_name = "" self.product_name = "" self.interface_number = 0 @@ -150,7 +138,7 @@ def info(self) -> str: """Return information about the USB interface.""" return f"{self.product_name:s} (0x{self.vid:04X}, 0x{self.pid:04X})" - def conf(self, config: dict): + def conf(self, config: dict) -> None: """Set HID report data. :param config: parameters dictionary @@ -163,28 +151,38 @@ def open(self) -> None: """Open the interface.""" logger.debug("Open Interface") try: + assert self.device self.device.open_path(self.path) self.device.set_nonblocking(False) # self.device.read(1021, 1000) self._opened = True except OSError: - raise SdpConnectionError(f"Unable to open device VIP={self.vid} PID={self.pid} SN='{self.sn}'") + raise SdpConnectionError( + f"Unable to open device VIP={self.vid} PID={self.pid} SN='{self.serial_number}'" + ) def close(self) -> None: """Close the interface.""" logging.debug("Close Interface") try: + assert self.device self.device.close() self._opened = False except OSError: - raise SdpConnectionError(f"Unable to close device VIP={self.vid} PID={self.pid} SN='{self.sn}'") + raise SdpConnectionError( + f"Unable to close device VIP={self.vid} PID={self.pid} SN='{self.serial_number}'" + ) def write(self, packet: Union[CmdPacket, bytes]) -> None: """Write data on the OUT endpoint associated to the HID interfaces. :param packet: Data to send :raises ValueError: Raises an error if packet type is incorrect + :raises SdpConnectionError: Raises an error if device is openned for writing """ + if not self.is_opened: + raise SdpConnectionError(f"Device is openned for writing") + if isinstance(packet, CmdPacket): report_id, report_size, hid_ep1 = HID_REPORT['CMD'] data = packet.to_bytes() @@ -194,23 +192,30 @@ def write(self, packet: Union[CmdPacket, bytes]) -> None: else: raise ValueError("Packet has to be either 'CmdPacket' or 'bytes'") + assert self.device data_index = 0 while data_index < len(data): - raw_data, data_index = self._encode_report(report_id, report_size, data, data_index) + raw_data, data_index = self._encode_report(report_id, report_size, + data, data_index) self.device.write(raw_data) def read(self, length: int = None) -> CmdResponse: """Read data on the IN endpoint associated to the HID interface. :return: Return CmdResponse object. + :raises SdpConnectionError: Raises an error if device is openned for reading """ + if not self.is_opened: + raise SdpConnectionError(f"Device is openned for reading") + + assert self.device raw_data = self.device.read(1024, self.timeout) if raw_data[0] == 0x04 and platform.system() == "Linux": raw_data += self.device.read(1024, self.timeout) return self._decode_report(bytes(raw_data)) @staticmethod - def enumerate(usb_device_filter: USBDeviceFilter) -> List[Interface]: + def enumerate(usb_device_filter: USBDeviceFilter) -> Sequence[Interface]: """Get list of all connected devices which matches device_id. :param usb_device_filter: USBDeviceFilter object diff --git a/spsdk/utils/crypto/__init__.py b/spsdk/utils/crypto/__init__.py index 650d3228..85aaabf0 100644 --- a/spsdk/utils/crypto/__init__.py +++ b/spsdk/utils/crypto/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -9,7 +9,7 @@ from .common import matches_key_and_cert, crypto_backend, Counter, serialize_ecc_signature from .abstract import BackendClass -from .cert_blocks import CertBlockV2, CertBlockV3, CertBlock +from .cert_blocks import CertBlockV2, CertBlockV31, CertBlock from .certificate import Certificate from .otfad import KeyBlob, Otfad diff --git a/spsdk/utils/crypto/abstract.py b/spsdk/utils/crypto/abstract.py index 8044ea3f..f68937ae 100644 --- a/spsdk/utils/crypto/abstract.py +++ b/spsdk/utils/crypto/abstract.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -129,6 +129,26 @@ def rsa_public_key(self, modulus: int, exponent: int) -> Any: :param exponent: The RSA public key exponent """ + def ecc_sign(self, private_key: bytes, data: bytes, algorithm: str = None) -> bytes: + """Sign data using (EC)DSA. + + :param private_key: ECC private key + :param data: Data to sign + :param algorithm: Hash algorithm, if None the hash length is determined from ECC curve size + :return: Signature, r and s coordinates as bytes + """ + + def ecc_verify(self, public_key: bytes, signature: bytes, data: bytes, algorithm: str = None) -> bool: + """Verify (EC)DSA signature. + + :param public_key: ECC public key + :param signature: Signature to verify, r and s coordinates as bytes + :param data: Data to validate + :param algorithm: Hash algorithm, if None the hash length is determined from ECC curve size + :return: True if the signature is valid + :raises SPSDKError: Signature length is invalid + """ + ######################################################################################################################## # Abstract Class for Data Classes diff --git a/spsdk/utils/crypto/backend_internal.py b/spsdk/utils/crypto/backend_internal.py index dfdd639b..9eacf741 100644 --- a/spsdk/utils/crypto/backend_internal.py +++ b/spsdk/utils/crypto/backend_internal.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -13,10 +13,11 @@ # Used security modules from Crypto import Random, Hash from Crypto.Cipher import AES -from Crypto.Hash import HMAC -from Crypto.PublicKey import RSA -from Crypto.Signature import pkcs1_15 +from Crypto.Hash import HMAC, CMAC +from Crypto.PublicKey import RSA, ECC +from Crypto.Signature import pkcs1_15, DSS +from spsdk import SPSDKError # Abstract Class Interface from .abstract import BackendClass @@ -60,6 +61,17 @@ def _get_algorithm(name: str, data: bytes) -> Any: raise ValueError(f'Unsupported algorithm: Hash.{name}'.format(name=name.upper())) return algo_cls.new(data) # type: ignore # pylint: disable=not-callable + def cmac(self, data: bytes, key: bytes) -> bytes: # pylint: disable=no-self-use + """Generate Cipher-based Message Authentication Code via AES. + + :param data: Data to digest + :param key: AES Key for CMAC computation + :return: CMAC bytes + """ + cipher = CMAC.new(key=key, ciphermod=AES) + cipher.update(data) + return cipher.digest() + def hash(self, data: bytes, algorithm: str = 'sha256') -> bytes: """Return a HASH from input data with specified algorithm. @@ -143,6 +155,23 @@ def aes_key_unwrap(self, kek: bytes, wrapped_key: bytes) -> bytes: raise ValueError(f"Integrity Check Failed: {a:016X} (expected {iv:016X})") return b''.join(r[1:]) + def aes_cbc_encrypt(self, key: bytes, plain_data: bytes, iv: bytes = None) -> bytes: + """Encrypt plain data with AES in CBC mode. + + :param key: Key for encryption + :param plain_data: Data to encrypt + :param iv: Initial vector for encryption, defaults to None + :return: Encrypted data + :raises SPSDKError: Incorrect key or initialization vector size + """ + if len(key) not in AES.key_size: + raise SPSDKError(f"The key must be a valid AES key length: {', '.join([str(k) for k in AES.key_size])}") + init_vector = iv or bytes(AES.block_size) + if len(init_vector) != AES.block_size: + raise SPSDKError(f"The initial vector length must be {AES.block_size}") + cipher = AES.new(key, mode=AES.MODE_CBC, iv=init_vector) + return cipher.encrypt(plain_data) + def aes_ctr_encrypt(self, key: bytes, plain_data: bytes, nonce: bytes) -> bytes: """Encrypt plain data with AES in CTR mode. @@ -217,6 +246,41 @@ def rsa_public_key(self, modulus: int, exponent: int) -> RSA.RsaKey: """ return RSA.construct((modulus, exponent)) + def ecc_sign(self, private_key: Union[ECC.EccKey, bytes], data: bytes, algorithm: str = None) -> bytes: + """Sign data using (EC)DSA. + + :param private_key: ECC private key, either as EccKey or bytes + :param data: Data to sign + :param algorithm: Hash algorithm, if None the hash length is determined from ECC curve size + :return: Signature, r and s coordinates as bytes + """ + key = private_key if isinstance(private_key, ECC.EccKey) else ECC.import_key(private_key) + hash_name = algorithm or f'sha{key.pointQ.size_in_bits()}' + hasher = self._get_algorithm(name=hash_name, data=data) + signer = DSS.new(key, mode='deterministic-rfc6979') + return signer.sign(hasher) + + def ecc_verify(self, key: Union[ECC.EccKey, bytes], signature: bytes, data: bytes, algorithm: str = None) -> bool: + """Verify (EC)DSA signature. + + :param key: ECC private or public key, either as EccKey or bytes + :param signature: Signature to verify, r and s coordinates as bytes + :param data: Data to validate + :param algorithm: Hash algorithm, if None the hash length is determined from ECC curve size + :return: True if the signature is valid + :raises SPSDKError: Signature length is invalid + """ + key = key if isinstance(key, ECC.EccKey) else ECC.import_key(key) + hash_name = algorithm or f'sha{key.pointQ.size_in_bits()}' + coordinate_size = key.pointQ.size_in_bytes() + if len(signature) != 2 * coordinate_size: + raise SPSDKError(f'Invalid signature size: expected {2 * coordinate_size}, actual: {len(signature)}') + hasher = self._get_algorithm(name=hash_name, data=data) + try: + DSS.new(key, mode='deterministic-rfc6979').verify(hasher, signature) + return True + except ValueError: + return False ######################################################################################################################## # SPSDK Backend instance diff --git a/spsdk/utils/crypto/backend_openssl.py b/spsdk/utils/crypto/backend_openssl.py index eb61fe74..80a73526 100644 --- a/spsdk/utils/crypto/backend_openssl.py +++ b/spsdk/utils/crypto/backend_openssl.py @@ -1,12 +1,13 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """OpenSSL implementation for security backend.""" +import math # Used security modules from secrets import token_bytes from typing import Any, Union @@ -14,9 +15,10 @@ from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, hmac, keywrap, serialization -from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives.asymmetric import rsa, padding, ec, utils from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from spsdk import SPSDKError # Abstract Class Interface from .abstract import BackendClass @@ -126,19 +128,19 @@ def aes_ctr_decrypt(self, key: bytes, encrypted_data: bytes, nonce: bytes) -> by enc = cipher.decryptor() return enc.update(encrypted_data) + enc.finalize() - def rsa_sign(self, priv_key: Union[rsa.RSAPrivateKey, bytes], data: bytes, algorithm: str = 'sha256') -> bytes: + def rsa_sign(self, private_key: Union[rsa.RSAPrivateKey, bytes], data: bytes, algorithm: str = 'sha256') -> bytes: """Sign input data. - :param priv_key: The private key: either rsa.RSAPrivateKey or decrypted binary data in PEM format + :param private_key: The private key: either rsa.RSAPrivateKey or decrypted binary data in PEM format :param data: Input data :param algorithm: Used algorithm :return: Signed data :raise ValueError: if algorithm not found """ - if isinstance(priv_key, bytes): - priv_key = serialization.load_pem_private_key(priv_key, None, default_backend()) - assert isinstance(priv_key, rsa.RSAPrivateKey) - return priv_key.sign(data=data, padding=padding.PKCS1v15(), algorithm=self._get_algorithm(algorithm)) + if isinstance(private_key, bytes): + private_key = serialization.load_pem_private_key(private_key, None, default_backend()) + assert isinstance(private_key, rsa.RSAPrivateKey) + return private_key.sign(data=data, padding=padding.PKCS1v15(), algorithm=self._get_algorithm(algorithm)) def rsa_verify(self, pub_key_mod: int, pub_key_exp: int, signature: bytes, data: bytes, algorithm: str = 'sha256') -> bool: @@ -171,6 +173,61 @@ def rsa_public_key(self, modulus: int, exponent: int) -> rsa.RSAPublicKey: """ return rsa.RSAPublicNumbers(exponent, modulus).public_key(default_backend()) + def ecc_sign( + self, private_key: Union[ec.EllipticCurvePrivateKey, bytes], + data: bytes, algorithm: str = None + ) -> bytes: + """Sign data using (EC)DSA. + + :param private_key: ECC private key + :param data: Data to sign + :param algorithm: Hash algorithm, if None the hash length is determined from ECC curve size + :return: Signature, r and s coordinates as bytes + """ + if isinstance(private_key, bytes): + private_key = serialization.load_pem_private_key(private_key, None, default_backend()) + assert isinstance(private_key, ec.EllipticCurvePrivateKey) + hash_name = algorithm or f'sha{private_key.key_size}' + der_signature = private_key.sign( + data, signature_algorithm=ec.ECDSA(self._get_algorithm(hash_name)) + ) + # pylint: disable=invalid-name # we want to use established names + r, s = utils.decode_dss_signature(der_signature) + coordinate_size = math.ceil(private_key.key_size / 8) + r_bytes = r.to_bytes(coordinate_size, byteorder='big') + s_bytes = s.to_bytes(coordinate_size, byteorder='big') + return r_bytes + s_bytes + + + def ecc_verify( + self, public_key: Union[ec.EllipticCurvePublicKey, bytes], + signature: bytes, data: bytes, algorithm: str = None + ) -> bool: + """Verify (EC)DSA signature. + + :param public_key: ECC public key + :param signature: Signature to verify, r and s coordinates as bytes + :param data: Data to validate + :param algorithm: Hash algorithm, if None the hash length is determined from ECC curve size + :return: True if the signature is valid + :raises SPSDKError: Signature length is invalid + """ + if isinstance(public_key, bytes): + public_key = serialization.load_pem_public_key(public_key, default_backend()) + assert isinstance(public_key, ec.EllipticCurvePublicKey) + coordinate_size = math.ceil(public_key.key_size / 8) + if len(signature) != 2 * coordinate_size: + raise SPSDKError(f'Invalid signature size: expected {2 * coordinate_size}, actual: {len(signature)}') + hash_name = algorithm or f'sha{public_key.key_size}' + der_signature = utils.encode_dss_signature( + int.from_bytes(signature[:coordinate_size], byteorder='big'), + int.from_bytes(signature[coordinate_size:], byteorder='big') + ) + try: + public_key.verify(der_signature, data, ec.ECDSA(self._get_algorithm(hash_name))) + return True + except InvalidSignature: + return False ######################################################################################################################## # SPSDK OpenSSL Backend instance diff --git a/spsdk/utils/crypto/cert_blocks.py b/spsdk/utils/crypto/cert_blocks.py index dc0d39c3..7fda1d89 100644 --- a/spsdk/utils/crypto/cert_blocks.py +++ b/spsdk/utils/crypto/cert_blocks.py @@ -1,15 +1,16 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Module for handling Certificate block.""" import re +from spsdk.sbfile.sb31.commands import BaseCmd from struct import pack, unpack_from, calcsize -from typing import List, Optional, Union +from typing import List, Optional, Sequence, Union from Crypto.PublicKey import ECC @@ -365,14 +366,20 @@ def parse(cls, data: bytes, offset: int = 0) -> 'CertBlockV2': ######################################################################################################################## # Certificate Block Class for SB 3.1 ######################################################################################################################## -def get_ecc_key_bytes(key: bytes) -> bytes: - """Function to get ECC Key.""" - key_obj = ECC.import_key(key) - point_x = key_obj.pointQ.x.to_bytes() # type: ignore - point_y = key_obj.pointQ.y.to_bytes() # type: ignore +def get_ecc_key_bytes(key: ECC.EccKey) -> bytes: + """Function to get ECC Key pointQ as bytes.""" + point_x = key.pointQ.x.to_bytes() # type: ignore + point_y = key.pointQ.y.to_bytes() # type: ignore return point_x + point_y +def convert_to_ecc_key(key: Union[ECC.EccKey, bytes]) -> ECC.EccKey: + """Convert key into EccKey instance.""" + if isinstance(key, ECC.EccKey): + return key + return ECC.import_key(key) + + class CertificateBlockHeader(BaseClass): """Create Certificate block header.""" @@ -431,7 +438,7 @@ class RootKeyRecord(BaseClass): def __init__(self, ca_flag: bool, - root_certs: List[bytes], + root_certs: Union[Sequence[ECC.EccKey], Sequence[bytes]], used_root_cert: int = 0 ) -> None: """Constructor for Root key record. @@ -441,7 +448,10 @@ def __init__(self, :param used_root_cert: Used root cert number 0-3 """ self.ca_flag = ca_flag - self.root_certs = root_certs + self.root_certs = [ + convert_to_ecc_key(cert) + for cert in root_certs + ] self.used_root_cert = used_root_cert self.flags = self._calculate_flags() self.ctrk_hash_table = self._create_ctrk_hash_table() @@ -466,9 +476,9 @@ def _calculate_flags(self) -> int: if self.used_root_cert: flags |= (self.used_root_cert << 8) flags |= (len(self.root_certs) << 4) - if ECC.import_key(self.root_certs[0]).curve == "NIST P-256": + if self.root_certs[0].curve == "NIST P-256": flags |= (1 << 0) - if ECC.import_key(self.root_certs[0]).curve == "NIST P-384": + if self.root_certs[0].curve == "NIST P-384": flags |= (1 << 1) return flags @@ -484,7 +494,9 @@ def _create_ctrk_hash_table(self) -> bytes: if len(self.root_certs) > 1: for key in self.root_certs: data_to_hash = get_ecc_key_bytes(key) - ctrk_hash = internal_backend.hash(data=data_to_hash, algorithm="sha256") + ctrk_hash = internal_backend.hash( + data=data_to_hash, algorithm=f"sha{key.pointQ.size_in_bits()}" + ) ctrk_hash_table += ctrk_hash return ctrk_hash_table @@ -513,8 +525,8 @@ class IskCertificate(BaseClass): def __init__( self, constraints: int, - isk_private_key: crypto.EllipticCurvePrivateKeyWithSerialization, - isk_cert: ECC.EccKey, + isk_private_key: Union[ECC.EccKey, bytes], + isk_cert: Union[ECC.EccKey, bytes], user_data: bytes = None ) -> None: """Constructor for ISK certificate. @@ -526,25 +538,16 @@ def __init__( """ self.flags = 0 self.constraints = constraints - self.isk_private_key = isk_private_key - self.isk_cert = isk_cert + self.isk_private_key = convert_to_ecc_key(isk_private_key) + self.isk_cert = convert_to_ecc_key(isk_cert) self.user_data = user_data or bytes() self.signature = bytes() - self.coordinate_length = 0 - if self.isk_cert.curve == "NIST P-256": - self.coordinate_length = 32 - self.isk_public_key = ecc_public_numbers_to_bytes( - self.isk_private_key.public_key().public_numbers(), length=self.coordinate_length - ) - if self.isk_cert.curve == "NIST P-384": - self.coordinate_length = 48 - self.isk_public_key = ecc_public_numbers_to_bytes( - self.isk_private_key.public_key().public_numbers(), length=self.coordinate_length - ) + self.coordinate_length = self.isk_private_key.pointQ.size_in_bytes() + self.isk_public_key_data = get_ecc_key_bytes(self.isk_cert) self._calculate_flags() - self.signature_offset = calcsize("<2L") + len(self.user_data) - self.signature_offset += 64 if self.flags & 1 else 98 + self.signature_offset = calcsize("<3L") + len(self.user_data) + self.signature_offset += 2 * self.isk_cert.pointQ.size_in_bytes() self.expected_size = 4 + 4 + 4 + 2 * self.coordinate_length + len(self.user_data) + 2 * self.coordinate_length def info(self) -> str: @@ -567,25 +570,17 @@ def create_isk_signature(self, key_record_data: bytes) -> None: """Function to create ISK signature.""" # pylint: disable=invalid-name data = key_record_data + pack("<3L", self.signature_offset, self.constraints, self.flags) - data += self.isk_public_key + self.user_data - - assert isinstance(self.isk_private_key, crypto.EllipticCurvePrivateKeyWithSerialization) - signature = bytes() - if self.isk_cert.curve == "NIST P-256": - signature = self.isk_private_key.sign(data, crypto.ec.ECDSA(crypto.hashes.SHA256())) - if self.isk_cert.curve == "NIST P-384": - signature = self.isk_private_key.sign(data, crypto.ec.ECDSA(crypto.hashes.SHA384())) - - coordinate_length = 32 if self.isk_cert.curve == "NIST P-256" else 48 - self.signature = serialize_ecc_signature(signature, coordinate_length) + data += self.isk_public_key_data + self.user_data + self.signature = internal_backend.ecc_sign(self.isk_private_key, data) def export(self) -> bytes: """Export ISK certificate as bytes array.""" assert self.signature, "Signature is not set." data = bytes() data += pack("<3L", self.signature_offset, self.constraints, self.flags) + # data += pack("<2L", self.signature_offset) # if self.isk_public_key: - data += self.isk_public_key + data += self.isk_public_key_data if self.user_data: data += self.user_data data += self.signature @@ -602,18 +597,22 @@ def parse(cls, data: bytes, offset: int = 0) -> "IskCertificate": raise NotImplementedError("This operation is not supported.") -class CertBlockV3(CertBlock): - """Create Certificate block.""" +class CertBlockV31(CertBlock): + """Create Certificate block version 3.1.""" + + MAGIC = b"chdr" def __init__( - self, root_certs: List[bytes], ca_flag: bool, used_root_cert: int = 0, - constraints: int = 0, isk_private_key: crypto.EllipticCurvePrivateKeyWithSerialization = None, - isk_cert: ECC.EccKey = None, user_data: bytes = None + self, root_certs: Union[Sequence[ECC.EccKey], Sequence[bytes]], + ca_flag: bool, version: str = "2.1", + used_root_cert: int = 0, constraints: int = 0, + isk_private_key: Union[ECC.EccKey, bytes] = None, + isk_cert: Union[ECC.EccKey, bytes] = None, user_data: bytes = None ) -> None: """The Constructor for Certificate block.""" # workaround for base MasterBootImage self.signature_size = 0 - self.header = CertificateBlockHeader() + self.header = CertificateBlockHeader(version) self.root_key_record = RootKeyRecord( ca_flag=ca_flag, used_root_cert=used_root_cert, root_certs=root_certs ) @@ -627,6 +626,9 @@ def __init__( ) self.expected_size = self._calculate_expected_size() + def _set_ca_flag(self, value: bool) -> None: + self.root_key_record.ca_flag = value + def _calculate_expected_size(self) -> int: expected_size = self.header.SIZE expected_size += self.root_key_record.expected_size diff --git a/spsdk/utils/crypto/common.py b/spsdk/utils/crypto/common.py index f3d9dda5..f1981b11 100644 --- a/spsdk/utils/crypto/common.py +++ b/spsdk/utils/crypto/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -77,7 +77,7 @@ def swap16(x: int) -> int: # TODO refactor: this should not be part of the crypto module def pack_timestamp(value: datetime) -> int: - """Converts datetime to millisec since 1.1.2000. + """Converts datetime to millisecond since 1.1.2000. :param value: datetime to be converted :return: number of milliseconds since 1.1.2000 00:00:00; 64-bit integer @@ -141,7 +141,7 @@ def ecc_public_numbers_to_bytes(public_numbers: EllipticCurvePublicNumbers, leng """ x = public_numbers.x y = public_numbers.y - length = length or math.ceil(x.bit_length() // 8) + length = length or math.ceil(x.bit_length() / 8) x_bytes = x.to_bytes(length, 'big') y_bytes = y.to_bytes(length, 'big') return x_bytes + y_bytes diff --git a/spsdk/utils/devicedescription.py b/spsdk/utils/devicedescription.py new file mode 100644 index 00000000..e5260306 --- /dev/null +++ b/spsdk/utils/devicedescription.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Module for NXP device description classes.""" + +import platform +import re +from abc import ABC, abstractmethod +from typing import Dict, List, Tuple + +from spsdk.mboot.interfaces.usb import USB_DEVICES as MB_USB_DEVICES +from spsdk.sdp.interfaces.usb import USB_DEVICES as SDP_USB_DEVICES + + +class DeviceDescription(ABC): + """Base class for all logical devices. + + The intent is to have a generic container for providing info about devices + of any type. Thus the class is named as 'logical', because it doesn't + allow you to control the device in any way. + + This is just a base class and as such shouldn't be used. If you want to + use it, create your own class inheriting from this class and redefining + the methods listed in this class! + """ + def __repr__(self) -> str: + return f"{self.__class__.__name__}({vars(self)})" + + def __str__(self) -> str: + """Should return the string from info function.""" + return self.info() + + @abstractmethod # pragma: no cover + def info(self) -> str: + """Shall return a string describing the device, e.g. Name: ; ID: .""" + +class UartDeviceDescription(DeviceDescription): + """Simple container holding information about UART device. + + This container should be used instead of any USB API related objects, as + this container will be the same all the time compared to specific UART API + implementations. + """ + def __init__(self, name: str = None, dev_type: str = None) -> None: + """Construtor. + + The 'dev_type' can be in general any string identifying the device type. + + :name: COM port name + :dev_type: 'mboot device' or 'SDP device' + """ + self.name = name or "Unknown port" + self.dev_type = dev_type or "Unknown device type" + + def info(self) -> str: + """Returns a formatted device description string.""" + return "Port: {}\nType: {}".format(self.name, self.dev_type) + +class USBDeviceDescription(DeviceDescription): + """Simple container holding information about USB device. + + This container should be used instead of any USB API related objects, as + this container will be the same all the time compared to specific USB API + implementations. + """ + def __init__( + self, + vid: int, pid: int, path: str, + product_string: str, + manufacturer_string: str, + name: str) -> None: + """Constructor. + + :vid: Vendor ID + :pid: Product ID + :product_string: Product string + :manufacturer_string: Manufacturer string + :name: Name(s) of NXP devices as defined under spsdk.mboot.interfaces.usb or spsdk.sdp.interfaces.usb + + See :py:func:`get_usb_device_name` function to getg the name from + VID and PID. + See :py:func:`convert_usb_path` function to provide a proper path string. + """ + self.vid = vid + self.pid = pid + self.path = path + self.product_string = product_string + self.manufacturer_string = manufacturer_string + self.name = name + + def info(self) -> str: + """Returns a formatted device description string.""" + return "{} - {}\n".format(self.product_string, self.manufacturer_string) + \ + "Vendor ID: 0x{:04x}\n".format(self.vid) + \ + "Product ID: 0x{:04x}\n".format(self.pid) + \ + "Path: {}\n".format(self.path) + \ + "Name: {}".format(self.name) + +def get_usb_device_name( + vid: int, + pid: int, + device_names: Dict[str, Tuple[int, int]] = None) -> List[str]: + """Returns 'name' device identifier based on VID/PID, from dicts. + + Searches provided dictionary for device name based on VID/PID. If the dict + is None, the search happens on USB_DEVICES under mboot/interfaces/usb.py and + sdphost/interfaces/usb.py + + DESIGN REMARK: this function is not part of the USBLogicalDevice, as the + class intention is to be just a simple container. But to help the class + to get the required inputs, this helper method has been provided. + + :vid: Vendor ID we are interested in + :pid: Product ID we are interested in + :device_names: dict where str is device name, first int vid, second int pid + + :return: list containing device names with corresponding VID/PID + """ + nxp_device_names = [] + if device_names is None: + for dname, vid_pid in MB_USB_DEVICES.items(): + if vid_pid[0] == vid and vid_pid[1] == pid: + nxp_device_names.append(dname) + + for dname, vid_pid in SDP_USB_DEVICES.items(): + if vid_pid[0] == vid and vid_pid[1] == pid: + nxp_device_names.append(dname) + else: + for dname, vid_pid in device_names.items(): + if vid_pid[0] == vid and vid_pid[1] == pid: + nxp_device_names.append(dname) + + return nxp_device_names + + +def convert_usb_path(hid_api_usb_path: bytes) -> str: + """Converts the HID API path into string, which can be observed from OS. + + DESIGN REMARK: this function is not part of the USBLogicalDevice, as the + class intention is to be just a simple container. But to help the class + to get the required inputs, this helper method has been provided. Additionally, + this method relies on the fact that the provided path comes from the HID API. + This method will most probably fail or provide improper results in case + path from different USB API is provided. + + :hid_api_usb_path: USB device path from HID API + :return: HID API path converted for given platform + """ + if platform.system() == "Windows": + device_manager_path = hid_api_usb_path.decode("utf-8").upper() + device_manager_path = device_manager_path.replace('#', "\\") + result = re.search(r'\\\\\?\\(.+?)\\{', device_manager_path) + if result: + device_manager_path = result.group(1) + + return device_manager_path + + if platform.system() == "Linux": + # we expect the path in form of #, HID API returns + # :: + linux_path = hid_api_usb_path.decode("utf-8") + linux_path_parts = linux_path.split(':') + + if len(linux_path_parts) > 1: + linux_path = str.format("{}#{}", int(linux_path_parts[0], 16), int(linux_path_parts[1], 16)) + + return linux_path + + if platform.system() == "Darwin": + return hid_api_usb_path.decode("utf-8") + + return '' diff --git a/spsdk/utils/misc.py b/spsdk/utils/misc.py index 2376f8b6..92fab7e7 100644 --- a/spsdk/utils/misc.py +++ b/spsdk/utils/misc.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -9,8 +9,6 @@ import os from typing import Callable, Iterable, Iterator, Optional, TypeVar, List, Union -from .crypto.common import crypto_backend - # for generics T = TypeVar('T') # pylint: disable=invalid-name @@ -42,6 +40,8 @@ def align_block(data: bytes, alignment: int = 4, padding: int = 0) -> bytes: if not num_padding: return data if padding == -1: + # pylint: disable=import-outside-toplevel + from spsdk.utils.crypto.common import crypto_backend return data + crypto_backend().random_bytes(num_padding) return data + bytes([padding]) * num_padding @@ -92,6 +92,21 @@ def load_binary(*args: str) -> bytes: return data +def load_text(*path_segments: str) -> str: + """Loads binary file into bytes. + + :param path_segments: list that consists of: + - absolute path + - optional sub-directory (any number) + - file name including file extension + All the fields together represents absolute path to the file + :return: content of the binary file as bytes + """ + text = load_file(*path_segments, mode='r') + assert isinstance(text, str) + return text + + def load_file(*path_segments: str, mode: str = 'r') -> Union[str, bytes]: """Loads a file into bytes. @@ -104,6 +119,7 @@ def load_file(*path_segments: str, mode: str = 'r') -> Union[str, bytes]: :return: content of the binary file as bytes or str (based on mode) """ path = os.path.join(*path_segments) + path = path.replace("\\", "/") with open(path, mode) as f: return f.read() @@ -117,6 +133,7 @@ def write_file(data: Union[str, bytes], *path_segments: str, mode: str = 'w') -> :return: number of written elements """ path = os.path.join(*path_segments) + path = path.replace("\\", "/") with open(path, mode) as f: return f.write(data) diff --git a/spsdk/utils/nxpdevscan.py b/spsdk/utils/nxpdevscan.py new file mode 100644 index 00000000..194331fd --- /dev/null +++ b/spsdk/utils/nxpdevscan.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2020-2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +"""NXP USB Device Scanner API.""" + + +from typing import Dict, Sequence, Type + +import hid +from serial.tools.list_ports import comports + +from spsdk.mboot.interfaces.uart import scan_uart as mb_scan_uart +from spsdk.sdp import SDP +from spsdk.sdp.interfaces.uart import Uart as SDP_Uart + +from .devicedescription import (DeviceDescription, UartDeviceDescription, + USBDeviceDescription, convert_usb_path, + get_usb_device_name) + + +NXP_USB_DEVICE_VIDS = [ + 0x1FC9, + 0x15A2, +] + +def search_nxp_usb_devices(extend_vid_list: list = None) -> Sequence[DeviceDescription]: + """Searches all NXP USB devices based on their Vendor ID. + + :extend_vid_list: list of VIDs, to extend the default NXP VID list (int) + :return: list of dicts corresponding to NXP devices + """ + all_usb_devices = hid.enumerate() + nxp_usb_devices = [] + + search_vids = NXP_USB_DEVICE_VIDS + + if extend_vid_list is not None: + search_vids = list(set(search_vids) | set(extend_vid_list)) + + for usb_device in all_usb_devices: + for nxp_vid in search_vids: + if nxp_vid == usb_device.get("vendor_id"): + # We found our device, let's create container for it + vid = usb_device.get("vendor_id") + pid = usb_device.get("product_id") + path = convert_usb_path(usb_device.get("path")) + product_string = usb_device.get("product_string") + manufacturer_string = usb_device.get("manufacturer_string") + name = ", ".join(get_usb_device_name(vid, pid, None)) + usb_dev = USBDeviceDescription( + vid, pid, path, + product_string, + manufacturer_string, + name) + + nxp_usb_devices.append(usb_dev) + break + + return nxp_usb_devices + +def search_nxp_uart_devices() -> Sequence[DeviceDescription]: + """Returns a list of all NXP devices connected via UART. + + :retval: list of UartDeviceDescription devices from devicedescription module + """ + retval = [] + + # Get all available COM ports on target PC + ports = [port.device for port in comports()] + + # Iterate over every com port we have and check, whether mboot or sdp responds + for port in ports: + if mb_scan_uart(port=port, timeout=50): + uart_dev = UartDeviceDescription(name=port, dev_type="mboot device") + retval.append(uart_dev) + continue + + # Seems the port is not mboot, let's try SDP protocol + # The SDP protocol is on uart interface, so opening just the port is not + # sufficient, to say, that the interface is SDP compared to mboot, where + # ping command must be sent. + # So we create an SDP interface and try to read the status code. If + # we get a response, we are connecte to an SDP device. + sdp_com = SDP(SDP_Uart(port=port, timeout=50)) + if sdp_com.read_status() is not None: + uart_dev = UartDeviceDescription(name=port, dev_type="SDP device") + retval.append(uart_dev) + + return retval + +# This function has been left for potential future uses. At the moment it's +# not clear, how do we identify different devices with SDP protocol, as +# in the company, there are so many different MCU's from NXP and Freescale +# with different MCU identification options, if any... +# def parse_sim_sdid(sim_sdid: int) -> Dict: +# """Converts the content of SIM_SDID register into string. + +# :sim_sdid: the value of SIM_SDID register +# :retval: {"family_id": int, "subfamily_id": int, "series_id": int, "rev_id": int} +# """ +# retval = {} +# retval["family_id"] = (sim_sdid >> 28) & 0xF +# retval["subfamily_id"] = (sim_sdid >> 24) & 0xF +# retval["series_id"] = (sim_sdid >> 20) & 0xF +# retval["rev_id"] = (sim_sdid >> 12) & 0xF +# return retval diff --git a/spsdk/utils/registers.py b/spsdk/utils/registers.py new file mode 100644 index 00000000..81ab526f --- /dev/null +++ b/spsdk/utils/registers.py @@ -0,0 +1,632 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2020-2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Module to handle registers descriptions with support for XML files.""" + +import os +import logging +from math import log, ceil +from typing import List, Dict, Any +import json + +import xml.etree.ElementTree as ET +from xml.dom import minidom + +from spsdk.exceptions import SPSDKError + +logger = logging.getLogger(__name__) + +class RegisterNotFound(SPSDKError): + """Register has not found.""" + pass + +class BitfieldNotFound(SPSDKError): + """Bitfield has not found.""" + pass + +class EnumNotFound(SPSDKError): + """Enum has not found.""" + pass + +def value_to_bytes(value: Any, align_to_2n: bool = True) -> bytes: + """Function loads value from lot of formats. + + :param value: Input value. + :param align_to_2n: When is set, the function aligns legth of return array to 1,2,4,8,12 etc. + :return: Value in bytes. + :raise TypeError: Unsupported input type. + """ + def bytes_cnt(value: int, align_to_2n: bool = True) -> int: + val = 1 if value == 0 else int(log(value, 256)) + 1 + if align_to_2n and val > 2: + val = int(ceil(val / 4)) * 4 + + return val + + if isinstance(value, bytes): + return value + + if isinstance(value, bytearray): + return bytes(value) + + if isinstance(value, int): + return value.to_bytes(bytes_cnt(value, align_to_2n), "big") + + if isinstance(value, str): + if value.lower().find('0x') >= 0: + val = int(value, 16) + return val.to_bytes(bytes_cnt(val, align_to_2n), "big") + if value.lower().find('0b') >= 0: + val = int(value, 2) + return val.to_bytes(bytes_cnt(val, align_to_2n), "big") + if value.lower().find("b'") >= 0: + value = value.replace("b'", "0b") + val = int(value, 2) + return val.to_bytes(bytes_cnt(val, align_to_2n), "big") + if value.isdecimal(): + val = int(value) + return val.to_bytes(bytes_cnt(val, align_to_2n), "big") + + raise TypeError(f"Invalid input number type({type(value)})") + + +class RegsEnum(): + """Storage for register enumerations.""" + def __init__(self, name: str, value: Any, description: str, max_width: int = 0) -> None: + """Constructor of RegsEnum class. Used to store enumeration information of bitfield. + + :param name: Name of enumeration. + :param value: Value of enumeration. + :param description: Text description of enumeration. + :param max_width: Maximal width of enum value used to format output + """ + self.name = name or "N/A" + try: + self.value = value_to_bytes(value) + except TypeError: + self.value = b'' + self.description = description or "N/A" + self.max_width = max_width + + @classmethod + def from_xml_element(cls, xml_element: ET.Element, maxwidth: int = 0) -> 'RegsEnum': + """Initialization Enum by XML ET element. + + :param xml_element: Input XML subelement with enumeration data. + :param maxwidth: The maximal width of bitfield for this enum (used for formating). + :return: The instance of this class. + """ + name = xml_element.attrib["name"] if "name" in xml_element.attrib else "N/A" + try: + value = value_to_bytes(xml_element.attrib["value"] if "value" in xml_element.attrib else b'') + except TypeError: + value = b'' + descr = xml_element.attrib["description"] if "description" in xml_element.attrib else "N/A" + + return cls(name, value, descr, maxwidth) + + def get_value_int(self) -> int: + """Method returns Integer value of enum. + + :return: Integer value of Enum. + """ + return int.from_bytes(self.value, "big") + + def _get_value_str(self) -> str: + """Method returns formated value. + + :return: Formatted string with enum value. + """ + if self.value == b'': + return "N/A" + + val = self.get_value_int() + if self.max_width == 0: + return bin(val) + + return f"0b{val:0{self.max_width}b}" + + def add_et_subelement(self, parent: ET.Element) -> None: + """Creates the register XML structure in ElementTree. + + :param parent: The parent object of ElementTree. + """ + element = ET.SubElement(parent, "bit_field_value") + element.set("name", self.name) + element.set("value", self._get_value_str()) + element.set("description", self.description) + + + def __str__(self) -> str: + """Overrided 'ToString()' to print register. + + :return: Friendly string with enum information. + """ + output = "" + output += f"Name: {self.name}\n" + output += f"Value: {self._get_value_str()}\n" + output += f"Description: {self.description}\n" + + return output + +class RegsBitField(): + """Storage for register bitfields.""" + def __init__(self, + parent: "RegsRegister", + name: str, + offset: int, + width: int, + description: str = None, + reset_val: str = "N/A", + access: str = "RW") -> None: + """Constructor of RegsBitField class. Used to store bitfield information. + + :param parent: Parent register of bitfield. + :param name: Name of bitfield. + :param offset: Bit offset of bitfield. + :param width: Bit width of bitfield. + :param description: Text description of bitfield. + :param reset_val: Reset value of bitfield. + :param access: Access type of bitfield. + """ + self.parent = parent + self.name = name or "N/A" + self.offset = offset + self.width = width + self.description = description or "N/A" + self.reset_value = reset_val + self.access = access + self._enums: List[RegsEnum] = [] + + @classmethod + def from_xml_element(cls, xml_element: ET.Element, parent: 'RegsRegister') -> 'RegsBitField': + """Initialization register by XML ET element. + + :param xml_element: Input XML subelement with register data. + :param parent: Reference to parent RegsRegister object. + :return: The instance of this class. + """ + name = xml_element.attrib["name"] if "name" in xml_element.attrib else "N/A" + offset = int(xml_element.attrib["offset"], 16) if "offset" in xml_element.attrib else 0 + width = int(xml_element.attrib["width"]) if "width" in xml_element.attrib else 0 + descr = xml_element.attrib["description"] if "description" in xml_element.attrib else "N/A" + access = xml_element.attrib["access"] if "access" in xml_element.attrib else "N/A" + reset_value = xml_element.attrib["reset_value"] \ + if "reset_value" in xml_element.attrib else "N/A" + bitfield = cls(parent, + name, + offset, + width, + descr, + reset_value, + access) + + if xml_element.text: + xml_enums = xml_element.findall(f"bit_field_value") + for xml_enum in xml_enums: + bitfield.add_enum(RegsEnum.from_xml_element(xml_enum, width)) + return bitfield + + def has_enums(self) -> bool: + """Returns if the bitfileds has enums. + + :return: True is has enums, False otherwise. + """ + return len(self._enums) > 0 + + def get_enums(self) -> List[RegsEnum]: + """Returns bitfield enums. + + :return: List of bitfield enumeration values. + """ + return self._enums + + def add_enum(self, enum: RegsEnum) -> None: + """Add bitfield enum. + + :param enum: New enumeration value for bitfiled. + """ + self._enums.append(enum) + + def get_value(self) -> int: + """Returns integer value of the bitfield. + + :return: Current value of bitfield. + """ + reg_val = int.from_bytes(self.parent.get_value(), "big") + value = reg_val >> self.offset + mask = ((1< None: + """Updates the value of the bitfield. + + :param new_val: New value of bitfield. + :raise ValueError: The input value is out of range. + """ + if new_val > 1< None: + """Updates the value of the bitfield by its enum value. + + :param new_val: New enum value of bitfield. + """ + self.set_value(self.get_enum_constant(new_val)) + + def get_enum_value(self) -> Any: + """Returns enum value of the bitfield. + + :return: Current value of bitfield. + """ + value = self.get_value() + if len(self._enums) > 0: + for enum in self._enums: + if enum.get_value_int() == value: + return enum.name + return int(value) + + def get_enum_constant(self, enum_name: str) -> int: + """Returns constant representation of enum by its name. + + :return: Constant of enum. + :raises EnumNotFound: The enum has not been found. + """ + for enum in self._enums: + if enum.name == enum_name: + return enum.get_value_int() + + raise EnumNotFound("The enum for {enum_name} has not been found.") + + def get_enum_names(self) -> List[str]: + """Returns list of the enum strings. + + :return: List of enum names. + """ + return [x.name for x in self._enums] + + def add_et_subelement(self, parent: ET.Element) -> None: + """Creates the register XML structure in ElementTree. + + :param parent: The parent object of ElementTree. + """ + element = ET.SubElement(parent, "bit_field") + element.set("offset", hex(self.offset)) + element.set("width", str(self.width)) + element.set("name", self.name) + element.set("access", self.access) + element.set("reset_value", str(self.reset_value)) + element.set("description", self.description) + for enum in self._enums: + enum.add_et_subelement(element) + + def __str__(self) -> str: + """Overrided 'ToString()' to print register. + + :return: Friendly looking string that describes the bitfield. + """ + output = "" + output += f"Name: {self.name}\n" + output += f"Offset: {self.offset} bits\n" + output += f"Width: {self.width} bits\n" + output += f"Access: {self.access} bits\n" + output += f"Reset val:{self.reset_value}\n" + output += f"Description: \n {self.description}\n" + + i = 0 + for enum in self._enums: + output += f"Enum #{i}: \n" + str(enum) + i += 1 + + return output + +class RegsRegister(): + """Initialization register by input information.""" + def __init__(self, + name: str, + offset: int, + width: int, + description: str = None, + reverse: bool = False, + access: str = None) -> None: + """Constructor of RegsRegister class. Used to store register information. + + :param name: Name of register. + :param offset: Byte offset of register. + :param width: Bit width of register. + :param description: Text description of register. + :param reverse: Multi register value is stored in reverse order. + :param access: Access type of register. + """ + self.name = name + self.offset = offset + self.width = width + self.description = description or "N/A" + self.access = access or "N/A" + self.reverse = reverse + self._bitfields: List[RegsBitField] = [] + self.value = bytes([0]) + + @classmethod + def from_xml_element(cls, xml_element: ET.Element) -> 'RegsRegister': + """Initialization register by XML ET element. + + :param xml_element: Input XML subelement with register data. + :return: The instance of this class. + """ + name = xml_element.attrib["name"] if "name" in xml_element.attrib else "N/A" + offset = int(xml_element.attrib["offset"], 16) if "offset" in xml_element.attrib else 0 + width = int(xml_element.attrib["width"]) if "width" in xml_element.attrib else 0 + descr = xml_element.attrib["description"] if "description" in xml_element.attrib else "N/A" + reverse = (xml_element.attrib["reversed"] if "reversed" in xml_element.attrib else "False") == "True" + access = "RW" #TODO solve this + + reg = cls(name, offset, width, descr, reverse, access) + if xml_element.text: + xml_bitfields = xml_element.findall(f"bit_field") + for xml_bitfield in xml_bitfields: + reg.add_bitfield(RegsBitField.from_xml_element(xml_bitfield, reg)) + return reg + + def add_et_subelement(self, parent: ET.Element) -> None: + """Creates the register XML structure in ElementTree. + + :param parent: The parent object of ElementTree. + """ + element = ET.SubElement(parent, "register") + element.set("offset", hex(self.offset)) + element.set("width", str(self.width)) + element.set("name", self.name) + element.set("reversed", str(self.reverse)) + element.set("description", self.description) + for bitfield in self._bitfields: + bitfield.add_et_subelement(element) + + def set_value(self, val: Any) -> None: + """Set the new value of register.""" + try: + self.value = value_to_bytes(val) + except TypeError: + logger.error("Loaded invalid value {str(val)}") + self.value = b'' + + def get_value(self) -> bytes: + """Get the value of register.""" + return self.value + + def get_hex_value(self) -> str: + """Get the value of register in string hex format.""" + return "0x"+ self.value.hex().replace("'", "") + + def add_bitfield(self, bitfield: RegsBitField) -> None: + """Add register bitfield. + + :param bitfield: New bitfield value for register. + """ + self._bitfields.append(bitfield) + + def get_bitfields(self) -> List[RegsBitField]: + """Returns register bitfields. + + :return: Returns List of added register bitfields. + """ + return self._bitfields + + def find_bitfield(self, name: str) -> RegsBitField: + """Returns the instance of the bitfield by its name. + + :param name: The name of the bitfield. + :return: The bitfield instance. + :raises BitfieldNotFound: The register doesn't exists. + """ + for bitf in self._bitfields: + if name == bitf.name: + return bitf + + raise BitfieldNotFound(f" The {name} is not found in register {self.name}.") + + def __str__(self) -> str: + """Overrided 'ToString()' to print register. + + :return: Friendly looking string that describes the register. + """ + output = "" + output += f"Name: {self.name}\n" + # if isinstance(self.alias, str) and self.alias != "": + # output += f"Alias: {self.alias}\n" + # if isinstance(self.type, str) and self.type != "": + # output += f"Type: {self.type}\n" + output += f"Offset: 0x{self.offset:04X}\n" + output += f"Width: {self.width} bits\n" + output += f"Access: {self.access}\n" + output += f"Description: \n {self.description}\n" + + i = 0 + for bitfiled in self._bitfields: + output += f"Bitfield #{i}: \n" + str(bitfiled) + i += 1 + + return output + +class Registers(): + """SPSDK Class for registers handling.""" + def __init__(self, device_name: str) -> None: + """Initialization of Registr class.""" + self.registers: List[RegsRegister] = [] + self.dev_name = device_name + + def find_reg(self, name: str) -> RegsRegister: + """Returns the instance of the register by its name. + + :param name: The name of the register. + :return: The register instance. + :raises RegisterNotFound: The register doesn't exists. + """ + for reg in self.registers: + if name == reg.name: + return reg + + raise RegisterNotFound(f" The {name} is not found in loaded registers for {self.dev_name} device.") + + def add_register(self, reg: RegsRegister) -> None: + """Adds register into register list. + + :param reg: Register to add to the class. + :raise TypeError: Invalid type has been provided. + """ + if not isinstance(reg, RegsRegister): + raise TypeError("The 'reg' has invalid type.") + + if reg.name not in self.get_reg_names(): + self.registers.append(reg) + else: + logger.warning(f"Cannot add register with same name: {reg.name}.") + + def remove_register(self, reg: RegsRegister) -> None: + """Remove register from register list by its instance reference. + + :reg: Instance of register that should be removed. + :raise TypeError: Invalid type has been provided. + """ + if not isinstance(reg, RegsRegister): + raise TypeError("The 'reg' has invalid type.") + + self.registers.remove(reg) + + def remove_register_by_name(self, reg_names: List[str]) -> None: + """Removes register from register list by List of its names. + + :reg_names: List of names of registers that should be removed. + """ + for reg in self.registers: + if any(reg.name in name for name in reg_names): + self.registers.remove(reg) + + def get_reg_names(self) -> List[str]: + """Returns list of the register names. + + :return: List of register names. + """ + return [x.name for x in self.registers] + + def clear(self) -> None: + """Method clears the regs class.""" + self.registers.clear() + + def __str__(self) -> str: + """Overrided 'ToString()' to print register. + + :return: Friendly looking string that describes the registers. + """ + output = "" + output += "Device name: " + self.dev_name + "\n" + for reg in self.registers: + output += str(reg) + "\n" + + return output + + def write_xml(self, file_name: str) -> None: + """Write loaded register structures into XML file. + + :param file_name: The name of XML file that should be created. + """ + xml_root = ET.Element("regs") + for reg in self.registers: + reg.add_et_subelement(xml_root) + + with open(file_name, 'w', encoding="utf-8") as xml_file: + no_pretty_data = minidom.parseString(ET.tostring(xml_root, encoding="unicode", short_empty_elements=False)) + xml_file.write(no_pretty_data.toprettyxml()) + + # pylint: disable=no-self-use #It's better to have this function visually close to callies + def _filter_by_names(self, items: List[ET.Element], names: List[str]) -> List[ET.Element]: + """Filter out all items in the "items" tree,whose name starts with one of the strings in "names" list. + + :param items: Items to be filtered out. + :param names: Names to filter out. + :return: Filtered item elements list. + """ + return [item for item in items if not item.attrib["name"].startswith(tuple(names))] + +# pylint: disable=dangerous-default-value + def load_registers_from_xml(self, xml: str, filter_reg: List[str] = None) -> None: + """Function loads the registers from the given XML. + + :param xml: Input XML data in string format. + :param filter_reg: List of register names that should be filtered out. + """ + xml_elements = ET.parse(xml) + xml_registers = xml_elements.findall("register") + xml_registers = self._filter_by_names(xml_registers, filter_reg or []) + # Load all registers into the class + for xml_reg in xml_registers: + self.add_register(RegsRegister.from_xml_element(xml_reg)) + +class RegConfig(): + """Class that helps manage the registers configuration.""" + + def __init__(self, path: str): + """Register Configuration class consructor. + + :param path: The path to configuration JSON file. + """ + self.path = path + self.config = RegConfig.load_config(path) + + @classmethod + def load_config(cls, path: str) -> dict: + """Load config file.""" + with open(path) as config_file: + return json.load(config_file) + + @classmethod + def devices(cls, path: str) -> List[str]: + """Classmethod to get list of supppoted devices.""" + config = cls.load_config(path) + return list(config['devices'].keys()) + + def get_latest_revision(self, device: str) -> str: + """Get latest revision for device.""" + return self.config["devices"][device]["latest"] + + def get_devices(self) -> List[str]: + """Get list of supported devices.""" + return list(self.config["devices"].keys()) + + def get_revisions(self, device: str) -> List[str]: + """Get list of revisions for given device.""" + return list(self.config["devices"][device]["revisions"].keys()) + + def get_address(self, device: str, remove_underscore: bool = False) -> str: + """Get the area address in chip memory.""" + address = self.config["devices"][device]["address"] + if remove_underscore: + return address.replace("_", "") + return address + + def get_data_file(self, device: str, revision: str) -> str: + """Return the full path to data file (xml).""" + file_name = self.config["devices"][device]["revisions"][revision] + dir_path = os.path.dirname(os.path.abspath(self.path)) + return os.path.join(dir_path, file_name) + + def get_antipole_regs(self, device: str) -> Dict[str, str]: + """Return the list of inverted registers.""" + inverted_regs = self.config["devices"][device]["inverted_regs"] + return inverted_regs + + def get_computed_fields(self, device: str) -> Dict[str, Dict[str, str]]: + """Return the list of computed fileds (not used in config YML files).""" + inverted_regs = self.config["devices"][device]["computed_fields"] + return inverted_regs diff --git a/spsdk/utils/serial_proxy.py b/spsdk/utils/serial_proxy.py index bc3f32db..200aa962 100644 --- a/spsdk/utils/serial_proxy.py +++ b/spsdk/utils/serial_proxy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -87,3 +87,42 @@ def reset_output_buffer(self) -> None: def flush(self) -> None: """Simulates flushing input buffer.""" + + +class SimpleReadSerialProxy(SerialProxy): + """SimpleReadSerialProxy is used to simulate communication with serial device. + + It simplifies reading method. + @patch(.Serial, SerialProxy.init_proxy(pre_recorded_responses)) + """ + + FULL_BUFFER = bytes() + + @classmethod + def init_data_proxy(cls, data: bytes) -> 'Type[SimpleReadSerialProxy]': + """Initialized response dictionary of write and read bytes. + + :param data: Dictionary of write and read bytes + :return: SerialProxy class with configured data + """ + cls.FULL_BUFFER = data + return cls + + def __init__(self, port: str, timeout: int, baudrate: int): + """Basic initialization for serial.Serial class. + + __init__ signature must accommodate instantiation of serial.Serial + + :param port: Serial port name + :param timeout: timeout (does nothing) + :param baudrate: Serial port speed (does nothing) + """ + super().__init__(port=port, timeout=timeout, baudrate=baudrate) + self.buffer = self.FULL_BUFFER + + def write(self, data: bytes) -> None: + """Simulates a write method, but it does nothing. + + :param data: Bytes to write, key in responses + """ + pass diff --git a/spsdk/utils/usbfilter.py b/spsdk/utils/usbfilter.py index d4970677..078da052 100644 --- a/spsdk/utils/usbfilter.py +++ b/spsdk/utils/usbfilter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -27,7 +27,8 @@ class USBDeviceFilter: The decimal number is restrictred only to have 1 - 5 digits, e.g. "65535" It's allowed to set the USB filter ID to decimal number "99999", however, as the USB VID number is four-byte hex number (max value is 65535), this will - lead to zero results. + lead to zero results. Leading zeros are not allowed e.g. 0001. This will + result as invalid match. vid/pid - string of vendor ID & product ID separated by ':' or ',' Same rules apply to the number format as in VID case, except, that the @@ -43,7 +44,16 @@ class USBDeviceFilter: see instance ID in device manager under Windows OS. Linux specific: - instance ID - TODO investigate the instance ID.... + USB device path - HID API returns path in following form: + '0003:0002:00' + + The first number represents the Bus, the second Device and the third interface. The Bus:Device + number is unique so interface is not necessary and Bus:Device should be sufficient. + + The Bus:Device can be observed using 'lsusb' command. The interface can be observed using + 'lsusb -t'. lsusb returns the Bus and Device as a 3-digit number. + It has been agreed, that the expected input is: + #, e.g. 3#11 Mac specific: USB device path - HID API returns path in roughly following form: @@ -58,15 +68,17 @@ class USBDeviceFilter: So the 'usb_id' name should be 'SE Blank RT Family @14200000' and the filter should be able to filter out such device. """ - __vid_regex = "(0[xX][0-9a-fA-F]{1,4}|[0-9]{1,5})" - __vid_pid_regex = "0[xX][0-9a-fA-F]{1,4}(,|:)0[xX][0-9a-fA-F]{1,4}|[0-9]{1,5}(,|:)[0-9]{1,5}" + # match anything starting with 0x or 0X followed by 0-9 or a-f or + # match either 0 or decimal number not starting with zero + __vid_regex = "0[xX][0-9a-fA-F]{1,4}|0|[1-9][0-9]{0,4}" + # same as above, except it's a combination of two numbers separated by : or , + __vid_pid_regex = "0[xX][0-9a-fA-F]{1,4}(,|:)0[xX][0-9a-fA-F]{1,4}|(0|[1-9][0-9]{0,4})(,|:)(0|[1-9][0-9]{0,4})" def __init__(self, usb_id: str = None, nxp_device_names: Dict[str, Tuple[int, int]] = None): """Initialize the USB Device Filtering. :param usb_id: usb_id string - :param nxp_device_names: Dictionary holding NXP device vid/pid - {"device_name": [vid(int), pid(int)]} + :param nxp_device_names: Dictionary holding NXP device vid/pid {"device_name": [vid(int), pid(int)]} """ self.usb_id = usb_id self.nxp_device_names = nxp_device_names or {} @@ -107,6 +119,13 @@ def compare(self, usb_device_object: Any) -> bool: #usb_path = usb_path.upper() usb_path = usb_path.replace('#', '\\') + if platform.system() == 'Linux': + # The user input is expected in form of #. So we + # convert the path returned by HID API into this form so we can + # compare it + nums = usb_path.split(":") + usb_path = str.format("{}#{}", int(nums[0], 16), int(nums[1], 16)) + # Determine, whether given device matches one of the expected criterion if self.usb_id is None: return True diff --git a/tests/conftest.py b/tests/conftest.py index 41e69fa5..60785eb1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,18 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause + +import logging +from os import path +import pytest + + +@pytest.fixture(scope="module") +def data_dir(request): + logging.debug(f"data_dir for module: {request.fspath}") + data_path = path.join(path.dirname(request.fspath), 'data') + logging.debug(f"data_dir: {data_path}") + return data_path diff --git a/tests/crypto/conftest.py b/tests/crypto/conftest.py deleted file mode 100644 index 503533f4..00000000 --- a/tests/crypto/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -from os import path -import pytest - - -@pytest.fixture -def data_dir(): - return path.join(path.dirname(__file__), 'data') diff --git a/tests/crypto/data/certgen_config.json b/tests/crypto/data/certgen_config.json new file mode 100644 index 00000000..05672756 --- /dev/null +++ b/tests/crypto/data/certgen_config.json @@ -0,0 +1,29 @@ +{ + "issuer": { + "COMMON_NAME": "ONE", + "COUNTRY_NAME": "PL", + "LOCALITY_NAME": "KR", + "STATE_OR_PROVINCE_NAME": "MLPK", + "STREET_ADDRESS": "BOT", + "ORGANIZATION_NAME": "PKO" + }, + "subject": { + "COMMON_NAME": "TWO", + "COUNTRY_NAME": "PL", + "LOCALITY_NAME": "LIMA", + "STATE_OR_PROVINCE_NAME": "MLPK", + "STREET_ADDRESS": "STOW", + "ORGANIZATION_NAME": "IBM", + "POSTAL_CODE": "31503" + }, + "issuer_private_key": "issuer_privatekey_rsa2048.pem", + "subject_public_key": "subject_publickey_rsa2048.pem", + "serial_number": 777, + "duration": 1234, + "extensions": { + "BASIC_CONSTRAINTS": { + "ca": true, + "path_length": 5 + } + } +} diff --git a/tests/crypto/data/issuer_privatekey_rsa2048.pem b/tests/crypto/data/issuer_privatekey_rsa2048.pem new file mode 100644 index 00000000..ddb8975a --- /dev/null +++ b/tests/crypto/data/issuer_privatekey_rsa2048.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCaPgYpn6pVYXej +Zpf1nVfQhlCtkdTwJ5jatsMQyMdOLlggW2TBbl3cHJsvuz25V0J4hEHYNHv7QtrZ +oChDA4fWaVU0m6wnNXrhjHXFbR3XTpVhVMMIMSlftrmmxSY61zsF4jVDgRNow7A9 +dghnX6GlZx67wlFbsyOKXjI+D582AqV2we9S0l1kmcvBWsKbohIiCQbCSWEwC7V9 +p/EWd3DxXN2/Ra4pRMorWmAwrjKZsH5BRxMEZ3kmIr49IqngjMS3OEWQz1R9UTLj +fuhEg3sT3mrZXNSXFGeGUQVz7pKdM9YeD/iBSci5TBM2ERR5kFUvuSsZLo1fvHSD +auFxGsvBAgMBAAECggEAPNwKTHQunn/V2AiMHyLVWncU8FehYJ0PKAWDFGVUltmc +di/5ooUszcQPy5ai9TWkCa+N14RmAy9O8R3LpR2IhKaNMzPViqcEdGias6qpYqIk +OQJb7iiPva94s4AI5KC2Lk0iXR9aDDVine2rxTYrrvrwWn+C4J7fgCJIysoZNRLb +Yvv7j6c4RwE1iaWWKKPioDA///gp00Wd0rMKaHhPdL1zTJsluwkn2FODVmLOlKrr +tEhB9xhkq/+VDx8/vcZDlP3kJR2scJfm9yK0gJs0ENGUWiDbt69A5rLZTKxGpqHA +ux6Kh8g4XWD4e2LzRYWZsptLov+kXhtkBUpt6bjC/QKBgQDLIBrRQtjOZtogmNBL +I4bNWdlHBUlkhtC4uDz0tWctlh0bWR/qzIObdR7mEJWO1Gjt7zGIrWU4pMps9LKm +YYQBLK7Xck930FuNlw6k1R5EfEZn/hDfd6XxtntdP3j95RU8I9yhPtirQU9L7lsz +7bcUUipTtbizVUdvD5ppFw47GwKBgQDCZHCLSnwtgVes31mh94ludaccCEbXl3xL +sxChAiqbeXRTv3gn+4T+G9c8DeswnGh1DfGkIB7a3BWcn6l5Q7beaqypjsZ96+3W +zqyYvS5rvoN6KZo1FxYRkQhQhtmYiBTdyFom1hIdYjHNqXeUsGPmAzeP2/fm/gkD +RIc6pQEGUwKBgEq20mZqeijheBZJX0RkGu9pvxv6e9Z4KEnpqrIwD3WbI7WtgKny +8+24lNb2/qv8wWPTeZrWfMbrBnIxR40fjViJQP6KU2S0xRg9metTYMC8HYGe2EH6 +VpWkE5hnAnARVVA7uKmu3i+P5ET/tZhSNedJmbwUuY1tLstRJ/DDxD5FAoGAc/t1 +YsY4PPZVF2HTmt2UdAFiiUku62cI1CmsnEQso8LdczJkQIbDHNIDo2Myljhb4qtF +J4W6ox55Do/8j8ulCiNfjXMQXwUrTOTW+fK2BbcPvw2fYlm54xVDfrhWj7lxqbnm +gBFtkXaK/IhGPsLXd98ngX/e80VSn0592LGmYN0CgYBrUPrGuHuHJicdjAb6PA9T +3ybOc24uaUUt1xd3ciP/FGN7DMu6Y/Yj4O4dniQkhMGBFiH2zMEeQZqd7DP1aylI +E/dzrHnYe0Qb8uKTEioelaGhl/kj8UxcGMwIucXGWmREDk6J5GBav7cQQ1UUn8A6 +nvoKoNSg2iPUPaWd9FzXyw== +-----END PRIVATE KEY----- diff --git a/tests/crypto/data/subject_publickey_rsa2048.pem b/tests/crypto/data/subject_publickey_rsa2048.pem new file mode 100644 index 00000000..8ccc9beb --- /dev/null +++ b/tests/crypto/data/subject_publickey_rsa2048.pem @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEApMvPV63L7gq9iISoEQgjiHaaDXV0iGj9atNgySjwWhMJc5qkoymS +ym2HP3ISbuMQL3aaP3e4uNuHn2gwEwzCzqQ9/RB3FX4f/gEtnrZ5heDWL5gbwMYp +pd+CQs6+6o/bhqiCFGvCy2lssD0056Dq7DiWbSPralke9Ktg8axDZME3yY5Ye26q +CVReAayqK/592wr6Mh5XbJg8OwJEmmdDG1ATUpCnxI1e4BWFq0bPBIVJd1uDHy0V +IKY2/ZQlb2RidTvrbNynl97Htu5oWJBUtPHKCKhhY2c/JA6IH15vXLh3lFMErZGk +OZOiFLK/8e+dFoo20quOddO3Gqw/0gsXywIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tests/crypto/test_certgen.py b/tests/crypto/test_certgen.py index 217b5f7e..29c46b18 100644 --- a/tests/crypto/test_certgen.py +++ b/tests/crypto/test_certgen.py @@ -1,11 +1,12 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """ Tests for certificate management (generating certificate, CSR, validating certificate, chains) """ +import os from os import path from typing import List @@ -13,23 +14,16 @@ from spsdk.crypto import generate_rsa_private_key, generate_rsa_public_key, save_rsa_private_key, \ save_rsa_public_key, validate_ca_flag_in_cert_chain, Certificate, Encoding -from spsdk.crypto import _get_encoding_type, load_private_key, load_certificate, load_public_key +from spsdk.crypto import _get_encoding_type, load_private_key, load_certificate, load_public_key, ExtensionOID, NameOID from spsdk.crypto import validate_certificate_chain, validate_certificate, is_ca_flag_set -from spsdk.crypto import generate_certificate, save_crypto_item, x509 +from spsdk.crypto import generate_certificate, save_crypto_item +from spsdk.crypto.certificate_management import generate_name_struct +from spsdk.utils.misc import use_working_directory -def gen_name_struct(name: str) -> x509.Name: - """Set the issuer/subject distinguished name. +from click.testing import CliRunner - :param name: name of issuer/subject - :return: ordered list of attributes of certificate - """ - return x509.Name([ - x509.NameAttribute(x509.oid.NameOID.COUNTRY_NAME, "CZ"), - x509.NameAttribute(x509.oid.NameOID.STATE_OR_PROVINCE_NAME, "RpR"), - x509.NameAttribute(x509.oid.NameOID.LOCALITY_NAME, "1maje"), - x509.NameAttribute(x509.oid.NameOID.ORGANIZATION_NAME, name) - ]) +from spsdk.apps.nxpcertgen import main def get_certificate(data_dir, cert_file_name: str) -> Certificate: @@ -190,7 +184,7 @@ def test_certificate_generation(tmpdir): assert path.isfile(path.join(tmpdir, "ca_private_key.pem")) assert path.isfile(path.join(tmpdir, "ca_pub_key.pem")) - subject = issuer = gen_name_struct("highest") + subject = issuer = generate_name_struct("highest", "CZ") ca_cert = generate_certificate(subject, issuer, ca_pub_key, ca_priv_key, if_ca=True) save_crypto_item(ca_cert, path.join(tmpdir, "ca_cert.pem")) assert path.isfile(path.join(tmpdir, "ca_cert.pem")) @@ -201,7 +195,24 @@ def test_certificate_generation(tmpdir): srk_pub_key = generate_rsa_public_key(srk_priv_key) save_rsa_public_key(srk_pub_key, path.join(tmpdir, "srk_pub_key.pem")) assert path.isfile(path.join(tmpdir, "srk_pub_key.pem")) - srk_subject = gen_name_struct('srk') + srk_subject = generate_name_struct("srk", "UK") srk_cert = generate_certificate(srk_subject, issuer, srk_pub_key, ca_priv_key, if_ca=False) save_crypto_item(srk_cert, path.join(tmpdir, "srk1.pem")) assert path.isfile(path.join(tmpdir, "srk1.pem")) + + +def test_certificate_generation_cli(tmpdir, data_dir): + with use_working_directory(data_dir): + cert_path = os.path.join(tmpdir, "cert.crt") + cmd = f'-j {os.path.join(data_dir, "certgen_config.json")} -c {cert_path}' + runner = CliRunner() + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + assert os.path.isfile(cert_path) + + generated_cert = load_certificate(cert_path) + assert isinstance(generated_cert, Certificate) + assert generated_cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME).pop(0).value == 'ONE' + assert generated_cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME).pop(0).value == 'TWO' + assert generated_cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS).value.ca + assert generated_cert.serial_number == 777 diff --git a/tests/crypto/test_sign_provider.py b/tests/crypto/test_sign_provider.py index b83940c5..280d66b1 100644 --- a/tests/crypto/test_sign_provider.py +++ b/tests/crypto/test_sign_provider.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Tests for Signature Provider interface.""" @@ -15,10 +15,10 @@ def test_types(): assert types == ['file'] class TestSP(SignatureProvider): - sp_type = 'test' + sp_type = 'test-typesp-test' types = SignatureProvider.get_types() - assert types == ['file', 'test'] + assert types == ['file', 'test-typesp-test'] def test_invalid_sp_type(): diff --git a/tests/dat/conftest.py b/tests/dat/conftest.py deleted file mode 100644 index 503533f4..00000000 --- a/tests/dat/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -from os import path -import pytest - - -@pytest.fixture -def data_dir(): - return path.join(path.dirname(__file__), 'data') diff --git a/tests/dat/data/signature_provider.py b/tests/dat/data/signature_provider.py index 11e1c111..c0d5d1a1 100644 --- a/tests/dat/data/signature_provider.py +++ b/tests/dat/data/signature_provider.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -15,7 +15,7 @@ def __init__(self, param: str) -> None: def info(self) -> str: msg = "Test Signature provider" - msg += f'param: {param}' + msg += f'param: {self.param}' def sign(self, data: bytes) -> bytes: return b'x' * self.param diff --git a/tests/dat/test_nxpdebugmbox.py b/tests/dat/test_nxpdebugmbox.py new file mode 100644 index 00000000..26a33238 --- /dev/null +++ b/tests/dat/test_nxpdebugmbox.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +""" Tests for nxpkeygen utility.""" + +from click.testing import CliRunner + +from spsdk.apps.nxpdebugmbox import main +from tests.debuggers.debug_probe_virtual import DebugProbeVirtual + +def test_command_line_interface_main(): + """Test for main menu options.""" + runner = CliRunner() + result = runner.invoke(main, ['--help']) + assert result.exit_code == 0 + + assert 'main [OPTIONS] COMMAND [ARGS]' in result.output + assert 'NXP Debug Mailbox Tool.' in result.output + assert '-i, --interface TEXT' in result.output + assert '-p, --protocol VERSION' in result.output + assert '-d, --debug LEVEL' in result.output + assert '-s, --serial-no TEXT' in result.output + assert 't, --timing FLOAT' in result.output + assert '-n, --no-reset' in result.output + assert '-o, --debug-probe-option TEXT' in result.output + assert '-v, --version' in result.output + assert '--help' in result.output + assert 'auth Perform the Debug Authentication.' in result.output + assert 'erase Erase Flash.' in result.output + assert 'exit Exit DebugMailBox.' in result.output + assert 'famode Set Fault Analysis Mode.' in result.output + assert 'ispmode Enter ISP Mode.' in result.output + assert 'start Start DebugMailBox.' in result.output + +def test_command_line_interface_auth(): + """Test for auth menu options.""" + runner = CliRunner() + cmd = f'auth --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'auth [OPTIONS]' in result.output + assert 'Perform the Debug Authentication.' in result.output + assert '-b, --beacon INTEGER Authentication beacon' in result.output + assert '-c, --certificate TEXT Path to Debug Credentials.' in result.output + assert '-k, --key TEXT Path to DCK private key.' in result.output + assert '-f, --force' in result.output + assert '--help Show this message and exit.' in result.output + +def test_command_line_interface_erase(): + """Test for erase menu options.""" + runner = CliRunner() + cmd = f'erase --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'erase [OPTIONS]' in result.output + assert 'Erase Flash.' in result.output + assert '--help Show this message and exit.' in result.output + +def test_command_line_interface_exit(): + """Test for exit menu options.""" + runner = CliRunner() + cmd = f'exit --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'exit [OPTIONS]' in result.output + assert 'Exit DebugMailBox.' in result.output + assert '--help Show this message and exit.' in result.output + +def test_command_line_interface_famode(): + """Test for famode menu options.""" + runner = CliRunner() + cmd = f'famode --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'famode [OPTIONS]' in result.output + assert 'Set Fault Analysis Mode.' in result.output + assert '--help Show this message and exit.' in result.output + +def test_command_line_interface_ispmode(): + """Test for ispmode menu options.""" + runner = CliRunner() + cmd = f'ispmode --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'ispmode [OPTIONS]' in result.output + assert 'Enter ISP Mode.' in result.output + assert '-m, --mode INTEGER [required]' in result.output + assert '--help Show this message and exit.' in result.output + +def test_command_line_interface_start(): + """Test for start menu options.""" + runner = CliRunner() + cmd = f'start --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'start [OPTIONS]' in result.output + assert 'Start DebugMailBox.' in result.output + assert '--help Show this message and exit.' in result.output + + +def test_nxpdebugmbox_invalid_probe_user_param(): + """Test for Invalid debug probe user params.""" + runner = CliRunner() + cmd = f'-o user_par -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} -d debug start' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 1 + +def test_nxpdebugmbox_invalid_probe(): + """Test for Invalid debug probe.""" + runner = CliRunner() + cmd = f'-i virtual -d debug start' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + assert "There is no any debug probe connected in system!" in result.output + +def test_nxpdebugmbox_valid_probe_user_param(): + """Test for Invalid debug probe user params.""" + runner = CliRunner() + cmd = f'-o user_par=1 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} -d debug start' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_nxpdebugmbox_start_exe(): + """Test for start command of nxp debug mailbox.""" + runner = CliRunner() + cmd = f'-i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} -d debug start' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_nxpdebugmbox_exit_exe(): + """Test for exit command of nxp debug mailbox.""" + runner = CliRunner() + cmd = f'-i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} -d debug exit' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_nxpdebugmbox_ispmode_exe(): + """Test for ispmode command of nxp debug mailbox.""" + runner = CliRunner() + cmd = f'-i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} -d debug ispmode -m 0' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_nxpdebugmbox_famode_exe(): + """Test for famode command of nxp debug mailbox.""" + runner = CliRunner() + cmd = f'-i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} -d debug famode' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_nxpdebugmbox_erase_exe(): + """Test for erase command of nxp debug mailbox.""" + runner = CliRunner() + cmd = f'-i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} -d debug erase' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 diff --git a/tests/debuggers/conftest.py b/tests/debuggers/conftest.py new file mode 100644 index 00000000..45b8d07c --- /dev/null +++ b/tests/debuggers/conftest.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2020-2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +from spsdk.debuggers.utils import PROBES +from tests.debuggers.debug_probe_virtual import DebugProbeVirtual + +# Extend standard list of debug probes by virtual to allow unit testing +PROBES['virtual'] = DebugProbeVirtual diff --git a/tests/debuggers/debug_probe_virtual.py b/tests/debuggers/debug_probe_virtual.py new file mode 100644 index 00000000..a57fa441 --- /dev/null +++ b/tests/debuggers/debug_probe_virtual.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Module for DebugMailbox Virtual Debug probes support used for product testing.""" + +from json.decoder import JSONDecodeError +import logging +import json +from typing import Dict, Any + +from spsdk.debuggers.debug_probe import (DebugProbe, + DebugProbeTransferError, + DebugProbeNotOpenError, + DebugProbeError, + DebugProbeMemoryInterfaceNotEnabled) + +logger = logging.getLogger(__name__) + +def set_logger(level: int) -> None: + """Sets the log level for this module. + + param level: Requested level. + """ + logger.setLevel(level) + +set_logger(logging.ERROR) + + +class DebugProbeVirtual(DebugProbe): + """Class to define Virtual package interface for NXP SPSDK.""" + + UNIQUE_SERIAL = "Virtual_DebugProbe_SPSDK" + + def __init__(self, hardware_id: str, user_params: Dict = None) -> None: + """The Virtual class initialization. + + The Virtual initialization function for SPSDK library to support various DEBUG PROBES. + """ + super().__init__(hardware_id, user_params) + + set_logger(logging.root.level) + + self.opened = False + self.enabled_memory_interface = False + self.virtual_memory: Dict[Any, Any] = {} + self.virtual_memory_substituted: Dict[Any, Any] = {} + self.coresight_ap: Dict[Any, Any] = {} + self.coresight_ap_substituted: Dict[Any, Any] = {} + self.coresight_dp: Dict[Any, Any] = {} + self.coresight_dp_write_exception = False + self.coresight_dp_substituted: Dict[Any, Any] = {} + + if user_params is not None: + if "exc" in user_params.keys(): + raise DebugProbeError("Forced exception from constructor.") + if "subs_ap" in user_params.keys(): + self.set_coresight_ap_substitute_data(self._load_subs_from_param(user_params["subs_ap"])) + if "subs_dp" in user_params.keys(): + self.set_coresight_dp_substitute_data(self._load_subs_from_param(user_params["subs_dp"])) + if "subs_mem" in user_params.keys(): + self.set_virtual_memory_substitute_data(self._load_subs_from_param(user_params["subs_mem"])) + + logger.debug(f"The SPSDK Virtual Interface has been initialized") + + @classmethod + def get_connected_probes(cls, hardware_id: str = None, user_params: Dict = None) -> list: + """Get all connected probes over Virtual. + + This functions returns the list of all connected probes in system by Virtual package. + :param hardware_id: None to list all probes, otherwise the the only probe with matching + hardware id is listed. + :param user_params: The user params dictionary + :return: probe_description + :raises DebugProbeError: In case of invoked test Exception. + """ + #TODO fix problems with cyclic import + from spsdk.debuggers.utils import DebugProbes, ProbeDescription + + probes = DebugProbes() + + if user_params is not None and "exc" in user_params.keys(): + raise DebugProbeError("Forced exception from discovery function.") + + # Find this 'probe' just in case of direct request (user must know the hardware id :-) ) + if hardware_id == DebugProbeVirtual.UNIQUE_SERIAL: + probes.append(ProbeDescription("Virtual", + DebugProbeVirtual.UNIQUE_SERIAL, + "Special virtual debug probe used for product testing", + DebugProbeVirtual)) + return probes + + def open(self) -> None: + """Open Virtual interface for NXP SPSDK. + + The Virtual opening function for SPSDK library to support various DEBUG PROBES. + The function is used to initialize the connection to target and enable using debug probe + for DAT purposes. + """ + self.dbgmlbx_ap_ix = 2 + self.opened = True + + def enable_memory_interface(self) -> None: + """Debug probe enabling memory interface. + + General memory interface enabling method (it should be called after open method) for SPSDK library + to support various DEBUG PROBES. The function is used to initialize the target memory interface + and enable using memory access of target over debug probe. + """ + self.enabled_memory_interface = True + + def close(self) -> None: + """Close Virtual interface. + + The Virtual closing function for SPSDK library to support various DEBUG PROBES. + """ + self.opened = False + self.enabled_memory_interface = False + + def _get_requested_value(self, values: Dict, subs_values: Dict, addr: Any) -> int: + """Method to return back the requested value. + + :param values: The dictionary with already loaded values. + :param subs_values: The dictionary with substituted values. + :param addr: Address of value. + :return: Value by address. + :raises DebugProbeError: General virtual probe error. + """ + if subs_values and addr in subs_values.keys(): + if len(subs_values[addr]) > 0: + svalue = subs_values[addr].pop() + if isinstance(svalue, int): + return svalue + if isinstance(svalue, str) and svalue == "Exception": + raise DebugProbeError("Simulated Debug probe exception") + + return int(values[addr]) if addr in values.keys() else 0 + + def dbgmlbx_reg_read(self, addr: int = 0) -> int: + """Read debug mailbox access port register. + + This is read debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises DebugProbeNotOpenError: The virtual probe is not open + """ + if not self.opened: + raise DebugProbeNotOpenError("The Virtual debug probe is not opened yet") + + # Add ap selection to 2 as a standard index of debug mailbox + return self.coresight_reg_read(access_port=True, addr=addr | 2< None: + """Write debug mailbox access port register. + + This is write debug mailbox register function for SPSDK library to support various DEBUG PROBES. + :param addr: the register address + :param data: the data to be written into register + :raises DebugProbeNotOpenError: The virtual probe is not open + """ + if not self.opened: + raise DebugProbeNotOpenError("The Virtual debug probe is not opened yet") + + # Add ap selection to 2 as a standard index of debug mailbox + self.coresight_reg_write(access_port=True, addr=addr | 2< int: + """Read 32-bit register in memory space of MCU. + + This is read 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises DebugProbeNotOpenError: The Virtual probe is NOT opened + :raises DebugProbeMemoryInterfaceNotEnabled: The Virtual is using just CoreSight access. + :raises DebugProbeError: General virtual probe error. + """ + if not self.opened: + raise DebugProbeNotOpenError("The Virtual debug probe is not opened yet") + + if not self.enabled_memory_interface: + raise DebugProbeMemoryInterfaceNotEnabled("Memory interface is not enabled over Virtual.") + + return self._get_requested_value( + self.virtual_memory, + self.virtual_memory_substituted, + addr) + + def mem_reg_write(self, addr: int = 0, data: int = 0) -> None: + """Write 32-bit register in memory space of MCU. + + This is write 32-bit register in memory space of MCU function for SPSDK library + to support various DEBUG PROBES. + :param addr: the register address + :param data: the data to be written into register + :raises DebugProbeNotOpenError: The Virtual probe is NOT opened + :raises DebugProbeMemoryInterfaceNotEnabled: The Virtual is using just CoreSight access. + """ + if not self.opened: + raise DebugProbeNotOpenError("The Virtual debug probe is not opened yet") + + if not self.enabled_memory_interface: + raise DebugProbeMemoryInterfaceNotEnabled("Memory interface is not enabled over Virtual.") + + self.virtual_memory[addr] = data + + def coresight_reg_read(self, access_port: bool = True, addr: int = 0) -> int: + """Read coresight register over Virtual interface. + + The Virtual read coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be read(defau1lt), otherwise the Debug Port + :param addr: the register address + :return: The read value of addressed register (4 bytes) + :raises DebugProbeTransferError: The IO operation failed + :raises DebugProbeNotOpenError: The Virtual probe is NOT opened + :raises DebugProbeError: General virtual probe error. + """ + if not self.opened: + raise DebugProbeNotOpenError("The Virtual debug probe is not opened yet") + # As first try to solve AP requests + if access_port: + return self._get_requested_value( + self.coresight_ap, + self.coresight_ap_substituted, + addr) + + #DP requests + return self._get_requested_value( + self.coresight_dp, + self.coresight_dp_substituted, + addr) + + def coresight_reg_write(self, access_port: bool = True, addr: int = 0, data: int = 0) -> None: + """Write coresight register over Virtual interface. + + The Virtual write coresight register function for SPSDK library to support various DEBUG PROBES. + :param access_port: if True, the Access Port (AP) register will be write(default), otherwise the Debug Port + :param addr: the register address + :param data: the data to be written into register + :raises DebugProbeTransferError: The IO operation failed + :raises DebugProbeNotOpenError: The Virtual probe is NOT opened + """ + if not self.opened: + raise DebugProbeNotOpenError("The Virtual debug probe is not opened yet") + + if access_port: + self.coresight_ap[addr] = data + else: + if self.coresight_dp_write_exception: + self.coresight_dp_write_exception = False + raise DebugProbeTransferError(f"The Coresight write operation failed.") + self.coresight_dp[addr] = data + + + def reset(self) -> None: + """Reset a target. + + It resets a target. + :raises DebugProbeNotOpenError: The Virtual probe is NOT opened + """ + if not self.opened: + raise DebugProbeNotOpenError("The Virtual debug probe is not opened yet") + + logger.debug("The Virtual probe did reset of virtual target.") + + def clear(self, only_substitute: bool = False) -> None: + """Clear the buffered values. + + :param only_substitute: When set, it clers just substitute data. + """ + if not only_substitute: + self.coresight_dp.clear() + self.coresight_ap.clear() + self.virtual_memory.clear() + + self.coresight_dp_substituted.clear() + self.coresight_dp_write_exception = False + self.coresight_ap_substituted.clear() + self.virtual_memory_substituted.clear() + + def set_virtual_memory_substitute_data(self, substitute_data: Dict) -> None: + """Set the virtual memory read substitute data. + + :param substitute_data: Dictionary of list of substitute data. + """ + for key in substitute_data.keys(): + substitute_data[key].reverse() + self.virtual_memory_substituted = substitute_data + + def set_coresight_dp_substitute_data(self, substitute_data: Dict) -> None: + """Set the virtual memory read substitute data. + + :param substitute_data: Dictionary of list of substitute data. + """ + for key in substitute_data.keys(): + substitute_data[key].reverse() + self.coresight_dp_substituted = substitute_data + + def set_coresight_ap_substitute_data(self, substitute_data: Dict) -> None: + """Set the coresigth AP read substitute data. + + :param substitute_data: Dictionary of list of substitute data. + """ + for key in substitute_data.keys(): + substitute_data[key].reverse() + + self.coresight_ap_substituted = substitute_data + + def dp_write_cause_exception(self) -> None: + """Attempt to write to DP register cause exception.""" + self.coresight_dp_write_exception = True + + def _load_subs_from_param(self, arg: str) -> Dict: + """Get the substituted values from input arguments. + + :param arg: Input string arguments with substitude values. + :return: List of values for the substituted values. + :raises DebugProbeError: The input string is not able do parse. + """ + try: + subs_data_raw = json.loads(arg) + subs_data = {} + for key in subs_data_raw.keys(): + subs_data[int(key)] = subs_data_raw[key] + return subs_data + except (TypeError, JSONDecodeError) as exc: + raise DebugProbeError(f"Cannot parse substituted values: ({str(exc)})") diff --git a/tests/debuggers/test_debug_probe virtual.py b/tests/debuggers/test_debug_probe virtual.py new file mode 100644 index 00000000..1d54b26e --- /dev/null +++ b/tests/debuggers/test_debug_probe virtual.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +""" Tests for Virtual Debug Probe.""" +import pytest + +from spsdk.debuggers.debug_probe import ( + DebugProbeError, + DebugProbeNotOpenError, + DebugProbeMemoryInterfaceNotEnabled, + DebugProbeTransferError + ) +from tests.debuggers.debug_probe_virtual import DebugProbeVirtual + + +def test_virtualprobe_basic(): + vp = DebugProbeVirtual("ID", None) + assert vp is not None + assert vp.hardware_id == "ID" + + assert not vp.opened + vp.open() + assert vp.opened + vp.close() + assert not vp.opened + +def test_virtualprobe_dp(): + vp = DebugProbeVirtual("ID", None) + with pytest.raises(DebugProbeNotOpenError): + vp.coresight_reg_read(False, 0) + with pytest.raises(DebugProbeNotOpenError): + vp.coresight_reg_write(False, 0, 0) + vp.open() + + assert vp.coresight_reg_read(False, 0) == 0 + vp.coresight_reg_write(False, 0, 1) + assert vp.coresight_reg_read(False, 0) == 1 + + vp.coresight_reg_write(False, 0, 1) + assert vp.coresight_reg_read(False, 0) == 1 + + vp.set_coresight_dp_substitute_data({0:[2, 3, "Exception", "Invalid"]}) + assert vp.coresight_reg_read(False, 0) == 2 + assert vp.coresight_reg_read(False, 0) == 3 + with pytest.raises(DebugProbeError): + assert vp.coresight_reg_read(False, 0) == 3 + + assert vp.coresight_reg_read(False, 0) == 1 + assert vp.coresight_reg_read(False, 0) == 1 + + vp.dp_write_cause_exception() + + with pytest.raises(DebugProbeTransferError): + vp.coresight_reg_write(False, 0, 0) + +def test_virtualprobe_ap(): + vp = DebugProbeVirtual("ID", None) + with pytest.raises(DebugProbeNotOpenError): + vp.coresight_reg_read(True, 0) + + with pytest.raises(DebugProbeNotOpenError): + vp.coresight_reg_write(True, 0, 0) + + vp.open() + + assert vp.coresight_reg_read(True, 0) == 0 + vp.coresight_reg_write(True, 0, 1) + assert vp.coresight_reg_read(True, 0) == 1 + + vp.coresight_reg_write(True, 0, 1) + assert vp.coresight_reg_read(True, 0) == 1 + + vp.set_coresight_ap_substitute_data({0:[2, 3, "Exception", "Invalid"]}) + assert vp.coresight_reg_read(True, 0) == 2 + assert vp.coresight_reg_read(True, 0) == 3 + with pytest.raises(DebugProbeError): + assert vp.coresight_reg_read(True, 0) == 3 + assert vp.coresight_reg_read(True, 0) == 1 + assert vp.coresight_reg_read(True, 0) == 1 + +def test_virtualprobe_debugmbox(): + vp = DebugProbeVirtual("ID", None) + with pytest.raises(DebugProbeNotOpenError): + vp.dbgmlbx_reg_read(0) + with pytest.raises(DebugProbeNotOpenError): + vp.dbgmlbx_reg_write(0, 0) + + vp.open() + + assert vp.dbgmlbx_reg_read(0) == 0 + vp.dbgmlbx_reg_write(0, 1) + assert vp.dbgmlbx_reg_read(0) == 1 + + vp.dbgmlbx_reg_write(0, 1) + assert vp.dbgmlbx_reg_read(0) == 1 + + vp.set_coresight_ap_substitute_data({0x02000000:[2, 3]}) + assert vp.dbgmlbx_reg_read(0) == 2 + assert vp.dbgmlbx_reg_read(0) == 3 + assert vp.dbgmlbx_reg_read(0) == 1 + +def test_virtualprobe_memory(): + vp = DebugProbeVirtual("ID", None) + with pytest.raises(DebugProbeNotOpenError): + vp.mem_reg_read(0) + + with pytest.raises(DebugProbeNotOpenError): + vp.mem_reg_write(0, 0) + + vp.open() + with pytest.raises(DebugProbeMemoryInterfaceNotEnabled): + vp.mem_reg_read(0) + with pytest.raises(DebugProbeMemoryInterfaceNotEnabled): + vp.mem_reg_write(0, 0) + + vp.enable_memory_interface() + + assert vp.mem_reg_read(0) == 0 + vp.mem_reg_write(0, 1) + assert vp.mem_reg_read(0) == 1 + + vp.mem_reg_write(0, 1) + assert vp.mem_reg_read(0) == 1 + + vp.set_virtual_memory_substitute_data({0:[2, 3, "Exception", "Invalid"]}) + assert vp.mem_reg_read(0) == 2 + assert vp.mem_reg_read(0) == 3 + with pytest.raises(DebugProbeError): + assert vp.mem_reg_read(0) == 3 + assert vp.mem_reg_read(0) == 1 + assert vp.mem_reg_read(0) == 1 + +def test_virtualprobe_reset(): + vp = DebugProbeVirtual("ID", None) + with pytest.raises(DebugProbeNotOpenError): + vp.reset() + vp.open() + vp.reset() + +def test_virtualprobe_init(): + with pytest.raises(DebugProbeError): + vp = DebugProbeVirtual("ID", {"exc":None}) + + vp = DebugProbeVirtual("ID", {"subs_ap":'{"0":[1,2]}', "subs_dp":'{"0":[1,2]}', "subs_mem":'{"0":[1,2]}'}) + assert vp.coresight_ap_substituted == {0:[2, 1]} + assert vp.coresight_dp_substituted == {0:[2, 1]} + assert vp.virtual_memory_substituted == {0:[2, 1]} + vp.clear(True) + vp.clear(False) + +def test_virtualprobe_init_false(): + with pytest.raises(DebugProbeError): + DebugProbeVirtual("ID", {"subs_ap":'{"0":1,2]}'}) diff --git a/tests/debuggers/test_debug_probe.py b/tests/debuggers/test_debug_probe.py new file mode 100644 index 00000000..ea045135 --- /dev/null +++ b/tests/debuggers/test_debug_probe.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +""" Tests for Debug Probe interface.""" +import pytest +import spsdk +import spsdk.debuggers.debug_probe as DP + +def test_probe_ap_address(): + assert DP.DebugProbe.get_coresight_ap_address(8, 8) == 0x08000008 + with pytest.raises((spsdk.SPSDKError, ValueError)): + assert DP.DebugProbe.get_coresight_ap_address(256, 8) == 0xFF000008 + +def test_probe_get_connected_probes(): + with pytest.raises(NotImplementedError): + DP.DebugProbe.get_connected_probes() + +def test_probe_not_implemented(): + probe = DP.DebugProbe("ID", None) + with pytest.raises(NotImplementedError): + probe.get_connected_probes() + + assert probe.debug_mailbox_access_port == -1 + probe.debug_mailbox_access_port = 10 + assert probe.debug_mailbox_access_port == 10 + + with pytest.raises(NotImplementedError): + probe.open() + + probe.enable_memory_interface() + + with pytest.raises(NotImplementedError): + probe.close() + + with pytest.raises(NotImplementedError): + probe.dbgmlbx_reg_read() + + with pytest.raises(NotImplementedError): + probe.dbgmlbx_reg_write() + + with pytest.raises(NotImplementedError): + probe.mem_reg_read() + + with pytest.raises(NotImplementedError): + probe.mem_reg_write() + + with pytest.raises(NotImplementedError): + probe.coresight_reg_read() + + with pytest.raises(NotImplementedError): + probe.coresight_reg_write() + + with pytest.raises(NotImplementedError): + probe.reset() diff --git a/tests/debuggers/test_debug_probe_utils.py b/tests/debuggers/test_debug_probe_utils.py new file mode 100644 index 00000000..9c2ca33d --- /dev/null +++ b/tests/debuggers/test_debug_probe_utils.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +""" Tests for Debug Probe utilities.""" +import pytest + +from tests.debuggers.debug_probe_virtual import DebugProbeVirtual +from spsdk.debuggers.utils import DebugProbeUtils, DebugProbes, ProbeDescription +import spsdk.debuggers.debug_probe as DP + +def test_debugprobes_append(): + + probe_list = DebugProbes() + probe_descr = ProbeDescription("None", "None", "None", DP.DebugProbe) + probe_list.append(probe_descr) + + assert probe_list.pop() == probe_descr + + with pytest.raises(TypeError): + probe_list.append("Invalid Type") + +def test_debugprobes_insert(): + + probe_list = DebugProbes() + probe_descr = ProbeDescription("None", "None", "None", DP.DebugProbe) + probe_list.insert(0, probe_descr) + + assert probe_list.pop() == probe_descr + + with pytest.raises(TypeError): + probe_list.insert(0, "Invalid Type") + +def test_debugprobes_discovery(): + probe_list = DebugProbeUtils.get_connected_probes("virtual", DebugProbeVirtual.UNIQUE_SERIAL) + + assert probe_list.pop().description == "Special virtual debug probe used for product testing" + + probe_list = DebugProbeUtils.get_connected_probes("virtual", DebugProbeVirtual.UNIQUE_SERIAL, {"exc":None}) + assert len(probe_list) == 0 + +def test_debugprobes_get_probe(): + probe_list = DebugProbeUtils.get_connected_probes("virtual", DebugProbeVirtual.UNIQUE_SERIAL) + + probe = probe_list.select_probe().get_probe() + assert isinstance(probe, DebugProbeVirtual) + + with pytest.raises(DP.DebugProbeError): + assert probe_list.select_probe().get_probe({"exc":None}) is None diff --git a/tests/elftosb/conftest.py b/tests/elftosb/conftest.py deleted file mode 100644 index 503533f4..00000000 --- a/tests/elftosb/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -from os import path -import pytest - - -@pytest.fixture -def data_dir(): - return path.join(path.dirname(__file__), 'data') diff --git a/tests/elftosb/data/sb3_256_256.json b/tests/elftosb/data/sb3_256_256.json index 551d6371..6fb5a02e 100644 --- a/tests/elftosb/data/sb3_256_256.json +++ b/tests/elftosb/data/sb3_256_256.json @@ -28,6 +28,8 @@ "testCorruptRkhRecordId": 0, "testCorruptIskSignature": false, + "timestamp": "0x123456", + "commands": [ {"erase": {"address": "0x0", "size": "0x10000"}}, {"load": {"address": "0x0", "file": ".\\workspace\\output_images\\normal_boot_sign.bin"}} diff --git a/tests/elftosb/data/sb3_256_256_notime.json b/tests/elftosb/data/sb3_256_256_notime.json new file mode 100644 index 00000000..f923f408 --- /dev/null +++ b/tests/elftosb/data/sb3_256_256_notime.json @@ -0,0 +1,36 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_256_256.sb3", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp256r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp256r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp256r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp256r1_cert3.pem", + "rootCertificateEllipticCurve": "secp256r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp256r1_cert0.pem", + "useIsk": true, + "signingCertificateFile": ".\\workspace\\keys_certs\\ec_secp256r1_sign_cert.pem", + "signingCertificatePrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp256r1_sign_cert.pem", + "signingCertificateConstraint": "0x0000", + "iskCertificateEllipticCurve": "secp256r1", + "signCertData": ".\\workspace\\input_images\\testfffffff.bin", + + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + + "commands": [ + {"erase": {"address": "0x0", "size": "0x10000"}}, + {"load": {"address": "0x0", "file": ".\\workspace\\output_images\\normal_boot_sign.bin"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_256_256_notime.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_256_none.json b/tests/elftosb/data/sb3_256_none.json new file mode 100644 index 00000000..7b29ab97 --- /dev/null +++ b/tests/elftosb/data/sb3_256_none.json @@ -0,0 +1,32 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_256_none.sb3", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp256r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp256r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp256r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp256r1_cert3.pem", + "rootCertificateEllipticCurve": "secp256r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp256r1_cert0.pem", + "useIsk": false, + + "timestamp": "0x123456", + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + + "commands": [ + {"erase": {"address": "0x0", "size": "0x10000"}}, + {"load": {"address": "0x0", "file": ".\\workspace\\output_images\\normal_boot_sign.bin"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_256_none.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_256_none_ernad.json b/tests/elftosb/data/sb3_256_none_ernad.json new file mode 100644 index 00000000..0a70348d --- /dev/null +++ b/tests/elftosb/data/sb3_256_none_ernad.json @@ -0,0 +1,31 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_256_none.sb3", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp256r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp256r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp256r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp256r1_cert3.pem", + "rootCertificateEllipticCurve": "secp256r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp256r1_cert0.pem", + "useIsk": false, + + "timestamp": "0x123456", + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + + "commands": [ + {"load": {"address": "0x0", "file": ".\\workspace\\input_images\\new_image_ernad.bin"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_256_none_ernad.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_384_256.json b/tests/elftosb/data/sb3_384_256.json new file mode 100644 index 00000000..f310519c --- /dev/null +++ b/tests/elftosb/data/sb3_384_256.json @@ -0,0 +1,37 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_384_256.sb3", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp384r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp384r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp384r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp384r1_cert3.pem", + "rootCertificateEllipticCurve": "secp384r1", + "mainRootCertId": 2, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_cert2.pem", + "useIsk": true, + "signingCertificateFile": ".\\workspace\\keys_certs\\ec_secp256r1_sign_cert.pem", + "signingCertificatePrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp256r1_sign_cert.pem", + "signingCertificateConstraint": "0x0000", + "iskCertificateEllipticCurve": "secp256r1", + "signCertData": ".\\workspace\\input_images\\testfffffff.bin", + + "timestamp": "0x123456", + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + + "commands": [ + {"erase": {"address": "0x0", "size": "0x10000"}}, + {"load": {"address": "0x0", "file": ".\\workspace\\output_images\\normal_boot_sign.bin"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_384_256.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_384_256_fixed_timestamp.json b/tests/elftosb/data/sb3_384_256_fixed_timestamp.json new file mode 100644 index 00000000..a7088a74 --- /dev/null +++ b/tests/elftosb/data/sb3_384_256_fixed_timestamp.json @@ -0,0 +1,37 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_384_256.sb3", + "timestamp" : "0x123456789abcdef", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp384r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp384r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp384r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp384r1_cert3.pem", + "rootCertificateEllipticCurve": "secp384r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_cert0.pem", + "useIsk": true, + "signingCertificateFile": ".\\workspace\\keys_certs\\ec_secp256r1_sign_cert.pem", + "signingCertificatePrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp256r1_sign_cert.pem", + "signingCertificateConstraint": "0x0000", + "iskCertificateEllipticCurve": "secp256r1", + "signCertData": ".\\workspace\\input_images\\testfffffff.bin", + + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + + "commands": [ + {"erase": {"address": "0x0", "size": "0x10000"}}, + {"load": {"address": "0x0", "file": ".\\workspace\\output_images\\normal_boot_sign.bin"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_384_256_fixed_timestamp.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_384_256_unencrypted.json b/tests/elftosb/data/sb3_384_256_unencrypted.json new file mode 100644 index 00000000..df106336 --- /dev/null +++ b/tests/elftosb/data/sb3_384_256_unencrypted.json @@ -0,0 +1,38 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_384_256.sb3", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp384r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp384r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp384r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp384r1_cert3.pem", + "rootCertificateEllipticCurve": "secp384r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_cert0.pem", + "useIsk": true, + "signingCertificateFile": ".\\workspace\\keys_certs\\ec_secp256r1_sign_cert.pem", + "signingCertificatePrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp256r1_sign_cert.pem", + "signingCertificateConstraint": "0x0000", + "iskCertificateEllipticCurve": "secp256r1", + "signCertData": ".\\workspace\\input_images\\testfffffff.bin", + + "timestamp": "0x123456", + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + "isEncrypted": false, + + "commands": [ + {"erase": {"address": "0x0", "size": "0x10000"}}, + {"load": {"address": "0x0", "file": ".\\workspace\\output_images\\normal_boot_sign.bin"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_384_256_unencrypted.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_384_384.json b/tests/elftosb/data/sb3_384_384.json new file mode 100644 index 00000000..1ee02190 --- /dev/null +++ b/tests/elftosb/data/sb3_384_384.json @@ -0,0 +1,34 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": true, + "description": "sb3_384_384.sb3", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp384r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp384r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp384r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp384r1_cert3.pem", + "rootCertificateEllipticCurve": "secp384r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_cert0.pem", + "useIsk": true, + "signingCertificateFile": ".\\workspace\\keys_certs\\ec_secp384r1_sign_cert.pem", + "signingCertificatePrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_sign_cert.pem", + "signingCertificateConstraint": "0x0000", + "iskCertificateEllipticCurve": "secp384r1", + "timestamp": "0x123456", + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + + "commands": [ + {"erase": {"address": "0x0", "size": "0x10000"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_384_384_nxp.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_384_none.json b/tests/elftosb/data/sb3_384_none.json new file mode 100644 index 00000000..d9fc618b --- /dev/null +++ b/tests/elftosb/data/sb3_384_none.json @@ -0,0 +1,31 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_384_none.sb3", + "kdkAccessRights": 3, + "containerConfigurationWord": "0x0", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp384r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp384r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp384r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp384r1_cert3.pem", + "rootCertificateEllipticCurve": "secp384r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_cert0.pem", + "useIsk": false, + "timestamp": "0x123456", + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + + "commands": [ + {"erase": {"address": "0x0", "size": "0x10000"}}, + {"load": {"address": "0x0", "file": ".\\workspace\\output_images\\normal_boot_sign.bin"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_384_none.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/sb3_test_384_384_unencrypted.json b/tests/elftosb/data/sb3_test_384_384_unencrypted.json new file mode 100644 index 00000000..ea7bf400 --- /dev/null +++ b/tests/elftosb/data/sb3_test_384_384_unencrypted.json @@ -0,0 +1,57 @@ +{ + "family": "lpc55s3x", + "containerKeyBlobEncryptionKey": ".\\workspace\\keys\\userkey.txt", + "isNxpContainer": false, + "description": "sb3_test_384_384_unencrypted.sb3", + "kdkKeyAccesRights": 3, + "containerConfigurationWord": "0x0", + "timestamp" : "0x123456789abcdef", + "firmwareVersion": "0x1", + "rootCertificate0File": ".\\workspace\\keys_certs\\ec_secp384r1_cert0.pem", + "rootCertificate1File": ".\\workspace\\keys_certs\\ec_secp384r1_cert1.pem", + "rootCertificate2File": ".\\workspace\\keys_certs\\ec_secp384r1_cert2.pem", + "rootCertificate3File": ".\\workspace\\keys_certs\\ec_secp384r1_cert3.pem", + "rootCertificateEllipticCurve": "secp384r1", + "mainRootCertId": 0, + "mainRootCertPrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_cert0.pem", + "useIsk": true, + "signingCertificateFile": ".\\workspace\\keys_certs\\ec_secp384r1_sign_cert.pem", + "signingCertificatePrivateKeyFile": ".\\workspace\\keys_certs\\ec_pk_secp384r1_sign_cert.pem", + "signingCertificateConstraint": "0x0000", + "iskCertificateEllipticCurve": "secp384r1", + "signCertData": ".\\workspace\\input_images\\testfffffff.bin", + + "testSb3Magic": "", + "testSb3ImageType": false, + "testSb3ImageTypeValue": 255, + "testCertBlockMagic": "", + "testCorruptRkhRecord": false, + "testCorruptRkhRecordId": 0, + "testCorruptIskSignature": false, + "isEncrypted": false, + + "commands": [ + {"erase": {"address": "0x2588", "size": "0xFFFF", "memoryId": "0xA"}}, + {"load": {"address": "0x1256", "file": ".\\workspace\\input_images\\test1.bin"}}, + {"load": {"address": "0x2588", "file": ".\\workspace\\input_images\\test2.bin", "authentication": "none", "memoryId": "0xA"}}, + {"load": {"address": "0x1256", "file": ".\\workspace\\input_images\\test1.bin", "authentication": "hashlocking"}}, + {"load": {"address": "0x2588", "file": ".\\workspace\\input_images\\test2.bin", "authentication": "cmac"}}, + {"loadKeyBlob": {"offset": "0x1256", "file": ".\\workspace\\input_images\\test1.bin", "wrappingKeyId": "NXP_CUST_KEK_INT_SK"}}, + {"loadKeyBlob": {"offset": "0x2588", "file": ".\\workspace\\input_images\\test2.bin", "wrappingKeyId": "NXP_CUST_KEK_EXT_SK"}}, + {"programFuses": {"address": "0x1384", "values": "0x138498, 0x0, 0x5, 0x1ab, 0x1ab, 0xffffffff, 0xfffffff1"}}, + {"programFuses": {"address": "0x2588", "values": "0x138498, 0x0, 0x5, 0x1ab, 0x1ab, 0xffffffff, 0xfffffff1"}}, + {"programIFR": {"address": "0x1384", "file": ".\\workspace\\input_ifr\\ifr1.bin"}}, + {"programIFR": {"address": "0x2588", "file": ".\\workspace\\input_ifr\\ifr2.bin"}}, + {"call": {"address": "0x1384"}}, + {"call": {"address": "0x2588"}}, + {"execute": {"address": "0x1384"}}, + {"configureMemory": {"memoryId": "0xA", "configAddress": "0x1842"}}, + {"load": {"address": "0x25", "values": "0x138498"}}, + {"load": {"address": "0x25", "values": "0x138498, 0x25, 0x4856974"}}, + {"fillMemory": {"address": "0x25", "pattern": "0xFFFFFFFF", "size": "0xFF"}}, + {"copy": {"addressFrom": "0xF", "memoryIdFrom": "0xFF", "addressTo": "0xFFF", "memoryIdTo": "0xFFFFF", "size": "0xA"}}, + {"checkFwVersion": {"counterId": "nonsecure", "value": "0x1"}}, + {"checkFwVersion": {"counterId": "secure", "value": "0x3"}} + ], + "containerOutputFile": ".\\workspace\\output_images\\sb3_test_384_384_unencrypted.sb3" +} \ No newline at end of file diff --git a/tests/elftosb/data/workspace/output_images/sb3_256_256.sb3 b/tests/elftosb/data/workspace/output_images/sb3_256_256.sb3 index 12f01428..6a795ca2 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_256_256.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_256_256.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_256_256_1.sb3 b/tests/elftosb/data/workspace/output_images/sb3_256_256_1.sb3 deleted file mode 100644 index 225db692..00000000 Binary files a/tests/elftosb/data/workspace/output_images/sb3_256_256_1.sb3 and /dev/null differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_256_256_notime.sb3 b/tests/elftosb/data/workspace/output_images/sb3_256_256_notime.sb3 new file mode 100644 index 00000000..c1d34748 Binary files /dev/null and b/tests/elftosb/data/workspace/output_images/sb3_256_256_notime.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_256_none.sb3 b/tests/elftosb/data/workspace/output_images/sb3_256_none.sb3 index d11a8261..4599ce64 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_256_none.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_256_none.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_256_none_ernad.sb3 b/tests/elftosb/data/workspace/output_images/sb3_256_none_ernad.sb3 index 4a3af5c5..1576465b 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_256_none_ernad.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_256_none_ernad.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_384_256.sb3 b/tests/elftosb/data/workspace/output_images/sb3_384_256.sb3 index 0ca6175c..bda11258 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_384_256.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_384_256.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_384_256_fixed_timestamp.sb3 b/tests/elftosb/data/workspace/output_images/sb3_384_256_fixed_timestamp.sb3 index 5130ec93..9b731270 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_384_256_fixed_timestamp.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_384_256_fixed_timestamp.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_384_256_unencrypted.sb3 b/tests/elftosb/data/workspace/output_images/sb3_384_256_unencrypted.sb3 index 8c92acce..300d969d 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_384_256_unencrypted.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_384_256_unencrypted.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_384_384_nxp.sb3 b/tests/elftosb/data/workspace/output_images/sb3_384_384_nxp.sb3 index 28c8ce54..9928cac9 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_384_384_nxp.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_384_384_nxp.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_384_none.sb3 b/tests/elftosb/data/workspace/output_images/sb3_384_none.sb3 index 7f0bbb07..f2c4590a 100644 Binary files a/tests/elftosb/data/workspace/output_images/sb3_384_none.sb3 and b/tests/elftosb/data/workspace/output_images/sb3_384_none.sb3 differ diff --git a/tests/elftosb/data/workspace/output_images/sb3_test_384_384_unencrypted_old.sb3 b/tests/elftosb/data/workspace/output_images/sb3_test_384_384_unencrypted_old.sb3 deleted file mode 100644 index 6bf7914b..00000000 Binary files a/tests/elftosb/data/workspace/output_images/sb3_test_384_384_unencrypted_old.sb3 and /dev/null differ diff --git a/tests/elftosb/test_elftosb_mbi.py b/tests/elftosb/test_elftosb_mbi.py index fd8d39dc..61cb4bda 100644 --- a/tests/elftosb/test_elftosb_mbi.py +++ b/tests/elftosb/test_elftosb_mbi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -17,6 +17,7 @@ from spsdk.utils.misc import use_working_directory from spsdk.image import MasterBootImageN4Analog, MasterBootImageType + def process_config_file(config_path: str, destination: str): with open(config_path) as f: config_data = json.load(f) @@ -46,6 +47,7 @@ def test_elftosb_mbi_basic(data_dir, tmpdir, config_file): cmd = f"--image-conf {new_config}" result = runner.invoke(elftosb.main, cmd.split()) + assert result.exit_code == 0 assert os.path.isfile(new_binary) assert filecmp.cmp(new_binary, ref_binary) diff --git a/tests/elftosb/test_elftosb_sb31.py b/tests/elftosb/test_elftosb_sb31.py index 54d2533c..bf71787d 100644 --- a/tests/elftosb/test_elftosb_sb31.py +++ b/tests/elftosb/test_elftosb_sb31.py @@ -1,26 +1,84 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Test SecureBinary part of elftosb app.""" import json +import os +import filecmp +from typing import Tuple +import pytest from click.testing import CliRunner from spsdk.apps import elftosb, elftosb_helper +from spsdk.utils.misc import use_working_directory -def test_elftosb_sb31_basic(data_dir): - cmd = f"--container-conf {data_dir}/lpc55xxA1.json" - result = CliRunner().invoke(elftosb.main, cmd.split()) - assert isinstance(result.exception, NotImplementedError) +def process_config_file( + config_path: str, destination: str, config_member: str +) -> Tuple[str, str, str]: + with open(config_path) as f: + config_data = json.load(f) + for key in config_data: + if isinstance(config_data[key], str): + config_data[key] = config_data[key].replace('\\', '/') + ref_binary = config_data[config_member] + new_binary = f"{destination}/{os.path.basename(ref_binary)}" + new_config = f"{destination}/new_config.json" + config_data[config_member] = new_binary + with open(new_config, 'w') as f: + json.dump(config_data, f, indent=2) + return ref_binary, new_binary, new_config -def test_elftosb_sb31_config(data_dir): - with open(f"{data_dir}/sb3_256_256.json") as f: - config_data = json.load(f) - config = elftosb_helper.SB31Config(config_data) - assert len(config.commands) == 2 +@pytest.mark.parametrize( + "config_file", + [ + "sb3_256_256.json", + "sb3_256_none.json", + "sb3_256_none_ernad.json", + "sb3_384_256.json", + "sb3_384_256_fixed_timestamp.json", + "sb3_384_256_unencrypted.json", + "sb3_384_384.json", + "sb3_384_none.json", + "sb3_test_384_384_unencrypted.json", + ] +) +def test_elftosb_sb31(data_dir, tmpdir, config_file): + runner = CliRunner() + with use_working_directory(data_dir): + config_file = f"{data_dir}/{config_file}" + ref_binary, new_binary, new_config = process_config_file(config_file, tmpdir, "containerOutputFile") + cmd = f"--container-conf {new_config}" + result = runner.invoke(elftosb.main, cmd.split()) + assert result.exit_code == 0 + assert os.path.isfile(new_binary) + assert filecmp.cmp(ref_binary, new_binary, shallow=False) + +def test_elftosb_sb31_notime(data_dir, tmpdir): + + config_file = "sb3_256_256.json" + runner = CliRunner() + with use_working_directory(data_dir): + config_file = f"{data_dir}/{config_file}" + ref_binary, new_binary, new_config = process_config_file(config_file, tmpdir, "containerOutputFile") + cmd = f"--container-conf {new_config}" + result = runner.invoke(elftosb.main, cmd.split()) + assert result.exit_code == 0 + assert os.path.isfile(new_binary) + + # Since there's a new timestamp, compare only portions of files + with open(ref_binary, "rb") as f: + ref_data = f.read() + with open(new_binary, "rb") as f: + new_data = f.read() + + assert len(ref_data) == len(new_data) + assert ref_data[:20] == new_data[:20] + assert ref_data[0x1c:0x3c] == new_data[0x1c:0x3c] + diff --git a/tests/image/conftest.py b/tests/image/conftest.py deleted file mode 100644 index 6b71d812..00000000 --- a/tests/image/conftest.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2019 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -import pytest -from os import path - - -@pytest.fixture(scope="module") -def data_dir(): - return path.join(path.dirname(path.abspath(__file__)), 'data') diff --git a/tests/image/data/dcd_test.bin b/tests/image/data/dcd_test.bin deleted file mode 100644 index f722498f..00000000 Binary files a/tests/image/data/dcd_test.bin and /dev/null differ diff --git a/tests/image/data/dcd_test.txt b/tests/image/data/dcd_test.txt deleted file mode 100644 index d4c58c81..00000000 --- a/tests/image/data/dcd_test.txt +++ /dev/null @@ -1,37 +0,0 @@ -# DCD test file (12 commands) - -# Write value at specific address -WriteValue 4 0x30340004 0x4F400005 - -# Clear bit-mask at specific address -ClearBitMask 4 0x307900C4 0x00000001 - -# Set bit-mask at specific address -SetBitMask 4 0x307900C4 0x00000001 - -# Check if all bits from mask are cleared at specific address -CheckAllClear 4 0x307900C4 0x00000001 - -# Check five times if all bits from mask are cleared at specific address -CheckAllClear 4 0x307900C4 0x00000001 5 - -# Check if any bit from mask is cleared at specific address -CheckAnyClear 4 0x307900C4 0x00000001 - -# Check five times if any bit from mask is cleared at specific address -CheckAnyClear 4 0x307900C4 0x00000001 5 - -# Check if all bits from mask are set at specific address -CheckAllSet 4 0x307900C4 0x00000001 - -# Check five times if all bits from mask are set at specific address -CheckAllSet 4 0x307900C4 0x00000001 5 - -# Check if any bit from mask is set at specific address -CheckAnySet 4 0x307900C4 0x00000001 - -# Check five times if any bit from mask is set at specific address -CheckAnySet 4 0x307900C4 0x00000001 5 - -# Generic nop command -Nop \ No newline at end of file diff --git a/tests/image/images/data/cpu_data/rt1020/hab_audit_log_data.txt b/tests/image/images/data/cpu_data/rt1020/hab_audit_log_data.txt new file mode 100644 index 00000000..88168834 --- /dev/null +++ b/tests/image/images/data/cpu_data/rt1020/hab_audit_log_data.txt @@ -0,0 +1,141 @@ +INFO MBOOT:mcuboot.py:192 Connect: COM8 +DEBUG MBOOT:UART:uart.py:216 [5a a6] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <00 02 01 50 00 00 aa ea> +INFO MBOOT:mcuboot.py:487 CMD: GetProperty('ReservedRegions', index=0) +DEBUG MBOOT:mcuboot.py:77 TX-PACKET: Tag=GetProperty, Flags=0x00, P[0]=0x0000000C, P[1]=0x00000000 +DEBUG MBOOT:UART:uart.py:216 [5a a4 0c 00 2f 65 07 00 00 02 0c 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <18 00> +DEBUG MBOOT:UART:uart.py:207 <02 c5> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:mcuboot.py:87 RX-PACKET: Tag=GetPropertyResponse, Status=Success, v0=0x2020A000, v1=0x2021E7A0, v2=0x20000000, v3=0x2000CFFF +INFO MBOOT:mcuboot.py:448 CMD: WriteMemory(address=0x20200000, length=8772, mem_id=0) +INFO MBOOT:mcuboot.py:487 CMD: GetProperty('MaxPacketSize', index=0) +DEBUG MBOOT:mcuboot.py:77 TX-PACKET: Tag=GetProperty, Flags=0x00, P[0]=0x0000000B, P[1]=0x00000000 +DEBUG MBOOT:UART:uart.py:216 [5a a4 0c 00 37 a2 07 00 00 02 0b 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <0c 00> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:mcuboot.py:87 RX-PACKET: Tag=GetPropertyResponse, Status=Success, v0=0x00000200 +DEBUG MBOOT:mcuboot.py:77 TX-PACKET: Tag=WriteMemory, Flags=0x01, P[0]=0x20200000, P[1]=0x00002244, P[2]=0x00000000 +DEBUG MBOOT:UART:uart.py:216 [5a a4 10 00 a0 c0 04 01 00 03 00 00 20 20 44 22 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <0c 00> +DEBUG MBOOT:UART:uart.py:207 <23 72> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:mcuboot.py:87 RX-PACKET: Tag=GenericResponse, Status=Success, Cmd=WriteMemory +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 75 9a 00 80 20 20 c9 02 20 20 43 03 20 20 d5 0e 20 20 47 03 20 20 49 03 20 20 4b 03 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4d 03 20 20 4f 03 20 20 00 00 00 00 51 03 20 20 53 03 20 20 59 0a 20 20 61 0a 20 20 69 0a 20 20 71 0a 20 20 79 0a 20 20 81 0a 20 20 89 0a 20 20 91 0a 20 20 99 0a 20 20 a1 0a 20 20 a9 0a 20 20 b1 0a 20 20 b9 0a 20 20 c1 0a 20 20 c9 0a 20 20 d1 0a 20 20 d9 0a 20 20 e1 0a 20 20 e9 0a 20 20 f1 0a 20 20 f9 0a 20 20 01 0b 20 20 09 0b 20 20 11 0b 20 20 19 0b 20 20 21 0b 20 20 29 0b 20 20 31 0b 20 20 39 0b 20 20 41 0b 20 20 49 0b 20 20 51 0b 20 20 59 0b 20 20 61 0b 20 20 69 0b 20 20 71 0b 20 20 79 0b 20 20 81 0b 20 20 89 0b 20 20 91 0b 20 20 99 0b 20 20 a1 0b 20 20 a9 0b 20 20 b1 0b 20 20 b9 0b 20 20 c1 0b 20 20 c9 0b 20 20 d1 0b 20 20 d9 0b 20 20 e1 0b 20 20 e9 0b 20 20 f1 0b 20 20 f9 0b 20 20 01 0c 20 20 09 0c 20 20 11 0c 20 20 19 0c 20 20 21 0c 20 20 29 0c 20 20 31 0c 20 20 39 0c 20 20 41 0c 20 20 49 0c 20 20 51 0c 20 20 59 0c 20 20 61 0c 20 20 69 0c 20 20 71 0c 20 20 79 0c 20 20 81 0c 20 20 89 0c 20 20 91 0c 20 20 99 0c 20 20 a1 0c 20 20 a9 0c 20 20 b1 0c 20 20 b9 0c 20 20 c1 0c 20 20 c9 0c 20 20 d1 0c 20 20 d9 0c 20 20 e1 0c 20 20 e9 0c 20 20 f1 0c 20 20 f9 0c 20 20 01 0d 20 20 09 0d 20 20 11 0d 20 20 19 0d 20 20 21 0d 20 20 29 0d 20 20 31 0d 20 20 39 0d 20 20 41 0d 20 20 49 0d 20 20 51 0d 20 20 59 0d 20 20 61 0d 20 20 69 0d 20 20 71 0d 20 20 79 0d 20 20 81 0d 20 20 89 0d 20 20 91 0d 20 20 99 0d 20 20 a1 0d 20 20 a9 0d 20 20 b1 0d 20 20 b9 0d 20 20 c1 0d 20 20 c9 0d 20 20 d1 0d 20 20] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 cb 30 d9 0d 20 20 e1 0d 20 20 e9 0d 20 20 f1 0d 20 20 f9 0d 20 20 01 0e 20 20 09 0e 20 20 11 0e 20 20 19 0e 20 20 21 0e 20 20 29 0e 20 20 31 0e 20 20 39 0e 20 20 41 0e 20 20 49 0e 20 20 51 0e 20 20 59 0e 20 20 61 0e 20 20 69 0e 20 20 71 0e 20 20 79 0e 20 20 81 0e 20 20 89 0e 20 20 91 0e 20 20 99 0e 20 20 a1 0e 20 20 a9 0e 20 20 b1 0e 20 20 b9 0e 20 20 c1 0e 20 20 44 12 20 20 44 12 20 20 00 10 00 00 44 12 20 20 00 00 00 00 00 00 00 00 44 12 20 20 00 00 00 80 00 00 00 00 44 12 20 20 00 00 e0 81 00 00 00 00 44 22 20 20 e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00 00 00 e0 81 00 00 00 00 10 b5 72 b6 00 f0 aa f9 0d 4b 07 e0 03 f1 0c 04 9a 68 59 68 18 68 00 f0 19 f8 23 46 09 4a 93 42 f4 d3 06 e0 1c 46 59 68 54 f8 08 0b 00 f0 1c f8 23 46 05 4a 93 42 f5 d3 62 b6 00 f0 03 fe fe e7 78 02 20 20 a8 02 20 20 c8 02 20 20 00 23 93 42 09 d2 10 b4 50 f8 04 4b 41 f8 04 4b 04 33 93 42 f8 d3 10 bc 70 47 70 47 00 23 8b 42 04 d2 00 22 40 f8 04 2b 04 33 f8 e7 70 47 fe e7 fe e7 fe e7 fe e7 fe e7 fe e7 fe e7 fe e7 fe e7 fe e7 ff ff 90 b5 b3 b0 02 af 78 60 4f f4 80 52 00 21 55 48 00 f0 d9 fd 54 4b 1b 68 c7 f8 b0 30 d7 f8 b0 30 52 4a 93 42 04 d9 d7 f8 b0 30 51 4a 93 42 06 d9 04 22 ff 21 4b 48 00 f0 c6 fd 00 23 8e e0 4d 4b 1b 68 c7 f8 ac 30 4c 4b 1b 68 c7 f8 a8 30 4b 4b 1b 68 c7 f8 a4 30 4a 4b 1b 68 c7 f8 a0 30 49 4b 1b 68 c7 f8 9c 30 7b 68 00 2b 19 d0 d7 f8 ac 30 98 47 4f f0 c0 43 c7 f8 94 30 4f f4 00 33 c7 f8 90 30 07 f1 90 03 07 f1 94 02 00 21 00 91 d7 f8 a4 40 4f f4 80 51 00 20 a0 47 d7 f8 a8 30 98 47 ff 23 87 f8 9b 30 ff 23] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a8 da 87 f8 9a 30 07 f1 9a 01 07 f1 9b 02 d7 f8 a0 30 10 46 98 47 03 46 1a 46 28 4b 1a 70 97 f8 9b 20 26 4b 5a 70 97 f8 9a 20 24 4b 9a 70 23 4b 1b 78 f0 2b 3e d0 2a 4b c7 f8 bc 30 00 23 c7 f8 b8 30 80 23 c7 f8 8c 30 40 f6 fc 73 a7 f8 b6 30 21 e0 b7 f8 b6 20 d7 f8 8c 30 9b b2 d3 1a 9b b2 a7 f8 b6 30 b7 f9 b6 30 00 2b 22 db d7 f8 8c 20 07 f1 0c 03 19 46 d7 f8 bc 00 00 f0 4f fd d7 f8 8c 30 d7 f8 bc 20 13 44 c7 f8 bc 30 d7 f8 b8 30 01 33 c7 f8 b8 30 07 f1 8c 03 07 f1 0c 02 d7 f8 9c 40 d7 f8 b8 10 00 20 a0 47 03 46 f0 2b d0 d0 00 e0 00 bf 00 23 18 46 c4 37 bd 46 90 bd 44 12 20 20 04 10 00 60 ff 0f 00 60 ff ff 00 60 c4 02 20 00 c8 02 20 00 ec 02 20 00 e4 02 20 00 e0 02 20 00 48 12 20 20 80 b5 00 af 05 4b 1b 68 05 4a 52 68 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 40 18 40 80 b5 00 af 05 4b 1b 68 05 4a 92 68 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 80 18 40 80 b5 00 af 05 4b 1b 68 05 4a d2 68 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 c0 18 40 80 b5 00 af 05 4b 1b 68 05 4a 12 69 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 00 19 40 80 b5 00 af 05 4b 1b 68 05 4a 52 69 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 40 19 40 80 b5 00 af 05 4b 1b 68 05 4a 92 69 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 80 19 40 80 b5 00 af 05 4b 1b 68 05 4a d2 69 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 c0 19 40 80 b5 00 af] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 43 ae 05 4b 1b 68 05 4a 12 6a 11 46 05 48 98 47 bf f3 4f 8f 00 bf 00 bf 80 bd 68 22 20 20 44 22 20 20 00 00 1a 40 80 b5 84 b0 00 af 69 4b 69 4a 9a 60 69 4b 1b 89 9b b2 68 4a 23 f0 01 03 9b b2 13 81 66 4b 1b 89 9b b2 65 4a 23 f0 01 03 9b b2 13 81 61 4b 1b 88 9b b2 03 f0 04 03 00 2b 07 d0 5e 4b 1b 88 9b b2 5c 4a 23 f0 04 03 9b b2 13 80 5b 4b 1b 88 9b b2 03 f0 04 03 00 2b 07 d0 57 4b 1b 88 9b b2 56 4a 23 f0 04 03 9b b2 13 80 54 4b 1b 68 03 f4 00 53 00 2b 03 d0 51 4b 52 4a 5a 60 07 e0 4f 4b 4c f2 20 52 5a 60 4d 4b 4d f6 28 12 5a 60 4b 4b 4f f6 ff 72 9a 60 49 4b 1b 68 23 f0 a0 03 47 4a 43 f0 20 03 13 60 47 4b 1b 68 03 f0 01 03 00 2b 05 d0 44 4b 1b 68 43 4a 23 f0 01 03 13 60 3b 4b 5b 69 03 f4 00 33 b3 f5 00 3f 22 d0 38 4b 5b 69 03 f4 00 33 00 2b 1b d1 bf f3 4f 8f 00 bf bf f3 6f 8f 00 bf 32 4b 00 22 c3 f8 50 22 bf f3 4f 8f 00 bf bf f3 6f 8f 00 bf 2d 4b 5b 69 2c 4a 43 f4 00 33 53 61 bf f3 4f 8f 00 bf bf f3 6f 8f 00 e0 00 bf 26 4b 5b 69 03 f4 80 33 b3 f5 80 3f 3f d0 23 4b 5b 69 03 f4 80 33 00 2b 38 d1 20 4b 00 22 c3 f8 84 20 bf f3 4f 8f 00 bf 1c 4b d3 f8 80 30 fb 60 fb 68 5b 0b c3 f3 0e 03 bb 60 fb 68 db 08 c3 f3 09 03 7b 60 bb 68 5a 01 43 f6 e0 73 13 40 7a 68 92 07 12 49 13 43 c1 f8 60 32 7b 68 5a 1e 7a 60 00 2b ef d1 bb 68 5a 1e ba 60 00 2b e5 d1 bf f3 4f 8f 00 bf 09 4b 5b 69 08 4a 43 f4 80 33 53 61 bf f3 4f 8f 00 bf bf f3 6f 8f 00 e0 00 bf 00 f0 9d fb 00 bf 10 37 bd 46 80 bd 00 bf 00 ed 00 e0 00 00 20 20 00 80 0b 40 00 00 0d 40 00 c0 0b 40 20 c5 28 d9 10 e0 00 e0 2d e9 f0 41 d5 68 57 69 eb 02 80 46 0e 46 14 46 05 d5 38 46] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 ff 7b 00 f0 f8 fc 45 f0 10 05 a0 61 12 4b 2b 40 8b b1 a1 69 38 46 00 f0 00 fd 00 28 06 da e3 68 43 f0 80 03 e3 60 4f f0 ff 30 11 e0 25 f4 00 35 25 f0 10 05 e5 60 32 46 41 46 38 46 00 f0 f9 fc 20 f0 00 41 76 1a a1 69 0e 44 a6 61 00 28 e6 d1 bd e8 f0 81 00 bf 10 00 02 00 2d e9 f8 43 c6 68 3d 4b 33 40 04 46 0d 46 90 46 1b b9 02 27 38 46 bd e8 f8 83 d0 f8 14 90 48 46 00 f0 c2 fc 07 46 00 28 f3 d1 b8 f1 01 0f 2c d0 b8 f1 02 0f 2e d0 b8 f1 00 0f ea d1 00 2d e8 db b0 04 04 d5 23 68 e2 6a 9a 42 38 bf e3 62 a1 69 a9 42 0e dc 23 68 e2 6a 93 42 38 bf 13 46 22 69 58 18 80 1a a8 42 04 db 20 6b 01 eb 00 0c ac 45 34 dc 00 23 c4 e9 01 33 46 f0 20 06 a5 62 26 f4 03 26 26 f0 40 06 e6 60 c4 e7 20 46 00 f0 38 f8 05 44 d3 e7 48 46 00 f0 81 fc b0 f1 00 0c e2 68 04 da 42 f0 80 02 e2 60 01 27 b3 e7 23 68 18 46 a3 69 19 46 e3 6a 98 42 2c bf 09 18 c9 18 0b 46 21 69 92 06 a3 eb 01 03 03 d5 a2 6a 93 42 b8 bf 13 46 63 45 ac bf ed 18 65 44 af e7 6d 1a b1 07 44 bf 28 1a a0 60 f1 07 44 bf 9b 1a eb 1a 15 44 48 bf 63 60 25 60 26 f0 20 06 c0 e7 03 00 10 00 c3 68 9a 07 05 d1 0e 4b 21 22 1a 60 4f f0 ff 30 70 47 03 f0 20 02 1b 03 09 d5 12 b1 80 6a 01 38 70 47 03 68 82 69 00 69 13 44 18 1a f7 e7 0a b1 80 6a 70 47 03 68 82 69 00 69 13 44 18 1a 70 47 20 23 20 20 10 b5 17 4a 17 49 00 20 00 f0 63 fb 4f f4 80 73 4f f4 80 62 00 21 12 48 00 f0 16 fb 12 4a 13 49 01 20 00 f0 56 fb 4f f4 80 73 0f 48 4f f4 80 62 00 21 00 f0 09 fb 0e 4b 1b 68 02 2b 0e dd 0d 4a 0a 49 02 20 00 f0 45 fb bd e8 10 40 09 48 4f f4 80 73 4f f4 80 62 00 21 00 f0 f6 ba 10 bd 00 bf 6c 22 20 20] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 20 be 40 12 20 20 a8 22 20 20 42 12 20 20 3c 12 20 20 e4 22 20 20 f7 b5 04 46 98 b1 03 b0 bd e8 f0 40 00 f0 8f bb 07 fb 04 60 00 f0 8b fb 00 28 18 bf 4f f0 ff 35 01 34 01 9b a3 42 f3 dc 28 46 03 b0 f0 bd 03 4b 03 4e 1b 68 01 93 05 46 3c 27 f2 e7 3c 12 20 20 6c 22 20 20 08 b5 ff f7 7b fc 08 bd 08 b5 ff f7 77 fc 08 bd 08 b5 ff f7 73 fc 08 bd 08 b5 ff f7 6f fc 08 bd 08 b5 ff f7 6b fc 08 bd 08 b5 ff f7 67 fc 08 bd 08 b5 ff f7 63 fc 08 bd 08 b5 ff f7 5f fc 08 bd 08 b5 ff f7 5b fc 08 bd 08 b5 ff f7 57 fc 08 bd 08 b5 ff f7 53 fc 08 bd 08 b5 ff f7 4f fc 08 bd 08 b5 ff f7 4b fc 08 bd 08 b5 ff f7 47 fc 08 bd 08 b5 ff f7 43 fc 08 bd 08 b5 ff f7 3f fc 08 bd 08 b5 ff f7 3b fc 08 bd 08 b5 ff f7 37 fc 08 bd 08 b5 ff f7 33 fc 08 bd 08 b5 ff f7 2f fc 08 bd 08 b5 ff f7 f3 fc 08 bd 08 b5 ff f7 03 fd 08 bd 08 b5 ff f7 13 fd 08 bd 08 b5 ff f7 23 fd 08 bd 08 b5 ff f7 33 fd 08 bd 08 b5 ff f7 43 fd 08 bd 08 b5 ff f7 53 fd 08 bd 08 b5 ff f7 63 fd 08 bd 08 b5 ff f7 0b fc 08 bd 08 b5 ff f7 07 fc 08 bd 08 b5 ff f7 03 fc 08 bd 08 b5 ff f7 ff fb 08 bd 08 b5 ff f7 fb fb 08 bd 08 b5 ff f7 f7 fb 08 bd 08 b5 ff f7 f3 fb 08 bd 08 b5 ff f7 ef fb 08 bd 08 b5 ff f7 eb fb 08 bd 08 b5 ff f7 e7 fb 08 bd 08 b5 ff f7 e3 fb 08 bd 08 b5 ff f7 df fb 08 bd 08 b5 ff f7 db fb 08 bd 08 b5 ff f7 d7 fb 08 bd 08 b5 ff f7 d3 fb 08 bd 08 b5 ff f7 cf fb 08 bd 08 b5 ff f7 cb fb 08 bd 08 b5 ff f7 c7 fb 08 bd 08 b5 ff f7 c3 fb 08 bd 08 b5 ff f7 bf fb 08 bd 08 b5 ff f7 bb fb 08 bd 08 b5 ff f7 b7 fb 08 bd 08 b5 ff f7 b3 fb 08 bd 08 b5 ff f7 af fb 08 bd 08 b5 ff f7 ab fb 08 bd] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 e4 6b 08 b5 ff f7 a7 fb 08 bd 08 b5 ff f7 a3 fb 08 bd 08 b5 ff f7 9f fb 08 bd 08 b5 ff f7 9b fb 08 bd 08 b5 ff f7 97 fb 08 bd 08 b5 ff f7 93 fb 08 bd 08 b5 ff f7 8f fb 08 bd 08 b5 ff f7 8b fb 08 bd 08 b5 ff f7 87 fb 08 bd 08 b5 ff f7 83 fb 08 bd 08 b5 ff f7 7f fb 08 bd 08 b5 ff f7 7b fb 08 bd 08 b5 ff f7 77 fb 08 bd 08 b5 ff f7 73 fb 08 bd 08 b5 ff f7 6f fb 08 bd 08 b5 ff f7 6b fb 08 bd 08 b5 ff f7 67 fb 08 bd 08 b5 ff f7 63 fb 08 bd 08 b5 ff f7 5f fb 08 bd 08 b5 ff f7 5b fb 08 bd 08 b5 ff f7 57 fb 08 bd 08 b5 ff f7 53 fb 08 bd 08 b5 ff f7 4f fb 08 bd 08 b5 ff f7 4b fb 08 bd 08 b5 ff f7 47 fb 08 bd 08 b5 ff f7 43 fb 08 bd 08 b5 ff f7 3f fb 08 bd 08 b5 ff f7 3b fb 08 bd 08 b5 ff f7 37 fb 08 bd 08 b5 ff f7 33 fb 08 bd 08 b5 ff f7 2f fb 08 bd 08 b5 ff f7 2b fb 08 bd 08 b5 ff f7 27 fb 08 bd 08 b5 ff f7 23 fb 08 bd 08 b5 ff f7 1f fb 08 bd 08 b5 ff f7 1b fb 08 bd 08 b5 ff f7 17 fb 08 bd 08 b5 ff f7 13 fb 08 bd 08 b5 ff f7 0f fb 08 bd 08 b5 ff f7 0b fb 08 bd 08 b5 ff f7 07 fb 08 bd 08 b5 ff f7 03 fb 08 bd 08 b5 ff f7 ff fa 08 bd 08 b5 ff f7 fb fa 08 bd 08 b5 ff f7 f7 fa 08 bd 08 b5 ff f7 f3 fa 08 bd 08 b5 ff f7 ef fa 08 bd 08 b5 ff f7 eb fa 08 bd 08 b5 ff f7 e7 fa 08 bd 08 b5 ff f7 e3 fa 08 bd 08 b5 ff f7 df fa 08 bd 08 b5 ff f7 db fa 08 bd 08 b5 ff f7 d7 fa 08 bd 08 b5 ff f7 d3 fa 08 bd 08 b5 ff f7 cf fa 08 bd 08 b5 ff f7 cb fa 08 bd 08 b5 ff f7 c7 fa 08 bd 08 b5 ff f7 c3 fa 08 bd 08 b5 ff f7 bf fa 08 bd 08 b5 ff f7 bb fa 08 bd 08 b5 ff f7 b7 fa 08 bd 08 b5 ff f7 b3 fa 08 bd 08 b5 ff f7 af fa 08 bd 08 b5 ff f7 ab fa 08 bd] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 61 6e 08 b5 ff f7 a7 fa 08 bd 08 b5 ff f7 a3 fa 08 bd 08 b5 ff f7 9f fa 08 bd 08 b5 ff f7 9b fa 08 bd 08 b5 ff f7 97 fa 08 bd 08 b5 ff f7 93 fa 08 bd 08 b5 ff f7 8f fa 08 bd 08 b5 ff f7 8b fa 08 bd 08 b5 ff f7 87 fa 08 bd 08 b5 ff f7 83 fa 08 bd 08 b5 ff f7 7f fa 08 bd 08 b5 ff f7 7b fa 08 bd 08 b5 ff f7 77 fa 08 bd 08 b5 ff f7 73 fa 08 bd 08 b5 ff f7 6f fa 08 bd 08 b5 ff f7 6b fa 08 bd 08 b5 ff f7 67 fa 08 bd 08 b5 ff f7 63 fa 08 bd 08 b5 ff f7 5f fa 08 bd 08 b5 ff f7 5b fa 08 bd 08 b5 ff f7 57 fa 08 bd 08 b5 ff f7 53 fa 08 bd 08 b5 ff f7 4f fa 08 bd 08 b5 ff f7 4b fa 08 bd 08 b5 ff f7 47 fa 08 bd 80 b5 00 af 01 20 ff f7 43 fa fe e7 04 20 71 46 08 42 02 d0 ef f3 09 80 01 e0 ef f3 08 80 81 69 0a 88 4b f6 ab 63 9a 42 00 d0 fe e7 02 31 81 61 20 21 01 60 70 47 00 bf 80 b4 00 af 00 bf bd 46 80 bc 70 47 08 b5 ff f7 45 fd bd e8 08 40 ff f7 d7 bf 00 f0 32 b9 00 f0 4a b9 c3 68 82 69 23 f0 20 03 10 b5 c3 60 83 6a 9a 42 04 46 0e d0 00 f0 14 f8 e3 68 23 f4 40 53 23 f0 10 03 43 f0 10 03 e3 60 a3 6a a3 61 23 69 e3 62 23 60 e3 68 23 f4 80 43 23 f0 40 03 e3 60 10 bd 38 b5 01 68 05 69 c3 6a 04 46 c0 68 20 f4 00 22 e2 60 00 f0 82 02 02 2a 02 d0 4f f0 ff 30 38 bd 10 f4 80 30 fb d0 99 42 38 bf 19 46 8d 42 08 d1 e3 68 e5 62 00 20 23 f4 80 33 25 60 a0 60 e3 60 ed e7 22 46 49 1b 28 46 ff f7 1f fc 00 28 ef d0 e3 e7 a8 b1 4f f0 80 53 40 f8 0c 3c 50 f8 04 2c 72 b1 13 68 b3 f1 80 5f 0a d1 50 f8 08 3c 51 68 03 33 0b 44 40 f8 08 3c 93 68 40 f8 04 3c ed e7 70 47 70 b5 c4 68 a5 07 08 d0 b2 f5 00 7f 11 d0 b2 f5 80 6f 04 d0 b2 f5 80 7f 0b d0 01 20] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 eb 1a 08 e0 00 f1 24 01 01 23 22 43 01 61 01 60 c3 61 c2 60 00 20 70 bd 5e 1e 6f f0 7f 45 ae 42 f3 d3 ed e7 10 b5 42 1c 83 07 04 46 16 d1 23 46 54 f8 04 1b a1 f1 01 30 20 ea 01 00 10 f0 80 3f f5 d0 11 f0 ff 0f 11 d0 11 f4 7f 4f 0c d0 11 f4 7f 0f 14 bf 23 46 03 33 98 1a 04 e0 10 f8 01 3b 00 2b e1 d1 80 1a 10 bd 02 33 f5 e7 01 33 f3 e7 70 b5 0d 46 06 46 10 46 14 46 00 f0 2d f8 2b 78 72 2b 17 d0 77 2b 18 d0 61 2b 23 d1 08 23 48 f2 02 02 15 f8 01 1f 2b 29 12 d0 62 29 15 d0 1b 07 e2 60 66 61 04 d5 02 22 00 21 20 46 ff f7 d5 fb 20 46 70 bd 00 23 01 22 eb e7 04 23 02 22 e8 e7 42 f0 03 02 43 f0 02 03 e3 e7 42 f0 04 02 43 f0 01 03 de e7 00 24 eb e7 f0 b5 c4 68 a1 07 89 b0 05 46 22 d0 22 07 18 d4 d0 e9 04 67 ff f7 93 fc 38 46 00 f0 7a f8 23 05 02 d5 30 46 ff f7 5b ff a4 0d a4 05 14 f1 a5 4f 07 d1 29 6a 20 22 68 46 00 f0 89 f8 68 46 00 f0 0b f8 3c 22 00 21 28 46 ff f7 fe fe 00 20 09 b0 f0 bd 4f f0 ff 30 fa e7 10 b5 04 46 ff f7 76 ff 01 46 20 46 bd e8 10 40 00 f0 65 b8 c3 68 70 b5 13 f0 03 06 04 46 11 d0 9b 06 11 d5 85 6a ff f7 e5 fe e3 68 23 f4 40 53 e3 60 20 46 ff f7 fe fe 00 22 06 46 29 46 20 46 ff f7 73 fb 30 46 70 bd 81 69 05 68 0d 44 01 69 6d 1a ea e7 40 ea 01 03 db 07 03 46 0c d4 03 46 04 3a 22 bf 51 f8 04 cb 43 f8 04 cb b2 f1 04 02 f7 d2 04 32 08 bf 70 47 01 3a 24 bf 11 f8 01 cb 03 f8 01 cb f8 d8 70 47 03 46 13 f0 03 0f 0e d1 01 f0 ff 01 41 ea 01 21 41 ea 01 41 04 3a 24 bf 43 f8 04 1b b2 f1 04 02 f9 d2 02 f1 04 02 01 3a 24 bf 03 f8 01 1b fa e7 70 47 03 b4 69 46 02 20 ab be 02 b0 70 47 03 b4 69 46 0c 20 ab be 02 b0 70 47] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 c8 ca 03 b4 69 46 09 20 ab be 02 b0 70 47 03 b4 69 46 0e 20 ab be 02 b0 70 47 03 b4 69 46 0a 20 ab be 02 b0 70 47 0f b4 69 46 0d 20 ab be 04 b0 70 47 0f b4 69 46 05 20 ab be 04 b0 70 47 03 00 00 00 72 00 77 00 a5 5a 11 22 33 44 55 66 77 88 88 99 aa bb cc dd ee ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a9 f7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a9 f7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a9 f7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a9 f7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a9 f7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a9 f7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 00 02 a9 f7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a5 44 00 66 99 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <0c 00> +DEBUG MBOOT:UART:uart.py:207 <23 72> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:mcuboot.py:162 RX-PACKET: Tag=GenericResponse, Status=Success, Cmd=WriteMemory +INFO MBOOT:mcuboot.py:171 CMD: Successfully Send 8772 Bytes +INFO MBOOT:mcuboot.py:537 CMD: Call(address=0x20200359, argument=0x00000000) +DEBUG MBOOT:mcuboot.py:77 TX-PACKET: Tag=Call, Flags=0x00, P[0]=0x20200359, P[1]=0x00000000 +DEBUG MBOOT:UART:uart.py:216 [5a a4 0c 00 c2 16 0a 00 00 02 59 03 20 20 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <0c 00> +DEBUG MBOOT:UART:uart.py:207 <79 d0> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:mcuboot.py:87 RX-PACKET: Tag=GenericResponse, Status=Success, Cmd=Call +INFO MBOOT:mcuboot.py:433 CMD: ReadMemory(address=0x20201244, length=100, mem_id=0) +DEBUG MBOOT:mcuboot.py:77 TX-PACKET: Tag=ReadMemory, Flags=0x00, P[0]=0x20201244, P[1]=0x00000064, P[2]=0x00000000 +DEBUG MBOOT:UART:uart.py:216 [5a a4 10 00 a4 56 03 00 00 03 44 12 20 20 64 00 00 00 00 00 00 00] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <0c 00> +DEBUG MBOOT:UART:uart.py:207 <27 f6> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:mcuboot.py:87 RX-PACKET: Tag=ReadMemoryResponse, Status=Success, Length=100 +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <64 00> +DEBUG MBOOT:UART:uart.py:207 <00 b5> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:UART:uart.py:207 <5a> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:207 <0c 00> +DEBUG MBOOT:UART:uart.py:207 <0e 23> +DEBUG MBOOT:UART:uart.py:207 +DEBUG MBOOT:UART:uart.py:216 [5a a1] +DEBUG MBOOT:mcuboot.py:123 RX-PACKET: Tag=GenericResponse, Status=Success, Cmd=ReadMemory +INFO MBOOT:mcuboot.py:134 CMD: Successfully Received 100 from 100 Bytes diff --git a/tests/image/data/imx7d_uboot.imx b/tests/image/images/data/imx7d_uboot.imx similarity index 100% rename from tests/image/data/imx7d_uboot.imx rename to tests/image/images/data/imx7d_uboot.imx diff --git a/tests/image/data/imx8qma0mek-sd.bin b/tests/image/images/data/imx8qma0mek-sd.bin similarity index 100% rename from tests/image/data/imx8qma0mek-sd.bin rename to tests/image/images/data/imx8qma0mek-sd.bin diff --git a/tests/image/images/test_hab_audit_log.py b/tests/image/images/test_hab_audit_log.py index 953bcf03..5f3b8114 100644 --- a/tests/image/images/test_hab_audit_log.py +++ b/tests/image/images/test_hab_audit_log.py @@ -1,12 +1,19 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause -from spsdk.image.hab_audit_log import parse_hab_log, get_hab_enum_descr +from spsdk.image.hab_audit_log import hab_audit_xip_app, parse_hab_log, CpuData, \ + get_hab_enum_descr, check_reserved_regions, get_hab_log_info from spsdk.utils.easy_enum import Enum +# from spsdk.utils.serial_proxy import SerialProxy +from spsdk.mboot.interfaces import Uart +from spsdk.mboot import McuBoot +from unittest.mock import patch +# responses from rt1020 for mcu emulating +from spsdk.utils.serial_proxy import SimpleReadSerialProxy class TestEnum(Enum): @@ -32,3 +39,46 @@ def test_parse_hab_log(): assert lines lines = parse_hab_log(51, 240, 102, b'\xdb\x00\x08\x43\x33\x22\x0a\x00' + b'\x00' * 60) assert lines + + +def test_hab_audit_xip_app_simple(data_dir): + """Test `hab_audit_log` function. """ + import re + import os + + captured_log = os.path.join(data_dir, "cpu_data", "rt1020", "hab_audit_log_data.txt") + with open(captured_log) as f: + text_data = f.read() + + matches = re.finditer(f"<(?P[ 0-9a-z]*)>", text_data, re.MULTILINE) + + bin_data = bytes() + for match in matches: + found = match.group('data') + bin_data += bytes(int(value, 16) for value in found.split(' ')) + + with patch('spsdk.mboot.interfaces.uart.Serial', SimpleReadSerialProxy.init_data_proxy(bin_data)): + with McuBoot(Uart(port='totally-legit-port')) as mboot: + # test valid use case + log = hab_audit_xip_app(CpuData.MIMXRT1020, mboot, read_log_only=True) + assert log[:4] != b'\xFF' * 4 + + +def test_get_hab_log_info(): + """Test `get_hab_log_info` function. """ + # checks the situation when hab log is valid + assert get_hab_log_info(b'\xAA\xBB\xCC\xDD') + # checks the situation when hab log is empty + assert not get_hab_log_info(None) + # checks the situation when flashloader is not accessible + assert not get_hab_log_info(b'\xFF\xFF\xFF\xFF') + + +def test_check_reserved_regions(): + """Test `check_reserved_region` function. """ + # checks the situation when parameter with reserved regions is empty + assert check_reserved_regions(0x20200000, None) + # checks the situation when hab log address is not in conflict + assert check_reserved_regions(0x20200000, [0x20100000, 0x20190000]) + # checks the situation when hab log address is in conflict + assert not check_reserved_regions(0x20200000, [0x20190000, 0x20230000]) diff --git a/tests/image/data/mbi/evkmimxrt595_hello_world.bin b/tests/image/mbi/data/evkmimxrt595_hello_world.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt595_hello_world.bin rename to tests/image/mbi/data/evkmimxrt595_hello_world.bin diff --git a/tests/image/data/mbi/evkmimxrt595_hello_world_ram.bin b/tests/image/mbi/data/evkmimxrt595_hello_world_ram.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt595_hello_world_ram.bin rename to tests/image/mbi/data/evkmimxrt595_hello_world_ram.bin diff --git a/tests/image/data/mbi/evkmimxrt595_hello_world_ram_crc_default_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt595_hello_world_ram_crc_default_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt595_hello_world_ram_crc_default_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt595_hello_world_ram_crc_default_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt595_hello_world_xip_crc_custom_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt595_hello_world_xip_crc_custom_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt595_hello_world_xip_crc_custom_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt595_hello_world_xip_crc_custom_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt595_hello_world_xip_crc_default_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt595_hello_world_xip_crc_default_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt595_hello_world_xip_crc_default_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt595_hello_world_xip_crc_default_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt595_hello_world_xip_crc_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt595_hello_world_xip_crc_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt595_hello_world_xip_crc_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt595_hello_world_xip_crc_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_hello_world.bin b/tests/image/mbi/data/evkmimxrt685_hello_world.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_hello_world.bin rename to tests/image/mbi/data/evkmimxrt685_hello_world.bin diff --git a/tests/image/data/mbi/evkmimxrt685_hello_world_xip_crc_custom_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_hello_world_xip_crc_custom_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_hello_world_xip_crc_custom_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_hello_world_xip_crc_custom_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_hello_world_xip_crc_default_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_hello_world_xip_crc_default_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_hello_world_xip_crc_default_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_hello_world_xip_crc_default_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_hello_world_xip_crc_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_hello_world_xip_crc_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_hello_world_xip_crc_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_hello_world_xip_crc_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_ram_encrypted2048_keystore_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_ram_encrypted2048_keystore_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_ram_encrypted2048_keystore_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_ram_encrypted2048_keystore_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_ram_encrypted2048_none_keystore_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_ram_encrypted2048_none_keystore_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_ram_encrypted2048_none_keystore_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_ram_encrypted2048_none_keystore_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_ram_encrypted2048_otp_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_ram_encrypted2048_otp_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_ram_encrypted2048_otp_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_ram_encrypted2048_otp_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_ram_key_store_signed2048_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_ram_key_store_signed2048_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_ram_key_store_signed2048_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_ram_key_store_signed2048_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_ram_signed2048_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_ram_signed2048_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_ram_signed2048_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_ram_signed2048_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_2_certs_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_2_certs_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_2_certs_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_2_certs_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_3_certs_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_3_certs_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_3_certs_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_3_certs_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_4_certs_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_4_certs_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_4_certs_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_4_certs_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_chain_2_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_chain_2_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_chain_2_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_chain_2_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_chain_3_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_chain_3_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_chain_3_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_chain_3_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_signed2048_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_signed2048_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_signed2048_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_signed2048_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_signed3072_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_signed3072_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_signed3072_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_signed3072_no_tz_mbi.bin diff --git a/tests/image/data/mbi/evkmimxrt685_testfffffff_xip_signed4096_no_tz_mbi.bin b/tests/image/mbi/data/evkmimxrt685_testfffffff_xip_signed4096_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/evkmimxrt685_testfffffff_xip_signed4096_no_tz_mbi.bin rename to tests/image/mbi/data/evkmimxrt685_testfffffff_xip_signed4096_no_tz_mbi.bin diff --git a/tests/image/data/mbi/key_store_rt6xx.bin b/tests/image/mbi/data/key_store_rt6xx.bin similarity index 100% rename from tests/image/data/mbi/key_store_rt6xx.bin rename to tests/image/mbi/data/key_store_rt6xx.bin diff --git a/tests/image/data/mbi/keys_and_certs/ca0_v3.der.crt b/tests/image/mbi/data/keys_and_certs/ca0_v3.der.crt similarity index 100% rename from tests/image/data/mbi/keys_and_certs/ca0_v3.der.crt rename to tests/image/mbi/data/keys_and_certs/ca0_v3.der.crt diff --git a/tests/image/data/mbi/keys_and_certs/ch3_crt2_v3.der.crt b/tests/image/mbi/data/keys_and_certs/ch3_crt2_v3.der.crt similarity index 100% rename from tests/image/data/mbi/keys_and_certs/ch3_crt2_v3.der.crt rename to tests/image/mbi/data/keys_and_certs/ch3_crt2_v3.der.crt diff --git a/tests/image/data/mbi/keys_and_certs/ch3_crt_v3.der.crt b/tests/image/mbi/data/keys_and_certs/ch3_crt_v3.der.crt similarity index 100% rename from tests/image/data/mbi/keys_and_certs/ch3_crt_v3.der.crt rename to tests/image/mbi/data/keys_and_certs/ch3_crt_v3.der.crt diff --git a/tests/image/data/mbi/keys_and_certs/crt2_privatekey_rsa2048.pem b/tests/image/mbi/data/keys_and_certs/crt2_privatekey_rsa2048.pem similarity index 100% rename from tests/image/data/mbi/keys_and_certs/crt2_privatekey_rsa2048.pem rename to tests/image/mbi/data/keys_and_certs/crt2_privatekey_rsa2048.pem diff --git a/tests/image/data/mbi/keys_and_certs/crt_privatekey_rsa2048.pem b/tests/image/mbi/data/keys_and_certs/crt_privatekey_rsa2048.pem similarity index 100% rename from tests/image/data/mbi/keys_and_certs/crt_privatekey_rsa2048.pem rename to tests/image/mbi/data/keys_and_certs/crt_privatekey_rsa2048.pem diff --git a/tests/image/data/mbi/keys_and_certs/crt_v3.der.crt b/tests/image/mbi/data/keys_and_certs/crt_v3.der.crt similarity index 100% rename from tests/image/data/mbi/keys_and_certs/crt_v3.der.crt rename to tests/image/mbi/data/keys_and_certs/crt_v3.der.crt diff --git a/tests/image/data/mbi/keys_and_certs/key_generation_scripts/ca0_and_crt.cmd b/tests/image/mbi/data/keys_and_certs/key_generation_scripts/ca0_and_crt.cmd similarity index 100% rename from tests/image/data/mbi/keys_and_certs/key_generation_scripts/ca0_and_crt.cmd rename to tests/image/mbi/data/keys_and_certs/key_generation_scripts/ca0_and_crt.cmd diff --git a/tests/image/data/mbi/keys_and_certs/key_generation_scripts/create_pem.cmd b/tests/image/mbi/data/keys_and_certs/key_generation_scripts/create_pem.cmd similarity index 100% rename from tests/image/data/mbi/keys_and_certs/key_generation_scripts/create_pem.cmd rename to tests/image/mbi/data/keys_and_certs/key_generation_scripts/create_pem.cmd diff --git a/tests/image/data/mbi/keys_and_certs/key_generation_scripts/openssl.cnf b/tests/image/mbi/data/keys_and_certs/key_generation_scripts/openssl.cnf similarity index 100% rename from tests/image/data/mbi/keys_and_certs/key_generation_scripts/openssl.cnf rename to tests/image/mbi/data/keys_and_certs/key_generation_scripts/openssl.cnf diff --git a/tests/image/data/mbi/keys_and_certs/key_generation_scripts/selfsign.cmd b/tests/image/mbi/data/keys_and_certs/key_generation_scripts/selfsign.cmd similarity index 100% rename from tests/image/data/mbi/keys_and_certs/key_generation_scripts/selfsign.cmd rename to tests/image/mbi/data/keys_and_certs/key_generation_scripts/selfsign.cmd diff --git a/tests/image/data/mbi/keys_and_certs/key_generation_scripts/v3_ca.ext b/tests/image/mbi/data/keys_and_certs/key_generation_scripts/v3_ca.ext similarity index 100% rename from tests/image/data/mbi/keys_and_certs/key_generation_scripts/v3_ca.ext rename to tests/image/mbi/data/keys_and_certs/key_generation_scripts/v3_ca.ext diff --git a/tests/image/data/mbi/keys_and_certs/key_generation_scripts/v3_noca.ext b/tests/image/mbi/data/keys_and_certs/key_generation_scripts/v3_noca.ext similarity index 100% rename from tests/image/data/mbi/keys_and_certs/key_generation_scripts/v3_noca.ext rename to tests/image/mbi/data/keys_and_certs/key_generation_scripts/v3_noca.ext diff --git a/tests/image/data/mbi/keys_and_certs/private_rsa3072.pem b/tests/image/mbi/data/keys_and_certs/private_rsa3072.pem similarity index 100% rename from tests/image/data/mbi/keys_and_certs/private_rsa3072.pem rename to tests/image/mbi/data/keys_and_certs/private_rsa3072.pem diff --git a/tests/image/data/mbi/keys_and_certs/private_rsa4096.pem b/tests/image/mbi/data/keys_and_certs/private_rsa4096.pem similarity index 100% rename from tests/image/data/mbi/keys_and_certs/private_rsa4096.pem rename to tests/image/mbi/data/keys_and_certs/private_rsa4096.pem diff --git a/tests/image/data/mbi/keys_and_certs/selfsign_2048_v3.der.crt b/tests/image/mbi/data/keys_and_certs/selfsign_2048_v3.der.crt similarity index 100% rename from tests/image/data/mbi/keys_and_certs/selfsign_2048_v3.der.crt rename to tests/image/mbi/data/keys_and_certs/selfsign_2048_v3.der.crt diff --git a/tests/image/data/mbi/keys_and_certs/selfsign_3072_v3.der.crt b/tests/image/mbi/data/keys_and_certs/selfsign_3072_v3.der.crt similarity index 100% rename from tests/image/data/mbi/keys_and_certs/selfsign_3072_v3.der.crt rename to tests/image/mbi/data/keys_and_certs/selfsign_3072_v3.der.crt diff --git a/tests/image/data/mbi/keys_and_certs/selfsign_4096_v3.der.crt b/tests/image/mbi/data/keys_and_certs/selfsign_4096_v3.der.crt similarity index 100% rename from tests/image/data/mbi/keys_and_certs/selfsign_4096_v3.der.crt rename to tests/image/mbi/data/keys_and_certs/selfsign_4096_v3.der.crt diff --git a/tests/image/data/mbi/keys_and_certs/selfsign_privatekey_rsa2048.pem b/tests/image/mbi/data/keys_and_certs/selfsign_privatekey_rsa2048.pem similarity index 100% rename from tests/image/data/mbi/keys_and_certs/selfsign_privatekey_rsa2048.pem rename to tests/image/mbi/data/keys_and_certs/selfsign_privatekey_rsa2048.pem diff --git a/tests/image/data/mbi/lpc55_crc_custom_tz.cmd b/tests/image/mbi/data/lpc55_crc_custom_tz.cmd similarity index 100% rename from tests/image/data/mbi/lpc55_crc_custom_tz.cmd rename to tests/image/mbi/data/lpc55_crc_custom_tz.cmd diff --git a/tests/image/data/mbi/lpc55_crc_custom_tz.json b/tests/image/mbi/data/lpc55_crc_custom_tz.json similarity index 100% rename from tests/image/data/mbi/lpc55_crc_custom_tz.json rename to tests/image/mbi/data/lpc55_crc_custom_tz.json diff --git a/tests/image/data/mbi/lpc55_crc_custom_tz_mbi.bin b/tests/image/mbi/data/lpc55_crc_custom_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/lpc55_crc_custom_tz_mbi.bin rename to tests/image/mbi/data/lpc55_crc_custom_tz_mbi.bin diff --git a/tests/image/data/mbi/lpc55_crc_deafult_tz.cmd b/tests/image/mbi/data/lpc55_crc_deafult_tz.cmd similarity index 100% rename from tests/image/data/mbi/lpc55_crc_deafult_tz.cmd rename to tests/image/mbi/data/lpc55_crc_deafult_tz.cmd diff --git a/tests/image/data/mbi/lpc55_crc_default_tz.json b/tests/image/mbi/data/lpc55_crc_default_tz.json similarity index 100% rename from tests/image/data/mbi/lpc55_crc_default_tz.json rename to tests/image/mbi/data/lpc55_crc_default_tz.json diff --git a/tests/image/data/mbi/lpc55_crc_default_tz_mbi.bin b/tests/image/mbi/data/lpc55_crc_default_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/lpc55_crc_default_tz_mbi.bin rename to tests/image/mbi/data/lpc55_crc_default_tz_mbi.bin diff --git a/tests/image/data/mbi/lpc55_crc_no_tz.cmd b/tests/image/mbi/data/lpc55_crc_no_tz.cmd similarity index 100% rename from tests/image/data/mbi/lpc55_crc_no_tz.cmd rename to tests/image/mbi/data/lpc55_crc_no_tz.cmd diff --git a/tests/image/data/mbi/lpc55_crc_no_tz.json b/tests/image/mbi/data/lpc55_crc_no_tz.json similarity index 100% rename from tests/image/data/mbi/lpc55_crc_no_tz.json rename to tests/image/mbi/data/lpc55_crc_no_tz.json diff --git a/tests/image/data/mbi/lpc55_crc_no_tz_mbi.bin b/tests/image/mbi/data/lpc55_crc_no_tz_mbi.bin similarity index 100% rename from tests/image/data/mbi/lpc55_crc_no_tz_mbi.bin rename to tests/image/mbi/data/lpc55_crc_no_tz_mbi.bin diff --git a/tests/image/data/lpc55xxA1.json b/tests/image/mbi/data/lpc55xxA1.json similarity index 100% rename from tests/image/data/lpc55xxA1.json rename to tests/image/mbi/data/lpc55xxA1.json diff --git a/tests/image/data/mbi/lpcxpresso55s69_led_blinky.bin b/tests/image/mbi/data/lpcxpresso55s69_led_blinky.bin similarity index 100% rename from tests/image/data/mbi/lpcxpresso55s69_led_blinky.bin rename to tests/image/mbi/data/lpcxpresso55s69_led_blinky.bin diff --git a/tests/image/data/mbi/multicore/expected_output.bin b/tests/image/mbi/data/multicore/expected_output.bin similarity index 100% rename from tests/image/data/mbi/multicore/expected_output.bin rename to tests/image/mbi/data/multicore/expected_output.bin diff --git a/tests/image/data/mbi/multicore/how_create_expected_output.txt b/tests/image/mbi/data/multicore/how_create_expected_output.txt similarity index 100% rename from tests/image/data/mbi/multicore/how_create_expected_output.txt rename to tests/image/mbi/data/multicore/how_create_expected_output.txt diff --git a/tests/image/data/mbi/multicore/normal_boot.bin b/tests/image/mbi/data/multicore/normal_boot.bin similarity index 100% rename from tests/image/data/mbi/multicore/normal_boot.bin rename to tests/image/mbi/data/multicore/normal_boot.bin diff --git a/tests/image/data/mbi/multicore/rt5xxA0.json b/tests/image/mbi/data/multicore/rt5xxA0.json similarity index 100% rename from tests/image/data/mbi/multicore/rt5xxA0.json rename to tests/image/mbi/data/multicore/rt5xxA0.json diff --git a/tests/image/data/mbi/multicore/special_boot.bin b/tests/image/mbi/data/multicore/special_boot.bin similarity index 100% rename from tests/image/data/mbi/multicore/special_boot.bin rename to tests/image/mbi/data/multicore/special_boot.bin diff --git a/tests/image/data/mbi/multicore/testfffffff.bin b/tests/image/mbi/data/multicore/testfffffff.bin similarity index 100% rename from tests/image/data/mbi/multicore/testfffffff.bin rename to tests/image/mbi/data/multicore/testfffffff.bin diff --git a/tests/image/data/mbi/rt5xxA0.json b/tests/image/mbi/data/rt5xxA0.json similarity index 100% rename from tests/image/data/mbi/rt5xxA0.json rename to tests/image/mbi/data/rt5xxA0.json diff --git a/tests/image/data/mbi/rt5xx_empty.json b/tests/image/mbi/data/rt5xx_empty.json similarity index 100% rename from tests/image/data/mbi/rt5xx_empty.json rename to tests/image/mbi/data/rt5xx_empty.json diff --git a/tests/image/data/mbi/rt5xx_few.json b/tests/image/mbi/data/rt5xx_few.json similarity index 100% rename from tests/image/data/mbi/rt5xx_few.json rename to tests/image/mbi/data/rt5xx_few.json diff --git a/tests/image/data/mbi/rt6xx_test.json b/tests/image/mbi/data/rt6xx_test.json similarity index 100% rename from tests/image/data/mbi/rt6xx_test.json rename to tests/image/mbi/data/rt6xx_test.json diff --git a/tests/image/data/mbi/testfffffff.bin b/tests/image/mbi/data/testfffffff.bin similarity index 100% rename from tests/image/data/mbi/testfffffff.bin rename to tests/image/mbi/data/testfffffff.bin diff --git a/tests/image/mbi/test_mbi.py b/tests/image/mbi/test_mbi.py index 93c62cf8..813f5cc8 100644 --- a/tests/image/mbi/test_mbi.py +++ b/tests/image/mbi/test_mbi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -24,10 +24,6 @@ # - usage of elftosb-gui is highly encouraged ################################################################# -@pytest.fixture(scope="module") -def data_dir(data_dir): - return os.path.join(data_dir, "mbi") - def certificate_block(data_dir, der_file_names, index=0, chain_der_file_names=None) -> CertBlockV2: """ diff --git a/tests/image/misc/test_format_value.py b/tests/image/misc/test_format_value.py index 9afbe66f..2e6ddbef 100644 --- a/tests/image/misc/test_format_value.py +++ b/tests/image/misc/test_format_value.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause - +import io import pytest -from spsdk.image.misc import format_value +from spsdk.image.misc import format_value, read_raw_data, NotEnoughBytesException @pytest.mark.parametrize( @@ -20,3 +20,13 @@ ) def test_format_value(value, size, expected): assert format_value(value, size) == expected + + +def test_read_raw_segment(): + stream = io.BytesIO() + with pytest.raises(ValueError): + read_raw_data(stream, length=0, index=-1) + with pytest.raises(ValueError): + read_raw_data(stream, length=-1, index=1) + with pytest.raises(NotEnoughBytesException): + read_raw_data(stream, length=1, index=1) \ No newline at end of file diff --git a/tests/image/data/SRK1_sha256_4096_65537_v3_ca_crt.pem b/tests/image/secret/data/SRK1_sha256_4096_65537_v3_ca_crt.pem similarity index 100% rename from tests/image/data/SRK1_sha256_4096_65537_v3_ca_crt.pem rename to tests/image/secret/data/SRK1_sha256_4096_65537_v3_ca_crt.pem diff --git a/tests/image/data/SRK2_sha256_4096_65537_v3_ca_crt.pem b/tests/image/secret/data/SRK2_sha256_4096_65537_v3_ca_crt.pem similarity index 100% rename from tests/image/data/SRK2_sha256_4096_65537_v3_ca_crt.pem rename to tests/image/secret/data/SRK2_sha256_4096_65537_v3_ca_crt.pem diff --git a/tests/image/data/SRK3_sha256_4096_65537_v3_ca_crt.pem b/tests/image/secret/data/SRK3_sha256_4096_65537_v3_ca_crt.pem similarity index 100% rename from tests/image/data/SRK3_sha256_4096_65537_v3_ca_crt.pem rename to tests/image/secret/data/SRK3_sha256_4096_65537_v3_ca_crt.pem diff --git a/tests/image/data/SRK4_sha256_4096_65537_v3_ca_crt.pem b/tests/image/secret/data/SRK4_sha256_4096_65537_v3_ca_crt.pem similarity index 100% rename from tests/image/data/SRK4_sha256_4096_65537_v3_ca_crt.pem rename to tests/image/secret/data/SRK4_sha256_4096_65537_v3_ca_crt.pem diff --git a/tests/image/data/SRK_1_2_3_4_fuse.bin b/tests/image/secret/data/SRK_1_2_3_4_fuse.bin similarity index 100% rename from tests/image/data/SRK_1_2_3_4_fuse.bin rename to tests/image/secret/data/SRK_1_2_3_4_fuse.bin diff --git a/tests/image/data/SRK_1_2_3_4_table.bin b/tests/image/secret/data/SRK_1_2_3_4_table.bin similarity index 100% rename from tests/image/data/SRK_1_2_3_4_table.bin rename to tests/image/secret/data/SRK_1_2_3_4_table.bin diff --git a/tests/image/data/SRK_1_2_H3_H4_table.bin b/tests/image/secret/data/SRK_1_2_H3_H4_table.bin similarity index 100% rename from tests/image/data/SRK_1_2_H3_H4_table.bin rename to tests/image/secret/data/SRK_1_2_H3_H4_table.bin diff --git a/tests/image/data/SRK_prime256v1_fuse.bin b/tests/image/secret/data/SRK_prime256v1_fuse.bin similarity index 100% rename from tests/image/data/SRK_prime256v1_fuse.bin rename to tests/image/secret/data/SRK_prime256v1_fuse.bin diff --git a/tests/image/data/SRK_prime256v1_table.bin b/tests/image/secret/data/SRK_prime256v1_table.bin similarity index 100% rename from tests/image/data/SRK_prime256v1_table.bin rename to tests/image/secret/data/SRK_prime256v1_table.bin diff --git a/tests/image/secret/data/ecc.crt b/tests/image/secret/data/ecc.crt new file mode 100644 index 00000000..0f204926 --- /dev/null +++ b/tests/image/secret/data/ecc.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBwDCCAWWgAwIBAgICAwkwCgYIKoZIzj0EAwIwUzEMMAoGA1UEAwwDT05FMQsw +CQYDVQQGEwJQTDELMAkGA1UEBwwCS1IxDTALBgNVBAgMBE1MUEsxDDAKBgNVBAkM +A0JPVDEMMAoGA1UECgwDUEtPMB4XDTIxMDIwNDE4MDE0N1oXDTI0MDYyMjE4MDE0 +N1owZjEMMAoGA1UEAwwDVFdPMQswCQYDVQQGEwJQTDENMAsGA1UEBwwETElNQTEN +MAsGA1UECAwETUxQSzENMAsGA1UECQwEU1RPVzEMMAoGA1UECgwDSUJNMQ4wDAYD +VQQRDAUzMTUwMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABG4TfhuUc0mefz/C +BnkWqqampaGMJFtCEotu8vuuiN86grKP16b4jK1GXF+hUTxeW3cjGQWXbTUXmyS/ +fK9VX/ijFjAUMBIGA1UdEwEB/wQIMAYBAf8CAQUwCgYIKoZIzj0EAwIDSQAwRgIh +APnnYKCYedRs2Ln5T5uDlpCCKEdLIWpGBsYqOhC1eaQiAiEAgcmpg9cvSy3EBlQT +joy6xfm0bVDsDz7llsstNFLbFA8= +-----END CERTIFICATE----- diff --git a/tests/image/secret/test_sec_api.py b/tests/image/secret/test_sec_api.py index 43ef1e81..7f12f479 100644 --- a/tests/image/secret/test_sec_api.py +++ b/tests/image/secret/test_sec_api.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -11,7 +11,9 @@ from cryptography import x509 from cryptography.hazmat.backends import default_backend from spsdk.image import SrkTable, SrkItem, MAC, Signature, CertificateImg, SecretKeyBlob -from spsdk.image.secret import NotImplementedSRKPublicKeyType +from spsdk.image.secret import NotImplementedSRKPublicKeyType, NotImplementedSRKItem, SrkItemRSA, \ + NotImplementedSRKCertificate, SrkItemHash +from spsdk.crypto.loaders import load_certificate @pytest.fixture(scope="module", name="srk_pem") @@ -195,3 +197,27 @@ def test_keyblob_export_parse(): packed_key = tested_key.export() unpacked = tested_key.parse(packed_key) assert unpacked == tested_key + + +def test_srktable_parse_not_valid_header(): + srkitem_rsa = SrkItemRSA(modulus=bytes(2048), exponent=bytes(4)) + srkitem_rsa._header._tag = 0xFF + + srkitem_rsa_out = srkitem_rsa.export() + with pytest.raises(NotImplementedSRKItem): + SrkItem.parse(srkitem_rsa_out) + + +def test_srktable_from_certificate_ecc(data_dir): + certificate = load_certificate(os.path.join(data_dir, 'ecc.crt')) + + with pytest.raises(NotImplementedSRKCertificate): + SrkItem.from_certificate(certificate) + + +def test_srkitemhash_parse_not_valid_header(): + srkhash = SrkItemHash(algorithm=0x17, digest=bytes(0x10)) + srkhash._header.param = 0x88 + srkhash_out = srkhash.export() + with pytest.raises(NotImplementedSRKItem): + SrkItemHash.parse(srkhash_out) diff --git a/tests/image/data/dcd/IMXRT1050-EVKB.mex b/tests/image/segments/data/IMXRT1050-EVKB.mex similarity index 100% rename from tests/image/data/dcd/IMXRT1050-EVKB.mex rename to tests/image/segments/data/IMXRT1050-EVKB.mex diff --git a/tests/image/data/dcd/dcd.bin b/tests/image/segments/data/dcd.bin similarity index 100% rename from tests/image/data/dcd/dcd.bin rename to tests/image/segments/data/dcd.bin diff --git a/tests/image/data/dcd/dcd.txt b/tests/image/segments/data/dcd.txt similarity index 100% rename from tests/image/data/dcd/dcd.txt rename to tests/image/segments/data/dcd.txt diff --git a/tests/image/data/fastauth.csf.bin b/tests/image/segments/data/fastauth.csf.bin similarity index 100% rename from tests/image/data/fastauth.csf.bin rename to tests/image/segments/data/fastauth.csf.bin diff --git a/tests/image/data/rt105x_flex_spi.fcb b/tests/image/segments/data/rt105x_flex_spi.fcb similarity index 100% rename from tests/image/data/rt105x_flex_spi.fcb rename to tests/image/segments/data/rt105x_flex_spi.fcb diff --git a/tests/image/segments/test_csf.py b/tests/image/segments/test_csf.py index 7094e900..9d6b98b5 100644 --- a/tests/image/segments/test_csf.py +++ b/tests/image/segments/test_csf.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause diff --git a/tests/image/segments/test_csf_api.py b/tests/image/segments/test_csf_api.py index fafa5887..d691f88e 100644 --- a/tests/image/segments/test_csf_api.py +++ b/tests/image/segments/test_csf_api.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2017-2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause diff --git a/tests/image/segments/test_dcd_api.py b/tests/image/segments/test_dcd_api.py index 6b79882a..5fd8b6a5 100644 --- a/tests/image/segments/test_dcd_api.py +++ b/tests/image/segments/test_dcd_api.py @@ -2,7 +2,7 @@ # -*- coding: UTF-8 -*- # # Copyright 2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -11,11 +11,6 @@ from spsdk.image import SegDCD, CmdWriteData, CmdCheckData, CmdNop, EnumWriteOps, EnumCheckOps -@pytest.fixture(scope="module") -def data_dir(data_dir): - return os.path.join(data_dir, "dcd") - - @pytest.fixture(scope="module") def ref_dcd_obj(): # Prepare reference DCD object diff --git a/tests/image/segments/test_ivt.py b/tests/image/segments/test_ivt.py index 7da5a790..4fa6418a 100644 --- a/tests/image/segments/test_ivt.py +++ b/tests/image/segments/test_ivt.py @@ -2,13 +2,14 @@ # -*- coding: UTF-8 -*- # # Copyright 2018 Martin Olejar -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause import pytest from spsdk.image import SegIVT2, SegAPP, SegIVT3a, SegIVT3b +from spsdk.image.segments import _format_ivt_item def test_ivt2_segment_api(): @@ -155,7 +156,7 @@ def test_ivt3a_eq(): def test_ivt3a_info(): ivt3a = SegIVT3a(0) output = ivt3a.info() - repr_strings = ["VER", "IVT", "BDT", "DCD", "CSF", "NEXT"] + repr_strings = ["Format version", "IVT", "BDT", "DCD", "CSF", "NEXT"] for req_string in repr_strings: assert req_string in output, f'string {req_string} is not in the output: {output}' @@ -230,3 +231,11 @@ def test_ivt3b_info(): repr_strings = ["IVT", "BDT", "DCD", "CSF", "SCD"] for req_string in repr_strings: assert req_string in output, f'string {req_string} is not in the output: {output}' + + +def test_format_ivt_item(): + assert _format_ivt_item(0x123) == "0x00000123" + assert _format_ivt_item(0xabcdef) == "0x00abcdef" + assert _format_ivt_item(0) == "none" + assert _format_ivt_item(0x10, 2) == "0x10" + assert _format_ivt_item(0x12, 3) == "0x012" diff --git a/tests/image/data/mbi/lpc55xxA1.json b/tests/image/trustzone/data/lpc55xxA1.json similarity index 100% rename from tests/image/data/mbi/lpc55xxA1.json rename to tests/image/trustzone/data/lpc55xxA1.json diff --git a/tests/image/data/lpc55xxA1_tzFile.bin b/tests/image/trustzone/data/lpc55xxA1_tzFile.bin similarity index 100% rename from tests/image/data/lpc55xxA1_tzFile.bin rename to tests/image/trustzone/data/lpc55xxA1_tzFile.bin diff --git a/tests/mboot/blhost/test_blhost_cli.py b/tests/mboot/blhost/test_blhost_cli.py index 211f79c9..f0bf5142 100644 --- a/tests/mboot/blhost/test_blhost_cli.py +++ b/tests/mboot/blhost/test_blhost_cli.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -29,7 +29,11 @@ b'\x5a\xa1\x5a\xa4\x0c\x00\x65\x1c\xa7\x00\x00\x02\x00\x00\x00\x00\x00\x00\x03\x4b', # efuse-read-one b'\x5a\xa4\x0c\x00\x14\x27\x0f\x00\x00\x02\x64\x00\x00\x00\x04\x00\x00\x00': - b'\x5a\xa1\x5a\xa4\x10\x00\xc5\xbf\xaf\x00\x00\x03\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00' + b'\x5a\xa1\x5a\xa4\x10\x00\xc5\xbf\xaf\x00\x00\x03\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00', + + # efuse-read-one 0x98 with unknown error code 0xbeef (48,879) + b'\x5a\xa4\x0c\x00\x2e\x7b\x0f\x00\x00\x02\x98\x00\x00\x00\x04\x00\x00\x00': + b'\x5a\xa1\x5a\xa4\x0c\x00\x36\xc2\xaf\x00\x00\x02\xef\xbe\x00\x00\x00\x00\x00\x00', } def test_version(): @@ -62,3 +66,14 @@ def test_efuse_read_once(caplog): assert result.exit_code == 0 assert 'Response word 1 = 4 (0x4)' in result.output assert 'Response word 2 = 0 (0x0)' in result.output + + +def test_efuse_read_once_unknown_error(caplog): + caplog.set_level(100_000) + runner = CliRunner() + cmd = '-p super-com efuse-read-once 0x98' + with patch('spsdk.mboot.interfaces.uart.Serial', SerialProxy.init_proxy(data_responses)): + result = runner.invoke(blhost.main, cmd.split()) + assert result.exit_code == 0 + assert 'Response word 1 = 4 (0x4)' not in result.output + assert 'Unknown error code' in result.output diff --git a/tests/mboot/blhost/test_blhost_utils.py b/tests/mboot/blhost/test_blhost_utils.py new file mode 100644 index 00000000..347a8a04 --- /dev/null +++ b/tests/mboot/blhost/test_blhost_utils.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Testing utilities for the BLHost application.""" +import pytest + +from spsdk.apps.blhost_helper import parse_property_tag + + +@pytest.mark.parametrize( + 'input,expected', + [ + ('1', 1), ('0xa', 10), ('0b100', 4), + ('list-properties', 0), ('target-version', 24), + ('abc', 0xFF), ('012', 0xFF), ('some-nonsense', 0xFF) + ] +) +def test_parse_property_tag(input, expected): + actual = parse_property_tag(input) + assert actual == expected diff --git a/tests/mboot/devices/virtual_device.yaml b/tests/mboot/devices/virtual_device.yaml index 367b6ca6..d64a9b3e 100644 --- a/tests/mboot/devices/virtual_device.yaml +++ b/tests/mboot/devices/virtual_device.yaml @@ -10,16 +10,21 @@ Properties: FlashPageSize: 10 MaxPacketSize: 1024 AvailableCommands: + - 'Call' + - 'ConfigureMemory' + - 'Execute' - 'FlashEraseAll' - 'FlashEraseRegion' - 'ReadMemory' - 'WriteMemory' - 'FillMemory' + - 'NoCommand' - 'GetProperty' - 'FlashSecurityDisable' - 'Reset' - 'GenerateKeyBlob' - 'KeyProvisioning' + - 'ReceiveSBFile' RamStartAddress: 0x20000000 RamSize: 32000 diff --git a/tests/mboot/test_mboot_api.py b/tests/mboot/test_mboot_api.py index 92e0813e..0c5f7ccb 100644 --- a/tests/mboot/test_mboot_api.py +++ b/tests/mboot/test_mboot_api.py @@ -1,13 +1,14 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause import pytest from spsdk.mboot.mcuboot import PropertyTag, StatusCode, McuBootConnectionError, CmdPacket, CommandTag, ExtMemId -from spsdk.mboot.mcuboot import KeyProvUserKeyType +from spsdk.mboot.mcuboot import KeyProvUserKeyType, McuBoot +from spsdk.mboot.error_codes import StatusCode def test_class(mcuboot, target, config): @@ -42,6 +43,17 @@ def test_cmd_read_memory(mcuboot, target): assert len(data) == 1000 +def test_cmd_read_memory_data_abort(mcuboot, target): + mcuboot._device.fail_step = StatusCode.FLASH_OUT_OF_DATE_CFPA_PAGE + mcuboot.read_memory(0, 1000) + assert mcuboot.status_code == StatusCode.FLASH_OUT_OF_DATE_CFPA_PAGE + + +def test_cmd_read_memory_timeout(mcuboot, target): + mcuboot._device.fail_step = 0 + with pytest.raises(McuBootConnectionError): + mcuboot.read_memory(0, 100) + def test_cmd_write_memory(mcuboot, target): data = b'\x00' * 100 assert mcuboot.write_memory(0, data) @@ -72,18 +84,29 @@ def test_cmd_set_property(mcuboot, target): def test_cmd_receive_sb_file(mcuboot, target): - assert not mcuboot.receive_sb_file(b'\x00' * 1000) - assert mcuboot.status_code == StatusCode.UNKNOWN_COMMAND + mcuboot._device.fail_step = None + assert mcuboot.receive_sb_file(bytes(1000)) + assert mcuboot.status_code == StatusCode.SUCCESS + mcuboot._device.fail_step = StatusCode.ROMLDR_SIGNATURE + assert not mcuboot.receive_sb_file(bytes(1000)) + assert mcuboot.status_code == StatusCode.ROMLDR_SIGNATURE + def test_cmd_execute(mcuboot, target): assert not mcuboot.execute(0, 0, 0) - assert mcuboot.status_code == StatusCode.UNKNOWN_COMMAND + assert mcuboot.status_code == StatusCode.FAIL + + assert mcuboot.execute(0x123, 0x0, 0x100) + assert mcuboot.status_code == StatusCode.SUCCESS def test_cmd_call(mcuboot, target): assert not mcuboot.call(0, 0) - assert mcuboot.status_code == StatusCode.UNKNOWN_COMMAND + assert mcuboot.status_code == StatusCode.FAIL + + assert mcuboot.call(0x600, 0) + assert mcuboot.status_code == StatusCode.SUCCESS def test_cmd_reset_no_reopen(mcuboot, target): @@ -229,3 +252,17 @@ def test_cmd_key_provisioning_read_key_store(mcuboot, target): data = mcuboot.kp_read_key_store() assert data is None + +def test_cmd_configure_memory(mcuboot, target): + response = mcuboot.configure_memory(address=0x100, mem_id=0) + assert response is True + + response = mcuboot.configure_memory(address=0x100, mem_id=2) + assert response is False + response = mcuboot.configure_memory(address=0x100, mem_id=1234) + assert response is False + + +def test_load_image(mcuboot, target): + assert mcuboot.load_image(bytes(1000)) + mcuboot.status_code == StatusCode.SUCCESS diff --git a/tests/mboot/test_properties.py b/tests/mboot/test_properties.py index eed8c0cd..a2b48458 100644 --- a/tests/mboot/test_properties.py +++ b/tests/mboot/test_properties.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -10,6 +10,7 @@ AvailableCommandsValue, AvailablePeripheralsValue, ExternalMemoryAttributesValue, \ DeviceUidValue, FlashReadMargin, IrqNotifierPinValue, PfrKeystoreUpdateOpt, \ parse_property_value, PropertyTag +from spsdk.mboot.commands import CommandTag def test_version_class(): @@ -89,3 +90,11 @@ def test_device_uid_value(): assert value.desc == PropertyTag.desc(PropertyTag.UNIQUE_DEVICE_IDENT) assert value.value == 0x4B0001024B000102 assert value.to_str() == '4B0001024B000102' + + +def test_available_commands(): + value = parse_property_value(PropertyTag.AVAILABLE_COMMANDS, [0xF]) + assert value.tags == [1, 2, 3, 4] + assert all(index in value for index in [1, 2, 3, 4]) + command_names = [CommandTag.name(i) for i in [1, 2, 3, 4]] + assert all(name in value.to_str() for name in command_names) diff --git a/tests/mboot/virtual_device.py b/tests/mboot/virtual_device.py index b62a8f6a..45c62501 100644 --- a/tests/mboot/virtual_device.py +++ b/tests/mboot/virtual_device.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -13,7 +13,8 @@ from spsdk.mboot.commands import CommandTag, CmdPacket, ResponseTag, parse_cmd_response, KeyProvOperation from spsdk.mboot.error_codes import StatusCode from spsdk.mboot.interfaces import Interface - +from spsdk.mboot.memories import ExtMemId +from spsdk.mboot.exceptions import McuBootDataAbortError ######################################################################################################################## # Helper functions @@ -31,6 +32,21 @@ def set_error_code(step_index: int, fail_step: int) -> int: ######################################################################################################################## # Commands functions ######################################################################################################################## +def cmd_call(*args, **kwargs): + assert len(args) == 2 + address, _ = args + status = StatusCode.FAIL if address == 0 else StatusCode.SUCCESS + return pack_response(ResponseTag.GENERIC, status, CommandTag.CALL) + + +def cmd_configure_memory(*args, **kwargs): + assert len(args) == 2 + memory_id, address = args + assert address >= 0 + status = StatusCode.FAIL if memory_id not in ExtMemId.tags() + [0] else StatusCode.SUCCESS + return pack_response(ResponseTag.GENERIC, status, CommandTag.CONFIGURE_MEMORY) + + def cmd_flash_erase_all(*args, **_kwargs): assert len(args) == 1 # TODO remove unused code: mem_id = args[0] @@ -53,11 +69,30 @@ def cmd_flash_erase_region(*args, **kwargs): return pack_response(ResponseTag.GENERIC, status, CommandTag.FLASH_ERASE_ALL) +def cmd_execute(*args, **kwargs): + assert len(args) == 3 + address, arg, _ = args + status = StatusCode.SUCCESS if arg < address else StatusCode.FAIL + return pack_response(ResponseTag.GENERIC, status, CommandTag.EXECUTE) + + def cmd_read_memory(*args, **kwargs): assert len(args) == 3 address, length, mem_id = args cfg = kwargs['config'] response_index = kwargs['index'] + fail_step = kwargs['fail_step'] + caller = kwargs['full_ref'] + + if fail_step is not None: + if response_index == 0: + return pack_response(ResponseTag.READ_MEMORY, StatusCode.SUCCESS, 0) + if response_index == 1: + caller._response_index += 1 + error = McuBootDataAbortError if fail_step else TimeoutError + raise error() + return pack_response(ResponseTag.GENERIC, fail_step, CommandTag.READ_MEMORY) + if response_index == 0: # TODO: check arguments return pack_response(ResponseTag.READ_MEMORY, StatusCode.SUCCESS, length) @@ -92,6 +127,11 @@ def cmd_flash_security_disable(*args, **kwargs): return pack_response(ResponseTag.GENERIC, StatusCode.SUCCESS, CommandTag.FLASH_SECURITY_DISABLE) +def cmd_load_image(*args, **kwargs): + assert len(args) == 1 + return pack_response(ResponseTag.GENERIC, StatusCode.SUCCESS, 0) + + def cmd_get_property(*args, **kwargs): assert len(args) == 2 cfg = kwargs['config'] @@ -109,22 +149,20 @@ def cmd_set_property(*args, **kwargs): return pack_response(ResponseTag.GENERIC, StatusCode.SUCCESS, tag) -@pytest.mark.skip def cmd_receive_sb_file(*args, **kwargs): - # TODO implement the test - pass - - -@pytest.mark.skip -def cmd_execute(*args, **kwargs): - # TODO implement the test - pass - - -@pytest.mark.skip -def cmd_call(*args, **kwargs): - # TODO implement the test - pass + assert len(args) == 1 + response_index = kwargs['index'] + fail_step = kwargs['fail_step'] + caller = kwargs['full_ref'] + if not fail_step: + return pack_response(ResponseTag.GENERIC, StatusCode.SUCCESS, CommandTag.RECEIVE_SB_FILE) + # introducing failures + if response_index == 0: + return pack_response(ResponseTag.GENERIC, StatusCode.SUCCESS, CommandTag.RECEIVE_SB_FILE) + if response_index == 1: + caller._response_index += 1 + raise McuBootDataAbortError() + return pack_response(ResponseTag.GENERIC, fail_step, CommandTag.RECEIVE_SB_FILE) def cmd_reset(*args, **kwargs): @@ -200,11 +238,16 @@ def cmd_key_provisioning(*args, index, fail_step, **kwargs): return response +def cmd_no_command(*args, **kwargs): + return pack_response(ResponseTag.GENERIC, StatusCode.SUCCESS, CommandTag.NO_COMMAND) + + ######################################################################################################################## # Virtual Device Class ######################################################################################################################## class VirtualDevice(Interface): CMD = { + CommandTag.NO_COMMAND: cmd_no_command, CommandTag.FLASH_ERASE_ALL: cmd_flash_erase_all, CommandTag.FLASH_ERASE_REGION: cmd_flash_erase_region, CommandTag.READ_MEMORY: cmd_read_memory, @@ -221,7 +264,7 @@ class VirtualDevice(Interface): CommandTag.FLASH_PROGRAM_ONCE: None, CommandTag.FLASH_READ_ONCE: None, CommandTag.FLASH_READ_RESOURCE: None, - CommandTag.CONFIGURE_MEMORY: None, + CommandTag.CONFIGURE_MEMORY: cmd_configure_memory, CommandTag.RELIABLE_UPDATE: None, CommandTag.GENERATE_KEY_BLOB: cmd_generate_keyblob, CommandTag.KEY_PROVISIONING: cmd_key_provisioning @@ -244,7 +287,7 @@ def __init__(self, config, **kwargs): self._cmd_data = bytes() self._response_index = 0 self._need_data_split = True - self.fail_step = False + self.fail_step = None def open(self): self._opened = True @@ -257,7 +300,8 @@ def read(self, timeout=1000): cmd, raw_data = self.CMD[self._cmd_tag](*self._cmd_params, index=self._response_index, config=self._dev_conf, - fail_step=self.fail_step) + fail_step=self.fail_step, + full_ref=self) self._response_index += 1 else: cmd, raw_data = pack_response(ResponseTag.GENERIC, StatusCode.UNKNOWN_COMMAND, self._cmd_tag) diff --git a/tests/mcu_examples/conftest.py b/tests/mcu_examples/conftest.py deleted file mode 100644 index 8aecc87a..00000000 --- a/tests/mcu_examples/conftest.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2019-2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -import pytest -from os import path - - -@pytest.fixture(scope="module") -def data_dir(): - return path.join(path.dirname(path.abspath(__file__)), 'data') diff --git a/tests/mcu_examples/data/rt102x/exec_hab_audit.bin b/tests/mcu_examples/data/rt102x/exec_hab_audit.bin deleted file mode 100644 index f5a9b645..00000000 Binary files a/tests/mcu_examples/data/rt102x/exec_hab_audit.bin and /dev/null differ diff --git a/tests/mcu_examples/data/rt105x/crts/CSF1_3_sha256_2048_65537_v3_usr_crt.der b/tests/mcu_examples/data/rt105x/crts/CSF1_3_sha256_2048_65537_v3_usr_crt.der deleted file mode 100644 index 9df7eeec..00000000 Binary files a/tests/mcu_examples/data/rt105x/crts/CSF1_3_sha256_2048_65537_v3_usr_crt.der and /dev/null differ diff --git a/tests/mcu_examples/data/rt105x/crts/CSF1_3_sha256_2048_65537_v3_usr_crt.pem b/tests/mcu_examples/data/rt105x/crts/CSF1_3_sha256_2048_65537_v3_usr_crt.pem deleted file mode 100644 index ef399be2..00000000 --- a/tests/mcu_examples/data/rt105x/crts/CSF1_3_sha256_2048_65537_v3_usr_crt.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC3zCCAcegAwIBAgIEGZmZpzANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxT -UksxX3NoYTI1Nl8yMDQ4XzY1NTM3X3YzX2NhMB4XDTIwMDUyNjE0MzYwMFoXDTMw -MDIyMzE0MzYwMFowKjEoMCYGA1UEAwwfQ1NGMV8zX3NoYTI1Nl8yMDQ4XzY1NTM3 -X3YzX3VzcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJh37Jm+nLg/ -OjKjRY0sUbQwEgSkNMWNPMVa4RNTfrecgkZ+po0LxIZ2OIM6ahK6ntYbXjB6VyN4 -1Nu1YqUH4ziIaiadDsBy7dNEw37X5zXXAJPNCB/eYlgYit9k5BrZcOr2GXUnw8oE -nG5YPkLVChve/Pj3bayfH3lft5i2Hp17A46ZlBdwpHN7A9NbKcGugvCq7pPsdjtY -wkW8fpNPATj28DRtuU2CEVI7EUE45WcAv1LKdyGFDjwC/Y/G33wdfZ1BCoEPoiD7 -Nr2P14GtwBcBur7dhyDC2qnGQaipbsQHZgni6nCWGp71N0IE34x7F7g8gBIeqK4y -ZB6b+bc1g7kCAwEAAaMQMA4wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOC -AQEAk3HgF7uHmiFlBKUYgGVpeHJz2+nYDI1cjkRDSAXh4xXQ/O5Cs2mqs5fPCmJF -L9X9YDNLH+0pgM6g0gpyu5OlZgOSgOQfxYuo0saBrxI07aQ0mGyG6FQ6MetRraZ3 -9e9MEksi8cHzprImdoMOz6c3Ncr0y6lhCvC9ez5N9iUvSDbGcpCePi0P8clTV3I1 -vy2dGWYZa1QFhijScDxeicAZ2fEmQKuv3V4IesfQ76IYE0/G9Xcb1Rcc2rBC+c20 -BUMT7eZucYa7mOy9P8XU70NTuKG0UqpTQnrj3Mvo1xu9jxfwkSuZvRkpP/IOTgkK -Ue1Sj9L8IrDu87ITrtg+mZiOjg== ------END CERTIFICATE----- diff --git a/tests/mcu_examples/data/rt105x/crts/IMG1_3_sha256_2048_65537_v3_usr_crt.der b/tests/mcu_examples/data/rt105x/crts/IMG1_3_sha256_2048_65537_v3_usr_crt.der deleted file mode 100644 index 17801ba7..00000000 Binary files a/tests/mcu_examples/data/rt105x/crts/IMG1_3_sha256_2048_65537_v3_usr_crt.der and /dev/null differ diff --git a/tests/mcu_examples/data/rt105x/crts/IMG1_3_sha256_2048_65537_v3_usr_crt.pem b/tests/mcu_examples/data/rt105x/crts/IMG1_3_sha256_2048_65537_v3_usr_crt.pem deleted file mode 100644 index 37f575d2..00000000 --- a/tests/mcu_examples/data/rt105x/crts/IMG1_3_sha256_2048_65537_v3_usr_crt.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC3zCCAcegAwIBAgIEGZmZpzANBgkqhkiG9w0BAQsFADAnMSUwIwYDVQQDDBxT -UksxX3NoYTI1Nl8yMDQ4XzY1NTM3X3YzX2NhMB4XDTIwMDUyNjE0MzYwMFoXDTMw -MDIyMzE0MzYwMFowKjEoMCYGA1UEAwwfSU1HMV8zX3NoYTI1Nl8yMDQ4XzY1NTM3 -X3YzX3VzcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANiYmUgVYJHd -EPNC8VsgMjyEU5F58LMNQFD0AkcBpmEVWI0j1oPaIG8Eu5ydusS0nl0bL70fmnpj -BPBtduh4vOfvtblGYtVUylBbBl7Ol4amI3t+ei9I7icoI6CD72eQW2iGFwlvFF35 -/cifFojtISXoTPs4h2x6O7ygjJ1TmpyP8FF8zIeVBzzXe3E0SDvzRbIO//jjfG7V -ZHv/1pqKrNQHN3cMQFZLOnlIUJv3VOqDD5lcJ2mrAeP05y52XhbC3JrltIz71zAz -pu7YKMcS21Vhk0aS5f2i3qQ0xBLx4rbjg9XMhKc1dBg4WJzIB3tQDzNxrGyNtYfZ -EQg5rId5es0CAwEAAaMQMA4wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOC -AQEAeOnHTF5Jck8N8+YwGsA2ggnVIfNb96TJBEo5JUzvKPyWbnfIToejNBHb+XMZ -BYRtQ5MddCIngEQiQlKNyjGd+UKqvzz0SBmaKNfe95TvRgzElCm9niYyjMOzsvoN -Cl0kqj69/5F0HBkCtyODAhpRign2gfPp0dW4BgOJss+EWI28WL4nvSpElqkoTJpn -5/sGlzUYBOt0wkZAMeWLEd/phrC9lT79wbKlJjs6mtN6yXymDEUt25dWSAmBCnoi -tl1rtMw/+8TIv0JpWduEhb+4rSOFj8lxV6Azx9Z4Rv5G6UkUGy2bhykyab3pKXcJ -mbW1ZhWTX8fflK0rL6agtqaiDg== ------END CERTIFICATE----- diff --git a/tests/mcu_examples/data/rt105x/debug_logs/evkbimxrt1050_iled_blinky_ext_FLASH_unsigned_nofcb.sb.txt b/tests/mcu_examples/data/rt105x/debug_logs/evkbimxrt1050_iled_blinky_ext_FLASH_unsigned_nofcb.sb.txt deleted file mode 100644 index 20a5abab..00000000 --- a/tests/mcu_examples/data/rt105x/debug_logs/evkbimxrt1050_iled_blinky_ext_FLASH_unsigned_nofcb.sb.txt +++ /dev/null @@ -1,35 +0,0 @@ -[SB-FILE-1.x] -[SB-file-Header] -[digest] -hex=00ef9e65a005e171d48bbb3110f4cc8c697ad893 -len=20=0x14 -[attrs] -hex=53544d500102000031040000070000000000000000000700060001000100db007367746c006b3edb094702000999000009990000099900000999000009990000099900000000767af4810b86 -len=76=0x4c -[Sections-Header-Table] -Section-Header-Item=00000000080000002704000001000000 -[Sections] -[Section] -Section-header=88010100000000002704000001000000 -[Commands] -[Command:FILL] -cmd-header=9b0300000020000004000000073023c0 -[Command:MEM_ENABLE] -cmd-header=90090009002000000400000000000000 -[Command:ERASE] -cmd-header=21070000000000600060000000000000 -[Command:FILL] -cmd-header=8003000000200000040000000f0000f0 -[Command:MEM_ENABLE] -cmd-header=90090009002000000400000000000000 -[Command:LOAD] -cmd-header=2f0200000010006010420000f2f7d157 -[load-data] -hex=d1002040052300600000000000000000201000600010006000000000000000000000006008520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002200523006081230060214700600000000000000000000000000000000000000000000000000000000085230060000000000000000087230060192500606142006069420060714200607942006081420060894200609142006099420060a1420060a9420060b1420060b9420060c1420060c9420060d1420060d9420060e1420060e9420060f1420060f94200600143006009430060114300601943006021430060294300603143006039430060414300604943006051430060594300606143006069430060714300607943006081430060894300609143006099430060a1430060a9430060b1430060b9430060c1430060c9430060d1430060d9430060e1430060e9430060f1430060f94300600144006009440060114400601944006021440060294400603144006039440060414400604944006051440060594400606144006069440060714400607944006081440060894400609144006099440060a1440060a9440060b1440060b9440060c1440060c9440060d1440060d9440060e1440060e9440060f1440060f94400600145006009450060114500601945006021450060294500603145006039450060414500604945006051450060594500606145006069450060714500607945006081450060894500609145006099450060a1450060a9450060b1450060b9450060c1450060c9450060d1450060d9450060e1450060e9450060f1450060f94500600146006009460060114600601946006021460060294600603146006039460060414600604946006051460060594600606146006069460060714600607946006081460060894600609146006099460060a1460060a9460060b1460060b9460060c1460060c9460060d1460060d9460060e1460060e9460060f1460060f946006001470060094700601147006019470060045200600000002004000000045200600000000000000000045200600000202000000000045200600000008000000000045200600000e0810000000004000020100100000000000000000000000020200000000000000080000000000000e0810000000010b572b600f0b8fc0d4b07e003f10c049a685968186800f019f82346094a9342f4d306e01c46596854f8080b00f01df82346054a9342f5d362b602f012fbfee7a0220060dc22006004230060002393420ad210b450f8044b41f8044b04339342f8d35df8044b7047704700238b4204d2002240f8042b0433f8e77047fee7fee7fee7fee7fee7fee780b582b000af78607968024800f038f800befde76c4d006080b584b000af7860396043f22153fb607b68002b02d11048fff7e6ff3b68002b02d10e48fff7e0ff0d4b1b68002b01d100230de03a6879680a4802f001fa0346fb60fb68002b01d13b6801e04ff0ff3318461037bd4680bd804d0060a84d006020000020180000200fb480b5a4b000af0023c7f88c300023c7f888303b1d80220021184602f0a4fa124b1b68002b01d1002318e007f19c03c7f884303a1d0e4bd7f88410d7f8980001f082fdc7f88c00d7f88c203b1d11461846fff7a1ffc7f88800d7f8883018469037bd46bde8804004b0704720000020dd3d006080b483b000af03463960fb80b7f90630002b0adb3b68dab20c49b7f906301201d2b20b4483f800230ae03b68dab20849fb8803f00f03043b1201d2b20b441a7600bf0c37bd465df8047b704700e100e000ed00e080b582b000af78607b68013bb3f1807f01d301230fe00a4a7b68013b53600f214ff0ff30fff7c2ff054b00229a60044b07221a60002318460837bd4680bd00bf10e000e080b400af064b1b68002b04d0044b1b68013b034a136000bfbd465df8047b70472400002080b483b000af7860064a7b68136000bf044b1b68002bfbd100bf0c37bd465df8047b70472400002080b500af01f0aefa02f0b6f900f068fc134b1b68134aa2fb03239b091846fff7a7ff0346002b00d0fee74ff47a70fff7d3ff0d4b1b78dbb2002b08d0002209210a4800f0fdf9084b00221a70ede701220921064800f0f4f9034b01221a70e4e700000020d34d62101001002000801b4080b582b000af0346fb71fb7983f00103dbb2002b02d11348fff7cefe124b4ff08042c3f8582100bf0f4bd3f8703203f48033002bf8d00c4b4ff48032c3f8542100bf094bd3f8503103f40043002bf8d0054b4ff48032c3f8582100bf0837bd4680bd00bfd04d006000800d4080b483b000af0346fb71fb79002b04d1074b1022c3f8742203e0054b1022c3f8782200bf0c37bd465df8047b704700bf00800d4080b400af064bd3f87032054a43f00103c2f8703200bfbd465df8047b704700bf00800d4080b483b000af7860154b1b6823f4e0327b681b799b039bb21343114a43f4803313600f4b1a680f4b13407a68126802f07f0213430a4a43f40053136000bf084b1b68002bfbda064b1b68054a23f48033136000bf0c37bd465df8047b704700bf00800d4080cfffff80b483b000af7860224b1b6b23f4e0327b681b7b9b039bb213431e4a43f4803313631c4b1a6b1c4b13407a68127802f001021343174a43f4005313637b685b68144a23f0404313657b689b68114a23f0404313667b685b8ac3f30e027b681b7cdb039bb21a437b68db891b04094913430b6400bf074b1b6b002bfbda054b1b6b044a23f48033136300bf0c37bd465df8047b704700800d40fecfffff80b400af034b00221a6200bfbd465df8047b704700800d4080b400af044b4ff480521a6700bfbd465df8047b704700bf00800d4080b400af044b4ff48052c3f8e02000bfbd465df8047b704700800d4080b485b000af03460a46fb711346bb71fb79fb60124bd3f80021fb68db00bf2101fa03f3db431340bb60fb68db0080229a400b49bb681343c1f80031bb7903f03f02fb68db009a400549bb681343c1f8003100bf1437bd465df8047b704700bf00800d4080b586b000af03460a46fb8013467b71b7f906301b0a7b61fb8803f01f033b617b69062b02d90d48fff788fd7b699a000b4b1344fb60fb681a6803213b6901fa03f3db431a4079793b6901fa03f31a43fb681a6000bf1837bd4680bdf44d006068c00f4080b584b000af78600023fb6009e00d4afb6852f823307a689a4206d0fb680133fb60fb68052bf2d900e000bffb68052b02d90548fff750fdfb6818461037bd4680bd00bf184e00603c4e006080b586b000aff860b9607a60f868fff7d1ff78617b69052b0dd8214a7b6932f91330b3f1ff3f06d01d4a7b6932f91330184601f00bfffb685a690121bb6801fa03f3db431a40fb685a617b681b78002b0ad1fb685a680121bb6801fa03f3db431a40fb685a600fe07b685b781a46b968f86800f017f8fb685a680121bb6801fa03f31a43fb685a607b689b781a46b968f86801f0eafe00bf1837bd4680bd00bf304e006080b584b000aff860b9601346fb71bb681f2b02d90e48fff7e7fcfb79002b0ad1fb681a680121bb6801fa03f3db431a40fb681a6008e0fb681a680121bb6801fa03f31a43fb681a6000bf1037bd4680bd704e006080b487b000aff860b9601346fb71bb683b61fb68da690121bb6801fa03f3db431a40fb68da61bb680f2b03d8fb680c337b6105e0fb6810337b613b69103b3b61fb79013b042b52d801a252f823f000bf5d2a0060752a0060992a0060bd2a0060d32a00607b691a683b695b00032101fa03f3db431a407b691a6039e07b691a683b695b00032101fa03f3db431a403b695b00012101fa03f31a437b691a6027e07b691a683b695b00032101fa03f3db431a403b695b00022101fa03f31a437b691a6015e07b691a683b695b00032101fa03f31a437b691a600ae0fb68da690121bb6801fa03f31a43fb68da6100e000bf00bf1c37bd465df8047b704780b586b000aff860b9607a60bb68002b02d11448fff740fcbb687b617b683b6111e000bffb685b6903f40003002bf9d07b691b781a46fb68da617b6901337b613b69013b3b613b69002bead100bffb685b6903f48003002bf9d000bf1837bd4680bd00bf8c4e006080b500af044b1b68044a526811460448984700bf80bd00bf54000020300000200040184080b500af044b1b68044a926811460448984700bf80bd00bf54000020300000200080184080b500af044b1b68044ad26811460448984700bf80bd00bf540000203000002000c0184080b500af044b1b68044a126911460448984700bf80bd00bf54000020300000200000194080b500af044b1b68044a526911460448984700bf80bd00bf54000020300000200040194080b500af044b1b68044a926911460448984700bf80bd00bf54000020300000200080194080b500af044b1b68044ad26911460448984700bf80bd00bf540000203000002000c0194080b500af044b1b68044a126a11460448984700bf80bd00bf540000203000002000001a4080b584b000af694bd3f88830674a43f47003c2f88830654b654a9a60654b1b899bb2644a23f001039bb21381624b1b899bb2614a23f001039bb213815d4b1b889bb203f00403002b07d05a4b1b889bb2584a23f004039bb21380574b1b889bb203f00403002b07d0534b1b889bb2524a23f004039bb21380504b1b6803f40053002b03d04d4b4e4a5a6007e04b4b4cf220525a60494b4df628125a60474b4ff6ff729a60454b1b6823f0a003434a43f020031360434b1b6803f00103002b05d0404b1b683f4a23f001031360374b5b6903f40033b3f5003f1dd0344b5b6903f40033002b16d1bff34f8fbff36f8f2f4b0022c3f85022bff34f8fbff36f8f2b4b5b692a4a43f400335361bff34f8fbff36f8f00e000bf254b5b6903f48033b3f5803f3cd0214b5b6903f48033002b35d11e4b0022c3f88420bff34f8f1b4bd3f88030fb60fb685b0bc3f30e03bb60fb68db08c3f309037b60bb685a0143f6e07313407a68920711491343c1f860327b685a1e7a60002befd1bb685a1eba60002be5d1bff34f8f094b5b69084a43f480335361bff34f8fbff36f8f00e000bf01f0b7fc00bf1037bd4680bd00bf00ed00e00020006000800b4000000d4000c00b4020c528d910e000e090b585b000af784b5b6903f00073002b45d0754b9b6903f44053b3f5805f21d0b3f5005f21d0002b2bd1704b1b6903f48033002b0bd06d4b1b699b0b03f00303002b01d16a4b00e00023fb601ce0674b1b6903f00203002b01d0664b00e0664bfb6011e0624bfb600ee0604b1b6b9b0b03f00303002b01d15d4b00e00023fb6002e00023fb6000bf574b5b69db0e03f007030133fa68b2fbf3f3fb6095e0534b1b6803f48033002b0bd0504b1b689b0b03f00303002b01d14d4b00e00023bb6008e04a4b1b6803f07f03494a02fb03f35b08bb60454b1b6b03f48033002b0bd0424b1b6b9b0b03f00303002b01d1404b00e000237b6009e03c4b1b6b03f00103002b01d03b4b00e03b4b7b60374b1b6d4ff00004364a02fb04f1002202fb03f20a443349a3fb0101531819462f4b1b6e4ff000041a46234601f00dfe03460c461a467b6813447b60274b9b6903f44023b3f5802f0fd0b3f5802f02d8002b07d030e0b3f5002f15d0b3f5402f20d029e07b68fb6029e01d4bd3f800311b0c03f03f037a68b2fbf3f21346db0013445b00fb601ae0154bd3f8003103f03f037a68b2fbf3f21346db0013445b00fb600ce00d4b1b6903f007030133ba68b2fbf3f3fb6002e00023fb6000bf074b5b699b0a03f007030133fa68b2fbf3f3074a136000bf1437bd4690bd00c00f4000800d4000366e0100a4781f00389c1c0000002080b586b000aff860b9607a60fb68002b02d11048fff798f9bb68002b02d10e48fff792f97b68002b02d10c48fff78cf9fb687b617b691b781a46094b53f822307a68b9681846fff733fd002318461837bd4680bdd44e0060f44e0060144f0060b04e006080b586b000aff860b9607a60fb68002b02d11248fff766f9bb68002b02d11048fff760f97b68002b02d10e48fff75af9fb687b617b691b683b613b69002b02d10948fff74ff97b68ba687969386901f050fb034618461837bd4680bd344f0060584f0060784f0060984f006080b586b000aff860b9607a60fb68002b02d10e48fff730f9bb68002b02d10c48fff72af97b68002b02d10a48fff724f9fb687b617b697a68b9681846fff778ff034618461837bd4680bd00bfb84f0060e04f00600450006080b584b000af786039607b689b0efb607b68dab21f4b13441a687b685b0bc3f30c017b681b0a03f01f0301fa03f3db4302ea03017b681b0a03f01f033a689a407b685b0bc3f30c007b681b0a03f01f0300fa03f31a407b68d8b20e4b0344184641ea02030360fb68202b02d90a48fff7d7f8fb68202b09d000bf064b9a6cfb6822fa03f303f00103002bf6d100bf1037bd4680bd00c00f402850006080b584b000af786039607b689b0efb607b68dab21f4b13441a687b685b0bc3f30c017b681b0a03f01f0301fa03f3db4302ea03017b681b0a03f01f033a689a407b685b0bc3f30c007b681b0a03f01f0300fa03f31a407b68d8b20e4b0344184641ea02030360fb68202b02d90a48fff789f8fb68202b09d000bf064b9a6c0121fb6801fa03f31340002bf6d100bf1037bd4680bd00c00f40c050006080b586b000af03460a46fb8013467b71b7f906301b0a7b61fb8803f01f033b617b69062b02d90d48fff75ef87b699a000b4b1344fb60fb681a6803213b6901fa03f3db431a4079793b6901fa03f31a43fb681a6000bf1837bd4680bd5851006068c00f4080b483b000af7860044a7b68136000bf0c37bd465df8047b704700bf2800002080b483b000af7860044a7b68136000bf0c37bd465df8047b704700bf2c00002080b485b000aff860b9601346fb71ba680c4b1340bb60fb79002b06d0fb685a68bb681a43fb685a6006e0fb685a68bb68db431a40fb685a6000bf1437bd465df8047b704700f0ffff80b485b000af78600b46fb701346bb707b689a680a4b1340fb60fb789b0603f08062fb681a43bb781b0403f47f031a437b689a6000bf1437bd465df8047b7047ffff00fb80b582b000af4ff40040fff7a3ffd94bd3f8c032d74a43f48033c2f8c032d54bd3f8c032d34a23f40033c2f8c032d248fff780ff0020fff7e3f8fff731f90120fff714f9cd4b1b6823f0ff03cb4a43f07f0313600121ca48fff79efe0121c948fff79afec84bdb6823f01f03c64a43f01303d36000bfc44b1b6803f00043b3f1004ff8d10021c148fff7d4fe4ff4887001f0cef94ff4847001f0caf940f2162001f0c6f94ff4067001f0c2f940f20e2001f0bef90321b648fff7bcfe0121b548fff7b8fe0021b448fff7b4fe4ff48a7001f0aef94ff48b7001f0aaf9182001f0a7f91a2001f0a4f94ff4867001f0a0f90121aa48fff79efe40f2026001f098f90121a748fff796fe0021a648fff744fe40f2046001f08cf90121a348fff78afe0021a248fff738fe4ff4417001f080f907214ff21400fff77dfe00219c48fff72bfe00219b48fff727fe40f2022001f06ff901219848fff76dfe00219748fff71bfe4ff4807001f063f94ff4817001f05ff94ff4827001f05bf94ff4837001f057f904218e48fff755fe02218d48fff703fe162001f04cf902218b48fff74afe02218a48fff7f8fd40f2125001f040f903218748fff73efe01218648fff73afe00218548fff7e8fd40f2145001f030f903218248fff72efe01218148fff72afe00218048fff7d8fd40f2165001f020f903217d48fff71efe01217c48fff71afe00217b48fff7c8fd40f2062001f010f94ff4027001f00cf940f20a2001f008f900217448fff706fe00217348fff7b4fd0e2001f0fdf8122001f0faf8102001f0f7f8142001f0f4f801216c48fff7f2fd02216b48fff7a0fd4ff4a36001f0e8f81c2001f0e5f80c2001f0e2f84ff48c7001f0def840f2023001f0daf840f2063001f0d6f840f21a5001f0d2f840f20e6001f0cef800215b48fff7ccfd00215a48fff77afd40f20a3001f0c2f801215748fff7c0fd03215648fff7bcfd05215548fff76afd40f20e5001f0b2f801215248fff7b0fd07215148fff7acfd03215048fff75afd40f2025001f0a2f801214d48fff7a0fd07214c48fff79cfd03214b48fff74afd4ff4407001f092f801214848fff790fd07214748fff78cfd03214648fff73afd00214548fff736fd4448fef7c9ff4448fef7faff1b210020fff76cf810210120fff768f818210220fff764f810210320fff760f8064b1b6b054a23f480231363fff73cf801223749014801f06ef86be000800d4000366e0100c00f40186c0080143900140000084014ea00041468008010e0004014fb00801ce0078024eb00801c30008024f000801c31008014270080142600803ceb00803c69008018fa00801864008024790080186e008028e6008028e007801c6a00802ce600802ce007801c6c008028f6008028f007801c6e008038f307803832008020e207802068008024e007802426008038ec008018f7008038ef008030f9008030f600803074008030ec008030e900803067008028e9008028f90080207300800c200080e0510060e85100600d007000834bd3f87031824a23f40043c2f870317f4bd3f870317e4a23f40003c2f870317b4b1b6f7a4a43f400531367784bd3f8a030774a23f40053c2f8a030744bd3f8a03023f4e033724a43f48033c2f8a0306f4b0022c3f8b0206d4b0122c3f8c0206b4bd3f8a0306b4a1a4042f21f0313437b607b6843f400237b60654bd3f87031634a43f04043c2f87031614a7b68c2f8a03000bf5e4bd3f8a030002bfada5c4bd3f8a0305a4a23f48023c2f8a03000225949574800f0a5fffef77aff01225749534800f09eff524bd3f8e03023f003034f4a43f00103c2f8e0304d4bd3f8e0304b4a43f40053c2f8e030494bd3f8e030474a43f40013c2f8e030fef73fff01224749434800f07dff414b1b6a404a43f40053136203214348fff718fc00214248fff714fc00214148fff710fc00214048fff70cfc364bd3f86031354a23f01f03c2f860313b4b1b6e3a4a23f070031366384b1b6e23f00f03364a43f001031366344b1b6e334a23f460031366314b1b6e23f4f8132f4a43f4901313662d4b1b6e2c4a23f4807313662a4b1b6e294a23f080031366274b1b6e264a23f08073136600220021244800f053ff00220321224800f04eff002206211f4800f049ff002208211d4800f044ff00220a211a4800f03fff002200211848fff7cafc00224ff400511548fff7a0fc134b5b69124a23f080535361104b5b690f4a23f0005353610e4b0f4a1a6000bf0837bd4680bd00bf00800d4080cfffff0d00a0000d00e0000d0020001872008014390014186c00801c26008000c00f4000c00a40000000200046c32380b586b000af03460a46fb8013467b71b7f906301b0a7b61fb8803f01f033b617b69062b02d90d48fef77efc7b699a000b4b1344fb60fb681a6803213b6901fa03f3db431a4079793b6901fa03f31a43fb681a6000bf1837bd4680bd5851006068c00f4080b584b002af40f2024000f009ff01233b7100237b710023bb713b1d1a4609210d48fef709ff002301930c4b00930023002205210a4800f002ff41f2b0030193064b0093002300220521054800f014ff00bf0837bd4680bd00801b40d0821f40e0801f4010b5174a0020174900f05eff4ff480734ff480620021124800f02ef8124a1349012000f051ff4ff480734ff4806200210d4800f021f80e4b1b68022b0edd0d4a02200a4900f040ff4ff480734ff4806200210848bde8104000f00eb810bd00bf5c000020005200609800002002520060fc510060d400002070b5c468a50708d0b2f5007f11d0b2f5806f04d0b2f5807f0bd0012008e000f124010123144301610160c361c460002070bd5e1e014dae42f4d9eee7feffff00f7b5044698b103b0bde8f04000f06abf07fb0460013400f065ff002818bf4ff0ff35019ba342f3dc284603b0f0bd034b05463c27024e1b680193f2e7fc5100605c0000202de9f843c66804463c4b0d46904633401bb902273846bde8f883d0f81490484601f081f807460028f3d1b8f1010f2cd0b8f1020f2ed0b8f1000fead1002de8dbb00404d52368e26a9a4238bfe362a169a9420edc2368e26a934238bf134622695818801aa84204db206b01eb000cac4534dc002346f02006a562c4e9013326f4032626f04006e660c4e7204600f038f80544d3e7484601f043f8b0f1000ce26804da42f080020127e260b3e7e36a1846a3691946236883422cbfc918091892060b462169a3eb010303d5a26a9342b8bf13466345acbfed186544afe76d1ab10744bf281aa060f10726f0200644bf9b1aeb1a154448bf63602560c0e703001000c3689a0705d10f4b21224ff0ff301a60704703f020021b030ad512b1806a0138704703688269006913441b1a581e70470ab1806a70470368826900691344181a704700bf580000202de9f041d56880460e461446eb02576905d545f01005384600f0deffa061124b2b408bb1a169384600f0dfff002806dae3684ff0ff3043f08003e36011e025f4003525f01005e56032464146384600f0d2ff20f00043f61aa3691e44a6610028e6d1bde8f08100bf1000020080b586b000aff860b9603b601346fb7100237b6100237b611ce0bb681b6801337f2b08d9bb681b681946f868fef7ccfabb6800221a60bb681b681a46fb681344fa791a70bb681b685a1cbb681a607b6901337b617a693b689a42dedb00bf1837bd4680bd80b48db000aff860b9607a603b600023fb61fb68bb61bb695a1cba6100221a707b68002b35d0bb681b68fb62fb6a002b2bd1bb6930221a70fb690133fb61fb6962e0fa6a3b6892fbf3f37b617b693a6802fb03f3fa6ad31abb62bb6a002b06dabb6a3b623b6ac3f13003bb6202e0bb6a3033bb627b69fb62bb695a1cba61ba6ad2b21a70fb690133fb61fb6a002bd8d139e0bb681b687b627b6a002b30d1bb6930221a70fb690133fb61fb692ce03b687a6ab2fbf3f33b613b683a6902fb03f37a6ad31a3b623b6a092b03d83b6a30333b620ae097f83830002b01d0412200e061223b6a13440a3b3b623b697b62bb695a1cba613a6ad2b21a70fb690133fb617b6a002bd3d1fb6918463437bd465df8047b704790b59db002aff860b9607a603b6000233b660023fb650023bb61002387f84a3000233b61fb687b6664e17b6c252b08d07b6cdab207f118013c6801237868a04755e1012387f84b3000237b650023bb651be07b6e01337b667b6e1b787b647b6c2f2b0ddd7b6c392b0adc7a6d13469b0013445b001a467b6c1344303b7b6504e07b6e013b7b660123bb65bb6d002be0d006233b657b6e01337b667b6e1b782e2b24d100233b650023bb651be07b6e01337b667b6e1b787b647b6c2f2b0ddd7b6c392b0adc3a6d13469b0013445b001a467b6c1344303b3b6504e07b6e013b7b660123bb65bb6d002be0d002e07b6e013b7b667b6e01337b667b6e1b787b647b6c642b1bd07b6c692b18d07b6c662b15d07b6c462b12d07b6c782b0fd07b6c582b0cd07b6c6f2b09d07b6c622b06d07b6c702b03d07b6c752b40f096807b6c642b02d07b6c692b15d1bb681a1dba601b687b6107f1140107f11c0097f84b3000930a230122fff7c2fef86507f11c02fb6d13443b667b6c582b02d07b6c782b1bd17b6c782b02d1002387f84b30bb681a1dba601b683b6107f1100107f11c0097f84b30009310230022fff7a0fef86507f11c02fb6d13443b667b6c6f2b08d07b6c622b05d07b6c702b02d07b6c752b2fd1bb681a1dba601b683b617b6c6f2b03d1082387f84a3010e07b6c622b03d1022387f84a3009e07b6c702b03d1102387f84a3002e00a2387f84a3097f84a2007f1100107f11c0097f84b30009313460022fff764fef86507f11c02fb6d13443b66fb6d7a6dd31a07f118013c6820227868a0473b6e002b52d009e03b6e5a1e3a661a7807f118013c6801237868a0473b6e1b78002bf1d142e07b6c632b0ed1bb681a1dba601b68dbb23b643b6cdab207f118013c6801237868a04730e07b6c732b25d1bb681a1dba601b68fb64fb6c002b25d0f86c00f0d6fb0346fb65fb6d7a6dd31a07f118013c6820227868a04709e0fb6c5a1cfa641a7807f118013c6801237868a047fb6c1b78002bf1d107e07b6cdab207f118013c6801237868a0477b6e01337b667b6e1b787b647b6c002b7ff494aebb6918466c37bd4690bd08b5fef792f808bd08b5fef78ef808bd08b5fef78af808bd08b5fef786f808bd08b5fef782f808bd08b5fef77ef808bd08b5fef77af808bd08b5fef776f808bd08b5fef772f808bd08b5fef76ef808bd08b5fef76af808bd08b5fef766f808bd08b5fef762f808bd08b5fef75ef808bd08b5fef75af808bd08b5fef756f808bd08b5fef752f808bd08b5fef74ef808bd08b5fef74af808bd08b5fef746f808bd08b5fef72bfc08bd08b5fef739fc08bd08b5fef747fc08bd08b5fef755fc08bd08b5fef763fc08bd08b5fef771fc08bd08b5fef77ffc08bd08b5fef78dfc08bd08b5fef722f808bd08b5fef71ef808bd08b5fef71af808bd08b5fef716f808bd08b5fef712f808bd08b5fef70ef808bd08b5fef70af808bd08b5fef706f808bd08b5fef702f808bd08b5fdf7feff08bd08b5fdf7faff08bd08b5fdf7f6ff08bd08b5fdf7f2ff08bd08b5fdf7eeff08bd08b5fdf7eaff08bd08b5fdf7e6ff08bd08b5fdf7e2ff08bd08b5fdf7deff08bd08b5fdf7daff08bd08b5fdf7d6ff08bd08b5fdf7d2ff08bd08b5fdf7ceff08bd08b5fdf7caff08bd08b5fdf7c6ff08bd08b5fdf7c2ff08bd08b5fdf7beff08bd08b5fdf7baff08bd08b5fdf7b6ff08bd08b5fdf7b2ff08bd08b5fdf7aeff08bd08b5fdf7aaff08bd08b5fdf7a6ff08bd08b5fdf7a2ff08bd08b5fdf79eff08bd08b5fdf79aff08bd08b5fdf796ff08bd08b5fdf792ff08bd08b5fdf78eff08bd08b5fdf78aff08bd08b5fdf786ff08bd08b5fdf782ff08bd08b5fdf77eff08bd08b5fdf77aff08bd08b5fdf776ff08bd08b5fdf772ff08bd08b5fdf76eff08bd08b5fdf76aff08bd08b5fdf766ff08bd08b5fdf762ff08bd08b5fdf75eff08bd08b5fdf75aff08bd08b5fdf756ff08bd08b5fdf752ff08bd08b5fdf74eff08bd08b5fdf74aff08bd08b5fdf746ff08bd08b5fdf742ff08bd08b5fdf73eff08bd08b5fdf73aff08bd08b5fdf736ff08bd08b5fdf732ff08bd08b5fdf72eff08bd08b5fdf72aff08bd08b5fdf726ff08bd08b5fdf722ff08bd08b5fdf71eff08bd08b5fdf71aff08bd08b5fdf716ff08bd08b5fdf712ff08bd08b5fdf70eff08bd08b5fdf70aff08bd08b5fdf706ff08bd08b5fdf702ff08bd08b5fdf7fefe08bd08b5fdf7fafe08bd08b5fdf7f6fe08bd08b5fdf7f2fe08bd08b5fdf7eefe08bd08b5fdf7eafe08bd08b5fdf7e6fe08bd08b5fdf7e2fe08bd08b5fdf7defe08bd08b5fdf7dafe08bd08b5fdf7d6fe08bd08b5fdf7d2fe08bd08b5fdf7cefe08bd08b5fdf7cafe08bd08b5fdf7c6fe08bd08b5fdf7c2fe08bd08b5fdf7befe08bd08b5fdf7bafe08bd08b5fdf7b6fe08bd08b5fdf7b2fe08bd08b5fdf7aefe08bd08b5fdf7aafe08bd08b5fdf7a6fe08bd08b5fdf7a2fe08bd08b5fdf79efe08bd08b5fdf79afe08bd08b5fdf796fe08bd08b5fdf792fe08bd08b5fdf78efe08bd08b5fdf78afe08bd08b5fdf786fe08bd08b5fdf782fe08bd08b5fdf77efe08bd08b5fdf77afe08bd08b5fdf776fe08bd08b5fdf772fe08bd08b5fdf76efe08bd08b5fdf76afe08bd08b5fdf766fe08bd08b5fdf762fe08bd08b5fdf75efe08bd08b5fdf75afe08bd08b5fdf756fe08bd08b5fdf752fe08bd08b5fdf74efe08bd08b5fdf74afe08bd08b5fdf746fe08bd08b5fdf742fe08bd08b5fdf73efe08bd08b5fdf73afe08bd08b5fdf736fe08bd04207146084202d0eff3098001e0eff3088081690a884bf6ab639a4200d0fee70231816120210160704700bf80b582b000af0346fb80b7f9063003211846fef777f800bf0837bd4680bd80b584b000aff860b9601346fb71fb791a46b968f868fef73af900bf1037bd4680bd80b400af00bfbd465df8047b704780b586b000aff860b9607a603b6043f22153fb82bb68002b11d0fb681b7b012b09d1fb6808333a6879681846fef7a5fc0346fb8204e043f22153fb8200e000bffb8a18461837bd4680bd80b584b000aff860b9607a607a68b968f868fef757fc034618461037bd4680bd80b582b000af0346fb80b7f9063000211846fef745fd00bf0837bd4680bd80b485b000aff860b9601346fb71fb79002b0bd0bb681b0cc3f30b02fb68134404331a464ff4803313600ae0bb681b0cc3f30b02fb68134408331a464ff48033136000bf1437bd465df8047b704780b485b000af78600b46fb701346bb70fb78032b12d97b685a68fb78032101fa03f3db431340fb60bb7803f00302fb789a40fb681a437b685a6011e07b685a68fb78072101fa03f3db431340fb60bb7803f00702fb789a40fb681a437b685a6000bf1437bd465df8047b704780b500affef778fd00bf80bd80b582b000af0346fb80b7f9063003211846fff7b3f800bf0837bd4680bd80b485b000aff860b9607a603b60bb6803f00701fb691b0103f01002fb680a431a607b68002b02d07b683a681a6000bf1437bd465df8047b704780b485b000aff860b9607a603b60bb69002b02d0bb69fa691a6000bf1437bd465df8047b704708b5fff7e0f8bde80840fdf7f8bd00f0ccb9a8b14ff0805340f80c3c50f8042c72b11368b3f1805f0ad150f8083c516803330b4440f8083c936840f8043cede77047421c10b58307044616d1234654f8041ba1f1013020ea010010f0803ff5d011f0ff0f11d011f47f4f0cd011f47f0f14bf23460333981a04e010f8013b002be1d1801a10bd0233f5e70133f3e770b50d4606461046144600f02df82b78722b17d0772b18d0612b23d1082348f2020215f8011f2b2912d0622915d01b07e260666104d5022200212046fff7faf8204670bd00230122ebe704230222e8e742f0030243f00203e3e742f0040243f00103dee70024ebe7f0b5c46889b00546a10722d0220718d4d0e90467fff7b8f8384600f064f9230502d53046fff77affa40da40514f1a54f07d12022296a684600f064f9684600f00bf83c2200212846fff766ff002009b0f0bd4ff0ff30fae710b50446fff776ff01462046bde8104000f046b9c36870b513f00306044611d09b0611d5856a00f03ef8e368204623f44053e36000f00ef80022064629462046fff798f8304670bd836905681d440369ed1aeae738b5016804460569c36ac06820f40022e26000f08202022a02d04ff0ff3038bd10f48030fbd0994238bf19468d4208d1e3680020e56223f480332560a060e360ede72246491b2846fff70ef90028efd0e3e7c368826923f0200310b5c3600446836a9a420ed0fff7cbffe36823f4405323f0100343f01003e360a36aa3612369e3622360e36823f4804323f04003e36010bd5ff0000c30b51c0022d112006fd0914206d305460846002100f06ff80446284600f06bf80a462146bde830401cf0404f08bf704703d54942404261f100015fea8c0c38bf70475b42524263f10003704742d41c0c04bf1b040cf1100c13f07f4f04bf1b020cf1080c13f0704f04bf1b010cf1040c13f0404f04bf0cf1020c5fea83035cbf0cf1010c5b00ccf1200e22fa0ef4234302fa0cf200fa0cf520fa0ef001fa0cf4204321fa0ef114461a4600f024f8a4fb0023aa1a71eb03033cbf1b190138ccf1200e22fa0cf203fa0ef4224323fa0cf30021a7e7821a71eb03033cbf02460b464ff0000141f100009ce700200021bde8304000f03db80423b1eb122f09d2090241ea10610002013b08bf7047b1eb122ff5d30018494134bf9142891a4041494134bf9142891a4041494134bf9142891a4041494134bf9142891a4041494134bf9142891a4041494134bf9142891a4041494134bf9142891a4041494134bf9142891a4041a3f1010313f00f0fd2d170477047034613f0030f0ed101f0ff0141ea012141ea0141043a24bf43f8041bb2f10402f9d202f10402013a24bf03f8011bfae770474ff0ff3070474ff0ff3070474ff0ff3070474ff0ff3070474ff0ff3070474ff0ff30704710467047ffff415353455254204552524f522022202573200a002e66736c5f64656275675f636f6e736f6c652e633a343839203a204e554c4c20213d2063680000002e66736c5f64656275675f636f6e736f6c652e633a343930203a203020213d2073697a6500ffffff2e66736c5f636c6f636b2e633a313631203a20216279706173735874616c4f736300ffff2e66736c5f636c6f636b2e683a31303438203a20696e646578203c3d2036554c00ffffff0000000000801b4000c01b4000001c4000401c4000000c40ffff1a011e001a020c031e012e66736c5f6770696f2e633a3537203a20696e7374616e6365203c2041525241595f53495a4528735f6770696f426173657329002e66736c5f6770696f2e633a313133203a2070696e203c20333255002e66736c5f6c70756172742e633a383235203a204e554c4c20213d206461746100ffffff00000000004018400080184000c0184000001940004019400080194000c0194000001a402e6c70756172745f616461707465722e633a333436203a2068616e646c6500002e6c70756172745f616461707465722e633a333437203a2064617461000000002e6c70756172745f616461707465722e633a333438203a206c656e67746800ff2e73657269616c5f6d616e616765722e633a373832203a20777269746548616e646c65002e73657269616c5f6d616e616765722e633a373833203a2062756666657200002e73657269616c5f6d616e616765722e633a373834203a206c656e67746800002e73657269616c5f6d616e616765722e633a373839203a2068616e646c6500ff2e73657269616c5f706f72745f756172742e633a323637203a2073657269616c48616e646c6500002e73657269616c5f706f72745f756172742e633a323638203a20627566666572000000002e73657269616c5f706f72745f756172742e633a323639203a206c656e67746800ffffff433a5c55736572735c6e786131373134315c446f63756d656e74735c4d43555870726573736f4944455f31312e312e305f333136395f707263335c776f726b73706163655c65766b62696d787274313035305f696c65645f626c696e6b795c647269766572732f66736c5f636c6f636b2e683a393737203a20627573795368696674203c3d2043434d5f4e4f5f425553595f5741495400ff433a5c55736572735c6e786131373134315c446f63756d656e74735c4d43555870726573736f4944455f31312e312e305f333136395f707263335c776f726b73706163655c65766b62696d787274313035305f696c65645f626c696e6b795c647269766572732f66736c5f636c6f636b2e683a31303134203a20627573795368696674203c3d2043434d5f4e4f5f425553595f5741495400433a5c55736572735c6e786131373134315c446f63756d656e74735c4d43555870726573736f4944455f31312e312e305f333136395f707263335c776f726b73706163655c65766b62696d787274313035305f696c65645f626c696e6b795c647269766572732f66736c5f636c6f636b2e683a31303438203a20696e646578203c3d2036554c000064000000000000000100000000000000010000000000000000000000030000007200770000a4781f4920938e898f4388 -len=16912=0x4210 -[SHA1] -hex=209ab7de226dcd6205a1c3322e31e66ee267d19a -len=20=0x14 -[padding] -hex=3672f499920534d2d517a0f7 -len=12=0xc diff --git a/tests/mcu_examples/data/rt105x/keys/CSF1_3_sha256_2048_65537_v3_usr_key.der b/tests/mcu_examples/data/rt105x/keys/CSF1_3_sha256_2048_65537_v3_usr_key.der deleted file mode 100644 index 756fa45e..00000000 Binary files a/tests/mcu_examples/data/rt105x/keys/CSF1_3_sha256_2048_65537_v3_usr_key.der and /dev/null differ diff --git a/tests/mcu_examples/data/rt105x/keys/CSF1_3_sha256_2048_65537_v3_usr_key.pem b/tests/mcu_examples/data/rt105x/keys/CSF1_3_sha256_2048_65537_v3_usr_key.pem deleted file mode 100644 index 1ace81d6..00000000 --- a/tests/mcu_examples/data/rt105x/keys/CSF1_3_sha256_2048_65537_v3_usr_key.pem +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIzMamagJNcYMCAggA -MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAx/gYChqdmd1VRTktzXZQABIIE -0MJC7w3bfT3+F6u6jKCsyb3BXGdOlx2Wrf/df9pzxc4SepVb2+apQSZsf9dLwWSy -GCQSFWV2X2eJJl2saiXqYF96FkVbIHbBMFTF8K7f/PswXNv/+hpjnLfHYGHy+8Hg -fqeKQTXR8C6WSJHSjz4WUvag0Ir8RtEG4U8o/4UdzPxTzIErsPD9blG9ntaEb5Yu -GIB0UGygKEORE0hvJl75MxxmbO6F4MNUpb4PcP9uGEhEoyyNc2wxC5ubxk16qQlv -Ozao8OQf3tQWwyV/321wjkebb4TcBO/vKZoOQTab/wn2DI7/N1FI+c47/pW4IM79 -lBRQ0XJjVlP+Uzi9FpA/N2oAAqbnvcPytgVcoaBKWbzxMnabnGmFQHXef7SqECN0 -+h+niYi/JkjbugxDdVYn8Qe/0gViLbAshd5BxuNWOP9a8DnSRcVAtZfO03qsnmng -c9iJnR23UIyjnjl/gaac3gsthibtqmDxglNfnWMLvELBJeRLsWNMDiXrCIXERSPP -HUdf7amLpr/1yPuvdnAuMUJr/lYdR2QOCnMIdrIinGVGEhiKFZ2kn03YLogostpg -IFNMmI498FYE2oElsNPtzj199oPIMeXekeqmsxwWOpu3cT2krKRb4xc2mPV5l5tg -LHcwklxeG2Ja/uTKVDs3NIgPLRD5D1l6YFO+t9aE2rFAJrzCJZP2QZJn/3qdTMAT -vqLacIUbPrhlU5E5q5W/fEtfyXU72frmoe/WRCt/PiMDJdGG9S9+FqM9ZTW/MaeI -9W34RrBWzZl1Z1FNfYsD8R+EuOD6ZZNehcFEAN/OmGw+aYB71rk2lbTdcV1HgeYC -vFGWMU5CekjDH5OfS5EeLQ0wRqxTvFTPS5DNAM66aE3X6R836M4n87c+V2kp5m9s -qmKyMqUFLxZCizzFP6I2d9u6Hi31E3qkPt2zUTkTfV9xfSuBTrYfMzyyn8Mu59CN -hps16p1jXBDo+Liq5+PQNNabiYla16zN5MtwxDeerTrfMXfaKwy7VKSEHJ0bBjiW -/OPkcIsur7BrwVkJdjLNh6i6iqK7Fe11UPcxdK1kCCWga0c6dE3WyEtB/xUhws7U -FExpnqdgZkj7EPeF5SRqE4OZ9i3ssYkVkSYFqvcrAZuLKk+5jElbiFCPR+DaLImB -EYfZrGqzcHAkIGB74peP/2GcH3/k88OnN+mRPiowkdv3hIfAi7yNdaNf3icYkrf2 -NZFZv2Zc6DkEft6FzCP/lAIHktQM+2qjBtJMRrMYcUqSqrJGHfJM38lpoASEX3cx -eqbM1SyShJG7fPy5depufz+61uu1baE9Hu/0P/S+nChJ08T/XHUC30wxHYEsdIgs -kKBVXdwzu/DvIzta1r/sJ5xKr6OQCxvSAcUzZpYX0hTQLPcLGX86/10zD0ImQ1bf -yQIFXjkdI3UBIXqfe9p25LScav1FI2tVfxvm1h9mu/F/cIW7Oj5CHwdCVSIlAqHs -/2apUiGU33eTn4Ba+fkzIl2XDDaDES8R3PjFhxb2trLdy1ymmodnXVb8/KzuWOoo -L+o51bzgdzehP/W9bd25QMTMFdBhGvGYmWBZ6uI2LjIUOxW7N8chj90jnJWifGrS -MoQNTQYNgzVVc6WHsr6PZk3L2rUptwOpDEYA+/GLYybb ------END ENCRYPTED PRIVATE KEY----- diff --git a/tests/mcu_examples/data/rt105x/keys/IMG1_3_sha256_2048_65537_v3_usr_key.der b/tests/mcu_examples/data/rt105x/keys/IMG1_3_sha256_2048_65537_v3_usr_key.der deleted file mode 100644 index 8160b93d..00000000 Binary files a/tests/mcu_examples/data/rt105x/keys/IMG1_3_sha256_2048_65537_v3_usr_key.der and /dev/null differ diff --git a/tests/mcu_examples/data/rt105x/keys/IMG1_3_sha256_2048_65537_v3_usr_key.pem b/tests/mcu_examples/data/rt105x/keys/IMG1_3_sha256_2048_65537_v3_usr_key.pem deleted file mode 100644 index db1bb0eb..00000000 --- a/tests/mcu_examples/data/rt105x/keys/IMG1_3_sha256_2048_65537_v3_usr_key.pem +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIwN6F6qaj3qMCAggA -MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDjBHs+Op2cjxbnz3u4TJjwBIIE -0PWjCIyQynofrfNhFknF8fBC8XumOpk2AqOw606oHvlryBupDfRv5Wiih+9kG0+5 -6+sel9hfQrSwmU7cRGN4sz22YY5du8aIo+vV4J1YNlvrfJrdNOPFrmbCM018tOCF -hdbdGobzlTruBHcqGWKPg7BuMrjZTFUICSITdm1uk/A3svkl9rIZSGTY4r20cp7v -cLEwr+ubvrrpE4lEIZxbZglcmrfIZPQwT07J3pbQm8JU0NDOeREOue4I+TXoVZN6 -WksQCHm+5Sr5P3VJK225ZkI/9OHOOZzjMg2PtFGKX+Pi2IxnQISNJ+NjbTGYePns -uFMHv3lLWE9gW/bo6ay46i+Dz2m3TL+MOC0VhN4MElNKu7J4nkjHs82e1DNNhsWC -ikxpHZcaWow0v1lD7h2YQr3iTP+ZWWijFCrVF0zOVeykS9lqf+pxzns9KYIUBF5a -WUt59mFgbg3LykDrOmhlrsihryxFIX8v8jRz7P1gJhpPj3qBzx2Hxgyebla1M6qX -XQltBudlIojjo0y+bELkwMmownc3BM1hNi6mXLwhVi2OHAjEW5/uu1SO3hP5BDQW -CoSev7HjQLOAoM0w2mTscWQACr4muHBKeO0w7lioLgZbd7wwwn/ejMTi1QarOTis -h1pCz876Neur3yqpjUTkLWBHCfF17BFi6y2xyftUanJywU93N+oPI4sx4I/RvJ84 -Zmz832xlXcGSlph9+Kt2qLUn3DmHC4DLRvyuCRjYRi1jPcCyoozy55Zu4d5m+9N7 -xB3fCrPIr9DAXCaBZ3Hbi90miceJZ35L2jRiddK6Yay50JRBSF7tDw+JRGhDSk5E -iCJhX3E/kHDlVeaIqjghmNO+Px+RwerCjEmDDq/jbbA/ZbHuUVN1JGGkHgmVwxc7 -DOTeOeFJTU0MB3jDr0+bhw7D9ysRA+sD6QSHsZ0YfqOHZ8yM0i2venST3RhzObbt -vR+9uo1hgnRLPqZqJL7mj/lA2JlyfBlqD0ZafvsyrDU2WzkGHf9soA9c+p5YDpl7 -2PILIxFYdhntOxJop4KQjNPSxxtxxb6il50/g2XlX7tpenU1axmwB62IQ1lnSOCy -31vU/0aU+xWQLNhAwLvIAC5iGPnONETfsuozvkSkZffK7bKRyxI/8D+pl9BKyH16 -utTbJXaOPLT4UwM9RzyOeVSA93qwd8pOqnP6mZQbtnO+Cxx4Qz5IhyjpDpktIVDA -803cRC2hzmQe+MSqNKsBvvIg5nXvltZSf0Eo9TtFmqUxvpYNsgMPkm4VlyKrgMS4 -dylr4VrWUgcWkwnvYnyD/4Z3MbOwKTiAA1lOx7KwtWvXERJR/ZXjh6cnY1Ph+qFO -qhOr3ZkNq0D/ltxZbPYhO9Q8tnzUbojNQw7Pyr+ZJMJhSzwDWrICcElIKs0J+j3W -WcGg/pv8BTur7Bk2jcE/d4/q5OmHrJjOB/lq74L6r1zdhOXOqTjME9wVQLYD+KHA -AdaBjH9fEDwBO+/VmHuL1pJm/3zv+Q8l6D6sCY8sgUpNbHs7pBNol1fN4/ljPJWn -l2hlhQiko7snl6ISg1AVVXguWURNpX61D6HnNgQAwEZUdfYFrV/CkVTXw0rmUia/ -NeYLbacjA+HopnTdQB1kcp6QsaZPRTQv4I2jQJPh7izM ------END ENCRYPTED PRIVATE KEY----- diff --git a/tests/mcu_examples/data/rt105x/output/evkbimxrt1050_iled_blinky_ext_FLASH_unsigned_nofcb.sb b/tests/mcu_examples/data/rt105x/output/evkbimxrt1050_iled_blinky_ext_FLASH_unsigned_nofcb.sb deleted file mode 100644 index 74938317..00000000 Binary files a/tests/mcu_examples/data/rt105x/output/evkbimxrt1050_iled_blinky_ext_FLASH_unsigned_nofcb.sb and /dev/null differ diff --git a/tests/mcu_examples/test_rt10xx.py b/tests/mcu_examples/test_rt10xx.py index 1d2e0dfa..cb617c13 100644 --- a/tests/mcu_examples/test_rt10xx.py +++ b/tests/mcu_examples/test_rt10xx.py @@ -1,10 +1,12 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause +"""Module for RT10xx boards testing.""" + import os from datetime import datetime, timezone from struct import pack @@ -16,7 +18,6 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization import PrivateFormat, NoEncryption from cryptography.hazmat.primitives.serialization import load_pem_private_key - from spsdk.crypto import generate_certificate, save_crypto_item, Encoding from spsdk.crypto import generate_rsa_private_key, generate_rsa_public_key, save_rsa_private_key from spsdk.image import BootImgRT, SrkTable, SrkItem, PaddingFCB, FlexSPIConfBlockFCB, MAC, hab_audit_log, \ @@ -27,6 +28,7 @@ from spsdk.sdp import SDP, ResponseValue, StatusCode, SdpCommandError from spsdk.sdp import scan_usb as sdp_scan_usb from spsdk.utils.easy_enum import Enum +from enum import Enum as PyEnum from spsdk.utils.misc import load_binary, DebugInfo, align, align_block from tests.misc import compare_bin_files, write_dbg_log @@ -75,21 +77,25 @@ # version of the SB file SB_FILE_VERSION = '1.2' +# direcotry to the test_rt10xx.py (this file) "./" +MAIN_FILE_DIR = os.path.dirname(__file__) +# direcotry to the cpu specific data for test +DATA_DIR = os.path.join(MAIN_FILE_DIR, "data") + class CpuParams: - """Processor specific parameters of the test""" + """Processor specific parameters of the test.""" - def __init__(self, data_dir: str, data_subdir: str, com_processor_name: str, board: str, ext_flash_cfg_word0: int, - exec_hab_audit_base: int, exec_hab_audit_start: int): + def __init__(self, data_dir: str, data_subdir: str, com_processor_name: str, board: str, cpu_data: hab_audit_log.CpuData, + ext_flash_cfg_word0: int): """Constructor. :param data_dir: base absolute path for test data :param data_subdir: name of processor specific data sub-directory :param com_processor_name: SPSDK-specific name of the target processor for communication API (MBOOT and SDP) :param board: name of the board (used to select name of the source and output image) + :param cpu_data: contains important specific data for each cpu :param ext_flash_cfg_word0: configuration word 0 for external FLASH - :param exec_hab_audit_base: base address of the `exec_hab_audit` image; `-1` if not supported - :param exec_hab_audit_start: address of the `exec_hab_audit` function in internal RAM (DTCM); `-1` not supported """ # ID of the test configuration self.id = data_subdir @@ -106,29 +112,39 @@ def __init__(self, data_dir: str, data_subdir: str, com_processor_name: str, boa self.board = board self.ext_flash_cfg_word0 = ext_flash_cfg_word0 self.ext_flash_cfg_word1 = 0 # currently zero for all RT - self.exec_hab_audit_base = exec_hab_audit_base - self.exec_hab_audit_start = exec_hab_audit_start + self.cpu_data = cpu_data def __str__(self) -> str: return self.id + ' ' + self.__class__.__name__ @classmethod - def rt1020(cls, data_dir: str) -> 'CpuParams': - """Parameters for RT1020""" - return CpuParams(data_dir, ID_RT1020, 'MXRT20', 'evkmimxrt1020', IS25WP_FLASH_CFG_WORD0, - exec_hab_audit_base=0x20008000, exec_hab_audit_start=0x2000833c) + def rt1020(cls) -> 'CpuParams': + """Parameters for RT1020.""" + return CpuParams(DATA_DIR, ID_RT1020, 'MXRT20', 'evkmimxrt1020', hab_audit_log.CpuData.MIMXRT1020, + IS25WP_FLASH_CFG_WORD0) @classmethod - def rt1050(cls, data_dir: str) -> 'CpuParams': - """Parameters for RT1050""" - return CpuParams(data_dir, ID_RT1050, 'MXRT50', 'evkbimxrt1050', IS26KS_FLASH_CFG_WORD0, - exec_hab_audit_base=0x20018000, exec_hab_audit_start=0x20018378) + def rt1050(cls) -> 'CpuParams': + """Parameters for RT1050.""" + return CpuParams(DATA_DIR, ID_RT1050, 'MXRT50', 'evkbimxrt1050', hab_audit_log.CpuData.MIMXRT1050, + IS26KS_FLASH_CFG_WORD0) @classmethod - def rt1060(cls, data_dir: str) -> 'CpuParams': - """Parameters for RT1060""" - return CpuParams(data_dir, ID_RT1060, 'MXRT60', 'evkmimxrt1060', IS25WP_FLASH_CFG_WORD0, - exec_hab_audit_base=0x20018000, exec_hab_audit_start=0x200183A4) + def rt1060(cls) -> 'CpuParams': + """Parameters for RT1060.""" + return CpuParams(DATA_DIR, ID_RT1060, 'MXRT60', 'evkmimxrt1060', hab_audit_log.CpuData.MIMXRT1060, + IS25WP_FLASH_CFG_WORD0) + +"""Maps cpu ids from CpuParams into hab_audit_log.CpuData format.""" +CpuDataMapper = { + # maps rt102x into hab_audit_log.CpuData.MIMXRT1020 + ID_RT1020 : hab_audit_log.CpuData.MIMXRT1020, + # maps rt105x into hab_audit_log.CpuData.MIMXRT1050 + ID_RT1050 : hab_audit_log.CpuData.MIMXRT1050, + # maps rt106x into hab_audit_log.CpuData.MIMXRT1060 + ID_RT1060 : hab_audit_log.CpuData.MIMXRT1060 +} + # ############################## PROCESSOR/BOARD SPECIFIC INFO ######################################## @@ -154,8 +170,7 @@ def rt1060(cls, data_dir: str) -> 'CpuParams': def pytest_generate_tests(metafunc): """Create test configurations for all tested processors""" - data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') - cpus = [CpuParams.rt1020(data_dir), CpuParams.rt1050(data_dir), CpuParams.rt1060(data_dir)] + cpus = [CpuParams.rt1020(), CpuParams.rt1050(), CpuParams.rt1060()] metafunc.parametrize("cpu_params", cpus, indirect=False, ids=[cpu_params.id for cpu_params in cpus]) @@ -353,51 +368,6 @@ def _burn_image_to_sd(cpu_params: CpuParams, img: BootImgRT, img_data: bytes) -> mboot.close() -def hab_audit_xip_app(cpu_params: CpuParams, read_log_only: bool) -> None: - """Authenticate the application in external FLASH at 0x60000000 - - The function initialized the flashloader; initializes the external FLASH, - Loads application into RAM and invokes its function, that authenticates the application and read the HAB log. - Then the HAB log is downloaded and parsed and printed to stdout. - :param cpu_params: processor specific parameters of the test - :param read_log_only: true to read HAB log without invoking authentication; False to authenticate and read-log - It is recommended to call the function firstly with parameter `True` and second time with parameter False to - see the difference. - """ - exec_hab_audit_path = os.path.join(cpu_params.data_dir, 'exec_hab_audit.bin') - if not os.path.isfile(exec_hab_audit_path): - print('\nHAB logger not supported for the processor') - return - - mboot = init_flashloader(cpu_params) - - # ### Configure external FLASH on EVK: flex-spi-nor using options on address 0x2000 ### - # call "%blhost%" -u 0x15A2,0x0073 -j -- fill-memory 0x2000 4 0xC0233007 word - assert mboot.fill_memory(INT_RAM_ADDR_DATA, 4, cpu_params.ext_flash_cfg_word0) - # call "%blhost%" -u 0x15A2,0x0073 -j -- fill-memory 0x2004 4 0x00000000 word - assert mboot.fill_memory(INT_RAM_ADDR_DATA + 4, 4, cpu_params.ext_flash_cfg_word1) - # call "%blhost%" -u 0x15A2,0x0073 -j -- configure-memory 9 0x2000 - assert mboot.configure_memory(INT_RAM_ADDR_DATA, ExtMemId.FLEX_SPI_NOR) - - exec_hab_audit_code = load_binary(exec_hab_audit_path) - # find address of the buffer in RAM, where the HAB LOG will be stored - log_addr = cpu_params.exec_hab_audit_base + exec_hab_audit_code.find(b'\xA5\x5A\x11\x22\x33\x44\x55\x66') - assert log_addr > cpu_params.exec_hab_audit_base - assert mboot.write_memory(cpu_params.exec_hab_audit_base, exec_hab_audit_code, 0) - assert mboot.call(cpu_params.exec_hab_audit_start | 1, 0 if read_log_only else 1) - - log = mboot.read_memory(log_addr, 4096, 0) - mboot.close() - - print('\n') - if log[0:4] == b'\xFF\xFF\xFF\xFF': - print('Flash not accessible or application entry out of expected value') - else: - # first three bytes are HAB status, config and state - for line in hab_audit_log.parse_hab_log(log[0], log[1], log[2], log[4:]): - print(line) - - def _init_otpmk_bee_regions(mboot: McuBoot, bee_regions: Sequence[BeeFacRegion]) -> None: """Initialize PRDB regions for BEE encryption using master key. @@ -498,7 +468,8 @@ def _burn_image_to_flash(cpu_params: CpuParams, img: BootImgRT, img_data: bytes, if AUTHENTICATE and (img.address == EXT_FLASH_ADDR) and not otpmk_bee_regions: mboot.close() - hab_audit_xip_app(cpu_params, True) + test_hab_audit(cpu_params) + else: # detect XIP image app_data = img.decrypted_app_data @@ -641,10 +612,13 @@ def test_hab_audit(cpu_params: CpuParams) -> None: if TEST_IMG_CONTENT: return # this can be used only in production mode - # read current HAB log only - hab_audit_xip_app(cpu_params, True) + mboot = init_flashloader(cpu_params) + # TODO: repair getting cpu_data + cpu_data = CpuDataMapper[cpu_params.id] + hab_audit_log.hab_audit_xip_app(cpu_data, mboot, read_log_only=True) # authenticate the application and read all HAB logs - hab_audit_xip_app(cpu_params, False) + hab_audit_log.hab_audit_xip_app(cpu_data, mboot, read_log_only=False) + mboot.close() @pytest.mark.parametrize( diff --git a/tests/mcu_examples/test_rt5xx.py b/tests/mcu_examples/test_rt5xx.py index fb3eb656..a0f1a31c 100644 --- a/tests/mcu_examples/test_rt5xx.py +++ b/tests/mcu_examples/test_rt5xx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -18,9 +18,9 @@ from spsdk.image import MasterBootImage, MasterBootImageType from spsdk.image import TrustZone from spsdk.mboot import McuBoot, scan_usb, PropertyTag, ExtMemId, KeyProvUserKeyType -from spsdk.sbfile import BootImageV21, BootSectionV2, Certificate, CertBlockV2, SBV2xAdvancedParams -from spsdk.sbfile import CmdErase, CmdLoad, CmdFill, CmdMemEnable -from spsdk.utils.crypto import Otfad, KeyBlob +from spsdk.sbfile.images import BootImageV21, BootSectionV2, CertBlockV2, SBV2xAdvancedParams +from spsdk.sbfile.commands import CmdErase, CmdLoad, CmdFill, CmdMemEnable +from spsdk.utils.crypto import Otfad, KeyBlob, Certificate from spsdk.utils.misc import align_block, load_binary from tests.misc import compare_bin_files, write_dbg_log diff --git a/tests/pfr/conftest.py b/tests/pfr/conftest.py deleted file mode 100644 index 503533f4..00000000 --- a/tests/pfr/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -from os import path -import pytest - - -@pytest.fixture -def data_dir(): - return path.join(path.dirname(__file__), 'data') diff --git a/tests/sbfile/conftest.py b/tests/sbfile/conftest.py deleted file mode 100644 index 6c705216..00000000 --- a/tests/sbfile/conftest.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2019 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -import pytest -from os import path -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa - - -@pytest.fixture(scope="module") -def data_dir(): - return path.join(path.dirname(path.abspath(__file__)), 'data') - - -@pytest.fixture(scope="module") -def private_key(): - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, - backend=default_backend() - ) - return private_key diff --git a/tests/sbfile/data/ecc_secp256r1_priv_key.pem b/tests/sbfile/data/ecc_secp256r1_priv_key.pem new file mode 100644 index 00000000..beb0fc6e --- /dev/null +++ b/tests/sbfile/data/ecc_secp256r1_priv_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUX4RRFHgex+ErU6i +mh2I6DVMx4q21cqT8xfntjlWtBGhRANCAAQDTJdCNLZYzSIWjRgSmi2BmxlCJRW+ +p6FOjiS9Wm5ixN2CQZjhwJPUJXzP2x/Vtm9o8aP0pt9UcdsMUJ6QjFnW +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/tests/sbfile/data/ecc_secp256r1_pub_key.pem b/tests/sbfile/data/ecc_secp256r1_pub_key.pem new file mode 100644 index 00000000..44cd5c36 --- /dev/null +++ b/tests/sbfile/data/ecc_secp256r1_pub_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEA0yXQjS2WM0iFo0YEpotgZsZQiUV +vqehTo4kvVpuYsTdgkGY4cCT1CV8z9sf1bZvaPGj9KbfVHHbDFCekIxZ1g== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/tests/sbfile/sb31/conftest.py b/tests/sbfile/sb31/conftest.py deleted file mode 100644 index bde4326f..00000000 --- a/tests/sbfile/sb31/conftest.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: UTF-8 -*- -# -# Copyright 2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Create path to file function.""" - -from os import path - -import pytest - - -@pytest.fixture -def data_dir(): - """Function to specify path to file.""" - return path.join(path.dirname(path.abspath(__file__)), "..", "data", "sb31") diff --git a/tests/sbfile/sb31/test_commands_api.py b/tests/sbfile/sb31/test_commands_api.py index 69055444..f2b0a5b8 100644 --- a/tests/sbfile/sb31/test_commands_api.py +++ b/tests/sbfile/sb31/test_commands_api.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Test of commands.""" @@ -12,27 +12,22 @@ from spsdk.sbfile.sb31.commands import CmdErase, CmdLoad, CmdExecute, CmdCall, CmdProgFuses, CmdProgIfr, \ CmdLoadCmac, CmdLoadHashLocking, CmdCopy, CmdFillMemory, parse_command, CmdLoadKeyBlob, CmdConfigureMemory, \ CmdSectionHeader, CmdFwVersionCheck -from spsdk.sbfile.sb31.functions import BaseCmd +from spsdk.sbfile.sb31.commands import BaseCmd def test_cmd_erase(): """Test address, length, memory_id, info value, size after export and parsing of CmdErase command.""" - cmd = CmdErase(address=100, length=0, memory_id=0) + cmd = CmdErase(address=100, length=0xFF, memory_id=10) assert cmd.address == 100 - assert cmd.length == 0 - assert cmd.memory_id == 0 + assert cmd.length == 0xFF + assert cmd.memory_id == 10 assert cmd.info() data = cmd.export() - assert len(data) % 16 == 0 + assert len(data) == 32 cmd_parsed = CmdErase.parse(data=data) - assert cmd.address == cmd_parsed.address - assert cmd.length == cmd_parsed.length - assert cmd.memory_id == cmd_parsed.memory_id - - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - assert 0x00000000 <= cmd.length <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_erase_cmd_tag(): @@ -46,27 +41,22 @@ def test_parse_invalid_cmd_erase_cmd_tag(): def test_cmd_load(): """Test address, len, memory_id, info value, size after append, export and parsing of CmdLoad command.""" - cmd = CmdLoad(address=100, length=0, memory_id=0) + cmd = CmdLoad(address=100, data=bytes(range(10)), memory_id=1) assert cmd.address == 100 - assert cmd.length == 0 - assert cmd.memory_id == 0 + assert cmd.length == 10 + assert cmd.memory_id == 1 assert cmd.info() data = cmd.export() - assert len(data) % 16 == 0 + assert len(data) == 48 cmd_parsed = CmdLoad.parse(data=data) - assert cmd.address == cmd_parsed.address - assert cmd.length == cmd_parsed.length - assert cmd.memory_id == cmd_parsed.memory_id - - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - assert 0x00000000 <= cmd.length <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_load_cmd_tag(): """CmdLoad tag validity test.""" - cmd = CmdLoad(address=0, length=0, memory_id=0) + cmd = CmdLoad(address=0, data=bytes(4), memory_id=0) cmd.cmd_tag = EnumCmdTag.CALL data = cmd.export() with pytest.raises(ValueError): @@ -85,8 +75,6 @@ def test_cmd_execute(): cmd_parsed = CmdExecute.parse(data=data) assert cmd == cmd_parsed - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - def test_parse_invalid_cmd_execute_cmd_tag(): """CmdExecute tag validity test.""" @@ -109,8 +97,6 @@ def test_cmd_call(): cmd_parsed = CmdCall.parse(data=data) assert cmd == cmd_parsed - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - def test_parse_invalid_cmd_call_cmd_tag(): """CmdCall tag validity test.""" @@ -123,32 +109,17 @@ def test_parse_invalid_cmd_call_cmd_tag(): def test_program_cmd_progfuses(): """Test address, values, info value, size after export and parsing of CmdProgFuses command.""" - cmd = CmdProgFuses(address=100, data=[0, 1, 2, 3]) + cmd = CmdProgFuses(address=100, data=bytes(12)) assert cmd.address == 100 - assert cmd.data == [0, 1, 2, 3] - assert cmd.length == 4 + assert cmd.length == 3 assert cmd.info() - cmd.data = [0, 1, 2, 3, 4] - assert cmd.length == 5 - data = cmd.export() - assert len(data) == BaseCmd.SIZE + 5 * 4 + assert len(data) == BaseCmd.SIZE + 4 * 4 cmd_parsed = CmdProgFuses.parse(data=data) assert cmd == cmd_parsed - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - - -def test_parse_invalid_cmd_progfuses_cmd_tag(): - """CmdProgFuses tag validity test.""" - cmd = CmdProgFuses(address=0, data=[0, 1, 2, 3]) - cmd.cmd_tag = EnumCmdTag.LOAD - data = cmd.export() - with pytest.raises(Exception): - CmdProgFuses.parse(data=data) - def test_cmd_progifr(): """Test address, data, info value, size after export and parsing of CmdProgIfr command.""" @@ -158,46 +129,30 @@ def test_cmd_progifr(): assert cmd.info() data = cmd.export() - assert len(data) == BaseCmd.SIZE + len(cmd.data) + assert len(data) == BaseCmd.SIZE + len(cmd.data) + 12 cmd_parsed = CmdProgIfr.parse(data=data) assert cmd == cmd_parsed - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - - -def test_parse_invalid_cmd_progifr_cmd_tag(): - """CmdProgFuses tag validity test.""" - cmd = CmdProgIfr(address=100, data=bytes([0] * 100)) - cmd.cmd_tag = EnumCmdTag.LOAD - data = cmd.export() - with pytest.raises(Exception): - CmdProgFuses.parse(data=data) - def test_cmd_loadcmac(): """Test address, length, memory_id, info value, size after export and parsing of CmdLoadCmac command.""" - cmd = CmdLoadCmac(address=100, length=0, memory_id=0) + cmd = CmdLoadCmac(address=100, data=bytes(range(10)), memory_id=0) assert cmd.address == 100 - assert cmd.length == 0 + assert cmd.length == 10 assert cmd.memory_id == 0 assert cmd.info() data = cmd.export() - assert len(data) % 16 == 0 + assert len(data) == 48 cmd_parsed = CmdLoadCmac.parse(data=data) - assert cmd.address == cmd_parsed.address - assert cmd.length == cmd_parsed.length - assert cmd.memory_id == cmd_parsed.memory_id - - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - assert 0x00000000 <= cmd.length <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_loadcmac_cmd_tag(): """CmdLoadCmac tag validity test.""" - cmd = CmdLoadCmac(address=0, length=0, memory_id=0) + cmd = CmdLoadCmac(address=0, data=bytes(10), memory_id=0) cmd.cmd_tag = EnumCmdTag.CALL data = cmd.export() with pytest.raises(ValueError): @@ -207,26 +162,19 @@ def test_parse_invalid_cmd_loadcmac_cmd_tag(): def test_cmd_copy(): """Test address, length, destination_address, memory_id_from, memory_id_to, info value, size after export and parsing of CmdCopy command.""" - cmd = CmdCopy(address=100, length=0, destination_address=0, memory_id_from=0, memory_id_to=0) + cmd = CmdCopy(address=100, length=10, destination_address=20, memory_id_from=30, memory_id_to=40) assert cmd.address == 100 - assert cmd.length == 0 - assert cmd.destination_address == 0 - assert cmd.memory_id_from == 0 - assert cmd.memory_id_to == 0 + assert cmd.length == 10 + assert cmd.destination_address == 20 + assert cmd.memory_id_from == 30 + assert cmd.memory_id_to == 40 assert cmd.info() data = cmd.export() assert len(data) % 16 == 0 cmd_parsed = CmdCopy.parse(data=data) - assert cmd.address == cmd_parsed.address - assert cmd.length == cmd_parsed.length - assert cmd.destination_address == cmd_parsed.destination_address - assert cmd.memory_id_from == cmd_parsed.memory_id_from - assert cmd.memory_id_to == cmd_parsed.memory_id_to - - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - assert 0x00000000 <= cmd.length <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_copy_cmd_tag(): @@ -240,27 +188,22 @@ def test_parse_invalid_cmd_copy_cmd_tag(): def test_cmd_loadhashlocking(): """Test address, length, memory_id, info value, size after export and parsing of CmdHashLocking command.""" - cmd = CmdLoadHashLocking(address=100, length=0, memory_id=0) + cmd = CmdLoadHashLocking(address=100, data=bytes(range(10)), memory_id=5) assert cmd.address == 100 - assert cmd.length == 0 - assert cmd.memory_id == 0 + assert cmd.length == 10 + assert cmd.memory_id == 5 assert cmd.info() data = cmd.export() - assert len(data) % 16 == 0 + assert len(data) == 48 + 64 cmd_parsed = CmdLoadHashLocking.parse(data=data) - assert cmd.address == cmd_parsed.address - assert cmd.length == cmd_parsed.length - assert cmd.memory_id == cmd_parsed.memory_id - - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - assert 0x00000000 <= cmd.length <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_loadhashlocking_cmd_tag(): """CmdLoadCmac tag validity test.""" - cmd = CmdLoadHashLocking(address=0, length=0, memory_id=0) + cmd = CmdLoadHashLocking(address=0, data=bytes(10), memory_id=0) cmd.cmd_tag = EnumCmdTag.CALL data = cmd.export() with pytest.raises(ValueError): @@ -269,7 +212,7 @@ def test_parse_invalid_cmd_loadhashlocking_cmd_tag(): def test_cmd_loadkeyblob(): """Test offset, length, key_wrap, data info value, size after export and parsing of CmdLoadKeyBlob command.""" - cmd = CmdLoadKeyBlob(offset=100, key_wrap_id=CmdLoadKeyBlob.NXP_CUST_KEK_EXT_SK, data=10 * b"x") + cmd = CmdLoadKeyBlob(offset=100, key_wrap_id=CmdLoadKeyBlob.KeyWraps.NXP_CUST_KEK_EXT_SK, data=10 * b"x") assert cmd.address == 100 assert cmd.length == 10 assert cmd.key_wrap_id == 17 @@ -285,7 +228,7 @@ def test_cmd_loadkeyblob(): def test_parse_invalid_cmd_loadkeyblob_cmd_tag(): """CmdLoadKeyBlob tag validity test.""" - cmd = CmdLoadKeyBlob(offset=100, key_wrap_id=CmdLoadKeyBlob.NXP_CUST_KEK_EXT_SK, data=bytes(10)) + cmd = CmdLoadKeyBlob(offset=100, key_wrap_id=CmdLoadKeyBlob.KeyWraps.NXP_CUST_KEK_EXT_SK, data=bytes(10)) cmd.cmd_tag = EnumCmdTag.CALL data = cmd.export() with pytest.raises(ValueError): @@ -294,19 +237,16 @@ def test_parse_invalid_cmd_loadkeyblob_cmd_tag(): def test_cmd_configurememory(): """Test address, memory_id, info value, size after export and parsing of CmdConfigureMemory command.""" - cmd = CmdConfigureMemory(address=100, memory_id=0) + cmd = CmdConfigureMemory(address=100, memory_id=10) assert cmd.address == 100 - assert cmd.memory_id == 0 + assert cmd.memory_id == 10 assert cmd.info() data = cmd.export() - assert len(data) % 16 == 0 + assert len(data) == 16 cmd_parsed = CmdConfigureMemory.parse(data=data) - assert cmd.address == cmd_parsed.address - assert cmd.memory_id == cmd_parsed.memory_id - - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_configurememory_cmd_tag(): @@ -320,27 +260,22 @@ def test_parse_invalid_cmd_configurememory_cmd_tag(): def test_cmd_fillmemory(): """Test address, length, info value, size after export and parsing of CmdFillMemory command.""" - cmd = CmdFillMemory(address=100, length=0, memory_id=0) + cmd = CmdFillMemory(address=100, length=100, pattern=0xFF1111FF) assert cmd.address == 100 - assert cmd.length == 0 - assert cmd.memory_id == 0 + assert cmd.length == 100 + assert cmd.pattern == 0xFF1111FF assert cmd.info() data = cmd.export() - assert len(data) % 16 == 0 + assert len(data) == 32 cmd_parsed = CmdFillMemory.parse(data=data) - assert cmd.address == cmd_parsed.address - assert cmd.length == cmd_parsed.length - assert cmd.memory_id == cmd_parsed.memory_id - - assert 0x00000000 <= cmd.address <= 0xFFFFFFFF - assert 0x00000000 <= cmd.length <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_fillmemory_cmd_tag(): """CmdFillMemory tag validity test.""" - cmd = CmdFillMemory(address=0, length=0, memory_id=0) + cmd = CmdFillMemory(address=0, length=0, pattern=0) cmd.cmd_tag = EnumCmdTag.CALL data = cmd.export() with pytest.raises(ValueError): @@ -349,7 +284,7 @@ def test_parse_invalid_cmd_fillmemory_cmd_tag(): def test_cmd_fwversioncheck(): """Test value, counter_id, info value, size after export and parsing of CmdFwVersionCheck command.""" - cmd = CmdFwVersionCheck(value=100, counter_id=CmdFwVersionCheck.SECURE) + cmd = CmdFwVersionCheck(value=100, counter_id=CmdFwVersionCheck.COUNTER_ID.SECURE) assert cmd.value == 100 assert cmd.counter_id == 2 assert cmd.info() @@ -358,15 +293,12 @@ def test_cmd_fwversioncheck(): assert len(data) % 16 == 0 cmd_parsed = CmdFwVersionCheck.parse(data=data) - assert cmd.value == cmd_parsed.value - assert cmd.counter_id == cmd_parsed.counter_id - - assert 0x00000000 <= cmd.value <= 0xFFFFFFFF + assert cmd == cmd_parsed def test_parse_invalid_cmd_fwversioncheck_cmd_tag(): """CmdFwVersionCheck tag validity test.""" - cmd = CmdFwVersionCheck(value=100, counter_id=CmdFwVersionCheck.SECURE) + cmd = CmdFwVersionCheck(value=100, counter_id=CmdFwVersionCheck.COUNTER_ID.SECURE) cmd.cmd_tag = EnumCmdTag.CALL data = cmd.export() with pytest.raises(ValueError): @@ -385,22 +317,20 @@ def test_section_header_cmd(): assert len(data) == BaseCmd.SIZE cmd_parsed = CmdSectionHeader.parse(data=data) - assert cmd_parsed.section_uid == 10 - assert cmd_parsed.section_type == 10 - assert cmd_parsed.length == 100 + assert cmd == cmd_parsed def test_section_cmd_header_basic(): """Test whether two section headers cmd are identical.""" - section_header = CmdSectionHeader(section_uid=10) - section_header2 = CmdSectionHeader(section_uid=500) + section_header = CmdSectionHeader(length=10) + section_header2 = CmdSectionHeader(length=500) assert section_header != section_header2, "Two different images are the same!" def test_section_cmd_header_info(): """Test presence of all keywords in info() method of section header cmd.""" - section_header = CmdSectionHeader() + section_header = CmdSectionHeader(length=100) output = section_header.info() required_strings = ["UID", "Type"] for required_string in required_strings: @@ -409,7 +339,7 @@ def test_section_cmd_header_info(): def test_section_cmd_header_offset(): """Section header cmd size validity test.""" - section_header = CmdSectionHeader() + section_header = CmdSectionHeader(length=100) data = section_header.export() with pytest.raises(ValueError): CmdSectionHeader.parse(data=data, offset=50) diff --git a/tests/sbfile/sb31/test_functions.py b/tests/sbfile/sb31/test_functions.py index 75a94370..4f0a0fc7 100644 --- a/tests/sbfile/sb31/test_functions.py +++ b/tests/sbfile/sb31/test_functions.py @@ -1,16 +1,16 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause """Test of commands.""" import pytest -from spsdk.sbfile.sb31.commands import CmdErase, CmdLoadKeyBlob -from spsdk.sbfile.sb31.functions import BaseCmd, MainCmd, add_trailing_zeros, add_leading_zeros - +from spsdk.sbfile.sb31.commands import BaseCmd, MainCmd, CmdErase, CmdLoadKeyBlob +from spsdk.sbfile.sb31.functions import add_trailing_zeros, add_leading_zeros +from spsdk.sbfile.sb31.functions import _get_key_derivation_data, KeyDerivator def test_invalid_header_parse(): """Test invalid header parse function.""" @@ -62,3 +62,35 @@ def test_value_range(): # ) # # assert cmd.data == bytes(16) + +@pytest.mark.parametrize( + [ + 'derivation_constant', 'kdk_access_rights', 'mode', + 'key_length', 'iteration', 'result' + ], + [ + (15, 3, 2, 256, 1, '0F00000000000000000000000000000000000000c01000210000010000000001'), + (15, 3, 2, 256, 2, '0F00000000000000000000000000000000000000c01000210000010000000002'), + (0x27C0E97C, 3, 1, 256, 1, '7ce9c02700000000000000000000000000000000c00100210000010000000001'), + ] +) +def test_get_key_derivation_data( + derivation_constant, kdk_access_rights, mode, + key_length, iteration, result +): + derivation_data = _get_key_derivation_data( + derivation_constant, kdk_access_rights, + mode, key_length, iteration) + assert derivation_data == bytes.fromhex(result) + + +def test_key_derivator(): + pck = bytes.fromhex("24e517d4ac417737235b6efc9afced8224e517d4ac417737235b6efc9afced82") + derivator = KeyDerivator( + pck=pck, timestamp=0x27C0E97C, kdk_access_rights=3, key_length=128 + ) + assert derivator.kdk == bytes.fromhex("751d0802bc9eb9adb42b68d40880aa6e") + assert derivator.get_block_key(10) == bytearray.fromhex("40902f79dd0ec371307f7069590ad07a") + assert derivator.get_block_key(13) == bytearray.fromhex("69362b5634b99b689a7c43df76f15b63") + assert derivator.get_block_key(6) == bytearray.fromhex("4c28803b5de193c21f31e6fa10c76b03") + diff --git a/tests/sbfile/sb31/test_imge_api.py b/tests/sbfile/sb31/test_imge_api.py new file mode 100644 index 00000000..2cfab740 --- /dev/null +++ b/tests/sbfile/sb31/test_imge_api.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +import pytest + +from spsdk import SPSDKError +from spsdk.sbfile.sb31.images import SecureBinary31Commands, SecureBinary31Header +from spsdk.sbfile.sb31 import commands + +def test_sb31_header_error(): + with pytest.raises(NotImplementedError): + SecureBinary31Header.parse(bytes(100)) + + with pytest.raises(SPSDKError): + SecureBinary31Header(firmware_version=1, curve_name='totally-legit-curve') + + header = SecureBinary31Header(1, 'secp256r1') + header.curve_name = 'wrong-name' + with pytest.raises(SPSDKError): + header.calculate_block_size() + with pytest.raises(SPSDKError): + header.calculate_cert_block_offset() + + +def test_sb31_header_description(): + header = SecureBinary31Header(1, 'secp256r1') + assert header.description == bytes(16) + header = SecureBinary31Header(1, 'secp256r1', description='desc') + assert header.description == b'desc' + bytes(12) + header = SecureBinary31Header(1, 'secp256r1', description='very long description') + assert header.description == b'very long descri' + assert header.info() + + +def test_sb31_commands_errors(): + with pytest.raises(NotImplementedError): + SecureBinary31Commands.parse(bytes(100)) + + with pytest.raises(SPSDKError): + SecureBinary31Commands(curve_name='secp384r1') + + +def test_sb31_commands_add(): + sc = SecureBinary31Commands(curve_name='secp256r1', is_encrypted=False) + sc.add_command(commands.CmdCall(0x100)) + assert len(sc.commands) == 1 + info = sc.info() + assert "CALL: Address=" in info diff --git a/tests/sbfile/test_backend_openssl.py b/tests/sbfile/test_backend_openssl.py index b72a9144..d87e279b 100644 --- a/tests/sbfile/test_backend_openssl.py +++ b/tests/sbfile/test_backend_openssl.py @@ -1,16 +1,18 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause import os from binascii import unhexlify -from cryptography.hazmat.backends import openssl +import pytest +from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization +from spsdk import SPSDKError from spsdk.utils.crypto.backend_openssl import openssl_backend @@ -74,7 +76,7 @@ def test_rsa_sign(data_dir): with open(os.path.join(data_dir, 'selfsign_privatekey_rsa2048.pem'), "rb") as key_file: key_data = key_file.read() - private_key = serialization.load_pem_private_key(data=key_data, password=None, backend=openssl.backend) + private_key = serialization.load_pem_private_key(data=key_data, password=None, backend=default_backend()) signature = b'\xc2!b\xf9\xb7$<2\x07|\x86,\xdd\x003\x93\x95\x1a\x92?\xf5\x1c\xfce\xfd#\x02\x1b\xd5&\xec\xf8`' \ b'\xe1\x1ex\xfd=Ls\xaf\x81\x12[\xe8\x80n\xc17\x8b\x9a\xba\x86N\xcbCd\xfb\xe7\xfei\xcc\x90\x02\xbf)' \ @@ -100,7 +102,7 @@ def test_rsa_verify(data_dir): private_key = serialization.load_pem_private_key( data=key_file.read(), password=None, - backend=openssl.backend + backend=default_backend() ) signature = b'\xc2!b\xf9\xb7$<2\x07|\x86,\xdd\x003\x93\x95\x1a\x92?\xf5\x1c\xfce\xfd#\x02\x1b\xd5&\xec\xf8`' \ @@ -115,3 +117,44 @@ def test_rsa_verify(data_dir): pub_nums = private_key.public_key().public_numbers() is_valid = openssl_backend.rsa_verify(pub_nums.n, pub_nums.e, signature, data) assert is_valid + + +def test_ecc_sign_verify(data_dir): + with open(os.path.join(data_dir, 'ecc_secp256r1_priv_key.pem'), "rb") as key_file: + private_key_data = key_file.read() + with open(os.path.join(data_dir, 'ecc_secp256r1_pub_key.pem'), "rb") as key_file: + public_key_data = key_file.read() + + private_key = serialization.load_pem_private_key(data=private_key_data, password=None, backend=default_backend()) + data = b'THIS IS MESSAGE TO BE SIGNED' + calc_signature = openssl_backend.ecc_sign(private_key_data, data) + calc_signature2 = openssl_backend.ecc_sign(private_key, data) + # openssl utilize randomized signature thus two signatures are different + assert calc_signature != calc_signature2 + + public_key = serialization.load_pem_public_key(data=public_key_data, backend=default_backend()) + is_valid = openssl_backend.ecc_verify(public_key=public_key, signature=calc_signature, data=data) + is_valid2 = openssl_backend.ecc_verify(public_key=public_key_data, signature=calc_signature2, data=data) + # randomized signatures are still valid + assert is_valid == is_valid2 == True + + +def test_ecc_sign_verify_incorrect(data_dir): + with open(os.path.join(data_dir, 'ecc_secp256r1_priv_key.pem'), "rb") as key_file: + private_key_data = key_file.read() + with open(os.path.join(data_dir, 'ecc_secp256r1_pub_key.pem'), "rb") as key_file: + public_key_data = key_file.read() + + private_key = serialization.load_pem_private_key(data=private_key_data, password=None, backend=default_backend()) + data = b'THIS IS MESSAGE TO BE SIGNED' + calc_signature = openssl_backend.ecc_sign(private_key_data, data) + + # malform the signature + bad_signature = calc_signature[:-2] + bytes(2) + is_valid = openssl_backend.ecc_verify(public_key=public_key_data, signature=bad_signature, data=data) + assert is_valid == False + + # make signature bigger than expected + with pytest.raises(SPSDKError): + bad_signature = calc_signature + bytes(2) + openssl_backend.ecc_verify(public_key=public_key_data, signature=bad_signature, data=data) diff --git a/tests/sbfile/test_backend_python.py b/tests/sbfile/test_backend_python.py index aa124011..bfb4bf0c 100644 --- a/tests/sbfile/test_backend_python.py +++ b/tests/sbfile/test_backend_python.py @@ -1,15 +1,17 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause import os from binascii import unhexlify +import pytest from Crypto.PublicKey import RSA +from spsdk import SPSDKError from spsdk.utils.crypto.backend_internal import internal_backend @@ -69,6 +71,21 @@ def test_aes_ctr_decrypt(): assert calc_plain_text == plain_text +def test_aes_encrypt(): + key = b'1234567812345678' + plain_text = bytes(16) + cipher_text = b'\x9a\xe8\xfd\x02\xb3@(\x8a\x0e{\xbf\xf0\xf0\xbaT\xd6' + calc_cipher_text = internal_backend.aes_cbc_encrypt(key, plain_text) + assert calc_cipher_text == cipher_text + + +def test_aes_encrypt_error(): + with pytest.raises(SPSDKError): + internal_backend.aes_cbc_encrypt(bytes(2), bytes(16)) + with pytest.raises(SPSDKError): + internal_backend.aes_cbc_encrypt(bytes(16), bytes(16), bytes(2)) + + def test_rsa_sign(data_dir): with open(os.path.join(data_dir, 'selfsign_privatekey_rsa2048.pem'), "rb") as key_file: key_pem_data = key_file.read() @@ -109,3 +126,53 @@ def test_rsa_verify(data_dir): data = bytes([v % 256 for v in range(0, 512)]) is_valid = internal_backend.rsa_verify(public_key.n, public_key.e, signature, data) assert is_valid + + +def test_ecc_verify(data_dir): + with open(os.path.join(data_dir, 'ecc_secp256r1_pub_key.pem'), "rb") as key_file: + public_key = key_file.read() + data = b'THIS IS MESSAGE TO BE SIGNED' + signature = ( + b'\x1f\xfd\x07\xf2\xa5GJ\x08\xa11\xce\x90\xcb\x80\xa1\x8dD)\x89|Ko' + b'\xc3\x0f~\x8b\xd7<\xb02,DIr\x15\x0br\x98\x8c\xa7\x93\x0f\x19\x85' + b'V\x13\xfc\x94\x07Y\xab\xcax\xc5\x15\x07\x8d\xaeQ-mE1\xc9' + ) + is_valid = internal_backend.ecc_verify(key=public_key, signature=signature, data=data) + assert is_valid + +def test_ecc_sign(data_dir): + with open(os.path.join(data_dir, 'ecc_secp256r1_priv_key.pem'), "rb") as key_file: + private_key_pem_data = key_file.read() + signature = ( + b'\x1f\xfd\x07\xf2\xa5GJ\x08\xa11\xce\x90\xcb\x80\xa1\x8dD)\x89|Ko' + b'\xc3\x0f~\x8b\xd7<\xb02,DIr\x15\x0br\x98\x8c\xa7\x93\x0f\x19\x85' + b'V\x13\xfc\x94\x07Y\xab\xcax\xc5\x15\x07\x8d\xaeQ-mE1\xc9' + ) + data = b'THIS IS MESSAGE TO BE SIGNED' + calc_signature = internal_backend.ecc_sign(private_key_pem_data, data) + assert calc_signature == signature + + +def test_ecc_verify_wrong(data_dir): + with open(os.path.join(data_dir, 'ecc_secp256r1_priv_key.pem'), "rb") as key_file: + private_key_pem_data = key_file.read() + data = b'THIS IS MESSAGE TO BE SIGNED' + calc_signature = internal_backend.ecc_sign(private_key_pem_data, data) + + # malform the signature + bad_signature = calc_signature[:-2] + bytes(2) + is_valid = internal_backend.ecc_verify(private_key_pem_data, bad_signature, data) + assert is_valid == False + + # make signature bigger than expected + with pytest.raises(SPSDKError): + bad_signature = calc_signature + bytes(2) + internal_backend.ecc_verify(private_key_pem_data, bad_signature, data) + + +def test_cmac(): + data = b'THIS IS MESSAGE' + key = b'Sixteen byte key' + generated_cmac = internal_backend.cmac(data=data, key=key) + openssl_cmac = b'\xa2\n\xd4O\xc1\xea\x19\xd1\xff\x00{\xac\xc5xZ\x9f' + assert generated_cmac == openssl_cmac diff --git a/tests/sbfile/test_commands_api.py b/tests/sbfile/test_commands_api.py index a227fa55..555ca73b 100644 --- a/tests/sbfile/test_commands_api.py +++ b/tests/sbfile/test_commands_api.py @@ -1,14 +1,14 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause import pytest -from spsdk.sbfile import CmdNop, CmdCall, CmdErase, CmdFill, CmdJump, CmdLoad, CmdMemEnable, CmdProg, CmdReset -from spsdk.sbfile import VersionCheckType, CmdVersionCheck, CmdKeyStoreBackup, CmdKeyStoreRestore +from spsdk.sbfile.commands import CmdNop, CmdCall, CmdErase, CmdFill, CmdJump, CmdLoad, CmdMemEnable, CmdProg, CmdReset +from spsdk.sbfile.commands import VersionCheckType, CmdVersionCheck, CmdKeyStoreBackup, CmdKeyStoreRestore from spsdk.sbfile.commands import CmdTag, parse_command from spsdk.mboot import ExtMemId diff --git a/tests/sbfile/test_sbfile_image.py b/tests/sbfile/test_sbfile_image.py index 5459404a..312bff95 100644 --- a/tests/sbfile/test_sbfile_image.py +++ b/tests/sbfile/test_sbfile_image.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2019-2020 NXP +# Copyright 2019-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -12,10 +12,10 @@ import pytest -from spsdk.sbfile import BootImageV20, BootImageV21, BootSectionV2, Certificate, CertBlockV2, SBV2xAdvancedParams -from spsdk.sbfile import CmdErase, CmdLoad, CmdCall, CmdJump, CmdReset -from spsdk.sbfile import CmdVersionCheck, VersionCheckType, CmdKeyStoreBackup, CmdKeyStoreRestore -from spsdk.utils.crypto import Otfad, KeyBlob +from spsdk.sbfile.images import BootImageV20, BootImageV21, BootSectionV2, CertBlockV2, SBV2xAdvancedParams +from spsdk.sbfile.commands import CmdErase, CmdLoad, CmdCall, CmdJump, CmdReset +from spsdk.sbfile.commands import CmdVersionCheck, VersionCheckType, CmdKeyStoreBackup, CmdKeyStoreRestore +from spsdk.utils.crypto import Otfad, KeyBlob, Certificate from spsdk.utils.easy_enum import Enum from spsdk.utils.misc import align_block diff --git a/tests/shadowregs/conftest.py b/tests/shadowregs/conftest.py new file mode 100644 index 00000000..432236a9 --- /dev/null +++ b/tests/shadowregs/conftest.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2020-2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +from os import path +import pytest + +from spsdk.debuggers.utils import PROBES +from tests.debuggers.debug_probe_virtual import DebugProbeVirtual + +# Extend standard list of debug probes by virtual to allow unit testing +PROBES['virtual'] = DebugProbeVirtual diff --git a/tests/shadowregs/data/reg_config.json b/tests/shadowregs/data/reg_config.json new file mode 100644 index 00000000..192edfda --- /dev/null +++ b/tests/shadowregs/data/reg_config.json @@ -0,0 +1,33 @@ +{ + "devices": { + "test_device1": { + "revisions": { + "x0": "test_device1_x0.xml", + "x1": "test_device1_x1.xml" + }, + "latest": "x1", + "address": "0xA5A5_1234", + "inverted_regs": { + "INVERTED_REG": "INVERTED_REG_AP" + }, + "computed_fields": { + "COMPUTED_REG": {"TEST_FIELD1": "computed_reg_test_field1", "TEST_FIELD2": "computed_reg_test_field2"}, + "COMPUTED_REG2": {"TEST_FIELD1": "computed_reg2_test_field1", "TEST_FIELD2": "computed_reg2_test_field2"} + } + }, + "test_device2": { + "revisions": { + "b0": "test_device2_b0.xml" + }, + "latest": "b0", + "address": "0x4000_0000", + "inverted_regs": { + "INVERTED_REG": "INVERTED_REG_AP" + }, + "computed_fields": { + "COMPUTED_REG": {"TEST_FIELD1": "computed_reg_test_field1", "TEST_FIELD2": "computed_reg_test_field2"}, + "COMPUTED_REG2": {"TEST_FIELD1": "computed_reg2_test_field1", "TEST_FIELD2": "computed_reg2_test_field2"} + } + } + } +} diff --git a/tests/shadowregs/data/registers.xml b/tests/shadowregs/data/registers.xml new file mode 100644 index 00000000..c72c16b2 --- /dev/null +++ b/tests/shadowregs/data/registers.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/shadowregs/data/registers_corr.xml b/tests/shadowregs/data/registers_corr.xml new file mode 100644 index 00000000..130a5e7e --- /dev/null +++ b/tests/shadowregs/data/registers_corr.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/shadowregs/data/sh_regs_corrupted.yml b/tests/shadowregs/data/sh_regs_corrupted.yml new file mode 100644 index 00000000..1b75101b --- /dev/null +++ b/tests/shadowregs/data/sh_regs_corrupted.yml @@ -0,0 +1,52 @@ +registers: + REG1: +# Reg Description:Register 1, used for testing antipole register and some computed fields. + name: REG1 # The name of the register + bitfields: + CRC8: 120 # The width: 8 bits + BITFIELD1: DISABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD2: ENABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD3: ENABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD4: DISABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD5: ENABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD6: DISABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD7: ENABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD8: DISABLED # The width: 1 bits, (Possible values: DISABLED, ENABLED) + BITFIELD9: CLOSE # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD10: CLOSE # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD11: OPEN # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD12: CLOSE # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD13: OPEN # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD14: OPEN # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD15: CLOSE # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD16: CLOSE # The width: 1 bits, (Possible values: CLOSE, OPEN) + BITFIELD17: 0 # The width: 1 bits + BITFIELD18_NONE_EXISTING: 0 # The width: 1 bits + RSRVD: 9 # The width: 7 bits + REG_NONE_EXISTING: +# Reg Description:None existing register used for testing. + name: REG_NONE_EXISTING # The name of the register + value: '0x5a5a5a5a' # The value of the register + REG2: +# Reg Description:Register 2 used for shadow registers testing. + name: REG2 # The name of the register + bitfields: + BITFIELD 1: 1 # The width: 4 bits + BITFIELD 2: 0 # The width: 1 bits + BITFIELD 3: 1 # The width: 2 bits + BITFIELD 4: STOP # The width: 1 bits, (Possible values: STOP, RUN) + BITFIELD 5: STOP # The width: 1 bits, (Possible values: HALT, STOP) + BITFIELD 6: RED # The width: 1 bits, (Possible values: GREEN, RED) + RESERVED: 134 # The width: 11 bits + REG_INVERTED_AP: +# Reg Description:Anti-pole (inverse) value REG1. + name: REG_INVERTED_AP # The name of the register + value: '0xa5a5a5a5' # The value of the register + REG_BIG: +# Reg Description:Testing register for long values. + name: REG_BIG # The name of the register +# value: '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' # The value of the register This register has no data for testing! + REG_BIG_REV: +# Reg Description:Testing register for long reversed values. + name: REG_BIG_REV # The name of the register + value: '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' # The value of the register diff --git a/tests/shadowregs/data/sh_test_dev.json b/tests/shadowregs/data/sh_test_dev.json new file mode 100644 index 00000000..1f8e430a --- /dev/null +++ b/tests/shadowregs/data/sh_test_dev.json @@ -0,0 +1,18 @@ +{ + "devices": { + "sh_test_dev": { + "revisions": { + "x0": "sh_test_dev_x0.xml" + }, + "latest": "x0", + "address": "0x4000_0000", + "inverted_regs": { + "REG1": "REG_INVERTED_AP" + }, + "computed_fields": { + "REG1": {"CMP1": "comalg_dcfg_cc_socu_rsvd", "CRC8": "comalg_dcfg_cc_socu_crc8"}, + "REG2": {"RESERVED": "comalg_do_nothig"} + } + } + } +} diff --git a/tests/shadowregs/data/sh_test_dev_x0.xml b/tests/shadowregs/data/sh_test_dev_x0.xml new file mode 100644 index 00000000..40926c29 --- /dev/null +++ b/tests/shadowregs/data/sh_test_dev_x0.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/shadowregs/data/test_database.json b/tests/shadowregs/data/test_database.json new file mode 100644 index 00000000..1f8e430a --- /dev/null +++ b/tests/shadowregs/data/test_database.json @@ -0,0 +1,18 @@ +{ + "devices": { + "sh_test_dev": { + "revisions": { + "x0": "sh_test_dev_x0.xml" + }, + "latest": "x0", + "address": "0x4000_0000", + "inverted_regs": { + "REG1": "REG_INVERTED_AP" + }, + "computed_fields": { + "REG1": {"CMP1": "comalg_dcfg_cc_socu_rsvd", "CRC8": "comalg_dcfg_cc_socu_crc8"}, + "REG2": {"RESERVED": "comalg_do_nothig"} + } + } + } +} diff --git a/tests/shadowregs/data/test_database_invalid_computed.json b/tests/shadowregs/data/test_database_invalid_computed.json new file mode 100644 index 00000000..4771d845 --- /dev/null +++ b/tests/shadowregs/data/test_database_invalid_computed.json @@ -0,0 +1,18 @@ +{ + "devices": { + "sh_test_dev": { + "revisions": { + "x0": "sh_test_dev_x0.xml" + }, + "latest": "x0", + "address": "0x4000_0000", + "inverted_regs": { + "REG1": "REG_INVERTED_AP" + }, + "computed_fields": { + "REG1": {"CMP1": "comalg_dcfg_cc_socu_invalid", "CRC8": "comalg_dcfg_cc_socu_crc8"}, + "REG2": {"RESERVED": "comalg_do_nothig"} + } + } + } +} diff --git a/tests/shadowregs/test_registers.py b/tests/shadowregs/test_registers.py new file mode 100644 index 00000000..cdbc84da --- /dev/null +++ b/tests/shadowregs/test_registers.py @@ -0,0 +1,461 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +""" Tests for registers utility.""" + +import os +import filecmp +import pytest + +from spsdk.utils.registers import (BitfieldNotFound, EnumNotFound, RegConfig, Registers, + RegsRegister, + RegsBitField, + RegsEnum, + RegisterNotFound) + +from spsdk.utils.misc import use_working_directory + +TEST_DEVICE_NAME = "TestDevice1" +TEST_REG_NAME = "TestReg" +TEST_REG_OFFSET = 1024 +TEST_REG_WIDTH = 32 +TEST_REG_DESCR = "TestReg Description" +TEST_REG_REV = False +TEST_REG_ACCESS = "RW" +TEST_REG_VALUE = 0xA5A5A5A5 + +TEST_BITFIELD_NAME = "TestBitfiled" +TEST_BITFILED_OFFSET = 0x0F +TEST_BITFILED_WIDTH = 5 +TEST_BITFIELD_RESET_VAL = 33 +TEST_BITFIELD_ACCESS = "RW" +TEST_BITFIELD_DESCR = "Test Bitfield Description" +TEST_BITFIELD_SAVEVAL = 29 +TEST_BITFIELD_OUTOFRANGEVAL = 70 + +TEST_ENUM_NAME = "TestEnum" +TEST_ENUM_VALUE_BIN = "0b10001" +TEST_ENUM_VALUE_HEX = "0x11" +TEST_ENUM_VALUE_STRINT = "017" +TEST_ENUM_VALUE_INT = 17 +TEST_ENUM_VALUE_BYTES = b'\x11' +TEST_ENUM_RES_VAL = "0b010001" +TEST_ENUM_DESCR = "Test Enum Description" +TEST_ENUM_MAXWIDTH = 6 + +TEST_XML_FILE = "unit_test.xml" + +def test_basic_regs(tmpdir): + """Basic test of registers class.""" + regs = Registers(TEST_DEVICE_NAME) + + assert regs.dev_name == TEST_DEVICE_NAME + + reg1 = RegsRegister(TEST_REG_NAME, TEST_REG_OFFSET, TEST_REG_WIDTH, TEST_REG_DESCR, TEST_REG_REV, TEST_REG_ACCESS) + + with pytest.raises(RegisterNotFound): + regs.find_reg("NonExisting") + + # Ther Registers MUST return empty erray + assert regs.get_reg_names() == [] + + with pytest.raises(TypeError): + regs.remove_register("String") + + with pytest.raises(ValueError): + regs.remove_register(reg1) + + # Now we could do tests with a register added to list + regs.add_register(reg1) + + regs.remove_register_by_name(["String"]) + + assert TEST_REG_NAME in regs.get_reg_names() + + regt = regs.find_reg(TEST_REG_NAME) + + assert regt == reg1 + + with pytest.raises(TypeError): + regs.add_register("Invalid Parameter") + + regt.set_value(TEST_REG_VALUE) + assert reg1.get_value() == TEST_REG_VALUE.to_bytes(4,"big") + + filename = os.path.join(tmpdir, TEST_XML_FILE) + regs.write_xml(filename) + assert os.path.isfile(filename) + + printed_str = str(regs) + + assert TEST_DEVICE_NAME in printed_str + assert TEST_REG_NAME in printed_str + + regs.remove_register_by_name([TEST_REG_NAME]) + + with pytest.raises(RegisterNotFound): + regs.find_reg(TEST_REG_NAME) + assert False + +def test_register(): + parent_reg = RegsRegister(TEST_REG_NAME, + TEST_REG_OFFSET, + TEST_REG_WIDTH, + TEST_REG_DESCR, + TEST_REG_REV, + TEST_REG_ACCESS) + + bitfield = RegsBitField(parent_reg, + TEST_BITFIELD_NAME, + TEST_BITFILED_OFFSET, + TEST_BITFILED_WIDTH, + TEST_BITFIELD_DESCR, + TEST_BITFIELD_RESET_VAL, + TEST_BITFIELD_ACCESS) + + enum = RegsEnum(TEST_ENUM_NAME, 0, TEST_ENUM_DESCR) + bitfield.add_enum(enum) + + parent_reg.add_bitfield(bitfield) + + printed_str = str(parent_reg) + + assert "Name:" in printed_str + assert TEST_REG_NAME in printed_str + assert TEST_REG_DESCR in printed_str + assert "Width:" in printed_str + assert "Access:" in printed_str + assert "Bitfield" in printed_str + assert TEST_BITFIELD_NAME in printed_str + assert TEST_BITFIELD_DESCR in printed_str + assert TEST_ENUM_NAME in printed_str + assert TEST_ENUM_DESCR in printed_str + +def test_register_invalid_val(): + reg = RegsRegister(TEST_REG_NAME, + TEST_REG_OFFSET, + TEST_REG_WIDTH, + TEST_REG_DESCR, + TEST_REG_REV, + TEST_REG_ACCESS) + + reg.set_value("Invalid") + assert reg.get_value() == b'' + + reg.set_value([1, 2]) + assert reg.get_value() == b'' + +def test_enum(): + enum = RegsEnum(TEST_ENUM_NAME, 0, TEST_ENUM_DESCR) + + printed_str = str(enum) + + assert "Name:" in printed_str + assert "Value:" in printed_str + assert "Description:" in printed_str + assert TEST_ENUM_NAME in printed_str + assert "0b0" in printed_str + assert TEST_ENUM_DESCR in printed_str + +def test_enum_bin(): + enum = RegsEnum(TEST_ENUM_NAME, TEST_ENUM_VALUE_BIN, TEST_ENUM_DESCR, TEST_ENUM_MAXWIDTH) + printed_str = str(enum) + assert TEST_ENUM_RES_VAL in printed_str + +def test_enum_hex(): + enum = RegsEnum(TEST_ENUM_NAME, TEST_ENUM_VALUE_HEX, TEST_ENUM_DESCR, TEST_ENUM_MAXWIDTH) + printed_str = str(enum) + assert TEST_ENUM_RES_VAL in printed_str + +def test_enum_strint(): + enum = RegsEnum(TEST_ENUM_NAME, TEST_ENUM_VALUE_STRINT, TEST_ENUM_DESCR, TEST_ENUM_MAXWIDTH) + printed_str = str(enum) + assert TEST_ENUM_RES_VAL in printed_str + +def test_enum_int(): + enum = RegsEnum(TEST_ENUM_NAME, TEST_ENUM_VALUE_INT, TEST_ENUM_DESCR, TEST_ENUM_MAXWIDTH) + printed_str = str(enum) + assert TEST_ENUM_RES_VAL in printed_str + +def test_enum_bytes(): + enum = RegsEnum(TEST_ENUM_NAME, TEST_ENUM_VALUE_BYTES, TEST_ENUM_DESCR, TEST_ENUM_MAXWIDTH) + printed_str = str(enum) + assert TEST_ENUM_RES_VAL in printed_str + +def test_enum_invalidval(): + try: + enum = RegsEnum(TEST_ENUM_NAME, "InvalidValue", TEST_ENUM_DESCR, TEST_ENUM_MAXWIDTH) + printed_str = str(enum) + assert "N/A" in printed_str + except TypeError: + assert 0 + +def test_bitfield(): + parent_reg = RegsRegister(TEST_REG_NAME, + TEST_REG_OFFSET, + TEST_REG_WIDTH, + TEST_REG_DESCR, + TEST_REG_REV, + TEST_REG_ACCESS) + + bitfield = RegsBitField(parent_reg, + TEST_BITFIELD_NAME, + TEST_BITFILED_OFFSET, + TEST_BITFILED_WIDTH, + TEST_BITFIELD_DESCR, + TEST_BITFIELD_RESET_VAL, + TEST_BITFIELD_ACCESS) + + enum = RegsEnum(TEST_ENUM_NAME, 0, TEST_ENUM_DESCR) + bitfield.add_enum(enum) + + parent_reg.add_bitfield(bitfield) + + printed_str = str(bitfield) + + assert "Name:" in printed_str + assert "Offset:" in printed_str + assert "Width:" in printed_str + assert "Access:" in printed_str + assert "Reset val:" in printed_str + assert "Description:" in printed_str + assert "Enum" in printed_str + +def test_bitfield_find(): + parent_reg = RegsRegister(TEST_REG_NAME, + TEST_REG_OFFSET, + TEST_REG_WIDTH, + TEST_REG_DESCR, + TEST_REG_REV, + TEST_REG_ACCESS) + + bitfield = RegsBitField(parent_reg, + TEST_BITFIELD_NAME, + TEST_BITFILED_OFFSET, + TEST_BITFILED_WIDTH, + TEST_BITFIELD_DESCR, + TEST_BITFIELD_RESET_VAL, + TEST_BITFIELD_ACCESS) + + enum = RegsEnum(TEST_ENUM_NAME, 0, TEST_ENUM_DESCR) + bitfield.add_enum(enum) + + parent_reg.add_bitfield(bitfield) + + assert bitfield == parent_reg.find_bitfield(TEST_BITFIELD_NAME) + + with pytest.raises(BitfieldNotFound): + parent_reg.find_bitfield("Invalid Name") + +def test_bitfield_has_enums(): + parent_reg = RegsRegister(TEST_REG_NAME, + TEST_REG_OFFSET, + TEST_REG_WIDTH, + TEST_REG_DESCR, + TEST_REG_REV, + TEST_REG_ACCESS) + + bitfield = RegsBitField(parent_reg, + TEST_BITFIELD_NAME, + TEST_BITFILED_OFFSET, + TEST_BITFILED_WIDTH, + TEST_BITFIELD_DESCR, + TEST_BITFIELD_RESET_VAL, + TEST_BITFIELD_ACCESS) + + parent_reg.add_bitfield(bitfield) + + assert bitfield.has_enums() is False + enum = RegsEnum(TEST_ENUM_NAME, 0, TEST_ENUM_DESCR) + bitfield.add_enum(enum) + + assert bitfield.has_enums() is True + + assert enum in bitfield.get_enums() + +def test_bitfield_value(): + parent_reg = RegsRegister(TEST_REG_NAME, + TEST_REG_OFFSET, + TEST_REG_WIDTH, + TEST_REG_DESCR, + TEST_REG_REV, + TEST_REG_ACCESS) + + bitfield = RegsBitField(parent_reg, + TEST_BITFIELD_NAME, + TEST_BITFILED_OFFSET, + TEST_BITFILED_WIDTH, + TEST_BITFIELD_DESCR, + TEST_BITFIELD_RESET_VAL, + TEST_BITFIELD_ACCESS) + + bitfield.set_value(TEST_BITFIELD_SAVEVAL) + assert bitfield.get_value() == TEST_BITFIELD_SAVEVAL + + with pytest.raises(ValueError): + bitfield.set_value(TEST_BITFIELD_OUTOFRANGEVAL) + +def test_bitfield_enums(): + parent_reg = RegsRegister(TEST_REG_NAME, + TEST_REG_OFFSET, + TEST_REG_WIDTH, + TEST_REG_DESCR, + TEST_REG_REV, + TEST_REG_ACCESS) + + bitfield = RegsBitField(parent_reg, + TEST_BITFIELD_NAME, + TEST_BITFILED_OFFSET, + TEST_BITFILED_WIDTH, + TEST_BITFIELD_DESCR, + TEST_BITFIELD_RESET_VAL, + TEST_BITFIELD_ACCESS) + + parent_reg.add_bitfield(bitfield) + + enums = [] + for n in range((1 << TEST_BITFILED_WIDTH)-1): + enum = RegsEnum(f"{TEST_ENUM_NAME}{n}", n, f"{TEST_ENUM_DESCR}{n}", TEST_BITFILED_WIDTH) + enums.append(enum) + bitfield.add_enum(enum) + + enum_names = bitfield.get_enum_names() + + for n in range((1 << TEST_BITFILED_WIDTH)-1): + assert n == bitfield.get_enum_constant(f"{TEST_ENUM_NAME}{n}") + assert enums[n].name in enum_names + + for n in range((1 << TEST_BITFILED_WIDTH)): + bitfield.set_value(n) + if n < (1 << TEST_BITFILED_WIDTH)-1: + assert f"{TEST_ENUM_NAME}{n}" == bitfield.get_enum_value() + else: + assert n == bitfield.get_enum_value() + + for n in range((1 << TEST_BITFILED_WIDTH)-1): + bitfield.set_enum_value(f"{TEST_ENUM_NAME}{n}") + assert n == bitfield.get_value() + + with pytest.raises(EnumNotFound): + bitfield.get_enum_constant("Invalid name") + + regs = Registers(TEST_DEVICE_NAME) + + regs.add_register(parent_reg) + +def test_registers_xml(data_dir, tmpdir): + regs = Registers(TEST_DEVICE_NAME) + + with use_working_directory(data_dir): + regs.load_registers_from_xml("registers.xml") + + with use_working_directory(tmpdir): + regs.write_xml("registers.xml") + + regs2 = Registers(TEST_DEVICE_NAME) + + with use_working_directory(tmpdir): + regs2.load_registers_from_xml("registers.xml") + + assert str(regs) == str(regs2) + +def test_registers_corrupted_xml(data_dir, tmpdir): + regs = Registers(TEST_DEVICE_NAME) + + with use_working_directory(data_dir): + regs.load_registers_from_xml("registers_corr.xml") + + with use_working_directory(tmpdir): + regs.write_xml("registers_corr.xml") + + assert not filecmp.cmp(os.path.join(data_dir, "registers_corr.xml"), os.path.join(tmpdir, "registers_corr.xml")) + + regs.clear() + + with use_working_directory(tmpdir): + regs.load_registers_from_xml("registers_corr.xml") + regs.write_xml("registers_corr1.xml") + + assert filecmp.cmp(os.path.join(tmpdir, "registers_corr.xml"), os.path.join(tmpdir, "registers_corr1.xml")) + + # Without clear - Cannot add register with same name as is already added + with use_working_directory(tmpdir): + regs.load_registers_from_xml("registers_corr.xml") + regs.write_xml("registers_corr1.xml") + + assert filecmp.cmp(os.path.join(tmpdir, "registers_corr.xml"), os.path.join(tmpdir, "registers_corr1.xml")) + +def test_reg_config_get_devices(data_dir): + reg_config = RegConfig(os.path.join(data_dir, "reg_config.json")) + devices = reg_config.get_devices() + + assert "test_device1" in devices + assert "test_device2" in devices + +def test_reg_config_get_devices_class(data_dir): + devices = RegConfig.devices(os.path.join(data_dir, "reg_config.json")) + + assert "test_device1" in devices + assert "test_device2" in devices + +def test_reg_config_get_latest_revision(data_dir): + reg_config = RegConfig(os.path.join(data_dir, "reg_config.json")) + + rev = reg_config.get_latest_revision("test_device1") + assert rev == "x1" + + rev = reg_config.get_latest_revision("test_device2") + assert rev == "b0" + +def test_reg_config_get_revisions(data_dir): + reg_config = RegConfig(os.path.join(data_dir, "reg_config.json")) + + revs = reg_config.get_revisions("test_device1") + assert "x0" in revs + assert "x1" in revs + + revs = reg_config.get_revisions("test_device2") + assert "b0" in revs + +def test_reg_config_get_address(data_dir): + reg_config = RegConfig(os.path.join(data_dir, "reg_config.json")) + + addr = reg_config.get_address("test_device1") + assert addr == "0xA5A5_1234" + + addr = reg_config.get_address("test_device2", remove_underscore=True) + assert addr == "0x40000000" + +def test_reg_config_get_data_file(data_dir): + reg_config = RegConfig(os.path.join(data_dir, "reg_config.json")) + + data_file = reg_config.get_data_file("test_device1", "x0") + assert os.path.join(data_dir, "test_device1_x0.xml") == data_file + + data_file = reg_config.get_data_file("test_device1", "x1") + assert os.path.join(data_dir, "test_device1_x1.xml") == data_file + + data_file = reg_config.get_data_file("test_device2", "b0") + assert os.path.join(data_dir, "test_device2_b0.xml") == data_file + +def test_reg_config_get_antipoleregs(data_dir): + reg_config = RegConfig(os.path.join(data_dir, "reg_config.json")) + + antipole = reg_config.get_antipole_regs("test_device1") + assert antipole["INVERTED_REG"] == "INVERTED_REG_AP" + + antipole = reg_config.get_antipole_regs("test_device2") + assert antipole["INVERTED_REG"] == "INVERTED_REG_AP" + +def test_reg_config_get_computed_fields(data_dir): + reg_config = RegConfig(os.path.join(data_dir, "reg_config.json")) + + computed_fields = reg_config.get_computed_fields("test_device1") + assert computed_fields["COMPUTED_REG"]["TEST_FIELD1"] == "computed_reg_test_field1" + assert computed_fields["COMPUTED_REG"]["TEST_FIELD2"] == "computed_reg_test_field2" + assert computed_fields["COMPUTED_REG2"]["TEST_FIELD1"] == "computed_reg2_test_field1" + assert computed_fields["COMPUTED_REG2"]["TEST_FIELD2"] == "computed_reg2_test_field2" + diff --git a/tests/shadowregs/test_shadowregs.py b/tests/shadowregs/test_shadowregs.py new file mode 100644 index 00000000..f3df3db2 --- /dev/null +++ b/tests/shadowregs/test_shadowregs.py @@ -0,0 +1,292 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +""" Tests for nxpkeygen utility.""" +import os +import pytest +from spsdk.exceptions import SPSDKError + +import spsdk.dat.shadow_regs as SR +import spsdk.utils.registers as REGS +import spsdk.debuggers.debug_probe as DP + + +from tests.debuggers.debug_probe_virtual import DebugProbeVirtual +# from spsdk.utils.misc import use_working_directory + +TEST_DEV_NAME = "sh_test_dev" +TEST_DATABASE = "test_database.json" +TEST_DATABASE_BAD_COMPUTED_FUNC = "test_database_invalid_computed.json" + +def get_probe(): + probe = DebugProbeVirtual(DebugProbeVirtual.UNIQUE_SERIAL) + probe.open() + probe.enable_memory_interface() + return probe + +def get_registers(xml_filename, filter_reg=None): + registers = REGS.Registers(TEST_DEV_NAME) + registers.load_registers_from_xml(xml_filename, filter_reg=filter_reg) + return registers + +def get_config(database_filename): + config = SR.RegConfig(database_filename) + return config + +def test_shadowreg_basic(data_dir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + assert shadowregs.device == TEST_DEV_NAME + +def test_shadowreg_set_get_reg(data_dir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + + test_val = bytearray(32) + for i, val in enumerate(test_val): + test_val[i] = i + + shadowregs.set_register("REG1", 0x12345678) + shadowregs.set_register("REG2", 0x4321) + shadowregs.set_register("REG_INVERTED_AP", 0xA5A5A5A5) + shadowregs.set_register("REG_BIG", test_val) + shadowregs.set_register("REG_BIG_REV", test_val) + + assert shadowregs.get_register("REG1") == 0x12345678.to_bytes(4, 'big') + assert shadowregs.get_register("REG2") == 0x00004321.to_bytes(4, 'big') + assert shadowregs.get_register("REG_INVERTED_AP") == 0xA5A5A5A5.to_bytes(4, 'big') + assert shadowregs.get_register("REG_BIG") == test_val + assert shadowregs.get_register("REG_BIG_REV") == test_val + +def test_shadowreg_set_reg_invalid(data_dir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + with pytest.raises(SPSDKError): + shadowregs.set_register("REG1", 0x1234567800004321) + + with pytest.raises(SPSDKError): + shadowregs.set_register("REG1_Invalid", 0x12345678) + +def test_shadowreg_get_reg_invalid(data_dir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + with pytest.raises(SPSDKError): + shadowregs.get_register("REG1_Invalid") + +def test_shadowreg_invalid_probe(data_dir): + probe = None + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + + with pytest.raises(DP.DebugProbeError): + shadowregs.set_register("REG1", 0x12345678) + + with pytest.raises(DP.DebugProbeError): + shadowregs.get_register("REG1") + +def test_shadowreg_verify_write(data_dir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + + shadowregs._write_shadow_reg(1, 0x12345678, verify=True) + shadowregs._write_shadow_reg(1, 0x87654321, verify=False) + + assert probe.mem_reg_read(1) == 0x87654321 + + probe.set_virtual_memory_substitute_data({1: [0x12345678, 0x5555AAAA]}) + + with pytest.raises(SR.IoVerificationError): + shadowregs._write_shadow_reg(1, 0x87654321, verify=True) + + assert probe.mem_reg_read(1) == 0x5555AAAA + +def test_shadowreg_reverse(): + test_val = b'\x01\x02\x03\x04\x11\x12\x13\x14\x21\x22\x23\x24\x31\x32\x33\x34' + test_val_ret = b'\x04\x03\x02\x01\x14\x13\x12\x11\x24\x23\x22\x21\x34\x33\x32\x31' + + assert SR.ShadowRegisters._reverse_bytes_in_longs(test_val) == test_val_ret + assert SR.ShadowRegisters._reverse_bytes_in_longs(test_val_ret) == test_val + + test_val1 = b'\x01\x02\x03\x04\x11\x12' + with pytest.raises(ValueError): + SR.ShadowRegisters._reverse_bytes_in_longs(test_val1) + +def test_shadowreg_yml(data_dir, tmpdir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + + test_val = bytearray(32) + for i, val in enumerate(test_val): + test_val[i] = i + + shadowregs.set_register("REG1", 0x12345678) + shadowregs.set_register("REG2", 0x4321) + shadowregs.set_register("REG_INVERTED_AP", 0xA5A5A5A5) + shadowregs.set_register("REG_BIG", test_val) + shadowregs.set_register("REG_BIG_REV", test_val) + + assert shadowregs.get_register("REG1") == 0x12345678.to_bytes(4, 'big') + assert shadowregs.get_register("REG2") == 0x00004321.to_bytes(4, 'big') + assert shadowregs.get_register("REG_INVERTED_AP") == 0xA5A5A5A5.to_bytes(4, 'big') + assert shadowregs.get_register("REG_BIG") == test_val + assert shadowregs.get_register("REG_BIG_REV") == test_val + + shadowregs.create_yml_config(os.path.join(tmpdir, "sh_regs.yml"), raw=False) + shadowregs.create_yml_config(os.path.join(tmpdir, "sh_regs_raw.yml"), raw=True) + + probe.clear() + + shadowregs_load_raw = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + shadowregs_load_raw.load_yml_config(os.path.join(tmpdir, "sh_regs_raw.yml"), raw=True) + shadowregs_load_raw.sets_all_registers() + + assert shadowregs_load_raw.get_register("REG1") == 0x12345678.to_bytes(4, 'big') + assert shadowregs_load_raw.get_register("REG2") == 0x00004321.to_bytes(4, 'big') + assert shadowregs_load_raw.get_register("REG_INVERTED_AP") == 0xA5A5A5A5.to_bytes(4, 'big') + assert shadowregs_load_raw.get_register("REG_BIG") == test_val + assert shadowregs_load_raw.get_register("REG_BIG_REV") == test_val + + probe.clear() + + shadowregs_load = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + shadowregs_load.load_yml_config(os.path.join(tmpdir, "sh_regs.yml"), raw=False) + shadowregs_load.sets_all_registers() + + assert shadowregs_load.get_register("REG1") == b'\x40\x34\x56\x66' + assert shadowregs_load.get_register("REG2") == b'\x00\x00\x03!' + assert shadowregs_load.get_register("REG_INVERTED_AP") == b'\xbf\xcb\xa9\x99' + assert shadowregs_load.get_register("REG_BIG") == test_val + assert shadowregs_load.get_register("REG_BIG_REV") == test_val + + probe.clear() + + shadowregs_load2 = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + shadowregs_load2.load_yml_config(os.path.join(tmpdir, "sh_regs_raw.yml"), raw=False) + shadowregs_load2.sets_all_registers() + + assert shadowregs_load2.get_register("REG1") == b'\x40\x34\x56\x66' + assert shadowregs_load2.get_register("REG2") == b'\x00\x00\x03!' + assert shadowregs_load2.get_register("REG_INVERTED_AP") == b'\xbf\xcb\xa9\x99' + assert shadowregs_load2.get_register("REG_BIG") == test_val + assert shadowregs_load2.get_register("REG_BIG_REV") == test_val + +def test_shadowreg_yml_corrupted(data_dir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + test_val = bytearray(32) + for i, val in enumerate(test_val): + test_val[i] = i + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + shadowregs.load_yml_config(os.path.join(data_dir, "sh_regs_corrupted.yml"), raw=True) + shadowregs.sets_all_registers() + + assert shadowregs.get_register("REG1") == 0x12345678.to_bytes(4, 'big') + assert shadowregs.get_register("REG2") == 0x00004321.to_bytes(4, 'big') + assert shadowregs.get_register("REG_INVERTED_AP") == 0xA5A5A5A5.to_bytes(4, 'big') + assert shadowregs.get_register("REG_BIG_REV") == test_val + +def test_shadowreg_yml_invalid_computed(tmpdir, data_dir): + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE_BAD_COMPUTED_FUNC)) + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + + test_val = bytearray(32) + for i, val in enumerate(test_val): + test_val[i] = i + + shadowregs.set_register("REG1", 0x12345678) + shadowregs.set_register("REG2", 0x4321) + shadowregs.set_register("REG_INVERTED_AP", 0xA5A5A5A5) + shadowregs.set_register("REG_BIG", test_val) + shadowregs.set_register("REG_BIG_REV", test_val) + + assert shadowregs.get_register("REG1") == 0x12345678.to_bytes(4, 'big') + assert shadowregs.get_register("REG2") == 0x00004321.to_bytes(4, 'big') + assert shadowregs.get_register("REG_INVERTED_AP") == 0xA5A5A5A5.to_bytes(4, 'big') + assert shadowregs.get_register("REG_BIG") == test_val + assert shadowregs.get_register("REG_BIG_REV") == test_val + + shadowregs.create_yml_config(os.path.join(tmpdir, "sh_regs.yml"), raw=False) + + shadowregs1 = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + + with pytest.raises(SPSDKError): + shadowregs1.load_yml_config(os.path.join(tmpdir, "sh_regs.yml")) + +def test_shadowreg_yml_none_existing(data_dir): + + probe = get_probe() + config = get_config(os.path.join(data_dir, TEST_DATABASE)) + + test_val = bytearray(32) + for i, val in enumerate(test_val): + test_val[i] = i + + shadowregs = SR.ShadowRegisters(probe, config, TEST_DEV_NAME) + with pytest.raises(SPSDKError): + shadowregs.load_yml_config(os.path.join(data_dir, "sh_regs_none.yml"), raw=True) + +def test_shadow_register_crc8(): + crc = SR.ShadowRegisters.crc_update(b'\x12\x34', is_final=False) + crc = SR.ShadowRegisters.crc_update(b'\x56', crc=crc) + assert crc == 0x29 + +def test_shadow_register_crc8_hook(): + bval = SR.value_to_bytes(0x03020100) + assert SR.ShadowRegisters.comalg_dcfg_cc_socu_crc8(SR.ShadowRegisters, bval) == b'\x03\x02\x01\x1d' + + bval = SR.value_to_bytes(0x80FFFF00) + assert SR.ShadowRegisters.comalg_dcfg_cc_socu_crc8(SR.ShadowRegisters, bval) == SR.value_to_bytes(0x80FFFF20) + +def test_shadow_register_enable_debug_invalid_probe(): + probe = None + with pytest.raises(SPSDKError): + SR.enable_debug(probe) + +def test_shadow_register_enable_debug_device_cannot_enable(): + probe = get_probe() + # invalid run (the mcu returns nonse values) + assert not SR.enable_debug(probe) + +def test_shadow_register_enable_debug(): + probe = get_probe() + #valid run, the right values are prepared + + #Setup the simulated data for reading of AP registers + ap = {12:["Exception", 0x12345678],0x02000000:[2, 0, 2, 0], 0x02000008:[0]} + probe.set_coresight_ap_substitute_data(ap) + assert SR.enable_debug(probe) + + +def test_shadow_register_enable_debug_already_enabled(): + probe = get_probe() + #Setup the simulated data for reading of AP registers + mem_ap = {12:[0x12345678]} + probe.set_coresight_ap_substitute_data(mem_ap) + assert SR.enable_debug(probe) + + +def test_shadow_register_enable_debug_probe_exceptions(): + probe = get_probe() + with pytest.raises(SPSDKError): + probe.dp_write_cause_exception() + assert not SR.enable_debug(probe) diff --git a/tests/shadowregs/test_shadowregs_app.py b/tests/shadowregs/test_shadowregs_app.py new file mode 100644 index 00000000..f1c0ecc3 --- /dev/null +++ b/tests/shadowregs/test_shadowregs_app.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Tests for nxpkeygen utility.""" +import os + +from click.testing import CliRunner + +from spsdk.apps.shadowregs import main +from tests.debuggers.debug_probe_virtual import DebugProbeVirtual + +def test_command_line_interface_main(): + """Test for main menu options.""" + runner = CliRunner() + result = runner.invoke(main, ['--help']) + assert result.exit_code == 0 + + assert 'main [OPTIONS] COMMAND [ARGS]' in result.output + assert 'NXP Shadow Registers control Tool.' in result.output + assert '-i, --interface TEXT' in result.output + assert '-d, --debug LEVEL' in result.output + assert '-s, --serial-no TEXT' in result.output + assert '-dev, --device TEXT' in result.output + assert '-o, --debug-probe-option TEXT' in result.output + assert '-v, --version' in result.output + assert '--help' in result.output + assert 'getreg The command prints the current value of one shadow register.' in result.output + assert 'listdevs The command prints a list of supported devices.' in result.output + assert 'loadconfig Load new state of shadow registers from YML file into' in result.output + assert 'printregs Print all Shadow registers including theirs current values.' in result.output + assert 'reset The command resets connected device.' in result.output + assert 'saveconfig Save current state of shadow registers to YML file.' in result.output + assert 'setreg The command sets a value of one shadow register defined by' in result.output + +def test_command_line_interface_getreg(): + """Test for getreg menu options.""" + runner = CliRunner() + cmd = f'getreg --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'getreg [OPTIONS]' in result.output + assert '-r, --reg TEXT' in result.output + assert '--help' in result.output + +def test_command_line_interface_listdevs(): + """Test for listdevs menu options.""" + runner = CliRunner() + cmd = f'listdevs --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'listdevs [OPTIONS]' in result.output + assert '--help' in result.output + +def test_command_line_interface_loadconfig(): + """Test for loadconfig menu options.""" + runner = CliRunner() + cmd = f'loadconfig --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'loadconfig [OPTIONS]' in result.output + assert '-f, --filename TEXT The name of file used to load a new configuration.' in result.output + assert '-r, --raw In loaded configuration will accepted also the computed' in result.output + assert '--help' in result.output + +def test_command_line_interface_printregs(): + """Test for printregs menu options.""" + runner = CliRunner() + cmd = f'printregs --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'printregs [OPTIONS]' in result.output + assert '-r, --rich Enables rich format of printed output.' in result.output + assert '--help' in result.output + +def test_command_line_interface_saveconfig(): + """Test for saveconfig menu options.""" + runner = CliRunner() + cmd = f'saveconfig --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'saveconfig [OPTIONS]' in result.output + assert '-f, --filename TEXT The name of file used to save the current' in result.output + assert '-r, --raw The stored configuration will include also the computed' in result.output + assert '--help' in result.output + +def test_command_line_interface_setreg(): + """Test for setreg menu options.""" + runner = CliRunner() + cmd = f'setreg --help' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + assert 'setreg [OPTIONS]' in result.output + assert '-r, --reg TEXT The name of register to be set.' in result.output + assert '-v, --reg_val TEXT The new value of register in hex format.' in result.output + assert '--help' in result.output + + +# The execution tests +def test_command_line_interface_listdevs_exe(): + """Test for listdevs execution menu options.""" + runner = CliRunner() + result = runner.invoke(main, ["listdevs"]) + assert result.exit_code == 0 + + assert 'imxrt595' in result.output + assert 'imxrt685' in result.output + +# This is testing none connected any probe +def test_command_line_interface_printregs_no_probe_exe(): + """Test for printregs execution menu options.""" + runner = CliRunner() + cmd = f'-dev imxrt595 -i virtual printregs' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 1 + +def test_command_line_interface_invalid_device(): + """Test for reset execution menu options.""" + runner = CliRunner() + cmd = f'-dev invalid -i virtual reset' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 1 + +def test_command_line_interface_printregs_exe_fail(): + """Test for printregs execution menu options.""" + runner = CliRunner() + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} printregs' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 1 + +def test_command_line_interface_printregs_exe(): + """Test for printregs execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} printregs' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 0 + +def test_command_line_interface_printregs_r_exe(): + """Test for printregs rich execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} printregs -r' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 0 + + assert 'Register description:' in result.output + + +def test_command_line_interface_setreg_exe(): + """Test for printregs execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} setreg -r DCFG_CC_SOCU -v 12345678' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 0 + +def test_command_line_interface_getreg_exe(): + """Test for printregs execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} getreg -r DCFG_CC_SOCU' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 0 + +def test_command_line_interface_saveloadconfig_r_exe(tmpdir): + """Test for saveconfig rich execution menu options.""" + runner = CliRunner() + # create path in TMP DIR + filename = os.path.join(tmpdir, "SR_COV_TEST.yml") + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} saveconfig -f {filename} -r' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + # check if the file really exists + assert os.path.isfile(filename) + + #Try to load the generated file + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} loadconfig -f {filename} -r' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + +def test_command_line_interface_saveloadconfig_exe(tmpdir): + """Test for saveconfig execution menu options.""" + runner = CliRunner() + filename = os.path.join(tmpdir, "SR_COV_TEST.yml") + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} saveconfig -f {filename}' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + + # check if the file really exists + assert os.path.isfile(filename) + + #Try to load the generated file + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} loadconfig -f {filename}' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_command_line_interface_reset_exe(): + """Test for reset execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} reset' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_command_line_interface_logger(): + """Test for reset execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} -d debug reset' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 0 + +def test_command_line_interface_invalid_o_param(): + """Test for reset execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} -d debug reset' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 1 + +def test_command_line_interface_saveconfig_exe_fail(tmpdir): + """Test for saveconfig rich execution menu options.""" + runner = CliRunner() + # create path in TMP DIR + filename = os.path.join(tmpdir, "SR_COV_TEST.yml") + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]} '\ + '-o subs_mem={"1074987040":["Exception"]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} saveconfig -f {filename}' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 1 + +def test_command_line_interface_loadconfig_exe_fail(data_dir): + """Test for saveconfig rich execution menu options.""" + runner = CliRunner() + # create path in TMP DIR + filename = os.path.join(data_dir, "sh_regs_corrupted.yml") + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]} '\ + '-o subs_mem={"1074987040":["Exception"]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} loadconfig -f {filename}' + result = runner.invoke(main, cmd.split()) + assert result.exit_code == 1 + +def test_command_line_interface_printregs_exe_fail1(): + """Test for printregs execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]} '\ + '-o subs_mem={"1074987040":["Exception"]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} printregs' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 1 + +def test_command_line_interface_setreg_exe_fail(): + """Test for printregs execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]} '\ + '-o subs_mem={"1074987040":["Exception"]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} setreg -r CUST_WR_RD_LOCK0 -v 12345678' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 1 + +def test_command_line_interface_getreg_exe_fail(): + """Test for printregs execution menu options.""" + runner = CliRunner() + enable_debug = '-o subs_ap={"12":["Exception",12345678],"33554432":[2,0,2,0],"33554440":[0]} '\ + '-o subs_mem={"1074987040":["Exception"]}' + cmd = f'-dev imxrt595 -i virtual -s {DebugProbeVirtual.UNIQUE_SERIAL} {enable_debug} getreg -r CUST_WR_RD_LOCK0' + result = runner.invoke(main, cmd.split()) + + assert result.exit_code == 1 diff --git a/tests/utils/apps/test_utils.py b/tests/utils/apps/test_utils.py index 10836a24..718dc38f 100644 --- a/tests/utils/apps/test_utils.py +++ b/tests/utils/apps/test_utils.py @@ -1,12 +1,16 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause import pytest from spsdk.apps import utils +from spsdk import SPSDKError + +from spsdk.apps.utils import catch_spsdk_error +from spsdk.mboot.exceptions import McuBootConnectionError def test_split_string(): @@ -34,3 +38,57 @@ def test_file_size_composite(input_param, exp_path, exp_size): path, size = utils.parse_file_and_size(input_param) assert path == exp_path assert size == exp_size + + +@pytest.mark.parametrize( + "input_hex_data,output_bytes", + [ + ("{{11223344}}", b"\x11\x22\x33\x44"), + ("{{11 22 33 44}}", b"\x11\x22\x33\x44"), + (" { { 11 22 33 44}}", b"\x11\x22\x33\x44"), + ] +) +def test_parse_hex_data(input_hex_data, output_bytes): + parsed_data = utils.parse_hex_data(input_hex_data) + assert parsed_data == output_bytes + + +@pytest.mark.parametrize( + "input_hex_data", + [ + ("{ { } }"), + ("11223344"), + ("{{11223344"), + ("11223344}}"), + ("{11223344}"), + ("{{123}}"), + ("{{11 xa}}"), + ("{{ab zz}}"), + ] +) +def test_parse_hex_data_error(input_hex_data): + with pytest.raises(SPSDKError): + utils.parse_hex_data(input_hex_data) + + +@catch_spsdk_error +def function_under_test(to_raise: Exception = None) -> int: + if to_raise is None: + return 0 + raise to_raise + + +def test_catch_spsdk_error(): + with pytest.raises(SystemExit) as exc: + function_under_test(AssertionError()) + assert exc.value.code == 2 + + with pytest.raises(SystemExit) as exc_2: + function_under_test(McuBootConnectionError()) + assert exc_2.value.code == 2 + + with pytest.raises(SystemExit) as exc_3: + function_under_test(IndexError()) + assert exc_3.value.code == 3 + + assert function_under_test(None) == 0 \ No newline at end of file diff --git a/tests/utils/conftest.py b/tests/utils/conftest.py deleted file mode 100644 index 503533f4..00000000 --- a/tests/utils/conftest.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2020 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -from os import path -import pytest - - -@pytest.fixture -def data_dir(): - return path.join(path.dirname(__file__), 'data') diff --git a/tests/utils/crypto/test_cert_blocks.py b/tests/utils/crypto/test_cert_blocks.py index 5b2008af..8ed556b5 100644 --- a/tests/utils/crypto/test_cert_blocks.py +++ b/tests/utils/crypto/test_cert_blocks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -13,11 +13,6 @@ from spsdk.utils.crypto import Certificate, CertBlockV2 -@pytest.fixture(scope="module") -def data_dir(): - return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') - - def test_cert_block_header(): header = CertBlockHeader() assert header.version == '1.0' diff --git a/tests/utils/crypto/test_certificate.py b/tests/utils/crypto/test_certificate.py index 3b547003..87045956 100644 --- a/tests/utils/crypto/test_certificate.py +++ b/tests/utils/crypto/test_certificate.py @@ -1,23 +1,16 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause import os from datetime import datetime -import pytest - from spsdk.utils.crypto import Certificate -@pytest.fixture(scope="module") -def data_dir(): - return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') - - def test_basics(data_dir: str) -> None: """Test basic features of the Certificate class""" with open(os.path.join(data_dir, 'selfsign_2048_v3.der.crt'), 'rb') as f: diff --git a/tests/utils/crypto/test_otfad.py b/tests/utils/crypto/test_otfad.py index fc5435ae..ccc4fb44 100644 --- a/tests/utils/crypto/test_otfad.py +++ b/tests/utils/crypto/test_otfad.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -12,11 +12,6 @@ from spsdk.utils.crypto import KeyBlob, Otfad -@pytest.fixture(scope="module") -def data_dir(): - return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') - - def test_otfad_keyblob(data_dir): """ Test generation of key blob for OTFAD """ # generate key blob using random keys diff --git a/tests/utils/test_devicedescription.py b/tests/utils/test_devicedescription.py new file mode 100644 index 00000000..74ab5eb2 --- /dev/null +++ b/tests/utils/test_devicedescription.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +from unittest.mock import MagicMock, patch + +import pytest +import spsdk.utils.devicedescription as devicedescription + +from spsdk.mboot.interfaces.usb import USB_DEVICES as MB_USB_DEVICES + +def test_uart_device_description(): + formatted_output = "Port: some name\nType: some type" + dev = devicedescription.UartDeviceDescription(name="some name", dev_type="some type") + + assert dev.info() == formatted_output + +def test_usb_device_description(): + formatted_output = ( + "my product - manufacturer X\n" + "Vendor ID: 0x000a\n" + "Product ID: 0x0014\n" + "Path: some_path\n" + "Name: mboot device") + dev = devicedescription.USBDeviceDescription( + vid=10, pid=20, path="some_path", + product_string="my product", + manufacturer_string="manufacturer X", + name="mboot device") + + assert dev.info() == formatted_output + +def test_str(): + formatted_output = "Port: some name\nType: some type" + dev = devicedescription.UartDeviceDescription(name="some name", dev_type="some type") + + assert str(dev) == formatted_output + +def test_repr(): + formatted_output = "UartDeviceDescription({'name': 'some name', 'dev_type': 'some type'})" + dev = devicedescription.UartDeviceDescription(name="some name", dev_type="some type") + + assert repr(dev) == formatted_output + + +@pytest.mark.parametrize( + "vid, pid, expected_result", + [ + (0x1111, 0x2222, []), + (0x15a2, 0x0073, ["MKL27", "MXRT20", "MXRT50", "MXRT60"]), + (0x1FC9, 0x0135, ["IMXRT", "MXRT60"]), + ] +) +def test_get_device_name(vid, pid, expected_result): + """Verify search works and returns appropripate name based on VID/PID + """ + assert devicedescription.get_usb_device_name(vid, pid) == expected_result + +@pytest.mark.parametrize( + "vid, pid, expected_result", + [ + (0x1111, 0x2222, []), + (0x15a2, 0x0073, ["MKL27", "MXRT20", "MXRT50", "MXRT60"]), + (0x1FC9, 0x0135, ["IMXRT"]), + ] +) +def test_get_device_name(vid, pid, expected_result): + """Verify search works and returns appropripate name based on VID/PID + """ + assert devicedescription.get_usb_device_name(vid, pid, MB_USB_DEVICES) == expected_result + +def test_path_conversion(): + """Verify, that path gets converted properly. + """ + with patch('platform.system', MagicMock(return_value="Windows")): + win_path = b'\\\\?\\hid#vid_1fc9&pid_0130#6&1625c75b&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' + assert devicedescription.convert_usb_path(win_path) == 'HID\\VID_1FC9&PID_0130\\6&1625C75B&0&0000' + + with patch('platform.system', MagicMock(return_value="Linux")): + linux_path = b'000A:000B:00' + assert devicedescription.convert_usb_path(linux_path) == "10#11" + + linux_path = b'' + assert devicedescription.convert_usb_path(linux_path) == '' + + with patch('platform.system', MagicMock(return_value="Darwin")): + mac_path = b"IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/HS02@14200000/SE Blank RT Family @14200000" + + assert devicedescription.convert_usb_path(mac_path) == "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/HS02@14200000/SE Blank RT Family @14200000" + + with patch('platform.system', MagicMock(return_value="Unknown System")): + path = b"" + + assert devicedescription.convert_usb_path(path) == '' \ No newline at end of file diff --git a/tests/utils/test_nxpdevscan.py b/tests/utils/test_nxpdevscan.py new file mode 100644 index 00000000..e1bef6f8 --- /dev/null +++ b/tests/utils/test_nxpdevscan.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +from unittest.mock import MagicMock, patch + +import pytest +import spsdk.utils.nxpdevscan as nds +import spsdk.utils.devicedescription as devicedescription + +from spsdk.mboot.interfaces.uart import Uart as SDP_Uart + +from serial import Serial +from serial.tools.list_ports_common import ListPortInfo + +def test_usb_device_search(): + """Test, that search method returns all NXP devices based on their VID. + Default VID's so far are 0x1fc9, 0x15a2. + """ + test_vector = [ + {"vendor_id": 0x0001, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x15, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x1fc9, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x15a2, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + ] + result = [ + devicedescription.USBDeviceDescription(0x1fc9, 0, "", "", "", ""), + devicedescription.USBDeviceDescription(0x15a2, 0, "", "", "", ""), + ] + + with patch('hid.enumerate', MagicMock(return_value=test_vector)): + devices = nds.search_nxp_usb_devices() + + assert len(devices) == len(result) + + for dev, res in zip(devices, result): + assert dev.info() == res.info() + +def test_usb_device_search_extended(): + """Verify search method returns all NXP devices based on their VID + all + additional devices. + Default VID's so far are 0x1fc9, 0x15a2 + """ + test_vector = [ + {"vendor_id": 0x1FC9, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x0001, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x15, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x1fc9, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x0002, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + {"vendor_id": 0x15a2, "product_id": 0, "path": b"", "manufacturer_string": "", "product_string": ""}, + ] + result = [ + devicedescription.USBDeviceDescription(0x1fc9, 0, "", "", "", ""), + devicedescription.USBDeviceDescription(0x1fc9, 0, "", "", "", ""), + devicedescription.USBDeviceDescription(0x0002, 0, "", "", "", ""), + devicedescription.USBDeviceDescription(0x15a2, 0, "", "", "", ""), + ] + with patch('hid.enumerate', MagicMock(return_value=test_vector)): + devices = nds.search_nxp_usb_devices([0x2]) + + assert len(devices) == len(result) + + for dev, res in zip(devices, result): + assert dev.info() == res.info() + +# following mock functions are only for `test_uart_device_search usage` +def mock_mb_scan_uart(port, *args, **kwargs): + return True if port == "COM1" else False + +def mock_sdp_read_status(self, *args, **kwargs): + print("inside mock_sdp_read_status") + retval = 1 if self._device.device.port == "COM5" else None + return retval + +def mock_sdp_uart_init(self, port: str = None, timeout: int = 5000, baudrate: int = 115200): + self.device = Serial(port=None, timeout=timeout / 1000, baudrate=baudrate) + self.device.port = port + self.expect_status = True + +list_port_info_mock = [ListPortInfo(device="COM1"), ListPortInfo(device="COM5"), ListPortInfo(device="COM28")] + +@patch("spsdk.utils.nxpdevscan.mb_scan_uart", mock_mb_scan_uart) +@patch("spsdk.utils.nxpdevscan.SDP.read_status", mock_sdp_read_status) +@patch("spsdk.utils.nxpdevscan.SDP_Uart.__init__", mock_sdp_uart_init) +@patch("spsdk.utils.nxpdevscan.comports", MagicMock(return_value=list_port_info_mock)) +def test_uart_device_search(): + """Test, that search method returns all NXP Uart devices.""" + + result = [ + devicedescription.UartDeviceDescription(name="COM1", dev_type="mboot device"), + devicedescription.UartDeviceDescription(name="COM5", dev_type="SDP device"), + ] + + devices = nds.search_nxp_uart_devices() + + assert len(devices) == len(result) + + for dev, res in zip(devices, result): + assert dev.info() == res.info() + +@pytest.mark.parametrize( + "vid, pid, expected_result", + [ + (0x1111, 0x2222, []), + (0x15a2, 0x0073, ["MKL27", "MXRT20", "MXRT50", "MXRT60"]), + (0x1FC9, 0x0135, ["IMXRT", "MXRT60"]), + ] +) +def test_get_device_name(vid, pid, expected_result): + """Verify search works and returns appropripate name based on VID/PID + """ + assert devicedescription.get_usb_device_name(vid, pid) == expected_result + + +def test_path_conversion(): + """Verify, that path gets converted properly. + """ + with patch('platform.system', MagicMock(return_value="Windows")): + win_path = b'\\\\?\\hid#vid_1fc9&pid_0130#6&1625c75b&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' + assert devicedescription.convert_usb_path(win_path) == 'HID\\VID_1FC9&PID_0130\\6&1625C75B&0&0000' + + with patch('platform.system', MagicMock(return_value="Linux")): + linux_path = b'000A:000B:00' + + assert devicedescription.convert_usb_path(linux_path) == "10#11" + + with patch('platform.system', MagicMock(return_value="Darwin")): + mac_path = b"IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/HS02@14200000/SE Blank RT Family @14200000" + + assert devicedescription.convert_usb_path(mac_path) == "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/HS02@14200000/SE Blank RT Family @14200000" + \ No newline at end of file diff --git a/tests/utils/test_usbfilter.py b/tests/utils/test_usbfilter.py index 88082f1a..39032af8 100644 --- a/tests/utils/test_usbfilter.py +++ b/tests/utils/test_usbfilter.py @@ -95,12 +95,24 @@ def test_vid_pid_regex_invalid_ids(usb_id): b"\\\\?\\hid#vid_413c&pid_301a#a&2d263b2&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", True), ] -mac_uses_cases = common_use_cases + [ +mac_use_cases = common_use_cases + [ ("SE Blank RT Family @14200000", "0x413c", "0x301a", b"IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/HS02@14200000/SE Blank RT Family @14200000", True), ] - +linux_use_cases = [ + ("0x1234", "0x1234", "0x0", b"0:0", True), # match in vid (hex form) + ("4660", "0x1234", "0x0", b"0:0", True), # match in vid (dec form) + ("0x1", "0x1234", "0x0", b"0:0", False), # no match - vid differs + ("1", "0x1234", "0x0", b"0:0", False), # no match - vid differs + ("", "0x1234", "0x0", b"0:0", False), # no match - empty filtering string + ("0x1234,0xabcd", "0x1234", "0xabcd", b"0:0", True), # match in vid,pid combination + ("0x1234:0xabcd", "0x1234", "0xabcd", b"0:0", True), # match in vid:pid combination + ("1,12345", "1", "12345", b"0:0", True), # match in vid,pid combination + ("1:12345", "1", "12345", b"0:0", True), # match in vid,pid combination + ("3#11", "0x413c", "0x301a", b"0003:000b:00", True), + ("2#2", "0x413c", "0x301a", b"0003:000b:00", False) +] @pytest.mark.parametrize( "filter_usb_id,vid,pid,path,expected", win_use_cases @@ -115,7 +127,7 @@ def test_usb_match_win(filter_usb_id: str, vid: str, pid: str, path: str, expect @pytest.mark.parametrize( "filter_usb_id,vid,pid,path,expected", - mac_uses_cases + mac_use_cases ) def test_usb_match_mac(filter_usb_id: str, vid: str, pid: str, path: str, expected: bool): with patch('platform.system', MagicMock(return_value="Darwin")): @@ -123,3 +135,14 @@ def test_usb_match_mac(filter_usb_id: str, vid: str, pid: str, path: str, expect g_virtual_hid_device = {"vendor_id":int(vid, 0), "product_id":int(pid, 0), "path":path} assert usb_filter.compare(g_virtual_hid_device) == expected + +@pytest.mark.parametrize( + "filter_usb_id,vid,pid,path,expected", + linux_use_cases +) +def test_usb_match_linux(filter_usb_id: str, vid: str, pid: str, path: str, expected: bool): + with patch('platform.system', MagicMock(return_value="Linux")): + usb_filter = USBDeviceFilter(usb_id=filter_usb_id) + g_virtual_hid_device = {"vendor_id":int(vid, 0), "product_id":int(pid, 0), "path":path} + + assert usb_filter.compare(g_virtual_hid_device) == expected diff --git a/tools/checker_copyright_year.py b/tools/checker_copyright_year.py new file mode 100644 index 00000000..485feec8 --- /dev/null +++ b/tools/checker_copyright_year.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Script used during pre-commit to check if changed files have valid copyright year.""" +import argparse +import datetime +import os +import re +import sys +from typing import Sequence + +EXTENSIONS = ['.py'] +COPYRIGHT_REGEX_STR = r"Copyright.*(?P\d{4}) (?P.*)" +COPYRIGHT_REGEX = re.compile(COPYRIGHT_REGEX_STR) +THIS_YEAR = datetime.datetime.now().year + + +def check_file(file: str) -> int: + """Run the check on single file.""" + ret_val = 0 + if not os.path.isfile(file): + print(f"'{file}' doesn't exist anymore") + return 0 + with open(file) as f: + content = f.read() + copyrights = COPYRIGHT_REGEX.findall(content) + for cp_instance in copyrights: + cp_year = int(cp_instance[0]) + if cp_year == THIS_YEAR: + break + else: + print(f"File: '{file}' doesn't have {THIS_YEAR} Copyright") + ret_val = 1 + return ret_val + + +def check_files(files: Sequence[str]) -> int: + """Run the check on a list of files.""" + ret_val = 0 + for file in files: + _, extension = os.path.splitext(file) + if extension in EXTENSIONS: + ret_val += check_file(file) + return ret_val + + +def main(argv: Sequence[str] = None) -> int: + """Main function.""" + parser = argparse.ArgumentParser( + description="""Check whether "files" have the current year in Copyright.""" + ) + parser.add_argument('files', nargs='*', help='Files to analyze') + args = parser.parse_args(argv) + + return check_files(args.files) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/clr.py b/tools/clr.py index 307b19d0..4defcf22 100644 --- a/tools/clr.py +++ b/tools/clr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause @@ -95,12 +95,6 @@ def main() -> None: for clr_info in no_nxp_cp: print(f' - {clr_info.path}: {clr_info.copyrights}') - this_year = datetime.now().year - not_this_year = [item for item in clr_info_list if not any(f'{this_year} NXP' in x for x in item.copyrights)] - print(f'{len(not_this_year)} Files without "{this_year} NXP" copyright') - for clr_info in not_this_year: - print(f' - {clr_info.path}: {clr_info.copyrights}') - no_lic = [item for item in clr_info_list if not item.license] print(f'{len(no_lic)} Files without license info') for clr_info in no_lic: @@ -111,6 +105,12 @@ def main() -> None: for clr_info in no_bsd_3: print(f' - {clr_info.path}: {clr_info.license}') + this_year = datetime.now().year + not_this_year = [item for item in clr_info_list if not any(f'{this_year} NXP' in x for x in item.copyrights)] + print(f'{len(not_this_year)} Files without "{this_year} NXP" copyright') + for clr_info in not_this_year: + print(f' - {clr_info.path}: {clr_info.copyrights}') + if __name__ == "__main__": main() diff --git a/tools/deps-licenses.py b/tools/deps-licenses.py new file mode 100644 index 00000000..a6fd118a --- /dev/null +++ b/tools/deps-licenses.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Script to list all SPSDK dependencies and their dependencies.""" + +from typing import List, Iterator +from pip._internal.commands.show import search_packages_info +import itertools + + +def get_requires(module_names: List[str]) -> Iterator[List[str]]: + for pkg_info in search_packages_info(module_names): + yield pkg_info['requires'] + yield from get_requires(pkg_info['requires']) + + +def get_licenses(module_names: List[str]) -> Iterator[str]: + for pkg_info in search_packages_info(module_names): + yield pkg_info['license'] + + +if __name__ == "__main__": + items = set(itertools.chain(*get_requires(['spsdk']))) + + for module in sorted(list(items)): + print(f"{module:20} -> {next(search_packages_info([module]))['license']}") diff --git a/tools/gitcov-defaults.ini b/tools/gitcov-defaults.ini new file mode 100644 index 00000000..e29daf3f --- /dev/null +++ b/tools/gitcov-defaults.ini @@ -0,0 +1,19 @@ +[gitcov] +skip-files = + sdp/interfaces, + mboot/interfaces, + spsdk/apps, + spsdk/dat, + spsdk/debuggers/debug_probe_jlink.py, + spsdk/debuggers/debug_probe_pemicro.py, + spsdk/debuggers/debug_probe_pyocd.py, + spsdk/debuggers/debug_probe_trace32.py + +repo-path=. +module=spsdk +coverage-report=reports/coverage.xml +coverage-cutoff=0.8 +parent-branch=origin/master +include-merges=0 +verbose=1 +debug=0 diff --git a/tools/gitcov.py b/tools/gitcov.py index 71caf036..a5494d08 100644 --- a/tools/gitcov.py +++ b/tools/gitcov.py @@ -1,127 +1,206 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- # -# Copyright 2020 NXP +# Copyright 2020-2021 NXP # # SPDX-License-Identifier: BSD-3-Clause +"""GitCov script is to calculate code coverage for changed files.""" + import argparse import logging import re import subprocess import sys +from configparser import ConfigParser from os import path +from typing import Sequence, Tuple from xml.etree import ElementTree as et + class MyFormatter( - argparse.ArgumentDefaultsHelpFormatter, - argparse.RawDescriptionHelpFormatter, + argparse.ArgumentDefaultsHelpFormatter, + argparse.RawDescriptionHelpFormatter, ): - pass + """Class customizing behavior for argparse.""" + + +def parse_input(input_args: Sequence[str] = None) -> argparse.Namespace: + """Parse default configuration file and process user inputs.""" + # read the gitcov-defaults.ini use values to set defaults to argparse + config = ConfigParser() + config.read(path.join(path.dirname(__file__), "gitcov-defaults.ini")) + gitcov_config = config["gitcov"] -def parse_input(input_args=None): parser = argparse.ArgumentParser( description=""" Check test coverage of changed lines of code. -!!! For accurate results, make sure to update your referrence branch !!! -!!! The name of referrence branch is passed as 'parent_branch' parameter !!!""", +!!! For accurate results, make sure to update your reference branch !!! +!!! The name of reference branch is passed as 'parent_branch' parameter !!!""", formatter_class=MyFormatter ) parser.add_argument( - "-p", "--repo-path", required=False, default=".", + "-p", "--repo-path", required=False, default=gitcov_config["repo-path"], help="Path to root of repository" ) parser.add_argument( - "-m", "--module", required=False, default="spsdk", + "-m", "--module", required=False, default=gitcov_config["module"], help="Module for branch coverage analysis" ) parser.add_argument( - "-cr", "--coverage-report", required=False, default="coverage.xml", + "-cr", "--coverage-report", required=False, default=gitcov_config["coverage-report"], help="File containing the XML coverage report" ) parser.add_argument( - "-cc", "--coverage-cutoff", required=False, default=0.8, - help="Cutoff for success" + "-cc", "--coverage-cutoff", required=False, default=gitcov_config.getfloat("coverage-cutoff"), + help="Cutoff for success", type=float ) parser.add_argument( - "-b", "--parent-branch", required=False, default='origin/master', + "-b", "--parent-branch", required=False, default=gitcov_config["parent-branch"], help="Branch to compare HEAD to" ) parser.add_argument( - "--cached", required=False, default=False, action="store_true", - help="Analyze staged files on current branch" + "-i", "--include-merges", default=config.BOOLEAN_STATES[gitcov_config["include-merges"]], + action="store_true", required=False, help="Include files brought in by merge commits" ) parser.add_argument( - "-v", "--verbose", dest='log_level', action='store_const', - help="Verbose output", const=logging.INFO, default=logging.WARNING + "-v", "--verbose", default=config.BOOLEAN_STATES[gitcov_config["verbose"]], + required=False, action='store_true', help="Verbose output" ) parser.add_argument( - "-d", "--debug", dest='log_level', action='store_const', - help="Debugging output", const=logging.DEBUG) + "-d", "--debug", default=config.BOOLEAN_STATES[gitcov_config["debug"]], + required=False, action='store_true', help="Debugging output" + ) + parser.add_argument( + "-c", "--config-file", required=False, + help=("""Path to config .ini file. + You can create your custom config file by copy-modify the gitcov-defaults.ini""") + ) args = parser.parse_args(input_args) - logging.basicConfig(level=args.log_level) - args.coverage_cutoff = float(args.coverage_cutoff) + if args.config_file: + if path.isfile(args.config_file): + config.read(args.config_file) + # if the custom file exists let's use the files's location as base ;) + args.repo_path = path.normpath( + path.join(path.dirname(args.config_file), gitcov_config["repo-path"]) + ) + else: + parser.error(f"Given config file '{args.config_file}' doesn't exists!") + + log_level = logging.WARNING + if gitcov_config.getint("verbose", fallback=0) or args.verbose: + log_level = logging.INFO + if gitcov_config.getint("debug", fallback=0) or args.debug: + log_level = logging.DEBUG + logging.basicConfig(level=log_level) + assert path.isdir(args.repo_path), f"Repo path '{args.repo_path}' doesn't exist" args.repo_path = path.abspath(args.repo_path) if not path.isabs(args.coverage_report): args.coverage_report = path.normpath(path.join(args.repo_path, args.coverage_report)) assert path.isfile(args.coverage_report), f"Coverage report '{args.coverage_report}' doesn't exist" + + args.skip_files = gitcov_config.get("skip-files").replace("\n", "").split(",") + return args -def get_number_of_commits(path, parent_branch='origin/master'): - cmd = f"git log --oneline {parent_branch}..HEAD" - logging.debug(f"Executing: {cmd}") - logs = subprocess.check_output(cmd.split(), cwd=path).decode("utf-8") - distance = len(logs.splitlines()) - logging.debug(f"Current branch is {distance} commits away from {parent_branch}") - return distance +def get_changed_files(repo_path: str, parent_branch: str, include_merges: bool) -> Sequence[str]: + """Get a list of changed files. -def get_changed_files(path, commits=1, cached=False): + :param repo_path: Path to the root of the repository + :param parent_branch: Git branch to compare to + :param include_merges: Include changes done via merge-commits + :return: List of changed files + """ file_regex_str = r"^(?P[AM])\s+(?P[a-zA-Z0-9_/\\]+\.py)$" file_regex = re.compile(file_regex_str) - cmd = f"git diff --name-status {'--cached' if cached else f'HEAD~{commits}'}" + # fetch changed files from previous commits + logging.info("Fetching files from previous commits\n") + cmd = f"git log {'' if include_merges else '--no-merges --first-parent'} --name-status {parent_branch}..HEAD" + logging.debug(f"Executing: {cmd}") + all_files = subprocess.check_output(cmd.split(), cwd=repo_path).decode("utf-8") + logging.debug(f"Result:\n{all_files}") + + # fetch changed files that are potentionally not committed yet + logging.info("Fetching uncommitted files\n") + cmd = f"git diff --name-status" logging.debug(f"Executing: {cmd}") - all_files = subprocess.check_output(cmd.split(), cwd=path).decode("utf-8") + uncommitted = subprocess.check_output(cmd.split(), cwd=repo_path).decode("utf-8") + logging.debug(f"Result:\n{uncommitted}") + all_files += uncommitted + + # fetch staged new files + logging.info("Fetching new files... those need to be stagged\n") + cmd = f"git diff --name-status --cached" + logging.debug(f"Executing: {cmd}") + staged = subprocess.check_output(cmd.split(), cwd=repo_path).decode("utf-8") + logging.debug(f"Result:\n{staged}") + all_files += staged + filtered = [] for item in all_files.split("\n"): - m = file_regex.match(item) - if m: - filtered.append(m.group("path")) - return filtered + match = file_regex.match(item) + if match: + filtered.append(match.group("path")) + # remove duplicates + filtered = list(set(filtered)) + logging.debug(f"Files to consider: {len(filtered)}: {filtered}") + return list(set(filtered)) + -def extract_linenumber(base_dir, file_path, commits=1, cached=False): +def extract_linenumber(base_dir: str, file_path: str, parent_branch: str) -> Sequence[int]: + """Get changed lines in given file. + + :param base_dir: Path to root of the repository + :param file_path: Path to file + :param parent_branch: Git branch to compare to + :return: List of changed lines in file + """ line_regex_str = r"^@@ -\d{1,3}[0-9,]*\s\+(?P\d{1,3}),?(?P\d*)" line_regex = re.compile(line_regex_str) - cmd = f"git diff {'--cached' if cached else f'HEAD~{commits}'} --unified=0 -- {file_path}" + cmd = f"git diff {parent_branch} --unified=0 -- {file_path}" + logging.debug(f"Executing: {cmd}") git_diff = subprocess.check_output(cmd.split(), cwd=base_dir).decode("utf-8") - line_nums = [] + line_numbers = [] for line in git_diff.split("\n"): - m = line_regex.match(line) - if m: - start = int(m.group("start")) - count = int(m.group("count") or 1) + match = line_regex.match(line) + if match: + start = int(match.group("start")) + count = int(match.group("count") or 1) for i in range(count): - line_nums.append(start + i) - return line_nums + line_numbers.append(start + i) + return line_numbers + -def _cov_statement_category(line): +def _cov_statement_category(line: et.Element) -> str: + """Get the coverate category for one record of statement coverage.""" hit = int(line.attrib["hits"]) return "hit" if hit else "miss" -def _cov_branch_category(line): + +def _cov_branch_category(line: et.Element) -> str: + """Get the coverage category for one record of branch coverage.""" category = _cov_statement_category(line) if "missing-branches" in line.attrib: category = "partial" return category -def extract_coverage(cov_report, file_path, line_numbers): + +def extract_coverage(cov_report: et.ElementTree, file_path: str, line_numbers: Sequence[int]) -> dict: + """Extract coverage data for a given file. + + :param cov_report: Parsed xml coverage report + :param file_path: Path to file to get the data for + :param line_numbers: List of changed line numbers + :return: Coverage data for a given file + """ lines_elem = cov_report.findall(f".//*/class[@filename='{file_path}']/lines/line") - data = {"statement": {"hit": [], "miss": []}, "branch": {"hit": [], "miss": [], "partial": []}} + data: dict = {"statement": {"hit": [], "miss": []}, "branch": {"hit": [], "miss": [], "partial": []}} for item in lines_elem: line_num = int(item.attrib["number"]) if line_num not in line_numbers: @@ -131,59 +210,81 @@ def extract_coverage(cov_report, file_path, line_numbers): data["branch"][_cov_branch_category(item)].append(line_num) return data -def calc_statement_coverage(stamenent_data): - hit = len(stamenent_data["hit"]) - total = hit + len(stamenent_data["miss"]) + +def calc_statement_coverage(statement_data: dict) -> float: + """Calculate result statement coverage.""" + hit = len(statement_data["hit"]) + total = hit + len(statement_data["miss"]) return (hit/total) if total else -1 -def calc_branch_coverage(branc_data): - hit = len(branc_data["hit"]) - miss = len(branc_data["miss"]) - partial = len(branc_data["partial"]) + +def calc_branch_coverage(branch_data: dict) -> float: + """Calculate result branch coverage.""" + hit = len(branch_data["hit"]) + miss = len(branch_data["miss"]) + partial = len(branch_data["partial"]) total = 2 * (hit + miss + partial) if total == 0: return -1 return (2 * hit + partial) / total -def calc_coverage(cov_data): + +def calc_coverage(cov_data: dict) -> Tuple[float, float]: + """Calculate overall coverage.""" statement = calc_statement_coverage(cov_data["statement"]) brach = calc_branch_coverage(cov_data["branch"]) return statement, brach -def did_pass(number, cutoff): + +def did_pass(number: float, cutoff: float) -> bool: + """Check whether cutoff treshold is met.""" return number == -1 or number >= cutoff -def stringify_pass(number, cutoff): + +def stringify_pass(number: float, cutoff: float) -> str: + """Stringify treshold result to human-friendly format.""" msg = "OK" if did_pass(number, cutoff) else "FAILED" msg += f" ({number*100:2.2f}%)" if number != -1 else " (Not Used)" return msg -def main(): - args = parse_input() +def is_skipped(file_path: str, skip_patterns: Sequence[str]) -> bool: + """Find whether file should qualifies given filer patterns.""" + return any(skip_pattern in file_path for skip_pattern in skip_patterns) + + +def main(argv: Sequence[str] = None) -> int: + """Main function.""" + args = parse_input(argv) logging.debug(args) - commits = get_number_of_commits(args.repo_path, args.parent_branch) - files = get_changed_files(args.repo_path, commits, args.cached) + + files = get_changed_files( + repo_path=args.repo_path, parent_branch=args.parent_branch, + include_merges=args.include_merges + ) files = [f for f in files if f.startswith(args.module)] - # files = filter(lambda x: x.startswith(args.module), files) - logging.debug(f"files to process: {files}\n") + logging.debug(f"files to process: {len(files)}: {files}\n") cov_report = et.parse(args.coverage_report) error_counter = 0 for f in files: logging.info(f"processing: {f}") - git_numbers = extract_linenumber(args.repo_path, f, commits, args.cached) + is_skipped_file = is_skipped(f, args.skip_files) + if is_skipped_file: + logging.info("This file is skipped and will not contribute to the error counter.") + + git_numbers = extract_linenumber(args.repo_path, f, args.parent_branch) logging.debug(f"git lines: {git_numbers}") # the coverage.xml removes the module name from path sanitized_name = f.replace(f"{args.module}/", "") cov_numbers = extract_coverage(cov_report, sanitized_name, git_numbers) logging.debug(f"cov lines: {cov_numbers}") statement_cov, branch_cov = calc_coverage(cov_numbers) - if not did_pass(statement_cov, args.coverage_cutoff): - logging.info(f"uncovered lines: {cov_numbers['statement']['miss']}") + logging.info(f"uncovered lines: {cov_numbers['statement']['miss']}") + if not did_pass(statement_cov, args.coverage_cutoff) and not is_skipped_file: error_counter += 1 - if not did_pass(branch_cov, args.coverage_cutoff): - logging.info(f"uncovered branches: {cov_numbers['branch']['miss']}") - logging.info(f"partially covered branches: {cov_numbers['branch']['partial']}") + logging.info(f"uncovered branches: {cov_numbers['branch']['miss']}") + logging.info(f"partially covered branches: {cov_numbers['branch']['partial']}") + if not did_pass(branch_cov, args.coverage_cutoff) and not is_skipped_file: error_counter += 1 logging.info(f"Statement coverage: {stringify_pass(statement_cov, args.coverage_cutoff)}") logging.info(f"Branch coverage: {stringify_pass(branch_cov, args.coverage_cutoff)}\n") @@ -195,5 +296,6 @@ def main(): return error_counter + if __name__ == "__main__": sys.exit(main()) diff --git a/tools/sr_xls2xml.py b/tools/sr_xls2xml.py new file mode 100644 index 00000000..9b1296d4 --- /dev/null +++ b/tools/sr_xls2xml.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Module to covert Shadow register description EXCEL file to XML.""" + + +from typing import Any +import re +import sys +import os + +import click + +import openpyxl +import openpyxl.utils as utils +import openpyxl.utils.cell as cell_utils + +from spsdk.utils.registers import (Registers, RegsRegister, RegsBitField, RegsEnum) + + +XLS_COLUMN_NAMES = ("Block Name", "OTP Word", "Register Name", "Field Name", "Enum Name", + "Description", "Shadow Register Offset/bit offset", "Register Width / Field width", + "Value", "Access rw = has shadow register") + + +@click.group() +@click.option('-x', '--xls', type=str) +@click.option('-m', '--xml', type=str) +@click.option('-t', '--xls_type', type=int, default=1) +@click.help_option('--help') +@click.pass_context +def main(ctx: click.Context, xls: str, xml: str, xls_type: int=1) -> int: + + if not isinstance(xls, str): + return -1 + + if not isinstance(xml, str): + xml = "" + try: + xls2xml_class = XLS_TYPES[str(xls_type)] + xls2xml_class(xls, xml, xls_type) + except Exception as exc: + print(str(exc)) + return -1 + + return 0 + +@main.command() +@click.pass_obj +def convert(pass_obj: dict) -> None: + """List supported Devices.""" + print("convert") + +class ShadowRegsXlsToXml(): + "Class to convert XLSX to XML with shadow register description" + def __init__(self, xls_file: str, xml_file: str = "", xls_type: int=1) -> None: + self.registers = Registers("Unknown") + self.xls_type = xls_type + self.header_cells = {} + self.xml_file_name = xml_file if xml_file != "" else xls_file.replace(".xlsx", ".xml") + self.wb = None + print(os.path.dirname(os.path.realpath(__file__))) + self.wb = openpyxl.load_workbook(xls_file) + print(f"Loaded XLSX file ({xls_file})") + self.convert() + self.registers.write_xml(self.xml_file_name) + print(f"Written XML file ({self.xml_file_name})") + print(str(self.registers)) + + def convert(self) -> None: + raise NotImplementedError + + def _get_worksheet(self) -> Any: + """Find the valid worksheet with the fuse map.""" + raise NotImplementedError + + def _get_header(self) -> None: + """Returns the dictionary with cells of header.""" + raise NotImplementedError + + def _get_registers(self) -> None: + """Function finds all registers in XLS sheet and store them.""" + raise NotImplementedError + + def __del__(self) -> None: + """Just close all open files.""" + if self.wb: + self.wb.close() + +class ShadowRegsXlsToXml_Type1(ShadowRegsXlsToXml): + + def convert(self) -> None: + self.ws = self._get_worksheet() + #Get all merged cells + self.merged_cells = self.ws.merged_cells.ranges + self._get_header() + self._get_registers() + + def _get_worksheet(self) -> Any: + """Find the valid worksheet with the fuse map.""" + return self.wb.active + + def _get_header(self) -> None: + """Returns the dictionary with cells of header.""" + ret = {} + for head in XLS_COLUMN_NAMES: + self.header_cells[head] = self._find_cell_coor_by_val(head) + + def _filterout_bitrange(self, name: str) -> (str, bool): + """Function filter out the bit ranges in various shapes from register name.""" + bits_rev1 = re.search(r"_\d{1,4}_\d{1,4}$", name) + bits_rev2 = re.search(r"\[\d{1,4}:\d{1,4}\]$", name) + reverse = False + if bits_rev1 or bits_rev2: + bits = bits_rev1 if bits_rev1 else bits_rev2 + name = name.replace(bits.group(0), "") + # Determine the order of the multiple registers. + # Just find if the first multiple register contains zero + bit_numbers = re.findall(r"(\d{1,4})+", bits.group(0)) + reverse = bit_numbers[len(bit_numbers) - 1] != "0" + + return name, reverse + + def _get_registers(self) -> None: + """Function finds all registers in XLS sheet and store them.""" + regname_cr = cell_utils.coordinate_from_string(self.header_cells["Register Name"]) + sr_access_cr = cell_utils.coordinate_from_string(self.header_cells["Access rw = has shadow register"]) + desc_cr = cell_utils.coordinate_from_string(self.header_cells["Description"]) + offset_cr = cell_utils.coordinate_from_string(self.header_cells["Shadow Register Offset/bit offset"]) + width_cr = cell_utils.coordinate_from_string(self.header_cells["Register Width / Field width"]) + + s = 1 + regname_cr[1] + skip = 0 + for r in range(s, self.ws.max_row + 1): + cell = regname_cr[0] + str(r) + if skip > 0: + skip -= 1 + elif isinstance(self.ws[cell].value, str): + # We have a register, just Mask out the Fuse register only + access = self.ws[sr_access_cr[0] + str(r)].value \ + if isinstance(self.ws[sr_access_cr[0] + str(r)].value, str) else "" + if any(x in access for x in ["rw", "ro", "wo"]): + # Now we have just Shadow registers only + # Some registers are defined multiply to store bigger data + # those could be detected by merged description field + reg_name = self.ws[cell].value + # Now, normalize the name + reg_name, reg_reverse = self._filterout_bitrange(reg_name) + cells = self._get_merged_by_first_cell(desc_cr[0] + str(r)) + if cells is not None: + # set the skip for next search + cells = cells.split(':') + skip = cell_utils.coordinate_from_string(cells[1])[1] - \ + cell_utils.coordinate_from_string(cells[0])[1] + + reg_offset = int(self.ws[offset_cr[0] + str(r)].value, 16) + reg_width = int(self.ws[width_cr[0] + str(r)].value) * (skip + 1) + reg_descr = self.ws[desc_cr[0] + str(r)].value + reg_name = reg_name.strip() + + register = RegsRegister(reg_name, reg_offset, reg_width, reg_descr, reg_reverse, access) + + self.registers.add_register(register) + + cells = self._get_merged_by_first_cell(regname_cr[0] + str(r)) + if cells is not None: + # find the number of rows of the register description + cells = cells.split(':') + reg_lines = cell_utils.coordinate_from_string(cells[1])[1] - cell_utils.coordinate_from_string(cells[0])[1] + self._get_bitfields(register, r, reg_lines + 1) + + def _get_bitfields(self, reg: Any, excel_row: int, excel_row_cnt: int) -> None: + """Tried to find and fill up all register bitfields.""" + if excel_row_cnt <= 1: + # There is no bitfields + return + + bitfieldname_cr = cell_utils.coordinate_from_string(self.header_cells["Field Name"]) + desc_cr = cell_utils.coordinate_from_string(self.header_cells["Description"]) + offset_cr = cell_utils.coordinate_from_string(self.header_cells["Shadow Register Offset/bit offset"]) + width_cr = cell_utils.coordinate_from_string(self.header_cells["Register Width / Field width"]) + rv_cr = cell_utils.coordinate_from_string(self.header_cells["Value"]) + + excel_row += 1 + excel_row_cnt -= 1 + + for r in range(excel_row, excel_row + excel_row_cnt): + cell = bitfieldname_cr[0] + str(r) + if isinstance(self.ws[cell].value, str): + bitfield_name = self.ws[cell].value + bitfield_offset = int(self.ws[offset_cr[0] + str(r)].value) + bitfield_width = int(self.ws[width_cr[0] + str(r)].value) + bitfield_descr = self.ws[desc_cr[0] + str(r)].value + bitfield_rv = self.ws[rv_cr[0] + str(r)].value + bitfield_rv = bitfield_rv if bitfield_rv is not None else "N/A" + bitf = RegsBitField(reg, + bitfield_name, + bitfield_offset, + bitfield_width, + bitfield_descr, + reset_val=bitfield_rv) + reg.add_bitfield(bitf) + + cells = self._get_merged_by_first_cell(bitfieldname_cr[0] + str(r)) + if cells is not None: + # find the number of rows of the register description + cells = cells.split(':') + reg_lines = cell_utils.coordinate_from_string(cells[1])[1] - \ + cell_utils.coordinate_from_string(cells[0])[1] + self._get_enums(bitf, r, reg_lines + 1) + + def _get_enums(self, bitfield: Any, excel_row: int, excel_row_cnt: int) -> None: + """Tried to find and fill up all register bitfields enumerations.""" + if excel_row_cnt <= 1: + # There is no enums + return + + enumname_cr = cell_utils.coordinate_from_string(self.header_cells["Enum Name"]) + desc_cr = cell_utils.coordinate_from_string(self.header_cells["Description"]) + value_cr = cell_utils.coordinate_from_string(self.header_cells["Value"]) + + excel_row += 1 + excel_row_cnt -= 1 + + for r in range(excel_row, excel_row + excel_row_cnt): + cell = enumname_cr[0] + str(r) + if isinstance(self.ws[cell].value, str): + enum_name = self.ws[cell].value + enum_descr = self.ws[desc_cr[0] + str(r)].value + enum_value: str = self.ws[value_cr[0] + str(r)].value + if enum_value is None: + print(f"Warning: The Enum {enum_name} is missing and it will be skipped.") + else: + bitfield.add_enum(RegsEnum(enum_name, enum_value, enum_descr, bitfield.width)) + + def _get_merged_by_first_cell(self, cell:str) -> str: + """ Function returns the merged range by first cell.""" + for merged in self.merged_cells: + if merged.coord.find(cell+":") >= 0: + return merged.coord + return None + + + def _find_cell_coor_by_val(self, value:Any, start:str = "", end:str = "") -> str: + """Search engine for the cell values""" + if start == "" or start == None: + start = "A1" + if end == "" or end == None: + end = utils.get_column_letter(self.ws.max_column) + str(self.ws.max_row) + + s = cell_utils.coordinate_from_string(start) + e = cell_utils.coordinate_from_string(end) + sc = utils.column_index_from_string(s[0]) + sr = s[1] + ec = utils.column_index_from_string(e[0]) + er = e[1] + + for r in range(sr, er+1): + for c in range(sc, ec+1): + val = self.ws[utils.get_column_letter(c) + str(r)].value + if isinstance(val, str): + val = val.replace("\n", " ") + val = val.replace(" ", " ") + if value == val: + return utils.get_column_letter(c) + str(r) + + return None + + +class ShadowRegsXlsToXml_Type2(ShadowRegsXlsToXml): + + def convert(self) -> None: + self.ws = self._get_worksheet() + #Get all merged cells + self._get_header() + self._get_registers() + + def _get_worksheet(self) -> None: + """Find the valid worksheet with the fuse map.""" + return self.wb["Fuse Definitions"] + + def _get_header(self) -> None: + self.header_cells["reg_base"] = "A" + self.header_cells["fuse_address"] = "B" + self.header_cells["fuse_index"] = "D" + self.header_cells["fuse_name"] = "E" + self.header_cells["fuse_width"] = "F" + self.header_cells["fuse_descr"] = "G" + self.header_cells["fuse_sett"] = "H" + self.header_cells["burned_value"] = "J" + self.header_cells["customer_visible"] = "L" + + def _get_regbase(self, line: int) -> int: + try: + base = int(self.ws[self.header_cells["reg_base"] + str(line)].value, 16) + except Exception as exc: + base = -1 + return base + + def _get_fusename(self, line: int) -> int: + try: + name = self.ws[self.header_cells["fuse_name"] + str(line)].value + except Exception as exc: + name = "Unknown name :-(" + return name + + def _get_fuseoffset(self, line: int) -> int: + + try: + reg_offset_bits = (self._get_regbase(line) - 0x400) + fuse_offset = self.ws[self.header_cells["fuse_index"] + str(line)].value + fuse_offset = fuse_offset - reg_offset_bits + except Exception as exc: + fuse_offset = -1 + return fuse_offset + + def _get_fusewidth(self, line: int) -> int: + + try: + fuse_width = self.ws[self.header_cells["fuse_width"] + str(line)].value + except Exception as exc: + fuse_width = -1 + return fuse_width + + def _get_fusedescription(self, line: int) -> int: + try: + fuse_description = self.ws[self.header_cells["fuse_descr"] + str(line)].value + except Exception as exc: + fuse_description = "There is no any special description" + return fuse_description + + def _get_fuse_resetvalue(self, line: int) -> int: + try: + fuse_resetvalue = self.ws[self.header_cells["burned_value"] + str(line)].value + except Exception as exc: + fuse_resetvalue = "N/A" + return fuse_resetvalue + + def _get_fuse_bitfield_info(self, line: int) -> (int,int): + try: + fuse_width = self._get_fusewidth(line) + fuse_address = self.ws[self.header_cells["fuse_address"] + str(line)].value + pattern = re.compile(r'\[([^)]*)\]') + offsets = pattern.findall(fuse_address)[0] + if offsets.count(":") > 0: + offsets = offsets.split(":") + offsets.reverse() + offset = int(offsets[0]) + else: + offset = int(offsets) + except Exception as exc: + print(f"Issue with get the getting bitfield info ({str(exc)})") + + return offset, fuse_width + + def _get_registers(self) -> None: + # Start line in excel style 2 is 3! + reg_base = 0 + try: + for r in range(3, self.ws.max_row + 1): + new_reg_base = self._get_regbase(r) + if new_reg_base == -1: + break + if new_reg_base != reg_base: + # This is new register, just create it + reg_base = new_reg_base + reg_name = f"REG_0x{reg_base:04X}" + reg_offset = 0x400 - reg_base + reg_width = 32 # TODO solve that fields + reg_dscr = f"This is description string of {reg_name} register" + reg_reverse = False + reg_access = "RW" + reg = RegsRegister(reg_name, reg_offset, reg_width, reg_dscr, reg_reverse, reg_access) + self.registers.add_register(reg) + + # we have added register, so this is about a adding of bitfield + bitfield_name = self._get_fusename(r) + bitfield_offset, bitfield_width = self._get_fuse_bitfield_info(r) + bitfield_descr = self._get_fusedescription(r) + bitfield_rv = self._get_fuse_resetvalue(r) + bitf = RegsBitField(reg, + bitfield_name, + bitfield_offset, + bitfield_width, + bitfield_descr, + reset_val=bitfield_rv) + reg.add_bitfield(bitf) + except Exception as exc: + print(f"Unwanted exception during getting registers({str(exc)})") + + + + +XLS_TYPES = {"1": ShadowRegsXlsToXml_Type1, + "2": ShadowRegsXlsToXml_Type2} + +if __name__ == "__main__": + sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter +# xls = ShadowRegsXlsToXml("tools/OTP6.xlsx") + # regs = Registers("pokus 685", None) + # regs.load_registers_from_xml("tools/OTP.xml") + # regs.write_xml("tools/OPT2.xml") diff --git a/tools/test_debuggers.py b/tools/test_debuggers.py new file mode 100644 index 00000000..0222b838 --- /dev/null +++ b/tools/test_debuggers.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +# +# Copyright 2021 NXP +# +# SPDX-License-Identifier: BSD-3-Clause +"""Module for testing debuggers support in SPSDK.""" + +import logging +import sys +import random +import click + +from spsdk.exceptions import SPSDKError +from spsdk.apps.utils import INT + +from spsdk.debuggers.utils import DebugProbeUtils + +logger = logging.getLogger("DebugProbesUtils") +LOG_LEVEL_NAMES = [name.lower() for name in logging._nameToLevel] + +@click.group() +@click.option('-i', '--interface') +@click.option('-d', '--debug', 'log_level', metavar='LEVEL', default='debug', + help=f'Set the level of system logging output. ' + f'Available options are: {", ".join(LOG_LEVEL_NAMES)}', + type=click.Choice(LOG_LEVEL_NAMES)) +@click.option('-s', '--serial-no') +@click.option('-ip', '--ip', 'ip_addr') +@click.pass_context +def main(ctx: click.Context, interface: str, log_level: str, + serial_no: str, ip_addr: str) -> int: + """NXP Debug Mailbox Tool.""" + logging.basicConfig(level=log_level.upper()) + logger.setLevel(level=log_level.upper()) + + # Get the Debug probe object + try: + #TODO solve following parameters: + # ip_addr + # tool + debug_probes = DebugProbeUtils.get_connected_probes(interface=interface, hardware_id=serial_no) + selected_probe = debug_probes.select_probe() + debug_probe = DebugProbeUtils.get_probe(interface=selected_probe.interface, + hardware_id=selected_probe.hardware_id) + debug_probe.open() + + ctx.obj = { + 'debug_probe': debug_probe + } + + except: + logger.error("Test of SPSDK debug probes failed") + return -1 + return 0 + +@main.command() +@click.option('-a', '--address', type=INT(), help='Testing address', default="0x20000000") +@click.option('-s', '--size', type=INT(), help='Testing block size', default="1") +@click.pass_obj +def regs(pass_obj: dict, address: int, size: int) -> None: + """Test Shadow registers.""" + if size == 0: + logger.error("Invalid test vector size") + return + error_cnt = 0 + + try: + probe = pass_obj['debug_probe'] + test_vector = [random.randint(0, 0xffffffff) for x in range(size)] + + for i in range(size): + probe.mem_reg_write(address + i * 4, test_vector[i]) + + for i in range(size): + if test_vector[i] != probe.mem_reg_read(address + i * 4): + error_cnt += 1 + + if error_cnt == 0: + logger.info("Debug Probe shadow register test ends successfully") + else: + logger.error(f"Debug Probe shadow register test ends with {error_cnt} fails from {size}") + except Exception as exc: + logger.error(f"Debug Probe shadow register test failed! ({str(exc)})") + +if __name__ == "__main__": + sys.exit(main()) # pragma: no cover # pylint: disable=no-value-for-parameter \ No newline at end of file