From 8c5b9963e3467434777eeb104111478e39190384 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Wed, 19 Nov 2025 14:25:48 -0800 Subject: Added a bunch of docs, helpers and test functions. Also formatted. --- rust/src/lib.rs | 479 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 311 insertions(+), 168 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 88d3410..2ccc2cf 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,9 +1,34 @@ +//! 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::vec::Vec; -use std::io::{Write,Read,Cursor,Error,ErrorKind}; +use std::io::{Cursor, Error, ErrorKind, Read, Write}; use std::num::FpCategory; use std::sync::LazyLock; +use std::vec::Vec; +/// Represents a value in a Gats structure. #[derive(PartialEq)] pub enum Value { Integer(i64), @@ -12,14 +37,21 @@ pub enum Value { List(Vec), Dictionary(HashMap), Float(f64), - Nothing, + 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, @@ -55,21 +87,53 @@ impl PartialEq for Value { } } -impl std::cmp::Eq for Value { +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()); -fn write_packed_int( x: i64 ) -> Vec -{ +/// 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 }; + 0x00_u8 + } | (v & 0x3F) as u8 + | if v > (v & 0x3F) { 0x80_u8 } else { 0x00_u8 }; w.push(bb); v >>= 6; while v > 0 { @@ -81,119 +145,125 @@ fn write_packed_int( x: i64 ) -> Vec w } -fn read_packed_int( r: &mut R ) -> Result { - let mut out : i64; +/// 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; + 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); + 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) - } + if negative { Ok(-out) } else { Ok(out) } } impl Value { + 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 ))?; - }, + 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 )?; - }, + 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 )?; - }, + w.write_all(&bb)?; + } Self::List(l) => { let mut bb = [b'l']; w.write_all(&bb)?; for v in l { - v.write( w )?; + 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 )?; + (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 )?; - }, + 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 )?; - }, + 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 )?; - }, + 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 )?; - }, + w.write_all(&bb)?; + } FpCategory::Normal => { let bb = [b'f']; - w.write_all( &bb )?; + 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 (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 ); + 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 ); + 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) ) )?; + w.write_all(&write_packed_int(-(bin.len() as i64)))?; } else { - w.write_all( &write_packed_int( bin.len() as i64 ) )?; + w.write_all(&write_packed_int(bin.len() as i64))?; } - w.write_all( &bin )?; - w.write_all( &write_packed_int( scale ) )?; - }, + w.write_all(&bin)?; + w.write_all(&write_packed_int(scale))?; + } } - }, - Self::Nothing => { + } + Self::Null => { let bb = [b'n']; - w.write_all( &bb )?; - }, + w.write_all(&bb)?; + } } Ok(()) } @@ -201,19 +271,19 @@ impl Value { fn read_parse(r: &mut R) -> Result { let it = { let mut it = [0u8]; - r.read_exact( &mut it )?; + 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::Nothing)), + '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 )?; + r.read_exact(&mut body)?; Ok(ValueParse::Value(Value::ByteString(body))) } 'l' => { @@ -225,22 +295,18 @@ impl Value { } 'f' => { let (lng, neg) = { - let lng = read_packed_int( r )?; - if lng < 0 { - (-lng, true) - } else { - (lng, false) - } + 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 )?; + r.read_exact(&mut buf)?; for b in buf[1..].iter().rev() { - value = (value+(*b as f64))*(1.0/256.0); + 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 ); + let scale = read_packed_int(r)?; + value *= 256.0f64.powi(scale as i32); if neg { Ok(ValueParse::Value(Value::Float(-value))) } else { @@ -250,7 +316,7 @@ impl Value { 'F' => { let st = { let mut st = [0u8]; - r.read_exact( &mut st )?; + r.read_exact(&mut st)?; st[0] }; match st as char { @@ -260,7 +326,10 @@ impl Value { '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.")), + _ => Err(Error::new( + ErrorKind::InvalidData, + "Unknown exceptional float subtype found.", + )), } } 'd' => { @@ -270,102 +339,128 @@ impl Value { if let Ok(s) = String::from_utf8(s) { s } else { - return Err(Error::new(ErrorKind::InvalidData, "Keys must be utf8 compatbile strings.")); + 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.")); + 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.")); + return Err(Error::new( + ErrorKind::InvalidData, + "Premature end marker in dictionary.", + )); } }; - body.insert( k, v ); + body.insert(k, v); } Ok(ValueParse::Value(Value::Dictionary(body))) } - _ => { - Err(Error::new(ErrorKind::InvalidData, "Invalid data type specified.")) - } + _ => 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, "Unexpecetd EndMarker while parsing structure.")), - Err(e) => Err(e) + 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 )?; + 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() ); + body[1..5].copy_from_slice(&len.to_be_bytes()); Ok(body) } - pub fn write_packet( &self, w: &mut W ) -> Result<(), Error> { + pub fn write_packet(&self, w: &mut W) -> Result<(), Error> { match self.to_packet() { - Ok(body) => { - w.write_all( &body ) - } - Err(e) => { - Err(e) - } + Ok(body) => w.write_all(&body), + Err(e) => Err(e), } } - pub fn bytes_to_read( r: &[u8] ) -> Result { + 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.")); + 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)) + Ok(BytesToRead::Final(i32::from_be_bytes(ar) as usize)) } else { - Err(Error::new(ErrorKind::UnexpectedEof, "Insufficient data presented to decode packet header.")) + Err(Error::new( + ErrorKind::UnexpectedEof, + "Insufficient data presented to decode packet header.", + )) } } } - pub fn from_packet( r: &[u8] ) -> Result { + pub fn from_packet(r: &[u8]) -> Result { if r.len() < 5 { - return Err(Error::new(ErrorKind::UnexpectedEof, "Insufficient data presented to decode packet header.")); + 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.")); + 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 ) + i32::from_be_bytes(ar) } else { - return Err(Error::new(ErrorKind::UnexpectedEof, "Insufficient data presented to decode packet header.")); + 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.")); + return Err(Error::new( + ErrorKind::InvalidData, + "Packet buffer is the wrong length.", + )); } - Value::read( &mut Cursor::new( &r[5..] ) ) + Value::read(&mut Cursor::new(&r[5..])) } - pub fn read_packet( r: &mut R ) -> Result { + pub fn read_packet(r: &mut R) -> Result { let mut buf = Vec::::new(); let mut fill = 0usize; loop { - match Value::bytes_to_read( &buf ) { + match Value::bytes_to_read(&buf) { Ok(BytesToRead::Estimate(size)) => { buf.resize(size, 0u8); - r.read_exact( &mut buf[fill..] )?; + r.read_exact(&mut buf[fill..])?; fill = buf.len(); } Ok(BytesToRead::Final(size)) => { buf.resize(size, 0u8); - r.read_exact( &mut buf[fill..] )?; - //fill = buf.len(); - return Value::from_packet( &buf ); + r.read_exact(&mut buf[fill..])?; + return Value::from_packet(&buf); } Err(e) => { return Err(e); @@ -375,6 +470,24 @@ impl Value { } } +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) + } +} + #[cfg(test)] mod tests { use super::*; @@ -383,97 +496,120 @@ mod tests { use std::io::Cursor; #[test] - fn round_int() -> Result<(),Box> { + 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); + } + + #[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 ); + 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]); + 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] ); + 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> { + 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) )?; + 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 ); + assert!(v == v2); + assert!(v != v3); + assert!(v == true); Ok(()) } - + #[test] - fn round_bool_false() -> Result<(),Box> { + 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) )?; + 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 ); + assert!(v == v2); + assert!(v != v3); + assert!(v == false); Ok(()) } #[test] - fn round_list() -> Result<(),Box> { + 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), + 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 ); + 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([ - ("biggerint".to_string(), Value::Integer(98765)), - ("negint".to_string(), Value::Integer(-98765)), + ("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::Nothing), + ( + "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 ); + 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::Nothing; + let v = Value::Null; let mut buf = Vec::::new(); - v.write( &mut buf )?; - let v2 = Value::read( &mut Cursor::new(buf) )?; - assert!( v == v2 ); + v.write(&mut buf)?; + let v2 = Value::read(&mut Cursor::new(buf))?; + assert!(v == v2); Ok(()) } @@ -481,29 +617,36 @@ mod tests { 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 ); + 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([ - ("biggerint".to_string(), Value::Integer(98765)), - ("negint".to_string(), Value::Integer(-98765)), + ("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::Nothing), + ( + "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 ); + let v2 = Value::from_packet(&buf)?; + assert!(v == v2); Ok(()) } } -- cgit v1.2.3