OXYGENHUB
# 6f16ce2be33b oxygen / crates / oxy-core / src / objects.rs
29.8 KiB · Rust · last changed Unbound hosted repositories adopt the first pusher's id 1 month ago source blame history raw
ff0af1ee unknown 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 std::collections::BTreeMap;
14
15
pub const FORMAT_VERSION: u64 = 1;
16
17
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
18
pub enum ObjectError {
19
    #[error("cbor: {0}")]
20
    Cbor(#[from] cbor::CborError),
21
    #[error("schema: {0}")]
22
    Schema(String),
23
}
24
25
pub(crate) fn schema<T>(msg: impl Into<String>) -> Result<T, ObjectError> {
26
    Err(ObjectError::Schema(msg.into()))
27
}
28
29
pub(crate) type Map = BTreeMap<u64, Value>;
30
31
pub(crate) fn field<'a>(m: &'a Map, k: u64, what: &str) -> Result<&'a Value, ObjectError> {
75167672 Andy McCall 32
    m.get(&k)
33
        .ok_or_else(|| ObjectError::Schema(format!("missing field {k} ({what})")))
ff0af1ee unknown 34
}
35
pub(crate) fn uint(v: &Value, what: &str) -> Result<u64, ObjectError> {
75167672 Andy McCall 36
    v.as_uint()
37
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected unsigned integer")))
ff0af1ee unknown 38
}
39
pub(crate) fn u32_(v: &Value, what: &str) -> Result<u32, ObjectError> {
40
    u32::try_from(uint(v, what)?).map_err(|_| ObjectError::Schema(format!("{what}: exceeds u32")))
41
}
42
pub(crate) fn text<'a>(v: &'a Value, what: &str) -> Result<&'a str, ObjectError> {
75167672 Andy McCall 43
    v.as_text()
44
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected text")))
ff0af1ee unknown 45
}
46
pub(crate) fn array<'a>(v: &'a Value, what: &str) -> Result<&'a [Value], ObjectError> {
75167672 Andy McCall 47
    v.as_array()
48
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected array")))
ff0af1ee unknown 49
}
50
pub(crate) fn map<'a>(v: &'a Value, what: &str) -> Result<&'a Map, ObjectError> {
75167672 Andy McCall 51
    v.as_map()
52
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected map")))
ff0af1ee unknown 53
}
54
pub(crate) fn bytes<'a>(v: &'a Value, what: &str) -> Result<&'a [u8], ObjectError> {
75167672 Andy McCall 55
    v.as_bytes()
