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