@cloudpss/ubjson
Version:
Opinionated UBJSON encoder/decoder for CloudPSS.
420 lines (405 loc) • 13.8 kB
text/typescript
import type { DecodeOptions } from '../options.js';
import { constants } from './constants.js';
import { UnexpectedEofError, unsupportedType } from './errors.js';
import { decode } from './string-decoder.js';
import { toUint8Array } from './utils.js';
const { defineProperty } = Object;
const { fromCharCode } = String;
/** 数据包装 */
export interface DecodeCursor {
/** 数据 */
readonly view: DataView;
/** 数据 */
readonly data: Uint8Array;
/** 当前读指针位置 */
offset: number;
/** 读取到末尾时调用 */
eof(): never;
/** 选项 */
readonly options: DecodeOptions | undefined;
}
/** 创建数据包装 */
export function DecodeCursor(data: BufferSource, options?: DecodeOptions): DecodeCursor {
const d = toUint8Array(data);
const v = new DataView(d.buffer, d.byteOffset, d.byteLength);
return {
data: d,
view: v,
offset: 0,
eof() {
throw new UnexpectedEofError();
},
options,
};
}
/**
* 读取第一个非 NOOP 的字节
* @returns 返回读取到的字节,如果读取到末尾则返回 undefined
*/
export function readMarkerOrUndefined(cursor: DecodeCursor): number | undefined {
let marker: number | undefined;
do {
marker = cursor.data[cursor.offset++];
} while (marker === constants.NO_OP);
return marker;
}
/**
* 读取第一个非 NOOP 的字节
*/
export function readMarker(cursor: DecodeCursor): number {
let marker: number | undefined;
do {
marker = cursor.data[cursor.offset++];
} while (marker === constants.NO_OP);
if (marker === undefined) return cursor.eof();
return marker;
}
/** 读取一个大于 0 的整数 */
export function readLength(cursor: DecodeCursor): number {
const marker = readMarker(cursor);
let length: number;
switch (marker) {
case constants.INT8:
length = readInt8Data(cursor);
break;
case constants.UINT8:
length = readUint8Data(cursor);
break;
case constants.INT16:
length = readInt16Data(cursor);
break;
case constants.INT32:
length = readInt32Data(cursor);
break;
case constants.INT64: {
const l = readInt64Data(cursor);
if (l < 0 || l > Number.MAX_SAFE_INTEGER) {
throw new Error('Invalid length');
}
length = Number(l);
break;
}
default:
throw new Error(`Unexpected marker '${fromCharCode(marker)}'(${marker}) for int length`);
}
if (length < 0) {
throw new Error('Invalid length');
}
return length;
}
/** readInt8Data */
export function readInt8Data(cursor: DecodeCursor): number {
try {
return cursor.view.getInt8(cursor.offset++);
} catch {
return cursor.eof();
}
}
/** readUint8Data */
export function readUint8Data(cursor: DecodeCursor): number {
const result = cursor.data[cursor.offset++];
if (result === undefined) return cursor.eof();
return result;
}
/** readInt16Data */
export function readInt16Data(cursor: DecodeCursor): number {
try {
const result = cursor.view.getInt16(cursor.offset);
cursor.offset += 2;
return result;
} catch {
return cursor.eof();
}
}
/** readInt32Data */
export function readInt32Data(cursor: DecodeCursor): number {
try {
const result = cursor.view.getInt32(cursor.offset);
cursor.offset += 4;
return result;
} catch {
return cursor.eof();
}
}
/** readInt64Data */
export function readInt64Data(cursor: DecodeCursor): bigint {
try {
const result = cursor.view.getBigInt64(cursor.offset);
cursor.offset += 8;
return result;
} catch {
return cursor.eof();
}
}
/** readFloat32Data */
export function readFloat32Data(cursor: DecodeCursor): number {
try {
const result = cursor.view.getFloat32(cursor.offset);
cursor.offset += 4;
return result;
} catch {
return cursor.eof();
}
}
/** readFloat64Data */
export function readFloat64Data(cursor: DecodeCursor): number {
try {
const result = cursor.view.getFloat64(cursor.offset);
cursor.offset += 8;
return result;
} catch {
return cursor.eof();
}
}
/** 读取数据 */
export function read(cursor: DecodeCursor): unknown {
const marker = readMarker(cursor);
return readData(cursor, marker);
}
/** 处理 `__proto__` */
export function protoAction(cursor: Pick<DecodeCursor, 'options'>, obj: Record<string, unknown>, value: unknown): void {
if (cursor.options?.protoAction === 'error') {
throw new Error('Unexpected "__proto__"');
} else if (cursor.options?.protoAction === 'allow') {
defineProperty(obj, '__proto__', {
value,
enumerable: true,
configurable: true,
writable: true,
});
}
}
/** 处理 `constructor` */
export function constructorAction(
cursor: Pick<DecodeCursor, 'options'>,
obj: Record<string, unknown>,
value: unknown,
): void {
if (cursor.options?.constructorAction === 'error') {
throw new Error('Unexpected "constructor"');
} else if (cursor.options?.constructorAction === 'remove') {
return;
}
// eslint-disable-next-line @typescript-eslint/dot-notation
obj['constructor'] = value;
}
/** 读取优化对象数据 */
function readObjectOptimizedData(cursor: DecodeCursor, marker: OptimizedFormatMarkers): unknown {
const { count, type } = marker;
const object: Record<string, unknown> = {};
for (let i = 0; i < count; i++) {
const key = readKey(cursor);
const value = readData(cursor, type ?? readMarker(cursor));
if (key === '__proto__') {
protoAction(cursor, object, value);
continue;
}
if (key === 'constructor') {
constructorAction(cursor, object, value);
continue;
}
object[key] = value;
}
return object;
}
/** 根据标签读取后续数据 */
export function readData(cursor: DecodeCursor, marker: number): unknown {
// 按照出现频率排序
switch (marker) {
case constants.STRING: {
const length = readLength(cursor);
const begin = cursor.offset;
const end = begin + length;
cursor.offset = end;
const { data } = cursor;
if (end > data.length) cursor.eof();
return decode(data, begin, end);
}
case constants.OBJECT: {
const markers = readOptimizedFormatMarkers(cursor);
if (markers == null) {
// 直到 '}'
const object: Record<string, unknown> = {};
while (readMarker(cursor) !== constants.OBJECT_END) {
cursor.offset--;
const key = readKey(cursor);
const value = read(cursor);
if (key === '__proto__') {
protoAction(cursor, object, value);
continue;
}
if (key === 'constructor') {
constructorAction(cursor, object, value);
continue;
}
object[key] = value;
}
return object;
}
return readObjectOptimizedData(cursor, markers);
}
case constants.ARRAY: {
const markers = readOptimizedFormatMarkers(cursor);
if (markers == null) {
const array = [];
for (;;) {
const marker = readMarker(cursor);
// 直到 ']'
if (marker === constants.ARRAY_END) break;
array.push(readData(cursor, marker));
}
return array;
}
const { count, type } = markers;
switch (type) {
case constants.UINT8:
try {
const buf = new Uint8Array(
cursor.data.buffer,
cursor.data.byteOffset + cursor.offset,
count,
).slice();
cursor.offset += count;
return buf;
} catch {
return cursor.eof();
}
case constants.INT8:
try {
const buf = new Int8Array(
cursor.data.buffer,
cursor.data.byteOffset + cursor.offset,
count,
).slice();
cursor.offset += count;
return buf;
} catch {
return cursor.eof();
}
case constants.INT16: {
const result = new Int16Array(count);
for (let i = 0; i < count; i++) {
result[i] = readInt16Data(cursor);
}
return result;
}
case constants.INT32: {
const result = new Int32Array(count);
for (let i = 0; i < count; i++) {
result[i] = readInt32Data(cursor);
}
return result;
}
case constants.FLOAT32: {
const result = new Float32Array(count);
for (let i = 0; i < count; i++) {
result[i] = readFloat32Data(cursor);
}
return result;
}
case constants.FLOAT64: {
const result = new Float64Array(count);
for (let i = 0; i < count; i++) {
result[i] = readFloat64Data(cursor);
}
return result;
}
case constants.INT64: {
const result = new BigInt64Array(count);
for (let i = 0; i < count; i++) {
result[i] = readInt64Data(cursor);
}
return result;
}
case constants.NULL:
return Array.from({ length: count }).fill(null);
case constants.TRUE:
return Array.from({ length: count }).fill(true);
case constants.FALSE:
return Array.from({ length: count }).fill(false);
case undefined:
default:
break;
}
const array: unknown[] = [];
array.length = count;
for (let i = 0; i < count; i++) {
array[i] = type === undefined ? read(cursor) : readData(cursor, type);
}
return array;
}
case constants.FLOAT64:
return readFloat64Data(cursor);
case constants.UINT8:
return readUint8Data(cursor);
case constants.INT16:
return readInt16Data(cursor);
case constants.FLOAT32:
return readFloat32Data(cursor);
case constants.CHAR:
return fromCharCode(readUint8Data(cursor));
case constants.INT32:
return readInt32Data(cursor);
case constants.INT8:
return readInt8Data(cursor);
case constants.NULL:
return null;
case constants.TRUE:
return true;
case constants.FALSE:
return false;
case constants.INT64: {
const n = readInt64Data(cursor);
if (n < Number.MIN_SAFE_INTEGER || n > Number.MAX_SAFE_INTEGER) {
return n;
}
return Number(n);
}
case constants.HIGH_PRECISION_NUMBER: {
const length = readLength(cursor);
try {
const _buffer = new Uint8Array(cursor.data.buffer, cursor.offset, length).slice();
cursor.offset += length;
// return buffer;
} catch {
return cursor.eof();
}
unsupportedType('high precision number');
}
}
if (typeof marker != 'number') return marker;
throw new Error(`Unexpected marker '${fromCharCode(marker)}'(${marker})`);
}
/** readKey */
export function readKey(cursor: DecodeCursor): string {
const length = readLength(cursor);
const begin = cursor.offset;
const end = begin + length;
cursor.offset = end;
const { data } = cursor;
if (end > data.length) cursor.eof();
return decode(data, begin, end);
}
/** Optimized Format 数据 */
export type OptimizedFormatMarkers = { type?: number; count: number };
/** 读取 Optimized Format 数据 */
export function readOptimizedFormatMarkers(cursor: DecodeCursor): OptimizedFormatMarkers | undefined {
let type;
let count;
switch (readMarker(cursor)) {
case constants.TYPE_MARKER:
type = readMarker(cursor);
if (readMarker(cursor) !== constants.COUNT_MARKER) {
throw new Error('Expected count marker');
}
/* fall through */
case constants.COUNT_MARKER:
count = readLength(cursor);
break;
default:
// 不是 '$' 或 '#',回溯
cursor.offset--;
return undefined;
}
return { type, count };
}