56
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected bytes")))
ff0af1ee unknown 57
}
58
pub(crate) fn oref(v: &Value, what: &str) -> Result<ObjectRef, ObjectError> {
75167672 Andy McCall 59
    ObjectRef::from_slice(bytes(v, what)?)
60
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected 32 bytes")))
ff0af1ee unknown 61
}
62
pub(crate) fn node_id(v: &Value, what: &str) -> Result<NodeId, ObjectError> {
75167672 Andy McCall 63
    NodeId::from_slice(bytes(v, what)?)
64
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected 16 bytes")))
ff0af1ee unknown 65
}
66
pub(crate) fn check_version(m: &Map) -> Result<(), ObjectError> {
67
    let v = uint(field(m, 0, "version")?, "version")?;
68
    if v != FORMAT_VERSION {
69
        return schema(format!("unsupported format version {v}"));
70
    }
71
    Ok(())
72
}
73
74
// ---------------------------------------------------------------- Hlc
75
76
/// Hybrid logical clock (spec §6.4).
77
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
78
pub struct Hlc {
79
    pub wall_ms: u64,
80
    pub logical: u32,
81
}
82
83
impl Hlc {
84
    pub fn now() -> Hlc {
85
        let wall_ms = std::time::SystemTime::now()
86
            .duration_since(std::time::UNIX_EPOCH)
87
            .map(|d| d.as_millis() as u64)
88
            .unwrap_or(0);
75167672 Andy McCall 89
        Hlc {
90
            wall_ms,
91
            logical: 0,
92
        }
ff0af1ee unknown 93
    }
94
    pub(crate) fn to_value(self) -> Value {
75167672 Andy McCall 95
        Value::map([
96
            (0, Value::Uint(self.wall_ms)),
97
            (1, Value::Uint(self.logical as u64)),
98
        ])
ff0af1ee unknown 99
    }
100
    pub(crate) fn from_value(v: &Value) -> Result<Hlc, ObjectError> {
101
        let m = map(v, "hlc")?;
102
        Ok(Hlc {
103
            wall_ms: uint(field(m, 0, "hlc.wall_ms")?, "hlc.wall_ms")?,
104
            logical: u32_(field(m, 1, "hlc.logical")?, "hlc.logical")?,
105
        })
106
    }
107
}
108
109
// ---------------------------------------------------------------- tree
110
111
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112
pub enum NodeKind {
113
    File,
114
    Dir,
115
    Symlink,
4dee277c Andy McCall ↶ 116
    /// A nested repository pinned to a state (spec §7.8); content is a pin.
117
    Subrepo,
ff0af1ee unknown 118
}
119
120
impl NodeKind {
252e8cb2 Andy McCall ↶ 121
    pub(crate) fn tag(self) -> u64 {
ff0af1ee unknown 122
        match self {
123
            NodeKind::File => 0,
124
            NodeKind::Dir => 1,
125
            NodeKind::Symlink => 2,
4dee277c Andy McCall ↶ 126
            NodeKind::Subrepo => 3,
ff0af1ee unknown 127
        }
128
    }
252e8cb2 Andy McCall ↶ 129
    pub(crate) fn from_tag(t: u64) -> Result<NodeKind, ObjectError> {
ff0af1ee unknown 130
        Ok(match t {
131
            0 => NodeKind::File,
132
            1 => NodeKind::Dir,
133
            2 => NodeKind::Symlink,
4dee277c Andy McCall ↶ 134
            3 => NodeKind::Subrepo,
ff0af1ee unknown 135
            _ => return schema(format!("unknown node kind {t}")),
136
        })
137
    }
138
}
139
140
/// Executable bit plus content mode (spec §7.1, §7.5).
141
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
142
pub struct Mode {
143
    pub exec: bool,
144
    pub opaque: bool,
145
}
146
147
impl Mode {
75167672 Andy McCall 148
    pub const TEXT: Mode = Mode {
149
        exec: false,
150
        opaque: false,
151
    };
152
    pub const OPAQUE: Mode = Mode {
153
        exec: false,
154
        opaque: true,
155
    };
252e8cb2 Andy McCall ↶ 156
    pub(crate) fn bits(self) -> u64 {
ff0af1ee unknown 157
        (self.exec as u64) | ((self.opaque as u64) << 1)
158
    }
252e8cb2 Andy McCall ↶ 159
    pub(crate) fn from_bits(b: u64) -> Result<Mode, ObjectError> {
ff0af1ee unknown 160
        if b > 3 {
161
            return schema(format!("unknown mode bits {b}"));
162
        }
75167672 Andy McCall 163
        Ok(Mode {
164
            exec: b & 1 != 0,
165
            opaque: b & 2 != 0,
166
        })
ff0af1ee unknown 167
    }
168
}
169
170
/// Reference to a tree operation of some change: the placement or mode atom it
171
/// introduced. `change == ObjectRef::ZERO` means this change.
172
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
173
pub struct OpRef {
174
    pub change: ChangeHash,
175
    pub index: u32,
176
}
177
178
impl OpRef {
179
    fn to_value(self) -> Value {
75167672 Andy McCall 180
        Value::Array(vec![
181
            Value::Bytes(self.change.0.to_vec()),
182
            Value::Uint(self.index as u64),
183
        ])
ff0af1ee unknown 184
    }
185
    fn from_value(v: &Value, what: &str) -> Result<OpRef, ObjectError> {
186
        let a = array(v, what)?;
187
        if a.len() != 2 {
188
            return schema(format!("{what}: expected [hash, index]"));
189
        }
75167672 Andy McCall 190
        Ok(OpRef {
191
            change: oref(&a[0], what)?,
192
            index: u32_(&a[1], what)?,
193
        })
ff0af1ee unknown 194
    }
195
    /// Replace a self reference with the concrete change hash.
196
    pub fn resolve(self, me: ChangeHash) -> OpRef {
75167672 Andy McCall 197
        if self.change.is_zero() {
198
            OpRef { change: me, ..self }
199
        } else {
200
            self
201
        }
ff0af1ee unknown 202
    }
203
}
204
205
#[derive(Debug, Clone, PartialEq, Eq)]
206
pub enum TreeOp {
75167672 Andy McCall 207
    AddNode {
208
        node: NodeId,
209
        kind: NodeKind,
210
        mode: Mode,
211
        parent: NodeId,
212
        name: String,
213
    },
214
    RemoveNode {
215
        node: NodeId,
216
        placement: OpRef,
217
    },
218
    Move {
219
        node: NodeId,
220
        old: OpRef,
221
        parent: NodeId,
222
        name: String,
223
    },
224
    SetMode {
225
        node: NodeId,
226
        old: OpRef,
227
        mode: Mode,
228
    },
ff0af1ee unknown 229
}
230
231
/// Names must be valid, non-empty, not `.`/`..`, and free of `/` and NUL (spec §7.1).
232
pub fn validate_name(name: &str) -> Result<(), ObjectError> {
233
    if name.is_empty() || name == "." || name == ".." || name.contains('/') || name.contains('\0') {
234
        return schema(format!("invalid placement name {name:?}"));
235
    }
236
    Ok(())
237
}
238
239
impl TreeOp {
240
    pub fn node(&self) -> NodeId {
241
        match self {
242
            TreeOp::AddNode { node, .. }
243
            | TreeOp::RemoveNode { node, .. }
244
            | TreeOp::Move { node, .. }
245
            | TreeOp::SetMode { node, .. } => *node,
246
        }
247
    }
248
249
    fn to_value(&self) -> Value {
250
        let nid = |n: &NodeId| Value::Bytes(n.0.to_vec());
251
        match self {
75167672 Andy McCall 252
            TreeOp::AddNode {
253
                node,
254
                kind,
255
                mode,
256
                parent,
257
                name,
258
            } => Value::map([
ff0af1ee unknown 259
                (0, Value::Uint(0)),
260
                (1, nid(node)),
261
                (2, Value::Uint(kind.tag())),
262
                (3, Value::Uint(mode.bits())),
263
                (4, nid(parent)),
264
                (5, Value::Text(name.clone())),
265
            ]),
75167672 Andy McCall 266
            TreeOp::RemoveNode { node, placement } => Value::map([
267
                (0, Value::Uint(1)),
268
                (1, nid(node)),
269
                (2, placement.to_value()),
270
            ]),
271
            TreeOp::Move {
272
                node,
273
                old,
274
                parent,
275
                name,
276
            } => Value::map([
ff0af1ee unknown 277
                (0, Value::Uint(2)),
278
                (1, nid(node)),
279
                (2, old.to_value()),
280
                (4, nid(parent)),
281
                (5, Value::Text(name.clone())),
282
            ]),
283
            TreeOp::SetMode { node, old, mode } => Value::map([
284
                (0, Value::Uint(3)),
285
                (1, nid(node)),
286
                (2, old.to_value()),
287
                (3, Value::Uint(mode.bits())),
288
            ]),
289
        }
290
    }
291
292
    fn from_value(v: &Value) -> Result<TreeOp, ObjectError> {
293
        let m = map(v, "tree_op")?;
294
        let tag = uint(field(m, 0, "tree_op tag")?, "tree_op tag")?;
295
        let node = node_id(field(m, 1, "tree_op.node")?, "tree_op.node")?;
296
        let op = match tag {
297
            0 => {
298
                let name = text(field(m, 5, "name")?, "name")?.to_owned();
299
                validate_name(&name)?;
300
                TreeOp::AddNode {
301
                    node,
302
                    kind: NodeKind::from_tag(uint(field(m, 2, "kind")?, "kind")?)?,
303
                    mode: Mode::from_bits(uint(field(m, 3, "mode")?, "mode")?)?,
304
                    parent: node_id(field(m, 4, "parent")?, "parent")?,
305
                    name,
306
                }
307
            }
75167672 Andy McCall 308
            1 => TreeOp::RemoveNode {
309
                node,
310
                placement: OpRef::from_value(field(m, 2, "placement")?, "placement")?,
311
            },
ff0af1ee unknown 312
            2 => {
313
                let name = text(field(m, 5, "name")?, "name")?.to_owned();
314
                validate_name(&name)?;
315
                TreeOp::Move {
316
                    node,
317
                    old: OpRef::from_value(field(m, 2, "old")?, "old")?,
318
                    parent: node_id(field(m, 4, "parent")?, "parent")?,
319
                    name,
320
                }
321
            }
322
            3 => TreeOp::SetMode {
323
                node,
324
                old: OpRef::from_value(field(m, 2, "old")?, "old")?,
325
                mode: Mode::from_bits(uint(field(m, 3, "mode")?, "mode")?)?,
326
            },
327
            _ => return schema(format!("unknown tree op tag {tag}")),
328
        };
329
        if node.is_root() {
330
            return schema("tree op on the root node");
331
        }
332
        Ok(op)
333
    }
334
}
335
336
// ---------------------------------------------------------------- edits
337
338
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
339
pub enum AtomRef {
340
    Start,
341
    End,
342
    Atom { change: ChangeHash, index: u32 },
343
}
344
345
impl AtomRef {
346
    fn to_value(self) -> Value {
347
        match self {
348
            AtomRef::Start => Value::Uint(0),
349
            AtomRef::End => Value::Uint(1),
75167672 Andy McCall 350
            AtomRef::Atom { change, index } => Value::Array(vec![
351
                Value::Bytes(change.0.to_vec()),
352
                Value::Uint(index as u64),
353
            ]),
ff0af1ee unknown 354
        }
355
    }
356
    fn from_value(v: &Value, what: &str) -> Result<AtomRef, ObjectError> {
357
        match v {
358
            Value::Uint(0) => Ok(AtomRef::Start),
359
            Value::Uint(1) => Ok(AtomRef::End),
75167672 Andy McCall 360
            Value::Array(a) if a.len() == 2 => Ok(AtomRef::Atom {
361
                change: oref(&a[0], what)?,
362
                index: u32_(&a[1], what)?,
363
            }),
ff0af1ee unknown 364
            _ => schema(format!("{what}: expected atom reference")),
365
        }
366
    }
367
    pub fn resolve(self, me: ChangeHash) -> AtomRef {
368
        match self {
75167672 Andy McCall 369
            AtomRef::Atom { change, index } if change.is_zero() => {
370
                AtomRef::Atom { change: me, index }
371
            }
ff0af1ee unknown 372
            other => other,
373
        }
374
    }
375
}
376
377
/// Half-open range of atoms introduced by one change.
378
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
379
pub struct AtomRange {
380
    pub change: ChangeHash,
381
    pub from: u32,
382
    pub to: u32,
383
}
384
385
impl AtomRange {
386
    fn to_value(self) -> Value {
387
        Value::Array(vec![
388
            Value::Bytes(self.change.0.to_vec()),
389
            Value::Uint(self.from as u64),
390
            Value::Uint(self.to as u64),
391
        ])
392
    }
393
    fn from_value(v: &Value) -> Result<AtomRange, ObjectError> {
394
        let a = array(v, "atom range")?;
395
        if a.len() != 3 {
396
            return schema("atom range: expected [hash, from, to]");
397
        }
75167672 Andy McCall 398
        let r = AtomRange {
399
            change: oref(&a[0], "range.change")?,
400
            from: u32_(&a[1], "range.from")?,
401
            to: u32_(&a[2], "range.to")?,
402
        };
ff0af1ee unknown 403
        if r.from >= r.to {
404
            return schema("atom range: empty or inverted");
405
        }
406
        Ok(r)
407
    }
408
    pub fn resolve(self, me: ChangeHash) -> AtomRange {
75167672 Andy McCall 409
        if self.change.is_zero() {
410
            AtomRange { change: me, ..self }
411
        } else {
412
            self
413
        }
ff0af1ee unknown 414
    }
415
}
416
417
#[derive(Debug, Clone, PartialEq, Eq)]
418
pub enum Edit {
75167672 Andy McCall 419
    Insert {
420
        node: NodeId,
421
        after: AtomRef,
422
        before: AtomRef,
423
        lines: Vec<Vec<u8>>,
424
    },
425
    Delete {
426
        node: NodeId,
427
        atoms: Vec<AtomRange>,
428
    },
429
    Replace {
430
        node: NodeId,
431
        old: Option<ObjectRef>,
432
        new: Option<ObjectRef>,
433
    },
ff0af1ee unknown 434
}
435
436
impl Edit {
437
    pub fn node(&self) -> NodeId {
438
        match self {
75167672 Andy McCall 439
            Edit::Insert { node, .. } | Edit::Delete { node, .. } | Edit::Replace { node, .. } => {
440
                *node
441
            }
ff0af1ee unknown 442
        }
443
    }
444
445
    fn to_value(&self) -> Value {
446
        let nid = |n: &NodeId| Value::Bytes(n.0.to_vec());
447
        let opt = |o: &Option<ObjectRef>| match o {
448
            Some(r) => Value::Bytes(r.0.to_vec()),
449
            None => Value::Null,
450
        };
451
        match self {
75167672 Andy McCall 452
            Edit::Insert {
453
                node,
454
                after,
455
                before,
456
                lines,
457
            } => Value::map([
ff0af1ee unknown 458
                (0, Value::Uint(0)),
459
                (1, nid(node)),
460
                (2, after.to_value()),
461
                (3, before.to_value()),
75167672 Andy McCall 462
                (
463
                    4,
464
                    Value::Array(lines.iter().map(|l| Value::Bytes(l.clone())).collect()),
465
                ),
ff0af1ee unknown 466
            ]),
467
            Edit::Delete { node, atoms } => Value::map([
468
                (0, Value::Uint(1)),
469
                (1, nid(node)),
75167672 Andy McCall 470
                (
471
                    2,
472
                    Value::Array(atoms.iter().map(|r| r.to_value()).collect()),
473
                ),
ff0af1ee unknown 474
            ]),
75167672 Andy McCall 475
            Edit::Replace { node, old, new } => Value::map([
476
                (0, Value::Uint(2)),
477
                (1, nid(node)),
478
                (2, opt(old)),
479
                (3, opt(new)),
480
            ]),
ff0af1ee unknown 481
        }
482
    }
483
484
    fn from_value(v: &Value) -> Result<Edit, ObjectError> {
485
        let m = map(v, "edit")?;
486
        let tag = uint(field(m, 0, "edit tag")?, "edit tag")?;
487
        let node = node_id(field(m, 1, "edit.node")?, "edit.node")?;
488
        let opt = |v: &Value, what: &str| -> Result<Option<ObjectRef>, ObjectError> {
75167672 Andy McCall 489
            if v.is_null() {
490
                Ok(None)
491
            } else {
492
                Ok(Some(oref(v, what)?))
493
            }
ff0af1ee unknown 494
        };
495
        Ok(match tag {
496
            0 => {
497
                let lines = array(field(m, 4, "lines")?, "lines")?
498
                    .iter()
499
                    .map(|l| bytes(l, "line").map(|b| b.to_vec()))
500
                    .collect::<Result<Vec<_>, _>>()?;
501
                if lines.is_empty() {
502
                    return schema("insert with no lines");
503
                }
504
                Edit::Insert {
505
                    node,
506
                    after: AtomRef::from_value(field(m, 2, "after")?, "after")?,
507
                    before: AtomRef::from_value(field(m, 3, "before")?, "before")?,
508
                    lines,
509
                }
510
            }
511
            1 => {
512
                let atoms = array(field(m, 2, "atoms")?, "atoms")?
513
                    .iter()
514
                    .map(AtomRange::from_value)
515
                    .collect::<Result<Vec<_>, _>>()?;
516
                if atoms.is_empty() {
517
                    return schema("delete with no atoms");
518
                }
519
                Edit::Delete { node, atoms }
520
            }
75167672 Andy McCall 521
            2 => Edit::Replace {
522
                node,
523
                old: opt(field(m, 2, "old")?, "old")?,
524
                new: opt(field(m, 3, "new")?, "new")?,
525
            },
ff0af1ee unknown 526
            _ => return schema(format!("unknown edit tag {tag}")),
527
        })
528
    }
529
}
530
531
// ---------------------------------------------------------------- change
532
533
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
534
pub enum ChangeKind {
535
    Normal,
536
    Resolution,
537
    WorkingCopy,
538
}
539
540
impl ChangeKind {
252e8cb2 Andy McCall ↶ 541
    pub(crate) fn tag(self) -> u64 {
ff0af1ee unknown 542
        match self {
543
            ChangeKind::Normal => 0,
544
            ChangeKind::Resolution => 1,
545
            ChangeKind::WorkingCopy => 2,
546
        }
547
    }
252e8cb2 Andy McCall ↶ 548
    pub(crate) fn from_tag(t: u64) -> Result<ChangeKind, ObjectError> {
ff0af1ee unknown 549
        Ok(match t {
550
            0 => ChangeKind::Normal,
551
            1 => ChangeKind::Resolution,
552
            2 => ChangeKind::WorkingCopy,
553
            _ => return schema(format!("unknown change kind {t}")),
554
        })
555
    }
556
}
557
558
/// A change (spec §6.1). `deps` is kept sorted and unique by `normalize`.
559
#[derive(Debug, Clone, PartialEq, Eq)]
560
pub struct Change {
561
    pub change_id: ChangeId,
562
    pub deps: Vec<ChangeHash>,
563
    pub author: KeyId,
564
    pub hlc: Hlc,
565
    pub message: String,
566
    pub tree_ops: Vec<TreeOp>,
567
    pub edits: Vec<Edit>,
568
    pub kind: ChangeKind,
569
}
570
571
impl Change {
572
    pub fn normalize(&mut self) {
573
        self.deps.sort();
574
        self.deps.dedup();
575
    }
576
577
    pub fn to_value(&self) -> Value {
578
        Value::map([
579
            (0, Value::Uint(FORMAT_VERSION)),
580
            (1, Value::Bytes(self.change_id.0.to_vec())),
75167672 Andy McCall 581
            (
582
                2,
583
                Value::Array(
584
                    self.deps
585
                        .iter()
586
                        .map(|d| Value::Bytes(d.0.to_vec()))
587
                        .collect(),
588
                ),
589
            ),
ff0af1ee unknown 590
            (3, Value::Bytes(self.author.0.to_vec())),
591
            (4, self.hlc.to_value()),
592
            (5, Value::Text(self.message.clone())),
75167672 Andy McCall 593
            (
594
                6,
595
                Value::Array(self.tree_ops.iter().map(|t| t.to_value()).collect()),
596
            ),
597
            (
598
                7,
599
                Value::Array(self.edits.iter().map(|e| e.to_value()).collect()),
600
            ),
ff0af1ee unknown 601
            (8, Value::Uint(self.kind.tag())),
602
            (9, Value::Map(BTreeMap::new())),
603
        ])
604
    }
605
606
    pub fn encode(&self) -> Vec<u8> {
75167672 Andy McCall 607
        debug_assert!(
608
            self.deps.windows(2).all(|w| w[0] < w[1]),
609
            "deps must be normalized"
610
        );
ff0af1ee unknown 611
        cbor::encode(&self.to_value())
612
    }
613
614
    pub fn hash(&self) -> ChangeHash {
615
        hash_object(ObjectType::Change, &self.encode())
616
    }
617
618
    pub fn decode(bytes: &[u8]) -> Result<Change, ObjectError> {
619
        let v = cbor::decode(bytes)?;
620
        Self::from_value(&v)
621
    }
622
623
    pub fn from_value(v: &Value) -> Result<Change, ObjectError> {
624
        let m = map(v, "change")?;
625
        check_version(m)?;
626
        let change_id = ChangeId::from_slice(bytes(field(m, 1, "change_id")?, "change_id")?)
627
            .ok_or_else(|| ObjectError::Schema("change_id: expected 16 bytes".into()))?;
628
        let deps = array(field(m, 2, "deps")?, "deps")?
629
            .iter()
630
            .map(|d| oref(d, "dep"))
631
            .collect::<Result<Vec<_>, _>>()?;
632
        if !deps.windows(2).all(|w| w[0] < w[1]) {
633
            return schema("deps must be sorted and unique");
634
        }
635
        if deps.iter().any(|d| d.is_zero()) {
636
            return schema("deps may not contain the self reference");
637
        }
638
        let extra = map(field(m, 9, "extra")?, "extra")?;
639
        if !extra.is_empty() {
640
            return schema("extra must be empty in format version 1");
641
        }
642
        Ok(Change {
643
            change_id,
644
            deps,
645
            author: oref(field(m, 3, "author")?, "author")?,
646
            hlc: Hlc::from_value(field(m, 4, "hlc")?)?,
647
            message: text(field(m, 5, "message")?, "message")?.to_owned(),
648
            tree_ops: array(field(m, 6, "tree_ops")?, "tree_ops")?
649
                .iter()
650
                .map(TreeOp::from_value)
651
                .collect::<Result<Vec<_>, _>>()?,
652
            edits: array(field(m, 7, "edits")?, "edits")?
653
                .iter()
654
                .map(Edit::from_value)
655
                .collect::<Result<Vec<_>, _>>()?,
656
            kind: ChangeKind::from_tag(uint(field(m, 8, "kind")?, "kind")?)?,
657
        })
658
    }
659
}
660
661
// ---------------------------------------------------------------- blob
662
663
/// File content as an ordered list of chunks (spec §13.2).
664
#[derive(Debug, Clone, PartialEq, Eq)]
665
pub struct Blob {
666
    pub length: u64,
667
    pub chunks: Vec<ObjectRef>,
668
}
669
670
impl Blob {
671
    pub fn encode(&self) -> Vec<u8> {
672
        cbor::encode(&Value::map([
673
            (0, Value::Uint(FORMAT_VERSION)),
674
            (1, Value::Uint(self.length)),
75167672 Andy McCall 675
            (
676
                2,
677
                Value::Array(
678
                    self.chunks
679
                        .iter()
680
                        .map(|c| Value::Bytes(c.0.to_vec()))
681
                        .collect(),
682
                ),
683
            ),
ff0af1ee unknown 684
        ]))
685
    }
686
    pub fn hash(&self) -> ObjectRef {
687
        hash_object(ObjectType::Blob, &self.encode())
688
    }
689
    pub fn decode(bytes: &[u8]) -> Result<Blob, ObjectError> {
690
        let v = cbor::decode(bytes)?;
691
        let m = map(&v, "blob")?;
692
        check_version(m)?;
693
        Ok(Blob {
694
            length: uint(field(m, 1, "length")?, "length")?,
695
            chunks: array(field(m, 2, "chunks")?, "chunks")?
696
                .iter()
697
                .map(|c| oref(c, "chunk"))
698
                .collect::<Result<Vec<_>, _>>()?,
699
        })
700
    }
701
}
702
252e8cb2 Andy McCall ↶ 703
// ---------------------------------------------------------------- checkpoint
704
705
/// A materialized state (spec §11): the heads it stands for, how many changes
706
/// it holds, and a blob with the serialized graphs (`State::checkpoint_payload`).
707
#[derive(Debug, Clone, PartialEq, Eq)]
708
pub struct Checkpoint {
709
    pub heads: Vec<ChangeHash>,
710
    pub count: u64,
711
    pub payload: ObjectRef,
712
}
713
714
impl Checkpoint {
715
    pub fn encode(&self) -> Vec<u8> {
716
        cbor::encode(&Value::map([
717
            (0, Value::Uint(FORMAT_VERSION)),
75167672 Andy McCall 718
            (
719
                1,
720
                Value::Array(
721
                    self.heads
722
                        .iter()
723
                        .map(|h| Value::Bytes(h.0.to_vec()))
724
                        .collect(),
725
                ),
726
            ),
252e8cb2 Andy McCall ↶ 727
            (2, Value::Uint(self.count)),
728
            (3, Value::Bytes(self.payload.0.to_vec())),
729
        ]))
730
    }
731
    pub fn hash(&self) -> ObjectRef {
732
        hash_object(ObjectType::Checkpoint, &self.encode())
733
    }
734
    pub fn decode(bytes: &[u8]) -> Result<Checkpoint, ObjectError> {
735
        let v = cbor::decode(bytes)?;
736
        let m = map(&v, "checkpoint")?;
737
        check_version(m)?;
738
        Ok(Checkpoint {
75167672 Andy McCall 739
            heads: array(field(m, 1, "heads")?, "heads")?
740
                .iter()
741
                .map(|h| oref(h, "head"))
742
                .collect::<Result<Vec<_>, _>>()?,
252e8cb2 Andy McCall ↶ 743
            count: uint(field(m, 2, "count")?, "count")?,
744
            payload: oref(field(m, 3, "payload")?, "payload")?,
745
        })
746
    }
747
}
748
86e682c2 Andy McCall ↶ 749
// ---------------------------------------------------------------- state
750
751
/// A state named by its heads (spec §10.1): what a bookmark or tag points at.
752
#[derive(Debug, Clone, PartialEq, Eq, Default)]
753
pub struct StateObject {
754
    pub heads: Vec<ChangeHash>,
755
}
756
757
impl StateObject {
758
    pub fn new(mut heads: Vec<ChangeHash>) -> StateObject {
759
        heads.sort();
760
        heads.dedup();
761
        StateObject { heads }
762
    }
763
    pub fn encode(&self) -> Vec<u8> {
764
        debug_assert!(self.heads.windows(2).all(|w| w[0] < w[1]));
765
        cbor::encode(&Value::map([
766
            (0, Value::Uint(FORMAT_VERSION)),
75167672 Andy McCall 767
            (
768
                1,
769
                Value::Array(
770
                    self.heads
771
                        .iter()
772
                        .map(|h| Value::Bytes(h.0.to_vec()))
773
                        .collect(),
774
                ),
775
            ),
86e682c2 Andy McCall ↶ 776
        ]))
777
    }
778
    pub fn hash(&self) -> ObjectRef {
779
        hash_object(ObjectType::State, &self.encode())
780
    }
781
    pub fn decode(bytes: &[u8]) -> Result<StateObject, ObjectError> {
782
        let v = cbor::decode(bytes)?;
783
        let m = map(&v, "state")?;
784
        check_version(m)?;
75167672 Andy McCall 785
        let heads = array(field(m, 1, "heads")?, "heads")?
786
            .iter()
787
            .map(|h| oref(h, "head"))
788
            .collect::<Result<Vec<_>, _>>()?;
86e682c2 Andy McCall ↶ 789
        if !heads.windows(2).all(|w| w[0] < w[1]) {
790
            return schema("state heads must be sorted and unique");
791
        }
792
        Ok(StateObject { heads })
793
    }
794
}
795
ff0af1ee unknown 796
#[cfg(test)]
797
pub(crate) mod tests {
798
    use super::*;
799
800
    pub(crate) fn sample_change() -> Change {
801
        let n = NodeId([7; 16]);
802
        let dep = ObjectRef([1; 32]);
803
        let mut c = Change {
804
            change_id: ChangeId([9; 16]),
805
            deps: vec![ObjectRef([2; 32]), dep],
806
            author: ObjectRef([3; 32]),
75167672 Andy McCall 807
            hlc: Hlc {
808
                wall_ms: 1_700_000_000_000,
809
                logical: 4,
810
            },
ff0af1ee unknown 811
            message: "Add greeting".into(),
812
            tree_ops: vec![
75167672 Andy McCall 813
                TreeOp::AddNode {
814
                    node: n,
815
                    kind: NodeKind::File,
816
                    mode: Mode::TEXT,
817
                    parent: NodeId::ROOT,
818
                    name: "hello.txt".into(),
819
                },
820
                TreeOp::SetMode {
821
                    node: n,
822
                    old: OpRef {
823
                        change: ObjectRef::ZERO,
824
                        index: 0,
825
                    },
826
                    mode: Mode {
827
                        exec: true,
828
                        opaque: false,
829
                    },
830
                },
ff0af1ee unknown 831
            ],
832
            edits: vec![
75167672 Andy McCall 833
                Edit::Insert {
834
                    node: n,
835
                    after: AtomRef::Start,
836
                    before: AtomRef::End,
837
                    lines: vec![b"hello\n".to_vec(), b"world\n".to_vec()],
838
                },
839
                Edit::Delete {
840
                    node: n,
841
                    atoms: vec![AtomRange {
842
                        change: dep,
843
                        from: 0,
844
                        to: 2,
845
                    }],
846
                },
847
                Edit::Replace {
848
                    node: n,
849
                    old: None,
850
                    new: Some(ObjectRef([5; 32])),
851
                },
ff0af1ee unknown 852
            ],
853
            kind: ChangeKind::Normal,
854
        };
855
        c.normalize();
856
        c
857
    }
858
859
    #[test]
860
    fn change_round_trips_and_hash_is_stable() {
861
        let c = sample_change();
862
        let bytes = c.encode();
863
        let d = Change::decode(&bytes).unwrap();
864
        assert_eq!(c, d);
865
        assert_eq!(c.hash(), d.hash());
866
        assert_eq!(d.encode(), bytes);
867
        // Any semantic difference changes the hash.
868
        let mut e = c.clone();
869
        e.message.push('!');
870
        assert_ne!(e.hash(), c.hash());
871
    }
872
873
    #[test]
874
    fn decode_rejects_bad_changes() {
875
        let c = sample_change();
876
        let mut v = c.to_value();
877
        if let Value::Map(m) = &mut v {
878
            m.insert(0, Value::Uint(2));
879
        }
75167672 Andy McCall 880
        assert!(
881
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("version"))
882
        );
ff0af1ee unknown 883
884
        let mut v = c.to_value();
885
        if let Value::Map(m) = &mut v {
75167672 Andy McCall 886
            m.insert(
887
                2,
888
                Value::Array(vec![Value::Bytes(vec![2; 32]), Value::Bytes(vec![1; 32])]),
889
            );
ff0af1ee unknown 890
        }
75167672 Andy McCall 891
        assert!(
892
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("sorted"))
893
        );
