OXYGENHUB

Coverage / crates/oxy-core/src/objects.rs 94.4%

1065 of 1128 lines run, 222 of 298 functions · run #258 on main · run missed

1
//! Typed objects and their canonical encodings (spec §6, §7, §13.2).
2
//!
3
//! Encoding conventions not fixed by the spec text are defined here:
4
//! - Enum-like items (`TreeOp`, `Edit`) are maps whose key 0 is the variant tag.
5
//! - An `OpRef`/atom reference is `[bytes32, uint]`; `AtomRef::Start` is `0`
6
//!   and `AtomRef::End` is `1`.
7
//! - A `Mode` is an unsigned integer: bit 0 = executable, bit 1 = opaque.
8
//! - `NodeKind`: 0 = file, 1 = dir, 2 = symlink.
9
10
use crate::cbor::{self, Value};
11
use crate::hash::{hash_object, ChangeHash, KeyId, ObjectRef, ObjectType};
12
use crate::ids::{ChangeId, NodeId};
13
use crate::lines::Lines;
14
use std::collections::BTreeMap;
15
16
pub const FORMAT_VERSION: u64 = 1;
17
18
/// The change format writers produce (spec §6.1). Version 2 stores each
19
/// large insert's lines as a text blob holding a CBOR array of them;
20
/// version 3 holds the lines' bytes themselves when splitting them by the
21
/// line rule gives them back. Readers accept 1 to 3, and a change keeps
22
/// the version it was made in, so its hash never moves.
23
pub const CHANGE_VERSION: u64 = 3;
24
25
/// How a text blob holds an insert's lines (spec §7.4).
26
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27
pub enum TextForm {
28
    /// A CBOR array of the lines' byte strings: version 2.
29
    Cbor,
30
    /// The lines' bytes, split again by the line rule: version 3.
31
    Raw,
32
    /// A CBOR array in a version 3 change, for lines the line rule would
33
    /// not split back (marked by key 7): never for lines it would.
34
    CborFallback,
35
}
36
37
/// In a version 2 change, an insert whose lines encode (as a CBOR array of
38
/// byte strings) to at least this many bytes is stored as a text blob
39
/// holding exactly that encoding; a shorter one is inline (spec §6.1).
40
pub const TEXT_BLOB_MIN: usize = 4096;
41
42
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
43
pub enum ObjectError {
44
    #[error("cbor: {0}")]
45
    Cbor(#[from] cbor::CborError),
46
    #[error("schema: {0}")]
47
    Schema(String),
48
}
49
5018×
pub(crate) fn schema<T>(msg: impl Into<String>) -> Result<T, ObjectError> {
5118×
    Err(ObjectError::Schema(msg.into()))
5218×
}
53
54
pub(crate) type Map = BTreeMap<u64, Value>;
55
56904408×
pub(crate) fn field<'a>(m: &'a Map, k: u64, what: &str) -> Result<&'a Value, ObjectError> {
57904408×
    m.get(&k)
58904408×
        .ok_or_else(|| ObjectError::Schema(format!("missing field {k} ({what})")))
59904408×
}
60372916×
pub(crate) fn uint(v: &Value, what: &str) -> Result<u64, ObjectError> {
61372916×
    v.as_uint()
62372916×
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected unsigned integer")))
63372916×
}
6466454×
pub(crate) fn u32_(v: &Value, what: &str) -> Result<u32, ObjectError> {
6566454×
    u32::try_from(uint(v, what)?).map_err(|_| ObjectError::Schema(format!("{what}: exceeds u32")))
6666454×
}
67122806×
pub(crate) fn text<'a>(v: &'a Value, what: &str) -> Result<&'a str, ObjectError> {
68122806×
    v.as_text()
69122806×
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected text")))
70122806×
}
71275017×
pub(crate) fn array<'a>(v: &'a Value, what: &str) -> Result<&'a [Value], ObjectError> {
72275017×
    v.as_array()
73275017×
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected array")))
74275017×
}
75133865×
pub(crate) fn map<'a>(v: &'a Value, what: &str) -> Result<&'a Map, ObjectError> {
76133865×
    v.as_map()
77133865×
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected map")))
78133865×
}
79389194×
pub(crate) fn bytes<'a>(v: &'a Value, what: &str) -> Result<&'a [u8], ObjectError> {
80389194×
    v.as_bytes()
