diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..843ba14 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +insert_final_newline = true +end_of_line = lf + +[*.{yml,yaml,ksy}] +indent_style = space +indent_size = 2 diff --git a/.github/.templateMarker b/.github/.templateMarker new file mode 100644 index 0000000..5e3a3e0 --- /dev/null +++ b/.github/.templateMarker @@ -0,0 +1 @@ +KOLANICH/python_project_boilerplate.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..89ff339 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "all" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..7fe33b3 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,15 @@ +name: CI +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - name: typical python workflow + uses: KOLANICH-GHActions/typical-python-workflow@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f1da2c --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/hcd2lime/kaitai +/kaitai_struct_formats + +__pycache__ +*.pyc +*.pyo +/*.egg-info +/build +/dist +/.eggs +/monkeytype.sqlite3 +/.coverage +*.py,cover diff --git a/Code_Of_Conduct.md b/Code_Of_Conduct.md new file mode 100644 index 0000000..bcaa2bf --- /dev/null +++ b/Code_Of_Conduct.md @@ -0,0 +1 @@ +No codes of conduct! \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..20f0fa8 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include UNLICENSE +include *.md +include tests +include .editorconfig diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..9d1ab84 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,11 @@ +hcd2lime.py [![Unlicensed work](https://raw.githubusercontent.com/unlicense/unlicense.org/master/static/favicon.png)](https://unlicense.org/) +=============== +~~[wheel](https://gitlab.com/KOLANICH/hcd2lime.py/-/jobs/artifacts/master/raw/dist/lime-0.CI-py3-none-any.whl?job=build)~~ +~~![GitLab Build Status](https://gitlab.com/KOLANICH/hcd2lime.py/badges/master/pipeline.svg)~~ +~~![GitLab Coverage](https://gitlab.com/KOLANICH/hcd2lime.py/badges/master/coverage.svg)~~ +~~[![GitHub Actions](https://github.com/KOLANICH/hcd2lime.py/workflows/CI/badge.svg)](https://github.com/KOLANICH/hcd2lime.py/actions/)~~ +[![Coveralls Coverage](https://img.shields.io/coveralls/KOLANICH/hcd2lime.py.svg)](https://coveralls.io/r/KOLANICH/hcd2lime.py) +[![Libraries.io Status](https://img.shields.io/librariesio/github/KOLANICH/hcd2lime.py.svg)](https://libraries.io/github/KOLANICH/hcd2lime.py) +[![Code style: antiflash](https://img.shields.io/badge/code%20style-antiflash-FFF.svg)](https://codeberg.org/KOLANICH-tools/antiflash.py) + +Just a lib converting HCI communication dumps to Cypress chips into LiME memory dumps. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..efb9808 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/hcd2lime/__init__.py b/hcd2lime/__init__.py new file mode 100644 index 0000000..922042c --- /dev/null +++ b/hcd2lime/__init__.py @@ -0,0 +1,18 @@ +def blobToWriteCommands(b, invalidCallback): + from .kaitai import parse + + return parsedToWriteCommands(parse(b), invalidCallback) + + +def parsedToWriteCommands(p, invalidCallback): + for i, c in enumerate(p.commands): + if c.opcode.group == c.Group.vendor_specific: + vSC = c.parameters.payload + if vSC.command == vSC.Command.write_ram: + pld = vSC.payload + yield (pld.address, pld.data) + continue + elif vSC.command == vSC.Command.launch_ram: + yield None + + invalidCallback(i, c) diff --git a/hcd2lime/__main__.py b/hcd2lime/__main__.py new file mode 100644 index 0000000..bfda170 --- /dev/null +++ b/hcd2lime/__main__.py @@ -0,0 +1,44 @@ +from pathlib import Path +from warnings import warn + +from lime import dump as limeDump +from plumbum import cli + +from . import blobToWriteCommands + + +class HCD2LiMECLI(cli.Application): + """Converts an *.hcd file for Cypress chips into LiME memory dump representing the writes found in the dump""" + + format = cli.switches.SwitchAttr(["-f", "--format"], help="Format of the dump") + allowNonPatchram = cli.switches.Flag(["--allow-non-patchram"], help="Allow non-patchram HCI commands.") + + def main(self, dumpFile="./bcm4334.hcd"): + hcdF = Path(dumpFile) + + if self.allowNonPatchram: + + def processInvalid(i, c): + if c.opcode.group != c.Group.vendor_specific: + warn("Command " + repr(i) + " is not vendor-specific: " + repr(c.opcode.group) + " " + repr(c.opcode.command)) + else: + vSC = c.parameters.payload + warn("Command " + repr(i) + " is not used for patchram: " + repr(vSC.command)) + + else: + + def processInvalid(i, c): + if c.opcode.group != c.Group.vendor_specific: + raise ValueError("Command " + repr(i) + " is not vendor-specific: " + repr(c.opcode.group) + " " + repr(c.opcode.command), c) + else: + vSC = c.parameters.payload + raise ValueError("Command " + repr(i) + " is not used for patchram: " + repr(vSC.command), c) + + data2up = list(blobToWriteCommands(hcdF.read_bytes(), processInvalid)) + outFile = hcdF.parent / (hcdF.name + ".lime") + with outFile.open("wb") as of: + limeDump(data2up, of) + + +if __name__ == "__main__": + HCD2LiMECLI.run() diff --git a/hcd2lime/kaitai/__init__.py b/hcd2lime/kaitai/__init__.py new file mode 100644 index 0000000..225385a --- /dev/null +++ b/hcd2lime/kaitai/__init__.py @@ -0,0 +1,12 @@ +from io import BytesIO + +from kaitaistruct import KaitaiStream + +from .bluetooth_hcd import BluetoothHcd +from .bluetooth_vendors_ids import BluetoothVendorsIds + + +def parse(hcdBin): + if isinstance(hcdBin, (bytes, bytearray)): + iO = BytesIO(hcdBin) + return BluetoothHcd(BluetoothVendorsIds.Vendor.cypress_semiconductor, KaitaiStream(iO)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c23bde6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,44 @@ +[build-system] +requires = ["setuptools>=61.2.0", "wheel", "setuptools_scm[toml]>=3.4.3", "kaitaiStructCompile.setuptools[toml]"] +build-backend = "setuptools.build_meta" + +[project] +name = "hcd2lime" +authors = [{name = "KOLANICH"}] +description = "Converts an *.hcd file for Cypress chips into LiME memory dump representing the writes found in the dump" +readme = "ReadMe.md" +keywords = ["LiME", "Cypress", "Broadcom", "patchram", "internalblue", "bluetooth", "hci", "hcd"] +license = {text = "Unlicense"} +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "License :: Public Domain", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", +] +urls = {Homepage = "https://codeberg.org/KOLANICH-tools/hcd2lime.py"} +requires-python = ">=3.4" +dependencies = [ + "kaitaistruct @ git+https://github.com/kaitai-io/kaitai_struct_python_runtime", + "lime @ git+https://codeberg.org/KOLANICH-libs/lime.py.git", +] +dynamic = ["version"] + +[tool.setuptools] +zip-safe = true +packages = ["hcd2lime", "hcd2lime.kaitai"] + +[tool.setuptools_scm] + +[tool.kaitai] +outputDir = "hcd2lime/kaitai" + +[tool.kaitai.repos."https://codeberg.org/KOLANICH/kaitai_struct_formats.git"."broadcom_patchram"] +update = true +localPath = "kaitai_struct_formats" + +[tool.kaitai.repos."https://codeberg.org/KOLANICH/kaitai_struct_formats.git"."broadcom_patchram".formats.bluetooth_hcd] +path = "hardware/bluetooth/bluetooth_hcd.ksy" diff --git a/test/tests.py b/test/tests.py new file mode 100644 index 0000000..a218aaa --- /dev/null +++ b/test/tests.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import typing +import itertools +import os +import sys +import unittest +from pathlib import Path +from random import _urandom, randint + +from lime import * + +sys.path.insert(0, str(Path(__file__).absolute().parent.parent)) + +#from collections import OrderedDict +#dict = OrderedDict + + +def genRandRecord(minAddr=0, minSize=10, maxSize=20, maxAddr=(1 << 64)): + start = randint(minAddr, maxAddr) + stop = randint(start + minSize, min(start + maxSize, maxAddr)) + len = stop - start + payload = _urandom(len) + return (start, payload) + + +def genRandDump(): + testStruct = [(0, b"")] + for i in range(10): + testStruct.append(genRandRecord(testStruct[-1][0] + len(testStruct[-1][1]))) + del testStruct[0] + return testStruct + + +class Tests(unittest.TestCase): + def test_roundtrip(self): + d = genRandDump() + f = dumps(d) + d1 = loads(f) + self.assertEqual(d, d1) + + +if __name__ == "__main__": + unittest.main()