From f2bd5e4795afb1f089689b20941dfe122b701d13 Mon Sep 17 00:00:00 2001 From: Moon Man Date: Sun, 28 Jan 2024 08:23:50 -0500 Subject: [PATCH] maybe fix length issue --- dist/bert.d.ts | 2 +- dist/bert.js | 18 +- src/bert.ts | 929 +++++++++++++++++++++++++------------------------ 3 files changed, 476 insertions(+), 473 deletions(-) diff --git a/dist/bert.d.ts b/dist/bert.d.ts index bd49274..5c15cbc 100644 --- a/dist/bert.d.ts +++ b/dist/bert.d.ts @@ -134,7 +134,7 @@ export declare class Bert { readonly [Symbol.unscopables]?: boolean | undefined; }; }; - encode: (obj: any, noCopy?: boolean) => Buffer; + encode: (obj: any, copy?: boolean) => Buffer; decode: (buffer: Buffer) => any; encode_string: (obj: string, buffer: Buffer) => Buffer; encode_boolean: (obj: boolean, buffer: Buffer) => any; diff --git a/dist/bert.js b/dist/bert.js index 1e4eb22..0f8a013 100644 --- a/dist/bert.js +++ b/dist/bert.js @@ -38,18 +38,18 @@ export class Bert { toAtom = toAtom; toTuple = toTuple; #encode = (obj, buffer) => this[`encode_${typeof obj}`](obj, buffer); - encode = (obj, noCopy = false) => { + encode = (obj, copy = true) => { const tailBuffer = this.#encode(obj, Buffer.from(this.outputBuffer, 1)); if (tailBuffer.length === 0) { throw new Error("Bert encode a too big term, encoding buffer overflow"); } - else if (noCopy) { - return Buffer.from(this.outputBuffer, 0, this.outputBuffer.length - tailBuffer.length); + else if (copy) { + const ret = Buffer.alloc(tailBuffer.length); + this.outputBuffer.copy(ret, 0, 0, ret.length); + return ret; } else { - const res = Buffer.alloc(this.outputBuffer.length - tailBuffer.length); - this.outputBuffer.copy(res, 0, 0, res.length); - return res; + return Buffer.from(this.outputBuffer, 0, tailBuffer.length); } }; #decode = (buffer) => { @@ -106,7 +106,7 @@ export class Bert { } else { buffer[0] = Types.STRING; - buffer.writeUInt16BE(obj.length, 1); + buffer.writeUInt16BE(Buffer.byteLength(obj, "utf-8"), 1); const len = buffer.write(obj, 3); return Buffer.from(buffer, 0, 3 + len); } @@ -201,8 +201,8 @@ export class Bert { encode_binary = (obj, buffer) => { buffer[0] = Types.BINARY; buffer.writeUInt32BE(obj.length, 1); - obj.copy(buffer, 5); - return Buffer.from(buffer, 0, 5 + obj.length); + const len = obj.copy(buffer, 5); + return Buffer.from(buffer, 0, 5 + len); }; encode_undefined = (_obj, buffer) => { return this.#encode(null, buffer); diff --git a/src/bert.ts b/src/bert.ts index d6dcfab..dfd511b 100644 --- a/src/bert.ts +++ b/src/bert.ts @@ -1,484 +1,487 @@ export const Types = { - BERT_START: 131, - SMALL_ATOM: 115, - ATOM: 100, - BINARY: 109, - SMALL_INTEGER: 97, - INTEGER: 98, - SMALL_BIG: 110, - LARGE_BIG: 111, - FLOAT: 99, - STRING: 107, - LIST: 108, - SMALL_TUPLE: 104, - LARGE_TUPLE: 105, // TODO: Implement. - MAP: 116, - NIL: 106, - NEW_FLOAT: 70, - ZERO: 0, + BERT_START: 131, + SMALL_ATOM: 115, + ATOM: 100, + BINARY: 109, + SMALL_INTEGER: 97, + INTEGER: 98, + SMALL_BIG: 110, + LARGE_BIG: 111, + FLOAT: 99, + STRING: 107, + LIST: 108, + SMALL_TUPLE: 104, + LARGE_TUPLE: 105, // TODO: Implement. + MAP: 116, + NIL: 106, + NEW_FLOAT: 70, + ZERO: 0, }; export const Lang = { - ELIXIR: 0, - ERLANG: 1, + ELIXIR: 0, + ERLANG: 1, }; export class Bert { - private allBinariesAsString; - private mapKeyAsAtom; - private decodeUndefinedValues; - private convention; - private outputBuffer; + private allBinariesAsString; + private mapKeyAsAtom; + private decodeUndefinedValues; + private convention; + private outputBuffer; - constructor( - allBinariesAsString = false, - mapKeyAsAtom = true, - decodeUndefinedValues = true, - convention = Lang.ELIXIR, - ) { - this.allBinariesAsString = allBinariesAsString; - this.mapKeyAsAtom = mapKeyAsAtom; - this.decodeUndefinedValues = decodeUndefinedValues; - this.convention = convention; - this.outputBuffer = Buffer.alloc(10000000); - this.outputBuffer[0] = Types.BERT_START; - } - - toAtom = toAtom; - toTuple = toTuple; - - #encode = (obj: any, buffer: Buffer) => - (this as any)[`encode_${typeof obj}`](obj, buffer); - - encode = (obj: any, noCopy = false) => { - const tailBuffer = this.#encode(obj, Buffer.from(this.outputBuffer, 1)); - if (tailBuffer.length === 0) { - throw new Error("Bert encode a too big term, encoding buffer overflow"); - } else if (noCopy) { - return Buffer.from( - this.outputBuffer, - 0, - this.outputBuffer.length - tailBuffer.length, - ); - } else { - const res = Buffer.alloc(this.outputBuffer.length - tailBuffer.length); - this.outputBuffer.copy(res, 0, 0, res.length); - return res; - } - }; - - #decode = (buffer: Buffer): any => { - const t = buffer[0]; - buffer = Buffer.from(buffer, 1); - - switch (t) { - case Types.SMALL_ATOM: - return this.decode_atom(buffer, 1); - case Types.ATOM: - return this.decode_atom(buffer, 2); - case Types.BINARY: - return this.decode_binary(buffer); - case Types.SMALL_INTEGER: - return this.decode_integer(buffer, 1, true); - case Types.INTEGER: - return this.decode_integer(buffer, 4); - case Types.SMALL_BIG: - return this.decode_big(buffer, 1); - case Types.LARGE_BIG: - return this.decode_big(buffer, 4); - case Types.FLOAT: - return this.decode_float(buffer); - case Types.NEW_FLOAT: - return this.decode_new_float(buffer); - case Types.STRING: - return this.decode_string(buffer); - case Types.LIST: - return this.decode_list(buffer); - case Types.SMALL_TUPLE: - return this.decode_tuple(buffer, 1); - // case Types.LARGE_TUPLE: - // return this.decode_large_tuple(buffer, 4) - case Types.NIL: - return this.decode_nil(buffer); - case Types.MAP: - return this.decode_map(buffer); - default: - throw new Error(`Unexpected BERT type: ${t}`); - } - }; - - decode = (buffer: Buffer) => { - if (buffer[0] !== Types.BERT_START) { - throw new Error("Not a valid BERT"); - } - - const obj = this.#decode(Buffer.from(buffer, 1)); - - if (obj.rest.length !== 0) { - throw new Error("Invalid BERT"); - } - - return obj.value; - }; - - encode_string = (obj: string, buffer: Buffer) => { - if (this.convention === Lang.ELIXIR) { - return this.encode_binary(Buffer.from(obj), buffer); - } - else { - buffer[0] = Types.STRING; - buffer.writeUInt16BE(obj.length, 1); - const len = buffer.write(obj, 3); - return Buffer.from(buffer, 0, 3 + len); - } - }; - - encode_boolean = (obj: boolean, buffer: Buffer) => { - if (obj) { - return this.#encode(this.toAtom("true"), buffer); - } else { - return this.#encode(this.toAtom("false"), buffer); - } - }; - - encode_number = (obj: number, buffer: Buffer) => { - const isInteger = obj % 1 === 0; - - // Handle floats... - if (!isInteger) { - return this.encode_float(obj, buffer); - } - - // Small int... - if (isInteger && obj >= 0 && obj < 256) { - buffer[0] = Types.SMALL_INTEGER; - buffer.writeUInt8(obj, 1); - return Buffer.from(buffer, 0, 2); - } - - // 4 byte int... - if (isInteger && obj >= -134217728 && obj <= 134217727) { - buffer[0] = Types.INTEGER; - buffer.writeInt32BE(obj, 1); - return Buffer.from(buffer, 0, 5); - } - - // Bignum... - const numBuffer = Buffer.alloc(buffer.length); - if (obj < 0) { - obj *= -1; - numBuffer[0] = 1; - } else { - numBuffer[0] = 0; - } - - let offset = 1; - while (obj !== 0) { - numBuffer[offset] = obj % 256; - obj = Math.floor(obj / 256); - offset++; - } - - if (offset < 256) { - buffer[0] = Types.SMALL_BIG; - buffer.writeUInt8(offset - 1, 1); - numBuffer.copy(buffer, 2, 0, offset); - return Buffer.from(buffer, 0, 2 + offset); - } else { - buffer[0] = Types.LARGE_BIG; - buffer.writeUInt32BE(offset - 1, 1); - numBuffer.copy(buffer, 5, 0, offset); - return Buffer.from(buffer, 0, 5 + offset); - } - }; - - encode_float = (obj: number, buffer: Buffer) => { - // float... - buffer[0] = Types.NEW_FLOAT; - buffer.writeDoubleBE(obj, 1); - return Buffer.from(buffer, 0, 9); - }; - - encode_object = (obj: any, buffer: Buffer) => { - // Check if it's an atom, binary, or tuple... - if (obj === null) { - const undefinedAtom = - this.convention === Lang.ELIXIR ? "nil" : "undefined"; - return this.#encode(this.toAtom(undefinedAtom), buffer); - } - - if (Buffer.isBuffer(obj)) { - return this.encode_binary(obj, buffer); - } - if (Array.isArray(obj)) { - return this.encode_array(obj, buffer); - } - if (obj.type === "Atom") { - return this.encode_atom(obj, buffer); - } - if (obj.type === "Tuple") { - return this.encode_tuple(obj, buffer); - } - // Treat the object as an associative array... - return this.encode_map(obj, buffer); - }; - - encode_atom = (obj: any, buffer: Buffer) => { - buffer[0] = Types.ATOM; - buffer.writeUInt16BE(obj.value.length, 1); - const len = buffer.write(obj.value, 3); - return Buffer.from(buffer, 0, 3 + len); - }; - - encode_binary = (obj: Buffer, buffer: Buffer) => { - buffer[0] = Types.BINARY; - buffer.writeUInt32BE(obj.length, 1); - obj.copy(buffer, 5); - return Buffer.from(buffer, 0, 5 + obj.length); - }; - - encode_undefined = (_obj: Buffer, buffer: Buffer) => { - return this.#encode(null, buffer); - }; - - encode_tuple = (obj: Buffer, buffer: Buffer) => { - if (obj.length < 256) { - buffer[0] = Types.SMALL_TUPLE; - buffer.writeUInt8(obj.length, 1); - buffer = Buffer.from(buffer, 0, 2); - } else { - buffer[0] = Types.LARGE_TUPLE; - buffer.writeUInt32BE(obj.length, 1); - buffer = Buffer.from(buffer, 0, 5); - } - - for (let i = 0; i < obj.length; ++i) { - buffer = this.#encode(obj[i], buffer); - } - - return buffer; - }; - - encode_array = (obj: any[], buffer: Buffer) => { - if (obj.length === 0) { - buffer[0] = Types.NIL; - return Buffer.from(buffer, 0, 1); - } - - buffer[0] = Types.LIST; - buffer.writeUInt32BE(obj.length, 1); - buffer = Buffer.from(buffer, 0, 5); - - for (let i = 0; i < obj.length; ++i) { - buffer = this.#encode(obj[i], buffer); - } - buffer[0] = Types.NIL; - return Buffer.from(buffer, 0, 1); - }; - - encode_map = (obj: Record, buffer: Buffer) => { - const keys = Object.keys(obj); - buffer[0] = Types.MAP; - buffer.writeUInt32BE(keys.length, 1); - buffer = Buffer.from(buffer, 0, 5); - - for (let i = 0; i < keys.length; ++i) { - const key = this.mapKeyAsAtom ? this.toAtom(keys[i]) : keys[i]; - buffer = this.#encode(key, buffer); - buffer = this.#encode(obj[keys[i]], buffer); - } - return buffer; - }; - - decode_atom = (buffer: Buffer, count: 1 | 2 | 4) => { - const size = this.bytesToInt(buffer, count, true); - buffer = Buffer.from(buffer, count); - let value: any = buffer.toString("utf8", 0, size); - if (value === "true") { - value = true; - } else if (value === "false") { - value = false; - } else if ( - this.decodeUndefinedValues && - this.convention === Lang.ELIXIR && - value === "nil" + constructor( + allBinariesAsString = false, + mapKeyAsAtom = true, + decodeUndefinedValues = true, + convention = Lang.ELIXIR, ) { - value = null; - } else if ( - this.decodeUndefinedValues && - this.convention === Lang.ERLANG && - value === "undefined" - ) { - value = null; - } else { - value = this.toAtom(value); - } - return { - value, - rest: Buffer.from(buffer, size), - }; - }; - - decode_binary = (buffer: Buffer) => { - const size = this.bytesToInt(buffer, 4, true); - buffer = Buffer.from(buffer, 4); - const bin = Buffer.alloc(size); - buffer.copy(bin, 0, 0, size); - return { - value: - this.convention === Lang.ELIXIR && this.allBinariesAsString - ? bin.toString() - : bin, - rest: Buffer.from(buffer, size), - }; - }; - - decode_integer = (buffer: Buffer, count: 1 | 2 | 4, unsigned = false) => { - return { - value: this.bytesToInt(buffer, count, unsigned), - rest: Buffer.from(buffer, count), - }; - }; - - decode_big = (buffer: Buffer, count: 1 | 2 | 4) => { - const size = this.bytesToInt(buffer, count, false); - buffer = Buffer.from(buffer, count); - - let num = 0; - const isNegative = buffer[0] === 1; - - buffer = Buffer.from(buffer, 1); - - for (let i = size - 1; i >= 0; --i) { - const n = buffer[i]; - if (num === 0) { - num = n; - } else { - num = num * 256 + n; - } - } - if (isNegative) { - num = num * -1; + this.allBinariesAsString = allBinariesAsString; + this.mapKeyAsAtom = mapKeyAsAtom; + this.decodeUndefinedValues = decodeUndefinedValues; + this.convention = convention; + this.outputBuffer = Buffer.alloc(10000000); + this.outputBuffer[0] = Types.BERT_START; } - return { - value: num, - rest: Buffer.from(buffer, size), + toAtom = toAtom; + toTuple = toTuple; + + #encode = (obj: any, buffer: Buffer) => + (this as any)[`encode_${typeof obj}`](obj, buffer); + + encode = (obj: any, copy = true) => { + const tailBuffer = this.#encode(obj, Buffer.from(this.outputBuffer, 1)); + + if (tailBuffer.length === 0) { + throw new Error("Bert encode a too big term, encoding buffer overflow"); + } + else if (copy) { + const ret = Buffer.alloc(tailBuffer.length); + this.outputBuffer.copy(ret, 0, 0, ret.length); + return ret; + } + else { + return Buffer.from( + this.outputBuffer, + 0, + tailBuffer.length, + ); + } }; - }; - decode_float = (buffer: Buffer) => { - const size = 31; + #decode = (buffer: Buffer): any => { + const t = buffer[0]; + buffer = Buffer.from(buffer, 1); - return { - value: parseFloat(buffer.toString("utf8", 0, size)), - rest: Buffer.from(buffer, size), + switch (t) { + case Types.SMALL_ATOM: + return this.decode_atom(buffer, 1); + case Types.ATOM: + return this.decode_atom(buffer, 2); + case Types.BINARY: + return this.decode_binary(buffer); + case Types.SMALL_INTEGER: + return this.decode_integer(buffer, 1, true); + case Types.INTEGER: + return this.decode_integer(buffer, 4); + case Types.SMALL_BIG: + return this.decode_big(buffer, 1); + case Types.LARGE_BIG: + return this.decode_big(buffer, 4); + case Types.FLOAT: + return this.decode_float(buffer); + case Types.NEW_FLOAT: + return this.decode_new_float(buffer); + case Types.STRING: + return this.decode_string(buffer); + case Types.LIST: + return this.decode_list(buffer); + case Types.SMALL_TUPLE: + return this.decode_tuple(buffer, 1); + // case Types.LARGE_TUPLE: + // return this.decode_large_tuple(buffer, 4) + case Types.NIL: + return this.decode_nil(buffer); + case Types.MAP: + return this.decode_map(buffer); + default: + throw new Error(`Unexpected BERT type: ${t}`); + } }; - }; - decode_new_float = (buffer: Buffer) => { - return { - value: buffer.readDoubleBE(0), - rest: Buffer.from(buffer, 8), + decode = (buffer: Buffer) => { + if (buffer[0] !== Types.BERT_START) { + throw new Error("Not a valid BERT"); + } + + const obj = this.#decode(Buffer.from(buffer, 1)); + + if (obj.rest.length !== 0) { + throw new Error("Invalid BERT"); + } + + return obj.value; }; - }; - decode_string = (buffer: Buffer) => { - const size = this.bytesToInt(buffer, 2, true); - - buffer = Buffer.from(buffer, 2); - - return { - value: buffer.toString("utf8", 0, size), - rest: Buffer.from(buffer, size), + encode_string = (obj: string, buffer: Buffer) => { + if (this.convention === Lang.ELIXIR) { + return this.encode_binary(Buffer.from(obj), buffer); + } + else { + buffer[0] = Types.STRING; + buffer.writeUInt16BE(Buffer.byteLength(obj, "utf-8"), 1); + const len = buffer.write(obj, 3); + return Buffer.from(buffer, 0, 3 + len); + } }; - }; - decode_list = (buffer: Buffer) => { - const arr = []; - const size = this.bytesToInt(buffer, 4, true); - buffer = Buffer.from(buffer, 4); - - for (let i = 0; i < size; ++i) { - const el = this.#decode(buffer); - arr.push(el.value); - buffer = el.rest; - } - - const lastChar = buffer[0]; - - if (lastChar !== Types.NIL) { - throw new Error("List does not end with NIL"); - } - - buffer = Buffer.from(buffer, 1); - - return { - value: arr, - rest: buffer, + encode_boolean = (obj: boolean, buffer: Buffer) => { + if (obj) { + return this.#encode(this.toAtom("true"), buffer); + } else { + return this.#encode(this.toAtom("false"), buffer); + } }; - }; - decode_map = (buffer: Buffer) => { - const map: Record = {}; - const size = this.bytesToInt(buffer, 4, true); + encode_number = (obj: number, buffer: Buffer) => { + const isInteger = obj % 1 === 0; - buffer = Buffer.from(buffer, 4); + // Handle floats... + if (!isInteger) { + return this.encode_float(obj, buffer); + } - for (let i = 0; i < size; ++i) { - let el = this.#decode(buffer); - const key = el.value; - el = this.#decode(el.rest); - const value = el.value; - map[key] = value; - buffer = el.rest; - } - return { - value: map, - rest: buffer, + // Small int... + if (isInteger && obj >= 0 && obj < 256) { + buffer[0] = Types.SMALL_INTEGER; + buffer.writeUInt8(obj, 1); + return Buffer.from(buffer, 0, 2); + } + + // 4 byte int... + if (isInteger && obj >= -134217728 && obj <= 134217727) { + buffer[0] = Types.INTEGER; + buffer.writeInt32BE(obj, 1); + return Buffer.from(buffer, 0, 5); + } + + // Bignum... + const numBuffer = Buffer.alloc(buffer.length); + if (obj < 0) { + obj *= -1; + numBuffer[0] = 1; + } else { + numBuffer[0] = 0; + } + + let offset = 1; + while (obj !== 0) { + numBuffer[offset] = obj % 256; + obj = Math.floor(obj / 256); + offset++; + } + + if (offset < 256) { + buffer[0] = Types.SMALL_BIG; + buffer.writeUInt8(offset - 1, 1); + numBuffer.copy(buffer, 2, 0, offset); + return Buffer.from(buffer, 0, 2 + offset); + } else { + buffer[0] = Types.LARGE_BIG; + buffer.writeUInt32BE(offset - 1, 1); + numBuffer.copy(buffer, 5, 0, offset); + return Buffer.from(buffer, 0, 5 + offset); + } }; - }; - decode_tuple = (buffer: Buffer, count: 1 | 2 | 4) => { - const arr = []; - const size = this.bytesToInt(buffer, count, true); - buffer = Buffer.from(buffer, count); - for (let i = 0; i < size; ++i) { - const el = this.#decode(buffer); - arr.push(el.value); - buffer = el.rest; - } - return { - value: this.toTuple(arr), - rest: buffer, + encode_float = (obj: number, buffer: Buffer) => { + // float... + buffer[0] = Types.NEW_FLOAT; + buffer.writeDoubleBE(obj, 1); + return Buffer.from(buffer, 0, 9); }; - }; - decode_nil = (buffer: Buffer) => { - // nil is an empty list - return { - value: [], - rest: buffer, + encode_object = (obj: any, buffer: Buffer) => { + // Check if it's an atom, binary, or tuple... + if (obj === null) { + const undefinedAtom = + this.convention === Lang.ELIXIR ? "nil" : "undefined"; + return this.#encode(this.toAtom(undefinedAtom), buffer); + } + + if (Buffer.isBuffer(obj)) { + return this.encode_binary(obj, buffer); + } + if (Array.isArray(obj)) { + return this.encode_array(obj, buffer); + } + if (obj.type === "Atom") { + return this.encode_atom(obj, buffer); + } + if (obj.type === "Tuple") { + return this.encode_tuple(obj, buffer); + } + // Treat the object as an associative array... + return this.encode_map(obj, buffer); }; - }; - bytesToInt = (buffer: Buffer, length: 1 | 2 | 4, unsigned: boolean) => { - switch (length) { - case 1: - return unsigned ? buffer.readUInt8(0) : buffer.readInt8(0); - case 2: - return unsigned ? buffer.readUInt16BE(0) : buffer.readInt16BE(0); - case 4: - return unsigned ? buffer.readUInt32BE(0) : buffer.readInt32BE(0); - } - }; + encode_atom = (obj: any, buffer: Buffer) => { + buffer[0] = Types.ATOM; + buffer.writeUInt16BE(obj.value.length, 1); + const len = buffer.write(obj.value, 3); + return Buffer.from(buffer, 0, 3 + len); + }; - binary_to_list = (str: string) => { - const ret = []; - for (let i = 0; i < str.length; ++i) ret.push(str[i]); + encode_binary = (obj: Buffer, buffer: Buffer) => { + buffer[0] = Types.BINARY; + buffer.writeUInt32BE(obj.length, 1); + const len = obj.copy(buffer, 5); + return Buffer.from(buffer, 0, 5 + len); + }; - return ret; - }; + encode_undefined = (_obj: Buffer, buffer: Buffer) => { + return this.#encode(null, buffer); + }; + + encode_tuple = (obj: Buffer, buffer: Buffer) => { + if (obj.length < 256) { + buffer[0] = Types.SMALL_TUPLE; + buffer.writeUInt8(obj.length, 1); + buffer = Buffer.from(buffer, 0, 2); + } else { + buffer[0] = Types.LARGE_TUPLE; + buffer.writeUInt32BE(obj.length, 1); + buffer = Buffer.from(buffer, 0, 5); + } + + for (let i = 0; i < obj.length; ++i) { + buffer = this.#encode(obj[i], buffer); + } + + return buffer; + }; + + encode_array = (obj: any[], buffer: Buffer) => { + if (obj.length === 0) { + buffer[0] = Types.NIL; + return Buffer.from(buffer, 0, 1); + } + + buffer[0] = Types.LIST; + buffer.writeUInt32BE(obj.length, 1); + buffer = Buffer.from(buffer, 0, 5); + + for (let i = 0; i < obj.length; ++i) { + buffer = this.#encode(obj[i], buffer); + } + buffer[0] = Types.NIL; + return Buffer.from(buffer, 0, 1); + }; + + encode_map = (obj: Record, buffer: Buffer) => { + const keys = Object.keys(obj); + buffer[0] = Types.MAP; + buffer.writeUInt32BE(keys.length, 1); + buffer = Buffer.from(buffer, 0, 5); + + for (let i = 0; i < keys.length; ++i) { + const key = this.mapKeyAsAtom ? this.toAtom(keys[i]) : keys[i]; + buffer = this.#encode(key, buffer); + buffer = this.#encode(obj[keys[i]], buffer); + } + return buffer; + }; + + decode_atom = (buffer: Buffer, count: 1 | 2 | 4) => { + const size = this.bytesToInt(buffer, count, true); + buffer = Buffer.from(buffer, count); + let value: any = buffer.toString("utf8", 0, size); + if (value === "true") { + value = true; + } else if (value === "false") { + value = false; + } else if ( + this.decodeUndefinedValues && + this.convention === Lang.ELIXIR && + value === "nil" + ) { + value = null; + } else if ( + this.decodeUndefinedValues && + this.convention === Lang.ERLANG && + value === "undefined" + ) { + value = null; + } else { + value = this.toAtom(value); + } + return { + value, + rest: Buffer.from(buffer, size), + }; + }; + + decode_binary = (buffer: Buffer) => { + const size = this.bytesToInt(buffer, 4, true); + buffer = Buffer.from(buffer, 4); + const bin = Buffer.alloc(size); + buffer.copy(bin, 0, 0, size); + return { + value: + this.convention === Lang.ELIXIR && this.allBinariesAsString + ? bin.toString() + : bin, + rest: Buffer.from(buffer, size), + }; + }; + + decode_integer = (buffer: Buffer, count: 1 | 2 | 4, unsigned = false) => { + return { + value: this.bytesToInt(buffer, count, unsigned), + rest: Buffer.from(buffer, count), + }; + }; + + decode_big = (buffer: Buffer, count: 1 | 2 | 4) => { + const size = this.bytesToInt(buffer, count, false); + buffer = Buffer.from(buffer, count); + + let num = 0; + const isNegative = buffer[0] === 1; + + buffer = Buffer.from(buffer, 1); + + for (let i = size - 1; i >= 0; --i) { + const n = buffer[i]; + if (num === 0) { + num = n; + } else { + num = num * 256 + n; + } + } + if (isNegative) { + num = num * -1; + } + + return { + value: num, + rest: Buffer.from(buffer, size), + }; + }; + + decode_float = (buffer: Buffer) => { + const size = 31; + + return { + value: parseFloat(buffer.toString("utf8", 0, size)), + rest: Buffer.from(buffer, size), + }; + }; + + decode_new_float = (buffer: Buffer) => { + return { + value: buffer.readDoubleBE(0), + rest: Buffer.from(buffer, 8), + }; + }; + + decode_string = (buffer: Buffer) => { + const size = this.bytesToInt(buffer, 2, true); + + buffer = Buffer.from(buffer, 2); + + return { + value: buffer.toString("utf8", 0, size), + rest: Buffer.from(buffer, size), + }; + }; + + decode_list = (buffer: Buffer) => { + const arr = []; + const size = this.bytesToInt(buffer, 4, true); + buffer = Buffer.from(buffer, 4); + + for (let i = 0; i < size; ++i) { + const el = this.#decode(buffer); + arr.push(el.value); + buffer = el.rest; + } + + const lastChar = buffer[0]; + + if (lastChar !== Types.NIL) { + throw new Error("List does not end with NIL"); + } + + buffer = Buffer.from(buffer, 1); + + return { + value: arr, + rest: buffer, + }; + }; + + decode_map = (buffer: Buffer) => { + const map: Record = {}; + const size = this.bytesToInt(buffer, 4, true); + + buffer = Buffer.from(buffer, 4); + + for (let i = 0; i < size; ++i) { + let el = this.#decode(buffer); + const key = el.value; + el = this.#decode(el.rest); + const value = el.value; + map[key] = value; + buffer = el.rest; + } + return { + value: map, + rest: buffer, + }; + }; + + decode_tuple = (buffer: Buffer, count: 1 | 2 | 4) => { + const arr = []; + const size = this.bytesToInt(buffer, count, true); + buffer = Buffer.from(buffer, count); + for (let i = 0; i < size; ++i) { + const el = this.#decode(buffer); + arr.push(el.value); + buffer = el.rest; + } + return { + value: this.toTuple(arr), + rest: buffer, + }; + }; + + decode_nil = (buffer: Buffer) => { + // nil is an empty list + return { + value: [], + rest: buffer, + }; + }; + + bytesToInt = (buffer: Buffer, length: 1 | 2 | 4, unsigned: boolean) => { + switch (length) { + case 1: + return unsigned ? buffer.readUInt8(0) : buffer.readInt8(0); + case 2: + return unsigned ? buffer.readUInt16BE(0) : buffer.readInt16BE(0); + case 4: + return unsigned ? buffer.readUInt32BE(0) : buffer.readInt32BE(0); + } + }; + + binary_to_list = (str: string) => { + const ret = []; + for (let i = 0; i < str.length; ++i) ret.push(str[i]); + + return ret; + }; } /** @@ -486,9 +489,9 @@ export class Bert { * */ export const toAtom = (str: string) => ({ - type: "Atom", - value: str, - toString: () => str, + type: "Atom", + value: str, + toString: () => str, }); /** @@ -496,15 +499,15 @@ export const toAtom = (str: string) => ({ * */ export const toTuple = (arr: any[]) => ({ - ...arr, - type: "Tuple", - length: arr.length, - value: arr, - toString: () => - "{" + - arr - .filter((i) => i !== "") - .map((i) => i.toString()) - .join(", ") + - "}", + ...arr, + type: "Tuple", + length: arr.length, + value: arr, + toString: () => + "{" + + arr + .filter((i) => i !== "") + .map((i) => i.toString()) + .join(", ") + + "}", });