reddb_file/
graph_label_registry.rs1pub const GRAPH_LABEL_REGISTRY_MAX_LABEL_LEN: usize = 512;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct GraphLabelRegistryEntry {
10 pub id: u32,
11 pub namespace: u8,
12 pub label: String,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum GraphLabelRegistryFrameError {
17 LabelTooLong { len: usize, max: usize },
18 Malformed { offset: usize, reason: &'static str },
19}
20
21impl std::fmt::Display for GraphLabelRegistryFrameError {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Self::LabelTooLong { len, max } => {
25 write!(f, "graph label too long: {len} bytes (max {max})")
26 }
27 Self::Malformed { offset, reason } => {
28 write!(
29 f,
30 "malformed graph label registry at offset {offset}: {reason}"
31 )
32 }
33 }
34 }
35}
36
37impl std::error::Error for GraphLabelRegistryFrameError {}
38
39pub fn encode_graph_label_registry_frame(
40 entries: &[GraphLabelRegistryEntry],
41) -> Result<Vec<u8>, GraphLabelRegistryFrameError> {
42 let mut buf = Vec::with_capacity(4 + entries.len() * 16);
43 buf.extend_from_slice(&(entries.len() as u32).to_le_bytes());
44 for entry in entries {
45 let bytes = entry.label.as_bytes();
46 if bytes.len() > GRAPH_LABEL_REGISTRY_MAX_LABEL_LEN {
47 return Err(GraphLabelRegistryFrameError::LabelTooLong {
48 len: bytes.len(),
49 max: GRAPH_LABEL_REGISTRY_MAX_LABEL_LEN,
50 });
51 }
52 let len =
53 u16::try_from(bytes.len()).map_err(|_| GraphLabelRegistryFrameError::LabelTooLong {
54 len: bytes.len(),
55 max: GRAPH_LABEL_REGISTRY_MAX_LABEL_LEN,
56 })?;
57
58 buf.extend_from_slice(&entry.id.to_le_bytes());
59 buf.push(entry.namespace);
60 buf.extend_from_slice(&len.to_le_bytes());
61 buf.extend_from_slice(bytes);
62 }
63 Ok(buf)
64}
65
66pub fn decode_graph_label_registry_frame(
67 data: &[u8],
68) -> Result<Vec<GraphLabelRegistryEntry>, GraphLabelRegistryFrameError> {
69 if data.len() < 4 {
70 return Err(GraphLabelRegistryFrameError::Malformed {
71 offset: 0,
72 reason: "header truncated",
73 });
74 }
75
76 let count = u32::from_le_bytes(data[0..4].try_into().expect("header length checked")) as usize;
77 let mut off = 4;
78 let mut entries = Vec::with_capacity(count);
79 for _ in 0..count {
80 if data.len() < off + 7 {
81 return Err(GraphLabelRegistryFrameError::Malformed {
82 offset: off,
83 reason: "entry header truncated",
84 });
85 }
86 let id = u32::from_le_bytes(data[off..off + 4].try_into().expect("entry length checked"));
87 let namespace = data[off + 4];
88 let len = u16::from_le_bytes(
89 data[off + 5..off + 7]
90 .try_into()
91 .expect("entry length checked"),
92 ) as usize;
93 off += 7;
94 if data.len() < off + len {
95 return Err(GraphLabelRegistryFrameError::Malformed {
96 offset: off,
97 reason: "label bytes truncated",
98 });
99 }
100 let label = std::str::from_utf8(&data[off..off + len])
101 .map_err(|_| GraphLabelRegistryFrameError::Malformed {
102 offset: off,
103 reason: "label not utf8",
104 })?
105 .to_string();
106 entries.push(GraphLabelRegistryEntry {
107 id,
108 namespace,
109 label,
110 });
111 off += len;
112 }
113 Ok(entries)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn registry_frame_round_trips() {
122 let entries = vec![
123 GraphLabelRegistryEntry {
124 id: 1,
125 namespace: 0,
126 label: "host".to_string(),
127 },
128 GraphLabelRegistryEntry {
129 id: 64,
130 namespace: 1,
131 label: "purchased".to_string(),
132 },
133 ];
134 let encoded = encode_graph_label_registry_frame(&entries).unwrap();
135 let decoded = decode_graph_label_registry_frame(&encoded).unwrap();
136 assert_eq!(decoded, entries);
137 }
138
139 #[test]
140 fn registry_frame_rejects_truncated_input() {
141 assert!(matches!(
142 decode_graph_label_registry_frame(&[1, 2, 3]),
143 Err(GraphLabelRegistryFrameError::Malformed { .. })
144 ));
145 let encoded = encode_graph_label_registry_frame(&[GraphLabelRegistryEntry {
146 id: 64,
147 namespace: 0,
148 label: "abcd".to_string(),
149 }])
150 .unwrap();
151 assert!(matches!(
152 decode_graph_label_registry_frame(&encoded[..encoded.len() - 1]),
153 Err(GraphLabelRegistryFrameError::Malformed { .. })
154 ));
155 }
156}