diff --git a/src/json-crdt-patch/codec/binary/Decoder.ts b/src/json-crdt-patch/codec/binary/Decoder.ts index 39a49dd91d..a291833bfa 100644 --- a/src/json-crdt-patch/codec/binary/Decoder.ts +++ b/src/json-crdt-patch/codec/binary/Decoder.ts @@ -1,4 +1,4 @@ -import {CrdtReader} from '../../util/binary/CrdtDecoder'; +import {CrdtReader} from '../../util/binary/CrdtReader'; import {interval, ITimespanStruct, ITimestampStruct, VectorClock, ServerVectorClock, Timestamp} from '../../clock'; import {Patch} from '../../Patch'; import {PatchBuilder} from '../../PatchBuilder'; diff --git a/src/json-crdt-patch/codec/binary/Encoder.ts b/src/json-crdt-patch/codec/binary/Encoder.ts index 1ebe3cab7f..14f0b8eadb 100644 --- a/src/json-crdt-patch/codec/binary/Encoder.ts +++ b/src/json-crdt-patch/codec/binary/Encoder.ts @@ -1,6 +1,6 @@ import * as operations from '../../operations'; import {JsonCrdtPatchOpcode} from '../../constants'; -import {CrdtWriter} from '../../util/binary/CrdtEncoder'; +import {CrdtWriter} from '../../util/binary/CrdtWriter'; import {ITimespanStruct, ITimestampStruct, Timestamp} from '../../clock'; import {CborEncoder} from '../../../json-pack/cbor/CborEncoder'; import type {JsonCrdtPatchOperation, Patch} from '../../Patch'; diff --git a/src/json-crdt-patch/codec/binary/shared.ts b/src/json-crdt-patch/codec/binary/shared.ts index 8fc3995b4e..110427ace3 100644 --- a/src/json-crdt-patch/codec/binary/shared.ts +++ b/src/json-crdt-patch/codec/binary/shared.ts @@ -1,7 +1,7 @@ import {Encoder} from './Encoder'; import {Decoder} from './Decoder'; import {Patch} from '../../Patch'; -import {CrdtWriter} from '../../util/binary/CrdtEncoder'; +import {CrdtWriter} from '../../util/binary/CrdtWriter'; /** A shared instance of the {@link CrdtWriter} class. */ const writer = new CrdtWriter(1024 * 4); diff --git a/src/json-crdt-patch/codec/clock/ClockDecoder.ts b/src/json-crdt-patch/codec/clock/ClockDecoder.ts index 2c03875ad6..e57f955423 100644 --- a/src/json-crdt-patch/codec/clock/ClockDecoder.ts +++ b/src/json-crdt-patch/codec/clock/ClockDecoder.ts @@ -1,5 +1,4 @@ import {ITimestampStruct, VectorClock, ts} from '../../clock'; -import type {CrdtReader} from '../../util/binary/CrdtDecoder'; export class ClockDecoder { /** Clock session index to logical clock. */ diff --git a/src/json-crdt-patch/codec/clock/ClockEncoder.ts b/src/json-crdt-patch/codec/clock/ClockEncoder.ts index ed45518459..315a759f24 100644 --- a/src/json-crdt-patch/codec/clock/ClockEncoder.ts +++ b/src/json-crdt-patch/codec/clock/ClockEncoder.ts @@ -1,6 +1,5 @@ import {ITimestampStruct, IVectorClock, tick} from '../../clock'; import {RelativeTimestamp} from './RelativeTimestamp'; -import type {CrdtWriter} from '../../util/binary/CrdtEncoder'; class ClockTableEntry { constructor(public index: number, public clock: ITimestampStruct) {} diff --git a/src/json-crdt-patch/codec/clock/ClockTable.ts b/src/json-crdt-patch/codec/clock/ClockTable.ts index b492e27173..8d0da0d307 100644 --- a/src/json-crdt-patch/codec/clock/ClockTable.ts +++ b/src/json-crdt-patch/codec/clock/ClockTable.ts @@ -1,6 +1,6 @@ import {ITimestampStruct, IVectorClock, Timestamp} from '../../clock'; -import {CrdtReader} from '../../util/binary/CrdtDecoder'; -import {CrdtWriter} from '../../util/binary/CrdtEncoder'; +import {CrdtReader} from '../../util/binary/CrdtReader'; +import {CrdtWriter} from '../../util/binary/CrdtWriter'; export class ClockTableEntry { constructor(public index: number, public id: ITimestampStruct) {} diff --git a/src/json-crdt-patch/util/binary/CrdtDecoder.ts b/src/json-crdt-patch/util/binary/CrdtReader.ts similarity index 99% rename from src/json-crdt-patch/util/binary/CrdtDecoder.ts rename to src/json-crdt-patch/util/binary/CrdtReader.ts index c116ee0861..cdd14e853f 100644 --- a/src/json-crdt-patch/util/binary/CrdtDecoder.ts +++ b/src/json-crdt-patch/util/binary/CrdtReader.ts @@ -1,5 +1,6 @@ import {Reader} from '../../../util/buffers/Reader'; +/** @todo Rename file name. */ export class CrdtReader extends Reader { public id(): [x: number, y: number] { const byte = this.u8(); diff --git a/src/json-crdt-patch/util/binary/CrdtEncoder.ts b/src/json-crdt-patch/util/binary/CrdtWriter.ts similarity index 100% rename from src/json-crdt-patch/util/binary/CrdtEncoder.ts rename to src/json-crdt-patch/util/binary/CrdtWriter.ts diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.b1vuint28.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.b1vuint28.spec.ts similarity index 93% rename from src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.b1vuint28.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtReader.b1vuint28.spec.ts index 3357144550..14163de129 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.b1vuint28.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.b1vuint28.spec.ts @@ -1,5 +1,5 @@ -import {CrdtWriter} from '../CrdtEncoder'; -import {CrdtReader} from '../CrdtDecoder'; +import {CrdtWriter} from '../CrdtWriter'; +import {CrdtReader} from '../CrdtReader'; const encoder = new CrdtWriter(); const decoder = new CrdtReader(); diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.b1vuint56.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.b1vuint56.spec.ts similarity index 96% rename from src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.b1vuint56.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtReader.b1vuint56.spec.ts index 234ae29422..610fa9607a 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.b1vuint56.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.b1vuint56.spec.ts @@ -1,5 +1,5 @@ -import {CrdtWriter} from '../CrdtEncoder'; -import {CrdtReader} from '../CrdtDecoder'; +import {CrdtWriter} from '../CrdtWriter'; +import {CrdtReader} from '../CrdtReader'; const encoder = new CrdtWriter(); const decoder = new CrdtReader(); diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.id.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.id.spec.ts similarity index 93% rename from src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.id.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtReader.id.spec.ts index cc00d87fdd..90d36df8c1 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.id.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.id.spec.ts @@ -1,5 +1,5 @@ -import {CrdtWriter} from '../CrdtEncoder'; -import {CrdtReader} from '../CrdtDecoder'; +import {CrdtWriter} from '../CrdtWriter'; +import {CrdtReader} from '../CrdtReader'; const encoder = new CrdtWriter(); const decoder = new CrdtReader(); diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.uint53vuint39.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.uint53vuint39.spec.ts similarity index 93% rename from src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.uint53vuint39.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtReader.uint53vuint39.spec.ts index 061c995e5f..12080eef71 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.uint53vuint39.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.uint53vuint39.spec.ts @@ -1,5 +1,5 @@ -import {CrdtWriter} from '../CrdtEncoder'; -import {CrdtReader} from '../CrdtDecoder'; +import {CrdtWriter} from '../CrdtWriter'; +import {CrdtReader} from '../CrdtReader'; const encoder = new CrdtWriter(); const decoder = new CrdtReader(); diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.vuint39.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.vuint39.spec.ts similarity index 94% rename from src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.vuint39.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtReader.vuint39.spec.ts index 28a954a4bd..60ba17bfbf 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.vuint39.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.vuint39.spec.ts @@ -1,5 +1,5 @@ -import {CrdtWriter} from '../CrdtEncoder'; -import {CrdtReader} from '../CrdtDecoder'; +import {CrdtWriter} from '../CrdtWriter'; +import {CrdtReader} from '../CrdtReader'; const encoder = new CrdtWriter(); const decoder = new CrdtReader(); diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.vuint57.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.vuint57.spec.ts similarity index 96% rename from src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.vuint57.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtReader.vuint57.spec.ts index 503630b078..4ba8538c5a 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtDecoder.vuint57.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtReader.vuint57.spec.ts @@ -1,5 +1,5 @@ -import {CrdtWriter} from '../CrdtEncoder'; -import {CrdtReader} from '../CrdtDecoder'; +import {CrdtWriter} from '../CrdtWriter'; +import {CrdtReader} from '../CrdtReader'; const encoder = new CrdtWriter(); const decoder = new CrdtReader(); diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.b1vuint28.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.b1vuint28.spec.ts similarity index 98% rename from src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.b1vuint28.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtWriter.b1vuint28.spec.ts index 53ed155349..ed77cf5070 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.b1vuint28.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.b1vuint28.spec.ts @@ -1,4 +1,4 @@ -import {CrdtWriter} from '../CrdtEncoder'; +import {CrdtWriter} from '../CrdtWriter'; const encoder = new CrdtWriter(1); const encode = (flag: boolean, num: number): Uint8Array => { diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.b1vuint56.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.b1vuint56.spec.ts similarity index 99% rename from src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.b1vuint56.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtWriter.b1vuint56.spec.ts index 386df38376..01a6c09195 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.b1vuint56.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.b1vuint56.spec.ts @@ -1,4 +1,4 @@ -import {CrdtWriter} from '../CrdtEncoder'; +import {CrdtWriter} from '../CrdtWriter'; const encoder = new CrdtWriter(1); const encode = (flag: boolean, num: number): Uint8Array => { diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.uint53vuint39.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.uint53vuint39.spec.ts similarity index 98% rename from src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.uint53vuint39.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtWriter.uint53vuint39.spec.ts index cd0cd370f8..4c549e27a4 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.uint53vuint39.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.uint53vuint39.spec.ts @@ -1,4 +1,4 @@ -import {CrdtWriter} from '../CrdtEncoder'; +import {CrdtWriter} from '../CrdtWriter'; const encoder = new CrdtWriter(1); const encode = (x: number, z: number): Uint8Array => { diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.vuint39.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.vuint39.spec.ts similarity index 98% rename from src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.vuint39.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtWriter.vuint39.spec.ts index 2ab57265b4..e7db59fc07 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.vuint39.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.vuint39.spec.ts @@ -1,4 +1,4 @@ -import {CrdtWriter} from '../CrdtEncoder'; +import {CrdtWriter} from '../CrdtWriter'; const encoder = new CrdtWriter(1); const encode = (num: number): Uint8Array => { diff --git a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.vuint57.spec.ts b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.vuint57.spec.ts similarity index 98% rename from src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.vuint57.spec.ts rename to src/json-crdt-patch/util/binary/__tests__/CrdtWriter.vuint57.spec.ts index f34555a964..71bab2ef22 100644 --- a/src/json-crdt-patch/util/binary/__tests__/CrdtEncoder.vuint57.spec.ts +++ b/src/json-crdt-patch/util/binary/__tests__/CrdtWriter.vuint57.spec.ts @@ -1,4 +1,4 @@ -import {CrdtWriter} from '../CrdtEncoder'; +import {CrdtWriter} from '../CrdtWriter'; const encoder = new CrdtWriter(1); const encode = (num: number): Uint8Array => { diff --git a/src/json-crdt/codec/indexed/binary/Decoder.ts b/src/json-crdt/codec/indexed/binary/Decoder.ts index ad683fe294..4ead91dddc 100644 --- a/src/json-crdt/codec/indexed/binary/Decoder.ts +++ b/src/json-crdt/codec/indexed/binary/Decoder.ts @@ -11,7 +11,7 @@ import { StrChunk, } from '../../../nodes'; import {ClockTable} from '../../../../json-crdt-patch/codec/clock/ClockTable'; -import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtDecoder'; +import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtReader'; import {IndexedFields, FieldName, IndexedNodeFields} from './types'; import {ITimestampStruct, IVectorClock, Timestamp, VectorClock} from '../../../../json-crdt-patch/clock'; import {Model, UNDEFINED} from '../../../model/Model'; diff --git a/src/json-crdt/codec/indexed/binary/Encoder.ts b/src/json-crdt/codec/indexed/binary/Encoder.ts index e2afb9e361..63074d3638 100644 --- a/src/json-crdt/codec/indexed/binary/Encoder.ts +++ b/src/json-crdt/codec/indexed/binary/Encoder.ts @@ -1,6 +1,6 @@ import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock'; import {ClockTable} from '../../../../json-crdt-patch/codec/clock/ClockTable'; -import {CrdtWriter} from '../../../../json-crdt-patch/util/binary/CrdtEncoder'; +import {CrdtWriter} from '../../../../json-crdt-patch/util/binary/CrdtWriter'; import {MsgPackEncoder} from '../../../../json-pack/msgpack'; import {Model} from '../../../model'; import {ConNode, JsonNode, ValNode, ArrNode, BinNode, ObjNode, StrNode} from '../../../nodes'; diff --git a/src/json-crdt/codec/structural/README.md b/src/json-crdt/codec/structural/README.md index 7ee10e0f6f..d9afc0ea5b 100644 --- a/src/json-crdt/codec/structural/README.md +++ b/src/json-crdt/codec/structural/README.md @@ -1,3 +1,4 @@ # Structural Codecs -Structural codecs encode the hierarchical structure of the document. +Structural codecs encode the hierarchical structure of the document. Indexed +codecs encode the document as a flat map of nodes. diff --git a/src/json-crdt/codec/structural/binary/Decoder.ts b/src/json-crdt/codec/structural/binary/Decoder.ts index c0c66194bc..4d0a6e8e56 100644 --- a/src/json-crdt/codec/structural/binary/Decoder.ts +++ b/src/json-crdt/codec/structural/binary/Decoder.ts @@ -1,8 +1,8 @@ import {ClockDecoder} from '../../../../json-crdt-patch/codec/clock/ClockDecoder'; -import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtDecoder'; +import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtReader'; import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock'; import {Model, UNDEFINED} from '../../../model/Model'; -import {MsgPackDecoderFast} from '../../../../json-pack/msgpack'; +import {CborDecoderBase} from '../../../../json-pack/cbor/CborDecoderBase'; import {SESSION} from '../../../../json-crdt-patch/constants'; import { ArrNode, @@ -18,8 +18,9 @@ import { VecNode, type JsonNode, } from '../../../nodes'; +import {CRDT_MAJOR} from './constants'; -export class Decoder extends MsgPackDecoderFast { +export class Decoder extends CborDecoderBase { protected doc!: Model; protected clockDecoder?: ClockDecoder; protected time: number = -1; @@ -79,74 +80,62 @@ export class Decoder extends MsgPackDecoderFast { return !peek ? UNDEFINED : this.cNode(); } - public cNode(): JsonNode { + protected cNode(): JsonNode { const reader = this.reader; const id = this.ts(); - const byte = reader.u8(); - if (byte <= 0b10001111) return this.cObj(id, byte & 0b1111); - else if (byte <= 0b10011111) return this.cArr(id, byte & 0b1111); - else if (byte <= 0b10111111) return this.cStr(id, byte & 0b11111); - else { - switch (byte) { - case 0xc4: - return this.cBin(id, reader.u8()); - case 0xc5: - return this.cBin(id, reader.u16()); - case 0xc6: - return this.cBin(id, reader.u32()); - case 0xd4: { - const obj = new ConNode(id, this.val()); - this.doc.index.set(id, obj); - return obj; - } - case 0xd5: { - const obj = new ConNode(id, this.ts()); - this.doc.index.set(id, obj); - return obj; - } - case 0xd6: { - const val = this.cNode(); - const obj = new ValNode(this.doc, id, val.id); - this.doc.index.set(id, obj); - return obj; - } - case 0xde: - return this.cObj(id, reader.u16()); - case 0xdf: - return this.cObj(id, reader.u32()); - case 0xdc: - return this.cArr(id, reader.u16()); - case 0xdd: - return this.cArr(id, reader.u32()); - case 0xd9: - return this.cStr(id, reader.u8()); - case 0xda: - return this.cStr(id, reader.u16()); - case 0xdb: - return this.cStr(id, reader.u32()); - case 0xc7: - return this.cTup(id); - } + const octet = reader.u8(); + const major = octet >> 5; + const minor = octet & 0b11111; + const length = minor < 24 ? minor : minor === 24 ? reader.u8() : minor === 25 ? reader.u16() : reader.u32(); + switch (major) { + case CRDT_MAJOR.CON: + return this.cCon(id, length); + case CRDT_MAJOR.VAL: + return this.cVal(id); + case CRDT_MAJOR.VEC: + return this.cVec(id, length); + case CRDT_MAJOR.OBJ: + return this.cObj(id, length); + case CRDT_MAJOR.STR: + return this.cStr(id, length); + case CRDT_MAJOR.BIN: + return this.cBin(id, length); + case CRDT_MAJOR.ARR: + return this.cArr(id, length); } throw new Error('UNKNOWN_NODE'); } - public cObj(id: ITimestampStruct, length: number): ObjNode { + protected cCon(id: ITimestampStruct, length: number): ConNode { + const doc = this.doc; + const data = !length ? this.val() : this.ts(); + const node = new ConNode(id, data); + doc.index.set(id, node); + return node; + } + + protected cVal(id: ITimestampStruct): ValNode { + const child = this.cNode(); + const doc = this.doc; + const node = new ValNode(doc, id, child.id); + doc.index.set(id, node); + return node; + } + + protected cObj(id: ITimestampStruct, length: number): ObjNode { const obj = new ObjNode(this.doc, id); for (let i = 0; i < length; i++) this.cObjChunk(obj); this.doc.index.set(id, obj); return obj; } - private cObjChunk(obj: ObjNode): void { + protected cObjChunk(obj: ObjNode): void { const key: string = this.key(); obj.keys.set(key, this.cNode().id); } - public cTup(id: ITimestampStruct): VecNode { + protected cVec(id: ITimestampStruct, length: number): VecNode { const reader = this.reader; - const length = this.reader.u8(); - reader.x++; const obj = new VecNode(this.doc, id); const elements = obj.elements; for (let i = 0; i < length; i++) { @@ -160,7 +149,7 @@ export class Decoder extends MsgPackDecoderFast { return obj; } - public cArr(id: ITimestampStruct, length: number): ArrNode { + protected cArr(id: ITimestampStruct, length: number): ArrNode { const obj = new ArrNode(this.doc, id); obj.ingest(length, this.cArrChunk); this.doc.index.set(id, obj); @@ -176,7 +165,7 @@ export class Decoder extends MsgPackDecoderFast { return new ArrChunk(id, length, ids); }; - public cStr(id: ITimestampStruct, length: number): StrNode { + protected cStr(id: ITimestampStruct, length: number): StrNode { const node = new StrNode(id); if (length) node.ingest(length, this.cStrChunk); this.doc.index.set(id, node); @@ -192,11 +181,11 @@ export class Decoder extends MsgPackDecoderFast { const length = reader.vu39(); return new StrChunk(id, length, ''); } - const text: string = this.str() as string; + const text: string = this.readAsStr() as string; return new StrChunk(id, text.length, text); }; - public cBin(id: ITimestampStruct, length: number): BinNode { + protected cBin(id: ITimestampStruct, length: number): BinNode { const node = new BinNode(id); if (length) node.ingest(length, this.cBinChunk); this.doc.index.set(id, node); diff --git a/src/json-crdt/codec/structural/binary/Encoder.ts b/src/json-crdt/codec/structural/binary/Encoder.ts index c242accc6d..5555468cea 100644 --- a/src/json-crdt/codec/structural/binary/Encoder.ts +++ b/src/json-crdt/codec/structural/binary/Encoder.ts @@ -1,12 +1,13 @@ import {ConNode, RootNode, JsonNode, ValNode, VecNode, ArrNode, BinNode, ObjNode, StrNode} from '../../../nodes'; import {ClockEncoder} from '../../../../json-crdt-patch/codec/clock/ClockEncoder'; -import {CrdtWriter} from '../../../../json-crdt-patch/util/binary/CrdtEncoder'; +import {CrdtWriter} from '../../../../json-crdt-patch/util/binary/CrdtWriter'; import {ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock'; -import {MsgPackEncoder} from '../../../../json-pack/msgpack'; +import {CborEncoder} from '../../../../json-pack/cbor/CborEncoder'; import {SESSION} from '../../../../json-crdt-patch/constants'; +import {CRDT_MAJOR_OVERLAY} from './constants'; import type {Model} from '../../../model'; -export class Encoder extends MsgPackEncoder { +export class Encoder extends CborEncoder { protected clockEncoder: ClockEncoder = new ClockEncoder(); protected time: number = 0; protected doc!: Model; @@ -76,21 +77,30 @@ export class Encoder extends MsgPackEncoder { else this.cNode(root.node()); } + protected writeTL(majorOverlay: CRDT_MAJOR_OVERLAY, length: number): void { + const writer = this.writer; + if (length < 24) writer.u8(majorOverlay + length); + else if (length <= 0xff) writer.u16(((majorOverlay + 24) << 8) + length); + else if (length <= 0xffff) writer.u8u16(majorOverlay + 25, length); + else writer.u8u32(majorOverlay + 26, length); + } + protected cNode(node: JsonNode): void { - // TODO: PERF: use a switch - if (node instanceof ConNode) this.cConst(node); + // TODO: PERF: use a switch? + if (node instanceof ConNode) this.cCon(node); else if (node instanceof ValNode) this.cVal(node); else if (node instanceof StrNode) this.cStr(node); else if (node instanceof ObjNode) this.cObj(node); - else if (node instanceof VecNode) this.cTup(node); + else if (node instanceof VecNode) this.cVec(node); else if (node instanceof ArrNode) this.cArr(node); else if (node instanceof BinNode) this.cBin(node); } - protected cObj(obj: ObjNode): void { - this.ts(obj.id); - this.writeObjHdr(obj.keys.size); - obj.keys.forEach(this.cKey); + protected cObj(node: ObjNode): void { + this.ts(node.id); + const keys = node.keys; + this.writeTL(CRDT_MAJOR_OVERLAY.OBJ, keys.size); + keys.forEach(this.cKey); } protected readonly cKey = (val: ITimestampStruct, key: string) => { @@ -98,12 +108,11 @@ export class Encoder extends MsgPackEncoder { this.cNode(this.doc.index.get(val)!); }; - protected cTup(obj: VecNode): void { - this.ts(obj.id); - const elements = obj.elements; + protected cVec(node: VecNode): void { + const elements = node.elements; const length = elements.length; - const writer = this.writer; - writer.u8u16(0xc7, length << 8); + this.ts(node.id); + this.writeTL(CRDT_MAJOR_OVERLAY.VEC, length); const index = this.doc.index; for (let i = 0; i < length; i++) { const elementId = elements[i]; @@ -112,13 +121,13 @@ export class Encoder extends MsgPackEncoder { } } - protected cArr(obj: ArrNode): void { + protected cArr(node: ArrNode): void { const ts = this.ts; const writer = this.writer; - ts(obj.id); - this.writeArrHdr(obj.size()); + ts(node.id); + this.writeTL(CRDT_MAJOR_OVERLAY.ARR, node.count); const index = this.doc.index; - for (let chunk = obj.first(); chunk; chunk = obj.next(chunk)) { + for (let chunk = node.first(); chunk; chunk = node.next(chunk)) { const span = chunk.span; const deleted = chunk.del; writer.b1vu28(deleted, span); @@ -129,28 +138,26 @@ export class Encoder extends MsgPackEncoder { } } - protected cStr(obj: StrNode): void { + protected cStr(node: StrNode): void { const ts = this.ts; const writer = this.writer; - ts(obj.id); - const length = obj.size(); - this.writeStrHdr(length); - for (let chunk = obj.first(); chunk; chunk = obj.next(chunk)) { + ts(node.id); + this.writeTL(CRDT_MAJOR_OVERLAY.STR, node.count); + for (let chunk = node.first(); chunk; chunk = node.next(chunk)) { ts(chunk.id); if (chunk.del) { writer.u8(0); writer.vu39(chunk.span); - } else this.encodeString(chunk.data!); + } else this.writeStr(chunk.data!); } } - protected cBin(obj: BinNode): void { + protected cBin(node: BinNode): void { const ts = this.ts; const writer = this.writer; - ts(obj.id); - const length = obj.size(); - this.writeBinHdr(length); - for (let chunk = obj.first(); chunk; chunk = obj.next(chunk)) { + ts(node.id); + this.writeTL(CRDT_MAJOR_OVERLAY.BIN, node.count); + for (let chunk = node.first(); chunk; chunk = node.next(chunk)) { const length = chunk.span; const deleted = chunk.del; writer.b1vu28(chunk.del, length); @@ -160,20 +167,20 @@ export class Encoder extends MsgPackEncoder { } } - protected cVal(obj: ValNode): void { - this.ts(obj.id); - this.writer.u8(0xd6); - this.cNode(obj.node()); + protected cVal(node: ValNode): void { + this.ts(node.id); + this.writeTL(CRDT_MAJOR_OVERLAY.VAL, 0); + this.cNode(node.node()); } - protected cConst(obj: ConNode): void { - this.ts(obj.id); - const val = obj.val; + protected cCon(node: ConNode): void { + const val = node.val; + this.ts(node.id); if (val instanceof Timestamp) { - this.writer.u8(0xd5); + this.writeTL(CRDT_MAJOR_OVERLAY.CON, 1); this.ts(val as Timestamp); } else { - this.writer.u8(0xd4); + this.writeTL(CRDT_MAJOR_OVERLAY.CON, 0); this.writeAny(val); } } diff --git a/src/json-crdt/codec/structural/binary/README.md b/src/json-crdt/codec/structural/binary/README.md index 57c230d3c3..db876dee46 100644 --- a/src/json-crdt/codec/structural/binary/README.md +++ b/src/json-crdt/codec/structural/binary/README.md @@ -1,888 +1,9 @@ -# JSON CRDT document binary encoding - -The binary encoding of a JSON CRDT document encodes the structure of the latest -state (snapshot) of a JSON CRDT document, which contains all the nodes and -tombstones necessary to be able to merge any subsequent patches, but does not -contain information to reconstruct previous patches. - -This document specifies two types of encoding: (1) where logical clocks are used -as unique IDs; (2) where monotonically growing sequence number is used as unique -IDs. - - -## Message encoding - -Notation in diagrams: - -``` -One byte: -+--------+ -| | -+--------+ - -Zero or more repeating bytes: -+........+ -| | -+........+ - -Zero or one byte which ends a repeating byte sequence: -+········+ -| | -+········+ - -Variable number of bytes: -+========+ -| | -+========+ -``` - - -## Document structure - -The general structure of the document is composed of three sections: - -1. Header section. -2. Optional, Vector clock table section. -3. Data section. - - -### The header section - -The header begins with a single b1vuint56 value, where the first bit is a flag, -which encodes which vector clock type is used. - -``` -+===========+ -| b1vuint56 | -+===========+ -``` - -If the flag is set, the document is using "server timestamps" as unique IDs. Where -a timestamp is just an increasing sequence of numbers, and server enforces the -order. When the flag is set, the value of the b1vuint56 is the number which represents -the next timestamp in the document. - -If the flag is not set, the document is using "logical timestamps" as unique IDs. -A logical timestamp is a 2-tuple, where the fist element is a randomly generated -editing session ID, and the second element is a monotonically increasing sequence -number. When the flag is not set, the value of the b1vuint56 is the length of the -clock table. - - -### The vector clock table section - -The vector clock section is present only if the document uses logical timestamps -as IDs. If document uses increasing sequence numbers as IDs, this section is -empty. - -The vector clock table section starts with a vuint57 integer. The value of this -integer specifies the number of entries in the vector clock section. - -The remaining part of the vector clock section is composed of a flat list of -logical clock entries. - -Each logical clock entry consists of: (1) a session ID; and (2) sequence time. - -Each logical clock is encoded as uint53vuint39. - -The first clock entry contains the session ID of the CRDT document. If the -sequence time part of the first entry is zero, it means no ID with that session -ID has been generated yet. - - -### The data section - -The data section consists of the encoded root node. - - -#### Node encodings - -The JSON document consists of the following seven node types: (1) root; -(2) object; (3) array; (4) string; (5) number; (6) boolean; (7) null. - -Each node type, except the root node, starts with a leading byte, which identifies -the node type and its size. - -| Node type | Hexadecimal | Binary | Description | -|-------------------|-------------------|-----------------------------|-------------------------------------------------| -| uint7 | 0x00 - 0x7F | `0b0.......` | Unsigned 7 bit integer. | -| obj4 | 0x80 - 0x8F | `0b1000....` | Object with up to 15 chunks. | -| arr4 | 0x90 - 0x9F | `0b1001....` | Array with up to 15 chunks. | -| str5 | 0xA0 - 0xBF | `0b101.....` | String with up to 31 chunks. | -| null | 0xC0 | `0b11000000` | "null" value. | -| undefined | 0xC1 | `0b11000001` | "undefined" value. | -| false | 0xC2 | `0b11000010` | "false" value. | -| true | 0xC3 | `0b11000011` | "true" value. | -| bin8 | 0xC4 | `0b11000100` | Binary with up to 256 chunks. | -| bin16 | 0xC5 | `0b11000101` | Binary with up to 65,535 chunks. | -| bin32 | 0xC6 | `0b11000110` | Binary with up to 4,294,967,295 chunks. | -| float32 | 0xCA | `0b11001010` | 32-bit floating point number. | -| float64 | 0xCB | `0b11001011` | 64-bit floating point number. | -| uint8 | 0xCC | `0b11001100` | Unsigned 8 bit integer. | -| uint16 | 0xCD | `0b11001101` | Unsigned 16 bit integer. | -| uint32 | 0xCE | `0b11001110` | Unsigned 32 bit integer. | -| uint64 | 0xCF | `0b11001111` | Unsigned 64 bit integer (53 bit in JavaScript). | -| int8 | 0xD0 | `0b11010000` | Signed 8 bit integer. | -| int16 | 0xD1 | `0b11010001` | Signed 16 bit integer. | -| int32 | 0xD2 | `0b11010010` | Signed 32 bit integer. | -| int64 | 0xD3 | `0b11010011` | Signed 64 bit integer (53 bit in JavaScript). | -| const | 0xD4 | `0b11010100` | Immutable JSON value. | -| val | 0xD5 | `0b11010101` | LWW JSON value register. | -| vallit | 0xD6 | `0b11010110` | LWW JSON value register encoded as literal. | -| str8 | 0xD9 | `0b11011001` | String with up to 255 chunks. | -| str16 | 0xDA | `0b11011010` | String with up to 65,535 chunks. | -| str32 | 0xDB | `0b11011011` | String with up to 4,294,967,295 chunks. | -| arr16 | 0xDC | `0b11011100` | Array with up to 65,535 chunks. | -| arr32 | 0xDD | `0b11011101` | Array with up to 4,294,967,295 chunks. | -| obj16 | 0xDE | `0b11011110` | Object with up to 65,535 chunks. | -| obj32 | 0xDF | `0b11011111` | Object with up to 4,294,967,295 chunks. | -| nint5 | 0xE0 - 0xFF | `0b111.....` | Negative 5 bit integer. | - -(The above encoding table is adopted from [MessagePack encoding](https://github.com/msgpack/msgpack/blob/master/spec.md#overview) format.) - -##### Root node encoding - -If document root node is empty, root node consists of a single zero byte. - -If document root node has a value it is encoded as follows: - -1. Relative ID of the operation used to set the root node. -2. Value of the root, encoded as a node. - -The root node can appear in the document only once, as the very first node in -the data section. - - -##### Object node encoding - -The object node contains the below parts in the following order. - -1. First byte encodes whether the object node is of type `obj4`, `obj16` or `obj32`. -2. Followed by 0, 2, or 4 bytes of unsigned integer encoding of number of chunks. -2. ID of the object, encoded as a relative ID. -4. A flat list of object chunks. - -The number of chunks is encoded with `s` bits as an unsigned integer. - -``` -obj4 -+--------+========+========+ -|1000ssss| ID | chunks | -+--------+========+========+ - -obj16 -+--------+--------+--------+========+========+ -|11011100|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+========+========+ - -obj32 -+--------+--------+--------+--------+--------+========+========+ -|11011101|ssssssss|ssssssss|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+--------+--------+========+========+ -``` - -Each object chunk is encoded as follows: - -1. ID of the operation that set this field, encoded as relative ID. -2. The length of field name, encoded as vuint57. -3. The field `name`, encoded in UTF-8. -4. Value of the field, encoded as a node. - -``` -One object chunk: -+========+=========+========+========+ -| ID | vuint57 | name | node | -+========+=========+========+========+ -``` - - -##### Array node encoding - -The array node contains the below parts in the following order. - -1. First byte encodes whether the array node is of type `arr4`, `arr16` or `arr32`. -2. Followed by 0, 2, or 4 bytes of unsigned integer encoding of number of chunks. -2. ID of the array, encoded as a relative ID. -4. A flat list of array chunks. - -The number of chunks is encoded with `s` bits as an unsigned integer. - -``` -arr4 -+----|----+========+========+ -|1001|ssss| ID | chunks | -+----^----+========+========+ - -arr16 -+--------+--------+--------+========+========+ -|11011100|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+========+========+ - -arr32 -+--------+--------+--------+--------+--------+========+========+ -|11011101|ssssssss|ssssssss|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+--------+--------+========+========+ -``` - -Each array chunk contains the bellow parts in the following order. - -1. Number of nodes in the chunk, encoded using b1vuint56. - 1. If b1vuint56 boolean bit is 1, the chunk is considered deleted. It is - followed by the ID of the first chunk element, encode as a relative ID. - 2. If b1vuint56 boolean bit is 0, the following data contains the first chunk - element ID, encoded as relative ID, followed a flat ordered list of nodes, - encoded as nodes. - -``` -Deleted chunk: -+===========+========+ -| b1vuint56 | ID | -+===========+========+ - -Not deleted chunk: -+===========+========+=========+ -| b1vuint56 | ID | nodes | -+===========+========+=========+ -``` - -Assuming the node count is encoded using `t` bits. - -``` -A deleted chunk: -+-|-------+........+········+========+ -|1|?tttttt|?ttttttt|tttttttt| ID | -+-^-------+........+········+========+ - -A deleted chunk with three nodes: -+-|-------+========+ -|1|0000011| ID | -+-^-------+========+ - -A deleted chunk with 256 nodes: -+-|-------+--------+========+ -|1|1000000|00000100| ID | -+-^-------+--------+========+ - -A chunk with nodes which are not deleted: -+-|-------+........+········+========+=========+ -|0|?tttttt|?ttttttt|tttttttt| ID | nodes | -+-^-------+........+········+========+=========+ - -A chunk with 3 nodes: -+-|-------+========+========+========+========+ -|0|0000011| ID | node 1 | node 2 | node 3 | -+-^-------+========+========+========+========+ -``` - - -##### String node encoding - -The string node contains the below parts in the following order. - -1. First byte encodes whether the string node is of type `str5`, `str8`, `arr16` or `arr32`. -2. Followed by 0, 1, 2, or 4 bytes of unsigned integer encoding the number of chunks. -2. ID of the string, encoded as a relative ID. -4. A flat list of string chunks. - -The number of chunks is encoded with `s` bits as an unsigned integer. - -``` -str5 -+---|-----+========+========+ -|101|sssss| ID | chunks | -+---^-----+========+========+ - -str8 -+--------+--------+========+========+ -|11011001|ssssssss| ID | chunks | -+--------+--------+========+========+ - -str16 -+--------+--------+--------+========+========+ -|11011010|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+========+========+ - -str32 -+--------+--------+--------+--------+--------+========+========+ -|11011011|ssssssss|ssssssss|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+--------+--------+========+========+ -``` - -Each chunk contains the bellow parts in the following order. - -1. The text length in the chunk, encoded as b1vuint56. - 1. If b1vuint56 boolean bit is 1, the text is considered deleted, there - is no text contents to follow. The text length is used as the - length of the UTF-8 text that was deleted. - 2. If b1vuint56 boolean bit is 0, the following `data` contains UTF-8 encoded - text contents, where the length is the length in bytes of the UTF-8 - data. -2. ID of the first UTF-8 character in the chunk, encoded as a relative ID. -3. Chunk's UTF-8 text contents `text`, encoded as UTF-8 string, if b1vuint56 - boolean bit was set to 0, no data otherwise. - -``` -+===========+========+========+ -| b1vuint56 | ID | text | -+===========+========+========+ -``` - -Assuming the node count is encoded using `t` bits. - -``` -A deleted chunk: -+-|-------+........+········+========+ -|1|?tttttt|?ttttttt|tttttttt| ID | -+-^-------+........+········+========+ - -A deleted chunk with text of length 3: -+-|-------+========+ -|1|0000011| ID | -+-^-------+========+ - -A deleted chunk with text of length 256: -+-|-------+--------+========+ -|1|1000000|00000100| ID | -+-^-------+--------+========+ - -A chunk with text which is not deleted: -+-|-------+........+········+========+========+ -|0|?tttttt|?ttttttt|tttttttt| ID | text | -+-^-------+........+········+========+========+ -``` - - -##### Binary node encoding - -The binary node contains the below parts in the following order. - -1. First byte encodes whether the binary node is of type `bin8`, `bin16`, or `bin32`. -2. Followed by 1, 2, or 4 bytes of unsigned integer encoding the number of chunks. -2. ID of the binary, encoded as a relative ID. -4. A flat list of binary chunks. - -The number of chunks is encoded with `s` bits as an unsigned integer. - -``` -bin8 -+--------+--------+========+========+ -|11000100|ssssssss| ID | chunks | -+--------+--------+========+========+ - -bin16 -+--------+--------+--------+========+========+ -|11000101|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+========+========+ - -bin32 -+--------+--------+--------+--------+--------+========+========+ -|11000110|ssssssss|ssssssss|ssssssss|ssssssss| ID | chunks | -+--------+--------+--------+--------+--------+========+========+ -``` - -Each chunk contains the bellow parts in the following order. - -1. The contents length in the chunk, encoded as b1vuint56. - 1. If b1vuint56 boolean bit is 1, the contents is considered deleted. - There is no contents to follow. - 2. If b1vuint56 boolean bit is 0, then the value specifies the - length of the following `data` binary contents of the chunk. -2. ID of the first UTF-8 character in the chunk, encoded as a relative ID. -3. Chunk's `data` contents, if b1vuint56 boolean bit was set to 0, no data otherwise. - -``` -+===========+========+========+ -| b1vuint56 | ID | data | -+===========+========+========+ -``` - -Assuming the node count is encoded using `t` bits: - -``` -A deleted chunk: -+-|-------+........+········+========+ -|1|?tttttt|?ttttttt|tttttttt| ID | -+-^-------+........+········+========+ - -A deleted chunk with data contents of length 3: -+-|-------+========+ -|1|0000011| ID | -+-^-------+========+ - -A deleted chunk with data contents of length 256: -+-|-------+--------+========+ -|1|1000000|00000100| ID | -+-^-------+--------+========+ - -A chunk with data, which is not deleted: -+-|-------+........+········+========+========+ -|0|?tttttt|?ttttttt|tttttttt| ID | data | -+-^-------+........+········+========+========+ -``` - - -##### Constant node encoding - -Constant nodes are ones which contain a immutable JSON value, without any other -meta information. - -If the value of the constant node is `null`, `undefined`, `false`, `true` or a number, the -node is encoded as MessagePack value. - -``` -null -+--------+ -| 0xC0 | -+--------+ - -undefined -+--------+ -| 0xC1 | -+--------+ - -false -+--------+ -| 0xC2 | -+--------+ - -true -+--------+ -| 0xC3 | -+--------+ - -uint7 -+-|-------+ -|0|xxxxxxx| -+-^-------+ - -float32 -+--------+--------+--------+--------+--------+ -| 0xCA |xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx| -+--------+--------+--------+--------+--------+ - -float64 -+--------+--------+--------+--------+--------+--------+--------+--------+--------+ -| 0xCB |xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx| -+--------+--------+--------+--------+--------+--------+--------+--------+--------+ - -uint8 -+--------+--------+ -| 0xCC |xxxxxxxx| -+--------+--------+ - -uint16 -+--------+--------+--------+ -| 0xCD |xxxxxxxx|xxxxxxxx| -+--------+--------+--------+ - -uint32 -+--------+--------+--------+--------+--------+ -| 0xCE |xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx| -+--------+--------+--------+--------+--------+ - -uint64 -+--------+--------+--------+--------+--------+--------+--------+--------+--------+ -| 0xCF |xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx| -+--------+--------+--------+--------+--------+--------+--------+--------+--------+ - -int8 -+--------+--------+ -| 0xD0 |xxxxxxxx| -+--------+--------+ - -int16 -+--------+--------+--------+ -| 0xD1 |xxxxxxxx|xxxxxxxx| -+--------+--------+--------+ - -int32 -+--------+--------+--------+--------+--------+ -| 0xD2 |xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx| -+--------+--------+--------+--------+--------+ - -int64 -+--------+--------+--------+--------+--------+--------+--------+--------+--------+ -| 0xD3 |xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx| -+--------+--------+--------+--------+--------+--------+--------+--------+--------+ - -nint5 -+---|-----+ -|111|xxxxx| -+---^-----+ -``` - -If the value of the constant node is string, array or object, it is encoded -as a `const` node, which consists of: - -1. Leading 0xD4 byte. -2. JSON value `data`, encoded in MessagePack format. - -``` -const -+--------+========+ -| 0xD4 | data | -+--------+========+ -``` - - -##### Value node encoding - -Value nodes are LWW registers which contain immutable JSON value. - -A value node is encoded as a `val` node, which consists of: - -1. Leading 0xD5 byte. -2. ID of the node `id`, encoded as a relative ID. -3. ID of the last write operation `writeId`, encoded as a relative ID. -4. JSON value `data`, encoded in MessagePack format. - -``` -val -+--------+========+=========+========+ -| 0xD5 | id | writeId | data | -+--------+========+=========+========+ -``` - - -## Encoding components - -### Relative ID - -*Absolute IDs* (or simply IDs) are unique totally ordered identifiers, used to -identify everything that needs identification in the document such as its -structural parts and/or operations. - -A *relative ID* is a compact way to encode an absolute ID, however, to decode -back the relative ID to an absolute ID, the vector clock table of the document -is used or the latest sequence number of the document is used. - -Each relative ID is encoded differently, depending if the document uses logical -clocks or sequence numbers for absolute IDs. - -#### When document uses logical clocks - -Each relative ID is a 2-tuple. - -1. The first element is the index in the vector - clock table. For example, 1 means "the first logic clock" in the vector clock - table, or 4 means "the fourth logical clock" in the vector clock table. -2. The second element encodes the difference of the time between the time of - the logical clock in the vector clock table and the time component of the - absolute ID. - -In the below encoding diagrams bits are annotated as follows: - -- "x" - vector table index, reference to the logical clock. -- "y" - time difference. -- "?" - whether the next byte is used for encoding. - -If x is less than 8 and y is less than 16, the relative ID is encoded as a -single byte: - -``` -+-|---|----+ -|0|xxx|yyyy| -+-^---^----+ -``` - -Otherwise the top bit of the first byte is set to 1; and x and y are encoded -separately using b1vuint28 and vuint39, respectively. - -``` - x y -+===========+=========+ -| b1vuint28 | vuint39 | -+===========+=========+ -``` - -The boolean flag of x b1vuint28 value is always set to 1. - - -#### When document uses sequence numbers - -Each relative ID is the difference between documents sequence number and the -absolute ID and is encoded as vuint57. - -``` -+=========+ -| vuint57 | -+=========+ -``` - - -### Variable length integers - - -#### `vuint57` (variable length unsigned 57 bit integer) - -Variable length unsigned 57 bit integer is encoded using up to 8 bytes. The maximum -size of the decoded value is 57 bits of data. - -The high bit "?" of each byte indicates if the next byte should be consumed, up -to 8 bytes. - -``` -byte 1 byte 8 -+--------+........+........+........+........+........+........+········+ -|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|zzzzzzzz| -+--------+........+........+........+........+........+........+········+ - - 11111 2211111 2222222 3333332 4443333 4444444 55555555 - 7654321 4321098 1098765 8765432 5432109 2109876 9876543 76543210 - | | | | - 5th bit of z | | | - 28th bit of z | 57th bit of z - 39th bit of z -``` - -Encoding examples: - -``` -+--------+ -|0zzzzzzz| -+--------+ - -+--------+--------+ -|1zzzzzzz|0zzzzzzz| -+--------+--------+ - -+--------+--------+--------+ -|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+ - -+--------+--------+--------+--------+ -|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+ - -+--------+--------+--------+--------+--------+ -|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+--------+ - -+--------+--------+--------+--------+--------+--------+ -|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+--------+--------+ - -+--------+--------+--------+--------+--------+--------+--------+ -|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+--------+--------+--------+ - -+--------+--------+--------+--------+--------+--------+--------+--------+ -|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|zzzzzzzz| -+--------+--------+--------+--------+--------+--------+--------+--------+ -``` - - -#### `vuint39` (variable length unsigned 39 bit integer) - -Variable length unsigned 39 bit integer is encoded using up to 6 bytes. The maximum -size of the decoded value is 39 bits of data. - -The high bit "?" of each byte indicates if the next byte should be consumed, up -to 6 bytes. - -``` -byte 1 byte 6 -+--------+........+........+........+........+········+ -|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|0000zzzz| -+--------+........+........+........+........+········+ - - 11111 2211111 2222222 3333332 3333 - 7654321 4321098 1098765 8765432 5432109 9876 - | | | - 5th bit of z | 39th bit of z - 28th bit of z -``` - -Encoding examples: - -``` -+--------+ -|x0zzzzzz| -+--------+ - -+--------+--------+ -|x1zzzzzz|0zzzzzzz| -+--------+--------+ - -+--------+--------+--------+ -|x1zzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+ - -+--------+--------+--------+--------+ -|x1zzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+ - -+--------+--------+--------+--------+--------+ -|x1zzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+--------+ - -+--------+--------+--------+--------+--------+--------+ -|x1zzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0000zzzz| -+--------+--------+--------+--------+--------+--------+ -``` - - -#### `b1vuint56` (variable length unsigned 56 bit integer with 1 bit bitfield) - -Boolean variable length unsigned 56 bit integer with 1 bit bitfield is encoded -in a similar way to vuint57, but the first bit x is used for storing a boolean -value. - -b1vuint56 is encoded using up to 8 bytes. Because the first bit is used to store -a boolean value, the maximum integer data b1vuint56 can hold is 56 bits. - -``` -byte 1 byte 8 -+-|-------+........+........+........+........+........+........+········+ -|x|?zzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|zzzzzzzz| -+-^-------+........+........+........+........+........+........+········+ - | - | 11111 2111111 2222222 3333322 4433333 4444444 55555554 - | 654321 3210987 0987654 7654321 4321098 1098765 8765432 65432109 - | | | | | - | 5th bit of z | | | - | 27th bit of z | 56th bit of z - | 38th bit of z - x stores a boolean value -``` - -Encoding examples: - -``` -+-|-------+ -|x|0zzzzzz| -+-^-------+ - -+-|-------+--------+ -|x|1zzzzzz|0zzzzzzz| -+-^-------+--------+ - -+-|-------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|0zzzzzzz| -+-^-------+--------+--------+ - -+-|-------+--------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+-^-------+--------+--------+--------+ - -+-|-------+--------+--------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+-^-------+--------+--------+--------+--------+ - -+-|-------+--------+--------+--------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+-^-------+--------+--------+--------+--------+--------+ - -+-|-------+--------+--------+--------+--------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+-^-------+--------+--------+--------+--------+--------+--------+ - -+-|-------+--------+--------+--------+--------+--------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|zzzzzzzz| -+-^-------+--------+--------+--------+--------+--------+--------+--------+ -``` - - -#### `b1vuint28` (variable length unsigned 28 bit integer with 1 bit bitfield) - -Boolean variable length unsigned 28 bit integer with 1 bit bitfield is encoded -in a similar way to b1vuint56, but the numeric value expands only up to 4 bytes. - -b1vuint28 is encoded using up to 4 bytes. Because the first bit is used to store -a boolean value, the maximum integer data b1vuint28 can hold is 28 bits. - -``` -byte 1 byte 4 -+-|-------+........+........+········+ -|x|?zzzzzz|?zzzzzzz|?zzzzzzz|zzzzzzzz| -+-^-------+........+........+········+ - | - | 11111 2111111 22222222 - | 654321 3210987 0987654 87654321 - | | | - | 5th bit of z | - | 27th bit of z - | - x stores a boolean value -``` - -Encoding examples: - -``` -+-|-------+ -|x|0zzzzzz| -+-^-------+ - -+-|-------+--------+ -|x|1zzzzzz|0zzzzzzz| -+-^-------+--------+ - -+-|-------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|0zzzzzzz| -+-^-------+--------+--------+ - -+-|-------+--------+--------+--------+ -|x|1zzzzzz|1zzzzzzz|1zzzzzzz|zzzzzzzz| -+-^-------+--------+--------+--------+ -``` - - -#### `uint53vuint39` (unsigned 53 bit integer and variable length unsigned 39 bit integer) - -uint53vuint39 specifies encoding of a 2-tuple, which consists of - -1. Unsigned 53 bit integer, in below diagrams represented by "x".. -2. Variable length unsigned 39 bit integer, in below diagrams represented by "z". - -Each uint53vuint39 entry is variable length. It is at least 8 bytes long and at -most 12 bytes long. - -The "x" is always encoded as a 53-bit unsigned integer. The "z" -is encoded as an unsigned integer of at least 10-bits to at most 39-bits in -size. - -The "?" bit specifies whether the next byte should be used for "z" decoding. If -"?" is set to 1, the next byte should be read. If "?" is set to 0, no -further bytes should be read after the byte containing the "?" set to 0. - -Encoding schema: - -``` -byte 1 byte 8 byte 12 -+--------+--------+--------+--------+--------+--------+-----|---+--------+........+........+........+········+ -|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxx|?zz|zzzzzzzz|?zzzzzzz|?zzzzzzz|?zzzzzzz|zzzzzzzz| -+--------+--------+--------+--------+--------+--------+-----^---+--------+........+........+........+········+ - - 33322222 22222111 1111111 44444444 43333333 55554 .1 .1111111 .2222211 .3322222 33333333 - 21098765 43210987 65432109 87654321 87654321 09876543 32109 .09 87654321 .7654321 .4321098 .1098765 98765432 - | | | | | - | | | 10th bit of z | - | 46th bit of x | | - | | 22nd bit of z - | 53rd bit of x - 32nd bit of x -``` - -Encoding examples of all the different length possibilities. - -``` -+--------+--------+--------+--------+--------+--------+-----|---+--------+ -|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxx|0zz|zzzzzzzz| -+--------+--------+--------+--------+--------+--------+-----^---+--------+ - -+--------+--------+--------+--------+--------+--------+-----|---+--------+········+ -|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxx|1zz|zzzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+--------+--------+-----^---+--------+········+ - -+--------+--------+--------+--------+--------+--------+-----|---+--------+........+········+ -|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxx|1zz|zzzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+--------+--------+-----^---+--------+........+········+ - -+--------+--------+--------+--------+--------+--------+-----|---+--------+........+........+········+ -|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxx|1zz|zzzzzzzz|1zzzzzzz|1zzzzzzz|0zzzzzzz| -+--------+--------+--------+--------+--------+--------+-----^---+--------+........+........+········+ - -+--------+--------+--------+--------+--------+--------+-----|---+--------+........+........+........+········+ -|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxx|1zz|zzzzzzzz|1zzzzzzz|1zzzzzzz|1zzzzzzz|zzzzzzzz| -+--------+--------+--------+--------+--------+--------+-----^---+--------+........+........+........+········+ -``` +| JSON CRDT node type | CBOR encoding | +| ------------------- | ------------- | +| `con` | Major type 0 | +| `val` | Major type 1 | +| `vec` | Major type 2 | +| `obj` | Major type 3 | +| `str` | Major type 4 | +| `bin` | Major type 5 | +| `arr` | Major type 6 | diff --git a/src/json-crdt/codec/structural/binary/ViewDecoder.ts b/src/json-crdt/codec/structural/binary/ViewDecoder.ts index 32c78c37e0..d6c035d3e6 100644 --- a/src/json-crdt/codec/structural/binary/ViewDecoder.ts +++ b/src/json-crdt/codec/structural/binary/ViewDecoder.ts @@ -1,7 +1,8 @@ -import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtDecoder'; -import {MsgPackDecoderFast} from '../../../../json-pack/msgpack'; +import {CrdtReader} from '../../../../json-crdt-patch/util/binary/CrdtReader'; +import {CborDecoderBase} from '../../../../json-pack/cbor/CborDecoderBase'; +import {CRDT_MAJOR} from './constants'; -export class ViewDecoder extends MsgPackDecoderFast { +export class ViewDecoder extends CborDecoderBase { protected time: number = -1; constructor() { @@ -32,49 +33,37 @@ export class ViewDecoder extends MsgPackDecoderFast { return !peek ? undefined : this.cNode(); } - public cNode(): unknown { + protected cNode(): unknown { const reader = this.reader; this.ts(); - const byte = reader.u8(); - if (byte <= 0b10001111) return this.cObj(byte & 0b1111); - else if (byte <= 0b10011111) return this.cArr(byte & 0b1111); - else if (byte <= 0b10111111) return this.cStr(byte & 0b11111); - else { - switch (byte) { - case 0xc4: - return this.cBin(reader.u8()); - case 0xc5: - return this.cBin(reader.u16()); - case 0xc6: - return this.cBin(reader.u32()); - case 0xd4: - return this.val(); - case 0xd5: - return null; - case 0xd6: - return this.cNode(); - case 0xde: - return this.cObj(reader.u16()); - case 0xdf: - return this.cObj(reader.u32()); - case 0xdc: - return this.cArr(reader.u16()); - case 0xdd: - return this.cArr(reader.u32()); - case 0xd9: - return this.cStr(reader.u8()); - case 0xda: - return this.cStr(reader.u16()); - case 0xdb: - return this.cStr(reader.u32()); - case 0xc7: - return this.cTup(); - } + const octet = reader.u8(); + const major = octet >> 5; + const minor = octet & 0b11111; + const length = minor < 24 ? minor : minor === 24 ? reader.u8() : minor === 25 ? reader.u16() : reader.u32(); + switch (major) { + case CRDT_MAJOR.CON: + return this.cCon(length); + case CRDT_MAJOR.VAL: + return this.cNode(); + case CRDT_MAJOR.VEC: + return this.cVec(length); + case CRDT_MAJOR.OBJ: + return this.cObj(length); + case CRDT_MAJOR.STR: + return this.cStr(length); + case CRDT_MAJOR.BIN: + return this.cBin(length); + case CRDT_MAJOR.ARR: + return this.cArr(length); } return undefined; } - public cObj(length: number): Record { + protected cCon(length: number): unknown { + return !length ? this.val() : (this.ts(), null); + } + + protected cObj(length: number): Record { const obj: Record = {}; for (let i = 0; i < length; i++) { const key: string = this.key(); @@ -84,10 +73,8 @@ export class ViewDecoder extends MsgPackDecoderFast { return obj; } - public cTup(): unknown[] { + protected cVec(length: number): unknown[] { const reader = this.reader; - const length = this.reader.u8(); - reader.x++; const obj: unknown[] = []; for (let i = 0; i < length; i++) { const octet = reader.peak(); @@ -99,7 +86,7 @@ export class ViewDecoder extends MsgPackDecoderFast { return obj; } - public cArr(length: number): unknown[] { + protected cArr(length: number): unknown[] { const arr: unknown[] = []; for (let i = 0; i < length; i++) { const values = this.cArrChunk(); @@ -108,7 +95,7 @@ export class ViewDecoder extends MsgPackDecoderFast { return arr; } - private cArrChunk(): unknown[] | undefined { + protected cArrChunk(): unknown[] | undefined { const [deleted, length] = this.reader.b1vu28(); this.ts(); if (deleted) { @@ -120,7 +107,7 @@ export class ViewDecoder extends MsgPackDecoderFast { } } - public cStr(length: number): string { + protected cStr(length: number): string { const reader = this.reader; let str = ''; for (let i = 0; i < length; i++) { @@ -137,7 +124,7 @@ export class ViewDecoder extends MsgPackDecoderFast { return str; } - public cBin(length: number): Uint8Array { + protected cBin(length: number): Uint8Array { const reader = this.reader; const buffers: Uint8Array[] = []; let totalLength = 0; diff --git a/src/json-crdt/codec/structural/binary/__tests__/ViewDecoder.spec.ts b/src/json-crdt/codec/structural/binary/__tests__/ViewDecoder.spec.ts index 485aa5c62d..e6362d2271 100644 --- a/src/json-crdt/codec/structural/binary/__tests__/ViewDecoder.spec.ts +++ b/src/json-crdt/codec/structural/binary/__tests__/ViewDecoder.spec.ts @@ -1,4 +1,4 @@ -import {Timestamp, VectorClock, equal} from '../../../../../json-crdt-patch/clock'; +import {Timestamp, VectorClock} from '../../../../../json-crdt-patch/clock'; import {Model} from '../../../../model'; import {Encoder} from '../Encoder'; import {Decoder} from '../Decoder'; @@ -6,7 +6,7 @@ import {ViewDecoder} from '../ViewDecoder'; import {konst} from '../../../../../json-crdt-patch/builder/Konst'; describe('logical', () => { - test('decodes clock', () => { + test('can decode various documents', () => { const doc1 = Model.withLogicalClock(new VectorClock(222, 1)); const encoder = new Encoder(); const viewDecoder = new ViewDecoder(); @@ -15,6 +15,9 @@ describe('logical', () => { const encoded = encoder.encode(doc1); const decoded = viewDecoder.decode(encoded); const decoded2 = decoder.decode(encoded).view(); + // console.log(decoded); + // console.log(decoded2); + // console.log(doc1.view()); expect(decoded).toStrictEqual(doc1.view()); expect(decoded2).toStrictEqual(doc1.view()); }; @@ -39,7 +42,7 @@ describe('logical', () => { assertCanDecode(); }); - test('can decode ID as const value', () => { + test('decodes "con" timestamp as null', () => { const model = Model.withLogicalClock(); model.api.root({ foo: konst(new Timestamp(model.clock.sid, 2)), @@ -50,4 +53,34 @@ describe('logical', () => { const view = viewDecoder.decode(encoded); expect((view as any).foo).toBe(null); }); + + test('can decode a simple number', () => { + const model = Model.withLogicalClock(); + model.api.root(123); + const encoder = new Encoder(); + const viewDecoder = new ViewDecoder(); + const encoded = encoder.encode(model); + const view = viewDecoder.decode(encoded); + expect(view).toStrictEqual(123); + }); + + test('can decode a simple string', () => { + const model = Model.withLogicalClock(); + model.api.root('asdf'); + const encoder = new Encoder(); + const viewDecoder = new ViewDecoder(); + const encoded = encoder.encode(model); + const view = viewDecoder.decode(encoded); + expect(view).toStrictEqual('asdf'); + }); + + test('can decode a simple object', () => { + const model = Model.withLogicalClock(); + model.api.root({yes: false}); + const encoder = new Encoder(); + const viewDecoder = new ViewDecoder(); + const encoded = encoder.encode(model); + const view = viewDecoder.decode(encoded); + expect(view).toStrictEqual({yes: false}); + }); }); diff --git a/src/json-crdt/codec/structural/binary/__tests__/codec-logical.spec.ts b/src/json-crdt/codec/structural/binary/__tests__/codec-logical.spec.ts index 6eab244b55..88886e0eac 100644 --- a/src/json-crdt/codec/structural/binary/__tests__/codec-logical.spec.ts +++ b/src/json-crdt/codec/structural/binary/__tests__/codec-logical.spec.ts @@ -7,6 +7,53 @@ import {konst} from '../../../../../json-crdt-patch/builder/Konst'; const encoder = new Encoder(); const decoder = new Decoder(); +test('empty model', () => { + const model1 = Model.withLogicalClock(new VectorClock(5, 0)); + const encoded1 = encoder.encode(model1); + const model2 = decoder.decode(encoded1); + expect(model1.view()).toBe(undefined); + expect(model2.view()).toBe(undefined); + expect(model2.clock.sid).toBe(model1.clock.sid); + expect(model2.clock.time).toBe(model1.clock.time); + expect(model2.clock).toStrictEqual(model1.clock); +}); + +test('model with just a single number', () => { + const model1 = Model.withLogicalClock(new VectorClock(5, 0)); + model1.api.root(123); + const encoded1 = encoder.encode(model1); + const model2 = decoder.decode(encoded1); + expect(model1.view()).toBe(123); + expect(model2.view()).toBe(123); + expect(model2.clock.sid).toBe(model1.clock.sid); + expect(model2.clock.time).toBe(model1.clock.time); + expect(model2.clock).toStrictEqual(model1.clock); +}); + +test('model with just a single string', () => { + const model1 = Model.withLogicalClock(new VectorClock(5, 0)); + model1.api.root('aaaa'); + const encoded1 = encoder.encode(model1); + const model2 = decoder.decode(encoded1); + expect(model1.view()).toBe('aaaa'); + expect(model2.view()).toBe('aaaa'); + expect(model2.clock.sid).toBe(model1.clock.sid); + expect(model2.clock.time).toBe(model1.clock.time); + expect(model2.clock).toStrictEqual(model1.clock); +}); + +test('model with a simple object', () => { + const model1 = Model.withLogicalClock(new VectorClock(5, 0)); + model1.api.root({foo: 123}); + const encoded1 = encoder.encode(model1); + const model2 = decoder.decode(encoded1); + expect(model1.view()).toStrictEqual({foo: 123}); + expect(model2.view()).toStrictEqual({foo: 123}); + expect(model2.clock.sid).toBe(model1.clock.sid); + expect(model2.clock.time).toBe(model1.clock.time); + expect(model2.clock).toStrictEqual(model1.clock); +}); + test('encoding/decoding a model results in the same node IDs', () => { const model1 = Model.withLogicalClock(new VectorClock(5, 0)); model1.api.root(''); diff --git a/src/json-crdt/codec/structural/binary/constants.ts b/src/json-crdt/codec/structural/binary/constants.ts new file mode 100644 index 0000000000..8e99ad5cea --- /dev/null +++ b/src/json-crdt/codec/structural/binary/constants.ts @@ -0,0 +1,21 @@ +import {JsonCrdtDataType} from '../../../../json-crdt-patch/constants'; + +export const enum CRDT_MAJOR { + CON = JsonCrdtDataType.con, + VAL = JsonCrdtDataType.val, + OBJ = JsonCrdtDataType.obj, + VEC = JsonCrdtDataType.vec, + STR = JsonCrdtDataType.str, + BIN = JsonCrdtDataType.bin, + ARR = JsonCrdtDataType.arr, +} + +export const enum CRDT_MAJOR_OVERLAY { + CON = JsonCrdtDataType.con << 5, + VAL = CRDT_MAJOR.VAL << 5, + VEC = CRDT_MAJOR.VEC << 5, + OBJ = CRDT_MAJOR.OBJ << 5, + STR = CRDT_MAJOR.STR << 5, + BIN = CRDT_MAJOR.BIN << 5, + ARR = CRDT_MAJOR.ARR << 5, +} diff --git a/src/json-crdt/codec/structural/json/__tests__/runCodecAllTypesSmokeTests.ts b/src/json-crdt/codec/structural/json/__tests__/runCodecAllTypesSmokeTests.ts index 4953022668..e183cc8aea 100644 --- a/src/json-crdt/codec/structural/json/__tests__/runCodecAllTypesSmokeTests.ts +++ b/src/json-crdt/codec/structural/json/__tests__/runCodecAllTypesSmokeTests.ts @@ -1,6 +1,5 @@ import {konst} from '../../../../../json-crdt-patch/builder/Konst'; import {vec} from '../../../../../json-crdt-patch/builder/Tuple'; -import {Timestamp} from '../../../../../json-crdt-patch/clock'; import {Model} from '../../../../model'; export const runCodecAllTypesSmokeTests = (assertCodec: (doc: Model) => void) => { @@ -16,13 +15,13 @@ export const runCodecAllTypesSmokeTests = (assertCodec: (doc: Model) => void) => assertCodec(model); }); - test('number with server clock', () => { + test('numbers with server clock', () => { const model = Model.withServerClock(); model.api.root([1, 0, -4, 1.132, 8324.234234, 888888888888]); assertCodec(model); }); - test('string', () => { + test('strings', () => { const model = Model.withLogicalClock(); model.api.root(['', 'abc', '😛']); assertCodec(model); diff --git a/src/json-pack/cbor/CborDecoderBase.ts b/src/json-pack/cbor/CborDecoderBase.ts index 33b5c2a282..3b27a6ea8d 100644 --- a/src/json-pack/cbor/CborDecoderBase.ts +++ b/src/json-pack/cbor/CborDecoderBase.ts @@ -56,7 +56,7 @@ export class CborDecoderBase> 5; + const minor = octet & CONST.MINOR_MASK; + if (major !== MAJOR.STR) throw ERROR.UNEXPECTED_STR_MAJOR; + return this.readStr(minor); + } + public readStr(minor: number): string { const reader = this.reader; if (minor <= 23) return reader.utf8(minor); diff --git a/src/json-pack/cbor/constants.ts b/src/json-pack/cbor/constants.ts index 984e24f7b8..86b3a5ae2b 100644 --- a/src/json-pack/cbor/constants.ts +++ b/src/json-pack/cbor/constants.ts @@ -38,4 +38,5 @@ export const enum ERROR { INVALID_SIZE, KEY_NOT_FOUND, INDEX_OUT_OF_BOUNDS, + UNEXPECTED_STR_MAJOR, }