ff0af1ee unknown 894
895
        let mut v = c.to_value();
896
        if let Value::Map(m) = &mut v {
897
            m.insert(9, Value::map([(0, Value::Null)]));
898
        }
75167672 Andy McCall 899
        assert!(
900
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("extra"))
901
        );
ff0af1ee unknown 902
    }
903
904
    #[test]
905
    fn names_are_validated() {
906
        for bad in ["", ".", "..", "a/b", "a\0b"] {
907
            assert!(validate_name(bad).is_err(), "{bad:?}");
908
        }
909
        assert!(validate_name("Foo.c").is_ok());
910
        assert!(validate_name("...").is_ok());
911
    }
912
913
    #[test]
914
    fn mode_and_kind_tags() {
75167672 Andy McCall 915
        for m in [
916
            Mode::TEXT,
917
            Mode::OPAQUE,
918
            Mode {
919
                exec: true,
920
                opaque: true,
921
            },
922
        ] {
ff0af1ee unknown 923
            assert_eq!(Mode::from_bits(m.bits()).unwrap(), m);
924
        }
925
        assert!(Mode::from_bits(4).is_err());
75167672 Andy McCall 926
        for k in [
927
            NodeKind::File,
928
            NodeKind::Dir,
929
            NodeKind::Symlink,
930
            NodeKind::Subrepo,
931
        ] {
ff0af1ee unknown 932
            assert_eq!(NodeKind::from_tag(k.tag()).unwrap(), k);
933
        }
934
    }
