Skip to main content

compression/
aa_header.rs

1use crate::{
2    aa_byte_stream::ArchiveFlags,
3    aa_field_key::{FieldKey, FieldKeySet},
4    ffi, util, CompressionError, Result,
5};
6use std::ffi::{c_void, CStr};
7use std::ptr::{null_mut, NonNull};
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
10pub enum HashFunction {
11    Crc32 = 1,
12    Sha1 = 2,
13    Sha256 = 3,
14    Sha384 = 4,
15    Sha512 = 5,
16}
17
18impl HashFunction {
19    pub const fn digest_len(self) -> usize {
20        match self {
21            Self::Crc32 => 4,
22            Self::Sha1 => 20,
23            Self::Sha256 => 32,
24            Self::Sha384 => 48,
25            Self::Sha512 => 64,
26        }
27    }
28
29    const fn from_raw(raw: u32) -> Option<Self> {
30        match raw {
31            1 => Some(Self::Crc32),
32            2 => Some(Self::Sha1),
33            3 => Some(Self::Sha256),
34            4 => Some(Self::Sha384),
35            5 => Some(Self::Sha512),
36            _ => None,
37        }
38    }
39
40    const fn as_raw(self) -> u32 {
41        self as u32
42    }
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
46pub enum EntryType {
47    RegularFile = 'F' as isize,
48    Directory = 'D' as isize,
49    SymbolicLink = 'L' as isize,
50    Fifo = 'P' as isize,
51    CharacterDevice = 'C' as isize,
52    BlockDevice = 'B' as isize,
53    Socket = 'S' as isize,
54    Whiteout = 'W' as isize,
55    Door = 'R' as isize,
56    Port = 'T' as isize,
57    Metadata = 'M' as isize,
58}
59
60impl EntryType {
61    pub fn from_raw(raw: u32) -> Option<Self> {
62        match raw {
63            x if x == Self::RegularFile as u32 => Some(Self::RegularFile),
64            x if x == Self::Directory as u32 => Some(Self::Directory),
65            x if x == Self::SymbolicLink as u32 => Some(Self::SymbolicLink),
66            x if x == Self::Fifo as u32 => Some(Self::Fifo),
67            x if x == Self::CharacterDevice as u32 => Some(Self::CharacterDevice),
68            x if x == Self::BlockDevice as u32 => Some(Self::BlockDevice),
69            x if x == Self::Socket as u32 => Some(Self::Socket),
70            x if x == Self::Whiteout as u32 => Some(Self::Whiteout),
71            x if x == Self::Door as u32 => Some(Self::Door),
72            x if x == Self::Port as u32 => Some(Self::Port),
73            x if x == Self::Metadata as u32 => Some(Self::Metadata),
74            _ => None,
75        }
76    }
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
80pub enum FieldType {
81    Flag = 0,
82    UInt = 1,
83    String = 2,
84    Hash = 3,
85    Timespec = 4,
86    Blob = 5,
87}
88
89impl FieldType {
90    fn from_raw(raw: i32) -> Option<Self> {
91        match raw {
92            0 => Some(Self::Flag),
93            1 => Some(Self::UInt),
94            2 => Some(Self::String),
95            3 => Some(Self::Hash),
96            4 => Some(Self::Timespec),
97            5 => Some(Self::Blob),
98            _ => None,
99        }
100    }
101}
102
103#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
104pub struct Timespec {
105    pub seconds: i64,
106    pub nanoseconds: i64,
107}
108
109#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct HashValue {
111    pub function: HashFunction,
112    pub bytes: Vec<u8>,
113}
114
115#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
116pub struct BlobDescription {
117    pub size: u64,
118    pub offset: u64,
119}
120
121#[derive(Clone, Debug, Eq, PartialEq)]
122pub enum HeaderFieldValue {
123    Flag,
124    UInt(u64),
125    String(String),
126    Hash(HashValue),
127    Timespec(Timespec),
128    Blob(BlobDescription),
129}
130
131#[derive(Debug)]
132pub struct Header {
133    handle: NonNull<c_void>,
134}
135
136impl Header {
137    pub(crate) fn from_handle(handle: *mut c_void, operation: &'static str) -> Result<Self> {
138        Ok(Self {
139            handle: util::nonnull_handle(handle, operation)?,
140        })
141    }
142
143    pub fn new() -> Result<Self> {
144        let handle = unsafe { ffi::aa_header::compression_rs_aa_header_create() };
145        Self::from_handle(handle, "AAHeaderCreate")
146    }
147
148    pub fn from_encoded_data(data: &[u8]) -> Result<Self> {
149        let handle = unsafe {
150            ffi::aa_header::compression_rs_aa_header_create_with_encoded_data(
151                data.len(),
152                data.as_ptr(),
153            )
154        };
155        Self::from_handle(handle, "AAHeaderCreateWithEncodedData")
156    }
157
158    pub fn from_path(
159        key_set: &FieldKeySet,
160        dir: &str,
161        path: &str,
162        flags: ArchiveFlags,
163    ) -> Result<Self> {
164        let dir = util::cstring("dir", dir)?;
165        let path = util::cstring("path", path)?;
166        let handle = unsafe {
167            ffi::aa_header::compression_rs_aa_header_create_with_path(
168                key_set.as_ptr(),
169                dir.as_ptr(),
170                path.as_ptr(),
171                flags.bits(),
172            )
173        };
174        Self::from_handle(handle, "AAHeaderCreateWithPath")
175    }
176
177    pub(crate) fn as_ptr(&self) -> *mut c_void {
178        self.handle.as_ptr()
179    }
180
181    pub fn assign(&mut self, other: &Self) -> Result<()> {
182        let status = unsafe {
183            ffi::aa_header::compression_rs_aa_header_assign(self.as_ptr(), other.as_ptr())
184        };
185        util::status_result("AAHeaderAssign", status)
186    }
187
188    pub fn field_count(&self) -> u32 {
189        unsafe { ffi::aa_header::compression_rs_aa_header_get_field_count(self.as_ptr()) }
190    }
191
192    pub fn is_empty(&self) -> bool {
193        self.field_count() == 0
194    }
195
196    pub fn key_index(&self, key: FieldKey) -> Result<Option<u32>> {
197        match unsafe {
198            ffi::aa_header::compression_rs_aa_header_get_key_index(self.as_ptr(), key.raw())
199        } {
200            -1 => Ok(None),
201            value if value < -1 => Err(CompressionError::OperationFailed {
202                operation: "AAHeaderGetKeyIndex",
203                code: value,
204            }),
205            value => Ok(Some(value.unsigned_abs())),
206        }
207    }
208
209    pub fn field_type(&self, index: u32) -> Result<FieldType> {
210        let raw = unsafe {
211            ffi::aa_header::compression_rs_aa_header_get_field_type(self.as_ptr(), index)
212        };
213        if raw < 0 {
214            return Err(CompressionError::OperationFailed {
215                operation: "AAHeaderGetFieldType",
216                code: raw,
217            });
218        }
219        FieldType::from_raw(raw).ok_or(CompressionError::OperationFailed {
220            operation: "AAHeaderGetFieldType",
221            code: raw,
222        })
223    }
224
225    pub fn field_key(&self, index: u32) -> Result<FieldKey> {
226        if index >= self.field_count() {
227            return Err(CompressionError::OperationFailed {
228                operation: "AAHeaderGetFieldKey",
229                code: -1,
230            });
231        }
232
233        Ok(FieldKey::from_raw(unsafe {
234            ffi::aa_header::compression_rs_aa_header_get_field_key(self.as_ptr(), index)
235        }))
236    }
237
238    pub fn payload_size(&self) -> u64 {
239        unsafe { ffi::aa_header::compression_rs_aa_header_get_payload_size(self.as_ptr()) }
240    }
241
242    pub fn remove_field(&mut self, index: u32) -> Result<()> {
243        let status =
244            unsafe { ffi::aa_header::compression_rs_aa_header_remove_field(self.as_ptr(), index) };
245        util::status_result("AAHeaderRemoveField", status)
246    }
247
248    pub fn clear(&mut self) -> Result<()> {
249        let status = unsafe { ffi::aa_header::compression_rs_aa_header_clear(self.as_ptr()) };
250        util::status_result("AAHeaderClear", status)
251    }
252
253    pub fn set_field_flag(&mut self, index: u32, key: FieldKey) -> Result<()> {
254        let status = unsafe {
255            ffi::aa_header::compression_rs_aa_header_set_field_flag(self.as_ptr(), index, key.raw())
256        };
257        util::status_result("AAHeaderSetFieldFlag", status)
258    }
259
260    pub fn set_field_uint(&mut self, index: u32, key: FieldKey, value: u64) -> Result<()> {
261        let status = unsafe {
262            ffi::aa_header::compression_rs_aa_header_set_field_uint(
263                self.as_ptr(),
264                index,
265                key.raw(),
266                value,
267            )
268        };
269        util::status_result("AAHeaderSetFieldUInt", status)
270    }
271
272    pub fn set_field_string(&mut self, index: u32, key: FieldKey, value: &str) -> Result<()> {
273        let value = util::cstring("value", value)?;
274        let status = unsafe {
275            ffi::aa_header::compression_rs_aa_header_set_field_string(
276                self.as_ptr(),
277                index,
278                key.raw(),
279                value.as_ptr(),
280                value.as_bytes().len(),
281            )
282        };
283        util::status_result("AAHeaderSetFieldString", status)
284    }
285
286    pub fn set_field_hash(
287        &mut self,
288        index: u32,
289        key: FieldKey,
290        function: HashFunction,
291        value: &[u8],
292    ) -> Result<()> {
293        if value.len() != function.digest_len() {
294            return Err(CompressionError::InvalidHashLength {
295                expected: function.digest_len(),
296                actual: value.len(),
297            });
298        }
299        let status = unsafe {
300            ffi::aa_header::compression_rs_aa_header_set_field_hash(
301                self.as_ptr(),
302                index,
303                key.raw(),
304                function.as_raw(),
305                value.as_ptr(),
306            )
307        };
308        util::status_result("AAHeaderSetFieldHash", status)
309    }
310
311    pub fn set_field_timespec(&mut self, index: u32, key: FieldKey, value: Timespec) -> Result<()> {
312        let status = unsafe {
313            ffi::aa_header::compression_rs_aa_header_set_field_timespec(
314                self.as_ptr(),
315                index,
316                key.raw(),
317                value.seconds,
318                value.nanoseconds,
319            )
320        };
321        util::status_result("AAHeaderSetFieldTimespec", status)
322    }
323
324    pub fn set_field_blob(&mut self, index: u32, key: FieldKey, size: u64) -> Result<()> {
325        let status = unsafe {
326            ffi::aa_header::compression_rs_aa_header_set_field_blob(
327                self.as_ptr(),
328                index,
329                key.raw(),
330                size,
331            )
332        };
333        util::status_result("AAHeaderSetFieldBlob", status)
334    }
335
336    pub fn append_field_flag(&mut self, key: FieldKey) -> Result<()> {
337        self.set_field_flag(u32::MAX, key)
338    }
339
340    pub fn append_field_uint(&mut self, key: FieldKey, value: u64) -> Result<()> {
341        self.set_field_uint(u32::MAX, key, value)
342    }
343
344    pub fn append_field_string(&mut self, key: FieldKey, value: &str) -> Result<()> {
345        self.set_field_string(u32::MAX, key, value)
346    }
347
348    pub fn append_field_hash(
349        &mut self,
350        key: FieldKey,
351        function: HashFunction,
352        value: &[u8],
353    ) -> Result<()> {
354        self.set_field_hash(u32::MAX, key, function, value)
355    }
356
357    pub fn append_field_timespec(&mut self, key: FieldKey, value: Timespec) -> Result<()> {
358        self.set_field_timespec(u32::MAX, key, value)
359    }
360
361    pub fn append_field_blob(&mut self, key: FieldKey, size: u64) -> Result<()> {
362        self.set_field_blob(u32::MAX, key, size)
363    }
364
365    pub fn field_uint(&self, index: u32) -> Result<u64> {
366        let mut value = 0_u64;
367        let status = unsafe {
368            ffi::aa_header::compression_rs_aa_header_get_field_uint(
369                self.as_ptr(),
370                index,
371                &mut value,
372            )
373        };
374        util::status_result("AAHeaderGetFieldUInt", status)?;
375        Ok(value)
376    }
377
378    pub fn field_string(&self, index: u32) -> Result<String> {
379        let mut length = 0_usize;
380        let status = unsafe {
381            ffi::aa_header::compression_rs_aa_header_get_field_string(
382                self.as_ptr(),
383                index,
384                0,
385                null_mut(),
386                &mut length,
387            )
388        };
389        util::status_result("AAHeaderGetFieldString", status)?;
390
391        let mut buffer = vec![0_i8; length.saturating_add(1)];
392        let status = unsafe {
393            ffi::aa_header::compression_rs_aa_header_get_field_string(
394                self.as_ptr(),
395                index,
396                buffer.len(),
397                buffer.as_mut_ptr(),
398                &mut length,
399            )
400        };
401        util::status_result("AAHeaderGetFieldString", status)?;
402
403        let value = unsafe { CStr::from_ptr(buffer.as_ptr()) }
404            .to_str()
405            .map_err(|_| CompressionError::Utf8Error {
406                operation: "AAHeaderGetFieldString",
407            })?;
408        Ok(value.to_string())
409    }
410
411    pub fn field_hash(&self, index: u32) -> Result<HashValue> {
412        let mut function = 0_u32;
413        let mut bytes = vec![0_u8; HashFunction::Sha512.digest_len()];
414        let status = unsafe {
415            ffi::aa_header::compression_rs_aa_header_get_field_hash(
416                self.as_ptr(),
417                index,
418                bytes.len(),
419                &mut function,
420                bytes.as_mut_ptr(),
421            )
422        };
423        util::status_result("AAHeaderGetFieldHash", status)?;
424        let function =
425            HashFunction::from_raw(function).ok_or(CompressionError::OperationFailed {
426                operation: "AAHeaderGetFieldHash",
427                code: -1,
428            })?;
429        bytes.truncate(function.digest_len());
430        Ok(HashValue { function, bytes })
431    }
432
433    pub fn field_timespec(&self, index: u32) -> Result<Timespec> {
434        let mut seconds = 0_i64;
435        let mut nanoseconds = 0_i64;
436        let status = unsafe {
437            ffi::aa_header::compression_rs_aa_header_get_field_timespec(
438                self.as_ptr(),
439                index,
440                &mut seconds,
441                &mut nanoseconds,
442            )
443        };
444        util::status_result("AAHeaderGetFieldTimespec", status)?;
445        Ok(Timespec {
446            seconds,
447            nanoseconds,
448        })
449    }
450
451    pub fn field_blob(&self, index: u32) -> Result<BlobDescription> {
452        let mut size = 0_u64;
453        let mut offset = 0_u64;
454        let status = unsafe {
455            ffi::aa_header::compression_rs_aa_header_get_field_blob(
456                self.as_ptr(),
457                index,
458                &mut size,
459                &mut offset,
460            )
461        };
462        util::status_result("AAHeaderGetFieldBlob", status)?;
463        Ok(BlobDescription { size, offset })
464    }
465
466    pub fn field_value(&self, index: u32) -> Result<HeaderFieldValue> {
467        match self.field_type(index)? {
468            FieldType::Flag => Ok(HeaderFieldValue::Flag),
469            FieldType::UInt => self.field_uint(index).map(HeaderFieldValue::UInt),
470            FieldType::String => self.field_string(index).map(HeaderFieldValue::String),
471            FieldType::Hash => self.field_hash(index).map(HeaderFieldValue::Hash),
472            FieldType::Timespec => self.field_timespec(index).map(HeaderFieldValue::Timespec),
473            FieldType::Blob => self.field_blob(index).map(HeaderFieldValue::Blob),
474        }
475    }
476
477    pub fn uint_with_key(&self, key: FieldKey) -> Result<Option<u64>> {
478        self.key_index(key)?
479            .map(|index| self.field_uint(index))
480            .transpose()
481    }
482
483    pub fn string_with_key(&self, key: FieldKey) -> Result<Option<String>> {
484        self.key_index(key)?
485            .map(|index| self.field_string(index))
486            .transpose()
487    }
488
489    pub fn hash_with_key(&self, key: FieldKey) -> Result<Option<HashValue>> {
490        self.key_index(key)?
491            .map(|index| self.field_hash(index))
492            .transpose()
493    }
494
495    pub fn timespec_with_key(&self, key: FieldKey) -> Result<Option<Timespec>> {
496        self.key_index(key)?
497            .map(|index| self.field_timespec(index))
498            .transpose()
499    }
500
501    pub fn blob_with_key(&self, key: FieldKey) -> Result<Option<BlobDescription>> {
502        self.key_index(key)?
503            .map(|index| self.field_blob(index))
504            .transpose()
505    }
506
507    pub fn value_with_key(&self, key: FieldKey) -> Result<Option<HeaderFieldValue>> {
508        self.key_index(key)?
509            .map(|index| self.field_value(index))
510            .transpose()
511    }
512
513    pub fn entry_type(&self) -> Result<Option<EntryType>> {
514        Ok(self
515            .uint_with_key(FieldKey::TYP)?
516            .and_then(|raw| u32::try_from(raw).ok())
517            .and_then(EntryType::from_raw))
518    }
519
520    pub fn path(&self) -> Result<Option<String>> {
521        self.string_with_key(FieldKey::PAT)
522    }
523
524    pub fn encoded_size(&self) -> usize {
525        unsafe { ffi::aa_header::compression_rs_aa_header_get_encoded_size(self.as_ptr()) }
526    }
527
528    pub fn encoded_data(&self) -> Result<Vec<u8>> {
529        let size = self.encoded_size();
530        let mut data = vec![0_u8; size];
531        if size == 0 {
532            return Ok(data);
533        }
534        let copied = unsafe {
535            ffi::aa_header::compression_rs_aa_header_copy_encoded_data(
536                self.as_ptr(),
537                data.as_mut_ptr(),
538            )
539        };
540        if copied {
541            Ok(data)
542        } else {
543            Err(CompressionError::OperationFailed {
544                operation: "AAHeaderGetEncodedData",
545                code: -1,
546            })
547        }
548    }
549}
550
551impl Clone for Header {
552    fn clone(&self) -> Self {
553        let handle = unsafe { ffi::aa_header::compression_rs_aa_header_clone(self.as_ptr()) };
554        Self::from_handle(handle, "AAHeaderClone").expect("AAHeaderClone returned null")
555    }
556}
557
558impl Drop for Header {
559    fn drop(&mut self) {
560        unsafe { ffi::aa_header::compression_rs_aa_header_release(self.as_ptr()) };
561    }
562}