mod core;
use super::dictionary::PreparedDictionary;
use super::{Compressor, EncodeError, EncoderSession, StreamConfig};
use std::io::{self, Write};
use thiserror::Error;
#[derive(Debug, Clone, Copy)]
pub struct FramingConfig {
pub container: bool,
pub central_directory: bool,
pub repeat_metadata: bool,
pub chunk_bytes: usize,
pub max_metadata_bytes: usize,
pub max_buffer_bytes: usize,
pub max_resources: u64,
pub max_chunks: u64,
}
impl Default for FramingConfig {
fn default() -> Self {
Self {
container: true,
central_directory: true,
repeat_metadata: false,
chunk_bytes: 65536,
max_metadata_bytes: 1 << 20,
max_buffer_bytes: 8 << 20,
max_resources: 10000,
max_chunks: 1000000,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DictionaryId(pub [u8; 32]);
#[derive(Debug, Clone, Copy)]
pub enum DictionaryReference {
PrefixId(DictionaryId),
SerializedId(DictionaryId),
PrefixResource(u64),
SerializedResource(u64),
PrefixChunk(u64),
}
#[derive(Debug, Default, Clone, Copy)]
pub struct ResourceOptions {
pub hidden: bool,
pub id: Option<DictionaryId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetadataKind {
Resource,
Footer,
Global,
}
#[derive(Debug, Clone, Copy)]
pub struct MetadataField<'a> {
pub code: [u8; 2],
pub value: &'a [u8],
}
#[derive(Debug, Default, Clone, Copy)]
pub enum MetadataEncoding<'a> {
#[default]
Uncompressed,
Brotli,
Shared {
dictionary: &'a PreparedDictionary,
references: &'a [DictionaryReference],
},
}
#[derive(Debug, Default, Clone, Copy)]
pub struct MetadataOptions<'a> {
pub encoding: MetadataEncoding<'a>,
pub repeated_encoding: MetadataEncoding<'a>,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FramingError {
#[error("invalid framing operation: {0}")]
Invalid(&'static str),
#[error("framing resource limit exceeded: {0}")]
Limit(&'static str),
#[error("framing size or offset overflow")]
Overflow,
#[error("resource encoding failed: {0}")]
Encode(#[from] EncodeError),
#[error("container output failed: {0}")]
Io(#[from] io::Error),
}
impl From<FramingError> for io::Error {
fn from(error: FramingError) -> Self {
match error {
FramingError::Io(error) => error,
other => Self::other(other),
}
}
}
#[derive(Debug)]
pub struct FramingFinishError<T> {
pub writer: T,
pub error: FramingError,
}
#[derive(Debug)]
pub struct FramedWriter<'c, W> {
compressor: &'c mut Compressor,
core: core::Container<W>,
}
impl Compressor {
pub fn framed_writer<W: Write>(
&mut self,
writer: W,
config: FramingConfig,
) -> Result<FramedWriter<'_, W>, FramingError> {
Ok(FramedWriter {
compressor: self,
core: core::Container::new(writer, config)?,
})
}
}
impl<W: Write> FramedWriter<'_, W> {
pub fn resource(
&mut self,
options: ResourceOptions,
stream: StreamConfig,
) -> Result<ResourceWriter<'_, 'static, W>, FramingError> {
if stream.stream_offset() != 0 {
return Err(FramingError::Invalid(
"a new resource requires a stream header; its offset must be zero",
));
}
self.core.begin()?;
let references =
if self.compressor.config().window().encoding() == super::WindowEncoding::Large {
vec![0]
} else {
Vec::new()
};
let session = self.compressor.start(stream)?;
Ok(ResourceWriter::new(
&mut self.core,
Some(session),
options,
references,
))
}
pub fn resource_with_dictionary<'a, 'd>(
&'a mut self,
options: ResourceOptions,
stream: StreamConfig,
dictionary: &'d PreparedDictionary,
references: &[DictionaryReference],
) -> Result<ResourceWriter<'a, 'd, W>, FramingError> {
if stream.stream_offset() != 0 {
return Err(FramingError::Invalid(
"a new resource requires a stream header; its offset must be zero",
));
}
let encoded = self.core.references(references)?;
self.core.begin()?;
let session = self.compressor.start_with_dictionary(dictionary, stream)?;
Ok(ResourceWriter::new(
&mut self.core,
Some(session),
options,
encoded,
))
}
pub fn uncompressed_resource(
&mut self,
options: ResourceOptions,
) -> Result<ResourceWriter<'_, 'static, W>, FramingError> {
self.core.begin()?;
Ok(ResourceWriter::new(
&mut self.core,
None,
options,
Vec::new(),
))
}
pub fn metadata(
&mut self,
kind: MetadataKind,
fields: &[MetadataField<'_>],
) -> Result<(), FramingError> {
self.metadata_with_options(kind, fields, MetadataOptions::default())
}
pub fn metadata_with_options(
&mut self,
kind: MetadataKind,
fields: &[MetadataField<'_>],
options: MetadataOptions<'_>,
) -> Result<(), FramingError> {
self.core.metadata(self.compressor, kind, fields, options)
}
pub fn repeat_metadata_fields(&mut self, codes: &[[u8; 2]]) -> Result<(), FramingError> {
self.core.repeat_metadata_fields(codes)
}
pub fn padding(&mut self, bytes: usize) -> Result<(), FramingError> {
self.core.padding(bytes)
}
pub fn flush(&mut self) -> Result<(), FramingError> {
self.core.drain()?;
self.core.writer.flush()?;
Ok(())
}
pub fn try_finish(&mut self) -> Result<(), FramingError> {
self.core.finish()
}
pub fn finish(mut self) -> Result<W, Box<FramingFinishError<Self>>> {
match self.try_finish() {
Ok(()) => Ok(self.core.writer),
Err(error) => Err(Box::new(FramingFinishError {
writer: self,
error,
})),
}
}
pub const fn get_ref(&self) -> &W {
&self.core.writer
}
pub fn get_mut(&mut self) -> &mut W {
&mut self.core.writer
}
pub const fn next_chunk_offset(&self) -> u64 {
self.core.offset()
}
pub fn into_inner(self) -> W {
self.core.writer
}
}
#[derive(Debug)]
pub struct ResourceWriter<'a, 'd, W> {
inner: core::Resource<'a, 'd, W>,
}
impl<'a, 'd, W: Write> ResourceWriter<'a, 'd, W> {
fn new(
core: &'a mut core::Container<W>,
session: Option<EncoderSession<'a, 'd>>,
options: ResourceOptions,
references: Vec<u8>,
) -> Self {
Self {
inner: core::Resource::new(core, session, options, references),
}
}
pub fn try_finish(&mut self) -> Result<(), FramingError> {
self.inner.try_finish()
}
pub fn get_mut(&mut self) -> &mut W {
self.inner.get_mut()
}
}
impl<W: Write> Write for ResourceWriter<'_, '_, W> {
fn write(&mut self, input: &[u8]) -> io::Result<usize> {
self.inner.write(input)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}