diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4465f..97cc7c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Update `protobuf` dependency to 5.26 - Vendor `spsdk` dependency to reduce the total number of dependencies +- Replace `libusbsio` dependency with `hidapi` ## [v0.1.0](https://github.com/Nitrokey/nitrokey-sdk-py/releases/tag/v0.1.0) (2024-07-29) diff --git a/poetry.lock b/poetry.lock index 25b2f80..e550918 100644 --- a/poetry.lock +++ b/poetry.lock @@ -398,6 +398,87 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" +[[package]] +name = "hidapi" +version = "0.14.0.post2" +description = "A Cython interface to the hidapi from https://github.com/libusb/hidapi" +optional = false +python-versions = "*" +files = [ + {file = "hidapi-0.14.0.post2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:023bca2a95856c185b01978b3794a8302e5a38cf1a8fa7b9c14c537b928ec762"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f6c27a5efd629dde57c97bb621bfe87fd40ece01c3c80ca8f937cfd8023adbe"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fe7645dfb3d577428023ea855664602d55dacdcf474c059217c6f5cc175bcd6"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b606a4123a296f63a0e8d9904ddbed6b90957f454cb3df315684eac43bedc1e6"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b981c6828b92ac8c5e2fb3182fe95c528dc8355d298ec457ae8773dc1ffb2a"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8b000a44176e09ab2921aa1402efbaa84c708786c3e825b64fbac294353b373e"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd985ca84d7abd0d8fa57a5f1639d14292429028588b19f58b729b02c0ea9928"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad21f0da766df0177d2d8bc06c8fd65303a37f79d2890e8133980080e1159eb0"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-win32.whl", hash = "sha256:4379f218712f9800c6d89c17902da5ebb06c1e6afde66aaf2d8bd0f5f6fb9232"}, + {file = "hidapi-0.14.0.post2-cp310-cp310-win_amd64.whl", hash = "sha256:4635c294a2ccfe1e862ba324dc4a5c068fd989c138b09880052a5903b37e0d54"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d3ca0ff9179bfa2337b36eaa6a4f1a3e4c8e643cf698adfef65700bffb6fe5c"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b1bca2741492c67a6cb4d7b57c0db734543556b369d3604505c62a47607767f"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef474da943187befd9a55270ba521f42930565b84bd59caa276adeda29669853"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a6991f55aebd3f84cc924fd838e0052422a5a3921f5d7df0ce6c9600f09db9a"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f95340c0d69245820a2075d516df9a99c466d92db29fa78df5f13d9cfee4716d"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5ebdc20915c6256c738116a3491bc5194d74233bb6a4ad7d05983dfec54a22f2"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:04fd0c791af679dc6de02205f5fe321e46b7cae0e8d10d7f579abb8bcca0c595"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70038c3bd26c2ec7520964d9cfe5df81928ff34a2821e83914c28a9d4d8550b7"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-win32.whl", hash = "sha256:bfe65ee33f0ecafde4e742fd7c8482a914bcd0bc69c04edf40788c714eec865d"}, + {file = "hidapi-0.14.0.post2-cp311-cp311-win_amd64.whl", hash = "sha256:c79d60d42b3437e0553052de108253db0e9e4a5c432996d95028d219afd4a3f3"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eaa04f8fb7b5d9d7e3eeb502317941559ecfc4a76d2527ee0d1d835794ac5aa"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6e81890f51bae29ae81c3ff02fed754d05f69a408fa038ff1056aec90c56333"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:511ccf1fa7f159b95d31d9fb718ebfc35322252d7355a3afaa724b65cdbbeff6"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eecc24722c561348c04f3f0819534028a7dd2e35a16f195625e2465f95fb8c6f"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b979408ba1c65ea7e39e35e6cb9d29705265ebfca568627333876f7a030818"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:556f99a3887065f0e86fb4b3968800c34fa25e6ea54047ef850ee639c69032da"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:59465c87f028e70162f04d3e69014cd03bb2a4ceed7db6d0a160ec9627fb9324"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:de561a9336aa92263a4a5757deffdacb0e04de84590e15a7d5413f574790a210"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-win32.whl", hash = "sha256:6f35ad2007e04d6dd80807d72a66781915da538818587f950cea3c3c36512aa7"}, + {file = "hidapi-0.14.0.post2-cp312-cp312-win_amd64.whl", hash = "sha256:493aad5fe1630f547857ef93a150eb99c07e836b82a340bd0cfdf87f495c983d"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6dba51ebd2c31c06f33fefc3cff0f48cb6a0e94015392f09e91a1504cb2b3a32"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:127e4f0b048ec9fc08e29596de411bbb856b6f224ecca26b32ded398340f9d4f"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f30df6f41d504a7bceeece097e2078ffccb91acdcdc0a553b27377c48f9cffbc"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:826dc8b82a094e80538c68a3e0d6b252b765ea49daa9bfbde0221fdb1eead058"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:ad280e0db1496d14c471d83c0157f1ac440c06cb143e05096940d397b3ab04ba"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:84e921d9de9dfb39a0cbce4db94d9ea00365f1137f12f24b22fa14170d0efb4b"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:b1c2eaa44f86700dba943bf6217157631e3ea836569b6494abfa3fff49f50b0e"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-win32.whl", hash = "sha256:a1cf34598f7642dc602de88ae1ed80ef5f4efb7e5b054528e33745bdfd93379e"}, + {file = "hidapi-0.14.0.post2-cp36-cp36m-win_amd64.whl", hash = "sha256:5bd9b996882d9542cd1a927fbfc6c5ac34fe3290362050ad68d379b6a100c470"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:251f93ae3e919bfc06217a2362239982b8d0913b38071b0a9253934275158e10"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39104841a2dce697cb4deeefe0ec08710dfb4258c6b078656989e11d1c62e71a"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a6e7570cee92762a9d6bc785d1ffb221081fc28ba2bcbbd89030cd2757b5773"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92ac5a88cc56f2b30d13cfb4aa208b6c4874a1d3fe80855d092e1c33207eaf27"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:fafef403f32514c069cefe9484f255ac6b2bfa11f9160f22da3ffae8c4b01dea"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:07841b71d78e21f15fd2ea9d7d313c7aa088c3415d6cefa1e932353fac6eb0b3"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:44438fff75b4ccd158a0ed692dba828a3735ce4963d0ae3a03c4b6dc2a020f18"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-win32.whl", hash = "sha256:209208db37a061ca08786184bc7060aace9e38bc45de268e60ac808e0f42952c"}, + {file = "hidapi-0.14.0.post2-cp37-cp37m-win_amd64.whl", hash = "sha256:466b4c2222ba58dd6603ca31777b8210d414751ae3a6e16d27d830e6d402a691"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6110c47d531f9d5e2947619f11623371d3efaade5b1ad81501ed0b6767296bbb"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:03926c1e788daff7e038f2d39460348ce5d624a98a72d9d291f619c9fbbb754e"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5526d812a53e3050317003bfa6a923ab1e8d4bf10a75a3ec4e2a6361b9967d2"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4b7c2e84ba004140d8555db302daf5d3f9d1d073cbc680b4cd384ee9a943395"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f87614ebc0cbc399eb7682a6f1f18f4afd522e2b76df888ddc4305f6ec3af186"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:857b81b124e6602f77f711b11ce08cb809a7e7e698751b0e09fff26f40571ffe"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:0eab40fb174db083233d105fdae17e5efaeb41d887b48fe4cfe98bfe48c03617"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9494327db1e6e5fd6630701cf4cc28da9dc2465d7903e9f2c1e9cf2228f3c50"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-win32.whl", hash = "sha256:10f015eb35e28f5101af5ac16df0f08e0a31912dc8a5704c828d8ff0c3776b0d"}, + {file = "hidapi-0.14.0.post2-cp38-cp38-win_amd64.whl", hash = "sha256:2e604d59634a6162fcfbf16b07d2c6cc6dc278ba260246aa22f0b4ae6dc0e586"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc991af741ca3caf582ad1c717ce81c6c4021cbcb237eae534701beb819f867c"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8727975928662fa3d43abcc97acd581a3d4a62d6e701881e3296f40335f1778e"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e68dc10e6a06ba818d5f0cdc90045332784cf485279c18d7a054b22344814f"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eccc63cd08142791ffa2c213c6f47f3094a267a3ab2c10261704bdad08ecd806"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:386a9df574ae5715f811eafe5d8207c2eec986543146c27b4beea9957ec95567"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:91695c2f5ab3f91e27b4e1782cea57a517472b2d57e49a3eec5c86b5fbecd13f"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9de52268c4f29f23061d97020b46d56b03047834897be3bfcb95ebd89d32d636"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f1d6f11234b9a3fcfd1d81e328cd4af09ab4e7e9b251f9eb772542f1ae1fada"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-win32.whl", hash = "sha256:d678979fcffd93d1d2f69ca080f3af994ce8be032bff3d929f45f1cbf2ce74a3"}, + {file = "hidapi-0.14.0.post2-cp39-cp39-win_amd64.whl", hash = "sha256:8e1d0396f415cb5d1cd3a1dfa8c0aa15d14f8849e7e0f9b4782e614128ec15ad"}, + {file = "hidapi-0.14.0.post2.tar.gz", hash = "sha256:6c0e97ba6b059a309d51b495a8f0d5efbcea8756b640d98b6f6bb9fdef2458ac"}, +] + +[package.dependencies] +setuptools = ">=19.0" + [[package]] name = "idna" version = "3.7" @@ -437,17 +518,6 @@ files = [ [package.extras] test = ["black", "codecov", "coloredlogs", "coverage", "flake8", "mypy", "pytest", "pytest-cov", "pytest-runner", "readme-renderer"] -[[package]] -name = "libusbsio" -version = "2.1.12" -description = "Python wrapper around NXP LIBUSBSIO library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "libusbsio-2.1.12-py3-none-any.whl", hash = "sha256:284637214c7804748fc7d26757134a3e6591c2b69b5b8a45d5d2eb4495b67e1b"}, - {file = "libusbsio-2.1.12.tar.gz", hash = "sha256:45d521c229413b0835f5acb73e8df3b31aa5055f454f2ddbb490db3b8604292f"}, -] - [[package]] name = "mccabe" version = "0.7.0" @@ -654,6 +724,22 @@ files = [ {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, ] +[[package]] +name = "setuptools" +version = "72.1.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, +] + +[package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -821,4 +907,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "cfe3c03b76ea221cb289b74b77847ef36129d3b1a4d71266f8b74480da5b0145" +content-hash = "b0d861931d98ed257cb871383ebd9d85676d4093a21b73c2af07e470f16d8e5d" diff --git a/pyproject.toml b/pyproject.toml index f1541a1..2628b8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ tlv8 = "^0.10" # lpc55 crcmod = "^1.7" cryptography = ">=42" -libusbsio = "^2.1" +hidapi = "^0.14" # nrf52 ecdsa = "^0.19" @@ -59,8 +59,3 @@ mypy_path = "stubs" show_error_codes = true python_version = "3.9" strict = true - -# libusbsio is used by lpc55_upload, will be replaced eventually -[[tool.mypy.overrides]] -module = ["libusbsio.*"] -ignore_missing_imports = true diff --git a/src/nitrokey/trussed/_bootloader/lpc55.py b/src/nitrokey/trussed/_bootloader/lpc55.py index 39e7134..ddde9c2 100644 --- a/src/nitrokey/trussed/_bootloader/lpc55.py +++ b/src/nitrokey/trussed/_bootloader/lpc55.py @@ -19,7 +19,6 @@ from .lpc55_upload.mboot.properties import PropertyTag from .lpc55_upload.sbfile.sb2.images import BootImageV21 from .lpc55_upload.utils.interfaces.device.usb_device import UsbDevice -from .lpc55_upload.utils.usbfilter import USBDeviceFilter RKTH = bytes.fromhex("050aad3e77791a81e59c5b2ba5a158937e9460ee325d8ccba09734b8fdebb171") KEK = bytes([0xAA] * 32) @@ -101,9 +100,8 @@ def update( @classmethod def _list_vid_pid(cls: type[T], vid: int, pid: int) -> list[T]: - device_filter = USBDeviceFilter(f"0x{vid:x}:0x{pid:x}") devices = [] - for device in UsbDevice.enumerate(device_filter): + for device in UsbDevice.enumerate(vid=vid, pid=pid): try: devices.append(cls(device)) except ValueError: @@ -114,8 +112,7 @@ def _list_vid_pid(cls: type[T], vid: int, pid: int) -> list[T]: @classmethod def _open(cls: type[T], path: str) -> Optional[T]: - device_filter = USBDeviceFilter(path) - devices = UsbDevice.enumerate(device_filter) + devices = UsbDevice.enumerate(path=path) if len(devices) == 0: logger.warn(f"No HID device at {path}") return None diff --git a/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/interfaces/device/usb_device.py b/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/interfaces/device/usb_device.py index 5a9b995..27d901c 100644 --- a/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/interfaces/device/usb_device.py +++ b/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/interfaces/device/usb_device.py @@ -7,14 +7,15 @@ """Low level Hid device.""" import logging -from typing import List, Optional - -import libusbsio +import warnings +from typing import TYPE_CHECKING, List, Optional from ....exceptions import SPSDKConnectionError, SPSDKError from ....utils.exceptions import SPSDKTimeoutError from ....utils.interfaces.device.base import DeviceBase -from ....utils.usbfilter import USBDeviceFilter + +if TYPE_CHECKING: + import hid logger = logging.getLogger(__name__) @@ -24,29 +25,23 @@ class UsbDevice(DeviceBase): def __init__( self, - vid: Optional[int] = None, - pid: Optional[int] = None, - path: Optional[bytes] = None, - serial_number: Optional[str] = None, - vendor_name: Optional[str] = None, - product_name: Optional[str] = None, - interface_number: Optional[int] = None, - timeout: Optional[int] = None, + vid: int, + pid: int, + path: bytes, + vendor_name: str, + product_name: str, + interface_number: int, ) -> None: """Initialize the USB interface object.""" self._opened = False - self.vid = vid or 0 - self.pid = pid or 0 - self.path = path or b"" - self.serial_number = serial_number or "" - self.vendor_name = vendor_name or "" - self.product_name = product_name or "" - self.interface_number = interface_number or 0 - self._timeout = timeout or 2000 - libusbsio_logger = logging.getLogger("libusbsio") - self._device: libusbsio.LIBUSBSIO.HID_DEVICE = libusbsio.usbsio( - loglevel=libusbsio_logger.getEffectiveLevel() - ).HIDAPI_DeviceCreate() + self.vid = vid + self.pid = pid + self.path = path + self.vendor_name = vendor_name + self.product_name = product_name + self.interface_number = interface_number + self._timeout = 2000 + self._device: Optional["hid.device"] = None @property def timeout(self) -> int: @@ -64,7 +59,7 @@ def is_opened(self) -> bool: :return: True if device is open, False othervise. """ - return self._opened + return self._device is not None def open(self) -> None: """Open the interface. @@ -72,14 +67,17 @@ def open(self) -> None: :raises SPSDKError: if device is already opened :raises SPSDKConnectionError: if the device can not be opened """ + import hid + logger.debug(f"Opening the Interface: {str(self)}") if self.is_opened: # This would get HID_DEVICE into broken state raise SPSDKError("Can't open already opened device") try: - self._device.Open(self.path) - self._opened = True + self._device = hid.device() + self._device.open_path(self.path) except Exception as error: + self._device = None raise SPSDKConnectionError( f"Unable to open device '{str(self)}'" ) from error @@ -91,10 +89,10 @@ def close(self) -> None: :raises SPSDKConnectionError: if the device can not be opened """ logger.debug(f"Closing the Interface: {str(self)}") - if self.is_opened: + if self._device is not None: try: - self._device.Close() - self._opened = False + self._device.close() + self._device = None except Exception as error: raise SPSDKConnectionError( f"Unable to close device '{str(self)}'" @@ -110,17 +108,16 @@ def read(self, length: int, timeout: Optional[int] = None) -> bytes: :raises SPSDKTimeoutError: Time-out """ timeout = timeout or self.timeout - if not self.is_opened: + if self._device is None: raise SPSDKConnectionError("Device is not opened for reading") try: - (data, result) = self._device.Read(length, timeout_ms=timeout) + data = self._device.read(length, timeout_ms=timeout) except Exception as e: raise SPSDKConnectionError(str(e)) from e if not data: - logger.error(f"Cannot read from HID device, error={result}") + logger.error("Cannot read from HID device") raise SPSDKTimeoutError() - assert isinstance(data, bytes) - return data + return bytes(data) def write(self, data: bytes, timeout: Optional[int] = None) -> None: """Send data to device. @@ -130,10 +127,10 @@ def write(self, data: bytes, timeout: Optional[int] = None) -> None: :raises SPSDKConnectionError: Sending data to device failure """ timeout = timeout or self.timeout - if not self.is_opened: + if self._device is None: raise SPSDKConnectionError("Device is not opened for writing") try: - bytes_written = self._device.Write(data, timeout_ms=timeout) + bytes_written = self._device.write(data) except Exception as e: raise SPSDKConnectionError(str(e)) from e if bytes_written < 0 or bytes_written < len(data): @@ -145,7 +142,7 @@ def __str__(self) -> str: """Return information about the USB interface.""" return ( f"{self.product_name:s} (0x{self.vid:04X}, 0x{self.pid:04X})" - f"path={self.path!r} sn='{self.serial_number}'" + f"path={self.path!r}" ) def __hash__(self) -> int: @@ -153,22 +150,27 @@ def __hash__(self) -> int: @classmethod def enumerate( - cls, usb_device_filter: USBDeviceFilter, timeout: Optional[int] = None + cls, + vid: Optional[int] = None, + pid: Optional[int] = None, + path: Optional[str] = None, ) -> List["UsbDevice"]: - """Get list of all connected devices which matches device_id. + """Get list of all connected devices which matches device_id.""" + try: + import hid + except ImportError as err: + logger.warning("Failed to import hid module", exc_info=True) + warnings.warn( + f"Failed to list LPC55 bootloaders due to a missing library: {err}", + category=RuntimeWarning, + ) + return [] - :param usb_device_filter: USBDeviceFilter object - :param timeout: Default timeout to be set - :return: List of interfaces found - """ devices = [] - libusbsio_logger = logging.getLogger("libusbsio") - sio = libusbsio.usbsio(loglevel=libusbsio_logger.getEffectiveLevel()) - all_hid_devices = sio.HIDAPI_Enumerate() # iterate on all devices found - for dev in all_hid_devices: - if usb_device_filter.compare(vars(dev)) is True: + for dev in hid.enumerate(vendor_id=vid or 0, product_id=pid or 0): + if path is None or dev["path"] == path.encode(): new_device = cls( vid=dev["vendor_id"], pid=dev["product_id"], @@ -176,7 +178,6 @@ def enumerate( vendor_name=dev["manufacturer_string"], product_name=dev["product_string"], interface_number=dev["interface_number"], - timeout=timeout, ) devices.append(new_device) return devices diff --git a/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/misc.py b/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/misc.py index fcd3414..f7f8121 100644 --- a/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/misc.py +++ b/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/misc.py @@ -5,7 +5,6 @@ # SPDX-License-Identifier: BSD-3-Clause """Miscellaneous functions used throughout the SPSDK.""" -import hashlib import re from enum import Enum from math import ceil @@ -259,10 +258,3 @@ def swap16(x: int) -> int: if x < 0 or x > 0xFFFF: raise SPSDKError("Incorrect number to be swapped") return ((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF) - - -def get_hash(text: Union[str, bytes]) -> str: - """Returns hash of given text.""" - if isinstance(text, str): - text = text.encode("utf-8") - return hashlib.sha1(text).digest().hex()[:8] diff --git a/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/usbfilter.py b/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/usbfilter.py deleted file mode 100644 index 95855ae..0000000 --- a/src/nitrokey/trussed/_bootloader/lpc55_upload/utils/usbfilter.py +++ /dev/null @@ -1,226 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2019-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Module defining a USB filtering class.""" -import platform -import re -from typing import Any, Dict, Optional - -from .misc import get_hash - - -class USBDeviceFilter: - """Generic USB Device Filtering class. - - Create a filtering instance. This instance holds the USB ID you are interested - in during USB HID device search and allows you to compare, whether - provided USB HID object is the one you are interested in. - The allowed format of `usb_id` string is following: - - vid or pid - vendor ID or product ID. String holding hex or dec number. - Hex number must be preceded by 0x or 0X. Number of characters after 0x is - 1 - 4. Mixed upper & lower case letters is allowed. e.g. "0xaB12", "0XAB12", - "0x1", "0x0001". - The decimal number is restricted 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. Leading zeros are not allowed e.g. 0001. This will - result as invalid match. - - The user may provide a single number as usb_id. In such a case the number - may represent either VID or PID. By default, the filter expects this number - to be a VID. In rare cases the user may want to filter based on PID. - Initialize the `search_by_pid` parameter to True in such cases. - - 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 - string consists of two numbers separated by ':' or ','. It's not allowed - to mix hex and dec numbers, e.g. "0xab12:12345" is not allowed. - Valid vid/pid strings: - "0x12aB:0xabc", "1,99999" - - Windows specific: - instance ID - String in following format "HID\\VID_&PID_\\", - see instance ID in device manager under Windows OS. - - Linux specific: - 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: - 'IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC1@14/XHC1@14000000/HS01@14100000/SE - Blank RT Family @14100000/IOUSBHostInterface@0/AppleUserUSBHostHIDDevice' - - This path can be found using the 'ioreg' utility or using 'IO Hardware Registry Explorer' tool. - However, using the system report from 'About This MAC -> System Report -> USB' a partial path - can also be gathered. Using the name of USB device from the 'USB Device Tree' and appending - the 'Location ID' should work. The name can be 'SE Blank RT Family' and the 'Location ID' is - in form / , e.g. '0x14200000 / 18'. - So the 'usb_id' name should be 'SE Blank RT Family @14200000' and the filter should be able to - filter out such device. - """ - - def __init__( - self, - usb_id: Optional[str] = None, - search_by_pid: bool = False, - ): - """Initialize the USB Device Filtering. - - :param usb_id: usb_id string - :param search_by_pid: if true, expects usb_id to be a PID number, VID otherwise. - """ - self.usb_id = usb_id - self.search_by_pid = search_by_pid - - def compare(self, usb_device_object: Dict[str, Any]) -> bool: - """Compares the internal `usb_id` with provided `usb_device_object`. - - The provided USB ID during initialization may be VID or PID, VID/PID pair, - or a path. See private methods for details. - - :param usb_device_object: Libusbsio/HID_API device object (dictionary) - - :return: True on match, False otherwise - """ - # Determine, whether given device matches one of the expected criterion - if self.usb_id is None: - return True - - vendor_id = usb_device_object.get("vendor_id") - product_id = usb_device_object.get("product_id") - serial_number = usb_device_object.get("serial_number") - device_name = usb_device_object.get("device_name") - # the Libusbsio/HID_API holds the path as bytes, so we convert it to string - usb_path_raw = usb_device_object.get("path") - - if usb_path_raw: - if self.usb_id == get_hash(usb_path_raw): - return True - usb_path = self.convert_usb_path(usb_path_raw) - if self._is_path(usb_path=usb_path): - return True - - if self._is_vid_or_pid(vid=vendor_id, pid=product_id): - return True - - if vendor_id and product_id and self._is_vid_pid(vid=vendor_id, pid=product_id): - return True - - if serial_number and self.usb_id.casefold() == serial_number.casefold(): - return True - - if device_name and self.usb_id.casefold() == device_name.casefold(): - return True - - return False - - def _is_path(self, usb_path: str) -> bool: - """Compares the internal usb_id with provided path. - - If the path is a substring of the usb_id, this is considered as a match - and True is returned. - - :param usb_path: path to be compared with usd_id. - :return: true on a match, false otherwise. - """ - # we check the len of usb_id, because usb_id = "" is considered - # to be always in the string returning True, which is not expected - # behavior - # the provided usb string id fully matches the instance ID - usb_id = self.usb_id or "" - if usb_id.casefold() in usb_path.casefold() and len(usb_id) > 0: - return True - - return False - - def _is_vid_or_pid(self, vid: Optional[int], pid: Optional[int]) -> bool: - # 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 - # this regex is the same for vid and pid => xid - xid_regex = "0[xX][0-9a-fA-F]{1,4}|0|[1-9][0-9]{0,4}" - usb_id = self.usb_id or "" - if re.fullmatch(xid_regex, usb_id) is not None: - # the string corresponds to the vid/pid specification, check a match - if self.search_by_pid and pid: - if int(usb_id, 0) == pid: - return True - elif vid: - if int(usb_id, 0) == vid: - return True - - return False - - def _is_vid_pid(self, vid: int, pid: int) -> bool: - """If usb_id corresponds to VID/PID pair, compares it with provided vid/pid. - - :param vid: vendor ID to compare. - :param pid: product ID to compare. - :return: true on a match, false otherwise. - """ - # 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 - # Above pattern is combined to match a pair corresponding to vid/pid. - 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})" - usb_id = self.usb_id or "" - if re.fullmatch(vid_pid_regex, usb_id): - # the string corresponds to the vid/pid specification, check a match - vid_pid = re.split(":|,", usb_id) - if vid == int(vid_pid[0], 0) and pid == int(vid_pid[1], 0): - return True - - return False - - @staticmethod - def convert_usb_path(hid_api_usb_path: bytes) -> str: - """Converts the Libusbsio/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 Libusbsio/HID_API. - This method will most probably fail or provide improper results in case - path from different USB API is provided. - - :param hid_api_usb_path: USB device path from Libusbsio/HID_API - :return: Libusbsio/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 #, Libusbsio/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/stubs/hid.pyi b/stubs/hid.pyi new file mode 100644 index 0000000..81247cb --- /dev/null +++ b/stubs/hid.pyi @@ -0,0 +1,17 @@ +from typing import Any, Optional, TypedDict + +class device: + def open_path(self, path: bytes) -> None: ... + def close(self) -> None: ... + def read(self, max_length: int, timeout_ms: int) -> list[int]: ... + def write(self, data: bytes) -> int: ... + +class _DeviceDict(TypedDict): + path: bytes + vendor_id: int + product_id: int + manufacturer_string: str + product_string: str + interface_number: int + +def enumerate(vendor_id: int = 0, product_id: int = 0) -> list[_DeviceDict]: ...