1use crate::scalar::{
11 format_key, format_number, format_scalar, parse_quoted_string, parse_scalar, quote_string,
12 split_respecting_quotes, ScalarValue,
13};
14use serde_json::{Map, Value};
15use std::collections::HashMap;
16use std::fmt::Write;
17
18const NULL: Value = Value::Null;
19
20#[derive(Debug, Clone, PartialEq)]
25pub struct GenericSet {
26 pub name: String,
27 pub key: String,
28 pub fields: Vec<String>,
29 pub rows: Vec<Map<String, Value>>,
30}
31
32#[derive(Debug, Clone, Default, PartialEq)]
35pub struct GenericDeltaPayload {
36 pub tool: String,
37 pub key: String,
38 pub fields: Vec<String>,
39 pub base_root: String,
40 pub new_root: String,
41 pub added: Vec<Map<String, Value>>,
42 pub changed: Vec<Map<String, Value>>,
43 pub removed: Vec<Value>,
44 pub delta_tokens: u64,
45 pub full_tokens: u64,
46}
47
48pub fn canonical_cell(v: &Value) -> String {
58 match v {
59 Value::Null => "-".to_string(),
60 Value::Bool(b) => if *b { "true" } else { "false" }.to_string(),
61 Value::Number(n) => format_number(n),
62 Value::String(s) => quote_string(s),
63 other => quote_string(&other.to_string()),
64 }
65}
66
67pub fn generic_pack_root(s: &GenericSet) -> String {
72 let mut sorted_fields = s.fields.clone();
73 sorted_fields.sort();
74
75 let mut records: Vec<String> = s
76 .rows
77 .iter()
78 .map(|row| {
79 let mut r = String::from("R");
80 for f in &sorted_fields {
81 r.push('\t');
82 r.push_str(f);
83 r.push('\t');
84 r.push_str(&canonical_cell(row.get(f).unwrap_or(&NULL)));
85 }
86 r.push('\n');
87 r
88 })
89 .collect();
90 records.sort();
91
92 format!("sha256:{}", sha256_hex(records.concat().as_bytes()))
93}
94
95fn index_by_key(s: &GenericSet) -> Result<HashMap<String, &Map<String, Value>>, String> {
97 let mut m = HashMap::with_capacity(s.rows.len());
98 for row in &s.rows {
99 let id = canonical_cell(row.get(&s.key).unwrap_or(&NULL));
100 if m.contains_key(&id) {
101 return Err(format!(
102 "delta_invalid: duplicate identity {} for key \"{}\"",
103 id, s.key
104 ));
105 }
106 m.insert(id, row);
107 }
108 Ok(m)
109}
110
111fn rows_equal(a: &Map<String, Value>, b: &Map<String, Value>, fields: &[String]) -> bool {
112 fields.iter().all(|f| {
113 canonical_cell(a.get(f).unwrap_or(&NULL)) == canonical_cell(b.get(f).unwrap_or(&NULL))
114 })
115}
116
117fn key_of(row: &Map<String, Value>, key: &str) -> String {
118 canonical_cell(row.get(key).unwrap_or(&NULL))
119}
120
121pub fn diff_generic_sets(
128 base: &GenericSet,
129 next: &GenericSet,
130) -> Result<GenericDeltaPayload, String> {
131 if next.key.is_empty() {
132 return Err("delta_invalid: no identity key".to_string());
133 }
134 if next.key != base.key || base.fields != next.fields {
135 return Err("delta_invalid: schema change (send full)".to_string());
136 }
137 let base_idx = index_by_key(base)?;
138 let next_idx = index_by_key(next)?;
139
140 let mut added: Vec<Map<String, Value>> = Vec::new();
141 let mut changed: Vec<Map<String, Value>> = Vec::new();
142 let mut removed: Vec<Value> = Vec::new();
143
144 for (id, row) in &next_idx {
145 match base_idx.get(id) {
146 None => added.push((*row).clone()),
147 Some(brow) => {
148 if !rows_equal(brow, row, &next.fields) {
149 changed.push((*row).clone());
150 }
151 }
152 }
153 }
155 for (id, brow) in &base_idx {
156 if !next_idx.contains_key(id) {
157 removed.push(brow.get(&next.key).cloned().unwrap_or(Value::Null));
158 }
159 }
160
161 added.sort_by_key(|r| key_of(r, &next.key));
162 changed.sort_by_key(|r| key_of(r, &next.key));
163 removed.sort_by_key(canonical_cell);
164
165 Ok(GenericDeltaPayload {
166 tool: String::new(),
167 key: next.key.clone(),
168 fields: next.fields.clone(),
169 base_root: generic_pack_root(base),
170 new_root: generic_pack_root(next),
171 added,
172 changed,
173 removed,
174 delta_tokens: 0,
175 full_tokens: 0,
176 })
177}
178
179fn field_decl(fields: &[String], key: &str) -> String {
182 fields
183 .iter()
184 .map(|f| {
185 if f == key {
186 format!("@{}", format_key(f))
187 } else {
188 format_key(f)
189 }
190 })
191 .collect::<Vec<_>>()
192 .join(",")
193}
194
195fn encode_row(row: &Map<String, Value>, fields: &[String]) -> String {
196 fields
197 .iter()
198 .map(|f| format_scalar(row.get(f).unwrap_or(&NULL), '|'))
199 .collect::<Vec<_>>()
200 .join("|")
201}
202
203pub fn encode_generic_full(s: &GenericSet, tool: &str) -> String {
206 let name = if s.name.is_empty() { "rows" } else { &s.name };
207 let mut b = String::from("GCF profile=generic");
208 if !tool.is_empty() {
209 write!(b, " tool={}", tool).unwrap();
210 }
211 writeln!(b, " pack_root={} key={}", generic_pack_root(s), s.key).unwrap();
212 writeln!(
213 b,
214 "## {} [{}]{{{}}}",
215 name,
216 s.rows.len(),
217 field_decl(&s.fields, &s.key)
218 )
219 .unwrap();
220 for row in &s.rows {
221 b.push_str(&encode_row(row, &s.fields));
222 b.push('\n');
223 }
224 b
225}
226
227pub fn encode_generic_delta(d: &GenericDeltaPayload) -> String {
230 let mut b = String::from("GCF profile=generic");
231 if !d.tool.is_empty() {
232 write!(b, " tool={}", d.tool).unwrap();
233 }
234 write!(
235 b,
236 " delta=true base_root={} new_root={} key={}",
237 d.base_root, d.new_root, d.key
238 )
239 .unwrap();
240 if d.full_tokens > 0 {
241 let savings = 100.0 * (1.0 - d.delta_tokens as f64 / d.full_tokens as f64);
242 write!(b, " savings={:.0}%", savings).unwrap();
243 }
244 b.push('\n');
245
246 if !d.added.is_empty() {
247 writeln!(
248 b,
249 "## added [{}]{{{}}}",
250 d.added.len(),
251 field_decl(&d.fields, &d.key)
252 )
253 .unwrap();
254 for row in &d.added {
255 b.push_str(&encode_row(row, &d.fields));
256 b.push('\n');
257 }
258 }
259 if !d.changed.is_empty() {
260 writeln!(
261 b,
262 "## changed [{}]{{{}}}",
263 d.changed.len(),
264 field_decl(&d.fields, &d.key)
265 )
266 .unwrap();
267 for row in &d.changed {
268 b.push_str(&encode_row(row, &d.fields));
269 b.push('\n');
270 }
271 }
272 if !d.removed.is_empty() {
273 writeln!(b, "## removed [{}]{{@{}}}", d.removed.len(), d.key).unwrap();
274 for idv in &d.removed {
275 b.push_str(&format_scalar(idv, '|'));
276 b.push('\n');
277 }
278 }
279 b
280}
281
282pub fn verify_generic_delta(
286 base: &GenericSet,
287 d: &GenericDeltaPayload,
288 expected_new_root: &str,
289) -> Result<GenericSet, String> {
290 if generic_pack_root(base) != d.base_root {
291 return Err("base_mismatch: base root does not equal delta base_root".to_string());
292 }
293 let base_idx = index_by_key(base)?;
294
295 for idv in &d.removed {
297 if !base_idx.contains_key(&canonical_cell(idv)) {
298 return Err(format!(
299 "delta_invalid: removing identity {} not in base",
300 canonical_cell(idv)
301 ));
302 }
303 }
304 for row in &d.added {
305 if base_idx.contains_key(&key_of(row, &d.key)) {
306 return Err(format!(
307 "delta_invalid: adding identity {} that already exists",
308 key_of(row, &d.key)
309 ));
310 }
311 }
312 for row in &d.changed {
313 if !base_idx.contains_key(&key_of(row, &d.key)) {
314 return Err(format!(
315 "delta_invalid: changing identity {} not in base",
316 key_of(row, &d.key)
317 ));
318 }
319 }
320
321 let mut work: HashMap<String, Map<String, Value>> = base_idx
323 .iter()
324 .map(|(k, v)| (k.clone(), (*v).clone()))
325 .collect();
326 for idv in &d.removed {
327 work.remove(&canonical_cell(idv));
328 }
329 for row in &d.added {
330 work.insert(key_of(row, &d.key), row.clone());
331 }
332 for row in &d.changed {
333 work.insert(key_of(row, &d.key), row.clone());
334 }
335
336 let result = GenericSet {
337 name: base.name.clone(),
338 key: base.key.clone(),
339 fields: base.fields.clone(),
340 rows: work.into_values().collect(),
341 };
342 let got = generic_pack_root(&result);
343 if got != expected_new_root {
344 return Err(format!(
345 "root_mismatch: computed {}, expected {}",
346 got, expected_new_root
347 ));
348 }
349 Ok(result)
350}
351
352fn scalar_to_value(sv: ScalarValue) -> Result<Value, String> {
355 match sv {
356 ScalarValue::Null => Ok(Value::Null),
357 ScalarValue::Bool(b) => Ok(Value::Bool(b)),
358 ScalarValue::Int(i) => Ok(Value::Number(i.into())),
359 ScalarValue::Float(f) => serde_json::Number::from_f64(f)
360 .map(Value::Number)
361 .ok_or_else(|| "delta_invalid: non-finite number".to_string()),
362 ScalarValue::Str(s) => Ok(Value::String(s)),
363 ScalarValue::Missing => {
364 Err("delta_invalid: missing (~) not allowed in delta row".to_string())
365 }
366 ScalarValue::Attachment => {
367 Err("delta_invalid: attachment (^) not allowed in delta row".to_string())
368 }
369 }
370}
371
372fn parse_header_fields(header: &str) -> HashMap<String, String> {
373 let mut m = HashMap::new();
374 for tok in header.split_whitespace() {
375 if let Some(i) = tok.find('=') {
376 if i > 0 {
377 m.insert(tok[..i].to_string(), tok[i + 1..].to_string());
378 }
379 }
380 }
381 m
382}
383
384fn parse_count(s: &str) -> Result<usize, String> {
385 if s == "0" {
386 return Ok(0);
387 }
388 if s.is_empty() || s.starts_with('0') {
389 return Err(format!("delta_invalid: invalid count {}", s));
390 }
391 s.parse::<usize>()
392 .map_err(|_| format!("delta_invalid: invalid count {}", s))
393}
394
395fn find_bracket_start(s: &str) -> Option<usize> {
397 let mut in_quote = false;
398 let mut escaped = false;
399 for (i, c) in s.char_indices() {
400 if escaped {
401 escaped = false;
402 continue;
403 }
404 if c == '\\' && in_quote {
405 escaped = true;
406 continue;
407 }
408 if c == '"' {
409 in_quote = !in_quote;
410 continue;
411 }
412 if c == '[' && !in_quote {
413 return Some(i);
414 }
415 }
416 None
417}
418
419fn split_delta_field_decl(decl: &str) -> Result<(Vec<String>, String), String> {
422 if decl.len() < 2 || !decl.starts_with('{') || !decl.ends_with('}') {
423 return Err(format!(
424 "delta_invalid: invalid field declaration: {}",
425 decl
426 ));
427 }
428 let inner = &decl[1..decl.len() - 1];
429 if inner.is_empty() {
430 return Ok((Vec::new(), String::new()));
431 }
432 let mut fields = Vec::new();
433 let mut key_field = String::new();
434 for raw in split_respecting_quotes(inner, ',') {
435 let mut f = raw.trim().to_string();
436 let mut is_key = false;
437 if let Some(rest) = f.strip_prefix('@') {
438 f = rest.to_string();
439 is_key = true;
440 }
441 if f.len() >= 2 && f.starts_with('"') && f.ends_with('"') {
442 f = parse_quoted_string(&f)?;
443 }
444 if is_key {
445 key_field = f.clone();
446 }
447 fields.push(f);
448 }
449 Ok((fields, key_field))
450}
451
452fn parse_section_header(content: &str) -> Result<(String, usize, Vec<String>, String), String> {
455 let bi = find_bracket_start(content)
456 .ok_or_else(|| format!("delta_invalid: section header without count: {}", content))?;
457 let name = content[..bi].trim().to_string();
458 let rest = &content[bi..]; if !rest.starts_with('[') {
460 return Err(format!(
461 "delta_invalid: malformed section header: {}",
462 content
463 ));
464 }
465 let close = rest
466 .find(']')
467 .ok_or_else(|| format!("delta_invalid: unterminated count: {}", content))?;
468 let count = parse_count(&rest[1..close])?;
469 let (fields, key_field) = split_delta_field_decl(&rest[close + 1..])?;
470 Ok((name, count, fields, key_field))
471}
472
473fn parse_row(line: &str, fields: &[String]) -> Result<Map<String, Value>, String> {
474 let cells = split_respecting_quotes(line, '|');
475 if cells.len() != fields.len() {
476 return Err(format!(
477 "delta_invalid: row has {} cells, expected {}: {}",
478 cells.len(),
479 fields.len(),
480 line
481 ));
482 }
483 let mut row = Map::new();
484 for (i, f) in fields.iter().enumerate() {
485 row.insert(f.clone(), scalar_to_value(parse_scalar(&cells[i], true)?)?);
486 }
487 Ok(row)
488}
489
490pub fn decode_generic_full(text: &str) -> Result<(GenericSet, String), String> {
493 let trimmed = text.trim_end_matches('\n');
494 let lines: Vec<&str> = trimmed.split('\n').collect();
495 let hdr = parse_header_fields(lines[0]);
496 if hdr.get("profile").map(String::as_str) != Some("generic") {
497 return Err("not a generic payload".to_string());
498 }
499 let mut set = GenericSet {
500 name: String::new(),
501 key: hdr.get("key").cloned().unwrap_or_default(),
502 fields: Vec::new(),
503 rows: Vec::new(),
504 };
505 let mut i = 1;
506 while i < lines.len() {
507 let line = lines[i];
508 if !line.starts_with("## ") {
509 if line.is_empty() || line.starts_with("# ") || line.starts_with("##! ") {
513 i += 1;
514 continue;
515 }
516 return Err(format!(
517 "count_mismatch: unexpected content after declared section rows: {:?}",
518 line
519 ));
520 }
521 let (name, count, fields, key_field) = parse_section_header(&line[3..])?;
522 set.name = name;
523 set.fields = fields.clone();
524 if set.key.is_empty() {
525 set.key = key_field;
526 }
527 i += 1;
528 for j in 0..count {
529 if i >= lines.len() || lines[i].starts_with("## ") {
530 return Err(format!(
531 "count_mismatch: declared {} rows, got {}",
532 count, j
533 ));
534 }
535 set.rows.push(parse_row(lines[i], &fields)?);
536 i += 1;
537 }
538 }
539 Ok((set, hdr.get("pack_root").cloned().unwrap_or_default()))
540}
541
542pub fn decode_generic_delta(text: &str) -> Result<GenericDeltaPayload, String> {
545 let trimmed = text.trim_end_matches('\n');
546 let lines: Vec<&str> = trimmed.split('\n').collect();
547 let hdr = parse_header_fields(lines[0]);
548 if hdr.get("profile").map(String::as_str) != Some("generic") {
549 return Err("not a generic payload".to_string());
550 }
551 if hdr.get("delta").map(String::as_str) != Some("true") {
552 return Err("not a delta payload".to_string());
553 }
554 let mut d = GenericDeltaPayload {
555 tool: hdr.get("tool").cloned().unwrap_or_default(),
556 key: hdr.get("key").cloned().unwrap_or_default(),
557 base_root: hdr.get("base_root").cloned().unwrap_or_default(),
558 new_root: hdr.get("new_root").cloned().unwrap_or_default(),
559 ..Default::default()
560 };
561 let mut fields_set = false;
562 let mut i = 1;
563 while i < lines.len() {
564 let line = lines[i];
565 if !line.starts_with("## ") {
566 if line.is_empty() || line.starts_with("# ") || line.starts_with("##! ") {
570 i += 1;
571 continue;
572 }
573 return Err(format!(
574 "count_mismatch: unexpected content after declared section rows: {:?}",
575 line
576 ));
577 }
578 let (name, count, fields, key_field) = parse_section_header(&line[3..])?;
579 if d.key.is_empty() && !key_field.is_empty() {
580 d.key = key_field;
581 }
582 if !fields_set && (name == "added" || name == "changed") {
583 d.fields = fields.clone();
584 fields_set = true;
585 }
586 i += 1;
587 match name.as_str() {
588 "added" | "changed" => {
589 let mut rows = Vec::with_capacity(count);
590 for j in 0..count {
591 if i >= lines.len() || lines[i].starts_with("## ") {
592 return Err(format!(
593 "count_mismatch: declared {} rows in ## {}, got {}",
594 count, name, j
595 ));
596 }
597 rows.push(parse_row(lines[i], &fields)?);
598 i += 1;
599 }
600 if name == "added" {
601 d.added = rows;
602 } else {
603 d.changed = rows;
604 }
605 }
606 "removed" => {
607 for j in 0..count {
608 if i >= lines.len() || lines[i].starts_with("## ") {
609 return Err(format!(
610 "count_mismatch: declared {} identities in ## removed, got {}",
611 count, j
612 ));
613 }
614 d.removed
615 .push(scalar_to_value(parse_scalar(lines[i], true)?)?);
616 i += 1;
617 }
618 }
619 other => {
620 return Err(format!("delta_invalid: unknown delta section {}", other));
621 }
622 }
623 }
624 Ok(d)
625}
626
627pub const DEFAULT_REANCHOR_N: usize = 15;
631
632#[derive(Debug, Clone, Copy, PartialEq, Eq)]
640pub enum ReanchorPolicy {
641 FixedN(usize),
643 SizeGuard,
646}
647
648impl ReanchorPolicy {
649 pub fn fixed_n(n: usize) -> Self {
651 ReanchorPolicy::FixedN(if n == 0 { DEFAULT_REANCHOR_N } else { n })
652 }
653
654 pub fn size_guard() -> Self {
659 ReanchorPolicy::SizeGuard
660 }
661}
662
663#[derive(Debug, Clone)]
669pub struct GenericDeltaSession {
670 base: GenericSet,
671 tool: String,
672 policy: ReanchorPolicy,
673 turn: usize,
674 cum: usize, }
676
677impl GenericDeltaSession {
678 pub fn new(base: GenericSet, tool: String, policy: ReanchorPolicy) -> Self {
681 let policy = match policy {
682 ReanchorPolicy::FixedN(0) => ReanchorPolicy::FixedN(DEFAULT_REANCHOR_N),
683 p => p,
684 };
685 GenericDeltaSession {
686 base,
687 tool,
688 policy,
689 turn: 0,
690 cum: 0,
691 }
692 }
693
694 pub fn current_full(&self) -> String {
697 encode_generic_full(&self.base, &self.tool)
698 }
699
700 pub fn turn(&self) -> usize {
702 self.turn
703 }
704
705 pub fn next(&mut self, next: GenericSet) -> Result<(String, bool), String> {
711 self.turn += 1;
712
713 if next.key != self.base.key || self.base.fields != next.fields {
715 return Ok((self.reanchor(next), true));
716 }
717
718 let d = diff_generic_sets(&self.base, &next)?;
719 let delta_wire = encode_generic_delta(&d);
720
721 let reanchor = match self.policy {
722 ReanchorPolicy::SizeGuard => {
723 self.cum + delta_wire.len() >= encode_generic_full(&next, &self.tool).len()
724 }
725 ReanchorPolicy::FixedN(n) => self.turn.is_multiple_of(n),
726 };
727
728 if reanchor {
729 return Ok((self.reanchor(next), true));
730 }
731 self.base = next;
732 self.cum += delta_wire.len();
733 Ok((delta_wire, false))
734 }
735
736 fn reanchor(&mut self, next: GenericSet) -> String {
739 let wire = encode_generic_full(&next, &self.tool);
740 self.base = next;
741 self.cum = 0;
742 wire
743 }
744}
745
746const SHA256_K: [u32; 64] = [
749 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
750 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
751 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
752 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
753 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
754 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
755 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
756 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
757];
758
759pub(crate) fn sha256_hex(data: &[u8]) -> String {
760 let mut h: [u32; 8] = [
761 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
762 0x5be0cd19,
763 ];
764
765 let bit_len = (data.len() as u64).wrapping_mul(8);
766 let mut msg = data.to_vec();
767 msg.push(0x80);
768 while msg.len() % 64 != 56 {
769 msg.push(0);
770 }
771 msg.extend_from_slice(&bit_len.to_be_bytes());
772
773 for chunk in msg.chunks(64) {
774 let mut w = [0u32; 64];
775 for (i, word) in w.iter_mut().take(16).enumerate() {
776 *word = u32::from_be_bytes([
777 chunk[i * 4],
778 chunk[i * 4 + 1],
779 chunk[i * 4 + 2],
780 chunk[i * 4 + 3],
781 ]);
782 }
783 for i in 16..64 {
784 let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
785 let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
786 w[i] = w[i - 16]
787 .wrapping_add(s0)
788 .wrapping_add(w[i - 7])
789 .wrapping_add(s1);
790 }
791
792 let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = h;
793 for i in 0..64 {
794 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
795 let ch = (e & f) ^ ((!e) & g);
796 let t1 = hh
797 .wrapping_add(s1)
798 .wrapping_add(ch)
799 .wrapping_add(SHA256_K[i])
800 .wrapping_add(w[i]);
801 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
802 let maj = (a & b) ^ (a & c) ^ (b & c);
803 let t2 = s0.wrapping_add(maj);
804 hh = g;
805 g = f;
806 f = e;
807 e = d.wrapping_add(t1);
808 d = c;
809 c = b;
810 b = a;
811 a = t1.wrapping_add(t2);
812 }
813 h[0] = h[0].wrapping_add(a);
814 h[1] = h[1].wrapping_add(b);
815 h[2] = h[2].wrapping_add(c);
816 h[3] = h[3].wrapping_add(d);
817 h[4] = h[4].wrapping_add(e);
818 h[5] = h[5].wrapping_add(f);
819 h[6] = h[6].wrapping_add(g);
820 h[7] = h[7].wrapping_add(hh);
821 }
822
823 let mut out = String::with_capacity(64);
824 for v in h {
825 write!(out, "{:08x}", v).unwrap();
826 }
827 out
828}
829
830#[cfg(test)]
831mod tests {
832 use super::*;
833 use serde_json::json;
834
835 #[test]
836 fn sha256_matches_known_vectors() {
837 assert_eq!(
838 sha256_hex(b""),
839 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
840 );
841 assert_eq!(
842 sha256_hex(b"abc"),
843 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
844 );
845 }
846
847 fn row(v: Value) -> Map<String, Value> {
848 v.as_object().unwrap().clone()
849 }
850
851 fn orders_base() -> GenericSet {
852 GenericSet {
853 name: "orders".into(),
854 key: "id".into(),
855 fields: vec![
856 "id".into(),
857 "total".into(),
858 "status".into(),
859 "customer".into(),
860 ],
861 rows: vec![
862 row(json!({"id": 1001, "total": 59.98, "status": "shipped", "customer": "Alice"})),
863 row(json!({"id": 1002, "total": 29.99, "status": "pending", "customer": "Bob"})),
864 row(json!({"id": 1003, "total": 129.50, "status": "shipped", "customer": "Carol"})),
865 ],
866 }
867 }
868
869 fn orders_next() -> GenericSet {
870 GenericSet {
871 name: "orders".into(),
872 key: "id".into(),
873 fields: vec![
874 "id".into(),
875 "total".into(),
876 "status".into(),
877 "customer".into(),
878 ],
879 rows: vec![
880 row(json!({"id": 1002, "total": 29.99, "status": "shipped", "customer": "Bob"})),
881 row(json!({"id": 1003, "total": 129.50, "status": "shipped", "customer": "Carol"})),
882 row(json!({"id": 1004, "total": 75.00, "status": "pending", "customer": "Dave"})),
883 ],
884 }
885 }
886
887 #[test]
888 fn roundtrip_by_root() {
889 let base = orders_base();
890 let next = orders_next();
891 let d = diff_generic_sets(&base, &next).unwrap();
892 assert_eq!((d.added.len(), d.changed.len(), d.removed.len()), (1, 1, 1));
893 assert_eq!(d.new_root, generic_pack_root(&next));
894 let result = verify_generic_delta(&base, &d, &generic_pack_root(&next)).unwrap();
895 assert_eq!(generic_pack_root(&result), generic_pack_root(&next));
896 }
897
898 #[test]
899 fn pack_root_row_order_invariant() {
900 let a = orders_base();
901 let mut b = orders_base();
902 b.rows.swap(0, 2);
903 assert_eq!(generic_pack_root(&a), generic_pack_root(&b));
904 }
905
906 #[test]
907 fn canonical_cell_no_collision() {
908 assert_eq!(canonical_cell(&Value::Null), "-");
909 assert_eq!(canonical_cell(&json!(true)), "true");
910 assert_eq!(canonical_cell(&json!("true")), "\"true\"");
911 assert_eq!(canonical_cell(&json!("-")), "\"-\"");
912 assert_eq!(canonical_cell(&json!(59.98)), "59.98");
913 assert_eq!(canonical_cell(&json!("59.98")), "\"59.98\"");
914 assert_eq!(canonical_cell(&json!("a\tb")), "\"a\\tb\"");
915 }
916
917 #[test]
918 fn invariants() {
919 let base = orders_base();
920 let base_root = generic_pack_root(&base);
921
922 let mut dup = orders_base();
923 dup.rows.push(row(
924 json!({"id": 1001, "total": 1.0, "status": "x", "customer": "y"}),
925 ));
926 assert!(diff_generic_sets(&dup, &orders_next())
927 .unwrap_err()
928 .contains("duplicate identity"));
929
930 let mut sc = orders_next();
931 sc.fields = vec!["id".into(), "total".into(), "status".into()];
932 assert!(diff_generic_sets(&base, &sc)
933 .unwrap_err()
934 .contains("schema change"));
935
936 let add_existing = GenericDeltaPayload {
937 key: "id".into(),
938 fields: base.fields.clone(),
939 base_root: base_root.clone(),
940 added: vec![row(
941 json!({"id": 1001, "total": 1.0, "status": "s", "customer": "c"}),
942 )],
943 ..Default::default()
944 };
945 assert!(verify_generic_delta(&base, &add_existing, "sha256:x")
946 .unwrap_err()
947 .contains("already exists"));
948
949 let change_missing = GenericDeltaPayload {
950 key: "id".into(),
951 fields: base.fields.clone(),
952 base_root: base_root.clone(),
953 changed: vec![row(
954 json!({"id": 9999, "total": 1.0, "status": "s", "customer": "c"}),
955 )],
956 ..Default::default()
957 };
958 assert!(verify_generic_delta(&base, &change_missing, "sha256:x")
959 .unwrap_err()
960 .contains("not in base"));
961
962 let remove_missing = GenericDeltaPayload {
963 key: "id".into(),
964 fields: base.fields.clone(),
965 base_root: base_root.clone(),
966 removed: vec![json!(9999)],
967 ..Default::default()
968 };
969 assert!(verify_generic_delta(&base, &remove_missing, "sha256:x")
970 .unwrap_err()
971 .contains("not in base"));
972
973 let wrong_base = GenericDeltaPayload {
974 key: "id".into(),
975 fields: base.fields.clone(),
976 base_root: "sha256:wrong".into(),
977 ..Default::default()
978 };
979 assert!(verify_generic_delta(&base, &wrong_base, &base_root)
980 .unwrap_err()
981 .contains("base_mismatch"));
982
983 let d = diff_generic_sets(&base, &orders_next()).unwrap();
984 assert!(verify_generic_delta(&base, &d, "sha256:deadbeef")
985 .unwrap_err()
986 .contains("root_mismatch"));
987 }
988
989 #[test]
990 fn full_wire_roundtrip() {
991 let base = orders_base();
992 let (got, pr) = decode_generic_full(&encode_generic_full(&base, "orders_query")).unwrap();
993 assert_eq!(generic_pack_root(&got), generic_pack_root(&base));
994 assert_eq!(pr, generic_pack_root(&base));
995 }
996
997 #[test]
998 fn end_to_end() {
999 let base = orders_base();
1000 let next = orders_next();
1001 let (held, _) = decode_generic_full(&encode_generic_full(&base, "orders_query")).unwrap();
1002 let d = diff_generic_sets(&base, &next).unwrap();
1003 let parsed = decode_generic_delta(&encode_generic_delta(&d)).unwrap();
1004 let result = verify_generic_delta(&held, &parsed, &generic_pack_root(&next)).unwrap();
1005 assert_eq!(generic_pack_root(&result), generic_pack_root(&next));
1006 }
1007
1008 #[test]
1009 fn nulls_and_string_keys() {
1010 let nulls = GenericSet {
1011 name: "items".into(),
1012 key: "id".into(),
1013 fields: vec![
1014 "id".into(),
1015 "total".into(),
1016 "status".into(),
1017 "customer".into(),
1018 ],
1019 rows: vec![
1020 row(json!({"id": 2001, "total": 10.0, "status": null, "customer": "Amy"})),
1021 row(json!({"id": 2002, "total": null, "status": "open", "customer": null})),
1022 ],
1023 };
1024 let (got, _) = decode_generic_full(&encode_generic_full(&nulls, "")).unwrap();
1025 assert_eq!(generic_pack_root(&got), generic_pack_root(&nulls));
1026
1027 let sku = GenericSet {
1028 name: "parts".into(),
1029 key: "sku".into(),
1030 fields: vec!["sku".into(), "name".into(), "qty".into()],
1031 rows: vec![
1032 row(json!({"sku": "1001", "name": "Widget", "qty": 5})),
1033 row(json!({"sku": "A-200", "name": "Gadget", "qty": 3})),
1034 ],
1035 };
1036 let (got2, _) = decode_generic_full(&encode_generic_full(&sku, "")).unwrap();
1037 assert_eq!(generic_pack_root(&got2), generic_pack_root(&sku));
1038 }
1039
1040 #[test]
1041 fn decode_malformed_fails_closed() {
1042 let cases = [
1043 "",
1044 "GCF profile=graph delta=true base_root=a new_root=b key=id\n",
1045 "GCF profile=generic pack_root=r key=id\n## t [1]{@id}\n1\n",
1046 "GCF profile=generic delta=true base_root=a new_root=b key=id\n## added [2]{@id,x}\n1|2\n",
1047 "GCF profile=generic delta=true base_root=a new_root=b key=id\n## added [1]{@id,x}\n1\n",
1048 "GCF profile=generic delta=true base_root=a new_root=b key=id\n## bogus [1]{@id}\n1\n",
1049 "GCF profile=generic delta=true base_root=a new_root=b key=id\n## added [01]{@id,x}\n1|2\n",
1050 ];
1051 for wire in cases {
1052 assert!(
1053 decode_generic_delta(wire).is_err(),
1054 "expected error for {:?}",
1055 wire
1056 );
1057 }
1058 }
1059}
1060
1061#[cfg(test)]
1062mod session_tests {
1063 use super::*;
1064 use serde_json::json;
1065
1066 fn row(v: Value) -> Map<String, Value> {
1067 v.as_object().unwrap().clone()
1068 }
1069
1070 fn sess_base() -> GenericSet {
1071 GenericSet {
1072 name: "orders".into(),
1073 key: "id".into(),
1074 fields: vec![
1075 "id".into(),
1076 "total".into(),
1077 "status".into(),
1078 "customer".into(),
1079 ],
1080 rows: vec![
1081 row(json!({"id": 1001, "total": 59.98, "status": "shipped", "customer": "Alice"})),
1082 row(json!({"id": 1002, "total": 29.99, "status": "pending", "customer": "Bob"})),
1083 row(json!({"id": 1003, "total": 129.50, "status": "shipped", "customer": "Carol"})),
1084 ],
1085 }
1086 }
1087
1088 fn mk(rows: Vec<Map<String, Value>>) -> GenericSet {
1089 GenericSet {
1090 name: "orders".into(),
1091 key: "id".into(),
1092 fields: vec![
1093 "id".into(),
1094 "total".into(),
1095 "status".into(),
1096 "customer".into(),
1097 ],
1098 rows,
1099 }
1100 }
1101
1102 fn sess_updates() -> Vec<GenericSet> {
1104 vec![
1105 mk(vec![
1106 row(json!({"id": 1001, "total": 59.98, "status": "shipped", "customer": "Alice"})),
1107 row(json!({"id": 1002, "total": 29.99, "status": "shipped", "customer": "Bob"})), row(json!({"id": 1003, "total": 129.50, "status": "shipped", "customer": "Carol"})),
1109 ]),
1110 mk(vec![
1111 row(json!({"id": 1001, "total": 59.98, "status": "shipped", "customer": "Alice"})),
1113 row(json!({"id": 1002, "total": 29.99, "status": "shipped", "customer": "Bob"})),
1114 row(json!({"id": 1003, "total": 129.50, "status": "shipped", "customer": "Carol"})),
1115 row(json!({"id": 1004, "total": 75.00, "status": "pending", "customer": "Dave"})),
1116 ]),
1117 mk(vec![
1118 row(json!({"id": 1002, "total": 29.99, "status": "shipped", "customer": "Bob"})),
1120 row(json!({"id": 1003, "total": 129.50, "status": "shipped", "customer": "Carol"})),
1121 row(json!({"id": 1004, "total": 75.00, "status": "pending", "customer": "Dave"})),
1122 ]),
1123 mk(vec![
1124 row(json!({"id": 1002, "total": 29.99, "status": "shipped", "customer": "Bob"})),
1126 row(
1127 json!({"id": 1003, "total": 140.00, "status": "delivered", "customer": "Carol"}),
1128 ),
1129 row(json!({"id": 1004, "total": 75.00, "status": "pending", "customer": "Dave"})),
1130 ]),
1131 mk(vec![
1132 row(json!({"id": 1002, "total": 29.99, "status": "shipped", "customer": "Bob"})),
1134 row(
1135 json!({"id": 1003, "total": 140.00, "status": "delivered", "customer": "Carol"}),
1136 ),
1137 row(json!({"id": 1004, "total": 75.00, "status": "pending", "customer": "Dave"})),
1138 row(json!({"id": 1005, "total": 12.00, "status": "pending", "customer": "Eve"})),
1139 ]),
1140 ]
1141 }
1142
1143 fn size_guard_base() -> GenericSet {
1145 let names = [
1146 "Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Heidi", "Ivan", "Judy",
1147 "Mallory", "Niaj", "Olivia", "Peggy", "Rupert", "Sybil", "Trent", "Uma", "Victor",
1148 "Walter",
1149 ];
1150 let rows = names
1151 .iter()
1152 .enumerate()
1153 .map(|(i, n)| {
1154 row(json!({"id": 2000 + i, "total": 10 + i, "status": "pending", "customer": n}))
1155 })
1156 .collect();
1157 GenericSet {
1158 name: "rows".into(),
1159 key: "id".into(),
1160 fields: vec![
1161 "id".into(),
1162 "total".into(),
1163 "status".into(),
1164 "customer".into(),
1165 ],
1166 rows,
1167 }
1168 }
1169
1170 fn size_guard_updates() -> Vec<GenericSet> {
1171 let base = size_guard_base();
1172 (0..6)
1173 .map(|turn| {
1174 let mut g = base.clone();
1175 g.rows[turn].insert("status".into(), json!("shipped"));
1177 g
1178 })
1179 .collect()
1180 }
1181
1182 #[test]
1183 fn fixed_n_pattern() {
1184 let mut s = GenericDeltaSession::new(
1185 sess_base(),
1186 "orders_query".into(),
1187 ReanchorPolicy::fixed_n(3),
1188 );
1189 let want_full = [false, false, true, false, false]; for (i, up) in sess_updates().into_iter().enumerate() {
1191 let (_, is_full) = s.next(up).unwrap();
1192 assert_eq!(is_full, want_full[i], "turn {}", i + 1);
1193 }
1194 }
1195
1196 #[test]
1197 fn size_guard_triggers() {
1198 let mut s =
1199 GenericDeltaSession::new(size_guard_base(), "".into(), ReanchorPolicy::size_guard());
1200 let mut anchors = 0;
1201 for up in size_guard_updates() {
1202 let (_, is_full) = s.next(up).unwrap();
1203 if is_full {
1204 anchors += 1;
1205 }
1206 }
1207 assert!(
1208 anchors > 0,
1209 "SizeGuard never re-anchored across 6 turns; scenario should trigger at least one"
1210 );
1211 }
1212
1213 #[test]
1214 fn schema_change_reanchors() {
1215 let mut s = GenericDeltaSession::new(
1216 sess_base(),
1217 "orders_query".into(),
1218 ReanchorPolicy::fixed_n(15),
1219 );
1220 let changed = GenericSet {
1221 name: "orders".into(),
1222 key: "id".into(),
1223 fields: vec!["id".into(), "total".into(), "status".into()], rows: vec![row(
1225 json!({"id": 1001, "total": 59.98, "status": "shipped"}),
1226 )],
1227 };
1228 let (_, is_full) = s.next(changed).unwrap();
1229 assert!(is_full, "schema change must force a full re-anchor");
1230 }
1231
1232 #[test]
1235 fn fixed_n_15_over_30_turns() {
1236 let mut s = GenericDeltaSession::new(
1237 sess_base(),
1238 "orders_query".into(),
1239 ReanchorPolicy::fixed_n(15),
1240 );
1241 let _ = s.current_full(); let mut fulls = 0;
1244 let mut deltas = 0;
1245 let mut full_turns: Vec<usize> = Vec::new();
1246 let mut prev = sess_base();
1247 for turn in 1..=30usize {
1248 let mut next = GenericSet {
1250 name: prev.name.clone(),
1251 key: prev.key.clone(),
1252 fields: prev.fields.clone(),
1253 rows: Vec::new(),
1254 };
1255 let n_rows = prev.rows.len();
1256 for (j, r) in prev.rows.iter().enumerate() {
1257 let mut nr = r.clone();
1258 if j == turn % n_rows {
1259 nr.insert("total".into(), json!(turn as f64 + 0.5));
1260 }
1261 next.rows.push(nr);
1262 }
1263 let (_, is_full) = s.next(next.clone()).unwrap();
1264 if is_full {
1265 fulls += 1;
1266 full_turns.push(turn);
1267 } else {
1268 deltas += 1;
1269 }
1270 prev = next;
1271 }
1272 assert_eq!((fulls, deltas), (2, 28), "over 30 turns");
1273 assert_eq!(full_turns, vec![15, 30], "full re-anchor turns");
1274 }
1275
1276 #[test]
1280 fn consumer_stays_in_sync() {
1281 let cases: Vec<(&str, GenericSet, Vec<GenericSet>, String, ReanchorPolicy)> = vec![
1282 (
1283 "fixedN3",
1284 sess_base(),
1285 sess_updates(),
1286 "orders_query".into(),
1287 ReanchorPolicy::fixed_n(3),
1288 ),
1289 (
1290 "sizeGuard",
1291 size_guard_base(),
1292 size_guard_updates(),
1293 "".into(),
1294 ReanchorPolicy::size_guard(),
1295 ),
1296 ];
1297 for (name, base, ups, tool, policy) in cases {
1298 let mut s = GenericDeltaSession::new(base, tool, policy);
1299 let (mut held, _) = decode_generic_full(&s.current_full()).unwrap();
1300 for (i, up) in ups.into_iter().enumerate() {
1301 let (wire, is_full) = s.next(up.clone()).unwrap();
1302 if is_full {
1303 held = decode_generic_full(&wire).unwrap().0;
1304 } else {
1305 let d = decode_generic_delta(&wire).unwrap();
1306 held = verify_generic_delta(&held, &d, &d.new_root).unwrap();
1307 }
1308 assert_eq!(
1309 generic_pack_root(&held),
1310 generic_pack_root(&up),
1311 "{}: turn {} consumer root != producer root (is_full={})",
1312 name,
1313 i + 1,
1314 is_full
1315 );
1316 }
1317 }
1318 }
1319}