bitlab 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bitlab/__init__.py +18 -0
- bitlab/bitutils/__init__.py +25 -0
- bitlab/bitutils/core.py +73 -0
- bitlab/cli.py +157 -0
- bitlab/crc/__init__.py +52 -0
- bitlab/crc/codegen.py +91 -0
- bitlab/crc/engine.py +134 -0
- bitlab/crc/explain.py +79 -0
- bitlab/crc/functions.py +59 -0
- bitlab/crc/presets.py +94 -0
- bitlab/parity/__init__.py +27 -0
- bitlab/parity/hamming.py +110 -0
- bitlab/parity/parity.py +89 -0
- bitlab/py.typed +0 -0
- bitlab-0.1.0.dist-info/METADATA +266 -0
- bitlab-0.1.0.dist-info/RECORD +19 -0
- bitlab-0.1.0.dist-info/WHEEL +4 -0
- bitlab-0.1.0.dist-info/entry_points.txt +2 -0
- bitlab-0.1.0.dist-info/licenses/LICENSE +21 -0
bitlab/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""bitlab: a toolkit for embedded systems, binary data, and digital
|
|
2
|
+
communications.
|
|
3
|
+
|
|
4
|
+
Organized as submodules by domain rather than one flat namespace:
|
|
5
|
+
|
|
6
|
+
from bitlab.parity import get_parity_bit, encode_hamming, decode_hamming
|
|
7
|
+
from bitlab.crc import crc8, crc16, crc32, explain, export_c
|
|
8
|
+
from bitlab.bitutils import popcount, rotate_left, reflect
|
|
9
|
+
|
|
10
|
+
`import bitlab` alone also gives you `bitlab.parity`, `bitlab.crc`, and
|
|
11
|
+
`bitlab.bitutils` as attributes, e.g. `bitlab.crc.crc32(b"...")`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from . import bitutils, crc, parity
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
__all__ = ["bitutils", "parity", "crc", "__version__"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Foundational bit-manipulation helpers used across bitlab's submodules."""
|
|
2
|
+
|
|
3
|
+
from .core import (
|
|
4
|
+
flip_bit,
|
|
5
|
+
get_bit,
|
|
6
|
+
has_odd_parity,
|
|
7
|
+
popcount,
|
|
8
|
+
random_bit_position,
|
|
9
|
+
reflect,
|
|
10
|
+
rotate_left,
|
|
11
|
+
rotate_right,
|
|
12
|
+
set_bit,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"popcount",
|
|
17
|
+
"has_odd_parity",
|
|
18
|
+
"get_bit",
|
|
19
|
+
"set_bit",
|
|
20
|
+
"flip_bit",
|
|
21
|
+
"reflect",
|
|
22
|
+
"rotate_left",
|
|
23
|
+
"rotate_right",
|
|
24
|
+
"random_bit_position",
|
|
25
|
+
]
|
bitlab/bitutils/core.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Low-level bit helpers shared across every bitlab submodule.
|
|
2
|
+
|
|
3
|
+
These are foundational operations used by parity, crc, and future
|
|
4
|
+
submodules (register field mapping, endianness, etc).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import random
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def popcount(n: int) -> int:
|
|
13
|
+
"""Counts the number of 1-bits in a non-negative integer."""
|
|
14
|
+
if n < 0:
|
|
15
|
+
raise ValueError("popcount expects a non-negative integer")
|
|
16
|
+
return bin(n).count("1")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def has_odd_parity(n: int) -> bool:
|
|
20
|
+
"""Returns True if `n` has an odd number of 1-bits."""
|
|
21
|
+
return popcount(n) % 2 == 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_bit(n: int, position: int) -> int:
|
|
25
|
+
"""Extracts the bit at `position` (0 = least significant) from `n`."""
|
|
26
|
+
return (n >> position) & 1
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def set_bit(n: int, position: int, bit: int) -> int:
|
|
30
|
+
"""Returns a copy of `n` with the bit at `position` set to `bit` (0 or 1)."""
|
|
31
|
+
if bit:
|
|
32
|
+
return n | (1 << position)
|
|
33
|
+
return n & ~(1 << position)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def flip_bit(n: int, position: int) -> int:
|
|
37
|
+
"""Flips (inverts) the bit at `position` in `n`."""
|
|
38
|
+
return n ^ (1 << position)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def reflect(value: int, width: int) -> int:
|
|
42
|
+
"""Reverses the lowest `width` bits of `value` (bit-mirror).
|
|
43
|
+
|
|
44
|
+
Used throughout CRC algorithms (refin/refout) and relevant to Gray-code
|
|
45
|
+
style bit-order tricks. Example: reflect(0b1100, 4) -> 0b0011.
|
|
46
|
+
"""
|
|
47
|
+
result = 0
|
|
48
|
+
for _ in range(width):
|
|
49
|
+
result = (result << 1) | (value & 1)
|
|
50
|
+
value >>= 1
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def rotate_left(value: int, amount: int, width: int) -> int:
|
|
55
|
+
"""Rotates `value` left by `amount` bits within a `width`-bit register."""
|
|
56
|
+
amount %= width
|
|
57
|
+
mask = (1 << width) - 1
|
|
58
|
+
value &= mask
|
|
59
|
+
return ((value << amount) | (value >> (width - amount))) & mask
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def rotate_right(value: int, amount: int, width: int) -> int:
|
|
63
|
+
"""Rotates `value` right by `amount` bits within a `width`-bit register."""
|
|
64
|
+
amount %= width
|
|
65
|
+
mask = (1 << width) - 1
|
|
66
|
+
value &= mask
|
|
67
|
+
return ((value >> amount) | (value << (width - amount))) & mask
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def random_bit_position(width: int) -> int:
|
|
71
|
+
"""Returns a random bit position in [0, width) using the `random` module
|
|
72
|
+
(not cryptographically secure)."""
|
|
73
|
+
return random.randrange(width)
|
bitlab/cli.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Unified command-line interface for bitlab.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
bitlab parity bit <value> [--type even|odd] [--width N]
|
|
5
|
+
bitlab parity append <value> [--type even|odd] [--width N]
|
|
6
|
+
bitlab parity check <value> [--type even|odd] [--width N]
|
|
7
|
+
|
|
8
|
+
bitlab hamming encode <4bitValue>
|
|
9
|
+
bitlab hamming decode <7bitCodeword>
|
|
10
|
+
|
|
11
|
+
bitlab crc compute <data> [--preset crc8|crc16-ccitt|crc16-modbus|crc32] [--hex]
|
|
12
|
+
bitlab crc explain <data> [--preset ...] [--hex]
|
|
13
|
+
bitlab crc export-c [--preset ...] [--name FN_NAME]
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
from dataclasses import asdict
|
|
23
|
+
|
|
24
|
+
from .crc.engine import compute
|
|
25
|
+
from .crc.codegen import export_c as crc_export_c
|
|
26
|
+
from .crc.explain import explain as crc_explain
|
|
27
|
+
from .crc.presets import PRESETS
|
|
28
|
+
from .parity.hamming import decode_hamming, encode_hamming
|
|
29
|
+
from .parity.parity import ParityType, append_parity, check_parity, get_parity_bit
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _parse_number(token: str) -> int:
|
|
33
|
+
"""Parses a decimal, 0b-binary, or 0x-hex integer literal."""
|
|
34
|
+
return int(token, 0) if token.lower().startswith(("0b", "0x", "0o")) else int(token)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _parse_crc_data(value: str, as_hex: bool) -> bytes:
|
|
38
|
+
if as_hex:
|
|
39
|
+
cleaned = value.replace(" ", "").replace("0x", "")
|
|
40
|
+
return bytes.fromhex(cleaned)
|
|
41
|
+
return value.encode("utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
45
|
+
parser = argparse.ArgumentParser(
|
|
46
|
+
prog="bitlab",
|
|
47
|
+
description="Toolkit for parity, Hamming codes, and CRC — computation, "
|
|
48
|
+
"explanation, and C code export.",
|
|
49
|
+
)
|
|
50
|
+
subparsers = parser.add_subparsers(dest="group")
|
|
51
|
+
|
|
52
|
+
# --- parity ---
|
|
53
|
+
parity_parser = subparsers.add_parser("parity", help="Parity bit tools")
|
|
54
|
+
parity_sub = parity_parser.add_subparsers(dest="command")
|
|
55
|
+
|
|
56
|
+
def add_parity_args(sub: argparse.ArgumentParser) -> None:
|
|
57
|
+
sub.add_argument("value", type=str, help="Integer value (decimal, 0b, or 0x)")
|
|
58
|
+
sub.add_argument("--type", choices=["even", "odd"], default="even")
|
|
59
|
+
sub.add_argument("--width", type=int, default=8)
|
|
60
|
+
|
|
61
|
+
add_parity_args(parity_sub.add_parser("bit", help="Print the parity bit for a value"))
|
|
62
|
+
add_parity_args(parity_sub.add_parser("append", help="Append a parity bit to a value"))
|
|
63
|
+
add_parity_args(
|
|
64
|
+
parity_sub.add_parser("check", help="Check parity of a value with parity bit appended")
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# --- hamming ---
|
|
68
|
+
hamming_parser = subparsers.add_parser("hamming", help="Hamming(7,4) error correction")
|
|
69
|
+
hamming_sub = hamming_parser.add_subparsers(dest="command")
|
|
70
|
+
encode_p = hamming_sub.add_parser("encode", help="Encode a 4-bit value into a 7-bit codeword")
|
|
71
|
+
encode_p.add_argument("value", type=str)
|
|
72
|
+
decode_p = hamming_sub.add_parser("decode", help="Decode/correct a 7-bit codeword")
|
|
73
|
+
decode_p.add_argument("value", type=str)
|
|
74
|
+
|
|
75
|
+
# --- crc ---
|
|
76
|
+
crc_parser = subparsers.add_parser("crc", help="CRC compute / explain / export-c")
|
|
77
|
+
crc_sub = crc_parser.add_subparsers(dest="command")
|
|
78
|
+
|
|
79
|
+
compute_p = crc_sub.add_parser("compute", help="Compute a CRC")
|
|
80
|
+
compute_p.add_argument("data", type=str, help="Input data (UTF-8 text, or hex with --hex)")
|
|
81
|
+
compute_p.add_argument("--preset", choices=list(PRESETS), default="crc32")
|
|
82
|
+
compute_p.add_argument("--hex", action="store_true", help="Treat <data> as hex bytes")
|
|
83
|
+
|
|
84
|
+
explain_p = crc_sub.add_parser("explain", help="Step-by-step CRC trace")
|
|
85
|
+
explain_p.add_argument("data", type=str)
|
|
86
|
+
explain_p.add_argument("--preset", choices=list(PRESETS), default="crc32")
|
|
87
|
+
explain_p.add_argument("--hex", action="store_true")
|
|
88
|
+
explain_p.add_argument("--max-bytes", type=int, default=4)
|
|
89
|
+
|
|
90
|
+
export_p = crc_sub.add_parser("export-c", help="Export a CRC config as C source")
|
|
91
|
+
export_p.add_argument("--preset", choices=list(PRESETS), default="crc32")
|
|
92
|
+
export_p.add_argument("--name", type=str, default=None, help="C function name")
|
|
93
|
+
|
|
94
|
+
return parser
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def main(argv: "list[str] | None" = None) -> int:
|
|
98
|
+
try:
|
|
99
|
+
return _main(argv)
|
|
100
|
+
except BrokenPipeError:
|
|
101
|
+
# Output was piped into something like `head` that closed early.
|
|
102
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
103
|
+
os.dup2(devnull, sys.stdout.fileno())
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _main(argv: "list[str] | None") -> int:
|
|
108
|
+
parser = _build_parser()
|
|
109
|
+
args = parser.parse_args(argv)
|
|
110
|
+
|
|
111
|
+
if args.group is None or getattr(args, "command", None) is None:
|
|
112
|
+
parser.print_help()
|
|
113
|
+
return 0 if args.group is None else 1
|
|
114
|
+
|
|
115
|
+
if args.group == "parity":
|
|
116
|
+
value = _parse_number(args.value)
|
|
117
|
+
type_: ParityType = args.type
|
|
118
|
+
width: int = args.width
|
|
119
|
+
if args.command == "bit":
|
|
120
|
+
print(get_parity_bit(value, type_, width))
|
|
121
|
+
elif args.command == "append":
|
|
122
|
+
result = append_parity(value, type_, width)
|
|
123
|
+
print(f"{result} (0b{result:b})")
|
|
124
|
+
elif args.command == "check":
|
|
125
|
+
print(check_parity(value, type_, width))
|
|
126
|
+
return 0
|
|
127
|
+
|
|
128
|
+
if args.group == "hamming":
|
|
129
|
+
value = _parse_number(args.value)
|
|
130
|
+
if args.command == "encode":
|
|
131
|
+
codeword = encode_hamming(value)
|
|
132
|
+
print(f"{codeword} (0b{codeword:07b})")
|
|
133
|
+
elif args.command == "decode":
|
|
134
|
+
result = decode_hamming(value)
|
|
135
|
+
print(json.dumps(asdict(result), indent=2))
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
if args.group == "crc":
|
|
139
|
+
config = PRESETS[args.preset]
|
|
140
|
+
if args.command == "compute":
|
|
141
|
+
data = _parse_crc_data(args.data, args.hex)
|
|
142
|
+
result = compute(data, config)
|
|
143
|
+
hexw = config.width // 4
|
|
144
|
+
print(f"0x{result:0{hexw}X} ({result})")
|
|
145
|
+
elif args.command == "explain":
|
|
146
|
+
data = _parse_crc_data(args.data, args.hex)
|
|
147
|
+
print(crc_explain(data, config, max_bytes=args.max_bytes))
|
|
148
|
+
elif args.command == "export-c":
|
|
149
|
+
print(crc_export_c(config, function_name=args.name))
|
|
150
|
+
return 0
|
|
151
|
+
|
|
152
|
+
parser.print_help()
|
|
153
|
+
return 1
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
sys.exit(main())
|
bitlab/crc/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""CRC (Cyclic Redundancy Check) generation, checking, explanation, and C
|
|
2
|
+
code export.
|
|
3
|
+
|
|
4
|
+
Quick start:
|
|
5
|
+
>>> from bitlab.crc import crc32
|
|
6
|
+
>>> hex(crc32(b"123456789"))
|
|
7
|
+
'0xcbf43926'
|
|
8
|
+
|
|
9
|
+
Custom polynomial:
|
|
10
|
+
>>> from bitlab.crc import crc, CRCConfig
|
|
11
|
+
>>> config = CRCConfig("my-crc", 16, 0x8005, 0xFFFF, True, True, 0x0000, 0x4B37)
|
|
12
|
+
>>> crc(b"123456789", config)
|
|
13
|
+
|
|
14
|
+
Educational trace:
|
|
15
|
+
>>> from bitlab.crc import explain, CRC8_SMBUS
|
|
16
|
+
>>> print(explain(b"AB", CRC8_SMBUS))
|
|
17
|
+
|
|
18
|
+
Export to C for firmware use:
|
|
19
|
+
>>> from bitlab.crc import export_c, CRC32_ISO_HDLC
|
|
20
|
+
>>> print(export_c(CRC32_ISO_HDLC))
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .codegen import export_c
|
|
24
|
+
from .engine import build_table, compute, compute_bitwise, compute_table, self_test
|
|
25
|
+
from .explain import explain
|
|
26
|
+
from .functions import crc, crc8, crc16, crc32
|
|
27
|
+
from .presets import (
|
|
28
|
+
CRC8_SMBUS,
|
|
29
|
+
CRC16_CCITT_FALSE,
|
|
30
|
+
CRC16_MODBUS,
|
|
31
|
+
CRC32_ISO_HDLC,
|
|
32
|
+
CRCConfig,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"CRCConfig",
|
|
37
|
+
"CRC8_SMBUS",
|
|
38
|
+
"CRC16_CCITT_FALSE",
|
|
39
|
+
"CRC16_MODBUS",
|
|
40
|
+
"CRC32_ISO_HDLC",
|
|
41
|
+
"crc",
|
|
42
|
+
"crc8",
|
|
43
|
+
"crc16",
|
|
44
|
+
"crc32",
|
|
45
|
+
"compute",
|
|
46
|
+
"compute_bitwise",
|
|
47
|
+
"compute_table",
|
|
48
|
+
"build_table",
|
|
49
|
+
"self_test",
|
|
50
|
+
"explain",
|
|
51
|
+
"export_c",
|
|
52
|
+
]
|
bitlab/crc/codegen.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Export a CRC configuration as standalone, table-driven C source.
|
|
2
|
+
|
|
3
|
+
This is what turns bitlab from "a Python learning tool" into something an
|
|
4
|
+
embedded developer actually keeps: design/verify a CRC in Python, then drop
|
|
5
|
+
the generated C straight into a firmware project. The lookup table embedded
|
|
6
|
+
in the generated code is built by the exact same `engine.build_table` used
|
|
7
|
+
by bitlab's own fast Python path, so the C output and the Python output are
|
|
8
|
+
guaranteed to agree.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
from ..bitutils import reflect
|
|
16
|
+
from .engine import build_table
|
|
17
|
+
from .presets import CRCConfig
|
|
18
|
+
|
|
19
|
+
_C_TYPE_BY_WIDTH = {8: "uint8_t", 16: "uint16_t", 32: "uint32_t"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _sanitize_identifier(name: str) -> str:
|
|
23
|
+
ident = re.sub(r"[^0-9a-zA-Z_]", "_", name.lower())
|
|
24
|
+
ident = re.sub(r"_+", "_", ident).strip("_")
|
|
25
|
+
if ident and ident[0].isdigit():
|
|
26
|
+
ident = f"_{ident}"
|
|
27
|
+
return ident or "crc"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def export_c(config: CRCConfig, function_name: str = None) -> str:
|
|
31
|
+
"""Returns standalone C source (C99) implementing `config` as a
|
|
32
|
+
table-driven CRC function with an embedded 256-entry lookup table.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config: The CRC configuration to export.
|
|
36
|
+
function_name: Optional explicit C function name. Defaults to a
|
|
37
|
+
sanitized version of `config.name` (e.g. "CRC-32/ISO-HDLC" ->
|
|
38
|
+
"crc_32_iso_hdlc").
|
|
39
|
+
"""
|
|
40
|
+
if config.width not in _C_TYPE_BY_WIDTH:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"export_c only supports width 8, 16, or 32 (got {config.width})"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
ctype = _C_TYPE_BY_WIDTH[config.width]
|
|
46
|
+
fn_name = function_name or _sanitize_identifier(config.name)
|
|
47
|
+
hexw = config.width // 4
|
|
48
|
+
table = build_table(config)
|
|
49
|
+
|
|
50
|
+
rows = []
|
|
51
|
+
for i in range(0, 256, 8):
|
|
52
|
+
row = ", ".join(f"0x{v:0{hexw}X}" for v in table[i : i + 8])
|
|
53
|
+
rows.append(" " + row + ",")
|
|
54
|
+
table_str = "\n".join(rows)
|
|
55
|
+
|
|
56
|
+
if config.refin:
|
|
57
|
+
init_val = reflect(config.init, config.width)
|
|
58
|
+
body = (
|
|
59
|
+
f" {ctype} crc = 0x{init_val:0{hexw}X}U;\n"
|
|
60
|
+
f" for (size_t i = 0; i < len; i++) {{\n"
|
|
61
|
+
f" uint8_t idx = (uint8_t)(crc ^ data[i]);\n"
|
|
62
|
+
f" crc = (crc >> 8) ^ {fn_name}_table[idx];\n"
|
|
63
|
+
f" }}\n"
|
|
64
|
+
f" return (crc ^ 0x{config.xorout:0{hexw}X}U);"
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
shift = config.width - 8
|
|
68
|
+
body = (
|
|
69
|
+
f" {ctype} crc = 0x{config.init:0{hexw}X}U;\n"
|
|
70
|
+
f" for (size_t i = 0; i < len; i++) {{\n"
|
|
71
|
+
f" uint8_t idx = (uint8_t)((crc >> {shift}) ^ data[i]);\n"
|
|
72
|
+
f" crc = (crc << 8) ^ {fn_name}_table[idx];\n"
|
|
73
|
+
f" }}\n"
|
|
74
|
+
f" return (crc ^ 0x{config.xorout:0{hexw}X}U);"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
f"/* Generated by bitlab.crc.codegen.export_c -- {config.name}\n"
|
|
79
|
+
f" * width={config.width} poly=0x{config.poly:0{hexw}X} init=0x{config.init:0{hexw}X}\n"
|
|
80
|
+
f" * refin={config.refin} refout={config.refout} xorout=0x{config.xorout:0{hexw}X}\n"
|
|
81
|
+
f" */\n"
|
|
82
|
+
f"#include <stdint.h>\n"
|
|
83
|
+
f"#include <stddef.h>\n\n"
|
|
84
|
+
f"static const {ctype} {fn_name}_table[256] = {{\n"
|
|
85
|
+
f"{table_str}\n"
|
|
86
|
+
f"}};\n\n"
|
|
87
|
+
f"{ctype} {fn_name}(const uint8_t *data, size_t len)\n"
|
|
88
|
+
f"{{\n"
|
|
89
|
+
f"{body}\n"
|
|
90
|
+
f"}}\n"
|
|
91
|
+
)
|
bitlab/crc/engine.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Generic, parameterized CRC engine.
|
|
2
|
+
|
|
3
|
+
Implements the standard "Rocksoft model" CRC algorithm: any CRC (CRC-8,
|
|
4
|
+
CRC-16, CRC-32, ...) can be described by (width, poly, init, refin, refout,
|
|
5
|
+
xorout). Two computation strategies are provided:
|
|
6
|
+
|
|
7
|
+
- `compute_bitwise`: the canonical, easy-to-verify reference algorithm.
|
|
8
|
+
Processes one bit at a time. This is what `CRCConfig.check` is validated
|
|
9
|
+
against, and what `explain()` walks through step by step.
|
|
10
|
+
- `compute_table`: a table-driven implementation (the same algorithm real
|
|
11
|
+
hardware/firmware uses for speed). The lookup table is also what
|
|
12
|
+
`codegen.export_c()` embeds in the generated C source, so the Python fast
|
|
13
|
+
path and the exported C code are built from the exact same table.
|
|
14
|
+
|
|
15
|
+
Both strategies are cross-validated against each other and against published
|
|
16
|
+
CRC-catalogue check values in the test suite.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import List
|
|
22
|
+
|
|
23
|
+
from ..bitutils import reflect
|
|
24
|
+
from .presets import CRCConfig
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def compute_bitwise(data: bytes, config: CRCConfig) -> int:
|
|
28
|
+
"""Computes a CRC using the canonical bit-by-bit algorithm.
|
|
29
|
+
|
|
30
|
+
This is the reference implementation: simple enough to read and trust,
|
|
31
|
+
at the cost of processing 8 shifts per input byte instead of 1 table
|
|
32
|
+
lookup. Fine for typical prototyping payloads; use `compute_table` (or
|
|
33
|
+
just `compute`, which picks the fast path automatically) for large data.
|
|
34
|
+
"""
|
|
35
|
+
width = config.width
|
|
36
|
+
mask = (1 << width) - 1
|
|
37
|
+
topbit = 1 << (width - 1)
|
|
38
|
+
reg = config.init & mask
|
|
39
|
+
|
|
40
|
+
for byte in data:
|
|
41
|
+
b = reflect(byte, 8) if config.refin else byte
|
|
42
|
+
reg ^= (b << (width - 8)) & mask
|
|
43
|
+
for _ in range(8):
|
|
44
|
+
if reg & topbit:
|
|
45
|
+
reg = ((reg << 1) ^ config.poly) & mask
|
|
46
|
+
else:
|
|
47
|
+
reg = (reg << 1) & mask
|
|
48
|
+
|
|
49
|
+
if config.refout:
|
|
50
|
+
reg = reflect(reg, width)
|
|
51
|
+
return (reg ^ config.xorout) & mask
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build_table(config: CRCConfig) -> List[int]:
|
|
55
|
+
"""Builds the 256-entry lookup table for `config`.
|
|
56
|
+
|
|
57
|
+
Two distinct table algorithms exist depending on whether the CRC
|
|
58
|
+
processes bits MSB-first (refin=False) or LSB-first (refin=True); this
|
|
59
|
+
picks the correct one automatically. The returned table is exactly what
|
|
60
|
+
`codegen.export_c` embeds in generated C source.
|
|
61
|
+
"""
|
|
62
|
+
width = config.width
|
|
63
|
+
mask = (1 << width) - 1
|
|
64
|
+
|
|
65
|
+
if config.refin:
|
|
66
|
+
rpoly = reflect(config.poly, width)
|
|
67
|
+
table = []
|
|
68
|
+
for i in range(256):
|
|
69
|
+
reg = i
|
|
70
|
+
for _ in range(8):
|
|
71
|
+
if reg & 1:
|
|
72
|
+
reg = (reg >> 1) ^ rpoly
|
|
73
|
+
else:
|
|
74
|
+
reg = reg >> 1
|
|
75
|
+
table.append(reg & mask)
|
|
76
|
+
return table
|
|
77
|
+
|
|
78
|
+
topbit = 1 << (width - 1)
|
|
79
|
+
table = []
|
|
80
|
+
for i in range(256):
|
|
81
|
+
reg = i << (width - 8)
|
|
82
|
+
for _ in range(8):
|
|
83
|
+
if reg & topbit:
|
|
84
|
+
reg = ((reg << 1) ^ config.poly) & mask
|
|
85
|
+
else:
|
|
86
|
+
reg = (reg << 1) & mask
|
|
87
|
+
table.append(reg & mask)
|
|
88
|
+
return table
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def compute_table(data: bytes, config: CRCConfig, table: List[int] = None) -> int:
|
|
92
|
+
"""Computes a CRC using a lookup table (builds one if not supplied).
|
|
93
|
+
|
|
94
|
+
Pass a pre-built table (from `build_table`) when computing many CRCs
|
|
95
|
+
with the same config to avoid rebuilding it each call.
|
|
96
|
+
"""
|
|
97
|
+
width = config.width
|
|
98
|
+
mask = (1 << width) - 1
|
|
99
|
+
if table is None:
|
|
100
|
+
table = build_table(config)
|
|
101
|
+
|
|
102
|
+
if config.refin:
|
|
103
|
+
reg = reflect(config.init & mask, width)
|
|
104
|
+
for byte in data:
|
|
105
|
+
idx = (reg ^ byte) & 0xFF
|
|
106
|
+
reg = ((reg >> 8) ^ table[idx]) & mask
|
|
107
|
+
return (reg ^ config.xorout) & mask
|
|
108
|
+
|
|
109
|
+
reg = config.init & mask
|
|
110
|
+
shift = width - 8
|
|
111
|
+
for byte in data:
|
|
112
|
+
idx = ((reg >> shift) ^ byte) & 0xFF
|
|
113
|
+
reg = ((reg << 8) ^ table[idx]) & mask
|
|
114
|
+
if config.refout:
|
|
115
|
+
reg = reflect(reg, width)
|
|
116
|
+
return (reg ^ config.xorout) & mask
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def compute(data: bytes, config: CRCConfig) -> int:
|
|
120
|
+
"""Computes a CRC using the fast table-driven path. This is what the
|
|
121
|
+
`crc8`/`crc16`/`crc32` convenience functions and the generic `crc()`
|
|
122
|
+
function call."""
|
|
123
|
+
return compute_table(data, config)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def self_test(config: CRCConfig) -> bool:
|
|
127
|
+
"""Verifies `config` against its known check value (CRC of b"123456789"),
|
|
128
|
+
using both the bitwise and table-driven implementations. Returns True
|
|
129
|
+
only if both agree with `config.check`."""
|
|
130
|
+
check_input = b"123456789"
|
|
131
|
+
return (
|
|
132
|
+
compute_bitwise(check_input, config) == config.check
|
|
133
|
+
and compute_table(check_input, config) == config.check
|
|
134
|
+
)
|
bitlab/crc/explain.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Human-readable, step-by-step CRC trace — the educational counterpart to
|
|
2
|
+
the fast `compute()` path.
|
|
3
|
+
|
|
4
|
+
`explain()` runs the exact same bitwise algorithm as `engine.compute_bitwise`
|
|
5
|
+
(this is not a separate/approximate explanation — it's the real computation
|
|
6
|
+
with a narration attached), so what a student reads here is guaranteed to
|
|
7
|
+
match what the fast path actually computes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from ..bitutils import reflect
|
|
13
|
+
from .presets import CRCConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def explain(data: bytes, config: CRCConfig, max_bytes: int = 4) -> str:
|
|
17
|
+
"""Returns a step-by-step trace of computing the CRC of `data` under
|
|
18
|
+
`config`.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
data: The bytes to checksum.
|
|
22
|
+
config: The CRC configuration to explain.
|
|
23
|
+
max_bytes: Show detailed per-byte register state for at most this
|
|
24
|
+
many leading bytes, then summarize the rest (keeps the trace
|
|
25
|
+
readable for longer payloads).
|
|
26
|
+
"""
|
|
27
|
+
width = config.width
|
|
28
|
+
mask = (1 << width) - 1
|
|
29
|
+
topbit = 1 << (width - 1)
|
|
30
|
+
hexw = width // 4
|
|
31
|
+
reg = config.init & mask
|
|
32
|
+
|
|
33
|
+
lines = []
|
|
34
|
+
lines.append(f"CRC algorithm: {config.name}")
|
|
35
|
+
lines.append(
|
|
36
|
+
f" width={width} poly=0x{config.poly:0{hexw}X} init=0x{config.init:0{hexw}X} "
|
|
37
|
+
f"refin={config.refin} refout={config.refout} xorout=0x{config.xorout:0{hexw}X}"
|
|
38
|
+
)
|
|
39
|
+
lines.append("")
|
|
40
|
+
lines.append(f"Input bytes ({len(data)} total): {data[:16].hex(' ')}" + (" ..." if len(data) > 16 else ""))
|
|
41
|
+
lines.append("")
|
|
42
|
+
lines.append(f"Initial register: 0x{reg:0{hexw}X}")
|
|
43
|
+
|
|
44
|
+
for i, byte in enumerate(data):
|
|
45
|
+
b = reflect(byte, 8) if config.refin else byte
|
|
46
|
+
reg ^= (b << (width - 8)) & mask
|
|
47
|
+
|
|
48
|
+
if i < max_bytes:
|
|
49
|
+
note = " (bit-reversed for refin)" if config.refin else ""
|
|
50
|
+
lines.append("")
|
|
51
|
+
lines.append(f"Byte {i}: 0x{byte:02X}{note} -> XORed in, register = 0x{reg:0{hexw}X}")
|
|
52
|
+
|
|
53
|
+
for bit_num in range(8):
|
|
54
|
+
if reg & topbit:
|
|
55
|
+
reg = ((reg << 1) ^ config.poly) & mask
|
|
56
|
+
bit_note = "MSB=1 -> shift left, XOR polynomial"
|
|
57
|
+
else:
|
|
58
|
+
reg = (reg << 1) & mask
|
|
59
|
+
bit_note = "MSB=0 -> shift left"
|
|
60
|
+
if i < max_bytes:
|
|
61
|
+
lines.append(f" bit {bit_num}: {bit_note} -> 0x{reg:0{hexw}X}")
|
|
62
|
+
|
|
63
|
+
if i == max_bytes and len(data) > max_bytes:
|
|
64
|
+
lines.append("")
|
|
65
|
+
lines.append(f"... ({len(data) - max_bytes} more bytes processed the same way) ...")
|
|
66
|
+
|
|
67
|
+
lines.append("")
|
|
68
|
+
lines.append(f"Register after all bytes: 0x{reg:0{hexw}X}")
|
|
69
|
+
|
|
70
|
+
if config.refout:
|
|
71
|
+
reg = reflect(reg, width)
|
|
72
|
+
lines.append(f"refout=True -> bit-reverse register: 0x{reg:0{hexw}X}")
|
|
73
|
+
|
|
74
|
+
result = (reg ^ config.xorout) & mask
|
|
75
|
+
lines.append(f"XOR with xorout (0x{config.xorout:0{hexw}X}): 0x{result:0{hexw}X}")
|
|
76
|
+
lines.append("")
|
|
77
|
+
lines.append(f"Final CRC: 0x{result:0{hexw}X}")
|
|
78
|
+
|
|
79
|
+
return "\n".join(lines)
|
bitlab/crc/functions.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Convenience functions for the common CRC variants, plus a generic
|
|
2
|
+
parameterized entry point for custom polynomials."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from .engine import compute
|
|
9
|
+
from .presets import (
|
|
10
|
+
CRC8_SMBUS,
|
|
11
|
+
CRC16_CCITT_FALSE,
|
|
12
|
+
CRC16_MODBUS,
|
|
13
|
+
CRC32_ISO_HDLC,
|
|
14
|
+
CRCConfig,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def crc8(data: bytes) -> int:
|
|
19
|
+
"""CRC-8/SMBUS of `data`."""
|
|
20
|
+
return compute(data, CRC8_SMBUS)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def crc16(data: bytes, variant: str = "ccitt") -> int:
|
|
24
|
+
"""CRC-16 of `data`.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
variant: 'ccitt' (CRC-16/CCITT-FALSE, default) or 'modbus'
|
|
28
|
+
(CRC-16/MODBUS, used by the Modbus protocol).
|
|
29
|
+
"""
|
|
30
|
+
if variant == "ccitt":
|
|
31
|
+
return compute(data, CRC16_CCITT_FALSE)
|
|
32
|
+
if variant == "modbus":
|
|
33
|
+
return compute(data, CRC16_MODBUS)
|
|
34
|
+
raise ValueError(f"Unknown CRC-16 variant: {variant!r} (expected 'ccitt' or 'modbus')")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def crc32(data: bytes) -> int:
|
|
38
|
+
"""CRC-32/ISO-HDLC of `data` (the CRC used by Ethernet, zlib, PNG)."""
|
|
39
|
+
return compute(data, CRC32_ISO_HDLC)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def crc(data: bytes, config: Union[CRCConfig, str]) -> int:
|
|
43
|
+
"""Generic CRC entry point.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
data: The bytes to checksum.
|
|
47
|
+
config: Either a `CRCConfig` (for a fully custom polynomial) or the
|
|
48
|
+
name of a built-in preset: 'crc8', 'crc16-ccitt', 'crc16-modbus',
|
|
49
|
+
or 'crc32'.
|
|
50
|
+
"""
|
|
51
|
+
if isinstance(config, str):
|
|
52
|
+
from .presets import PRESETS
|
|
53
|
+
|
|
54
|
+
if config not in PRESETS:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"Unknown preset {config!r}. Available: {', '.join(PRESETS)}"
|
|
57
|
+
)
|
|
58
|
+
config = PRESETS[config]
|
|
59
|
+
return compute(data, config)
|
bitlab/crc/presets.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""CRC configuration presets.
|
|
2
|
+
|
|
3
|
+
Each preset's parameters (poly/init/refin/refout/xorout) and `check` value
|
|
4
|
+
come from the standard CRC catalogue (the CRC of the ASCII string
|
|
5
|
+
"123456789"). Every preset here is verified against that check value in the
|
|
6
|
+
test suite via `bitlab.crc.engine.self_test`, so you can trust these match
|
|
7
|
+
what real hardware/protocols expect — not just "a CRC that happens to run".
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class CRCConfig:
|
|
17
|
+
"""Parameters that fully define a CRC algorithm (the "Rocksoft model").
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
name: Human-readable name, e.g. "CRC-32/ISO-HDLC".
|
|
21
|
+
width: Register width in bits (8, 16, or 32).
|
|
22
|
+
poly: The generator polynomial, without its implicit leading term.
|
|
23
|
+
init: Initial register value.
|
|
24
|
+
refin: If True, each input byte is bit-reversed before processing.
|
|
25
|
+
refout: If True, the final register is bit-reversed before xorout.
|
|
26
|
+
xorout: Value XORed with the final register to produce the result.
|
|
27
|
+
check: The known-correct CRC of b"123456789" for this configuration,
|
|
28
|
+
used to self-test the engine against the published catalogue.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
width: int
|
|
33
|
+
poly: int
|
|
34
|
+
init: int
|
|
35
|
+
refin: bool
|
|
36
|
+
refout: bool
|
|
37
|
+
xorout: int
|
|
38
|
+
check: int
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# CRC-8/SMBUS -- used in SMBus, some simple embedded checksums.
|
|
42
|
+
CRC8_SMBUS = CRCConfig(
|
|
43
|
+
name="CRC-8/SMBUS",
|
|
44
|
+
width=8,
|
|
45
|
+
poly=0x07,
|
|
46
|
+
init=0x00,
|
|
47
|
+
refin=False,
|
|
48
|
+
refout=False,
|
|
49
|
+
xorout=0x00,
|
|
50
|
+
check=0xF4,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# CRC-16/CCITT-FALSE -- widely used in firmware/bootloader integrity checks.
|
|
54
|
+
CRC16_CCITT_FALSE = CRCConfig(
|
|
55
|
+
name="CRC-16/CCITT-FALSE",
|
|
56
|
+
width=16,
|
|
57
|
+
poly=0x1021,
|
|
58
|
+
init=0xFFFF,
|
|
59
|
+
refin=False,
|
|
60
|
+
refout=False,
|
|
61
|
+
xorout=0x0000,
|
|
62
|
+
check=0x29B1,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# CRC-16/MODBUS -- the CRC used by the Modbus serial protocol.
|
|
66
|
+
CRC16_MODBUS = CRCConfig(
|
|
67
|
+
name="CRC-16/MODBUS",
|
|
68
|
+
width=16,
|
|
69
|
+
poly=0x8005,
|
|
70
|
+
init=0xFFFF,
|
|
71
|
+
refin=True,
|
|
72
|
+
refout=True,
|
|
73
|
+
xorout=0x0000,
|
|
74
|
+
check=0x4B37,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# CRC-32/ISO-HDLC -- the "standard" CRC-32 used by Ethernet, zlib/gzip, PNG.
|
|
78
|
+
CRC32_ISO_HDLC = CRCConfig(
|
|
79
|
+
name="CRC-32/ISO-HDLC",
|
|
80
|
+
width=32,
|
|
81
|
+
poly=0x04C11DB7,
|
|
82
|
+
init=0xFFFFFFFF,
|
|
83
|
+
refin=True,
|
|
84
|
+
refout=True,
|
|
85
|
+
xorout=0xFFFFFFFF,
|
|
86
|
+
check=0xCBF43926,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
PRESETS = {
|
|
90
|
+
"crc8": CRC8_SMBUS,
|
|
91
|
+
"crc16-ccitt": CRC16_CCITT_FALSE,
|
|
92
|
+
"crc16-modbus": CRC16_MODBUS,
|
|
93
|
+
"crc32": CRC32_ISO_HDLC,
|
|
94
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Parity bit checking and Hamming(7,4) single-bit error correction."""
|
|
2
|
+
|
|
3
|
+
from .hamming import HammingDecodeResult, decode_hamming, encode_hamming
|
|
4
|
+
from .parity import (
|
|
5
|
+
ParityType,
|
|
6
|
+
append_parity,
|
|
7
|
+
check_parity,
|
|
8
|
+
check_parity_array,
|
|
9
|
+
compute_message_parity,
|
|
10
|
+
flip_random_bit,
|
|
11
|
+
generate_parity_array,
|
|
12
|
+
get_parity_bit,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ParityType",
|
|
17
|
+
"get_parity_bit",
|
|
18
|
+
"append_parity",
|
|
19
|
+
"check_parity",
|
|
20
|
+
"generate_parity_array",
|
|
21
|
+
"check_parity_array",
|
|
22
|
+
"compute_message_parity",
|
|
23
|
+
"flip_random_bit",
|
|
24
|
+
"HammingDecodeResult",
|
|
25
|
+
"encode_hamming",
|
|
26
|
+
"decode_hamming",
|
|
27
|
+
]
|
bitlab/parity/hamming.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Hamming(7,4) single-error-correcting code.
|
|
2
|
+
|
|
3
|
+
Encodes 4 data bits into a 7-bit codeword using 3 parity bits, positioned
|
|
4
|
+
(1-indexed) as:
|
|
5
|
+
bit 1 = p1, bit 2 = p2, bit 3 = d1, bit 4 = p3, bit 5 = d2, bit 6 = d3, bit 7 = d4
|
|
6
|
+
|
|
7
|
+
p1 covers bits 1,3,5,7
|
|
8
|
+
p2 covers bits 2,3,6,7
|
|
9
|
+
p3 covers bits 4,5,6,7
|
|
10
|
+
|
|
11
|
+
This is the classic textbook layout, so codewords produced here match what
|
|
12
|
+
you'd compute by hand on paper.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class HammingDecodeResult:
|
|
22
|
+
"""Result of decoding a 7-bit Hamming codeword."""
|
|
23
|
+
|
|
24
|
+
data: int
|
|
25
|
+
"""The 4 recovered data bits, packed as d1d2d3d4 -> value 0-15."""
|
|
26
|
+
|
|
27
|
+
error_position: int
|
|
28
|
+
"""1-indexed position of the corrected bit, or 0 if no error was found."""
|
|
29
|
+
|
|
30
|
+
corrected: bool
|
|
31
|
+
"""True if a single-bit error was detected (and corrected)."""
|
|
32
|
+
|
|
33
|
+
codeword: int
|
|
34
|
+
"""The (possibly corrected) 7-bit codeword."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _bit_at(codeword: int, position_1_indexed: int) -> int:
|
|
38
|
+
# Bit 1 (1-indexed) is stored as the MSB of a 7-bit value.
|
|
39
|
+
shift = 7 - position_1_indexed
|
|
40
|
+
return (codeword >> shift) & 1
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _with_bit(codeword: int, position_1_indexed: int, bit: int) -> int:
|
|
44
|
+
shift = 7 - position_1_indexed
|
|
45
|
+
if bit:
|
|
46
|
+
return codeword | (1 << shift)
|
|
47
|
+
return codeword & ~(1 << shift)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def encode_hamming(data: int) -> int:
|
|
51
|
+
"""Encodes 4 data bits (0-15, interpreted as d1 d2 d3 d4 MSB-first) into
|
|
52
|
+
a 7-bit Hamming codeword."""
|
|
53
|
+
if data < 0 or data > 0b1111:
|
|
54
|
+
raise ValueError("encode_hamming expects a 4-bit value (0-15)")
|
|
55
|
+
|
|
56
|
+
d1 = (data >> 3) & 1
|
|
57
|
+
d2 = (data >> 2) & 1
|
|
58
|
+
d3 = (data >> 1) & 1
|
|
59
|
+
d4 = data & 1
|
|
60
|
+
|
|
61
|
+
codeword = 0
|
|
62
|
+
codeword = _with_bit(codeword, 3, d1)
|
|
63
|
+
codeword = _with_bit(codeword, 5, d2)
|
|
64
|
+
codeword = _with_bit(codeword, 6, d3)
|
|
65
|
+
codeword = _with_bit(codeword, 7, d4)
|
|
66
|
+
|
|
67
|
+
p1 = d1 ^ d2 ^ d4 # covers 1,3,5,7
|
|
68
|
+
p2 = d1 ^ d3 ^ d4 # covers 2,3,6,7
|
|
69
|
+
p3 = d2 ^ d3 ^ d4 # covers 4,5,6,7
|
|
70
|
+
|
|
71
|
+
codeword = _with_bit(codeword, 1, p1)
|
|
72
|
+
codeword = _with_bit(codeword, 2, p2)
|
|
73
|
+
codeword = _with_bit(codeword, 4, p3)
|
|
74
|
+
|
|
75
|
+
return codeword
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def decode_hamming(codeword: int) -> HammingDecodeResult:
|
|
79
|
+
"""Decodes a 7-bit Hamming codeword, detecting and correcting a
|
|
80
|
+
single-bit error if present."""
|
|
81
|
+
if codeword < 0 or codeword > 0b1111111:
|
|
82
|
+
raise ValueError("decode_hamming expects a 7-bit value (0-127)")
|
|
83
|
+
|
|
84
|
+
bits = [0] + [_bit_at(codeword, i) for i in range(1, 8)]
|
|
85
|
+
|
|
86
|
+
c1 = bits[1] ^ bits[3] ^ bits[5] ^ bits[7]
|
|
87
|
+
c2 = bits[2] ^ bits[3] ^ bits[6] ^ bits[7]
|
|
88
|
+
c3 = bits[4] ^ bits[5] ^ bits[6] ^ bits[7]
|
|
89
|
+
|
|
90
|
+
error_position = (c1 << 0) | (c2 << 1) | (c3 << 2)
|
|
91
|
+
|
|
92
|
+
corrected = False
|
|
93
|
+
fixed_codeword = codeword
|
|
94
|
+
if error_position != 0:
|
|
95
|
+
bad_bit = _bit_at(codeword, error_position)
|
|
96
|
+
fixed_codeword = _with_bit(codeword, error_position, bad_bit ^ 1)
|
|
97
|
+
corrected = True
|
|
98
|
+
|
|
99
|
+
d1 = _bit_at(fixed_codeword, 3)
|
|
100
|
+
d2 = _bit_at(fixed_codeword, 5)
|
|
101
|
+
d3 = _bit_at(fixed_codeword, 6)
|
|
102
|
+
d4 = _bit_at(fixed_codeword, 7)
|
|
103
|
+
data = (d1 << 3) | (d2 << 2) | (d3 << 1) | d4
|
|
104
|
+
|
|
105
|
+
return HammingDecodeResult(
|
|
106
|
+
data=data,
|
|
107
|
+
error_position=error_position,
|
|
108
|
+
corrected=corrected,
|
|
109
|
+
codeword=fixed_codeword,
|
|
110
|
+
)
|
bitlab/parity/parity.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Parity bit generation, appending, and checking for integers, sequences,
|
|
2
|
+
and strings."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import List, Literal, Sequence
|
|
7
|
+
|
|
8
|
+
from ..bitutils import flip_bit, get_bit, popcount, random_bit_position, set_bit
|
|
9
|
+
|
|
10
|
+
ParityType = Literal["even", "odd"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_parity_bit(value: int, type: ParityType = "even", bit_width: int = 8) -> int:
|
|
14
|
+
"""Computes the parity bit needed so that `value`'s 1-bit count (over
|
|
15
|
+
`bit_width` bits) plus the parity bit satisfies the requested parity type.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
value: The data value to compute parity for.
|
|
19
|
+
type: 'even' (default) or 'odd'.
|
|
20
|
+
bit_width: Number of bits of `value` to consider (default 8).
|
|
21
|
+
"""
|
|
22
|
+
mask = (1 << bit_width) - 1
|
|
23
|
+
ones = popcount(value & mask)
|
|
24
|
+
is_odd = ones % 2 == 1
|
|
25
|
+
|
|
26
|
+
if type == "even":
|
|
27
|
+
return 1 if is_odd else 0
|
|
28
|
+
return 0 if is_odd else 1
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def append_parity(value: int, type: ParityType = "even", bit_width: int = 8) -> int:
|
|
32
|
+
"""Appends a parity bit to `value` as the most-significant bit (at
|
|
33
|
+
position `bit_width`), returning the combined (bit_width + 1)-bit value.
|
|
34
|
+
|
|
35
|
+
Example: append_parity(0b1011, 'even', 4) -> 0b11011 (parity bit = 1)
|
|
36
|
+
"""
|
|
37
|
+
parity_bit = get_parity_bit(value, type, bit_width)
|
|
38
|
+
return set_bit(value, bit_width, parity_bit)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def check_parity(
|
|
42
|
+
value_with_parity: int, type: ParityType = "even", bit_width: int = 8
|
|
43
|
+
) -> bool:
|
|
44
|
+
"""Checks whether `value_with_parity` (a value that includes its parity
|
|
45
|
+
bit at position `bit_width`) is internally consistent with the requested
|
|
46
|
+
parity type. Returns False if a (single-bit) error is detected.
|
|
47
|
+
"""
|
|
48
|
+
data_mask = (1 << bit_width) - 1
|
|
49
|
+
data = value_with_parity & data_mask
|
|
50
|
+
parity_bit = get_bit(value_with_parity, bit_width)
|
|
51
|
+
expected = get_parity_bit(data, type, bit_width)
|
|
52
|
+
return parity_bit == expected
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def generate_parity_array(
|
|
56
|
+
values: Sequence[int], type: ParityType = "even", bit_width: int = 8
|
|
57
|
+
) -> List[int]:
|
|
58
|
+
"""Generates a parity bit for each value (bit_width-wide) in a sequence."""
|
|
59
|
+
return [get_parity_bit(v, type, bit_width) for v in values]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_parity_array(
|
|
63
|
+
values_with_parity: Sequence[int], type: ParityType = "even", bit_width: int = 8
|
|
64
|
+
) -> List[bool]:
|
|
65
|
+
"""Checks parity for a sequence of values that already include their
|
|
66
|
+
parity bit (see append_parity). Returns a list of booleans, one per input
|
|
67
|
+
value."""
|
|
68
|
+
return [check_parity(v, type, bit_width) for v in values_with_parity]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def compute_message_parity(message: str, type: ParityType = "even") -> int:
|
|
72
|
+
"""Computes a single overall parity bit for a whole message (string),
|
|
73
|
+
based on the total 1-bit count across all UTF-8 code units. Useful as a
|
|
74
|
+
cheap, whole-message integrity check (NOT a substitute for a real
|
|
75
|
+
checksum/CRC on anything that matters).
|
|
76
|
+
"""
|
|
77
|
+
ones = sum(popcount(b) for b in message.encode("utf-8"))
|
|
78
|
+
is_odd = ones % 2 == 1
|
|
79
|
+
if type == "even":
|
|
80
|
+
return 1 if is_odd else 0
|
|
81
|
+
return 0 if is_odd else 1
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def flip_random_bit(value: int, bit_width: int = 8) -> int:
|
|
85
|
+
"""Flips a random bit within the lowest `bit_width` bits of `value`.
|
|
86
|
+
Handy for simulating a single-bit transmission error when testing
|
|
87
|
+
parity/Hamming code."""
|
|
88
|
+
position = random_bit_position(bit_width)
|
|
89
|
+
return flip_bit(value, position)
|
bitlab/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bitlab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A toolkit for embedded systems, binary data, and digital communications: parity, Hamming codes, and CRC — with C code export.
|
|
5
|
+
Project-URL: Homepage, https://github.com/KenKambi/bitlab
|
|
6
|
+
Project-URL: Issues, https://github.com/KenKambi/bitlab/issues
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: bit-manipulation,c-code-generation,crc,crc16,crc32,crc8,embedded,error-correction,error-detection,hamming-code,parity,serial,uart
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Communications
|
|
15
|
+
Classifier: Topic :: Education
|
|
16
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: build; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: twine; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# bitlab
|
|
26
|
+
|
|
27
|
+
[](https://github.com/KenKambi/bitlab/actions/workflows/ci.yml)
|
|
28
|
+
[](https://pypi.org/project/bitlab/)
|
|
29
|
+
|
|
30
|
+
A toolkit for embedded systems, binary data, and digital communications.
|
|
31
|
+
|
|
32
|
+
Built around the topics an Electronic & Computer Engineering degree
|
|
33
|
+
actually covers: digital logic, computer architecture, embedded systems,
|
|
34
|
+
and communications. Each domain is a self-contained submodule, so the
|
|
35
|
+
package can keep growing without becoming a junk drawer.
|
|
36
|
+
|
|
37
|
+
- Zero dependencies
|
|
38
|
+
- Type-hinted (PEP 561, ships a `py.typed` marker)
|
|
39
|
+
- One CLI: `bitlab`
|
|
40
|
+
- Production functions **and** step-by-step educational explanations, side by side
|
|
41
|
+
- CRC configs export directly to compiled, table-driven C — the same table your Python code uses
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
bitlab/
|
|
45
|
+
├── bitutils/ # foundational bit ops used by every other submodule
|
|
46
|
+
├── parity/ # parity bit checking + Hamming(7,4) error correction
|
|
47
|
+
└── crc/ # CRC-8/16/32, generic engine, explain(), export_c()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install bitlab
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## `bitlab.crc` — CRC generation, checking, explanation, and C export
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from bitlab.crc import crc8, crc16, crc32
|
|
60
|
+
|
|
61
|
+
crc8(b"hello") # CRC-8/SMBUS
|
|
62
|
+
crc16(b"hello") # CRC-16/CCITT-FALSE (default)
|
|
63
|
+
crc16(b"hello", variant="modbus") # CRC-16/MODBUS
|
|
64
|
+
crc32(b"hello") # CRC-32/ISO-HDLC (Ethernet, zlib, PNG)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Every built-in preset is verified against its published CRC-catalogue check
|
|
68
|
+
value (the CRC of `b"123456789"`) in the test suite — these aren't "a CRC
|
|
69
|
+
that happens to run", they match what real hardware and protocols expect.
|
|
70
|
+
|
|
71
|
+
### Custom polynomials
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from bitlab.crc import crc, CRCConfig
|
|
75
|
+
|
|
76
|
+
my_crc = CRCConfig(
|
|
77
|
+
name="my-custom-crc",
|
|
78
|
+
width=16, poly=0x8005, init=0xFFFF,
|
|
79
|
+
refin=True, refout=True, xorout=0x0000,
|
|
80
|
+
check=0x4B37, # CRC of b"123456789", for self-testing your config
|
|
81
|
+
)
|
|
82
|
+
crc(b"some data", my_crc)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Step-by-step explanation (the same computation, narrated)
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from bitlab.crc import explain, CRC8_SMBUS
|
|
89
|
+
|
|
90
|
+
print(explain(b"AB", CRC8_SMBUS))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
CRC algorithm: CRC-8/SMBUS
|
|
95
|
+
width=8 poly=0x07 init=0x00 refin=False refout=False xorout=0x00
|
|
96
|
+
|
|
97
|
+
Input bytes (2 total): 41 42
|
|
98
|
+
|
|
99
|
+
Initial register: 0x00
|
|
100
|
+
|
|
101
|
+
Byte 0: 0x41 -> XORed in, register = 0x41
|
|
102
|
+
bit 0: MSB=0 -> shift left -> 0x82
|
|
103
|
+
bit 1: MSB=1 -> shift left, XOR polynomial -> 0x03
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
Final CRC: 0x87
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`explain()` runs the real bitwise algorithm with a narration attached — it's
|
|
110
|
+
not a separate simplified explanation, so what you read is guaranteed to
|
|
111
|
+
match what `crc8`/`crc16`/`crc32` actually compute.
|
|
112
|
+
|
|
113
|
+
### Export to C
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from bitlab.crc import export_c, CRC32_ISO_HDLC
|
|
117
|
+
|
|
118
|
+
print(export_c(CRC32_ISO_HDLC, function_name="my_crc32"))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```c
|
|
122
|
+
#include <stdint.h>
|
|
123
|
+
#include <stddef.h>
|
|
124
|
+
|
|
125
|
+
static const uint32_t my_crc32_table[256] = {
|
|
126
|
+
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, ...
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
uint32_t my_crc32(const uint8_t *data, size_t len)
|
|
130
|
+
{
|
|
131
|
+
uint32_t crc = 0xFFFFFFFFU;
|
|
132
|
+
for (size_t i = 0; i < len; i++) {
|
|
133
|
+
uint8_t idx = (uint8_t)(crc ^ data[i]);
|
|
134
|
+
crc = (crc >> 8) ^ my_crc32_table[idx];
|
|
135
|
+
}
|
|
136
|
+
return (crc ^ 0xFFFFFFFFU);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The exported table is built by the exact same code Python's fast path
|
|
141
|
+
uses — the test suite actually compiles the generated C with `gcc` and
|
|
142
|
+
checks the output against the CRC catalogue, so this isn't just
|
|
143
|
+
"code that looks right."
|
|
144
|
+
|
|
145
|
+
## `bitlab.parity` — parity bits and Hamming(7,4)
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from bitlab.parity import get_parity_bit, append_parity, check_parity
|
|
149
|
+
|
|
150
|
+
get_parity_bit(0b1011, "even", 4) # -> 1
|
|
151
|
+
append_parity(0b1011, "even", 4) # -> 0b11011
|
|
152
|
+
check_parity(0b11011, "even", 4) # -> True
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from bitlab.parity import encode_hamming, decode_hamming
|
|
157
|
+
|
|
158
|
+
codeword = encode_hamming(0b1101)
|
|
159
|
+
corrupted = codeword ^ 0b0000100 # simulate a single-bit flip
|
|
160
|
+
result = decode_hamming(corrupted)
|
|
161
|
+
# HammingDecodeResult(data=13, error_position=6, corrected=True, codeword=...)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
See the full parity/Hamming API reference at the bottom of this file.
|
|
165
|
+
|
|
166
|
+
## `bitlab.bitutils` — shared low-level bit helpers
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from bitlab.bitutils import popcount, reflect, rotate_left, rotate_right
|
|
170
|
+
|
|
171
|
+
popcount(0b1011) # -> 3
|
|
172
|
+
reflect(0b1100, 4) # -> 0b0011 (bit-mirror, used internally by crc)
|
|
173
|
+
rotate_left(0b10000001, 1, 8) # -> 0b00000011
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## CLI
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
bitlab parity bit 0b1011 --type even --width 4
|
|
180
|
+
# -> 1
|
|
181
|
+
|
|
182
|
+
bitlab hamming encode 13
|
|
183
|
+
# -> 85 (0b1010101)
|
|
184
|
+
|
|
185
|
+
bitlab crc compute "123456789" --preset crc32
|
|
186
|
+
# -> 0xCBF43926 (3421780262)
|
|
187
|
+
|
|
188
|
+
bitlab crc explain "AB" --preset crc8
|
|
189
|
+
# -> step-by-step trace
|
|
190
|
+
|
|
191
|
+
bitlab crc export-c --preset crc32 --name my_crc32
|
|
192
|
+
# -> C source on stdout
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Run `bitlab --help`, `bitlab crc --help`, etc. for full usage.
|
|
196
|
+
|
|
197
|
+
## API reference
|
|
198
|
+
|
|
199
|
+
### `bitlab.crc`
|
|
200
|
+
|
|
201
|
+
| Function | Description |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `crc8(data)` | CRC-8/SMBUS. |
|
|
204
|
+
| `crc16(data, variant="ccitt")` | CRC-16/CCITT-FALSE or CRC-16/MODBUS. |
|
|
205
|
+
| `crc32(data)` | CRC-32/ISO-HDLC (Ethernet/zlib/PNG). |
|
|
206
|
+
| `crc(data, config)` | Generic entry point. `config` is a `CRCConfig` or a preset name string. |
|
|
207
|
+
| `explain(data, config, max_bytes=4)` | Step-by-step trace of the computation. |
|
|
208
|
+
| `export_c(config, function_name=None)` | Table-driven C99 source implementing `config`. |
|
|
209
|
+
| `build_table(config)` | The 256-entry lookup table (also used internally and by `export_c`). |
|
|
210
|
+
| `compute_bitwise(data, config)` | Canonical bit-by-bit reference implementation. |
|
|
211
|
+
| `compute_table(data, config, table=None)` | Fast table-driven implementation. |
|
|
212
|
+
| `self_test(config)` | Verifies a config against its `check` value. |
|
|
213
|
+
| `CRCConfig` | Dataclass: `name, width, poly, init, refin, refout, xorout, check`. |
|
|
214
|
+
| `CRC8_SMBUS`, `CRC16_CCITT_FALSE`, `CRC16_MODBUS`, `CRC32_ISO_HDLC` | Built-in preset configs. |
|
|
215
|
+
|
|
216
|
+
### `bitlab.parity`
|
|
217
|
+
|
|
218
|
+
| Function | Description |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `get_parity_bit(value, type="even", bit_width=8)` | Computes the parity bit for `value`. |
|
|
221
|
+
| `append_parity(value, type="even", bit_width=8)` | Returns `value` with a parity bit appended as bit `bit_width`. |
|
|
222
|
+
| `check_parity(value_with_parity, type="even", bit_width=8)` | Returns `True` if the parity bit matches the data. |
|
|
223
|
+
| `generate_parity_array(values, type="even", bit_width=8)` | Parity bit for each value in a sequence. |
|
|
224
|
+
| `check_parity_array(values_with_parity, type="even", bit_width=8)` | Parity check for each value in a sequence. |
|
|
225
|
+
| `compute_message_parity(message, type="even")` | Single overall parity bit for a whole string. |
|
|
226
|
+
| `flip_random_bit(value, bit_width=8)` | Flips one random bit — useful for simulating errors in tests. |
|
|
227
|
+
| `encode_hamming(data)` | Encodes a 4-bit value (0–15) into a 7-bit Hamming codeword. |
|
|
228
|
+
| `decode_hamming(codeword)` | Decodes a 7-bit codeword, correcting a single-bit error if present. |
|
|
229
|
+
|
|
230
|
+
### `bitlab.bitutils`
|
|
231
|
+
|
|
232
|
+
| Function | Description |
|
|
233
|
+
|---|---|
|
|
234
|
+
| `popcount(n)` | Number of 1-bits. |
|
|
235
|
+
| `has_odd_parity(n)` | True if `n` has an odd number of 1-bits. |
|
|
236
|
+
| `get_bit` / `set_bit` / `flip_bit` | Single-bit read/write/toggle. |
|
|
237
|
+
| `reflect(value, width)` | Bit-mirror the lowest `width` bits. |
|
|
238
|
+
| `rotate_left` / `rotate_right` | Bitwise rotation within a fixed-width register. |
|
|
239
|
+
|
|
240
|
+
## Roadmap
|
|
241
|
+
|
|
242
|
+
Planned next:
|
|
243
|
+
|
|
244
|
+
- **Register field mapper** — define a hardware register's bit layout in
|
|
245
|
+
Python, pack/unpack values, export to C `#define`s or a bitfield struct.
|
|
246
|
+
- **Computer architecture toolkit** — IEEE 754 float deconstruction,
|
|
247
|
+
endianness swapping, Gray code, Q-format fixed-point conversion.
|
|
248
|
+
- **COBS framing** — Consistent Overhead Byte Stuffing for serial protocols.
|
|
249
|
+
- **Reed-Solomon** — burst error correction (QR codes, satellite comms) —
|
|
250
|
+
planned as a later, dedicated release given its complexity.
|
|
251
|
+
|
|
252
|
+
## Development
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
pip install -e ".[dev]"
|
|
256
|
+
pytest # run the test suite (56 tests, including compiling
|
|
257
|
+
# and running the generated C against gcc)
|
|
258
|
+
python -m build # produce a wheel + sdist in dist/
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full dev workflow and release
|
|
262
|
+
process, and [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
bitlab/__init__.py,sha256=Y9cj8jKfiQDXFAFLWTmR4wb1dwJs-qL-teUARnqRAuc,616
|
|
2
|
+
bitlab/cli.py,sha256=lJDBOYguT4LKT9yIch3lyrYILAAqWai17ubhPWoBaKw,6005
|
|
3
|
+
bitlab/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
bitlab/bitutils/__init__.py,sha256=eUQrAL2utxzy_3XzeT1O4tSZLrgaQQouUEvWeSYEiJ4,427
|
|
5
|
+
bitlab/bitutils/core.py,sha256=rTNfPF7uaJw6wNOnXnSZwp3xPnOaqormMiQCtQA2kHg,2214
|
|
6
|
+
bitlab/crc/__init__.py,sha256=E7A21HYGLT2ckhABUgxkss1OIzSWoGXe7VGGzpa98Xk,1217
|
|
7
|
+
bitlab/crc/codegen.py,sha256=iXOvGEGMpr3rOpF22EdqpZ25mCwq29Zg-lGeS_obtAQ,3339
|
|
8
|
+
bitlab/crc/engine.py,sha256=OCUfAkblvpXHmA5Tfe0T5qhfWgwdztITUulHwlKr7AI,4611
|
|
9
|
+
bitlab/crc/explain.py,sha256=lIwCv-McnDnMCBWgCaY6LeBzyk2Jzotyd73kObScY4M,2952
|
|
10
|
+
bitlab/crc/functions.py,sha256=s3kDPhMS--qqA6aFpN4tyLCp8N3BsNAkQ6sAFWaAcx8,1665
|
|
11
|
+
bitlab/crc/presets.py,sha256=M84YVRAlzpGudKBQavYJombtKosTsfYG6fxeeiNjmk4,2479
|
|
12
|
+
bitlab/parity/__init__.py,sha256=aUjmhVdLDr6uzlAOgQOeCG4xmpDM4Fgr2IQBqfgTqGY,618
|
|
13
|
+
bitlab/parity/hamming.py,sha256=VB5HROfOI4S99q-ByqC8XyYsahZ3CdYG-sRpxUHyadQ,3257
|
|
14
|
+
bitlab/parity/parity.py,sha256=wa_ChuLzJ0BGGrMxte9CiQJyIP6jMfDcmXApV83lwX0,3342
|
|
15
|
+
bitlab-0.1.0.dist-info/METADATA,sha256=dcu-x1B_6W6ekWPovY7_7iGFmLaWjjOV3duaJCwsHfc,9254
|
|
16
|
+
bitlab-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
17
|
+
bitlab-0.1.0.dist-info/entry_points.txt,sha256=2kxt8uDPirHOjiI3pKConKVOedj9jfQDl0XbHOD2_yg,43
|
|
18
|
+
bitlab-0.1.0.dist-info/licenses/LICENSE,sha256=3F_ETwL5bPb1_XAbkPVss05-xZbSWuBf0f4tsiQld_g,1066
|
|
19
|
+
bitlab-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ken Kambi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|