Skip to main content

cadmpeg_core/
bytes.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Shared byte-order assembly and byte-slice search over decode data.
3//!
4//! Empty needles are never a match. That matches the codec helpers this
5//! module replaces and avoids `memchr`'s empty-needle-at-zero behavior.
6
7/// Assemble a 16-bit little-endian integer from an exact byte array.
8pub const fn assemble_u16_le(bytes: [u8; 2]) -> u16 {
9    // endian-exception: reconstructed-scalar
10    u16::from_le_bytes(bytes)
11}
12
13/// Assemble a 16-bit big-endian integer from an exact byte array.
14pub const fn assemble_u16_be(bytes: [u8; 2]) -> u16 {
15    // endian-exception: reconstructed-scalar
16    u16::from_be_bytes(bytes)
17}
18
19/// Assemble a 24-bit little-endian integer from an exact byte array.
20pub const fn assemble_u24_le(bytes: [u8; 3]) -> u32 {
21    // endian-exception: reconstructed-scalar
22    u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0])
23}
24
25/// Assemble a 24-bit big-endian integer from an exact byte array.
26pub const fn assemble_u24_be(bytes: [u8; 3]) -> u32 {
27    // endian-exception: reconstructed-scalar
28    u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]])
29}
30
31/// Assemble a 32-bit little-endian integer from an exact byte array.
32pub const fn assemble_u32_le(bytes: [u8; 4]) -> u32 {
33    // endian-exception: reconstructed-scalar
34    u32::from_le_bytes(bytes)
35}
36
37/// Assemble a 32-bit big-endian integer from an exact byte array.
38pub const fn assemble_u32_be(bytes: [u8; 4]) -> u32 {
39    // endian-exception: reconstructed-scalar
40    u32::from_be_bytes(bytes)
41}
42
43/// Assemble a 64-bit little-endian integer from an exact byte array.
44pub const fn assemble_u64_le(bytes: [u8; 8]) -> u64 {
45    // endian-exception: reconstructed-scalar
46    u64::from_le_bytes(bytes)
47}
48
49/// Assemble a 64-bit big-endian integer from an exact byte array.
50pub const fn assemble_u64_be(bytes: [u8; 8]) -> u64 {
51    // endian-exception: reconstructed-scalar
52    u64::from_be_bytes(bytes)
53}
54
55/// Assemble an IEEE-754 binary32 value from exact little-endian bytes.
56pub const fn assemble_f32_le(bytes: [u8; 4]) -> f32 {
57    f32::from_bits(assemble_u32_le(bytes))
58}
59
60/// Assemble an IEEE-754 binary32 value from exact big-endian bytes.
61pub const fn assemble_f32_be(bytes: [u8; 4]) -> f32 {
62    f32::from_bits(assemble_u32_be(bytes))
63}
64
65/// Assemble an IEEE-754 binary64 value from exact little-endian bytes.
66pub const fn assemble_f64_le(bytes: [u8; 8]) -> f64 {
67    f64::from_bits(assemble_u64_le(bytes))
68}
69
70/// Assemble an IEEE-754 binary64 value from exact big-endian bytes.
71pub const fn assemble_f64_be(bytes: [u8; 8]) -> f64 {
72    f64::from_bits(assemble_u64_be(bytes))
73}
74
75/// First offset of `needle` in `haystack`, or `None` when `needle` is empty.
76pub fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
77    if needle.is_empty() {
78        return None;
79    }
80    memchr::memmem::find(haystack, needle)
81}
82
83/// First offset of `needle` at or after `from`, or `None` when `needle` is empty.
84pub fn find_from(haystack: &[u8], needle: &[u8], from: usize) -> Option<usize> {
85    let tail = haystack.get(from..)?;
86    find(tail, needle).map(|relative| from + relative)
87}
88
89/// First offset of `needle` in `haystack[start..end]`, returned as an absolute offset.
90pub fn find_in(haystack: &[u8], needle: &[u8], start: usize, end: usize) -> Option<usize> {
91    let window = haystack.get(start..end)?;
92    find(window, needle).map(|relative| start + relative)
93}
94
95/// Whether `needle` occurs in `haystack`. Empty needles are absent.
96pub fn contains(haystack: &[u8], needle: &[u8]) -> bool {
97    find(haystack, needle).is_some()
98}
99
100/// Offsets of every non-overlapping occurrence of `needle`.
101///
102/// An empty needle yields no offsets.
103pub fn find_iter<'a>(haystack: &'a [u8], needle: &'a [u8]) -> impl Iterator<Item = usize> + 'a {
104    let search = if needle.is_empty() {
105        None
106    } else {
107        Some(memchr::memmem::find_iter(haystack, needle))
108    };
109    search.into_iter().flatten()
110}
111
112#[cfg(test)]
113mod tests {
114    use super::{contains, find, find_from, find_in, find_iter};
115
116    #[test]
117    fn assembles_wire_byte_orders_without_host_endian_assumptions() {
118        assert_eq!(super::assemble_u16_le([0x02, 0x01]), 0x0102);
119        assert_eq!(super::assemble_u24_be([0x01, 0x02, 0x03]), 0x0001_0203);
120        assert_eq!(
121            super::assemble_u32_be([0x01, 0x02, 0x03, 0x04]),
122            0x0102_0304
123        );
124        assert_eq!(super::assemble_u64_le([1, 0, 0, 0, 0, 0, 0, 0]), 1);
125        assert_eq!(super::assemble_f32_be([0x3f, 0xc0, 0, 0]), 1.5);
126        assert_eq!(super::assemble_f64_le([0, 0, 0, 0, 0, 0, 0xf0, 0x3f]), 1.0);
127    }
128
129    #[test]
130    fn empty_needle_is_never_a_match() {
131        assert_eq!(find(b"abc", b""), None);
132        assert_eq!(find_from(b"abc", b"", 1), None);
133        assert_eq!(find_in(b"abc", b"", 0, 3), None);
134        assert!(!contains(b"abc", b""));
135        assert_eq!(
136            find_iter(b"abc", b"").collect::<Vec<_>>(),
137            Vec::<usize>::new()
138        );
139    }
140
141    #[test]
142    fn finds_absolute_and_ranged_offsets() {
143        let haystack = b"xxabcxxabc";
144        assert_eq!(find(haystack, b"abc"), Some(2));
145        assert_eq!(find_from(haystack, b"abc", 3), Some(7));
146        assert_eq!(find_in(haystack, b"abc", 3, 10), Some(7));
147        assert_eq!(find_in(haystack, b"abc", 3, 6), None);
148        assert!(contains(haystack, b"abc"));
149        assert_eq!(find_iter(haystack, b"abc").collect::<Vec<_>>(), [2, 7]);
150    }
151}