1use std::io::Read;
9
10use bytes::{Bytes, BytesMut};
11
12use crate::error::{Error, Result};
13use crate::source::ByteSource;
14
15pub const HEADER_SIZE: usize = 18;
16pub const EOF_SIZE: usize = 28;
17pub const MAX_BLOCK_SIZE: usize = 65536;
19
20pub static BGZF_EOF_BLOCK: [u8; EOF_SIZE] = [
25 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x06, 0x00, 0x42, 0x43, 0x02, 0x00,
26 0x1b, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
27];
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
32pub struct VirtualOffset(pub u64);
33
34impl VirtualOffset {
35 #[inline]
36 pub fn block_offset(self) -> u64 {
37 self.0 >> 16
38 }
39
40 #[inline]
41 pub fn within_block(self) -> usize {
42 (self.0 & 0xFFFF) as usize
43 }
44
45 #[inline]
46 pub fn new(block_offset: u64, within_block: u16) -> Self {
47 VirtualOffset((block_offset << 16) | within_block as u64)
48 }
49}
50
51fn check_block_header(head: &[u8], path: &str, at: u64) -> Result<()> {
57 let ok = head[0] == 0x1f
58 && head[1] == 0x8b
59 && head[2] == 8
60 && (head[3] & 0x04) != 0
61 && head[12] == b'B'
62 && head[13] == b'C'
63 && u16::from_le_bytes([head[14], head[15]]) == 2;
64 if ok {
65 Ok(())
66 } else {
67 Err(Error::corrupt(
68 path,
69 at,
70 format!(
71 "no bgzf block header at {at}, so the file is corrupt or its index \
72 does not belong to it"
73 ),
74 ))
75 }
76}
77
78#[inline]
84fn block_size(head: &[u8]) -> usize {
85 u16::from_le_bytes([head[16], head[17]]) as usize + 1
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct Chunk {
91 pub begin: VirtualOffset,
92 pub end: VirtualOffset,
93}
94
95impl Chunk {
96 #[inline]
98 pub fn compressed_size(&self) -> u64 {
99 self.end
100 .block_offset()
101 .saturating_sub(self.begin.block_offset())
102 }
103}
104
105pub fn decompress_chunk(source: &dyn ByteSource, chunk: Chunk, path: &str) -> Result<Bytes> {
111 let first_block = chunk.begin.block_offset();
112 let last_block = chunk.end.block_offset();
113 let begin_within = chunk.begin.within_block();
114 let end_within = chunk.end.within_block();
115
116 let wanted = (last_block - first_block) as usize + MAX_BLOCK_SIZE;
120 let raw = source.read_at(first_block, wanted)?;
123
124 let mut out = BytesMut::new();
125 let mut index = 0usize;
126 loop {
130 let at = first_block + index as u64;
131 if at > last_block {
134 break;
135 }
136 if at == last_block && end_within == 0 {
139 break;
140 }
141
142 if index + HEADER_SIZE > raw.len() {
143 return Err(Error::corrupt(
144 path,
145 at,
146 format!(
147 "the bgzf block at {at} is cut short (its {HEADER_SIZE}-byte header \
148 does not fit what is left of the file)"
149 ),
150 ));
151 }
152 let head = &raw[index..index + HEADER_SIZE];
153 check_block_header(head, path, at)?;
154 let size = block_size(head);
155 if size < HEADER_SIZE {
156 return Err(Error::corrupt(
157 path,
158 at,
159 format!(
160 "the bgzf block at {at} declares {size} bytes, which is less than \
161 its own header"
162 ),
163 ));
164 }
165 if index + size > raw.len() {
166 return Err(Error::corrupt(
167 path,
168 at,
169 format!(
170 "the bgzf block at {at} declares {size} bytes and only {} are left, \
171 so the file is truncated",
172 raw.len() - index
173 ),
174 ));
175 }
176
177 let block = inflate(&raw[index..index + size], path, at)?;
178 index += size;
179
180 let from = if at == first_block { begin_within } else { 0 };
184 let to = if at == last_block {
185 end_within.min(block.len())
186 } else {
187 block.len()
188 };
189 if to > from {
190 out.extend_from_slice(&block[from..to]);
191 }
192 }
193 Ok(out.freeze())
194}
195
196fn inflate(block: &[u8], path: &str, at: u64) -> Result<Vec<u8>> {
198 let mut out = Vec::with_capacity(MAX_BLOCK_SIZE);
199 flate2::read::GzDecoder::new(block)
200 .read_to_end(&mut out)
201 .map_err(|e| {
202 Error::corrupt(
203 path,
204 at,
205 format!("could not inflate the bgzf block at {at}: {e}"),
206 )
207 })?;
208 Ok(out)
209}
210
211pub fn check_eof(source: &dyn ByteSource) -> Result<()> {
216 let path = source.path();
217 let len = source.len()?;
218 if len < EOF_SIZE as u64 {
219 return Err(Error::format(
220 path,
221 "file is too short to be a bam (it does not hold even the bgzf end-of-file block)",
222 ));
223 }
224 let tail = source.read_exact_at(len - EOF_SIZE as u64, EOF_SIZE)?;
225 if tail[..] != BGZF_EOF_BLOCK[..] {
226 return Err(Error::format(
227 path,
228 "bam file is truncated (it does not end with the bgzf end-of-file block)",
229 ));
230 }
231 Ok(())
232}
233
234pub fn header_reader(source: &dyn ByteSource) -> impl Read + '_ {
240 flate2::read::MultiGzDecoder::new(SourceReader {
241 source,
242 offset: 0,
243 buffer: Bytes::new(),
244 })
245}
246
247struct SourceReader<'a> {
249 source: &'a dyn ByteSource,
250 offset: u64,
251 buffer: Bytes,
252}
253
254impl Read for SourceReader<'_> {
255 fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
256 if self.buffer.is_empty() {
257 self.buffer = self
258 .source
259 .read_at(self.offset, MAX_BLOCK_SIZE)
260 .map_err(std::io::Error::other)?;
261 if self.buffer.is_empty() {
262 return Ok(0);
263 }
264 self.offset += self.buffer.len() as u64;
265 }
266 let take = out.len().min(self.buffer.len());
267 out[..take].copy_from_slice(&self.buffer[..take]);
268 self.buffer = self.buffer.slice(take..);
269 Ok(take)
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use crate::source::testing::MemorySource;
277 use std::io::Write;
278
279 fn block(payload: &[u8]) -> Vec<u8> {
281 let mut encoder =
282 flate2::write::DeflateEncoder::new(Vec::new(), flate2::Compression::new(6));
283 encoder.write_all(payload).unwrap();
284 let deflated = encoder.finish().unwrap();
285
286 let total = HEADER_SIZE + deflated.len() + 8;
287 let mut out = Vec::with_capacity(total);
288 out.extend_from_slice(&[0x1f, 0x8b, 8, 4, 0, 0, 0, 0, 0, 0xff]);
289 out.extend_from_slice(&6u16.to_le_bytes()); out.extend_from_slice(b"BC");
291 out.extend_from_slice(&2u16.to_le_bytes());
292 out.extend_from_slice(&((total - 1) as u16).to_le_bytes()); out.extend_from_slice(&deflated);
294 out.extend_from_slice(&crc32(payload).to_le_bytes());
295 out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
296 assert_eq!(out.len(), total);
297 out
298 }
299
300 fn crc32(data: &[u8]) -> u32 {
301 let mut hasher = flate2::Crc::new();
302 hasher.update(data);
303 hasher.sum()
304 }
305
306 #[test]
307 fn a_virtual_offset_splits_into_its_two_halves() {
308 let offset = VirtualOffset::new(0x0001_2345_6789, 0xABCD);
309 assert_eq!(offset.block_offset(), 0x0001_2345_6789);
310 assert_eq!(offset.within_block(), 0xABCD);
311 assert_eq!(offset.0, 0x0001_2345_6789_ABCD);
313 }
314
315 #[test]
316 fn a_chunk_inside_one_block_is_trimmed_at_both_ends() {
317 let payload: Vec<u8> = (0..200u8).collect();
318 let source = MemorySource::new(block(&payload));
319 let chunk = Chunk {
320 begin: VirtualOffset::new(0, 50),
321 end: VirtualOffset::new(0, 120),
322 };
323 let got = decompress_chunk(&source, chunk, "test").unwrap();
324 assert_eq!(&got[..], &payload[50..120]);
325 }
326
327 #[test]
328 fn a_chunk_spanning_blocks_takes_the_middle_ones_whole() {
329 let first: Vec<u8> = (0..100u8).collect();
330 let second: Vec<u8> = (100..200u8).collect();
331 let third: Vec<u8> = (200..250u8).collect();
332 let mut bytes = block(&first);
333 let second_at = bytes.len() as u64;
334 bytes.extend_from_slice(&block(&second));
335 let third_at = bytes.len() as u64;
336 bytes.extend_from_slice(&block(&third));
337 let source = MemorySource::new(bytes);
338
339 let chunk = Chunk {
340 begin: VirtualOffset::new(0, 90),
341 end: VirtualOffset::new(third_at, 10),
342 };
343 let got = decompress_chunk(&source, chunk, "test").unwrap();
344 let mut expected = first[90..].to_vec();
345 expected.extend_from_slice(&second);
346 expected.extend_from_slice(&third[..10]);
347 assert_eq!(&got[..], &expected[..]);
348 assert!(second_at > 0);
349 }
350
351 #[test]
352 fn a_chunk_ending_on_a_block_boundary_does_not_need_that_block() {
353 let first: Vec<u8> = (0..100u8).collect();
354 let bytes = block(&first);
355 let end_at = bytes.len() as u64;
356 let source = MemorySource::new(bytes);
358 let chunk = Chunk {
359 begin: VirtualOffset::new(0, 0),
360 end: VirtualOffset::new(end_at, 0),
361 };
362 assert_eq!(
363 &decompress_chunk(&source, chunk, "test").unwrap()[..],
364 &first[..]
365 );
366 }
367
368 #[test]
369 fn something_that_is_not_a_bgzf_block_is_refused_by_name() {
370 let source = MemorySource::new(vec![0u8; 4096]);
371 let chunk = Chunk {
372 begin: VirtualOffset::new(0, 0),
373 end: VirtualOffset::new(0, 10),
374 };
375 let err = decompress_chunk(&source, chunk, "test")
376 .unwrap_err()
377 .to_string();
378 assert!(err.contains("no bgzf block header"), "{err}");
379 }
380
381 #[test]
382 fn a_truncated_block_is_corrupt_not_a_short_read() {
383 let payload: Vec<u8> = (0..200u8).collect();
384 let mut bytes = block(&payload);
385 bytes.truncate(bytes.len() - 10);
386 let source = MemorySource::new(bytes);
387 let chunk = Chunk {
388 begin: VirtualOffset::new(0, 0),
389 end: VirtualOffset::new(0, 200),
390 };
391 let err = decompress_chunk(&source, chunk, "test")
392 .unwrap_err()
393 .to_string();
394 assert!(err.contains("truncated"), "{err}");
395 }
396
397 #[test]
398 fn the_eof_block_is_what_says_a_file_is_whole() {
399 let mut bytes = block(b"hello");
400 bytes.extend_from_slice(&BGZF_EOF_BLOCK);
401 assert!(check_eof(&MemorySource::new(bytes.clone())).is_ok());
402
403 bytes.truncate(bytes.len() - 1);
404 let err = check_eof(&MemorySource::new(bytes))
405 .unwrap_err()
406 .to_string();
407 assert!(err.contains("truncated"), "{err}");
408
409 let err = check_eof(&MemorySource::new(vec![0u8; 4]))
410 .unwrap_err()
411 .to_string();
412 assert!(err.contains("too short"), "{err}");
413 }
414
415 #[test]
416 fn the_header_reader_walks_every_member() {
417 let mut bytes = block(b"BAM\x01first ");
418 bytes.extend_from_slice(&block(b"second"));
419 bytes.extend_from_slice(&BGZF_EOF_BLOCK);
420 let source = MemorySource::new(bytes);
421 let mut text = Vec::new();
422 header_reader(&source).read_to_end(&mut text).unwrap();
423 assert_eq!(&text, b"BAM\x01first second");
424 }
425
426 #[test]
427 fn a_chunk_compressed_size_is_its_block_span() {
428 let chunk = Chunk {
429 begin: VirtualOffset::new(1000, 5),
430 end: VirtualOffset::new(9000, 7),
431 };
432 assert_eq!(chunk.compressed_size(), 8000);
433 }
434}