use rkyv::{Archive, Deserialize, Serialize};
use std::collections::HashMap;
const ZSHRS_MAGIC: u32 = 0x5A52_5343; const STRYKE_MAGIC: u32 = 0x5354_5259; const AWKRS_MAGIC: u32 = 0x4157_4B52; const VIMLRS_MAGIC: u32 = 0x5649_4D4C;
const AUTOLOAD_MAGIC: u32 = 0x5A52_414C;
const ELISP_MAGIC: u32 = 0x454C_5350;
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct ShardHeader {
magic: u32,
format_version: u32,
zshrs_version: String,
pointer_width: u32,
built_at_secs: u64,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct ScriptEntry {
mtime_secs: i64,
mtime_nsecs: i64,
binary_mtime_at_cache: i64,
cached_at_secs: i64,
chunk_blob: Vec<u8>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct ScriptShard {
header: ShardHeader,
entries: HashMap<String, ScriptEntry>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct StrykeEntry {
mtime_secs: i64,
mtime_nsecs: i64,
binary_mtime_at_cache: i64,
cached_at_secs: i64,
program_blob: Vec<u8>,
chunk_blob: Vec<u8>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct StrykeShard {
header: ShardHeader,
entries: HashMap<String, StrykeEntry>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct AutoloadEntry {
binary_mtime_at_cache: i64,
cached_at_secs: i64,
chunk_blob: Vec<u8>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct AutoloadShard {
header: ShardHeader,
entries: HashMap<String, AutoloadEntry>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct ElispHeader {
magic: u32,
format_version: u32,
pointer_width: u32,
built_at_secs: u64,
schema_key: String,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct ElispEntry {
mtime_ns: i64,
binary_mtime_at_cache: i64,
cached_at_secs: i64,
forms: Vec<Vec<u8>>,
heap: Vec<u8>,
oclosure_meta: Vec<u8>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct ElispShard {
header: ElispHeader,
entries: HashMap<String, ElispEntry>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct PyEntry {
key: u64,
verify: u64,
source: String,
blob: Vec<u8>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct PyShard {
entries: Vec<PyEntry>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct HashEntry {
key: u64,
blob: Vec<u8>,
}
#[derive(Archive, Serialize, Deserialize)]
#[archive(check_bytes)]
struct HashShard {
entries: Vec<HashEntry>,
}
pub struct KvRecord {
pub key: String,
pub del_key: String,
pub value: Vec<u8>,
pub fields: Vec<(String, String)>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FormatKind {
Script,
Stryke,
Autoload,
Elisp,
Python,
Hash,
}
pub struct Decoded {
pub format: String,
pub kind: FormatKind,
pub header: Vec<(String, String)>,
pub records: Vec<KvRecord>,
}
pub fn try_decode(bytes: &[u8]) -> Option<Decoded> {
for (magic, name) in [
(ZSHRS_MAGIC, "zshrs script cache (ZRSC)"),
(AWKRS_MAGIC, "awkrs script cache (AWKR)"),
(VIMLRS_MAGIC, "vimlrs script cache (VIML)"),
] {
if contains_u32_le(bytes, magic) {
if let Ok(s) = rkyv::check_archived_root::<ScriptShard>(bytes) {
if u32::from(s.header.magic) == magic {
return Some(decode_script(s, name));
}
}
}
}
if contains_u32_le(bytes, STRYKE_MAGIC) {
if let Ok(s) = rkyv::check_archived_root::<StrykeShard>(bytes) {
if u32::from(s.header.magic) == STRYKE_MAGIC {
return Some(decode_stryke(s));
}
}
if let Ok(s) = rkyv::check_archived_root::<ScriptShard>(bytes) {
if u32::from(s.header.magic) == STRYKE_MAGIC {
return Some(decode_script(s, "strykelang script cache (STRY, compat)"));
}
}
}
if contains_u32_le(bytes, AUTOLOAD_MAGIC) {
if let Ok(s) = rkyv::check_archived_root::<AutoloadShard>(bytes) {
if u32::from(s.header.magic) == AUTOLOAD_MAGIC {
return Some(decode_autoload(s));
}
}
}
if contains_u32_le(bytes, ELISP_MAGIC) {
if let Ok(s) = rkyv::check_archived_root::<ElispShard>(bytes) {
if u32::from(s.header.magic) == ELISP_MAGIC {
return Some(decode_elisp(s));
}
}
}
if let Ok(s) = rkyv::check_archived_root::<PyShard>(bytes) {
if plausible_hash_shard(s.entries.len(), bytes.len()) {
return Some(decode_python(s));
}
}
if let Ok(s) = rkyv::check_archived_root::<HashShard>(bytes) {
if plausible_hash_shard(s.entries.len(), bytes.len()) {
return Some(decode_hash(s));
}
}
None
}
fn plausible_hash_shard(entries: usize, bytes_len: usize) -> bool {
entries >= 1 && bytes_len >= 16
}
fn decode_script(s: &ArchivedScriptShard, name: &str) -> Decoded {
let mut records: Vec<KvRecord> = s
.entries
.iter()
.map(|(k, v)| KvRecord {
key: k.as_str().to_string(),
del_key: k.as_str().to_string(),
value: v.chunk_blob.as_slice().to_vec(),
fields: vec![
("mtime_secs".into(), i64::from(v.mtime_secs).to_string()),
("mtime_nsecs".into(), i64::from(v.mtime_nsecs).to_string()),
(
"binary_mtime_at_cache".into(),
i64::from(v.binary_mtime_at_cache).to_string(),
),
(
"cached_at_secs".into(),
i64::from(v.cached_at_secs).to_string(),
),
("chunk_blob_len".into(), v.chunk_blob.len().to_string()),
],
})
.collect();
records.sort_by(|a, b| a.key.cmp(&b.key));
Decoded {
format: name.into(),
kind: FormatKind::Script,
header: header_fields(&s.header),
records,
}
}
fn decode_stryke(s: &ArchivedStrykeShard) -> Decoded {
let mut records: Vec<KvRecord> = s
.entries
.iter()
.map(|(k, v)| KvRecord {
key: k.as_str().to_string(),
del_key: k.as_str().to_string(),
value: v.chunk_blob.as_slice().to_vec(),
fields: vec![
("mtime_secs".into(), i64::from(v.mtime_secs).to_string()),
(
"binary_mtime_at_cache".into(),
i64::from(v.binary_mtime_at_cache).to_string(),
),
(
"cached_at_secs".into(),
i64::from(v.cached_at_secs).to_string(),
),
("program_blob_len".into(), v.program_blob.len().to_string()),
("chunk_blob_len".into(), v.chunk_blob.len().to_string()),
],
})
.collect();
records.sort_by(|a, b| a.key.cmp(&b.key));
Decoded {
format: "strykelang script cache (STRY)".into(),
kind: FormatKind::Stryke,
header: header_fields(&s.header),
records,
}
}
fn decode_elisp(s: &ArchivedElispShard) -> Decoded {
let mut records: Vec<KvRecord> = s
.entries
.iter()
.map(|(k, v)| KvRecord {
key: k.as_str().to_string(),
del_key: k.as_str().to_string(),
value: v.heap.as_slice().to_vec(),
fields: vec![
("mtime_ns".into(), i64::from(v.mtime_ns).to_string()),
(
"binary_mtime_at_cache".into(),
i64::from(v.binary_mtime_at_cache).to_string(),
),
(
"cached_at_secs".into(),
i64::from(v.cached_at_secs).to_string(),
),
("forms".into(), v.forms.len().to_string()),
("heap_len".into(), v.heap.len().to_string()),
(
"oclosure_meta_len".into(),
v.oclosure_meta.len().to_string(),
),
],
})
.collect();
records.sort_by(|a, b| a.key.cmp(&b.key));
Decoded {
format: "elisprs heap-image cache (ELSP)".into(),
kind: FormatKind::Elisp,
header: vec![
(
"magic".into(),
format!("{:#010x}", u32::from(s.header.magic)),
),
(
"format_version".into(),
u32::from(s.header.format_version).to_string(),
),
(
"pointer_width".into(),
u32::from(s.header.pointer_width).to_string(),
),
(
"built_at_secs".into(),
u64::from(s.header.built_at_secs).to_string(),
),
(
"schema_key".into(),
s.header.schema_key.as_str().to_string(),
),
],
records,
}
}
fn decode_python(s: &ArchivedPyShard) -> Decoded {
let mut records: Vec<KvRecord> = s
.entries
.iter()
.map(|e| KvRecord {
key: e.source.as_str().to_string(),
del_key: format!("{:#018x}", u64::from(e.key)),
value: e.blob.as_slice().to_vec(),
fields: vec![
("key".into(), format!("{:#018x}", u64::from(e.key))),
("verify".into(), format!("{:#018x}", u64::from(e.verify))),
("blob_len".into(), e.blob.len().to_string()),
],
})
.collect();
records.sort_by(|a, b| a.key.cmp(&b.key));
Decoded {
format: "pythonrs bytecode cache (no header)".into(),
kind: FormatKind::Python,
header: vec![("entries".into(), records.len().to_string())],
records,
}
}
fn decode_hash(s: &ArchivedHashShard) -> Decoded {
let records: Vec<KvRecord> = s
.entries
.iter()
.map(|e| KvRecord {
key: format!("{:#018x}", u64::from(e.key)),
del_key: format!("{:#018x}", u64::from(e.key)),
value: e.blob.as_slice().to_vec(),
fields: vec![("blob_len".into(), e.blob.len().to_string())],
})
.collect();
Decoded {
format: "hash-keyed script cache (rubylang / arb, no header)".into(),
kind: FormatKind::Hash,
header: vec![("entries".into(), records.len().to_string())],
records,
}
}
fn decode_autoload(s: &ArchivedAutoloadShard) -> Decoded {
let mut records: Vec<KvRecord> = s
.entries
.iter()
.map(|(k, v)| KvRecord {
key: k.as_str().to_string(),
del_key: k.as_str().to_string(),
value: v.chunk_blob.as_slice().to_vec(),
fields: vec![
(
"binary_mtime_at_cache".into(),
i64::from(v.binary_mtime_at_cache).to_string(),
),
(
"cached_at_secs".into(),
i64::from(v.cached_at_secs).to_string(),
),
("chunk_blob_len".into(), v.chunk_blob.len().to_string()),
],
})
.collect();
records.sort_by(|a, b| a.key.cmp(&b.key));
Decoded {
format: "zshrs autoload cache (ZRAL)".into(),
kind: FormatKind::Autoload,
header: header_fields(&s.header),
records,
}
}
fn header_fields(h: &ArchivedShardHeader) -> Vec<(String, String)> {
vec![
("magic".into(), format!("{:#010x}", u32::from(h.magic))),
(
"format_version".into(),
u32::from(h.format_version).to_string(),
),
("version".into(), h.zshrs_version.as_str().to_string()),
(
"pointer_width".into(),
u32::from(h.pointer_width).to_string(),
),
(
"built_at_secs".into(),
u64::from(h.built_at_secs).to_string(),
),
]
}
fn contains_u32_le(bytes: &[u8], val: u32) -> bool {
let needle = val.to_le_bytes();
bytes.windows(4).any(|w| w == needle)
}
fn edit_shard<T, F>(bytes: &[u8], f: F) -> Result<Vec<u8>, String>
where
T: Archive + rkyv::Serialize<rkyv::ser::serializers::AllocSerializer<4096>>,
T::Archived: rkyv::Deserialize<T, rkyv::Infallible>
+ for<'a> rkyv::CheckBytes<rkyv::validation::validators::DefaultValidator<'a>>,
F: FnOnce(&mut T),
{
let archived = rkyv::check_archived_root::<T>(bytes).map_err(|e| e.to_string())?;
let mut owned: T = archived
.deserialize(&mut rkyv::Infallible)
.map_err(|_| "deserialize failed".to_string())?;
f(&mut owned);
rkyv::to_bytes::<_, 4096>(&owned)
.map(|b| b.into_vec())
.map_err(|e| e.to_string())
}
pub fn delete_record(bytes: &[u8], kind: FormatKind, del_key: &str) -> Result<Vec<u8>, String> {
match kind {
FormatKind::Script => edit_shard::<ScriptShard, _>(bytes, |s| {
s.entries.remove(del_key);
}),
FormatKind::Stryke => edit_shard::<StrykeShard, _>(bytes, |s| {
s.entries.remove(del_key);
}),
FormatKind::Autoload => edit_shard::<AutoloadShard, _>(bytes, |s| {
s.entries.remove(del_key);
}),
FormatKind::Elisp => edit_shard::<ElispShard, _>(bytes, |s| {
s.entries.remove(del_key);
}),
FormatKind::Python => {
let want = parse_hex_u64(del_key)?;
edit_shard::<PyShard, _>(bytes, |s| s.entries.retain(|e| e.key != want))
}
FormatKind::Hash => {
let want = parse_hex_u64(del_key)?;
edit_shard::<HashShard, _>(bytes, |s| s.entries.retain(|e| e.key != want))
}
}
}
fn parse_hex_u64(s: &str) -> Result<u64, String> {
s.strip_prefix("0x")
.and_then(|h| u64::from_str_radix(h, 16).ok())
.ok_or_else(|| format!("bad delete key: {}", s))
}
fn key_to_u64(s: &str) -> u64 {
if let Ok(v) = parse_hex_u64(s) {
return v;
}
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut h);
h.finish()
}
pub fn set_value(
bytes: &[u8],
kind: FormatKind,
del_key: &str,
value: Vec<u8>,
) -> Result<Vec<u8>, String> {
match kind {
FormatKind::Script => edit_shard::<ScriptShard, _>(bytes, |s| {
if let Some(e) = s.entries.get_mut(del_key) {
e.chunk_blob = value;
}
}),
FormatKind::Stryke => edit_shard::<StrykeShard, _>(bytes, |s| {
if let Some(e) = s.entries.get_mut(del_key) {
e.chunk_blob = value;
}
}),
FormatKind::Autoload => edit_shard::<AutoloadShard, _>(bytes, |s| {
if let Some(e) = s.entries.get_mut(del_key) {
e.chunk_blob = value;
}
}),
FormatKind::Elisp => edit_shard::<ElispShard, _>(bytes, |s| {
if let Some(e) = s.entries.get_mut(del_key) {
e.heap = value;
}
}),
FormatKind::Python => {
let want = parse_hex_u64(del_key)?;
edit_shard::<PyShard, _>(bytes, move |s| {
if let Some(e) = s.entries.iter_mut().find(|e| e.key == want) {
e.blob = value;
}
})
}
FormatKind::Hash => {
let want = parse_hex_u64(del_key)?;
edit_shard::<HashShard, _>(bytes, move |s| {
if let Some(e) = s.entries.iter_mut().find(|e| e.key == want) {
e.blob = value;
}
})
}
}
}
pub fn add_record(
bytes: &[u8],
kind: FormatKind,
key: &str,
value: Vec<u8>,
) -> Result<Vec<u8>, String> {
match kind {
FormatKind::Script => edit_shard::<ScriptShard, _>(bytes, |s| {
s.entries.insert(
key.to_string(),
ScriptEntry {
mtime_secs: 0,
mtime_nsecs: 0,
binary_mtime_at_cache: 0,
cached_at_secs: 0,
chunk_blob: value,
},
);
}),
FormatKind::Stryke => edit_shard::<StrykeShard, _>(bytes, |s| {
s.entries.insert(
key.to_string(),
StrykeEntry {
mtime_secs: 0,
mtime_nsecs: 0,
binary_mtime_at_cache: 0,
cached_at_secs: 0,
program_blob: Vec::new(),
chunk_blob: value,
},
);
}),
FormatKind::Autoload => edit_shard::<AutoloadShard, _>(bytes, |s| {
s.entries.insert(
key.to_string(),
AutoloadEntry {
binary_mtime_at_cache: 0,
cached_at_secs: 0,
chunk_blob: value,
},
);
}),
FormatKind::Elisp => edit_shard::<ElispShard, _>(bytes, |s| {
s.entries.insert(
key.to_string(),
ElispEntry {
mtime_ns: 0,
binary_mtime_at_cache: 0,
cached_at_secs: 0,
forms: Vec::new(),
heap: value,
oclosure_meta: Vec::new(),
},
);
}),
FormatKind::Python => {
let k = key_to_u64(key);
edit_shard::<PyShard, _>(bytes, move |s| {
s.entries.push(PyEntry {
key: k,
verify: 0,
source: key.to_string(),
blob: value,
});
})
}
FormatKind::Hash => {
let k = key_to_u64(key);
edit_shard::<HashShard, _>(bytes, move |s| {
s.entries.push(HashEntry {
key: k,
blob: value,
});
})
}
}
}
pub fn rename_record(
bytes: &[u8],
kind: FormatKind,
del_key: &str,
new_key: &str,
) -> Result<Vec<u8>, String> {
let new_key = new_key.to_string();
match kind {
FormatKind::Script => edit_shard::<ScriptShard, _>(bytes, move |s| {
if let Some(v) = s.entries.remove(del_key) {
s.entries.insert(new_key, v);
}
}),
FormatKind::Stryke => edit_shard::<StrykeShard, _>(bytes, move |s| {
if let Some(v) = s.entries.remove(del_key) {
s.entries.insert(new_key, v);
}
}),
FormatKind::Autoload => edit_shard::<AutoloadShard, _>(bytes, move |s| {
if let Some(v) = s.entries.remove(del_key) {
s.entries.insert(new_key, v);
}
}),
FormatKind::Elisp => edit_shard::<ElispShard, _>(bytes, move |s| {
if let Some(v) = s.entries.remove(del_key) {
s.entries.insert(new_key, v);
}
}),
FormatKind::Python | FormatKind::Hash => {
Err("rename is not supported for header-less hash-keyed formats".into())
}
}
}
#[cfg(test)]
pub(crate) fn test_script_shard_bytes(key: &str, blob: &[u8]) -> Vec<u8> {
let mut entries = HashMap::new();
entries.insert(
key.to_string(),
ScriptEntry {
mtime_secs: 1,
mtime_nsecs: 2,
binary_mtime_at_cache: 3,
cached_at_secs: 4,
chunk_blob: blob.to_vec(),
},
);
let shard = ScriptShard {
header: ShardHeader {
magic: ZSHRS_MAGIC,
format_version: 1,
zshrs_version: "9.9.9".into(),
pointer_width: 8,
built_at_secs: 5,
},
entries,
};
rkyv::to_bytes::<_, 256>(&shard)
.expect("serialize test shard")
.to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn script_shard_roundtrips_through_try_decode() {
let mut entries = HashMap::new();
entries.insert(
"/tmp/a.sh".to_string(),
ScriptEntry {
mtime_secs: 111,
mtime_nsecs: 222,
binary_mtime_at_cache: 333,
cached_at_secs: 444,
chunk_blob: vec![1, 2, 3, 4],
},
);
let shard = ScriptShard {
header: ShardHeader {
magic: ZSHRS_MAGIC,
format_version: 1,
zshrs_version: "9.9.9".into(),
pointer_width: 8,
built_at_secs: 555,
},
entries,
};
let bytes = rkyv::to_bytes::<_, 256>(&shard).expect("serialize");
let d = try_decode(&bytes[..]).expect("recognized");
assert_eq!(d.format, "zshrs script cache (ZRSC)");
assert_eq!(d.records.len(), 1);
assert_eq!(d.records[0].key, "/tmp/a.sh");
assert_eq!(d.records[0].value, vec![1, 2, 3, 4]);
assert!(d.header.iter().any(|(k, v)| k == "version" && v == "9.9.9"));
}
#[test]
fn hash_shard_roundtrips() {
let shard = HashShard {
entries: vec![
HashEntry {
key: 0xAABB,
blob: vec![9, 9, 9],
},
HashEntry {
key: 0xCCDD,
blob: vec![7],
},
],
};
let bytes = rkyv::to_bytes::<_, 256>(&shard).expect("serialize");
let d = try_decode(&bytes[..]).expect("recognized");
assert!(d.format.contains("hash-keyed"));
assert_eq!(d.records.len(), 2);
assert_eq!(d.records[0].value, vec![9, 9, 9]);
}
#[test]
fn delete_record_removes_and_reserializes() {
let mut entries = HashMap::new();
for name in ["/a.sh", "/b.sh"] {
entries.insert(
name.to_string(),
ScriptEntry {
mtime_secs: 1,
mtime_nsecs: 2,
binary_mtime_at_cache: 3,
cached_at_secs: 4,
chunk_blob: vec![0xaa],
},
);
}
let shard = ScriptShard {
header: ShardHeader {
magic: ZSHRS_MAGIC,
format_version: 1,
zshrs_version: "1.0".into(),
pointer_width: 8,
built_at_secs: 0,
},
entries,
};
let bytes = rkyv::to_bytes::<_, 256>(&shard).expect("serialize");
let new_bytes = delete_record(&bytes, FormatKind::Script, "/a.sh").expect("delete");
let d = try_decode(&new_bytes).expect("still valid");
assert_eq!(d.records.len(), 1);
assert_eq!(d.records[0].key, "/b.sh");
assert!(d.header.iter().any(|(k, v)| k == "version" && v == "1.0"));
}
#[test]
fn python_delete_removes_exactly_one_of_duplicate_sources() {
let shard = PyShard {
entries: vec![
PyEntry {
key: 1,
verify: 0,
source: "<string>".into(),
blob: vec![0xa1],
},
PyEntry {
key: 2,
verify: 0,
source: "<string>".into(),
blob: vec![0xa2],
},
],
};
let bytes = rkyv::to_bytes::<_, 256>(&shard).expect("serialize");
let d = try_decode(&bytes).expect("recognized");
assert_eq!(d.records.len(), 2);
assert!(d.records.iter().all(|r| r.key == "<string>"));
let target = d.records.iter().find(|r| r.value == vec![0xa1]).unwrap();
let new_bytes = delete_record(&bytes, FormatKind::Python, &target.del_key).unwrap();
let after = try_decode(&new_bytes).expect("valid");
assert_eq!(after.records.len(), 1, "exactly one removed");
assert_eq!(after.records[0].value, vec![0xa2]);
}
#[test]
fn full_crud_roundtrip_on_map_format() {
let shard = ScriptShard {
header: ShardHeader {
magic: ZSHRS_MAGIC,
format_version: 1,
zshrs_version: "1.0".into(),
pointer_width: 8,
built_at_secs: 0,
},
entries: HashMap::new(),
};
let mut bytes = rkyv::to_bytes::<_, 256>(&shard).unwrap().into_vec();
bytes = add_record(&bytes, FormatKind::Script, "/x.sh", vec![1, 2, 3]).unwrap();
let d = try_decode(&bytes).unwrap();
assert_eq!(d.records.len(), 1);
assert_eq!(d.records[0].value, vec![1, 2, 3]);
bytes = set_value(&bytes, FormatKind::Script, "/x.sh", vec![9, 9]).unwrap();
let d = try_decode(&bytes).unwrap();
assert_eq!(d.records[0].value, vec![9, 9]);
bytes = rename_record(&bytes, FormatKind::Script, "/x.sh", "/y.sh").unwrap();
let d = try_decode(&bytes).unwrap();
assert_eq!(d.records[0].key, "/y.sh");
assert_eq!(d.records[0].value, vec![9, 9]);
bytes = delete_record(&bytes, FormatKind::Script, "/y.sh").unwrap();
assert_eq!(try_decode(&bytes).unwrap().records.len(), 0);
}
#[test]
fn hash_delete_by_hex_key() {
let shard = HashShard {
entries: vec![
HashEntry {
key: 0x10,
blob: vec![1],
},
HashEntry {
key: 0x20,
blob: vec![2],
},
],
};
let bytes = rkyv::to_bytes::<_, 256>(&shard).expect("serialize");
let key = format!("{:#018x}", 0x10u64);
let new_bytes = delete_record(&bytes, FormatKind::Hash, &key).expect("delete");
let d = try_decode(&new_bytes).expect("valid");
assert_eq!(d.records.len(), 1);
}
#[test]
fn garbage_is_not_recognized() {
assert!(try_decode(&[0u8; 64]).is_none());
assert!(try_decode(b"not an archive at all, just plain text bytes").is_none());
}
}