1use std::{collections::hash_map::RandomState, fmt, hash::BuildHasher, io::Write};
4
5use hashbrown::HashTable;
6
7use crate::{Error, Limits, layout::u32_at};
8
9const MAGIC: &[u8; 8] = b"NIVALUES";
10const HEADER: usize = 12;
11
12pub struct ValuePoolBuilder {
14 limits: Limits,
15 offsets: Vec<u32>,
16 bytes: Vec<u8>,
17 hashes: HashTable<(u64, u32)>,
18 hash_state: RandomState,
19}
20
21impl ValuePoolBuilder {
22 pub fn new(limits: Limits) -> Self {
24 Self {
25 limits,
26 offsets: Vec::new(),
27 bytes: Vec::new(),
28 hashes: HashTable::new(),
29 hash_state: RandomState::new(),
30 }
31 }
32
33 pub fn intern(&mut self, value: &[u8]) -> Result<u32, Error> {
41 let hash = self.hash_state.hash_one(value);
42
43 for &(_, id) in self.hashes.iter_hash(hash) {
44 let start = *self
45 .offsets
46 .get(id as usize)
47 .ok_or(Error::Invalid("value ID"))? as usize;
48 let end = self
49 .offsets
50 .get(id as usize + 1)
51 .map_or(self.bytes.len(), |v| *v as usize);
52
53 if self.bytes.get(start..end) == Some(value) {
54 return Ok(id);
55 }
56 }
57
58 let count = self
59 .offsets
60 .len()
61 .checked_add(1)
62 .ok_or(Error::Limit("shared values"))?;
63 let size = self
64 .bytes
65 .len()
66 .checked_add(value.len())
67 .ok_or(Error::Limit("shared value bytes"))?;
68 check_size(count, size, self.limits)?;
69
70 let id = u32::try_from(self.offsets.len()).map_err(|_| Error::Limit("shared values"))?;
71 let offset =
72 u32::try_from(self.bytes.len()).map_err(|_| Error::Limit("shared value bytes"))?;
73
74 self.offsets.try_reserve(1)?;
75 self.bytes.try_reserve(value.len())?;
76 self.hashes.try_reserve(1, |row| row.0)?;
77
78 self.offsets.push(offset);
79 self.bytes.extend_from_slice(value);
80 self.hashes.insert_unique(hash, (hash, id), |row| row.0);
81
82 Ok(id)
83 }
84
85 pub fn len(&self) -> usize {
87 self.offsets.len()
88 }
89
90 pub fn is_empty(&self) -> bool {
92 self.offsets.is_empty()
93 }
94
95 pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
100 let size = self.encoded_length()?;
101 let mut output = Vec::new();
102 output.try_reserve_exact(size)?;
103 self.write_to(&mut output)?;
104
105 Ok(output)
106 }
107
108 pub fn write_to(self, mut output: impl Write) -> Result<(), Error> {
113 self.encoded_length()?;
114 drop(self.hashes);
115
116 output.write_all(MAGIC)?;
117 output.write_all(&(self.offsets.len() as u32).to_le_bytes())?;
118
119 for offset in self.offsets {
120 output.write_all(&offset.to_le_bytes())?;
121 }
122
123 output.write_all(&(self.bytes.len() as u32).to_le_bytes())?;
124 output.write_all(&self.bytes)?;
125 output.flush()?;
126
127 Ok(())
128 }
129
130 pub(crate) fn encoded_length(&self) -> Result<usize, Error> {
131 check_size(self.offsets.len(), self.bytes.len(), self.limits)
132 }
133}
134
135impl fmt::Debug for ValuePoolBuilder {
136 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137 formatter
138 .debug_struct("ValuePoolBuilder")
139 .field("values", &self.len())
140 .field("value_bytes", &self.bytes.len())
141 .field("limits", &self.limits)
142 .finish()
143 }
144}
145
146#[derive(Clone, Copy)]
148pub struct ValuePool<'a> {
149 offsets: &'a [[u8; 4]],
150 body: &'a [u8],
151}
152
153impl<'a> ValuePool<'a> {
154 pub fn open(bytes: &'a [u8], limits: Limits) -> Result<Self, Error> {
162 if bytes.len() > limits.bytes {
163 return Err(Error::Limit("shared value bytes"));
164 }
165
166 if bytes.get(..8) != Some(MAGIC.as_slice()) {
167 return Err(Error::Invalid("value pool magic"));
168 }
169
170 let count = u32_at(bytes, 8)? as usize;
171 if count > limits.records {
172 return Err(Error::Limit("shared values"));
173 }
174
175 let data = count
176 .checked_add(1)
177 .and_then(|v| v.checked_mul(4))
178 .and_then(|v| v.checked_add(HEADER))
179 .ok_or(Error::Invalid("value offsets"))?;
180 let body = bytes.get(data..).ok_or(Error::Invalid("value offsets"))?;
181
182 if u32_at(bytes, HEADER)? != 0 || u32_at(bytes, data - 4)? as usize != body.len() {
183 return Err(Error::Invalid("value pool length"));
184 }
185
186 let mut previous = 0;
187 for id in 0..count {
188 let next = u32_at(bytes, HEADER + (id + 1) * 4)? as usize;
189 if next < previous || next > body.len() {
190 return Err(Error::Invalid("value offset order"));
191 }
192
193 previous = next;
194 }
195
196 let directory = bytes
197 .get(HEADER..data)
198 .ok_or(Error::Invalid("value offsets"))?;
199 let (offsets, _) = directory.as_chunks::<4>();
200
201 Ok(Self { offsets, body })
202 }
203
204 pub fn len(self) -> usize {
206 self.offsets.len() - 1
207 }
208
209 pub fn is_empty(self) -> bool {
211 self.len() == 0
212 }
213
214 #[inline]
219 pub fn get(self, id: u32) -> Result<&'a [u8], Error> {
220 let id = id as usize;
221 let &[start, end] = self
222 .offsets
223 .get(id..)
224 .and_then(|offsets| offsets.first_chunk::<2>())
225 .ok_or(Error::Invalid("value ID"))?;
226
227 self.body
228 .get(u32::from_le_bytes(start) as usize..u32::from_le_bytes(end) as usize)
229 .ok_or(Error::Invalid("truncated section"))
230 }
231}
232
233impl fmt::Debug for ValuePool<'_> {
234 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
235 formatter
236 .debug_struct("ValuePool")
237 .field("values", &self.len())
238 .field(
239 "pool_bytes",
240 &(HEADER + self.offsets.len() * 4 + self.body.len()),
241 )
242 .finish()
243 }
244}
245
246#[derive(Debug, Clone, Copy)]
248pub struct StringPool<'a> {
249 values: ValuePool<'a>,
250}
251
252impl<'a> StringPool<'a> {
253 pub fn open(bytes: &'a [u8], limits: Limits) -> Result<Self, Error> {
258 let values = ValuePool::open(bytes, limits)?;
259
260 #[cfg(feature = "simd-utf8")]
261 let body = simdutf8::compat::from_utf8(values.body);
262 #[cfg(not(feature = "simd-utf8"))]
263 let body = std::str::from_utf8(values.body);
264
265 let valid = body.is_ok_and(|body| {
266 values
267 .offsets
268 .iter()
269 .all(|offset| body.is_char_boundary(u32::from_le_bytes(*offset) as usize))
270 });
271
272 if !valid {
273 for id in 0..values.len() {
275 std::str::from_utf8(values.get(id as u32)?)?;
276 }
277 }
278
279 Ok(Self { values })
280 }
281
282 pub fn len(self) -> usize {
284 self.values.len()
285 }
286
287 pub fn is_empty(self) -> bool {
289 self.values.is_empty()
290 }
291
292 #[inline]
297 pub fn get(self, id: u32) -> Result<&'a str, Error> {
298 let bytes = self.values.get(id)?;
299
300 Ok(unsafe { std::str::from_utf8_unchecked(bytes) })
303 }
304}
305
306fn check_size(count: usize, bytes: usize, limits: Limits) -> Result<usize, Error> {
307 if count > limits.records || count > u32::MAX as usize {
308 return Err(Error::Limit("shared values"));
309 }
310
311 let size = count
312 .checked_add(1)
313 .and_then(|v| v.checked_mul(4))
314 .and_then(|v| v.checked_add(HEADER))
315 .and_then(|v| v.checked_add(bytes))
316 .ok_or(Error::Limit("shared value bytes"))?;
317
318 if size > limits.bytes || bytes > u32::MAX as usize {
319 return Err(Error::Limit("shared value bytes"));
320 }
321
322 Ok(size)
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn hash_collisions_compare_value_bytes_before_reusing_ids() -> Result<(), Error> {
331 let mut builder = ValuePoolBuilder::new(Limits::default());
332 let first = builder.intern(b"first")?;
333 let hash = builder.hash_state.hash_one(b"other");
334 builder
335 .hashes
336 .insert_unique(hash, (hash, first), |row| row.0);
337
338 let other = builder.intern(b"other")?;
339 assert_ne!(first, other);
340 assert_eq!(builder.intern(b"other")?, other);
341
342 let bytes = builder.into_bytes()?;
343 let pool = ValuePool::open(&bytes, Limits::default())?;
344
345 assert_eq!(pool.get(first)?, b"first");
346 assert_eq!(pool.get(other)?, b"other");
347
348 Ok(())
349 }
350}