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:
parent
bce35aeb32
commit
e43b0b6bb2
7 changed files with 147 additions and 58 deletions
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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()})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
76
src/lib.rs
76
src/lib.rs
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue