1
0
Fork 0
mirror of https://codeberg.org/icewind/bitbuffer.git synced 2026-06-03 16:44:06 +02:00

add write bytes and string and rename ReadError -> BitError

This commit is contained in:
Robin Appelman 2020-02-15 15:49:49 +01:00
commit e43b0b6bb2
7 changed files with 147 additions and 58 deletions

View file

@ -9,6 +9,7 @@ repository = "https://github.com/icewind1991/bitbuffer"
[dependencies] [dependencies]
num-traits = "0.2" num-traits = "0.2"
err-derive = "0.2.2"
bitbuffer_derive = { version = "0.7", path = "bitbuffer_derive" } bitbuffer_derive = { version = "0.7", path = "bitbuffer_derive" }
memchr = "2.2" memchr = "2.2"

View file

@ -387,7 +387,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
Ok(match discriminant { Ok(match discriminant {
#(#match_arms)* #(#match_arms)*
_ => { _ => {
return Err(::bitbuffer::ReadError::UnmatchedDiscriminant{discriminant, enum_name: #enum_name.to_string()}) return Err(::bitbuffer::BitError::UnmatchedDiscriminant{discriminant, enum_name: #enum_name.to_string()})
} }
}) })
} }

View file

@ -54,9 +54,7 @@
#![warn(missing_docs)] #![warn(missing_docs)]
use std::error::Error; use err_derive::Error;
use std::fmt;
use std::fmt::Display;
pub use std::string::FromUtf8Error; pub use std::string::FromUtf8Error;
pub use bitbuffer_derive::{BitRead, BitReadSized}; pub use bitbuffer_derive::{BitRead, BitReadSized};
@ -74,9 +72,14 @@ mod readstream;
mod writestream; mod writestream;
/// Errors that can be returned when trying to read from a buffer /// Errors that can be returned when trying to read from a buffer
#[derive(Debug)] #[derive(Debug, Error)]
pub enum ReadError { pub enum BitError {
/// Too many bits requested to fit in the requested data type /// Too many bits requested to fit in the requested data type
#[error(
display = "Too many bits requested to fit in the requested data type, requested to read {} bits while only {} fit in the datatype",
requested,
max
)]
TooManyBits { TooManyBits {
/// The number of bits requested to read /// The number of bits requested to read
requested: usize, requested: usize,
@ -84,6 +87,11 @@ pub enum ReadError {
max: usize, max: usize,
}, },
/// Not enough data in the buffer to read all requested bits /// Not enough data in the buffer to read all requested bits
#[error(
display = "Not enough data in the buffer to read all requested bits, requested to read {} bits while only {} bits are left",
requested,
bits_left
)]
NotEnoughData { NotEnoughData {
/// The number of bits requested to read /// The number of bits requested to read
requested: usize, requested: usize,
@ -91,6 +99,11 @@ pub enum ReadError {
bits_left: usize, bits_left: usize,
}, },
/// The requested position is outside the bounds of the stream or buffer /// The requested position is outside the bounds of the stream or buffer
#[error(
display = "The requested position is outside the bounds of the stream, requested position {} while the stream or buffer is only {} bits long",
pos,
size
)]
IndexOutOfBounds { IndexOutOfBounds {
/// The requested position /// The requested position
pos: usize, pos: usize,
@ -98,6 +111,11 @@ pub enum ReadError {
size: usize, size: usize,
}, },
/// Unmatched discriminant found while trying to read an enum /// Unmatched discriminant found while trying to read an enum
#[error(
display = "Unmatched discriminant '{}' found while trying to read enum '{}'",
discriminant,
enum_name
)]
UnmatchedDiscriminant { UnmatchedDiscriminant {
/// The read discriminant /// The read discriminant
discriminant: usize, discriminant: usize,
@ -105,42 +123,24 @@ pub enum ReadError {
enum_name: String, enum_name: String,
}, },
/// The read slice of bytes are not valid utf8 /// The read slice of bytes are not valid utf8
Utf8Error(FromUtf8Error), #[error(display = "The read slice of bytes are not valid utf8: {}", _0)]
} Utf8Error(#[error(source)] FromUtf8Error),
/// The string that was requested to be written does not fit in the specified fixed length
impl Display for ReadError { #[error(
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { display = "The string that was requested to be written does not fit in the specified fixed length, string is {} bytes long, while a size of {} has been specified",
match self { string_length,
ReadError::TooManyBits { requested, max } => requested_length
write!(f, "Too many bits requested to fit in the requested data type, requested to read {} bits while only {} fit in the datatype", requested, max), )]
ReadError::NotEnoughData { requested, bits_left } => StringToLong {
write!(f, "Not enough data in the buffer to read all requested bits, requested to read {} bits while only {} bits are left", requested, bits_left), /// Length of the string that was requested to be written
ReadError::IndexOutOfBounds { pos, size } => string_length: usize,
write!(f, "The requested position is outside the bounds of the stream, requested position {} while the stream or buffer is only {} bits long", pos, size), /// The requested fixed size to encode the string into
ReadError::UnmatchedDiscriminant { discriminant, enum_name } => requested_length: usize,
write!(f, "Unmatched discriminant '{}' found while trying to read enum '{}'", discriminant, enum_name), },
ReadError::Utf8Error(err) => err.fmt(f)
}
}
}
impl From<FromUtf8Error> for ReadError {
fn from(err: FromUtf8Error) -> ReadError {
ReadError::Utf8Error(err)
}
}
impl Error for ReadError {
fn cause(&self) -> Option<&dyn Error> {
match self {
ReadError::Utf8Error(err) => Some(err),
_ => None,
}
}
} }
/// Either the read bits in the requested format or a [`ReadError`](enum.ReadError.html) /// Either the read bits in the requested format or a [`ReadError`](enum.ReadError.html)
pub type Result<T> = std::result::Result<T, ReadError>; pub type Result<T> = std::result::Result<T, BitError>;
/// Get the number of bits required to read a type from stream /// Get the number of bits required to read a type from stream
#[inline(always)] #[inline(always)]

