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 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
+ ]
@@ -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)
@@ -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
+ ]
@@ -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
+ )
@@ -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
+ [![CI](https://github.com/KenKambi/bitlab/actions/workflows/ci.yml/badge.svg)](https://github.com/KenKambi/bitlab/actions/workflows/ci.yml)
28
+ [![PyPI](https://img.shields.io/pypi/v/bitlab.svg)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bitlab = bitlab.cli:main
@@ -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.