noodles_cram/async/io/reader/header/container/
block.rs1use 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 Ok(decoder)
51}
52
53pin_project! {
54 #[allow(missing_docs)]
56 #[project = DecoderProjection]
57 pub enum Decoder<R> {
58 None { #[pin] inner: Take<R> },
60 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}