81389194×
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected bytes")))
82389194×
}
83133010×
pub(crate) fn oref(v: &Value, what: &str) -> Result<ObjectRef, ObjectError> {
84133010×
    ObjectRef::from_slice(bytes(v, what)?)
85133009×
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected 32 bytes")))
86133010×
}
87149629×
pub(crate) fn node_id(v: &Value, what: &str) -> Result<NodeId, ObjectError> {
88149629×
    NodeId::from_slice(bytes(v, what)?)
89149629×
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected 16 bytes")))
90149629×
}
9126543×
pub(crate) fn check_version(m: &Map) -> Result<(), ObjectError> {
9226543×
    let v = uint(field(m, 0, "version")?, "version")?;
9326543×
    if v != FORMAT_VERSION {
940×
        let exe = std::env::current_exe()
950×
            .map(|p| format!(" at {}", p.display()))
960×
            .unwrap_or_default();
970×
        let which = format!("oxy {}{exe}", crate::VERSION);
980×
        return schema(if v > FORMAT_VERSION {
990×
            format!(
100
                "this data is format {v}, written by a newer oxy, and {which} reads format {FORMAT_VERSION}: install the current oxy, and check that no older copy comes first on PATH"
101
            )
102
        } else {
1030×
            format!(
104
                "this data is format {v}, written by an older oxy, and {which} reads format {FORMAT_VERSION} only"
105
            )
106
        });
10726543×
    }
10826543×
    Ok(())
10926543×
}
110
111
// ---------------------------------------------------------------- Hlc
112
113
/// Hybrid logical clock (spec §6.4).
114
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
115
pub struct Hlc {
116
    pub wall_ms: u64,
117
    pub logical: u32,
118
}
119
120
impl Hlc {
1215947×
    pub fn now() -> Hlc {
1225947×
        let wall_ms = std::time::SystemTime::now()
1235947×
            .duration_since(std::time::UNIX_EPOCH)
1245947×
            .map(|d| d.as_millis() as u64)
1255947×
            .unwrap_or(0);
1265947×
        Hlc {
1275947×
            wall_ms,
1285947×
            logical: 0,
1295947×
        }
1305947×
    }
13111822×
    pub(crate) fn to_value(self) -> Value {
13211822×
        Value::map([
13311822×
            (0, Value::Uint(self.wall_ms)),
13411822×
            (1, Value::Uint(self.logical as u64)),
13511822×
        ])
13611822×
    }
13734759×
    pub(crate) fn from_value(v: &Value) -> Result<Hlc, ObjectError> {
13834759×
        let m = map(v, "hlc")?;
139
        Ok(Hlc {
14034759×
            wall_ms: uint(field(m, 0, "hlc.wall_ms")?, "hlc.wall_ms")?,
14134759×
            logical: u32_(field(m, 1, "hlc.logical")?, "hlc.logical")?,
142
        })
14334759×
    }
144
}
145
146
// ---------------------------------------------------------------- tree
147
148
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
149
pub enum NodeKind {
150
    File,
151
    Dir,
152
    Symlink,
153
    /// A nested repository pinned to a state (spec §7.8); content is a pin.
154
    Subrepo,
155
}
156
157
impl NodeKind {
15873239×
    pub(crate) fn tag(self) -> u64 {
15973239×
        match self {
16072253×
            NodeKind::File => 0,
161963×
            NodeKind::Dir => 1,
1623×
            NodeKind::Symlink => 2,
16320×
            NodeKind::Subrepo => 3,
164
        }
16573239×
    }
16697233×
    pub(crate) fn from_tag(t: u64) -> Result<NodeKind, ObjectError> {
16797233×
        Ok(match t {
16895074×
            0 => NodeKind::File,
1692058×
            1 => NodeKind::Dir,
1709×
            2 => NodeKind::Symlink,
17192×
            3 => NodeKind::Subrepo,
1720×
            _ => return schema(format!("unknown node kind {t}")),
173
        })
17497233×
    }
175
}
176
177
/// Executable bit plus content mode (spec §7.1, §7.5).
178
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
179
pub struct Mode {
180
    pub exec: bool,
181
    pub opaque: bool,
182
}
183
184
impl Mode {
185
    pub const TEXT: Mode = Mode {
186
        exec: false,
187
        opaque: false,
188
    };
189
    pub const OPAQUE: Mode = Mode {
190
        exec: false,
191
        opaque: true,
192
    };
19372866×
    pub(crate) fn bits(self) -> u64 {
19472866×
        (self.exec as u64) | ((self.opaque as u64) << 1)
19572866×
    }
19697194×
    pub(crate) fn from_bits(b: u64) -> Result<Mode, ObjectError> {
19797194×
        if b > 3 {
1981×
            return schema(format!("unknown mode bits {b}"));
19997193×
        }
20097193×
        Ok(Mode {
20197193×
            exec: b & 1 != 0,
20297193×
            opaque: b & 2 != 0,
20397193×
        })
20497194×
    }
205
}
206
207
/// Reference to a tree operation of some change: the placement or mode atom it
208
/// introduced. `change == ObjectRef::ZERO` means this change.
209
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
210
pub struct OpRef {
211
    pub change: ChangeHash,
212
    pub index: u32,
213
}
214
215
impl OpRef {
216641×
    fn to_value(self) -> Value {
217641×
        Value::Array(vec![
218641×
            Value::Bytes(self.change.0.to_vec()),
219641×
            Value::Uint(self.index as u64),
220641×
        ])
221641×
    }
222952×
    fn from_value(v: &Value, what: &str) -> Result<OpRef, ObjectError> {
223952×
        let a = array(v, what)?;
224952×
        if a.len() != 2 {
2250×
            return schema(format!("{what}: expected [hash, index]"));
226952×
        }
227
        Ok(OpRef {
228952×
            change: oref(&a[0], what)?,
229952×
            index: u32_(&a[1], what)?,
230
        })
231952×
    }
232
    /// Replace a self reference with the concrete change hash.
2331025×
    pub fn resolve(self, me: ChangeHash) -> OpRef {
2341025×
        if self.change.is_zero() {
2351×
            OpRef { change: me, ..self }
236
        } else {
2371024×
            self
238
        }
2391025×
    }
240
}
241
242
#[derive(Debug, Clone, PartialEq, Eq)]
243
pub enum TreeOp {
244
    AddNode {
245
        node: NodeId,
246
        kind: NodeKind,
247
        mode: Mode,
248
        parent: NodeId,
249
        name: String,
250
    },
251
    RemoveNode {
252
        node: NodeId,
253
        placement: OpRef,
254
    },
255
    Move {
256
        node: NodeId,
257
        old: OpRef,
258
        parent: NodeId,
259
        name: String,
260
    },
261
    SetMode {
262
        node: NodeId,
263
        old: OpRef,
264
        mode: Mode,
265
    },
266
}
267
268
/// Names must be valid, non-empty, not `.`/`..`, and free of `/` and NUL (spec §7.1).
26947475×
pub fn validate_name(name: &str) -> Result<(), ObjectError> {
27047475×
    if name.is_empty() || name == "." || name == ".." || name.contains('/') || name.contains('\0') {
2716×
        return schema(format!("invalid placement name {name:?}"));
27247469×
    }
27347469×
    Ok(())
27447475×
}
275
276
impl TreeOp {
277102×
    pub fn node(&self) -> NodeId {
278102×
        match self {
27981×
            TreeOp::AddNode { node, .. }
28010×
            | TreeOp::RemoveNode { node, .. }
28110×
            | TreeOp::Move { node, .. }
282102×
            | TreeOp::SetMode { node, .. } => *node,
283
        }
284102×
    }
285
2866458×
    fn to_value(&self) -> Value {
28712303×
        let nid = |n: &NodeId| Value::Bytes(n.0.to_vec());
2886458×
        match self {
289
            TreeOp::AddNode {
2905817×
                node,
2915817×
                kind,
2925817×
                mode,
2935817×
                parent,
2945817×
                name,
2955817×
            } => Value::map([
2965817×
                (0, Value::Uint(0)),
2975817×
                (1, nid(node)),
2985817×
                (2, Value::Uint(kind.tag())),
2995817×
                (3, Value::Uint(mode.bits())),
3005817×
                (4, nid(parent)),
3015817×
                (5, Value::Text(name.clone())),
3025817×
            ]),
303549×
            TreeOp::RemoveNode { node, placement } => Value::map([
304549×
                (0, Value::Uint(1)),
305549×
                (1, nid(node)),
306549×
                (2, placement.to_value()),
307549×
            ]),
308
            TreeOp::Move {
30928×
                node,
31028×
                old,
31128×
                parent,
31228×
                name,
31328×
            } => Value::map([
31428×
                (0, Value::Uint(2)),
31528×
                (1, nid(node)),
31628×
                (2, old.to_value()),
31728×
                (4, nid(parent)),
31828×
                (5, Value::Text(name.clone())),
31928×
            ]),
32064×
            TreeOp::SetMode { node, old, mode } => Value::map([
32164×
                (0, Value::Uint(3)),
32264×
                (1, nid(node)),
32364×
                (2, old.to_value()),
32464×
                (3, Value::Uint(mode.bits())),
32564×
            ]),
326
        }
3276458×
    }
328
32948258×
    fn from_value(v: &Value) -> Result<TreeOp, ObjectError> {
33048258×
        let m = map(v, "tree_op")?;
33148258×
        let tag = uint(field(m, 0, "tree_op tag")?, "tree_op tag")?;
33248258×
        let node = node_id(field(m, 1, "tree_op.node")?, "tree_op.node")?;
33348258×
        let op = match tag {
334
            0 => {
33547306×
                let name = text(field(m, 5, "name")?, "name")?.to_owned();
33647306×
                validate_name(&name)?;
337
                TreeOp::AddNode {
33847306×
                    node,
33947306×
                    kind: NodeKind::from_tag(uint(field(m, 2, "kind")?, "kind")?)?,
34047306×
                    mode: Mode::from_bits(uint(field(m, 3, "mode")?, "mode")?)?,
34147306×
                    parent: node_id(field(m, 4, "parent")?, "parent")?,
34247306×
                    name,
343
                }
344
            }
345
            1 => TreeOp::RemoveNode {
346764×
                node,
347764×
                placement: OpRef::from_value(field(m, 2, "placement")?, "placement")?,
348
            },
349
            2 => {
350150×
                let name = text(field(m, 5, "name")?, "name")?.to_owned();
351150×
                validate_name(&name)?;
352
                TreeOp::Move {
353150×
                    node,
354150×
                    old: OpRef::from_value(field(m, 2, "old")?, "old")?,
355150×
                    parent: node_id(field(m, 4, "parent")?, "parent")?,
356150×
                    name,
357
                }
358
            }
359
            3 => TreeOp::SetMode {
36038×
                node,
36138×
                old: OpRef::from_value(field(m, 2, "old")?, "old")?,
36238×
                mode: Mode::from_bits(uint(field(m, 3, "mode")?, "mode")?)?,
363
            },
3640×
            _ => return schema(format!("unknown tree op tag {tag}")),
365
        };
36648258×
        if node.is_root() {
3670×
            return schema("tree op on the root node");
36848258×
        }
36948258×
        Ok(op)
37048258×
    }
371
}
372
373
// ---------------------------------------------------------------- edits
374
375
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
376
pub enum AtomRef {
377
    Start,
378
    End,
379
    Atom { change: ChangeHash, index: u32 },
380
}
381
382
impl AtomRef {
38315478×
    fn to_value(self) -> Value {
38415478×
        match self {
3855587×
            AtomRef::Start => Value::Uint(0),
3865711×
            AtomRef::End => Value::Uint(1),
3874180×
            AtomRef::Atom { change, index } => Value::Array(vec![
3884180×
                Value::Bytes(change.0.to_vec()),
3894180×
                Value::Uint(index as u64),
3904180×
            ]),
391
        }
39215478×
    }
393100054×
    fn from_value(v: &Value, what: &str) -> Result<AtomRef, ObjectError> {
3948273×
        match v {
39545226×
            Value::Uint(0) => Ok(AtomRef::Start),
39646555×
            Value::Uint(1) => Ok(AtomRef::End),
3978273×
            Value::Array(a) if a.len() == 2 => Ok(AtomRef::Atom {
3988273×
                change: oref(&a[0], what)?,
3998273×
                index: u32_(&a[1], what)?,
400
            }),
4010×
            _ => schema(format!("{what}: expected atom reference")),
402
        }
403100054×
    }
40462190×
    pub fn resolve(self, me: ChangeHash) -> AtomRef {
4059007×
        match self {
4069007×
            AtomRef::Atom { change, index } if change.is_zero() => {
40734×
                AtomRef::Atom { change: me, index }
408
            }
40962156×
            other => other,
410
        }
41162190×
    }
412
}
413
414
/// Half-open range of atoms introduced by one change.
415
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
416
pub struct AtomRange {
417
    pub change: ChangeHash,
418
    pub from: u32,
419
    pub to: u32,
420
}
421
422
impl AtomRange {
4232426×
    fn to_value(self) -> Value {
4242426×
        Value::Array(vec![
4252426×
            Value::Bytes(self.change.0.to_vec()),
4262426×
            Value::Uint(self.from as u64),
4272426×
            Value::Uint(self.to as u64),
4282426×
        ])
4292426×
    }
4303943×
    fn from_value(v: &Value) -> Result<AtomRange, ObjectError> {
4313943×
        let a = array(v, "atom range")?;
4323943×
        if a.len() != 3 {
4330×
            return schema("atom range: expected [hash, from, to]");
4343943×
        }
4353943×
        let r = AtomRange {
4363943×
            change: oref(&a[0], "range.change")?,
4373943×
            from: u32_(&a[1], "range.from")?,
4383943×
            to: u32_(&a[2], "range.to")?,
439
        };
4403943×
        if r.from >= r.to {
4410×
            return schema("atom range: empty or inverted");
4423943×
        }
4433943×
        Ok(r)
4443943×
    }
4454366×
    pub fn resolve(self, me: ChangeHash) -> AtomRange {
4464366×
        if self.change.is_zero() {
44718×
            AtomRange { change: me, ..self }
448
        } else {
4494348×
            self
450
        }
4514366×
    }
452
}
453
454
#[derive(Debug, Clone, PartialEq, Eq)]
455
pub enum Edit {
456
    Insert {
457
        node: NodeId,
458
        after: AtomRef,
459
        before: AtomRef,
460
        lines: crate::lines::Lines,
461
    },
462
    Delete {
463
        node: NodeId,
464
        atoms: Vec<AtomRange>,
465
    },
466
    Replace {
467
        node: NodeId,
468
        old: Option<ObjectRef>,
469
        new: Option<ObjectRef>,
470
    },
471
}
472
473
impl Edit {
47447423×
    pub fn node(&self) -> NodeId {
47547423×
        match self {
47644027×
            Edit::Insert { node, .. } | Edit::Delete { node, .. } | Edit::Replace { node, .. } => {
47747423×
                *node
478
            }
479
        }
48047423×
    }
481
482
    /// Whether this insert is stored as a text blob in a change of `version`.
4839994×
    fn stored_text(&self, version: u64) -> bool {
4847742×
        matches!(self, Edit::Insert { lines, .. }
4857742×
            if version >= 2 && cbor::byte_strings_len(lines) >= TEXT_BLOB_MIN)
4869994×
    }
487
488
    /// The edit's value; `stored` is the text blob of an insert stored as
489
    /// one, and its form (see [`Change::value_with`], which works them out).
490
    /// The edit's value; `compact` writes an inline insert's lines as
491
    /// their encoding rather than a value per line (same bytes encoded).
4929987×
    fn to_value(&self, stored: Option<(ObjectRef, TextForm)>, compact: bool) -> Value {
4939987×
        let nid = |n: &NodeId| Value::Bytes(n.0.to_vec());
4949987×
        let opt = |o: &Option<ObjectRef>| match o {
495176×
            Some(r) => Value::Bytes(r.0.to_vec()),
496116×
            None => Value::Null,
497292×
        };
4989987×
        match (self, stored) {
499
            (
500
                Edit::Insert {
50170×
                    node,
50270×
                    after,
50370×
                    before,
50470×
                    lines,
505
                },
50670×
                Some((r, form)),
507
            ) => {
50870×
                let mut fields = vec![
50970×
                    (0, Value::Uint(0)),
51070×
                    (1, nid(node)),
51170×
                    (2, after.to_value()),
51270×
                    (3, before.to_value()),
51370×
                    (5, Value::Bytes(r.0.to_vec())),
51470×
                    (6, Value::Uint(lines.len() as u64)),
515
                ];
51670×
                if form == TextForm::CborFallback {
5171×
                    fields.push((7, Value::Uint(1)));
51869×
                }
51970×
                Value::map(fields)
520
            }
521
            (
522
                Edit::Insert {
5237669×
                    node,
5247669×
                    after,
5257669×
                    before,
5267669×
                    lines,
527
                },
528
                None,
5297669×
            ) => Value::map([
5307669×
                (0, Value::Uint(0)),
5317669×
                (1, nid(node)),
5327669×
                (2, after.to_value()),
5337669×
                (3, before.to_value()),
534
                (
535
                    4,
5367669×
                    if compact {
5377663×
                        Value::Encoded(cbor::encode_byte_strings(lines))
538
                    } else {
5391008×
                        Value::Array(lines.iter().map(|l| Value::Bytes(l.to_vec())).collect())
540
                    },
541
                ),
542
            ]),
5432102×
            (Edit::Delete { node, atoms }, _) => Value::map([
5442102×
                (0, Value::Uint(1)),
5452102×
                (1, nid(node)),
546
                (
547
                    2,
5482426×
                    Value::Array(atoms.iter().map(|r| r.to_value()).collect()),
549
                ),
550
            ]),
551146×
            (Edit::Replace { node, old, new }, _) => Value::map([
552146×
                (0, Value::Uint(2)),
553146×
                (1, nid(node)),
554146×
                (2, opt(old)),
555146×
                (3, opt(new)),
556146×
            ]),
557
        }
5589987×
    }
559
5608094×
    fn from_value(v: &Value, version: u64, resolve: &mut Resolver) -> Result<Edit, ObjectError> {
5618094×
        let m = map(v, "edit")?;
5628094×
        let tag = uint(field(m, 0, "edit tag")?, "edit tag")?;
5638094×
        let node = node_id(field(m, 1, "edit.node")?, "edit.node")?;
5648094×
        let opt = |v: &Value, what: &str| -> Result<Option<ObjectRef>, ObjectError> {
5651590×
            if v.is_null() {
566697×
                Ok(None)
567
            } else {
568893×
                Ok(Some(oref(v, what)?))
569
            }
5701590×
        };
5718094×
        Ok(match tag {
572
            0 => {
5734209×
                let lines = match (m.get(&4), m.get(&5)) {
5743919×
                    (Some(inline), None) => {
5753919×
                        let lines = array_lines(inline)?;
5763919×
                        if version >= 2 && cbor::byte_strings_len(&lines) >= TEXT_BLOB_MIN {
5771×
                            return schema("an insert this large is stored as a text blob in a version 2 change");
5783918×
                        }
5793918×
                        if lines.is_empty() {
5800×
                            return schema("insert with no lines");
5813918×
                        }
5823918×
                        lines
583
                    }
584290×
                    (None, Some(text)) if version >= 2 => {
585290×
                        let text = oref(text, "insert text")?;
586290×
                        let count = u32_(field(m, 6, "insert line count")?, "insert line count")?;
587290×
                        if count == 0 {
5880×
                            return schema("insert with no lines");
589290×
                        }
590290×
                        let form = match (version, m.get(&7)) {
5911×
                            (2, None) => TextForm::Cbor,
592288×
                            (_, None) => TextForm::Raw,
5931×
                            (3.., Some(Value::Uint(1))) => TextForm::CborFallback,
5940×
                            _ => return schema("unknown text blob form"),
595
                        };
596
                        // The resolver checks the count (`text_lines`).
597290×
                        resolve(&text, count, form)?
598
                    }
599
                    _ => {
6000×
                        return schema(
601
                            "an insert has either its lines or, in a version 2 change, a text blob",
602
                        )
603
                    }
604
                };
605
                Edit::Insert {
6064206×
                    node,
6074206×
                    after: AtomRef::from_value(field(m, 2, "after")?, "after")?,
6084206×
                    before: AtomRef::from_value(field(m, 3, "before")?, "before")?,
6094206×
                    lines,
610
                }
611
            }
612
            1 => {
6133090×
                let atoms = array(field(m, 2, "atoms")?, "atoms")?
6143090×
                    .iter()
6153090×
                    .map(AtomRange::from_value)
6163090×
                    .collect::<Result<Vec<_>, _>>()?;
6173090×
                if atoms.is_empty() {
6180×
                    return schema("delete with no atoms");
6193090×
                }
6203090×
                Edit::Delete { node, atoms }
621
            }
622
            2 => Edit::Replace {
623795×
                node,
624795×
                old: opt(field(m, 2, "old")?, "old")?,
625795×
                new: opt(field(m, 3, "new")?, "new")?,
626
            },
6270×
            _ => return schema(format!("unknown edit tag {tag}")),
628
        })
6298094×
    }
630
}
631
632
// ---------------------------------------------------------------- change
633
634
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
635
pub enum ChangeKind {
636
    Normal,
637
    Resolution,
638
    WorkingCopy,
639
}
640
641
impl ChangeKind {
64212022×
    pub(crate) fn tag(self) -> u64 {
64312022×
        match self {
6449504×
            ChangeKind::Normal => 0,
64541×
            ChangeKind::Resolution => 1,
6462477×
            ChangeKind::WorkingCopy => 2,
647
        }
64812022×
    }
64918449×
    pub(crate) fn from_tag(t: u64) -> Result<ChangeKind, ObjectError> {
65018449×
        Ok(match t {
65115554×
            0 => ChangeKind::Normal,
652119×
            1 => ChangeKind::Resolution,
6532776×
            2 => ChangeKind::WorkingCopy,
6540×
            _ => return schema(format!("unknown change kind {t}")),
655
        })
65618449×
    }
657
}
658
659
/// A change (spec §6.1). `deps` is kept sorted and unique by `normalize`.
660
#[derive(Debug, Clone, PartialEq, Eq)]
661
pub struct Change {
662
    /// The format this change is encoded in: [`CHANGE_VERSION`] for a new
663
    /// one, whatever it was made in for one read back.
664
    pub version: u64,
665
    pub change_id: ChangeId,
666
    pub deps: Vec<ChangeHash>,
667
    pub author: KeyId,
668
    pub hlc: Hlc,
669
    pub message: String,
670
    pub tree_ops: Vec<TreeOp>,
671
    pub edits: Vec<Edit>,
672
    pub kind: ChangeKind,
673
}
674
675
impl Change {
6765687×
    pub fn normalize(&mut self) {
6775687×
        self.deps.sort();
6785687×
        self.deps.dedup();
6795687×
    }
680
6816×
    pub fn to_value(&self) -> Value {
6826×
        self.value_with(&mut |_| true, false)
6836×
    }
684
685
    /// The text blob of each edit stored as one, `None` for the others;
686
    /// the stored texts go to `texts`, in order, when it is given. Worked
687
    /// out on every core, a bounded batch at a time: chunking and hashing
688
    /// a whole tree's text is most of encoding a change that adds it.
6897998×
    fn stored_texts(&self, sink: &mut TextSink) -> Vec<Option<(ObjectRef, TextForm)>> {
690
        use rayon::prelude::*;
6917998×
        let mut refs: Vec<Option<(ObjectRef, TextForm)>> = vec![None; self.edits.len()];
6927998×
        let stored: Vec<usize> = (0..self.edits.len())
6939987×
            .filter(|i| self.edits[*i].stored_text(self.version))
6947998×
            .collect();
695
        // At most 256 texts or 32 MiB of them a batch: some directories
696
        // hold hundreds of generated files of megabytes each, and a batch
697
        // is copied and compressed at once.
6987998×
        let mut batches: Vec<&[usize]> = Vec::new();
6997998×
        let (mut start, mut bytes) = (0, 0usize);
7007998×
        for (k, i) in stored.iter().enumerate() {
70170×
            if let Edit::Insert { lines, .. } = &self.edits[*i] {
70270×
                bytes += lines.bytes().len();
70370×
            }
70470×
            if k + 1 - start == 256 || bytes >= 32 << 20 {
7050×
                batches.push(&stored[start..=k]);
7060×
                (start, bytes) = (k + 1, 0);
70770×
            }
708
        }
7097998×
        if start < stored.len() {
71069×
            batches.push(&stored[start..]);
7117929×
        }
7127998×
        for batch in batches {
71369×
            let made: Vec<StoredText> = batch
71469×
                .par_iter()
71570×
                .map(|i| {
71670×
                    let Edit::Insert { lines, .. } = &self.edits[*i] else {
7170×
                        unreachable!("only inserts are stored as text");
718
                    };
71970×
                    let form = text_form(self.version, lines);
72070×
                    let content = match form {
72168×
                        TextForm::Raw => lines.bytes().to_vec(),
7222×
                        TextForm::Cbor | TextForm::CborFallback => cbor::encode_byte_strings(lines),
723
                    };
72470×
                    StoredText {
72570×
                        blob: Blob::of(&content),
72670×
                        content,
72770×
                        form,
72870×
                    }
72970×
                })
73069×
                .collect();
73170×
            for (i, t) in batch.iter().zip(&made) {
73270×
                refs[*i] = Some((t.blob.hash(), t.form));
73370×
            }
73469×
            if !sink(made) {
7350×
                break;
73669×
            }
737
        }
7387998×
        refs
7397998×
    }
740
741
    /// The change's value, its stored texts handed to `sink`; `compact`
742
    /// as [`Edit::to_value`] takes it, for a value that is only encoded.
7437998×
    fn value_with(&self, sink: &mut TextSink, compact: bool) -> Value {
7447998×
        Value::map([
7457998×
            (0, Value::Uint(self.version)),
7467998×
            (1, Value::Bytes(self.change_id.0.to_vec())),
747
            (
748
                2,
749
                Value::Array(
7507998×
                    self.deps
7517998×
                        .iter()
7529705×
                        .map(|d| Value::Bytes(d.0.to_vec()))
7537998×
                        .collect(),
754
                ),
755
            ),
7567998×
            (3, Value::Bytes(self.author.0.to_vec())),
7577998×
            (4, self.hlc.to_value()),
7587998×
            (5, Value::Text(self.message.clone())),
759
            (
760
                6,
7617998×
                Value::Array(self.tree_ops.iter().map(|t| t.to_value()).collect()),
762
            ),
763
            (
764
                7,
765
                Value::Array(
7667998×
                    self.edits
7677998×
                        .iter()
7687998×
                        .zip(self.stored_texts(sink))
7699987×
                        .map(|(e, stored)| e.to_value(stored, compact))
7707998×
                        .collect(),
771
                ),
772
            ),
7737998×
            (8, Value::Uint(self.kind.tag())),
7747998×
            (9, Value::Map(BTreeMap::new())),
775
        ])
7767998×
    }
777
7784180×
    pub fn encode(&self) -> Vec<u8> {
7794180×
        debug_assert!(
7804180×
            self.deps.windows(2).all(|w| w[0] < w[1]),
781
            "deps must be normalized"
782
        );
7834180×
        cbor::encode(&self.value_with(&mut |_| true, true))
7844180×
    }
785
786
    /// The encoding and the text blobs its large inserts are stored as,
787
    /// which must be stored with it.
788115×
    pub fn encode_parts(&self) -> (Vec<u8>, Vec<StoredText>) {
789115×
        debug_assert!(
790115×
            self.deps.windows(2).all(|w| w[0] < w[1]),
791
            "deps must be normalized"
792
        );
793115×
        let mut texts = Vec::new();
794115×
        let bytes = cbor::encode(&self.value_with(
7955×
            &mut |batch| {
7965×
                texts.extend(batch);
7975×
                true
7985×
            },
799
            true,
800
        ));
801115×
        (bytes, texts)
802115×
    }
803
804
    /// The encoding, handing the stored texts to `store` a batch at a time
805
    /// as they are made, so a change adding a whole tree never holds all
806
    /// its text twice. The first error from `store` is returned.
8073697×
    pub fn encode_streaming<E>(
8083697×
        &self,
8093697×
        mut store: impl FnMut(Vec<StoredText>) -> Result<(), E>,
8103697×
    ) -> Result<Vec<u8>, E> {
8113697×
        debug_assert!(
8123697×
            self.deps.windows(2).all(|w| w[0] < w[1]),
813
            "deps must be normalized"
814
        );
8153697×
        let mut failed = None;
8163697×
        let value = self.value_with(
81759×
            &mut |batch| match store(batch) {
81859×
                Ok(()) => true,
8190×
                Err(e) => {
8200×
                    failed = Some(e);
8210×
                    false
822
                }
82359×
            },
824
            true,
825
        );
8263697×
        match failed {
8270×
            Some(e) => Err(e),
8283697×
            None => Ok(cbor::encode(&value)),
829
        }
8303697×
    }
831
832
    /// Whether any insert is stored as a text blob.
8333×
    pub fn has_stored_text(&self) -> bool {
8347×
        self.edits.iter().any(|e| e.stored_text(self.version))
8353×
    }
836
8374173×
    pub fn hash(&self) -> ChangeHash {
8384173×
        hash_object(ObjectType::Change, &self.encode())
8394173×
    }
840
841
    /// Decode a change that stores no text blob; one that does needs
842
    /// [`Change::decode_with`] and something to read them from.
8434×
    pub fn decode(bytes: &[u8]) -> Result<Change, ObjectError> {
8444×
        Self::decode_with(bytes, &mut |r, _, _| {
8451×
            schema(format!(
846
                "text blob {} needs a store to read it from",
8471×
                r.short(12)
848
            ))
8491×
        })
8504×
    }
851
852
    /// Decode a change, reading each stored insert's lines with `resolve`
853
    /// (given the text blob and the line count the insert claims).
8545356×
    pub fn decode_with(bytes: &[u8], resolve: &mut Resolver) -> Result<Change, ObjectError> {
8555356×
        let v = cbor::decode(bytes)?;
8565356×
        Self::from_value_with(&v, resolve)
8575356×
    }
858
85910983×
    pub fn from_value(v: &Value) -> Result<Change, ObjectError> {
86010983×
        Self::from_value_with(v, &mut |r, _, _| {
8610×
            schema(format!(
862
                "text blob {} needs a store to read it from",
8630×
                r.short(12)
864
            ))
8650×
        })
86610983×
    }
867
86816339×
    pub(crate) fn from_value_with(
86916339×
        v: &Value,
87016339×
        resolve: &mut Resolver,
87116339×
    ) -> Result<Change, ObjectError> {
87216339×
        let m = map(v, "change")?;
87316339×
        let version = uint(field(m, 0, "version")?, "version")?;
87416339×
        if !(1..=CHANGE_VERSION).contains(&version) {
8751×
            return schema(format!("unsupported change format version {version}"));
87616338×
        }
87716338×
        let change_id = ChangeId::from_slice(bytes(field(m, 1, "change_id")?, "change_id")?)
87816338×
            .ok_or_else(|| ObjectError::Schema("change_id: expected 16 bytes".into()))?;
87916338×
        let deps = array(field(m, 2, "deps")?, "deps")?
88016338×
            .iter()
88116338×
            .map(|d| oref(d, "dep"))
88216338×
            .collect::<Result<Vec<_>, _>>()?;
88316338×
        if !deps.windows(2).all(|w| w[0] < w[1]) {
8841×
            return schema("deps must be sorted and unique");
88516337×
        }
88616337×
        if deps.iter().any(|d| d.is_zero()) {
8870×
            return schema("deps may not contain the self reference");
88816337×
        }
88916337×
        let extra = map(field(m, 9, "extra")?, "extra")?;
89016337×
        if !extra.is_empty() {
8911×
            return schema("extra must be empty in format version 1");
89216336×
        }
893
        Ok(Change {
89416336×
            version,
89516336×
            change_id,
89616336×
            deps,
89716336×
            author: oref(field(m, 3, "author")?, "author")?,
89816336×
            hlc: Hlc::from_value(field(m, 4, "hlc")?)?,
89916336×
            message: text(field(m, 5, "message")?, "message")?.to_owned(),
90016336×
            tree_ops: array(field(m, 6, "tree_ops")?, "tree_ops")?
90116336×
                .iter()
90216336×
                .map(TreeOp::from_value)
90316336×
                .collect::<Result<Vec<_>, _>>()?,
90416336×
            edits: array(field(m, 7, "edits")?, "edits")?
90516336×
                .iter()
90616336×
                .map(|e| Edit::from_value(e, version, resolve))
90716336×
                .collect::<Result<Vec<_>, _>>()?,
90816333×
            kind: ChangeKind::from_tag(uint(field(m, 8, "kind")?, "kind")?)?,
909
        })
91016339×
    }
911
}
912
913
/// Reads a stored insert's lines, given the text blob and the line count
914
/// the insert claims; [`text_lines`] reads and checks a blob's content.
915
/// A resolver may also return no lines, leaving a placeholder for a reader
916
/// that fills it in later (history-only states, spec §11).
917
pub type Resolver<'a> = dyn FnMut(&ObjectRef, u32, TextForm) -> Result<Lines, ObjectError> + 'a;
918
919
/// Receives the stored texts of a change being encoded, a batch at a
920
/// time; returns false to stop making more.
921
type TextSink<'a> = dyn FnMut(Vec<StoredText>) -> bool + 'a;
922
923
/// Where a history-only load finds an insert's lines when it builds that
924
/// file's graph (spec §11): it keeps no line until then.
925
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
926
pub enum TextRef {
927
    /// A stored insert: its text blob, line count, and the blob's form.
928
    Blob(ObjectRef, u32, TextForm),
929
    /// An inline insert: the byte range of its lines' array in the
930
    /// encoding of `change`.
931
    Inline {
932
        change: ChangeHash,
933
        start: u32,
934
        end: u32,
935
    },
936
}
937
938
/// The lines of an inline insert from its array's bytes (see
939
/// [`TextRef::Inline`]).
94031263×
pub fn inline_lines(array: &[u8]) -> Result<Lines, ObjectError> {
94131263×
    let v = cbor::decode(array)?;
94231263×
    let lines = array_lines(&v)?;
94331263×
    if lines.is_empty() {
9440×
        return schema("insert with no lines");
94531263×
    }
94631263×
    Ok(lines)
94731263×
}
948
949
/// The lines of a CBOR array of byte strings, copied once into one buffer.
95035188×
fn array_lines(v: &Value) -> Result<Lines, ObjectError> {
95135188×
    Ok(array(v, "lines")?
95235188×
        .iter()
95357796×
        .map(|l| bytes(l, "line"))
95435188×
        .collect::<Result<Vec<&[u8]>, _>>()?
95535188×
        .into_iter()
95635188×
        .collect())
95735188×
}
958
959
impl Change {
960
    /// Decode a change for a history-only state (spec §11): every insert is
961
    /// left without its lines (an empty placeholder), and the result says
962
    /// where each one's lines are, in order. The inline lines are stepped
963
    /// over, not decoded: for a change that adds a whole tree, most of its
964
    /// bytes. `me` is the change's hash.
9658142×
    pub fn decode_skeleton(
9668142×
        bytes: &[u8],
9678142×
        me: ChangeHash,
9688142×
    ) -> Result<(Change, Vec<TextRef>), ObjectError> {
9698142×
        let mut c = cbor::Cursor::new(bytes);
9708142×
        let (major, n) = c.head()?;
9718142×
        if major != 5 {
9720×
            return schema("change: expected map");
9738142×
        }
9748142×
        let mut fields = Map::new();
9758142×
        let mut edits = Vec::new();
9768142×
        let mut texts = Vec::new();
9778142×
        let mut version = None;
9788142×
        let mut last: Option<u64> = None;
9798142×
        for _ in 0..n {
98081418×
            let k = map_key(&mut c, &mut last)?;
98181418×
            if k == 7 {
9828142×
                let Some(v) = version else {
9830×
                    return schema("change: version must come first");
984
                };
9858142×
                let (am, an) = c.head()?;
9868142×
                if am != 4 {
9870×
                    return schema("edits: expected array");
9888142×
                }
9898142×
                for _ in 0..an {
99048760×
                    edits.push(edit_skeleton(&mut c, v, me, &mut texts)?);
991
                }
9928141×
                fields.insert(7, Value::Array(Vec::new()));
993
            } else {
99473276×
                let val = c.value()?;
99573276×
                if k == 0 {
9968142×
                    version = Some(uint(&val, "version")?);
99765134×
                }
99873276×
                fields.insert(k, val);
999
            }
1000
        }
10018141×
        if !c.at_end() {
10020×
            return Err(ObjectError::Cbor(cbor::CborError::TrailingBytes));
10038141×
        }
10048141×
        let mut change = Change::from_value(&Value::Map(fields))?;
10058141×
        change.edits = edits;
10068141×
        Ok((change, texts))
10078142×
    }
1008
}
1009
1010
impl Change {
1011
    /// A change's metadata alone: its tree operations and edits are
1012
    /// stepped over, not decoded. For log, signature checks and walks of
1013
    /// the dependency graph, which read nothing else.
10142838×
    pub fn decode_meta(bytes: &[u8]) -> Result<crate::state::ChangeMeta, ObjectError> {
10152838×
        let mut c = cbor::Cursor::new(bytes);
10162838×
        let (major, n) = c.head()?;
10172838×
        if major != 5 {
10180×
            return schema("change: expected map");
10192838×
        }
10202838×
        let mut fields = Map::new();
10212838×
        let mut last: Option<u64> = None;
10222838×
        for _ in 0..n {
102328380×
            let k = map_key(&mut c, &mut last)?;
102428380×
            if k == 6 || k == 7 {
10255676×
                c.skip()?;
10265676×
                fields.insert(k, Value::Array(Vec::new()));
1027
            } else {
102822704×
                fields.insert(k, c.value()?);
1029
            }
1030
        }
10312838×
        if !c.at_end() {
10320×
            return Err(ObjectError::Cbor(cbor::CborError::TrailingBytes));
10332838×
        }
10342838×
        Ok(crate::state::ChangeMeta::of(&Change::from_value(
10352838×
            &Value::Map(fields),
10360×
        )?))
10372838×
    }
1038
}
1039
1040
/// A canonical map key: an unsigned integer above the previous one.
1041348687×
fn map_key(c: &mut cbor::Cursor, last: &mut Option<u64>) -> Result<u64, ObjectError> {
1042348687×
    let (km, k) = c.head()?;
1043348687×
    if km != 0 || last.is_some_and(|l| k <= l) {
10440×
        return Err(ObjectError::Cbor(cbor::CborError::BadMapKey));
1045348687×
    }
1046348687×
    *last = Some(k);
1047348687×
    Ok(k)
1048348687×
}
1049
1050
/// One edit of [`Change::decode_skeleton`]: an insert's lines are left out.
105148760×
fn edit_skeleton(
105248760×
    c: &mut cbor::Cursor,
105348760×
    version: u64,
105448760×
    me: ChangeHash,
105548760×
    texts: &mut Vec<TextRef>,
105648760×
) -> Result<Edit, ObjectError> {
105748760×
    let (major, n) = c.head()?;
105848760×
    if major != 5 {
10590×
        return schema("edit: expected map");
106048760×
    }
106148760×
    let mut fields = Map::new();
106248760×
    let mut inline: Option<(usize, usize, u64)> = None;
106348760×
    let mut last: Option<u64> = None;
106448760×
    for _ in 0..n {
1065238889×
        let k = map_key(c, &mut last)?;
1066238889×
        if k == 4 {
106745822×
            let start = c.pos();
106845822×
            let (am, count) = c.head()?;
106945822×
            if am != 4 {
10700×
                return schema("lines: expected array");
107145822×
            }
107245822×
            for _ in 0..count {
107361968×
                c.skip()?;
1074
            }
107545822×
            inline = Some((start, c.pos(), count));
107645822×
            continue;
1077193067×
        }
1078193067×
        fields.insert(k, c.value()?);
1079
    }
108048760×
    let Some((start, end, count)) = inline else {
10812938×
        return Edit::from_value(&Value::Map(fields), version, &mut |text, count, form| {
1082121×
            texts.push(TextRef::Blob(*text, count, form));
1083121×
            Ok(Lines::default())
1084121×
        });
1085
    };
108645822×
    if uint(field(&fields, 0, "edit tag")?, "edit tag")? != 0 || fields.contains_key(&5) {
10870×
        return schema("only an insert has inline lines, and not with a text blob");
108845822×
    }
108945822×
    if count == 0 {
10900×
        return schema("insert with no lines");
109145822×
    }
109245822×
    if version >= 2 && end - start >= TEXT_BLOB_MIN {
10931×
        return schema("an insert this large is stored as a text blob in a version 2 change");
109445821×
    }
109545821×
    let (Ok(start), Ok(end)) = (u32::try_from(start), u32::try_from(end)) else {
10960×
        return schema("change too large");
1097
    };
109845821×
    texts.push(TextRef::Inline {
109945821×
        change: me,
110045821×
        start,
110145821×
        end,
110245821×
    });
1103
    Ok(Edit::Insert {
110445821×
        node: node_id(field(&fields, 1, "edit.node")?, "edit.node")?,
110545821×
        after: AtomRef::from_value(field(&fields, 2, "after")?, "after")?,
110645821×
        before: AtomRef::from_value(field(&fields, 3, "before")?, "before")?,
110745821×
        lines: Lines::default(),
1108
    })
110948760×
}
1110
1111
/// A large insert's lines as stored (spec §6.1): the blob and its content,
1112
/// the canonical CBOR array of the lines' byte strings.
1113
#[derive(Debug, Clone, PartialEq, Eq)]
1114
pub struct StoredText {
1115
    pub blob: Blob,
1116
    pub content: Vec<u8>,
1117
    pub form: TextForm,
1118
}
1119
1120
/// The form a change of `version` stores these lines in.
112170×
fn text_form(version: u64, lines: &Lines) -> TextForm {
112269×
    match version {
112370×
        ..=2 => TextForm::Cbor,
112469×
        _ if lines.is_split_form() => TextForm::Raw,
11251×
        _ => TextForm::CborFallback,
1126
    }
112770×
}
1128
1129
/// The lines a text blob's `content` holds, checked against the rules for
1130
/// one: canonical, as many as the insert claims, and not short enough to
1131
/// have been inline.
1132188×
pub fn text_lines(content: &[u8], count: u32, form: TextForm) -> Result<Lines, ObjectError> {
1133188×
    let lines = match form {
1134182×
        TextForm::Raw => Lines::split(content),
11356×
        TextForm::Cbor | TextForm::CborFallback => array_lines(&cbor::decode(content)?)?,
1136
    };
1137
    // One encoding per change: the size rule and the form are both fixed
1138
    // by the lines.
1139188×
    if cbor::byte_strings_len(&lines) < TEXT_BLOB_MIN {
11402×
        return schema(format!(
1141
            "a text blob of {} bytes is short enough to be inline",
11422×
            content.len()
1143
        ));
1144186×
    }
1145186×
    if form == TextForm::CborFallback && lines.is_split_form() {
11461×
        return schema("these lines are stored as their bytes in a version 3 change");
1147185×
    }
1148185×
    if lines.len() != count as usize {
11492×
        return schema(format!(
1150
            "a text blob holds {} lines, the insert says {count}",
11512×
            lines.len()
1152
        ));
1153183×
    }
1154183×
    Ok(lines)
1155188×
}
1156
1157
// ---------------------------------------------------------------- blob
1158
1159
/// File content as an ordered list of chunks (spec §13.2).
1160
#[derive(Debug, Clone, PartialEq, Eq)]
1161
pub struct Blob {
1162
    pub length: u64,
1163
    pub chunks: Vec<ObjectRef>,
1164
}
1165
1166
impl Blob {
1167
    /// The blob for `content`: its chunks as content-defined chunking cuts
1168
    /// them (spec §13.2).
1169459×
    pub fn of(content: &[u8]) -> Blob {
1170
        Blob {
1171459×
            length: content.len() as u64,
1172459×
            chunks: crate::chunk::chunks(content)
1173459×
                .into_iter()
1174562×
                .map(|c| hash_object(ObjectType::Chunk, c))
1175459×
                .collect(),
1176
        }
1177459×
    }
1178
1179581×
    pub fn encode(&self) -> Vec<u8> {
1180581×
        cbor::encode(&Value::map([
1181581×
            (0, Value::Uint(FORMAT_VERSION)),
1182581×
            (1, Value::Uint(self.length)),
1183
            (
1184
                2,
1185
                Value::Array(
1186581×
                    self.chunks
1187581×
                        .iter()
1188783×
                        .map(|c| Value::Bytes(c.0.to_vec()))
1189581×
                        .collect(),
1190
                ),
1191
            ),
1192
        ]))
1193581×
    }
1194376×
    pub fn hash(&self) -> ObjectRef {
1195376×
        hash_object(ObjectType::Blob, &self.encode())
1196376×
    }
1197465×
    pub fn decode(bytes: &[u8]) -> Result<Blob, ObjectError> {
1198465×
        let v = cbor::decode(bytes)?;
1199465×
        let m = map(&v, "blob")?;
1200465×
        check_version(m)?;
1201
        Ok(Blob {
1202465×
            length: uint(field(m, 1, "length")?, "length")?,
1203465×
            chunks: array(field(m, 2, "chunks")?, "chunks")?
1204465×
                .iter()
1205694×
                .map(|c| oref(c, "chunk"))
1206465×
                .collect::<Result<Vec<_>, _>>()?,
1207
        })
1208465×
    }
1209
}
1210
1211
// ---------------------------------------------------------------- checkpoint
1212
1213
/// A materialized state (spec §11): the heads it stands for, how many changes
1214
/// it holds, and a blob with the serialized graphs (`State::checkpoint_payload`).
1215
#[derive(Debug, Clone, PartialEq, Eq)]
1216
pub struct Checkpoint {
1217
    pub heads: Vec<ChangeHash>,
1218
    pub count: u64,
1219
    pub payload: ObjectRef,
1220
}
1221
1222
impl Checkpoint {
122312×
    pub fn encode(&self) -> Vec<u8> {
122412×
        cbor::encode(&Value::map([
122512×
            (0, Value::Uint(FORMAT_VERSION)),
1226
            (
1227
                1,
1228
                Value::Array(
122912×
                    self.heads
123012×
                        .iter()
123113×
                        .map(|h| Value::Bytes(h.0.to_vec()))
123212×
                        .collect(),
1233
                ),
1234
            ),
123512×
            (2, Value::Uint(self.count)),
123612×
            (3, Value::Bytes(self.payload.0.to_vec())),
1237
        ]))
123812×
    }
12390×
    pub fn hash(&self) -> ObjectRef {
12400×
        hash_object(ObjectType::Checkpoint, &self.encode())
12410×
    }
124289×
    pub fn decode(bytes: &[u8]) -> Result<Checkpoint, ObjectError> {
124389×
        let v = cbor::decode(bytes)?;
124489×
        let m = map(&v, "checkpoint")?;
124589×
        check_version(m)?;
1246
        Ok(Checkpoint {
124789×
            heads: array(field(m, 1, "heads")?, "heads")?
124889×
                .iter()
124991×
                .map(|h| oref(h, "head"))
125089×
                .collect::<Result<Vec<_>, _>>()?,
125189×
            count: uint(field(m, 2, "count")?, "count")?,
125289×
            payload: oref(field(m, 3, "payload")?, "payload")?,
1253
        })
125489×
    }
1255
}
1256
1257
// ---------------------------------------------------------------- state
1258
1259
/// A state named by its heads (spec §10.1): what a bookmark or tag points at.
1260
#[derive(Debug, Clone, PartialEq, Eq, Default)]
1261
pub struct StateObject {
1262
    pub heads: Vec<ChangeHash>,
1263
}
1264
1265
impl StateObject {
12663192×
    pub fn new(mut heads: Vec<ChangeHash>) -> StateObject {
12673192×
        heads.sort();
12683192×
        heads.dedup();
12693192×
        StateObject { heads }
12703192×
    }
12713191×
    pub fn encode(&self) -> Vec<u8> {
12723191×
        debug_assert!(self.heads.windows(2).all(|w| w[0] < w[1]));
12733191×
        cbor::encode(&Value::map([
12743191×
            (0, Value::Uint(FORMAT_VERSION)),
1275
            (
1276
                1,
1277
                Value::Array(
12783191×
                    self.heads
12793191×
                        .iter()
12804083×
                        .map(|h| Value::Bytes(h.0.to_vec()))
12813191×
                        .collect(),
1282
                ),
1283
            ),
1284
        ]))
12853191×
    }
12862×
    pub fn hash(&self) -> ObjectRef {
12872×
        hash_object(ObjectType::State, &self.encode())
12882×
    }
12895370×
    pub fn decode(bytes: &[u8]) -> Result<StateObject, ObjectError> {
12905370×
        let v = cbor::decode(bytes)?;
12915370×
        let m = map(&v, "state")?;
12925370×
        check_version(m)?;
12935370×
        let heads = array(field(m, 1, "heads")?, "heads")?
12945370×
            .iter()
12958997×
            .map(|h| oref(h, "head"))
12965370×
            .collect::<Result<Vec<_>, _>>()?;
12975370×
        if !heads.windows(2).all(|w| w[0] < w[1]) {
12980×
            return schema("state heads must be sorted and unique");
12995370×
        }
13005370×
        Ok(StateObject { heads })
13015370×
    }
1302
}
1303
1304
#[cfg(test)]
1305
pub(crate) mod tests {
1306
    use super::*;
1307
130816×
    pub(crate) fn sample_change() -> Change {
130916×
        let n = NodeId([7; 16]);
131016×
        let dep = ObjectRef([1; 32]);
131116×
        let mut c = Change {
131216×
            version: crate::objects::CHANGE_VERSION,
131316×
            change_id: ChangeId([9; 16]),
131416×
            deps: vec![ObjectRef([2; 32]), dep],
131516×
            author: ObjectRef([3; 32]),
131616×
            hlc: Hlc {
131716×
                wall_ms: 1_700_000_000_000,
131816×
                logical: 4,
131916×
            },
132016×
            message: "Add greeting".into(),
132116×
            tree_ops: vec![
132216×
                TreeOp::AddNode {
132316×
                    node: n,
132416×
                    kind: NodeKind::File,
132516×
                    mode: Mode::TEXT,
132616×
                    parent: NodeId::ROOT,
132716×
                    name: "hello.txt".into(),
132816×
                },
132916×
                TreeOp::SetMode {
133016×
                    node: n,
133116×
                    old: OpRef {
133216×
                        change: ObjectRef::ZERO,
133316×
                        index: 0,
133416×
                    },
133516×
                    mode: Mode {
133616×
                        exec: true,
133716×
                        opaque: false,
133816×
                    },
133916×
                },
134016×
            ],
134116×
            edits: vec![
134216×
                Edit::Insert {
134316×
                    node: n,
134416×
                    after: AtomRef::Start,
134516×
                    before: AtomRef::End,
134616×
                    lines: vec![b"hello\n".to_vec(), b"world\n".to_vec()].into(),
134716×
                },
134816×
                Edit::Delete {
134916×
                    node: n,
135016×
                    atoms: vec![AtomRange {
135116×
                        change: dep,
135216×
                        from: 0,
135316×
                        to: 2,
135416×
                    }],
135516×
                },
135616×
                Edit::Replace {
135716×
                    node: n,
135816×
                    old: None,
135916×
                    new: Some(ObjectRef([5; 32])),
136016×
                },
136116×
            ],
136216×
            kind: ChangeKind::Normal,
136316×
        };
136416×
        c.normalize();
136516×
        c
136616×
    }
1367
1368
    #[test]
13691×
    fn change_round_trips_and_hash_is_stable() {
13701×
        let c = sample_change();
13711×
        let bytes = c.encode();
13721×
        let d = Change::decode(&bytes).unwrap();
13731×
        assert_eq!(c, d);
13741×
        assert_eq!(c.hash(), d.hash());
13751×
        assert_eq!(d.encode(), bytes);
1376
        // Any semantic difference changes the hash.
13771×
        let mut e = c.clone();
13781×
        e.message.push('!');
13791×
        assert_ne!(e.hash(), c.hash());
13801×
    }
1381
1382
    #[test]
13831×
    fn compact_encoding_is_the_plain_one() {
1384
        // Inline lines written as their encoding give the same bytes, so
1385
        // the same hash, as a value per line.
13861×
        let c = sample_change();
13871×
        assert!(!c.has_stored_text(), "the sample's insert is inline");
13881×
        let plain = cbor::encode(&c.to_value());
13891×
        assert_eq!(c.encode(), plain);
13901×
        assert_eq!(c.encode_parts().0, plain);
13911×
        let streamed: Result<Vec<u8>, ()> = c.encode_streaming(|_| Ok(()));
13921×
        assert_eq!(streamed.unwrap(), plain);
13931×
        assert_eq!(Change::decode(&plain).unwrap(), c);
13941×
    }
1395
1396
    #[test]
13971×
    fn decode_rejects_bad_changes() {
13981×
        let c = sample_change();
13991×
        let mut v = c.to_value();
14001×
        if let Value::Map(m) = &mut v {
14011×
            m.insert(0, Value::Uint(4));
14021×
        }
14031×
        assert!(
14041×
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("version"))
1405
        );
1406
14071×
        let mut v = c.to_value();
14081×
        if let Value::Map(m) = &mut v {
14091×
            m.insert(
14101×
                2,
14111×
                Value::Array(vec![Value::Bytes(vec![2; 32]), Value::Bytes(vec![1; 32])]),
14121×
            );
14131×
        }
14141×
        assert!(
14151×
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("sorted"))
1416
        );
