#![cfg(feature = "futures")]
use super::{ZstdFrame, ZstdFrameSize, ZstdSeekTable};
use futures::io::{AsyncReadExt as _, AsyncSeekExt as _};
pub async fn read_seek_table<R>(mut reader: R) -> std::io::Result<Option<ZstdSeekTable>>
where
R: Unpin + futures::AsyncRead + futures::AsyncSeek,
{
let initial_position = reader.stream_position().await?;
let seek_table_result = read_seek_table_inner(&mut reader).await;
let seek_result = reader
.seek(std::io::SeekFrom::Start(initial_position))
.await;
let seek_table = seek_table_result?;
seek_result?;
Ok(seek_table)
}
async fn read_seek_table_inner<R>(mut reader: R) -> std::io::Result<Option<ZstdSeekTable>>
where
R: Unpin + futures::AsyncRead + futures::AsyncSeek,
{
reader.seek(std::io::SeekFrom::End(-9)).await?;
let mut num_frames_bytes = [0; 4];
reader.read_exact(&mut num_frames_bytes).await?;
let mut seek_table_descriptor_bytes = [0; 1];
reader.read_exact(&mut seek_table_descriptor_bytes).await?;
let mut seekable_magic_number_bytes = [0; 4];
reader.read_exact(&mut seekable_magic_number_bytes).await?;
if seekable_magic_number_bytes != crate::SEEKABLE_FOOTER_MAGIC_BYTES {
return Ok(None);
}
let num_frames = u32::from_le_bytes(num_frames_bytes);
let [seek_table_descriptor] = seek_table_descriptor_bytes;
let has_checksum = seek_table_descriptor & 0b1000_0000 != 0;
let is_reserved_valid = seek_table_descriptor & 0b0111_1100 == 0;
if !is_reserved_valid {
return Err(std::io::Error::other(
"zstd seek table has unsupported descriptor",
));
}
let table_entry_size: u32 = if has_checksum { 12 } else { 8 };
let table_frame_size = table_entry_size
.checked_mul(num_frames)
.and_then(|size| size.checked_add(9))
.ok_or_else(|| std::io::Error::other("zstd seek table size overflowed"))?;
reader
.seek(std::io::SeekFrom::Current(-i64::from(table_frame_size) - 8))
.await?;
let mut skippable_magic_number_bytes = [0; 4];
reader.read_exact(&mut skippable_magic_number_bytes).await?;
let mut actual_table_frame_size_bytes = [0; 4];
reader
.read_exact(&mut actual_table_frame_size_bytes)
.await?;
if skippable_magic_number_bytes != crate::SKIPPABLE_HEADER_MAGIC_BYTES {
return Err(std::io::Error::other(
"zstd seek table has unsupported skippable frame magic number",
));
}
let actual_table_frame_size = u32::from_le_bytes(actual_table_frame_size_bytes);
if actual_table_frame_size != table_frame_size {
return Err(std::io::Error::other("zstd seek table size did not match"));
}
let mut table = ZstdSeekTable::empty();
let mut compressed_pos = 0;
let mut decompressed_pos = 0;
for frame_index in 0..num_frames {
let frame_index = usize::try_from(frame_index).unwrap();
let mut compressed_size_bytes = [0; 4];
reader.read_exact(&mut compressed_size_bytes).await?;
let compressed_size = u32::from_le_bytes(compressed_size_bytes);
let mut decompressed_size_bytes = [0; 4];
reader.read_exact(&mut decompressed_size_bytes).await?;
let decompressed_size = u32::from_le_bytes(decompressed_size_bytes);
if has_checksum {
reader.seek(std::io::SeekFrom::Current(4)).await?;
}
let frame = ZstdFrame {
compressed_pos,
decompressed_pos,
index: frame_index,
size: ZstdFrameSize {
compressed_size: compressed_size.into(),
decompressed_size: decompressed_size.into(),
},
};
table.insert(frame);
compressed_pos += u64::from(compressed_size);
decompressed_pos += u64::from(decompressed_size);
}
Ok(Some(table))
}