Skip to content

Commit 38f29b3

Browse files
authored
Merge pull request #399 from streamich/crdt-indexed-codec
JSON CRDT indexed codec
2 parents 95826b1 + 40c43ba commit 38f29b3

File tree

8 files changed

+365
-219
lines changed

8 files changed

+365
-219
lines changed

src/json-crdt/__tests__/fuzzer/SessionLogical.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {encode as encodeJson} from '../../../json-crdt-patch/codec/verbose/encod
1010
import {Encoder as BinaryEncoder} from '../../codec/structural/binary/Encoder';
1111
import {Encoder as CompactEncoder} from '../../codec/structural/compact/Encoder';
1212
import {Encoder as JsonEncoder} from '../../codec/structural/verbose/Encoder';
13+
import {Encoder as IndexedBinaryEncoder} from '../../codec/indexed/binary/Encoder';
14+
import {Decoder as IndexedBinaryDecoder} from '../../codec/indexed/binary/Decoder';
1315
import {generateInteger} from './util';
1416
import {Model} from '../..';
1517
import {Patch} from '../../../json-crdt-patch/Patch';
@@ -27,6 +29,8 @@ const compactEncoder = new CompactEncoder();
2729
const compactDecoder = new CompactDecoder();
2830
const binaryEncoder = new BinaryEncoder();
2931
const binaryDecoder = new BinaryDecoder();
32+
const indexedBinaryEncoder = new IndexedBinaryEncoder();
33+
const indexedBinaryDecoder = new IndexedBinaryDecoder();
3034