1417
14181×
        let mut v = c.to_value();
14191×
        if let Value::Map(m) = &mut v {
14201×
            m.insert(9, Value::map([(0, Value::Null)]));
14211×
        }
14221×
        assert!(
14231×
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("extra"))
1424
        );
14251×
    }
1426
14276×
    fn big_lines(n: usize) -> Lines {
14286×
        (0..n)
14293000×
            .map(|i| format!("line {i} of a large insert\n").into_bytes())
14306×
            .collect()
14316×
    }
1432
14336×
    fn with_insert(mut c: Change, lines: Lines) -> Change {
14346×
        c.edits[0] = Edit::Insert {
14356×
            node: NodeId([7; 16]),
14366×
            after: AtomRef::Start,
14376×
            before: AtomRef::End,
14386×
            lines,
14396×
        };
14406×
        c
14416×
    }
1442
1443
    #[test]
14441×
    fn large_inserts_are_stored_as_text_blobs() {
14451×
        assert!(
14461×
            !sample_change().has_stored_text(),
1447
            "a small insert stays inline"
1448
        );
14491×
        let lines = big_lines(500);
14501×
        let c = with_insert(sample_change(), lines.clone());
14511×
        assert!(c.has_stored_text());
14521×
        let (bytes, texts) = c.encode_parts();
14531×
        assert_eq!(bytes, c.encode());
14541×
        assert!(bytes.len() < 1024, "{}", bytes.len());
14551×
        assert_eq!(texts.len(), 1);
1456
        // Version 3: the lines' bytes themselves.
14571×
        assert_eq!(texts[0].form, TextForm::Raw);
14581×
        assert_eq!(texts[0].content, lines.bytes());
14591×
        assert_eq!(texts[0].blob, Blob::of(&texts[0].content));
14601×
        assert!(Change::decode(&bytes).is_err(), "reading it needs the text");
14611×
        let d = Change::decode_with(&bytes, &mut |r, n, form| {
14621×
            assert_eq!(*r, texts[0].blob.hash());
14631×
            assert_eq!(form, TextForm::Raw);
14641×
            text_lines(&texts[0].content, n, form)
14651×
        })
14661×
        .unwrap();
14671×
        assert_eq!(d, c);
14681×
        assert_eq!(d.hash(), c.hash());
1469
        // Version 2 held the same lines as a CBOR array, and still reads.
14701×
        let mut v2 = c.clone();
14711×
        v2.version = 2;
14721×
        let (b2, t2) = v2.encode_parts();
14731×
        assert_eq!(t2[0].form, TextForm::Cbor);
14741×
        assert_eq!(t2[0].content, cbor::encode_byte_strings(&lines));
14751×
        let d2 = Change::decode_with(&b2, &mut |_, n, form| text_lines(&t2[0].content, n, form))
14761×
            .unwrap();
14771×
        assert_eq!(d2, v2);
1478
        // Lines the line rule would not split back stay a CBOR array,
1479
        // marked; and a marked array of lines that would is refused.
14801×
        let odd: Lines = (0..500)
1481500×
            .map(|i| format!("no newline {i}").into_bytes())
14821×
            .collect();
14831×
        let c3 = with_insert(sample_change(), odd.clone());
14841×
        let (b3, t3) = c3.encode_parts();
14851×
        assert_eq!(t3[0].form, TextForm::CborFallback);
14861×
        let d3 = Change::decode_with(&b3, &mut |_, n, form| text_lines(&t3[0].content, n, form))
14871×
            .unwrap();
14881×
        assert_eq!(d3, c3);
14891×
        let marked = cbor::encode_byte_strings(&lines);
14901×
        assert!(text_lines(&marked, 500, TextForm::CborFallback).is_err());
14911×
    }
