use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use flate2::read::MultiGzDecoder;
pub const BGZF_EOF: [u8; 28] = [
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,
];
#[derive(Debug, Default, Clone, Copy)]
pub struct QuickcheckOpts {
pub no_eof: bool,
}
#[derive(Debug)]
pub enum QuickcheckError {
Io(std::io::Error),
BadBgzfMagic,
BadBamMagic,
MissingEof,
Truncated,
}
impl std::fmt::Display for QuickcheckError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "io: {e}"),
Self::BadBgzfMagic => f.write_str("bad BGZF magic (not a BGZF/BAM file)"),
Self::BadBamMagic => f.write_str("bad BAM header magic"),
Self::MissingEof => f.write_str("missing BGZF EOF marker"),
Self::Truncated => f.write_str("file too short to be a valid BAM"),
}
}
}
impl std::error::Error for QuickcheckError {}
impl From<std::io::Error> for QuickcheckError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
pub fn quickcheck(path: &Path, opts: &QuickcheckOpts) -> Result<(), QuickcheckError> {
let mut f = File::open(path)?;
let meta = f.metadata()?;
let len = meta.len();
if len < 28 {
return Err(QuickcheckError::Truncated);
}
let mut head = [0u8; 4];
f.read_exact(&mut head)?;
if head != [0x1f, 0x8b, 0x08, 0x04] {
return Err(QuickcheckError::BadBgzfMagic);
}
f.seek(SeekFrom::Start(0))?;
let mut bam_magic = [0u8; 4];
MultiGzDecoder::new(&mut f).read_exact(&mut bam_magic)?;
if bam_magic != *b"BAM\x01" {
return Err(QuickcheckError::BadBamMagic);
}
if !opts.no_eof {
let mut tail = [0u8; 28];
f.seek(SeekFrom::End(-28))?;
f.read_exact(&mut tail)?;
if tail != BGZF_EOF {
return Err(QuickcheckError::MissingEof);
}
}
Ok(())
}