compact binary serialization format with built-in compression
at trunk 7.6 kB view raw
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>;