1492
1493
    #[test]
14941×
    fn version_1_changes_keep_their_inline_inserts_and_hashes() {
14951×
        let mut c = with_insert(sample_change(), big_lines(500));
14961×
        c.version = 1;
14971×
        let bytes = c.encode();
14981×
        assert!(bytes.len() > TEXT_BLOB_MIN);
14991×
        let (again, texts) = c.encode_parts();
15001×
        assert!(texts.is_empty());
15011×
        assert_eq!(again, bytes);
15021×
        let d = Change::decode(&bytes).unwrap();
15031×
        assert_eq!(d.version, 1);
15041×
        assert_eq!(d.encode(), bytes);
1505
        // The same inline insert in a version 2 change is not canonical.
15061×
        let mut v = c.to_value();
15071×
        if let Value::Map(m) = &mut v {
15081×
            m.insert(0, Value::Uint(2));
15091×
        }
15101×
        assert!(
15111×
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("text blob"))
1512
        );
15131×
    }
1514
1515
    #[test]
15161×
    fn a_skeleton_leaves_every_insert_out_and_says_where_its_lines_are() {
15171×
        let mut c = with_insert(sample_change(), big_lines(500));
15181×
        c.edits.push(Edit::Insert {
15191×
            node: NodeId([8; 16]),
15201×
            after: AtomRef::Start,
15211×
            before: AtomRef::End,
15221×
            lines: vec![b"small\n".to_vec(), b"insert\n".to_vec()].into(),
15231×
        });
15241×
        let (bytes, stored) = c.encode_parts();
15251×
        let me = ObjectRef([4; 32]);
15261×
        let (skel, texts) = Change::decode_skeleton(&bytes, me).unwrap();
15271×
        assert_eq!(texts.len(), 2);
15281×
        let mut filled = skel.clone();
15291×
        let mut next = texts.iter();
15304×
        for e in filled.edits.iter_mut() {
15314×
            if let Edit::Insert { lines, .. } = e {
15322×
                assert!(lines.is_empty(), "no line kept");
15332×
                *lines = match next.next().unwrap() {
15341×
                    TextRef::Blob(r, n, form) => {
15351×
                        assert_eq!(*r, stored[0].blob.hash());
15361×
                        text_lines(&stored[0].content, *n, *form).unwrap()
1537
                    }
15381×
                    TextRef::Inline { change, start, end } => {
15391×
                        assert_eq!(*change, me);
15401×
                        inline_lines(&bytes[*start as usize..*end as usize]).unwrap()
1541
                    }
1542
                };
15432×
            }
1544
        }
15451×
        assert_eq!(filled, c, "filled in, it is the change");
1546
        // A version 1 change's large inline insert is fine; in version 2 it is not.
15471×
        let mut v1 = with_insert(sample_change(), big_lines(500));
15481×
        v1.version = 1;
15491×
        assert!(Change::decode_skeleton(&v1.encode(), me).is_ok());
15501×
        let mut v = v1.to_value();
15511×
        if let Value::Map(m) = &mut v {
15521×
            m.insert(0, Value::Uint(2));
15531×
        }
15541×
        assert!(Change::decode_skeleton(&cbor::encode(&v), me).is_err());
15551×
    }
