def send_firmware(self, firmware):
        def try_to_recover():
            if response['offset'] == 0:
                # Nothing to recover
                return

            expected_crc = binascii.crc32(
                firmware[:response['offset']]) & 0xFFFFFFFF
            remainder = response['offset'] % response['max_size']

            if expected_crc != response['crc']:
                # Invalid CRC. Remove corrupted data.
                response[
                    'offset'] -= remainder if remainder != 0 else response[
                        'max_size']
                response['crc']     = \
                        binascii.crc32(firmware[:response['offset']]) & 0xFFFFFFFF
                return

            if (remainder != 0) and (response['offset'] != len(firmware)):
                # Send rest of the page.
                try:
                    to_send = firmware[response['offset']:response['offset'] +
                                       response['max_size'] - remainder]
                    response['crc'] = self.__stream_data(
                        data=to_send,
                        crc=response['crc'],
                        offset=response['offset'])
                    response['offset'] += len(to_send)
                except ValidationException:
                    # Remove corrupted data.
                    response['offset'] -= remainder
                    response['crc']     = \
                        binascii.crc32(firmware[:response['offset']]) & 0xFFFFFFFF
                    return

            self.__execute()
            self._send_event(event_type=DfuEvent.PROGRESS_EVENT,
                             progress=response['offset'])

        response = self.__select_data()
        try_to_recover()
        for i in range(response['offset'], len(firmware),
                       response['max_size']):
            data = firmware[i:i + response['max_size']]
            try:
                self.__create_data(len(data))
                response['crc'] = self.__stream_data(data=data,
                                                     crc=response['crc'],
                                                     offset=i)
                self.__execute()
            except ValidationException:
                raise NordicSemiException("Failed to send firmware")

            self._send_event(event_type=DfuEvent.PROGRESS_EVENT,
                             progress=len(data))
 def send_message(self, data):
     packet = Slip.encode(data)
     logger.log(TRANSPORT_LOGGING_LEVEL, 'SLIP: --> ' + str(data))
     try:
         self.serial_port.write(packet)
     except SerialException as e:
         raise NordicSemiException(
             'Writing to serial port failed: ' + str(e) + '. '
             'If MSD is enabled on the target device, try to disable it ref. '
             'https://wiki.segger.com/index.php?title=J-Link-OB_SAM3U')
Example #3
0
 def wrapper(wrapped, instance, args, kwargs):
     err_code = wrapped(*args, **kwargs)
     if err_code != expected:
         try:
             err_string = 'Error code: {}'.format(NrfError(err_code))
         except ValueError:
             err_string = 'Error code: 0x{:04x}, {}'.format(
                 err_code, err_code)
         raise NordicSemiException('Failed to {}. {}'.format(
             wrapped.__name__, err_string))
Example #4
0
    def open(self):
        super(DfuTransportSerial, self).open()

        try:
            self.serial_port = Serial(port=self.com_port, 
                baudrate=self.baud_rate, rtscts=self.flow_control, timeout=self.timeout)
            self.dfu_adapter = DFUAdapter(self.serial_port)
        except Exception, e:
            raise NordicSemiException("Serial port could not be opened on {0}" 
            + ". Reason: {1}".format(self.com_port, e.message))
 def read_req(self, conn_handle, uuid):
     handle = self.db_conns[conn_handle].get_char_value_handle(uuid)
     if handle is None:
         raise NordicSemiException("Characteristic value handler not found")
     self.driver.ble_gattc_read(conn_handle, handle, 0)
     result = self.evt_sync[conn_handle].wait(evt=BLEEvtID.gattc_evt_read_rsp)
     gatt_res = result["status"]
     if gatt_res == BLEGattStatusCode.success:
         return gatt_res, result["data"]
     else:
         return gatt_res, None
