Skip to main content

compression/
aa_byte_stream.rs

1use crate::{ffi, util, CompressionError, Result};
2use std::ffi::c_void;
3use std::fs::File;
4use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign};
5use std::os::fd::{IntoRawFd, RawFd};
6use std::ptr::NonNull;
7
8const READ_CHUNK_LEN: usize = 32 * 1024;
9
10/// Wraps `OPEN_READ_ONLY`.
11pub const OPEN_READ_ONLY: i32 = 0x0000;
12/// Wraps `OPEN_WRITE_ONLY`.
13pub const OPEN_WRITE_ONLY: i32 = 0x0001;
14/// Wraps `OPEN_READ_WRITE`.
15pub const OPEN_READ_WRITE: i32 = 0x0002;
16/// Wraps `OPEN_CREATE`.
17pub const OPEN_CREATE: i32 = 0x0200;
18/// Wraps `OPEN_TRUNCATE`.
19pub const OPEN_TRUNCATE: i32 = 0x0400;
20/// Wraps `DEFAULT_FILE_MODE`.
21pub const DEFAULT_FILE_MODE: u32 = 0o644;
22
23/// Wraps AppleArchive compression algorithm identifiers.
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
25pub enum ArchiveCompressionAlgorithm {
26    /// Wraps the `None` variant of `ArchiveCompressionAlgorithm`.
27    None,
28    /// Wraps the `Lz4` variant of `ArchiveCompressionAlgorithm`.
29    Lz4,
30    /// Wraps the `Zlib` variant of `ArchiveCompressionAlgorithm`.
31    Zlib,
32    /// Wraps the `Lzma` variant of `ArchiveCompressionAlgorithm`.
33    Lzma,
34    /// Wraps the `Lzfse` variant of `ArchiveCompressionAlgorithm`.
35    Lzfse,
36    /// Wraps the `Lzbitmap` variant of `ArchiveCompressionAlgorithm`.
37    Lzbitmap,
38}
39
40impl ArchiveCompressionAlgorithm {
41    /// Wraps raw AppleArchive compression algorithm values.
42    pub const fn from_raw(raw: u32) -> Option<Self> {
43        match raw {
44            0x000 => Some(Self::None),
45            0x100 => Some(Self::Lz4),
46            0x505 => Some(Self::Zlib),
47            0x306 => Some(Self::Lzma),
48            0x801 => Some(Self::Lzfse),
49            0x702 => Some(Self::Lzbitmap),
50            _ => None,
51        }
52    }
53
54    /// Wraps raw AppleArchive compression algorithm values.
55    pub const fn as_raw(self) -> u32 {
56        match self {
57            Self::None => 0x000,
58            Self::Lz4 => 0x100,
59            Self::Zlib => 0x505,
60            Self::Lzma => 0x306,
61            Self::Lzfse => 0x801,
62            Self::Lzbitmap => 0x702,
63        }
64    }
65}
66
67/// Wraps AppleArchive archive-processing flags.
68#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
69pub struct ArchiveFlags(u64);
70
71impl ArchiveFlags {
72    /// Wraps the `IGNORE_EPERM` AppleArchive archive flag bit.
73    pub const IGNORE_EPERM: Self = Self(1_u64 << 0);
74    /// Wraps the `ARCHIVE_DEDUPLICATE_DAT` AppleArchive archive flag bit.
75    pub const ARCHIVE_DEDUPLICATE_DAT: Self = Self(1_u64 << 1);
76    /// Wraps the `ARCHIVE_NO_RESOLVE_ACL_QUALIFIERS` AppleArchive archive flag bit.
77    pub const ARCHIVE_NO_RESOLVE_ACL_QUALIFIERS: Self = Self(1_u64 << 2);
78    /// Wraps the `REPLACE_ATTRIBUTES` AppleArchive archive flag bit.
79    pub const REPLACE_ATTRIBUTES: Self = Self(1_u64 << 3);
80    /// Wraps the `EXTRACT_NO_AUTO_DEDUP` AppleArchive archive flag bit.
81    pub const EXTRACT_NO_AUTO_DEDUP: Self = Self(1_u64 << 4);
82    /// Wraps the `EXTRACT_NO_AUTO_SPARSE` AppleArchive archive flag bit.
83    pub const EXTRACT_NO_AUTO_SPARSE: Self = Self(1_u64 << 5);
84    /// Wraps the `CROSS_VOLUME_BOUNDARIES` AppleArchive archive flag bit.
85    pub const CROSS_VOLUME_BOUNDARIES: Self = Self(1_u64 << 6);
86    /// Wraps the `EXTRACT_AUTO_DEDUP_AS_HARD_LINKS` AppleArchive archive flag bit.
87    pub const EXTRACT_AUTO_DEDUP_AS_HARD_LINKS: Self = Self(1_u64 << 7);
88    /// Wraps the `DECODE_INSERT_IDX` AppleArchive archive flag bit.
89    pub const DECODE_INSERT_IDX: Self = Self(1_u64 << 8);
90    /// Wraps the `EXCLUDE_METADATA_ENTRIES` AppleArchive archive flag bit.
91    pub const EXCLUDE_METADATA_ENTRIES: Self = Self(1_u64 << 9);
92    /// Wraps the `PROCESS_RANDOM_ACCESS_OUTPUT` AppleArchive archive flag bit.
93    pub const PROCESS_RANDOM_ACCESS_OUTPUT: Self = Self(1_u64 << 10);
94    /// Wraps the `VERBOSITY_0` AppleArchive archive flag bit.
95    pub const VERBOSITY_0: Self = Self(0_u64 << 62);
96    /// Wraps the `VERBOSITY_1` AppleArchive archive flag bit.
97    pub const VERBOSITY_1: Self = Self(1_u64 << 62);
98    /// Wraps the `VERBOSITY_2` AppleArchive archive flag bit.
99    pub const VERBOSITY_2: Self = Self(2_u64 << 62);
100    /// Wraps the `VERBOSITY_3` AppleArchive archive flag bit.
101    pub const VERBOSITY_3: Self = Self(3_u64 << 62);
102
103    /// Wraps an empty AppleArchive archive flag set.
104    pub const fn empty() -> Self {
105        Self(0)
106    }
107
108    /// Wraps the raw AppleArchive archive flag bits.
109    pub const fn bits(self) -> u64 {
110        self.0
111    }
112
113    /// Wraps raw AppleArchive archive flag bits.
114    pub const fn from_bits(bits: u64) -> Self {
115        Self(bits)
116    }
117
118    /// Wraps AppleArchive archive flag containment checks.
119    pub const fn contains(self, other: Self) -> bool {
120        (self.0 & other.0) == other.0
121    }
122
123    /// Wraps AppleArchive archive verbosity flag construction.
124    pub const fn verbosity(level: u64) -> Self {
125        let clamped = if level > 3 { 3 } else { level };
126        Self(clamped << 62)
127    }
128}
129
130impl BitOr for ArchiveFlags {
131    type Output = Self;
132
133    fn bitor(self, rhs: Self) -> Self::Output {
134        Self(self.0 | rhs.0)
135    }
136}
137
138impl BitOrAssign for ArchiveFlags {
139    fn bitor_assign(&mut self, rhs: Self) {
140        self.0 |= rhs.0;
141    }
142}
143
144impl BitAnd for ArchiveFlags {
145    type Output = Self;
146
147    fn bitand(self, rhs: Self) -> Self::Output {
148        Self(self.0 & rhs.0)
149    }
150}
151
152impl BitAndAssign for ArchiveFlags {
153    fn bitand_assign(&mut self, rhs: Self) {
154        self.0 &= rhs.0;
155    }
156}
157
158#[allow(dead_code)]
159#[derive(Debug)]
160pub enum ByteStreamUpstream {
161    Stream(Box<ByteStream>),
162}
163
164/// Wraps an `AAByteStream` handle.
165#[derive(Debug)]
166pub struct ByteStream {
167    handle: NonNull<c_void>,
168    _upstream: Option<ByteStreamUpstream>,
169    closed: bool,
170}
171
172impl ByteStream {
173    pub(crate) fn from_handle_with_upstream(
174        handle: *mut c_void,
175        operation: &'static str,
176        upstream: Option<ByteStreamUpstream>,
177    ) -> Result<Self> {
178        Ok(Self {
179            handle: util::nonnull_handle(handle, operation)?,
180            _upstream: upstream,
181            closed: false,
182        })
183    }
184
185    /// Wraps `AAFileStreamOpenWithFD`.
186    pub fn from_fd(fd: RawFd, automatic_close: bool) -> Result<Self> {
187        let handle = unsafe {
188            ffi::aa_byte_stream::compression_rs_aa_byte_stream_open_with_fd(fd, automatic_close)
189        };
190        Ok(Self {
191            handle: util::nonnull_handle(handle, "AAFileStreamOpenWithFD")?,
192            _upstream: None,
193            closed: false,
194        })
195    }
196
197    /// Wraps `AAFileStreamOpenWithFD` through `File` ownership.
198    pub fn from_file(file: File) -> Result<Self> {
199        Self::from_fd(file.into_raw_fd(), true)
200    }
201
202    /// Wraps `AAFileStreamOpenWithPath`.
203    pub fn open_with_path(path: &str, open_flags: i32, open_mode: u32) -> Result<Self> {
204        let path = util::cstring("path", path)?;
205        let handle = unsafe {
206            ffi::aa_byte_stream::compression_rs_aa_byte_stream_open_with_path(
207                path.as_ptr(),
208                open_flags,
209                open_mode,
210            )
211        };
212        Ok(Self {
213            handle: util::nonnull_handle(handle, "AAFileStreamOpenWithPath")?,
214            _upstream: None,
215            closed: false,
216        })
217    }
218
219    /// Wraps `AATempFileStreamOpen`.
220    pub fn temp_file() -> Result<Self> {
221        let handle = unsafe { ffi::aa_byte_stream::compression_rs_aa_temp_file_stream_open() };
222        Ok(Self {
223            handle: util::nonnull_handle(handle, "AATempFileStreamOpen")?,
224            _upstream: None,
225            closed: false,
226        })
227    }
228
229    /// Wraps `AASharedBufferPipeOpen`.
230    pub fn shared_buffer_pipe(buffer_capacity: usize) -> Result<(Self, Self)> {
231        let mut ostream = std::ptr::null_mut();
232        let mut istream = std::ptr::null_mut();
233        let status = unsafe {
234            ffi::aa_byte_stream::compression_rs_aa_shared_buffer_pipe_open(
235                &mut ostream,
236                &mut istream,
237                buffer_capacity,
238            )
239        };
240        util::status_result("AASharedBufferPipeOpen", status)?;
241        Ok((
242            Self {
243                handle: util::nonnull_handle(ostream, "AASharedBufferPipeOpen(ostream)")?,
244                _upstream: None,
245                closed: false,
246            },
247            Self {
248                handle: util::nonnull_handle(istream, "AASharedBufferPipeOpen(istream)")?,
249                _upstream: None,
250                closed: false,
251            },
252        ))
253    }
254
255    pub(crate) fn as_ptr(&self) -> *mut c_void {
256        self.handle.as_ptr()
257    }
258
259    pub(crate) fn mark_closed(&mut self) {
260        self.closed = true;
261    }
262
263    fn ensure_open(&self) -> Result<()> {
264        if self.closed {
265            Err(CompressionError::Closed {
266                resource: "byte stream",
267            })
268        } else {
269            Ok(())
270        }
271    }
272
273    /// Wraps `AACompressionOutputStreamOpen`.
274    pub fn into_compression_output(
275        self,
276        compression_algorithm: ArchiveCompressionAlgorithm,
277        block_size: usize,
278        flags: ArchiveFlags,
279        n_threads: i32,
280    ) -> Result<Self> {
281        let handle = unsafe {
282            ffi::aa_byte_stream::compression_rs_aa_compression_output_stream_open(
283                self.as_ptr(),
284                compression_algorithm.as_raw(),
285                block_size,
286                flags.bits(),
287                n_threads,
288            )
289        };
290        Ok(Self {
291            handle: util::nonnull_handle(handle, "AACompressionOutputStreamOpen")?,
292            _upstream: Some(ByteStreamUpstream::Stream(Box::new(self))),
293            closed: false,
294        })
295    }
296
297    /// Wraps `AACompressionOutputStreamOpenExisting`.
298    pub fn into_existing_compression_output(
299        self,
300        flags: ArchiveFlags,
301        n_threads: i32,
302    ) -> Result<Self> {
303        let handle = unsafe {
304            ffi::aa_byte_stream::compression_rs_aa_compression_output_stream_open_existing(
305                self.as_ptr(),
306                flags.bits(),
307                n_threads,
308            )
309        };
310        Ok(Self {
311            handle: util::nonnull_handle(handle, "AACompressionOutputStreamOpenExisting")?,
312            _upstream: Some(ByteStreamUpstream::Stream(Box::new(self))),
313            closed: false,
314        })
315    }
316
317    /// Wraps `AADecompressionInputStreamOpen`.
318    pub fn into_decompression_input(self, flags: ArchiveFlags, n_threads: i32) -> Result<Self> {
319        let handle = unsafe {
320            ffi::aa_byte_stream::compression_rs_aa_decompression_input_stream_open(
321                self.as_ptr(),
322                flags.bits(),
323                n_threads,
324            )
325        };
326        Ok(Self {
327            handle: util::nonnull_handle(handle, "AADecompressionInputStreamOpen")?,
328            _upstream: Some(ByteStreamUpstream::Stream(Box::new(self))),
329            closed: false,
330        })
331    }
332
333    /// Wraps `AADecompressionRandomAccessInputStreamOpen`.
334    pub fn into_random_access_decompression_input(
335        self,
336        alloc_limit: usize,
337        flags: ArchiveFlags,
338        n_threads: i32,
339    ) -> Result<Self> {
340        let handle = unsafe {
341            ffi::aa_byte_stream::compression_rs_aa_decompression_random_access_input_stream_open(
342                self.as_ptr(),
343                alloc_limit,
344                flags.bits(),
345                n_threads,
346            )
347        };
348        Ok(Self {
349            handle: util::nonnull_handle(handle, "AADecompressionRandomAccessInputStreamOpen")?,
350            _upstream: Some(ByteStreamUpstream::Stream(Box::new(self))),
351            closed: false,
352        })
353    }
354
355    /// Wraps `AAByteStreamWrite`.
356    pub fn write(&mut self, buffer: &[u8]) -> Result<usize> {
357        self.ensure_open()?;
358        util::ssize_result("AAByteStreamWrite", unsafe {
359            ffi::aa_byte_stream::compression_rs_aa_byte_stream_write(
360                self.as_ptr(),
361                buffer.as_ptr(),
362                buffer.len(),
363            )
364        })
365    }
366
367    /// Wraps `AAByteStreamWrite`.
368    pub fn write_all(&mut self, mut buffer: &[u8]) -> Result<()> {
369        while !buffer.is_empty() {
370            let written = self.write(buffer)?;
371            if written == 0 {
372                return Err(CompressionError::OperationFailed {
373                    operation: "AAByteStreamWrite",
374                    code: -1,
375                });
376            }
377            buffer = &buffer[written..];
378        }
379        Ok(())
380    }
381
382    /// Wraps `AAByteStreamPWrite`.
383    pub fn pwrite(&mut self, buffer: &[u8], offset: i64) -> Result<usize> {
384        self.ensure_open()?;
385        util::ssize_result("AAByteStreamPWrite", unsafe {
386            ffi::aa_byte_stream::compression_rs_aa_byte_stream_pwrite(
387                self.as_ptr(),
388                buffer.as_ptr(),
389                buffer.len(),
390                offset,
391            )
392        })
393    }
394
395    /// Wraps `AAByteStreamRead`.
396    pub fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
397        self.ensure_open()?;
398        util::ssize_result("AAByteStreamRead", unsafe {
399            ffi::aa_byte_stream::compression_rs_aa_byte_stream_read(
400                self.as_ptr(),
401                buffer.as_mut_ptr(),
402                buffer.len(),
403            )
404        })
405    }
406
407    /// Wraps `AAByteStreamPRead`.
408    pub fn pread(&mut self, buffer: &mut [u8], offset: i64) -> Result<usize> {
409        self.ensure_open()?;
410        util::ssize_result("AAByteStreamPRead", unsafe {
411            ffi::aa_byte_stream::compression_rs_aa_byte_stream_pread(
412                self.as_ptr(),
413                buffer.as_mut_ptr(),
414                buffer.len(),
415                offset,
416            )
417        })
418    }
419
420    /// Wraps `AAByteStreamSeek`.
421    pub fn seek(&mut self, offset: i64, whence: i32) -> Result<u64> {
422        self.ensure_open()?;
423        util::off_t_result("AAByteStreamSeek", unsafe {
424            ffi::aa_byte_stream::compression_rs_aa_byte_stream_seek(self.as_ptr(), offset, whence)
425        })
426    }
427
428    /// Wraps repeated `AAByteStreamRead` calls.
429    pub fn read_to_end(&mut self) -> Result<Vec<u8>> {
430        let mut output = Vec::new();
431        loop {
432            let mut buffer = vec![0_u8; READ_CHUNK_LEN];
433            let read = self.read(&mut buffer)?;
434            if read == 0 {
435                return Ok(output);
436            }
437            output.extend_from_slice(&buffer[..read]);
438        }
439    }
440
441    /// Wraps `AAByteStreamClose`.
442    pub fn cancel(&mut self) -> Result<()> {
443        self.ensure_open()?;
444        unsafe { ffi::aa_byte_stream::compression_rs_aa_byte_stream_cancel(self.as_ptr()) };
445        Ok(())
446    }
447
448    #[deprecated(
449        since = "0.2.2",
450        note = "Use ByteStream::cancel; AAByteStreamAbort is a deprecated AppleArchive compatibility shim."
451    )]
452    /// Wraps `AAByteStreamClose`.
453    pub fn abort(&mut self) -> Result<()> {
454        self.ensure_open()?;
455        unsafe { ffi::aa_byte_stream::compression_rs_aa_byte_stream_abort(self.as_ptr()) };
456        Ok(())
457    }
458
459    /// Wraps `AAByteStreamClose`.
460    pub fn close(&mut self) -> Result<()> {
461        if self.closed {
462            return Ok(());
463        }
464        let status =
465            unsafe { ffi::aa_byte_stream::compression_rs_aa_byte_stream_close(self.as_ptr()) };
466        self.closed = true;
467        util::status_result("AAByteStreamClose", status)
468    }
469
470    /// Wraps `AAByteStreamProcess`.
471    pub fn process_into(&mut self, output: &mut Self) -> Result<u64> {
472        self.ensure_open()?;
473        output.ensure_open()?;
474        util::off_t_result("AAByteStreamProcess", unsafe {
475            ffi::aa_byte_stream::compression_rs_aa_byte_stream_process(
476                self.as_ptr(),
477                output.as_ptr(),
478            )
479        })
480    }
481
482    /// Wraps `AARandomAccessByteStreamProcess`.
483    pub fn process_random_access_into(
484        &mut self,
485        output: &mut Self,
486        max_offset: i64,
487        block_size: usize,
488        flags: ArchiveFlags,
489        n_threads: i32,
490    ) -> Result<u64> {
491        self.ensure_open()?;
492        output.ensure_open()?;
493        util::off_t_result("AARandomAccessByteStreamProcess", unsafe {
494            ffi::aa_byte_stream::compression_rs_aa_random_access_byte_stream_process(
495                self.as_ptr(),
496                output.as_ptr(),
497                max_offset,
498                block_size,
499                flags.bits(),
500                n_threads,
501            )
502        })
503    }
504}
505
506impl Drop for ByteStream {
507    fn drop(&mut self) {
508        unsafe { ffi::aa_byte_stream::compression_rs_aa_byte_stream_release(self.as_ptr()) };
509    }
510}
511
512fn custom_byte_stream_error(operation: &'static str) -> CompressionError {
513    CompressionError::OperationFailed {
514        operation,
515        code: -1,
516    }
517}
518
519fn custom_byte_stream_code(error: &CompressionError) -> i32 {
520    match error {
521        CompressionError::OperationFailed { code, .. } if *code < 0 => *code,
522        _ => -1,
523    }
524}
525
526struct CustomByteStreamState {
527    callbacks: Box<dyn CustomByteStreamCallbacks>,
528}
529
530/// Wraps callbacks installed by `AACustomByteStreamSet*Proc`.
531pub trait CustomByteStreamCallbacks {
532    /// Wraps `AAByteStreamWrite`.
533    fn write(&mut self, _buffer: &[u8]) -> Result<usize> {
534        Err(custom_byte_stream_error("AAByteStreamWrite"))
535    }
536
537    /// Wraps `AAByteStreamPWrite`.
538    fn pwrite(&mut self, _buffer: &[u8], _offset: i64) -> Result<usize> {
539        Err(custom_byte_stream_error("AAByteStreamPWrite"))
540    }
541
542    /// Wraps `AAByteStreamRead`.
543    fn read(&mut self, _buffer: &mut [u8]) -> Result<usize> {
544        Err(custom_byte_stream_error("AAByteStreamRead"))
545    }
546
547    /// Wraps `AAByteStreamPRead`.
548    fn pread(&mut self, _buffer: &mut [u8], _offset: i64) -> Result<usize> {
549        Err(custom_byte_stream_error("AAByteStreamPRead"))
550    }
551
552    /// Wraps `AAByteStreamSeek`.
553    fn seek(&mut self, _offset: i64, _whence: i32) -> Result<i64> {
554        Err(custom_byte_stream_error("AAByteStreamSeek"))
555    }
556
557    /// Wraps the `cancel` convenience for `CustomByteStreamCallbacks`.
558    fn cancel(&mut self) {}
559
560    /// Wraps the `close` convenience for `CustomByteStreamCallbacks`.
561    fn close(&mut self) -> Result<()> {
562        Ok(())
563    }
564}
565
566unsafe fn custom_byte_stream_slice<'a>(buffer: *const c_void, length: usize) -> Option<&'a [u8]> {
567    if length == 0 {
568        Some(&[])
569    } else if buffer.is_null() {
570        None
571    } else {
572        Some(unsafe { std::slice::from_raw_parts(buffer.cast::<u8>(), length) })
573    }
574}
575
576unsafe fn custom_byte_stream_slice_mut<'a>(
577    buffer: *mut c_void,
578    length: usize,
579) -> Option<&'a mut [u8]> {
580    if length == 0 {
581        Some(&mut [])
582    } else if buffer.is_null() {
583        None
584    } else {
585        Some(unsafe { std::slice::from_raw_parts_mut(buffer.cast::<u8>(), length) })
586    }
587}
588
589unsafe fn custom_byte_stream_state(arg: *mut c_void) -> Option<&'static mut CustomByteStreamState> {
590    if arg.is_null() {
591        None
592    } else {
593        Some(unsafe { &mut *arg.cast::<CustomByteStreamState>() })
594    }
595}
596
597/// Run a user byte-stream callback body, converting a panic into the failure
598/// status `-1` instead of letting it unwind across the C ABI (UB).
599fn guard_byte_i64(f: impl FnOnce() -> i64) -> i64 {
600    std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(-1)
601}
602
603unsafe extern "C" fn custom_byte_stream_write(
604    arg: *mut c_void,
605    buffer: *const c_void,
606    length: usize,
607) -> i64 {
608    let Some(state) = (unsafe { custom_byte_stream_state(arg) }) else {
609        return -1;
610    };
611    let Some(buffer) = (unsafe { custom_byte_stream_slice(buffer, length) }) else {
612        return -1;
613    };
614    guard_byte_i64(|| match state.callbacks.write(buffer) {
615        Ok(count) => i64::try_from(count).unwrap_or(i64::MAX),
616        Err(error) => i64::from(custom_byte_stream_code(&error)),
617    })
618}
619
620unsafe extern "C" fn custom_byte_stream_pwrite(
621    arg: *mut c_void,
622    buffer: *const c_void,
623    length: usize,
624    offset: i64,
625) -> i64 {
626    let Some(state) = (unsafe { custom_byte_stream_state(arg) }) else {
627        return -1;
628    };
629    let Some(buffer) = (unsafe { custom_byte_stream_slice(buffer, length) }) else {
630        return -1;
631    };
632    guard_byte_i64(|| match state.callbacks.pwrite(buffer, offset) {
633        Ok(count) => i64::try_from(count).unwrap_or(i64::MAX),
634        Err(error) => i64::from(custom_byte_stream_code(&error)),
635    })
636}
637
638unsafe extern "C" fn custom_byte_stream_read(
639    arg: *mut c_void,
640    buffer: *mut c_void,
641    length: usize,
642) -> i64 {
643    let Some(state) = (unsafe { custom_byte_stream_state(arg) }) else {
644        return -1;
645    };
646    let Some(buffer) = (unsafe { custom_byte_stream_slice_mut(buffer, length) }) else {
647        return -1;
648    };
649    guard_byte_i64(|| match state.callbacks.read(buffer) {
650        Ok(count) => i64::try_from(count).unwrap_or(i64::MAX),
651        Err(error) => i64::from(custom_byte_stream_code(&error)),
652    })
653}
654
655unsafe extern "C" fn custom_byte_stream_pread(
656    arg: *mut c_void,
657    buffer: *mut c_void,
658    length: usize,
659    offset: i64,
660) -> i64 {
661    let Some(state) = (unsafe { custom_byte_stream_state(arg) }) else {
662        return -1;
663    };
664    let Some(buffer) = (unsafe { custom_byte_stream_slice_mut(buffer, length) }) else {
665        return -1;
666    };
667    guard_byte_i64(|| match state.callbacks.pread(buffer, offset) {
668        Ok(count) => i64::try_from(count).unwrap_or(i64::MAX),
669        Err(error) => i64::from(custom_byte_stream_code(&error)),
670    })
671}
672
673unsafe extern "C" fn custom_byte_stream_seek(arg: *mut c_void, offset: i64, whence: i32) -> i64 {
674    let Some(state) = (unsafe { custom_byte_stream_state(arg) }) else {
675        return -1;
676    };
677    guard_byte_i64(|| match state.callbacks.seek(offset, whence) {
678        Ok(position) => position,
679        Err(error) => i64::from(custom_byte_stream_code(&error)),
680    })
681}
682
683unsafe extern "C" fn custom_byte_stream_cancel(arg: *mut c_void) {
684    if let Some(state) = unsafe { custom_byte_stream_state(arg) } {
685        let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| state.callbacks.cancel()));
686    }
687}
688
689unsafe extern "C" fn custom_byte_stream_close(arg: *mut c_void) -> i32 {
690    if arg.is_null() {
691        return 0;
692    }
693    let mut state = unsafe { Box::from_raw(arg.cast::<CustomByteStreamState>()) };
694    std::panic::catch_unwind(std::panic::AssertUnwindSafe(
695        || match state.callbacks.close() {
696            Ok(()) => 0,
697            Err(error) => custom_byte_stream_code(&error),
698        },
699    ))
700    .unwrap_or(-1)
701}
702
703impl ByteStream {
704    /// Wraps `AACustomByteStreamOpen`.
705    pub fn custom<T: CustomByteStreamCallbacks + 'static>(callbacks: T) -> Result<Self> {
706        let handle = unsafe { ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_open() };
707        let stream = Self::from_handle_with_upstream(handle, "AACustomByteStreamOpen", None)?;
708        let state = Box::new(CustomByteStreamState {
709            callbacks: Box::new(callbacks),
710        });
711        let data = Box::into_raw(state).cast::<c_void>();
712        unsafe {
713            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_data(
714                stream.as_ptr(),
715                data,
716            );
717            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_write_proc(
718                stream.as_ptr(),
719                Some(custom_byte_stream_write),
720            );
721            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_pwrite_proc(
722                stream.as_ptr(),
723                Some(custom_byte_stream_pwrite),
724            );
725            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_read_proc(
726                stream.as_ptr(),
727                Some(custom_byte_stream_read),
728            );
729            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_pread_proc(
730                stream.as_ptr(),
731                Some(custom_byte_stream_pread),
732            );
733            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_seek_proc(
734                stream.as_ptr(),
735                Some(custom_byte_stream_seek),
736            );
737            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_cancel_proc(
738                stream.as_ptr(),
739                Some(custom_byte_stream_cancel),
740            );
741            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_abort_proc(
742                stream.as_ptr(),
743                Some(custom_byte_stream_cancel),
744            );
745            ffi::aa_byte_stream::compression_rs_aa_custom_byte_stream_set_close_proc(
746                stream.as_ptr(),
747                Some(custom_byte_stream_close),
748            );
749        }
750        Ok(stream)
751    }
752}