@openlfcp/shared-objects 0.2.0-beta.1 → 0.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -30,6 +30,13 @@ export interface ParsedChange {
|
|
|
30
30
|
/** Hex other actors, ascending: actor index i is otherActors[i - 1]. */
|
|
31
31
|
readonly otherActors: readonly string[];
|
|
32
32
|
readonly ops: readonly ParsedOp[];
|
|
33
|
+
/**
|
|
34
|
+
* §14.1: the extra bytes after the columns (which rule 4 leaves free) begin
|
|
35
|
+
* with an Automerge author — an unsigned LEB128 1, a length L, then at least
|
|
36
|
+
* L bytes. automerge 0.12 reads it and asserts the change's sequence number
|
|
37
|
+
* is 1, so a later change carrying one is refused (finding D5).
|
|
38
|
+
*/
|
|
39
|
+
readonly beginsWithAuthor: boolean;
|
|
33
40
|
}
|
|
34
41
|
/**
|
|
35
42
|
* §11.3: checks that `bytes` (one change chunk within the §11.1 limits) is
|
|
@@ -349,6 +349,36 @@ function checkValue(type, bytes) {
|
|
|
349
349
|
if (type === 6)
|
|
350
350
|
utf8(bytes, "a string value");
|
|
351
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* §14.1: whether the extra bytes after a change's columns begin with an
|
|
354
|
+
* Automerge author (finding D5). Rule 4 leaves these bytes free, but
|
|
355
|
+
* automerge 0.12 reads an author there — an unsigned LEB128 1, a length L,
|
|
356
|
+
* then L bytes — and asserts the change's sequence number is 1. The LEB128 is
|
|
357
|
+
* read as automerge reads it: up to ten bytes, under 2^64, in any encoding
|
|
358
|
+
* (not the shortest-form `Reader` the rest of the walk uses).
|
|
359
|
+
*/
|
|
360
|
+
function beginsWithAuthor(extra) {
|
|
361
|
+
let pos = 0;
|
|
362
|
+
const leb = () => {
|
|
363
|
+
let value = 0n;
|
|
364
|
+
let shift = 0n;
|
|
365
|
+
for (;;) {
|
|
366
|
+
if (pos >= extra.length)
|
|
367
|
+
return null;
|
|
368
|
+
const b = extra[pos++];
|
|
369
|
+
if (shift === 63n && b !== 0 && b !== 1)
|
|
370
|
+
return null;
|
|
371
|
+
value |= BigInt(b & 0x7f) << shift;
|
|
372
|
+
if ((b & 0x80) === 0)
|
|
373
|
+
return value;
|
|
374
|
+
shift += 7n;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
if (leb() !== 1n)
|
|
378
|
+
return false;
|
|
379
|
+
const length = leb();
|
|
380
|
+
return length !== null && BigInt(extra.length - pos) >= length;
|
|
381
|
+
}
|
|
352
382
|
/**
|
|
353
383
|
* §11.3: checks that `bytes` (one change chunk within the §11.1 limits) is
|
|
354
384
|
* the canonical encoding of its change, and returns the change's operations
|
|
@@ -403,7 +433,9 @@ export function checkCanonicalChange(bytes) {
|
|
|
403
433
|
const data = new Map();
|
|
404
434
|
for (const m of metas)
|
|
405
435
|
data.set(m.spec, r.take(m.length));
|
|
406
|
-
// What follows is the change's extra bytes.
|
|
436
|
+
// What follows is the change's extra bytes (§11.3 rule 4), free except that
|
|
437
|
+
// automerge reads an author from their start (§14.1, finding D5).
|
|
438
|
+
const authored = beginsWithAuthor(bytes.subarray(r.pos));
|
|
407
439
|
const rle = (spec) => {
|
|
408
440
|
const d = data.get(spec);
|
|
409
441
|
return d === undefined ? undefined : column(spec, d);
|
|
@@ -615,6 +647,7 @@ export function checkCanonicalChange(bytes) {
|
|
|
615
647
|
deps: Object.freeze(deps.map(hex)),
|
|
616
648
|
otherActors: Object.freeze(others.map(hex)),
|
|
617
649
|
ops: Object.freeze(ops),
|
|
650
|
+
beginsWithAuthor: authored,
|
|
618
651
|
});
|
|
619
652
|
}
|
|
620
653
|
//# sourceMappingURL=canonical.js.map
|
|
@@ -12,6 +12,12 @@ export interface CheckedChange {
|
|
|
12
12
|
readonly deps: readonly string[];
|
|
13
13
|
/** Hex IDs of the other actors the change refers to (§11.1: each must be known to the document). */
|
|
14
14
|
readonly otherActors: readonly string[];
|
|
15
|
+
/**
|
|
16
|
+
* §14.1: the change's extra bytes begin with an Automerge author, allowed
|
|
17
|
+
* only in an actor's first change (finding D5). See
|
|
18
|
+
* {@link ParsedChange.beginsWithAuthor}.
|
|
19
|
+
*/
|
|
20
|
+
readonly beginsWithAuthor: boolean;
|
|
15
21
|
}
|
|
16
22
|
/**
|
|
17
23
|
* Checks that `bytes` is exactly one valid Automerge change (§11, §12) and
|
|
@@ -69,6 +69,14 @@ export function admitBatch(changes, doc, accept = () => undefined) {
|
|
|
69
69
|
});
|
|
70
70
|
continue;
|
|
71
71
|
}
|
|
72
|
+
else if (c.beginsWithAuthor && c.seq !== 1) {
|
|
73
|
+
refused.push({
|
|
74
|
+
change: c,
|
|
75
|
+
error: new ProfileInvalidError("INVALID_AUTOMERGE_BYTES", `the change's extra bytes begin with an author but its sequence number is ${c.seq}, not 1 (§14.1)`),
|
|
76
|
+
held: false,
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
72
80
|
else {
|
|
73
81
|
const references = referenceRefusal(c, doc);
|
|
74
82
|
if (references !== null) {
|
|
@@ -124,6 +132,12 @@ export function admitChange(change, doc) {
|
|
|
124
132
|
kind: "invalid",
|
|
125
133
|
error: new ProfileInvalidError("INVALID_AUTOMERGE_BYTES", `actor ${change.actor} sequence ${change.seq} skips sequence ${latest + 1} (§14.1)`),
|
|
126
134
|
};
|
|
135
|
+
// §14.1: an author belongs only in an actor's first change (finding D5).
|
|
136
|
+
if (change.beginsWithAuthor && change.seq !== 1)
|
|
137
|
+
return {
|
|
138
|
+
kind: "invalid",
|
|
139
|
+
error: new ProfileInvalidError("INVALID_AUTOMERGE_BYTES", `the change's extra bytes begin with an author but its sequence number is ${change.seq}, not 1 (§14.1)`),
|
|
140
|
+
};
|
|
127
141
|
// §11.4: checked only; the caller adds the change once the engine applied it.
|
|
128
142
|
const references = referenceRefusal(change, doc);
|
|
129
143
|
if (references !== null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlfcp/shared-objects",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.2",
|
|
4
4
|
"description": "SHARED-OBJECTS-PROFILE-01 (org.openlfcp.shared-objects.v1) implementation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openlfcp",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@automerge/automerge": "3.5.0",
|
|
55
55
|
"fflate": "0.8.3",
|
|
56
|
-
"@openlfcp/core": "^0.2.0-beta.
|
|
57
|
-
"@openlfcp/crypto": "^0.2.0-beta.
|
|
56
|
+
"@openlfcp/core": "^0.2.0-beta.2",
|
|
57
|
+
"@openlfcp/crypto": "^0.2.0-beta.2"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"fast-check": "4.10.2"
|