migration to pyo3 app

This commit is contained in:
Christoph J. Scherr 2023-04-25 18:09:04 +02:00
parent 772b8faced
commit c9ea427dd7
Signed by: PlexSheep
GPG Key ID: 25B4ACF7D88186CC
16 changed files with 572 additions and 166 deletions

120
.github/workflows/CI.yml vendored Normal file
View File

@ -0,0 +1,120 @@
# This file is autogenerated by maturin v0.14.17
# To update, run
#
# maturin generate-ci github
#
name: CI
on:
push:
branches:
- main
- master
tags:
- '*'
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
architecture: ${{ matrix.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
macos:
runs-on: macos-latest
strategy:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v3
with:
name: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --skip-existing *

142
.gitignore vendored
View File

@ -1,162 +1,72 @@
# ---> Python
/target
# Byte-compiled / optimized / DLL files
__pycache__/
.pytest_cache/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
.venv/
env/
bin/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
include/
man/
venv/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-selfcheck.json
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
*.pot
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
.DS_Store
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
# VSCode
.vscode/
# Pyenv
.python-version

282
Cargo.lock generated Normal file
View File

@ -0,0 +1,282 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "indoc"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
[[package]]
name = "libc"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "memoffset"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "plexcryptool"
version = "0.1.0"
dependencies = [
"pyo3",
]
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pyo3"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b1ac5b3731ba34fdaa9785f8d74d17448cd18f30cf19e0c7e7b1fdb5272109"
dependencies = [
"cfg-if",
"indoc",
"libc",
"memoffset",
"parking_lot",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cb946f5ac61bb61a5014924910d936ebd2b23b705f7a4a3c40b05c720b079a3"
dependencies = [
"once_cell",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd4d7c5337821916ea2a1d21d1092e8443cf34879e53a0ac653fbb98f44ff65c"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-macros"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d39c55dab3fc5a4b25bbd1ac10a2da452c4aca13bb450f22818a002e29648d"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97daff08a4c48320587b5224cc98d609e3c27b6d437315bd40b605c98eeb5918"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "target-lexicon"
version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unindent"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "plexcryptool"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "plexcryptool"
crate-type = ["cdylib"]
[dependencies]
pyo3 = "0.18.1"

17
pyproject.toml Normal file
View File

@ -0,0 +1,17 @@
[build-system]
requires = ["maturin>=0.14,<0.15"]
build-backend = "maturin"
[project]
name = "plexcryptool"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
[tool.maturin]
features = ["pyo3/extension-module"]
python-source = "src-py"

5
pyvenv.cfg Normal file
View File

@ -0,0 +1,5 @@
home = /usr/bin
include-system-site-packages = false
version = 3.11.2
executable = /usr/bin/python3.11
command = /usr/bin/python -m venv /home/plex/Documents/code/python/plexcryptool

1
src-py/__init__.py Normal file
View File

@ -0,0 +1 @@
from .plexcryptool import *

View File

@ -11,6 +11,8 @@ Since this (auth) hash did not have a name before, I gave it the name 'authur1'
import math
import argparse
from plexcryptool import binary
# constants for authur1
SHIFT_LENGTH = 17
DEFINED_INITIAL = bytearray([0x52, 0x4f, 0x46, 0x4c])
@ -22,38 +24,15 @@ CHAR_BIT = 8
# python is being a dynamic dumbass, do a 32 bit shift ~ 4 byte
VALUE_SIZE = 4
"""
The rotations are tested agains a c implementation and are still garbage. FIXME
"""
def rotl(value: int, count: int) -> int:
# FIXME
mask: int = CHAR_BIT * VALUE_SIZE - 1;
print("mask length: %d" % mask.bit_length())
count = count & mask
result = (value << count) | (value >> (-count & mask));
assert result.bit_length() <= 32, "python made the numbers too big: %d bit" % result.bit_length()
return result
def rotr(value: int, count: int) -> int:
# FIXME
mask: int = CHAR_BIT * VALUE_SIZE - 1;
print("mask length: %d" % mask.bit_length())
count = count & mask
result = (value >> count) | (value << (-count & mask));
assert result.bit_length() <= 32, "python made the numbers too big: %d bit" % result.bit_length()
return result
"""
Now for the actual implementation of authur1
"""
def inner_authur1(input: int) -> int:
# should really be 32 bit block
# python sucks for binary operations
# assert input.bit_length() == 32, "not a 32 bit int :("
"""
passes all tests
"""
assert input.bit_length() <= 32, "input length is <= 32: %d" % input.bit_length()
output: int
output = input ^ (rotr(input, SHIFT_LENGTH))
# plexcryptool.binary uses u32 for shifting
output = input ^ (binary.rotl32(input, SHIFT_LENGTH))
assert output.bit_length() <= 32, "output length is <= 32: %d" % output.bit_length()
@ -68,46 +47,66 @@ def authur1(input: bytearray) -> bytearray:
continue
# else
assert len(internal_buffer) == 4, "internal buffer of authur1 not 4 byte long"
accumulator = bytearray(
inner_authur1(
# accumulator
int.from_bytes(accumulator, byteorder='big', signed=False)
# XOR
^
# internal_buffer
int.from_bytes(internal_buffer, byteorder='big', signed=False)
)
.to_bytes(length=2**16, byteorder="big", signed=False)
)
accuint = int.from_bytes(accumulator)
accuint = inner_authur1(accuint ^ int.from_bytes(internal_buffer))
accumulator = bytearray(accuint.to_bytes(4))
assert len(accumulator) == 4, "accumulator too long: %d bytes" % len(accumulator)
# finished loading input bytes into the hash, fill with padding and do it one last time
while not len(internal_buffer) == 4:
internal_buffer.append(PADDING)
assert len(internal_buffer) == 4, "internal buffer of authur1 not 4 byte long"
# same as above, one last time
accumulator = bytearray(
inner_authur1(
# accumulator
int.from_bytes(accumulator, byteorder='big', signed=False)
# XOR
^
# internal_buffer
int.from_bytes(internal_buffer, byteorder='big', signed=False)
)
.to_bytes(length=2**16, byteorder="big", signed=False)
)
assert len(accumulator) == 4, "accumulator too long: %d bytes" % len(accumulator)
accuint = int.from_bytes(accumulator)
accuint = inner_authur1(accuint ^ int.from_bytes(internal_buffer))
accumulator = bytearray(accuint.to_bytes(4))
assert len(accumulator) == 4, "accumulator too long: %d bytes" % len(accumulator)
return accumulator
def test():
init = int.from_bytes(DEFINED_INITIAL)
a = inner_authur1(init)
b = inner_authur1(a)
c = inner_authur1(b)
assert a == 0xded7e2d2, "Q(S0) returns wrong value: %s" % hex(a)
assert b == 0x1b725f7d, "Q(Q(S0)) returns wrong value: %s" % hex(b)
assert c == 0xa5886999, "Q(Q(Q(S0))) returns wrong value: %s" % hex(c)
print("Q aka inner_authur1 passed the test")
ha = authur1(bytearray(0))
hb = authur1(bytearray(b'A'))
hc = authur1(bytearray(b'AB'))
hd = authur1(bytearray(b'ABC'))
he = authur1(bytearray(b'ABCD'))
hf = authur1(bytearray(b'ABCDE'))
assert int.from_bytes(ha) == 0xded7e2d2, "H(\"\") returns wrong value: %s" % ha.hex()
assert int.from_bytes(hb) == 0x5d725f7f, "H(\"A\") returns wrong value: %s" % hb.hex()
assert int.from_bytes(hc) == 0x5f3b5f7f, "H(\"AB\") returns wrong value: %s" % hc.hex()
assert int.from_bytes(hd) == 0x5f39137f, "H(\"ABC\") returns wrong value: %s" % hd.hex()
assert int.from_bytes(he) == 0x5f391128, "H(\"ABCD\") returns wrong value: %s" % he.hex()
assert int.from_bytes(hf) == 0x2f69af58, "H(\"ABCDE\") returns wrong value: %s" % hf.hex()
print("H aka authur1 passed the test")
print("All tests passed!")
def main():
parser = argparse.ArgumentParser(prog="authur1 authentication hash", description='Implementation and attack for the custom authur1 hash')
parser.add_argument('-i', '--hash', type=str,
help='an input that should be hashed')
parser.add_argument('-t', '--test', action="store_true",
help='perform tests')
args = parser.parse_args()
if args.hash:
my_bytes: bytearray = bytearray(str.encode(args.hash))
hashed = authur1(my_bytes)
print("hash for \"%s\" is:\n%s" % (args.hash, hashed.hex()))
exit()
elif args.test:
test()
exit()
parser.print_help()
if __name__ == "__main__":

21
src-py/binary.pyi Normal file
View File

@ -0,0 +1,21 @@
"""
some tools for binary operations, i found python to be weird about
"""
def rotl32(value: int, count: int) -> int:
"""
left bitwise rotation / circular shift
:param value: the value that will be shifted
:param count: how far the value will be shifted
"""
...
def rotr32(value: int, count: int) -> int:
"""
right bitwise rotation / circular shift
:param value: the value that will be shifted
:param count: how far the value will be shifted
"""
...

6
src-py/plexcryptool.pyi Normal file
View File

@ -0,0 +1,6 @@
"""
Bindings for the plexcryptool rust library
plexcryptool.plexcryptool is direct access to the shared library, do not use it.
"""
from .plexcryptool import *

0
src-py/py.typed Normal file
View File

14
src/binary.rs Normal file
View File

@ -0,0 +1,14 @@
/**
* Pythons bit operations are trash, so I made a rust lib for that.
*/
use pyo3::prelude::*;
#[pyfunction]
pub fn rotl32 (value: u32, count: i32) -> u32 {
value.rotate_left(count as u32)
}
#[pyfunction]
pub fn rotr32 (value: u32, count: i32) -> u32 {
value.rotate_right(count as u32)
}

19
src/lib.rs Normal file
View File

@ -0,0 +1,19 @@
use pyo3::prelude::*;
mod binary;
#[pymodule]
fn register_binary_module(py: Python, parent_module: &PyModule) -> PyResult<()> {
let binary_module = PyModule::new(py, "binary")?;
binary_module.add_function(wrap_pyfunction!(binary::rotl32, binary_module)?)?;
binary_module.add_function(wrap_pyfunction!(binary::rotr32, binary_module)?)?;
parent_module.add_submodule(binary_module)?;
Ok(())
}
/// A Python module implemented in Rust.
#[pymodule]
fn plexcryptool(py: Python, m: &PyModule) -> PyResult<()> {
register_binary_module(py, m)?;
Ok(())
}