Skip to main content

protocache_core/
utils.rs

1//! Utility surface matching `utils.h`.
2
3use core::mem;
4
5pub type Words<'a> = &'a [u32];
6pub type Bytes<'a> = &'a [u8];
7pub type EnumValue = i32;
8
9#[inline(always)]
10pub const fn word_size(size: usize) -> usize {
11    size.div_ceil(4)
12}
13
14pub trait Scalar: Copy + Sized {
15    const WIDTH: usize;
16    fn from_words(words: &[u32]) -> Option<Self>;
17    fn write_words(self, words: &mut [u32]) -> Option<()>;
18}
19
20macro_rules! impl_scalar {
21    ($($ty:ty),* $(,)?) => {
22        $(
23            impl Scalar for $ty {
24                const WIDTH: usize = word_size(mem::size_of::<$ty>());
25
26                #[inline(always)]
27                fn from_words(words: &[u32]) -> Option<Self> {
28                    if words.len() != Self::WIDTH {
29                        return None;
30                    }
31                    let mut bytes = [0u8; mem::size_of::<$ty>()];
32                    let raw = unsafe {
33                        core::slice::from_raw_parts(
34                            words.as_ptr().cast::<u8>(),
35                            words.len() * mem::size_of::<u32>(),
36                        )
37                    };
38                    bytes.copy_from_slice(raw.get(..mem::size_of::<$ty>())?);
39                    Some(<$ty>::from_le_bytes(bytes))
40                }
41
42                #[inline(always)]
43                fn write_words(self, words: &mut [u32]) -> Option<()> {
44                    if words.len() != Self::WIDTH {
45                        return None;
46                    }
47                    words.fill(0);
48                    let raw = unsafe {
49                        core::slice::from_raw_parts_mut(
50                            words.as_mut_ptr().cast::<u8>(),
51                            words.len() * mem::size_of::<u32>(),
52                        )
53                    };
54                    raw.get_mut(..mem::size_of::<$ty>())?
55                        .copy_from_slice(&self.to_le_bytes());
56                    Some(())
57                }
58            }
59        )*
60    };
61}
62
63impl_scalar!(u32, i32, u64, i64, f32, f64);
64
65impl Scalar for bool {
66    const WIDTH: usize = 1;
67
68    #[inline(always)]
69    fn from_words(words: &[u32]) -> Option<Self> {
70        Some(u32::from_words(words)? != 0)
71    }
72
73    #[inline(always)]
74    fn write_words(self, words: &mut [u32]) -> Option<()> {
75        if words.len() != Self::WIDTH {
76            return None;
77        }
78        words[0] = u32::from(self);
79        Some(())
80    }
81}
82
83#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub enum CorruptionKind {
85    Truncated,
86    InvalidHeader,
87    InvalidUtf8,
88    IntegerOverflow,
89}
90
91#[derive(Clone, Copy, Debug, Eq, PartialEq)]
92pub struct ReadError {
93    pub kind: CorruptionKind,
94}
95
96impl ReadError {
97    pub const fn new(kind: CorruptionKind) -> Self {
98        Self { kind }
99    }
100}
101
102impl core::fmt::Display for ReadError {
103    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        write!(f, "{:?}", self.kind)
105    }
106}
107
108impl std::error::Error for ReadError {}
109
110#[derive(Clone, Debug, Default)]
111pub struct Buffer {
112    data: Vec<u32>,
113    off: usize,
114}
115
116impl Buffer {
117    #[inline(always)]
118    pub fn new() -> Self {
119        Self::default()
120    }
121
122    #[inline(always)]
123    pub fn with_capacity_words(words: usize) -> Self {
124        Self {
125            data: vec![0; words],
126            off: words,
127        }
128    }
129
130    #[inline(always)]
131    pub fn clear(&mut self) {
132        self.off = self.data.len();
133    }
134
135    #[inline(always)]
136    pub fn allocated_words(&self) -> usize {
137        self.data.len()
138    }
139
140    #[inline(always)]
141    pub fn len(&self) -> usize {
142        self.data.len().saturating_sub(self.off)
143    }
144
145    #[inline(always)]
146    pub fn is_empty(&self) -> bool {
147        self.len() == 0
148    }
149
150    #[inline(always)]
151    pub fn view(&self) -> &[u32] {
152        &self.data[self.off..]
153    }
154
155    #[inline(always)]
156    pub fn head(&self) -> &[u32] {
157        self.view()
158    }
159
160    #[inline(always)]
161    pub fn head_mut(&mut self) -> &mut [u32] {
162        let off = self.off;
163        &mut self.data[off..]
164    }
165
166    #[inline(always)]
167    pub fn at_from_end(&self, words: usize) -> Option<&[u32]> {
168        if words > self.data.len() {
169            return None;
170        }
171        let start = self.data.len() - words;
172        Some(&self.data[start..])
173    }
174
175    #[inline(always)]
176    pub fn at_from_end_mut(&mut self, words: usize) -> Option<&mut [u32]> {
177        if words > self.data.len() {
178            return None;
179        }
180        let start = self.data.len() - words;
181        Some(&mut self.data[start..])
182    }
183
184    #[inline(always)]
185    pub fn reserve_words(&mut self, words: usize) {
186        if words > self.data.len() {
187            self.grow(words);
188        }
189    }
190
191    #[inline(always)]
192    pub fn expand(&mut self, delta: usize) -> &mut [u32] {
193        if self.off < delta {
194            let needed = self.len().saturating_add(delta);
195            self.grow(needed);
196        }
197        self.off -= delta;
198        let ptr = unsafe { self.data.as_mut_ptr().add(self.off) };
199        // `off + delta` is within `data` because we either had capacity or just grew.
200        unsafe { core::slice::from_raw_parts_mut(ptr, delta) }
201    }
202
203    #[inline(always)]
204    pub fn shrink(&mut self, delta: usize) {
205        assert!(delta <= self.len());
206        self.off += delta;
207    }
208
209    #[inline(always)]
210    pub fn put(&mut self, value: u32) {
211        self.expand(1)[0] = value;
212    }
213
214    #[inline(always)]
215    pub fn put_words(&mut self, words: &[u32]) {
216        let dst = self.expand(words.len());
217        // `expand` returned a non-overlapping destination slice with the exact length.
218        unsafe { core::ptr::copy_nonoverlapping(words.as_ptr(), dst.as_mut_ptr(), words.len()) };
219    }
220
221    #[inline(always)]
222    fn grow(&mut self, min_words: usize) {
223        let active_len = self.len();
224        let mut new_words = self.data.len().max(8);
225        while new_words < min_words {
226            new_words = new_words.saturating_mul(2);
227        }
228        if new_words < min_words {
229            new_words = min_words;
230        }
231        let mut new_data = vec![0; new_words];
232        let new_off = new_words - active_len;
233        new_data[new_off..].copy_from_slice(self.view());
234        self.data = new_data;
235        self.off = new_off;
236    }
237}
238
239pub fn load_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Vec<u8>> {
240    std::fs::read(path)
241}
242
243pub fn compress(src: &[u8]) -> Vec<u8> {
244    let mut out = Vec::new();
245    compress_into(src, &mut out);
246    out
247}
248
249pub fn compress_into(src: &[u8], out: &mut Vec<u8>) {
250    if src.is_empty() {
251        out.clear();
252        return;
253    }
254
255    let header_len = varint_len(src.len());
256    let capacity = header_len + src.len() + src.len().div_ceil(14);
257    out.clear();
258    if out.capacity() < capacity {
259        out.reserve(capacity - out.capacity());
260    }
261
262    let mut size = src.len();
263    while (size & !0x7f) != 0 {
264        out.push(0x80 | (size as u8 & 0x7f));
265        size >>= 7;
266    }
267    out.push(size as u8);
268
269    // After the varint header, write the compressed body via a raw pointer so
270    // that the hot loop does not repeatedly reload Vec metadata (data ptr +
271    // length) from memory.  We call set_len once at the end.
272    // SAFETY: we reserved `capacity` bytes above; all writes stay within that.
273    let base = out.len();
274    let mut off = 0usize;
275    let dst = unsafe { out.as_mut_ptr().add(base) };
276
277    let mut pos = 0usize;
278    while pos < src.len() {
279        let a_start = pos;
280        let a = pick_run(src, &mut pos);
281        if pos == src.len() {
282            unsafe {
283                dst.add(off).write(a);
284            }
285            off += 1;
286            if (a & 0x8) == 0 {
287                off = emit_run_raw(src, a_start, pos, dst, off);
288            }
289            break;
290        }
291        let b_start = pos;
292        let b = pick_run(src, &mut pos);
293        unsafe {
294            dst.add(off).write(a | (b << 4));
295        }
296        off += 1;
297        if (a & 0x8) == 0 {
298            off = emit_run_raw(src, a_start, b_start, dst, off);
299        }
300        if (b & 0x8) == 0 {
301            off = emit_run_raw(src, b_start, pos, dst, off);
302        }
303    }
304
305    unsafe {
306        out.set_len(base + off);
307    }
308}
309
310pub fn decompress(src: &[u8]) -> Result<Vec<u8>, ReadError> {
311    let mut out = Vec::new();
312    decompress_into(src, &mut out)?;
313    Ok(out)
314}
315
316pub fn decompress_into(src: &[u8], out: &mut Vec<u8>) -> Result<(), ReadError> {
317    if src.is_empty() {
318        out.clear();
319        return Ok(());
320    }
321    let (target, mut pos) = parse_varint(src)?;
322    // Overallocate by 7 bytes so unpack can safely write 8 bytes at a time
323    // without a per-write bounds check on the destination.  Mirrors the C++
324    // technique of resize(size+7) followed by a final trim.
325    out.resize(target + 7, 0u8);
326    let mut out_pos = 0usize;
327    let buf = out.as_mut_slice();
328    while pos < src.len() {
329        let mark = src[pos];
330        pos += 1;
331        unpack(mark & 0x0f, src, &mut pos, buf, &mut out_pos, target)?;
332        unpack(mark >> 4, src, &mut pos, buf, &mut out_pos, target)?;
333    }
334    if out_pos != target {
335        out.clear();
336        return Err(ReadError::new(CorruptionKind::Truncated));
337    }
338    out.truncate(target);
339    Ok(())
340}
341
342#[inline]
343fn varint_len(mut value: usize) -> usize {
344    let mut len = 1usize;
345    while (value & !0x7f) != 0 {
346        value >>= 7;
347        len += 1;
348    }
349    len
350}
351
352#[inline(always)]
353fn pick_run(src: &[u8], pos: &mut usize) -> u8 {
354    let start = *pos;
355    let first = src[start];
356    *pos += 1;
357    // Same trick as C++: arithmetic right-shift of i8 is true only for 0x00 and 0xff,
358    // letting us handle both special bytes in a single branch instead of two.
359    let fi = first as i8;
360    if fi == fi >> 1 {
361        while *pos < src.len() && *pos - start < 4 && src[*pos] == first {
362            *pos += 1;
363        }
364        // first & 0x4 == 0 for 0x00, 0x4 for 0xff — encodes the fill value
365        0x8 | (first & 0x4) | ((*pos - start - 1) as u8)
366    } else {
367        while *pos < src.len() && *pos - start < 7 && src[*pos] != 0 && src[*pos] != 0xff {
368            *pos += 1;
369        }
370        (*pos - start) as u8
371    }
372}
373
374// Raw-pointer variant used by compress_into to avoid Vec metadata reloads.
375// SAFETY: caller must ensure dst has room for `end - start` bytes at offset `off`.
376#[inline(always)]
377fn emit_run_raw(src: &[u8], start: usize, end: usize, dst: *mut u8, off: usize) -> usize {
378    let len = end - start;
379    if len == 0 {
380        return off;
381    }
382    unsafe {
383        // When the source window has >= 8 bytes left, a single 8-byte word
384        // copy avoids a memcpy function call (mirrors the C++ optimisation).
385        if start + 8 <= src.len() {
386            let src_ptr = src.as_ptr().add(start) as *const u64;
387            (dst.add(off) as *mut u64).write_unaligned(src_ptr.read_unaligned());
388        } else {
389            std::ptr::copy_nonoverlapping(src.as_ptr().add(start), dst.add(off), len);
390        }
391    }
392    off + len
393}
394
395fn parse_varint(src: &[u8]) -> Result<(usize, usize), ReadError> {
396    let mut size = 0usize;
397    let mut pos = 0usize;
398    for shift in (0..32).step_by(7) {
399        let byte = *src
400            .get(pos)
401            .ok_or(ReadError::new(CorruptionKind::Truncated))?;
402        pos += 1;
403        if (byte & 0x80) != 0 {
404            size |= ((byte & 0x7f) as usize) << shift;
405        } else {
406            size |= (byte as usize) << shift;
407            return Ok((size, pos));
408        }
409    }
410    Err(ReadError::new(CorruptionKind::InvalidHeader))
411}
412
413#[inline(always)]
414fn unpack(
415    mark: u8,
416    src: &[u8],
417    pos: &mut usize,
418    out: &mut [u8],
419    out_pos: &mut usize,
420    target: usize,
421) -> Result<(), ReadError> {
422    // out.len() == target + 7; use `target` for logical end.
423    if *out_pos >= target {
424        return Ok(());
425    }
426    if (mark & 0x8) != 0 {
427        let count = ((mark & 0x3) + 1) as usize;
428        if count > target - *out_pos {
429            return Err(ReadError::new(CorruptionKind::Truncated));
430        }
431        // Write 4 bytes at once: out_pos < target, so out_pos + 4 <= target + 3 < target + 7.
432        // SAFETY: out is target+7 bytes, so this is always in bounds.
433        let fill_val: u32 = if (mark & 0x4) != 0 { u32::MAX } else { 0 };
434        unsafe {
435            (out.as_mut_ptr().add(*out_pos) as *mut u32).write_unaligned(fill_val);
436        }
437        *out_pos += count;
438        return Ok(());
439    }
440    let len = (mark & 0x7) as usize;
441    if len == 0 {
442        return Ok(());
443    }
444    if len > src.len() - *pos || len > target - *out_pos {
445        return Err(ReadError::new(CorruptionKind::Truncated));
446    }
447    // Fast path: when the source has >= 8 bytes left, copy 8 bytes at once.
448    // Writing 8 bytes is safe: out_pos <= target - len <= target - 1,
449    // so out_pos + 8 <= target + 7 == out.len().
450    if src.len() - *pos >= 8 {
451        unsafe {
452            let src_ptr = src.as_ptr().add(*pos) as *const u64;
453            let dst_ptr = out.as_mut_ptr().add(*out_pos) as *mut u64;
454            dst_ptr.write_unaligned(src_ptr.read_unaligned());
455        }
456        *pos += len;
457        *out_pos += len;
458    } else {
459        let end = *pos + len;
460        let out_end = *out_pos + len;
461        out[*out_pos..out_end].copy_from_slice(&src[*pos..end]);
462        *pos = end;
463        *out_pos = out_end;
464    }
465    Ok(())
466}
467
468#[cfg(test)]
469mod tests {
470    use super::{Buffer, compress, compress_into, decompress, decompress_into};
471
472    #[test]
473    fn put_prepends_words() {
474        let mut buffer = Buffer::new();
475        buffer.put(3);
476        buffer.put(2);
477        buffer.put(1);
478        assert_eq!(buffer.view(), &[1, 2, 3]);
479    }
480
481    #[test]
482    fn expand_preserves_existing_payload() {
483        let mut buffer = Buffer::with_capacity_words(4);
484        buffer.put_words(&[3, 4]);
485        buffer.put_words(&[1, 2]);
486        assert_eq!(buffer.view(), &[1, 2, 3, 4]);
487
488        buffer.put_words(&[0]);
489        assert_eq!(buffer.view(), &[0, 1, 2, 3, 4]);
490        assert!(buffer.allocated_words() >= 5);
491    }
492
493    #[test]
494    fn shrink_discards_from_front() {
495        let mut buffer = Buffer::new();
496        buffer.put_words(&[1, 2, 3, 4]);
497        buffer.shrink(2);
498        assert_eq!(buffer.view(), &[3, 4]);
499        buffer.clear();
500        assert!(buffer.is_empty());
501    }
502
503    #[test]
504    fn compression_roundtrip_preserves_payload() {
505        let src = b"\0\0\0\0abcd\xff\xff\xffefgh\0";
506        let compressed = compress(src);
507        let restored = decompress(&compressed).unwrap();
508        assert_eq!(restored, src);
509    }
510
511    #[test]
512    fn compression_into_reuses_buffers() {
513        let src = b"\0\0\0\0abcd\xff\xff\xffefgh\0";
514        let mut compressed = Vec::with_capacity(128);
515        let compressed_capacity = compressed.capacity();
516        compress_into(src, &mut compressed);
517        assert_eq!(compressed.capacity(), compressed_capacity);
518
519        let mut restored = Vec::with_capacity(128);
520        let restored_capacity = restored.capacity();
521        decompress_into(&compressed, &mut restored).unwrap();
522        assert_eq!(restored, src);
523        assert_eq!(restored.capacity(), restored_capacity);
524    }
525}