Example #6
0
 def write_prep(self, conn_handle, uuid, data, offset):
     handle = self.db_conns[conn_handle].get_char_value_handle(uuid)
     if handle == None:
         raise NordicSemiException('Characteristic value handler not found')
     write_params = BLEGattcWriteParams(
         BLEGattWriteOperation.prepare_write_req,
         BLEGattExecWriteFlag.prepared_write, handle, data, offset)
     self.driver.ble_gattc_write(conn_handle, write_params)
     result = self.evt_sync[conn_handle].wait(
         evt=BLEEvtID.gattc_evt_write_rsp)
     return result['status']
 def on_gatts_evt_exchange_mtu_request(self, ble_driver, conn_handle,
                                       client_mtu):
     try:
         ble_driver.ble_gatts_exchange_mtu_reply(conn_handle,
                                                 self.default_mtu)
     except NordicSemiException as ex:
         raise NordicSemiException(
             "MTU exchange reply failed. Common causes are: "
             "missing att_mtu setting in ble_cfg_set, "
             "different config tags used in ble_cfg_set and adv_start."
         ) from ex
    def __calculate_checksum(self):
        self.dfu_adapter.send_message([DfuTransportSerial.OP_CODE['CalcChecSum']])
        response = self.__get_response(DfuTransportSerial.OP_CODE['CalcChecSum'])

        if response is None:
            raise NordicSemiException('Did not receive checksum response from DFU target. '
                                      'If MSD is enabled on the target device, try to disable it ref. '
                                      'https://wiki.segger.com/index.php?title=J-Link-OB_SAM3U')

        (offset, crc) = struct.unpack('<II', bytearray(response))
        return {'offset': offset, 'crc': crc}
Example #9
0
    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)
Example #10
0
    def connect(self, target_device_name, target_device_addr):
        """ Connect to Bootloader or Application with Buttonless Service.

        Args:
            target_device_name (str): Device name to scan for.
            target_device_addr (str): Device addr to scan for.
        """
        self.target_device_name = target_device_name
        self.target_device_addr = target_device_addr

        logger.info('BLE: Scanning for {}'.format(self.target_device_name))
        self.adapter.driver.ble_gap_scan_start()
        self.verify_stable_connection()
        if self.conn_handle is None:
            raise NordicSemiException('Timeout. Target device not found.')

        logger.info('BLE: Service Discovery')
        self.adapter.service_discovery(conn_handle=self.conn_handle)

        # Check if connected peer has Buttonless service.
        if self.adapter.db_conns[self.conn_handle].get_cccd_handle(
                DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID):
            self.jump_from_buttonless_mode_to_bootloader(
                DFUAdapter.BLE_DFU_BUTTONLESS_CHAR_UUID)
        elif self.adapter.db_conns[self.conn_handle].get_cccd_handle(
                DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID):
            self.jump_from_buttonless_mode_to_bootloader(
                DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID)

        if self.bonded:
            # For combined Updates with bonds enabled, re-encryption is needed
            self.encrypt()

        if nrf_sd_ble_api_ver >= 3:
            if DFUAdapter.LOCAL_ATT_MTU > ATT_MTU_DEFAULT:
                logger.info('BLE: Enabling longer ATT MTUs')
                self.att_mtu = self.adapter.att_mtu_exchange(
                    self.conn_handle, DFUAdapter.LOCAL_ATT_MTU)

                logger.info('BLE: Enabling longer Data Length')
                max_data_length = 251  # Max data length for SD v5
                data_length = self.att_mtu + 4  # ATT PDU overhead is 4
                if data_length > max_data_length:
                    data_length = max_data_length
                self.adapter.data_length_update(self.conn_handle, data_length)
            else:
                logger.info('BLE: Using default ATT MTU')

        logger.debug('BLE: Enabling Notifications')
        self.adapter.enable_notification(conn_handle=self.conn_handle,
                                         uuid=DFUAdapter.CP_UUID)
        return self.target_device_name, self.target_device_addr
    def write_cmd(self, conn_handle, uuid, data, attr_handle=None):
        try:
            tx_complete = BLEEvtID.evt_tx_complete
        except:
            tx_complete = BLEEvtID.gattc_evt_write_cmd_tx_complete
        if attr_handle is None:
            attr_handle = self.db_conns[conn_handle].get_char_value_handle(
                uuid)
        if attr_handle is None:
            raise NordicSemiException("Characteristic value handler not found")
        write_params = BLEGattcWriteParams(
            BLEGattWriteOperation.write_cmd,
            BLEGattExecWriteFlag.unused,
            attr_handle,
            data,
            0,
        )

        # Send packet and skip waiting for TX-complete event. Try maximum 3 times.
        for _ in range(MAX_TRIES):
            try:
                response = self.driver.ble_gattc_write(conn_handle,
                                                       write_params)
                logger.debug(
                    "Call ble_gattc_write: response({}) write_params({})".
                    format(response, write_params))
                return
            except NordicSemiException as e:
                # Retry if NRF_ERROR_RESOURCES error code.
                err = str(e)
                if (("Error code: 19" in err) or ("NRF_ERROR_RESOURCES" in err)
                        or ("Error code: 12292" in err)
                        or ("BLE_ERROR_NO_TX_PACKETS" in err)):
                    self.evt_sync[conn_handle].wait(evt=tx_complete, timeout=2)
                else:
                    raise e
        raise NordicSemiException(
            "Unable to successfully call ble_gattc_write")
