Skip to main content

moddef_core/codec/
bytes.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Register/byte assembly honoring byte order within a word and word order
4//! across words (spec ยง9). Allocation-free: integer paths assemble straight
5//! into a u64; byte paths iterate.
6
7/// Logical byte index -> (register index, take high byte?) after applying
8/// word order across `n` words and byte order within each word.
9#[inline]
10fn locate(i: usize, n: usize, byte_big: bool, word_big: bool) -> (usize, bool) {
11    let w = i / 2;
12    let reg = if word_big { w } else { n - 1 - w };
13    let hi = if byte_big { i % 2 == 0 } else { i % 2 == 1 };
14    (reg, hi)
15}
16
17/// Byte at logical big-endian position `i` of the normalized byte stream.
18#[inline]
19pub fn byte_at(regs: &[u16], i: usize, byte_big: bool, word_big: bool) -> u8 {
20    let (reg, hi) = locate(i, regs.len(), byte_big, word_big);
21    let w = regs[reg];
22    if hi {
23        (w >> 8) as u8
24    } else {
25        (w & 0xff) as u8
26    }
27}
28
29/// Assemble up to the last 8 normalized bytes into a big-endian u64
30/// (equivalent of Go's assemble + decodeUint over the trailing window).
31pub fn assemble_u64(regs: &[u16], byte_big: bool, word_big: bool) -> u64 {
32    let total = regs.len() * 2;
33    let start = total.saturating_sub(8);
34    let mut v: u64 = 0;
35    for i in start..total {
36        v = (v << 8) | byte_at(regs, i, byte_big, word_big) as u64;
37    }
38    v
39}
40
41/// Copy the normalized big-endian byte stream into `out`; returns the byte
42/// count written (`regs.len()*2`, or None if `out` is too small).
43pub fn copy_bytes(regs: &[u16], byte_big: bool, word_big: bool, out: &mut [u8]) -> Option<usize> {
44    let total = regs.len() * 2;
45    if out.len() < total {
46        return None;
47    }
48    for (i, b) in out.iter_mut().enumerate().take(total) {
49        *b = byte_at(regs, i, byte_big, word_big);
50    }
51    Some(total)
52}
53
54/// Split a raw big-endian integer into `out` registers (inverse of
55/// [`assemble_u64`]); `out.len()` defines the width.
56pub fn words_from_u64(raw: u64, byte_big: bool, word_big: bool, out: &mut [u16]) {
57    let n = out.len();
58    for reg in out.iter_mut() {
59        *reg = 0;
60    }
61    let total = n * 2;
62    let mut v = raw;
63    for i in (0..total).rev() {
64        let b = (v & 0xff) as u8;
65        v >>= 8;
66        let (reg, hi) = locate(i, n, byte_big, word_big);
67        if hi {
68            out[reg] |= (b as u16) << 8;
69        } else {
70            out[reg] |= b as u16;
71        }
72    }
73}
74
75/// Write a normalized big-endian byte stream into registers.
76pub fn words_from_bytes(bytes: &[u8], byte_big: bool, word_big: bool, out: &mut [u16]) {
77    let n = out.len();
78    for reg in out.iter_mut() {
79        *reg = 0;
80    }
81    for i in 0..(n * 2) {
82        let b = *bytes.get(i).unwrap_or(&0);
83        let (reg, hi) = locate(i, n, byte_big, word_big);
84        if hi {
85            out[reg] |= (b as u16) << 8;
86        } else {
87            out[reg] |= b as u16;
88        }
89    }
90}
91
92#[inline]
93pub fn mask_for(bits: u32) -> u64 {
94    if bits >= 64 {
95        u64::MAX
96    } else {
97        (1u64 << bits) - 1
98    }
99}
100
101#[inline]
102pub fn sign_extend(v: u64, bits: u32) -> i64 {
103    if bits >= 64 {
104        return v as i64;
105    }
106    let sign = 1u64 << (bits - 1);
107    if v & sign != 0 {
108        (v | !mask_for(bits)) as i64
109    } else {
110        v as i64
111    }
112}