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]
num-traits = "0.2"
err-derive = "0.2.2"
bitbuffer_derive = { version = "0.7", path = "bitbuffer_derive" }
memchr = "2.2"

View file

@ -387,7 +387,7 @@ fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool)
Ok(match discriminant {
#(#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)]
use std::error::Error;
use std::fmt;
use std::fmt::Display;
use err_derive::Error;
pub use std::string::FromUtf8Error;
pub use bitbuffer_derive::{BitRead, BitReadSized};
@ -74,9 +72,14 @@ mod readstream;
mod writestream;
/// Errors that can be returned when trying to read from a buffer
#[derive(Debug)]
pub enum ReadError {
#[derive(Debug, Error)]
pub enum BitError {
/// 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 {
/// The number of bits requested to read
requested: usize,
@ -84,6 +87,11 @@ pub enum ReadError {
max: usize,
},
/// 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 {
/// The number of bits requested to read
requested: usize,
@ -91,6 +99,11 @@ pub enum ReadError {
bits_left: usize,
},
/// 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 {
/// The requested position
pos: usize,
@ -98,6 +111,11 @@ pub enum ReadError {
size: usize,
},
/// Unmatched discriminant found while trying to read an enum
#[error(
display = "Unmatched discriminant '{}' found while trying to read enum '{}'",
discriminant,
enum_name
)]
UnmatchedDiscriminant {
/// The read discriminant
discriminant: usize,
@ -105,42 +123,24 @@ pub enum ReadError {
enum_name: String,
},
/// The read slice of bytes are not valid utf8
Utf8Error(FromUtf8Error),
}
impl Display for ReadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ReadError::TooManyBits { requested, max } =>
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 } =>
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),
ReadError::IndexOutOfBounds { pos, size } =>
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),
ReadError::UnmatchedDiscriminant { discriminant, enum_name } =>
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,
}
}
#[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
#[error(
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",
string_length,
requested_length
)]
StringToLong {
/// Length of the string that was requested to be written
string_length: usize,
/// The requested fixed size to encode the string into
requested_length: usize,
},
}
/// 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
#[inline(always)]

View file

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

View file

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

View file

@ -6,7 +6,7 @@ use std::ops::{BitOrAssign, BitXor};
use crate::endianness::Endianness;
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_BITS: usize = USIZE_SIZE * 8;
@ -79,7 +79,6 @@ where
I: ExactSizeIterator,
I: DoubleEndedIterator<Item = u8>,
{
debug_assert!(bits.len() == count / 8);
let counts = repeat(8)
.take(bits.len() - 1)
.chain(once(count - (bits.len() - 1) * 8));
@ -160,7 +159,7 @@ where
let type_bit_size = size_of::<T>() * 8;
if type_bit_size < count {
return Err(ReadError::TooManyBits {
return Err(BitError::TooManyBits {
requested: count,
max: type_bit_size,
});
@ -213,6 +212,74 @@ where
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
pub fn finish(self) -> Vec<u8> {
self.bytes

View file

@ -113,3 +113,24 @@ fn test_write_float_be() {
// 0 padded
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());
}