mod attr;
mod builder;
mod header;
mod meta;
mod name;
mod options;
mod read;
mod reference;
mod write;
pub use self::{
attr::*,
builder::{EntryBuilder, SolidEntryBuilder},
header::*,
meta::*,
name::*,
options::*,
reference::*,
};
pub(crate) use self::{private::*, read::*, write::*};
use crate::{
Duration,
chunk::{
Chunk, ChunkExt, ChunkReader, ChunkType, MIN_CHUNK_BYTES_SIZE, RawChunk, chunk_data_split,
},
io::ChainReader,
util::slice::skip_while,
};
use std::{
borrow::Cow,
collections::VecDeque,
io::{self, Read, Write},
};
mod private {
use super::*;
pub trait SealedEntryExt {
fn into_chunks(self) -> Vec<RawChunk>;
fn write_in<W: Write>(&self, writer: &mut W) -> io::Result<usize>;
}
}
pub trait Entry: SealedEntryExt {}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub(crate) struct RawEntry<T = Vec<u8>>(pub(crate) Vec<RawChunk<T>>);
#[inline]
fn chunks_write_in<W: Write>(
chunks: impl Iterator<Item = impl Chunk>,
writer: &mut W,
) -> io::Result<usize> {
let mut total = 0;
for chunk in chunks {
total += chunk.write_chunk_in(writer)?;
}
Ok(total)
}
impl<T> SealedEntryExt for RawEntry<T>
where
RawChunk<T>: Chunk + Into<RawChunk>,
{
#[inline]
fn into_chunks(self) -> Vec<RawChunk> {
self.0.into_iter().map(Into::into).collect()
}
#[inline]
fn write_in<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
chunks_write_in(self.0.iter(), writer)
}
}
impl<T> Entry for RawEntry<T> where RawEntry<T>: SealedEntryExt {}
impl<'a> From<RawEntry<Cow<'a, [u8]>>> for RawEntry<Vec<u8>> {
#[inline]
fn from(value: RawEntry<Cow<'a, [u8]>>) -> Self {
Self(value.0.into_iter().map(Into::into).collect())
}
}
impl<'a> From<RawEntry<&'a [u8]>> for RawEntry<Vec<u8>> {
#[inline]
fn from(value: RawEntry<&'a [u8]>) -> Self {
Self(value.0.into_iter().map(Into::into).collect())
}
}
impl From<RawEntry<Vec<u8>>> for RawEntry<Cow<'_, [u8]>> {
#[inline]
fn from(value: RawEntry<Vec<u8>>) -> Self {
Self(value.0.into_iter().map(Into::into).collect())
}
}
impl<'a> From<RawEntry<&'a [u8]>> for RawEntry<Cow<'a, [u8]>> {
#[inline]
fn from(value: RawEntry<&'a [u8]>) -> Self {
Self(value.0.into_iter().map(Into::into).collect())
}
}
type InternalIterMap<'r, T> = std::iter::Map<std::slice::Iter<'r, T>, fn(&T) -> &[u8]>;
pub struct EntryDataReader<'r>(EntryReader<ChainReader<std::vec::IntoIter<&'r [u8]>, &'r [u8]>>);
impl<'r> Read for EntryDataReader<'r> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
#[cfg(feature = "unstable-async")]
impl<'r> futures_io::AsyncRead for EntryDataReader<'r> {
#[inline]
fn poll_read(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<io::Result<usize>> {
std::task::Poll::Ready(self.get_mut().read(buf))
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum ReadEntry<T = Vec<u8>> {
Solid(SolidEntry<T>),
Normal(NormalEntry<T>),
}
impl<T> SealedEntryExt for ReadEntry<T>
where
NormalEntry<T>: SealedEntryExt,
SolidEntry<T>: SealedEntryExt,
{
#[inline]
fn into_chunks(self) -> Vec<RawChunk> {
match self {
Self::Normal(r) => r.into_chunks(),
Self::Solid(s) => s.into_chunks(),
}
}
#[inline]
fn write_in<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
match self {
ReadEntry::Normal(r) => r.write_in(writer),
ReadEntry::Solid(s) => s.write_in(writer),
}
}
}
impl<T> Entry for ReadEntry<T> where ReadEntry<T>: SealedEntryExt {}
impl<T> TryFrom<RawEntry<T>> for ReadEntry<T>
where
RawChunk<T>: Chunk,
{
type Error = io::Error;
#[inline]
fn try_from(entry: RawEntry<T>) -> Result<Self, Self::Error> {
if let Some(first_chunk) = entry.0.first() {
match first_chunk.ty {
ChunkType::SHED => Ok(Self::Solid(SolidEntry::try_from(entry)?)),
ChunkType::FHED => Ok(Self::Normal(NormalEntry::try_from(entry)?)),
_ => Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid entry")),
}
} else {
Err(io::Error::new(io::ErrorKind::InvalidData, "Empty entry"))
}
}
}
impl<T> From<NormalEntry<T>> for ReadEntry<T> {
#[inline]
fn from(value: NormalEntry<T>) -> Self {
Self::Normal(value)
}
}
impl<T> From<SolidEntry<T>> for ReadEntry<T> {
#[inline]
fn from(value: SolidEntry<T>) -> Self {
Self::Solid(value)
}
}
impl<'a> From<ReadEntry<Cow<'a, [u8]>>> for ReadEntry<Vec<u8>> {
#[inline]
fn from(value: ReadEntry<Cow<'a, [u8]>>) -> Self {
match value {
ReadEntry::Solid(s) => Self::Solid(s.into()),
ReadEntry::Normal(r) => Self::Normal(r.into()),
}
}
}
impl<'a> From<ReadEntry<&'a [u8]>> for ReadEntry<Vec<u8>> {
#[inline]
fn from(value: ReadEntry<&'a [u8]>) -> Self {
match value {
ReadEntry::Solid(s) => Self::Solid(s.into()),
ReadEntry::Normal(r) => Self::Normal(r.into()),
}
}
}
impl From<ReadEntry<Vec<u8>>> for ReadEntry<Cow<'_, [u8]>> {
#[inline]
fn from(value: ReadEntry<Vec<u8>>) -> Self {
match value {
ReadEntry::Solid(s) => Self::Solid(s.into()),
ReadEntry::Normal(r) => Self::Normal(r.into()),
}
}
}
impl<'a> From<ReadEntry<&'a [u8]>> for ReadEntry<Cow<'a, [u8]>> {
#[inline]
fn from(value: ReadEntry<&'a [u8]>) -> Self {
match value {
ReadEntry::Solid(s) => Self::Solid(s.into()),
ReadEntry::Normal(r) => Self::Normal(r.into()),
}
}
}
pub(crate) struct EntryIterator<'s, T>(EntryReader<ChainReader<InternalIterMap<'s, T>, &'s [u8]>>);
#[inline]
fn read_next_normal_entry_from_stream<R: Read>(reader: &mut R) -> Option<io::Result<NormalEntry>> {
let mut chunk_reader = ChunkReader::new(reader, None);
let mut chunks = Vec::new();
loop {
let chunk = chunk_reader.read_chunk();
match chunk {
Ok(chunk) => match chunk.ty {
ChunkType::FEND => {
chunks.push(chunk);
break;
}
_ => chunks.push(chunk),
},
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
return if chunks.is_empty() {
None
} else {
Some(Err(e))
};
}
Err(e) => return Some(Err(e)),
}
}
Some(RawEntry(chunks).try_into())
}
impl<T> Iterator for EntryIterator<'_, T> {
type Item = io::Result<NormalEntry>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
read_next_normal_entry_from_stream(&mut self.0)
}
}
type BytesCursor = io::Cursor<Vec<u8>>;
pub(crate) struct SolidIntoEntries(
EntryReader<ChainReader<std::vec::IntoIter<BytesCursor>, BytesCursor>>,
);
impl Iterator for SolidIntoEntries {
type Item = io::Result<NormalEntry>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
read_next_normal_entry_from_stream(&mut self.0)
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct SolidEntry<T = Vec<u8>> {
header: SolidHeader,
phsf: Option<String>,
data: Vec<T>,
extra: Vec<RawChunk<T>>,
}
impl<T> SolidEntry<T>
where
RawChunk<T>: Chunk,
T: AsRef<[u8]>,
{
#[inline]
fn chunks_write_in<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
let mut total = 0;
total += (ChunkType::SHED, self.header.to_bytes()).write_chunk_in(writer)?;
for extra_chunk in &self.extra {
total += extra_chunk.write_chunk_in(writer)?;
}
if let Some(phsf) = &self.phsf {
total += (ChunkType::PHSF, phsf.as_bytes()).write_chunk_in(writer)?;
}
for data in &self.data {
total += (ChunkType::SDAT, data).write_chunk_in(writer)?;
}
total += (ChunkType::SEND, []).write_chunk_in(writer)?;
Ok(total)
}
}
impl<T> SealedEntryExt for SolidEntry<T>
where
T: AsRef<[u8]>,
RawChunk<T>: Chunk + Into<RawChunk>,
{
fn into_chunks(self) -> Vec<RawChunk> {
let mut chunks = vec![];
chunks.push(RawChunk::from_data(ChunkType::SHED, self.header.to_bytes()));
chunks.extend(self.extra.into_iter().map(Into::into));
if let Some(phsf) = self.phsf {
chunks.push(RawChunk::from_data(ChunkType::PHSF, phsf.into_bytes()));
}
for data in self.data {
chunks.push(RawChunk::from((ChunkType::SDAT, data)).into());
}
chunks.push(RawChunk::from_data(ChunkType::SEND, Vec::new()));
chunks
}
#[inline]
fn write_in<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
self.chunks_write_in(writer)
}
}
impl<T> Entry for SolidEntry<T> where SolidEntry<T>: SealedEntryExt {}
impl<T> SolidEntry<T> {
#[inline]
pub fn header(&self) -> &SolidHeader {
&self.header
}
#[inline]
pub const fn compression(&self) -> Compression {
self.header.compression
}
#[inline]
pub const fn encryption(&self) -> Encryption {
self.header.encryption
}
#[inline]
pub const fn cipher_mode(&self) -> CipherMode {
self.header.cipher_mode
}
#[inline]
pub fn extra_chunks(&self) -> &[RawChunk<T>] {
&self.extra
}
}
impl<T: AsRef<[u8]>> SolidEntry<T> {
#[inline]
pub fn entries(
&self,
password: Option<&[u8]>,
) -> io::Result<impl Iterator<Item = io::Result<NormalEntry>> + '_> {
let reader = decrypt_reader(
ChainReader::new(self.data.iter().map(AsRef::as_ref as fn(&T) -> &[u8])),
self.header.encryption,
self.header.cipher_mode,
self.phsf.as_deref(),
password,
)?;
let reader = decompress_reader(reader, self.header.compression)?;
Ok(EntryIterator(EntryReader(reader)))
}
}
impl<T> SolidEntry<T>
where
T: Into<Vec<u8>>,
{
#[inline]
pub(crate) fn into_entries(self, password: Option<&[u8]>) -> io::Result<SolidIntoEntries> {
let bufs = self
.data
.into_iter()
.map(|v| io::Cursor::new(v.into()))
.collect::<Vec<_>>();
let chain = ChainReader::new(bufs);
let reader = decrypt_reader(
chain,
self.header.encryption,
self.header.cipher_mode,
self.phsf.as_deref(),
password,
)?;
let reader = decompress_reader(reader, self.header.compression)?;
Ok(SolidIntoEntries(EntryReader(reader)))
}
}
impl<'a> From<SolidEntry<Cow<'a, [u8]>>> for SolidEntry<Vec<u8>> {
#[inline]
fn from(value: SolidEntry<Cow<'a, [u8]>>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
data: value.data.into_iter().map(Into::into).collect(),
extra: value.extra.into_iter().map(Into::into).collect(),
}
}
}
impl<'a> From<SolidEntry<&'a [u8]>> for SolidEntry<Vec<u8>> {
#[inline]
fn from(value: SolidEntry<&'a [u8]>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
data: value.data.into_iter().map(Into::into).collect(),
extra: value.extra.into_iter().map(Into::into).collect(),
}
}
}
impl<'a> From<SolidEntry<&'a [u8]>> for SolidEntry<Cow<'a, [u8]>> {
#[inline]
fn from(value: SolidEntry<&'a [u8]>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
data: value.data.into_iter().map(Into::into).collect(),
extra: value.extra.into_iter().map(Into::into).collect(),
}
}
}
impl From<SolidEntry<Vec<u8>>> for SolidEntry<Cow<'_, [u8]>> {
#[inline]
fn from(value: SolidEntry<Vec<u8>>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
data: value.data.into_iter().map(Into::into).collect(),
extra: value.extra.into_iter().map(Into::into).collect(),
}
}
}
impl<T> TryFrom<RawEntry<T>> for SolidEntry<T>
where
RawChunk<T>: Chunk,
{
type Error = io::Error;
#[inline]
fn try_from(entry: RawEntry<T>) -> Result<Self, Self::Error> {
let mut chunks = entry.0.into_iter();
let header = if let Some(first_chunk) = chunks.next() {
if first_chunk.ty != ChunkType::SHED {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Expected {} chunk, but {} chunk was found",
ChunkType::SHED,
first_chunk.ty
),
));
} else {
SolidHeader::try_from(first_chunk.data())?
}
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("{} chunk not found", ChunkType::SHED),
));
};
let mut extra = vec![];
let mut data = vec![];
let mut phsf = None;
for chunk in chunks {
match chunk.ty() {
ChunkType::SEND => break,
ChunkType::SDAT => data.push(chunk.data),
ChunkType::PHSF => {
phsf = Some(
String::from_utf8(chunk.data().into())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
)
}
_ => {
if chunk.ty().is_critical() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unknown critical chunk type: {}", chunk.ty()),
));
}
extra.push(chunk);
}
}
}
Ok(Self {
header,
phsf,
data,
extra,
})
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct NormalEntry<T = Vec<u8>> {
pub(crate) header: EntryHeader,
pub(crate) phsf: Option<String>,
pub(crate) extra: Vec<RawChunk<T>>,
pub(crate) data: Vec<T>,
pub(crate) metadata: Metadata,
pub(crate) xattrs: Vec<ExtendedAttribute>,
}
impl<T> TryFrom<RawEntry<T>> for NormalEntry<T>
where
RawChunk<T>: Chunk,
{
type Error = io::Error;
#[inline]
fn try_from(entry: RawEntry<T>) -> Result<Self, Self::Error> {
let mut chunks = entry.0.into_iter();
let header = if let Some(first_chunk) = chunks.next() {
if first_chunk.ty != ChunkType::FHED {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Expected {} chunk, but {} chunk was found",
ChunkType::FHED,
first_chunk.ty
),
));
}
EntryHeader::try_from(first_chunk.data())?
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("{} chunk not found", ChunkType::FHED),
));
};
if header.major != 0 || header.minor != 0 {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
format!(
"entry version {}.{} is not supported.",
header.major, header.minor
),
));
}
let mut compressed_size = 0;
let mut extra = vec![];
let mut data = vec![];
let mut xattrs = vec![];
let mut size = None;
let mut phsf = None;
let mut ctime = None;
let mut mtime = None;
let mut atime = None;
let mut ctime_ns = None;
let mut mtime_ns = None;
let mut atime_ns = None;
let mut permission = None;
let mut link_target_type = None;
for chunk in chunks {
match chunk.ty {
ChunkType::FEND => break,
ChunkType::PHSF => {
phsf = Some(
String::from_utf8(chunk.data().into())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
);
}
ChunkType::FDAT => {
compressed_size += chunk.data().len();
data.push(chunk.data);
}
ChunkType::fSIZ => size = Some(u128_from_be_bytes_last(chunk.data())),
ChunkType::cTIM => ctime = Some(timestamp(chunk.data())?),
ChunkType::mTIM => mtime = Some(timestamp(chunk.data())?),
ChunkType::aTIM => atime = Some(timestamp(chunk.data())?),
ChunkType::cTNS => ctime_ns = Some(nanos(chunk.data())?),
ChunkType::mTNS => mtime_ns = Some(nanos(chunk.data())?),
ChunkType::aTNS => atime_ns = Some(nanos(chunk.data())?),
ChunkType::fPRM => permission = Some(Permission::try_from_bytes(chunk.data())?),
ChunkType::xATR => xattrs.push(ExtendedAttribute::try_from_bytes(chunk.data())?),
ChunkType::fLTP => link_target_type = LinkTargetType::try_from_bytes(chunk.data())?,
_ => {
if chunk.ty.is_critical() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unknown critical chunk type: {}", chunk.ty),
));
}
extra.push(chunk);
}
}
}
let ctime = ctime.map(|t| t + Duration::nanoseconds(ctime_ns.unwrap_or(0) as _));
let mtime = mtime.map(|t| t + Duration::nanoseconds(mtime_ns.unwrap_or(0) as _));
let atime = atime.map(|t| t + Duration::nanoseconds(atime_ns.unwrap_or(0) as _));
Ok(Self {
header,
phsf,
extra,
metadata: Metadata {
raw_file_size: size,
compressed_size,
created: ctime,
modified: mtime,
accessed: atime,
permission,
link_target_type,
},
data,
xattrs,
})
}
}
impl<T> NormalEntry<T>
where
RawChunk<T>: Chunk,
T: AsRef<[u8]>,
{
#[inline]
fn chunks_write_in<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
let mut total = 0;
let Metadata {
raw_file_size,
compressed_size: _,
created,
modified,
accessed,
permission,
link_target_type,
} = &self.metadata;
total += (ChunkType::FHED, self.header.to_bytes()).write_chunk_in(writer)?;
for ex in &self.extra {
total += ex.write_chunk_in(writer)?;
}
if let Some(raw_file_size) = raw_file_size {
total += (
ChunkType::fSIZ,
skip_while(&raw_file_size.to_be_bytes(), |i| *i == 0),
)
.write_chunk_in(writer)?;
}
if let Some(p) = &self.phsf {
total += (ChunkType::PHSF, p.as_bytes()).write_chunk_in(writer)?;
}
for data_chunk in &self.data {
total += (ChunkType::FDAT, data_chunk).write_chunk_in(writer)?;
}
if let Some(c) = created {
total += (ChunkType::cTIM, c.whole_seconds().to_be_bytes()).write_chunk_in(writer)?;
if c.subsec_nanoseconds() != 0 {
total += (ChunkType::cTNS, c.subsec_nanoseconds().to_be_bytes())
.write_chunk_in(writer)?;
}
}
if let Some(d) = modified {
total += (ChunkType::mTIM, d.whole_seconds().to_be_bytes()).write_chunk_in(writer)?;
if d.subsec_nanoseconds() != 0 {
total += (ChunkType::mTNS, d.subsec_nanoseconds().to_be_bytes())
.write_chunk_in(writer)?;
}
}
if let Some(a) = accessed {
total += (ChunkType::aTIM, a.whole_seconds().to_be_bytes()).write_chunk_in(writer)?;
if a.subsec_nanoseconds() != 0 {
total += (ChunkType::aTNS, a.subsec_nanoseconds().to_be_bytes())
.write_chunk_in(writer)?;
}
}
if let Some(p) = permission {
total += (ChunkType::fPRM, p.to_bytes()).write_chunk_in(writer)?;
}
if let Some(ltp) = link_target_type {
total += (ChunkType::fLTP, ltp.to_bytes()).write_chunk_in(writer)?;
}
for xattr in &self.xattrs {
total += (ChunkType::xATR, xattr.to_bytes()).write_chunk_in(writer)?;
}
total += (ChunkType::FEND, []).write_chunk_in(writer)?;
Ok(total)
}
}
impl<T> SealedEntryExt for NormalEntry<T>
where
T: AsRef<[u8]>,
RawChunk<T>: Chunk + Into<RawChunk>,
{
fn into_chunks(self) -> Vec<RawChunk> {
let Metadata {
raw_file_size,
compressed_size: _,
created,
modified,
accessed,
permission,
link_target_type,
} = self.metadata;
let mut vec = Vec::new();
vec.push(RawChunk::from_data(ChunkType::FHED, self.header.to_bytes()));
vec.extend(self.extra.into_iter().map(Into::into));
if let Some(raw_file_size) = raw_file_size {
vec.push(RawChunk::from_data(
ChunkType::fSIZ,
skip_while(&raw_file_size.to_be_bytes(), |i| *i == 0),
));
}
if let Some(p) = self.phsf {
vec.push(RawChunk::from_data(ChunkType::PHSF, p.into_bytes()));
}
for data_chunk in self.data {
vec.push(RawChunk::from((ChunkType::FDAT, data_chunk)).into());
}
if let Some(c) = created {
vec.push(RawChunk::from_data(
ChunkType::cTIM,
c.whole_seconds().to_be_bytes(),
));
if c.subsec_nanoseconds() != 0 {
vec.push(RawChunk::from_data(
ChunkType::cTNS,
c.subsec_nanoseconds().to_be_bytes(),
));
}
}
if let Some(d) = modified {
vec.push(RawChunk::from_data(
ChunkType::mTIM,
d.whole_seconds().to_be_bytes(),
));
if d.subsec_nanoseconds() != 0 {
vec.push(RawChunk::from_data(
ChunkType::mTNS,
d.subsec_nanoseconds().to_be_bytes(),
));
}
}
if let Some(a) = accessed {
vec.push(RawChunk::from_data(
ChunkType::aTIM,
a.whole_seconds().to_be_bytes(),
));
if a.subsec_nanoseconds() != 0 {
vec.push(RawChunk::from_data(
ChunkType::aTNS,
a.subsec_nanoseconds().to_be_bytes(),
));
}
}
if let Some(p) = permission {
vec.push(RawChunk::from_data(ChunkType::fPRM, p.to_bytes()));
}
if let Some(ltp) = link_target_type {
vec.push(RawChunk::from_data(ChunkType::fLTP, ltp.to_bytes()));
}
for xattr in self.xattrs {
vec.push(RawChunk::from_data(ChunkType::xATR, xattr.to_bytes()));
}
vec.push(RawChunk::from_data(ChunkType::FEND, Vec::new()));
vec
}
#[inline]
fn write_in<W: Write>(&self, writer: &mut W) -> io::Result<usize> {
self.chunks_write_in(writer)
}
}
impl<T> Entry for NormalEntry<T> where NormalEntry<T>: SealedEntryExt {}
impl<T> NormalEntry<T> {
#[inline]
pub fn header(&self) -> &EntryHeader {
&self.header
}
#[inline]
pub fn name(&self) -> &EntryName {
&self.header.name
}
#[inline]
pub const fn data_kind(&self) -> DataKind {
self.header.data_kind
}
#[inline]
pub const fn compression(&self) -> Compression {
self.header.compression
}
#[inline]
pub const fn encryption(&self) -> Encryption {
self.header.encryption
}
#[inline]
pub const fn cipher_mode(&self) -> CipherMode {
self.header.cipher_mode
}
#[inline]
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
#[inline]
pub fn xattrs(&self) -> &[ExtendedAttribute] {
&self.xattrs
}
#[inline]
pub fn extra_chunks(&self) -> &[RawChunk<T>] {
&self.extra
}
#[inline]
pub fn with_metadata(mut self, mut metadata: Metadata) -> Self {
metadata.compressed_size = self.metadata.compressed_size;
metadata.raw_file_size = self.metadata.raw_file_size;
self.metadata = metadata;
self
}
#[inline]
pub fn with_xattrs(mut self, xattrs: impl Into<Vec<ExtendedAttribute>>) -> Self {
self.xattrs = xattrs.into();
self
}
#[inline]
pub fn with_name(mut self, name: EntryName) -> Self {
self.header = self.header.with_name(name);
self
}
}
impl<T: Clone> NormalEntry<T> {
#[inline]
pub fn with_extra_chunks(mut self, chunks: impl Into<Vec<RawChunk<T>>>) -> Self {
self.extra = chunks.into();
self
}
}
impl<T: AsRef<[u8]>> NormalEntry<T> {
#[inline]
pub fn reader(&self, option: impl ReadOption) -> io::Result<EntryDataReader<'_>> {
let raw_data_reader = ChainReader::new(
self.data
.iter()
.map(AsRef::as_ref as fn(&T) -> &[u8])
.collect::<Vec<_>>(),
);
let decrypt_reader = decrypt_reader(
raw_data_reader,
self.header.encryption,
self.header.cipher_mode,
self.phsf.as_deref(),
option.password(),
)?;
let reader = decompress_reader(decrypt_reader, self.header.compression)?;
Ok(EntryDataReader(EntryReader(reader)))
}
}
impl<'a> From<NormalEntry<Cow<'a, [u8]>>> for NormalEntry<Vec<u8>> {
#[inline]
fn from(value: NormalEntry<Cow<'a, [u8]>>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
extra: value.extra.into_iter().map(Into::into).collect(),
data: value.data.into_iter().map(Into::into).collect(),
metadata: value.metadata,
xattrs: value.xattrs,
}
}
}
impl<'a> From<NormalEntry<&'a [u8]>> for NormalEntry<Vec<u8>> {
#[inline]
fn from(value: NormalEntry<&'a [u8]>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
extra: value.extra.into_iter().map(Into::into).collect(),
data: value.data.into_iter().map(Into::into).collect(),
metadata: value.metadata,
xattrs: value.xattrs,
}
}
}
impl From<NormalEntry<Vec<u8>>> for NormalEntry<Cow<'_, [u8]>> {
#[inline]
fn from(value: NormalEntry<Vec<u8>>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
extra: value.extra.into_iter().map(Into::into).collect(),
data: value.data.into_iter().map(Into::into).collect(),
metadata: value.metadata,
xattrs: value.xattrs,
}
}
}
impl<'a> From<NormalEntry<&'a [u8]>> for NormalEntry<Cow<'a, [u8]>> {
#[inline]
fn from(value: NormalEntry<&'a [u8]>) -> Self {
Self {
header: value.header,
phsf: value.phsf,
extra: value.extra.into_iter().map(Into::into).collect(),
data: value.data.into_iter().map(Into::into).collect(),
metadata: value.metadata,
xattrs: value.xattrs,
}
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct EntryPart<T = Vec<u8>>(pub(crate) Vec<RawChunk<T>>);
impl<T> EntryPart<T>
where
RawChunk<T>: Chunk,
{
#[inline]
pub fn bytes_len(&self) -> usize {
self.0.iter().map(|chunk| chunk.bytes_len()).sum()
}
#[doc(hidden)]
#[inline]
pub fn as_ref(&self) -> EntryPart<&[u8]> {
EntryPart(self.0.iter().map(|it| it.as_ref()).collect())
}
}
impl EntryPart<&[u8]> {
#[inline]
pub fn try_split(self, max_bytes_len: usize) -> Result<(Self, Option<Self>), Self> {
if self.bytes_len() <= max_bytes_len {
return Ok((self, None));
}
let mut remaining = VecDeque::from(self.0);
let mut first = Vec::new();
let mut total_size = 0;
while let Some(chunk) = remaining.pop_front() {
if max_bytes_len < total_size + chunk.bytes_len() {
if chunk.is_stream_chunk() && total_size + MIN_CHUNK_BYTES_SIZE < max_bytes_len {
let available_bytes_len = max_bytes_len - total_size;
let chunk_split_index = available_bytes_len - MIN_CHUNK_BYTES_SIZE;
let (x, y) = chunk_data_split(chunk.ty, chunk.data, chunk_split_index);
first.push(x);
if let Some(y) = y {
remaining.push_front(y);
}
} else {
remaining.push_front(chunk);
}
break;
}
total_size += chunk.bytes_len();
first.push(chunk);
}
if first.is_empty() {
return Err(Self(Vec::from(remaining)));
}
Ok((Self(first), Some(Self(Vec::from(remaining)))))
}
}
#[doc(hidden)]
impl<T: SealedEntryExt> From<T> for EntryPart {
#[inline]
fn from(value: T) -> Self {
Self(value.into_chunks())
}
}
#[inline]
fn timestamp(bytes: &[u8]) -> io::Result<Duration> {
Ok(Duration::seconds(i64::from_be_bytes(
bytes
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
)))
}
#[inline]
fn nanos(bytes: &[u8]) -> io::Result<u32> {
let v = u32::from_be_bytes(
bytes
.try_into()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
);
if v >= 1_000_000_000 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid nanoseconds",
));
}
Ok(v)
}
#[inline]
fn u128_from_be_bytes_last(bytes: &[u8]) -> u128 {
const BUF_LEN: usize = std::mem::size_of::<u128>();
let mut buf = [0u8; BUF_LEN];
let min = BUF_LEN.min(bytes.len());
buf[BUF_LEN - min..].copy_from_slice(&bytes[bytes.len() - min..]);
u128::from_be_bytes(buf)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::LazyLock;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
#[test]
fn entry_trait_bounds() {
fn check_impl<T: Entry>() {}
check_impl::<NormalEntry<Vec<u8>>>();
check_impl::<NormalEntry<Cow<[u8]>>>();
check_impl::<NormalEntry<&[u8]>>();
check_impl::<NormalEntry<[u8; 1]>>();
check_impl::<SolidEntry<Vec<u8>>>();
check_impl::<SolidEntry<Cow<[u8]>>>();
check_impl::<SolidEntry<&[u8]>>();
check_impl::<SolidEntry<[u8; 1]>>();
check_impl::<ReadEntry<Vec<u8>>>();
check_impl::<ReadEntry<Cow<[u8]>>>();
check_impl::<ReadEntry<&[u8]>>();
check_impl::<ReadEntry<[u8; 1]>>();
check_impl::<RawEntry<Vec<u8>>>();
check_impl::<RawEntry<Cow<[u8]>>>();
check_impl::<RawEntry<&[u8]>>();
check_impl::<RawEntry<[u8; 1]>>();
}
#[test]
fn u128_from_be_bytes() {
assert_eq!(0, u128_from_be_bytes_last(&[]));
assert_eq!(1, u128_from_be_bytes_last(&[1]));
assert_eq!(
u32::MAX as u128,
u128_from_be_bytes_last(&u32::MAX.to_be_bytes())
);
assert_eq!(u128::MAX, u128_from_be_bytes_last(&u128::MAX.to_be_bytes()));
}
static TEST_ENTRY: LazyLock<RawEntry> = LazyLock::new(|| {
RawEntry(vec![
RawChunk::from_data(
ChunkType::FHED,
vec![0, 0, 0, 0, 0, 1, 116, 101, 115, 116, 46, 116, 120, 116],
),
RawChunk::from_data(ChunkType::FDAT, vec![116, 101, 120, 116]),
RawChunk::from_data(ChunkType::FEND, vec![]),
])
});
mod entry_part_try_split {
use super::*;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
#[test]
fn split_zero() {
let entry = TEST_ENTRY.clone();
let part = EntryPart::from(entry.clone());
assert_eq!(
part.as_ref().try_split(0),
Err(EntryPart::from(entry).as_ref())
)
}
#[test]
fn bounds_check_spans_unsplittable_chunks() {
assert_eq!(26, TEST_ENTRY.0.first().unwrap().bytes_len());
let entry = TEST_ENTRY.clone();
let part = EntryPart::from(entry.clone());
assert_eq!(
part.as_ref().try_split(25),
Err(EntryPart::from(entry).as_ref())
)
}
#[test]
fn bounds_check_just_end_unsplittable_chunks() {
assert_eq!(26, TEST_ENTRY.0.first().unwrap().bytes_len());
let entry = TEST_ENTRY.clone();
let part = EntryPart::from(entry.clone());
assert_eq!(
part.as_ref().try_split(26),
Ok((
EntryPart(vec![RawChunk::from_slice(
ChunkType::FHED,
&[0, 0, 0, 0, 0, 1, 116, 101, 115, 116, 46, 116, 120, 116],
)]),
Some(EntryPart(vec![
RawChunk::from_slice(ChunkType::FDAT, &[116, 101, 120, 116]),
RawChunk::from_slice(ChunkType::FEND, &[]),
]))
))
)
}
#[test]
fn spans_splittable_chunks_below_minimum_chunk_size() {
let entry = TEST_ENTRY.clone();
let part = EntryPart::from(entry.clone());
assert_eq!(
part.as_ref().try_split(27),
Ok((
EntryPart(vec![RawChunk::from_slice(
ChunkType::FHED,
&[0, 0, 0, 0, 0, 1, 116, 101, 115, 116, 46, 116, 120, 116],
)]),
Some(EntryPart(vec![
RawChunk::from_slice(ChunkType::FDAT, &[116, 101, 120, 116]),
RawChunk::from_slice(ChunkType::FEND, &[]),
]))
))
)
}
#[test]
fn spans_splittable_chunks() {
let entry = TEST_ENTRY.clone();
let part = EntryPart::from(entry.clone());
assert_eq!(
part.as_ref().try_split(39),
Ok((
EntryPart(vec![
RawChunk::from_slice(
ChunkType::FHED,
&[0, 0, 0, 0, 0, 1, 116, 101, 115, 116, 46, 116, 120, 116],
),
RawChunk::from_slice(ChunkType::FDAT, &[116]),
]),
Some(EntryPart(vec![
RawChunk::from_slice(ChunkType::FDAT, &[101, 120, 116]),
RawChunk::from_slice(ChunkType::FEND, &[]),
]))
)),
)
}
#[test]
fn spans_just_end_of_splittable_chunks() {
let entry = TEST_ENTRY.clone();
let part = EntryPart::from(entry.clone());
assert_eq!(
part.as_ref().try_split(42),
Ok((
EntryPart(vec![
RawChunk::from_slice(
ChunkType::FHED,
&[0, 0, 0, 0, 0, 1, 116, 101, 115, 116, 46, 116, 120, 116],
),
RawChunk::from_slice(ChunkType::FDAT, &[116, 101, 120, 116]),
]),
Some(EntryPart(vec![RawChunk::from_slice(ChunkType::FEND, &[])]))
))
);
}
}
#[test]
fn normal_entry_with_name_updates_path() {
let entry = EntryBuilder::new_dir("original".into()).build().unwrap();
let _ = entry.header().path(); let renamed = entry.with_name("new".into());
assert_eq!(renamed.header().path().as_str(), "new");
assert_eq!(renamed.name().as_str(), "new");
}
#[test]
fn reject_unknown_critical_chunk_in_normal_entry() {
let unknown_critical = RawChunk::from_data(
unsafe { ChunkType::from_unchecked(*b"XUNK") },
vec![1, 2, 3],
);
let fhed = RawChunk::from_data(ChunkType::FHED, vec![0, 0, 0, 0, 0, 0]);
let fend = RawChunk::from_data(ChunkType::FEND, vec![]);
let raw_entry = RawEntry(vec![fhed, unknown_critical, fend]);
let result = NormalEntry::try_from(raw_entry);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
assert!(err.to_string().contains("critical"));
}
#[test]
fn reject_unknown_critical_chunk_in_solid_entry() {
let unknown_critical = RawChunk::from_data(
unsafe { ChunkType::from_unchecked(*b"XUNK") },
vec![1, 2, 3],
);
let shed = RawChunk::from_data(ChunkType::SHED, vec![0, 0, 0, 0, 0]);
let send = RawChunk::from_data(ChunkType::SEND, vec![]);
let raw_entry = RawEntry(vec![shed, unknown_critical, send]);
let result = SolidEntry::try_from(raw_entry);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
assert!(err.to_string().contains("critical"));
}
#[test]
fn accept_unknown_ancillary_chunk_in_normal_entry() {
let unknown_ancillary = RawChunk::from_data(
unsafe { ChunkType::from_unchecked(*b"xUNK") },
vec![1, 2, 3],
);
let fhed = RawChunk::from_data(ChunkType::FHED, vec![0, 0, 0, 0, 0, 0]);
let fend = RawChunk::from_data(ChunkType::FEND, vec![]);
let raw_entry = RawEntry(vec![fhed, unknown_ancillary, fend]);
let result = NormalEntry::try_from(raw_entry);
assert!(result.is_ok());
let entry = result.unwrap();
assert_eq!(entry.extra.len(), 1);
}
#[test]
fn accept_unknown_ancillary_chunk_in_solid_entry() {
let unknown_ancillary = RawChunk::from_data(
unsafe { ChunkType::from_unchecked(*b"xUNK") },
vec![1, 2, 3],
);
let shed = RawChunk::from_data(ChunkType::SHED, vec![0, 0, 0, 0, 0]);
let send = RawChunk::from_data(ChunkType::SEND, vec![]);
let raw_entry = RawEntry(vec![shed, unknown_ancillary, send]);
let result = SolidEntry::try_from(raw_entry);
assert!(result.is_ok());
let entry = result.unwrap();
assert_eq!(entry.extra.len(), 1);
}
#[test]
fn solid_entry_stops_parsing_at_send() {
let shed = RawChunk::from_data(ChunkType::SHED, vec![0, 0, 0, 0, 0]);
let send = RawChunk::from_data(ChunkType::SEND, vec![]);
let trailing_critical = RawChunk::from_data(
unsafe { ChunkType::from_unchecked(*b"XUNK") },
vec![4, 5, 6],
);
let raw_entry = RawEntry(vec![shed, send, trailing_critical]);
let result = SolidEntry::try_from(raw_entry);
assert!(result.is_ok());
let entry = result.unwrap();
assert_eq!(entry.extra.len(), 0);
}
}