diff --git a/mypy.ini b/mypy.ini index 52324e3c..4e741870 100644 --- a/mypy.ini +++ b/mypy.ini @@ -9,5 +9,5 @@ disallow_untyped_defs = True ignore_errors = True # libraries without annotations -[mypy-cbor.*,cffi.*,click.*,cryptography.*,ecdsa.*,intelhex.*,nacl.*,nkdfu.*,serial.*,urllib3.*,usb.*,usb1.*] +[mypy-cbor.*,cffi.*,click.*,ecdsa.*,intelhex.*,nacl.*,nkdfu.*,ruamel.*,serial.*,urllib3.*,usb.*,usb1.*] ignore_missing_imports = True diff --git a/pynitrokey/cli/nk3/__init__.py b/pynitrokey/cli/nk3/__init__.py index 17d235ae..8c7eb404 100644 --- a/pynitrokey/cli/nk3/__init__.py +++ b/pynitrokey/cli/nk3/__init__.py @@ -7,12 +7,10 @@ # http://opensource.org/licenses/MIT>, at your option. This file may not be # copied, modified, or distributed except according to those terms. -import itertools import logging import os.path import platform import time -from concurrent.futures import ThreadPoolExecutor from typing import List, Optional, TypeVar import click @@ -512,25 +510,21 @@ def _print_update_warning() -> None: def _perform_update(device: Nitrokey3Bootloader, image: bytes) -> None: logger.debug("Starting firmware update") - - with ThreadPoolExecutor() as executor: - indicators = itertools.cycle(["/", "-", "\\", "|"]) - future = executor.submit(device.update, image) - while not future.done(): - print( - f"\r[{next(indicators)}] Performing firmware update " - "(may take several minutes) ... ", - end="", - ) - time.sleep(0.1) - print("done") - - if future.result(): - logger.debug("Firmware update finished successfully") - device.reboot() - else: - (code, message) = device.status - local_critical(f"Firmware update failed with status code {code}: {message}") + with ProgressBar( + desc="Performing firmware update", unit="B", unit_scale=True + ) as bar: + result = device.update(image, callback=bar.update_sum) + logger.debug(f"Firmware update finished with status {device.status}") + + # TODO: consider repeating firmware update for better diagnostics on failure, see: + # https://github.com/NXPmicro/spsdk/issues/29#issuecomment-1030130023 + + if result: + logger.debug("Firmware update finished successfully") + device.reboot() + else: + (code, message) = device.status + local_critical(f"Firmware update failed with status code {code}: {message}") @nk3.command() diff --git a/pynitrokey/helpers.py b/pynitrokey/helpers.py index 2f4f184a..453638d0 100644 --- a/pynitrokey/helpers.py +++ b/pynitrokey/helpers.py @@ -49,11 +49,26 @@ class ProgressBar: def __init__(self, **kwargs) -> None: self.bar: Optional[tqdm] = None self.kwargs = kwargs + self.sum = 0 + + def __enter__(self) -> "ProgressBar": + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.close() def update(self, n: int, total: int) -> None: if not self.bar: self.bar = tqdm(total=total, **self.kwargs) self.bar.update(n) + self.sum += n + + def update_sum(self, n: int, total: int) -> None: + if not self.bar: + self.bar = tqdm(total=total, **self.kwargs) + if n > self.sum: + self.bar.update(n - self.sum) + self.sum = n def close(self) -> None: if self.bar: diff --git a/pynitrokey/nk3/bootloader.py b/pynitrokey/nk3/bootloader.py index eeb41637..7c3fb4ee 100644 --- a/pynitrokey/nk3/bootloader.py +++ b/pynitrokey/nk3/bootloader.py @@ -10,12 +10,12 @@ import logging import platform import sys -from typing import List, Optional, Tuple +from typing import Callable, List, Optional, Tuple from spsdk.mboot import McuBoot, StatusCode from spsdk.mboot.interfaces import RawHid from spsdk.mboot.properties import PropertyTag -from spsdk.sbfile.images import BootImageV21 +from spsdk.sbfile.sb2.images import BootImageV21 from spsdk.utils.usbfilter import USBDeviceFilter from .base import Nitrokey3Base @@ -75,7 +75,7 @@ def reboot(self) -> None: raise Exception("Failed to reboot Nitrokey 3 bootloader") def uuid(self) -> Optional[int]: - uuid = self.device.get_property(PropertyTag.UNIQUE_DEVICE_IDENT) + uuid = self.device.get_property(PropertyTag.UNIQUE_DEVICE_IDENT) # type: ignore[arg-type] if not uuid: raise ValueError("Missing response for UUID property query") if len(uuid) != UUID_LEN: @@ -87,8 +87,17 @@ def uuid(self) -> Optional[int]: right_endian = wrong_endian.to_bytes(16, byteorder="little") return int.from_bytes(right_endian, byteorder="big") - def update(self, image: bytes) -> bool: - return self.device.receive_sb_file(image) + def update( + self, + image: bytes, + callback: Optional[Callable[[int, int], None]] = None, + check_errors: bool = False, + ) -> bool: + return self.device.receive_sb_file( + image, + progress_callback=callback, + check_errors=check_errors, + ) @staticmethod def list() -> List["Nitrokey3Bootloader"]: @@ -99,6 +108,8 @@ def list() -> List["Nitrokey3Bootloader"]: ) devices = [] for device in RawHid.enumerate(device_filter): + # TODO: remove assert if https://github.com/NXPmicro/spsdk/issues/32 is fixed + assert isinstance(device, RawHid) try: devices.append(Nitrokey3Bootloader(device)) except ValueError: @@ -119,6 +130,8 @@ def open(path: str) -> Optional["Nitrokey3Bootloader"]: return None try: + # TODO: remove assert if https://github.com/NXPmicro/spsdk/issues/32 is fixed + assert isinstance(devices[0], RawHid) return Nitrokey3Bootloader(devices[0]) except ValueError: logger.warn( diff --git a/pynitrokey/stubs/spsdk/__init__.py b/pynitrokey/stubs/spsdk/__init__.py deleted file mode 100644 index 1d3a18bf..00000000 --- a/pynitrokey/stubs/spsdk/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - - -class SPSDKError(Exception): - ... diff --git a/pynitrokey/stubs/spsdk/mboot/__init__.pyi b/pynitrokey/stubs/spsdk/mboot/__init__.pyi deleted file mode 100644 index 675519dc..00000000 --- a/pynitrokey/stubs/spsdk/mboot/__init__.pyi +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -from enum import Enum -from typing import List, Optional - -from .interfaces import Interface -from .properties import PropertyTag - -class StatusCode(int): - @classmethod - def desc(cls, key: int) -> str: ... - -class McuBoot: - def __init__(self, device: Interface) -> None: ... - @property - def status_code(self) -> StatusCode: ... - def open(self) -> None: ... - def close(self) -> None: ... - def receive_sb_file(self, data: bytes) -> bool: ... - def reset(self, reopen: bool) -> bool: ... - def get_property(self, prop_tag: PropertyTag) -> Optional[List[int]]: ... diff --git a/pynitrokey/stubs/spsdk/mboot/exceptions.pyi b/pynitrokey/stubs/spsdk/mboot/exceptions.pyi deleted file mode 100644 index 7c75c20e..00000000 --- a/pynitrokey/stubs/spsdk/mboot/exceptions.pyi +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2022 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -from spsdk import SPSDKError - -class McuBootError(SPSDKError): ... -class McuBootConnectionError(McuBootError): ... diff --git a/pynitrokey/stubs/spsdk/mboot/interfaces.pyi b/pynitrokey/stubs/spsdk/mboot/interfaces.pyi deleted file mode 100644 index 7bc932d3..00000000 --- a/pynitrokey/stubs/spsdk/mboot/interfaces.pyi +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -from typing import Union - -from ..utils.usbfilter import USBDeviceFilter - -class Interface: ... - -class RawHid(Interface): - vid: int - pid: int - path: Union[bytes, str] - @staticmethod - def enumerate(usb_device_filter: USBDeviceFilter): ... diff --git a/pynitrokey/stubs/spsdk/mboot/properties.pyi b/pynitrokey/stubs/spsdk/mboot/properties.pyi deleted file mode 100644 index 9cc2275a..00000000 --- a/pynitrokey/stubs/spsdk/mboot/properties.pyi +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -from enum import Enum -from typing import Tuple - -class PropertyTag(Enum): - UNIQUE_DEVICE_IDENT: Tuple[int, str, str] diff --git a/pynitrokey/stubs/spsdk/sbfile/__init__.pyi b/pynitrokey/stubs/spsdk/sbfile/__init__.pyi deleted file mode 100644 index 92a8ebd6..00000000 --- a/pynitrokey/stubs/spsdk/sbfile/__init__.pyi +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. diff --git a/pynitrokey/stubs/spsdk/sbfile/headers.pyi b/pynitrokey/stubs/spsdk/sbfile/headers.pyi deleted file mode 100644 index f7ba4de7..00000000 --- a/pynitrokey/stubs/spsdk/sbfile/headers.pyi +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -from .misc import BcdVersion3 - -class ImageHeaderV2: - product_version: BcdVersion3 diff --git a/pynitrokey/stubs/spsdk/sbfile/images.pyi b/pynitrokey/stubs/spsdk/sbfile/images.pyi deleted file mode 100644 index 10994b32..00000000 --- a/pynitrokey/stubs/spsdk/sbfile/images.pyi +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -from typing import Optional - -from ..utils.crypto import CertBlockV2 -from .headers import ImageHeaderV2 - -class BootImageV21: - cert_block: Optional[CertBlockV2] - @property - def header(self) -> ImageHeaderV2: ... - @classmethod - def parse(cls, data: bytes, kek: bytes = bytes()) -> "BootImageV21": ... diff --git a/pynitrokey/stubs/spsdk/sbfile/misc.pyi b/pynitrokey/stubs/spsdk/sbfile/misc.pyi deleted file mode 100644 index 6b6d3283..00000000 --- a/pynitrokey/stubs/spsdk/sbfile/misc.pyi +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -class BcdVersion3: - major: int - minor: int - service: int diff --git a/pynitrokey/stubs/spsdk/utils/__init__.pyi b/pynitrokey/stubs/spsdk/utils/__init__.pyi deleted file mode 100644 index 92a8ebd6..00000000 --- a/pynitrokey/stubs/spsdk/utils/__init__.pyi +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. diff --git a/pynitrokey/stubs/spsdk/utils/crypto.pyi b/pynitrokey/stubs/spsdk/utils/crypto.pyi deleted file mode 100644 index 803dc54c..00000000 --- a/pynitrokey/stubs/spsdk/utils/crypto.pyi +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -class CertBlockV2: - @property - def rkht(self) -> bytes: ... diff --git a/pynitrokey/stubs/spsdk/utils/usbfilter.pyi b/pynitrokey/stubs/spsdk/utils/usbfilter.pyi deleted file mode 100644 index 854abac8..00000000 --- a/pynitrokey/stubs/spsdk/utils/usbfilter.pyi +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2021 Nitrokey Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. - -class USBDeviceFilter: - def __init__(self, usb_id: str) -> None: ... diff --git a/pyproject.toml b/pyproject.toml index 93713f57..36d76e45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "cbor", "cffi", "click >=7.0", - "cryptography >=3.4", + "cryptography >=3.4.4", "ecdsa", "fido2 >=0.9.3", "intelhex", @@ -32,7 +32,7 @@ dependencies = [ "python-dateutil", "pyusb", "requests", - "spsdk >=1.5.0", + "spsdk >=1.6.0", "tqdm", "urllib3", ]