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 {
//}