+166
-251
| "use strict"; | ||
| /** | ||
| * @typedef Readable | ||
| */ | ||
| const Readable = require("stream").Readable; | ||
| class JceError extends Error {}; | ||
| class JceError extends Error { } | ||
| const BUF0 = Buffer.alloc(0); | ||
| /** | ||
| * @typedef {Object} JceStruct jce data struct | ||
| * K是字段名(String),V是tag值(UInt8) | ||
| * @typedef {{[k: string]: number}} JceStruct jce data struct | ||
| */ | ||
@@ -36,20 +32,19 @@ | ||
| const FLAG_STRUCT_END = Symbol("FLAG_STRUCT_END"); | ||
| let _encoding = "utf8"; | ||
| class Struct extends null { } | ||
| /** | ||
| * @param {Readable} stream | ||
| * @returns {Object} {tag: UInt8, type: UInt8, raw: Buffer} | ||
| */ | ||
| function readHead(stream, return_raw = false) { | ||
| let raw = stream.read(1); | ||
| const head = raw.readUInt8(); | ||
| function readHead(stream) { | ||
| const head = stream.read(1).readUInt8(); | ||
| const type = head & 0xf; | ||
| let tag = (head & 0xf0) >> 4; | ||
| if (tag === 0xf) { | ||
| tag = stream.read(1); | ||
| if (return_raw) | ||
| raw = Buffer.concat([raw, tag]); | ||
| tag = tag.readUInt8(); | ||
| tag = stream.read(1).readUInt8(); | ||
| } | ||
| return {tag, type, raw}; | ||
| return {tag, type}; | ||
| } | ||
@@ -59,57 +54,56 @@ | ||
| * @param {Readable} stream | ||
| * @param {Number} type UInt8 0~13 | ||
| * @returns {any} | ||
| * @param {number} type UInt8 0~13 | ||
| */ | ||
| function readBody(stream, type) { | ||
| var len; | ||
| let len; | ||
| switch(type) { | ||
| 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: | ||
| var value = stream.read(8).readBigInt64BE(); | ||
| if (value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) | ||
| value = parseInt(value); | ||
| return value; | ||
| case TYPE_FLOAT: | ||
| return stream.read(4).readFloatBE(); | ||
| case TYPE_DOUBLE: | ||
| return stream.read(8).readDoubleBE(); | ||
| case TYPE_STRING1: | ||
| len = stream.read(1).readUInt8(); | ||
| return len > 0 ? stream.read(len).toString(_encoding) : ""; | ||
| case TYPE_STRING4: | ||
| len = stream.read(4).readUInt32BE(); | ||
| return len > 0 ? stream.read(len).toString(_encoding) : ""; | ||
| case TYPE_MAP: | ||
| len = readElement(stream).value; | ||
| const map = {}; | ||
| while(len > 0) { | ||
| map[readElement(stream).value.toString(_encoding)] = readElement(stream).value; | ||
| --len; | ||
| } | ||
| return map; | ||
| case TYPE_LIST: | ||
| len = readElement(stream).value; | ||
| const list = []; | ||
| while(len > 0) { | ||
| list.push(readElement(stream).value); | ||
| --len; | ||
| } | ||
| return list; | ||
| case TYPE_STRUCT_BEGIN: | ||
| return Buffer.alloc(0); | ||
| case TYPE_STRUCT_END: | ||
| return Buffer.alloc(0); | ||
| case TYPE_ZERO: | ||
| return 0; | ||
| case TYPE_SIMPLE_LIST: | ||
| readHead(stream); | ||
| len = readElement(stream).value; | ||
| return len > 0 ? stream.read(len) : BUF0; | ||
| default: | ||
| throw new JceError("unknown jce type: " + 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(_encoding) : ""; | ||
| case TYPE_STRING4: | ||
| len = stream.read(4).readUInt32BE(); | ||
| return len > 0 ? stream.read(len).toString(_encoding) : ""; | ||
| 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.toString(_encoding)] = 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); | ||
| } | ||
@@ -120,74 +114,13 @@ } | ||
| * @param {Readable} stream | ||
| * @param {Number} type UInt8 0~13 | ||
| * @returns {Buffer} | ||
| */ | ||
| function skipField(stream, type) { | ||
| const chunk = []; | ||
| var len, l; | ||
| switch (type) { | ||
| case TYPE_INT8: | ||
| chunk.push(stream.read(1)); | ||
| break; | ||
| case TYPE_INT16: | ||
| chunk.push(stream.read(2)); | ||
| break; | ||
| case TYPE_INT32: | ||
| case TYPE_FLOAT: | ||
| chunk.push(stream.read(4)); | ||
| break; | ||
| case TYPE_INT64: | ||
| case TYPE_DOUBLE: | ||
| chunk.push(stream.read(8)); | ||
| break; | ||
| case TYPE_STRING1: | ||
| len = stream.read(1); | ||
| chunk.push(len); | ||
| l = len.readUInt8(); | ||
| chunk.push(l>0?stream.read(l):BUF0); | ||
| break; | ||
| case TYPE_STRING4: | ||
| len = stream.read(4); | ||
| chunk.push(len); | ||
| l = len.readUInt32BE(); | ||
| chunk.push(l>0?stream.read(l):BUF0); | ||
| break; | ||
| case TYPE_LIST: | ||
| case TYPE_MAP: | ||
| case TYPE_STRUCT_BEGIN: | ||
| case TYPE_STRUCT_END: | ||
| case TYPE_ZERO: | ||
| break; | ||
| case TYPE_SIMPLE_LIST: | ||
| chunk.push(stream.read(1)); | ||
| const {type, raw} = readHead(stream, true); | ||
| chunk.push(raw); | ||
| len = readBody(stream, type); | ||
| chunk.push(createBody(type, len)); | ||
| chunk.push(len>0?stream.read(len):BUF0); | ||
| break; | ||
| } | ||
| return Buffer.concat(chunk); | ||
| } | ||
| /** | ||
| * @param {Readable} stream | ||
| * @returns {Buffer} | ||
| */ | ||
| function skipStruct(stream) { | ||
| const chunks = []; | ||
| let nested_struct_num = 0; | ||
| function readStruct(stream) { | ||
| const struct = Object.create(Struct.prototype); | ||
| while(stream.readableLength) { | ||
| const {type, raw} = readHead(stream, true); | ||
| if (type === TYPE_STRUCT_BEGIN) { | ||
| ++nested_struct_num; | ||
| const {tag, value} = readElement(stream); | ||
| if (value === FLAG_STRUCT_END) { | ||
| return struct; | ||
| } else { | ||
| struct[tag] = value; | ||
| } | ||
| if (type === TYPE_STRUCT_END) { | ||
| --nested_struct_num; | ||
| if (nested_struct_num < 0) | ||
| break; | ||
| } | ||
| chunks.push(raw); | ||
| chunks.push(skipField(stream, type)); | ||
| } | ||
| return Buffer.concat(chunks); | ||
| } | ||
@@ -197,12 +130,6 @@ | ||
| * @param {Readable} stream | ||
| * @returns {Object} {tag: UInt8, value: any} | ||
| */ | ||
| function readElement(stream) { | ||
| var value; | ||
| const head = readHead(stream); | ||
| if (head.type === TYPE_STRUCT_BEGIN) { | ||
| value = skipStruct(stream); | ||
| } else { | ||
| value = readBody(stream, head.type); | ||
| } | ||
| const value = readBody(stream, head.type); | ||
| return { | ||
@@ -232,3 +159,3 @@ tag: head.tag, value | ||
| } else { | ||
| throw new JceError("Tag must be less than 256") | ||
| throw new JceError("Tag must be less than 256"); | ||
| } | ||
@@ -245,53 +172,53 @@ } | ||
| switch (type) { | ||
| case TYPE_INT8: | ||
| return Buffer.from([parseInt(value)]); | ||
| case TYPE_INT16: | ||
| body = Buffer.alloc(2); | ||
| body.writeInt16BE(parseInt(value)); | ||
| return body; | ||
| case TYPE_INT32: | ||
| body = Buffer.alloc(4); | ||
| body.writeInt32BE(parseInt(value)); | ||
| return body; | ||
| case TYPE_INT64: | ||
| body = Buffer.alloc(8); | ||
| body.writeBigInt64BE(BigInt(value)); | ||
| return body; | ||
| case TYPE_FLOAT: | ||
| body = Buffer.alloc(4); | ||
| body.writeFloatBE(value); | ||
| return body; | ||
| case TYPE_DOUBLE: | ||
| body = Buffer.alloc(8); | ||
| body.writeDoubleBE(value); | ||
| return body; | ||
| case TYPE_STRING1: | ||
| len = Buffer.from([value.length]); | ||
| return Buffer.concat([len, Buffer.from(value)]); | ||
| case TYPE_STRING4: | ||
| len = Buffer.alloc(4); | ||
| len.writeUInt32BE(value.length); | ||
| return Buffer.concat([len, Buffer.from(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 Buffer.alloc(0); | ||
| case TYPE_SIMPLE_LIST: | ||
| return Buffer.concat([createHead(0, TAG_BYTES), createElement(TAG_LENGTH, value.length), value]); | ||
| case TYPE_INT8: | ||
| return Buffer.from([parseInt(value)]); | ||
| case TYPE_INT16: | ||
| body = Buffer.alloc(2); | ||
| body.writeInt16BE(parseInt(value)); | ||
| return body; | ||
| case TYPE_INT32: | ||
| body = Buffer.alloc(4); | ||
| body.writeInt32BE(parseInt(value)); | ||
| return body; | ||
| case TYPE_INT64: | ||
| body = Buffer.alloc(8); | ||
| body.writeBigInt64BE(BigInt(value)); | ||
| return body; | ||
| case TYPE_FLOAT: | ||
| body = Buffer.alloc(4); | ||
| body.writeFloatBE(value); | ||
| return body; | ||
| case TYPE_DOUBLE: | ||
| body = Buffer.alloc(8); | ||
| body.writeDoubleBE(value); | ||
| return body; | ||
| case TYPE_STRING1: | ||
| len = Buffer.from([value.length]); | ||
| return Buffer.concat([len, Buffer.from(value)]); | ||
| case TYPE_STRING4: | ||
| len = Buffer.alloc(4); | ||
| len.writeUInt32BE(value.length); | ||
| return Buffer.concat([len, Buffer.from(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 Buffer.alloc(0); | ||
| case TYPE_SIMPLE_LIST: | ||
| return Buffer.concat([createHead(0, TAG_BYTES), createElement(TAG_LENGTH, value.length), value]); | ||
| } | ||
@@ -313,35 +240,35 @@ } | ||
| switch (type) { | ||
| case "string": | ||
| value = Buffer.from(value, _encoding); | ||
| type = value.length <= 0xff ? TYPE_STRING1 : TYPE_STRING4; | ||
| break; | ||
| case "object": | ||
| if (value === null) | ||
| throw new JceError("Unsupported type: null"); | ||
| if (value instanceof Buffer || value instanceof Uint8Array || value instanceof ArrayBuffer) | ||
| type = TYPE_SIMPLE_LIST; | ||
| case "string": | ||
| value = Buffer.from(value, _encoding); | ||
| type = value.length <= 0xff ? TYPE_STRING1 : TYPE_STRING4; | ||
| break; | ||
| case "object": | ||
| if (value === null) | ||
| throw new JceError("Unsupported type: null"); | ||
| if (value instanceof Buffer || value instanceof Uint8Array || value instanceof ArrayBuffer) | ||
| 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) || type === "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 | ||
| type = Array.isArray(value) ? TYPE_LIST : TYPE_MAP; | ||
| break; | ||
| case "bigint": | ||
| case "number": | ||
| if (value == 0) | ||
| type = TYPE_ZERO; | ||
| else if (Number.isInteger(value) || type === "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: " + type); | ||
| throw new JceError("Unsupported integer range: " + value); | ||
| } else { | ||
| type = TYPE_DOUBLE; //we don't use float | ||
| } | ||
| break; | ||
| default: | ||
| throw new JceError("Unsupported type: " + type); | ||
| } | ||
@@ -357,5 +284,3 @@ const head = createHead(type, tag); | ||
| * 设置字符串编码 | ||
| * @see https://nodejs.org/dist/latest/docs/api/buffer.html#buffer_buffers_and_character_encodings | ||
| * @param {String} encoding | ||
| * @returns {void} | ||
| * @param {BufferEncoding} encoding | ||
| */ | ||
@@ -368,20 +293,12 @@ function setEncoding(encoding = "utf8") { | ||
| * 调用此函数进行jce解码 | ||
| * 嵌套结构会跳过并返回此段buffer,需要再次decode | ||
| * @param {Buffer} blob | ||
| * @param {JceStruct|undefined} struct undefined时tag作为键返回 | ||
| * @returns {Object} 键值对 | ||
| * @returns {{[k: number]: any}} | ||
| */ | ||
| function decode(blob, struct = undefined) { | ||
| function decode(blob) { | ||
| const stream = Readable.from(blob, {objectMode: false}); | ||
| stream.read(0); | ||
| const result = {}; | ||
| const result = Object.create(null); | ||
| while(stream.readableLength) { | ||
| const {tag, value} = readElement(stream, struct); | ||
| if (struct) { | ||
| const name = Object.keys(struct).find((v)=>struct[v]===tag); | ||
| if (name) | ||
| result[name] = value; | ||
| } else { | ||
| result[tag] = value; | ||
| } | ||
| const {tag, value} = readElement(stream); | ||
| result[tag] = value; | ||
| } | ||
@@ -393,5 +310,4 @@ return result; | ||
| * 调用此函数进行jce编码 | ||
| * @param {Object|Array} object 键值对或数组(值为null或undefined自动跳过此tag) | ||
| * @param {JceStruct|undefined} struct undefined时取object的键为tag | ||
| * @returns {Buffer} | ||
| * @param {{[k: number]: any}|any[]} object 键值对或数组(值为null或undefined自动跳过此tag) | ||
| * @param {JceStruct} struct undefined时取object的键为tag | ||
| */ | ||
@@ -416,3 +332,3 @@ function encode(object, struct = undefined) { | ||
| for (const name of Object.keys(struct)) { | ||
| if (!object.hasOwnProperty(name)) | ||
| if (!Reflect.has(object, name)) | ||
| continue; | ||
@@ -427,7 +343,6 @@ elements.push(createElement(struct[name], object[name])); | ||
| * 嵌套结构数据必须调用此函数创建,暂不支持在struct中直接定义 | ||
| * @param {Object|Array} object | ||
| * @param {JceStruct|undefined} struct | ||
| * @returns {Nested} | ||
| * @param {{[k: number]: any}|any[]} object | ||
| * @param {JceStruct} struct | ||
| */ | ||
| function encodeNested(object, struct) { | ||
| function encodeNested(object, struct = undefined) { | ||
| return new Nested(encode(object, struct)); | ||
@@ -437,3 +352,3 @@ } | ||
| module.exports = { | ||
| setEncoding, decode, encode, encodeNested | ||
| setEncoding, decode, encode, encodeNested, Struct, Nested | ||
| }; |
+1
-1
| { | ||
| "name": "jce", | ||
| "version": "0.1.7", | ||
| "version": "0.2.0", | ||
| "description": "JCE reader and writer for JavaScript", | ||
@@ -5,0 +5,0 @@ "main": "jce.js", |
+1
-1
@@ -47,3 +47,3 @@ # **jce** | ||
| const encoded = jce.encode(object, struct); | ||
| const decoded = jce.decode(encoded, struct); | ||
| const decoded = jce.decode(encoded); | ||
| ``` | ||
@@ -50,0 +50,0 @@ |
+12
-10
@@ -40,2 +40,4 @@ "use strict"; | ||
| var encoded, decoded, encoded_nested, decoded_nested; | ||
| console.time(); | ||
@@ -48,4 +50,4 @@ for (let i = 0; i < 1; ++i) { | ||
| // jce.setEncoding("raw"); | ||
| decoded = jce.decode(encoded, struct); | ||
| decoded_nested = jce.decode(decoded.yhn, struct_nested); | ||
| decoded = jce.decode(encoded); | ||
| // decoded_nested = jce.decode(decoded.yhn, struct_nested); | ||
| } | ||
@@ -58,10 +60,10 @@ console.timeEnd(); | ||
| const encoded_arr = jce.encode([0,1,2,"abc",null,undefined,3.3,{a:1},[666,Buffer.from("qaz")]]) | ||
| const decoded_arr = jce.decode(encoded_arr) | ||
| console.log(encoded_arr) | ||
| console.log(decoded_arr) | ||
| // const encoded_arr = jce.encode([0,1,2,"abc",null,undefined,3.3,{a:1},[666,Buffer.from("qaz")]]) | ||
| // const decoded_arr = jce.decode(encoded_arr) | ||
| // console.log(encoded_arr) | ||
| // console.log(decoded_arr) | ||
| const encoded_map = jce.encode({0:1,3:"abc",5:[1,2,3]}) | ||
| const decoded_map = jce.decode(encoded_map) | ||
| console.log(encoded_map) | ||
| console.log(decoded_map) | ||
| // const encoded_map = jce.encode({0:1,3:"abc",5:[1,2,3]}) | ||
| // const decoded_map = jce.decode(encoded_map) | ||
| // console.log(encoded_map) | ||
| // console.log(decoded_map) |
12498
-19.29%379
-18.49%