use std::fmt::{Debug, Formatter};
use scroll::{Endian, Pwrite, ctx::TryIntoCtx};
use std::ops::*;
pub struct DynamicBuffer {
pub(crate) buffer: Vec<u8>,
alloc_increment: usize,
pub(crate) start_offset: usize,
write_end: usize,
}
impl DynamicBuffer {
pub fn new() -> DynamicBuffer {
Self::with_increment(1)
}
pub fn with_increment(alloc_increment: usize) -> DynamicBuffer {
DynamicBuffer {
buffer: vec![],
alloc_increment,
start_offset: 0,
write_end: 0,
}
}
pub fn get(&self) -> &[u8] {
&self.buffer[..self.write_end]
}
pub fn into_vec(mut self) -> Vec<u8> {
self.buffer.truncate(self.write_end);
self.buffer
}
pub fn clear(&mut self) {
self.buffer.clear();
self.start_offset = 0;
self.write_end = 0;
}
}
impl Debug for DynamicBuffer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self.get(), f)
}
}
impl Index<usize> for DynamicBuffer {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
self.buffer.index(self.start_offset + index)
}
}
impl IndexMut<usize> for DynamicBuffer {
fn index_mut(&mut self, mut index: usize) -> &mut Self::Output {
index += self.start_offset;
if let Some(diff) = index.checked_sub(self.buffer.len()) {
self.buffer
.extend(std::iter::repeat(0).take(std::cmp::max(self.alloc_increment, diff + 1)));
}
self.write_end = self.write_end.max(index + 1);
self.buffer.index_mut(index)
}
}
impl<Ctx: Copy, E> Pwrite<Ctx, E> for DynamicBuffer {
fn pwrite_with<N: TryIntoCtx<Ctx, Self, Error = E>>(
&mut self,
n: N,
offset: usize,
ctx: Ctx,
) -> Result<usize, E> {
self.start_offset += offset;
let end = n.try_into_ctx(self, ctx)?;
self.start_offset -= offset;
Ok(end)
}
}
impl TryIntoCtx<(), DynamicBuffer> for &'_ [u8] {
type Error = scroll::Error;
fn try_into_ctx(self, into: &mut DynamicBuffer, _: ()) -> Result<usize, Self::Error> {
let len = self.len();
into[len - 1] = 0;
into.buffer[into.start_offset..into.start_offset + len].copy_from_slice(self);
Ok(len)
}
}
macro_rules! num_impl {
($t:ty) => {
impl TryIntoCtx<scroll::Endian, DynamicBuffer> for $t {
type Error = scroll::Error;
fn try_into_ctx(
self,
buf: &mut DynamicBuffer,
ctx: Endian,
) -> Result<usize, Self::Error> {
let bytes = if ctx.is_little() {
self.to_le_bytes()
} else {
self.to_be_bytes()
};
TryIntoCtx::try_into_ctx(&bytes[..], buf, ())
}
}
impl TryIntoCtx<scroll::Endian, DynamicBuffer> for &$t {
type Error = scroll::Error;
fn try_into_ctx(
self,
buf: &mut DynamicBuffer,
ctx: Endian,
) -> Result<usize, Self::Error> {
(*self).try_into_ctx(buf, ctx)
}
}
};
}
num_impl!(i8);
num_impl!(i16);
num_impl!(i32);
num_impl!(i64);
num_impl!(i128);
num_impl!(u8);
num_impl!(u16);
num_impl!(u32);
num_impl!(u64);
num_impl!(u128);
num_impl!(f32);
num_impl!(f64);
#[cfg(test)]
mod tests {
use super::DynamicBuffer;
use scroll::{Endian, Pwrite, ctx::TryIntoCtx};
struct Test {
a: u16,
b: u32,
c: u64,
}
#[test]
fn int_write() {
let mut buf = DynamicBuffer::new();
buf.pwrite_with(0x1234u16, 0, Endian::Little).unwrap();
buf.pwrite_with(0x5678i16, 2, Endian::Big).unwrap();
assert_eq!(buf.get(), [0x34, 0x12, 0x56, 0x78]);
}
#[test]
fn offset_write() {
let mut buf = DynamicBuffer::new();
buf.pwrite_with(0x1234u16, 2, Endian::Big).unwrap();
assert_eq!(buf.get(), [0, 0, 0x12, 0x34]);
}
#[test]
fn slice_write() {
let mut buf = DynamicBuffer::new();
buf.pwrite([1u8; 4].as_slice(), 0).unwrap();
assert_eq!(buf.get(), [1, 1, 1, 1]);
buf.pwrite([2u8; 2].as_slice(), 2).unwrap();
assert_eq!(buf.get(), [1, 1, 2, 2]);
}
#[test]
fn basic_write() {
impl TryIntoCtx<Endian, DynamicBuffer> for Test {
type Error = scroll::Error;
fn try_into_ctx(
self,
buf: &mut DynamicBuffer,
ctx: Endian,
) -> Result<usize, Self::Error> {
let offset = &mut 0;
buf.gwrite_with(self.a, offset, ctx)?;
buf.gwrite_with(self.b, offset, ctx)?;
buf.gwrite_with(self.c, offset, ctx)?;
Ok(*offset)
}
}
let mut buf = DynamicBuffer::new();
buf.pwrite_with(Test { a: 1, b: 2, c: 3 }, 0, Endian::Little)
.unwrap();
assert_eq!(buf.get(), [1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]);
}
#[test]
fn dyn_size_write() {
impl TryIntoCtx<(bool, Endian), DynamicBuffer> for Test {
type Error = scroll::Error;
fn try_into_ctx(
self,
buf: &mut DynamicBuffer,
(is16, ctx): (bool, Endian),
) -> Result<usize, Self::Error> {
let offset = &mut 0;
if is16 {
buf.gwrite_with(self.a, offset, ctx)?;
} else {
buf.gwrite_with(self.a as u32, offset, ctx)?;
}
buf.gwrite_with(self.b, offset, ctx)?;
buf.gwrite_with(self.c, offset, ctx)?;
Ok(*offset)
}
}
let mut buf1 = DynamicBuffer::new();
buf1.pwrite_with(Test { a: 1, b: 2, c: 3 }, 0, (true, Endian::Little))
.unwrap();
assert_eq!(buf1.get(), [1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]);
let mut buf1 = DynamicBuffer::new();
buf1.pwrite_with(Test { a: 1, b: 2, c: 3 }, 0, (false, Endian::Little))
.unwrap();
assert_eq!(buf1.get(), [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]);
}
#[test]
fn alloc_size() {
let mut buf = DynamicBuffer::with_increment(8);
buf.pwrite_with(0xbabecafe_u32, 0, Endian::Big).unwrap();
assert_eq!(buf.buffer.len(), 8);
assert_eq!(buf.get(), [0xba, 0xbe, 0xca, 0xfe]);
}
}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");