Example #12
0
 def write_req(self, conn_handle, uuid, data):
     handle = self.db_conns[conn_handle].get_char_value_handle(uuid)
     if handle is None:
         raise NordicSemiException("Characteristic value handler not found")
     write_params = BLEGattcWriteParams(
         BLEGattWriteOperation.write_req,
         BLEGattExecWriteFlag.unused,
         handle,
         data,
         0,
     )
     self.driver.ble_gattc_write(conn_handle, write_params)
     result = self.evt_sync[conn_handle].wait(evt=BLEEvtID.gattc_evt_write_rsp)
     return result["status"]
Example #13
0
    def jump_from_buttonless_mode_to_bootloader(self, buttonless_uuid):
        """ Function for going to bootloader mode from application with
         buttonless service. It supports both bonded and unbonded
         buttonless characteristics.

        Args:
            buttonless_uuid: UUID of discovered buttonless characteristic.

        """
        if buttonless_uuid == DFUAdapter.BLE_DFU_BUTTONLESS_BONDED_CHAR_UUID:
            logger.info("Bonded Buttonless characteristic discovered -> Bond")
            self.bond()
        else:
            logger.info(
                "Un-bonded Buttonless characteristic discovered -> Increment target device addr"
            )
            self.target_device_addr = "{:X}".format(
                int(self.target_device_addr, 16) + 1)
            self.target_device_addr_type.addr[-1] += 1

        # Enable indication for Buttonless DFU Service
        self.adapter.enable_indication(self.conn_handle, buttonless_uuid)

        # Enable indication for Service changed Service, if present.
        if self.adapter.db_conns[self.conn_handle].get_char_handle(
                DFUAdapter.SERVICE_CHANGED_UUID):
            self.adapter.enable_indication(self.conn_handle,
                                           DFUAdapter.SERVICE_CHANGED_UUID)

        # Enter DFU mode
        self.adapter.write_req(self.conn_handle, buttonless_uuid, [0x01])
        response = self.indication_q.get(
            timeout=DfuTransportBle.DEFAULT_TIMEOUT)
        if response[DFUAdapter.ERROR_CODE_POS] != 0x01:
            raise Exception("Error - Unexpected response")

        # Wait for buttonless peer to disconnect
        self.evt_sync.wait('disconnected')

        # Reconnect
        self.target_device_name = None
        self.adapter.driver.ble_gap_scan_start()
        self.verify_stable_connection()
        if self.conn_handle is None:
            raise NordicSemiException('Timeout. Target device not found.')
        logger.info('BLE: Connected to target')

        logger.debug('BLE: Service Discovery')
        self.adapter.service_discovery(conn_handle=self.conn_handle)
Example #14
0
    def enable_indication(self, conn_handle, uuid):
        cccd_list = [2, 0]

        handle = self.db_conns[conn_handle].get_cccd_handle(uuid)
        if handle == None:
            raise NordicSemiException('CCCD not found')

        write_params = BLEGattcWriteParams(BLEGattWriteOperation.write_req,
                                           BLEGattExecWriteFlag.unused, handle,
                                           cccd_list, 0)

        self.driver.ble_gattc_write(conn_handle, write_params)
        result = self.evt_sync[conn_handle].wait(
            evt=BLEEvtID.gattc_evt_write_rsp)
        return result['status']
    def open(self):
        super(DfuTransportSerial, self).open()
        try:
            self.__ensure_bootloader()
            self.serial_port = Serial(port=self.com_port,
                baudrate=self.baud_rate, rtscts=self.flow_control, timeout=self.DEFAULT_SERIAL_PORT_TIMEOUT)
            self.dfu_adapter = DFUAdapter(self.serial_port)
        except Exception as e:
            raise NordicSemiException("Serial port could not be opened on {0}"
              ". Reason: {1}".format(self.com_port, e.strerror))

        if self.do_ping:
            ping_success = False
            start = datetime.now()
            while (datetime.now() - start < timedelta(seconds=self.timeout)
                    and ping_success == False):
                if self.__ping() == True:
                    ping_success = True

            if ping_success == False:
                raise NordicSemiException("No ping response after opening COM port")

        self.__set_prn()
        self.__get_mtu()
