oicq
Version:
QQ protocol!
275 lines (274 loc) • 9.03 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodeNested = exports.encode = exports.decode = exports.Nested = exports.Struct = void 0;
const stream_1 = require("stream");
const BUF0 = Buffer.alloc(0);
const TYPE_INT8 = 0;
const TYPE_INT16 = 1;
const TYPE_INT32 = 2;
const TYPE_INT64 = 3;
const TYPE_FLOAT = 4;
const TYPE_DOUBLE = 5;
const TYPE_STRING1 = 6;
const TYPE_STRING4 = 7;
const TYPE_MAP = 8;
const TYPE_LIST = 9;
const TYPE_STRUCT_BEGIN = 10;
const TYPE_STRUCT_END = 11;
const TYPE_ZERO = 12;
const TYPE_SIMPLE_LIST = 13;
const TAG_MAP_K = 0;
const TAG_MAP_V = 1;
const TAG_LIST_E = 0;
const TAG_BYTES = 0;
const TAG_LENGTH = 0;
const TAG_STRUCT_END = 0;
const FLAG_STRUCT_END = Symbol("FLAG_STRUCT_END");
class JceError extends Error {
constructor() {
super(...arguments);
this.name = "JceError";
}
}
//------------------------------------------------------------------ decode
class Struct extends null {
}
exports.Struct = Struct;
function readHead(readable) {
const head = readable.read(1).readUInt8();
const type = head & 0xf;
let tag = (head & 0xf0) >> 4;
if (tag === 0xf) {
tag = readable.read(1).readUInt8();
}
return { tag, type };
}
function readBody(stream, type) {
let len;
switch (type) {
case TYPE_ZERO:
return 0;
case TYPE_INT8:
return stream.read(1).readInt8();
case TYPE_INT16:
return stream.read(2).readInt16BE();
case TYPE_INT32:
return stream.read(4).readInt32BE();
case TYPE_INT64:
let value = stream.read(8).readBigInt64BE();
if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER)
value = Number(value);
return value;
case TYPE_STRING1:
len = stream.read(1).readUInt8();
return len > 0 ? stream.read(len).toString() : "";
case TYPE_STRING4:
len = stream.read(4).readUInt32BE();
return len > 0 ? stream.read(len).toString() : "";
case TYPE_SIMPLE_LIST:
readHead(stream);
len = readElement(stream).value;
return len > 0 ? stream.read(len) : BUF0;
case TYPE_LIST:
len = readElement(stream).value;
const list = [];
while (len > 0) {
list.push(readElement(stream).value);
--len;
}
return list;
case TYPE_MAP:
len = readElement(stream).value;
const map = Object.create(null);
while (len > 0) {
map[readElement(stream).value] = readElement(stream).value;
--len;
}
return map;
case TYPE_STRUCT_BEGIN:
return readStruct(stream);
case TYPE_STRUCT_END:
return FLAG_STRUCT_END;
case TYPE_FLOAT:
return stream.read(4).readFloatBE();
case TYPE_DOUBLE:
return stream.read(8).readDoubleBE();
default:
throw new JceError("unknown jce type: " + type);
}
}
function readStruct(readable) {
const struct = Object.create(Struct.prototype);
while (readable.readableLength) {
const { tag, value } = readElement(readable);
if (value === FLAG_STRUCT_END) {
return struct;
}
else {
struct[tag] = value;
}
}
}
function readElement(readable) {
const head = readHead(readable);
const value = readBody(readable, head.type);
return {
tag: head.tag, value
};
}
//------------------------------------------------------------------ encode
class Nested {
constructor(data) {
this.data = data;
}
}
exports.Nested = Nested;
function createHead(type, tag) {
if (tag < 15) {
return Buffer.from([(tag << 4) | type]);
}
else if (tag < 256) {
return Buffer.from([0xf0 | type, tag]);
}
else {
throw new JceError("Tag must be less than 256, received: " + tag);
}
}
function createBody(type, value) {
let body, len;
switch (type) {
case TYPE_INT8:
return Buffer.from([Number(value)]);
case TYPE_INT16:
body = Buffer.allocUnsafe(2);
body.writeInt16BE(Number(value));
return body;
case TYPE_INT32:
body = Buffer.allocUnsafe(4);
body.writeInt32BE(Number(value));
return body;
case TYPE_INT64:
body = Buffer.allocUnsafe(8);
body.writeBigInt64BE(BigInt(value));
return body;
case TYPE_FLOAT:
body = Buffer.allocUnsafe(4);
body.writeFloatBE(value);
return body;
case TYPE_DOUBLE:
body = Buffer.allocUnsafe(8);
body.writeDoubleBE(value);
return body;
case TYPE_STRING1:
len = Buffer.from([value.length]);
return Buffer.concat([len, value]);
case TYPE_STRING4:
len = Buffer.allocUnsafe(4);
len.writeUInt32BE(value.length);
return Buffer.concat([len, value]);
case TYPE_MAP:
body = [];
let n = 0;
for (let k of Object.keys(value)) {
++n;
body.push(createElement(TAG_MAP_K, k));
body.push(createElement(TAG_MAP_V, value[k]));
}
body.unshift(createElement(TAG_LENGTH, n));
return Buffer.concat(body);
case TYPE_LIST:
body = [createElement(TAG_LENGTH, value.length)];
for (let i = 0; i < value.length; ++i) {
body.push(createElement(TAG_LIST_E, value[i]));
}
return Buffer.concat(body);
// case TYPE_STRUCT_BEGIN:
// case TYPE_STRUCT_END:
case TYPE_ZERO:
return BUF0;
case TYPE_SIMPLE_LIST:
return Buffer.concat([createHead(0, TAG_BYTES), createElement(TAG_LENGTH, value.length), value]);
}
throw new JceError("Type must be 0 ~ 13, received: " + type);
}
function createElement(tag, value) {
if (value instanceof Nested) {
const begin = createHead(TYPE_STRUCT_BEGIN, tag);
const end = createHead(TYPE_STRUCT_END, TAG_STRUCT_END);
return Buffer.concat([begin, value.data, end]);
}
let type;
switch (typeof value) {
case "string":
value = Buffer.from(value);
type = value.length <= 0xff ? TYPE_STRING1 : TYPE_STRING4;
break;
case "object":
if (value instanceof Uint8Array)
type = TYPE_SIMPLE_LIST;
else
type = Array.isArray(value) ? TYPE_LIST : TYPE_MAP;
break;
case "bigint":
case "number":
if (value == 0)
type = TYPE_ZERO;
else if (Number.isInteger(value) || typeof value === "bigint") {
if (value >= -0x80 && value <= 0x7f)
type = TYPE_INT8;
else if (value >= -0x8000 && value <= 0x7fff)
type = TYPE_INT16;
else if (value >= -0x80000000 && value <= 0x7fffffff)
type = TYPE_INT32;
else if (value >= -0x8000000000000000n && value <= 0x7fffffffffffffffn)
type = TYPE_INT64;
else
throw new JceError("Unsupported integer range: " + value);
}
else {
type = TYPE_DOUBLE; //we don't use float
}
break;
default:
throw new JceError("Unsupported type: " + typeof value);
}
const head = createHead(type, tag);
const body = createBody(type, value);
return Buffer.concat([head, body]);
}
//--------------------------------------------------------------------
function decode(encoded) {
const readable = stream_1.Readable.from(encoded, { objectMode: false });
readable.read(0);
const decoded = Object.create(null);
while (readable.readableLength) {
const { tag, value } = readElement(readable);
decoded[tag] = value;
}
return decoded;
}
exports.decode = decode;
function encode(obj) {
const elements = [];
if (Array.isArray(obj)) {
for (let tag = 0; tag < obj.length; ++tag) {
if (obj[tag] === null || obj[tag] === undefined)
continue;
elements.push(createElement(tag, obj[tag]));
}
}
else {
for (const tag of Object.keys(obj).map(Number)) {
if (obj[tag] === null || obj[tag] === undefined)
continue;
elements.push(createElement(tag, obj[tag]));
}
}
return Buffer.concat(elements);
}
exports.encode = encode;
/** 嵌套结构数据必须调用此函数创建 */
function encodeNested(obj) {
return new Nested(encode(obj));
}
exports.encodeNested = encodeNested;