3135
export class SessionLogical {
3236
public models: Model[] = [];
@@ -192,6 +196,7 @@ export class SessionLogical {
192196
if (randomU32(0, 1)) model = jsonDecoder.decode(jsonEncoder.encode(model));
193197
if (randomU32(0, 1)) model = compactDecoder.decode(compactEncoder.encode(model));
194198
if (randomU32(0, 1)) model = binaryDecoder.decode(binaryEncoder.encode(model));
199+
if (randomU32(0, 1)) model = indexedBinaryDecoder.decode(indexedBinaryEncoder.encode(model));
195200
}
196201
for (let j = 0; j < this.concurrency; j++) {
197202
const patches = this.patches[j];
Lines changed: 98 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
import {
2-
ConNode,
3-
JsonNode,
4-
ValNode,
5-
ArrNode,
6-
ArrChunk,
7-
BinNode,
8-
BinChunk,
9-
ObjNode,
10-
StrNode,
11-
StrChunk,
12-
} from '../../../nodes';
1+
import * as nodes from '../../../nodes';
132
import {ClockTable} from '../../../../json-crdt-patch/codec/clock/ClockTable';
143
import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtReader';
154
import {IndexedFields, FieldName, IndexedNodeFields} from './types';
165
import {ITimestampStruct, IVectorClock, Timestamp, VectorClock} from '../../../../json-crdt-patch/clock';
176
import {Model, UNDEFINED} from '../../../model/Model';
18-
import {MsgPackDecoderFast} from '../../../../json-pack/msgpack';
7+
import {CborDecoderBase} from '../../../../json-pack/cbor/CborDecoderBase';
8+
import {CRDT_MAJOR} from '../../structural/binary/constants';
199

2010
export class Decoder {
21-
public readonly dec = new MsgPackDecoderFast<CrdtReader>(new CrdtReader());
11+
public readonly dec: CborDecoderBase<CrdtReader>;
2212
protected doc!: Model;
2313
protected clockTable?: ClockTable;
2414

15+
constructor(reader?: CrdtReader) {
16+
this.dec = new CborDecoderBase<CrdtReader>(reader || new CrdtReader());
17+
}
18+
2519
public decode<M extends Model>(
2620
fields: IndexedFields,
2721
ModelConstructor: new (clock: IVectorClock) => M = Model as unknown as new (clock: IVectorClock) => M,
@@ -47,14 +41,17 @@ export class Decoder {
4741
const rootValue = this.ts();
4842
doc.root.set(rootValue);
4943
}
50-
const docIndex = doc.index;
51-
for (const field in fields) {
44+
const index = doc.index;
45+
const keys = Object.keys(fields);
46+
const length = keys.length;
47+
for (let i = 0; i < length; i++) {
48+
const field = keys[i];
5249
if (field.length < 3) continue; // Skip "c" and "r".
5350
const arr = fields[field as FieldName];
5451
const id = clockTable.parseField(field as FieldName);
5552
reader.reset(arr);
5653
const node = this.decodeNode(id);
57-
docIndex.set(node.id, node);
54+
index.set(id, node);
5855
}
5956
return doc;
6057
}
@@ -64,107 +61,117 @@ export class Decoder {
6461
return new Timestamp(this.clockTable!.byIdx[sessionIndex].sid, timeDiff);
6562
}
6663

67-
protected decodeNode(id: ITimestampStruct): JsonNode {
64+
protected decodeNode(id: ITimestampStruct): nodes.JsonNode {
6865
const reader = this.dec.reader;
69-
const byte = reader.u8();
70-
if (byte <= 0b10001111) return this.cObj(id, byte & 0b1111);
71-
else if (byte <= 0b10011111) return this.cArr(id, byte & 0b1111);
72-
else if (byte <= 0b10111111) return this.cStr(id, byte & 0b11111);
73-
else {
74-
switch (byte) {
75-
case 0xc4:
76-
return this.cBin(id, reader.u8());
77-
case 0xc5:
78-
return this.cBin(id, reader.u16());
79-
case 0xc6:
80-
return this.cBin(id, reader.u32());
81-
case 0xd4:
82-
return this.cConst(id);
83-
case 0xd5:
84-
return new ConNode(id, this.ts());
85-
case 0xd6:
86-
return this.cVal(id);
87-
case 0xde:
88-
return this.cObj(id, reader.u16());
89-
case 0xdf:
90-
return this.cObj(id, reader.u32());
91-
case 0xdc:
92-
return this.cArr(id, reader.u16());
93-
case 0xdd:
94-
return this.cArr(id, reader.u32());
95-
case 0xd9:
96-
return this.cStr(id, reader.u8());
97-
case 0xda:
98-
return this.cStr(id, reader.u16());
99-
case 0xdb:
100-
return this.cStr(id, reader.u32());
101-
}
66+
const octet = reader.u8();
67+
const major = octet >> 5;
68+
const minor = octet & 0b11111;
69+
const length = minor < 24 ? minor : minor === 24 ? reader.u8() : minor === 25 ? reader.u16() : reader.u32();
70+
switch (major) {
71+
case CRDT_MAJOR.CON:
72+
return this.decodeCon(id, length);
73+
case CRDT_MAJOR.VAL:
74+
return this.decodeVal(id);
75+
case CRDT_MAJOR.OBJ:
76+
return this.decodeObj(id, length);
77+
case CRDT_MAJOR.VEC:
78+
return this.decodeVec(id, length);
79+
case CRDT_MAJOR.STR:
80+
return this.decodeStr(id, length);
81+
case CRDT_MAJOR.BIN:
82+
return this.decodeBin(id, length);
83+
case CRDT_MAJOR.ARR:
84+
return this.decodeArr(id, length);
10285
}
103-
10486
return UNDEFINED;
10587
}
10688

107-
public cConst(id: ITimestampStruct): ConNode {
108-
const val = this.dec.val();
109-
return new ConNode(id, val);
89+
public decodeCon(id: ITimestampStruct, length: number): nodes.ConNode {
90+
const decoder = this.dec;
91+
const data = !length ? decoder.val() : this.ts();
92+
const node = new nodes.ConNode(id, data);
93+
return node;
11094
}
11195

112-
public cVal(id: ITimestampStruct): ValNode {
96+
public decodeVal(id: ITimestampStruct): nodes.ValNode {
11397
const val = this.ts();
114-
return new ValNode(this.doc, id, val);
98+
const node = new nodes.ValNode(this.doc, id, val);
99+
return node;
115100
}
116101

117-
public cObj(id: ITimestampStruct, length: number): ObjNode {
102+
public decodeObj(id: ITimestampStruct, length: number): nodes.ObjNode {
118103
const decoder = this.dec;
119-
const obj = new ObjNode(this.doc, id);
104+
const obj = new nodes.ObjNode(this.doc, id);
120105
const keys = obj.keys;
121106
for (let i = 0; i < length; i++) {
122-
const key = String(decoder.val());
107+
const key = decoder.val() + '';
123108
const val = this.ts();
124109
keys.set(key, val);
125110
}
126111
return obj;
127112
}
128113

129-
protected cStr(id: ITimestampStruct, length: number): StrNode {
130-
const decoder = this.dec;
131-
const node = new StrNode(id);
132-
node.ingest(length, () => {
133-
const chunkId = this.ts();
134-
const val = decoder.val();
135-
if (typeof val === 'number') return new StrChunk(chunkId, val, '');
136-
const data = String(val);
137-
return new StrChunk(chunkId, data.length, data);
138-
});
114+
public decodeVec(id: ITimestampStruct, length: number): nodes.VecNode {
115+
const reader = this.dec.reader;
116+
const node = new nodes.VecNode(this.doc, id);
117+
const elements = node.elements;
118+
for (let i = 0; i < length; i++) {
119+
const octet = reader.u8();
120+
if (!octet) elements.push(undefined);
121+
else elements.push(this.ts());
122+
}
139123
return node;
140124
}
141125

142-
protected cBin(id: ITimestampStruct, length: number): BinNode {
143-
const decoder = this.dec;
144-
const reader = decoder.reader;
145-
const node = new BinNode(id);
146-
node.ingest(length, () => {
147-
const chunkId = this.ts();
148-
const [deleted, length] = reader.b1vu28();
149-
if (deleted) return new BinChunk(chunkId, length, undefined);
150-
const data = reader.buf(length);
151-
return new BinChunk(chunkId, length, data);
152-
});
126+
protected decodeStr(id: ITimestampStruct, length: number): nodes.StrNode {
127+
const node = new nodes.StrNode(id);
128+
node.ingest(length, this.decodeStrChunk);
153129
return node;
154130
}
155131

156-
protected cArr(id: ITimestampStruct, length: number): ArrNode {
132+
private decodeStrChunk = (): nodes.StrChunk => {
157133
const decoder = this.dec;
158134
const reader = decoder.reader;
159-
const node = new ArrNode(this.doc, id);
160-
node.ingest(length, () => {
161-
const chunkId = this.ts();
162-
const [deleted, length] = reader.b1vu28();
163-
if (deleted) return new ArrChunk(chunkId, length, undefined);
164-
const data: ITimestampStruct[] = [];
165-
for (let i = 0; i < length; i++) data.push(this.ts());
166-
return new ArrChunk(chunkId, length, data);
167-
});
135+
const id = this.ts();
136+
const isTombstone = reader.uint8[reader.x] === 0;
137+
if (isTombstone) {
138+
reader.x++;
139+
const length = reader.vu39();
140+
return new nodes.StrChunk(id, length, '');
141+
}
142+
const text: string = decoder.readAsStr() as string;
143+
return new nodes.StrChunk(id, text.length, text);
144+
};
145+
146+
protected decodeBin(id: ITimestampStruct, length: number): nodes.BinNode {
147+
const node = new nodes.BinNode(id);
148+
node.ingest(length, this.decodeBinChunk);
149+
return node;
150+
}
151+
152+
private decodeBinChunk = (): nodes.BinChunk => {
153+
const id = this.ts();
154+
const reader = this.dec.reader;
155+
const [deleted, length] = reader.b1vu56();
156+
if (deleted) return new nodes.BinChunk(id, length, undefined);
157+
else return new nodes.BinChunk(id, length, reader.buf(length));
158+
};
159+
160+
protected decodeArr(id: ITimestampStruct, length: number): nodes.ArrNode {
161+
const node = new nodes.ArrNode(this.doc, id);
162+
node.ingest(length, this.decodeArrChunk);
168163
return node;
169164
}
165+
166+
private decodeArrChunk = (): nodes.ArrChunk => {
167+
const id = this.ts();
168+
const reader = this.dec.reader;
169+
const [deleted, length] = reader.b1vu56();
170+
if (deleted) return new nodes.ArrChunk(id, length, undefined);
171+
else {
172+
const data: ITimestampStruct[] = [];
173+
for (let i = 0; i < length; i++) data.push(this.ts());
174+
return new nodes.ArrChunk(id, length, data);
175+
}
176+
};
170177
}

0 commit comments

Comments
 (0)