Example #16
0
    def to_list(self):
        data_list = []
        for k in self.records:
            data_list.append(len(self.records[k]) + 1)  # add type length
            data_list.append(k.value)
            if isinstance(self.records[k], str):
                data_list.extend([ord(c) for c in self.records[k]])

            elif isinstance(self.records[k], list):
                data_list.extend(self.records[k])

            else:
                raise NordicSemiException(
                    'Unsupported value type: 0x{:02X}'.format(
                        type(self.records[k])))
        return data_list
Example #17
0
    def __read_error(self):
        self.dfu_adapter.write_control_point(
            [DfuTransportBle.OP_CODE['ReadError']])
        response = self.__get_response(DfuTransportBle.OP_CODE['ReadError'])

        (err_code, size) = struct.unpack('<HH', bytearray(response))
        data = response[4:]

        while size < len(data):
            try:
                new = self.dfu_adapter.notifications_q.get(
                    timeout=DfuTransportBle.DEFAULT_TIMEOUT)
                data.extend(new)
            except Queue.Empty:
                raise NordicSemiException('Timeout Error Read')

        return {'err_code': err_code, 'data': data}
Example #18
0
def display(key_file, key, format, out_file):
    signer = Signing()

    if not os.path.isfile(key_file):
        raise NordicSemiException("File not found: %s" % key_file)

    default_key = signer.load_key(key_file)
    if default_key:
        display_sec_warning()

    if not key:
        click.echo("You must specify a key with --key (pk|sk).")
        return
    if key != "pk" and key != "sk":
        click.echo("Invalid key type. Valid types are (pk|sk).")
        return

    if not format:
        click.echo("You must specify a format with --format (hex|code|pem).")
        return
    if format != "hex" and format != "code" and format != "pem" and format != "dbgcode":
        click.echo("Invalid format. Valid formats are (hex|code|pem).")
        return

    if format == "dbgcode":
        format = "code"
        dbg = True
    else:
        dbg = False

    if format == "code" and key == "sk":
        click.echo("Displaying the private key as code is not available.")
        return

    if key == "pk":
        kstr = signer.get_vk(format, dbg)
    elif key == "sk":
        kstr = "\nWARNING: Security risk! Do not share the private key.\n\n"
        kstr = kstr + signer.get_sk(format, dbg)

    if not out_file:
        click.echo(kstr)
    else:
        with open(out_file, "w") as kfile:
            kfile.write(kstr)
Example #19
0
    def wrapper(wrapped, instance, args, kwargs):
        logger.debug("[%s] %s%s", instance.serial_port, wrapped.__name__, args)
        result = wrapped(*args, **kwargs)
        if isinstance(result, (list, tuple)):
            err_code = result[0]
            result = result[1:]
            if len(result) == 1:
                result = result[0]
        else:
            err_code = result
            result = None

        if err_code != expected:
            try:
                err_string = 'Error code: {}'.format(NrfError(err_code))
            except ValueError:
                err_string = 'Error code: 0x{:04x}, {}'.format(err_code, err_code)
            raise NordicSemiException('Failed to {}. {}'.format(wrapped.__name__, err_string), err_code)
        return result
Example #20
0
    def att_mtu_exchange(self, conn_handle, mtu):
        try:
            self.driver.ble_gattc_exchange_mtu_req(conn_handle, mtu)
        except NordicSemiException as ex:
            raise NordicSemiException(
                "MTU exchange request failed. Common causes are: "
                "missing att_mtu setting in ble_cfg_set, "
                "different config tags used in ble_cfg_set and connect.") from ex

        response = self.evt_sync[conn_handle].wait(evt=BLEEvtID.gattc_evt_exchange_mtu_rsp)

        if response is None:
            return self.db_conns[conn_handle].att_mtu

        # Use minimum of client and server mtu to ensure both sides support the value
        new_mtu = min(mtu, response["att_mtu"])
        logger.debug(f"New ATT MTU is {new_mtu}")
        self.db_conns[conn_handle].att_mtu = new_mtu
        return new_mtu
