diff options
| author | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 | 
|---|---|---|
| committer | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 | 
| commit | 4fd287655a72b9aea14cdac715ad5b90ed082ed2 (patch) | |
| tree | 65d393bc0e699dd12d05b29ba568e04cea666207 /circuitpython/lib/nrfutil/nordicsemi/dfu/package.py | |
| parent | 0150f70ce9c39e9e6dd878766c0620c85e47bed0 (diff) | |
add circuitpython code
Diffstat (limited to 'circuitpython/lib/nrfutil/nordicsemi/dfu/package.py')
| -rw-r--r-- | circuitpython/lib/nrfutil/nordicsemi/dfu/package.py | 369 | 
1 files changed, 369 insertions, 0 deletions
| diff --git a/circuitpython/lib/nrfutil/nordicsemi/dfu/package.py b/circuitpython/lib/nrfutil/nordicsemi/dfu/package.py new file mode 100644 index 0000000..68c1d80 --- /dev/null +++ b/circuitpython/lib/nrfutil/nordicsemi/dfu/package.py @@ -0,0 +1,369 @@ +# Copyright (c) 2015, Nordic Semiconductor +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +#   list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +#   this list of conditions and the following disclaimer in the documentation +#   and/or other materials provided with the distribution. +# +# * Neither the name of Nordic Semiconductor ASA nor the names of its +#   contributors may be used to endorse or promote products derived from +#   this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Python standard library +import os +import tempfile +import shutil + +# 3rd party libraries +from zipfile import ZipFile +import hashlib + + +# Nordic libraries +from nordicsemi.exceptions import NordicSemiException +from nordicsemi.dfu.nrfhex import * +from nordicsemi.dfu.init_packet import * +from nordicsemi.dfu.manifest import ManifestGenerator, Manifest +from nordicsemi.dfu.model import HexType, FirmwareKeys +from nordicsemi.dfu.crc16 import * + +from signing import Signing + + +class Package(object): +    """ +        Packages and unpacks Nordic DFU packages. Nordic DFU packages are zip files that contains firmware and meta-information +        necessary for utilities to perform a DFU on nRF5X devices. + +        The internal data model used in Package is a dictionary. The dictionary is expressed like this in +         json format: + +         { +            "manifest": { +                "bootloader": { +                    "bin_file": "asdf.bin", +                    "dat_file": "asdf.dat", +                    "init_packet_data": { +                        "application_version": null, +                        "device_revision": null, +                        "device_type": 5, +                        "firmware_hash": "asdfasdkfjhasdkfjashfkjasfhaskjfhkjsdfhasjkhf", +                        "softdevice_req": [ +                            17, +                            18 +                        ] +                    } +                } +        } + +        Attributes application, bootloader, softdevice, softdevice_bootloader shall not be put into the manifest if they are null + +    """ + +    DEFAULT_DEV_TYPE = 0xFFFF +    DEFAULT_DEV_REV = 0xFFFF +    DEFAULT_APP_VERSION = 0xFFFFFFFF +    DEFAULT_SD_REQ = [0xFFFE] +    DEFAULT_DFU_VER = 0.5 +    MANIFEST_FILENAME = "manifest.json" + +    def __init__(self, +                 dev_type=DEFAULT_DEV_TYPE, +                 dev_rev=DEFAULT_DEV_REV, +                 app_version=DEFAULT_APP_VERSION, +                 sd_req=DEFAULT_SD_REQ, +                 app_fw=None, +                 bootloader_fw=None, +                 softdevice_fw=None, +                 dfu_ver=DEFAULT_DFU_VER, +                 key_file=None): +        """ +        Constructor that requires values used for generating a Nordic DFU package. + +        :param int dev_type: Device type init-packet field +        :param int dev_rev: Device revision init-packet field +        :param int app_version: App version init-packet field +        :param list sd_req: Softdevice Requirement init-packet field +        :param str app_fw: Path to application firmware file +        :param str bootloader_fw: Path to bootloader firmware file +        :param str softdevice_fw: Path to softdevice firmware file +        :param float dfu_ver: DFU version to use when generating init-packet +        :param str key_file: Path to Signing key file (PEM) +        :return: None +        """ +        self.dfu_ver = dfu_ver + +        init_packet_vars = {} + +        if dev_type is not None: +            init_packet_vars[PacketField.DEVICE_TYPE] = dev_type + +        if dev_rev is not None: +            init_packet_vars[PacketField.DEVICE_REVISION] = dev_rev + +        if app_version is not None: +            init_packet_vars[PacketField.APP_VERSION] = app_version + +        if sd_req is not None: +            init_packet_vars[PacketField.REQUIRED_SOFTDEVICES_ARRAY] = sd_req + +        self.firmwares_data = {} + +        if app_fw: +            self.__add_firmware_info(HexType.APPLICATION, +                                     app_fw, +                                     init_packet_vars) + +        if bootloader_fw: +            self.__add_firmware_info(HexType.BOOTLOADER, +                                     bootloader_fw, +                                     init_packet_vars) + +        if softdevice_fw: +            self.__add_firmware_info(HexType.SOFTDEVICE, +                                     softdevice_fw, +                                     init_packet_vars) + +        if key_file: +            self.dfu_ver = 0.8 +            self.key_file = key_file + +    def generate_package(self, filename, preserve_work_directory=False): +        """ +        Generates a Nordic DFU package. The package is a zip file containing firmware(s) and metadata required +        for Nordic DFU applications to perform DFU onn nRF5X devices. + +        :param str filename: Filename for generated package. +        :param bool preserve_work_directory: True to preserve the temporary working directory. +        Useful for debugging of a package, and if the user wants to look at the generated package without having to +        unzip it. +        :return: None +        """ +        work_directory = self.__create_temp_workspace() + +        if Package._is_bootloader_softdevice_combination(self.firmwares_data): +            # Removing softdevice and bootloader data from dictionary and adding the combined later +            softdevice_fw_data = self.firmwares_data.pop(HexType.SOFTDEVICE) +            bootloader_fw_data = self.firmwares_data.pop(HexType.BOOTLOADER) + +            softdevice_fw_name = softdevice_fw_data[FirmwareKeys.FIRMWARE_FILENAME] +            bootloader_fw_name = bootloader_fw_data[FirmwareKeys.FIRMWARE_FILENAME] + +            new_filename = "sd_bl.bin" +            sd_bl_file_path = os.path.join(work_directory, new_filename) + +            nrf_hex = nRFHex(softdevice_fw_name, bootloader_fw_name) +            nrf_hex.tobinfile(sd_bl_file_path) + +            softdevice_size = nrf_hex.size() +            bootloader_size = nrf_hex.bootloadersize() + +            self.__add_firmware_info(HexType.SD_BL, +                                     sd_bl_file_path, +                                     softdevice_fw_data[FirmwareKeys.INIT_PACKET_DATA], +                                     softdevice_size, +                                     bootloader_size) + +        for key in self.firmwares_data: +            firmware = self.firmwares_data[key] + +            # Normalize the firmware file and store it in the work directory +            firmware[FirmwareKeys.BIN_FILENAME] = \ +                Package.normalize_firmware_to_bin(work_directory, firmware[FirmwareKeys.FIRMWARE_FILENAME]) + +            # Calculate the hash for the .bin file located in the work directory +            bin_file_path = os.path.join(work_directory, firmware[FirmwareKeys.BIN_FILENAME]) + +            init_packet_data = firmware[FirmwareKeys.INIT_PACKET_DATA] + +            if self.dfu_ver <= 0.5: +                firmware_hash = Package.calculate_crc16(bin_file_path) +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16] = firmware_hash +            elif self.dfu_ver == 0.6: +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID] = INIT_PACKET_USES_CRC16 +                firmware_hash = Package.calculate_crc16(bin_file_path) +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16] = firmware_hash +            elif self.dfu_ver == 0.7: +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID] = INIT_PACKET_USES_HASH +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH] = int(Package.calculate_file_size(bin_file_path)) +                firmware_hash = Package.calculate_sha256_hash(bin_file_path) +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH] = firmware_hash +            elif self.dfu_ver == 0.8: +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID] = INIT_PACKET_EXT_USES_ECDS +                firmware_hash = Package.calculate_sha256_hash(bin_file_path) +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH] = int(Package.calculate_file_size(bin_file_path)) +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH] = firmware_hash +                temp_packet = self._create_init_packet(firmware) +                signer = Signing() +                signer.load_key(self.key_file) +                signature = signer.sign(temp_packet) +                init_packet_data[PacketField.NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS] = signature + +            # Store the .dat file in the work directory +            init_packet = self._create_init_packet(firmware) +            init_packet_filename = firmware[FirmwareKeys.BIN_FILENAME].replace(".bin", ".dat") + +            with open(os.path.join(work_directory, init_packet_filename), 'wb') as init_packet_file: +                init_packet_file.write(init_packet) + +            firmware[FirmwareKeys.DAT_FILENAME] = \ +                init_packet_filename + +        # Store the manifest to manifest.json +        manifest = self.create_manifest() + +        with open(os.path.join(work_directory, Package.MANIFEST_FILENAME), "w") as manifest_file: +            manifest_file.write(manifest) + +        # Package the work_directory to a zip file +        Package.create_zip_package(work_directory, filename) + +        # Delete the temporary directory +        if not preserve_work_directory: +            shutil.rmtree(work_directory) + +    @staticmethod +    def __create_temp_workspace(): +        return tempfile.mkdtemp(prefix="nrf_dfu_") + +    @staticmethod +    def create_zip_package(work_directory, filename): +        files = os.listdir(work_directory) + +        with ZipFile(filename, 'w') as package: +            for _file in files: +                file_path = os.path.join(work_directory, _file) +                package.write(file_path, _file) + +    @staticmethod +    def calculate_file_size(firmware_filename): +        b = os.path.getsize(firmware_filename) +        return b + +    @staticmethod +    def calculate_sha256_hash(firmware_filename): +        read_buffer = 4096 + +        digest = hashlib.sha256() + +        with open(firmware_filename, 'rb') as firmware_file: +            while True: +                data = firmware_file.read(read_buffer) + +                if data: +                    digest.update(data) +                else: +                    break + +        return digest.digest() + +    @staticmethod +    def calculate_crc16(firmware_filename): +        """ +        Calculates CRC16 has on provided firmware filename + +        :type str firmware_filename: +        """ +        data_buffer = b'' +        read_size = 4096 + +        with open(firmware_filename, 'rb') as firmware_file: +            while True: +                data = firmware_file.read(read_size) + +                if data: +                    data_buffer += data +                else: +                    break + +        return calc_crc16(data_buffer, 0xffff) + +    def create_manifest(self): +        manifest = ManifestGenerator(self.dfu_ver, self.firmwares_data) +        return manifest.generate_manifest() + +    @staticmethod +    def _is_bootloader_softdevice_combination(firmwares): +        return (HexType.BOOTLOADER in firmwares) and (HexType.SOFTDEVICE in firmwares) + +    def __add_firmware_info(self, firmware_type, filename, init_packet_data, sd_size=None, bl_size=None): +        self.firmwares_data[firmware_type] = { +            FirmwareKeys.FIRMWARE_FILENAME: filename, +            FirmwareKeys.INIT_PACKET_DATA: init_packet_data.copy(), +            # Copying init packet to avoid using the same for all firmware +            } + +        if firmware_type == HexType.SD_BL: +            self.firmwares_data[firmware_type][FirmwareKeys.SD_SIZE] = sd_size +            self.firmwares_data[firmware_type][FirmwareKeys.BL_SIZE] = bl_size + +    @staticmethod +    def _create_init_packet(firmware_data): +        p = Packet(firmware_data[FirmwareKeys.INIT_PACKET_DATA]) +        return p.generate_packet() + +    @staticmethod +    def normalize_firmware_to_bin(work_directory, firmware_path): +        firmware_filename = os.path.basename(firmware_path) +        new_filename = firmware_filename.replace(".hex", ".bin") +        new_filepath = os.path.join(work_directory, new_filename) + +        if not os.path.exists(new_filepath): +            temp = nRFHex(firmware_path) +            temp.tobinfile(new_filepath) + +        return new_filepath + +    @staticmethod +    def unpack_package(package_path, target_dir): +        """ +        Unpacks a Nordic DFU package. + +        :param str package_path: Path to the package +        :param str target_dir: Target directory to unpack the package to +        :return: Manifest Manifest: Returns a manifest back to the user. The manifest is a parse datamodel +        of the manifest found in the Nordic DFU package. +        """ + +        if not os.path.isfile(package_path): +            raise NordicSemiException("Package {0} not found.".format(package_path)) + +        target_dir = os.path.abspath(target_dir) +        target_base_path = os.path.dirname(target_dir) + +        if not os.path.exists(target_base_path): +            raise NordicSemiException("Base path to target directory {0} does not exist.".format(target_base_path)) + +        if not os.path.isdir(target_base_path): +            raise NordicSemiException("Base path to target directory {0} is not a directory.".format(target_base_path)) + +        if os.path.exists(target_dir): +            raise NordicSemiException( +                "Target directory {0} exists, not able to unpack to that directory.", +                target_dir) + +        with ZipFile(package_path, 'r') as pkg: +            pkg.extractall(target_dir) + +            with open(os.path.join(target_dir, Package.MANIFEST_FILENAME), 'r') as f: +                _json = f.read() +                """:type :str """ + +                return Manifest.from_json(_json) | 
