use super::keys::{read_len_prefixed, read_len_prefixed_ref, write_len_prefixed};
pub const MEMORY_VIS_GROUP: u8 = 0;
pub const MEMORY_VIS_INDIVIDUAL: u8 = 1;
pub fn memory_by_key(scope: &str, vis_byte: u8, owner: &str, key: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + scope.len() + 1 + 1 + 2 + owner.len() + 1 + 2 + key.len());
let _ = write_len_prefixed(&mut out, scope.as_bytes());
out.push(0u8);
out.push(vis_byte);
let _ = write_len_prefixed(&mut out, owner.as_bytes());
out.push(0u8);
let _ = write_len_prefixed(&mut out, key.as_bytes());
out
}
pub fn memory_by_key_ns_prefix(scope: &str, vis_byte: u8, owner: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + scope.len() + 1 + 1 + 2 + owner.len() + 1);
let _ = write_len_prefixed(&mut out, scope.as_bytes());
out.push(0u8);
out.push(vis_byte);
let _ = write_len_prefixed(&mut out, owner.as_bytes());
out.push(0u8);
out
}
pub fn memory_scope_prefix(scope: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + scope.len() + 1);
let _ = write_len_prefixed(&mut out, scope.as_bytes());
out.push(0u8);
out
}
pub fn parse_memory_by_key(buf: &[u8]) -> Option<(String, u8, String, String)> {
let mut c = 0;
let scope = String::from_utf8(read_len_prefixed(buf, &mut c)?).ok()?;
if buf.len() <= c {
return None;
}
c += 1;
if buf.len() <= c {
return None;
}
let vis_byte = buf[c];
c += 1;
let owner = String::from_utf8(read_len_prefixed(buf, &mut c)?).ok()?;
if buf.len() <= c {
return None;
}
c += 1;
let key = String::from_utf8(read_len_prefixed(buf, &mut c)?).ok()?;
Some((scope, vis_byte, owner, key))
}
pub fn parse_memory_key_only(buf: &[u8]) -> Option<&str> {
let mut c = 0;
read_len_prefixed_ref(buf, &mut c)?;
c += 1;
if buf.len() <= c {
return None;
}
c += 1;
read_len_prefixed_ref(buf, &mut c)?;
if buf.len() <= c {
return None;
}
c += 1;
let key = read_len_prefixed_ref(buf, &mut c)?;
std::str::from_utf8(key).ok()
}
pub const PROPOSAL_KIND_MEMORY: u8 = 0;
pub const PROPOSAL_KIND_SKILL: u8 = 1;
pub const PROPOSAL_KIND_TOMBSTONE: u8 = 2;
pub fn proposal_by_id(scope: &str, kind_byte: u8, id: &str) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + scope.len() + 1 + 1 + 2 + id.len());
let _ = write_len_prefixed(&mut out, scope.as_bytes());
out.push(0u8);
out.push(kind_byte);
let _ = write_len_prefixed(&mut out, id.as_bytes());
out
}
pub fn proposal_ns_prefix(scope: &str, kind_byte: u8) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + scope.len() + 1 + 1);
let _ = write_len_prefixed(&mut out, scope.as_bytes());
out.push(0u8);
out.push(kind_byte);
out
}
pub fn parse_proposal_by_id(buf: &[u8]) -> Option<(String, u8, String)> {
let mut c = 0;
let scope = String::from_utf8(read_len_prefixed(buf, &mut c)?).ok()?;
if buf.len() <= c {
return None;
}
c += 1;
if buf.len() <= c {
return None;
}
let kind_byte = buf[c];
c += 1;
let id = String::from_utf8(read_len_prefixed(buf, &mut c)?).ok()?;
Some((scope, kind_byte, id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn memory_by_key_roundtrips_group() {
let raw = memory_by_key("scope-a", MEMORY_VIS_GROUP, "", "my.key");
assert_eq!(
parse_memory_by_key(&raw),
Some((
"scope-a".to_string(),
MEMORY_VIS_GROUP,
String::new(),
"my.key".to_string()
))
);
}
#[test]
fn memory_by_key_roundtrips_individual() {
let raw = memory_by_key("scope-a", MEMORY_VIS_INDIVIDUAL, "agent-7", "my.key");
assert_eq!(
parse_memory_by_key(&raw),
Some((
"scope-a".to_string(),
MEMORY_VIS_INDIVIDUAL,
"agent-7".to_string(),
"my.key".to_string()
))
);
}
#[test]
fn parse_memory_key_only_matches_full_parse() {
let cases = [
("scope-a", MEMORY_VIS_GROUP, "", "my.key"),
("scope-a", MEMORY_VIS_INDIVIDUAL, "agent-7", "ns:sub.key"),
("", MEMORY_VIS_GROUP, "", ""),
("s", MEMORY_VIS_INDIVIDUAL, "owner-with-dashes", "k"),
("scope/with/slashes", MEMORY_VIS_GROUP, "", "key.with.many.dots"),
];
for (scope, vis, owner, key) in cases {
let raw = memory_by_key(scope, vis, owner, key);
let full = parse_memory_by_key(&raw).map(|(_, _, _, k)| k);
let only = parse_memory_key_only(&raw).map(str::to_string);
assert_eq!(only, full, "key-only parse diverged for key {key:?}");
assert_eq!(only.as_deref(), Some(key));
}
}
#[test]
fn parse_memory_key_only_rejects_truncated_buffer() {
let raw = memory_by_key("scope-a", MEMORY_VIS_INDIVIDUAL, "agent-7", "my.key");
assert_eq!(parse_memory_key_only(&raw[..raw.len() - 3]), None);
assert_eq!(parse_memory_key_only(&[]), None);
}
#[test]
fn memory_namespace_prefixes_do_not_overlap() {
let scope = "scope-a";
let key = "shared.key";
let group_key = memory_by_key(scope, MEMORY_VIS_GROUP, "", key);
let indiv_key = memory_by_key(scope, MEMORY_VIS_INDIVIDUAL, "agent-7", key);
let group_prefix = memory_by_key_ns_prefix(scope, MEMORY_VIS_GROUP, "");
let indiv_prefix = memory_by_key_ns_prefix(scope, MEMORY_VIS_INDIVIDUAL, "agent-7");
assert!(
group_key.starts_with(&group_prefix),
"group key must extend the group namespace prefix"
);
assert!(
indiv_key.starts_with(&indiv_prefix),
"individual key must extend the individual namespace prefix"
);
assert!(
!group_key.starts_with(&indiv_prefix),
"a group key must NOT fall within an individual namespace prefix"
);
assert!(
!indiv_key.starts_with(&group_prefix),
"an individual key must NOT fall within the group namespace prefix"
);
}
#[test]
fn memory_individual_namespaces_isolate_by_owner() {
let scope = "scope-a";
let key = "k";
let a_key = memory_by_key(scope, MEMORY_VIS_INDIVIDUAL, "agent-a", key);
let b_prefix = memory_by_key_ns_prefix(scope, MEMORY_VIS_INDIVIDUAL, "agent-b");
assert!(
!a_key.starts_with(&b_prefix),
"agent-a's key must NOT fall within agent-b's namespace prefix"
);
}
}