View file

@ -10,7 +10,7 @@ use num_traits::{Float, PrimInt};
use crate::endianness::Endianness; use crate::endianness::Endianness;
use crate::num_traits::{IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt}; use crate::num_traits::{IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt};
use crate::{ReadError, Result}; use crate::{BitError, Result};
use std::convert::TryInto; use std::convert::TryInto;
const USIZE_SIZE: usize = size_of::<usize>(); const USIZE_SIZE: usize = size_of::<usize>();
@ -176,7 +176,7 @@ where
Ok(shifted & 0b1000_0000u8 == 0b1000_0000u8) Ok(shifted & 0b1000_0000u8 == 0b1000_0000u8)
} }
} else { } else {
Err(ReadError::NotEnoughData { Err(BitError::NotEnoughData {
requested: 1, requested: 1,
bits_left: self.bit_len().saturating_sub(position), bits_left: self.bit_len().saturating_sub(position),
}) })
@ -229,7 +229,7 @@ where
let type_bit_size = size_of::<T>() * 8; let type_bit_size = size_of::<T>() * 8;
if type_bit_size < count { if type_bit_size < count {
return Err(ReadError::TooManyBits { return Err(BitError::TooManyBits {
requested: count, requested: count,
max: type_bit_size, max: type_bit_size,
}); });
@ -237,12 +237,12 @@ where
if position + count > self.bit_len() { if position + count > self.bit_len() {
return if position > self.bit_len() { return if position > self.bit_len() {
Err(ReadError::IndexOutOfBounds { Err(BitError::IndexOutOfBounds {
pos: position, pos: position,
size: self.bit_len(), size: self.bit_len(),
}) })
} else { } else {
Err(ReadError::NotEnoughData { Err(BitError::NotEnoughData {
requested: count, requested: count,
bits_left: self.bit_len() - position, bits_left: self.bit_len() - position,
}) })
@ -363,12 +363,12 @@ where
pub fn read_bytes(&self, position: usize, byte_count: usize) -> Result<Vec<u8>> { pub fn read_bytes(&self, position: usize, byte_count: usize) -> Result<Vec<u8>> {
if position + byte_count * 8 > self.bit_len() { if position + byte_count * 8 > self.bit_len() {
if position > self.bit_len() { if position > self.bit_len() {
return Err(ReadError::IndexOutOfBounds { return Err(BitError::IndexOutOfBounds {
pos: position, pos: position,
size: self.bit_len(), size: self.bit_len(),
}); });
} else { } else {
return Err(ReadError::NotEnoughData { return Err(BitError::NotEnoughData {
requested: byte_count * 8, requested: byte_count * 8,
bits_left: self.bit_len() - position, bits_left: self.bit_len() - position,
}); });
@ -453,7 +453,7 @@ where
} }
None => { None => {
let bytes = self.read_string_bytes(position)?; let bytes = self.read_string_bytes(position)?;
String::from_utf8(bytes).map_err(ReadError::from) String::from_utf8(bytes).map_err(BitError::from)
} }
} }
} }
@ -535,12 +535,12 @@ where
let type_bit_size = size_of::<T>() * 8; let type_bit_size = size_of::<T>() * 8;
if position + type_bit_size > self.bit_len() { if position + type_bit_size > self.bit_len() {
if position > self.bit_len() { if position > self.bit_len() {
return Err(ReadError::IndexOutOfBounds { return Err(BitError::IndexOutOfBounds {
pos: position, pos: position,
size: self.bit_len(), size: self.bit_len(),
}); });
} else { } else {
return Err(ReadError::NotEnoughData { return Err(BitError::NotEnoughData {
requested: size_of::<T>() * 8, requested: size_of::<T>() * 8,
bits_left: self.bit_len() - position, bits_left: self.bit_len() - position,
}); });
@ -571,7 +571,7 @@ where
pub(crate) fn get_sub_buffer(&self, bit_len: usize) -> Result<Self> { pub(crate) fn get_sub_buffer(&self, bit_len: usize) -> Result<Self> {
if bit_len > self.bit_len() { if bit_len > self.bit_len() {
return Err(ReadError::NotEnoughData { return Err(BitError::NotEnoughData {
requested: bit_len, requested: bit_len,
bits_left: self.bit_len(), bits_left: self.bit_len(),
}); });

View file

@ -6,7 +6,7 @@ use num_traits::{Float, PrimInt};
use crate::endianness::Endianness; use crate::endianness::Endianness;
use crate::num_traits::{IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt}; use crate::num_traits::{IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt};
use crate::BitReadBuffer; use crate::BitReadBuffer;
use crate::{BitRead, BitReadSized, ReadError, Result}; use crate::{BitError, BitRead, BitReadSized, Result};
use std::cmp::min; use std::cmp::min;
/// Stream that provides an easy way to iterate trough a [`BitBuffer`] /// Stream that provides an easy way to iterate trough a [`BitBuffer`]
@ -303,7 +303,7 @@ where
let result = self.buffer.read_string(self.pos, byte_len).map_err(|err| { let result = self.buffer.read_string(self.pos, byte_len).map_err(|err| {
// still advance the stream on malformed utf8 // still advance the stream on malformed utf8
if let ReadError::Utf8Error(err) = &err { if let BitError::Utf8Error(err) = &err {
self.pos += match byte_len { self.pos += match byte_len {
Some(len) => len * 8, Some(len) => len * 8,
None => min((err.as_bytes().len() + 1) * 8, max_length), None => min((err.as_bytes().len() + 1) * 8, max_length),
@ -408,7 +408,7 @@ where
self.pos += count; self.pos += count;
Ok(()) Ok(())
} else { } else {
Err(ReadError::NotEnoughData { Err(BitError::NotEnoughData {
requested: count, requested: count,
bits_left: self.bits_left(), bits_left: self.bits_left(),
}) })
@ -444,7 +444,7 @@ where
/// [`ReadError::IndexOutOfBounds`]: enum.ReadError.html#variant.IndexOutOfBounds /// [`ReadError::IndexOutOfBounds`]: enum.ReadError.html#variant.IndexOutOfBounds
pub fn set_pos(&mut self, pos: usize) -> Result<()> { pub fn set_pos(&mut self, pos: usize) -> Result<()> {
if pos > self.bit_len() { if pos > self.bit_len() {
return Err(ReadError::IndexOutOfBounds { return Err(BitError::IndexOutOfBounds {
pos, pos,
size: self.bit_len(), size: self.bit_len(),
}); });
@ -642,7 +642,7 @@ where
/// Check if we can read a number of bits from the stream /// Check if we can read a number of bits from the stream
pub fn check_read(&self, count: usize) -> Result<()> { pub fn check_read(&self, count: usize) -> Result<()> {
if self.bits_left() < count { if self.bits_left() < count {
Err(ReadError::NotEnoughData { Err(BitError::NotEnoughData {
requested: count, requested: count,
bits_left: self.bits_left(), bits_left: self.bits_left(),
}) })

View file

@ -6,7 +6,7 @@ use std::ops::{BitOrAssign, BitXor};
use crate::endianness::Endianness; use crate::endianness::Endianness;
use crate::num_traits::{IntoBytes, IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt}; use crate::num_traits::{IntoBytes, IsSigned, UncheckedPrimitiveFloat, UncheckedPrimitiveInt};
use crate::{ReadError, Result}; use crate::{BitError, Result};
const USIZE_SIZE: usize = size_of::<usize>(); const USIZE_SIZE: usize = size_of::<usize>();
const USIZE_BITS: usize = USIZE_SIZE * 8; const USIZE_BITS: usize = USIZE_SIZE * 8;
@ -79,7 +79,6 @@ where
I: ExactSizeIterator, I: ExactSizeIterator,
I: DoubleEndedIterator<Item = u8>, I: DoubleEndedIterator<Item = u8>,
{ {
debug_assert!(bits.len() == count / 8);
let counts = repeat(8) let counts = repeat(8)
.take(bits.len() - 1) .take(bits.len() - 1)
.chain(once(count - (bits.len() - 1) * 8)); .chain(once(count - (bits.len() - 1) * 8));
@ -160,7 +159,7 @@ where
let type_bit_size = size_of::<T>() * 8; let type_bit_size = size_of::<T>() * 8;
if type_bit_size < count { if type_bit_size < count {
return Err(ReadError::TooManyBits { return Err(BitError::TooManyBits {
requested: count, requested: count,
max: type_bit_size, max: type_bit_size,
}); });
@ -213,6 +212,74 @@ where
Ok(()) Ok(())
} }
/// Write a number of bytes into the buffer
///
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut stream = BitWriteStream::new(LittleEndian);
/// stream.write_bytes(&[0, 1, 2 ,3])?;
/// #
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
self.push_non_fit_bits(bytes.iter().copied(), bytes.len() * 8);
Ok(())
}
/// Add a number of padding bytes
fn zero_pad(&mut self, count: usize) {
// since partly written bytes are already 0 padded, we don't need to go trough all the hoop
// of merging the padding bits into the partly written bytes
// (also because x | 0 == x)
self.bytes.resize(self.bytes.len() + count, 0);
self.bit_len += count * 8;
}
/// Write a string into the buffer
///
/// # Examples
///
/// ```
/// # use bitbuffer::{BitReadBuffer, LittleEndian, Result};
/// #
/// # fn main() -> Result<()> {
/// # use bitbuffer::{BitWriteStream, LittleEndian};
///
/// let mut stream = BitWriteStream::new(LittleEndian);
/// stream.write_string("zero terminated string", None)?;
/// stream.write_string("fixed size string, zero padded", Some(64))?;
/// #
/// # Ok(())
/// # }
/// ```
pub fn write_string(&mut self, string: &str, length: Option<usize>) -> Result<()> {
match length {
Some(length) => {
if length < string.len() {
return Err(BitError::StringToLong {
string_length: string.len(),
requested_length: length,
});
}
self.write_bytes(&string.as_bytes())?;
self.zero_pad(length - string.len());
}
None => {
self.write_bytes(&string.as_bytes())?;
self.zero_pad(1);
}
}
Ok(())
}
/// Convert the write buffer into the written bytes /// Convert the write buffer into the written bytes
pub fn finish(self) -> Vec<u8> { pub fn finish(self) -> Vec<u8> {
self.bytes self.bytes

View file

@ -113,3 +113,24 @@ fn test_write_float_be() {
// 0 padded // 0 padded
assert_eq!(false, read.read_bool().unwrap()); assert_eq!(false, read.read_bool().unwrap());
} }
#[test]
fn test_write_string_le() {
let mut stream = BitWriteStream::new(LittleEndian);
stream.write_bool(true).unwrap();
stream.write_string("null terminated", None).unwrap();
stream.write_string("fixed length1", Some(16)).unwrap();
stream.write_string("fixed length2", Some(16)).unwrap();
let data = stream.finish();
let mut read = BitReadStream::from(BitReadBuffer::new(data, LittleEndian));
assert_eq!(true, read.read_bool().unwrap());
assert_eq!("null terminated", read.read_string(None).unwrap());
assert_eq!("fixed length1", read.read_string(Some(16)).unwrap());
assert_eq!("fixed length2", read.read_string(Some(16)).unwrap());
// 0 padded
assert_eq!(false, read.read_bool().unwrap());
}