Skip to content

Commit

Permalink
Merge pull request #549 from streamich/dag-json
Browse files Browse the repository at this point in the history
DAG-JSON
  • Loading branch information
streamich authored Mar 12, 2024
2 parents 3b882fb + 1ff8765 commit 80d9f4a
Show file tree
Hide file tree
Showing 20 changed files with 536 additions and 83 deletions.
9 changes: 2 additions & 7 deletions src/json-pack/cbor/CborEncoderStable.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import {CborEncoder} from './CborEncoder';
import {sort} from '../../util/sort/insertion2';
import {MAJOR_OVERLAY} from './constants';

const objectKeyComparator = (a: string, b: string): number => {
const len1 = a.length;
const len2 = b.length;
return len1 === len2 ? (a > b ? 1 : -1) : len1 - len2;
};
import {objKeyCmp} from '../util/objKeyCmp';

const strHeaderLength = (strSize: number): 1 | 2 | 3 | 5 => {
if (strSize <= 23) return 1;
Expand All @@ -18,7 +13,7 @@ const strHeaderLength = (strSize: number): 1 | 2 | 3 | 5 => {
export class CborEncoderStable extends CborEncoder {
public writeObj(obj: Record<string, unknown>): void {
const keys = Object.keys(obj);
sort(keys, objectKeyComparator);
sort(keys, objKeyCmp);
const length = keys.length;
this.writeObjHdr(length);
for (let i = 0; i < length; i++) {
Expand Down
27 changes: 7 additions & 20 deletions src/json-pack/json/JsonDecoder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {decodeUtf8} from '../../util/buffers/utf8/decodeUtf8';
import {Reader} from '../../util/buffers/Reader';
import {fromBase64Bin} from '../../util/base64/fromBase64Bin';
import {findEndingQuote} from './util';
import type {BinaryJsonDecoder, PackValue} from '../types';

const REGEX_REPLACE_ESCAPED_CHARS = /\\(b|f|n|r|t|"|\/|\\)/g;
Expand Down Expand Up @@ -104,20 +105,6 @@ const isUndefined = (u8: Uint8Array, x: number) =>
u8[x++] === 0x3d && // =
u8[x++] === 0x22; // "

const findEndingQuote = (uint8: Uint8Array, x: number): number => {
const len = uint8.length;
let char = uint8[x];
let prev = 0;
while (x < len) {
if (char === 34 && prev !== 92) break;
if (char === 92 && prev === 92) prev = 0;
else prev = char;
char = uint8[++x];
}
if (x === len) throw new Error('Invalid JSON');
return x;
};

const fromCharCode = String.fromCharCode;

const readShortUtf8StrAndUnescape = (reader: Reader): string => {
Expand Down Expand Up @@ -198,7 +185,7 @@ const readShortUtf8StrAndUnescape = (reader: Reader): string => {
export class JsonDecoder implements BinaryJsonDecoder {
public reader = new Reader();

public read(uint8: Uint8Array): PackValue {
public read(uint8: Uint8Array): unknown {
this.reader.reset(uint8);
return this.readAny();
}
Expand All @@ -208,7 +195,7 @@ export class JsonDecoder implements BinaryJsonDecoder {
return this.readAny();
}

public readAny(): PackValue {
public readAny(): unknown {
this.skipWhitespace();
const reader = this.reader;
const x = reader.x;
Expand Down Expand Up @@ -653,10 +640,10 @@ export class JsonDecoder implements BinaryJsonDecoder {
return bin;
}

public readArr(): PackValue[] {
public readArr(): unknown[] {
const reader = this.reader;
if (reader.u8() !== 0x5b) throw new Error('Invalid JSON');
const arr: PackValue[] = [];
const arr: unknown[] = [];
const uint8 = reader.uint8;
while (true) {
this.skipWhitespace();
Expand All @@ -670,10 +657,10 @@ export class JsonDecoder implements BinaryJsonDecoder {
}
}

public readObj(): Record<string, PackValue> {
public readObj(): PackValue | Record<string, unknown> | unknown {
const reader = this.reader;
if (reader.u8() !== 0x7b) throw new Error('Invalid JSON');
const obj: Record<string, PackValue> = {};
const obj: Record<string, unknown> = {};
const uint8 = reader.uint8;
while (true) {
this.skipWhitespace();
Expand Down
133 changes: 133 additions & 0 deletions src/json-pack/json/JsonDecoderDag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {JsonDecoder} from './JsonDecoder';
import {findEndingQuote} from './util';
import type {PackValue} from '../types';
import {createFromBase64Bin} from '../../util/base64/createFromBase64Bin';

export const fromBase64Bin = createFromBase64Bin(undefined, '');

export class JsonDecoderDag extends JsonDecoder {
public readObj(): PackValue | Record<string, PackValue> | Uint8Array | unknown {
const bytes = this.tryReadBytes();
if (bytes) return bytes;
const cid = this.tryReadCid();
if (cid) return cid;
return super.readObj();
}

protected tryReadBytes(): Uint8Array | undefined {
const reader = this.reader;
const x = reader.x;
if (reader.u8() !== 0x7b) {
// {
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x22 || reader.u8() !== 0x2f || reader.u8() !== 0x22) {
// "/"
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x3a) {
// :
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x7b) {
// {
reader.x = x;
return;
}
this.skipWhitespace();
if (
reader.u8() !== 0x22 ||
reader.u8() !== 0x62 ||
reader.u8() !== 0x79 ||
reader.u8() !== 0x74 ||
reader.u8() !== 0x65 ||
reader.u8() !== 0x73 ||
reader.u8() !== 0x22
) {
// "bytes"
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x3a) {
// :
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x22) {
// "
reader.x = x;
return;
}
const bufStart = reader.x;
const bufEnd = findEndingQuote(reader.uint8, bufStart);
reader.x = 1 + bufEnd;
this.skipWhitespace();
if (reader.u8() !== 0x7d) {
// }
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x7d) {
// }
reader.x = x;
return;
}
const bin = fromBase64Bin(reader.view, bufStart, bufEnd - bufStart);
return bin;
}

protected tryReadCid(): undefined | unknown {
const reader = this.reader;
const x = reader.x;
if (reader.u8() !== 0x7b) {
// {
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x22 || reader.u8() !== 0x2f || reader.u8() !== 0x22) {
// "/"
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x3a) {
// :
reader.x = x;
return;
}
this.skipWhitespace();
if (reader.u8() !== 0x22) {
// "
reader.x = x;
return;
}
const bufStart = reader.x;
const bufEnd = findEndingQuote(reader.uint8, bufStart);
reader.x = 1 + bufEnd;
this.skipWhitespace();
if (reader.u8() !== 0x7d) {
// }
reader.x = x;
return;
}
const finalX = reader.x;
reader.x = bufStart;
const cid = reader.ascii(bufEnd - bufStart);
reader.x = finalX;
return this.readCid(cid);
}

public readCid(cid: string): unknown {
return cid;
}
}
15 changes: 13 additions & 2 deletions src/json-pack/json/JsonEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode
return writer.flush();
}

/**
* Called when the encoder encounters a value that it does not know how to encode.
*
* @param value Some JavaScript value.
*/
public writeUnknown(value: unknown): void {
this.writeNull();
}

public writeAny(value: unknown): void {
switch (typeof value) {
case 'boolean':
Expand All @@ -24,19 +33,21 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode
if (value === null) return this.writeNull();
const constructor = value.constructor;
switch (constructor) {
case Object:
return this.writeObj(value as Record<string, unknown>);
case Array:
return this.writeArr(value as unknown[]);
case Uint8Array:
return this.writeBin(value as Uint8Array);
default:
return this.writeObj(value as Record<string, unknown>);
return this.writeUnknown(value);
}
}
case 'undefined': {
return this.writeUndef();
}
default:
return this.writeNull();
return this.writeUnknown(value);
}
}

Expand Down
59 changes: 59 additions & 0 deletions src/json-pack/json/JsonEncoderDag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {JsonEncoderStable} from './JsonEncoderStable';
import {createToBase64Bin} from '../../util/base64/createToBase64Bin';

const objBaseLength = '{"/":{"bytes":""}}'.length;
const cidBaseLength = '{"/":""}'.length;
const base64Encode = createToBase64Bin(undefined, '');

/**
* Base class for implementing DAG-JSON encoders.
*
* @see https://ipld.io/specs/codecs/dag-json/spec/
*/
export class JsonEncoderDag extends JsonEncoderStable {
/**
* Encodes binary data as nested `["/", "bytes"]` object encoded in Base64
* without padding.
*
* Example:
*
* ```json
* {"/":{"bytes":"aGVsbG8gd29ybGQ"}}
* ```
*
* @param buf Binary data to write.
*/
public writeBin(buf: Uint8Array): void {
const writer = this.writer;
const length = buf.length;
writer.ensureCapacity(objBaseLength + (length << 1));
const view = writer.view;
const uint8 = writer.uint8;
let x = writer.x;
view.setUint32(x, 0x7b222f22); // {"/"
x += 4;
view.setUint32(x, 0x3a7b2262); // :{"b
x += 4;
view.setUint32(x, 0x79746573); // ytes
x += 4;
view.setUint16(x, 0x223a); // ":
x += 2;
uint8[x] = 0x22; // "
x += 1;
x = base64Encode(buf, 0, length, view, x);
view.setUint16(x, 0x227d); // "}
x += 2;
uint8[x] = 0x7d; // }
x += 1;
writer.x = x;
}

public writeCid(cid: string): void {
const writer = this.writer;
writer.ensureCapacity(cidBaseLength + cid.length);
writer.u32(0x7b222f22); // {"/"
writer.u16(0x3a22); // :"
writer.ascii(cid);
writer.u16(0x227d); // "}
}
}
23 changes: 23 additions & 0 deletions src/json-pack/json/JsonEncoderStable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {JsonEncoder} from './JsonEncoder';
import {sort} from '../../util/sort/insertion2';
import {objKeyCmp} from '../util/objKeyCmp';

export class JsonEncoderStable extends JsonEncoder {
public writeObj(obj: Record<string, unknown>): void {
const writer = this.writer;
const keys = Object.keys(obj);
sort(keys, objKeyCmp);
const length = keys.length;
if (!length) return writer.u16(0x7b7d); // {}
writer.u8(0x7b); // {
for (let i = 0; i < length; i++) {
const key = keys[i];
const value = obj[key];
this.writeStr(key);
writer.u8(0x3a); // :
this.writeAny(value);
writer.u8(0x2c); // ,
}
writer.uint8[writer.x - 1] = 0x7d; // }
}
}
Loading

0 comments on commit 80d9f4a

Please sign in to comment.