Skip to main content

lora_store/
binary.rs

1//! Segmented binary value support.
2
3use std::hash::{Hash, Hasher};
4
5/// A logical binary/blob value stored as one or more byte segments.
6///
7/// Small values usually have a single segment. Larger values can preserve
8/// producer-side chunking so codecs can write/read length-prefixed segments
9/// without building one large temporary buffer.
10#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
11pub struct LoraBinary {
12    segments: Vec<Vec<u8>>,
13    len: usize,
14}
15
16impl LoraBinary {
17    pub fn from_bytes(bytes: Vec<u8>) -> Self {
18        let len = bytes.len();
19        Self {
20            segments: if bytes.is_empty() {
21                Vec::new()
22            } else {
23                vec![bytes]
24            },
25            len,
26        }
27    }
28
29    pub fn from_segments(segments: Vec<Vec<u8>>) -> Self {
30        let len = segments.iter().map(Vec::len).sum();
31        Self { segments, len }
32    }
33
34    pub fn len(&self) -> usize {
35        self.len
36    }
37
38    pub fn is_empty(&self) -> bool {
39        self.len == 0
40    }
41
42    pub fn segments(&self) -> &[Vec<u8>] {
43        &self.segments
44    }
45
46    pub fn chunks(&self) -> impl Iterator<Item = &[u8]> + '_ {
47        self.segments.iter().map(Vec::as_slice)
48    }
49
50    pub fn into_segments(self) -> Vec<Vec<u8>> {
51        self.segments
52    }
53
54    pub fn to_vec(&self) -> Vec<u8> {
55        let mut out = Vec::with_capacity(self.len);
56        for segment in self.chunks() {
57            out.extend_from_slice(segment);
58        }
59        out
60    }
61}
62
63impl PartialEq for LoraBinary {
64    fn eq(&self, other: &Self) -> bool {
65        self.len == other.len
66            && self
67                .chunks()
68                .flat_map(|segment| segment.iter())
69                .eq(other.chunks().flat_map(|segment| segment.iter()))
70    }
71}
72
73impl Eq for LoraBinary {}
74
75impl Hash for LoraBinary {
76    fn hash<H: Hasher>(&self, state: &mut H) {
77        self.len.hash(state);
78        for segment in self.chunks() {
79            state.write(segment);
80        }
81    }
82}
83
84impl From<Vec<u8>> for LoraBinary {
85    fn from(value: Vec<u8>) -> Self {
86        Self::from_bytes(value)
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use std::collections::hash_map::DefaultHasher;
93    use std::hash::{Hash, Hasher};
94
95    use super::*;
96
97    #[test]
98    fn equality_and_hash_use_logical_bytes_not_chunking() {
99        let contiguous = LoraBinary::from_bytes(vec![1, 2, 3, 4]);
100        let segmented = LoraBinary::from_segments(vec![vec![1, 2], vec![3], vec![4]]);
101
102        assert_eq!(contiguous, segmented);
103        assert_eq!(hash(&contiguous), hash(&segmented));
104    }
105
106    fn hash(value: &LoraBinary) -> u64 {
107        let mut hasher = DefaultHasher::new();
108        value.hash(&mut hasher);
109        hasher.finish()
110    }
111}