//! GATS - Generalized Agile Transport System //! //! This is a format designed to pack complex data structures into compact and //! easily transmissible packets that are platform independent. This format //! supports integers, byte arrays, booleans, lists, dictionaries (with string //! keys), floats, and null. //! //! Integers are stored in a packed format that requires only as many bytes as //! it takes to represent the value. This generally results in a reduction of //! space used, and rarely an increase. //! //! Floating point numbers are architecture independent and stored in a similar //! packed format to present them as accurately as possible in the minimal //! amount of space. //! //! In theory, both the integer and floating point formats are unbound and could //! represent values with any number of bytes, but in reality most //! implementations limit this to 64 bits. //! //! For easy transmission each Gats Value can be serialized as a packet, which //! contains a version header and a total size which makes it easier to read //! from a socket or the like. Each packet can only contain one value, but //! values can be complex types. use std::collections::HashMap; use std::io::{Cursor, Error, ErrorKind, Read, Write}; use std::num::FpCategory; use std::sync::LazyLock; use std::vec::Vec; #[cfg(feature = "tokio")] use tokio::io::{AsyncReadExt,AsyncWriteExt,AsyncRead,AsyncWrite}; /// Represents a value in a Gats structure. #[derive(PartialEq,Debug)] pub enum Value { Integer(i64), ByteString(Vec), Boolean(bool), List(Vec), Dictionary(HashMap), Float(f64), Null, } /// Used by the Value::bytes_to_read function to communicate with the caller /// about their buffer so far. If Estimate is returned then you should /// reallocate, read data, and try again. If final is returned then there is /// no reason to call the estimate any more, you can just be done once you've /// resized and filled the buffer. pub enum BytesToRead { Estimate(usize), Final(usize), } /// Used by the parser internally to handle values in the format that are not /// Values but are needed to parse the data. enum ValueParse { Value(Value), EndMarker, } impl PartialEq for Value { fn eq(&self, other: &i64) -> bool { if let Value::Integer(x) = self { x == other } else { false } } } impl PartialEq> for Value { fn eq(&self, other: &Vec) -> bool { if let Value::ByteString(x) = self { x == other } else { false } } } impl PartialEq<[u8]> for Value { fn eq(&self, other: &[u8]) -> bool { if let Value::ByteString(x) = self { x == other } else { false } } } impl PartialEq for Value { fn eq(&self, other: &bool) -> bool { if let Value::Boolean(x) = self { x == other } else { false } } } impl PartialEq> for Value { fn eq(&self, other: &Vec) -> bool { if let Value::List(x) = self { x == other } else { false } } } impl PartialEq> for Value { fn eq(&self, other: &HashMap) -> bool { if let Value::Dictionary(x) = self { x == other } else { false } } } impl PartialEq for Value { fn eq(&self, other: &f64) -> bool { if let Value::Float(x) = self { x == other } else { false } } } impl std::cmp::Eq for Value {} /// Used for converting floating point numbers to their packed format, /// this isn't a number we can store easily as a literal without a hex format. static FLOAT_DIVISOR: LazyLock = LazyLock::new(|| (256_f64).ln()); /// Internal helper to convert a number into a packed format. fn write_packed_int(x: i64) -> Vec { let mut w = Vec::::with_capacity(10); let mut v = x; let mut bb = if x < 0 { v = -v; 0x40_u8 } else { 0x00_u8 } | (v & 0x3F) as u8 | if v > (v & 0x3F) { 0x80_u8 } else { 0x00_u8 }; w.push(bb); v >>= 6; while v > 0 { bb = (v & 0x7F) as u8 | if v > (v & 0x7F) { 0x80_u8 } else { 0x00_u8 }; w.push(bb); v >>= 7; } w } /// Internal helper to convert a buffer containing a packed integer back into /// an integer. Any extra data will be ignored. fn read_packed_int(r: &mut R) -> Result { let mut out: i64; let mut bb = [0u8]; r.read_exact(&mut bb)?; let negative = (bb[0] & 0x40_u8) == 0x40_u8; out = (bb[0] & 0x3F_u8) as i64; let mut c = 0; while (bb[0] & 0x80_u8) != 0 { r.read_exact(&mut bb)?; out |= ((bb[0] & 0x7F_u8) as i64) << (6 + 7 * c); c += 1; } if negative { Ok(-out) } else { Ok(out) } } impl Value { pub fn new_int( value: i64 ) -> Value { Value::Integer( value ) } pub fn new_byte_string( data: &[u8] ) -> Value { Value::ByteString( data.to_vec() ) } pub fn new_bool( value: bool ) -> Value { Value::Boolean( value ) } pub fn new_list() -> Value { Value::List( Vec::new() ) } pub fn new_dict() -> Value { Value::Dictionary(HashMap::new()) } pub fn dict_from( data: [(String,Value); N] ) -> Value { Value::Dictionary(HashMap::from( data )) } pub fn new_float( value: f64 ) -> Value { Value::Float( value ) } pub fn new_null() -> Value { Value::Null } pub fn is_null(&self) -> bool { matches!(self, Value::Null) } pub fn write(&self, w: &mut W) -> Result<(), Error> { match self { Self::Integer(x) => { let bb = [b'i']; w.write_all(&bb)?; w.write_all(&write_packed_int(*x))?; } Self::ByteString(s) => { let bb = [b's']; w.write_all(&bb)?; w.write_all(&write_packed_int(s.len() as i64))?; w.write_all(s)?; } Self::Boolean(b) => { let mut bb = [0u8]; bb[0] = if *b { b'1' } else { b'0' }; w.write_all(&bb)?; } Self::List(l) => { let mut bb = [b'l']; w.write_all(&bb)?; for v in l { v.write(w)?; } bb[0] = b'e'; w.write_all(&bb)?; } Self::Dictionary(d) => { let mut bb = [b'd']; w.write_all(&bb)?; for (k, v) in d { (Value::ByteString(k.clone().into_bytes())).write(w)?; v.write(w)?; } bb[0] = b'e'; w.write_all(&bb)?; } Self::Float(f) => { match f.classify() { FpCategory::Nan => { let mut bb = [b'F', 0u8]; bb[1] = if f.is_sign_negative() { 'N' } else { 'n' } as u8; w.write_all(&bb)?; } FpCategory::Infinite => { let mut bb = [b'F', 0u8]; bb[1] = if f.is_sign_negative() { 'I' } else { 'i' } as u8; w.write_all(&bb)?; } FpCategory::Zero => { let mut bb = [b'F', 0u8]; bb[1] = if f.is_sign_negative() { 'Z' } else { 'z' } as u8; w.write_all(&bb)?; } FpCategory::Subnormal => { let mut bb = [b'F', 0u8]; // The format doesn't account for these...uh...make them zero? bb[1] = if f.is_sign_negative() { 'Z' } else { 'z' } as u8; w.write_all(&bb)?; } FpCategory::Normal => { let bb = [b'f']; w.write_all(&bb)?; let mut bin = Vec::::with_capacity(10); let (negative, d) = if f.is_sign_negative() { (true, -*f) } else { (false, *f) }; let scale: i64 = (d.ln() / *FLOAT_DIVISOR) as i64; let scale = if scale < 0 { -1 } else { scale }; let mut d = d / 256.0_f64.powf(scale as f64); bin.push(d as u8); d = d.fract(); for _ in 0..15 { d *= 256.0; bin.push(d as u8); d = d.fract(); if d == 0.0 { break; } } if negative { w.write_all(&write_packed_int(-(bin.len() as i64)))?; } else { w.write_all(&write_packed_int(bin.len() as i64))?; } w.write_all(&bin)?; w.write_all(&write_packed_int(scale))?; } } } Self::Null => { let bb = [b'n']; w.write_all(&bb)?; } } Ok(()) } fn read_parse(r: &mut R) -> Result { let it = { let mut it = [0u8]; r.read_exact(&mut it)?; it[0] }; match it as char { '0' => Ok(ValueParse::Value(Value::Boolean(false))), '1' => Ok(ValueParse::Value(Value::Boolean(true))), 'n' => Ok(ValueParse::Value(Value::Null)), 'e' => Ok(ValueParse::EndMarker), 'i' => Ok(ValueParse::Value(Value::Integer(read_packed_int(r)?))), 's' => { let len = read_packed_int(r)?; let mut body = vec![0; len as usize]; r.read_exact(&mut body)?; Ok(ValueParse::Value(Value::ByteString(body))) } 'l' => { let mut body = Vec::::new(); while let ValueParse::Value(v) = Value::read_parse(r)? { body.push(v); } Ok(ValueParse::Value(Value::List(body))) } 'f' => { let (lng, neg) = { let lng = read_packed_int(r)?; if lng < 0 { (-lng, true) } else { (lng, false) } }; let mut value = 0.0f64; let mut buf = vec![0u8; lng as usize]; r.read_exact(&mut buf)?; for b in buf[1..].iter().rev() { value = (value + (*b as f64)) * (1.0 / 256.0); } value += buf[0] as f64; let scale = read_packed_int(r)?; value *= 256.0f64.powi(scale as i32); if neg { Ok(ValueParse::Value(Value::Float(-value))) } else { Ok(ValueParse::Value(Value::Float(value))) } } 'F' => { let st = { let mut st = [0u8]; r.read_exact(&mut st)?; st[0] }; match st as char { 'N' => Ok(ValueParse::Value(Value::Float(f64::NAN))), 'n' => Ok(ValueParse::Value(Value::Float(f64::NAN))), 'I' => Ok(ValueParse::Value(Value::Float(f64::NEG_INFINITY))), 'i' => Ok(ValueParse::Value(Value::Float(f64::INFINITY))), 'Z' => Ok(ValueParse::Value(Value::Float(-0.0f64))), 'z' => Ok(ValueParse::Value(Value::Float(0.0f64))), _ => Err(Error::new( ErrorKind::InvalidData, "Unknown exceptional float subtype found.", )), } } 'd' => { let mut body = HashMap::new(); while let ValueParse::Value(k) = Value::read_parse(r)? { let k = if let Value::ByteString(s) = k { if let Ok(s) = String::from_utf8(s) { s } else { return Err(Error::new( ErrorKind::InvalidData, "Keys must be utf8 compatible strings.", )); } } else { return Err(Error::new( ErrorKind::InvalidData, "Non-string cannot be dictionary key.", )); }; let v = match Value::read_parse(r)? { ValueParse::Value(v) => v, ValueParse::EndMarker => { return Err(Error::new( ErrorKind::InvalidData, "Premature end marker in dictionary.", )); } }; body.insert(k, v); } Ok(ValueParse::Value(Value::Dictionary(body))) } _ => Err(Error::new( ErrorKind::InvalidData, "Invalid data type specified.", )), } } pub fn read(r: &mut R) -> Result { match Value::read_parse(r) { Ok(ValueParse::Value(v)) => Ok(v), Ok(ValueParse::EndMarker) => Err(Error::new( ErrorKind::InvalidData, "Unexpected EndMarker while parsing structure.", )), Err(e) => Err(e), } } pub fn to_packet(&self) -> Result, Error> { let mut body: Vec = vec![1u8, 0u8, 0u8, 0u8, 0u8]; self.write(&mut body)?; let len = body.len() as u32; body[1..5].copy_from_slice(&len.to_be_bytes()); Ok(body) } pub fn write_packet(&self, w: &mut W) -> Result<(), Error> { match self.to_packet() { Ok(body) => w.write_all(&body), Err(e) => Err(e), } } #[cfg(feature = "tokio")] pub async fn write_packet_async(&self, w: &mut W) -> Result<(), Error> where W: AsyncWrite + Unpin { match self.to_packet() { Ok(body) => w.write_all(&body).await, Err(e) => Err(e), } } pub fn bytes_to_read(r: &[u8]) -> Result { if r.len() < 5 { Ok(BytesToRead::Estimate(5)) } else { if r[0] != 1 { return Err(Error::new( ErrorKind::InvalidData, "Invalid Gats packet version.", )); } if let Ok(ar) = r[1..5].try_into() { Ok(BytesToRead::Final(i32::from_be_bytes(ar) as usize)) } else { Err(Error::new( ErrorKind::UnexpectedEof, "Insufficient data presented to decode packet header.", )) } } } pub fn from_packet(r: &[u8]) -> Result { if r.len() < 5 { return Err(Error::new( ErrorKind::UnexpectedEof, "Insufficient data presented to decode packet header.", )); } if r[0] != 1 { return Err(Error::new( ErrorKind::InvalidData, "Invalid Gats packet version.", )); } let size = if let Ok(ar) = r[1..5].try_into() { i32::from_be_bytes(ar) } else { return Err(Error::new( ErrorKind::UnexpectedEof, "Insufficient data presented to decode packet header.", )); }; if r.len() != size as usize { return Err(Error::new( ErrorKind::InvalidData, "Packet buffer is the wrong length.", )); } Value::read(&mut Cursor::new(&r[5..])) } pub fn read_packet(r: &mut R) -> Result { let mut buf = Vec::::new(); let mut fill = 0usize; loop { match Value::bytes_to_read(&buf) { Ok(BytesToRead::Estimate(size)) => { buf.resize(size, 0u8); r.read_exact(&mut buf[fill..])?; fill = buf.len(); } Ok(BytesToRead::Final(size)) => { buf.resize(size, 0u8); r.read_exact(&mut buf[fill..])?; return Value::from_packet(&buf); } Err(e) => { return Err(e); } } } } #[cfg(feature = "tokio")] pub async fn read_packet_async(r: &mut R) -> Result where R: AsyncRead + Unpin { let mut buf = Vec::::new(); let mut fill = 0usize; loop { match Value::bytes_to_read(&buf) { Ok(BytesToRead::Estimate(size)) => { buf.resize(size, 0u8); let amount = r.read(&mut buf[fill..]).await?; fill += amount; } Ok(BytesToRead::Final(size)) => { buf.resize(size, 0u8); let amount = r.read(&mut buf[fill..]).await?; fill += amount; if fill == size { return Value::from_packet(&buf); } } Err(e) => { return Err(e); } } } } } impl From for Value { fn from(v: i64) -> Self { Value::Integer(v) } } impl From for Value { fn from(v: f64) -> Self { Value::Float(v) } } impl From for Value { fn from(v: bool) -> Self { Value::Boolean(v) } } impl From> for Value { fn from(v: Vec) -> Self { Value::ByteString( v ) } } #[cfg(test)] mod tests { use super::*; use core::error::Error; use std::io::Cursor; #[test] fn froms() { let v = Value::from(55); assert!(v == 55); let v = Value::from(987.654); assert!(v == 987.654); let v = Value::from(true); assert!(v == true); let v = Value::from(b"hello".to_vec()); assert!(v == b"hello".to_vec()); } #[test] fn round_int() -> Result<(), Box> { let v = Value::Integer(555666); let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; assert!(v == v2); assert!(v == 555666); Ok(()) } #[test] fn round_str() -> Result<(), Box> { let v = Value::ByteString(vec![1, 1, 2, 3, 5, 8, 13, 21, 34]); let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; let v3 = Value::ByteString(vec![1, 1, 2, 3, 5, 8, 13, 21, 35]); assert!(v == v2); assert!(v != v3); assert!(v == vec![1, 1, 2, 3, 5, 8, 13, 21, 34]); Ok(()) } #[test] fn round_bool_true() -> Result<(), Box> { let v = Value::Boolean(true); let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; let v3 = Value::Boolean(false); assert!(v == v2); assert!(v != v3); assert!(v == true); Ok(()) } #[test] fn round_bool_false() -> Result<(), Box> { let v = Value::Boolean(false); let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; let v3 = Value::Boolean(true); assert!(v == v2); assert!(v != v3); assert!(v == false); Ok(()) } #[test] fn round_list() -> Result<(), Box> { let v = Value::List(vec![ Value::Integer(1), Value::Integer(1), Value::Integer(2), Value::Integer(3), Value::Integer(5), Value::Integer(8), Value::Integer(13), Value::Integer(21), Value::Integer(34), ]); let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; assert!(v == v2); Ok(()) } #[test] fn round_dictionary() -> Result<(), Box> { let v = Value::Dictionary(HashMap::from([ ("bigger-int".to_string(), Value::Integer(98765)), ("neg-int".to_string(), Value::Integer(-98765)), ("integer".to_string(), Value::Integer(44)), ("boolean".to_string(), Value::Boolean(true)), ( "list".to_string(), Value::List(vec![ Value::Integer(1), Value::Integer(1), Value::Integer(2), Value::Integer(3), Value::Integer(5), Value::Integer(8), ]), ), ("null".to_string(), Value::Null), ("float".to_string(), Value::Float(123.456)), ])); let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; assert!(v == v2); Ok(()) } #[test] fn round_null() -> Result<(), Box> { let v = Value::Null; let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; assert!(v == v2); Ok(()) } #[test] fn round_float() -> Result<(), Box> { let v = Value::Float(123.456); let mut buf = Vec::::new(); v.write(&mut buf)?; let v2 = Value::read(&mut Cursor::new(buf))?; assert!(v == v2); Ok(()) } #[test] fn packet_1() -> Result<(), Box> { let v = Value::Dictionary(HashMap::from([ ("bigger-int".to_string(), Value::Integer(98765)), ("neg-int".to_string(), Value::Integer(-98765)), ("integer".to_string(), Value::Integer(44)), ("boolean".to_string(), Value::Boolean(true)), ( "list".to_string(), Value::List(vec![ Value::Integer(1), Value::Integer(1), Value::Integer(2), Value::Integer(3), Value::Integer(5), Value::Integer(8), ]), ), ("null".to_string(), Value::Null), ("float".to_string(), Value::Float(123.456)), ])); let buf = v.to_packet()?; let v2 = Value::from_packet(&buf)?; assert!(v == v2); Ok(()) } #[cfg(feature = "tokio")] #[test] fn async_test() -> Result<(), Box> { Ok(()) } }