use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DataType {
Boolean = 0x01,
Integer8 = 0x02,
Integer16 = 0x03,
Integer32 = 0x04,
Unsigned8 = 0x05,
Unsigned16 = 0x06,
Unsigned32 = 0x07,
Real32 = 0x08,
Real64 = 0x11,
Integer64 = 0x15,
Unsigned64 = 0x1B,
}
impl DataType {
pub const fn index(self) -> u16 {
self as u16
}
pub const fn from_index(index: u16) -> Option<Self> {
Some(match index {
0x01 => DataType::Boolean,
0x02 => DataType::Integer8,
0x03 => DataType::Integer16,
0x04 => DataType::Integer32,
0x05 => DataType::Unsigned8,
0x06 => DataType::Unsigned16,
0x07 => DataType::Unsigned32,
0x08 => DataType::Real32,
0x11 => DataType::Real64,
0x15 => DataType::Integer64,
0x1B => DataType::Unsigned64,
_ => return None,
})
}
pub const fn size(self) -> usize {
match self {
DataType::Boolean | DataType::Integer8 | DataType::Unsigned8 => 1,
DataType::Integer16 | DataType::Unsigned16 => 2,
DataType::Integer32 | DataType::Unsigned32 | DataType::Real32 => 4,
DataType::Integer64 | DataType::Unsigned64 | DataType::Real64 => 8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum Value {
Boolean(bool),
Integer8(i8),
Integer16(i16),
Integer32(i32),
Unsigned8(u8),
Unsigned16(u16),
Unsigned32(u32),
Real32(f32),
Real64(f64),
Integer64(i64),
Unsigned64(u64),
}
impl Value {
pub const fn data_type(&self) -> DataType {
match self {
Value::Boolean(_) => DataType::Boolean,
Value::Integer8(_) => DataType::Integer8,
Value::Integer16(_) => DataType::Integer16,
Value::Integer32(_) => DataType::Integer32,
Value::Unsigned8(_) => DataType::Unsigned8,
Value::Unsigned16(_) => DataType::Unsigned16,
Value::Unsigned32(_) => DataType::Unsigned32,
Value::Real32(_) => DataType::Real32,
Value::Real64(_) => DataType::Real64,
Value::Integer64(_) => DataType::Integer64,
Value::Unsigned64(_) => DataType::Unsigned64,
}
}
pub const fn size(&self) -> usize {
self.data_type().size()
}
pub fn encode_le(&self, buf: &mut [u8]) -> Result<usize> {
let n = self.size();
if buf.len() < n {
return Err(Error::BadLength);
}
match self {
Value::Boolean(v) => buf[0] = *v as u8,
Value::Integer8(v) => buf[0] = *v as u8,
Value::Unsigned8(v) => buf[0] = *v,
Value::Integer16(v) => buf[..2].copy_from_slice(&v.to_le_bytes()),
Value::Unsigned16(v) => buf[..2].copy_from_slice(&v.to_le_bytes()),
Value::Integer32(v) => buf[..4].copy_from_slice(&v.to_le_bytes()),
Value::Unsigned32(v) => buf[..4].copy_from_slice(&v.to_le_bytes()),
Value::Real32(v) => buf[..4].copy_from_slice(&v.to_le_bytes()),
Value::Integer64(v) => buf[..8].copy_from_slice(&v.to_le_bytes()),
Value::Unsigned64(v) => buf[..8].copy_from_slice(&v.to_le_bytes()),
Value::Real64(v) => buf[..8].copy_from_slice(&v.to_le_bytes()),
}
Ok(n)
}
pub fn decode_le(data_type: DataType, bytes: &[u8]) -> Result<Value> {
if bytes.len() != data_type.size() {
return Err(Error::BadLength);
}
let v = match data_type {
DataType::Boolean => Value::Boolean(bytes[0] != 0),
DataType::Integer8 => Value::Integer8(bytes[0] as i8),
DataType::Unsigned8 => Value::Unsigned8(bytes[0]),
DataType::Integer16 => Value::Integer16(i16::from_le_bytes([bytes[0], bytes[1]])),
DataType::Unsigned16 => Value::Unsigned16(u16::from_le_bytes([bytes[0], bytes[1]])),
DataType::Integer32 => {
Value::Integer32(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
DataType::Unsigned32 => {
Value::Unsigned32(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
DataType::Real32 => {
Value::Real32(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
DataType::Integer64 => Value::Integer64(i64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])),
DataType::Unsigned64 => Value::Unsigned64(u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])),
DataType::Real64 => Value::Real64(f64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])),
};
Ok(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sizes_match_spec() {
assert_eq!(DataType::Boolean.size(), 1);
assert_eq!(DataType::Unsigned16.size(), 2);
assert_eq!(DataType::Unsigned32.size(), 4);
assert_eq!(DataType::Real64.size(), 8);
}
#[test]
fn data_type_index_roundtrips() {
for dt in [
DataType::Boolean,
DataType::Unsigned32,
DataType::Real64,
DataType::Integer64,
DataType::Unsigned64,
] {
assert_eq!(DataType::from_index(dt.index()), Some(dt));
}
assert_eq!(DataType::Unsigned32.index(), 0x0007);
assert_eq!(DataType::from_index(0x09), None);
}
#[test]
fn u32_is_little_endian() {
let mut buf = [0u8; 8];
let n = Value::Unsigned32(0x1234_5678).encode_le(&mut buf).unwrap();
assert_eq!(n, 4);
assert_eq!(&buf[..4], &[0x78, 0x56, 0x34, 0x12]);
}
#[test]
fn decode_roundtrips() {
let cases = [
Value::Boolean(true),
Value::Integer8(-5),
Value::Unsigned16(0xBEEF),
Value::Integer32(-123456),
Value::Unsigned32(0xDEAD_BEEF),
Value::Unsigned64(0x0102_0304_0506_0708),
];
let mut buf = [0u8; 8];
for v in cases {
let n = v.encode_le(&mut buf).unwrap();
let back = Value::decode_le(v.data_type(), &buf[..n]).unwrap();
assert_eq!(v, back);
}
}
#[test]
fn encode_rejects_short_buffer() {
let mut buf = [0u8; 2];
assert_eq!(
Value::Unsigned32(0).encode_le(&mut buf),
Err(Error::BadLength)
);
}
#[test]
fn decode_rejects_wrong_length() {
assert_eq!(
Value::decode_le(DataType::Unsigned32, &[0, 0]),
Err(Error::BadLength)
);
}
}