Skip to main content

libbz2_rs_sys/
bzlib.rs

1use core::ffi::{c_char, c_int, c_uint, c_void};
2use core::mem::offset_of;
3use core::{mem, ptr};
4
5use crate::allocator::Allocator;
6use crate::compress::compress_block;
7use crate::crctable::BZ2_CRC32TABLE;
8use crate::debug_log;
9use crate::decompress::{self, decompress};
10#[cfg(feature = "stdio")]
11use crate::libbz2_rs_sys_version;
12
13#[cfg(feature = "stdio")]
14pub use crate::high_level::*;
15
16pub(crate) const BZ_MAX_ALPHA_SIZE: usize = 258;
17pub(crate) const BZ_MAX_CODE_LEN: usize = 23;
18
19pub(crate) const BZ_N_GROUPS: usize = 6;
20pub(crate) const BZ_N_ITERS: usize = 4;
21
22pub(crate) const BZ_G_SIZE: usize = 50;
23pub(crate) const BZ_MAX_SELECTORS: u16 = {
24    let tmp = 2 + (900000 / BZ_G_SIZE);
25    assert!(tmp >> 16 == 0);
26    tmp as u16
27};
28
29pub(crate) const BZ_RUNA: u16 = 0;
30pub(crate) const BZ_RUNB: u16 = 1;
31
32pub(crate) const BZ_MAX_UNUSED_U32: u32 = 5000;
33
34#[cfg(doc)]
35use crate::{
36    BZ_CONFIG_ERROR, BZ_DATA_ERROR, BZ_DATA_ERROR_MAGIC, BZ_FINISH, BZ_FINISH_OK, BZ_FLUSH,
37    BZ_FLUSH_OK, BZ_IO_ERROR, BZ_MEM_ERROR, BZ_OK, BZ_OUTBUFF_FULL, BZ_PARAM_ERROR, BZ_RUN,
38    BZ_RUN_OK, BZ_SEQUENCE_ERROR, BZ_STREAM_END, BZ_UNEXPECTED_EOF,
39};
40
41#[cfg(feature = "custom-prefix")]
42macro_rules! prefix {
43    ($name:expr) => {
44        concat!(env!("LIBBZ2_RS_SYS_PREFIX"), stringify!($name))
45    };
46}
47
48// NOTE: once we reach 1.0.0, the macro used for the `semver-prefix` feature should no longer include the
49// minor version in the name. The name is meant to be unique between semver-compatible versions!
50const _PRE_ONE_DOT_O: () = assert!(env!("CARGO_PKG_VERSION_MAJOR").as_bytes()[0] == b'0');
51
52#[cfg(feature = "semver-prefix")]
53macro_rules! prefix {
54    ($name:expr) => {
55        concat!(
56            "LIBBZ2_RS_SYS_v",
57            env!("CARGO_PKG_VERSION_MAJOR"),
58            ".",
59            env!("CARGO_PKG_VERSION_MINOR"),
60            ".x_",
61            stringify!($name)
62        )
63    };
64}
65
66#[cfg(all(
67    not(feature = "custom-prefix"),
68    not(feature = "semver-prefix"),
69    not(any(test, feature = "testing-prefix"))
70))]
71macro_rules! prefix {
72    ($name:expr) => {
73        stringify!($name)
74    };
75}
76
77#[cfg(all(
78    not(feature = "custom-prefix"),
79    not(feature = "semver-prefix"),
80    any(test, feature = "testing-prefix")
81))]
82macro_rules! prefix {
83    ($name:expr) => {
84        concat!("LIBBZ2_RS_SYS_TEST_", stringify!($name))
85    };
86}
87
88pub(crate) use prefix;
89
90/// The version of the libbzip2-rs-sys library.
91///
92/// Its value is a pointer to a NULL-terminated sequence of bytes.
93///
94/// The version string for this release is `
95#[doc = libbz2_rs_sys_version!()]
96/// `:
97///
98/// - The first component is the version of stock bzip2 that this release is compatible with
99/// - The final component is the libbzip2-rs-sys version used to build this release.
100#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzlibVersion))]
101#[cfg(feature = "stdio")]
102pub const extern "C" fn BZ2_bzlibVersion() -> *const core::ffi::c_char {
103    const LIBBZ2_RS_SYS_VERSION: &str = concat!(libbz2_rs_sys_version!(), "\0");
104    LIBBZ2_RS_SYS_VERSION.as_ptr().cast::<core::ffi::c_char>()
105}
106
107type AllocFunc = unsafe extern "C" fn(*mut c_void, c_int, c_int) -> *mut c_void;
108type FreeFunc = unsafe extern "C" fn(*mut c_void, *mut c_void) -> ();
109
110/// The current stream state.
111///
112/// # Custom allocators
113///
114/// The low-level API supports passing in a custom allocator as part of the [`bz_stream`]:
115///
116/// ```no_check
117/// struct bz_stream {
118///     // ...
119///     pub bzalloc: Option<unsafe extern "C" fn(_: *mut c_void, _: c_int, _: c_int) -> *mut c_void>,
120///     pub bzfree: Option<unsafe extern "C" fn(_: *mut c_void, _: *mut c_void)>,
121///     pub opaque: *mut c_void,
122/// }
123/// ```
124///
125/// When these fields are `None` (or `NULL` in C), the initialization functions will try to
126/// put in a default allocator, based on feature flags:
127///
128/// - `"rust-allocator"` uses the rust global allocator
129/// - `"c-allocator"` uses an allocator based on `malloc` and `free`
130///
131/// When both configured, `"rust-allocator"` is preferred. When no default allocator is configured,
132/// the high-level interface will return a [`BZ_CONFIG_ERROR`]. The low-level interface (the
133/// functions that take a [`bz_stream`] as their argument) return a [`BZ_PARAM_ERROR`], unless the
134/// user set the `bzalloc` and `bzfree` fields.
135///
136/// When custom `bzalloc` and `bzfree` functions are given, they must adhere to the following contract
137/// to be safe:
138///
139/// - a call `bzalloc(opaque, n, m)` must return a pointer `p` to `n * m` bytes of memory, or
140///   `NULL` if out of memory
141/// - a call `bzfree(opaque, p)` must free that memory
142///
143/// The `strm.opaque` value is passed to as the first argument to all calls to `bzalloc`
144/// and `bzfree`, but is otherwise ignored by the library.
145#[allow(non_camel_case_types)]
146#[repr(C)]
147pub struct bz_stream {
148    pub next_in: *const c_char,
149    pub avail_in: c_uint,
150    pub total_in_lo32: c_uint,
151    pub total_in_hi32: c_uint,
152    pub next_out: *mut c_char,
153    pub avail_out: c_uint,
154    pub total_out_lo32: c_uint,
155    pub total_out_hi32: c_uint,
156    pub state: *mut c_void,
157    pub bzalloc: Option<AllocFunc>,
158    pub bzfree: Option<FreeFunc>,
159    pub opaque: *mut c_void,
160}
161
162pub(crate) use stream::*;
163mod stream {
164    use super::*;
165
166    #[repr(C)]
167    pub(crate) struct BzStream<S: StreamState> {
168        pub next_in: *const c_char,
169        pub avail_in: c_uint,
170        pub total_in_lo32: c_uint,
171        pub total_in_hi32: c_uint,
172        pub next_out: *mut c_char,
173        pub avail_out: c_uint,
174        pub total_out_lo32: c_uint,
175        pub total_out_hi32: c_uint,
176        pub state: *mut S,
177        pub bzalloc: Option<AllocFunc>,
178        pub bzfree: Option<FreeFunc>,
179        pub opaque: *mut c_void,
180    }
181
182    macro_rules! check_layout {
183    ($($field:ident,)*) => {
184        const _: () = {
185            $(assert!(offset_of!(bz_stream, $field) == offset_of!(BzStream<DState>, $field));)*
186            $(assert!(offset_of!(bz_stream, $field) == offset_of!(BzStream<EState>, $field));)*
187        };
188    };
189}
190
191    check_layout!(
192        next_in,
193        avail_in,
194        total_in_lo32,
195        total_in_hi32,
196        next_out,
197        avail_out,
198        total_out_lo32,
199        total_out_hi32,
200        state,
201        bzalloc,
202        bzfree,
203        opaque,
204    );
205
206    pub(crate) trait StreamState {}
207
208    impl StreamState for EState {}
209    impl StreamState for DState {}
210
211    impl bz_stream {
212        pub const fn zeroed() -> Self {
213            Self {
214                next_in: ptr::null_mut::<c_char>(),
215                avail_in: 0,
216                total_in_lo32: 0,
217                total_in_hi32: 0,
218                next_out: ptr::null_mut::<c_char>(),
219                avail_out: 0,
220                total_out_lo32: 0,
221                total_out_hi32: 0,
222                state: ptr::null_mut::<c_void>(),
223                bzalloc: None,
224                bzfree: None,
225                opaque: ptr::null_mut::<c_void>(),
226            }
227        }
228    }
229
230    impl<S: StreamState> BzStream<S> {
231        pub(crate) const fn zeroed() -> Self {
232            Self {
233                next_in: ptr::null_mut::<c_char>(),
234                avail_in: 0,
235                total_in_lo32: 0,
236                total_in_hi32: 0,
237                next_out: ptr::null_mut::<c_char>(),
238                avail_out: 0,
239                total_out_lo32: 0,
240                total_out_hi32: 0,
241                state: ptr::null_mut::<S>(),
242                bzalloc: None,
243                bzfree: None,
244                opaque: ptr::null_mut::<c_void>(),
245            }
246        }
247
248        /// # Safety
249        ///
250        /// The given [`bz_stream`] must either have a NULL state or be initialized with the state
251        /// indicated by the generic param `S`. It must also have `bzalloc`/`bzfree`/`opaque` correctly
252        /// configured.
253        pub(crate) unsafe fn from_mut(s: &mut bz_stream) -> &mut Self {
254            unsafe { mem::transmute(s) }
255        }
256
257        /// # Safety
258        ///
259        /// The given [`bz_stream`] must be initialized and either have a NULL state or be initialized
260        /// with the state indicated by the generic param `S`. It must also have
261        /// `bzalloc`/`bzfree`/`opaque` correctly configured.
262        pub(crate) unsafe fn from_ptr<'a>(p: *mut bz_stream) -> Option<&'a mut Self> {
263            unsafe { p.cast::<Self>().as_mut() }
264        }
265
266        pub(super) fn allocator(&self) -> Option<Allocator> {
267            unsafe { Allocator::from_bz_stream(self) }
268        }
269
270        /// Read up to 7 bytes into the bit buffer.
271        ///
272        /// The caller is responsible for updating `self.total_in`!
273        #[must_use]
274        #[inline(always)]
275        pub(crate) fn pull_u64(
276            &mut self,
277            mut bit_buffer: u64,
278            bits_used: i32,
279        ) -> Option<(u64, i32)> {
280            // we should only ask for more input if there are at least 8 free bits
281            debug_assert!(bits_used <= 56);
282
283            if self.avail_in < 8 {
284                return None;
285            }
286
287            // of course this uses big endian values
288            let read = u64::from_be_bytes(unsafe { self.next_in.cast::<[u8; 8]>().read() });
289
290            // because of the endianness, we can only shift in whole bytes.
291            // this calculates the number of available bits, rounded down to the nearest multiple
292            // of 8.
293            let increment_bits = (63 - bits_used) & !7;
294
295            // shift existing bits to the end, and or new bits in
296            bit_buffer = (bit_buffer << increment_bits) | (read >> (64 - increment_bits));
297
298            // we read 8 bytes above, but can only process `increment_bytes` worth of bits
299            let increment_bytes = increment_bits / 8;
300            self.next_in = unsafe { (self.next_in).add(increment_bytes as usize) };
301            self.avail_in -= increment_bytes as u32;
302
303            // skips updating `self.total_in`: the caller is responsible for keeping it updated
304
305            Some((bit_buffer, bits_used + increment_bits))
306        }
307
308        /// Read exactly 1 byte into the buffer
309        ///
310        /// The caller is responsible for updating `self.total_in`!
311        #[must_use]
312        #[inline(always)]
313        pub(crate) fn pull_u8(
314            &mut self,
315            mut bit_buffer: u64,
316            bits_used: i32,
317        ) -> Option<(u64, i32)> {
318            // we should only ask for more input if there are at least 8 free bits
319            debug_assert!(bits_used <= 56);
320
321            if self.avail_in == 0 || bits_used > 56 {
322                return None;
323            }
324
325            let read = unsafe { self.next_in.cast::<u8>().read() };
326            bit_buffer <<= 8;
327            bit_buffer |= u64::from(read);
328
329            self.next_in = unsafe { (self.next_in).offset(1) };
330            self.avail_in -= 1;
331
332            // skips updating `self.total_in`: the caller is responsible for keeping it updated
333
334            Some((bit_buffer, bits_used + 8))
335        }
336
337        #[must_use]
338        pub(crate) fn read_byte(&mut self) -> Option<u8> {
339            if self.avail_in == 0 {
340                return None;
341            }
342            let b = unsafe { self.next_in.cast::<u8>().read() };
343            self.next_in = unsafe { (self.next_in).offset(1) };
344            self.avail_in -= 1;
345            self.total_in_lo32 = (self.total_in_lo32).wrapping_add(1);
346            if self.total_in_lo32 == 0 {
347                self.total_in_hi32 = (self.total_in_hi32).wrapping_add(1);
348            }
349            Some(b)
350        }
351
352        #[must_use]
353        pub(super) fn write_byte(&mut self, byte: u8) -> bool {
354            if self.avail_out == 0 {
355                return false;
356            }
357            unsafe {
358                *self.next_out = byte as c_char;
359            }
360            self.avail_out -= 1;
361            self.next_out = unsafe { (self.next_out).offset(1) };
362            self.total_out_lo32 = (self.total_out_lo32).wrapping_add(1);
363            if self.total_out_lo32 == 0 {
364                self.total_out_hi32 = (self.total_out_hi32).wrapping_add(1);
365            }
366            true
367        }
368    }
369
370    pub(super) fn configure_allocator<S: StreamState>(strm: &mut BzStream<S>) -> Option<Allocator> {
371        match (strm.bzalloc, strm.bzfree) {
372            (Some(allocate), Some(deallocate)) => {
373                Some(Allocator::custom(allocate, deallocate, strm.opaque))
374            }
375            (None, None) => {
376                let allocator = Allocator::DEFAULT?;
377                let (bzalloc, bzfree) = Allocator::default_function_pointers()?;
378
379                strm.bzalloc = Some(bzalloc);
380                strm.bzfree = Some(bzfree);
381
382                Some(allocator)
383            }
384            // Using a different allocator for alloc and free is UB. The user of libbzip2-rs can't get a
385            // reference to the default alloc or free function, so hitting this path means that using
386            // the default alloc or free function would cause two allocators to be mixed. As such return
387            // an error to prevent UB.
388            #[cfg(any(feature = "rust-allocator", not(feature = "c-allocator")))]
389            _ => None,
390
391            #[cfg(all(feature = "c-allocator", not(feature = "rust-allocator")))]
392            _ => {
393                // this is almost certainly a bug, but replicates the original C behavior.
394                //
395                // Note that this logic does not really work with the default rust allocator, because
396                // it will panic at runtime when called directly. Usually the idea here is that
397                // allocation is special, and free is just the default `libc::free` that we configure
398                // by default with the default C allocator.
399                let (default_bzalloc, default_bzfree) = crate::allocator::c_allocator::ALLOCATOR;
400
401                let bzalloc = strm.bzalloc.get_or_insert(default_bzalloc);
402                let bzfree = strm.bzfree.get_or_insert(default_bzfree);
403
404                Some(Allocator::custom(*bzalloc, *bzfree, strm.opaque))
405            }
406        }
407    }
408}
409
410#[repr(i32)]
411#[derive(Debug, Clone, Copy, PartialEq, Eq)]
412#[allow(non_camel_case_types)]
413pub(crate) enum ReturnCode {
414    BZ_OK = 0,
415    BZ_RUN_OK = 1,
416    BZ_FLUSH_OK = 2,
417    BZ_FINISH_OK = 3,
418    BZ_STREAM_END = 4,
419    BZ_SEQUENCE_ERROR = -1,
420    BZ_PARAM_ERROR = -2,
421    BZ_MEM_ERROR = -3,
422    BZ_DATA_ERROR = -4,
423    BZ_DATA_ERROR_MAGIC = -5,
424    BZ_IO_ERROR = -6,
425    BZ_UNEXPECTED_EOF = -7,
426    BZ_OUTBUFF_FULL = -8,
427    BZ_CONFIG_ERROR = -9,
428}
429
430#[repr(u8)]
431#[derive(Copy, Clone)]
432pub(crate) enum Mode {
433    Idle,
434    Running,
435    Flushing,
436    Finishing,
437}
438
439#[repr(u8)]
440#[derive(Copy, Clone)]
441pub(crate) enum State {
442    Output,
443    Input,
444}
445
446pub(crate) const BZ_N_RADIX: u32 = 2;
447pub(crate) const BZ_N_QSORT: u32 = 12;
448pub(crate) const BZ_N_SHELL: u32 = 18;
449pub(crate) const BZ_N_OVERSHOOT: usize = (BZ_N_RADIX + BZ_N_QSORT + BZ_N_SHELL + 2) as usize;
450
451pub(crate) const FTAB_LEN: usize = u16::MAX as usize + 2;
452
453pub(crate) struct EState {
454    pub strm_addr: usize, // Only for a consistency check
455    pub mode: Mode,
456    pub state: State,
457    pub avail_in_expect: u32,
458    pub arr1: Arr1,
459    pub arr2: Arr2,
460    pub ftab: Ftab,
461    pub origPtr: i32,
462    pub writer: crate::compress::EWriter,
463    pub workFactor: i32,
464    pub state_in_ch: u32,
465    pub state_in_len: i32,
466    pub nblock: i32,
467    pub nblockMAX: i32,
468    pub state_out_pos: i32,
469    pub nInUse: i32,
470    pub inUse: [bool; 256],
471    pub unseqToSeq: [u8; 256],
472    pub blockCRC: u32,
473    pub combinedCRC: u32,
474    pub verbosity: i32,
475    pub blockNo: i32,
476    pub blockSize100k: i32,
477    pub nMTF: i32,
478    pub mtfFreq: [i32; 258],
479    pub selector: [u8; 18002],
480    pub selectorMtf: [u8; 18002],
481    pub len: [[u8; BZ_MAX_ALPHA_SIZE]; BZ_N_GROUPS],
482    pub code: [[u32; 258]; 6],
483    pub rfreq: [[i32; 258]; 6],
484    pub len_pack: [[u32; 4]; 258],
485}
486
487/// Creates a new pointer that is dangling, but well-aligned.
488pub(crate) fn dangling<T>() -> *mut T {
489    ptr::null_mut::<T>().wrapping_add(mem::align_of::<T>())
490}
491
492pub(crate) struct Arr1 {
493    ptr: *mut u32,
494    len: usize,
495}
496
497impl Arr1 {
498    fn alloc(allocator: &Allocator, len: usize) -> Option<Self> {
499        let ptr = allocator.allocate_zeroed(len)?;
500        Some(Self { ptr, len })
501    }
502
503    unsafe fn dealloc(&mut self, allocator: &Allocator) {
504        let this = mem::replace(
505            self,
506            Self {
507                ptr: dangling(),
508                len: 0,
509            },
510        );
511        if this.len != 0 {
512            unsafe { allocator.deallocate(this.ptr, this.len) }
513        }
514    }
515
516    pub(crate) fn mtfv(&mut self) -> &mut [u16] {
517        unsafe { core::slice::from_raw_parts_mut(self.ptr.cast(), self.len * 2) }
518    }
519
520    pub(crate) fn ptr(&mut self) -> &mut [u32] {
521        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
522    }
523}
524
525pub(crate) struct Arr2 {
526    ptr: *mut u32,
527    len: usize,
528}
529
530impl Arr2 {
531    fn alloc(allocator: &Allocator, len: usize) -> Option<Self> {
532        let ptr = allocator.allocate_zeroed(len)?;
533        Some(Self { ptr, len })
534    }
535
536    unsafe fn dealloc(&mut self, allocator: &Allocator) {
537        let this = mem::replace(
538            self,
539            Self {
540                ptr: dangling(),
541                len: 0,
542            },
543        );
544        if this.len != 0 {
545            unsafe { allocator.deallocate(this.ptr, this.len) }
546        }
547    }
548
549    pub(crate) fn eclass(&mut self) -> &mut [u32] {
550        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
551    }
552
553    pub(crate) fn zbits(&mut self, nblock: usize) -> &mut [u8] {
554        assert!(nblock <= 4 * self.len);
555        unsafe {
556            core::slice::from_raw_parts_mut(
557                self.ptr.cast::<u8>().add(nblock),
558                self.len * 4 - nblock,
559            )
560        }
561    }
562
563    pub(crate) fn raw_block(&mut self) -> &mut [u8] {
564        unsafe { core::slice::from_raw_parts_mut(self.ptr.cast(), self.len * 4) }
565    }
566
567    pub(crate) fn block(&mut self, nblock: usize) -> &mut [u8] {
568        assert!(nblock <= 4 * self.len);
569        unsafe { core::slice::from_raw_parts_mut(self.ptr.cast(), nblock) }
570    }
571
572    pub(crate) fn block_and_quadrant(&mut self, nblock: usize) -> (&mut [u8], &mut [u16]) {
573        let len = nblock + BZ_N_OVERSHOOT;
574        assert!(3 * len.next_multiple_of(2) <= 4 * self.len);
575
576        let block = unsafe { core::slice::from_raw_parts_mut(self.ptr.cast(), len) };
577
578        let start_byte = len.next_multiple_of(2);
579        let quadrant: *mut u16 = unsafe { self.ptr.cast::<u16>().byte_add(start_byte) };
580        let quadrant = unsafe { core::slice::from_raw_parts_mut(quadrant, len) };
581        quadrant.fill(0);
582
583        (block, quadrant)
584    }
585}
586
587pub(crate) struct Ftab {
588    ptr: *mut u32,
589}
590
591impl Ftab {
592    fn alloc(allocator: &Allocator) -> Option<Self> {
593        let ptr = allocator.allocate_zeroed(FTAB_LEN)?;
594        Some(Self { ptr })
595    }
596
597    unsafe fn dealloc(&mut self, allocator: &Allocator) {
598        let this = mem::replace(
599            self,
600            Self {
601                ptr: ptr::null_mut(),
602            },
603        );
604        if !this.ptr.is_null() {
605            unsafe { allocator.deallocate(this.ptr, FTAB_LEN) }
606        }
607    }
608
609    pub(crate) fn ftab(&mut self) -> &mut [u32; FTAB_LEN] {
610        // NOTE: this panics if the pointer is NULL, that is important!
611        unsafe { self.ptr.cast::<[u32; FTAB_LEN]>().as_mut().unwrap() }
612    }
613}
614
615#[repr(C)]
616pub(crate) struct DState {
617    pub strm_addr: usize, // Only for a consistency check
618    pub state: decompress::State,
619    pub state_out_len: u32,
620    pub state_out_ch: u8,
621    pub blockRandomised: bool,
622    pub blockSize100k: u8,
623    pub k0: u8,
624    pub bsBuff: u64,
625    pub bsLive: i32,
626    pub rNToGo: u16,
627    pub rTPos: u16,
628    pub smallDecompress: DecompressMode,
629    pub currBlockNo: i32,
630    pub verbosity: i32,
631    pub origPtr: i32,
632    pub tPos: u32,
633    pub nblock_used: i32,
634    pub unzftab: [u32; 256],
635    pub cftab: [u32; 257],
636    pub cftabCopy: [u32; 257],
637    pub tt: DSlice<u32>,
638    pub ll16: DSlice<u16>,
639    pub ll4: DSlice<u8>,
640    pub storedBlockCRC: u32,
641    pub storedCombinedCRC: u32,
642    pub calculatedBlockCRC: u32,
643    pub calculatedCombinedCRC: u32,
644    pub nInUse: u16,
645    pub inUse: [bool; 256],
646    pub inUse16: [bool; 16],
647    pub seqToUnseq: [u8; 256],
648    pub mtfa: [u8; 4096],
649    pub mtfbase: [u16; 16],
650    pub selector: [u8; 18002],
651    pub selectorMtf: [u8; 18002],
652    pub len: [[u8; 258]; 6],
653    pub limit: [[i32; 258]; 6],
654    pub base: [[i32; 258]; 6],
655    pub perm: [[u16; 258]; 6],
656    pub minLens: [u8; 6],
657    pub save: SaveArea,
658}
659
660#[derive(Default)]
661#[repr(C)]
662pub(crate) struct SaveArea {
663    pub i: i32,
664    pub j: i32,
665    pub alphaSize: u16,
666    pub EOB: u16,
667    pub groupNo: i32,
668    pub nblock: u32,
669    pub es: u32,
670    pub zvec: i32,
671    pub nextSym: u16,
672    pub nSelectors: u16,
673    pub groupPos: u8,
674    pub zn: u8,
675    pub nGroups: u8,
676    pub t: u8,
677    pub curr: u8,
678    pub nblockMAX100k: u8,
679    pub logN: u8, // the log_2 of N
680    pub zj: bool,
681    pub gMinlen: u8,
682    pub gSel: u8,
683}
684
685pub(crate) struct DSlice<T> {
686    ptr: *mut T,
687    len: usize,
688}
689
690impl<T> DSlice<T> {
691    fn new() -> Self {
692        Self {
693            ptr: dangling(),
694            len: 0,
695        }
696    }
697
698    pub(crate) fn alloc(allocator: &Allocator, len: usize) -> Option<Self> {
699        let ptr = allocator.allocate_zeroed::<T>(len)?;
700        Some(Self { ptr, len })
701    }
702
703    pub(crate) unsafe fn dealloc(&mut self, allocator: &Allocator) {
704        let this = mem::replace(self, Self::new());
705        if this.len != 0 {
706            unsafe { allocator.deallocate(this.ptr, this.len) }
707        }
708    }
709
710    pub(crate) fn as_slice(&self) -> &[T] {
711        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
712    }
713
714    pub(crate) fn as_mut_slice(&mut self) -> &mut [T] {
715        unsafe { core::slice::from_raw_parts_mut(self.ptr, self.len) }
716    }
717}
718
719const _C_INT_SIZE: () = assert!(core::mem::size_of::<core::ffi::c_int>() == 4);
720const _C_SHORT_SIZE: () = assert!(core::mem::size_of::<core::ffi::c_short>() == 2);
721const _C_CHAR_SIZE: () = assert!(core::mem::size_of::<core::ffi::c_char>() == 1);
722
723fn prepare_new_block(s: &mut EState) {
724    s.nblock = 0;
725    s.writer.num_z = 0;
726    s.state_out_pos = 0;
727    s.blockCRC = 0xffffffff;
728    s.inUse.fill(false);
729    s.blockNo += 1;
730}
731
732fn init_rl(s: &mut EState) {
733    s.state_in_ch = 256 as c_int as u32;
734    s.state_in_len = 0 as c_int;
735}
736
737fn isempty_rl(s: &mut EState) -> bool {
738    !(s.state_in_ch < 256 && s.state_in_len > 0)
739}
740
741/// Prepares the stream for compression.
742///
743/// # Returns
744///
745/// - [`BZ_PARAM_ERROR`] if any of
746///     - `strm.is_null()`
747///     - `!(1..=9).contains(&blockSize100k)`
748///     - `!(0..=250).contains(&workFactor)`
749///     - no [valid allocator](bz_stream#custom-allocators) could be configured
750/// - [`BZ_MEM_ERROR`] if insufficient memory is available
751/// - [`BZ_OK`] otherwise
752///
753/// Note: unlike [`BZ2_bzDecompressInit`], `verbosity` is not validated. Only values in
754/// `0..=4` have any effect: `0` suppresses all output, and higher values enable
755/// progressively more verbose diagnostic output.
756///
757/// # Safety
758///
759/// The caller must guarantee that
760///
761/// * Either
762///     - `strm` is `NULL`
763///     - `strm` satisfies the requirements of `&mut *strm`
764/// * The `bzalloc`, `bzfree` and `opaque` fields form a [valid allocator](bz_stream#custom-allocators).
765#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzCompressInit))]
766pub unsafe extern "C" fn BZ2_bzCompressInit(
767    strm: *mut bz_stream,
768    blockSize100k: c_int,
769    verbosity: c_int,
770    workFactor: c_int,
771) -> c_int {
772    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
773        return ReturnCode::BZ_PARAM_ERROR as c_int;
774    };
775    BZ2_bzCompressInitHelp(strm, blockSize100k, verbosity, workFactor) as c_int
776}
777
778pub(crate) fn BZ2_bzCompressInitHelp(
779    strm: &mut BzStream<EState>,
780    blockSize100k: c_int,
781    verbosity: c_int,
782    mut workFactor: c_int,
783) -> ReturnCode {
784    if !(1..=9).contains(&blockSize100k) || !(0..=250).contains(&workFactor) {
785        return ReturnCode::BZ_PARAM_ERROR;
786    }
787
788    if workFactor == 0 {
789        workFactor = 30;
790    }
791
792    // return a param error when no [valid allocator](bz_stream#custom-allocators) could be configured
793    let Some(allocator) = configure_allocator(strm) else {
794        return ReturnCode::BZ_PARAM_ERROR;
795    };
796
797    let Some(s) = allocator.allocate_zeroed::<EState>(1) else {
798        return ReturnCode::BZ_MEM_ERROR;
799    };
800
801    // this `s.strm` pointer should _NEVER_ be used! it exists just as a consistency check to ensure
802    // that a given state belongs to a given strm.
803    unsafe { (*s).strm_addr = strm as *const _ as usize }; // FIXME use .addr() once stable
804
805    let n = 100000 * blockSize100k;
806
807    let arr1_len = n as usize;
808    let arr1 = Arr1::alloc(&allocator, arr1_len);
809
810    let arr2_len = n as usize + BZ_N_OVERSHOOT;
811    let arr2 = Arr2::alloc(&allocator, arr2_len);
812
813    let ftab = Ftab::alloc(&allocator);
814
815    match (arr1, arr2, ftab) {
816        (Some(arr1), Some(arr2), Some(ftab)) => unsafe {
817            (*s).arr1 = arr1;
818            (*s).arr2 = arr2;
819            (*s).ftab = ftab;
820        },
821        (arr1, arr2, ftab) => {
822            if let Some(mut arr1) = arr1 {
823                unsafe { arr1.dealloc(&allocator) };
824            }
825
826            if let Some(mut arr2) = arr2 {
827                unsafe { arr2.dealloc(&allocator) };
828            }
829
830            if let Some(mut ftab) = ftab {
831                unsafe { ftab.dealloc(&allocator) };
832            }
833
834            unsafe { allocator.deallocate(s, 1) };
835
836            return ReturnCode::BZ_MEM_ERROR;
837        }
838    };
839
840    strm.state = s;
841
842    // safety: the EState has now been sufficiently initialized; the allocator zeroes the memory,
843    // and the only fields where zero is not a valid value are the arrays that were just set
844    //
845    // note in particular that if the discriminant of the first variant of an enum is unspecified,
846    // then it is set to zero.
847    let s = unsafe { &mut *s };
848
849    s.blockNo = 0;
850    s.state = State::Output;
851    s.mode = Mode::Running;
852    s.combinedCRC = 0;
853    s.blockSize100k = blockSize100k;
854    s.nblockMAX = 100000 * blockSize100k - 19;
855    s.verbosity = verbosity;
856    s.workFactor = workFactor;
857
858    strm.total_in_lo32 = 0;
859    strm.total_in_hi32 = 0;
860    strm.total_out_lo32 = 0;
861    strm.total_out_hi32 = 0;
862
863    init_rl(s);
864    prepare_new_block(s);
865
866    ReturnCode::BZ_OK
867}
868
869macro_rules! BZ_UPDATE_CRC {
870    ($crcVar:expr, $cha:expr) => {
871        let index = ($crcVar >> 24) ^ ($cha as core::ffi::c_uint);
872        $crcVar = ($crcVar << 8) ^ BZ2_CRC32TABLE[index as usize];
873    };
874}
875
876fn add_pair_to_block(s: &mut EState) {
877    let ch: u8 = s.state_in_ch as u8;
878
879    for _ in 0..s.state_in_len {
880        BZ_UPDATE_CRC!(s.blockCRC, ch);
881    }
882
883    let block = s.arr2.raw_block();
884    s.inUse[s.state_in_ch as usize] = true;
885    match s.state_in_len {
886        1 => {
887            block[s.nblock as usize..][..1].fill(ch);
888            s.nblock += 1;
889        }
890        2 => {
891            block[s.nblock as usize..][..2].fill(ch);
892            s.nblock += 2;
893        }
894        3 => {
895            block[s.nblock as usize..][..3].fill(ch);
896            s.nblock += 3;
897        }
898        _ => {
899            s.inUse[(s.state_in_len - 4) as usize] = true;
900
901            block[s.nblock as usize..][..4].fill(ch);
902            s.nblock += 4;
903
904            block[s.nblock as usize] = (s.state_in_len - 4) as u8;
905            s.nblock += 1;
906        }
907    };
908}
909
910fn flush_rl(s: &mut EState) {
911    if s.state_in_ch < 256 {
912        add_pair_to_block(s);
913    }
914    init_rl(s);
915}
916
917macro_rules! ADD_CHAR_TO_BLOCK {
918    ($zs:expr, $zchh0:expr) => {
919        let zchh: u32 = $zchh0 as u32;
920
921        if zchh != $zs.state_in_ch && $zs.state_in_len == 1 {
922            /*-- fast track the common case --*/
923
924            let ch: u8 = $zs.state_in_ch as u8;
925            BZ_UPDATE_CRC!($zs.blockCRC, ch);
926            $zs.inUse[$zs.state_in_ch as usize] = true;
927            $zs.arr2.raw_block()[$zs.nblock as usize] = ch;
928            $zs.nblock += 1;
929            $zs.nblock;
930            $zs.state_in_ch = zchh;
931        } else if zchh != $zs.state_in_ch || $zs.state_in_len == 255 {
932            /*-- general, uncommon cases --*/
933
934            if $zs.state_in_ch < 256 {
935                add_pair_to_block($zs);
936            }
937            $zs.state_in_ch = zchh;
938            $zs.state_in_len = 1;
939        } else {
940            $zs.state_in_len += 1;
941        }
942    };
943}
944
945fn copy_input_until_stop(strm: &mut BzStream<EState>, s: &mut EState) -> bool {
946    let mut progress_in = false;
947
948    match s.mode {
949        Mode::Running => loop {
950            if s.nblock >= s.nblockMAX {
951                break;
952            }
953            if let Some(b) = strm.read_byte() {
954                progress_in = true;
955                ADD_CHAR_TO_BLOCK!(s, b as u32);
956            } else {
957                break;
958            }
959        },
960        Mode::Idle | Mode::Flushing | Mode::Finishing => loop {
961            if s.nblock >= s.nblockMAX {
962                break;
963            }
964            if s.avail_in_expect == 0 {
965                break;
966            }
967            if let Some(b) = strm.read_byte() {
968                progress_in = true;
969                ADD_CHAR_TO_BLOCK!(s, b as u32);
970            } else {
971                break;
972            }
973            s.avail_in_expect -= 1;
974        },
975    }
976    progress_in
977}
978
979fn copy_output_until_stop(strm: &mut BzStream<EState>, s: &mut EState) -> bool {
980    let mut progress_out = false;
981
982    let zbits = &mut s.arr2.raw_block()[s.nblock as usize..];
983
984    loop {
985        if s.state_out_pos >= s.writer.num_z as i32 {
986            break;
987        }
988        if !strm.write_byte(zbits[s.state_out_pos as usize]) {
989            break;
990        }
991        progress_out = true;
992        s.state_out_pos += 1;
993    }
994    progress_out
995}
996
997fn handle_compress(strm: &mut BzStream<EState>, s: &mut EState) -> bool {
998    let mut progress_in = false;
999    let mut progress_out = false;
1000
1001    loop {
1002        if let State::Input = s.state {
1003            progress_out |= copy_output_until_stop(strm, s);
1004            if s.state_out_pos < s.writer.num_z as i32 {
1005                break;
1006            }
1007            if matches!(s.mode, Mode::Finishing) && s.avail_in_expect == 0 && isempty_rl(s) {
1008                break;
1009            }
1010            prepare_new_block(s);
1011            s.state = State::Output;
1012            if matches!(s.mode, Mode::Flushing) && s.avail_in_expect == 0 && isempty_rl(s) {
1013                break;
1014            }
1015        }
1016        if let State::Input = s.state {
1017            continue;
1018        }
1019        progress_in |= copy_input_until_stop(strm, s);
1020        if !matches!(s.mode, Mode::Running) && s.avail_in_expect == 0 {
1021            flush_rl(s);
1022            let is_last_block = matches!(s.mode, Mode::Finishing);
1023            compress_block(s, is_last_block);
1024            s.state = State::Input;
1025        } else if s.nblock >= s.nblockMAX {
1026            compress_block(s, false);
1027            s.state = State::Input;
1028        } else if strm.avail_in == 0 {
1029            break;
1030        }
1031    }
1032
1033    progress_in || progress_out
1034}
1035
1036pub(crate) enum Action {
1037    Run = 0,
1038    Flush = 1,
1039    Finish = 2,
1040}
1041
1042impl TryFrom<i32> for Action {
1043    type Error = ();
1044
1045    fn try_from(value: i32) -> Result<Self, Self::Error> {
1046        match value {
1047            0 => Ok(Self::Run),
1048            1 => Ok(Self::Flush),
1049            2 => Ok(Self::Finish),
1050            _ => Err(()),
1051        }
1052    }
1053}
1054
1055/// Compresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full.
1056///
1057/// # Returns
1058///
1059/// - [`BZ_SEQUENCE_ERROR`] if called on an invalid stream, e.g.
1060///     - before [`BZ2_bzCompressInit`]
1061///     - after [`BZ2_bzCompressEnd`]
1062/// - [`BZ_PARAM_ERROR`] if any of
1063///     - `strm.is_null()`
1064///     - `strm.state.is_null()`
1065///     - action is not one of [`BZ_RUN`], [`BZ_FLUSH`] or [`BZ_FINISH`]
1066/// - [`BZ_RUN_OK`] successfully compressed, but ran out of input or output space
1067/// - [`BZ_FLUSH_OK`] not all compressed data has been written to the output yet
1068/// - [`BZ_FINISH_OK`] if all input has been read but not all output has been written to the output
1069///   buffer yet
1070/// - [`BZ_STREAM_END`] if all input has been read all output has been written to the output buffer
1071///
1072/// # Safety
1073///
1074/// * Either
1075///     - `strm` is `NULL`
1076///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzCompressInit`]
1077/// * Either
1078///     - `strm.next_in` is `NULL` and `strm.avail_in` is 0
1079///     - `strm.next_in` is readable for `strm.avail_in` bytes
1080/// * Either
1081///     - `strm.next_out` is `NULL` and `strm.avail_out` is `0`
1082///     - `strm.next_out` is writable for `strm.avail_out` bytes
1083#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzCompress))]
1084pub unsafe extern "C" fn BZ2_bzCompress(strm: *mut bz_stream, action: c_int) -> c_int {
1085    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1086        return ReturnCode::BZ_PARAM_ERROR as c_int;
1087    };
1088
1089    BZ2_bzCompressHelp(strm, action) as c_int
1090}
1091
1092pub(crate) fn BZ2_bzCompressHelp(strm: &mut BzStream<EState>, action: i32) -> ReturnCode {
1093    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1094        return ReturnCode::BZ_PARAM_ERROR;
1095    };
1096
1097    // FIXME use .addr() once stable
1098    if s.strm_addr != strm as *mut _ as usize {
1099        return ReturnCode::BZ_PARAM_ERROR;
1100    }
1101
1102    compress_loop(strm, s, action)
1103}
1104
1105fn compress_loop(strm: &mut BzStream<EState>, s: &mut EState, action: i32) -> ReturnCode {
1106    loop {
1107        match s.mode {
1108            Mode::Idle => return ReturnCode::BZ_SEQUENCE_ERROR,
1109            Mode::Running => match Action::try_from(action) {
1110                Ok(Action::Run) => {
1111                    let progress = handle_compress(strm, s);
1112                    return if progress {
1113                        ReturnCode::BZ_RUN_OK
1114                    } else {
1115                        ReturnCode::BZ_PARAM_ERROR
1116                    };
1117                }
1118                Ok(Action::Flush) => {
1119                    s.avail_in_expect = strm.avail_in;
1120                    s.mode = Mode::Flushing;
1121                }
1122                Ok(Action::Finish) => {
1123                    s.avail_in_expect = strm.avail_in;
1124                    s.mode = Mode::Finishing;
1125                }
1126                Err(()) => {
1127                    return ReturnCode::BZ_PARAM_ERROR;
1128                }
1129            },
1130            Mode::Flushing => {
1131                let Ok(Action::Flush) = Action::try_from(action) else {
1132                    return ReturnCode::BZ_SEQUENCE_ERROR;
1133                };
1134                if s.avail_in_expect != strm.avail_in {
1135                    return ReturnCode::BZ_SEQUENCE_ERROR;
1136                }
1137                handle_compress(strm, s);
1138                if s.avail_in_expect > 0
1139                    || !isempty_rl(s)
1140                    || s.state_out_pos < s.writer.num_z as i32
1141                {
1142                    return ReturnCode::BZ_FLUSH_OK;
1143                }
1144                s.mode = Mode::Running;
1145                return ReturnCode::BZ_RUN_OK;
1146            }
1147            Mode::Finishing => {
1148                let Ok(Action::Finish) = Action::try_from(action) else {
1149                    // unreachable in practice
1150                    return ReturnCode::BZ_SEQUENCE_ERROR;
1151                };
1152                if s.avail_in_expect != strm.avail_in {
1153                    // unreachable in practice
1154                    return ReturnCode::BZ_SEQUENCE_ERROR;
1155                }
1156                let progress = handle_compress(strm, s);
1157                if !progress {
1158                    return ReturnCode::BZ_SEQUENCE_ERROR;
1159                }
1160                if s.avail_in_expect > 0
1161                    || !isempty_rl(s)
1162                    || s.state_out_pos < s.writer.num_z as i32
1163                {
1164                    return ReturnCode::BZ_FINISH_OK;
1165                }
1166                s.mode = Mode::Idle;
1167                return ReturnCode::BZ_STREAM_END;
1168            }
1169        }
1170    }
1171}
1172
1173/// Deallocates all dynamically allocated data structures for this stream.
1174///
1175/// # Returns
1176///
1177/// - [`BZ_PARAM_ERROR`] if any of
1178///     - `strm.is_null()`
1179///     - `strm.state.is_null()`
1180///     - no [valid allocator](bz_stream#custom-allocators) could be configured
1181/// - [`BZ_OK`] otherwise
1182///
1183/// # Safety
1184///
1185/// * Either
1186///     - `strm` is `NULL`
1187///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzCompressInit`]
1188#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzCompressEnd))]
1189pub unsafe extern "C" fn BZ2_bzCompressEnd(strm: *mut bz_stream) -> c_int {
1190    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1191        return ReturnCode::BZ_PARAM_ERROR as c_int;
1192    };
1193    BZ2_bzCompressEndHelp(strm)
1194}
1195
1196fn BZ2_bzCompressEndHelp(strm: &mut BzStream<EState>) -> c_int {
1197    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1198        return ReturnCode::BZ_PARAM_ERROR as c_int;
1199    };
1200
1201    // FIXME use .addr() once stable
1202    if s.strm_addr != strm as *mut _ as usize {
1203        return ReturnCode::BZ_PARAM_ERROR as c_int;
1204    }
1205
1206    let Some(allocator) = strm.allocator() else {
1207        return ReturnCode::BZ_PARAM_ERROR as c_int;
1208    };
1209
1210    unsafe {
1211        s.arr1.dealloc(&allocator);
1212        s.arr2.dealloc(&allocator);
1213        s.ftab.dealloc(&allocator);
1214    }
1215
1216    unsafe {
1217        allocator.deallocate(strm.state.cast::<EState>(), 1);
1218    }
1219    strm.state = ptr::null_mut::<EState>();
1220
1221    ReturnCode::BZ_OK as c_int
1222}
1223
1224pub(crate) enum DecompressMode {
1225    Small,
1226    Fast,
1227}
1228
1229/// Prepares the stream for decompression.
1230///
1231/// # Returns
1232///
1233/// - [`BZ_PARAM_ERROR`] if any of
1234///     - `strm.is_null()`
1235///     - `!(0..=1).contains(&small)`
1236///     - `!(0..=4).contains(&verbosity)`
1237///     - no [valid allocator](bz_stream#custom-allocators) could be configured
1238/// - [`BZ_MEM_ERROR`] if insufficient memory is available
1239/// - [`BZ_OK`] otherwise
1240///
1241/// # Safety
1242///
1243/// The caller must guarantee that
1244///
1245/// * Either
1246///     - `strm` is `NULL`
1247///     - `strm` satisfies the requirements of `&mut *strm`
1248/// * The `bzalloc`, `bzfree` and `opaque` fields form a [valid allocator](bz_stream#custom-allocators).
1249#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzDecompressInit))]
1250pub unsafe extern "C" fn BZ2_bzDecompressInit(
1251    strm: *mut bz_stream,
1252    verbosity: c_int,
1253    small: c_int,
1254) -> c_int {
1255    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1256        return ReturnCode::BZ_PARAM_ERROR as c_int;
1257    };
1258    BZ2_bzDecompressInitHelp(strm, verbosity, small) as c_int
1259}
1260
1261pub(crate) fn BZ2_bzDecompressInitHelp(
1262    strm: &mut BzStream<DState>,
1263    verbosity: c_int,
1264    small: c_int,
1265) -> ReturnCode {
1266    let decompress_mode = match small {
1267        0 => DecompressMode::Fast,
1268        1 => DecompressMode::Small,
1269        _ => return ReturnCode::BZ_PARAM_ERROR,
1270    };
1271    if !(0..=4).contains(&verbosity) {
1272        return ReturnCode::BZ_PARAM_ERROR;
1273    }
1274
1275    // return a param error when no [valid allocator](bz_stream#custom-allocators) could be configured
1276    let Some(allocator) = configure_allocator(strm) else {
1277        return ReturnCode::BZ_PARAM_ERROR;
1278    };
1279
1280    let Some(s) = allocator.allocate_zeroed::<DState>(1) else {
1281        return ReturnCode::BZ_MEM_ERROR;
1282    };
1283
1284    // this `s.strm` pointer should _NEVER_ be used! it exists just as a consistency check to ensure
1285    // that a given state belongs to a given strm.
1286    unsafe { (*s).strm_addr = strm as *const _ as usize }; // FIXME use .addr() once stable
1287
1288    unsafe {
1289        (*s).state = decompress::State::BZ_X_MAGIC_1;
1290        (*s).bsLive = 0;
1291        (*s).bsBuff = 0;
1292        (*s).calculatedCombinedCRC = 0;
1293    }
1294
1295    unsafe {
1296        (*s).smallDecompress = decompress_mode;
1297        (*s).ll4 = DSlice::new();
1298        (*s).ll16 = DSlice::new();
1299        (*s).tt = DSlice::new();
1300        (*s).currBlockNo = 0;
1301        (*s).verbosity = verbosity;
1302    }
1303
1304    strm.state = s;
1305
1306    strm.total_in_lo32 = 0;
1307    strm.total_in_hi32 = 0;
1308    strm.total_out_lo32 = 0;
1309    strm.total_out_hi32 = 0;
1310
1311    ReturnCode::BZ_OK
1312}
1313
1314macro_rules! BZ_RAND_MASK {
1315    ($s:expr) => {
1316        ($s.rNToGo == 1) as u8
1317    };
1318}
1319
1320macro_rules! BZ_RAND_UPD_MASK {
1321    ($s:expr) => {
1322        if ($s.rNToGo == 0) {
1323            $s.rNToGo = $crate::randtable::BZ2_RNUMS[$s.rTPos as usize];
1324            $s.rTPos += 1;
1325            if ($s.rTPos == 512) {
1326                $s.rTPos = 0
1327            };
1328        }
1329        $s.rNToGo -= 1;
1330    };
1331}
1332
1333pub(crate) use BZ_RAND_UPD_MASK;
1334
1335macro_rules! BZ_GET_FAST {
1336    ($s:expr) => {
1337        match $s.tt.as_slice().get($s.tPos as usize) {
1338            None => return true,
1339            Some(&bits) => {
1340                $s.tPos = bits;
1341                let tmp = ($s.tPos & 0xff) as u8;
1342                $s.tPos >>= 8;
1343                tmp
1344            }
1345        }
1346    };
1347}
1348
1349fn un_rle_obuf_to_output_fast(strm: &mut BzStream<DState>, s: &mut DState) -> bool {
1350    let mut k1: u8;
1351    if s.blockRandomised {
1352        loop {
1353            /* try to finish existing run */
1354            loop {
1355                if s.state_out_len == 0 {
1356                    if strm.avail_out == 0 {
1357                        return false;
1358                    } else {
1359                        break;
1360                    }
1361                }
1362                if !strm.write_byte(s.state_out_ch) {
1363                    return false;
1364                }
1365                BZ_UPDATE_CRC!(s.calculatedBlockCRC, s.state_out_ch);
1366                s.state_out_len -= 1;
1367            }
1368
1369            /* can a new run be started? */
1370            if s.nblock_used == s.save.nblock as i32 + 1 {
1371                return false;
1372            }
1373
1374            /* Only caused by corrupt data stream? */
1375            if s.nblock_used > s.save.nblock as i32 + 1 {
1376                return true;
1377            }
1378
1379            s.state_out_ch = s.k0;
1380
1381            s.state_out_len = 1;
1382            k1 = BZ_GET_FAST!(s);
1383            BZ_RAND_UPD_MASK!(s);
1384            k1 ^= BZ_RAND_MASK!(s);
1385            s.nblock_used += 1;
1386            if s.nblock_used == s.save.nblock as i32 + 1 {
1387                continue;
1388            };
1389            if k1 != s.k0 {
1390                s.k0 = k1;
1391                continue;
1392            };
1393
1394            s.state_out_len = 2;
1395            k1 = BZ_GET_FAST!(s);
1396            BZ_RAND_UPD_MASK!(s);
1397            k1 ^= BZ_RAND_MASK!(s);
1398            s.nblock_used += 1;
1399            if s.nblock_used == s.save.nblock as i32 + 1 {
1400                continue;
1401            };
1402            if k1 != s.k0 {
1403                s.k0 = k1;
1404                continue;
1405            };
1406
1407            s.state_out_len = 3;
1408            k1 = BZ_GET_FAST!(s);
1409            BZ_RAND_UPD_MASK!(s);
1410            k1 ^= BZ_RAND_MASK!(s);
1411            s.nblock_used += 1;
1412            if s.nblock_used == s.save.nblock as i32 + 1 {
1413                continue;
1414            };
1415            if k1 != s.k0 {
1416                s.k0 = k1;
1417                continue;
1418            };
1419
1420            k1 = BZ_GET_FAST!(s);
1421            BZ_RAND_UPD_MASK!(s);
1422            k1 ^= BZ_RAND_MASK!(s);
1423            s.nblock_used += 1;
1424            s.state_out_len = k1 as u32 + 4;
1425            s.k0 = BZ_GET_FAST!(s);
1426            BZ_RAND_UPD_MASK!(s);
1427            s.k0 ^= BZ_RAND_MASK!(s);
1428            s.nblock_used += 1;
1429        }
1430    } else {
1431        /* restore */
1432        let mut c_calculatedBlockCRC: u32 = s.calculatedBlockCRC;
1433        let mut c_state_out_ch: u8 = s.state_out_ch;
1434        let mut c_state_out_len: u32 = s.state_out_len;
1435        let mut c_nblock_used: i32 = s.nblock_used;
1436        let mut c_k0: u8 = s.k0;
1437        let mut c_tPos: u32 = s.tPos;
1438        let mut cs_next_out: *mut c_char = strm.next_out;
1439        let mut cs_avail_out: c_uint = strm.avail_out;
1440        let ro_blockSize100k: u8 = s.blockSize100k;
1441        /* end restore */
1442
1443        let avail_out_INIT: u32 = cs_avail_out;
1444        let s_save_nblockPP: i32 = s.save.nblock as i32 + 1;
1445
1446        let tt = &s.tt.as_slice()[..100000usize.wrapping_mul(usize::from(ro_blockSize100k))];
1447
1448        macro_rules! BZ_GET_FAST_C {
1449            ($c_tPos:expr) => {
1450                match tt.get($c_tPos as usize) {
1451                    None => {
1452                        // return corrupt if we're past the length of the block
1453                        return true;
1454                    }
1455                    Some(&v) => (v >> 8, (v & 0xff) as u8),
1456                }
1457            };
1458        }
1459
1460        'return_notr: loop {
1461            macro_rules! write_one_byte {
1462                ($byte:expr) => {
1463                    if cs_avail_out == 0 {
1464                        c_state_out_len = 1;
1465                        break 'return_notr;
1466                    } else {
1467                        unsafe { *(cs_next_out as *mut u8) = $byte };
1468                        BZ_UPDATE_CRC!(c_calculatedBlockCRC, $byte);
1469                        cs_next_out = unsafe { cs_next_out.add(1) };
1470                        cs_avail_out -= 1;
1471                    }
1472                };
1473            }
1474
1475            if c_state_out_len > 0 {
1476                let bound = Ord::min(cs_avail_out, c_state_out_len);
1477
1478                unsafe {
1479                    core::ptr::write_bytes(cs_next_out as *mut u8, c_state_out_ch, bound as usize);
1480                    cs_next_out = cs_next_out.add(bound as usize);
1481                };
1482
1483                for _ in 0..bound {
1484                    BZ_UPDATE_CRC!(c_calculatedBlockCRC, c_state_out_ch);
1485                }
1486
1487                cs_avail_out -= bound;
1488                c_state_out_len -= bound;
1489
1490                if cs_avail_out == 0 {
1491                    break 'return_notr;
1492                }
1493            }
1494
1495            loop {
1496                /* Only caused by corrupt data stream? */
1497                if c_nblock_used > s_save_nblockPP {
1498                    return true;
1499                }
1500
1501                /* can a new run be started? */
1502                if c_nblock_used == s_save_nblockPP {
1503                    c_state_out_len = 0;
1504                    break 'return_notr;
1505                }
1506
1507                c_state_out_ch = c_k0;
1508                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1509                c_nblock_used += 1;
1510
1511                if k1 != c_k0 {
1512                    c_k0 = k1;
1513                    write_one_byte!(c_state_out_ch);
1514                    continue;
1515                }
1516
1517                if c_nblock_used == s_save_nblockPP {
1518                    write_one_byte!(c_state_out_ch);
1519                    continue;
1520                }
1521
1522                c_state_out_len = 2;
1523                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1524                c_nblock_used += 1;
1525
1526                if c_nblock_used == s_save_nblockPP {
1527                    continue 'return_notr;
1528                }
1529
1530                if k1 != c_k0 {
1531                    c_k0 = k1;
1532                    continue 'return_notr;
1533                }
1534
1535                c_state_out_len = 3;
1536                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1537                c_nblock_used += 1;
1538
1539                if c_nblock_used == s_save_nblockPP {
1540                    continue 'return_notr;
1541                }
1542
1543                if k1 != c_k0 {
1544                    c_k0 = k1;
1545                    continue 'return_notr;
1546                }
1547
1548                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1549                c_nblock_used += 1;
1550                c_state_out_len = k1 as u32 + 4;
1551                (c_tPos, c_k0) = BZ_GET_FAST_C!(c_tPos);
1552                c_nblock_used += 1;
1553
1554                continue 'return_notr;
1555            }
1556        }
1557
1558        /* save */
1559        let total_out_lo32_old: c_uint = strm.total_out_lo32;
1560        strm.total_out_lo32 =
1561            (strm.total_out_lo32).wrapping_add(avail_out_INIT.wrapping_sub(cs_avail_out));
1562        if strm.total_out_lo32 < total_out_lo32_old {
1563            strm.total_out_hi32 = (strm.total_out_hi32).wrapping_add(1);
1564        }
1565        s.calculatedBlockCRC = c_calculatedBlockCRC;
1566        s.state_out_ch = c_state_out_ch;
1567        s.state_out_len = c_state_out_len;
1568        s.nblock_used = c_nblock_used;
1569        s.k0 = c_k0;
1570        s.tPos = c_tPos;
1571        strm.next_out = cs_next_out;
1572        strm.avail_out = cs_avail_out;
1573        /* end save */
1574    }
1575
1576    false
1577}
1578
1579#[inline]
1580pub(crate) fn index_into_f(index: u32, cftab: &[u32; 257]) -> u8 {
1581    let mut nb = 0u16;
1582    let mut na = 256;
1583    loop {
1584        let mid = (nb + na) >> 1;
1585        if index >= cftab[mid as usize] {
1586            nb = mid;
1587        } else {
1588            na = mid;
1589        }
1590        if na - nb == 1 {
1591            break;
1592        }
1593    }
1594
1595    // NOTE: nb < na, hence nb will fit in a u8
1596    debug_assert!(u8::try_from(nb).is_ok());
1597    nb as u8
1598}
1599
1600macro_rules! GET_LL4 {
1601    ($s:expr, $i:expr) => {
1602        $s.ll4.as_slice()[($s.tPos >> 1) as usize] as u32 >> ($i << 2 & 0x4) & 0xf
1603    };
1604}
1605
1606macro_rules! BZ_GET_SMALL {
1607    ($s:expr) => {
1608        match $s.ll16.as_slice().get($s.tPos as usize) {
1609            None => return true,
1610            Some(&low_bits) => {
1611                let high_bits = GET_LL4!($s, $s.tPos);
1612                let tmp = index_into_f($s.tPos, &$s.cftab);
1613                $s.tPos = u32::from(low_bits) | high_bits << 16;
1614                tmp
1615            }
1616        }
1617    };
1618}
1619
1620fn un_rle_obuf_to_output_small(strm: &mut BzStream<DState>, s: &mut DState) -> bool {
1621    let mut k1: u8;
1622    if s.blockRandomised {
1623        loop {
1624            /* try to finish existing run */
1625            loop {
1626                if s.state_out_len == 0 {
1627                    match strm.avail_out {
1628                        0 => return false,
1629                        _ => break,
1630                    }
1631                }
1632                if !strm.write_byte(s.state_out_ch) {
1633                    return false;
1634                }
1635                BZ_UPDATE_CRC!(s.calculatedBlockCRC, s.state_out_ch);
1636                s.state_out_len -= 1;
1637            }
1638
1639            /* can a new run be started? */
1640            if s.nblock_used == s.save.nblock as i32 + 1 {
1641                return false;
1642            }
1643
1644            /* Only caused by corrupt data stream? */
1645            if s.nblock_used > s.save.nblock as i32 + 1 {
1646                return true;
1647            }
1648
1649            s.state_out_ch = s.k0;
1650
1651            s.state_out_len = 1;
1652            k1 = BZ_GET_SMALL!(s);
1653            BZ_RAND_UPD_MASK!(s);
1654            k1 ^= BZ_RAND_MASK!(s);
1655            s.nblock_used += 1;
1656            if s.nblock_used == s.save.nblock as i32 + 1 {
1657                continue;
1658            };
1659            if k1 != s.k0 {
1660                s.k0 = k1;
1661                continue;
1662            };
1663
1664            s.state_out_len = 2;
1665            k1 = BZ_GET_SMALL!(s);
1666            BZ_RAND_UPD_MASK!(s);
1667            k1 ^= BZ_RAND_MASK!(s);
1668            s.nblock_used += 1;
1669            if s.nblock_used == s.save.nblock as i32 + 1 {
1670                continue;
1671            }
1672            if k1 != s.k0 {
1673                s.k0 = k1;
1674                continue;
1675            };
1676
1677            s.state_out_len = 3;
1678            k1 = BZ_GET_SMALL!(s);
1679            BZ_RAND_UPD_MASK!(s);
1680            k1 ^= BZ_RAND_MASK!(s);
1681            s.nblock_used += 1;
1682            if s.nblock_used == s.save.nblock as i32 + 1 {
1683                continue;
1684            }
1685            if k1 != s.k0 {
1686                s.k0 = k1;
1687                continue;
1688            };
1689
1690            k1 = BZ_GET_SMALL!(s);
1691            BZ_RAND_UPD_MASK!(s);
1692            k1 ^= BZ_RAND_MASK!(s);
1693            s.nblock_used += 1;
1694            s.state_out_len = k1 as u32 + 4;
1695            s.k0 = BZ_GET_SMALL!(s);
1696            BZ_RAND_UPD_MASK!(s);
1697            s.k0 ^= BZ_RAND_MASK!(s);
1698            s.nblock_used += 1;
1699        }
1700    } else {
1701        loop {
1702            loop {
1703                if s.state_out_len == 0 {
1704                    if strm.avail_out == 0 {
1705                        return false;
1706                    } else {
1707                        break;
1708                    }
1709                }
1710                if !strm.write_byte(s.state_out_ch) {
1711                    return false;
1712                }
1713                BZ_UPDATE_CRC!(s.calculatedBlockCRC, s.state_out_ch);
1714                s.state_out_len -= 1;
1715            }
1716            if s.nblock_used == s.save.nblock as i32 + 1 {
1717                return false;
1718            }
1719            if s.nblock_used > s.save.nblock as i32 + 1 {
1720                return true;
1721            }
1722
1723            s.state_out_len = 1;
1724            s.state_out_ch = s.k0;
1725            k1 = BZ_GET_SMALL!(s);
1726            s.nblock_used += 1;
1727            if s.nblock_used == s.save.nblock as i32 + 1 {
1728                continue;
1729            }
1730            if k1 != s.k0 {
1731                s.k0 = k1;
1732                continue;
1733            };
1734
1735            s.state_out_len = 2;
1736            k1 = BZ_GET_SMALL!(s);
1737            s.nblock_used += 1;
1738            if s.nblock_used == s.save.nblock as i32 + 1 {
1739                continue;
1740            }
1741            if k1 != s.k0 {
1742                s.k0 = k1;
1743                continue;
1744            };
1745
1746            s.state_out_len = 3;
1747            k1 = BZ_GET_SMALL!(s);
1748            s.nblock_used += 1;
1749            if s.nblock_used == s.save.nblock as i32 + 1 {
1750                continue;
1751            }
1752            if k1 != s.k0 {
1753                s.k0 = k1;
1754                continue;
1755            };
1756
1757            k1 = BZ_GET_SMALL!(s);
1758            s.nblock_used += 1;
1759            s.state_out_len = k1 as u32 + 4;
1760            s.k0 = BZ_GET_SMALL!(s);
1761            s.nblock_used += 1;
1762        }
1763    }
1764}
1765
1766/// Decompresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full.
1767///
1768/// # Returns
1769///
1770/// - [`BZ_PARAM_ERROR`] if any of
1771///     - `strm.is_null()`
1772///     - `strm.state.is_null()`
1773/// - [`BZ_DATA_ERROR`] if a data integrity error is detected in the compressed stream
1774/// - [`BZ_DATA_ERROR_MAGIC`] if the compressed stream doesn't begin with the right magic bytes
1775/// - [`BZ_MEM_ERROR`] if there wasn't enough memory available
1776/// - [`BZ_STREAM_END`] if the logical end of the data stream was detected and all output has been
1777///   written to the output buffer
1778/// - [`BZ_OK`] otherwise
1779///
1780/// Note: the bzip2 manual documents `strm.avail_out < 1` as a [`BZ_PARAM_ERROR`] condition,
1781/// but does not actually check for it, and neither do we. Calling with `avail_out == 0`
1782/// returns [`BZ_OK`] with no output written; the caller should increase `avail_out` and retry.
1783///
1784/// # Safety
1785///
1786/// * Either
1787///     - `strm` is `NULL`
1788///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzDecompressInit`]
1789/// * Either
1790///     - `strm.next_in` is `NULL` and `strm.avail_in` is 0
1791///     - `strm.next_in` is readable for `strm.avail_in` bytes
1792/// * Either
1793///     - `strm.next_out` is `NULL` and `strm.avail_out` is `0`
1794///     - `strm.next_out` is writable for `strm.avail_out` bytes
1795#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzDecompress))]
1796pub unsafe extern "C" fn BZ2_bzDecompress(strm: *mut bz_stream) -> c_int {
1797    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1798        return ReturnCode::BZ_PARAM_ERROR as c_int;
1799    };
1800
1801    BZ2_bzDecompressHelp(strm) as c_int
1802}
1803
1804pub(crate) fn BZ2_bzDecompressHelp(strm: &mut BzStream<DState>) -> ReturnCode {
1805    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1806        return ReturnCode::BZ_PARAM_ERROR;
1807    };
1808
1809    // FIXME use .addr() once stable
1810    if s.strm_addr != strm as *mut _ as usize {
1811        return ReturnCode::BZ_PARAM_ERROR;
1812    }
1813
1814    let Some(allocator) = strm.allocator() else {
1815        return ReturnCode::BZ_PARAM_ERROR;
1816    };
1817
1818    loop {
1819        match s.state {
1820            decompress::State::BZ_X_IDLE => {
1821                return ReturnCode::BZ_SEQUENCE_ERROR;
1822            }
1823            decompress::State::BZ_X_OUTPUT => {
1824                let corrupt = match s.smallDecompress {
1825                    DecompressMode::Small => un_rle_obuf_to_output_small(strm, s),
1826                    DecompressMode::Fast => un_rle_obuf_to_output_fast(strm, s),
1827                };
1828
1829                if corrupt {
1830                    return ReturnCode::BZ_DATA_ERROR;
1831                }
1832
1833                if s.nblock_used == s.save.nblock as i32 + 1 && s.state_out_len == 0 {
1834                    s.calculatedBlockCRC = !s.calculatedBlockCRC;
1835                    if s.verbosity >= 3 {
1836                        debug_log!(
1837                            " {{{:#08x}, {:#08x}}}",
1838                            s.storedBlockCRC,
1839                            s.calculatedBlockCRC,
1840                        );
1841                    }
1842                    if s.verbosity >= 2 {
1843                        debug_log!("]");
1844                    }
1845                    #[cfg(not(feature = "__internal-fuzz-disable-checksum"))]
1846                    if s.calculatedBlockCRC != s.storedBlockCRC {
1847                        return ReturnCode::BZ_DATA_ERROR;
1848                    }
1849                    s.calculatedCombinedCRC = s.calculatedCombinedCRC.rotate_left(1);
1850                    s.calculatedCombinedCRC ^= s.calculatedBlockCRC;
1851                    s.state = decompress::State::BZ_X_BLKHDR_1;
1852
1853                    continue;
1854                } else {
1855                    return ReturnCode::BZ_OK;
1856                }
1857            }
1858            _ => match decompress(strm, s, &allocator) {
1859                ReturnCode::BZ_STREAM_END => {
1860                    if s.verbosity >= 3 {
1861                        debug_log!(
1862                            "\n    combined CRCs: stored = {:#08x}, computed = {:#08x}",
1863                            s.storedCombinedCRC,
1864                            s.calculatedCombinedCRC,
1865                        );
1866                    }
1867                    #[cfg(not(feature = "__internal-fuzz-disable-checksum"))]
1868                    if s.calculatedCombinedCRC != s.storedCombinedCRC {
1869                        return ReturnCode::BZ_DATA_ERROR;
1870                    }
1871                    return ReturnCode::BZ_STREAM_END;
1872                }
1873                return_code => match s.state {
1874                    decompress::State::BZ_X_OUTPUT => continue,
1875                    _ => return return_code,
1876                },
1877            },
1878        }
1879    }
1880}
1881
1882/// Deallocates all dynamically allocated data structures for this stream.
1883///
1884/// # Returns
1885///
1886/// - [`BZ_PARAM_ERROR`] if any of
1887///     - `strm.is_null()`
1888///     - `strm.state.is_null()`
1889///     - no [valid allocator](bz_stream#custom-allocators) could be configured
1890/// - [`BZ_OK`] otherwise
1891///
1892/// # Safety
1893///
1894/// * Either
1895///     - `strm` is `NULL`
1896///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzDecompressInit`]
1897#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzDecompressEnd))]
1898pub unsafe extern "C" fn BZ2_bzDecompressEnd(strm: *mut bz_stream) -> c_int {
1899    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1900        return ReturnCode::BZ_PARAM_ERROR as c_int;
1901    };
1902    BZ2_bzDecompressEndHelp(strm) as c_int
1903}
1904
1905fn BZ2_bzDecompressEndHelp(strm: &mut BzStream<DState>) -> ReturnCode {
1906    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1907        return ReturnCode::BZ_PARAM_ERROR;
1908    };
1909
1910    // FIXME use .addr() once stable
1911    if s.strm_addr != strm as *mut _ as usize {
1912        return ReturnCode::BZ_PARAM_ERROR;
1913    }
1914
1915    let Some(allocator) = strm.allocator() else {
1916        return ReturnCode::BZ_PARAM_ERROR;
1917    };
1918
1919    unsafe {
1920        s.tt.dealloc(&allocator);
1921        s.ll16.dealloc(&allocator);
1922        s.ll4.dealloc(&allocator);
1923    }
1924
1925    unsafe { allocator.deallocate(strm.state, 1) };
1926    strm.state = ptr::null_mut::<DState>();
1927
1928    ReturnCode::BZ_OK
1929}
1930
1931/// Compress the input data into the destination buffer.
1932///
1933/// This function attempts to compress the data in `source[0 .. sourceLen]` into `dest[0 .. *destLen]`.
1934/// If the destination buffer is big enough, `*destLen` is set to the size of the compressed data, and [`BZ_OK`] is returned.
1935/// If the compressed data won't fit, `*destLen` is unchanged, and [`BZ_OUTBUFF_FULL`] is returned.
1936///
1937/// For the meaning of parameters `blockSize100k`, `verbosity` and `workFactor`, see [`BZ2_bzCompressInit`].
1938///
1939/// A safe choice for the length of the output buffer is a size 1% larger than the input length,
1940/// plus 600 extra bytes.
1941///
1942/// # Returns
1943///
1944/// - [`BZ_PARAM_ERROR`] if any of
1945///     - `dest.is_null()`
1946///     - `destLen.is_null()`
1947///     - `source.is_null()`
1948///     - `!(1..=9).contains(&blockSize100k)`
1949///     - `!(0..=4).contains(&verbosity)`
1950///     - `!(0..=250).contains(&workFactor)`
1951/// - [`BZ_MEM_ERROR`] if insufficient memory is available
1952/// - [`BZ_OUTBUFF_FULL`] if the size of the compressed data exceeds `*destLen`
1953/// - [`BZ_OK`] otherwise
1954///
1955/// # Safety
1956///
1957/// The caller must guarantee that
1958///
1959/// * `destLen` satisfies the requirements of [`pointer::as_mut`]
1960/// * Either
1961///     - `dest` is `NULL`
1962///     - `dest` is writable for `*destLen` bytes
1963/// * Either
1964///     - `source` is `NULL`
1965///     - `source` is readable for `sourceLen`
1966///
1967/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1968#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzBuffToBuffCompress))]
1969pub unsafe extern "C" fn BZ2_bzBuffToBuffCompress(
1970    dest: *mut c_char,
1971    destLen: *mut c_uint,
1972    source: *mut c_char,
1973    sourceLen: c_uint,
1974    blockSize100k: c_int,
1975    verbosity: c_int,
1976    workFactor: c_int,
1977) -> c_int {
1978    if dest.is_null() || source.is_null() {
1979        return ReturnCode::BZ_PARAM_ERROR as c_int;
1980    }
1981
1982    if !(0..=4).contains(&verbosity) {
1983        return ReturnCode::BZ_PARAM_ERROR as c_int;
1984    }
1985
1986    let Some(destLen) = (unsafe { destLen.as_mut() }) else {
1987        return ReturnCode::BZ_PARAM_ERROR as c_int;
1988    };
1989
1990    match unsafe {
1991        BZ2_bzBuffToBuffCompressHelp(
1992            dest,
1993            *destLen,
1994            source,
1995            sourceLen,
1996            blockSize100k,
1997            verbosity,
1998            workFactor,
1999        )
2000    } {
2001        Ok(written) => {
2002            *destLen -= written;
2003            ReturnCode::BZ_OK as c_int
2004        }
2005        Err(err) => err as c_int,
2006    }
2007}
2008
2009unsafe fn BZ2_bzBuffToBuffCompressHelp(
2010    dest: *mut c_char,
2011    destLen: c_uint,
2012    source: *mut c_char,
2013    sourceLen: c_uint,
2014    blockSize100k: c_int,
2015    verbosity: c_int,
2016    workFactor: c_int,
2017) -> Result<c_uint, ReturnCode> {
2018    let mut strm = BzStream::zeroed();
2019
2020    match BZ2_bzCompressInitHelp(&mut strm, blockSize100k, verbosity, workFactor) {
2021        ReturnCode::BZ_OK => {}
2022        ret => return Err(ret),
2023    }
2024
2025    strm.next_in = source;
2026    strm.next_out = dest;
2027    strm.avail_in = sourceLen;
2028    strm.avail_out = destLen;
2029
2030    match BZ2_bzCompressHelp(&mut strm, Action::Finish as i32) {
2031        ReturnCode::BZ_FINISH_OK => {
2032            BZ2_bzCompressEndHelp(&mut strm);
2033            Err(ReturnCode::BZ_OUTBUFF_FULL)
2034        }
2035        ReturnCode::BZ_STREAM_END => {
2036            BZ2_bzCompressEndHelp(&mut strm);
2037            Ok(strm.avail_out)
2038        }
2039        error => {
2040            BZ2_bzCompressEndHelp(&mut strm);
2041            Err(error)
2042        }
2043    }
2044}
2045
2046/// Decompress the input data into the destination buffer.
2047///
2048/// This function attempts to decompress the data in `source[0 .. sourceLen]` into `dest[0 .. *destLen]`.
2049/// If the destination buffer is big enough, `*destLen` is set to the size of the decompressed data, and [`BZ_OK`] is returned.
2050/// If the decompressed data won't fit, `*destLen` is unchanged, and [`BZ_OUTBUFF_FULL`] is returned.
2051///
2052/// For the meaning of parameters `small`, `verbosity`, see [`BZ2_bzDecompressInit`].
2053///
2054/// Because the compression ratio of the compressed data cannot be known in advance,
2055/// there is no easy way to guarantee that the output buffer will be big enough.
2056/// You may of course make arrangements in your code to record the size of the uncompressed data,
2057/// but such a mechanism is beyond the scope of this library.
2058///
2059/// # Returns
2060///
2061/// - [`BZ_PARAM_ERROR`] if any of
2062///     - `dest.is_null()`
2063///     - `destLen.is_null()`
2064///     - `source.is_null()`
2065///     - `!(0..=1).contains(&small)`
2066///     - `!(0..=4).contains(&verbosity)`
2067/// - [`BZ_MEM_ERROR`] if insufficient memory is available
2068/// - [`BZ_OUTBUFF_FULL`] if the size of the compressed data exceeds `*destLen`
2069/// - [`BZ_DATA_ERROR`] if a data integrity error is detected in the compressed stream
2070/// - [`BZ_DATA_ERROR_MAGIC`] if the compressed stream doesn't begin with the right magic bytes
2071/// - [`BZ_UNEXPECTED_EOF`] if the compressed data ends before the logical end-of-stream was detected
2072/// - [`BZ_OK`] otherwise
2073///
2074/// # Safety
2075///
2076/// The caller must guarantee that
2077///
2078/// * `destLen` satisfies the requirements of [`pointer::as_mut`]
2079/// * Either
2080///     - `dest` is `NULL`
2081///     - `dest` is writable for `*destLen` bytes
2082/// * Either
2083///     - `source` is `NULL`
2084///     - `source` is readable for `sourceLen`
2085///
2086/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
2087#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzBuffToBuffDecompress))]
2088pub unsafe extern "C" fn BZ2_bzBuffToBuffDecompress(
2089    dest: *mut c_char,
2090    destLen: *mut c_uint,
2091    source: *mut c_char,
2092    sourceLen: c_uint,
2093    small: c_int,
2094    verbosity: c_int,
2095) -> c_int {
2096    if dest.is_null() || source.is_null() {
2097        return ReturnCode::BZ_PARAM_ERROR as c_int;
2098    }
2099
2100    let Some(destLen) = (unsafe { destLen.as_mut() }) else {
2101        return ReturnCode::BZ_PARAM_ERROR as c_int;
2102    };
2103
2104    match unsafe {
2105        BZ2_bzBuffToBuffDecompressHelp(dest, *destLen, source, sourceLen, small, verbosity)
2106    } {
2107        Ok(written) => {
2108            *destLen -= written;
2109            ReturnCode::BZ_OK as c_int
2110        }
2111        Err(err) => err as c_int,
2112    }
2113}
2114
2115unsafe fn BZ2_bzBuffToBuffDecompressHelp(
2116    dest: *mut c_char,
2117    destLen: c_uint,
2118    source: *mut c_char,
2119    sourceLen: c_uint,
2120    small: c_int,
2121    verbosity: c_int,
2122) -> Result<c_uint, ReturnCode> {
2123    let mut strm = BzStream::zeroed();
2124
2125    match BZ2_bzDecompressInitHelp(&mut strm, verbosity, small) {
2126        ReturnCode::BZ_OK => {}
2127        ret => return Err(ret),
2128    }
2129
2130    strm.next_in = source;
2131    strm.next_out = dest;
2132    strm.avail_in = sourceLen;
2133    strm.avail_out = destLen;
2134
2135    match BZ2_bzDecompressHelp(&mut strm) {
2136        ReturnCode::BZ_OK => {
2137            BZ2_bzDecompressEndHelp(&mut strm);
2138            match strm.avail_out {
2139                0 => Err(ReturnCode::BZ_OUTBUFF_FULL),
2140                _ => Err(ReturnCode::BZ_UNEXPECTED_EOF),
2141            }
2142        }
2143        ReturnCode::BZ_STREAM_END => {
2144            BZ2_bzDecompressEndHelp(&mut strm);
2145            Ok(strm.avail_out)
2146        }
2147        error => {
2148            BZ2_bzDecompressEndHelp(&mut strm);
2149            Err(error)
2150        }
2151    }
2152}