935
936
    #[test]
937
    fn self_references_resolve() {
938
        let me = ObjectRef([8; 32]);
75167672 Andy McCall 939
        assert_eq!(
940
            AtomRef::Atom {
941
                change: ObjectRef::ZERO,
942
                index: 3
943
            }
944
            .resolve(me),
945
            AtomRef::Atom {
946
                change: me,
947
                index: 3
948
            }
949
        );
ff0af1ee unknown 950
        assert_eq!(AtomRef::Start.resolve(me), AtomRef::Start);
951
        let other = ObjectRef([1; 32]);
75167672 Andy McCall 952
        assert_eq!(
953
            OpRef {
954
                change: other,
955
                index: 1
956
            }
957
            .resolve(me)
958
            .change,
959
            other
960
        );
ff0af1ee unknown 961
    }
962
963
    #[test]
86e682c2 Andy McCall ↶ 964
    fn state_object_round_trip() {
75167672 Andy McCall 965
        let st = StateObject::new(vec![
966
            ObjectRef([2; 32]),
967
            ObjectRef([1; 32]),
968
            ObjectRef([2; 32]),
969
        ]);
86e682c2 Andy McCall ↶ 970
        assert_eq!(st.heads, vec![ObjectRef([1; 32]), ObjectRef([2; 32])]);
971
        assert_eq!(StateObject::decode(&st.encode()).unwrap(), st);
972
        assert_eq!(StateObject::new(vec![]).heads, vec![]);
973
        assert_ne!(StateObject::new(vec![]).hash(), st.hash());
974
    }
975
976
    #[test]
ff0af1ee unknown 977
    fn blob_round_trip() {
75167672 Andy McCall 978
        let b = Blob {
979
            length: 10,
980
            chunks: vec![ObjectRef([1; 32]), ObjectRef([2; 32])],
981
        };
ff0af1ee unknown 982
        assert_eq!(Blob::decode(&b.encode()).unwrap(), b);
983
    }
984
}