1556
1557
    #[test]
15581×
    fn metadata_reads_without_the_operations() {
15591×
        let c = with_insert(sample_change(), big_lines(500));
15601×
        let (bytes, _) = c.encode_parts();
15611×
        let m = Change::decode_meta(&bytes).unwrap();
15621×
        assert_eq!(m, crate::state::ChangeMeta::of(&c));
15631×
    }
1564
1565
    #[test]
15661×
    fn text_blobs_are_checked() {
15671×
        let lines = big_lines(500);
15681×
        let content = cbor::encode_byte_strings(&lines);
15691×
        assert_eq!(content.len(), cbor::byte_strings_len(&lines));
15701×
        assert_eq!(
1571
            content,
15721×
            cbor::encode(&Value::Array(
1573500×
                lines.iter().map(|l| Value::Bytes(l.to_vec())).collect()
1574
            ))
1575
        );
15761×
        assert_eq!(text_lines(&content, 500, TextForm::Cbor).unwrap(), lines);
15771×
        assert!(text_lines(&content, 499, TextForm::Cbor).is_err());
15781×
        assert_eq!(
15791×
            text_lines(lines.bytes(), 500, TextForm::Raw).unwrap(),
1580
            lines
1581
        );
15821×
        assert!(text_lines(lines.bytes(), 501, TextForm::Raw).is_err());
15831×
        let small = cbor::encode_byte_strings(&vec![b"x\n".to_vec()].into());
15841×
        assert!(
15851×
            text_lines(&small, 1, TextForm::Cbor).is_err(),
1586
            "short enough to be inline"
1587
        );
15881×
        assert!(
15891×
            text_lines(b"x\n", 1, TextForm::Raw).is_err(),
1590
            "short enough to be inline"
1591
        );
15921×
    }
