libs5/src/serialization/unpack.ts

195 lines
5.8 KiB
TypeScript

export default class Unpacker {
private _list: Buffer;
private _offset: number = 0;
private _d: DataView;
constructor(list: Buffer) {
this._list = list;
this._d = new DataView(list.buffer, list.byteOffset);
}
public static fromPacked(data: Uint8Array) {
return new Unpacker(Buffer.from(data));
}
public unpackBool(): boolean | null {
const b = this._d.getUint8(this._offset++);
if (b === 0xc2) return false;
if (b === 0xc3) return true;
if (b === 0xc0) return null;
throw this._formatException("bool", b);
}
public unpackInt(): number | null {
const b = this._d.getUint8(this._offset++);
if (b <= 0x7f || (b >= 0xe0 && b <= 0xff)) {
return b;
} else if (b === 0xcc) {
return this._d.getUint8(this._offset++);
} else if (b === 0xcd) {
this._offset += 2;
return this._d.getUint16(this._offset - 2);
} else if (b === 0xce) {
this._offset += 4;
return this._d.getUint32(this._offset - 4);
} else if (b === 0xcf) {
this._offset += 8;
const high = this._d.getUint32(this._offset - 8);
const low = this._d.getUint32(this._offset - 4);
return high * 0x100000000 + low;
} else if (b === 0xd0) {
return this._d.getInt8(this._offset++);
} else if (b === 0xd1) {
this._offset += 2;
return this._d.getInt16(this._offset - 2);
} else if (b === 0xd2) {
this._offset += 4;
return this._d.getInt32(this._offset - 4);
} else if (b === 0xd3) {
this._offset += 8;
const high = this._d.getInt32(this._offset - 8);
const low = this._d.getUint32(this._offset - 4);
return high * 0x100000000 + low;
} else if (b === 0xc0) {
return null;
} else {
throw this._formatException("integer", b);
}
}
public unpackDouble(): number | null {
const b = this._d.getUint8(this._offset++);
if (b === 0xca) {
this._offset += 4;
return this._d.getFloat32(this._offset - 4);
} else if (b === 0xcb) {
this._offset += 8;
return this._d.getFloat64(this._offset - 8);
} else if (b === 0xc0) {
return null;
} else {
throw this._formatException("double", b);
}
}
public unpackString(): string | null {
const b = this._d.getUint8(this._offset++);
let len: number;
if ((b & 0xe0) === 0xa0) {
len = b & 0x1f;
} else if (b === 0xd9) {
len = this._d.getUint8(this._offset++);
} else if (b === 0xda) {
this._offset += 2;
len = this._d.getUint16(this._offset - 2);
} else if (b === 0xdb) {
this._offset += 4;
len = this._d.getUint32(this._offset - 4);
} else if (b === 0xc0) {
return null;
} else {
throw this._formatException("String", b);
}
const str = this._list.toString("utf-8", this._offset, this._offset + len);
this._offset += len;
return str;
}
public unpackBinary(): Buffer {
const b = this._d.getUint8(this._offset++);
let len: number;
if (b === 0xc4) {
len = this._d.getUint8(this._offset++);
} else if (b === 0xc5) {
this._offset += 2;
len = this._d.getUint16(this._offset - 2);
} else if (b === 0xc6) {
this._offset += 4;
len = this._d.getUint32(this._offset - 4);
} else if (b === 0xc0) {
len = 0;
} else {
throw this._formatException("Binary", b);
}
const data = this._list.slice(this._offset, this._offset + len);
this._offset += len;
return data;
}
public unpackList(): any[] {
const length = this.unpackListLength();
return Array.from({ length }, () => this._unpack());
}
public unpackMap(): { [key: string]: any } {
const length = this.unpackMapLength();
const obj: { [key: string]: any } = {};
for (let i = 0; i < length; i++) {
const key = this._unpack();
obj[key as string] = this._unpack();
}
return obj;
}
public unpackListLength(): number {
const b = this._d.getUint8(this._offset++);
if ((b & 0xf0) === 0x90) {
return b & 0xf;
} else if (b === 0xdc) {
this._offset += 2;
return this._d.getUint16(this._offset - 2);
} else if (b === 0xdd) {
this._offset += 4;
return this._d.getUint32(this._offset - 4);
} else if (b === 0xc0) {
return 0;
} else {
throw this._formatException("List length", b);
}
}
public unpackMapLength(): number {
const b = this._d.getUint8(this._offset++);
if ((b & 0xf0) === 0x80) {
return b & 0xf;
} else if (b === 0xde) {
this._offset += 2;
return this._d.getUint16(this._offset - 2);
} else if (b === 0xdf) {
this._offset += 4;
return this._d.getUint32(this._offset - 4);
} else if (b === 0xc0) {
return 0;
} else {
throw this._formatException("Map length", b);
}
}
private _unpack(): any {
const b = this._d.getUint8(this._offset);
if (b <= 0x7f || (b >= 0xe0 && b <= 0xff)) {
return this.unpackInt();
} else if (b === 0xc2 || b === 0xc3 || b === 0xc0) {
return this.unpackBool();
} else if (b === 0xca || b === 0xcb) {
return this.unpackDouble();
} else if ((b & 0xe0) === 0xa0 || b === 0xd9 || b === 0xda || b === 0xdb) {
return this.unpackString();
} else if (b === 0xc4 || b === 0xc5 || b === 0xc6) {
return this.unpackBinary();
} else if ((b & 0xf0) === 0x90 || b === 0xdc || b === 0xdd) {
return this.unpackList();
} else if ((b & 0xf0) === 0x80 || b === 0xde || b === 0xdf) {
return this.unpackMap();
} else {
throw this._formatException("Unknown", b);
}
}
// Implement other methods here, following the same pattern as unpackBool and unpackInt
private _formatException(type: string, b: number) {
return new Error(
`Try to unpack ${type} value, but it's not a ${type}, byte = ${b}`,
);
}
}