mirror of
https://github.com/Noratrieb/haesli.git
synced 2026-01-16 20:55:03 +01:00
rename classes to methods
This commit is contained in:
parent
9b48dec533
commit
7ce8f8058d
8 changed files with 26 additions and 26 deletions
2507
amqp_transport/src/methods/generated.rs
Normal file
2507
amqp_transport/src/methods/generated.rs
Normal file
File diff suppressed because it is too large
Load diff
58
amqp_transport/src/methods/mod.rs
Normal file
58
amqp_transport/src/methods/mod.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::error::{ConException, TransError};
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod generated;
|
||||
mod parse_helper;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod write_helper;
|
||||
|
||||
pub type TableFieldName = String;
|
||||
|
||||
pub type Table = HashMap<TableFieldName, FieldValue>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FieldValue {
|
||||
Boolean(bool),
|
||||
ShortShortInt(i8),
|
||||
ShortShortUInt(u8),
|
||||
ShortInt(i16),
|
||||
ShortUInt(u16),
|
||||
LongInt(i32),
|
||||
LongUInt(u32),
|
||||
LongLongInt(i64),
|
||||
LongLongUInt(u64),
|
||||
Float(f32),
|
||||
Double(f64),
|
||||
DecimalValue(u8, u32),
|
||||
ShortString(Shortstr),
|
||||
LongString(Longstr),
|
||||
FieldArray(Vec<FieldValue>),
|
||||
Timestamp(u64),
|
||||
FieldTable(Table),
|
||||
Void,
|
||||
}
|
||||
|
||||
pub use generated::*;
|
||||
|
||||
/// Parses the payload of a method frame into the class/method
|
||||
pub fn parse_method(payload: &[u8]) -> Result<generated::Method, TransError> {
|
||||
let nom_result = generated::parse::parse_method(payload);
|
||||
|
||||
match nom_result {
|
||||
Ok(([], class)) => Ok(class),
|
||||
Ok((_, _)) => {
|
||||
Err(
|
||||
ConException::SyntaxError(vec!["could not consume all input".to_string()])
|
||||
.into_trans(),
|
||||
)
|
||||
}
|
||||
Err(nom::Err::Incomplete(_)) => {
|
||||
Err(
|
||||
ConException::SyntaxError(vec!["there was not enough data".to_string()])
|
||||
.into_trans(),
|
||||
)
|
||||
}
|
||||
Err(nom::Err::Failure(err) | nom::Err::Error(err)) => Err(err),
|
||||
}
|
||||
}
|
||||
254
amqp_transport/src/methods/parse_helper.rs
Normal file
254
amqp_transport/src/methods/parse_helper.rs
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
use crate::error::{ConException, ProtocolError, TransError};
|
||||
use crate::methods::generated::parse::IResult;
|
||||
use crate::methods::generated::{
|
||||
Bit, Long, Longlong, Longstr, Octet, Short, Shortstr, Table, Timestamp,
|
||||
};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{tag, take};
|
||||
use nom::error::ErrorKind;
|
||||
use nom::multi::{count, many0};
|
||||
use nom::number::complete::{f32, f64, i16, i32, i64, i8, u16, u32, u64, u8};
|
||||
use nom::number::Endianness::Big;
|
||||
use nom::Err;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// todo: remove the debug machinery or change it in a way that actually does what it should lmao
|
||||
// I'm probably misusing nom hard
|
||||
|
||||
impl<T> nom::error::ParseError<T> for TransError {
|
||||
fn from_error_kind(_input: T, _kind: ErrorKind) -> Self {
|
||||
ConException::SyntaxError(vec![]).into_trans()
|
||||
}
|
||||
|
||||
fn append(_input: T, _kind: ErrorKind, other: Self) -> Self {
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fail_err<S: Into<String>>(msg: S) -> impl FnOnce(Err<TransError>) -> Err<TransError> {
|
||||
move |err| {
|
||||
let error_level = if matches!(err, nom::Err::Failure(_)) {
|
||||
Err::Failure
|
||||
} else {
|
||||
Err::Error
|
||||
};
|
||||
|
||||
let msg = msg.into();
|
||||
let stack = match err {
|
||||
Err::Error(e) | Err::Failure(e) => match e {
|
||||
TransError::Invalid(ProtocolError::ConException(ConException::SyntaxError(
|
||||
mut stack,
|
||||
))) => {
|
||||
stack.push(msg);
|
||||
stack
|
||||
}
|
||||
_ => vec![msg],
|
||||
},
|
||||
_ => vec![msg],
|
||||
};
|
||||
error_level(ConException::SyntaxError(stack).into_trans())
|
||||
}
|
||||
}
|
||||
pub fn err_other<E, S: Into<String>>(msg: S) -> impl FnOnce(E) -> Err<TransError> {
|
||||
move |_| Err::Error(ConException::SyntaxError(vec![msg.into()]).into_trans())
|
||||
}
|
||||
|
||||
pub fn failure<E>(err: Err<E>) -> Err<E> {
|
||||
match err {
|
||||
Err::Incomplete(needed) => Err::Incomplete(needed),
|
||||
Err::Error(e) => Err::Failure(e),
|
||||
Err::Failure(e) => Err::Failure(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fail {
|
||||
($cause:expr) => {
|
||||
return Err(nom::Err::Failure(
|
||||
crate::error::ProtocolError::ConException(crate::error::ConException::SyntaxError(
|
||||
vec![String::from($cause)],
|
||||
))
|
||||
.into(),
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
use crate::methods::{FieldValue, TableFieldName};
|
||||
pub use fail;
|
||||
|
||||
pub fn octet(input: &[u8]) -> IResult<'_, Octet> {
|
||||
u8(input)
|
||||
}
|
||||
|
||||
pub fn short(input: &[u8]) -> IResult<'_, Short> {
|
||||
u16(Big)(input)
|
||||
}
|
||||
|
||||
pub fn long(input: &[u8]) -> IResult<'_, Long> {
|
||||
u32(Big)(input)
|
||||
}
|
||||
|
||||
pub fn longlong(input: &[u8]) -> IResult<'_, Longlong> {
|
||||
u64(Big)(input)
|
||||
}
|
||||
|
||||
pub fn bit(input: &[u8], amount: usize) -> IResult<'_, Vec<Bit>> {
|
||||
let octets = (amount + 7) / 8;
|
||||
let (input, bytes) = take(octets)(input)?;
|
||||
|
||||
let mut vec = Vec::new();
|
||||
let mut byte_index = 0;
|
||||
let mut total_index = 0;
|
||||
|
||||
for &byte in bytes {
|
||||
while byte_index < 8 && total_index < amount {
|
||||
let next_bit = 1 & (byte >> byte_index);
|
||||
let bit_bool = match next_bit {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
vec.push(bit_bool);
|
||||
byte_index += 1;
|
||||
total_index += 1;
|
||||
}
|
||||
byte_index = 0;
|
||||
}
|
||||
|
||||
Ok((input, vec))
|
||||
}
|
||||
|
||||
pub fn shortstr(input: &[u8]) -> IResult<'_, Shortstr> {
|
||||
let (input, len) = u8(input)?;
|
||||
let (input, str_data) = take(usize::from(len))(input)?;
|
||||
let data = String::from_utf8(str_data.into()).map_err(err_other("shortstr"))?;
|
||||
Ok((input, data))
|
||||
}
|
||||
|
||||
pub fn longstr(input: &[u8]) -> IResult<'_, Longstr> {
|
||||
let (input, len) = u32(Big)(input)?;
|
||||
let (input, str_data) = take(usize::try_from(len).unwrap())(input)?;
|
||||
let data = str_data.into();
|
||||
Ok((input, data))
|
||||
}
|
||||
|
||||
pub fn timestamp(input: &[u8]) -> IResult<'_, Timestamp> {
|
||||
u64(Big)(input)
|
||||
}
|
||||
|
||||
pub fn table(input: &[u8]) -> IResult<'_, Table> {
|
||||
let (input, size) = u32(Big)(input)?;
|
||||
let (table_input, rest_input) = input.split_at(size.try_into().unwrap());
|
||||
|
||||
let (input, values) = many0(table_value_pair)(table_input)?;
|
||||
|
||||
if !input.is_empty() {
|
||||
fail!(format!(
|
||||
"table longer than expected, expected = {size}, remaining = {}",
|
||||
input.len()
|
||||
));
|
||||
}
|
||||
|
||||
let table = HashMap::from_iter(values.into_iter());
|
||||
Ok((rest_input, table))
|
||||
}
|
||||
|
||||
fn table_value_pair(input: &[u8]) -> IResult<'_, (TableFieldName, FieldValue)> {
|
||||
let (input, field_name) = shortstr(input)?;
|
||||
let (input, field_value) =
|
||||
field_value(input).map_err(fail_err(format!("field {field_name}")))?;
|
||||
Ok((input, (field_name, field_value)))
|
||||
}
|
||||
|
||||
fn field_value(input: &[u8]) -> IResult<'_, FieldValue> {
|
||||
type R<'a> = IResult<'a, FieldValue>;
|
||||
|
||||
fn boolean(input: &[u8]) -> R<'_> {
|
||||
let (input, _) = tag(b"t")(input)?;
|
||||
let (input, bool_byte) = u8(input)?;
|
||||
match bool_byte {
|
||||
0 => Ok((input, FieldValue::Boolean(false))),
|
||||
1 => Ok((input, FieldValue::Boolean(true))),
|
||||
value => fail!(format!("invalid bool value {value}")),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! number {
|
||||
($tag:literal, $name:ident, $comb:expr, $value:ident, $r:path) => {
|
||||
fn $name(input: &[u8]) -> $r {
|
||||
let (input, _) = tag($tag)(input)?;
|
||||
$comb(input).map(|(input, int)| (input, FieldValue::$value(int)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
number!(b"b", short_short_int, i8, ShortShortInt, R<'_>);
|
||||
number!(b"B", short_short_uint, u8, ShortShortUInt, R<'_>);
|
||||
number!(b"U", short_int, i16(Big), ShortInt, R<'_>);
|
||||
number!(b"u", short_uint, u16(Big), ShortUInt, R<'_>);
|
||||
number!(b"I", long_int, i32(Big), LongInt, R<'_>);
|
||||
number!(b"i", long_uint, u32(Big), LongUInt, R<'_>);
|
||||
number!(b"L", long_long_int, i64(Big), LongLongInt, R<'_>);
|
||||
number!(b"l", long_long_uint, u64(Big), LongLongUInt, R<'_>);
|
||||
number!(b"f", float, f32(Big), Float, R<'_>);
|
||||
number!(b"d", double, f64(Big), Double, R<'_>);
|
||||
|
||||
fn decimal(input: &[u8]) -> R<'_> {
|
||||
let (input, _) = tag("D")(input)?;
|
||||
let (input, scale) = u8(input)?;
|
||||
let (input, value) = u32(Big)(input)?;
|
||||
Ok((input, FieldValue::DecimalValue(scale, value)))
|
||||
}
|
||||
|
||||
fn short_str(input: &[u8]) -> R<'_> {
|
||||
let (input, _) = tag("s")(input)?;
|
||||
let (input, str) = shortstr(input)?;
|
||||
Ok((input, FieldValue::ShortString(str)))
|
||||
}
|
||||
|
||||
fn long_str(input: &[u8]) -> R<'_> {
|
||||
let (input, _) = tag("S")(input)?;
|
||||
let (input, str) = longstr(input)?;
|
||||
Ok((input, FieldValue::LongString(str)))
|
||||
}
|
||||
|
||||
fn field_array(input: &[u8]) -> R<'_> {
|
||||
let (input, _) = tag("A")(input)?;
|
||||
// todo is it i32?
|
||||
let (input, len) = u32(Big)(input)?;
|
||||
count(field_value, usize::try_from(len).unwrap())(input)
|
||||
.map(|(input, value)| (input, FieldValue::FieldArray(value)))
|
||||
}
|
||||
|
||||
number!(b"T", timestamp, u64(Big), Timestamp, R<'_>);
|
||||
|
||||
fn field_table(input: &[u8]) -> R<'_> {
|
||||
let (input, _) = tag("F")(input)?;
|
||||
table(input).map(|(input, value)| (input, FieldValue::FieldTable(value)))
|
||||
}
|
||||
|
||||
fn void(input: &[u8]) -> R<'_> {
|
||||
tag("V")(input).map(|(input, _)| (input, FieldValue::Void))
|
||||
}
|
||||
|
||||
alt((
|
||||
boolean,
|
||||
short_short_int,
|
||||
short_short_uint,
|
||||
short_int,
|
||||
short_uint,
|
||||
long_int,
|
||||
long_uint,
|
||||
long_long_int,
|
||||
long_long_uint,
|
||||
float,
|
||||
double,
|
||||
decimal,
|
||||
short_str,
|
||||
long_str,
|
||||
field_array,
|
||||
timestamp,
|
||||
field_table,
|
||||
void,
|
||||
))(input)
|
||||
}
|
||||
148
amqp_transport/src/methods/tests.rs
Normal file
148
amqp_transport/src/methods/tests.rs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// create random methods to test the ser/de code together. if they diverge, we have a bug
|
||||
// this is not perfect, if they both have the same bug it won't be found, but tha's an ok tradeoff
|
||||
|
||||
use crate::methods::{FieldValue, Method};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Allows the creation of a random instance of that type
|
||||
pub(crate) trait RandomMethod<R: Rng> {
|
||||
fn random(rng: &mut R) -> Self;
|
||||
}
|
||||
|
||||
impl<R: Rng> RandomMethod<R> for String {
|
||||
fn random(rng: &mut R) -> Self {
|
||||
let n = rng.gen_range(0_u16..9999);
|
||||
format!("string{n}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Rng, T: RandomMethod<R>> RandomMethod<R> for Vec<T> {
|
||||
fn random(rng: &mut R) -> Self {
|
||||
let len = rng.gen_range(1_usize..10);
|
||||
let mut vec = Vec::with_capacity(len);
|
||||
(0..len).for_each(|_| vec.push(RandomMethod::random(rng)));
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! rand_random_method {
|
||||
($($ty:ty),+) => {
|
||||
$(
|
||||
impl<R: Rng> RandomMethod<R> for $ty {
|
||||
fn random(rng: &mut R) -> Self {
|
||||
rng.gen()
|
||||
}
|
||||
})+
|
||||
};
|
||||
}
|
||||
|
||||
rand_random_method!(bool, u8, i8, u16, i16, u32, i32, u64, i64, f32, f64);
|
||||
|
||||
impl<R: Rng> RandomMethod<R> for HashMap<String, FieldValue> {
|
||||
fn random(rng: &mut R) -> Self {
|
||||
let len = rng.gen_range(0..3);
|
||||
HashMap::from_iter((0..len).map(|_| (String::random(rng), FieldValue::random(rng))))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Rng> RandomMethod<R> for FieldValue {
|
||||
fn random(rng: &mut R) -> Self {
|
||||
let index = rng.gen_range(0_u32..17);
|
||||
match index {
|
||||
0 => FieldValue::Boolean(RandomMethod::random(rng)),
|
||||
1 => FieldValue::ShortShortInt(RandomMethod::random(rng)),
|
||||
2 => FieldValue::ShortShortUInt(RandomMethod::random(rng)),
|
||||
3 => FieldValue::ShortInt(RandomMethod::random(rng)),
|
||||
4 => FieldValue::ShortUInt(RandomMethod::random(rng)),
|
||||
5 => FieldValue::LongInt(RandomMethod::random(rng)),
|
||||
6 => FieldValue::LongUInt(RandomMethod::random(rng)),
|
||||
7 => FieldValue::LongLongInt(RandomMethod::random(rng)),
|
||||
8 => FieldValue::LongLongUInt(RandomMethod::random(rng)),
|
||||
9 => FieldValue::Float(RandomMethod::random(rng)),
|
||||
10 => FieldValue::Double(RandomMethod::random(rng)),
|
||||
11 => FieldValue::ShortString(RandomMethod::random(rng)),
|
||||
12 => FieldValue::LongString(RandomMethod::random(rng)),
|
||||
13 => FieldValue::FieldArray(RandomMethod::random(rng)),
|
||||
14 => FieldValue::Timestamp(RandomMethod::random(rng)),
|
||||
15 => FieldValue::FieldTable(RandomMethod::random(rng)),
|
||||
16 => FieldValue::Void,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_few_bits() {
|
||||
let bits = [true, false, true];
|
||||
|
||||
let mut buffer = [0u8; 2];
|
||||
super::write_helper::bit(&bits, &mut buffer.as_mut_slice()).unwrap();
|
||||
|
||||
let (_, parsed_bits) = super::parse_helper::bit(&buffer, 3).unwrap();
|
||||
assert_eq!(bits.as_slice(), parsed_bits.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_many_bits() {
|
||||
let bits = [
|
||||
/* first 8 */
|
||||
true, true, true, true, false, false, false, false, /* second 4 */
|
||||
true, false, true, true,
|
||||
];
|
||||
let mut buffer = [0u8; 2];
|
||||
super::write_helper::bit(&bits, &mut buffer.as_mut_slice()).unwrap();
|
||||
|
||||
let (_, parsed_bits) = super::parse_helper::bit(&buffer, 12).unwrap();
|
||||
assert_eq!(bits.as_slice(), parsed_bits.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_ser_de() {
|
||||
const ITERATIONS: usize = 1000;
|
||||
let mut rng = rand::rngs::StdRng::from_seed([0; 32]);
|
||||
|
||||
for _ in 0..ITERATIONS {
|
||||
let class = Method::random(&mut rng);
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
if let Err(err) = super::write::write_method(class.clone(), &mut bytes) {
|
||||
eprintln!("{class:#?}");
|
||||
eprintln!("{err:?}");
|
||||
panic!("Failed to serialize");
|
||||
}
|
||||
|
||||
match super::parse_method(&bytes) {
|
||||
Ok(parsed) => {
|
||||
assert_eq!(class, parsed);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{class:#?}");
|
||||
eprintln!("{bytes:?}");
|
||||
eprintln!("{err:?}");
|
||||
panic!("Failed to deserialize");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_table() {
|
||||
let table = HashMap::from([(
|
||||
"A".to_string(),
|
||||
FieldValue::FieldTable(HashMap::from([(
|
||||
"B".to_string(),
|
||||
FieldValue::Boolean(true),
|
||||
)])),
|
||||
)]);
|
||||
eprintln!("{table:?}");
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
crate::methods::write_helper::table(table.clone(), &mut bytes).unwrap();
|
||||
eprintln!("{bytes:?}");
|
||||
|
||||
let (rest, parsed_table) = crate::methods::parse_helper::table(&bytes).unwrap();
|
||||
|
||||
assert!(rest.is_empty());
|
||||
assert_eq!(table, parsed_table);
|
||||
}
|
||||
198
amqp_transport/src/methods/write_helper.rs
Normal file
198
amqp_transport/src/methods/write_helper.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
use crate::error::TransError;
|
||||
use crate::methods::generated::{
|
||||
Bit, Long, Longlong, Longstr, Octet, Short, Shortstr, Table, Timestamp,
|
||||
};
|
||||
use crate::methods::FieldValue;
|
||||
use anyhow::Context;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn octet<W: Write>(value: Octet, writer: &mut W) -> Result<(), TransError> {
|
||||
writer.write_all(&[value])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn short<W: Write>(value: Short, writer: &mut W) -> Result<(), TransError> {
|
||||
writer.write_all(&value.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn long<W: Write>(value: Long, writer: &mut W) -> Result<(), TransError> {
|
||||
writer.write_all(&value.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn longlong<W: Write>(value: Longlong, writer: &mut W) -> Result<(), TransError> {
|
||||
writer.write_all(&value.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bit<W: Write>(value: &[Bit], writer: &mut W) -> Result<(), TransError> {
|
||||
// accumulate bits into bytes, starting from the least significant bit in each byte
|
||||
|
||||
// how many bits have already been packed into `current_buf`
|
||||
let mut already_filled = 0;
|
||||
let mut current_buf = 0u8;
|
||||
|
||||
for &bit in value {
|
||||
if already_filled >= 8 {
|
||||
writer.write_all(&[current_buf])?;
|
||||
current_buf = 0;
|
||||
already_filled = 0;
|
||||
}
|
||||
|
||||
let new_bit = (u8::from(bit)) << already_filled;
|
||||
current_buf |= new_bit;
|
||||
already_filled += 1;
|
||||
}
|
||||
|
||||
if already_filled > 0 {
|
||||
writer.write_all(&[current_buf])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn shortstr<W: Write>(value: Shortstr, writer: &mut W) -> Result<(), TransError> {
|
||||
let len = u8::try_from(value.len()).context("shortstr too long")?;
|
||||
writer.write_all(&[len])?;
|
||||
writer.write_all(value.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn longstr<W: Write>(value: Longstr, writer: &mut W) -> Result<(), TransError> {
|
||||
let len = u32::try_from(value.len()).context("longstr too long")?;
|
||||
writer.write_all(&len.to_be_bytes())?;
|
||||
writer.write_all(value.as_slice())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// this appears to be unused right now, but it could be used in `Basic` things?
|
||||
#[allow(dead_code)]
|
||||
pub fn timestamp<W: Write>(value: Timestamp, writer: &mut W) -> Result<(), TransError> {
|
||||
writer.write_all(&value.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn table<W: Write>(table: Table, writer: &mut W) -> Result<(), TransError> {
|
||||
let mut table_buf = Vec::new();
|
||||
|
||||
for (field_name, value) in table {
|
||||
shortstr(field_name, &mut table_buf)?;
|
||||
field_value(value, &mut table_buf)?;
|
||||
}
|
||||
|
||||
let len = u32::try_from(table_buf.len()).context("table too big")?;
|
||||
writer.write_all(&len.to_be_bytes())?;
|
||||
writer.write_all(&table_buf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn field_value<W: Write>(value: FieldValue, writer: &mut W) -> Result<(), TransError> {
|
||||
match value {
|
||||
FieldValue::Boolean(bool) => {
|
||||
writer.write_all(&[b't', u8::from(bool)])?;
|
||||
}
|
||||
FieldValue::ShortShortInt(int) => {
|
||||
writer.write_all(b"b")?;
|
||||
writer.write_all(&int.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::ShortShortUInt(int) => {
|
||||
writer.write_all(&[b'B', int])?;
|
||||
}
|
||||
FieldValue::ShortInt(int) => {
|
||||
writer.write_all(b"U")?;
|
||||
writer.write_all(&int.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::ShortUInt(int) => {
|
||||
writer.write_all(b"u")?;
|
||||
writer.write_all(&int.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::LongInt(int) => {
|
||||
writer.write_all(b"I")?;
|
||||
writer.write_all(&int.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::LongUInt(int) => {
|
||||
writer.write_all(b"i")?;
|
||||
writer.write_all(&int.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::LongLongInt(int) => {
|
||||
writer.write_all(b"L")?;
|
||||
writer.write_all(&int.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::LongLongUInt(int) => {
|
||||
writer.write_all(b"l")?;
|
||||
writer.write_all(&int.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::Float(float) => {
|
||||
writer.write_all(b"f")?;
|
||||
writer.write_all(&float.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::Double(float) => {
|
||||
writer.write_all(b"d")?;
|
||||
writer.write_all(&float.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::DecimalValue(scale, long) => {
|
||||
writer.write_all(&[b'D', scale])?;
|
||||
writer.write_all(&long.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::ShortString(str) => {
|
||||
writer.write_all(b"s")?;
|
||||
shortstr(str, writer)?;
|
||||
}
|
||||
FieldValue::LongString(str) => {
|
||||
writer.write_all(b"S")?;
|
||||
longstr(str, writer)?;
|
||||
}
|
||||
FieldValue::FieldArray(array) => {
|
||||
writer.write_all(b"A")?;
|
||||
let len = u32::try_from(array.len()).context("array too long")?;
|
||||
writer.write_all(&len.to_be_bytes())?;
|
||||
|
||||
for element in array {
|
||||
field_value(element, writer)?;
|
||||
}
|
||||
}
|
||||
FieldValue::Timestamp(time) => {
|
||||
writer.write_all(b"T")?;
|
||||
writer.write_all(&time.to_be_bytes())?;
|
||||
}
|
||||
FieldValue::FieldTable(value) => {
|
||||
writer.write_all(b"F")?;
|
||||
table(value, writer)?;
|
||||
}
|
||||
FieldValue::Void => {
|
||||
writer.write_all(b"V")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn pack_few_bits() {
|
||||
let bits = [true, false, true];
|
||||
|
||||
let mut buffer = [0u8; 1];
|
||||
super::bit(&bits, &mut buffer.as_mut_slice()).unwrap();
|
||||
|
||||
assert_eq!(buffer, [0b00000101])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_many_bits() {
|
||||
let bits = [
|
||||
/* first 8 */
|
||||
true, true, true, true, false, false, false, false, /* second 4 */
|
||||
true, false, true, true,
|
||||
];
|
||||
|
||||
let mut buffer = [0u8; 2];
|
||||
super::bit(&bits, &mut buffer.as_mut_slice()).unwrap();
|
||||
|
||||
assert_eq!(buffer, [0b00001111, 0b00001101]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue