noodles_cram/async/io/reader/header/container/
block.rs

1use std::{
2    pin::Pin,
3    task::{Context, Poll},
4};
5
6use async_compression::tokio::bufread::GzipDecoder;
7use pin_project_lite::pin_project;
8use tokio::io::{self, AsyncRead, AsyncReadExt, BufReader, ReadBuf, Take};
9
10use crate::{
11    container::block::{CompressionMethod, ContentType},
12    r#async::io::reader::num::{read_itf8, read_itf8_as},
13};
14
15pub(super) async fn read_block<R>(reader: &mut R) -> io::Result<Decoder<&mut R>>
16where
17    R: AsyncRead + Unpin,
18{
19    let compression_method = read_compression_method(reader).await?;
20
21    let content_type = read_content_type(reader).await?;
22    validate_content_type(content_type)?;
23
24    let _content_type_id = read_itf8(reader).await?;
25    let compressed_size = read_itf8_as(reader).await?;
26    let uncompressed_size = read_itf8_as(reader).await?;
27
28    let decoder = match compression_method {
29        CompressionMethod::None => Decoder::None {
30            inner: reader.take(uncompressed_size),
31        },
32        CompressionMethod::Gzip => Decoder::Gzip {
33            inner: GzipDecoder::new(BufReader::new(reader.take(compressed_size))),
34        },
35        _ => {
36            return Err(io::Error::new(
37                io::ErrorKind::InvalidData,
38                format!(
39                    "invalid block compression method: expected {:?} or {:?}, got {:?}",
40                    CompressionMethod::None,
41                    CompressionMethod::Gzip,
42                    compression_method,
43                ),
44            ))
45        }
46    };
47
48    // Skip CRC32.
49
50    Ok(decoder)
51}
52
53pin_project! {
54    /// An async CRAM header container block data decoder.
55    #[allow(missing_docs)]
56    #[project = DecoderProjection]
57    pub enum Decoder<R> {
58        /// Uncompressed.
59        None { #[pin] inner: Take<R> },
60        /// gzip-compressed.
61        Gzip { #[pin] inner: GzipDecoder<BufReader<Take<R>>> },
62    }
63}
64
65impl<R> AsyncRead for Decoder<R>
66where
67    R: AsyncRead + Unpin,
68{
69    fn poll_read(
70        self: Pin<&mut Self>,
71        cx: &mut Context<'_>,
72        buf: &mut ReadBuf<'_>,
73    ) -> Poll<io::Result<()>> {
74        match self.project() {
75            DecoderProjection::None { inner } => inner.poll_read(cx, buf),
76            DecoderProjection::Gzip { inner } => inner.poll_read(cx, buf),
77        }
78    }
79}
80
81async fn read_compression_method<R>(reader: &mut R) -> io::Result<CompressionMethod>
82where
83    R: AsyncRead + Unpin,
84{
85    match reader.read_u8().await? {
86        0 => Ok(CompressionMethod::None),
87        1 => Ok(CompressionMethod::Gzip),
88        2 => Ok(CompressionMethod::Bzip2),
89        3 => Ok(CompressionMethod::Lzma),
90        4 => Ok(CompressionMethod::Rans4x8),
91        5 => Ok(CompressionMethod::RansNx16),
92        6 => Ok(CompressionMethod::AdaptiveArithmeticCoding),
93        7 => Ok(CompressionMethod::Fqzcomp),
94        8 => Ok(CompressionMethod::NameTokenizer),
95        _ => Err(io::Error::new(
96            io::ErrorKind::InvalidData,
97            "invalid compression method",
98        )),
99    }
100}
101
102async fn read_content_type<R>(reader: &mut R) -> io::Result<ContentType>
103where
104    R: AsyncRead + Unpin,
105{
106    match reader.read_u8().await? {
107        0 => Ok(ContentType::FileHeader),
108        1 => Ok(ContentType::CompressionHeader),
109        2 => Ok(ContentType::SliceHeader),
110        3 => Ok(ContentType::Reserved),
111        4 => Ok(ContentType::ExternalData),
112        5 => Ok(ContentType::CoreData),
113        _ => Err(io::Error::new(
114            io::ErrorKind::InvalidData,
115            "invalid content type",
116        )),
117    }
118}
119
120fn validate_content_type(actual: ContentType) -> io::Result<()> {
121    const EXPECTED: ContentType = ContentType::FileHeader;
122
123    if actual == EXPECTED {
124        Ok(())
125    } else {
126        Err(io::Error::new(
127            io::ErrorKind::InvalidData,
128            format!("invalid block content type: expected {EXPECTED:?}, got {actual:?}"),
129        ))
130    }
131}