UNPKG

@cloudpss/ubjson

Version:

632 lines (620 loc) 24.3 kB
import type { DecodeOptions } from '../options.js'; import { constants } from './constants.js'; import { protoAction, constructorAction } from './decode.js'; import { unsupportedType } from './errors.js'; import { decode } from './string-decoder.js'; import { toUint8Array } from './utils.js'; const { fromCharCode } = String; /** * 数据读取函数 * `yield` 返回需要的字节数,`return` 返回解析结果 */ export type DecodeFuncAe<T = unknown> = Generator<number, T, void>; /** 数据包装 */ export interface DecodeCursorAe { /** 当前数据块 */ readonly view: DataView; /** 当前数据块 */ readonly data: Uint8Array<ArrayBuffer>; /** 当前数据大小 */ readonly size: number; /** 当前读指针位置 */ offset: number; /** 选项 */ readonly options: DecodeOptions | undefined; } /** 创建数据包装 */ export function DecodeCursor(data: BufferSource, options?: DecodeOptions): DecodeCursorAe { const d = toUint8Array(data); const v = new DataView(d.buffer, d.byteOffset, d.byteLength); return { data: d, view: v, size: v.byteLength, offset: 0, options, }; } /** * 读取第一个非 NOOP 的字节 */ export function* readMarker(cursor: DecodeCursorAe): DecodeFuncAe<number> { let marker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } while (marker === constants.NO_OP); return marker; } /** 读取一个大于 0 的整数 */ export function* readLength(cursor: DecodeCursorAe): DecodeFuncAe<number> { let marker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } while (marker === constants.NO_OP); let length: number; switch (marker) { case constants.INT8: if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } length = cursor.view.getInt8(cursor.offset++); break; case constants.UINT8: length = yield* readUint8Data(cursor); break; case constants.INT16: length = yield* readInt16Data(cursor); break; case constants.INT32: length = yield* readInt32Data(cursor); break; case constants.INT64: { const l = yield* 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: DecodeCursorAe): DecodeFuncAe<number> { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } return cursor.view.getInt8(cursor.offset++); } /** readUint8Data */ export function* readUint8Data(cursor: DecodeCursorAe): DecodeFuncAe<number> { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } return cursor.data[cursor.offset++]!; } /** readInt16Data */ export function* readInt16Data(cursor: DecodeCursorAe): DecodeFuncAe<number> { if (cursor.offset + 2 > cursor.size) { yield cursor.offset - cursor.size + 2; } const value = cursor.view.getInt16(cursor.offset); cursor.offset += 2; return value; } /** readInt32Data */ export function* readInt32Data(cursor: DecodeCursorAe): DecodeFuncAe<number> { if (cursor.offset + 4 > cursor.size) { yield cursor.offset - cursor.size + 4; } const value = cursor.view.getInt32(cursor.offset); cursor.offset += 4; return value; } /** readInt64Data */ export function* readInt64Data(cursor: DecodeCursorAe): DecodeFuncAe<bigint> { if (cursor.offset + 8 > cursor.size) { yield cursor.offset - cursor.size + 8; } const value = cursor.view.getBigInt64(cursor.offset); cursor.offset += 8; return value; } /** readFloat32Data */ export function* readFloat32Data(cursor: DecodeCursorAe): DecodeFuncAe<number> { if (cursor.offset + 4 > cursor.size) { yield cursor.offset - cursor.size + 4; } const value = cursor.view.getFloat32(cursor.offset); cursor.offset += 4; return value; } /** readFloat64Data */ export function* readFloat64Data(cursor: DecodeCursorAe): DecodeFuncAe<number> { if (cursor.offset + 8 > cursor.size) { yield cursor.offset - cursor.size + 8; } const value = cursor.view.getFloat64(cursor.offset); cursor.offset += 8; return value; } /** 读取数据 */ export function* read(cursor: DecodeCursorAe): DecodeFuncAe<unknown> { let marker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } while (marker === constants.NO_OP); return yield* readData(cursor, marker); } /** 读取优化对象数据 */ function* readObjectOptimizedData(cursor: DecodeCursorAe, marker: OptimizedFormatMarkers): DecodeFuncAe<unknown> { const { count, type } = marker; const object: Record<string, unknown> = {}; for (let i = 0; i < count; i++) { const key = yield* readKey(cursor); const value = yield* readData(cursor, type ?? (yield* 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: DecodeCursorAe, marker: number): DecodeFuncAe<unknown> { // 按照出现频率排序 switch (marker) { case constants.STRING: { let marker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } while (marker === constants.NO_OP); let length: number; switch (marker) { case constants.INT8: if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } length = cursor.view.getInt8(cursor.offset++); break; case constants.UINT8: length = yield* readUint8Data(cursor); break; case constants.INT16: length = yield* readInt16Data(cursor); break; case constants.INT32: length = yield* readInt32Data(cursor); break; case constants.INT64: { const l = yield* 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'); } if (cursor.offset + length > cursor.size) { yield cursor.offset - cursor.size + length; } const begin = cursor.offset; const end = begin + length; cursor.offset = end; const { data } = cursor; return decode(data, begin, end); } case constants.OBJECT: { let type; let count; let marker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } while (marker === constants.NO_OP); switch (marker) { case constants.TYPE_MARKER: { do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } type = cursor.data[cursor.offset++]!; } while (type === constants.NO_OP); let marker2; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker2 = cursor.data[cursor.offset++]!; } while (marker2 === constants.NO_OP); if (marker2 !== constants.COUNT_MARKER) { throw new Error('Expected count marker'); } } /* fall through */ case constants.COUNT_MARKER: { count = yield* readLength(cursor); break; } default: { // 不是 '$' 或 '#' // 直到 '}' const object: Record<string, unknown> = {}; for (;;) { while (marker === constants.NO_OP) { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } if (marker === constants.OBJECT_END) break; let length: number; switch (marker) { case constants.INT8: if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } length = cursor.view.getInt8(cursor.offset++); break; case constants.UINT8: length = yield* readUint8Data(cursor); break; case constants.INT16: length = yield* readInt16Data(cursor); break; case constants.INT32: length = yield* readInt32Data(cursor); break; case constants.INT64: { const l = yield* 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'); } if (cursor.offset + length > cursor.size) { yield cursor.offset - cursor.size + length; } marker = constants.NO_OP; const begin = cursor.offset; const end = begin + length; cursor.offset = end; const { data } = cursor; const key = decode(data, begin, end); let valueMarker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } valueMarker = cursor.data[cursor.offset++]!; } while (valueMarker === constants.NO_OP); const value = yield* readData(cursor, valueMarker); if (key === '__proto__') { protoAction(cursor, object, value); continue; } if (key === 'constructor') { constructorAction(cursor, object, value); continue; } object[key] = value; } return object; } } return yield* readObjectOptimizedData(cursor, { type, count }); } case constants.ARRAY: { let type; let count; let marker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } while (marker === constants.NO_OP); switch (marker) { case constants.TYPE_MARKER: { do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } type = cursor.data[cursor.offset++]!; } while (type === constants.NO_OP); let marker2; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker2 = cursor.data[cursor.offset++]!; } while (marker2 === constants.NO_OP); if (marker2 !== constants.COUNT_MARKER) { throw new Error('Expected count marker'); } } /* fall through */ case constants.COUNT_MARKER: { count = yield* readLength(cursor); break; } default: { // 不是 '$' 或 '#' const array = []; for (;;) { while (marker === constants.NO_OP) { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } // 直到 ']' if (marker === constants.ARRAY_END) break; array.push(yield* readData(cursor, marker)); marker = constants.NO_OP; } return array; } } switch (type) { case constants.UINT8: { if (cursor.offset + count > cursor.size) { yield cursor.offset - cursor.size + count; } const buf = new Uint8Array( cursor.data.buffer, cursor.data.byteOffset + cursor.offset, count, ).slice(); cursor.offset += count; return buf; } case constants.INT8: { if (cursor.offset + count > cursor.size) { yield cursor.offset - cursor.size + count; } const buf = new Int8Array( cursor.data.buffer, cursor.data.byteOffset + cursor.offset, count, ).slice(); cursor.offset += count; return buf; } case constants.INT16: { if (cursor.offset + count * 2 > cursor.size) { yield cursor.offset - cursor.size + count * 2; } const result = new Int16Array(count); for (let i = 0; i < count; i++) { result[i] = yield* readInt16Data(cursor); } return result; } case constants.INT32: { if (cursor.offset + count * 4 > cursor.size) { yield cursor.offset - cursor.size + count * 4; } const result = new Int32Array(count); for (let i = 0; i < count; i++) { result[i] = yield* readInt32Data(cursor); } return result; } case constants.FLOAT32: { if (cursor.offset + count * 4 > cursor.size) { yield cursor.offset - cursor.size + count * 4; } const result = new Float32Array(count); for (let i = 0; i < count; i++) { result[i] = yield* readFloat32Data(cursor); } return result; } case constants.FLOAT64: { if (cursor.offset + count * 8 > cursor.size) { yield cursor.offset - cursor.size + count * 8; } const result = new Float64Array(count); for (let i = 0; i < count; i++) { result[i] = yield* readFloat64Data(cursor); } return result; } case constants.INT64: { if (cursor.offset + count * 8 > cursor.size) { yield cursor.offset - cursor.size + count * 8; } const result = new BigInt64Array(count); for (let i = 0; i < count; i++) { result[i] = yield* 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 ? yield* read(cursor) : yield* readData(cursor, type); } return array; } case constants.FLOAT64: { if (cursor.offset + 8 > cursor.size) { yield cursor.offset - cursor.size + 8; } const value = cursor.view.getFloat64(cursor.offset); cursor.offset += 8; return value; } case constants.UINT8: { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } return cursor.data[cursor.offset++]!; } case constants.INT16: { if (cursor.offset + 2 > cursor.size) { yield cursor.offset - cursor.size + 2; } const value = cursor.view.getInt16(cursor.offset); cursor.offset += 2; return value; } case constants.FLOAT32: { if (cursor.offset + 4 > cursor.size) { yield cursor.offset - cursor.size + 4; } const value = cursor.view.getFloat32(cursor.offset); cursor.offset += 4; return value; } case constants.CHAR: { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } return fromCharCode(cursor.data[cursor.offset++]!); } case constants.INT32: { if (cursor.offset + 4 > cursor.size) { yield cursor.offset - cursor.size + 4; } const value = cursor.view.getInt32(cursor.offset); cursor.offset += 4; return value; } case constants.INT8: { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } return cursor.view.getInt8(cursor.offset++); } case constants.NULL: return null; case constants.TRUE: return true; case constants.FALSE: return false; case constants.INT64: { if (cursor.offset + 8 > cursor.size) { yield cursor.offset - cursor.size + 8; } const value = cursor.view.getBigInt64(cursor.offset); cursor.offset += 8; if (value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) { return value; } return Number(value); } case constants.HIGH_PRECISION_NUMBER: { const length = yield* readLength(cursor); if (cursor.offset + length > cursor.size) { yield cursor.offset - cursor.size + length; } const begin = cursor.offset; const _buffer = new Uint8Array(cursor.data.buffer, begin, length).slice(); cursor.offset = begin + length; // return _buffer unsupportedType('high precision number'); } } throw new Error(`Unexpected marker '${fromCharCode(marker)}'(${marker})`); } /** readKey */ export function* readKey(cursor: DecodeCursorAe): DecodeFuncAe<string> { let marker: number; do { if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } marker = cursor.data[cursor.offset++]!; } while (marker === constants.NO_OP); let length: number; switch (marker) { case constants.INT8: if (cursor.offset + 1 > cursor.size) { yield cursor.offset - cursor.size + 1; } length = cursor.view.getInt8(cursor.offset++); break; case constants.UINT8: length = yield* readUint8Data(cursor); break; case constants.INT16: length = yield* readInt16Data(cursor); break; case constants.INT32: length = yield* readInt32Data(cursor); break; case constants.INT64: { const l = yield* 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'); } if (cursor.offset + length > cursor.size) { yield cursor.offset - cursor.size + length; } const begin = cursor.offset; const end = begin + length; cursor.offset = end; const { data } = cursor; return decode(data, begin, end); } /** Optimized Format 数据 */ export type OptimizedFormatMarkers = { type?: number; count: number };