Skip to content

Instantly share code, notes, and snippets.

@dcoles
Created October 5, 2024 05:50
Show Gist options
  • Save dcoles/d2da9000a4f3b2369e064e942cf6b2ab to your computer and use it in GitHub Desktop.
Save dcoles/d2da9000a4f3b2369e064e942cf6b2ab to your computer and use it in GitHub Desktop.
A simple Type-Length-Value format inspired by `postcard-rpc`
//! A simple Type-Length-Value format inspired by `postcard-rpc`
//!
//! Author: David Coles <https://github.com/dcoles/>
//! SPDX-License-Identifier: MIT
use std::io::{Read, Write, Cursor};
use std::time;
use postcard::experimental::schema::Schema;
use postcard_rpc::hash::fnv1a64::hash_ty_path;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
struct TLV<'a> {
pub type_: [u8; 8],
pub value: &'a [u8],
}
fn encode<T>(writer: impl Write, key: &str, value: &T) -> postcard::Result<()>
where T: Serialize + Schema
{
let val = postcard::to_allocvec(value)?;
let tlv = TLV {
type_: hash_ty_path::<T>(key),
value: val.as_slice(),
};
postcard::to_io(&tlv, writer)?;
Ok(())
}
fn decode(reader: impl Read, visitor: &dyn Fn(TLV) -> postcard::Result<()>) -> postcard::Result<()> {
let mut buf = [0u8; 2048];
let tlv: TLV = postcard::from_io((reader, buf.as_mut()))?.0;
if tlv.type_ == [0u8; 8] {
return Err(postcard::Error::DeserializeUnexpectedEnd);
}
visitor(tlv)
}
macro_rules! match_tlv {
($tlv:expr, { $( $tok:ident : $t:ty as $k:literal => $tt:tt),* $(,)? }) => {
$(
if ($tlv).type_ == hash_ty_path::<$t>($k) {
let $tok : $t = ::postcard::from_bytes(($tlv).value)?;
$tt;
}
)*;
};
}
#[derive(Clone, Debug, Serialize, Deserialize, Schema)]
struct Header<'a> {
pub name: &'a str,
pub time: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize, Schema)]
struct Value<'a> {
pub value: &'a str,
}
fn main() {
let ts = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap();
let mut buf = Vec::new();
encode(&mut buf, "header", &Header { name: "TLV format", time: ts.as_secs() }).unwrap();
encode(&mut buf, "one", &Value { value: "Hello" }).unwrap();
encode(&mut buf, "two", &Value { value: "World" }).unwrap();
encode(&mut buf, "one", &Value { value: ":)" }).unwrap();
encode(&mut buf, "three", &Value { value: "!" }).unwrap();
print!("data: ");
for x in &buf {
print!("{:02X}", *x);
}
println!();
let visitor = |tlv: TLV| {
match_tlv!(tlv, {
hdr: Header as "header" => {
println!("Header: {hdr:?}");
},
value: Value as "one" => {
println!("Value \"one\": {value:?}");
},
value: Value as "two" => {
println!("Value \"two\": {value:?}");
},
// value: Value as "three" => { ... },
});
Ok(())
};
let mut reader = Cursor::new(buf);
loop {
if let Err(err) = decode(&mut reader, &visitor) {
if matches!(err, postcard::Error::DeserializeUnexpectedEnd) {
break;
}
panic!("read error: {}", err);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment