use std::string::FromUtf8Error;
use bon::Builder;
use crate::{atom::FourCC, parser::ParseAtomData, writer::SerializeAtom, ParseError};
pub const DREF: FourCC = FourCC::new(b"dref");
pub mod entry_types {
pub const URL: &[u8; 4] = b"url ";
pub const URN: &[u8; 4] = b"urn ";
pub const ALIS: &[u8; 4] = b"alis";
}
pub mod flags {
pub const SELF_CONTAINED: u32 = 0x000001;
}
#[derive(Debug, Clone)]
pub enum DataReferenceEntryInner {
Url(String),
Urn(String),
Alias(Vec<u8>),
Unknown(FourCC, Vec<u8>),
}
impl DataReferenceEntryInner {
fn new(entry_type: FourCC, data: Vec<u8>) -> Result<Self, FromUtf8Error> {
Ok(match &entry_type.0 {
entry_types::URL => DataReferenceEntryInner::Url(String::from_utf8(data)?),
entry_types::URN => DataReferenceEntryInner::Urn(String::from_utf8(data)?),
entry_types::ALIS => DataReferenceEntryInner::Alias(data),
_ => DataReferenceEntryInner::Unknown(entry_type, data),
})
}
}
#[derive(Debug, Clone, Builder)]
pub struct DataReferenceEntry {
#[builder(setters(vis = ""))]
pub inner: DataReferenceEntryInner,
#[builder(default)]
pub version: u8,
#[builder(default)]
pub flags: [u8; 3],
}
impl<S: data_reference_entry_builder::State> DataReferenceEntryBuilder<S> {
pub fn url(
self,
url: impl Into<String>,
) -> DataReferenceEntryBuilder<data_reference_entry_builder::SetInner<S>>
where
S::Inner: data_reference_entry_builder::IsUnset,
{
self.inner(DataReferenceEntryInner::Url(url.into()))
}
pub fn urn(
self,
urn: impl Into<String>,
) -> DataReferenceEntryBuilder<data_reference_entry_builder::SetInner<S>>
where
S::Inner: data_reference_entry_builder::IsUnset,
{
self.inner(DataReferenceEntryInner::Urn(urn.into()))
}
}
impl DataReferenceEntry {
pub fn is_self_contained(&self) -> bool {
let flags_u32 = u32::from_be_bytes([0, self.flags[0], self.flags[1], self.flags[2]]);
(flags_u32 & flags::SELF_CONTAINED) != 0
}
}
#[derive(Debug, Clone, Builder)]
pub struct DataReferenceAtom {
#[builder(default = 0)]
pub version: u8,
#[builder(default = [0u8; 3])]
pub flags: [u8; 3],
#[builder(with = FromIterator::from_iter)]
pub entries: Vec<DataReferenceEntry>,
}
impl<S: data_reference_atom_builder::State> DataReferenceAtomBuilder<S> {
pub fn entry(
self,
entry: DataReferenceEntry,
) -> DataReferenceAtomBuilder<data_reference_atom_builder::SetEntries<S>>
where
S::Entries: data_reference_atom_builder::IsUnset,
{
self.entries(vec![entry])
}
}
impl ParseAtomData for DataReferenceAtom {
fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
crate::atom::util::parser::assert_atom_type!(atom_type, DREF);
use crate::atom::util::parser::stream;
use winnow::Parser;
Ok(parser::parse_dref_data.parse(stream(input))?)
}
}
impl SerializeAtom for DataReferenceAtom {
fn atom_type(&self) -> FourCC {
DREF
}
fn into_body_bytes(self) -> Vec<u8> {
serializer::serialize_dref_data(self)
}
}
mod serializer {
use crate::atom::{
dref::{entry_types, DataReferenceEntry, DataReferenceEntryInner},
DataReferenceAtom,
};
pub fn serialize_dref_data(data: DataReferenceAtom) -> Vec<u8> {
let entries = data.entries;
vec![
version(data.version),
flags(data.flags),
entry_count(entries.len()),
entries.into_iter().flat_map(entry).collect(),
]
.into_iter()
.flatten()
.collect()
}
fn version(version: u8) -> Vec<u8> {
vec![version]
}
fn flags(flags: [u8; 3]) -> Vec<u8> {
flags.to_vec()
}
fn entry_count(n: usize) -> Vec<u8> {
u32::try_from(n)
.expect("entries len must fit in a u32")
.to_be_bytes()
.to_vec()
}
fn entry(e: DataReferenceEntry) -> Vec<u8> {
let e = raw_entry(e);
let data: Vec<u8> = vec![version(e.version), flags(e.flags), e.data]
.into_iter()
.flatten()
.collect();
let header_size = 4 + 4; vec![
entry_size(header_size + data.len()).to_vec(),
e.typ.to_vec(),
data,
]
.into_iter()
.flatten()
.collect()
}
fn entry_size(n: usize) -> [u8; 4] {
u32::try_from(n)
.expect("entry size len must fit in a u32")
.to_be_bytes()
}
struct RawEntry {
version: u8,
flags: [u8; 3],
typ: [u8; 4],
data: Vec<u8>,
}
fn raw_entry(e: DataReferenceEntry) -> RawEntry {
let (typ, data) = match e.inner {
DataReferenceEntryInner::Url(url) => (*entry_types::URL, url.into_bytes()),
DataReferenceEntryInner::Urn(urn) => (*entry_types::URN, urn.into_bytes()),
DataReferenceEntryInner::Alias(alias_data) => (*entry_types::ALIS, alias_data),
DataReferenceEntryInner::Unknown(typ, unknown_data) => (typ.0, unknown_data),
};
RawEntry {
version: e.version,
flags: e.flags,
typ,
data,
}
}
}
mod parser {
use winnow::{
binary::length_repeat,
combinator::{seq, trace},
error::StrContext,
Parser,
};
use super::{DataReferenceAtom, DataReferenceEntry, DataReferenceEntryInner};
use crate::atom::util::parser::{
atom_size, be_u32_as_usize, combinators::inclusive_length_and_then, flags3, fourcc,
rest_vec, version, Stream,
};
pub fn parse_dref_data(input: &mut Stream<'_>) -> winnow::ModalResult<DataReferenceAtom> {
trace(
"dref",
(version, flags3, entries)
.map(|(version, flags, entries)| DataReferenceAtom {
version,
flags,
entries,
})
.context(StrContext::Label("dref")),
)
.parse_next(input)
}
fn entries(input: &mut Stream<'_>) -> winnow::ModalResult<Vec<DataReferenceEntry>> {
trace(
"entries",
length_repeat(
be_u32_as_usize.context(StrContext::Label("entry_count")),
entry.context(StrContext::Label("entry")),
),
)
.parse_next(input)
}
fn entry(input: &mut Stream<'_>) -> winnow::ModalResult<DataReferenceEntry> {
trace(
"entry",
inclusive_length_and_then(atom_size, move |input: &mut Stream<'_>| {
let typ = fourcc.parse_next(input)?;
seq!(DataReferenceEntry {
version: version,
flags: flags3,
inner: rest_vec
.try_map(|data| DataReferenceEntryInner::new(typ, data))
.context(StrContext::Label("data")),
})
.parse_next(input)
}),
)
.parse_next(input)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::atom::test_utils::test_atom_roundtrip;
#[test]
fn test_dref_roundtrip() {
test_atom_roundtrip::<DataReferenceAtom>(DREF);
}
}