1593
1594
    #[test]
15951×
    fn names_are_validated() {
15965×
        for bad in ["", ".", "..", "a/b", "a\0b"] {
15975×
            assert!(validate_name(bad).is_err(), "{bad:?}");
1598
        }
15991×
        assert!(validate_name("Foo.c").is_ok());
16001×
        assert!(validate_name("...").is_ok());
16011×
    }
1602
1603
    #[test]
16041×
    fn mode_and_kind_tags() {
16053×
        for m in [
16061×
            Mode::TEXT,
16071×
            Mode::OPAQUE,
16081×
            Mode {
16091×
                exec: true,
16101×
                opaque: true,
16111×
            },
16121×
        ] {
16133×
            assert_eq!(Mode::from_bits(m.bits()).unwrap(), m);
1614
        }
16151×
        assert!(Mode::from_bits(4).is_err());
16164×
        for k in [
16171×
            NodeKind::File,
16181×
            NodeKind::Dir,
16191×
            NodeKind::Symlink,
16201×
            NodeKind::Subrepo,
16211×
        ] {
16224×
            assert_eq!(NodeKind::from_tag(k.tag()).unwrap(), k);
1623
        }
16241×
    }
1625
1626
    #[test]
16271×
    fn self_references_resolve() {
16281×
        let me = ObjectRef([8; 32]);
16291×
        assert_eq!(
16301×
            AtomRef::Atom {
16311×
                change: ObjectRef::ZERO,
16321×
                index: 3
16331×
            }
16341×
            .resolve(me),
16351×
            AtomRef::Atom {
16361×
                change: me,
16371×
                index: 3
16381×
            }
1639
        );
16401×
        assert_eq!(AtomRef::Start.resolve(me), AtomRef::Start);
16411×
        let other = ObjectRef([1; 32]);
16421×
        assert_eq!(
16431×
            OpRef {
16441×
                change: other,
16451×
                index: 1
16461×
            }
16471×
            .resolve(me)
1648
            .change,
1649
            other
1650
        );
16511×
    }
1652
1653
    #[test]
16541×
    fn state_object_round_trip() {
16551×
        let st = StateObject::new(vec![
16561×
            ObjectRef([2; 32]),
16571×
            ObjectRef([1; 32]),
16581×
            ObjectRef([2; 32]),
1659
        ]);
16601×
        assert_eq!(st.heads, vec![ObjectRef([1; 32]), ObjectRef([2; 32])]);
16611×
        assert_eq!(StateObject::decode(&st.encode()).unwrap(), st);
16621×
        assert_eq!(StateObject::new(vec![]).heads, vec![]);
16631×
        assert_ne!(StateObject::new(vec![]).hash(), st.hash());
16641×
    }
1665
1666
    #[test]
16671×
    fn blob_round_trip() {
16681×
        let b = Blob {
16691×
            length: 10,
16701×
            chunks: vec![ObjectRef([1; 32]), ObjectRef([2; 32])],
16711×
        };
16721×
        assert_eq!(Blob::decode(&b.encode()).unwrap(), b);
16731×
    }
1674
}