[star] add a crate for DASL's STAR archives
823 unmodified linesExpand all8248258268278288298308318328338348358368378388398408418423571 unmodified linesExpand all823 unmodified linesExpand all "parking_lot_core",]
[[package]]name = "dasl-star"version = "0.1.0"dependencies = [ "ipld-core", "serde", "serde_ipld_dagcbor 0.6.4", "serde_json", "thiserror",]
[[package]]name = "data-encoding"version = "2.10.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"3571 unmodified linesExpand all1223456116 unmodified linesExpand all[workspace]members = ["ffi"]members = ["ffi", "star"]
[package]name = "hydrant"version = "0.1.0"116 unmodified linesExpand all9 unmodified lines1011121314141516171812 unmodified lines9 unmodified lines
src = lib.fileset.toSource { root = ./.; fileset = lib.fileset.unions [ ./src ./examples ./benches ./ffi ./Cargo.toml ./Cargo.lock ./src ./examples ./benches ./ffi ./star ./Cargo.toml ./Cargo.lock ]; }; nativeBuildInputs = [cmake];
12 unmodified lines123456789101112131415161718192021MIT License
Copyright (c) 2026 microcosm
Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.1234567891011121314151617[package]name = "dasl-star"version = "0.1.0"edition = "2024"license = "MIT"description = "DASL STAR streaming archives"publish = false
[dependencies]ipld-core = "0.4"serde = "1.0"serde_ipld_dagcbor = "0.6"thiserror = "2.0"
[dev-dependencies]serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305//! [STAR](https://dasl.ing/star.html), DASL's streaming archive://!//! ```text//! 2a 6c 01 00 | varint len | meta | ( 01 | varint len | key | varint len | value )* | 00//! ```//!//! the writers are free functions over any [`Write`]. the header and the end byte can go in zstd//! frames of their own.
use std::io::{self, Read, Write};
use ipld_core::ipld::Ipld;use serde::Serialize;
pub const MAGIC: [u8; 4] = [0x2a, 0x6c, 0x01, 0x00];pub const PAIR: u8 = 0x01;pub const END: u8 = 0x00;/// a varint holds 63 bits, 7 per byteconst VARINT_MAX_BYTES: u32 = 9;
pub type Pair = (Vec<u8>, Vec<u8>);
#[derive(Debug, thiserror::Error)]pub enum Error { #[error(transparent)] Io(io::Error), #[error("not a STAR")] Magic, #[error("a STAR cut short")] Truncated, #[error("a varint {0}")] Varint(&'static str), #[error("a header {0}")] Meta(String), #[error("control byte {0:#04x}, neither a pair nor the end")] Control(u8), #[error("bytes after the end of a STAR")] TrailingData,}
impl From<io::Error> for Error { fn from(e: io::Error) -> Self { match e.kind() { io::ErrorKind::UnexpectedEof => Self::Truncated, _ => Self::Io(e), } }}
/// refuses a `meta` that isn't a DRISL map with a text `$type`pub fn write_header(out: &mut impl Write, meta: &impl Serialize) -> Result<(), Error> { let meta = serde_ipld_dagcbor::to_vec(meta) .map_err(|e| Error::Meta(format!("that can't be encoded: {e}")))?; check_meta(&meta)?; out.write_all(&MAGIC)?; write_varint(out, meta.len() as u64)?; out.write_all(&meta)?; Ok(())}
pub fn write_pair(out: &mut impl Write, key: &[u8], value: &[u8]) -> io::Result<()> { out.write_all(&[PAIR])?; write_varint(out, key.len() as u64)?; out.write_all(key)?; write_varint(out, value.len() as u64)?; out.write_all(value)}
/// nothing may be written after itpub fn write_end(out: &mut impl Write) -> io::Result<()> { out.write_all(&[END])}
/// a slice can't be longer than `isize::MAX`, which fits in the 63 bits a varint hasfn write_varint(out: &mut impl Write, mut n: u64) -> io::Result<()> { let mut buf = [0; VARINT_MAX_BYTES as usize]; let mut len = 0; while n >= 0x80 { buf[len] = n as u8 | 0x80; n >>= 7; len += 1; } buf[len] = n as u8; out.write_all(&buf[..=len])}
/// yields the pairs in archive order. the first error ends it, and the pairs before an error are/// from an archive that isn't valid, so a caller that can't take them back should collect firstpub struct Reader<R> { inner: R, meta: Vec<u8>, done: bool,}
impl<R: Read> Reader<R> { /// reads and checks the magic and the header. `inner` is read a byte at a time between keys /// and values, so it should be buffered pub fn new(mut inner: R) -> Result<Self, Error> { let mut magic = [0; 4]; inner.read_exact(&mut magic)?; if magic != MAGIC { return Err(Error::Magic); } let len = read_varint(&mut inner)?; let meta = read_len(&mut inner, len)?; check_meta(&meta)?; Ok(Self { inner, meta, done: false, }) }
/// the header's DRISL bytes, already checked to be a map with a text `$type` pub fn meta(&self) -> &[u8] { &self.meta }
fn pair(&mut self) -> Result<Option<Pair>, Error> { let mut control = [0]; self.inner.read_exact(&mut control)?; match control[0] { PAIR => { let len = read_varint(&mut self.inner)?; let key = read_len(&mut self.inner, len)?; let len = read_varint(&mut self.inner)?; let value = read_len(&mut self.inner, len)?; Ok(Some((key, value))) } END => match self.inner.read_exact(&mut control) { Ok(()) => Err(Error::TrailingData), Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(None), Err(e) => Err(Error::Io(e)), }, other => Err(Error::Control(other)), } }}
impl<R: Read> Iterator for Reader<R> { type Item = Result<Pair, Error>;
fn next(&mut self) -> Option<Self::Item> { if self.done { return None; } let pair = self.pair(); self.done = !matches!(pair, Ok(Some(_))); pair.transpose() }}
fn read_varint(r: &mut impl Read) -> Result<u64, Error> { let mut n = 0; for i in 0..VARINT_MAX_BYTES { let mut byte = [0]; r.read_exact(&mut byte)?; n |= u64::from(byte[0] & 0x7f) << (7 * i); if byte[0] & 0x80 == 0 { if byte[0] == 0 && i > 0 { return Err(Error::Varint("that isn't minimally encoded")); } return Ok(n); } } Err(Error::Varint("longer than 9 bytes"))}
/// grows with what arrives, so a huge length on a short input can't allocate it up frontfn read_len(r: &mut impl Read, len: u64) -> Result<Vec<u8>, Error> { let mut out = Vec::new(); r.take(len).read_to_end(&mut out)?; if out.len() as u64 != len { return Err(Error::Truncated); } Ok(out)}
/// a value that changes when decoded and re-encoded isn't DRISL, which has one encoding per valuefn check_meta(meta: &[u8]) -> Result<(), Error> { let value: Ipld = serde_ipld_dagcbor::from_slice(meta) .map_err(|e| Error::Meta(format!("that isn't DRISL: {e}")))?; let canonical = serde_ipld_dagcbor::to_vec(&value) .map_err(|e| Error::Meta(format!("that isn't DRISL: {e}")))?; if canonical != meta { return Err(Error::Meta( "that isn't deterministically encoded".to_owned(), )); } let Ipld::Map(map) = value else { return Err(Error::Meta("that isn't a map".to_owned())); }; match map.get("$type") { Some(Ipld::String(_)) => Ok(()), _ => Err(Error::Meta("without a text $type".to_owned())), }}
#[cfg(test)]mod tests { use std::collections::BTreeMap;
use super::*;
fn varint(n: u64) -> Vec<u8> { let mut out = Vec::new(); write_varint(&mut out, n).unwrap(); out }
fn read(bytes: &[u8]) -> Result<u64, Error> { read_varint(&mut &bytes[..]) }
#[test] fn varints_round_trip_at_every_byte_boundary() { for bytes in 1..=VARINT_MAX_BYTES { let low = if bytes == 1 { 0 } else { 1u64 << (7 * (bytes - 1)) }; let high = (1u64 << (7 * bytes)) - 1; for n in [low, high] { let encoded = varint(n); assert_eq!(encoded.len(), bytes as usize, "{n}"); assert_eq!(read(&encoded).unwrap(), n); } } }
#[test] fn varints_the_spec_forbids_are_refused() { assert!(matches!(read(&[0x80, 0x00]), Err(Error::Varint(_)))); assert!(matches!(read(&[0xff, 0x80, 0x00]), Err(Error::Varint(_)))); assert!(matches!(read(&[0xff; 9]), Err(Error::Varint(_)))); assert!(matches!(read(&[0x80]), Err(Error::Truncated))); assert!(matches!(read(&[]), Err(Error::Truncated))); }
#[derive(Serialize)] struct Meta { #[serde(rename = "$type")] kind: &'static str, stream: &'static str, first: u64, }
#[test] fn what_is_written_reads_back() { let mut star = Vec::new(); let meta = Meta { kind: "a.b.c", stream: "s", first: 300, }; write_header(&mut star, &meta).unwrap(); write_pair(&mut star, b"b", &[0; 200]).unwrap(); write_pair(&mut star, b"", b"").unwrap(); write_pair(&mut star, b"b", b"x").unwrap(); write_end(&mut star).unwrap();
let reader = Reader::new(&star[..]).unwrap(); assert_eq!(reader.meta(), serde_ipld_dagcbor::to_vec(&meta).unwrap()); let pairs: Vec<_> = reader.collect::<Result<_, _>>().unwrap(); assert_eq!( pairs, [ (b"b".to_vec(), vec![0; 200]), (vec![], vec![]), (b"b".to_vec(), b"x".to_vec()), ] ); }
#[test] fn a_header_without_a_text_type_is_not_written() { let no_type = BTreeMap::from([("kind", "a.b.c")]); assert!(matches!( write_header(&mut Vec::new(), &no_type), Err(Error::Meta(_)) )); let int_type = BTreeMap::from([("$type", 1)]); assert!(matches!( write_header(&mut Vec::new(), &int_type), Err(Error::Meta(_)) )); assert!(matches!( write_header(&mut Vec::new(), &"a.b.c"), Err(Error::Meta(_)) )); }
#[test] fn the_reader_stops_at_the_first_error() { let mut star = Vec::new(); write_header(&mut star, &BTreeMap::from([("$type", "")])).unwrap(); write_pair(&mut star, b"k", b"v").unwrap(); star.push(0x02); let mut reader = Reader::new(&star[..]).unwrap(); assert!(matches!(reader.next(), Some(Ok(_)))); assert!(matches!(reader.next(), Some(Err(Error::Control(0x02))))); assert!(reader.next().is_none()); }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263//! vectors from https://tangled.org/microcosm.blue/star/tree/main/interop at 41bdec1//! license in licenses/star-interop-MIT.txt
use dasl_star::{Reader, write_end, write_header, write_pair};use ipld_core::ipld::Ipld;use serde_json::Value;
fn fixtures(name: &str) -> Vec<Value> { let path = format!("{}/tests/fixtures/{name}", env!("CARGO_MANIFEST_DIR")); serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap()}
fn hex(s: &Value) -> Vec<u8> { let s = s.as_str().unwrap(); (0..s.len()) .step_by(2) .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) .collect()}
#[test]fn every_valid_vector_reads_as_expected_and_writes_back_byte_for_byte() { let fixtures = fixtures("valid-star-fixtures.json"); assert!(!fixtures.is_empty()); for fixture in fixtures { let comment = &fixture["comment"]; let star = hex(&fixture["star_hex"]); let reader = Reader::new(&star[..]).unwrap_or_else(|e| panic!("{comment}: {e}")); let meta: Value = serde_ipld_dagcbor::from_slice(reader.meta()).unwrap(); assert_eq!(meta, fixture["decoded"]["meta"], "{comment}"); let ipld: Ipld = serde_ipld_dagcbor::from_slice(reader.meta()).unwrap(); let pairs: Vec<_> = reader .collect::<Result<_, _>>() .unwrap_or_else(|e| panic!("{comment}: {e}")); let expected: Vec<_> = fixture["decoded"]["pairs"] .as_array() .unwrap() .iter() .map(|pair| (hex(&pair["key_hex"]), hex(&pair["value_hex"]))) .collect(); assert_eq!(pairs, expected, "{comment}");
let mut written = Vec::new(); write_header(&mut written, &ipld).unwrap(); for (key, value) in &pairs { write_pair(&mut written, key, value).unwrap(); } write_end(&mut written).unwrap(); assert_eq!(written, star, "{comment}"); }}
#[test]fn every_invalid_vector_is_refused() { let fixtures = fixtures("invalid-star-fixtures.json"); assert!(!fixtures.is_empty()); for fixture in fixtures { let comment = &fixture["comment"]; let star = hex(&fixture["star_hex"]); let read = Reader::new(&star[..]).and_then(|reader| reader.collect::<Result<Vec<_>, _>>()); assert!(read.is_err(), "{comment} was accepted: {read:?}"); }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150[ { "comment": "missing magic (empty)", "star_hex": "" }, { "comment": "missing magic", "star_hex": "08A16524747970656000" }, { "comment": "magic only (meta with $type is required)", "star_hex": "2A6C0100" }, { "comment": "truncated magic", "star_hex": "2A6C01" }, { "comment": "wrong magic (0x01 instead of nul fourth byte)", "star_hex": "2A6C010108A16524747970656000" }, { "comment": "truncated within meta length prefix", "star_hex": "2A6C010080" }, { "comment": "zero-sized meta", "star_hex": "2A6C01000000" }, { "comment": "non-map meta (int 1)", "star_hex": "2A6C0100010100" }, { "comment": "non-map meta (array)", "star_hex": "2A6C0100018000" }, { "comment": "missing meta $type", "star_hex": "2A6C010001A000" }, { "comment": "non-string meta $type value", "star_hex": "2A6C010008A16524747970650100" }, { "comment": "non-string meta key (invalid drisl)", "star_hex": "2A6C01000AA201606524747970656000" }, { "comment": "duplicate meta $type key (invalid drisl)", "star_hex": "2A6C01000FA2652474797065606524747970656000" }, { "comment": "non-canonical meta serialization", "star_hex": "2A6C01000BA265247479706560617E6000" }, { "comment": "meta with too-short length prefix", "star_hex": "2A6C010007A16524747970656000" }, { "comment": "meta with trailing nul padding", "star_hex": "2A6C010009A1652474797065600000" }, { "comment": "meta shorter than its length with no padding or data following", "star_hex": "2A6C010009A165247479706560" }, { "comment": "non-canonical meta length prefix", "star_hex": "2A6C01008800A16524747970656000" }, { "comment": ">9-byte meta length prefix (should reject the varint, not OOM)", "star_hex": "2A6C010080808080808080808001A16524747970656000" }, { "comment": "invalid control byte following meta", "star_hex": "2A6C010008A16524747970656002000000" }, { "comment": "missing terminating null byte", "star_hex": "2A6C010008A165247479706560" }, { "comment": "double terminating null byte", "star_hex": "2A6C010008A1652474797065600000" }, { "comment": "non-null byte after terminator", "star_hex": "2A6C010008A1652474797065600001" }, { "comment": "empty key no value", "star_hex": "2A6C010008A1652474797065600100" }, { "comment": "truncated before key content", "star_hex": "2A6C010008A1652474797065600101" }, { "comment": "truncated multi-byte key length prefix", "star_hex": "2A6C010008A1652474797065600180" }, { "comment": "truncated mid-key content", "star_hex": "2A6C010008A1652474797065600103AA" }, { "comment": "non-canonical varint zero-length key prefix", "star_hex": "2A6C010008A1652474797065600180000000" }, { "comment": "non-canonical varint zero as terminator", "star_hex": "2A6C010008A1652474797065608000" }, { "comment": "non-canonical non-zero-length key prefix", "star_hex": "2A6C010008A165247479706560018100AA0000" }, { "comment": "present key no value", "star_hex": "2A6C010008A1652474797065600101AA" }, { "comment": "truncated value after length prefix", "star_hex": "2A6C010008A1652474797065600101AA01" }, { "comment": "truncated multi-byte value length prefix", "star_hex": "2A6C010008A165247479706560010080" }, { "comment": "truncated mid-value", "star_hex": "2A6C010008A1652474797065600101AA02BB" }, { "comment": "non-canonical zero value length prefix", "star_hex": "2A6C010008A1652474797065600100800000" }, { "comment": "non-canonical non-zero-length value prefix", "star_hex": "2A6C010008A16524747970656001008100BB00" }, { "comment": "invalid control byte after data pair", "star_hex": "2A6C010008A16524747970656001000002000000" }]123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206[ { "comment": "minimal: empty-string $type, no pairs", "star_hex": "2A6C010008A16524747970656000", "decoded": { "meta": { "$type": "" }, "pairs": [] } }, { "comment": "empty-string $type, one empty-key/empty-val", "star_hex": "2A6C010008A16524747970656001000000", "decoded": { "meta": { "$type": "" }, "pairs": [ { "key_hex": "", "value_hex": "" } ] } }, { "comment": "empty-string $type, two empty-key/empty-val pairs", "star_hex": "2A6C010008A16524747970656001000001000000", "decoded": { "meta": { "$type": "" }, "pairs": [ { "key_hex": "", "value_hex": "" }, { "key_hex": "", "value_hex": "" } ] } }, { "comment": "short non-empty $type, no pairs", "star_hex": "2A6C01000BA16524747970656361626300", "decoded": { "meta": { "$type": "abc" }, "pairs": [] } }, { "comment": "non-ascii $type is allowed (if discouraged)", "star_hex": "2A6C01000BA165247479706563E2988500", "decoded": { "meta": { "$type": "★" }, "pairs": [] } }, { "comment": "non-http-friendly utf-8 $type is allowed (if discouraged)", "star_hex": "2A6C01000EA1652474797065666120622F220000", "decoded": { "meta": { "$type": "a b/\"\u0000" }, "pairs": [] } }, { "comment": "additional meta key, no pairs", "star_hex": "2A6C010017A2652474797065636162636662616E616E61646C6F616600", "decoded": { "meta": { "$type": "abc", "banana": "loaf" }, "pairs": [] } }, { "comment": "additional meta key with $-prefix, no pairs", "star_hex": "2A6C010013A26524747970656361626366246F746865720100", "decoded": { "meta": { "$type": "abc", "$other": 1 }, "pairs": [] } }, { "comment": "multi-byte meta varint length prefix", "star_hex": "2A6C01008001A2652474797065606666696C6C6572786F44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444400", "decoded": { "meta": { "$type": "", "filler": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" }, "pairs": [] } }, { "comment": "structured meta values are allowed", "star_hex": "2A6C010010A26361727280652474797065636162630101AA01BB00", "decoded": { "meta": { "$type": "abc", "arr": [] }, "pairs": [ { "key_hex": "AA", "value_hex": "BB" } ] } }, { "comment": "non-empty $type, empty key/nul byte val", "star_hex": "2A6C01000BA1652474797065636162630100010000", "decoded": { "meta": { "$type": "abc" }, "pairs": [ { "key_hex": "", "value_hex": "00" } ] } }, { "comment": "non-empty $type, nul byte key/empty val", "star_hex": "2A6C01000BA1652474797065636162630101000000", "decoded": { "meta": { "$type": "abc" }, "pairs": [ { "key_hex": "00", "value_hex": "" } ] } }, { "comment": "biggest single-byte key prefix (=varint 127)", "star_hex": "2A6C010008A165247479706560017F444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444440000", "decoded": { "meta": { "$type": "" }, "pairs": [ { "key_hex": "44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444", "value_hex": "" } ] } }, { "comment": "smallest two-byte key prefix (=varint 128)", "star_hex": "2A6C010008A16524747970656001800144444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444440000", "decoded": { "meta": { "$type": "" }, "pairs": [ { "key_hex": "4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444", "value_hex": "" } ] } }, { "comment": "biggest single-byte val prefix (=varint 127)", "star_hex": "2A6C010008A16524747970656001007F4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444400", "decoded": { "meta": { "$type": "" }, "pairs": [ { "key_hex": "", "value_hex": "44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" } ] } }, { "comment": "smallest two-byte val prefix (=varint 128)", "star_hex": "2A6C010008A16524747970656001008001444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444400", "decoded": { "meta": { "$type": "" }, "pairs": [ { "key_hex": "", "value_hex": "4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" } ] } }, { "comment": "a few non-empty pairs", "star_hex": "2A6C01000BA1652474797065636162630101AA01BB0102CCDD02EEFF00", "decoded": { "meta": { "$type": "abc" }, "pairs": [ { "key_hex": "AA", "value_hex": "BB" }, { "key_hex": "CCDD", "value_hex": "EEFF" } ] } }, { "comment": "non-sorted key order (format is ordered, not sorted)", "star_hex": "2A6C01000BA1652474797065636162630101FF000101AA0000", "decoded": { "meta": { "$type": "abc" }, "pairs": [ { "key_hex": "FF", "value_hex": "" }, { "key_hex": "AA", "value_hex": "" } ] } }, { "comment": "duplicate non-empty keys are allowed", "star_hex": "2A6C01000BA1652474797065636162630101AA01BB0101AA01CC00", "decoded": { "meta": { "$type": "abc" }, "pairs": [ { "key_hex": "AA", "value_hex": "BB" }, { "key_hex": "AA", "value_hex": "CC" } ] } }]