use std::any::type_name;
use std::error::Error;
use std::fmt;
use std::io::{self, Cursor, Read, Seek, Write};
#[cfg(feature = "async")]
use std::sync::{Arc, Mutex};
use crate::BoxInfo;
use crate::FourCc;
#[cfg(feature = "async")]
use crate::async_io::AsyncReadSeek;
use crate::boxes::{BoxRegistry, default_registry};
use crate::codec::{
CodecBox, CodecError, DynCodecBox, read_exact_vec_untrusted, unmarshal_any_with_context,
};
#[cfg(feature = "async")]
use crate::codec::{read_exact_vec_untrusted_async, unmarshal_any_with_context_async};
use crate::header::HeaderError;
#[cfg(feature = "async")]
use crate::walk::{
AsyncWalkFuture, AsyncWalkHandle, AsyncWalkVisitor,
walk_structure_from_box_with_registry_async, walk_structure_with_registry_async,
};
use crate::walk::{
BoxPath, PathMatch, WalkControl, WalkError, WalkHandle, walk_structure_from_box_with_registry,
walk_structure_with_registry,
};
#[cfg(feature = "async")]
use tokio::io::AsyncWrite;
#[cfg(feature = "async")]
use tokio::io::{AsyncReadExt, AsyncSeekExt};
pub struct ExtractedBox {
pub info: BoxInfo,
pub payload: Box<dyn DynCodecBox>,
}
pub fn extract_box<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<BoxInfo>, ExtractError>
where
R: Read + Seek,
{
let paths = [path];
extract_boxes(reader, parent, &paths)
}
pub fn extract_boxes<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<BoxInfo>, ExtractError>
where
R: Read + Seek,
{
let registry = default_registry();
extract_boxes_with_registry(reader, parent, paths, ®istry)
}
pub fn extract_box_with_payload<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<ExtractedBox>, ExtractError>
where
R: Read + Seek,
{
let paths = [path];
extract_boxes_with_payload(reader, parent, &paths)
}
pub fn extract_boxes_with_payload<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<ExtractedBox>, ExtractError>
where
R: Read + Seek,
{
let registry = default_registry();
extract_boxes_with_payload_with_registry(reader, parent, paths, ®istry)
}
pub fn extract_box_as<R, T>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<T>, ExtractError>
where
R: Read + Seek,
T: CodecBox + Clone + 'static,
{
let paths = [path];
extract_boxes_as(reader, parent, &paths)
}
pub fn extract_boxes_as<R, T>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<T>, ExtractError>
where
R: Read + Seek,
T: CodecBox + Clone + 'static,
{
let registry = default_registry();
extract_boxes_as_with_registry(reader, parent, paths, ®istry)
}
pub fn extract_box_bytes<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: Read + Seek,
{
let paths = [path];
extract_boxes_bytes(reader, parent, &paths)
}
pub fn extract_boxes_bytes<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: Read + Seek,
{
let registry = default_registry();
extract_boxes_bytes_with_registry(reader, parent, paths, ®istry)
}
pub fn extract_box_payload_bytes<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: Read + Seek,
{
let paths = [path];
extract_boxes_payload_bytes(reader, parent, &paths)
}
pub fn extract_boxes_payload_bytes<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: Read + Seek,
{
let registry = default_registry();
extract_boxes_payload_bytes_with_registry(reader, parent, paths, ®istry)
}
pub fn copy_box_bytes_to<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: Read + Seek,
W: Write,
{
let paths = [path];
copy_boxes_bytes_to(reader, parent, &paths, writer)
}
pub fn copy_boxes_bytes_to<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: Read + Seek,
W: Write,
{
let registry = default_registry();
copy_matched_bytes_to(
reader,
parent,
paths,
®istry,
ExtractedByteRange::FullBox,
writer,
)
}
pub fn copy_box_payload_bytes_to<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: Read + Seek,
W: Write,
{
let paths = [path];
copy_boxes_payload_bytes_to(reader, parent, &paths, writer)
}
pub fn copy_boxes_payload_bytes_to<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: Read + Seek,
W: Write,
{
let registry = default_registry();
copy_matched_bytes_to(
reader,
parent,
paths,
®istry,
ExtractedByteRange::Payload,
writer,
)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_box_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<BoxInfo>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = [path];
extract_boxes_async(reader, parent.as_ref(), &paths).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<BoxInfo>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = paths.to_vec();
let registry = default_registry();
validate_paths(&paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths,
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, &parent, ®istry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, ®istry, visitor).await?;
}
let matches = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
Ok(matches.into_iter().map(|matched| matched.info).collect())
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_box_with_payload_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<ExtractedBox>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = [path];
extract_boxes_with_payload_async(reader, parent.as_ref(), &paths).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_with_payload_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<ExtractedBox>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = paths.to_vec();
let registry = default_registry();
validate_paths(&paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths,
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, &parent, ®istry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, ®istry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut matches = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
let payload = decode_payload_async(reader, &matched, ®istry).await?;
matches.push(ExtractedBox {
info: matched.info,
payload,
});
}
Ok(matches)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_box_as_async<R, T>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<T>, ExtractError>
where
R: AsyncReadSeek,
T: CodecBox + Clone + 'static,
{
let parent = parent.copied();
let paths = [path];
extract_boxes_as_async(reader, parent.as_ref(), &paths).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_as_async<R, T>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<T>, ExtractError>
where
R: AsyncReadSeek,
T: CodecBox + Clone + 'static,
{
let parent = parent.copied();
let paths = paths.to_vec();
let registry = default_registry();
validate_paths(&paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths,
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, &parent, ®istry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, ®istry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut payloads = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
let payload = decode_payload_async(reader, &matched, ®istry).await?;
let typed = payload
.as_ref()
.as_any()
.downcast_ref::<T>()
.cloned()
.ok_or_else(|| ExtractError::UnexpectedPayloadType {
path: matched.path.clone(),
box_type: matched.info.box_type(),
offset: matched.info.offset(),
expected_type: type_name::<T>(),
})?;
payloads.push(typed);
}
Ok(payloads)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_box_bytes_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = [path];
extract_boxes_bytes_async(reader, parent.as_ref(), &paths).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_bytes_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = paths.to_vec();
let registry = default_registry();
validate_paths(&paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths,
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, &parent, ®istry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, ®istry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut extracted = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
extracted.push(
read_matched_bytes_async(reader, matched.info, ExtractedByteRange::FullBox).await?,
);
}
Ok(extracted)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_box_payload_bytes_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = [path];
extract_boxes_payload_bytes_async(reader, parent.as_ref(), &paths).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_payload_bytes_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: AsyncReadSeek,
{
let parent = parent.copied();
let paths = paths.to_vec();
let registry = default_registry();
validate_paths(&paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths,
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, &parent, ®istry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, ®istry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut extracted = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
extracted.push(
read_matched_bytes_async(reader, matched.info, ExtractedByteRange::Payload).await?,
);
}
Ok(extracted)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn copy_box_bytes_to_async<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: AsyncReadSeek,
W: AsyncWrite + Unpin,
{
let parent = parent.copied();
let paths = [path];
copy_boxes_bytes_to_async(reader, parent.as_ref(), &paths, writer).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn copy_boxes_bytes_to_async<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: AsyncReadSeek,
W: AsyncWrite + Unpin,
{
let parent = parent.copied();
let paths = paths.to_vec();
let registry = default_registry();
copy_matched_bytes_to_async(
reader,
parent.as_ref(),
&paths,
®istry,
ExtractedByteRange::FullBox,
writer,
)
.await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn copy_box_payload_bytes_to_async<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
path: BoxPath,
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: AsyncReadSeek,
W: AsyncWrite + Unpin,
{
let parent = parent.copied();
let paths = [path];
copy_boxes_payload_bytes_to_async(reader, parent.as_ref(), &paths, writer).await
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn copy_boxes_payload_bytes_to_async<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: AsyncReadSeek,
W: AsyncWrite + Unpin,
{
let parent = parent.copied();
let paths = paths.to_vec();
let registry = default_registry();
copy_matched_bytes_to_async(
reader,
parent.as_ref(),
&paths,
®istry,
ExtractedByteRange::Payload,
writer,
)
.await
}
pub fn extract_box_as_bytes<T>(input: &[u8], path: BoxPath) -> Result<Vec<T>, ExtractError>
where
T: CodecBox + Clone + 'static,
{
let paths = [path];
extract_boxes_as_bytes::<T>(input, &paths)
}
pub fn extract_boxes_as_bytes<T>(input: &[u8], paths: &[BoxPath]) -> Result<Vec<T>, ExtractError>
where
T: CodecBox + Clone + 'static,
{
let mut reader = Cursor::new(input);
extract_boxes_as(&mut reader, None, paths)
}
pub fn extract_boxes_with_registry<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<BoxInfo>, ExtractError>
where
R: Read + Seek,
{
Ok(collect_matches(reader, parent, paths, registry)?
.into_iter()
.map(|matched| matched.info)
.collect())
}
pub fn extract_boxes_with_payload_with_registry<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<ExtractedBox>, ExtractError>
where
R: Read + Seek,
{
let matched_boxes = collect_matches(reader, parent, paths, registry)?;
let mut matches = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
let payload = decode_payload(reader, &matched, registry)?;
matches.push(ExtractedBox {
info: matched.info,
payload,
});
}
Ok(matches)
}
pub fn extract_boxes_bytes_with_registry<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: Read + Seek,
{
extract_matched_bytes(reader, parent, paths, registry, ExtractedByteRange::FullBox)
}
pub fn extract_boxes_payload_bytes_with_registry<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: Read + Seek,
{
extract_matched_bytes(reader, parent, paths, registry, ExtractedByteRange::Payload)
}
pub fn extract_boxes_as_with_registry<R, T>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<T>, ExtractError>
where
R: Read + Seek,
T: CodecBox + Clone + 'static,
{
let matched_boxes = collect_matches(reader, parent, paths, registry)?;
let mut payloads = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
let payload = decode_payload(reader, &matched, registry)?;
let typed = payload
.as_ref()
.as_any()
.downcast_ref::<T>()
.cloned()
.ok_or_else(|| ExtractError::UnexpectedPayloadType {
path: matched.path.clone(),
box_type: matched.info.box_type(),
offset: matched.info.offset(),
expected_type: type_name::<T>(),
})?;
payloads.push(typed);
}
Ok(payloads)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_with_registry_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<BoxInfo>, ExtractError>
where
R: AsyncReadSeek,
{
validate_paths(paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths: paths.to_vec(),
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, parent, registry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, registry, visitor).await?;
}
let matches = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
Ok(matches.into_iter().map(|matched| matched.info).collect())
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_with_payload_with_registry_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<ExtractedBox>, ExtractError>
where
R: AsyncReadSeek,
{
validate_paths(paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths: paths.to_vec(),
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, parent, registry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, registry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut matches = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
let payload = decode_payload_async(reader, &matched, registry).await?;
matches.push(ExtractedBox {
info: matched.info,
payload,
});
}
Ok(matches)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_bytes_with_registry_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: AsyncReadSeek,
{
validate_paths(paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths: paths.to_vec(),
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, parent, registry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, registry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut extracted = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
extracted.push(
read_matched_bytes_async(reader, matched.info, ExtractedByteRange::FullBox).await?,
);
}
Ok(extracted)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_payload_bytes_with_registry_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: AsyncReadSeek,
{
validate_paths(paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths: paths.to_vec(),
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, parent, registry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, registry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut extracted = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
extracted.push(
read_matched_bytes_async(reader, matched.info, ExtractedByteRange::Payload).await?,
);
}
Ok(extracted)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn extract_boxes_as_with_registry_async<R, T>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<T>, ExtractError>
where
R: AsyncReadSeek,
T: CodecBox + Clone + 'static,
{
validate_paths(paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths: paths.to_vec(),
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, parent, registry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, registry, visitor).await?;
}
let matched_boxes = Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))?;
let mut payloads = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
let payload = decode_payload_async(reader, &matched, registry).await?;
let typed = payload
.as_ref()
.as_any()
.downcast_ref::<T>()
.cloned()
.ok_or_else(|| ExtractError::UnexpectedPayloadType {
path: matched.path.clone(),
box_type: matched.info.box_type(),
offset: matched.info.offset(),
expected_type: type_name::<T>(),
})?;
payloads.push(typed);
}
Ok(payloads)
}
struct MatchedBox {
info: BoxInfo,
path: BoxPath,
}
#[cfg(feature = "async")]
struct AsyncMatchCollector {
has_parent: bool,
paths: Vec<BoxPath>,
matches: Arc<Mutex<Vec<MatchedBox>>>,
}
#[cfg(feature = "async")]
impl<R> AsyncWalkVisitor<R> for AsyncMatchCollector
where
R: AsyncReadSeek,
{
type Future<'a>
= AsyncWalkFuture<'a>
where
Self: 'a,
R: 'a;
fn visit<'a, 'r>(&'a mut self, handle: &'a mut AsyncWalkHandle<'r, R>) -> Self::Future<'a>
where
'r: 'a,
{
Box::pin(async move {
if handle.info().box_type() == FourCc::ANY {
return Ok(WalkControl::Continue);
}
let relative_path = if self.has_parent {
BoxPath::from(handle.path().as_slice()[1..].to_vec())
} else {
handle.path().clone()
};
let PathMatch {
forward_match,
exact_match,
} = match_paths(&self.paths, &relative_path);
if exact_match {
self.matches
.lock()
.map_err(|_| WalkError::Io(io::Error::other("async match collector poisoned")))?
.push(MatchedBox {
info: *handle.info(),
path: relative_path.clone(),
});
}
Ok(if forward_match {
WalkControl::Descend
} else {
WalkControl::Continue
})
})
}
}
#[derive(Clone, Copy)]
enum ExtractedByteRange {
FullBox,
Payload,
}
fn collect_matches<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<MatchedBox>, ExtractError>
where
R: Read + Seek,
{
validate_paths(paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let mut matches = Vec::new();
let mut visitor = |handle: &mut WalkHandle<'_, R>| {
if handle.info().box_type() == FourCc::ANY {
return Ok(WalkControl::Continue);
}
let relative_path = if parent.is_some() {
BoxPath::from(handle.path().as_slice()[1..].to_vec())
} else {
handle.path().clone()
};
let PathMatch {
forward_match,
exact_match,
} = match_paths(paths, &relative_path);
if exact_match {
matches.push(MatchedBox {
info: *handle.info(),
path: relative_path.clone(),
});
}
Ok(if forward_match {
WalkControl::Descend
} else {
WalkControl::Continue
})
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry(reader, parent, registry, &mut visitor)?;
} else {
walk_structure_with_registry(reader, registry, &mut visitor)?;
}
Ok(matches)
}
#[cfg(feature = "async")]
async fn collect_matches_async<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
) -> Result<Vec<MatchedBox>, ExtractError>
where
R: AsyncReadSeek,
{
validate_paths(paths)?;
if paths.is_empty() {
return Ok(Vec::new());
}
let matches = Arc::new(Mutex::new(Vec::new()));
let visitor = AsyncMatchCollector {
has_parent: parent.is_some(),
paths: paths.to_vec(),
matches: Arc::clone(&matches),
};
if let Some(parent) = parent {
walk_structure_from_box_with_registry_async(reader, parent, registry, visitor).await?;
} else {
walk_structure_with_registry_async(reader, registry, visitor).await?;
}
Arc::try_unwrap(matches)
.map_err(|_| io::Error::other("async match collector remained shared"))?
.into_inner()
.map_err(|_| io::Error::other("async match collector poisoned"))
.map_err(ExtractError::Io)
}
fn extract_matched_bytes<R>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
range: ExtractedByteRange,
) -> Result<Vec<Vec<u8>>, ExtractError>
where
R: Read + Seek,
{
let matched_boxes = collect_matches(reader, parent, paths, registry)?;
let mut extracted = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
extracted.push(read_matched_bytes(reader, &matched.info, range)?);
}
Ok(extracted)
}
fn copy_matched_bytes_to<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
range: ExtractedByteRange,
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: Read + Seek,
W: Write,
{
let matched_boxes = collect_matches(reader, parent, paths, registry)?;
let mut copied = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
copied.push(copy_matched_bytes_range_to(
reader,
&matched.info,
range,
writer,
)?);
}
Ok(copied)
}
fn decode_payload<R>(
reader: &mut R,
matched: &MatchedBox,
registry: &BoxRegistry,
) -> Result<Box<dyn DynCodecBox>, ExtractError>
where
R: Read + Seek,
{
matched.info.seek_to_payload(reader)?;
let payload_size = matched.info.payload_size()?;
let (payload, _) = unmarshal_any_with_context(
reader,
payload_size,
matched.info.box_type(),
registry,
matched.info.lookup_context(),
None,
)
.map_err(|source| ExtractError::PayloadDecode {
path: matched.path.clone(),
box_type: matched.info.box_type(),
offset: matched.info.offset(),
source,
})?;
Ok(payload)
}
#[cfg(feature = "async")]
async fn decode_payload_async<R>(
reader: &mut R,
matched: &MatchedBox,
registry: &BoxRegistry,
) -> Result<Box<dyn DynCodecBox>, ExtractError>
where
R: AsyncReadSeek,
{
matched.info.seek_to_payload_async(reader).await?;
let payload_size = matched.info.payload_size()?;
let (payload, _) = unmarshal_any_with_context_async(
reader,
payload_size,
matched.info.box_type(),
registry,
matched.info.lookup_context(),
None,
)
.await
.map_err(|source| ExtractError::PayloadDecode {
path: matched.path.clone(),
box_type: matched.info.box_type(),
offset: matched.info.offset(),
source,
})?;
Ok(payload)
}
fn read_matched_bytes<R>(
reader: &mut R,
info: &BoxInfo,
range: ExtractedByteRange,
) -> Result<Vec<u8>, ExtractError>
where
R: Read + Seek,
{
let len = match range {
ExtractedByteRange::FullBox => {
info.seek_to_start(reader)?;
info.size()
}
ExtractedByteRange::Payload => {
info.seek_to_payload(reader)?;
info.payload_size()?
}
};
read_exact_bytes(reader, len)
}
#[cfg(feature = "async")]
async fn read_matched_bytes_async<R>(
reader: &mut R,
info: BoxInfo,
range: ExtractedByteRange,
) -> Result<Vec<u8>, ExtractError>
where
R: AsyncReadSeek,
{
let len = match range {
ExtractedByteRange::FullBox => {
reader.seek(io::SeekFrom::Start(info.offset())).await?;
info.size()
}
ExtractedByteRange::Payload => {
reader
.seek(io::SeekFrom::Start(info.offset() + info.header_size()))
.await?;
info.payload_size()?
}
};
read_exact_bytes_async(reader, len).await
}
fn copy_matched_bytes_range_to<R, W>(
reader: &mut R,
info: &BoxInfo,
range: ExtractedByteRange,
writer: &mut W,
) -> Result<u64, ExtractError>
where
R: Read + Seek,
W: Write,
{
let len = match range {
ExtractedByteRange::FullBox => {
info.seek_to_start(reader)?;
info.size()
}
ExtractedByteRange::Payload => {
info.seek_to_payload(reader)?;
info.payload_size()?
}
};
let mut limited = (&mut *reader).take(len);
let copied = io::copy(&mut limited, writer)?;
if copied != len {
return Err(ExtractError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"extracted byte range was truncated during copy",
)));
}
Ok(copied)
}
#[cfg(feature = "async")]
async fn copy_matched_bytes_to_async<R, W>(
reader: &mut R,
parent: Option<&BoxInfo>,
paths: &[BoxPath],
registry: &BoxRegistry,
range: ExtractedByteRange,
writer: &mut W,
) -> Result<Vec<u64>, ExtractError>
where
R: AsyncReadSeek,
W: AsyncWrite + Unpin,
{
let matched_boxes = collect_matches_async(reader, parent, paths, registry).await?;
let mut copied = Vec::with_capacity(matched_boxes.len());
for matched in matched_boxes {
copied.push(copy_matched_bytes_range_to_async(reader, matched.info, range, writer).await?);
}
Ok(copied)
}
#[cfg(feature = "async")]
async fn copy_matched_bytes_range_to_async<R, W>(
reader: &mut R,
info: BoxInfo,
range: ExtractedByteRange,
writer: &mut W,
) -> Result<u64, ExtractError>
where
R: AsyncReadSeek,
W: AsyncWrite + Unpin,
{
let len = match range {
ExtractedByteRange::FullBox => {
reader.seek(io::SeekFrom::Start(info.offset())).await?;
info.size()
}
ExtractedByteRange::Payload => {
reader
.seek(io::SeekFrom::Start(info.offset() + info.header_size()))
.await?;
info.payload_size()?
}
};
let mut limited = (&mut *reader).take(len);
let copied = tokio::io::copy(&mut limited, writer).await?;
if copied != len {
return Err(ExtractError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"extracted byte range was truncated during async copy",
)));
}
Ok(copied)
}
fn read_exact_bytes<R>(reader: &mut R, len: u64) -> Result<Vec<u8>, ExtractError>
where
R: Read,
{
let len = usize::try_from(len).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"requested byte range exceeds the supported in-memory size",
)
})?;
read_exact_vec_untrusted(reader, len).map_err(ExtractError::Io)
}
#[cfg(feature = "async")]
async fn read_exact_bytes_async<R>(reader: &mut R, len: u64) -> Result<Vec<u8>, ExtractError>
where
R: AsyncReadSeek,
{
let len = usize::try_from(len).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"requested byte range exceeds the supported in-memory size",
)
})?;
read_exact_vec_untrusted_async(reader, len)
.await
.map_err(ExtractError::Io)
}
fn validate_paths(paths: &[BoxPath]) -> Result<(), ExtractError> {
if paths.iter().any(BoxPath::is_empty) {
return Err(ExtractError::EmptyPath);
}
Ok(())
}
fn match_paths(paths: &[BoxPath], current: &BoxPath) -> PathMatch {
paths
.iter()
.fold(PathMatch::default(), |mut matched, path| {
let next = current.compare_with(path);
matched.forward_match |= next.forward_match;
matched.exact_match |= next.exact_match;
matched
})
}
#[derive(Debug)]
pub enum ExtractError {
Io(io::Error),
Header(HeaderError),
Codec(CodecError),
Walk(WalkError),
EmptyPath,
PayloadDecode {
path: BoxPath,
box_type: FourCc,
offset: u64,
source: CodecError,
},
UnexpectedPayloadType {
path: BoxPath,
box_type: FourCc,
offset: u64,
expected_type: &'static str,
},
}
impl fmt::Display for ExtractError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => error.fmt(f),
Self::Header(error) => error.fmt(f),
Self::Codec(error) => error.fmt(f),
Self::Walk(error) => error.fmt(f),
Self::EmptyPath => f.write_str("box path must not be empty"),
Self::PayloadDecode {
path,
box_type,
offset,
source,
} => write!(
f,
"failed to decode payload at {path} (type={box_type}, offset={offset}): {source}"
),
Self::UnexpectedPayloadType {
path,
box_type,
offset,
expected_type,
} => write!(
f,
"unexpected decoded payload type at {path} (type={box_type}, offset={offset}): expected {expected_type}"
),
}
}
}
impl Error for ExtractError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(error) => Some(error),
Self::Header(error) => Some(error),
Self::Codec(error) => Some(error),
Self::Walk(error) => Some(error),
Self::PayloadDecode { source, .. } => Some(source),
Self::EmptyPath | Self::UnexpectedPayloadType { .. } => None,
}
}
}
impl From<io::Error> for ExtractError {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl From<HeaderError> for ExtractError {
fn from(value: HeaderError) -> Self {
Self::Header(value)
}
}
impl From<CodecError> for ExtractError {
fn from(value: CodecError) -> Self {
Self::Codec(value)
}
}
impl From<WalkError> for ExtractError {
fn from(value: WalkError) -> Self {
Self::Walk(value)
}
}