1#![warn(missing_docs)]
2
3mod error;
6mod reader;
7mod writer;
8
9#[cfg(test)]
10mod tests;
11
12use std::{
13 fs::File,
14 io::{Seek, SeekFrom},
15 path::PathBuf,
16};
17
18pub use crate::error::SnowBinError;
19
20pub const VERSION_SPEC: u64 = 2; const DEFAULT_HEADER_SIZE: u32 = 8;
24const DATA_SIZES: [u8; 4] = [8, 16, 32, 64];
25const DEFAULT_DATA_SIZE: usize = 3;
26
27const DATA_START: u64 = 21;
29const HASH_SIZE: u32 = 32;
30
31#[derive(Copy, Clone, Eq, PartialEq, Debug)]
40pub struct SnowBinInfo {
41 header_size: u32,
42 data_size: u8,
43}
44
45impl SnowBinInfo {
46 pub const fn new(header_size: u32, data_size: u8) -> Result<Self, SnowBinError> {
56 let header_size = if header_size >= 8 {
57 header_size
58 }
59 else {
60 return Err(SnowBinError::HeaderSizeTooSmall);
61 };
62
63 let data_size = match data_size {
64 8 => 8,
65 16 => 16,
66 32 => 32,
67 64 => 64,
68 _ => return Err(SnowBinError::DataSizeNotAllowed),
69 };
70
71 Ok(Self {
72 header_size,
73 data_size,
74 })
75 }
76}
77
78impl Default for SnowBinInfo {
79 fn default() -> Self {
80 Self {
81 header_size: DEFAULT_HEADER_SIZE,
82 data_size: DATA_SIZES[DEFAULT_DATA_SIZE],
83 }
84 }
85}
86
87#[derive(Debug)]
89pub struct SnowBinWriter {
90 info: SnowBinInfo,
91 file: File,
92 hasher: blake3::Hasher,
93 done: bool,
94}
95
96impl SnowBinWriter {
97 pub fn new(info: SnowBinInfo, path: PathBuf) -> Result<Self, SnowBinError> {
109 let Ok(mut file) = File::create(path)
110 else {
111 return Err(SnowBinError::CouldNotCreateOrOpenFile);
112 };
113
114 let mut hasher = blake3::Hasher::new();
115
116 Self::init_file(&mut file, &mut hasher, info)?;
117
118 Ok(Self {
119 info,
120 file,
121 hasher,
122 done: false,
123 })
124 }
125
126 fn init_file(
127 file: &mut File,
128 hasher: &mut blake3::Hasher,
129 info: SnowBinInfo,
130 ) -> Result<(), SnowBinError> {
131 file.rewind().map_err(|_| SnowBinError::IOWriteError)?;
132
133 writer::write_header(file, "SNOW_BIN", 8)?;
134 writer::write_u64(file, VERSION_SPEC)?;
135 writer::write_u32(file, info.header_size)?;
136 writer::write_u8(file, info.data_size)?;
137
138 hasher.update(b"SNOW_BIN");
139 hasher.update(&VERSION_SPEC.to_le_bytes());
140 hasher.update(&info.header_size.to_le_bytes());
141 hasher.update(&info.data_size.to_le_bytes());
142
143 Ok(())
144 }
145
146 pub fn write(&mut self, header: &str, data: &[u8]) -> Result<(), SnowBinError> {
166 if !self.done {
167 if header.len() > self.info.header_size as usize {
169 return Err(SnowBinError::HeaderTooLong);
170 }
171 let max = self.get_max_size()?;
172 if data.len() as u64 > max {
173 return Err(SnowBinError::DataTooLong);
174 }
175
176 let header = writer::write_header(&mut self.file, header, self.info.header_size)?;
178 self.hasher.update(&header);
179
180 #[allow(clippy::cast_possible_truncation)]
181 match self.info.data_size {
182 8 => {
183 writer::write_u8(&mut self.file, data.len() as u8)?;
184 self.hasher.update(&(data.len() as u8).to_le_bytes());
185 }
186 16 => {
187 writer::write_u16(&mut self.file, data.len() as u16)?;
188 self.hasher.update(&(data.len() as u16).to_le_bytes());
189 }
190 32 => {
191 writer::write_u32(&mut self.file, data.len() as u32)?;
192 self.hasher.update(&(data.len() as u32).to_le_bytes());
193 }
194 64 => {
195 writer::write_u64(&mut self.file, data.len() as u64)?;
196 self.hasher.update(&(data.len() as u64).to_le_bytes());
197 }
198 _ => return Err(SnowBinError::DataSizeNotAllowed),
199 }
200
201 writer::write_bytes(&mut self.file, data)?;
202 self.hasher.update(data);
203
204 return Ok(());
205 }
206
207 Err(SnowBinError::IOWriterClosed)
208 }
209
210 fn get_max_size(&self) -> Result<u64, SnowBinError> {
211 Ok(match self.info.data_size {
212 8 => u64::from(u8::MAX),
213 16 => u64::from(u16::MAX),
214 32 => u64::from(u32::MAX),
215 64 => u64::MAX,
216 _ => return Err(SnowBinError::DataSizeNotAllowed),
217 })
218 }
219
220 pub fn close(&mut self) -> Result<(), SnowBinError> {
239 use std::io::Write;
240
241 if !self.done {
242 let header = writer::write_header(&mut self.file, "SNOW_END", self.info.header_size)?;
243
244 self.hasher.update(&header);
246 let hash = self.hasher.finalize();
247 let hash = hash.as_bytes();
248 writer::write_bytes(&mut self.file, hash)?;
249
250 self.file.flush().map_err(|_| SnowBinError::IOWriteError)?;
251
252 self.done = true;
253
254 return Ok(());
255 }
256
257 Err(SnowBinError::IOWriterClosed)
258 }
259}
260
261impl Drop for SnowBinWriter {
262 fn drop(&mut self) {
263 if !self.done {
264 self.close()
265 .expect("Could not properly drop SnowBinWriter.");
266 }
267 }
268}
269
270#[derive(Debug)]
272pub struct SnowBinReader {
273 info: SnowBinInfo,
274 file: File,
275}
276
277impl SnowBinReader {
279 pub fn new(path: PathBuf) -> Result<Self, SnowBinError> {
291 let Ok(mut file) = File::open(path)
292 else {
293 return Err(SnowBinError::CouldNotCreateOrOpenFile);
294 };
295
296 let info = Self::read_info(&mut file)?;
297
298 Ok(Self { info, file })
299 }
300
301 fn read_info(file: &mut File) -> Result<SnowBinInfo, SnowBinError> {
302 use std::io::Read;
303
304 let hash = {
306 file.rewind().map_err(|_| SnowBinError::IOReadError)?;
307 let mut buffer = Vec::new();
308 file.read_to_end(&mut buffer)
309 .map_err(|_| SnowBinError::IOReadError)?;
310 buffer.drain((buffer.len() - HASH_SIZE as usize)..buffer.len());
311
312 blake3::hash(&buffer)
313 };
314 let hash = hash.as_bytes();
315
316 {
317 file.seek(SeekFrom::End(-(i64::from(HASH_SIZE))))
318 .map_err(|_| SnowBinError::IOReadError)?;
319 let read_hash = reader::read_bytes(file, u64::from(HASH_SIZE))?;
320
321 if !read_hash.eq(hash) {
322 return Err(SnowBinError::HashDoesNotMatch);
323 }
324 }
325
326 file.rewind().map_err(|_| SnowBinError::IOReadError)?;
328
329 let snow_header = reader::read_header(file, 8)?;
330 if !snow_header.eq("SNOW_BIN") {
331 return Err(SnowBinError::MalformedHeader);
332 }
333
334 let version = reader::read_u64(file)?;
335 if version != VERSION_SPEC {
336 return Err(SnowBinError::WrongSpecVersion);
337 }
338
339 let header_size = reader::read_u32(file)?;
340
341 let data_size = reader::read_u8(file)?;
342 match data_size {
343 8 | 16 | 32 | 64 => (),
344 _ => return Err(SnowBinError::DataSizeNotAllowed),
345 };
346
347 Ok(SnowBinInfo {
348 header_size,
349 data_size,
350 })
351 }
352
353 pub fn read(&mut self, header: &str) -> Result<Vec<u8>, SnowBinError> {
373 self.file
374 .seek(SeekFrom::Start(DATA_START))
375 .map_err(|_| SnowBinError::IOReadError)?;
376
377 let mut buffer = vec![32_u8; self.info.header_size as usize];
378 buffer.splice(0..header.len(), header.as_bytes().iter().copied());
379 let header = String::from_utf8(buffer).map_err(|_| SnowBinError::MalformedHeader)?;
380
381 let mut f_header = String::new();
382 let mut data = Vec::new();
383 let mut store = 0_u64;
384 while !f_header.eq(&header) {
385 f_header = reader::read_header(&mut self.file, self.info.header_size)?;
386
387 if f_header.starts_with("SNOW_END") {
388 return Err(SnowBinError::ReachedEOF);
389 }
390
391 let size = match self.info.data_size {
392 8 => u64::from(reader::read_u8(&mut self.file)?),
393 16 => u64::from(reader::read_u16(&mut self.file)?),
394 32 => u64::from(reader::read_u32(&mut self.file)?),
395 64 => reader::read_u64(&mut self.file)?,
396 _ => return Err(SnowBinError::DataSizeNotAllowed),
397 };
398
399 if f_header.eq(&header) {
400 data = reader::read_bytes(&mut self.file, size)?;
401 }
402 else {
403 let mut file = &self.file;
404
405 let tmp = if size > i64::MAX as u64 {
406 let s = size - i64::MAX as u64;
407 file.seek(SeekFrom::Current(i64::MAX))
408 .map_err(|_| SnowBinError::IOReadError)?;
409 file.seek(SeekFrom::Current(s.try_into().unwrap()))
410 .map_err(|_| SnowBinError::IOReadError)?
411 }
412 else {
413 file.seek(SeekFrom::Current(size.try_into().unwrap()))
414 .map_err(|_| SnowBinError::IOReadError)?
415 };
416
417 if tmp == store {
418 return Err(SnowBinError::ReachedEOF);
419 }
420 store = tmp;
421 }
422 }
423
424 Ok(data)
425 }
426}