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 zlib 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 zlib that this release is compatible with
99/// - The final component is the zlib-rs 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 as *mut u8) };
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 as *mut u8) };
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: i32 = 2;
447pub(crate) const BZ_N_QSORT: i32 = 12;
448pub(crate) const BZ_N_SHELL: i32 = 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 rNToGo: i32,
625    pub rTPos: i32,
626    pub bsBuff: u64,
627    pub bsLive: i32,
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..=4).contains(&verbosity)`
749///     - `!(0..=250).contains(&workFactor)`
750///     - no [valid allocator](bz_stream#custom-allocators) could be configured
751/// - [`BZ_MEM_ERROR`] if insufficient memory is available
752/// - [`BZ_OK`] otherwise
753///
754/// # Safety
755///
756/// The caller must guarantee that
757///
758/// * Either
759///     - `strm` is `NULL`
760///     - `strm` satisfies the requirements of `&mut *strm`
761/// * The `bzalloc`, `bzfree` and `opaque` fields form a [valid allocator](bz_stream#custom-allocators).
762#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzCompressInit))]
763pub unsafe extern "C" fn BZ2_bzCompressInit(
764    strm: *mut bz_stream,
765    blockSize100k: c_int,
766    verbosity: c_int,
767    workFactor: c_int,
768) -> c_int {
769    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
770        return ReturnCode::BZ_PARAM_ERROR as c_int;
771    };
772    BZ2_bzCompressInitHelp(strm, blockSize100k, verbosity, workFactor) as c_int
773}
774
775pub(crate) fn BZ2_bzCompressInitHelp(
776    strm: &mut BzStream<EState>,
777    blockSize100k: c_int,
778    verbosity: c_int,
779    mut workFactor: c_int,
780) -> ReturnCode {
781    if !(1..=9).contains(&blockSize100k) || !(0..=250).contains(&workFactor) {
782        return ReturnCode::BZ_PARAM_ERROR;
783    }
784
785    if workFactor == 0 {
786        workFactor = 30;
787    }
788
789    // return a param error when no [valid allocator](bz_stream#custom-allocators) could be configured
790    let Some(allocator) = configure_allocator(strm) else {
791        return ReturnCode::BZ_PARAM_ERROR;
792    };
793
794    let Some(s) = allocator.allocate_zeroed::<EState>(1) else {
795        return ReturnCode::BZ_MEM_ERROR;
796    };
797
798    // this `s.strm` pointer should _NEVER_ be used! it exists just as a consistency check to ensure
799    // that a given state belongs to a given strm.
800    unsafe { (*s).strm_addr = strm as *const _ as usize }; // FIXME use .addr() once stable
801
802    let n = 100000 * blockSize100k;
803
804    let arr1_len = n as usize;
805    let arr1 = Arr1::alloc(&allocator, arr1_len);
806
807    let arr2_len = n as usize + (2 + 12 + 18 + 2);
808    let arr2 = Arr2::alloc(&allocator, arr2_len);
809
810    let ftab = Ftab::alloc(&allocator);
811
812    match (arr1, arr2, ftab) {
813        (Some(arr1), Some(arr2), Some(ftab)) => unsafe {
814            (*s).arr1 = arr1;
815            (*s).arr2 = arr2;
816            (*s).ftab = ftab;
817        },
818        (arr1, arr2, ftab) => {
819            if let Some(mut arr1) = arr1 {
820                unsafe { arr1.dealloc(&allocator) };
821            }
822
823            if let Some(mut arr2) = arr2 {
824                unsafe { arr2.dealloc(&allocator) };
825            }
826
827            if let Some(mut ftab) = ftab {
828                unsafe { ftab.dealloc(&allocator) };
829            }
830
831            unsafe { allocator.deallocate(s, 1) };
832
833            return ReturnCode::BZ_MEM_ERROR;
834        }
835    };
836
837    strm.state = s;
838
839    // safety: the EState has now been sufficiently initialized; the allocator zeroes the memory,
840    // and the only fields where zero is not a valid value are the arrays that were just set
841    //
842    // note in particular that if the discriminant of the first variant of an enum is unspecified,
843    // then it is set to zero.
844    let s = unsafe { &mut *s };
845
846    s.blockNo = 0;
847    s.state = State::Output;
848    s.mode = Mode::Running;
849    s.combinedCRC = 0;
850    s.blockSize100k = blockSize100k;
851    s.nblockMAX = 100000 * blockSize100k - 19;
852    s.verbosity = verbosity;
853    s.workFactor = workFactor;
854
855    strm.total_in_lo32 = 0;
856    strm.total_in_hi32 = 0;
857    strm.total_out_lo32 = 0;
858    strm.total_out_hi32 = 0;
859
860    init_rl(s);
861    prepare_new_block(s);
862
863    ReturnCode::BZ_OK
864}
865
866macro_rules! BZ_UPDATE_CRC {
867    ($crcVar:expr, $cha:expr) => {
868        let index = ($crcVar >> 24) ^ ($cha as core::ffi::c_uint);
869        $crcVar = ($crcVar << 8) ^ BZ2_CRC32TABLE[index as usize];
870    };
871}
872
873fn add_pair_to_block(s: &mut EState) {
874    let ch: u8 = s.state_in_ch as u8;
875
876    for _ in 0..s.state_in_len {
877        BZ_UPDATE_CRC!(s.blockCRC, ch);
878    }
879
880    let block = s.arr2.raw_block();
881    s.inUse[s.state_in_ch as usize] = true;
882    match s.state_in_len {
883        1 => {
884            block[s.nblock as usize..][..1].fill(ch);
885            s.nblock += 1;
886        }
887        2 => {
888            block[s.nblock as usize..][..2].fill(ch);
889            s.nblock += 2;
890        }
891        3 => {
892            block[s.nblock as usize..][..3].fill(ch);
893            s.nblock += 3;
894        }
895        _ => {
896            s.inUse[(s.state_in_len - 4) as usize] = true;
897
898            block[s.nblock as usize..][..4].fill(ch);
899            s.nblock += 4;
900
901            block[s.nblock as usize] = (s.state_in_len - 4) as u8;
902            s.nblock += 1;
903        }
904    };
905}
906
907fn flush_rl(s: &mut EState) {
908    if s.state_in_ch < 256 {
909        add_pair_to_block(s);
910    }
911    init_rl(s);
912}
913
914macro_rules! ADD_CHAR_TO_BLOCK {
915    ($zs:expr, $zchh0:expr) => {
916        let zchh: u32 = $zchh0 as u32;
917
918        if zchh != $zs.state_in_ch && $zs.state_in_len == 1 {
919            /*-- fast track the common case --*/
920
921            let ch: u8 = $zs.state_in_ch as u8;
922            BZ_UPDATE_CRC!($zs.blockCRC, ch);
923            $zs.inUse[$zs.state_in_ch as usize] = true;
924            $zs.arr2.raw_block()[$zs.nblock as usize] = ch;
925            $zs.nblock += 1;
926            $zs.nblock;
927            $zs.state_in_ch = zchh;
928        } else if zchh != $zs.state_in_ch || $zs.state_in_len == 255 {
929            /*-- general, uncommon cases --*/
930
931            if $zs.state_in_ch < 256 {
932                add_pair_to_block($zs);
933            }
934            $zs.state_in_ch = zchh;
935            $zs.state_in_len = 1;
936        } else {
937            $zs.state_in_len += 1;
938        }
939    };
940}
941
942fn copy_input_until_stop(strm: &mut BzStream<EState>, s: &mut EState) -> bool {
943    let mut progress_in = false;
944
945    match s.mode {
946        Mode::Running => loop {
947            if s.nblock >= s.nblockMAX {
948                break;
949            }
950            if let Some(b) = strm.read_byte() {
951                progress_in = true;
952                ADD_CHAR_TO_BLOCK!(s, b as u32);
953            } else {
954                break;
955            }
956        },
957        Mode::Idle | Mode::Flushing | Mode::Finishing => loop {
958            if s.nblock >= s.nblockMAX {
959                break;
960            }
961            if s.avail_in_expect == 0 {
962                break;
963            }
964            if let Some(b) = strm.read_byte() {
965                progress_in = true;
966                ADD_CHAR_TO_BLOCK!(s, b as u32);
967            } else {
968                break;
969            }
970            s.avail_in_expect -= 1;
971        },
972    }
973    progress_in
974}
975
976fn copy_output_until_stop(strm: &mut BzStream<EState>, s: &mut EState) -> bool {
977    let mut progress_out = false;
978
979    let zbits = &mut s.arr2.raw_block()[s.nblock as usize..];
980
981    loop {
982        if s.state_out_pos >= s.writer.num_z as i32 {
983            break;
984        }
985        if !strm.write_byte(zbits[s.state_out_pos as usize]) {
986            break;
987        }
988        progress_out = true;
989        s.state_out_pos += 1;
990    }
991    progress_out
992}
993
994fn handle_compress(strm: &mut BzStream<EState>, s: &mut EState) -> bool {
995    let mut progress_in = false;
996    let mut progress_out = false;
997
998    loop {
999        if let State::Input = s.state {
1000            progress_out |= copy_output_until_stop(strm, s);
1001            if s.state_out_pos < s.writer.num_z as i32 {
1002                break;
1003            }
1004            if matches!(s.mode, Mode::Finishing) && s.avail_in_expect == 0 && isempty_rl(s) {
1005                break;
1006            }
1007            prepare_new_block(s);
1008            s.state = State::Output;
1009            if matches!(s.mode, Mode::Flushing) && s.avail_in_expect == 0 && isempty_rl(s) {
1010                break;
1011            }
1012        }
1013        if let State::Input = s.state {
1014            continue;
1015        }
1016        progress_in |= copy_input_until_stop(strm, s);
1017        if !matches!(s.mode, Mode::Running) && s.avail_in_expect == 0 {
1018            flush_rl(s);
1019            let is_last_block = matches!(s.mode, Mode::Finishing);
1020            compress_block(s, is_last_block);
1021            s.state = State::Input;
1022        } else if s.nblock >= s.nblockMAX {
1023            compress_block(s, false);
1024            s.state = State::Input;
1025        } else if strm.avail_in == 0 {
1026            break;
1027        }
1028    }
1029
1030    progress_in || progress_out
1031}
1032
1033pub(crate) enum Action {
1034    Run = 0,
1035    Flush = 1,
1036    Finish = 2,
1037}
1038
1039impl TryFrom<i32> for Action {
1040    type Error = ();
1041
1042    fn try_from(value: i32) -> Result<Self, Self::Error> {
1043        match value {
1044            0 => Ok(Self::Run),
1045            1 => Ok(Self::Flush),
1046            2 => Ok(Self::Finish),
1047            _ => Err(()),
1048        }
1049    }
1050}
1051
1052/// Compresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full.
1053///
1054/// # Returns
1055///
1056/// - [`BZ_SEQUENCE_ERROR`] if called on an invalid stream, e.g.
1057///     - before [`BZ2_bzCompressInit`]
1058///     - after [`BZ2_bzCompressEnd`]
1059/// - [`BZ_PARAM_ERROR`] if any of
1060///     - `strm.is_null()`
1061///     - `strm.s.is_null()`
1062///     - action is not one of [`BZ_RUN`], [`BZ_FLUSH`] or [`BZ_FINISH`]
1063/// - [`BZ_RUN_OK`] successfully compressed, but ran out of input or output space
1064/// - [`BZ_FLUSH_OK`] not all compressed data has been written to the output yet
1065/// - [`BZ_FINISH_OK`] if all input has been read but not all output has been written to the output
1066///   buffer yet
1067/// - [`BZ_STREAM_END`] if all input has been read all output has been written to the output buffer
1068///
1069/// # Safety
1070///
1071/// * Either
1072///     - `strm` is `NULL`
1073///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzCompressInit`]
1074/// * Either
1075///     - `strm.next_in` is `NULL` and `strm.avail_in` is 0
1076///     - `strm.next_in` is readable for `strm.avail_in` bytes
1077/// * Either
1078///     - `strm.next_out` is `NULL` and `strm.avail_out` is `0`
1079///     - `strm.next_out` is writable for `strm.avail_out` bytes
1080#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzCompress))]
1081pub unsafe extern "C" fn BZ2_bzCompress(strm: *mut bz_stream, action: c_int) -> c_int {
1082    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1083        return ReturnCode::BZ_PARAM_ERROR as c_int;
1084    };
1085
1086    BZ2_bzCompressHelp(strm, action) as c_int
1087}
1088
1089pub(crate) fn BZ2_bzCompressHelp(strm: &mut BzStream<EState>, action: i32) -> ReturnCode {
1090    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1091        return ReturnCode::BZ_PARAM_ERROR;
1092    };
1093
1094    // FIXME use .addr() once stable
1095    if s.strm_addr != strm as *mut _ as usize {
1096        return ReturnCode::BZ_PARAM_ERROR;
1097    }
1098
1099    compress_loop(strm, s, action)
1100}
1101
1102fn compress_loop(strm: &mut BzStream<EState>, s: &mut EState, action: i32) -> ReturnCode {
1103    loop {
1104        match s.mode {
1105            Mode::Idle => return ReturnCode::BZ_SEQUENCE_ERROR,
1106            Mode::Running => match Action::try_from(action) {
1107                Ok(Action::Run) => {
1108                    let progress = handle_compress(strm, s);
1109                    return if progress {
1110                        ReturnCode::BZ_RUN_OK
1111                    } else {
1112                        ReturnCode::BZ_PARAM_ERROR
1113                    };
1114                }
1115                Ok(Action::Flush) => {
1116                    s.avail_in_expect = strm.avail_in;
1117                    s.mode = Mode::Flushing;
1118                }
1119                Ok(Action::Finish) => {
1120                    s.avail_in_expect = strm.avail_in;
1121                    s.mode = Mode::Finishing;
1122                }
1123                Err(()) => {
1124                    return ReturnCode::BZ_PARAM_ERROR;
1125                }
1126            },
1127            Mode::Flushing => {
1128                let Ok(Action::Flush) = Action::try_from(action) else {
1129                    return ReturnCode::BZ_SEQUENCE_ERROR;
1130                };
1131                if s.avail_in_expect != strm.avail_in {
1132                    return ReturnCode::BZ_SEQUENCE_ERROR;
1133                }
1134                handle_compress(strm, s);
1135                if s.avail_in_expect > 0
1136                    || !isempty_rl(s)
1137                    || s.state_out_pos < s.writer.num_z as i32
1138                {
1139                    return ReturnCode::BZ_FLUSH_OK;
1140                }
1141                s.mode = Mode::Running;
1142                return ReturnCode::BZ_RUN_OK;
1143            }
1144            Mode::Finishing => {
1145                let Ok(Action::Finish) = Action::try_from(action) else {
1146                    // unreachable in practice
1147                    return ReturnCode::BZ_SEQUENCE_ERROR;
1148                };
1149                if s.avail_in_expect != strm.avail_in {
1150                    // unreachable in practice
1151                    return ReturnCode::BZ_SEQUENCE_ERROR;
1152                }
1153                let progress = handle_compress(strm, s);
1154                if !progress {
1155                    return ReturnCode::BZ_SEQUENCE_ERROR;
1156                }
1157                if s.avail_in_expect > 0
1158                    || !isempty_rl(s)
1159                    || s.state_out_pos < s.writer.num_z as i32
1160                {
1161                    return ReturnCode::BZ_FINISH_OK;
1162                }
1163                s.mode = Mode::Idle;
1164                return ReturnCode::BZ_STREAM_END;
1165            }
1166        }
1167    }
1168}
1169
1170/// Deallocates all dynamically allocated data structures for this stream.
1171///
1172/// # Returns
1173///
1174/// - [`BZ_PARAM_ERROR`] if any of
1175///     - `strm.is_null()`
1176///     - `strm.s.is_null()`
1177///     - no [valid allocator](bz_stream#custom-allocators) could be configured
1178/// - [`BZ_OK`] otherwise
1179///
1180/// # Safety
1181///
1182/// * Either
1183///     - `strm` is `NULL`
1184///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzCompressInit`]
1185#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzCompressEnd))]
1186pub unsafe extern "C" fn BZ2_bzCompressEnd(strm: *mut bz_stream) -> c_int {
1187    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1188        return ReturnCode::BZ_PARAM_ERROR as c_int;
1189    };
1190    BZ2_bzCompressEndHelp(strm)
1191}
1192
1193fn BZ2_bzCompressEndHelp(strm: &mut BzStream<EState>) -> c_int {
1194    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1195        return ReturnCode::BZ_PARAM_ERROR as c_int;
1196    };
1197
1198    // FIXME use .addr() once stable
1199    if s.strm_addr != strm as *mut _ as usize {
1200        return ReturnCode::BZ_PARAM_ERROR as c_int;
1201    }
1202
1203    let Some(allocator) = strm.allocator() else {
1204        return ReturnCode::BZ_PARAM_ERROR as c_int;
1205    };
1206
1207    unsafe {
1208        s.arr1.dealloc(&allocator);
1209        s.arr2.dealloc(&allocator);
1210        s.ftab.dealloc(&allocator);
1211    }
1212
1213    unsafe {
1214        allocator.deallocate(strm.state.cast::<EState>(), 1);
1215    }
1216    strm.state = ptr::null_mut::<EState>();
1217
1218    ReturnCode::BZ_OK as c_int
1219}
1220
1221pub(crate) enum DecompressMode {
1222    Small,
1223    Fast,
1224}
1225
1226/// Prepares the stream for decompression.
1227///
1228/// # Returns
1229///
1230/// - [`BZ_PARAM_ERROR`] if any of
1231///     - `strm.is_null()`
1232///     - `!(0..=1).contains(&small)`
1233///     - `!(0..=4).contains(&verbosity)`
1234///     - no [valid allocator](bz_stream#custom-allocators) could be configured
1235/// - [`BZ_MEM_ERROR`] if insufficient memory is available
1236/// - [`BZ_OK`] otherwise
1237///
1238/// # Safety
1239///
1240/// The caller must guarantee that
1241///
1242/// * Either
1243///     - `strm` is `NULL`
1244///     - `strm` satisfies the requirements of `&mut *strm`
1245/// * The `bzalloc`, `bzfree` and `opaque` fields form a [valid allocator](bz_stream#custom-allocators).
1246#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzDecompressInit))]
1247pub unsafe extern "C" fn BZ2_bzDecompressInit(
1248    strm: *mut bz_stream,
1249    verbosity: c_int,
1250    small: c_int,
1251) -> c_int {
1252    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1253        return ReturnCode::BZ_PARAM_ERROR as c_int;
1254    };
1255    BZ2_bzDecompressInitHelp(strm, verbosity, small) as c_int
1256}
1257
1258pub(crate) fn BZ2_bzDecompressInitHelp(
1259    strm: &mut BzStream<DState>,
1260    verbosity: c_int,
1261    small: c_int,
1262) -> ReturnCode {
1263    let decompress_mode = match small {
1264        0 => DecompressMode::Fast,
1265        1 => DecompressMode::Small,
1266        _ => return ReturnCode::BZ_PARAM_ERROR,
1267    };
1268    if !(0..=4).contains(&verbosity) {
1269        return ReturnCode::BZ_PARAM_ERROR;
1270    }
1271
1272    // return a param error when no [valid allocator](bz_stream#custom-allocators) could be configured
1273    let Some(allocator) = configure_allocator(strm) else {
1274        return ReturnCode::BZ_PARAM_ERROR;
1275    };
1276
1277    let Some(s) = allocator.allocate_zeroed::<DState>(1) else {
1278        return ReturnCode::BZ_MEM_ERROR;
1279    };
1280
1281    // this `s.strm` pointer should _NEVER_ be used! it exists just as a consistency check to ensure
1282    // that a given state belongs to a given strm.
1283    unsafe { (*s).strm_addr = strm as *const _ as usize }; // FIXME use .addr() once stable
1284
1285    unsafe {
1286        (*s).state = decompress::State::BZ_X_MAGIC_1;
1287        (*s).bsLive = 0;
1288        (*s).bsBuff = 0;
1289        (*s).calculatedCombinedCRC = 0;
1290    }
1291
1292    unsafe {
1293        (*s).smallDecompress = decompress_mode;
1294        (*s).ll4 = DSlice::new();
1295        (*s).ll16 = DSlice::new();
1296        (*s).tt = DSlice::new();
1297        (*s).currBlockNo = 0;
1298        (*s).verbosity = verbosity;
1299    }
1300
1301    strm.state = s;
1302
1303    strm.total_in_lo32 = 0;
1304    strm.total_in_hi32 = 0;
1305    strm.total_out_lo32 = 0;
1306    strm.total_out_hi32 = 0;
1307
1308    ReturnCode::BZ_OK
1309}
1310
1311macro_rules! BZ_RAND_MASK {
1312    ($s:expr) => {
1313        ($s.rNToGo == 1) as u8
1314    };
1315}
1316
1317macro_rules! BZ_RAND_UPD_MASK {
1318    ($s:expr) => {
1319        if ($s.rNToGo == 0) {
1320            $s.rNToGo = $crate::randtable::BZ2_RNUMS[$s.rTPos as usize];
1321            $s.rTPos += 1;
1322            if ($s.rTPos == 512) {
1323                $s.rTPos = 0
1324            };
1325        }
1326        $s.rNToGo -= 1;
1327    };
1328}
1329
1330pub(crate) use BZ_RAND_UPD_MASK;
1331
1332macro_rules! BZ_GET_FAST {
1333    ($s:expr) => {
1334        match $s.tt.as_slice().get($s.tPos as usize) {
1335            None => return true,
1336            Some(&bits) => {
1337                $s.tPos = bits;
1338                let tmp = ($s.tPos & 0xff) as u8;
1339                $s.tPos >>= 8;
1340                tmp
1341            }
1342        }
1343    };
1344}
1345
1346fn un_rle_obuf_to_output_fast(strm: &mut BzStream<DState>, s: &mut DState) -> bool {
1347    let mut k1: u8;
1348    if s.blockRandomised {
1349        loop {
1350            /* try to finish existing run */
1351            loop {
1352                if s.state_out_len == 0 {
1353                    if strm.avail_out == 0 {
1354                        return false;
1355                    } else {
1356                        break;
1357                    }
1358                }
1359                if !strm.write_byte(s.state_out_ch) {
1360                    return false;
1361                }
1362                BZ_UPDATE_CRC!(s.calculatedBlockCRC, s.state_out_ch);
1363                s.state_out_len -= 1;
1364            }
1365
1366            /* can a new run be started? */
1367            if s.nblock_used == s.save.nblock as i32 + 1 {
1368                return false;
1369            }
1370
1371            /* Only caused by corrupt data stream? */
1372            if s.nblock_used > s.save.nblock as i32 + 1 {
1373                return true;
1374            }
1375
1376            s.state_out_ch = s.k0;
1377
1378            s.state_out_len = 1;
1379            k1 = BZ_GET_FAST!(s);
1380            BZ_RAND_UPD_MASK!(s);
1381            k1 ^= BZ_RAND_MASK!(s);
1382            s.nblock_used += 1;
1383            if s.nblock_used == s.save.nblock as i32 + 1 {
1384                continue;
1385            };
1386            if k1 != s.k0 {
1387                s.k0 = k1;
1388                continue;
1389            };
1390
1391            s.state_out_len = 2;
1392            k1 = BZ_GET_FAST!(s);
1393            BZ_RAND_UPD_MASK!(s);
1394            k1 ^= BZ_RAND_MASK!(s);
1395            s.nblock_used += 1;
1396            if s.nblock_used == s.save.nblock as i32 + 1 {
1397                continue;
1398            };
1399            if k1 != s.k0 {
1400                s.k0 = k1;
1401                continue;
1402            };
1403
1404            s.state_out_len = 3;
1405            k1 = BZ_GET_FAST!(s);
1406            BZ_RAND_UPD_MASK!(s);
1407            k1 ^= BZ_RAND_MASK!(s);
1408            s.nblock_used += 1;
1409            if s.nblock_used == s.save.nblock as i32 + 1 {
1410                continue;
1411            };
1412            if k1 != s.k0 {
1413                s.k0 = k1;
1414                continue;
1415            };
1416
1417            k1 = BZ_GET_FAST!(s);
1418            BZ_RAND_UPD_MASK!(s);
1419            k1 ^= BZ_RAND_MASK!(s);
1420            s.nblock_used += 1;
1421            s.state_out_len = k1 as u32 + 4;
1422            s.k0 = BZ_GET_FAST!(s);
1423            BZ_RAND_UPD_MASK!(s);
1424            s.k0 ^= BZ_RAND_MASK!(s);
1425            s.nblock_used += 1;
1426        }
1427    } else {
1428        /* restore */
1429        let mut c_calculatedBlockCRC: u32 = s.calculatedBlockCRC;
1430        let mut c_state_out_ch: u8 = s.state_out_ch;
1431        let mut c_state_out_len: u32 = s.state_out_len;
1432        let mut c_nblock_used: i32 = s.nblock_used;
1433        let mut c_k0: u8 = s.k0;
1434        let mut c_tPos: u32 = s.tPos;
1435        let mut cs_next_out: *mut c_char = strm.next_out;
1436        let mut cs_avail_out: c_uint = strm.avail_out;
1437        let ro_blockSize100k: u8 = s.blockSize100k;
1438        /* end restore */
1439
1440        let avail_out_INIT: u32 = cs_avail_out;
1441        let s_save_nblockPP: i32 = s.save.nblock as i32 + 1;
1442
1443        let tt = &s.tt.as_slice()[..100000usize.wrapping_mul(usize::from(ro_blockSize100k))];
1444
1445        macro_rules! BZ_GET_FAST_C {
1446            ($c_tPos:expr) => {
1447                match tt.get($c_tPos as usize) {
1448                    None => {
1449                        // return corrupt if we're past the length of the block
1450                        return true;
1451                    }
1452                    Some(&v) => (v >> 8, (v & 0xff) as u8),
1453                }
1454            };
1455        }
1456
1457        'return_notr: loop {
1458            macro_rules! write_one_byte {
1459                ($byte:expr) => {
1460                    if cs_avail_out == 0 {
1461                        c_state_out_len = 1;
1462                        break 'return_notr;
1463                    } else {
1464                        unsafe { *(cs_next_out as *mut u8) = $byte };
1465                        BZ_UPDATE_CRC!(c_calculatedBlockCRC, $byte);
1466                        cs_next_out = unsafe { cs_next_out.add(1) };
1467                        cs_avail_out -= 1;
1468                    }
1469                };
1470            }
1471
1472            if c_state_out_len > 0 {
1473                let bound = Ord::min(cs_avail_out, c_state_out_len);
1474
1475                unsafe {
1476                    core::ptr::write_bytes(cs_next_out as *mut u8, c_state_out_ch, bound as usize);
1477                    cs_next_out = cs_next_out.add(bound as usize);
1478                };
1479
1480                for _ in 0..bound {
1481                    BZ_UPDATE_CRC!(c_calculatedBlockCRC, c_state_out_ch);
1482                }
1483
1484                cs_avail_out -= bound;
1485                c_state_out_len -= bound;
1486
1487                if cs_avail_out == 0 {
1488                    break 'return_notr;
1489                }
1490            }
1491
1492            loop {
1493                /* Only caused by corrupt data stream? */
1494                if c_nblock_used > s_save_nblockPP {
1495                    return true;
1496                }
1497
1498                /* can a new run be started? */
1499                if c_nblock_used == s_save_nblockPP {
1500                    c_state_out_len = 0;
1501                    break 'return_notr;
1502                }
1503
1504                c_state_out_ch = c_k0;
1505                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1506                c_nblock_used += 1;
1507
1508                if k1 != c_k0 {
1509                    c_k0 = k1;
1510                    write_one_byte!(c_state_out_ch);
1511                    continue;
1512                }
1513
1514                if c_nblock_used == s_save_nblockPP {
1515                    write_one_byte!(c_state_out_ch);
1516                    continue;
1517                }
1518
1519                c_state_out_len = 2;
1520                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1521                c_nblock_used += 1;
1522
1523                if c_nblock_used == s_save_nblockPP {
1524                    continue 'return_notr;
1525                }
1526
1527                if k1 != c_k0 {
1528                    c_k0 = k1;
1529                    continue 'return_notr;
1530                }
1531
1532                c_state_out_len = 3;
1533                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1534                c_nblock_used += 1;
1535
1536                if c_nblock_used == s_save_nblockPP {
1537                    continue 'return_notr;
1538                }
1539
1540                if k1 != c_k0 {
1541                    c_k0 = k1;
1542                    continue 'return_notr;
1543                }
1544
1545                (c_tPos, k1) = BZ_GET_FAST_C!(c_tPos);
1546                c_nblock_used += 1;
1547                c_state_out_len = k1 as u32 + 4;
1548                (c_tPos, c_k0) = BZ_GET_FAST_C!(c_tPos);
1549                c_nblock_used += 1;
1550
1551                continue 'return_notr;
1552            }
1553        }
1554
1555        /* save */
1556        let total_out_lo32_old: c_uint = strm.total_out_lo32;
1557        strm.total_out_lo32 =
1558            (strm.total_out_lo32).wrapping_add(avail_out_INIT.wrapping_sub(cs_avail_out));
1559        if strm.total_out_lo32 < total_out_lo32_old {
1560            strm.total_out_hi32 = (strm.total_out_hi32).wrapping_add(1);
1561        }
1562        s.calculatedBlockCRC = c_calculatedBlockCRC;
1563        s.state_out_ch = c_state_out_ch;
1564        s.state_out_len = c_state_out_len;
1565        s.nblock_used = c_nblock_used;
1566        s.k0 = c_k0;
1567        s.tPos = c_tPos;
1568        strm.next_out = cs_next_out;
1569        strm.avail_out = cs_avail_out;
1570        /* end save */
1571    }
1572
1573    false
1574}
1575
1576#[inline]
1577pub(crate) fn index_into_f(index: u32, cftab: &[u32; 257]) -> u8 {
1578    let mut nb = 0u16;
1579    let mut na = 256;
1580    loop {
1581        let mid = (nb + na) >> 1;
1582        if index >= cftab[mid as usize] {
1583            nb = mid;
1584        } else {
1585            na = mid;
1586        }
1587        if na - nb == 1 {
1588            break;
1589        }
1590    }
1591
1592    // NOTE: nb < na, hence nb will fit in a u8
1593    debug_assert!(u8::try_from(nb).is_ok());
1594    nb as u8
1595}
1596
1597macro_rules! GET_LL4 {
1598    ($s:expr, $i:expr) => {
1599        $s.ll4.as_slice()[($s.tPos >> 1) as usize] as u32 >> ($s.tPos << 2 & 0x4) & 0xf
1600    };
1601}
1602
1603macro_rules! BZ_GET_SMALL {
1604    ($s:expr) => {
1605        match $s.ll16.as_slice().get($s.tPos as usize) {
1606            None => return true,
1607            Some(&low_bits) => {
1608                let high_bits = GET_LL4!($s, $s.tPos);
1609                let tmp = index_into_f($s.tPos, &$s.cftab);
1610                $s.tPos = u32::from(low_bits) | high_bits << 16;
1611                tmp
1612            }
1613        }
1614    };
1615}
1616
1617fn un_rle_obuf_to_output_small(strm: &mut BzStream<DState>, s: &mut DState) -> bool {
1618    let mut k1: u8;
1619    if s.blockRandomised {
1620        loop {
1621            /* try to finish existing run */
1622            loop {
1623                if s.state_out_len == 0 {
1624                    match strm.avail_out {
1625                        0 => return false,
1626                        _ => break,
1627                    }
1628                }
1629                if !strm.write_byte(s.state_out_ch) {
1630                    return false;
1631                }
1632                BZ_UPDATE_CRC!(s.calculatedBlockCRC, s.state_out_ch);
1633                s.state_out_len -= 1;
1634            }
1635
1636            /* can a new run be started? */
1637            if s.nblock_used == s.save.nblock as i32 + 1 {
1638                return false;
1639            }
1640
1641            /* Only caused by corrupt data stream? */
1642            if s.nblock_used > s.save.nblock as i32 + 1 {
1643                return true;
1644            }
1645
1646            s.state_out_ch = s.k0;
1647
1648            s.state_out_len = 1;
1649            k1 = BZ_GET_SMALL!(s);
1650            BZ_RAND_UPD_MASK!(s);
1651            k1 ^= BZ_RAND_MASK!(s);
1652            s.nblock_used += 1;
1653            if s.nblock_used == s.save.nblock as i32 + 1 {
1654                continue;
1655            };
1656            if k1 != s.k0 {
1657                s.k0 = k1;
1658                continue;
1659            };
1660
1661            s.state_out_len = 2;
1662            k1 = BZ_GET_SMALL!(s);
1663            BZ_RAND_UPD_MASK!(s);
1664            k1 ^= BZ_RAND_MASK!(s);
1665            s.nblock_used += 1;
1666            if s.nblock_used == s.save.nblock as i32 + 1 {
1667                continue;
1668            }
1669            if k1 != s.k0 {
1670                s.k0 = k1;
1671                continue;
1672            };
1673
1674            s.state_out_len = 3;
1675            k1 = BZ_GET_SMALL!(s);
1676            BZ_RAND_UPD_MASK!(s);
1677            k1 ^= BZ_RAND_MASK!(s);
1678            s.nblock_used += 1;
1679            if s.nblock_used == s.save.nblock as i32 + 1 {
1680                continue;
1681            }
1682            if k1 != s.k0 {
1683                s.k0 = k1;
1684                continue;
1685            };
1686
1687            k1 = BZ_GET_SMALL!(s);
1688            BZ_RAND_UPD_MASK!(s);
1689            k1 ^= BZ_RAND_MASK!(s);
1690            s.nblock_used += 1;
1691            s.state_out_len = k1 as u32 + 4;
1692            s.k0 = BZ_GET_SMALL!(s);
1693            BZ_RAND_UPD_MASK!(s);
1694            s.k0 ^= BZ_RAND_MASK!(s);
1695            s.nblock_used += 1;
1696        }
1697    } else {
1698        loop {
1699            loop {
1700                if s.state_out_len == 0 {
1701                    if strm.avail_out == 0 {
1702                        return false;
1703                    } else {
1704                        break;
1705                    }
1706                }
1707                if !strm.write_byte(s.state_out_ch) {
1708                    return false;
1709                }
1710                BZ_UPDATE_CRC!(s.calculatedBlockCRC, s.state_out_ch);
1711                s.state_out_len -= 1;
1712            }
1713            if s.nblock_used == s.save.nblock as i32 + 1 {
1714                return false;
1715            }
1716            if s.nblock_used > s.save.nblock as i32 + 1 {
1717                return true;
1718            }
1719
1720            s.state_out_len = 1;
1721            s.state_out_ch = s.k0;
1722            k1 = BZ_GET_SMALL!(s);
1723            s.nblock_used += 1;
1724            if s.nblock_used == s.save.nblock as i32 + 1 {
1725                continue;
1726            }
1727            if k1 != s.k0 {
1728                s.k0 = k1;
1729                continue;
1730            };
1731
1732            s.state_out_len = 2;
1733            k1 = BZ_GET_SMALL!(s);
1734            s.nblock_used += 1;
1735            if s.nblock_used == s.save.nblock as i32 + 1 {
1736                continue;
1737            }
1738            if k1 != s.k0 {
1739                s.k0 = k1;
1740                continue;
1741            };
1742
1743            s.state_out_len = 3;
1744            k1 = BZ_GET_SMALL!(s);
1745            s.nblock_used += 1;
1746            if s.nblock_used == s.save.nblock as i32 + 1 {
1747                continue;
1748            }
1749            if k1 != s.k0 {
1750                s.k0 = k1;
1751                continue;
1752            };
1753
1754            k1 = BZ_GET_SMALL!(s);
1755            s.nblock_used += 1;
1756            s.state_out_len = k1 as u32 + 4;
1757            s.k0 = BZ_GET_SMALL!(s);
1758            s.nblock_used += 1;
1759        }
1760    }
1761}
1762
1763/// Decompresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full.
1764///
1765/// # Returns
1766///
1767/// - [`BZ_PARAM_ERROR`] if any of
1768///     - `strm.is_null()`
1769///     - `strm.s.is_null()`
1770///     - `strm.avail_out < 1`
1771/// - [`BZ_DATA_ERROR`] if a data integrity error is detected in the compressed stream
1772/// - [`BZ_DATA_ERROR_MAGIC`] if the compressed stream doesn't begin with the right magic bytes
1773/// - [`BZ_MEM_ERROR`] if there wasn't enough memory available
1774/// - [`BZ_STREAM_END`] if the logical end of the data stream was detected and all output has been
1775///   written to the output buffer
1776/// - [`BZ_OK`] otherwise
1777///
1778/// # Safety
1779///
1780/// * Either
1781///     - `strm` is `NULL`
1782///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzDecompressInit`]
1783/// * Either
1784///     - `strm.next_in` is `NULL` and `strm.avail_in` is 0
1785///     - `strm.next_in` is readable for `strm.avail_in` bytes
1786/// * Either
1787///     - `strm.next_out` is `NULL` and `strm.avail_out` is `0`
1788///     - `strm.next_out` is writable for `strm.avail_out` bytes
1789#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzDecompress))]
1790pub unsafe extern "C" fn BZ2_bzDecompress(strm: *mut bz_stream) -> c_int {
1791    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1792        return ReturnCode::BZ_PARAM_ERROR as c_int;
1793    };
1794
1795    BZ2_bzDecompressHelp(strm) as c_int
1796}
1797
1798pub(crate) fn BZ2_bzDecompressHelp(strm: &mut BzStream<DState>) -> ReturnCode {
1799    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1800        return ReturnCode::BZ_PARAM_ERROR;
1801    };
1802
1803    // FIXME use .addr() once stable
1804    if s.strm_addr != strm as *mut _ as usize {
1805        return ReturnCode::BZ_PARAM_ERROR;
1806    }
1807
1808    let Some(allocator) = strm.allocator() else {
1809        return ReturnCode::BZ_PARAM_ERROR;
1810    };
1811
1812    loop {
1813        match s.state {
1814            decompress::State::BZ_X_IDLE => {
1815                return ReturnCode::BZ_SEQUENCE_ERROR;
1816            }
1817            decompress::State::BZ_X_OUTPUT => {
1818                let corrupt = match s.smallDecompress {
1819                    DecompressMode::Small => un_rle_obuf_to_output_small(strm, s),
1820                    DecompressMode::Fast => un_rle_obuf_to_output_fast(strm, s),
1821                };
1822
1823                if corrupt {
1824                    return ReturnCode::BZ_DATA_ERROR;
1825                }
1826
1827                if s.nblock_used == s.save.nblock as i32 + 1 && s.state_out_len == 0 {
1828                    s.calculatedBlockCRC = !s.calculatedBlockCRC;
1829                    if s.verbosity >= 3 {
1830                        debug_log!(
1831                            " {{{:#08x}, {:#08x}}}",
1832                            s.storedBlockCRC,
1833                            s.calculatedBlockCRC,
1834                        );
1835                    }
1836                    if s.verbosity >= 2 {
1837                        debug_log!("]");
1838                    }
1839                    #[cfg(not(feature = "__internal-fuzz-disable-checksum"))]
1840                    if s.calculatedBlockCRC != s.storedBlockCRC {
1841                        return ReturnCode::BZ_DATA_ERROR;
1842                    }
1843                    s.calculatedCombinedCRC = s.calculatedCombinedCRC.rotate_left(1);
1844                    s.calculatedCombinedCRC ^= s.calculatedBlockCRC;
1845                    s.state = decompress::State::BZ_X_BLKHDR_1;
1846
1847                    continue;
1848                } else {
1849                    return ReturnCode::BZ_OK;
1850                }
1851            }
1852            _ => match decompress(strm, s, &allocator) {
1853                ReturnCode::BZ_STREAM_END => {
1854                    if s.verbosity >= 3 {
1855                        debug_log!(
1856                            "\n    combined CRCs: stored = {:#08x}, computed = {:#08x}",
1857                            s.storedCombinedCRC,
1858                            s.calculatedCombinedCRC,
1859                        );
1860                    }
1861                    #[cfg(not(feature = "__internal-fuzz-disable-checksum"))]
1862                    if s.calculatedCombinedCRC != s.storedCombinedCRC {
1863                        return ReturnCode::BZ_DATA_ERROR;
1864                    }
1865                    return ReturnCode::BZ_STREAM_END;
1866                }
1867                return_code => match s.state {
1868                    decompress::State::BZ_X_OUTPUT => continue,
1869                    _ => return return_code,
1870                },
1871            },
1872        }
1873    }
1874}
1875
1876/// Deallocates all dynamically allocated data structures for this stream.
1877///
1878/// # Returns
1879///
1880/// - [`BZ_PARAM_ERROR`] if any of
1881///     - `strm.is_null()`
1882///     - `strm.s.is_null()`
1883///     - no [valid allocator](bz_stream#custom-allocators) could be configured
1884/// - [`BZ_OK`] otherwise
1885///
1886/// # Safety
1887///
1888/// * Either
1889///     - `strm` is `NULL`
1890///     - `strm` satisfies the requirements of `&mut *strm` and was initialized with [`BZ2_bzDecompressInit`]
1891#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzDecompressEnd))]
1892pub unsafe extern "C" fn BZ2_bzDecompressEnd(strm: *mut bz_stream) -> c_int {
1893    let Some(strm) = (unsafe { BzStream::from_ptr(strm) }) else {
1894        return ReturnCode::BZ_PARAM_ERROR as c_int;
1895    };
1896    BZ2_bzDecompressEndHelp(strm) as c_int
1897}
1898
1899fn BZ2_bzDecompressEndHelp(strm: &mut BzStream<DState>) -> ReturnCode {
1900    let Some(s) = (unsafe { strm.state.as_mut() }) else {
1901        return ReturnCode::BZ_PARAM_ERROR;
1902    };
1903
1904    // FIXME use .addr() once stable
1905    if s.strm_addr != strm as *mut _ as usize {
1906        return ReturnCode::BZ_PARAM_ERROR;
1907    }
1908
1909    let Some(allocator) = strm.allocator() else {
1910        return ReturnCode::BZ_PARAM_ERROR;
1911    };
1912
1913    unsafe {
1914        s.tt.dealloc(&allocator);
1915        s.ll16.dealloc(&allocator);
1916        s.ll4.dealloc(&allocator);
1917    }
1918
1919    unsafe { allocator.deallocate(strm.state, 1) };
1920    strm.state = ptr::null_mut::<DState>();
1921
1922    ReturnCode::BZ_OK
1923}
1924
1925/// Compress the input data into the destination buffer.
1926///
1927/// This function attempts to compress the data in `source[0 .. sourceLen]` into `dest[0 .. *destLen]`.
1928/// If the destination buffer is big enough, `*destLen` is set to the size of the compressed data, and [`BZ_OK`] is returned.
1929/// If the compressed data won't fit, `*destLen` is unchanged, and [`BZ_OUTBUFF_FULL`] is returned.
1930///
1931/// For the meaning of parameters `blockSize100k`, `verbosity` and `workFactor`, see [`BZ2_bzCompressInit`].
1932///
1933/// A safe choice for the length of the output buffer is a size 1% larger than the input length,
1934/// plus 600 extra bytes.
1935///
1936/// # Returns
1937///
1938/// - [`BZ_PARAM_ERROR`] if any of
1939///     - `dest.is_null()`
1940///     - `destLen.is_null()`
1941///     - `source.is_null()`
1942///     - `!(1..=9).contains(&blockSize100k)`
1943///     - `!(0..=4).contains(&verbosity)`
1944///     - `!(0..=250).contains(&workFactor)`
1945/// - [`BZ_MEM_ERROR`] if insufficient memory is available
1946/// - [`BZ_OUTBUFF_FULL`] if the size of the compressed data exceeds `*destLen`
1947/// - [`BZ_OK`] otherwise
1948///
1949/// # Safety
1950///
1951/// The caller must guarantee that
1952///
1953/// * `destLen` satisfies the requirements of [`pointer::as_mut`]
1954/// * Either
1955///     - `dest` is `NULL`
1956///     - `dest` is writable for `*destLen` bytes
1957/// * Either
1958///     - `source` is `NULL`
1959///     - `source` is readable for `sourceLen`
1960///
1961/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
1962#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzBuffToBuffCompress))]
1963pub unsafe extern "C" fn BZ2_bzBuffToBuffCompress(
1964    dest: *mut c_char,
1965    destLen: *mut c_uint,
1966    source: *mut c_char,
1967    sourceLen: c_uint,
1968    blockSize100k: c_int,
1969    verbosity: c_int,
1970    workFactor: c_int,
1971) -> c_int {
1972    if dest.is_null() || source.is_null() {
1973        return ReturnCode::BZ_PARAM_ERROR as c_int;
1974    }
1975
1976    let Some(destLen) = (unsafe { destLen.as_mut() }) else {
1977        return ReturnCode::BZ_PARAM_ERROR as c_int;
1978    };
1979
1980    match unsafe {
1981        BZ2_bzBuffToBuffCompressHelp(
1982            dest,
1983            *destLen,
1984            source,
1985            sourceLen,
1986            blockSize100k,
1987            verbosity,
1988            workFactor,
1989        )
1990    } {
1991        Ok(written) => {
1992            *destLen -= written;
1993            ReturnCode::BZ_OK as c_int
1994        }
1995        Err(err) => err as c_int,
1996    }
1997}
1998
1999unsafe fn BZ2_bzBuffToBuffCompressHelp(
2000    dest: *mut c_char,
2001    destLen: c_uint,
2002    source: *mut c_char,
2003    sourceLen: c_uint,
2004    blockSize100k: c_int,
2005    verbosity: c_int,
2006    workFactor: c_int,
2007) -> Result<c_uint, ReturnCode> {
2008    let mut strm = BzStream::zeroed();
2009
2010    match BZ2_bzCompressInitHelp(&mut strm, blockSize100k, verbosity, workFactor) {
2011        ReturnCode::BZ_OK => {}
2012        ret => return Err(ret),
2013    }
2014
2015    strm.next_in = source;
2016    strm.next_out = dest;
2017    strm.avail_in = sourceLen;
2018    strm.avail_out = destLen;
2019
2020    match BZ2_bzCompressHelp(&mut strm, Action::Finish as i32) {
2021        ReturnCode::BZ_FINISH_OK => {
2022            BZ2_bzCompressEndHelp(&mut strm);
2023            Err(ReturnCode::BZ_OUTBUFF_FULL)
2024        }
2025        ReturnCode::BZ_STREAM_END => {
2026            BZ2_bzCompressEndHelp(&mut strm);
2027            Ok(strm.avail_out)
2028        }
2029        error => {
2030            BZ2_bzCompressEndHelp(&mut strm);
2031            Err(error)
2032        }
2033    }
2034}
2035
2036/// Decompress the input data into the destination buffer.
2037///
2038/// This function attempts to decompress the data in `source[0 .. sourceLen]` into `dest[0 .. *destLen]`.
2039/// If the destination buffer is big enough, `*destLen` is set to the size of the decompressed data, and [`BZ_OK`] is returned.
2040/// If the decompressed data won't fit, `*destLen` is unchanged, and [`BZ_OUTBUFF_FULL`] is returned.
2041///
2042/// For the meaning of parameters `small`, `verbosity`, see [`BZ2_bzDecompressInit`].
2043///
2044/// Because the compression ratio of the compressed data cannot be known in advance,
2045/// there is no easy way to guarantee that the output buffer will be big enough.
2046/// You may of course make arrangements in your code to record the size of the uncompressed data,
2047/// but such a mechanism is beyond the scope of this library.
2048///
2049/// # Returns
2050///
2051/// - [`BZ_PARAM_ERROR`] if any of
2052///     - `dest.is_null()`
2053///     - `destLen.is_null()`
2054///     - `source.is_null()`
2055///     - `!(0..=1).contains(&small)`
2056///     - `!(0..=4).contains(&verbosity)`
2057/// - [`BZ_MEM_ERROR`] if insufficient memory is available
2058/// - [`BZ_OUTBUFF_FULL`] if the size of the compressed data exceeds `*destLen`
2059/// - [`BZ_DATA_ERROR`] if a data integrity error is detected in the compressed stream
2060/// - [`BZ_DATA_ERROR_MAGIC`] if the compressed stream doesn't begin with the right magic bytes
2061/// - [`BZ_UNEXPECTED_EOF`] if the compressed data ends before the logical end-of-stream was detected
2062/// - [`BZ_OK`] otherwise
2063///
2064/// # Safety
2065///
2066/// The caller must guarantee that
2067///
2068/// * `destLen` satisfies the requirements of [`pointer::as_mut`]
2069/// * Either
2070///     - `dest` is `NULL`
2071///     - `dest` is writable for `*destLen` bytes
2072/// * Either
2073///     - `source` is `NULL`
2074///     - `source` is readable for `sourceLen`
2075///
2076/// [`pointer::as_mut`]: https://doc.rust-lang.org/core/primitive.pointer.html#method.as_mut
2077#[cfg_attr(feature = "export-symbols", export_name = prefix!(BZ2_bzBuffToBuffDecompress))]
2078pub unsafe extern "C" fn BZ2_bzBuffToBuffDecompress(
2079    dest: *mut c_char,
2080    destLen: *mut c_uint,
2081    source: *mut c_char,
2082    sourceLen: c_uint,
2083    small: c_int,
2084    verbosity: c_int,
2085) -> c_int {
2086    if dest.is_null() || destLen.is_null() || source.is_null() {
2087        return ReturnCode::BZ_PARAM_ERROR as c_int;
2088    }
2089
2090    let Some(destLen) = (unsafe { destLen.as_mut() }) else {
2091        return ReturnCode::BZ_PARAM_ERROR as c_int;
2092    };
2093
2094    match unsafe {
2095        BZ2_bzBuffToBuffDecompressHelp(dest, *destLen, source, sourceLen, small, verbosity)
2096    } {
2097        Ok(written) => {
2098            *destLen -= written;
2099            ReturnCode::BZ_OK as c_int
2100        }
2101        Err(err) => err as c_int,
2102    }
2103}
2104
2105unsafe fn BZ2_bzBuffToBuffDecompressHelp(
2106    dest: *mut c_char,
2107    destLen: c_uint,
2108    source: *mut c_char,
2109    sourceLen: c_uint,
2110    small: c_int,
2111    verbosity: c_int,
2112) -> Result<c_uint, ReturnCode> {
2113    let mut strm = BzStream::zeroed();
2114
2115    match BZ2_bzDecompressInitHelp(&mut strm, verbosity, small) {
2116        ReturnCode::BZ_OK => {}
2117        ret => return Err(ret),
2118    }
2119
2120    strm.next_in = source;
2121    strm.next_out = dest;
2122    strm.avail_in = sourceLen;
2123    strm.avail_out = destLen;
2124
2125    match BZ2_bzDecompressHelp(&mut strm) {
2126        ReturnCode::BZ_OK => {
2127            BZ2_bzDecompressEndHelp(&mut strm);
2128            match strm.avail_out {
2129                0 => Err(ReturnCode::BZ_OUTBUFF_FULL),
2130                _ => Err(ReturnCode::BZ_UNEXPECTED_EOF),
2131            }
2132        }
2133        ReturnCode::BZ_STREAM_END => {
2134            BZ2_bzDecompressEndHelp(&mut strm);
2135            Ok(strm.avail_out)
2136        }
2137        error => {
2138            BZ2_bzDecompressEndHelp(&mut strm);
2139            Err(error)
2140        }
2141    }
2142}