1use crate::error::{Result, ValidationError};
4use std::io::{Read, Write};
5
6#[derive(Debug, Clone, Copy)]
8pub struct PluginMetadata {
9 pub name: &'static str,
11
12 pub version: &'static str,
14
15 pub magic_number: [u8; 4],
19
20 pub throughput: f64,
22
23 pub compression_ratio: f64,
26
27 pub description: &'static str,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[repr(C)]
49pub struct CrushHeader {
50 pub magic: [u8; 4],
52
53 pub original_size: u64,
55
56 pub flags: u8,
58
59 pub reserved: [u8; 3],
61}
62
63pub mod flags {
65 pub const HAS_CRC32: u8 = 0x01;
67
68 pub const HAS_METADATA: u8 = 0x02;
70}
71
72impl CrushHeader {
73 pub const SIZE: usize = 16;
75
76 pub const MAGIC_PREFIX: [u8; 2] = [0x43, 0x52];
78
79 pub const VERSION: u8 = 0x01;
81
82 #[must_use]
84 pub fn new(magic: [u8; 4], original_size: u64) -> Self {
85 Self {
86 magic,
87 flags: 0,
88 original_size,
89 reserved: [0; 3],
90 }
91 }
92
93 #[must_use]
95 pub fn with_crc32(mut self) -> Self {
96 self.flags |= flags::HAS_CRC32;
97 self
98 }
99
100 #[must_use]
102 pub fn with_metadata(mut self) -> Self {
103 self.flags |= flags::HAS_METADATA;
104 self
105 }
106
107 #[must_use]
109 pub fn has_valid_prefix(&self) -> bool {
110 self.magic[0] == Self::MAGIC_PREFIX[0] && self.magic[1] == Self::MAGIC_PREFIX[1]
111 }
112
113 #[must_use]
115 pub fn has_valid_version(&self) -> bool {
116 self.magic[2] == Self::VERSION
117 }
118
119 #[must_use]
121 pub fn plugin_id(&self) -> u8 {
122 self.magic[3]
123 }
124
125 #[must_use]
127 pub fn has_crc32(&self) -> bool {
128 (self.flags & flags::HAS_CRC32) != 0
129 }
130
131 #[must_use]
133 pub fn has_metadata(&self) -> bool {
134 (self.flags & flags::HAS_METADATA) != 0
135 }
136
137 #[must_use]
139 pub fn to_bytes(&self) -> [u8; Self::SIZE] {
140 let mut bytes = [0u8; Self::SIZE];
141
142 bytes[0..4].copy_from_slice(&self.magic);
144
145 bytes[4..12].copy_from_slice(&self.original_size.to_le_bytes());
147
148 bytes[12] = self.flags;
150
151 bytes[13..16].copy_from_slice(&self.reserved);
153
154 bytes
155 }
156
157 pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result<Self> {
165 let header = Self {
166 magic: [bytes[0], bytes[1], bytes[2], bytes[3]],
167 original_size: u64::from_le_bytes([
168 bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11],
169 ]),
170 flags: bytes[12],
171 reserved: [bytes[13], bytes[14], bytes[15]],
172 };
173
174 if !header.has_valid_prefix() {
176 return Err(ValidationError::InvalidMagic(header.magic).into());
177 }
178
179 if !header.has_valid_version() {
180 return Err(ValidationError::InvalidHeader(format!(
181 "Unsupported version: 0x{:02x}",
182 header.magic[2]
183 ))
184 .into());
185 }
186
187 Ok(header)
188 }
189
190 pub fn write_to<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
196 writer.write_all(&self.to_bytes())
197 }
198
199 pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
207 let mut bytes = [0u8; Self::SIZE];
208 reader.read_exact(&mut bytes)?;
209 Self::from_bytes(&bytes)
210 }
211}
212
213use serde::Serialize; #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
217pub struct FileMetadata {
218 pub mtime: Option<i64>,
220
221 #[cfg(unix)]
224 pub permissions: Option<u32>,
225}
226
227impl FileMetadata {
228 #[must_use]
230 pub fn to_bytes(&self) -> Vec<u8> {
231 let mut bytes = Vec::new();
232 if let Some(mtime) = self.mtime {
233 bytes.push(0x01);
235 bytes.push(8);
237 bytes.extend_from_slice(&mtime.to_le_bytes());
239 }
240 #[cfg(unix)]
241 if let Some(permissions) = self.permissions {
242 bytes.push(0x02);
244 bytes.push(4);
246 bytes.extend_from_slice(&permissions.to_le_bytes());
248 }
249 bytes
250 }
251
252 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
261 let mut metadata = Self::default();
262 let mut i = 0;
263 while i < bytes.len() {
264 if i + 2 > bytes.len() {
265 return Err(
266 ValidationError::InvalidMetadata("Incomplete TLV record".into()).into(),
267 );
268 }
269 let type_ = bytes[i];
270 let length = bytes[i + 1] as usize;
271 i += 2;
272
273 if i + length > bytes.len() {
274 return Err(ValidationError::InvalidMetadata("Incomplete TLV value".into()).into());
275 }
276
277 let value = &bytes[i..i + length];
278 i += length;
279
280 match type_ {
281 0x01 => {
282 if length == 8 {
284 let mut mtime_bytes = [0u8; 8];
285 mtime_bytes.copy_from_slice(value);
286 metadata.mtime = Some(i64::from_le_bytes(mtime_bytes));
287 } else {
288 return Err(ValidationError::InvalidMetadata(
289 "Invalid mtime length".into(),
290 )
291 .into());
292 }
293 }
294 #[cfg(unix)]
295 0x02 => {
296 if length == 4 {
298 let mut perm_bytes = [0u8; 4];
299 perm_bytes.copy_from_slice(value);
300 metadata.permissions = Some(u32::from_le_bytes(perm_bytes));
301 } else {
302 return Err(ValidationError::InvalidMetadata(
303 "Invalid permissions length".into(),
304 )
305 .into());
306 }
307 }
308 _ => { }
309 }
310 }
311 Ok(metadata)
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_header_size() {
321 assert_eq!(CrushHeader::SIZE, 16);
323
324 let header = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 12345);
327 assert_eq!(header.to_bytes().len(), 16);
328 }
329
330 #[test]
331 #[allow(clippy::unwrap_used)]
332 fn test_header_roundtrip() {
333 let original = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 12345);
334 let bytes = original.to_bytes();
335 let deserialized = CrushHeader::from_bytes(&bytes).unwrap();
336
337 assert_eq!(original, deserialized);
338 assert_eq!(deserialized.original_size, 12345);
339 }
340
341 #[test]
342 fn test_invalid_magic_prefix() {
343 let mut bytes = [0u8; CrushHeader::SIZE];
344 bytes[0] = 0xFF; bytes[1] = 0xFF;
346
347 let result = CrushHeader::from_bytes(&bytes);
348 assert!(result.is_err());
349 }
350
351 #[test]
352 fn test_crc32_flag() {
353 let header = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 100).with_crc32();
354 assert!(header.has_crc32());
355
356 let bytes = header.to_bytes();
357 assert_eq!(bytes[12] & flags::HAS_CRC32, flags::HAS_CRC32);
358 }
359
360 #[test]
361 fn test_plugin_id() {
362 let header1 = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 100);
363 assert_eq!(header1.plugin_id(), 0x00);
364
365 let header2 = CrushHeader::new([0x43, 0x52, 0x01, 0xFF], 200);
366 assert_eq!(header2.plugin_id(), 0xFF);
367
368 let header3 = CrushHeader::new([0x43, 0x52, 0x01, 0x42], 300);
369 assert_eq!(header3.plugin_id(), 0x42);
370 }
371
372 #[test]
373 fn test_has_valid_prefix() {
374 let valid = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 100);
375 assert!(valid.has_valid_prefix());
376
377 let invalid = CrushHeader {
379 magic: [0xFF, 0xFF, 0x01, 0x00],
380 original_size: 100,
381 flags: 0,
382 reserved: [0; 3],
383 };
384 assert!(!invalid.has_valid_prefix());
385 }
386
387 #[test]
388 fn test_has_valid_version() {
389 let valid = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 100);
390 assert!(valid.has_valid_version());
391
392 let invalid = CrushHeader {
394 magic: [0x43, 0x52, 0x99, 0x00],
395 original_size: 100,
396 flags: 0,
397 reserved: [0; 3],
398 };
399 assert!(!invalid.has_valid_version());
400 }
401
402 #[test]
403 fn test_has_metadata_flag() {
404 let without = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 100);
405 assert!(!without.has_metadata());
406
407 let with = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 100).with_metadata();
408 assert!(with.has_metadata());
409
410 let bytes = with.to_bytes();
411 assert_eq!(bytes[12] & flags::HAS_METADATA, flags::HAS_METADATA);
412 }
413
414 #[test]
415 fn test_combined_flags() {
416 let header = CrushHeader::new([0x43, 0x52, 0x01, 0x00], 100)
417 .with_crc32()
418 .with_metadata();
419
420 assert!(header.has_crc32());
421 assert!(header.has_metadata());
422
423 let bytes = header.to_bytes();
424 assert_eq!(
425 bytes[12] & (flags::HAS_CRC32 | flags::HAS_METADATA),
426 flags::HAS_CRC32 | flags::HAS_METADATA
427 );
428 }
429}