1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
//---------------------------------------------------------------------------------------------------- Use
use anyhow::{anyhow,bail,ensure};
use std::path::PathBuf;
use crate::common;
use bincode::config::*;
//use log::{info,error,warn,trace,debug};
//use serde::{Serialize,Deserialize};
//---------------------------------------------------------------------------------------------------- Bincode
lazy_static::lazy_static! {
pub static ref ENCODING_OPTIONS: WithOtherIntEncoding<DefaultOptions, VarintEncoding> =
bincode::DefaultOptions::new().with_varint_encoding();
}
crate::common::impl_macro_binary!(Bincode, "bin");
/// [`Bincode`](https://docs.rs/bincode) (binary) file format
///
/// ## Encoding
/// The encoding options used is:
/// ```rust
/// # use bincode::Options;
/// bincode::DefaultOptions::new().with_varint_encoding();
/// ```
///
/// File extension is `.bin`.
///
/// ## Safety
/// When manually implementing, you are **promising** that the `PATH`'s manually specified are correct.
pub unsafe trait Bincode: serde::Serialize + serde::de::DeserializeOwned {
// Common data/functions.
common::impl_binary!("bincode");
#[inline(always)]
/// Create a `struct/enum` from bytes.
fn from_bytes(bytes: &[u8]) -> Result<Self, anyhow::Error> {
let len = bytes.len();
// Ensure our `[u8; 25]` HEADER + VERSION bytes are there.
if len < 25 {
bail!("Invalid Bincode header data, total byte length less than 25: {}", len);
}
// Ensure our HEADER is correct.
if bytes[..24] != Self::HEADER {
bail!("Incorrect Bincode header\nExpected: {:?}\nFound: {:?}", Self::HEADER, &bytes[..24],);
}
// Ensure our VERSION is correct.
if bytes[24] != Self::VERSION {
bail!("Incorrect Bincode version\nExpected: {:?}\nFound: {:?}", Self::VERSION, &bytes[24],);
}
common::convert_error(ENCODING_OPTIONS.deserialize(&bytes[25..]))
}
#[inline(always)]
/// Convert a `struct/enum` to bytes.
fn to_bytes(&self) -> Result<Vec<u8>, anyhow::Error> {
let mut vec = common::convert_error(ENCODING_OPTIONS.serialize(self))?;
let mut bytes = self.header_version_bytes().to_vec();
bytes.append(&mut vec);
Ok(bytes)
}
// Bincode specific.
/// A custom 24-byte length identifying header for your Bincode file.
///
/// This is combined with [`Self::VERSION`] to prefix your file with 25 bytes.
///
/// **Note: [`Self::save_gzip()`] applies compression AFTER, meaning the entire file must be decompressed to get these headers.**
const HEADER: [u8; 24];
/// What the version byte will be (0-255).
const VERSION: u8;
#[inline]
/// Return the 25 bytes header bytes.
///
/// First 24 bytes are the [`Self::HEADER`] bytes.
///
/// Last byte is [`Self::VERSION`].
fn header_version_bytes(&self) -> [u8; 25] {
[
Self::HEADER[0],
Self::HEADER[1],
Self::HEADER[2],
Self::HEADER[3],
Self::HEADER[4],
Self::HEADER[5],
Self::HEADER[6],
Self::HEADER[7],
Self::HEADER[8],
Self::HEADER[9],
Self::HEADER[10],
Self::HEADER[11],
Self::HEADER[12],
Self::HEADER[13],
Self::HEADER[14],
Self::HEADER[15],
Self::HEADER[16],
Self::HEADER[17],
Self::HEADER[18],
Self::HEADER[19],
Self::HEADER[20],
Self::HEADER[21],
Self::HEADER[22],
Self::HEADER[23],
Self::VERSION
]
}
#[inline(always)]
/// Read the associated file and attempt to convert the first 24 bytes to a [`String`].
///
/// This is useful if your [`Self::HEADER`] should be bytes representing a UTF-8 string.
fn file_header_to_string(&self) -> Result<String, anyhow::Error> {
let bytes = Self::file_bytes(0,24)?;
Ok(String::from_utf8(bytes)?)
}
#[inline]
/// Reads the first 24 bytes of the associated file and matches it against [`Self::HEADER`].
///
/// If the bytes match, the next byte _should_ be our [`Self::VERSION`] and is returned.
///
/// **Note: This only works on a non-compressed version.**
fn file_version() -> Result<u8, anyhow::Error> {
use std::io::Read;
let mut bytes = [0; 25];
let mut file = std::fs::File::open(Self::absolute_path()?)?;
file.read_exact(&mut bytes)?;
if bytes[0..24] == Self::HEADER {
Ok(bytes[24])
} else {
bail!("Bincode header failed to match.\nExpected: {:?}\nFound: {:?}", Self::HEADER, &bytes[0..24]);
}
}
}
//---------------------------------------------------------------------------------------------------- TESTS
//#[cfg(test)]
//mod tests {
//}