Example #21
0
    def bond(self):
        """ Bond to Application with Buttonless Service.

        """
        self.bonded = True
        self.setup_sec_params()
        self.setup_keyset()

        self.adapter.driver.ble_gap_authenticate(self.conn_handle,
                                                 self.sec_params)
        self.evt_sync.wait(evt="sec_params")
        self.adapter.driver.ble_gap_sec_params_reply(self.conn_handle,
                                                     BLEGapSecStatus.success,
                                                     None, self.keyset, None)

        result = self.evt_sync.wait(evt="auth_status")
        if result != BLEGapSecStatus.success:
            raise NordicSemiException(
                "Auth Status returned error code: {}".format(result))
Example #22
0
    def send_init_packet(self, init_packet):
        def try_to_recover():
            if response['offset'] == 0 or response['offset'] > len(
                    init_packet):
                # There is no init packet or present init packet is too long.
                return False

            expected_crc = (binascii.crc32(init_packet[:response['offset']])
                            & 0xFFFFFFFF)

            if expected_crc != response['crc']:
                # Present init packet is invalid.
                return False

            if len(init_packet) > response['offset']:
                # Send missing part.
                try:
                    self.__stream_data(data=init_packet[response['offset']:],
                                       crc=expected_crc,
                                       offset=response['offset'])
                except ValidationException:
                    return False

            self.__execute()
            return True

        response = self.__select_command()
        assert len(
            init_packet) <= response['max_size'], 'Init command is too long'

        if try_to_recover():
            return

        for r in range(DfuTransportBle.RETRIES_NUMBER):
            try:
                self.__create_command(len(init_packet))
                self.__stream_data(data=init_packet)
                self.__execute()
            except ValidationException:
                pass
            break
        else:
            raise NordicSemiException("Failed to send init packet")
Example #23
0
    def fromhexfile(self, f, arch=None):
        self.hex_file = f
        self.ihex.fromfile(f, format='hex')

        # autodetect based on base address
        base = self.ihex.minaddr()

        # check the 3 possible addresses for CRC matches
        try:
            self.probe_settings(BLDFUSettings.bl_sett_51_addr)
            self.set_arch('NRF51')
        except Exception as e:
            try:
                self.probe_settings(BLDFUSettings.bl_sett_52_addr)
                self.set_arch('NRF52')
            except Exception as e:
                try:
                    self.probe_settings(BLDFUSettings.bl_sett_52840_addr)
                    self.set_arch('NRF52840')
                except Exception as e:
                    raise NordicSemiException(
                        "Failed to parse .hex file: {0}".format(e))
Example #24
0
    def enter_bootloader_mode(self, listed_device):
        libusb_device = self.select_device(listed_device)
        if libusb_device is None:
            raise self.no_trigger_exception(listed_device)
        device_handle = libusb_device.open()
        dfu_iface = self.get_dfu_interface_num(libusb_device)

        if dfu_iface is None:
            raise self.no_trigger_exception(listed_device)

        with device_handle.claimInterface(dfu_iface):
            arr = bytearray("0", 'utf-8')
            try:
                device_handle.controlWrite(ReqTypeOUT, DFU_DETACH_REQUEST, 0,
                                           dfu_iface, arr)
            except Exception as err:
                if "LIBUSB_ERROR_PIPE" in str(err):
                    return
        raise NordicSemiException(
            "Device did not exit application mode after dfu was triggered. Serial number: {}, product id 0x{}, vendor id: 0x{}\n\n"
            .format(listed_device.serial_number, listed_device.product_id,
                    listed_device.vendor_id))
Example #25
0
    def connect(self):
        logger.debug('BLE: connect: target address: 0x{}'.format(self.target_device_addr))
        logger.info('BLE: Scanning...')
        self.adapter.driver.ble_gap_scan_start()
        self.conn_handle = self.evt_sync.wait('connected')
        if self.conn_handle is None:
            raise NordicSemiException('Timeout. Target device not found.')
        logger.info('BLE: Connected to target')
        logger.debug('BLE: Service Discovery')

        if nrf_sd_ble_api_ver >= 3:
            if DFUAdapter.LOCAL_ATT_MTU > ATT_MTU_DEFAULT:
                logger.info('BLE: Enabling longer ATT MTUs')
                self.att_mtu = self.adapter.att_mtu_exchange(self.conn_handle, BleSerial.LOCAL_ATT_MTU)
                logger.info('BLE: ATT MTU: {}'.format(self.att_mtu))
            else:
                logger.info('BLE: Using default ATT MTU')

        self.adapter.service_discovery(conn_handle=self.conn_handle)
        logger.debug('BLE: Enabling Notifications')
        self.adapter.enable_notification(conn_handle=self.conn_handle, uuid=BleSerial.RX_UUID)
        return self.target_device_name, self.target_device_addr
