From 008b5df43267848449e44d22fe8fdf392c835114 Mon Sep 17 00:00:00 2001 From: matejcik Date: Mon, 4 Nov 2024 12:16:07 +0100 Subject: [PATCH] feat(python): add support for unrecognized Trezor models (fixes #3993) --- python/.changelog.d/3993.added | 1 + python/.changelog.d/3993.deprecated | 1 + python/src/trezorlib/client.py | 16 +++------ python/src/trezorlib/models.py | 51 +++++++++++++++++++++-------- tests/conftest.py | 4 ++- 5 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 python/.changelog.d/3993.added create mode 100644 python/.changelog.d/3993.deprecated diff --git a/python/.changelog.d/3993.added b/python/.changelog.d/3993.added new file mode 100644 index 0000000000..db05c85e57 --- /dev/null +++ b/python/.changelog.d/3993.added @@ -0,0 +1 @@ +Added support for Trezor models not known by the current version of the library. diff --git a/python/.changelog.d/3993.deprecated b/python/.changelog.d/3993.deprecated new file mode 100644 index 0000000000..a232797df5 --- /dev/null +++ b/python/.changelog.d/3993.deprecated @@ -0,0 +1 @@ +Calling `models.by_internal_name(None)` is deprecated -- check the presence of `internal_model` explicitly before passing it in. diff --git a/python/src/trezorlib/client.py b/python/src/trezorlib/client.py index 7fd6dae32b..e99e115b85 100644 --- a/python/src/trezorlib/client.py +++ b/python/src/trezorlib/client.py @@ -273,19 +273,13 @@ class TrezorClient(Generic[UI]): """Update internal fields based on passed-in Features message.""" if not self.model: - # Trezor Model One bootloader 1.8.0 or older does not send model name - model = models.by_internal_name(features.internal_model) - if model is None: - model = models.by_name(features.model or "1") - if model is None: - raise RuntimeError( - "Unsupported Trezor model" - f" (internal_model: {features.internal_model}, model: {features.model})" - ) - self.model = model + if features.internal_model is not None: + self.model = models.by_internal_name(features.internal_model) + else: + self.model = models.by_name(features.model) if features.vendor not in self.model.vendors: - raise RuntimeError("Unsupported device") + raise exceptions.TrezorException(f"Unrecognized vendor: {features.vendor}") self.features = features self.version = ( diff --git a/python/src/trezorlib/models.py b/python/src/trezorlib/models.py index 4c430980da..998508bcce 100644 --- a/python/src/trezorlib/models.py +++ b/python/src/trezorlib/models.py @@ -14,8 +14,11 @@ # You should have received a copy of the License along with this library. # If not, see . +from __future__ import annotations + +import warnings from dataclasses import dataclass -from typing import Collection, Optional, Tuple +from typing import Collection, Tuple from . import mapping @@ -36,12 +39,17 @@ class TrezorModel: # ==== internal names ==== +USBID_TREZOR_ONE = (0x534C, 0x0001) +USBID_TREZOR_CORE = (0x1209, 0x53C1) +USBID_TREZOR_CORE_BOOTLOADER = (0x1209, 0x53C0) + + T1B1 = TrezorModel( name="1", internal_name="T1B1", minimum_version=(1, 8, 0), vendors=VENDORS, - usb_ids=((0x534C, 0x0001),), + usb_ids=(USBID_TREZOR_ONE,), default_mapping=mapping.DEFAULT_MAPPING, ) @@ -50,7 +58,7 @@ T2T1 = TrezorModel( internal_name="T2T1", minimum_version=(2, 1, 0), vendors=VENDORS, - usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), + usb_ids=(USBID_TREZOR_CORE, USBID_TREZOR_CORE_BOOTLOADER), default_mapping=mapping.DEFAULT_MAPPING, ) @@ -59,7 +67,7 @@ T2B1 = TrezorModel( internal_name="T2B1", minimum_version=(2, 1, 0), vendors=VENDORS, - usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), + usb_ids=(USBID_TREZOR_CORE, USBID_TREZOR_CORE_BOOTLOADER), default_mapping=mapping.DEFAULT_MAPPING, ) @@ -68,7 +76,7 @@ T3T1 = TrezorModel( internal_name="T3T1", minimum_version=(2, 1, 0), vendors=VENDORS, - usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), + usb_ids=(USBID_TREZOR_CORE, USBID_TREZOR_CORE_BOOTLOADER), default_mapping=mapping.DEFAULT_MAPPING, ) @@ -77,7 +85,7 @@ T3B1 = TrezorModel( internal_name="T3B1", minimum_version=(2, 1, 0), vendors=VENDORS, - usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), + usb_ids=(USBID_TREZOR_CORE, USBID_TREZOR_CORE_BOOTLOADER), default_mapping=mapping.DEFAULT_MAPPING, ) @@ -86,7 +94,7 @@ T3W1 = TrezorModel( internal_name="T3W1", minimum_version=(2, 1, 0), vendors=VENDORS, - usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), + usb_ids=(USBID_TREZOR_CORE, USBID_TREZOR_CORE_BOOTLOADER), default_mapping=mapping.DEFAULT_MAPPING, ) @@ -95,7 +103,7 @@ DISC1 = TrezorModel( internal_name="D001", minimum_version=(2, 1, 0), vendors=VENDORS, - usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), + usb_ids=(USBID_TREZOR_CORE, USBID_TREZOR_CORE_BOOTLOADER), default_mapping=mapping.DEFAULT_MAPPING, ) @@ -104,10 +112,24 @@ DISC2 = TrezorModel( internal_name="D002", minimum_version=(2, 1, 0), vendors=VENDORS, - usb_ids=((0x1209, 0x53C1), (0x1209, 0x53C0)), + usb_ids=(USBID_TREZOR_CORE, USBID_TREZOR_CORE_BOOTLOADER), default_mapping=mapping.DEFAULT_MAPPING, ) +# ==== unknown model ==== + +UNKNOWN_MODEL = TrezorModel( + name="Unknown Trezor model", + internal_name="????", + minimum_version=(0, 0, 0), + vendors=VENDORS, + usb_ids=(), + default_mapping=mapping.DEFAULT_MAPPING, +) +"""Unknown model is a placeholder for detected devices that respond to the Trezor wire +protocol, but are not in the list of known models -- presumably models newer than the +current library version.""" + # ==== model based names ==== TREZOR_ONE = T1B1 @@ -121,19 +143,20 @@ TREZOR_DISC2 = DISC2 TREZORS = frozenset({T1B1, T2T1, T2B1, T3T1, T3B1, T3W1, DISC1, DISC2}) -def by_name(name: Optional[str]) -> Optional[TrezorModel]: +def by_name(name: str | None) -> TrezorModel: if name is None: return T1B1 for model in TREZORS: if model.name == name: return model - return None + return UNKNOWN_MODEL -def by_internal_name(name: Optional[str]) -> Optional[TrezorModel]: +def by_internal_name(name: str) -> TrezorModel: if name is None: - return None + warnings.warn("by_internal_name will no longer accept None", stacklevel=2) + return None # type: ignore [incompatible with "TrezorModel"] for model in TREZORS: if model.internal_name == name: return model - return None + return UNKNOWN_MODEL diff --git a/tests/conftest.py b/tests/conftest.py index 499fdeb47c..596b6716d0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -229,7 +229,9 @@ class ModelsFilter: assert isinstance(marker, str) if marker in cls.MODEL_SHORTCUTS: selected_models |= cls.MODEL_SHORTCUTS[marker] - elif (model := models.by_internal_name(marker.upper())) is not None: + elif ( + model := models.by_internal_name(marker.upper()) + ) is not models.UNKNOWN_MODEL: selected_models.add(model) else: raise ValueError(f"Unknown model: {marker}")