#![deny(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(clippy::must_use_candidate, clippy::missing_errors_doc, clippy::missing_panics_doc)]
mod reader;
mod writer;
pub use reader::*;
pub use writer::*;
#[cfg(feature = "multithreading-simple")]
mod multithreaded_reader;
#[cfg(feature = "multithreading-simple")]
mod multithreaded_writer;
#[cfg(feature = "multithreading-simple")]
#[cfg_attr(docsrs, doc(cfg(feature = "multithreading-simple")))]
pub use multithreaded_reader::*;
#[cfg(feature = "multithreading-simple")]
#[cfg_attr(docsrs, doc(cfg(feature = "multithreading-simple")))]
pub use multithreaded_writer::*;
use std::io;
use byteorder::{ByteOrder, LittleEndian};
use libdeflater::CompressionLvl;
use thiserror::Error;
mod buffer_ops {
#[inline(always)]
#[allow(unsafe_code, clippy::uninit_vec)]
pub(crate) unsafe fn resize_uninit(buffer: &mut Vec<u8>, new_len: usize) {
buffer.clear();
buffer.reserve_exact(new_len);
buffer.set_len(new_len);
}
}
pub const BGZF_BLOCK_SIZE: usize = 65280;
pub const BUFSIZE: usize = 128 * 1024;
pub(crate) const MAX_BGZF_BLOCK_SIZE: usize = 64 * 1024;
pub(crate) static BGZF_EOF: &[u8] = &[
0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x06, 0x00, 0x42, 0x43, 0x02, 0x00, 0x1b, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
pub(crate) const BGZF_HEADER_SIZE: usize = 18;
pub(crate) const BGZF_FOOTER_SIZE: usize = 8;
pub(crate) const DEFLATE_STORED_HEADER_SIZE: usize = 5;
pub(crate) const BGZF_SIZEOF_CRC32: usize = 4;
pub(crate) const BGZF_NAME_COMMENT_EXTRA_FLAG: u8 = 4;
pub(crate) const BGZF_SUBFIELD_ID1: u8 = b'B';
pub(crate) const BGZF_SUBFIELD_ID2: u8 = b'C';
pub(crate) const BGZF_BLOCK_SIZE_OFFSET: usize = 16;
pub(crate) const BGZF_XFL_OFFSET: usize = 8;
pub(crate) const BGZF_COMPRESSION_HINT_BEST: u8 = 2;
pub(crate) const BGZF_COMPRESSION_HINT_FASTEST: u8 = 4;
pub(crate) const BGZF_COMPRESSION_HINT_OTHER: u8 = 0;
const HEADER_TEMPLATE: [u8; BGZF_HEADER_SIZE] = [
0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x06, 0x00, b'B', b'C', 0x02, 0x00, 0x00, 0x00, ];
type BgzfResult<T> = Result<T, BgzfError>;
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum BgzfError {
#[error("Compressed block size ({0}) exceeds max allowed: ({1})")]
BlockSizeExceeded(usize, usize),
#[error("Invalid compression level: {0}")]
CompressionLevel(u8),
#[error(transparent)]
Io(#[from] io::Error),
#[error("Invalid checksum, found {found}, expected {expected}")]
InvalidChecksum { found: u32, expected: u32 },
#[error("Invalid block header: {0}")]
InvalidHeader(&'static str),
#[error("Uncompressed block size ({found}) exceeds maximum ({max})")]
UncompressedSizeExceeded { found: usize, max: usize },
#[error("LibDeflater compression error: {0:?}")]
LibDeflaterCompress(libdeflater::CompressionError),
#[error(transparent)]
LibDelfaterDecompress(#[from] libdeflater::DecompressionError),
}
#[derive(Debug, Copy, Clone)]
struct ChecksumValues {
sum: u32,
amount: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompressionLevel(CompressionLvl);
#[allow(dead_code)]
impl CompressionLevel {
#[allow(clippy::cast_lossless)]
pub fn new(level: u8) -> BgzfResult<Self> {
Ok(Self(
CompressionLvl::new(level as i32).map_err(|_e| BgzfError::CompressionLevel(level))?,
))
}
fn inner(&self) -> &libdeflater::CompressionLvl {
&self.0
}
}
impl TryFrom<u8> for CompressionLevel {
type Error = BgzfError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<CompressionLevel> for u8 {
fn from(level: CompressionLevel) -> Self {
let inner: i32 = level.inner().into();
inner as u8
}
}
impl From<&CompressionLevel> for u8 {
fn from(level: &CompressionLevel) -> Self {
let inner: i32 = level.inner().into();
inner as u8
}
}
pub struct Compressor {
inner: libdeflater::Compressor,
level: CompressionLevel,
}
#[allow(dead_code)]
impl Compressor {
#[must_use]
pub fn new(level: CompressionLevel) -> Self {
Self { inner: libdeflater::Compressor::new(*level.inner()), level }
}
#[inline]
fn inner(&self) -> &libdeflater::Compressor {
&self.inner
}
#[inline]
fn inner_mut(&mut self) -> &mut libdeflater::Compressor {
&mut self.inner
}
#[inline(always)]
pub fn compress(&mut self, input: &[u8], buffer: &mut Vec<u8>) -> BgzfResult<()> {
let compress_bound = self.inner_mut().deflate_compress_bound(input.len());
let required_size = BGZF_HEADER_SIZE + compress_bound + BGZF_FOOTER_SIZE;
#[allow(unsafe_code)]
unsafe {
buffer_ops::resize_uninit(buffer, required_size);
}
let bytes_written = self
.inner_mut()
.deflate_compress(input, &mut buffer[BGZF_HEADER_SIZE..])
.map_err(BgzfError::LibDeflaterCompress)?;
let block_size = BGZF_HEADER_SIZE + bytes_written + BGZF_FOOTER_SIZE;
if block_size > MAX_BGZF_BLOCK_SIZE {
return Err(BgzfError::BlockSizeExceeded(block_size, MAX_BGZF_BLOCK_SIZE));
}
let header = header_inner(self.level, bytes_written as u16);
buffer[0..BGZF_HEADER_SIZE].copy_from_slice(&header);
let footer_offset = BGZF_HEADER_SIZE + bytes_written;
buffer[footer_offset..footer_offset + BGZF_SIZEOF_CRC32]
.copy_from_slice(&crc32(input).to_le_bytes());
buffer[footer_offset + BGZF_SIZEOF_CRC32..footer_offset + BGZF_FOOTER_SIZE]
.copy_from_slice(&(input.len() as u32).to_le_bytes());
buffer.truncate(footer_offset + BGZF_FOOTER_SIZE);
Ok(())
}
pub fn append_eof(bytes: &mut Vec<u8>) {
bytes.extend(BGZF_EOF);
}
}
struct Decompressor(libdeflater::Decompressor);
#[allow(dead_code)]
impl Decompressor {
fn new() -> Self {
Self(libdeflater::Decompressor::new())
}
#[inline]
fn inner(&self) -> &libdeflater::Decompressor {
&self.0
}
#[inline]
fn inner_mut(&mut self) -> &mut libdeflater::Decompressor {
&mut self.0
}
#[inline]
fn decompress(
&mut self,
input: &[u8],
output: &mut [u8],
checksum_values: ChecksumValues,
validate_crc: bool,
) -> BgzfResult<()> {
let decompressed = if checksum_values.amount != 0 {
self.inner_mut().deflate_decompress(input, output)?
} else {
0
};
if decompressed != output.len() {
let found = crc32(&output[..decompressed]);
return Err(BgzfError::InvalidChecksum { found, expected: checksum_values.sum });
}
if validate_crc {
let found = crc32(output);
if found != checksum_values.sum {
return Err(BgzfError::InvalidChecksum { found, expected: checksum_values.sum });
}
}
Ok(())
}
}
impl Default for Decompressor {
fn default() -> Self {
Self::new()
}
}
#[inline(always)]
fn header_inner(
compression_level: CompressionLevel,
compressed_size: u16,
) -> [u8; BGZF_HEADER_SIZE] {
let mut header = HEADER_TEMPLATE;
header[BGZF_XFL_OFFSET] = if compression_level.inner() >= &CompressionLvl::best() {
BGZF_COMPRESSION_HINT_BEST
} else if compression_level.inner() <= &CompressionLvl::fastest() {
BGZF_COMPRESSION_HINT_FASTEST
} else {
BGZF_COMPRESSION_HINT_OTHER
};
let bsize = compressed_size + BGZF_HEADER_SIZE as u16 + BGZF_FOOTER_SIZE as u16 - 1;
header[BGZF_BLOCK_SIZE_OFFSET..BGZF_BLOCK_SIZE_OFFSET + 2]
.copy_from_slice(&bsize.to_le_bytes());
header
}
#[inline]
fn check_header(bytes: &[u8]) -> BgzfResult<()> {
if bytes[3] & 4 != BGZF_NAME_COMMENT_EXTRA_FLAG {
Err(BgzfError::InvalidHeader("Extra field flag not set"))
} else if bytes[12] != BGZF_SUBFIELD_ID1 || bytes[13] != BGZF_SUBFIELD_ID2 {
Err(BgzfError::InvalidHeader("Bad SID"))
} else {
Ok(())
}
}
#[inline]
fn get_block_size(bytes: &[u8]) -> usize {
LittleEndian::read_u16(&bytes[BGZF_BLOCK_SIZE_OFFSET..]) as usize + 1
}
#[inline]
fn get_footer_values(input: &[u8]) -> ChecksumValues {
let check_sum = LittleEndian::read_u32(&input[input.len() - 8..input.len() - 4]);
let check_amount = LittleEndian::read_u32(&input[input.len() - 4..]);
ChecksumValues { sum: check_sum, amount: check_amount }
}
#[inline]
fn strip_footer(input: &[u8]) -> &[u8] {
&input[..input.len() - BGZF_FOOTER_SIZE]
}
#[inline]
fn crc32(data: &[u8]) -> u32 {
let mut crc = libdeflater::Crc::new();
crc.update(data);
crc.sum()
}
#[inline]
fn stored_block_len(deflate_header: &[u8]) -> Option<usize> {
if deflate_header.len() < DEFLATE_STORED_HEADER_SIZE || deflate_header[0] & 0b0000_0111 != 0b001
{
return None;
}
let len = u16::from_le_bytes([deflate_header[1], deflate_header[2]]);
let nlen = u16::from_le_bytes([deflate_header[3], deflate_header[4]]);
(nlen == !len).then_some(len as usize)
}
#[cfg(test)]
mod test {
use std::io::{Read, Write};
use std::{
fs::File,
io::{BufReader, BufWriter},
};
use proptest::prelude::*;
use tempfile::tempdir;
use super::*;
#[test]
fn test_eof_marker_written_once_with_finish() {
let mut output = Vec::new();
{
let mut writer = Writer::new(&mut output, CompressionLevel::new(3).unwrap());
writer.write_all(b"hello").unwrap();
writer.finish().unwrap();
}
assert!(output.ends_with(BGZF_EOF), "Output should end with BGZF_EOF marker");
let eof_count = output.windows(BGZF_EOF.len()).filter(|w| *w == BGZF_EOF).count();
assert_eq!(eof_count, 1, "EOF marker should appear exactly once");
}
#[test]
fn test_eof_marker_written_once_on_drop() {
let mut output = Vec::new();
{
let mut writer = Writer::new(&mut output, CompressionLevel::new(3).unwrap());
writer.write_all(b"hello").unwrap();
}
assert!(output.ends_with(BGZF_EOF), "Output should end with BGZF_EOF marker");
let eof_count = output.windows(BGZF_EOF.len()).filter(|w| *w == BGZF_EOF).count();
assert_eq!(eof_count, 1, "EOF marker should appear exactly once");
}
#[test]
fn test_eof_marker_empty_write() {
let mut output = Vec::new();
{
let writer = Writer::new(&mut output, CompressionLevel::new(3).unwrap());
writer.finish().unwrap();
}
assert!(
output.ends_with(BGZF_EOF),
"Output should end with BGZF_EOF marker even with no data written"
);
assert_eq!(output.as_slice(), BGZF_EOF);
}
#[test]
fn test_multiple_flush_single_eof() {
let mut output = Vec::new();
{
let mut writer = Writer::new(&mut output, CompressionLevel::new(3).unwrap());
writer.write_all(b"hello").unwrap();
writer.flush().unwrap();
writer.write_all(b"world").unwrap();
writer.flush().unwrap();
writer.finish().unwrap();
}
assert!(output.ends_with(BGZF_EOF), "Output should end with BGZF_EOF marker");
let eof_count = output.windows(BGZF_EOF.len()).filter(|w| *w == BGZF_EOF).count();
assert_eq!(
eof_count, 1,
"EOF marker should appear exactly once even after multiple flush() calls"
);
}
#[test]
fn test_simple_bgzfsync() {
let dir = tempdir().unwrap();
let input = b"
This is a longer test than normal to come up with a bunch of text.
We'll read just a few lines at a time.
What if this is a longer string, does that then make
things fail?
";
let orig_file = dir.path().join("orig.output.txt");
let mut orig_writer = BufWriter::new(File::create(&orig_file).unwrap());
orig_writer.write_all(input).unwrap();
drop(orig_writer);
let output_file = dir.path().join("output.txt");
let out_writer = BufWriter::new(File::create(&output_file).unwrap());
let mut bgzf = Writer::new(out_writer, CompressionLevel::new(3).unwrap());
bgzf.write_all(input).unwrap();
bgzf.finish().unwrap();
let mut reader = BufReader::new(File::open(output_file).unwrap());
let mut result = vec![];
reader.read_to_end(&mut result).unwrap();
let mut decoder = Reader::new(&result[..]);
let mut bytes = vec![];
decoder.read_to_end(&mut bytes).unwrap();
assert_eq!(input.to_vec(), bytes);
}
#[test]
fn block_claiming_more_bytes_than_payload_is_rejected() {
let mut compressor = Compressor::new(CompressionLevel::new(3).unwrap());
let mut block = vec![];
compressor.compress(b"hello world", &mut block).unwrap();
let len = block.len();
block[len - 4..].copy_from_slice(&200u32.to_le_bytes());
let mut out = vec![];
let result = Reader::new(block.as_slice()).read_to_end(&mut out);
assert!(result.is_err(), "block whose ISIZE exceeds its payload must error");
}
#[test]
fn compress_rejects_oversized_block_without_overflowing_bsize() {
fn incompressible(len: usize) -> Vec<u8> {
let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
let mut out = Vec::with_capacity(len + 8);
while out.len() < len {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
out.extend_from_slice(&state.to_le_bytes());
}
out.truncate(len);
out
}
let mut compressor = Compressor::new(CompressionLevel::new(12).unwrap());
let mut block = vec![];
let result = compressor.compress(&incompressible(65_515), &mut block);
assert!(
matches!(result, Err(BgzfError::BlockSizeExceeded(..))),
"expected BlockSizeExceeded, got {result:?}"
);
}
#[test]
fn block_with_oversized_isize_is_rejected() {
let mut compressor = Compressor::new(CompressionLevel::new(6).unwrap());
let mut block = vec![];
compressor.compress(&[b'A'; 1024], &mut block).unwrap();
let len = block.len();
block[len - 4..].copy_from_slice(&100_000u32.to_le_bytes());
let mut out = vec![];
assert_bgzf_err(Reader::new(block.as_slice()).read_to_end(&mut out), |e| {
matches!(e, BgzfError::UncompressedSizeExceeded { .. })
});
}
#[test]
fn level_0_emits_a_single_final_stored_block() {
let mut compressor = Compressor::new(CompressionLevel::new(0).unwrap());
let input = b"the quick brown fox jumps over the lazy dog";
let mut block = vec![];
compressor.compress(input, &mut block).unwrap();
let deflate = &block[BGZF_HEADER_SIZE..block.len() - BGZF_FOOTER_SIZE];
let len = stored_block_len(deflate).expect("level 0 should produce a stored block");
assert_eq!(len, input.len());
assert_eq!(&deflate[DEFLATE_STORED_HEADER_SIZE..], input);
}
#[test]
fn compressed_block_is_not_detected_as_stored() {
let mut compressor = Compressor::new(CompressionLevel::new(6).unwrap());
let input = vec![b'A'; 4096]; let mut block = vec![];
compressor.compress(&input, &mut block).unwrap();
let deflate = &block[BGZF_HEADER_SIZE..block.len() - BGZF_FOOTER_SIZE];
assert!(stored_block_len(deflate).is_none());
}
#[test]
fn store_only_writer_emits_stored_blocks_across_chunked_writes() {
let input: Vec<u8> = (0..150_000u32).map(|i| i.wrapping_mul(2_654_435_761) as u8).collect();
let one_shot = {
let mut out = vec![];
let mut writer = Writer::new(&mut out, CompressionLevel::new(0).unwrap());
writer.write_all(&input).unwrap();
writer.finish().unwrap();
out
};
let chunked = {
let mut out = vec![];
let mut writer = Writer::new(&mut out, CompressionLevel::new(0).unwrap());
for chunk in input.chunks(7) {
writer.write_all(chunk).unwrap();
}
writer.finish().unwrap();
out
};
assert_eq!(one_shot, chunked, "chunked writes must produce identical framing");
let first_deflate =
&one_shot[BGZF_HEADER_SIZE..BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE];
assert!(stored_block_len(first_deflate).is_some(), "level 0 must emit stored blocks");
let mut decoded = vec![];
Reader::new(one_shot.as_slice()).read_to_end(&mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn store_only_empty_input_writes_only_eof() {
let mut out = vec![];
Writer::new(&mut out, CompressionLevel::new(0).unwrap()).finish().unwrap();
assert_eq!(out.as_slice(), BGZF_EOF);
}
#[test]
fn store_only_exact_block_multiple_has_no_trailing_empty_block() {
let blocksize = 1024;
let input = vec![0x5Au8; blocksize * 3];
let mut out = vec![];
let mut writer =
Writer::with_capacity(&mut out, CompressionLevel::new(0).unwrap(), blocksize);
writer.write_all(&input).unwrap();
writer.finish().unwrap();
let block_bytes =
BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE + blocksize + BGZF_FOOTER_SIZE;
assert_eq!(out.len(), block_bytes * 3 + BGZF_EOF.len());
let mut decoded = vec![];
Reader::new(out.as_slice()).read_to_end(&mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn store_only_multiple_flush_single_eof_and_round_trips() {
let mut output = vec![];
{
let mut writer = Writer::new(&mut output, CompressionLevel::new(0).unwrap());
writer.write_all(b"hello ").unwrap();
writer.flush().unwrap();
writer.write_all(b"store-only ").unwrap();
writer.flush().unwrap();
writer.flush().unwrap(); writer.write_all(b"world").unwrap();
writer.finish().unwrap();
}
let eof_count = output.windows(BGZF_EOF.len()).filter(|w| *w == BGZF_EOF).count();
assert_eq!(eof_count, 1, "EOF marker should appear exactly once across flushes");
let mut decoded = vec![];
Reader::new(output.as_slice()).read_to_end(&mut decoded).unwrap();
assert_eq!(decoded, b"hello store-only world");
}
#[test]
fn store_only_eof_written_once_on_drop() {
let mut out = vec![];
{
let mut writer = Writer::new(&mut out, CompressionLevel::new(0).unwrap());
writer.write_all(b"some store-only data").unwrap();
}
assert!(out.ends_with(BGZF_EOF), "output should end with the EOF marker");
let eof_count = out.windows(BGZF_EOF.len()).filter(|w| *w == BGZF_EOF).count();
assert_eq!(eof_count, 1, "EOF marker should appear exactly once");
}
fn assert_bgzf_err(result: std::io::Result<usize>, want: impl Fn(&BgzfError) -> bool) {
let err = result.expect_err("expected an error");
let bgzf = err
.get_ref()
.and_then(|e| e.downcast_ref::<BgzfError>())
.expect("reader errors wrap a BgzfError");
assert!(want(bgzf), "unexpected error variant: {bgzf:?}");
}
#[test]
fn reader_can_skip_crc_validation_on_stored_blocks() {
let input = b"uncompressed payload bytes";
let mut blob = vec![];
let mut writer = Writer::new(&mut blob, CompressionLevel::new(0).unwrap());
writer.write_all(input).unwrap();
writer.finish().unwrap();
let crc_off = BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE + input.len();
blob[crc_off] ^= 0x01;
let mut out = vec![];
assert_bgzf_err(Reader::new(blob.as_slice()).read_to_end(&mut out), |e| {
matches!(e, BgzfError::InvalidChecksum { .. })
});
let mut out = vec![];
Reader::new(blob.as_slice())
.with_crc_validation(false)
.read_to_end(&mut out)
.expect("validation disabled must accept the block");
assert_eq!(out, input);
}
#[test]
fn reader_can_skip_crc_validation_on_compressed_blocks() {
let input = vec![b'A'; 5000]; let mut blob = vec![];
let mut writer = Writer::new(&mut blob, CompressionLevel::new(6).unwrap());
writer.write_all(&input).unwrap();
writer.finish().unwrap();
let crc_off = blob.len() - BGZF_EOF.len() - BGZF_FOOTER_SIZE;
blob[crc_off] ^= 0x01;
let mut out = vec![];
assert_bgzf_err(Reader::new(blob.as_slice()).read_to_end(&mut out), |e| {
matches!(e, BgzfError::InvalidChecksum { .. })
});
let mut out = vec![];
Reader::new(blob.as_slice())
.with_crc_validation(false)
.read_to_end(&mut out)
.expect("validation disabled must accept the block");
assert_eq!(out, input);
}
#[test]
fn truncated_block_errors_even_with_crc_validation_disabled() {
let input = vec![0x42u8; 1000];
let mut blob = vec![];
let mut writer = Writer::new(&mut blob, CompressionLevel::new(0).unwrap());
writer.write_all(&input).unwrap();
writer.finish().unwrap();
let truncated = &blob[..BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE + 100];
let mut out = vec![];
assert!(
Reader::new(truncated).with_crc_validation(false).read_to_end(&mut out).is_err(),
"a truncated block must error even with CRC validation off"
);
}
#[test]
fn stored_isize_mismatch_errors_even_with_crc_validation_disabled() {
let input = vec![0x42u8; 1000];
let mut blob = vec![];
let mut writer = Writer::new(&mut blob, CompressionLevel::new(0).unwrap());
writer.write_all(&input).unwrap();
writer.finish().unwrap();
let isize_off =
BGZF_HEADER_SIZE + DEFLATE_STORED_HEADER_SIZE + input.len() + BGZF_SIZEOF_CRC32;
blob[isize_off] = blob[isize_off].wrapping_add(1);
let mut out = vec![];
assert_bgzf_err(
Reader::new(blob.as_slice()).with_crc_validation(false).read_to_end(&mut out),
|e| matches!(e, BgzfError::InvalidHeader(_)),
);
}
#[test]
fn reader_skips_crc_across_multiple_blocks() {
let input: Vec<u8> = (0..200_000u32).map(|i| (i % 251) as u8).collect();
let mut blob = vec![];
let mut writer = Writer::new(&mut blob, CompressionLevel::new(0).unwrap());
writer.write_all(&input).unwrap();
writer.finish().unwrap();
let mut decoded = vec![];
Reader::new(blob.as_slice())
.with_crc_validation(false)
.read_to_end(&mut decoded)
.expect("multi-block stream must read with validation off");
assert_eq!(decoded, input);
}
#[test]
fn reader_round_trips_store_only_data() {
let input: Vec<u8> = (0..200_000u32).map(|i| (i % 251) as u8).collect();
let mut blob = vec![];
let mut writer = Writer::new(&mut blob, CompressionLevel::new(0).unwrap());
writer.write_all(&input).unwrap();
writer.finish().unwrap();
let mut decoded = vec![];
Reader::new(blob.as_slice()).read_to_end(&mut decoded).unwrap();
assert_eq!(decoded, input);
}
const DICT_SIZE: usize = 32768;
proptest! {
#[test]
fn proptest_bgzf(
input in prop::collection::vec(0..u8::MAX, 1..(DICT_SIZE * 10)),
buf_size in DICT_SIZE..BGZF_BLOCK_SIZE,
write_size in 1..BGZF_BLOCK_SIZE * 4,
comp_level in 0..=12_u8
) {
let dir = tempdir().unwrap();
let output_file = dir.path().join("output.txt");
let out_writer = BufWriter::new(File::create(&output_file).unwrap());
let mut writer = Writer::with_capacity(out_writer, CompressionLevel::new(comp_level).unwrap(), buf_size);
for chunk in input.chunks(write_size) {
writer.write_all(chunk).unwrap();
}
writer.finish().unwrap();
let mut reader = BufReader::new(File::open(output_file).unwrap());
let mut result = vec![];
reader.read_to_end(&mut result).unwrap();
let mut gz = Reader::new(&result[..]);
let mut bytes = vec![];
gz.read_to_end(&mut bytes).unwrap();
assert_eq!(input.clone(), bytes);
}
}
}