Example #26
0
def slip_decode_esc_chars(data):
    """Decode esc characters in a SLIP package.

    Replaces 0xDBDC with 0xCO and 0xDBDD with 0xDB.

    :return: str decoded data
    :type str data: data to decode
    """
    result = []
    while len(data):
        char = data.pop(0)
        if char == 0xDB:
            char2 = data.pop(0)
            if char2 == 0xDC:
                result.append(0xC0)
            elif char2 == 0xDD:
                result.append(0xDB)
            else:
                raise NordicSemiException(
                    'Char 0xDB NOT followed by 0xDC or 0xDD')
        else:
            result.append(char)
    return result
Example #27
0
    def calculate_crc(crc, 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
        if crc == 16:
            return calc_crc16(data_buffer, 0xffff)
        elif crc == 32:
            return binascii.crc32(data_buffer)
        else:
            raise NordicSemiException("Invalid CRC type")
Example #28
0
 def no_trigger_exception(self, device):
     return NordicSemiException(
         "No trigger interface found for device with serial number: {}, Product ID: 0x{} and Vendor ID: 0x{}\n"
         .format(device.serial_number, device.product_id, device.vendor_id))
Example #29
0
def generate(zipfile, debug_mode, application, application_version,
             application_version_string, bootloader, bootloader_version,
             hw_version, sd_req, softdevice, key_file):
    """
    Generate a zip package for distribution to apps that support Nordic DFU OTA.
    The application, bootloader, and SoftDevice files are converted to .bin if supplied as .hex files.
    For more information on the generated package, see:
    http://developer.nordicsemi.com/nRF5_SDK/doc/

    The following combinations are supported by this command:

    * BL only: Supported.

    * SD only: Supported (SD of same Major Version).

    * APP only: Supported.
   
    * BL + SD: Supported.

    * BL + APP: Not supported (use two packages instead).

    * BL + SD + APP: Supported.

    * SD + APP: Supported (SD of same Major Version).
    """
    zipfile_path = zipfile

    # Check combinations
    if bootloader is not None and application is not None and softdevice is None:
        click.echo(
            "Error: Invalid combination: use two .zip packages instead.")
        return

    if debug_mode is None:
        debug_mode = False

    # The user can specify the application version with two different
    # formats. As an integer, e.g. 102130, or as a string
    # "10.21.30". Internally we convert to integer.
    if application_version_string:
        application_version_internal = convert_version_string_to_int(
            application_version_string)
    else:
        application_version_internal = application_version

    if application_version_internal == 'none':
        application_version_internal = None

    if bootloader_version == 'none':
        bootloader_version = None

    if hw_version == 'none':
        hw_version = None

    # Convert multiple value into a single instance
    if len(sd_req) > 1:
        click.echo(
            "Please specify SoftDevice requirements as a comma-separated list: --sd-req 0xXXXX,0xYYYY,..."
        )
        return
    elif len(sd_req) == 0:
        sd_req = None
    else:
        sd_req = sd_req[0]
        if sd_req == 'none':
            sd_req = None

    # Initial consistency checks
    if application_version_internal is not None and application is None:
        click.echo("Error: Application version with no image.")
        return

    if bootloader_version is not None and bootloader is None:
        click.echo("Error: Bootloader version with no image.")
        return

    if debug_mode:
        display_debug_warning()
        # Default to no version checking
        if application_version_internal is None:
            application_version_internal = Package.DEFAULT_APP_VERSION
        if bootloader_version is None:
            bootloader_version = Package.DEFAULT_BL_VERSION
        if hw_version is None:
            hw_version = Package.DEFAULT_HW_VERSION
        if sd_req is None:
            # Use string as this will be mapped into an int below
            sd_req = str(Package.DEFAULT_SD_REQ[0])

    # Version checks
    if hw_version is None:
        click.echo("Error: --hw-version required.")
        return

    if sd_req is None:
        click.echo("Error: --sd-req required.")
        return

    if application is not None and application_version_internal is None:
        click.echo(
            'Error: --application-version or --application-version-string'
            'required with application image.')
        return

    if bootloader is not None and bootloader_version is None:
        click.echo(
            "Error: --bootloader-version required with bootloader image.")
        return

    sd_req_list = []
    if sd_req is not None:
        try:
            # This will parse any string starting with 0x as base 16.
            sd_req_list = sd_req.split(',')
            sd_req_list = map(int_as_text_to_int, sd_req_list)
        except ValueError:
            raise NordicSemiException("Could not parse value for --sd-req. "
                                      "Hex values should be prefixed with 0x.")

    signer = Signing()
    default_key = signer.load_key(key_file)
    if default_key:
        display_sec_warning()

    package = Package(debug_mode, hw_version, application_version_internal,
                      bootloader_version, sd_req_list, application, bootloader,
                      softdevice, key_file)

    package.generate_package(zipfile_path)

    log_message = "Zip created at {0}".format(zipfile_path)
    click.echo(log_message)
Example #30
0
    def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_addr):
        """
        Populates the settings object based on the given parameters.

        :param arch: Architecture family string, e.g. NRF51
        :param app_file: Path to application file
        :param app_ver: Application version number
        :param bl_ver: Bootloader version number
        :param bl_sett_ver: Bootloader settings version number
        :param custom_bl_sett_addr: Custom start address for the settings page
        :return:
        """

        # Set the architecture
        self.set_arch(arch)

        if custom_bl_sett_addr is not None:
            self.bl_sett_addr = custom_bl_sett_addr

        if bl_sett_ver == 1:
            self.setts = BLDFUSettingsStructV1()
        else:
            raise NordicSemiException("Unknown bootloader settings version")

        self.bl_sett_ver = bl_sett_ver & 0xffffffff
        self.bl_ver = bl_ver & 0xffffffff

        if app_ver is not None:
            self.app_ver = app_ver & 0xffffffff
        else:
            self.app_ver = 0x0 & 0xffffffff

        if app_file is not None:
            # load application to find out size and CRC
            self.temp_dir = tempfile.mkdtemp(prefix="nrf_dfu_bl_sett_")
            self.app_bin = Package.normalize_firmware_to_bin(self.temp_dir, app_file)

            # calculate application size and CRC32
            self.app_sz = int(Package.calculate_file_size(self.app_bin)) & 0xffffffff
            self.app_crc = int(Package.calculate_crc(32, self.app_bin)) & 0xffffffff
            self.bank0_bank_code = 0x1 & 0xffffffff
        else:
            self.app_sz = 0x0 & 0xffffffff
            self.app_crc = 0x0 & 0xffffffff
            self.bank0_bank_code = 0x0 & 0xffffffff

        # build the uint32_t array
        arr = [0x0] * self.setts.uint32_count

        # additional harcoded values
        self.bank_layout = 0x0 & 0xffffffff
        self.bank_current = 0x0 & 0xffffffff

        # fill in the settings
        arr[self.setts.offs_sett_ver] = self.bl_sett_ver
        arr[self.setts.offs_app_ver] = self.app_ver
        arr[self.setts.offs_bl_ver] = self.bl_ver
        arr[self.setts.offs_bank_layout] = self.bank_layout
        arr[self.setts.offs_bank_current] = self.bank_current
        arr[self.setts.offs_bank0_img_sz] = self.app_sz
        arr[self.setts.offs_bank0_img_crc] = self.app_crc
        arr[self.setts.offs_bank0_bank_code] = self.bank0_bank_code

        # calculate the CRC32 from the filled-in settings
        crc_format_str = '<' + ('I' * (self.setts.uint32_count - 1))
        crc_arr = arr[1:]
        crc_data = struct.pack(crc_format_str, *crc_arr)
        self.crc = binascii.crc32(crc_data) & 0xffffffff

        # fill in the calculated CRC32
        arr[self.setts.offs_crc] = self.crc

        format_str = '<' + ('I' * self.setts.uint32_count)

        # Get the packed data to insert into the hex instance
        data = struct.pack(format_str, *arr)

        # insert the data at the correct address
        self.ihex.puts(self.bl_sett_addr, data)