Source code for moddef.codec.bytes
# SPDX-License-Identifier: Apache-2.0
"""Register/byte assembly honoring byte order within a word and word order
across words (spec ยง9). Mirrors go/codec and the TS/Rust byte helpers."""
from __future__ import annotations
from collections.abc import Sequence
[docs]
def assemble(regs: Sequence[int], byte_big: bool, word_big: bool) -> bytes:
"""Normalize registers into a big-endian byte stream."""
n = len(regs)
out = bytearray(n * 2)
for i in range(n * 2):
w = i // 2
reg = regs[w if word_big else n - 1 - w] & 0xFFFF
hi = (i % 2 == 0) if byte_big else (i % 2 == 1)
out[i] = (reg >> 8) & 0xFF if hi else reg & 0xFF
return bytes(out)
[docs]
def decode_uint(b: bytes) -> int:
"""Big-endian unsigned integer of the byte stream (arbitrary width)."""
return int.from_bytes(b, "big")
[docs]
def words_from_uint(raw: int, n_words: int, byte_big: bool, word_big: bool) -> list[int]:
"""Split an unsigned integer into registers (inverse of assemble+decode)."""
b = (raw & mask_for(n_words * 16)).to_bytes(n_words * 2, "big")
return words_from_bytes(b, byte_big, word_big)
[docs]
def words_from_bytes(b: bytes, byte_big: bool, word_big: bool) -> list[int]:
"""Write a normalized big-endian byte stream into registers."""
n = (len(b) + 1) // 2
regs = [0] * n
for i in range(n * 2):
byte = b[i] if i < len(b) else 0
w = i // 2
reg = w if word_big else n - 1 - w
hi = (i % 2 == 0) if byte_big else (i % 2 == 1)
regs[reg] |= (byte << 8) if hi else byte
return regs
[docs]
def pad_bytes(b: bytes, total: int) -> bytes:
"""Zero-pad (or truncate) to exactly `total` bytes."""
if len(b) >= total:
return b[:total]
return b + bytes(total - len(b))
[docs]
def last_n(b: bytes, n: int) -> bytes:
return b[-n:] if len(b) > n else b
[docs]
def mask_for(bits: int) -> int:
return (1 << bits) - 1
[docs]
def sign_extend(v: int, bits: int) -> int:
sign = 1 << (bits - 1)
return v - (1 << bits) if v & sign else v