use std::io::{Read, Seek};
use crate::error::{Error, Result};
use crate::mkv::ebml::{Element, Header, child_path, indexed_path};
use crate::mkv::ids;
use crate::mkv::source::Source;
pub const MAX_ATTACHMENT_FIELD: u64 = 64 * 1024;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Attachment {
pub uid: u64,
pub name: String,
pub mime: String,
pub description: Option<String>,
pub offset: u64,
pub size: u64,
}
pub(crate) fn scan_attachments<R: Read + Seek>(
src: &mut Source<'_, R>,
pos: u64,
header: Header,
index: usize,
out: &mut Vec<Attachment>,
) -> Result<()> {
let path = indexed_path("Segment", ids::ATTACHMENTS, index);
let body = pos + header.len;
let end = body.saturating_add(header.size.unwrap_or_default());
let mut files = 0usize;
for_each_child(src, body, end, &path, |src, at, child| {
if child.id != ids::ATTACHED_FILE {
return Ok(());
}
files += 1;
let file_path = indexed_path(&path, ids::ATTACHED_FILE, files);
let file = scan_file(src, at, child, &file_path)?;
out.push(file);
Ok(())
})
}
fn scan_file<R: Read + Seek>(
src: &mut Source<'_, R>,
pos: u64,
header: Header,
path: &str,
) -> Result<Attachment> {
let body = pos + header.len;
let end = body.saturating_add(header.size.unwrap_or_default());
let mut uid = None;
let mut name = None;
let mut mime = None;
let mut description = None;
let mut data = None;
for_each_child(src, body, end, path, |src, at, child| {
let child_body = at + child.len;
let size = child.size.unwrap_or_default();
let child_path = child_path(path, child.id);
if child.id == ids::FILE_DATA {
data = Some((child_body, size));
return Ok(());
}
if !matches!(
child.id,
ids::FILE_UID | ids::FILE_NAME | ids::FILE_MEDIA_TYPE | ids::FILE_DESCRIPTION
) {
return Ok(());
}
let bytes = src.read_body(child_body, size, &child_path, MAX_ATTACHMENT_FIELD)?;
let leaf = Element {
id: child.id,
offset: at,
body_offset: child_body,
body: &bytes,
};
match child.id {
ids::FILE_UID => uid = Some(leaf.uint(&child_path)?),
ids::FILE_NAME => name = Some(leaf.string(&child_path)?),
ids::FILE_MEDIA_TYPE => mime = Some(leaf.string(&child_path)?),
_ => description = Some(leaf.string(&child_path)?),
}
Ok(())
})?;
let (offset, size) = data.ok_or_else(|| Error::mkv_missing(format!("{path}/FileData")))?;
Ok(Attachment {
uid: uid.ok_or_else(|| Error::mkv_missing(format!("{path}/FileUID")))?,
name: name.ok_or_else(|| Error::mkv_missing(format!("{path}/FileName")))?,
mime: mime.ok_or_else(|| Error::mkv_missing(format!("{path}/FileMediaType")))?,
description,
offset,
size,
})
}
fn for_each_child<R: Read + Seek>(
src: &mut Source<'_, R>,
body: u64,
end: u64,
path: &str,
mut visit: impl FnMut(&mut Source<'_, R>, u64, Header) -> Result<()>,
) -> Result<()> {
let mut at = body;
while at < end {
let child = src.header_at(at, path)?.ok_or_else(|| {
Error::mkv_malformed(path, at, "an element header runs past the input end")
})?;
let child_path = child_path(path, child.id);
let size = child.known_size(&child_path, at)?;
let child_end = (at + child.len).saturating_add(size);
if child_end > end {
return Err(Error::mkv_malformed(
&child_path,
at,
format!("the element declares {size} bytes, past its parent's end at {end}"),
));
}
visit(src, at, child)?;
at = child_end;
}
Ok(())
}