compact binary serialization format with built-in compression
1pub(crate) mod len;
2pub mod reader;
3pub mod writer;
4
5/// Kinds of [`Tag`]s.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[repr(u8)]
8pub enum TagKind {
9 U8 = 0x00,
10 I8 = 0x01,
11 U16 = 0x02,
12 I16 = 0x03,
13 U32 = 0x04,
14 I32 = 0x05,
15 U64 = 0x06,
16 I64 = 0x07,
17 F32 = 0x08,
18 F64 = 0x09,
19 Bool = 0x0A,
20 String = 0x0B,
21 Option = 0x0C,
22 List = 0x0D,
23 Map = 0x0E,
24 Array = 0x0F,
25 Timestamp = 0x10,
26 Uuid = 0x11,
27}
28
29impl TagKind {
30 pub fn from_byte(byte: u8) -> Option<TagKind> {
31 match byte {
32 0x00 => Some(TagKind::U8),
33 0x01 => Some(TagKind::I8),
34 0x02 => Some(TagKind::U16),
35 0x03 => Some(TagKind::I16),
36 0x04 => Some(TagKind::U32),
37 0x05 => Some(TagKind::I32),
38 0x06 => Some(TagKind::U64),
39 0x07 => Some(TagKind::I64),
40 0x08 => Some(TagKind::F32),
41 0x09 => Some(TagKind::F64),
42 0x0A => Some(TagKind::Bool),
43 0x0B => Some(TagKind::String),
44 0x0C => Some(TagKind::Option),
45 0x0D => Some(TagKind::List),
46 0x0E => Some(TagKind::Map),
47 0x0F => Some(TagKind::Array),
48 0x10 => Some(TagKind::Timestamp),
49 0x11 => Some(TagKind::Uuid),
50 _ => None,
51 }
52 }
53
54 pub fn valid_for_array_element(&self) -> bool {
55 matches!(
56 self,
57 TagKind::U8
58 | TagKind::I8
59 | TagKind::U16
60 | TagKind::I16
61 | TagKind::U32
62 | TagKind::I32
63 | TagKind::U64
64 | TagKind::I64
65 | TagKind::F32
66 | TagKind::F64
67 | TagKind::Bool
68 )
69 }
70
71 pub fn valid_for_map_key(&self) -> bool {
72 !matches!(
73 self,
74 TagKind::Option | TagKind::List | TagKind::Array | TagKind::Map
75 )
76 }
77}
78
79/// Hateno Tags.
80#[derive(Debug, Clone, PartialEq)]
81pub enum Tag {
82 U8(u8),
83 I8(i8),
84 U16(u16),
85 I16(i16),
86 U32(u32),
87 I32(i32),
88 U64(u64),
89 I64(i64),
90 F32(f32),
91 F64(f64),
92 Bool(bool),
93 String(String),
94 Option(TagKind, Option<Box<Tag>>),
95 List(Vec<Tag>),
96 Map(Vec<(Tag, Tag)>),
97 /// Holds a list of `Tag`s of one kind.
98 Array(TagKind, Vec<Tag>),
99 Timestamp(chrono::DateTime<chrono::Utc>),
100 Uuid(uuid::Uuid),
101}
102
103macro_rules! simple_tag_constructors {
104 ( $( $fn_name:ident : $variant:ident ( $ty:ty ) ),* $(,)? ) => {
105 $(
106 #[doc = concat!("Creates a new [`Tag::", stringify!($variant), "`].")]
107 pub fn $fn_name(value: $ty) -> Self {
108 Tag::$variant(value)
109 }
110 )*
111 };
112}
113
114impl Tag {
115 simple_tag_constructors! {
116 new_u8: U8(u8),
117 new_i8: I8(i8),
118 new_u16: U16(u16),
119 new_i16: I16(i16),
120 new_u32: U32(u32),
121 new_i32: I32(i32),
122 new_u64: U64(u64),
123 new_i64: I64(i64),
124 new_f32: F32(f32),
125 new_f64: F64(f64),
126 new_bool: Bool(bool),
127 }
128
129 /// Creates a new [`Tag::String`].
130 pub fn new_string<S: Into<String>>(s: S) -> Self {
131 Tag::String(s.into())
132 }
133
134 /// Creates a new [`Tag::Option`] containing the given `Tag`.
135 pub fn new_option(kind: TagKind, value: Option<Tag>) -> Self {
136 Tag::Option(kind, value.map(Box::new))
137 }
138
139 /// Creates a new [`Tag::List`] from a vector of `Tag`s.
140 pub fn new_list(values: Vec<Tag>) -> Self {
141 Tag::List(values)
142 }
143
144 /// Creates a new [`Tag::Map`] from a vector of key-value pairs.
145 pub fn new_map(entries: Vec<(Tag, Tag)>) -> Self {
146 Tag::Map(entries)
147 }
148
149 /// Creates a new [`Tag::Array`] of the specified [`TagKind`] and elements.
150 pub fn new_array(kind: TagKind, elements: Vec<Tag>) -> Self {
151 Tag::Array(kind, elements)
152 }
153
154 /// Creates a new [`Tag::Timestamp`] from a [`chrono::DateTime<Utc>`].
155 pub fn new_timestamp(value: chrono::DateTime<chrono::Utc>) -> Self {
156 Tag::Timestamp(value)
157 }
158
159 /// Creates a new [`Tag::Uuid`] from a [`uuid::Uuid`].
160 pub fn new_uuid(value: uuid::Uuid) -> Self {
161 Tag::Uuid(value)
162 }
163
164 /// Get this `Tag`'s [`TagKind`].
165 pub fn kind(&self) -> TagKind {
166 match self {
167 Tag::U8 { .. } => TagKind::U8,
168 Tag::I8 { .. } => TagKind::I8,
169 Tag::U16 { .. } => TagKind::U16,
170 Tag::I16 { .. } => TagKind::I16,
171 Tag::U32 { .. } => TagKind::U32,
172 Tag::I32 { .. } => TagKind::I32,
173 Tag::U64 { .. } => TagKind::U64,
174 Tag::I64 { .. } => TagKind::I64,
175 Tag::F32 { .. } => TagKind::F32,
176 Tag::F64 { .. } => TagKind::F64,
177 Tag::Bool { .. } => TagKind::Bool,
178 Tag::String { .. } => TagKind::String,
179 Tag::Option { .. } => TagKind::Option,
180 Tag::List { .. } => TagKind::List,
181 Tag::Map { .. } => TagKind::Map,
182 Tag::Array { .. } => TagKind::Array,
183 Tag::Timestamp { .. } => TagKind::Timestamp,
184 Tag::Uuid { .. } => TagKind::Uuid,
185 }
186 }
187}
188
189/// File header for Hateno files.
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct FileHeader {
192 pub version: u8,
193 pub little_endian: bool,
194 pub compression: CompressionMethod,
195 pub payload_length: u32,
196}
197
198impl FileHeader {
199 pub const MAGIC_BYTES: [u8; len::MAGIC_BYTES] = [b'H', b'T', b'N', b'O'];
200
201 pub const CURRENT_VERSION: u8 = 1;
202
203 pub fn new(compression: CompressionMethod, payload_length: u32) -> Self {
204 Self {
205 version: Self::CURRENT_VERSION,
206 little_endian: true,
207 compression,
208 payload_length,
209 }
210 }
211}
212
213/// Compression methods supported by Hateno.
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215#[repr(u8)]
216pub enum CompressionMethod {
217 None = 0x00,
218 Gzip = 0x01,
219 Zlib = 0x02,
220 Lz4 = 0x03,
221}
222
223impl CompressionMethod {
224 pub fn from_byte(byte: u8) -> Option<CompressionMethod> {
225 match byte {
226 0x00 => Some(CompressionMethod::None),
227 0x01 => Some(CompressionMethod::Gzip),
228 0x02 => Some(CompressionMethod::Zlib),
229 0x03 => Some(CompressionMethod::Lz4),
230 _ => None,
231 }
232 }
233}
234
235/// Errors that can occur during Hateno operations.
236#[derive(Debug, thiserror::Error)]
237pub enum Error {
238 #[error("Unexpected end of file")]
239 UnexpectedEof,
240 #[error("Invalid magic bytes sequence '{0:02X?}'")]
241 InvalidMagic([u8; len::MAGIC_BYTES]),
242 #[error("Unsupported version 0x{0:x}")]
243 UnsupportedVersion(u8),
244 #[error("Unsupported compression method {0:?}")]
245 UnsupportedCompression(CompressionMethod),
246 #[error("Unknown compression method type 0x{0:x}")]
247 UnknownCompression(u8),
248 #[error("Invalid tag ID 0x{0:x}")]
249 InvalidTagKind(u8),
250 #[error("Encountered an invalid UTF-8 sequence: {0}")]
251 InvalidUtf8(#[from] std::string::FromUtf8Error),
252 #[error("Invalid boolean value 0x{0:x}")]
253 InvalidBoolValue(u8),
254 #[error("Invalid Option discriminant 0x{0:x}")]
255 InvalidOptionDiscriminant(u8),
256 #[error("Invalid Option kind 0x{0:?}, expected 0x{1:?}")]
257 InvalidOptionKind(TagKind, TagKind),
258 #[error("Invalid element kind for Array tag '{0:?}'")]
259 InvalidArrayTagKind(TagKind),
260 #[error("IO error: {0}")]
261 IoError(#[from] std::io::Error),
262 #[error("Milliseconds for Timestamp are out of range: {0}")]
263 MillisecondsOutOfRange(i64),
264}
265
266pub type Result<T> = core::result::Result<T, Error>;