use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::marker::PhantomData;
use std::ops::Deref;
pub type Xc3Result<T> = Result<T, std::io::Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endian {
Little,
Big,
}
pub trait Xc3Write {
type Offsets<'a>
where
Self: 'a;
fn xc3_write<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
) -> Xc3Result<Self::Offsets<'_>>;
fn should_write(&self) -> Option<bool> {
Some(true)
}
const ALIGNMENT: u64 = 4;
}
pub trait Xc3WriteOffsets {
fn write_offsets<W: Write + Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
endian: Endian,
) -> Xc3Result<()>;
}
pub fn write_full<'a, T, W>(
value: &'a T,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
endian: Endian,
) -> Xc3Result<()>
where
W: Write + Seek,
T: Xc3Write + 'static,
T::Offsets<'a>: Xc3WriteOffsets,
{
let offsets = value.xc3_write(writer, endian)?;
*data_ptr = (*data_ptr).max(writer.stream_position()?);
offsets.write_offsets(writer, base_offset, data_ptr, endian)?;
*data_ptr = (*data_ptr).max(writer.stream_position()?);
Ok(())
}
pub use xc3_write_derive::Xc3Write;
pub use xc3_write_derive::Xc3WriteOffsets;
pub struct FieldPosition<'a, T> {
pub position: u64,
pub data: &'a T,
}
impl<'a, T> FieldPosition<'a, T> {
pub fn new(position: u64, data: &'a T) -> Self {
Self { position, data }
}
}
pub struct Offset<'a, P, T> {
pub position: u64,
pub data: &'a T,
pub field_alignment: Option<u64>,
pub padding_byte: u8,
phantom: PhantomData<P>,
}
impl<'a, P, T: Xc3Write> std::fmt::Debug for Offset<'a, P, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Offset")
.field("position", &self.position)
.field("data", &std::any::type_name::<T>())
.finish()
}
}
impl<'a, P, T> Offset<'a, P, T> {
pub fn new(position: u64, data: &'a T, field_alignment: Option<u64>, padding_byte: u8) -> Self {
Self {
position,
data,
field_alignment,
padding_byte,
phantom: PhantomData,
}
}
pub fn set_offset<W>(&self, writer: &mut W, offset: u64, endian: Endian) -> Xc3Result<()>
where
W: Write + Seek,
P: TryFrom<u64> + Xc3Write,
<P as TryFrom<u64>>::Error: std::fmt::Debug,
{
writer.seek(SeekFrom::Start(self.position))?;
let offset = P::try_from(offset).unwrap();
offset.xc3_write(writer, endian)?;
Ok(())
}
fn set_offset_seek<W>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
type_alignment: u64,
should_write: bool,
endian: Endian,
) -> Xc3Result<()>
where
W: Write + Seek,
P: TryFrom<u64> + Xc3Write,
<P as TryFrom<u64>>::Error: std::fmt::Debug,
{
*data_ptr = (*data_ptr).max(writer.stream_position()?);
let alignment = self.field_alignment.unwrap_or(type_alignment);
let aligned_data_pr = data_ptr.next_multiple_of(alignment);
self.set_offset(writer, aligned_data_pr - base_offset, endian)?;
if should_write {
writer.seek(SeekFrom::Start(*data_ptr))?;
vec![self.padding_byte; (aligned_data_pr - *data_ptr) as usize]
.xc3_write(writer, endian)?;
*data_ptr = (*data_ptr).max(writer.stream_position()?);
}
Ok(())
}
}
impl<'a, P, T> Offset<'a, P, T>
where
T: Xc3Write,
P: TryFrom<u64> + Xc3Write,
<P as TryFrom<u64>>::Error: std::fmt::Debug,
{
pub fn write<W: Write + Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
endian: Endian,
) -> Xc3Result<T::Offsets<'_>> {
if let Some(should_write) = self.data.should_write() {
self.set_offset_seek(
writer,
base_offset,
data_ptr,
T::ALIGNMENT,
should_write,
endian,
)?;
}
let offsets = self.data.xc3_write(writer, endian)?;
*data_ptr = (*data_ptr).max(writer.stream_position()?);
Ok(offsets)
}
}
impl<'a, P, T> Offset<'a, P, T>
where
T: Xc3Write + 'static,
T::Offsets<'a>: Xc3WriteOffsets,
P: TryFrom<u64> + Xc3Write,
<P as TryFrom<u64>>::Error: std::fmt::Debug,
{
pub fn write_full<W: Write + Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
endian: Endian,
) -> Xc3Result<()> {
if let Some(should_write) = self.data.should_write() {
self.set_offset_seek(
writer,
base_offset,
data_ptr,
T::ALIGNMENT,
should_write,
endian,
)?;
write_full(self.data, writer, base_offset, data_ptr, endian)?;
}
Ok(())
}
}
macro_rules! xc3_write_impl {
($($ty:ty),*) => {
$(
impl Xc3Write for $ty {
type Offsets<'a> = ();
fn xc3_write<W: std::io::Write + std::io::Seek>(
&self,
writer: &mut W,
endian: Endian,
) -> Xc3Result<Self::Offsets<'_>> {
match endian {
Endian::Little => writer.write_all(&self.to_le_bytes())?,
Endian::Big => writer.write_all(&self.to_be_bytes())?,
}
Ok(())
}
const ALIGNMENT: u64 = std::mem::align_of::<$ty>() as u64;
}
)*
};
}
xc3_write_impl!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64);
impl<A: Xc3Write, B: Xc3Write> Xc3Write for (A, B) {
type Offsets<'a> = (A::Offsets<'a>, B::Offsets<'a>) where A: 'a, B: 'a;
fn xc3_write<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
) -> Xc3Result<Self::Offsets<'_>> {
Ok((
self.0.xc3_write(writer, endian)?,
self.1.xc3_write(writer, endian)?,
))
}
}
impl<A: Xc3WriteOffsets, B: Xc3WriteOffsets> Xc3WriteOffsets for (A, B) {
fn write_offsets<W: Write + Seek>(
&self,
_: &mut W,
_: u64,
_: &mut u64,
_: Endian,
) -> Xc3Result<()> {
Ok(())
}
}
impl<const N: usize, T> Xc3Write for [T; N]
where
T: Xc3Write + 'static,
{
type Offsets<'a> = ();
fn xc3_write<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
) -> Xc3Result<Self::Offsets<'_>> {
for value in self {
value.xc3_write(writer, endian)?;
}
Ok(())
}
}
impl<T: Xc3Write> Xc3Write for Box<T> {
type Offsets<'a> = T::Offsets<'a>
where
Self: 'a;
fn xc3_write<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
) -> Xc3Result<Self::Offsets<'_>> {
self.deref().xc3_write(writer, endian)
}
}
impl Xc3Write for String {
type Offsets<'a> = ();
fn xc3_write<W: Write + Seek>(
&self,
writer: &mut W,
_: Endian,
) -> Xc3Result<Self::Offsets<'_>> {
writer.write_all(self.as_bytes())?;
writer.write_all(&[0u8])?;
Ok(())
}
const ALIGNMENT: u64 = 1;
}
pub struct VecOffsets<T>(pub Vec<T>);
impl<T> Xc3Write for Vec<T>
where
T: Xc3Write + 'static,
{
type Offsets<'a> = VecOffsets<T::Offsets<'a>>;
fn xc3_write<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
) -> Xc3Result<Self::Offsets<'_>> {
let offsets = if let Some(bytes) = <dyn core::any::Any>::downcast_ref::<Vec<u8>>(self) {
writer.write_all(bytes)?;
Vec::new()
} else {
self.iter()
.map(|v| v.xc3_write(writer, endian))
.collect::<Result<Vec<_>, _>>()?
};
Ok(VecOffsets(offsets))
}
fn should_write(&self) -> Option<bool> {
Some(!self.is_empty())
}
}
impl<T> Xc3WriteOffsets for VecOffsets<T>
where
T: Xc3WriteOffsets,
{
fn write_offsets<W: Write + Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
endian: Endian,
) -> Xc3Result<()> {
for item in &self.0 {
item.write_offsets(writer, base_offset, data_ptr, endian)?;
}
Ok(())
}
}
impl Xc3Write for () {
type Offsets<'a> = ();
fn xc3_write<W: Write + Seek>(&self, _: &mut W, _: Endian) -> Xc3Result<Self::Offsets<'_>> {
Ok(())
}
const ALIGNMENT: u64 = 1;
}
impl Xc3WriteOffsets for () {
fn write_offsets<W: Write + Seek>(
&self,
_: &mut W,
_: u64,
_: &mut u64,
_: Endian,
) -> Xc3Result<()> {
Ok(())
}
}
impl<T> Xc3Write for Option<T>
where
T: Xc3Write,
{
type Offsets<'a> = Option<T::Offsets<'a>>
where
Self: 'a;
fn xc3_write<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
) -> Xc3Result<Self::Offsets<'_>> {
self.as_ref()
.map(|v| v.xc3_write(writer, endian))
.transpose()
}
fn should_write(&self) -> Option<bool> {
self.as_ref().map(|_| true)
}
const ALIGNMENT: u64 = T::ALIGNMENT;
}
impl<T> Xc3WriteOffsets for Option<T>
where
T: Xc3WriteOffsets,
{
fn write_offsets<W: Write + Seek>(
&self,
writer: &mut W,
base_offset: u64,
data_ptr: &mut u64,
endian: Endian,
) -> Xc3Result<()> {
if let Some(value) = self {
value.write_offsets(writer, base_offset, data_ptr, endian)?;
}
Ok(())
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! assert_hex_eq {
($a:expr, $b:expr) => {
pretty_assertions::assert_str_eq!(hex::encode($a), hex::encode($b))
};
}