subetha_cxc/
schema_codec.rs1#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct SchemaTemplate {
31 width: usize,
32 template: Vec<u8>,
33 var_pos: Vec<u16>,
34 const_pos: Vec<u16>,
35}
36
37const FLAG_COMPACT: u8 = 0;
39const FLAG_ESCAPE: u8 = 1;
40
41impl SchemaTemplate {
42 pub fn learn(sample: &[&[u8]], width: usize) -> Self {
48 let mut template = vec![0u8; width];
49 let mut is_const = vec![false; width];
50 if let Some(first) = sample.first() {
51 assert!(first.len() >= width, "sample slot shorter than width");
52 template.copy_from_slice(&first[..width]);
53 is_const.iter_mut().for_each(|c| *c = true);
54 for s in &sample[1..] {
55 assert!(s.len() >= width, "sample slot shorter than width");
56 for p in 0..width {
57 if s[p] != template[p] {
58 is_const[p] = false;
59 }
60 }
61 }
62 }
63 let var_pos: Vec<u16> = (0..width)
64 .filter(|&p| !is_const[p])
65 .map(|p| p as u16)
66 .collect();
67 let const_pos: Vec<u16> = (0..width)
68 .filter(|&p| is_const[p])
69 .map(|p| p as u16)
70 .collect();
71 for &p in &var_pos {
74 template[p as usize] = 0;
75 }
76 Self {
77 width,
78 template,
79 var_pos,
80 const_pos,
81 }
82 }
83
84 pub fn width(&self) -> usize {
86 self.width
87 }
88
89 pub fn varying(&self) -> usize {
91 self.var_pos.len()
92 }
93
94 pub fn constant(&self) -> usize {
96 self.const_pos.len()
97 }
98
99 pub fn compact_len(&self) -> usize {
102 1 + self.var_pos.len()
103 }
104
105 pub fn is_escape(compact: &[u8]) -> bool {
109 compact.first() == Some(&FLAG_ESCAPE)
110 }
111
112 #[inline]
115 fn matches_template(&self, slot: &[u8]) -> bool {
116 self.const_pos
117 .iter()
118 .all(|&p| slot[p as usize] == self.template[p as usize])
119 }
120
121 #[inline]
125 pub fn encode(&self, slot: &[u8], out: &mut Vec<u8>) -> usize {
126 debug_assert_eq!(slot.len(), self.width);
127 if self.matches_template(slot) {
128 out.push(FLAG_COMPACT);
129 for &p in &self.var_pos {
130 out.push(slot[p as usize]);
131 }
132 1 + self.var_pos.len()
133 } else {
134 out.push(FLAG_ESCAPE);
135 out.extend_from_slice(&slot[..self.width]);
136 1 + self.width
137 }
138 }
139
140 #[inline]
144 pub fn decode(&self, inp: &[u8], out: &mut [u8]) -> usize {
145 debug_assert!(out.len() >= self.width);
146 match inp[0] {
147 FLAG_COMPACT => {
148 out[..self.width].copy_from_slice(&self.template);
149 for (i, &p) in self.var_pos.iter().enumerate() {
150 out[p as usize] = inp[1 + i];
151 }
152 1 + self.var_pos.len()
153 }
154 _ => {
155 out[..self.width].copy_from_slice(&inp[1..1 + self.width]);
156 1 + self.width
157 }
158 }
159 }
160
161 pub fn serialize(&self) -> Vec<u8> {
166 let mut out = Vec::with_capacity(4 + 2 * self.var_pos.len() + self.width);
167 out.extend_from_slice(&(self.width as u16).to_le_bytes());
168 out.extend_from_slice(&(self.var_pos.len() as u16).to_le_bytes());
169 for &p in &self.var_pos {
170 out.extend_from_slice(&p.to_le_bytes());
171 }
172 out.extend_from_slice(&self.template);
173 out
174 }
175
176 pub fn deserialize(bytes: &[u8]) -> Option<Self> {
179 if bytes.len() < 4 {
180 return None;
181 }
182 let width = u16::from_le_bytes([bytes[0], bytes[1]]) as usize;
183 let n_var = u16::from_le_bytes([bytes[2], bytes[3]]) as usize;
184 let pos_end = 4 + 2 * n_var;
185 if bytes.len() < pos_end + width {
186 return None;
187 }
188 let mut var_pos = Vec::with_capacity(n_var);
189 for i in 0..n_var {
190 let off = 4 + 2 * i;
191 var_pos.push(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
192 }
193 let template = bytes[pos_end..pos_end + width].to_vec();
194 let var_set: std::collections::HashSet<u16> = var_pos.iter().copied().collect();
195 let const_pos: Vec<u16> = (0..width as u16).filter(|p| !var_set.contains(p)).collect();
196 Some(Self {
197 width,
198 template,
199 var_pos,
200 const_pos,
201 })
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::shared_deque_khpd::{FatLineItem, LineItem};
209 use subetha_core::Marshal;
210
211 struct Rng(u64);
213 impl Rng {
214 fn new(s: u64) -> Self {
215 Self(s | 1)
216 }
217 fn next(&mut self) -> u64 {
218 let mut x = self.0;
219 x ^= x << 13;
220 x ^= x >> 7;
221 x ^= x << 17;
222 self.0 = x;
223 x
224 }
225 fn byte(&mut self) -> u8 {
226 (self.next() >> 24) as u8
227 }
228 fn below(&mut self, n: u64) -> u64 {
229 self.next() % n
230 }
231 }
232
233 fn fatline_slots(n: usize, rng: &mut Rng) -> Vec<[u8; 64]> {
234 let mut out = Vec::with_capacity(n);
235 let mut id = 0u32;
236 for _ in 0..n {
237 let cnt = 1 + rng.below(3) as usize;
238 let mut items = Vec::with_capacity(cnt);
239 for _ in 0..cnt {
240 let mut b = [0u8; 16];
241 b[0] = rng.below(16) as u8;
242 b[4..8].copy_from_slice(&id.to_le_bytes());
243 id = id.wrapping_add(1);
244 for x in b.iter_mut().skip(8) {
245 *x = rng.byte();
246 }
247 items.push(LineItem::new(&b).unwrap());
248 }
249 let fat = FatLineItem::from_items(&items).unwrap();
250 let mut s = [0u8; 64];
251 fat.marshal(&mut s);
252 out.push(s);
253 }
254 out
255 }
256
257 #[test]
260 fn roundtrip_exact_on_real_fatline_slots() {
261 let mut rng = Rng::new(0xabcd);
262 let slots = fatline_slots(5000, &mut rng);
263 let sample: Vec<&[u8]> = slots.iter().take(1000).map(|s| s.as_slice()).collect();
264 let tpl = SchemaTemplate::learn(&sample, 64);
265 assert!(
266 tpl.constant() >= 12,
267 "must find at least the 12 pad/reserved bytes constant, got {}",
268 tpl.constant()
269 );
270
271 let mut wire = Vec::new();
272 for s in &slots {
273 tpl.encode(s, &mut wire);
274 }
275 let mut cursor = 0usize;
276 let mut buf = [0u8; 64];
277 for (i, s) in slots.iter().enumerate() {
278 let used = tpl.decode(&wire[cursor..], &mut buf);
279 cursor += used;
280 assert_eq!(&buf[..], &s[..], "slot {i} round-trip mismatch");
281 }
282 assert_eq!(cursor, wire.len(), "consumed the whole wire stream");
283
284 let ratio = wire.len() as f64 / (slots.len() * 64) as f64;
285 assert!(
286 ratio < 0.75,
287 "constant-elision must shrink the stream, got ratio {ratio:.3}"
288 );
289 }
290
291 #[test]
294 fn escape_preserves_exactness() {
295 let zero = [0u8; 16];
296 let sample: Vec<&[u8]> = (0..8).map(|_| zero.as_slice()).collect();
297 let tpl = SchemaTemplate::learn(&sample, 16);
298 assert_eq!(tpl.constant(), 16, "all-zero sample makes every position constant");
299
300 let mut odd = [0u8; 16];
301 odd[3] = 0xff; let mut wire = Vec::new();
303 let n = tpl.encode(&odd, &mut wire);
304 assert_eq!(n, 1 + 16, "violating slot is escaped in full");
305 let mut buf = [0u8; 16];
306 let used = tpl.decode(&wire, &mut buf);
307 assert_eq!(used, n);
308 assert_eq!(buf, odd, "escaped slot round-trips exactly");
309 }
310
311 #[test]
314 fn serialize_roundtrips_codec() {
315 let mut rng = Rng::new(0x1357);
316 let slots = fatline_slots(500, &mut rng);
317 let sample: Vec<&[u8]> = slots.iter().map(|s| s.as_slice()).collect();
318 let tpl = SchemaTemplate::learn(&sample, 64);
319 let bytes = tpl.serialize();
320 let back = SchemaTemplate::deserialize(&bytes).expect("deserialize");
321 assert_eq!(tpl, back, "serialized template reconstructs identically");
322 }
323}