aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/testing.rs
blob: cd6c15bc98a657216c3eaf704d3d27ee8e708a37 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//! For tests: writing to documents the way the node does, one change at a time, so
//! that tests don't depend on how the engine groups writes into changes.

use super::{Document, Transaction};

/// The writes `f` makes to `doc`, as one change; what `f` returns.
pub(crate) fn change<T>(doc: &mut Document, f: impl FnOnce(&mut Transaction<'_>) -> T) -> T {
    let mut transaction = doc.transaction();
    let out = f(&mut transaction);
    transaction.commit();
    out
}

/// The writes `f` makes to `doc`, as one change if it succeeds, dropped if not; what
/// `f` returns. Committed at time 0: the same writes make the same change.
pub(crate) fn try_change<T, E>(
    doc: &mut Document,
    f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E>,
) -> Result<T, E> {
    let mut transaction = doc.transaction();
    let out = f(&mut transaction);
    match &out {
        Ok(_) => {
            transaction.commit_with(automerge::transaction::CommitOptions::default().with_time(0));
        }
        Err(_) => {
            transaction.rollback();
        }
    }
    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
}