aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/testing.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 01:40:44 +0100
committertv <tv@krebsco.de>2026-11-07 01:40:44 +0100
commite50a5ced65a1f03ebdbe5b0d3b09a57b11b7edbe (patch)
tree88493cdc06a841dab564cee46a989945a89530fa /src/document/testing.rs
parent4eb8eda89db812c3aa40f0226853aa9a20809a92 (diff)
refuse misplaced authors
Diffstat (limited to 'src/document/testing.rs')
-rw-r--r--src/document/testing.rs45
1 files changed, 45 insertions, 0 deletions
diff --git a/src/document/testing.rs b/src/document/testing.rs
index 7b75a96..cd6c15b 100644
--- a/src/document/testing.rs
+++ b/src/document/testing.rs
@@ -29,3 +29,48 @@ pub(crate) fn try_change<T, E>(
}
out
}
+
+/// `raw` (an uncompressed change chunk, as `Change::raw_bytes` gives one) with an author
+/// footer appended to its extra bytes, its checksum made right again: what no honest
+/// node sends for a change that isn't its actor's first (`changes::misplaced_author`).
+pub(crate) fn with_author_footer(raw: &[u8], author: &[u8]) -> Vec<u8> {
+ use sha2::{Digest, Sha256};
+ fn leb(mut n: usize, out: &mut Vec<u8>) {
+ loop {
+ let byte = (n & 0x7f) as u8;
+ n >>= 7;
+ if n == 0 {
+ out.push(byte);
+ return;
+ }
+ out.push(byte | 0x80);
+ }
+ }
+ assert_eq!(raw[8], 1, "an uncompressed change chunk");
+ let (mut length, mut read, mut shift) = (0usize, 0, 0);
+ loop {
+ let byte = raw[9 + read];
+ length |= usize::from(byte & 0x7f) << shift;
+ read += 1;
+ if byte & 0x80 == 0 {
+ break;
+ }
+ shift += 7;
+ }
+ let mut body = raw[9 + read..9 + read + length].to_vec();
+ body.push(1);
+ leb(author.len(), &mut body);
+ body.extend_from_slice(author);
+ let mut header = Vec::new();
+ leb(body.len(), &mut header);
+ let mut summed = vec![1u8];
+ summed.extend(&header);
+ summed.extend(&body);
+ let sum = Sha256::digest(&summed);
+ let mut out = raw[..4].to_vec();
+ out.extend(&sum[..4]);
+ out.push(1);
+ out.extend(header);
+ out.extend(body);
+ out
+}